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