7#include "toolbarlayout.h"
10#include <unordered_map>
12#include <QDeadlineTimer>
13#include <QQmlComponent>
16#include "loggingcategory.h"
17#include "toolbarlayoutdelegate.h"
19ToolBarLayoutAttached::ToolBarLayoutAttached(
QObject *parent)
29void ToolBarLayoutAttached::setAction(
QObject *action)
34class ToolBarLayoutPrivate
36 ToolBarLayout *
const q;
39 ToolBarLayoutPrivate(ToolBarLayout *qq)
43 ~ToolBarLayoutPrivate()
45 if (moreButtonIncubator) {
46 moreButtonIncubator->clear();
47 delete moreButtonIncubator;
51 void calculateImplicitSize();
53 QList<ToolBarLayoutDelegate *> createDelegates();
54 ToolBarLayoutDelegate *createDelegate(QObject *action);
55 qreal layoutStart(qreal layoutWidth);
56 void maybeHideDelegate(
int index, qreal ¤tWidth, qreal totalWidth);
58 QList<QObject *> actions;
59 ToolBarLayout::ActionsProperty actionsProperty;
60 QList<QObject *> hiddenActions;
61 QQmlComponent *fullDelegate =
nullptr;
62 QQmlComponent *iconDelegate =
nullptr;
63 QQmlComponent *separatorDelegate =
nullptr;
64 QQmlComponent *moreButton =
nullptr;
67 qreal visibleActionsWidth = 0.0;
68 qreal visibleWidth = 0.0;
72 bool completed =
false;
73 bool actionsChanged =
false;
74 bool implicitSizeValid =
false;
76 std::unordered_map<QObject *, std::unique_ptr<ToolBarLayoutDelegate>> delegates;
77 QList<ToolBarLayoutDelegate *> sortedDelegates;
78 QQuickItem *moreButtonInstance =
nullptr;
79 ToolBarDelegateIncubator *moreButtonIncubator =
nullptr;
80 bool shouldShowMoreButton =
false;
81 int firstHiddenIndex = -1;
83 QList<QObject *> removedActions;
84 QTimer *removalTimer =
nullptr;
86 QElapsedTimer performanceTimer;
88 static void appendAction(ToolBarLayout::ActionsProperty *list, QObject *action);
89 static qsizetype actionCount(ToolBarLayout::ActionsProperty *list);
90 static QObject *action(ToolBarLayout::ActionsProperty *list, qsizetype index);
91 static void clearActions(ToolBarLayout::ActionsProperty *list);
94ToolBarLayout::ToolBarLayout(
QQuickItem *parent)
96 , d(std::make_unique<ToolBarLayoutPrivate>(this))
98 d->actionsProperty = ActionsProperty(
this,
100 ToolBarLayoutPrivate::appendAction,
101 ToolBarLayoutPrivate::actionCount,
102 ToolBarLayoutPrivate::action,
103 ToolBarLayoutPrivate::clearActions);
108 d->removalTimer =
new QTimer{
this};
110 d->removalTimer->setSingleShot(
true);
112 for (auto action : std::as_const(d->removedActions)) {
113 if (!d->actions.contains(action)) {
114 d->delegates.erase(action);
117 d->removedActions.clear();
121ToolBarLayout::~ToolBarLayout()
125ToolBarLayout::ActionsProperty ToolBarLayout::actionsProperty()
const
127 return d->actionsProperty;
132 if (action ==
nullptr) {
135 d->actions.append(action);
136 d->actionsChanged =
true;
139 auto itr = d->delegates.find(action);
140 if (itr != d->delegates.end()) {
141 d->delegates.erase(itr);
144 d->actions.removeOne(action);
145 d->actionsChanged =
true;
155 auto itr = d->delegates.find(action);
156 if (itr != d->delegates.end()) {
160 d->actions.removeOne(action);
161 d->removedActions.append(action);
162 d->removalTimer->start();
163 d->actionsChanged =
true;
170 for (
auto action : std::as_const(d->actions)) {
171 auto itr = d->delegates.find(action);
172 if (itr != d->delegates.end()) {
177 d->removedActions.append(d->actions);
179 d->actionsChanged =
true;
186 return d->hiddenActions;
191 return d->fullDelegate;
194void ToolBarLayout::setFullDelegate(
QQmlComponent *newFullDelegate)
196 if (newFullDelegate == d->fullDelegate) {
200 d->fullDelegate = newFullDelegate;
201 d->delegates.clear();
203 Q_EMIT fullDelegateChanged();
208 return d->iconDelegate;
211void ToolBarLayout::setIconDelegate(
QQmlComponent *newIconDelegate)
213 if (newIconDelegate == d->iconDelegate) {
217 d->iconDelegate = newIconDelegate;
218 d->delegates.clear();
220 Q_EMIT iconDelegateChanged();
225 return d->separatorDelegate;
228void ToolBarLayout::setSeparatorDelegate(
QQmlComponent *newSeparatorDelegate)
230 if (newSeparatorDelegate == d->separatorDelegate) {
234 d->separatorDelegate = newSeparatorDelegate;
235 d->delegates.clear();
237 Q_EMIT separatorDelegateChanged();
242 return d->moreButton;
245void ToolBarLayout::setMoreButton(
QQmlComponent *newMoreButton)
247 if (newMoreButton == d->moreButton) {
251 d->moreButton = newMoreButton;
252 if (d->moreButtonInstance) {
253 d->moreButtonInstance->deleteLater();
254 d->moreButtonInstance =
nullptr;
257 Q_EMIT moreButtonChanged();
265void ToolBarLayout::setSpacing(qreal newSpacing)
267 if (newSpacing == d->spacing) {
271 d->spacing = newSpacing;
283 if (newAlignment == d->alignment) {
287 d->alignment = newAlignment;
289 Q_EMIT alignmentChanged();
294 return d->visibleWidth;
299 return d->moreButtonInstance ? d->moreButtonInstance->width() : 0;
304 return d->layoutDirection;
309 if (newLayoutDirection == d->layoutDirection) {
313 d->layoutDirection = newLayoutDirection;
315 Q_EMIT layoutDirectionChanged();
320 return d->heightMode;
323void ToolBarLayout::setHeightMode(HeightMode newHeightMode)
325 if (newHeightMode == d->heightMode) {
329 d->heightMode = newHeightMode;
331 Q_EMIT heightModeChanged();
336 d->implicitSizeValid =
false;
340void ToolBarLayout::componentComplete()
347void ToolBarLayout::geometryChange(
const QRectF &newGeometry,
const QRectF &oldGeometry)
349 if (newGeometry != oldGeometry) {
350 if (newGeometry.
size() != QSizeF{implicitWidth(), implicitHeight()}) {
367void ToolBarLayout::updatePolish()
386void ToolBarLayoutPrivate::calculateImplicitSize()
392 if (!fullDelegate || !iconDelegate || !separatorDelegate || !moreButton) {
393 qCWarning(KirigamiLayoutsLog) <<
"ToolBarLayout: Unable to layout, required properties are not set";
397 if (actions.isEmpty()) {
398 q->setImplicitSize(0., 0.);
402 hiddenActions.clear();
403 firstHiddenIndex = -1;
405 sortedDelegates = createDelegates();
407 bool ready = std::all_of(delegates.cbegin(), delegates.cend(), [](
const std::pair<QObject *
const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
408 return entry.second->isReady();
410 if (!ready || !moreButtonInstance) {
414 qreal maxHeight = 0.0;
415 qreal maxWidth = 0.0;
420 for (
auto entry : std::as_const(sortedDelegates)) {
421 if (!entry->isActionVisible()) {
426 if (entry->isHidden()) {
428 hiddenActions.append(entry->action());
432 if (entry->isIconOnly()) {
438 maxWidth += entry->width() + spacing;
439 maxHeight = std::max(maxHeight, entry->maxHeight());
445 visibleActionsWidth = 0.0;
447 if (maxWidth > q->width() - (hiddenActions.isEmpty() ? 0.0 : moreButtonInstance->width() + spacing)) {
450 qreal layoutWidth = q->width() - (moreButtonInstance->width() + spacing);
455 layoutWidth -= (moreButtonInstance->width() + spacing);
458 for (
int i = 0; i < sortedDelegates.size(); ++i) {
459 auto delegate = sortedDelegates.at(i);
461 maybeHideDelegate(i, visibleActionsWidth, layoutWidth);
463 if (delegate->isVisible()) {
464 visibleActionsWidth += delegate->width() + spacing;
467 if (!qFuzzyIsNull(visibleActionsWidth)) {
469 visibleActionsWidth -= spacing;
472 visibleActionsWidth = maxWidth;
475 if (!hiddenActions.isEmpty()) {
476 maxHeight = std::max(maxHeight, moreButtonInstance->implicitHeight());
479 q->setImplicitSize(maxWidth, maxHeight);
480 Q_EMIT q->hiddenActionsChanged();
482 implicitSizeValid =
true;
487void ToolBarLayoutPrivate::performLayout()
489 if (!completed || actions.isEmpty()) {
493 if (!implicitSizeValid) {
494 calculateImplicitSize();
497 if (sortedDelegates.isEmpty()) {
498 sortedDelegates = createDelegates();
501 bool ready = std::all_of(delegates.cbegin(), delegates.cend(), [](
const std::pair<QObject *
const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
502 return entry.second->isReady();
504 if (!ready || !moreButtonInstance) {
508 qreal width = q->width();
509 qreal height = q->height();
511 if (!hiddenActions.isEmpty()) {
513 moreButtonInstance->setX(width - moreButtonInstance->width());
515 moreButtonInstance->setX(0.0);
519 moreButtonInstance->setHeight(height);
521 if (moreButtonInstance->implicitHeight() > height) {
522 moreButtonInstance->setHeight(height);
524 moreButtonInstance->resetHeight();
527 moreButtonInstance->resetHeight();
530 moreButtonInstance->setY(qRound((height - moreButtonInstance->height()) / 2.0));
531 shouldShowMoreButton =
true;
532 moreButtonInstance->setVisible(
true);
534 shouldShowMoreButton =
false;
535 moreButtonInstance->setVisible(
false);
538 qreal currentX = layoutStart(visibleActionsWidth);
539 for (
auto entry : std::as_const(sortedDelegates)) {
540 if (!entry->isVisible()) {
545 entry->setHeight(height);
547 if (entry->implicitHeight() > height) {
548 entry->setHeight(height);
550 entry->resetHeight();
553 entry->resetHeight();
556 qreal y = qRound((height - entry->height()) / 2.0);
559 entry->setPosition(currentX, y);
560 currentX += entry->width() + spacing;
562 entry->setPosition(currentX - entry->width(), y);
563 currentX -= entry->width() + spacing;
569 qreal newVisibleWidth = visibleActionsWidth;
570 if (moreButtonInstance->isVisible()) {
571 newVisibleWidth += moreButtonInstance->width() + (newVisibleWidth > 0.0 ? spacing : 0.0);
573 if (!qFuzzyCompare(newVisibleWidth, visibleWidth)) {
574 visibleWidth = newVisibleWidth;
575 Q_EMIT q->visibleWidthChanged();
578 if (actionsChanged) {
582 Q_EMIT q->actionsChanged();
583 actionsChanged =
false;
586 sortedDelegates.clear();
591 QList<ToolBarLayoutDelegate *> result;
592 for (
auto action : std::as_const(actions)) {
593 if (delegates.find(action) != delegates.end()) {
594 result.
append(delegates.at(action).get());
596 auto delegate = std::unique_ptr<ToolBarLayoutDelegate>(createDelegate(action));
598 result.
append(delegate.get());
599 delegates.emplace(action, std::move(delegate));
604 if (!moreButtonInstance && !moreButtonIncubator) {
605 moreButtonIncubator =
new ToolBarDelegateIncubator(moreButton, qmlContext(moreButton));
606 moreButtonIncubator->setStateCallback([
this](QQuickItem *item) {
609 moreButtonIncubator->setCompletedCallback([
this](ToolBarDelegateIncubator *incubator) {
610 moreButtonInstance = qobject_cast<QQuickItem *>(incubator->
object());
611 moreButtonInstance->setVisible(
false);
614 moreButtonInstance->setVisible(shouldShowMoreButton);
618 Q_EMIT q->minimumWidthChanged();
621 delete moreButtonIncubator;
622 moreButtonIncubator =
nullptr;
625 moreButtonIncubator->create();
631ToolBarLayoutDelegate *ToolBarLayoutPrivate::createDelegate(
QObject *action)
633 QQmlComponent *fullComponent =
nullptr;
634 auto displayComponent = action->property(
"displayComponent");
635 if (displayComponent.isValid()) {
636 fullComponent = displayComponent.value<QQmlComponent *>();
639 if (!fullComponent) {
640 fullComponent = fullDelegate;
643 auto separator = action->property(
"separator");
644 if (separator.isValid() && separator.toBool()) {
645 fullComponent = separatorDelegate;
648 auto result =
new ToolBarLayoutDelegate(q);
649 result->setAction(action);
650 result->createItems(fullComponent, iconDelegate, [
this, action](QQuickItem *newItem) {
652 auto attached =
static_cast<ToolBarLayoutAttached *
>(qmlAttachedPropertiesObject<ToolBarLayout>(newItem,
true));
653 attached->setAction(action);
655 if (!q->childItems().isEmpty() && q->childItems().first() != newItem) {
668qreal ToolBarLayoutPrivate::layoutStart(qreal layoutWidth)
670 qreal availableWidth = moreButtonInstance->isVisible() ? q->width() - (moreButtonInstance->width() + spacing) : q->width();
675 return (q->width() / 2) + (layoutDirection ==
Qt::LeftToRight ? -layoutWidth / 2.0 : layoutWidth / 2.0);
677 qreal offset = availableWidth - layoutWidth;
678 return layoutDirection ==
Qt::LeftToRight ? offset : q->width() - offset;
683void ToolBarLayoutPrivate::maybeHideDelegate(
int index, qreal ¤tWidth, qreal totalWidth)
685 auto delegate = sortedDelegates.
at(index);
687 if (!delegate->isVisible()) {
692 if (currentWidth + delegate->width() < totalWidth && (firstHiddenIndex < 0 || index < firstHiddenIndex)) {
698 if (delegate->isKeepVisible()) {
704 if (currentWidth + delegate->iconWidth() > totalWidth) {
706 for (
auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
707 auto previousDelegate = sortedDelegates.at(currentIndex);
708 if (!previousDelegate->isVisible() || previousDelegate->isKeepVisible()) {
712 auto width = previousDelegate->width();
713 previousDelegate->hide();
714 hiddenActions.append(previousDelegate->action());
715 currentWidth -= (width + spacing);
717 if (currentWidth + delegate->fullWidth() <= totalWidth) {
718 delegate->showFull();
720 }
else if (currentWidth + delegate->iconWidth() <= totalWidth) {
721 delegate->showIcon();
726 if (currentWidth + delegate->width() <= totalWidth) {
732 for (
auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
733 auto previousDelegate = sortedDelegates.at(currentIndex);
734 if (!previousDelegate->isVisible() || !previousDelegate->isKeepVisible()) {
738 auto extraSpace = previousDelegate->width() - previousDelegate->iconWidth();
739 previousDelegate->showIcon();
740 currentWidth -= extraSpace;
742 if (currentWidth + delegate->fullWidth() <= totalWidth) {
743 delegate->showFull();
745 }
else if (currentWidth + delegate->iconWidth() <= totalWidth) {
746 delegate->showIcon();
752 if (currentWidth + delegate->width() > totalWidth) {
754 hiddenActions.append(delegate->action());
757 delegate->showIcon();
763 hiddenActions.append(delegate->action());
767 if (firstHiddenIndex < 0) {
768 firstHiddenIndex = index;
773void ToolBarLayoutPrivate::appendAction(ToolBarLayout::ActionsProperty *list,
QObject *action)
775 auto layout =
reinterpret_cast<ToolBarLayout *
>(
list->
data);
776 layout->addAction(action);
779qsizetype ToolBarLayoutPrivate::actionCount(ToolBarLayout::ActionsProperty *list)
781 return reinterpret_cast<ToolBarLayout *
>(
list->
data)->d->actions.count();
784QObject *ToolBarLayoutPrivate::action(ToolBarLayout::ActionsProperty *list, qsizetype index)
786 return reinterpret_cast<ToolBarLayout *
>(
list->
data)->d->actions.at(index);
789void ToolBarLayoutPrivate::clearActions(ToolBarLayout::ActionsProperty *list)
791 reinterpret_cast<ToolBarLayout *
>(
list->
data)->clearActions();
794#include "moc_toolbarlayout.cpp"
KIOCORE_EXPORT QStringList list(const QString &fileClass)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
QObject * object() const const
virtual void componentComplete() override
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
virtual void itemChange(ItemChange change, const ItemChangeData &value)
void setParentItem(QQuickItem *parent)
void stackBefore(const QQuickItem *sibling)
QSizeF size() const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void setInterval(int msec)