1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qstatusbar.h"
5
6#include "qlist.h"
7#include "qdebug.h"
8#include "qevent.h"
9#include "qlayout.h"
10#include "qpainter.h"
11#include "qtimer.h"
12#include "qstyle.h"
13#include "qstyleoption.h"
14#if QT_CONFIG(sizegrip)
15#include "qsizegrip.h"
16#endif
17#if QT_CONFIG(mainwindow)
18#include "qmainwindow.h"
19#endif
20
21#if QT_CONFIG(accessibility)
22#include "qaccessible.h"
23#endif
24
25#include <private/qlayoutengine_p.h>
26#include <private/qwidget_p.h>
27
28QT_BEGIN_NAMESPACE
29
30class QStatusBarPrivate : public QWidgetPrivate
31{
32 Q_DECLARE_PUBLIC(QStatusBar)
33public:
34 QStatusBarPrivate() {}
35
36 enum ItemCategory
37 {
38 Normal,
39 Permanent
40 };
41
42 struct SBItem {
43 QWidget *widget = nullptr;
44 int stretch = 0;
45 ItemCategory category = Normal;
46 bool isPermanent() const { return category == Permanent; }
47 };
48
49 QList<SBItem> items;
50 QString tempItem;
51
52 QBoxLayout *box;
53 QTimer *timer;
54
55#if QT_CONFIG(sizegrip)
56 QSizeGrip *resizer;
57 bool showSizeGrip;
58#endif
59
60 int savedStrut;
61
62 int indexToLastNonPermanentWidget() const
63 {
64 int i = items.size() - 1;
65 for (; i >= 0; --i) {
66 const SBItem &item = items.at(i);
67 if (!item.isPermanent())
68 break;
69 }
70 return i;
71 }
72
73#if QT_CONFIG(sizegrip)
74 void tryToShowSizeGrip()
75 {
76 if (!showSizeGrip)
77 return;
78 showSizeGrip = false;
79 if (!resizer || resizer->isVisible())
80 return;
81 resizer->setAttribute(Qt::WA_WState_ExplicitShowHide, on: false);
82 QMetaObject::invokeMethod(obj: resizer, member: "_q_showIfNotHidden", c: Qt::DirectConnection);
83 resizer->setAttribute(Qt::WA_WState_ExplicitShowHide, on: false);
84 }
85#endif
86
87 QRect messageRect() const;
88};
89
90
91QRect QStatusBarPrivate::messageRect() const
92{
93 Q_Q(const QStatusBar);
94 const bool rtl = q->layoutDirection() == Qt::RightToLeft;
95
96 int left = 6;
97 int right = q->width() - 12;
98
99#if QT_CONFIG(sizegrip)
100 if (resizer && resizer->isVisible()) {
101 if (rtl)
102 left = resizer->x() + resizer->width();
103 else
104 right = resizer->x();
105 }
106#endif
107
108 for (const auto &item : items) {
109 if (item.isPermanent() && item.widget->isVisible()) {
110 if (rtl)
111 left = qMax(a: left, b: item.widget->x() + item.widget->width() + 2);
112 else
113 right = qMin(a: right, b: item.widget->x() - 2);
114 break;
115 }
116 }
117 return QRect(left, 0, right-left, q->height());
118}
119
120
121/*!
122 \class QStatusBar
123 \brief The QStatusBar class provides a horizontal bar suitable for
124 presenting status information.
125
126 \ingroup mainwindow-classes
127 \ingroup helpsystem
128 \inmodule QtWidgets
129
130 Each status indicator falls into one of three categories:
131
132 \list
133 \li \e Temporary - briefly occupies most of the status bar. Used
134 to explain tool tip texts or menu entries, for example.
135 \li \e Normal - occupies part of the status bar and may be hidden
136 by temporary messages. Used to display the page and line
137 number in a word processor, for example.
138 \li \e Permanent - is never hidden. Used for important mode
139 indications, for example, some applications put a Caps Lock
140 indicator in the status bar.
141 \endlist
142
143 QStatusBar lets you display all three types of indicators.
144
145 Typically, a request for the status bar functionality occurs in
146 relation to a QMainWindow object. QMainWindow provides a main
147 application window, with a menu bar, tool bars, dock widgets \e
148 and a status bar around a large central widget. The status bar can
149 be retrieved using the QMainWindow::statusBar() function, and
150 replaced using the QMainWindow::setStatusBar() function.
151
152 Use the showMessage() slot to display a \e temporary message:
153
154 \snippet code/src_gui_widgets_qstatusbar.cpp 1
155
156 To remove a temporary message, use the clearMessage() slot, or set
157 a time limit when calling showMessage(). For example:
158
159 \snippet code/src_gui_widgets_qstatusbar.cpp 2
160
161 Use the currentMessage() function to retrieve the temporary
162 message currently shown. The QStatusBar class also provide the
163 messageChanged() signal which is emitted whenever the temporary
164 status message changes.
165
166 \target permanent message
167 \e Normal and \e Permanent messages are displayed by creating a
168 small widget (QLabel, QProgressBar or even QToolButton) and then
169 adding it to the status bar using the addWidget() or the
170 addPermanentWidget() function. Use the removeWidget() function to
171 remove such messages from the status bar.
172
173 \snippet code/src_gui_widgets_qstatusbar.cpp 0
174
175 By default QStatusBar provides a QSizeGrip in the lower-right
176 corner. You can disable it using the setSizeGripEnabled()
177 function. Use the isSizeGripEnabled() function to determine the
178 current status of the size grip.
179
180 \image fusion-statusbar-sizegrip.png A status bar shown in the Fusion widget style
181
182 \sa QMainWindow, QStatusTipEvent
183*/
184
185
186/*!
187 Constructs a status bar with a size grip and the given \a parent.
188
189 \sa setSizeGripEnabled()
190*/
191QStatusBar::QStatusBar(QWidget * parent)
192 : QWidget(*new QStatusBarPrivate, parent, { })
193{
194 Q_D(QStatusBar);
195 d->box = nullptr;
196 d->timer = nullptr;
197
198#if QT_CONFIG(sizegrip)
199 d->resizer = nullptr;
200 setSizeGripEnabled(true); // causes reformat()
201#else
202 reformat();
203#endif
204}
205
206/*!
207 Destroys this status bar and frees any allocated resources and
208 child widgets.
209*/
210QStatusBar::~QStatusBar()
211{
212}
213
214
215/*!
216 Adds the given \a widget to this status bar, reparenting the
217 widget if it isn't already a child of this QStatusBar object. The
218 \a stretch parameter is used to compute a suitable size for the
219 given \a widget as the status bar grows and shrinks. The default
220 stretch factor is 0, i.e giving the widget a minimum of space.
221
222 The widget is located to the far left of the first permanent
223 widget (see addPermanentWidget()) and may be obscured by temporary
224 messages.
225
226 \sa insertWidget(), removeWidget(), addPermanentWidget()
227*/
228
229void QStatusBar::addWidget(QWidget * widget, int stretch)
230{
231 if (!widget)
232 return;
233 insertWidget(index: d_func()->indexToLastNonPermanentWidget() + 1, widget, stretch);
234}
235
236/*!
237 \since 4.2
238
239 Inserts the given \a widget at the given \a index to this status bar,
240 reparenting the widget if it isn't already a child of this
241 QStatusBar object. If \a index is out of range, the widget is appended
242 (in which case it is the actual index of the widget that is returned).
243
244 The \a stretch parameter is used to compute a suitable size for
245 the given \a widget as the status bar grows and shrinks. The
246 default stretch factor is 0, i.e giving the widget a minimum of
247 space.
248
249 The widget is located to the far left of the first permanent
250 widget (see addPermanentWidget()) and may be obscured by temporary
251 messages.
252
253 \sa addWidget(), removeWidget(), addPermanentWidget()
254*/
255int QStatusBar::insertWidget(int index, QWidget *widget, int stretch)
256{
257 if (!widget)
258 return -1;
259
260 Q_D(QStatusBar);
261 QStatusBarPrivate::SBItem item{.widget: widget, .stretch: stretch, .category: QStatusBarPrivate::Normal};
262
263 int idx = d->indexToLastNonPermanentWidget();
264 if (Q_UNLIKELY(index < 0 || index > d->items.size() || (idx >= 0 && index > idx + 1))) {
265 qWarning(msg: "QStatusBar::insertWidget: Index out of range (%d), appending widget", index);
266 index = idx + 1;
267 }
268 d->items.insert(i: index, t: item);
269
270 if (!d->tempItem.isEmpty())
271 widget->hide();
272
273 reformat();
274 if (!widget->isHidden() || !widget->testAttribute(attribute: Qt::WA_WState_ExplicitShowHide))
275 widget->show();
276
277 return index;
278}
279
280/*!
281 Adds the given \a widget permanently to this status bar,
282 reparenting the widget if it isn't already a child of this
283 QStatusBar object. The \a stretch parameter is used to compute a
284 suitable size for the given \a widget as the status bar grows and
285 shrinks. The default stretch factor is 0, i.e giving the widget a
286 minimum of space.
287
288 Permanently means that the widget may not be obscured by temporary
289 messages. It is located at the far right of the status bar.
290
291 \sa insertPermanentWidget(), removeWidget(), addWidget()
292*/
293
294void QStatusBar::addPermanentWidget(QWidget * widget, int stretch)
295{
296 if (!widget)
297 return;
298 insertPermanentWidget(index: d_func()->items.size(), widget, stretch);
299}
300
301/*!
302 \since 4.2
303
304 Inserts the given \a widget at the given \a index permanently to this status bar,
305 reparenting the widget if it isn't already a child of this
306 QStatusBar object. If \a index is out of range, the widget is appended
307 (in which case it is the actual index of the widget that is returned).
308
309 The \a stretch parameter is used to compute a
310 suitable size for the given \a widget as the status bar grows and
311 shrinks. The default stretch factor is 0, i.e giving the widget a
312 minimum of space.
313
314 Permanently means that the widget may not be obscured by temporary
315 messages. It is located at the far right of the status bar.
316
317 \sa addPermanentWidget(), removeWidget(), addWidget()
318*/
319int QStatusBar::insertPermanentWidget(int index, QWidget *widget, int stretch)
320{
321 if (!widget)
322 return -1;
323
324 Q_D(QStatusBar);
325 QStatusBarPrivate::SBItem item{.widget: widget, .stretch: stretch, .category: QStatusBarPrivate::Permanent};
326
327 int idx = d->indexToLastNonPermanentWidget();
328 if (Q_UNLIKELY(index < 0 || index > d->items.size() || (idx >= 0 && index <= idx))) {
329 qWarning(msg: "QStatusBar::insertPermanentWidget: Index out of range (%d), appending widget", index);
330 index = d->items.size();
331 }
332 d->items.insert(i: index, t: item);
333
334 reformat();
335 if (!widget->isHidden() || !widget->testAttribute(attribute: Qt::WA_WState_ExplicitShowHide))
336 widget->show();
337
338 return index;
339}
340
341/*!
342 Removes the specified \a widget from the status bar.
343
344 \note This function does not delete the widget but \e hides it.
345 To add the widget again, you must call both the addWidget() and
346 show() functions.
347
348 \sa addWidget(), addPermanentWidget(), clearMessage()
349*/
350
351void QStatusBar::removeWidget(QWidget *widget)
352{
353 if (!widget)
354 return;
355
356 Q_D(QStatusBar);
357 if (d->items.removeIf(pred: [widget](const auto &item) { return item.widget == widget; })) {
358 widget->hide();
359 reformat();
360 }
361#if defined(QT_DEBUG)
362 else
363 qDebug(msg: "QStatusBar::removeWidget(): Widget not found.");
364#endif
365}
366
367/*!
368 \property QStatusBar::sizeGripEnabled
369
370 \brief whether the QSizeGrip in the bottom-right corner of the
371 status bar is enabled
372
373 The size grip is enabled by default.
374*/
375
376bool QStatusBar::isSizeGripEnabled() const
377{
378#if !QT_CONFIG(sizegrip)
379 return false;
380#else
381 Q_D(const QStatusBar);
382 return !!d->resizer;
383#endif
384}
385
386void QStatusBar::setSizeGripEnabled(bool enabled)
387{
388#if !QT_CONFIG(sizegrip)
389 Q_UNUSED(enabled);
390#else
391 Q_D(QStatusBar);
392 if (!enabled != !d->resizer) {
393 if (enabled) {
394 d->resizer = new QSizeGrip(this);
395 d->resizer->hide();
396 d->resizer->installEventFilter(filterObj: this);
397 d->showSizeGrip = true;
398 } else {
399 delete d->resizer;
400 d->resizer = nullptr;
401 d->showSizeGrip = false;
402 }
403 reformat();
404 if (d->resizer && isVisible())
405 d->tryToShowSizeGrip();
406 }
407#endif
408}
409
410
411/*!
412 Changes the status bar's appearance to account for item changes.
413
414 Special subclasses may need this function, but geometry management
415 will usually take care of any necessary rearrangements.
416*/
417void QStatusBar::reformat()
418{
419 Q_D(QStatusBar);
420 if (d->box)
421 delete d->box;
422
423 QBoxLayout *vbox;
424#if QT_CONFIG(sizegrip)
425 if (d->resizer) {
426 d->box = new QHBoxLayout(this);
427 d->box->setContentsMargins(QMargins());
428 vbox = new QVBoxLayout;
429 d->box->addLayout(layout: vbox);
430 } else
431#endif
432 {
433 vbox = d->box = new QVBoxLayout(this);
434 d->box->setContentsMargins(QMargins());
435 }
436 vbox->addSpacing(size: 3);
437 QBoxLayout* l = new QHBoxLayout;
438 vbox->addLayout(layout: l);
439 l->addSpacing(size: 2);
440 l->setSpacing(6);
441
442 int maxH = fontMetrics().height();
443
444 qsizetype i;
445 for (i = 0; i < d->items.size(); ++i) {
446 const auto &item = d->items.at(i);
447 if (item.isPermanent())
448 break;
449 l->addWidget(item.widget, stretch: item.stretch);
450 int itemH = qMin(a: qSmartMinSize(w: item.widget).height(), b: item.widget->maximumHeight());
451 maxH = qMax(a: maxH, b: itemH);
452 }
453
454 l->addStretch(stretch: 0);
455
456 for (; i < d->items.size(); ++i) {
457 const auto &item = d->items.at(i);
458 l->addWidget(item.widget, stretch: item.stretch);
459 int itemH = qMin(a: qSmartMinSize(w: item.widget).height(), b: item.widget->maximumHeight());
460 maxH = qMax(a: maxH, b: itemH);
461 }
462#if QT_CONFIG(sizegrip)
463 if (d->resizer) {
464 maxH = qMax(a: maxH, b: d->resizer->sizeHint().height());
465 d->box->addSpacing(size: 1);
466 d->box->addWidget(d->resizer, stretch: 0, alignment: Qt::AlignBottom);
467 }
468#endif
469 l->addStrut(maxH);
470 d->savedStrut = maxH;
471 vbox->addSpacing(size: 2);
472 d->box->activate();
473 update();
474}
475
476/*!
477
478 Hides the normal status indications and displays the given \a
479 message for the specified number of milli-seconds (\a{timeout}). If
480 \a{timeout} is 0 (default), the \a {message} remains displayed until
481 the clearMessage() slot is called or until the showMessage() slot is
482 called again to change the message.
483
484 Note that showMessage() is called to show temporary explanations of
485 tool tip texts, so passing a \a{timeout} of 0 is not sufficient to
486 display a \l{permanent message}{permanent message}.
487
488 \sa messageChanged(), currentMessage(), clearMessage()
489*/
490void QStatusBar::showMessage(const QString &message, int timeout)
491{
492 Q_D(QStatusBar);
493
494 if (timeout > 0) {
495 if (!d->timer) {
496 d->timer = new QTimer(this);
497 connect(sender: d->timer, SIGNAL(timeout()), receiver: this, SLOT(clearMessage()));
498 }
499 d->timer->start(msec: timeout);
500 } else if (d->timer) {
501 delete d->timer;
502 d->timer = nullptr;
503 }
504 if (d->tempItem == message)
505 return;
506 d->tempItem = message;
507
508 hideOrShow();
509}
510
511/*!
512 Removes any temporary message being shown.
513
514 \sa currentMessage(), showMessage(), removeWidget()
515*/
516
517void QStatusBar::clearMessage()
518{
519 Q_D(QStatusBar);
520 if (d->tempItem.isEmpty())
521 return;
522 if (d->timer) {
523 qDeleteInEventHandler(o: d->timer);
524 d->timer = nullptr;
525 }
526 d->tempItem.clear();
527 hideOrShow();
528}
529
530/*!
531 Returns the temporary message currently shown,
532 or an empty string if there is no such message.
533
534 \sa showMessage()
535*/
536QString QStatusBar::currentMessage() const
537{
538 Q_D(const QStatusBar);
539 return d->tempItem;
540}
541
542/*!
543 \fn void QStatusBar::messageChanged(const QString &message)
544
545 This signal is emitted whenever the temporary status message
546 changes. The new temporary message is passed in the \a message
547 parameter which is a null-string when the message has been
548 removed.
549
550 \sa showMessage(), clearMessage()
551*/
552
553/*!
554 Ensures that the right widgets are visible.
555
556 Used by the showMessage() and clearMessage() functions.
557*/
558void QStatusBar::hideOrShow()
559{
560 Q_D(QStatusBar);
561 bool haveMessage = !d->tempItem.isEmpty();
562
563 for (const auto &item : std::as_const(t&: d->items)) {
564 if (item.isPermanent())
565 break;
566 if (haveMessage && item.widget->isVisible()) {
567 item.widget->hide();
568 item.widget->setAttribute(Qt::WA_WState_ExplicitShowHide, on: false);
569 } else if (!haveMessage && !item.widget->testAttribute(attribute: Qt::WA_WState_ExplicitShowHide)) {
570 item.widget->show();
571 }
572 }
573
574 emit messageChanged(text: d->tempItem);
575
576#if QT_CONFIG(accessibility)
577 if (QAccessible::isActive()) {
578 QAccessibleEvent event(this, QAccessible::NameChanged);
579 QAccessible::updateAccessibility(event: &event);
580 }
581#endif
582
583 update(d->messageRect());
584}
585
586/*!
587 \reimp
588 */
589void QStatusBar::showEvent(QShowEvent *)
590{
591#if QT_CONFIG(sizegrip)
592 Q_D(QStatusBar);
593 if (d->resizer && d->showSizeGrip)
594 d->tryToShowSizeGrip();
595#endif
596}
597
598/*!
599 \reimp
600 \fn void QStatusBar::paintEvent(QPaintEvent *event)
601
602 Shows the temporary message, if appropriate, in response to the
603 paint \a event.
604*/
605void QStatusBar::paintEvent(QPaintEvent *event)
606{
607 Q_D(QStatusBar);
608 bool haveMessage = !d->tempItem.isEmpty();
609
610 QPainter p(this);
611 QStyleOption opt;
612 opt.initFrom(w: this);
613 style()->drawPrimitive(pe: QStyle::PE_PanelStatusBar, opt: &opt, p: &p, w: this);
614
615 for (const auto &item : std::as_const(t&: d->items)) {
616 if (item.widget->isVisible() && (!haveMessage || item.isPermanent())) {
617 QRect ir = item.widget->geometry().adjusted(xp1: -2, yp1: -1, xp2: 2, yp2: 1);
618 if (event->rect().intersects(r: ir)) {
619 QStyleOption opt(0);
620 opt.rect = ir;
621 opt.palette = palette();
622 opt.state = QStyle::State_None;
623 style()->drawPrimitive(pe: QStyle::PE_FrameStatusBarItem, opt: &opt, p: &p, w: item.widget);
624 }
625 }
626 }
627 if (haveMessage) {
628 p.setPen(palette().windowText().color());
629 p.drawText(r: d->messageRect(), flags: Qt::AlignLeading | Qt::AlignVCenter | Qt::TextSingleLine, text: d->tempItem);
630 }
631}
632
633/*!
634 \reimp
635*/
636void QStatusBar::resizeEvent(QResizeEvent * e)
637{
638 QWidget::resizeEvent(event: e);
639}
640
641/*!
642 \reimp
643*/
644
645bool QStatusBar::event(QEvent *e)
646{
647 Q_D(QStatusBar);
648
649 switch (e->type()) {
650 case QEvent::LayoutRequest: {
651 // Calculate new strut height and call reformat() if it has changed
652 int maxH = fontMetrics().height();
653
654 for (const auto &item : std::as_const(t&: d->items)) {
655 const int itemH = qMin(a: qSmartMinSize(w: item.widget).height(), b: item.widget->maximumHeight());
656 maxH = qMax(a: maxH, b: itemH);
657 }
658
659#if QT_CONFIG(sizegrip)
660 if (d->resizer)
661 maxH = qMax(a: maxH, b: d->resizer->sizeHint().height());
662#endif
663
664 if (maxH != d->savedStrut)
665 reformat();
666 else
667 update();
668 break;
669 }
670 case QEvent::ChildRemoved:
671 for (int i = 0; i < d->items.size(); ++i) {
672 const auto &item = d->items.at(i);
673 if (item.widget == static_cast<QChildEvent *>(e)->child())
674 d->items.removeAt(i);
675 }
676 break;
677 default:
678 break;
679 }
680
681 return QWidget::event(event: e);
682}
683
684QT_END_NAMESPACE
685
686#include "moc_qstatusbar.cpp"
687

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