VirtualBox

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

Last change on this file since 101562 was 101562, checked in by vboxsync, 7 months ago

FE/Qt: bugref:10450: Get rid of Qt5 stuff; This one is about touch/enter events related stuff.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use