1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtoolbox.h"
41
42#include <qapplication.h>
43#include <qeventloop.h>
44#include <qlayout.h>
45#include <qlist.h>
46#include <qpainter.h>
47#include <qscrollarea.h>
48#include <qstyle.h>
49#include <qstyleoption.h>
50#include <qtooltip.h>
51#include <qabstractbutton.h>
52
53#include <private/qmemory_p.h>
54
55#include "qframe_p.h"
56
57QT_BEGIN_NAMESPACE
58
59class QToolBoxButton : public QAbstractButton
60{
61 Q_OBJECT
62public:
63 QToolBoxButton(QWidget *parent)
64 : QAbstractButton(parent), selected(false), indexInPage(-1)
65 {
66 setBackgroundRole(QPalette::Window);
67 setSizePolicy(hor: QSizePolicy::Preferred, ver: QSizePolicy::Minimum);
68 setFocusPolicy(Qt::NoFocus);
69 }
70
71 inline void setSelected(bool b) { selected = b; update(); }
72 inline void setIndex(int newIndex) { indexInPage = newIndex; }
73
74 QSize sizeHint() const override;
75 QSize minimumSizeHint() const override;
76
77protected:
78 void initStyleOption(QStyleOptionToolBox *opt) const;
79 void paintEvent(QPaintEvent *) override;
80
81private:
82 bool selected;
83 int indexInPage;
84};
85
86
87class QToolBoxPrivate : public QFramePrivate
88{
89 Q_DECLARE_PUBLIC(QToolBox)
90public:
91 struct Page
92 {
93 QToolBoxButton *button;
94 QScrollArea *sv;
95 QWidget *widget;
96
97 inline void setText(const QString &text) { button->setText(text); }
98 inline void setIcon(const QIcon &is) { button->setIcon(is); }
99#ifndef QT_NO_TOOLTIP
100 inline void setToolTip(const QString &tip) { button->setToolTip(tip); }
101 inline QString toolTip() const { return button->toolTip(); }
102#endif
103 inline QString text() const { return button->text(); }
104 inline QIcon icon() const { return button->icon(); }
105
106 inline bool operator==(const Page& other) const
107 {
108 return widget == other.widget;
109 }
110 };
111 typedef std::vector<std::unique_ptr<Page>> PageList;
112
113 inline QToolBoxPrivate()
114 : currentPage(nullptr)
115 {
116 }
117 void _q_buttonClicked();
118 void _q_widgetDestroyed(QObject*);
119
120 const Page *page(const QObject *widget) const;
121 const Page *page(int index) const;
122 Page *page(int index);
123
124 void updateTabs();
125 void relayout();
126
127 PageList pageList;
128 QVBoxLayout *layout;
129 Page *currentPage;
130};
131
132const QToolBoxPrivate::Page *QToolBoxPrivate::page(const QObject *widget) const
133{
134 if (!widget)
135 return nullptr;
136
137 for (const auto &page : pageList) {
138 if (page->widget == widget)
139 return page.get();
140 }
141 return nullptr;
142}
143
144QToolBoxPrivate::Page *QToolBoxPrivate::page(int index)
145{
146 if (index >= 0 && index < static_cast<int>(pageList.size()))
147 return pageList[index].get();
148 return nullptr;
149}
150
151const QToolBoxPrivate::Page *QToolBoxPrivate::page(int index) const
152{
153 if (index >= 0 && index < static_cast<int>(pageList.size()))
154 return pageList[index].get();
155 return nullptr;
156}
157
158void QToolBoxPrivate::updateTabs()
159{
160 QToolBoxButton *lastButton = currentPage ? currentPage->button : nullptr;
161 bool after = false;
162 int index = 0;
163 for (const auto &page : pageList) {
164 QToolBoxButton *tB = page->button;
165 // update indexes, since the updates are delayed, the indexes will be correct
166 // when we actually paint.
167 tB->setIndex(index);
168 QWidget *tW = page->widget;
169 if (after) {
170 QPalette p = tB->palette();
171 p.setColor(acr: tB->backgroundRole(), acolor: tW->palette().color(cr: tW->backgroundRole()));
172 tB->setPalette(p);
173 tB->update();
174 } else if (tB->backgroundRole() != QPalette::Window) {
175 tB->setBackgroundRole(QPalette::Window);
176 tB->update();
177 }
178 after = tB == lastButton;
179 ++index;
180 }
181}
182
183QSize QToolBoxButton::sizeHint() const
184{
185 QSize iconSize(8, 8);
186 if (!icon().isNull()) {
187 int icone = style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: nullptr, widget: parentWidget() /* QToolBox */);
188 iconSize += QSize(icone + 2, icone);
189 }
190 QSize textSize = fontMetrics().size(flags: Qt::TextShowMnemonic, str: text()) + QSize(0, 8);
191
192 QSize total(iconSize.width() + textSize.width(), qMax(a: iconSize.height(), b: textSize.height()));
193 return total.expandedTo(otherSize: QApplication::globalStrut());
194}
195
196QSize QToolBoxButton::minimumSizeHint() const
197{
198 if (icon().isNull())
199 return QSize();
200 int icone = style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: nullptr, widget: parentWidget() /* QToolBox */);
201 return QSize(icone + 8, icone + 8);
202}
203
204void QToolBoxButton::initStyleOption(QStyleOptionToolBox *option) const
205{
206 if (!option)
207 return;
208 option->initFrom(w: this);
209 if (selected)
210 option->state |= QStyle::State_Selected;
211 if (isDown())
212 option->state |= QStyle::State_Sunken;
213 option->text = text();
214 option->icon = icon();
215
216 QToolBox *toolBox = static_cast<QToolBox *>(parentWidget()); // I know I'm in a tool box.
217 const int widgetCount = toolBox->count();
218 const int currIndex = toolBox->currentIndex();
219 if (widgetCount == 1) {
220 option->position = QStyleOptionToolBox::OnlyOneTab;
221 } else if (indexInPage == 0) {
222 option->position = QStyleOptionToolBox::Beginning;
223 } else if (indexInPage == widgetCount - 1) {
224 option->position = QStyleOptionToolBox::End;
225 } else {
226 option->position = QStyleOptionToolBox::Middle;
227 }
228 if (currIndex == indexInPage - 1) {
229 option->selectedPosition = QStyleOptionToolBox::PreviousIsSelected;
230 } else if (currIndex == indexInPage + 1) {
231 option->selectedPosition = QStyleOptionToolBox::NextIsSelected;
232 } else {
233 option->selectedPosition = QStyleOptionToolBox::NotAdjacent;
234 }
235}
236
237void QToolBoxButton::paintEvent(QPaintEvent *)
238{
239 QPainter paint(this);
240 QPainter *p = &paint;
241 QStyleOptionToolBox opt;
242 initStyleOption(option: &opt);
243 style()->drawControl(element: QStyle::CE_ToolBoxTab, opt: &opt, p, w: parentWidget());
244}
245
246/*!
247 \class QToolBox
248
249 \brief The QToolBox class provides a column of tabbed widget items.
250
251
252 \ingroup basicwidgets
253 \inmodule QtWidgets
254
255 A toolbox is a widget that displays a column of tabs one above the
256 other, with the current item displayed below the current tab.
257 Every tab has an index position within the column of tabs. A tab's
258 item is a QWidget.
259
260 Each item has an itemText(), an optional itemIcon(), an optional
261 itemToolTip(), and a widget(). The item's attributes can be
262 changed with setItemText(), setItemIcon(), and
263 setItemToolTip(). Each item can be enabled or disabled
264 individually with setItemEnabled().
265
266 Items are added using addItem(), or inserted at particular
267 positions using insertItem(). The total number of items is given
268 by count(). Items can be deleted with delete, or removed from the
269 toolbox with removeItem(). Combining removeItem() and insertItem()
270 allows you to move items to different positions.
271
272 The index of the current item widget is returned by currentIndex(),
273 and set with setCurrentIndex(). The index of a particular item can
274 be found using indexOf(), and the item at a given index is returned
275 by item().
276
277 The currentChanged() signal is emitted when the current item is
278 changed.
279
280 \sa QTabWidget
281*/
282
283/*!
284 \fn void QToolBox::currentChanged(int index)
285
286 This signal is emitted when the current item is changed. The new
287 current item's index is passed in \a index, or -1 if there is no
288 current item.
289*/
290
291
292/*!
293 Constructs a new toolbox with the given \a parent and the flags, \a f.
294*/
295QToolBox::QToolBox(QWidget *parent, Qt::WindowFlags f)
296 : QFrame(*new QToolBoxPrivate, parent, f)
297{
298 Q_D(QToolBox);
299 d->layout = new QVBoxLayout(this);
300 d->layout->setContentsMargins(QMargins());
301 setBackgroundRole(QPalette::Button);
302}
303
304/*!
305 Destroys the toolbox.
306*/
307
308QToolBox::~QToolBox()
309{
310}
311
312/*!
313 \fn int QToolBox::addItem(QWidget *w, const QString &text)
314 \overload
315
316 Adds the widget \a w in a new tab at bottom of the toolbox. The
317 new tab's text is set to \a text. Returns the new tab's index.
318*/
319
320/*!
321 \fn int QToolBox::addItem(QWidget *widget, const QIcon &iconSet,const QString &text)
322 Adds the \a widget in a new tab at bottom of the toolbox. The
323 new tab's text is set to \a text, and the \a iconSet is
324 displayed to the left of the \a text. Returns the new tab's index.
325*/
326
327/*!
328 \fn int QToolBox::insertItem(int index, QWidget *widget, const QString &text)
329 \overload
330
331 Inserts the \a widget at position \a index, or at the bottom
332 of the toolbox if \a index is out of range. The new item's text is
333 set to \a text. Returns the new item's index.
334*/
335
336/*!
337 Inserts the \a widget at position \a index, or at the bottom
338 of the toolbox if \a index is out of range. The new item's text
339 is set to \a text, and the \a icon is displayed to the left of
340 the \a text. Returns the new item's index.
341*/
342
343int QToolBox::insertItem(int index, QWidget *widget, const QIcon &icon, const QString &text)
344{
345 if (!widget)
346 return -1;
347
348 Q_D(QToolBox);
349 connect(sender: widget, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(_q_widgetDestroyed(QObject*)));
350
351 auto newPage = qt_make_unique<QToolBoxPrivate::Page>();
352 auto &c = *newPage;
353 c.widget = widget;
354 c.button = new QToolBoxButton(this);
355 c.button->setObjectName(QLatin1String("qt_toolbox_toolboxbutton"));
356 connect(sender: c.button, SIGNAL(clicked()), receiver: this, SLOT(_q_buttonClicked()));
357
358 c.sv = new QScrollArea(this);
359 c.sv->setWidget(widget);
360 c.sv->setWidgetResizable(true);
361 c.sv->hide();
362 c.sv->setFrameStyle(QFrame::NoFrame);
363
364 c.setText(text);
365 c.setIcon(icon);
366
367 if (index < 0 || index >= static_cast<int>(d->pageList.size())) {
368 index = static_cast<int>(d->pageList.size());
369 d->pageList.push_back(x: std::move(newPage));
370 d->layout->addWidget(c.button);
371 d->layout->addWidget(c.sv);
372 if (index == 0)
373 setCurrentIndex(index);
374 } else {
375 d->pageList.insert(position: d->pageList.cbegin() + index, x: std::move(newPage));
376 d->relayout();
377 if (d->currentPage) {
378 QWidget *current = d->currentPage->widget;
379 int oldindex = indexOf(widget: current);
380 if (index <= oldindex) {
381 d->currentPage = nullptr; // trigger change
382 setCurrentIndex(oldindex);
383 }
384 }
385 }
386
387 c.button->show();
388
389 d->updateTabs();
390 itemInserted(index);
391 return index;
392}
393
394void QToolBoxPrivate::_q_buttonClicked()
395{
396 Q_Q(QToolBox);
397 QToolBoxButton *tb = qobject_cast<QToolBoxButton*>(object: q->sender());
398 QWidget* item = nullptr;
399 for (const auto &page : pageList) {
400 if (page->button == tb) {
401 item = page->widget;
402 break;
403 }
404 }
405
406 q->setCurrentIndex(q->indexOf(widget: item));
407}
408
409/*!
410 \property QToolBox::count
411 \brief The number of items contained in the toolbox.
412
413 By default, this property has a value of 0.
414*/
415
416int QToolBox::count() const
417{
418 Q_D(const QToolBox);
419 return static_cast<int>(d->pageList.size());
420}
421
422void QToolBox::setCurrentIndex(int index)
423{
424 Q_D(QToolBox);
425 QToolBoxPrivate::Page *c = d->page(index);
426 if (!c || d->currentPage == c)
427 return;
428
429 c->button->setSelected(true);
430 if (d->currentPage) {
431 d->currentPage->sv->hide();
432 d->currentPage->button->setSelected(false);
433 }
434 d->currentPage = c;
435 d->currentPage->sv->show();
436 d->updateTabs();
437 emit currentChanged(index);
438}
439
440void QToolBoxPrivate::relayout()
441{
442 Q_Q(QToolBox);
443 delete layout;
444 layout = new QVBoxLayout(q);
445 layout->setContentsMargins(QMargins());
446 for (const auto &page : pageList) {
447 layout->addWidget(page->button);
448 layout->addWidget(page->sv);
449 }
450}
451
452auto pageEquals = [](const QToolBoxPrivate::Page *page) {
453 return [page](const std::unique_ptr<QToolBoxPrivate::Page> &ptr) {
454 return ptr.get() == page;
455 };
456};
457
458void QToolBoxPrivate::_q_widgetDestroyed(QObject *object)
459{
460 Q_Q(QToolBox);
461
462 const QToolBoxPrivate::Page * const c = page(widget: object);
463 if (!c)
464 return;
465
466 layout->removeWidget(w: c->sv);
467 layout->removeWidget(w: c->button);
468 c->sv->deleteLater(); // page might still be a child of sv
469 delete c->button;
470
471 bool removeCurrent = c == currentPage;
472 pageList.erase(first: std::remove_if(first: pageList.begin(), last: pageList.end(), pred: pageEquals(c)), last: pageList.end());
473
474 if (pageList.empty()) {
475 currentPage = nullptr;
476 emit q->currentChanged(index: -1);
477 } else if (removeCurrent) {
478 currentPage = nullptr;
479 q->setCurrentIndex(0);
480 }
481}
482
483/*!
484 Removes the item at position \a index from the toolbox. Note that
485 the widget is \e not deleted.
486*/
487
488void QToolBox::removeItem(int index)
489{
490 Q_D(QToolBox);
491 if (QWidget *w = widget(index)) {
492 disconnect(sender: w, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(_q_widgetDestroyed(QObject*)));
493 w->setParent(this);
494 // destroy internal data
495 d->_q_widgetDestroyed(object: w);
496 itemRemoved(index);
497 }
498}
499
500
501/*!
502 \property QToolBox::currentIndex
503 \brief the index of the current item
504
505 By default, for an empty toolbox, this property has a value of -1.
506
507 \sa indexOf(), widget()
508*/
509
510
511int QToolBox::currentIndex() const
512{
513 Q_D(const QToolBox);
514 return d->currentPage ? indexOf(widget: d->currentPage->widget) : -1;
515}
516
517/*!
518 Returns a pointer to the current widget, or \nullptr if there is
519 no such item.
520
521 \sa currentIndex(), setCurrentWidget()
522*/
523
524QWidget * QToolBox::currentWidget() const
525{
526 Q_D(const QToolBox);
527 return d->currentPage ? d->currentPage->widget : nullptr;
528}
529
530/*!
531 Makes\a widget the current widget. The \a widget must be an item in this tool box.
532
533 \sa addItem(), setCurrentIndex(), currentWidget()
534 */
535void QToolBox::setCurrentWidget(QWidget *widget)
536{
537 int i = indexOf(widget);
538 if (Q_UNLIKELY(i < 0))
539 qWarning(msg: "QToolBox::setCurrentWidget: widget not contained in tool box");
540 else
541 setCurrentIndex(i);
542}
543
544/*!
545 Returns the widget at position \a index, or \nullptr if there is
546 no such item.
547*/
548
549QWidget *QToolBox::widget(int index) const
550{
551 Q_D(const QToolBox);
552 if (index < 0 || index >= static_cast<int>(d->pageList.size()))
553 return nullptr;
554 return d->pageList[index]->widget;
555}
556
557/*!
558 Returns the index of \a widget, or -1 if the item does not
559 exist.
560*/
561
562int QToolBox::indexOf(QWidget *widget) const
563{
564 Q_D(const QToolBox);
565 const QToolBoxPrivate::Page *c = (widget ? d->page(widget) : nullptr);
566 if (!c)
567 return -1;
568 const auto it = std::find_if(first: d->pageList.cbegin(), last: d->pageList.cend(), pred: pageEquals(c));
569 if (it == d->pageList.cend())
570 return -1;
571 return static_cast<int>(it - d->pageList.cbegin());
572}
573
574/*!
575 If \a enabled is true then the item at position \a index is enabled; otherwise
576 the item at position \a index is disabled.
577*/
578
579void QToolBox::setItemEnabled(int index, bool enabled)
580{
581 Q_D(QToolBox);
582 QToolBoxPrivate::Page *c = d->page(index);
583 if (!c)
584 return;
585
586 c->button->setEnabled(enabled);
587 if (!enabled && c == d->currentPage) {
588 int curIndexUp = index;
589 int curIndexDown = curIndexUp;
590 const int count = static_cast<int>(d->pageList.size());
591 while (curIndexUp > 0 || curIndexDown < count-1) {
592 if (curIndexDown < count-1) {
593 if (d->page(index: ++curIndexDown)->button->isEnabled()) {
594 index = curIndexDown;
595 break;
596 }
597 }
598 if (curIndexUp > 0) {
599 if (d->page(index: --curIndexUp)->button->isEnabled()) {
600 index = curIndexUp;
601 break;
602 }
603 }
604 }
605 setCurrentIndex(index);
606 }
607}
608
609
610/*!
611 Sets the text of the item at position \a index to \a text.
612
613 If the provided text contains an ampersand character ('&'), a
614 mnemonic is automatically created for it. The character that
615 follows the '&' will be used as the shortcut key. Any previous
616 mnemonic will be overwritten, or cleared if no mnemonic is defined
617 by the text. See the \l {QShortcut#mnemonic}{QShortcut}
618 documentation for details (to display an actual ampersand, use
619 '&&').
620*/
621
622void QToolBox::setItemText(int index, const QString &text)
623{
624 Q_D(QToolBox);
625 QToolBoxPrivate::Page *c = d->page(index);
626 if (c)
627 c->setText(text);
628}
629
630/*!
631 Sets the icon of the item at position \a index to \a icon.
632*/
633
634void QToolBox::setItemIcon(int index, const QIcon &icon)
635{
636 Q_D(QToolBox);
637 QToolBoxPrivate::Page *c = d->page(index);
638 if (c)
639 c->setIcon(icon);
640}
641
642#ifndef QT_NO_TOOLTIP
643/*!
644 Sets the tooltip of the item at position \a index to \a toolTip.
645*/
646
647void QToolBox::setItemToolTip(int index, const QString &toolTip)
648{
649 Q_D(QToolBox);
650 QToolBoxPrivate::Page *c = d->page(index);
651 if (c)
652 c->setToolTip(toolTip);
653}
654#endif // QT_NO_TOOLTIP
655
656/*!
657 Returns \c true if the item at position \a index is enabled; otherwise returns \c false.
658*/
659
660bool QToolBox::isItemEnabled(int index) const
661{
662 Q_D(const QToolBox);
663 const QToolBoxPrivate::Page *c = d->page(index);
664 return c && c->button->isEnabled();
665}
666
667/*!
668 Returns the text of the item at position \a index, or an empty string if
669 \a index is out of range.
670*/
671
672QString QToolBox::itemText(int index) const
673{
674 Q_D(const QToolBox);
675 const QToolBoxPrivate::Page *c = d->page(index);
676 return (c ? c->text() : QString());
677}
678
679/*!
680 Returns the icon of the item at position \a index, or a null
681 icon if \a index is out of range.
682*/
683
684QIcon QToolBox::itemIcon(int index) const
685{
686 Q_D(const QToolBox);
687 const QToolBoxPrivate::Page *c = d->page(index);
688 return (c ? c->icon() : QIcon());
689}
690
691#ifndef QT_NO_TOOLTIP
692/*!
693 Returns the tooltip of the item at position \a index, or an
694 empty string if \a index is out of range.
695*/
696
697QString QToolBox::itemToolTip(int index) const
698{
699 Q_D(const QToolBox);
700 const QToolBoxPrivate::Page *c = d->page(index);
701 return (c ? c->toolTip() : QString());
702}
703#endif // QT_NO_TOOLTIP
704
705/*! \reimp */
706void QToolBox::showEvent(QShowEvent *e)
707{
708 QWidget::showEvent(event: e);
709}
710
711/*! \reimp */
712void QToolBox::changeEvent(QEvent *ev)
713{
714 Q_D(QToolBox);
715 if(ev->type() == QEvent::StyleChange)
716 d->updateTabs();
717 QFrame::changeEvent(ev);
718}
719
720/*!
721 This virtual handler is called after a new item was added or
722 inserted at position \a index.
723
724 \sa itemRemoved()
725 */
726void QToolBox::itemInserted(int index)
727{
728 Q_UNUSED(index)
729}
730
731/*!
732 This virtual handler is called after an item was removed from
733 position \a index.
734
735 \sa itemInserted()
736 */
737void QToolBox::itemRemoved(int index)
738{
739 Q_UNUSED(index)
740}
741
742/*! \reimp */
743bool QToolBox::event(QEvent *e)
744{
745 return QFrame::event(e);
746}
747
748QT_END_NAMESPACE
749
750#include "moc_qtoolbox.cpp"
751#include "qtoolbox.moc"
752

source code of qtbase/src/widgets/widgets/qtoolbox.cpp