VirtualBox

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

Last change on this file was 104358, checked in by vboxsync, 4 weeks ago

FE/Qt. bugref:10622. More refactoring around the retranslation functionality.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use