VirtualBox

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

Last change on this file since 76553 was 76553, checked in by vboxsync, 5 years ago

scm --update-copyright-year

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

© 2023 Oracle
ContactPrivacy policyTerms of Use