VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/widgets/UITabBar.cpp@ 100347

Last change on this file since 100347 was 100344, checked in by vboxsync, 15 months ago

FE/Qt: bugref:10450: Qt6 compatibility bits for QMouseEvent; Replacing QMouseEvent::pos with QSinglePointEvent::position; S.a. r157776.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.5 KB
Line 
1/* $Id: UITabBar.cpp 100344 2023-07-03 10:09:28Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UITabBar class implementation.
4 */
5
6/*
7 * Copyright (C) 2017-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QAction>
30#include <QApplication>
31#include <QDrag>
32#include <QDragEnterEvent>
33#include <QDragMoveEvent>
34#include <QDropEvent>
35#include <QEvent>
36#include <QHBoxLayout>
37#include <QLabel>
38#include <QMimeData>
39#include <QMouseEvent>
40#include <QStyleOption>
41#include <QPainter>
42#include <QStyle>
43#include <QToolButton>
44#ifdef VBOX_WS_MAC
45# include <QStackedLayout>
46#endif
47#ifdef VBOX_IS_QT6_OR_LATER
48# include <QWindow>
49#endif
50
51/* GUI includes: */
52#include "QIWithRetranslateUI.h"
53#include "UIIconPool.h"
54#include "UITabBar.h"
55
56/* Other VBox includes: */
57#include "iprt/assert.h"
58
59/* Forward declarations: */
60class QApplication;
61class QDrag;
62class QEvent;
63class QHBoxLayout;
64class QLabel;
65class QMimeData;
66class QMouseEvent;
67#ifdef VBOX_WS_MAC
68class QStackedLayout;
69#endif
70class QStyle;
71class QStyleOption;
72class QToolButton;
73
74
75/** Our own skinnable implementation of tabs for tab-bar. */
76class UITabBarItem : public QIWithRetranslateUI<QWidget>
77{
78 Q_OBJECT;
79
80signals:
81
82 /** Notifies about item was clicked. */
83 void sigClicked(UITabBarItem *pItem);
84
85 /** Notifies about item close button was clicked. */
86 void sigCloseClicked(UITabBarItem *pItem);
87
88 /** Notifies about drag-object destruction. */
89 void sigDragObjectDestroy();
90
91public:
92
93 /** Position styles. */
94 enum PositionStyle { PositionStyle_Left, PositionStyle_Middle, PositionStyle_Right, PositionStyle_Single };
95
96 /** Holds the mime-type for the D&D system. */
97 static const QString MimeType;
98
99 /** Creates tab-bar item on the basis of passed @a uuid and @a pAction. */
100 UITabBarItem(const QUuid &uuid, const QAction *pAction);
101
102 /** Returns item ID. */
103 const QUuid uuid() const { return m_uuid; }
104
105 /** Defines the item @a enmPositionStyle. */
106 void setPositionStyle(PositionStyle enmPositionStyle);
107
108 /** Marks item @a fCurrent. */
109 void setCurrent(bool fCurrent);
110
111protected:
112
113 /** Handles any Qt @a pEvent. */
114 virtual bool event(QEvent *pEvent) RT_OVERRIDE;
115
116 /** Handles translation event. */
117 virtual void retranslateUi() RT_OVERRIDE;
118
119 /** Handles paint @a pEvent. */
120 virtual void paintEvent(QPaintEvent *pEvent) RT_OVERRIDE;
121
122 /** Handles mouse-press @a pEvent. */
123 virtual void mousePressEvent(QMouseEvent *pEvent) RT_OVERRIDE;
124 /** Handles mouse-release @a pEvent. */
125 virtual void mouseReleaseEvent(QMouseEvent *pEvent) RT_OVERRIDE;
126 /** Handles mouse-move @a pEvent. */
127 virtual void mouseMoveEvent(QMouseEvent *pEvent) RT_OVERRIDE;
128 /** Handles mouse-enter @a pEvent. */
129#ifdef VBOX_IS_QT6_OR_LATER /* QWidget::enterEvent uses QEnterEvent since qt6 */
130 virtual void enterEvent(QEnterEvent *pEvent) RT_OVERRIDE;
131#else
132 virtual void enterEvent(QEvent *pEvent) RT_OVERRIDE;
133#endif
134 /** Handles mouse-leave @a pEvent. */
135 virtual void leaveEvent(QEvent *pEvent) RT_OVERRIDE;
136
137private slots:
138
139 /** Handles close button click. */
140 void sltCloseClicked() { emit sigCloseClicked(this); }
141
142private:
143
144 /** Prepares all. */
145 void prepare();
146 /** Update pixmap. */
147 void updatePixmap();
148
149 /** Holds the item ID. */
150 const QUuid m_uuid;
151 /** Holds the item action reference. */
152 const QAction *m_pAction;
153
154 /** Holds the item position style. */
155 PositionStyle m_enmPosition;
156
157 /** Holds whether the item is current. */
158 bool m_fCurrent;
159 /** Holds whether the item is hovered. */
160 bool m_fHovered;
161
162 /** Holds the main layout instance. */
163 QHBoxLayout *m_pLayout;
164#ifdef VBOX_WS_MAC
165 /** Holds the stacked layout instance. */
166 QStackedLayout *m_pLayoutStacked;
167#endif
168
169 /** Holds the icon label instance. */
170 QLabel *m_pLabelIcon;
171 /** Holds the name label instance. */
172 QLabel *m_pLabelName;
173 /** Holds the close button instance. */
174 QToolButton *m_pButtonClose;
175
176 /** Holds the last mouse-press position. */
177 QPoint m_mousePressPosition;
178};
179
180
181/*********************************************************************************************************************************
182* Class UITabBarItem implementation. *
183*********************************************************************************************************************************/
184
185/* static */
186const QString UITabBarItem::MimeType = QString("application/virtualbox;value=TabID");
187
188UITabBarItem::UITabBarItem(const QUuid &uuid, const QAction *pAction)
189 : m_uuid(uuid)
190 , m_pAction(pAction)
191 , m_enmPosition(PositionStyle_Single)
192 , m_fCurrent(false)
193 , m_fHovered(false)
194 , m_pLayout(0)
195#ifdef VBOX_WS_MAC
196 , m_pLayoutStacked(0)
197#endif
198 , m_pLabelIcon(0)
199 , m_pLabelName(0)
200 , m_pButtonClose(0)
201{
202 /* Prepare: */
203 prepare();
204}
205
206void UITabBarItem::setPositionStyle(PositionStyle enmPosition)
207{
208 /* Remember the position: */
209 m_enmPosition = enmPosition;
210
211 /* And call for repaint: */
212 update();
213}
214
215void UITabBarItem::setCurrent(bool fCurrent)
216{
217 /* Remember the state: */
218 m_fCurrent = fCurrent;
219
220#ifdef VBOX_WS_MAC
221 /* Adjust name color: */
222 QPalette pal = qApp->palette();
223 if (m_fCurrent)
224 pal.setColor(QPalette::ButtonText, pal.color(QPalette::BrightText));
225 m_pLabelName->setPalette(pal);
226#endif
227
228 /* And call for repaint: */
229 update();
230}
231
232bool UITabBarItem::event(QEvent *pEvent)
233{
234 /* Handle know event types: */
235 switch (pEvent->type())
236 {
237 case QEvent::Show:
238 case QEvent::ScreenChangeInternal:
239 {
240 /* Update pixmap: */
241 updatePixmap();
242 break;
243 }
244 default:
245 break;
246 }
247
248 /* Call to base-class: */
249 return QIWithRetranslateUI<QWidget>::event(pEvent);
250}
251
252void UITabBarItem::retranslateUi()
253{
254 /* Translate label: */
255 m_pLabelName->setText(m_pAction->text().remove('&'));
256}
257
258void UITabBarItem::paintEvent(QPaintEvent * /* pEvent */)
259{
260#ifdef VBOX_WS_MAC
261
262 /* Prepare painter: */
263 QPainter painter(this);
264
265 /* Prepare palette colors: */
266 const QPalette pal = QApplication::palette();
267 const QColor color0 = m_fCurrent
268 ? pal.color(QPalette::Shadow).darker(110)
269 : pal.color(QPalette::Window).lighter(105);
270 const QColor color1 = pal.color(QPalette::Window);
271 const QColor color2 = color0.darker(120);
272 const QColor color3 = color0.darker(130);
273
274 /* Invent pixel metric: */
275 const int iMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize) / 4;
276
277 /* Top-left corner: */
278 QRadialGradient grad1(QPointF(iMetric, iMetric), iMetric);
279 {
280 grad1.setColorAt(0, color0);
281 grad1.setColorAt(.8, color0);
282 grad1.setColorAt(.81, color2);
283 grad1.setColorAt(1, color1);
284 }
285 /* Top-right corner: */
286 QRadialGradient grad2(QPointF(width() - iMetric, iMetric), iMetric);
287 {
288 grad2.setColorAt(0, color0);
289 grad2.setColorAt(.8, color0);
290 grad2.setColorAt(.81, color2);
291 grad2.setColorAt(1, color1);
292 }
293 /* Bottom-left corner: */
294 QRadialGradient grad3(QPointF(iMetric, height() - iMetric), iMetric);
295 {
296 grad3.setColorAt(0, color0);
297 grad3.setColorAt(.8, color0);
298 grad3.setColorAt(.81, color3);
299 grad3.setColorAt(1, color1);
300 }
301 /* Botom-right corner: */
302 QRadialGradient grad4(QPointF(width() - iMetric, height() - iMetric), iMetric);
303 {
304 grad4.setColorAt(0, color0);
305 grad4.setColorAt(.8, color0);
306 grad4.setColorAt(.81, color3);
307 grad4.setColorAt(1, color1);
308 }
309
310 /* Top line: */
311 QLinearGradient grad5(QPointF(iMetric, 0), QPointF(iMetric, iMetric));
312 {
313 grad5.setColorAt(0, color1);
314 grad5.setColorAt(.19, color2);
315 grad5.setColorAt(.2, color0);
316 grad5.setColorAt(1, color0);
317 }
318 /* Bottom line: */
319 QLinearGradient grad6(QPointF(iMetric, height()), QPointF(iMetric, height() - iMetric));
320 {
321 grad6.setColorAt(0, color1);
322 grad6.setColorAt(.19, color3);
323 grad6.setColorAt(.2, color0);
324 grad6.setColorAt(1, color0);
325 }
326 /* Left line: */
327 QLinearGradient grad7(QPointF(0, height() - iMetric), QPointF(iMetric, height() - iMetric));
328 {
329 grad7.setColorAt(0, color1);
330 grad7.setColorAt(.19, color2);
331 grad7.setColorAt(.2, color0);
332 grad7.setColorAt(1, color0);
333 }
334 /* Right line: */
335 QLinearGradient grad8(QPointF(width(), height() - iMetric), QPointF(width() - iMetric, height() - iMetric));
336 {
337 grad8.setColorAt(0, color1);
338 grad8.setColorAt(.19, color2);
339 grad8.setColorAt(.2, color0);
340 grad8.setColorAt(1, color0);
341 }
342
343 /* Paint: */
344 painter.fillRect(QRect(iMetric, iMetric, width() - iMetric * 2, height() - iMetric * 2), color0);
345
346 if (m_enmPosition == PositionStyle_Left || m_enmPosition == PositionStyle_Single)
347 {
348 painter.fillRect(QRect(0, 0, iMetric, iMetric), grad1);
349 painter.fillRect(QRect(0, height() - iMetric, iMetric, iMetric), grad3);
350 }
351 if (m_enmPosition == PositionStyle_Right || m_enmPosition == PositionStyle_Single)
352 {
353 painter.fillRect(QRect(width() - iMetric, 0, iMetric, iMetric), grad2);
354 painter.fillRect(QRect(width() - iMetric, height() - iMetric, iMetric, iMetric), grad4);
355 }
356
357 int iX = 0;
358 int iYL = 0;
359 int iYR = 0;
360 int iWid = width();
361 int iHeiL = height();
362 int iHeiR = height();
363 if (m_enmPosition == PositionStyle_Left || m_enmPosition == PositionStyle_Single)
364 {
365 iX = iMetric;
366 iYL = iMetric;
367 iWid -= iMetric;
368 iHeiL -= iMetric * 2;
369 }
370 if (m_enmPosition == PositionStyle_Right || m_enmPosition == PositionStyle_Single)
371 {
372 iYR = iMetric;
373 iWid -= iMetric;
374 iHeiR -= iMetric * 2;
375 }
376 painter.fillRect(QRect(0, iYL, iMetric, iHeiL), grad7);
377 painter.fillRect(QRect(width() - iMetric, iYR, iMetric, iHeiR), grad8);
378 painter.fillRect(QRect(iX, 0, iWid, iMetric), grad5);
379 painter.fillRect(QRect(iX, height() - iMetric, iWid, iMetric), grad6);
380
381#else /* !VBOX_WS_MAC */
382
383 /* Prepare painter: */
384 QPainter painter(this);
385
386 /* Prepare palette colors: */
387 const QPalette pal = QApplication::palette();
388 const QColor color0 = m_fCurrent ? pal.color(QPalette::Base)
389 : m_fHovered ? pal.color(QPalette::Base).darker(102)
390 : pal.color(QPalette::Button).darker(102);
391 QColor color1 = color0;
392 color1.setAlpha(0);
393 QColor color2 = pal.color(QPalette::Shadow);
394
395 /* Invent pixel metric: */
396 const int iMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize) / 2;
397
398 /* Top-left corner: */
399 QRadialGradient grad1(QPointF(iMetric, iMetric), iMetric);
400 {
401 grad1.setColorAt(0, color0);
402 grad1.setColorAt(.8, color1);
403 grad1.setColorAt(.9, color2);
404 grad1.setColorAt(1, color1);
405 }
406 /* Bottom-left corner: */
407 QRadialGradient grad2(QPointF(iMetric, height() - iMetric), iMetric);
408 {
409 grad2.setColorAt(0, color0);
410 grad2.setColorAt(.8, color1);
411 grad2.setColorAt(.9, color2);
412 grad2.setColorAt(1, color1);
413 }
414 /* Top-right corner: */
415 QRadialGradient grad3(QPointF(width() - iMetric, iMetric), iMetric);
416 {
417 grad3.setColorAt(0, color0);
418 grad3.setColorAt(.8, color1);
419 grad3.setColorAt(.9, color2);
420 grad3.setColorAt(1, color1);
421 }
422 /* Botom-right corner: */
423 QRadialGradient grad4(QPointF(width() - iMetric, height() - iMetric), iMetric);
424 {
425 grad4.setColorAt(0, color0);
426 grad4.setColorAt(.8, color1);
427 grad4.setColorAt(.9, color2);
428 grad4.setColorAt(1, color1);
429 }
430
431 /* Left line: */
432 QLinearGradient grad5(QPointF(0, height() - iMetric), QPointF(iMetric, height() - iMetric));
433 {
434 grad5.setColorAt(0, color1);
435 grad5.setColorAt(.1, color2);
436 grad5.setColorAt(.2, color1);
437 grad5.setColorAt(1, color0);
438 }
439 /* Right line: */
440 QLinearGradient grad6(QPointF(width(), height() - iMetric), QPointF(width() - iMetric, height() - iMetric));
441 {
442 grad6.setColorAt(0, color1);
443 grad6.setColorAt(.1, color2);
444 grad6.setColorAt(.2, color1);
445 grad6.setColorAt(1, color0);
446 }
447 /* Top line: */
448 QLinearGradient grad7(QPointF(iMetric, 0), QPointF(iMetric, iMetric));
449 {
450 grad7.setColorAt(0, color1);
451 grad7.setColorAt(.1, color2);
452 grad7.setColorAt(.2, color1);
453 grad7.setColorAt(1, color0);
454 }
455 /* Bottom line: */
456 QLinearGradient grad8(QPointF(iMetric, height()), QPointF(iMetric, height() - iMetric));
457 {
458 grad8.setColorAt(0, color1);
459 grad8.setColorAt(.1, color2);
460 grad8.setColorAt(.2, color1);
461 grad8.setColorAt(1, color0);
462 }
463
464 /* Paint: */
465 painter.fillRect(QRect(iMetric, iMetric, width() - iMetric * 2, height() - iMetric * 2), color0);
466
467 if (m_enmPosition == PositionStyle_Left || m_enmPosition == PositionStyle_Single)
468 {
469 painter.fillRect(QRect(0, 0, iMetric, iMetric), grad1);
470 painter.fillRect(QRect(0, height() - iMetric, iMetric, iMetric), grad2);
471 }
472 if (m_enmPosition == PositionStyle_Right || m_enmPosition == PositionStyle_Single)
473 {
474 painter.fillRect(QRect(width() - iMetric, 0, iMetric, iMetric), grad3);
475 painter.fillRect(QRect(width() - iMetric, height() - iMetric, iMetric, iMetric), grad4);
476 }
477
478 int iX = 0;
479 int iYL = 0;
480 int iYR = 0;
481 int iWid = width();
482 int iHeiL = height();
483 int iHeiR = height();
484 if (m_enmPosition == PositionStyle_Left || m_enmPosition == PositionStyle_Single)
485 {
486 iX = iMetric;
487 iYL = iMetric;
488 iWid -= iMetric;
489 iHeiL -= iMetric * 2;
490 }
491 if (m_enmPosition == PositionStyle_Right || m_enmPosition == PositionStyle_Single)
492 {
493 iYR = iMetric;
494 iWid -= iMetric;
495 iHeiR -= iMetric * 2;
496 }
497
498 QPainterPath path5;
499 path5.moveTo(0, 0);
500 path5.lineTo(iMetric, iMetric);
501 path5.lineTo(iMetric, height() - iMetric);
502 path5.lineTo(0, height());
503 path5.closeSubpath();
504 painter.setClipPath(path5);
505 painter.fillRect(QRect(0, iYL, iMetric, iHeiL), grad5);
506 painter.setClipping(false);
507
508 QPainterPath path6;
509 path6.moveTo(width(), 0);
510 path6.lineTo(width() - iMetric, iMetric);
511 path6.lineTo(width() - iMetric, height() - iMetric);
512 path6.lineTo(width(), height());
513 path6.closeSubpath();
514 painter.setClipPath(path6);
515 painter.fillRect(QRect(width() - iMetric, iYR, iMetric, iHeiR), grad6);
516 painter.setClipping(false);
517
518 QPainterPath path7;
519 path7.moveTo(0, 0);
520 path7.lineTo(iMetric, iMetric);
521 path7.lineTo(width() - iMetric, iMetric);
522 path7.lineTo(width(), 0);
523 path7.closeSubpath();
524 painter.setClipPath(path7);
525 painter.fillRect(QRect(iX, 0, iWid, iMetric), grad7);
526 painter.setClipping(false);
527
528 QPainterPath path8;
529 path8.moveTo(0, height());
530 path8.lineTo(iMetric, height() - iMetric);
531 path8.lineTo(width() - iMetric, height() - iMetric);
532 path8.lineTo(width(), height());
533 path8.closeSubpath();
534 painter.setClipPath(path8);
535 painter.fillRect(QRect(iX, height() - iMetric, iWid, iMetric), grad8);
536 painter.setClipping(false);
537
538#endif /* !VBOX_WS_MAC */
539}
540
541void UITabBarItem::mousePressEvent(QMouseEvent *pEvent)
542{
543 /* We are interested in left button only: */
544 if (pEvent->button() != Qt::LeftButton)
545 return QWidget::mousePressEvent(pEvent);
546
547 /* Remember mouse-press position: */
548#ifndef VBOX_IS_QT6_OR_LATER /* QMouseEvent::globalPos was replaced with QSinglePointEvent::globalPosition in Qt6 */
549 m_mousePressPosition = pEvent->globalPos();
550#else
551 m_mousePressPosition = pEvent->globalPosition().toPoint();
552#endif
553}
554
555void UITabBarItem::mouseReleaseEvent(QMouseEvent *pEvent)
556{
557 /* We are interested in left button only: */
558 if (pEvent->button() != Qt::LeftButton)
559 return QWidget::mouseReleaseEvent(pEvent);
560
561 /* Forget mouse-press position: */
562 m_mousePressPosition = QPoint();
563
564 /* Notify listeners about the item was clicked: */
565 emit sigClicked(this);
566}
567
568void UITabBarItem::mouseMoveEvent(QMouseEvent *pEvent)
569{
570 /* Make sure item isn't already dragged: */
571 if (m_mousePressPosition.isNull())
572 return QWidget::mouseMoveEvent(pEvent);
573
574 /* Make sure item is now being dragged: */
575#ifndef VBOX_IS_QT6_OR_LATER /* QMouseEvent::globalPos was replaced with QSinglePointEvent::globalPosition in Qt6 */
576 const QPoint gPos = pEvent->globalPos();
577#else
578 const QPoint gPos = pEvent->globalPosition().toPoint();
579#endif
580 if (QLineF(gPos, m_mousePressPosition).length() < QApplication::startDragDistance())
581 return QWidget::mouseMoveEvent(pEvent);
582
583 /* Revoke hovered state: */
584#ifdef VBOX_WS_MAC
585 m_pLayoutStacked->setCurrentWidget(m_pLabelIcon);
586#endif
587 m_fHovered = false;
588 /* And call for repaint: */
589 update();
590
591 /* Initialize dragging: */
592 m_mousePressPosition = QPoint();
593 QDrag *pDrag = new QDrag(this);
594 connect(pDrag, &QObject::destroyed, this, &UITabBarItem::sigDragObjectDestroy);
595 QMimeData *pMimeData = new QMimeData;
596 pMimeData->setData(MimeType, uuid().toByteArray());
597 pDrag->setMimeData(pMimeData);
598 const int iMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
599#ifndef VBOX_IS_QT6_OR_LATER /* QIcon::pixmap taking QWindow is deprecated in Qt6 */
600 pDrag->setPixmap(m_pAction->icon().pixmap(window()->windowHandle(), QSize(iMetric, iMetric)));
601#else
602 const qreal fDevicePixelRatio = window() && window()->windowHandle() ? window()->windowHandle()->devicePixelRatio() : 1;
603 pDrag->setPixmap(m_pAction->icon().pixmap(QSize(iMetric, iMetric), fDevicePixelRatio));
604#endif
605 pDrag->exec();
606}
607
608#ifdef VBOX_IS_QT6_OR_LATER /* QWidget::enterEvent uses QEnterEvent since qt6 */
609void UITabBarItem::enterEvent(QEnterEvent *pEvent)
610#else
611void UITabBarItem::enterEvent(QEvent *pEvent)
612#endif
613{
614 /* Make sure button isn't hovered: */
615 if (m_fHovered)
616 return QWidget::enterEvent(pEvent);
617
618 /* Invert hovered state: */
619#ifdef VBOX_WS_MAC
620 m_pLayoutStacked->setCurrentWidget(m_pButtonClose);
621#endif
622 m_fHovered = true;
623 /* And call for repaint: */
624 update();
625}
626
627void UITabBarItem::leaveEvent(QEvent *pEvent)
628{
629 /* Make sure button is hovered: */
630 if (!m_fHovered)
631 return QWidget::leaveEvent(pEvent);
632
633 /* Invert hovered state: */
634#ifdef VBOX_WS_MAC
635 m_pLayoutStacked->setCurrentWidget(m_pLabelIcon);
636#endif
637 m_fHovered = false;
638 /* And call for repaint: */
639 update();
640}
641
642void UITabBarItem::prepare()
643{
644 /* Configure self: */
645 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
646
647 /* Create main layout: */
648 m_pLayout = new QHBoxLayout(this);
649 if (m_pLayout)
650 {
651 /* Invent pixel metric: */
652 const int iMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
653#ifdef VBOX_WS_MAC
654 const int iMargin = iMetric / 4;
655#else
656 const int iMargin = iMetric / 2;
657#endif
658 const int iSpacing = iMargin / 2;
659#ifdef VBOX_WS_MAC
660 const int iMetricCloseButton = iMetric * 3 / 4;
661#else
662 const int iMetricCloseButton = iMetric * 2 / 3;
663#endif
664
665 /* Configure layout: */
666#ifdef VBOX_WS_MAC
667 m_pLayout->setContentsMargins(iMargin + iSpacing, iMargin, iMargin + iSpacing, iMargin);
668#else
669 m_pLayout->setContentsMargins(iMargin + iSpacing, iMargin, iMargin, iMargin);
670#endif
671 m_pLayout->setSpacing(iSpacing);
672
673 /* Create icon label: */
674 m_pLabelIcon = new QLabel;
675 if (m_pLabelIcon)
676 {
677 /* Configure label: */
678 m_pLabelIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
679 }
680
681 /* Create name label: */
682 m_pLabelName = new QLabel;
683 if (m_pLabelName)
684 {
685 /* Configure label: */
686 m_pLabelName->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
687 }
688
689 /* Create close button: */
690 m_pButtonClose = new QToolButton;
691 if (m_pButtonClose)
692 {
693 /* Configure button: */
694 m_pButtonClose->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
695 m_pButtonClose->setIconSize(QSize(iMetricCloseButton, iMetricCloseButton));
696 m_pButtonClose->setIcon(UIIconPool::iconSet(":/close_16px.png"));
697#ifdef VBOX_WS_MAC
698 m_pButtonClose->setStyleSheet("QToolButton { border: 0px }");
699#else
700 m_pButtonClose->setAutoRaise(true);
701#endif
702 connect(m_pButtonClose, &QToolButton::clicked, this, &UITabBarItem::sltCloseClicked);
703 }
704
705#ifdef VBOX_WS_MAC
706 /* Create stacked-layout: */
707 m_pLayoutStacked = new QStackedLayout(m_pLayout);
708 if (m_pLayoutStacked)
709 {
710 m_pLayoutStacked->setAlignment(Qt::AlignCenter);
711
712 /* Add icon-label and close-button into stacked-layout: */
713 m_pLayoutStacked->addWidget(m_pLabelIcon);
714 m_pLayoutStacked->addWidget(m_pButtonClose);
715 m_pLayoutStacked->setAlignment(m_pLabelIcon, Qt::AlignCenter);
716 m_pLayoutStacked->setAlignment(m_pButtonClose, Qt::AlignCenter);
717
718 /* Add stacked-layout into main-layout: */
719 m_pLayout->addLayout(m_pLayoutStacked);
720 }
721
722 /* Add name-label into main-layout: */
723 m_pLayout->addWidget(m_pLabelName);
724#else /* !VBOX_WS_MAC */
725 /* Add everything into main-layout: */
726 m_pLayout->addWidget(m_pLabelIcon);
727 m_pLayout->addWidget(m_pLabelName);
728 m_pLayout->addWidget(m_pButtonClose);
729#endif /* !VBOX_WS_MAC */
730 }
731
732 /* Update pixmap: */
733 updatePixmap();
734
735 /* Apply language settings: */
736 retranslateUi();
737}
738
739void UITabBarItem::updatePixmap()
740{
741 /* Configure label icon: */
742 const int iMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
743#ifndef VBOX_IS_QT6_OR_LATER /* QIcon::pixmap taking QWindow is deprecated in Qt6 */
744 m_pLabelIcon->setPixmap(m_pAction->icon().pixmap(window()->windowHandle(), QSize(iMetric, iMetric)));
745#else
746 const qreal fDevicePixelRatio = window() && window()->windowHandle() ? window()->windowHandle()->devicePixelRatio() : 1;
747 m_pLabelIcon->setPixmap(m_pAction->icon().pixmap(QSize(iMetric, iMetric), fDevicePixelRatio));
748#endif
749}
750
751
752/*********************************************************************************************************************************
753* Class UITabBar implementation. *
754*********************************************************************************************************************************/
755
756UITabBar::UITabBar(Alignment enmAlignment, QWidget *pParent /* = 0 */)
757 : QWidget(pParent)
758 , m_enmAlignment(enmAlignment)
759 , m_pLayoutMain(0)
760 , m_pLayoutTab(0)
761 , m_pCurrentItem(0)
762 , m_pItemToken(0)
763 , m_fDropAfterTokenItem(0)
764{
765 /* Prepare: */
766 prepare();
767}
768
769QUuid UITabBar::addTab(const QAction *pAction)
770{
771 /* Generate unique ID: */
772 const QUuid uuid = QUuid::createUuid();
773 /* Create new tab item: */
774 UITabBarItem *pItem = new UITabBarItem(uuid, pAction);
775 AssertPtrReturn(pItem, QUuid());
776 {
777 /* Configure item: */
778 connect(pItem, &UITabBarItem::sigClicked, this, &UITabBar::sltHandleMakeChildCurrent);
779 connect(pItem, &UITabBarItem::sigCloseClicked, this, &UITabBar::sltHandleChildClose);
780 connect(pItem, &UITabBarItem::sigDragObjectDestroy, this, &UITabBar::sltHandleDragObjectDestroy);
781 /* Add item into layout and list: */
782 switch (m_enmAlignment)
783 {
784 case Align_Left:
785 m_pLayoutTab->addWidget(pItem);
786 m_aItems.append(pItem);
787 break;
788 case Align_Right:
789 m_pLayoutTab->insertWidget(0, pItem);
790 m_aItems.prepend(pItem);
791 break;
792 }
793 /* Update children styles: */
794 updateChildrenStyles();
795 /* Return unique ID: */
796 return uuid;
797 }
798}
799
800bool UITabBar::removeTab(const QUuid &uuid)
801{
802 /* Prepare result: */
803 bool fSuccess = false;
804
805 /* Do we need to bother about current item? */
806 bool fMoveCurrent = m_pCurrentItem->uuid() == uuid;
807
808 /* Search through all the items we have: */
809 for (int i = 0; i < m_aItems.size(); ++i)
810 {
811 /* Get iterated item: */
812 UITabBarItem *pItem = m_aItems.at(i);
813 /* If that item is what we are looking for: */
814 if (pItem->uuid() == uuid)
815 {
816 /* Delete it and wipe it from the list: */
817 delete pItem;
818 m_aItems[i] = 0;
819 fSuccess = true;
820 }
821 }
822 /* Flush wiped out items: */
823 m_aItems.removeAll((UITabBarItem *)0);
824
825 /* If we had removed current item: */
826 if (fMoveCurrent)
827 {
828 /* Mark it null initially: */
829 m_pCurrentItem = 0;
830 /* But choose something suitable if we have: */
831 if (!m_aItems.isEmpty())
832 sltHandleMakeChildCurrent(m_aItems.first());
833 }
834
835 /* Update children styles: */
836 updateChildrenStyles();
837
838 /* Return result: */
839 return fSuccess;
840}
841
842bool UITabBar::setCurrent(const QUuid &uuid)
843{
844 /* Prepare result: */
845 bool fSuccess = false;
846
847 /* Search through all the items we have: */
848 for (int i = 0; i < m_aItems.size(); ++i)
849 {
850 /* Get iterated item: */
851 UITabBarItem *pItem = m_aItems.at(i);
852 /* If that item is what we are looking for: */
853 if (pItem->uuid() == uuid)
854 {
855 /* Make it current: */
856 sltHandleMakeChildCurrent(pItem);
857 fSuccess = true;
858 break;
859 }
860 }
861
862 /* Return result: */
863 return fSuccess;
864}
865
866QList<QUuid> UITabBar::tabOrder() const
867{
868 QList<QUuid> list;
869 foreach (UITabBarItem *pItem, m_aItems)
870 list << pItem->uuid();
871 return list;
872}
873
874void UITabBar::paintEvent(QPaintEvent *pEvent)
875{
876 /* Call to base-class: */
877 QWidget::paintEvent(pEvent);
878
879 /* If we have a token item: */
880 if (m_pItemToken)
881 {
882 /* Prepare painter: */
883 QPainter painter(this);
884
885 /* Paint drop token: */
886 QStyleOption option;
887 option.state |= QStyle::State_Horizontal;
888 const QRect geo = m_pItemToken->geometry();
889 option.rect = !m_fDropAfterTokenItem
890 ? QRect(geo.topLeft() - QPoint(5, 5),
891 geo.bottomLeft() + QPoint(0, 5))
892 : QRect(geo.topRight() - QPoint(0, 5),
893 geo.bottomRight() + QPoint(5, 5));
894 QApplication::style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator,
895 &option, &painter);
896 }
897}
898
899void UITabBar::dragEnterEvent(QDragEnterEvent *pEvent)
900{
901 /* Make sure event is valid: */
902 AssertPtrReturnVoid(pEvent);
903 /* And mime-data is set: */
904 const QMimeData *pMimeData = pEvent->mimeData();
905 AssertPtrReturnVoid(pMimeData);
906
907 /* Make sure mime-data format is valid: */
908 if (!pMimeData->hasFormat(UITabBarItem::MimeType))
909 return;
910
911 /* Accept drag-enter event: */
912 pEvent->acceptProposedAction();
913}
914
915void UITabBar::dragMoveEvent(QDragMoveEvent *pEvent)
916{
917 /* Make sure event is valid: */
918 AssertPtrReturnVoid(pEvent);
919#ifndef VBOX_IS_QT6_OR_LATER /* QMouseEvent::pos was replaced with QSinglePointEvent::position in Qt6 */
920 const QPoint lPos = pEvent->pos();
921#else
922 const QPoint lPos = pEvent->position().toPoint();
923#endif
924
925 /* And mime-data is set: */
926 const QMimeData *pMimeData = pEvent->mimeData();
927 AssertPtrReturnVoid(pMimeData);
928
929 /* Make sure mime-data format is valid: */
930 if (!pMimeData->hasFormat(UITabBarItem::MimeType))
931 return;
932
933 /* Reset token: */
934 m_pItemToken = 0;
935 m_fDropAfterTokenItem = true;
936
937 /* Search for most suitable item: */
938 foreach (UITabBarItem *pItem, m_aItems)
939 {
940 /* Advance token: */
941 m_pItemToken = pItem;
942 const QRect geo = m_pItemToken->geometry();
943 if (lPos.x() < geo.center().x())
944 {
945 m_fDropAfterTokenItem = false;
946 break;
947 }
948 }
949
950 /* Update: */
951 update();
952}
953
954void UITabBar::dragLeaveEvent(QDragLeaveEvent * /* pEvent */)
955{
956 /* Reset token: */
957 m_pItemToken = 0;
958 m_fDropAfterTokenItem = true;
959
960 /* Update: */
961 update();
962}
963
964void UITabBar::dropEvent(QDropEvent *pEvent)
965{
966 /* Make sure event is valid: */
967 AssertPtrReturnVoid(pEvent);
968 /* And mime-data is set: */
969 const QMimeData *pMimeData = pEvent->mimeData();
970 AssertPtrReturnVoid(pMimeData);
971
972 /* Make sure mime-data format is valid: */
973 if (!pMimeData->hasFormat(UITabBarItem::MimeType))
974 return;
975
976 /* Make sure token-item set: */
977 if (!m_pItemToken)
978 return;
979
980 /* Determine ID of token-item: */
981 const QUuid tokenUuid = m_pItemToken->uuid();
982 /* Determine ID of dropped-item: */
983 const QUuid droppedUuid(pMimeData->data(UITabBarItem::MimeType));
984
985 /* Make sure these uuids are different: */
986 if (droppedUuid == tokenUuid)
987 return;
988
989 /* Search for an item with dropped ID: */
990 UITabBarItem *pItemDropped = 0;
991 foreach (UITabBarItem *pItem, m_aItems)
992 {
993 if (pItem->uuid() == droppedUuid)
994 {
995 pItemDropped = pItem;
996 break;
997 }
998 }
999
1000 /* Make sure dropped-item found: */
1001 if (!pItemDropped)
1002 return;
1003
1004 /* Remove dropped-item: */
1005 m_aItems.removeAll(pItemDropped);
1006 m_pLayoutTab->removeWidget(pItemDropped);
1007 /* Insert dropped-item into position of token-item: */
1008 int iPosition = m_aItems.indexOf(m_pItemToken);
1009 AssertReturnVoid(iPosition != -1);
1010 if (m_fDropAfterTokenItem)
1011 ++iPosition;
1012 m_aItems.insert(iPosition, pItemDropped);
1013 m_pLayoutTab->insertWidget(iPosition, pItemDropped);
1014
1015 /* Update children styles: */
1016 updateChildrenStyles();
1017}
1018
1019void UITabBar::sltHandleMakeChildCurrent(UITabBarItem *pItem)
1020{
1021 /* Make sure item exists: */
1022 AssertPtrReturnVoid(pItem);
1023
1024 /* Remove current mark from current item if exists: */
1025 if (m_pCurrentItem)
1026 m_pCurrentItem->setCurrent(false);
1027
1028 /* Assign new current item: */
1029 m_pCurrentItem = pItem;
1030
1031 /* Place current mark onto current item if exists: */
1032 if (m_pCurrentItem)
1033 m_pCurrentItem->setCurrent(true);
1034
1035 /* Notify listeners: */
1036 emit sigCurrentTabChanged(pItem->uuid());
1037}
1038
1039void UITabBar::sltHandleChildClose(UITabBarItem *pItem)
1040{
1041 /* Make sure item exists: */
1042 AssertPtrReturnVoid(pItem);
1043
1044 /* Notify listeners: */
1045 emit sigTabRequestForClosing(pItem->uuid());
1046}
1047
1048void UITabBar::sltHandleDragObjectDestroy()
1049{
1050 /* Reset token: */
1051 m_pItemToken = 0;
1052 m_fDropAfterTokenItem = true;
1053
1054 /* Update: */
1055 update();
1056}
1057
1058void UITabBar::prepare()
1059{
1060 /* Track D&D events: */
1061 setAcceptDrops(true);
1062
1063 /* Create main layout: */
1064 m_pLayoutMain = new QHBoxLayout(this);
1065 AssertPtrReturnVoid(m_pLayoutMain);
1066 {
1067 /* Configure layout: */
1068 m_pLayoutMain->setSpacing(0);
1069 m_pLayoutMain->setContentsMargins(0, 0, 0, 0);
1070
1071 /* Add strech to beginning: */
1072 if (m_enmAlignment == Align_Right)
1073 m_pLayoutMain->addStretch();
1074
1075 /* Create tab layout: */
1076 m_pLayoutTab = new QHBoxLayout;
1077 AssertPtrReturnVoid(m_pLayoutTab);
1078 {
1079 /* Add into layout: */
1080 m_pLayoutMain->addLayout(m_pLayoutTab);
1081 }
1082
1083 /* Add strech to end: */
1084 if (m_enmAlignment == Align_Left)
1085 m_pLayoutMain->addStretch();
1086 }
1087}
1088
1089void UITabBar::updateChildrenStyles()
1090{
1091 /* Single child has corresponding (rounded) style: */
1092 if (m_aItems.size() == 1)
1093 m_aItems.first()->setPositionStyle(UITabBarItem::PositionStyle_Single);
1094 /* If there are more than one child: */
1095 else if (m_aItems.size() > 1)
1096 {
1097 /* First make all children have no rounded sides: */
1098 foreach (UITabBarItem *pItem, m_aItems)
1099 pItem->setPositionStyle(UITabBarItem::PositionStyle_Middle);
1100 /* Then make first child rounded left, while last rounded right: */
1101 m_aItems.first()->setPositionStyle(UITabBarItem::PositionStyle_Left);
1102 m_aItems.last()->setPositionStyle(UITabBarItem::PositionStyle_Right);
1103 }
1104 /* Repaint: */
1105 update();
1106}
1107
1108#include "UITabBar.moc"
1109
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette