1/*
2 * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6
7#include "toolbarlayout.h"
8
9#include <cmath>
10#include <unordered_map>
11
12#include <QDeadlineTimer>
13#include <QElapsedTimer>
14#include <QQmlComponent>
15#include <QTimer>
16
17#include "loggingcategory.h"
18#include "toolbarlayoutdelegate.h"
19
20using namespace Qt::StringLiterals;
21
22ToolBarLayoutAttached::ToolBarLayoutAttached(QObject *parent)
23 : QObject(parent)
24{
25}
26
27QObject *ToolBarLayoutAttached::action() const
28{
29 return m_action;
30}
31
32void ToolBarLayoutAttached::setAction(QObject *action)
33{
34 m_action = action;
35}
36
37class ToolBarLayoutPrivate
38{
39 ToolBarLayout *const q;
40
41public:
42 ToolBarLayoutPrivate(ToolBarLayout *qq)
43 : q(qq)
44 {
45 }
46 ~ToolBarLayoutPrivate()
47 {
48 if (moreButtonIncubator) {
49 moreButtonIncubator->clear();
50 delete moreButtonIncubator;
51 }
52 }
53
54 void calculateImplicitSize();
55 void performLayout();
56 QList<ToolBarLayoutDelegate *> createDelegates();
57 ToolBarLayoutDelegate *createDelegate(QObject *action);
58 qreal layoutStart(qreal layoutWidth);
59 void maybeHideDelegate(int index, qreal &currentWidth, qreal totalWidth);
60
61 QList<QObject *> actions;
62 ToolBarLayout::ActionsProperty actionsProperty;
63 QList<QObject *> hiddenActions;
64 QQmlComponent *fullDelegate = nullptr;
65 QQmlComponent *iconDelegate = nullptr;
66 QQmlComponent *separatorDelegate = nullptr;
67 QQmlComponent *moreButton = nullptr;
68 qreal spacing = 0.0;
69 Qt::Alignment alignment = Qt::AlignLeft;
70 qreal visibleActionsWidth = 0.0;
71 qreal visibleWidth = 0.0;
72 Qt::LayoutDirection layoutDirection = Qt::LeftToRight;
73 ToolBarLayout::HeightMode heightMode = ToolBarLayout::ConstrainIfLarger;
74
75 bool completed = false;
76 bool actionsChanged = false;
77 bool implicitSizeValid = false;
78
79 std::unordered_map<QObject *, std::unique_ptr<ToolBarLayoutDelegate>> delegates;
80 QList<ToolBarLayoutDelegate *> sortedDelegates;
81 QQuickItem *moreButtonInstance = nullptr;
82 ToolBarDelegateIncubator *moreButtonIncubator = nullptr;
83 bool shouldShowMoreButton = false;
84 int firstHiddenIndex = -1;
85
86 QList<QObject *> removedActions;
87 QTimer *removalTimer = nullptr;
88
89 QElapsedTimer performanceTimer;
90
91 static void appendAction(ToolBarLayout::ActionsProperty *list, QObject *action);
92 static qsizetype actionCount(ToolBarLayout::ActionsProperty *list);
93 static QObject *action(ToolBarLayout::ActionsProperty *list, qsizetype index);
94 static void clearActions(ToolBarLayout::ActionsProperty *list);
95};
96
97ToolBarLayout::ToolBarLayout(QQuickItem *parent)
98 : QQuickItem(parent)
99 , d(std::make_unique<ToolBarLayoutPrivate>(args: this))
100{
101 d->actionsProperty = ActionsProperty(this,
102 this,
103 ToolBarLayoutPrivate::appendAction,
104 ToolBarLayoutPrivate::actionCount,
105 ToolBarLayoutPrivate::action,
106 ToolBarLayoutPrivate::clearActions);
107
108 // To prevent multiple assignments to actions from constantly recreating
109 // delegates, we cache the delegates and only remove them once they are no
110 // longer being used. This timer is responsible for triggering that removal.
111 d->removalTimer = new QTimer{this};
112 d->removalTimer->setInterval(1000);
113 d->removalTimer->setSingleShot(true);
114 connect(sender: d->removalTimer, signal: &QTimer::timeout, context: this, slot: [this]() {
115 for (auto action : std::as_const(t&: d->removedActions)) {
116 if (!d->actions.contains(t: action)) {
117 d->delegates.erase(x: action);
118 }
119 }
120 d->removedActions.clear();
121 });
122}
123
124ToolBarLayout::~ToolBarLayout()
125{
126}
127
128ToolBarLayout::ActionsProperty ToolBarLayout::actionsProperty() const
129{
130 return d->actionsProperty;
131}
132
133void ToolBarLayout::addAction(QObject *action)
134{
135 if (action == nullptr) {
136 return;
137 }
138 d->actions.append(t: action);
139 d->actionsChanged = true;
140
141 connect(sender: action, signal: &QObject::destroyed, context: this, slot: [this](QObject *action) {
142 auto itr = d->delegates.find(x: action);
143 if (itr != d->delegates.end()) {
144 d->delegates.erase(position: itr);
145 }
146
147 d->actions.removeOne(t: action);
148 d->actionsChanged = true;
149
150 relayout();
151 });
152
153 relayout();
154}
155
156void ToolBarLayout::removeAction(QObject *action)
157{
158 auto itr = d->delegates.find(x: action);
159 if (itr != d->delegates.end()) {
160 itr->second->hide();
161 }
162
163 d->actions.removeOne(t: action);
164 d->removedActions.append(t: action);
165 d->removalTimer->start();
166 d->actionsChanged = true;
167
168 relayout();
169}
170
171void ToolBarLayout::clearActions()
172{
173 for (auto action : std::as_const(t&: d->actions)) {
174 auto itr = d->delegates.find(x: action);
175 if (itr != d->delegates.end()) {
176 itr->second->hide();
177 }
178 }
179
180 d->removedActions.append(l: d->actions);
181 d->actions.clear();
182 d->actionsChanged = true;
183
184 relayout();
185}
186
187QList<QObject *> ToolBarLayout::hiddenActions() const
188{
189 return d->hiddenActions;
190}
191
192QQmlComponent *ToolBarLayout::fullDelegate() const
193{
194 return d->fullDelegate;
195}
196
197void ToolBarLayout::setFullDelegate(QQmlComponent *newFullDelegate)
198{
199 if (newFullDelegate == d->fullDelegate) {
200 return;
201 }
202
203 d->fullDelegate = newFullDelegate;
204 d->delegates.clear();
205 relayout();
206 Q_EMIT fullDelegateChanged();
207}
208
209QQmlComponent *ToolBarLayout::iconDelegate() const
210{
211 return d->iconDelegate;
212}
213
214void ToolBarLayout::setIconDelegate(QQmlComponent *newIconDelegate)
215{
216 if (newIconDelegate == d->iconDelegate) {
217 return;
218 }
219
220 d->iconDelegate = newIconDelegate;
221 d->delegates.clear();
222 relayout();
223 Q_EMIT iconDelegateChanged();
224}
225
226QQmlComponent *ToolBarLayout::separatorDelegate() const
227{
228 return d->separatorDelegate;
229}
230
231void ToolBarLayout::setSeparatorDelegate(QQmlComponent *newSeparatorDelegate)
232{
233 if (newSeparatorDelegate == d->separatorDelegate) {
234 return;
235 }
236
237 d->separatorDelegate = newSeparatorDelegate;
238 d->delegates.clear();
239 relayout();
240 Q_EMIT separatorDelegateChanged();
241}
242
243QQmlComponent *ToolBarLayout::moreButton() const
244{
245 return d->moreButton;
246}
247
248void ToolBarLayout::setMoreButton(QQmlComponent *newMoreButton)
249{
250 if (newMoreButton == d->moreButton) {
251 return;
252 }
253
254 d->moreButton = newMoreButton;
255 if (d->moreButtonInstance) {
256 d->moreButtonInstance->deleteLater();
257 d->moreButtonInstance = nullptr;
258 }
259 relayout();
260 Q_EMIT moreButtonChanged();
261}
262
263qreal ToolBarLayout::spacing() const
264{
265 return d->spacing;
266}
267
268void ToolBarLayout::setSpacing(qreal newSpacing)
269{
270 if (newSpacing == d->spacing) {
271 return;
272 }
273
274 d->spacing = newSpacing;
275 relayout();
276 Q_EMIT spacingChanged();
277}
278
279Qt::Alignment ToolBarLayout::alignment() const
280{
281 return d->alignment;
282}
283
284void ToolBarLayout::setAlignment(Qt::Alignment newAlignment)
285{
286 if (newAlignment == d->alignment) {
287 return;
288 }
289
290 d->alignment = newAlignment;
291 relayout();
292 Q_EMIT alignmentChanged();
293}
294
295qreal ToolBarLayout::visibleWidth() const
296{
297 return d->visibleWidth;
298}
299
300qreal ToolBarLayout::minimumWidth() const
301{
302 return d->moreButtonInstance ? d->moreButtonInstance->width() : 0;
303}
304
305Qt::LayoutDirection ToolBarLayout::layoutDirection() const
306{
307 return d->layoutDirection;
308}
309
310void ToolBarLayout::setLayoutDirection(Qt::LayoutDirection &newLayoutDirection)
311{
312 if (newLayoutDirection == d->layoutDirection) {
313 return;
314 }
315
316 d->layoutDirection = newLayoutDirection;
317 relayout();
318 Q_EMIT layoutDirectionChanged();
319}
320
321ToolBarLayout::HeightMode ToolBarLayout::heightMode() const
322{
323 return d->heightMode;
324}
325
326void ToolBarLayout::setHeightMode(HeightMode newHeightMode)
327{
328 if (newHeightMode == d->heightMode) {
329 return;
330 }
331
332 d->heightMode = newHeightMode;
333 relayout();
334 Q_EMIT heightModeChanged();
335}
336
337void ToolBarLayout::relayout()
338{
339 d->implicitSizeValid = false;
340 polish();
341}
342
343void ToolBarLayout::componentComplete()
344{
345 QQuickItem::componentComplete();
346 d->completed = true;
347 d->performLayout();
348}
349
350void ToolBarLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
351{
352 if (newGeometry != oldGeometry) {
353 relayout();
354 }
355 QQuickItem::geometryChange(newGeometry, oldGeometry);
356}
357
358void ToolBarLayout::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
359{
360 if (change == ItemSceneChange) {
361 relayout();
362 } else if (change == ItemVisibleHasChanged) {
363 // If we just became visible we want all the actions to be visible and laid out,
364 // in order to not draw a frame with an empty toolbar
365 for (auto &delegate : std::as_const(t&: d->delegates)) {
366 delegate.second->forceCompletion();
367 }
368 d->implicitSizeValid = false;
369 d->performLayout();
370 }
371 QQuickItem::itemChange(change, data);
372}
373
374void ToolBarLayout::updatePolish()
375{
376 d->performLayout();
377}
378
379/*
380 * Calculate the implicit size for this layout.
381 *
382 * This is a separate step from performing the actual layout, because of a nasty
383 * little issue with Control, where it will unconditionally set the height of
384 * its contentItem, which means QQuickItem::heightValid() becomes useless. So
385 * instead, we first calculate our implicit size, ignoring any explicitly set
386 * item size. Then we follow that by performing the actual layouting, using the
387 * width and height retrieved from the item, as those will return the explicitly
388 * set width/height if set and the implicit size otherwise. Since control
389 * watches for implicit size changes, we end up with correct behaviour both when
390 * we get an explicit size set and when we're relying on implicit size
391 * calculation.
392 */
393void ToolBarLayoutPrivate::calculateImplicitSize()
394{
395 if (!completed) {
396 return;
397 }
398
399 if (!fullDelegate || !iconDelegate || !separatorDelegate || !moreButton) {
400 qCWarning(KirigamiLayoutsLog) << "ToolBarLayout: Unable to layout, required properties are not set";
401 return;
402 }
403
404 if (actions.isEmpty()) {
405 q->setImplicitSize(0., 0.);
406 return;
407 }
408
409 hiddenActions.clear();
410 firstHiddenIndex = -1;
411
412 sortedDelegates = createDelegates();
413
414 bool ready = std::all_of(first: delegates.cbegin(), last: delegates.cend(), pred: [](const std::pair<QObject *const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
415 // If the delegate wants to always be hidden we don't care if it's still loading
416 return entry.second->isReady() || entry.second->isHidden();
417 });
418 if (!ready || !moreButtonInstance) {
419 return;
420 }
421
422 qreal maxHeight = 0.0;
423 qreal maxWidth = 0.0;
424
425 // First, calculate the total width and maximum height of all delegates.
426 // This will be used to determine which actions to show, which ones to
427 // collapse to icon-only etc.
428 for (auto entry : std::as_const(t&: sortedDelegates)) {
429 if (!entry->isActionVisible()) {
430 entry->hide();
431 continue;
432 }
433
434 if (entry->isHidden()) {
435 entry->hide();
436 hiddenActions.append(t: entry->action());
437 continue;
438 }
439
440 if (entry->isIconOnly()) {
441 entry->showIcon();
442 } else {
443 entry->showFull();
444 }
445
446 maxWidth += entry->width() + spacing;
447 maxHeight = std::max(a: maxHeight, b: entry->maxHeight());
448 }
449
450 // The last entry also gets spacing but shouldn't, so remove that.
451 maxWidth -= spacing;
452
453 visibleActionsWidth = 0.0;
454
455 q->setImplicitWidth(maxWidth);
456
457 if (maxWidth > q->width() - (hiddenActions.isEmpty() ? 0.0 : moreButtonInstance->width() + spacing)) {
458 // We have more items than fit into the view, so start hiding some.
459
460 qreal layoutWidth = q->width() - (moreButtonInstance->width() + spacing);
461 if (alignment & Qt::AlignHCenter) {
462 // When centering, we need to reserve space on both sides to make sure
463 // things are properly centered, otherwise we will be to the right of
464 // the center.
465 layoutWidth -= (moreButtonInstance->width() + spacing);
466 }
467
468 for (int i = 0; i < sortedDelegates.size(); ++i) {
469 auto delegate = sortedDelegates.at(i);
470
471 maybeHideDelegate(index: i, currentWidth&: visibleActionsWidth, totalWidth: layoutWidth);
472
473 if (delegate->isVisible()) {
474 visibleActionsWidth += delegate->width() + spacing;
475 }
476 }
477 if (!qFuzzyIsNull(d: visibleActionsWidth)) {
478 // Like above, remove spacing on the last element that incorrectly gets spacing added.
479 visibleActionsWidth -= spacing;
480 }
481 } else {
482 visibleActionsWidth = maxWidth;
483 }
484
485 if (!hiddenActions.isEmpty()) {
486 maxHeight = std::max(a: maxHeight, b: moreButtonInstance->implicitHeight());
487 };
488
489 q->setImplicitHeight(maxHeight);
490
491 Q_EMIT q->hiddenActionsChanged();
492
493 implicitSizeValid = true;
494
495 q->polish();
496}
497
498void ToolBarLayoutPrivate::performLayout()
499{
500 if (!completed || actions.isEmpty()) {
501 return;
502 }
503
504 if (!implicitSizeValid) {
505 calculateImplicitSize();
506 }
507
508 if (sortedDelegates.isEmpty() || !moreButtonInstance) {
509 sortedDelegates = createDelegates();
510 }
511
512 bool ready = std::all_of(first: delegates.cbegin(), last: delegates.cend(), pred: [](const std::pair<QObject *const, std::unique_ptr<ToolBarLayoutDelegate>> &entry) {
513 // If the delegate wants to always be hidden we don't care if it's still loading
514 return entry.second->isReady() || entry.second->isHidden();
515 });
516 if (!ready) {
517 // Don't continue to layout if all delegates aren't ready yet
518 return;
519 }
520
521 qreal width = q->width();
522 qreal height = q->height();
523
524 if (!hiddenActions.isEmpty()) {
525 if (layoutDirection == Qt::LeftToRight) {
526 moreButtonInstance->setX(width - moreButtonInstance->width());
527 } else {
528 moreButtonInstance->setX(0.0);
529 }
530
531 if (heightMode == ToolBarLayout::AlwaysFill) {
532 moreButtonInstance->setHeight(height);
533 } else if (heightMode == ToolBarLayout::ConstrainIfLarger) {
534 if (moreButtonInstance->implicitHeight() > height) {
535 moreButtonInstance->setHeight(height);
536 } else {
537 moreButtonInstance->resetHeight();
538 }
539 } else {
540 moreButtonInstance->resetHeight();
541 }
542
543 moreButtonInstance->setY(qRound(d: (height - moreButtonInstance->height()) / 2.0));
544 shouldShowMoreButton = true;
545 moreButtonInstance->setVisible(true);
546 } else {
547 shouldShowMoreButton = false;
548 moreButtonInstance->setVisible(false);
549 }
550
551 qreal currentX = layoutStart(layoutWidth: visibleActionsWidth);
552 for (auto entry : std::as_const(t&: sortedDelegates)) {
553 if (!entry->isVisible()) {
554 continue;
555 }
556
557 if (heightMode == ToolBarLayout::AlwaysFill) {
558 entry->setHeight(height);
559 } else if (heightMode == ToolBarLayout::ConstrainIfLarger) {
560 if (entry->implicitHeight() > height) {
561 entry->setHeight(height);
562 } else {
563 entry->resetHeight();
564 }
565 } else {
566 entry->resetHeight();
567 }
568
569 qreal y = qRound(d: (height - entry->height()) / 2.0);
570
571 if (layoutDirection == Qt::LeftToRight) {
572 entry->setPosition(x: currentX, y);
573 currentX += entry->width() + spacing;
574 } else {
575 entry->setPosition(x: currentX - entry->width(), y);
576 currentX -= entry->width() + spacing;
577 }
578
579 entry->show();
580 }
581
582 qreal newVisibleWidth = visibleActionsWidth;
583 if (moreButtonInstance->isVisible()) {
584 newVisibleWidth += moreButtonInstance->width() + (newVisibleWidth > 0.0 ? spacing : 0.0);
585 }
586 if (!qFuzzyCompare(p1: newVisibleWidth, p2: visibleWidth)) {
587 visibleWidth = newVisibleWidth;
588 Q_EMIT q->visibleWidthChanged();
589 }
590
591 if (actionsChanged) {
592 // Due to the way QQmlListProperty works, if we emit changed every time
593 // an action is added/removed, we end up emitting way too often. So
594 // instead only do it after everything else is done.
595 Q_EMIT q->actionsChanged();
596 actionsChanged = false;
597 }
598
599 sortedDelegates.clear();
600}
601
602QList<ToolBarLayoutDelegate *> ToolBarLayoutPrivate::createDelegates()
603{
604 QList<ToolBarLayoutDelegate *> result;
605 for (auto action : std::as_const(t&: actions)) {
606 if (delegates.find(x: action) != delegates.end()) {
607 result.append(t: delegates.at(k: action).get());
608 } else if (action) {
609 auto delegate = std::unique_ptr<ToolBarLayoutDelegate>(createDelegate(action));
610 if (delegate) {
611 result.append(t: delegate.get());
612 delegates.emplace(args&: action, args: std::move(delegate));
613 }
614 }
615 }
616
617 if (!moreButtonInstance) {
618 moreButtonInstance = qobject_cast<QQuickItem *>(
619 o: moreButton->createWithInitialProperties(initialProperties: {{u"parent"_s, QVariant::fromValue(value: q)}, {u"visible"_s, false}}, context: qmlContext(moreButton)));
620 QObject::connect(sender: moreButtonInstance, signal: &QQuickItem::visibleChanged, context: q, slot: [this]() {
621 moreButtonInstance->setVisible(shouldShowMoreButton);
622 });
623 QObject::connect(sender: moreButtonInstance, signal: &QQuickItem::widthChanged, context: q, slot: &ToolBarLayout::minimumWidthChanged);
624 q->relayout();
625 Q_EMIT q->minimumWidthChanged();
626 }
627
628 return result;
629}
630
631ToolBarLayoutDelegate *ToolBarLayoutPrivate::createDelegate(QObject *action)
632{
633 QQmlComponent *fullComponent = nullptr;
634 auto displayComponent = action->property(name: "displayComponent");
635 if (displayComponent.isValid()) {
636 fullComponent = displayComponent.value<QQmlComponent *>();
637 }
638
639 if (!fullComponent) {
640 fullComponent = fullDelegate;
641 }
642
643 auto separator = action->property(name: "separator");
644 if (separator.isValid() && separator.toBool()) {
645 fullComponent = separatorDelegate;
646 }
647
648 auto result = new ToolBarLayoutDelegate(q);
649 result->setAction(action);
650 result->createItems(fullComponent, iconComponent: iconDelegate, callback: [this, action](QQuickItem *newItem) {
651 newItem->setParentItem(q);
652 auto attached = static_cast<ToolBarLayoutAttached *>(qmlAttachedPropertiesObject<ToolBarLayout>(obj: newItem, create: true));
653 attached->setAction(action);
654
655 if (!q->childItems().isEmpty() && q->childItems().first() != newItem) {
656 // Due to asynchronous item creation, we end up creating the last item
657 // first. So move items before previously inserted items to ensure
658 // we have a more sensible tab order.
659 // Note that this will be incorrect if we end up completing in random
660 // order.
661 newItem->stackBefore(q->childItems().first());
662 }
663 });
664
665 return result;
666}
667
668qreal ToolBarLayoutPrivate::layoutStart(qreal layoutWidth)
669{
670 qreal availableWidth = moreButtonInstance->isVisible() ? q->width() - (moreButtonInstance->width() + spacing) : q->width();
671
672 if (alignment & Qt::AlignLeft) {
673 return layoutDirection == Qt::LeftToRight ? 0.0 : q->width();
674 } else if (alignment & Qt::AlignHCenter) {
675 return (q->width() / 2) + (layoutDirection == Qt::LeftToRight ? -layoutWidth / 2.0 : layoutWidth / 2.0);
676 } else if (alignment & Qt::AlignRight) {
677 qreal offset = availableWidth - layoutWidth;
678 return layoutDirection == Qt::LeftToRight ? offset : q->width() - offset;
679 }
680 return 0.0;
681}
682
683void ToolBarLayoutPrivate::maybeHideDelegate(int index, qreal &currentWidth, qreal totalWidth)
684{
685 auto delegate = sortedDelegates.at(i: index);
686
687 if (!delegate->isVisible()) {
688 // If the delegate isn't visible anyway, do nothing.
689 return;
690 }
691
692 if (currentWidth + delegate->width() < totalWidth && (firstHiddenIndex < 0 || index < firstHiddenIndex)) {
693 // If the delegate is fully visible and we have not already hidden
694 // actions, do nothing.
695 return;
696 }
697
698 if (delegate->isKeepVisible()) {
699 // If the action is marked as KeepVisible, we need to try our best to
700 // keep it in view. If the full size delegate does not fit, we try the
701 // icon-only delegate. If that also does not fit, try and find other
702 // actions to hide. Finally, if that also fails, we will hide the
703 // delegate.
704 if (currentWidth + delegate->iconWidth() > totalWidth) {
705 // First, hide any earlier actions that are not marked as KeepVisible.
706 for (auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
707 auto previousDelegate = sortedDelegates.at(i: currentIndex);
708 if (!previousDelegate->isVisible() || previousDelegate->isKeepVisible()) {
709 continue;
710 }
711
712 auto width = previousDelegate->width();
713 previousDelegate->hide();
714 hiddenActions.append(t: previousDelegate->action());
715 currentWidth -= (width + spacing);
716
717 if (currentWidth + delegate->fullWidth() <= totalWidth) {
718 delegate->showFull();
719 break;
720 } else if (currentWidth + delegate->iconWidth() <= totalWidth) {
721 delegate->showIcon();
722 break;
723 }
724 }
725
726 if (currentWidth + delegate->width() <= totalWidth) {
727 return;
728 }
729
730 // Hiding normal actions did not help enough, so go through actions
731 // with KeepVisible set and try and collapse them to IconOnly.
732 for (auto currentIndex = index - 1; currentIndex >= 0; --currentIndex) {
733 auto previousDelegate = sortedDelegates.at(i: currentIndex);
734 if (!previousDelegate->isVisible() || !previousDelegate->isKeepVisible()) {
735 continue;
736 }
737
738 auto extraSpace = previousDelegate->width() - previousDelegate->iconWidth();
739 previousDelegate->showIcon();
740 currentWidth -= extraSpace;
741
742 if (currentWidth + delegate->fullWidth() <= totalWidth) {
743 delegate->showFull();
744 break;
745 } else if (currentWidth + delegate->iconWidth() <= totalWidth) {
746 delegate->showIcon();
747 break;
748 }
749 }
750
751 // If that also did not work, then hide this action after all.
752 if (currentWidth + delegate->width() > totalWidth) {
753 delegate->hide();
754 hiddenActions.append(t: delegate->action());
755 }
756 } else {
757 delegate->showIcon();
758 }
759 } else {
760 // The action is not marked as KeepVisible and it does not fit within
761 // the current layout, so hide it.
762 delegate->hide();
763 hiddenActions.append(t: delegate->action());
764
765 // If this is the first item to be hidden, mark it so we know we should
766 // also hide the following items.
767 if (firstHiddenIndex < 0) {
768 firstHiddenIndex = index;
769 }
770 }
771}
772
773void ToolBarLayoutPrivate::appendAction(ToolBarLayout::ActionsProperty *list, QObject *action)
774{
775 auto layout = reinterpret_cast<ToolBarLayout *>(list->data);
776 layout->addAction(action);
777}
778
779qsizetype ToolBarLayoutPrivate::actionCount(ToolBarLayout::ActionsProperty *list)
780{
781 return reinterpret_cast<ToolBarLayout *>(list->data)->d->actions.count();
782}
783
784QObject *ToolBarLayoutPrivate::action(ToolBarLayout::ActionsProperty *list, qsizetype index)
785{
786 return reinterpret_cast<ToolBarLayout *>(list->data)->d->actions.at(i: index);
787}
788
789void ToolBarLayoutPrivate::clearActions(ToolBarLayout::ActionsProperty *list)
790{
791 reinterpret_cast<ToolBarLayout *>(list->data)->clearActions();
792}
793
794#include "moc_toolbarlayout.cpp"
795

source code of kirigami/src/layouts/toolbarlayout.cpp