VirtualBox

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

Last change on this file since 100347 was 100064, checked in by vboxsync, 16 months ago

FE/Qt: bugref:10421. Replacing VBOX_WS_X11 with VBOX_WS_NIX.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 38.4 KB
Line 
1/* $Id: UIMiniToolBar.cpp 100064 2023-06-04 09:10:01Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIMiniToolBar class implementation.
4 */
5
6/*
7 * Copyright (C) 2009-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 <QApplication>
30#include <QLabel>
31#include <QMenu>
32#include <QMoveEvent>
33#include <QPainter>
34#include <QPainterPath>
35#include <QPainterPathStroker>
36#include <QStateMachine>
37#include <QStyle>
38#include <QTimer>
39#include <QToolButton>
40#include <QVBoxLayout>
41#include <QWindow>
42#ifdef VBOX_WS_NIX
43# include <QWindowStateChangeEvent>
44#endif
45
46/* GUI includes: */
47#include "UIMiniToolBar.h"
48#include "UIAnimationFramework.h"
49#include "UIIconPool.h"
50#include "UIDesktopWidgetWatchdog.h"
51#include "UICommon.h"
52#ifdef VBOX_WS_NIX
53# include "UIExtraDataManager.h"
54#endif
55
56
57/** QIToolBar reimplementation
58 * providing UIMiniToolBar with mini-toolbar. */
59class UIMiniToolBarPrivate : public QIToolBar
60{
61 Q_OBJECT;
62
63signals:
64
65 /** Notifies listeners about we are resized. */
66 void sigResized();
67
68 /** Notifies listeners about action triggered to toggle auto-hide. */
69 void sigAutoHideToggled();
70 /** Notifies listeners about action triggered to minimize. */
71 void sigMinimizeAction();
72 /** Notifies listeners about action triggered to exit. */
73 void sigExitAction();
74 /** Notifies listeners about action triggered to close. */
75 void sigCloseAction();
76
77public:
78
79 /** Constructor. */
80 UIMiniToolBarPrivate();
81
82 /** Defines @a alignment. */
83 void setAlignment(Qt::Alignment alignment);
84
85 /** Returns whether we do auto-hide. */
86 bool autoHide() const;
87 /** Defines whether we do @a fAutoHide. */
88 void setAutoHide(bool fAutoHide);
89
90 /** Defines our @a strText. */
91 void setText(const QString &strText);
92
93 /** Adds our @a menus. */
94 void addMenus(const QList<QMenu*> &menus);
95
96protected:
97
98 /** Show @a pEvent handler. */
99 virtual void showEvent(QShowEvent *pEvent);
100 /** Polish @a pEvent handler. */
101 virtual void polishEvent(QShowEvent *pEvent);
102 /** Resize @a pEvent handler. */
103 virtual void resizeEvent(QResizeEvent *pEvent);
104 /** Paint @a pEvent handler. */
105 virtual void paintEvent(QPaintEvent *pEvent);
106
107private:
108
109 /** Prepare routine. */
110 void prepare();
111
112 /** Rebuilds our shape. */
113 void rebuildShape();
114
115 /** Holds whether this widget was polished. */
116 bool m_fPolished;
117 /** Holds the alignment type. */
118 Qt::Alignment m_alignment;
119 /** Holds the shape. */
120 QPainterPath m_shape;
121
122 /** Holds the action to toggle auto-hide. */
123 QAction *m_pAutoHideAction;
124 /** Holds the name label. */
125 QLabel *m_pLabel;
126 /** Holds the action to trigger minimize. */
127 QAction *m_pMinimizeAction;
128 /** Holds the action to trigger exit. */
129 QAction *m_pRestoreAction;
130 /** Holds the action to trigger close. */
131 QAction *m_pCloseAction;
132
133 /** Holds the pointer to the place to insert menu. */
134 QAction *m_pMenuInsertPosition;
135
136 /** Holds the spacings. */
137 QList<QWidget*> m_spacings;
138 /** Holds the margins. */
139 QList<QWidget*> m_margins;
140};
141
142
143/*********************************************************************************************************************************
144* Class UIMiniToolBarPrivate implementation. *
145*********************************************************************************************************************************/
146
147UIMiniToolBarPrivate::UIMiniToolBarPrivate()
148 /* Variables: General stuff: */
149 : m_fPolished(false)
150 , m_alignment(Qt::AlignBottom)
151 /* Variables: Contents stuff: */
152 , m_pAutoHideAction(0)
153 , m_pLabel(0)
154 , m_pMinimizeAction(0)
155 , m_pRestoreAction(0)
156 , m_pCloseAction(0)
157 /* Variables: Menu stuff: */
158 , m_pMenuInsertPosition(0)
159{
160 /* Prepare: */
161 prepare();
162}
163
164void UIMiniToolBarPrivate::setAlignment(Qt::Alignment alignment)
165{
166 /* Make sure alignment really changed: */
167 if (m_alignment == alignment)
168 return;
169
170 /* Update alignment: */
171 m_alignment = alignment;
172
173 /* Rebuild shape: */
174 rebuildShape();
175}
176
177bool UIMiniToolBarPrivate::autoHide() const
178{
179 /* Return auto-hide: */
180 return !m_pAutoHideAction->isChecked();
181}
182
183void UIMiniToolBarPrivate::setAutoHide(bool fAutoHide)
184{
185 /* Make sure auto-hide really changed: */
186 if (m_pAutoHideAction->isChecked() == !fAutoHide)
187 return;
188
189 /* Update auto-hide: */
190 m_pAutoHideAction->setChecked(!fAutoHide);
191}
192
193void UIMiniToolBarPrivate::setText(const QString &strText)
194{
195 /* Make sure text really changed: */
196 if (m_pLabel->text() == strText)
197 return;
198
199 /* Update text: */
200 m_pLabel->setText(strText);
201
202 /* Resize to sizehint: */
203 resize(sizeHint());
204}
205
206void UIMiniToolBarPrivate::addMenus(const QList<QMenu*> &menus)
207{
208 /* For each of the passed menu items: */
209 for (int i = 0; i < menus.size(); ++i)
210 {
211 /* Get corresponding menu-action: */
212 QAction *pAction = menus[i]->menuAction();
213 /* Insert it into corresponding place: */
214 insertAction(m_pMenuInsertPosition, pAction);
215 /* Configure corresponding tool-button: */
216 if (QToolButton *pButton = qobject_cast<QToolButton*>(widgetForAction(pAction)))
217 {
218 pButton->setPopupMode(QToolButton::InstantPopup);
219 pButton->setAutoRaise(true);
220 }
221 /* Add some spacing: */
222 if (i != menus.size() - 1)
223 m_spacings << widgetForAction(insertWidget(m_pMenuInsertPosition, new QWidget(this)));
224 }
225
226 /* Resize to sizehint: */
227 resize(sizeHint());
228}
229
230void UIMiniToolBarPrivate::showEvent(QShowEvent *pEvent)
231{
232 /* Make sure we should polish dialog: */
233 if (m_fPolished)
234 return;
235
236 /* Call to polish-event: */
237 polishEvent(pEvent);
238
239 /* Mark dialog as polished: */
240 m_fPolished = true;
241}
242
243void UIMiniToolBarPrivate::polishEvent(QShowEvent*)
244{
245 /* Toolbar spacings: */
246 foreach(QWidget *pSpacing, m_spacings)
247 pSpacing->setMinimumWidth(5);
248
249 /* Title spacings: */
250 foreach(QWidget *pLableMargin, m_margins)
251 pLableMargin->setMinimumWidth(15);
252
253 /* Resize to sizehint: */
254 resize(sizeHint());
255}
256
257void UIMiniToolBarPrivate::resizeEvent(QResizeEvent*)
258{
259 /* Rebuild shape: */
260 rebuildShape();
261
262 /* Notify listeners: */
263 emit sigResized();
264}
265
266void UIMiniToolBarPrivate::paintEvent(QPaintEvent*)
267{
268 /* Prepare painter: */
269 QPainter painter(this);
270
271 /* Fill background: */
272 if (!m_shape.isEmpty())
273 {
274 painter.setRenderHint(QPainter::Antialiasing);
275 painter.setClipPath(m_shape);
276 }
277 QRect backgroundRect = rect();
278 QColor backgroundColor = QApplication::palette().color(QPalette::Window);
279 QLinearGradient headerGradient(backgroundRect.bottomLeft(), backgroundRect.topLeft());
280 headerGradient.setColorAt(0, backgroundColor.darker(120));
281 headerGradient.setColorAt(1, backgroundColor.darker(90));
282 painter.fillRect(backgroundRect, headerGradient);
283}
284
285void UIMiniToolBarPrivate::prepare()
286{
287 /* Determine icon metric: */
288 const QStyle *pStyle = QApplication::style();
289 const int iIconMetric = pStyle->pixelMetric(QStyle::PM_SmallIconSize);
290
291 /* Configure toolbar: */
292 setIconSize(QSize(iIconMetric, iIconMetric));
293
294 /* Left margin: */
295#ifdef VBOX_WS_NIX
296 if (uiCommon().isCompositingManagerRunning())
297 m_spacings << widgetForAction(addWidget(new QWidget));
298#else /* !VBOX_WS_NIX */
299 m_spacings << widgetForAction(addWidget(new QWidget));
300#endif /* !VBOX_WS_NIX */
301
302 /* Prepare push-pin: */
303 m_pAutoHideAction = new QAction(this);
304 m_pAutoHideAction->setIcon(UIIconPool::iconSet(":/pin_16px.png"));
305 m_pAutoHideAction->setToolTip(UIMiniToolBar::tr("Always show the toolbar"));
306 m_pAutoHideAction->setCheckable(true);
307 connect(m_pAutoHideAction, SIGNAL(toggled(bool)), this, SIGNAL(sigAutoHideToggled()));
308 addAction(m_pAutoHideAction);
309
310 /* Left menu margin: */
311 m_spacings << widgetForAction(addWidget(new QWidget));
312
313 /* Right menu margin: */
314 m_pMenuInsertPosition = addWidget(new QWidget);
315 m_spacings << widgetForAction(m_pMenuInsertPosition);
316
317 /* Left label margin: */
318 m_margins << widgetForAction(addWidget(new QWidget));
319
320 /* Insert a label for VM Name: */
321 m_pLabel = new QLabel;
322 m_pLabel->setAlignment(Qt::AlignCenter);
323 addWidget(m_pLabel);
324
325 /* Right label margin: */
326 m_margins << widgetForAction(addWidget(new QWidget));
327
328 /* Minimize action: */
329 m_pMinimizeAction = new QAction(this);
330 m_pMinimizeAction->setIcon(UIIconPool::iconSet(":/minimize_16px.png"));
331 m_pMinimizeAction->setToolTip(UIMiniToolBar::tr("Minimize Window"));
332 connect(m_pMinimizeAction, SIGNAL(triggered()), this, SIGNAL(sigMinimizeAction()));
333 addAction(m_pMinimizeAction);
334
335 /* Exit action: */
336 m_pRestoreAction = new QAction(this);
337 m_pRestoreAction->setIcon(UIIconPool::iconSet(":/restore_16px.png"));
338 m_pRestoreAction->setToolTip(UIMiniToolBar::tr("Exit Full Screen or Seamless Mode"));
339 connect(m_pRestoreAction, SIGNAL(triggered()), this, SIGNAL(sigExitAction()));
340 addAction(m_pRestoreAction);
341
342 /* Close action: */
343 m_pCloseAction = new QAction(this);
344 m_pCloseAction->setIcon(UIIconPool::iconSet(":/close_16px.png"));
345 m_pCloseAction->setToolTip(UIMiniToolBar::tr("Close VM"));
346 connect(m_pCloseAction, SIGNAL(triggered()), this, SIGNAL(sigCloseAction()));
347 addAction(m_pCloseAction);
348
349 /* Right margin: */
350#ifdef VBOX_WS_NIX
351 if (uiCommon().isCompositingManagerRunning())
352 m_spacings << widgetForAction(addWidget(new QWidget));
353#else /* !VBOX_WS_NIX */
354 m_spacings << widgetForAction(addWidget(new QWidget));
355#endif /* !VBOX_WS_NIX */
356}
357
358void UIMiniToolBarPrivate::rebuildShape()
359{
360#ifdef VBOX_WS_NIX
361 if (!uiCommon().isCompositingManagerRunning())
362 return;
363#endif /* VBOX_WS_NIX */
364
365 /* Rebuild shape: */
366 QPainterPath shape;
367 switch (m_alignment)
368 {
369 case Qt::AlignTop:
370 {
371 shape.moveTo(0, 0);
372 shape.lineTo(shape.currentPosition().x(), height() - 10);
373 shape.arcTo(QRectF(shape.currentPosition(), QSizeF(20, 20)).translated(0, -10), 180, 90);
374 shape.lineTo(width() - 10, shape.currentPosition().y());
375 shape.arcTo(QRectF(shape.currentPosition(), QSizeF(20, 20)).translated(-10, -20), 270, 90);
376 shape.lineTo(shape.currentPosition().x(), 0);
377 shape.closeSubpath();
378 break;
379 }
380 case Qt::AlignBottom:
381 {
382 shape.moveTo(0, height());
383 shape.lineTo(shape.currentPosition().x(), 10);
384 shape.arcTo(QRectF(shape.currentPosition(), QSizeF(20, 20)).translated(0, -10), 180, -90);
385 shape.lineTo(width() - 10, shape.currentPosition().y());
386 shape.arcTo(QRectF(shape.currentPosition(), QSizeF(20, 20)).translated(-10, 0), 90, -90);
387 shape.lineTo(shape.currentPosition().x(), height());
388 shape.closeSubpath();
389 break;
390 }
391 default:
392 break;
393 }
394 m_shape = shape;
395
396 /* Update: */
397 update();
398}
399
400
401/*********************************************************************************************************************************
402* Class UIMiniToolBar implementation. *
403*********************************************************************************************************************************/
404
405/* static */
406Qt::WindowFlags UIMiniToolBar::defaultWindowFlags(GeometryType geometryType)
407{
408 /* Not everywhere: */
409 Q_UNUSED(geometryType);
410
411#ifdef VBOX_WS_NIX
412 /* Depending on current WM: */
413 switch (uiCommon().typeOfWindowManager())
414 {
415 // WORKAROUND:
416 // By strange reason, frameless full-screen windows under certain WMs
417 // do not respect the transient relationship between each other.
418 // By nor less strange reason, frameless full-screen *tool* windows
419 // respects such relationship, so we are doing what WM want.
420 case X11WMType_GNOMEShell:
421 case X11WMType_KWin:
422 case X11WMType_Metacity:
423 case X11WMType_Mutter:
424 case X11WMType_Xfwm4:
425 return geometryType == GeometryType_Full ?
426 Qt::Tool | Qt::FramelessWindowHint :
427 Qt::Window | Qt::FramelessWindowHint;
428 default: break;
429 }
430#endif /* VBOX_WS_NIX */
431
432 /* Frameless window by default: */
433 return Qt::Window | Qt::FramelessWindowHint;
434}
435
436UIMiniToolBar::UIMiniToolBar(QWidget *pParent,
437 GeometryType geometryType,
438 Qt::Alignment alignment,
439 bool fAutoHide /* = true */,
440 int iWindowIndex /* = -1 */)
441 : QWidget(0, defaultWindowFlags(geometryType))
442 /* Variables: General stuff: */
443 , m_pParent(pParent)
444 , m_geometryType(geometryType)
445 , m_alignment(alignment)
446 , m_fAutoHide(fAutoHide)
447 , m_iWindowIndex(iWindowIndex)
448 /* Variables: Contents stuff: */
449 , m_pArea(0)
450 , m_pToolbar(0)
451 /* Variables: Hover stuff: */
452 , m_fHovered(false)
453 , m_pHoverEnterTimer(0)
454 , m_pHoverLeaveTimer(0)
455 , m_pAnimation(0)
456#ifdef VBOX_WS_NIX
457 , m_fIsParentMinimized(false)
458#endif
459{
460 /* Prepare: */
461 prepare();
462}
463
464UIMiniToolBar::~UIMiniToolBar()
465{
466 /* Cleanup: */
467 cleanup();
468}
469
470void UIMiniToolBar::setAlignment(Qt::Alignment alignment)
471{
472 /* Make sure toolbar created: */
473 AssertPtrReturnVoid(m_pToolbar);
474
475 /* Make sure alignment really changed: */
476 if (m_alignment == alignment)
477 return;
478
479 /* Update alignment: */
480 m_alignment = alignment;
481
482 /* Adjust geometry: */
483 adjustGeometry();
484
485 /* Propagate to child to update shape: */
486 m_pToolbar->setAlignment(m_alignment);
487}
488
489void UIMiniToolBar::setAutoHide(bool fAutoHide, bool fPropagateToChild /* = true */)
490{
491 /* Make sure toolbar created: */
492 AssertPtrReturnVoid(m_pToolbar);
493
494 /* Make sure auto-hide really changed: */
495 if (m_fAutoHide == fAutoHide)
496 return;
497
498 /* Update auto-hide: */
499 m_fAutoHide = fAutoHide;
500
501 /* Adjust geometry: */
502 adjustGeometry();
503
504 /* Propagate to child to update action if necessary: */
505 if (fPropagateToChild)
506 m_pToolbar->setAutoHide(m_fAutoHide);
507}
508
509void UIMiniToolBar::setText(const QString &strText)
510{
511 /* Make sure toolbar created: */
512 AssertPtrReturnVoid(m_pToolbar);
513
514 /* Propagate to child: */
515 m_pToolbar->setText(strText);
516}
517
518void UIMiniToolBar::addMenus(const QList<QMenu*> &menus)
519{
520 /* Make sure toolbar created: */
521 AssertPtrReturnVoid(m_pToolbar);
522
523 /* Propagate to child: */
524 m_pToolbar->addMenus(menus);
525}
526
527void UIMiniToolBar::adjustGeometry()
528{
529 /* Resize toolbar to minimum size: */
530 m_pToolbar->resize(m_pToolbar->sizeHint());
531
532 /* Calculate toolbar position: */
533 int iX = 0, iY = 0;
534 iX = width() / 2 - m_pToolbar->width() / 2;
535 switch (m_alignment)
536 {
537 case Qt::AlignTop: iY = 0; break;
538 case Qt::AlignBottom: iY = height() - m_pToolbar->height(); break;
539 default: break;
540 }
541
542 /* Update auto-hide animation: */
543 m_shownToolbarPosition = QPoint(iX, iY);
544 switch (m_alignment)
545 {
546 case Qt::AlignTop: m_hiddenToolbarPosition = m_shownToolbarPosition - QPoint(0, m_pToolbar->height() - 3); break;
547 case Qt::AlignBottom: m_hiddenToolbarPosition = m_shownToolbarPosition + QPoint(0, m_pToolbar->height() - 3); break;
548 }
549 m_pAnimation->update();
550
551 /* Update toolbar geometry if known: */
552 if (property("AnimationState").toString() == "Final")
553 m_pToolbar->move(m_shownToolbarPosition);
554 else
555 m_pToolbar->move(m_hiddenToolbarPosition);
556
557#if defined(VBOX_WS_WIN) || defined(VBOX_WS_NIX)
558 /* Adjust window mask: */
559 setMask(m_pToolbar->geometry());
560#endif /* VBOX_WS_WIN || VBOX_WS_NIX */
561}
562
563bool UIMiniToolBar::eventFilter(QObject *pWatched, QEvent *pEvent)
564{
565 /* Detect if we have window activation stolen: */
566 if (pWatched == this && pEvent->type() == QEvent::WindowActivate)
567 {
568#if defined(VBOX_WS_WIN)
569 /* Just call the method asynchronously, after possible popups opened: */
570 QTimer::singleShot(0, this, SLOT(sltCheckWindowActivationSanity()));
571#elif defined(VBOX_WS_NIX)
572 // WORKAROUND:
573 // Under certain WMs we can receive stolen activation event too early,
574 // returning activation to initial source immediately makes no sense.
575 // In fact, Qt is not become aware of actual window activation later,
576 // so we are going to check for window activation in let's say 100ms.
577 QTimer::singleShot(100, this, SLOT(sltCheckWindowActivationSanity()));
578#endif /* VBOX_WS_NIX */
579 }
580
581 /* If that's parent window event: */
582 if (pWatched == m_pParent)
583 {
584 switch (pEvent->type())
585 {
586 case QEvent::Hide:
587 {
588 /* Skip if parent or we are minimized: */
589 if ( isParentMinimized()
590 || isMinimized())
591 break;
592
593 /* Asynchronously call for sltHide(): */
594 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent hide event\n"));
595 QMetaObject::invokeMethod(this, "sltHide", Qt::QueuedConnection);
596 break;
597 }
598 case QEvent::Show:
599 {
600 /* Skip if parent or we are minimized: */
601 if ( isParentMinimized()
602 || isMinimized())
603 break;
604
605 /* Asynchronously call for sltShow(): */
606 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent show event\n"));
607 QMetaObject::invokeMethod(this, "sltShow", Qt::QueuedConnection);
608 break;
609 }
610 case QEvent::Move:
611 {
612 // WORKAROUND:
613 // In certain cases there can be that parent is moving outside of
614 // full-screen geometry. That for example can happen if virtual
615 // desktop being changed. We should ignore Move event in such case.
616 /* Skip if parent is outside of full-screen geometry: */
617 QMoveEvent *pMoveEvent = static_cast<QMoveEvent*>(pEvent);
618 if (!gpDesktop->screenGeometry(m_pParent).contains(pMoveEvent->pos()))
619 break;
620 /* Skip if parent or we are invisible: */
621 if ( !m_pParent->isVisible()
622 || !isVisible())
623 break;
624 /* Skip if parent or we are minimized: */
625 if ( isParentMinimized()
626 || isMinimized())
627 break;
628
629 /* Asynchronously call for sltShow(): */
630 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent move event\n"));
631 QMetaObject::invokeMethod(this, "sltShow", Qt::QueuedConnection);
632 break;
633 }
634 case QEvent::Resize:
635 {
636 /* Skip if parent or we are invisible: */
637 if ( !m_pParent->isVisible()
638 || !isVisible())
639 break;
640 /* Skip if parent or we are minimized: */
641 if ( isParentMinimized()
642 || isMinimized())
643 break;
644
645 /* Asynchronously call for sltShow(): */
646 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent resize event\n"));
647 QMetaObject::invokeMethod(this, "sltShow", Qt::QueuedConnection);
648 break;
649 }
650#ifdef VBOX_WS_NIX
651 case QEvent::WindowStateChange:
652 {
653 /* Watch for parent window state changes: */
654 QWindowStateChangeEvent *pChangeEvent = static_cast<QWindowStateChangeEvent*>(pEvent);
655 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent window state changed from %d to %d\n",
656 (int)pChangeEvent->oldState(), (int)m_pParent->windowState()));
657
658 if ( m_pParent->windowState() & Qt::WindowMinimized
659 && !m_fIsParentMinimized)
660 {
661 /* Mark parent window minimized, isMinimized() is not enough due to Qt5vsX11 fight: */
662 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent window is minimized\n"));
663 m_fIsParentMinimized = true;
664 }
665 else
666 if (m_fIsParentMinimized)
667 {
668 switch (m_geometryType)
669 {
670 case GeometryType_Available:
671 {
672 if ( m_pParent->windowState() == Qt::WindowMaximized
673 && pChangeEvent->oldState() == Qt::WindowNoState)
674 {
675 /* Mark parent window non-minimized, isMinimized() is not enough due to Qt5vsX11 fight: */
676 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent window is maximized\n"));
677 m_fIsParentMinimized = false;
678 }
679 break;
680 }
681 case GeometryType_Full:
682 {
683 if ( m_pParent->windowState() == Qt::WindowFullScreen
684 && pChangeEvent->oldState() == Qt::WindowNoState)
685 {
686 /* Mark parent window non-minimized, isMinimized() is not enough due to Qt5vsX11 fight: */
687 LogRel2(("GUI: UIMiniToolBar::eventFilter: Parent window is full-screen\n"));
688 m_fIsParentMinimized = false;
689 }
690 break;
691 }
692 }
693 }
694 break;
695 }
696#endif /* VBOX_WS_NIX */
697 default:
698 break;
699 }
700 }
701
702 /* Call to base-class: */
703 return QWidget::eventFilter(pWatched, pEvent);
704}
705
706void UIMiniToolBar::resizeEvent(QResizeEvent*)
707{
708 /* Adjust geometry: */
709 adjustGeometry();
710}
711
712#ifdef VBOX_IS_QT6_OR_LATER /* QWidget::enterEvent uses QEnterEvent since qt6 */
713void UIMiniToolBar::enterEvent(QEnterEvent*)
714#else
715void UIMiniToolBar::enterEvent(QEvent*)
716#endif
717{
718 /* Stop the hover-leave timer if necessary: */
719 if (m_pHoverLeaveTimer && m_pHoverLeaveTimer->isActive())
720 m_pHoverLeaveTimer->stop();
721
722 /* Start the hover-enter timer: */
723 if (m_pHoverEnterTimer)
724 m_pHoverEnterTimer->start();
725}
726
727void UIMiniToolBar::leaveEvent(QEvent*)
728{
729 // WORKAROUND:
730 // No idea why, but GUI receives mouse leave event
731 // when the mouse cursor is on the border of screen
732 // even if underlying widget is on the border of
733 // screen as well, we should detect and ignore that.
734 // Besides that, this is a good way to keep the
735 // tool-bar visible when the mouse moving through
736 // the desktop strut till the real screen border.
737 const QPoint cursorPosition = QCursor::pos();
738 if ( cursorPosition.y() <= y() + 1
739 || cursorPosition.y() >= y() + height() - 1)
740 return;
741
742 /* Stop the hover-enter timer if necessary: */
743 if (m_pHoverEnterTimer && m_pHoverEnterTimer->isActive())
744 m_pHoverEnterTimer->stop();
745
746 /* Start the hover-leave timer: */
747 if (m_fAutoHide && m_pHoverLeaveTimer)
748 m_pHoverLeaveTimer->start();
749}
750
751void UIMiniToolBar::sltHandleToolbarResize()
752{
753 /* Adjust geometry: */
754 adjustGeometry();
755}
756
757void UIMiniToolBar::sltAutoHideToggled()
758{
759 /* Propagate from child: */
760 setAutoHide(m_pToolbar->autoHide(), false);
761 emit sigAutoHideToggled(m_pToolbar->autoHide());
762}
763
764void UIMiniToolBar::sltHoverEnter()
765{
766 /* Mark as 'hovered' if necessary: */
767 if (!m_fHovered)
768 {
769 m_fHovered = true;
770 emit sigHoverEnter();
771 }
772}
773
774void UIMiniToolBar::sltHoverLeave()
775{
776 /* Mark as 'unhovered' if necessary: */
777 if (m_fHovered)
778 {
779 m_fHovered = false;
780 if (m_fAutoHide)
781 emit sigHoverLeave();
782 }
783}
784
785void UIMiniToolBar::sltCheckWindowActivationSanity()
786{
787 /* Do nothing if parent window is already active: */
788 if ( m_pParent
789 && QGuiApplication::focusWindow() == m_pParent->windowHandle())
790 return;
791
792 /* We can't touch window activation if have modal or popup
793 * window opened, otherwise internal Qt state get flawed: */
794 if ( QApplication::activeModalWidget()
795 || QApplication::activePopupWidget())
796 {
797 /* But we should recheck the state in let's say 300ms: */
798 QTimer::singleShot(300, this, SLOT(sltCheckWindowActivationSanity()));
799 return;
800 }
801
802 /* Notify listener about we have stole window activation: */
803 emit sigNotifyAboutWindowActivationStolen();
804}
805
806void UIMiniToolBar::sltHide()
807{
808 LogRel(("GUI: Hide mini-toolbar for window #%d\n", m_iWindowIndex));
809
810#if defined(VBOX_WS_MAC)
811
812 // Nothing
813
814#elif defined(VBOX_WS_WIN)
815
816 /* Reset window state to NONE and hide it: */
817 setWindowState(Qt::WindowNoState);
818 hide();
819
820#elif defined(VBOX_WS_NIX)
821
822 /* Just hide window: */
823 hide();
824
825#else
826
827# warning "port me"
828
829#endif
830}
831
832void UIMiniToolBar::sltShow()
833{
834 LogRel(("GUI: Show mini-toolbar for window #%d\n", m_iWindowIndex));
835
836 /* Update transience: */
837 sltAdjustTransience();
838
839#if defined(VBOX_WS_MAC)
840
841 // Nothing
842
843#elif defined(VBOX_WS_WIN)
844
845 // WORKAROUND:
846 // If the host-screen is changed => we should
847 // reset window state to NONE first because
848 // we need an expose on showFullScreen call.
849 if (m_geometryType == GeometryType_Full)
850 setWindowState(Qt::WindowNoState);
851
852 /* Adjust window: */
853 sltAdjust();
854 /* Show window in necessary mode: */
855 switch (m_geometryType)
856 {
857 case GeometryType_Available:
858 {
859 /* Show normal: */
860 show();
861 break;
862 }
863 case GeometryType_Full:
864 {
865 /* Show full-screen: */
866 showFullScreen();
867 break;
868 }
869 }
870
871#elif defined(VBOX_WS_NIX)
872
873 /* Show window in necessary mode: */
874 switch (m_geometryType)
875 {
876 case GeometryType_Available:
877 {
878 /* Adjust window: */
879 sltAdjust();
880 /* Show maximized: */
881 if (!isMaximized())
882 showMaximized();
883 break;
884 }
885 case GeometryType_Full:
886 {
887 /* Show full-screen: */
888 showFullScreen();
889 /* Adjust window: */
890 sltAdjust();
891 break;
892 }
893 }
894
895#else
896
897# warning "port me"
898
899#endif
900
901 /* Simulate toolbar auto-hiding: */
902 simulateToolbarAutoHiding();
903}
904
905void UIMiniToolBar::sltAdjust()
906{
907 LogRel(("GUI: Adjust mini-toolbar for window #%d\n", m_iWindowIndex));
908
909 /* Get corresponding host-screen: */
910 const int iHostScreenCount = UIDesktopWidgetWatchdog::screenCount();
911 int iHostScreen = UIDesktopWidgetWatchdog::screenNumber(m_pParent);
912 // WORKAROUND:
913 // When switching host-screen count, especially in complex cases where RDP client is "replacing" host-screen(s) with own virtual-screen(s),
914 // Qt could behave quite arbitrary and laggy, and due to racing there could be a situation when QDesktopWidget::screenNumber() returns -1
915 // as a host-screen number where the parent window is currently located. We should handle this situation anyway, so let's assume the parent
916 // window is located on primary (0) host-screen if it's present or ignore this request at all.
917 if (iHostScreen < 0 || iHostScreen >= iHostScreenCount)
918 {
919 if (iHostScreenCount > 0)
920 {
921 LogRel(("GUI: Mini-toolbar parent window #%d is located on invalid host-screen #%d. Fallback to primary.\n", m_iWindowIndex, iHostScreen));
922 iHostScreen = 0;
923 }
924 else
925 {
926 LogRel(("GUI: Mini-toolbar parent window #%d is located on invalid host-screen #%d. Ignore request.\n", m_iWindowIndex, iHostScreen));
927 return;
928 }
929 }
930
931 /* Get corresponding working area: */
932 QRect workingArea;
933 switch (m_geometryType)
934 {
935 case GeometryType_Available: workingArea = gpDesktop->availableGeometry(iHostScreen); break;
936 case GeometryType_Full: workingArea = gpDesktop->screenGeometry(iHostScreen); break;
937 }
938 Q_UNUSED(workingArea);
939
940#if defined(VBOX_WS_MAC)
941
942 // Nothing
943
944#elif defined(VBOX_WS_WIN)
945
946 switch (m_geometryType)
947 {
948 case GeometryType_Available:
949 {
950 /* Set appropriate window size: */
951 const QSize newSize = workingArea.size();
952 LogRel(("GUI: Resize mini-toolbar for window #%d to %dx%d\n",
953 m_iWindowIndex, newSize.width(), newSize.height()));
954 resize(newSize);
955
956 /* Move window onto required screen: */
957 const QPoint newPosition = workingArea.topLeft();
958 LogRel(("GUI: Move mini-toolbar for window #%d to %dx%d\n",
959 m_iWindowIndex, newPosition.x(), newPosition.y()));
960 move(newPosition);
961
962 break;
963 }
964 case GeometryType_Full:
965 {
966 /* Map window onto required screen: */
967 LogRel(("GUI: Map mini-toolbar for window #%d to screen %d of %d\n",
968 m_iWindowIndex, iHostScreen, qApp->screens().size()));
969 windowHandle()->setScreen(qApp->screens().at(iHostScreen));
970
971 /* Set appropriate window size: */
972 const QSize newSize = workingArea.size();
973 LogRel(("GUI: Resize mini-toolbar for window #%d to %dx%d\n",
974 m_iWindowIndex, newSize.width(), newSize.height()));
975 resize(newSize);
976
977 break;
978 }
979 }
980
981#elif defined(VBOX_WS_NIX)
982
983 switch (m_geometryType)
984 {
985 case GeometryType_Available:
986 {
987 /* Make sure we are located on corresponding host-screen: */
988 if ( UIDesktopWidgetWatchdog::screenCount() > 1
989 && (x() != workingArea.x() || y() != workingArea.y()))
990 {
991 // WORKAROUND:
992 // With Qt5 on KDE we can't just move the window onto desired host-screen if
993 // window is maximized. So we have to show it normal first of all:
994 if (isVisible() && isMaximized())
995 showNormal();
996
997 // WORKAROUND:
998 // With Qt5 on X11 we can't just move the window onto desired host-screen if
999 // window size is more than the available geometry (working area) of that
1000 // host-screen. So we are resizing it to a smaller size first of all:
1001 const QSize newSize = workingArea.size() * .9;
1002 LogRel(("GUI: Resize mini-toolbar for window #%d to smaller size %dx%d\n",
1003 m_iWindowIndex, newSize.width(), newSize.height()));
1004 resize(newSize);
1005
1006 /* Move window onto required screen: */
1007 const QPoint newPosition = workingArea.topLeft();
1008 LogRel(("GUI: Move mini-toolbar for window #%d to %dx%d\n",
1009 m_iWindowIndex, newPosition.x(), newPosition.y()));
1010 move(newPosition);
1011 }
1012
1013 break;
1014 }
1015 case GeometryType_Full:
1016 {
1017 /* Determine whether we should use the native full-screen mode: */
1018 const bool fUseNativeFullScreen = NativeWindowSubsystem::X11SupportsFullScreenMonitorsProtocol()
1019 && !gEDataManager->legacyFullscreenModeRequested();
1020 if (fUseNativeFullScreen)
1021 {
1022 /* Tell recent window managers which host-screen this window should be mapped to: */
1023 NativeWindowSubsystem::X11SetFullScreenMonitor(this, iHostScreen);
1024 }
1025
1026 /* Set appropriate window size: */
1027 const QSize newSize = workingArea.size();
1028 LogRel(("GUI: Resize mini-toolbar for window #%d to %dx%d\n",
1029 m_iWindowIndex, newSize.width(), newSize.height()));
1030 resize(newSize);
1031
1032 /* Move window onto required screen: */
1033 const QPoint newPosition = workingArea.topLeft();
1034 LogRel(("GUI: Move mini-toolbar for window #%d to %dx%d\n",
1035 m_iWindowIndex, newPosition.x(), newPosition.y()));
1036 move(newPosition);
1037
1038 /* Re-apply the full-screen state lost on above move(): */
1039 setWindowState(Qt::WindowFullScreen);
1040
1041 break;
1042 }
1043 }
1044
1045#else
1046
1047# warning "port me"
1048
1049#endif
1050}
1051
1052void UIMiniToolBar::sltAdjustTransience()
1053{
1054 // WORKAROUND:
1055 // Make sure win id is generated,
1056 // else Qt5 can crash otherwise.
1057 winId();
1058 m_pParent->winId();
1059
1060 /* Add the transience dependency: */
1061 windowHandle()->setTransientParent(m_pParent->windowHandle());
1062}
1063
1064void UIMiniToolBar::prepare()
1065{
1066 /* Install event-filters: */
1067 installEventFilter(this);
1068 m_pParent->installEventFilter(this);
1069
1070#if defined(VBOX_WS_WIN)
1071 /* No background until first paint-event: */
1072 setAttribute(Qt::WA_NoSystemBackground);
1073 /* Enable translucency through Qt API: */
1074 setAttribute(Qt::WA_TranslucentBackground);
1075#elif defined(VBOX_WS_NIX)
1076 /* Enable translucency through Qt API if supported: */
1077 if (uiCommon().isCompositingManagerRunning())
1078 setAttribute(Qt::WA_TranslucentBackground);
1079#endif /* VBOX_WS_NIX */
1080
1081 /* Make sure we have no focus: */
1082 setFocusPolicy(Qt::NoFocus);
1083
1084 /* Prepare area: */
1085 m_pArea = new QWidget;
1086 {
1087 /* Allow any area size: */
1088 m_pArea->setMinimumSize(QSize(1, 1));
1089 /* Configure own background: */
1090 QPalette pal = m_pArea->palette();
1091 pal.setColor(QPalette::Window, QColor(Qt::transparent));
1092 m_pArea->setPalette(pal);
1093 /* Layout area according parent-widget: */
1094 QVBoxLayout *pMainLayout = new QVBoxLayout(this);
1095 pMainLayout->setContentsMargins(0, 0, 0, 0);
1096 pMainLayout->addWidget(m_pArea);
1097 /* Make sure we have no focus: */
1098 m_pArea->setFocusPolicy(Qt::NoFocus);
1099 }
1100
1101 /* Prepare mini-toolbar: */
1102 m_pToolbar = new UIMiniToolBarPrivate;
1103 {
1104 /* Make sure we have no focus: */
1105 m_pToolbar->setFocusPolicy(Qt::NoFocus);
1106 /* Propagate known options to child: */
1107 m_pToolbar->setAutoHide(m_fAutoHide);
1108 m_pToolbar->setAlignment(m_alignment);
1109 /* Configure own background: */
1110 QPalette pal = m_pToolbar->palette();
1111 pal.setColor(QPalette::Window, QApplication::palette().color(QPalette::Window));
1112 m_pToolbar->setPalette(pal);
1113 /* Configure child connections: */
1114 connect(m_pToolbar, &UIMiniToolBarPrivate::sigResized, this, &UIMiniToolBar::sltHandleToolbarResize);
1115 connect(m_pToolbar, &UIMiniToolBarPrivate::sigAutoHideToggled, this, &UIMiniToolBar::sltAutoHideToggled);
1116 connect(m_pToolbar, &UIMiniToolBarPrivate::sigMinimizeAction, this, &UIMiniToolBar::sigMinimizeAction);
1117 connect(m_pToolbar, &UIMiniToolBarPrivate::sigExitAction, this, &UIMiniToolBar::sigExitAction);
1118 connect(m_pToolbar, &UIMiniToolBarPrivate::sigCloseAction, this, &UIMiniToolBar::sigCloseAction);
1119 /* Add child to area: */
1120 m_pToolbar->setParent(m_pArea);
1121 /* Make sure we have no focus: */
1122 m_pToolbar->setFocusPolicy(Qt::NoFocus);
1123 }
1124
1125 /* Prepare hover-enter/leave timers: */
1126 m_pHoverEnterTimer = new QTimer(this);
1127 {
1128 m_pHoverEnterTimer->setSingleShot(true);
1129 m_pHoverEnterTimer->setInterval(500);
1130 connect(m_pHoverEnterTimer, &QTimer::timeout, this, &UIMiniToolBar::sltHoverEnter);
1131 }
1132 m_pHoverLeaveTimer = new QTimer(this);
1133 {
1134 m_pHoverLeaveTimer->setSingleShot(true);
1135 m_pHoverLeaveTimer->setInterval(500);
1136 connect(m_pHoverLeaveTimer, &QTimer::timeout, this, &UIMiniToolBar::sltHoverLeave);
1137 }
1138
1139 /* Install 'auto-hide' animation to 'toolbarPosition' property: */
1140 m_pAnimation = UIAnimation::installPropertyAnimation(this,
1141 "toolbarPosition",
1142 "hiddenToolbarPosition", "shownToolbarPosition",
1143 SIGNAL(sigHoverEnter()), SIGNAL(sigHoverLeave()),
1144 true);
1145
1146 /* Adjust geometry first time: */
1147 adjustGeometry();
1148
1149#ifdef VBOX_WS_NIX
1150 /* Hide mini-toolbar from taskbar and pager: */
1151 NativeWindowSubsystem::X11SetSkipTaskBarFlag(this);
1152 NativeWindowSubsystem::X11SetSkipPagerFlag(this);
1153#endif
1154}
1155
1156void UIMiniToolBar::cleanup()
1157{
1158 /* Stop hover-enter/leave timers: */
1159 if (m_pHoverEnterTimer && m_pHoverEnterTimer->isActive())
1160 m_pHoverEnterTimer->stop();
1161 if (m_pHoverLeaveTimer && m_pHoverLeaveTimer->isActive())
1162 m_pHoverLeaveTimer->stop();
1163
1164 /* Destroy animation before toolbar: */
1165 delete m_pAnimation;
1166 m_pAnimation = 0;
1167
1168 /* Destroy toolbar after animation: */
1169 delete m_pToolbar;
1170 m_pToolbar = 0;
1171}
1172
1173void UIMiniToolBar::simulateToolbarAutoHiding()
1174{
1175 /* This simulation helps user to notice
1176 * toolbar location, so it will be used only
1177 * 1. if toolbar unhovered and
1178 * 2. auto-hide feature enabled: */
1179 if (m_fHovered || !m_fAutoHide)
1180 return;
1181
1182 /* Simulate hover-leave event: */
1183 m_fHovered = true;
1184 m_pHoverLeaveTimer->start();
1185}
1186
1187void UIMiniToolBar::setToolbarPosition(QPoint point)
1188{
1189 /* Update position: */
1190 AssertPtrReturnVoid(m_pToolbar);
1191 m_pToolbar->move(point);
1192
1193#if defined(VBOX_WS_WIN) || defined(VBOX_WS_NIX)
1194 /* Update window mask: */
1195 setMask(m_pToolbar->geometry());
1196#endif /* VBOX_WS_WIN || VBOX_WS_NIX */
1197}
1198
1199QPoint UIMiniToolBar::toolbarPosition() const
1200{
1201 /* Return position: */
1202 AssertPtrReturn(m_pToolbar, QPoint());
1203 return m_pToolbar->pos();
1204}
1205
1206bool UIMiniToolBar::isParentMinimized() const
1207{
1208#ifdef VBOX_WS_NIX
1209 return m_fIsParentMinimized;
1210#else
1211 return m_pParent->isMinimized();
1212#endif
1213}
1214
1215#include "UIMiniToolBar.moc"
Note: See TracBrowser for help on using the repository browser.

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