[26714] | 1 | /* $Id: UIPopupPane.cpp 104358 2024-04-18 05:33:40Z vboxsync $ */
|
---|
[25177] | 2 | /** @file
|
---|
[52727] | 3 | * VBox Qt GUI - UIPopupPane class implementation.
|
---|
[25177] | 4 | */
|
---|
| 5 |
|
---|
| 6 | /*
|
---|
[98103] | 7 | * Copyright (C) 2013-2023 Oracle and/or its affiliates.
|
---|
[25177] | 8 | *
|
---|
[96407] | 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
|
---|
[25177] | 26 | */
|
---|
| 27 |
|
---|
[41587] | 28 | /* Qt includes: */
|
---|
[104358] | 29 | #include <QApplication>
|
---|
[76606] | 30 | #include <QPainter>
|
---|
[104358] | 31 | #include <QPainterPath>
|
---|
[76606] | 32 | #include <QTextEdit>
|
---|
[34736] | 33 |
|
---|
[41587] | 34 | /* GUI includes: */
|
---|
[104358] | 35 | #include "QIMessageBox.h"
|
---|
| 36 | #include "UIAnimationFramework.h"
|
---|
[76606] | 37 | #include "UIPopupPane.h"
|
---|
| 38 | #include "UIPopupPaneMessage.h"
|
---|
| 39 | #include "UIPopupPaneDetails.h"
|
---|
| 40 | #include "UIPopupPaneButtonPane.h"
|
---|
[104358] | 41 | #include "UITranslationEventListener.h"
|
---|
[25177] | 42 |
|
---|
[68540] | 43 | /* Other VBox includes: */
|
---|
[76606] | 44 | #include <iprt/assert.h>
|
---|
[68540] | 45 |
|
---|
[52730] | 46 |
|
---|
[45658] | 47 | UIPopupPane::UIPopupPane(QWidget *pParent,
|
---|
[45468] | 48 | const QString &strMessage, const QString &strDetails,
|
---|
[50138] | 49 | const QMap<int, QString> &buttonDescriptions)
|
---|
[104358] | 50 | : QWidget(pParent)
|
---|
[47016] | 51 | , m_fPolished(false)
|
---|
[45662] | 52 | , m_iLayoutMargin(10), m_iLayoutSpacing(5)
|
---|
[45691] | 53 | , m_strMessage(strMessage), m_strDetails(strDetails)
|
---|
| 54 | , m_buttonDescriptions(buttonDescriptions)
|
---|
[47016] | 55 | , m_fShown(false)
|
---|
| 56 | , m_pShowAnimation(0)
|
---|
[47694] | 57 | , m_fCanLooseFocus(!m_buttonDescriptions.isEmpty())
|
---|
| 58 | , m_fFocused(!m_fCanLooseFocus)
|
---|
| 59 | , m_fHovered(m_fFocused)
|
---|
[46985] | 60 | , m_iDefaultOpacity(180)
|
---|
| 61 | , m_iHoveredOpacity(250)
|
---|
[47694] | 62 | , m_iOpacity(m_fHovered ? m_iHoveredOpacity : m_iDefaultOpacity)
|
---|
[68540] | 63 | , m_pMessagePane(0), m_pDetailsPane(0), m_pButtonPane(0)
|
---|
[45468] | 64 | {
|
---|
[45490] | 65 | /* Prepare: */
|
---|
| 66 | prepare();
|
---|
| 67 | }
|
---|
[45468] | 68 |
|
---|
[47209] | 69 | void UIPopupPane::recall()
|
---|
| 70 | {
|
---|
| 71 | /* Close popup-pane with *escape* button: */
|
---|
| 72 | done(m_pButtonPane->escapeButton());
|
---|
| 73 | }
|
---|
| 74 |
|
---|
[45586] | 75 | void UIPopupPane::setMessage(const QString &strMessage)
|
---|
| 76 | {
|
---|
[47031] | 77 | /* Make sure the message has changed: */
|
---|
[45586] | 78 | if (m_strMessage == strMessage)
|
---|
| 79 | return;
|
---|
| 80 |
|
---|
| 81 | /* Fetch new message: */
|
---|
| 82 | m_strMessage = strMessage;
|
---|
[68540] | 83 | m_pMessagePane->setText(m_strMessage);
|
---|
[45586] | 84 | }
|
---|
| 85 |
|
---|
| 86 | void UIPopupPane::setDetails(const QString &strDetails)
|
---|
| 87 | {
|
---|
[47031] | 88 | /* Make sure the details has changed: */
|
---|
[45586] | 89 | if (m_strDetails == strDetails)
|
---|
| 90 | return;
|
---|
| 91 |
|
---|
| 92 | /* Fetch new details: */
|
---|
| 93 | m_strDetails = strDetails;
|
---|
[68540] | 94 | m_pDetailsPane->setText(prepareDetailsText());
|
---|
[45586] | 95 | }
|
---|
| 96 |
|
---|
[47016] | 97 | void UIPopupPane::setMinimumSizeHint(const QSize &minimumSizeHint)
|
---|
| 98 | {
|
---|
[47031] | 99 | /* Make sure the size-hint has changed: */
|
---|
[47016] | 100 | if (m_minimumSizeHint == minimumSizeHint)
|
---|
| 101 | return;
|
---|
| 102 |
|
---|
[47031] | 103 | /* Fetch new size-hint: */
|
---|
[47016] | 104 | m_minimumSizeHint = minimumSizeHint;
|
---|
| 105 |
|
---|
| 106 | /* Notify parent popup-stack: */
|
---|
| 107 | emit sigSizeHintChanged();
|
---|
| 108 | }
|
---|
| 109 |
|
---|
[45658] | 110 | void UIPopupPane::layoutContent()
|
---|
| 111 | {
|
---|
[45662] | 112 | /* Variables: */
|
---|
[45658] | 113 | const int iWidth = width();
|
---|
| 114 | const int iHeight = height();
|
---|
[45662] | 115 | const QSize buttonPaneMinimumSizeHint = m_pButtonPane->minimumSizeHint();
|
---|
| 116 | const int iButtonPaneMinimumWidth = buttonPaneMinimumSizeHint.width();
|
---|
| 117 | const int iButtonPaneMinimumHeight = buttonPaneMinimumSizeHint.height();
|
---|
| 118 | const int iTextPaneWidth = iWidth - 2 * m_iLayoutMargin - m_iLayoutSpacing - iButtonPaneMinimumWidth;
|
---|
[68540] | 119 | const int iTextPaneHeight = m_pMessagePane->minimumSizeHint().height();
|
---|
[45662] | 120 | const int iMaximumHeight = qMax(iTextPaneHeight, iButtonPaneMinimumHeight);
|
---|
| 121 | const int iMinimumHeight = qMin(iTextPaneHeight, iButtonPaneMinimumHeight);
|
---|
| 122 | const int iHeightShift = (iMaximumHeight - iMinimumHeight) / 2;
|
---|
| 123 | const bool fTextPaneShifted = iTextPaneHeight < iButtonPaneMinimumHeight;
|
---|
[68540] | 124 | const int iTextPaneYOffset = fTextPaneShifted ? m_iLayoutMargin + iHeightShift : m_iLayoutMargin;
|
---|
[45662] | 125 |
|
---|
[68540] | 126 | /* Message-pane: */
|
---|
| 127 | m_pMessagePane->move(m_iLayoutMargin, iTextPaneYOffset);
|
---|
| 128 | m_pMessagePane->resize(iTextPaneWidth, iTextPaneHeight);
|
---|
| 129 | m_pMessagePane->layoutContent();
|
---|
[47013] | 130 |
|
---|
| 131 | /* Button-pane: */
|
---|
[45662] | 132 | m_pButtonPane->move(m_iLayoutMargin + iTextPaneWidth + m_iLayoutSpacing,
|
---|
| 133 | m_iLayoutMargin);
|
---|
| 134 | m_pButtonPane->resize(iButtonPaneMinimumWidth,
|
---|
[68540] | 135 | iHeight - m_iLayoutSpacing);
|
---|
| 136 |
|
---|
| 137 | /* Details-pane: */
|
---|
[70474] | 138 | if (m_pDetailsPane->isVisible())
|
---|
[68540] | 139 | {
|
---|
| 140 | m_pDetailsPane->move(m_iLayoutMargin,
|
---|
| 141 | iTextPaneYOffset + iTextPaneHeight + m_iLayoutSpacing);
|
---|
| 142 | m_pDetailsPane->resize(iTextPaneWidth + iButtonPaneMinimumWidth,
|
---|
[68807] | 143 | m_pDetailsPane->minimumSizeHint().height());
|
---|
[68540] | 144 | m_pDetailsPane->layoutContent();
|
---|
| 145 | }
|
---|
[45658] | 146 | }
|
---|
| 147 |
|
---|
[47016] | 148 | void UIPopupPane::sltMarkAsShown()
|
---|
| 149 | {
|
---|
| 150 | /* Mark popup-pane as 'shown': */
|
---|
| 151 | m_fShown = true;
|
---|
| 152 | }
|
---|
| 153 |
|
---|
[68540] | 154 | void UIPopupPane::sltHandleProposalForSize(QSize newSize)
|
---|
[47455] | 155 | {
|
---|
[68540] | 156 | /* Prepare the width: */
|
---|
| 157 | int iWidth = newSize.width();
|
---|
[47455] | 158 |
|
---|
| 159 | /* Subtract layout margins: */
|
---|
| 160 | iWidth -= 2 * m_iLayoutMargin;
|
---|
| 161 | /* Subtract layout spacing: */
|
---|
| 162 | iWidth -= m_iLayoutSpacing;
|
---|
| 163 | /* Subtract button-pane width: */
|
---|
| 164 | iWidth -= m_pButtonPane->minimumSizeHint().width();
|
---|
| 165 |
|
---|
[68540] | 166 | /* Propose resulting width to the panes: */
|
---|
| 167 | emit sigProposePaneWidth(iWidth);
|
---|
| 168 |
|
---|
| 169 | /* Prepare the height: */
|
---|
| 170 | int iHeight = newSize.height();
|
---|
| 171 | /* Determine maximum height of the message-pane / button-pane: */
|
---|
| 172 | int iExtraHeight = qMax(m_pMessagePane->expandedSizeHint().height(),
|
---|
| 173 | m_pButtonPane->minimumSizeHint().height());
|
---|
| 174 |
|
---|
| 175 | /* Subtract height of the message pane: */
|
---|
| 176 | iHeight -= iExtraHeight;
|
---|
| 177 | /* Subtract layout margins: */
|
---|
| 178 | iHeight -= 2 * m_iLayoutMargin;
|
---|
| 179 | /* Subtract layout spacing: */
|
---|
| 180 | iHeight -= m_iLayoutSpacing;
|
---|
| 181 |
|
---|
| 182 | /* Propose resulting height to details-pane: */
|
---|
| 183 | emit sigProposeDetailsPaneHeight(iHeight);
|
---|
[47455] | 184 | }
|
---|
| 185 |
|
---|
[47031] | 186 | void UIPopupPane::sltUpdateSizeHint()
|
---|
[47014] | 187 | {
|
---|
[47031] | 188 | /* Calculate minimum width-hint: */
|
---|
| 189 | int iMinimumWidthHint = 0;
|
---|
| 190 | {
|
---|
| 191 | /* Take into account layout: */
|
---|
| 192 | iMinimumWidthHint += 2 * m_iLayoutMargin;
|
---|
| 193 | {
|
---|
| 194 | /* Take into account widgets: */
|
---|
[68540] | 195 | iMinimumWidthHint += m_pMessagePane->minimumSizeHint().width();
|
---|
[47031] | 196 | iMinimumWidthHint += m_iLayoutSpacing;
|
---|
| 197 | iMinimumWidthHint += m_pButtonPane->minimumSizeHint().width();
|
---|
| 198 | }
|
---|
| 199 | }
|
---|
[47014] | 200 |
|
---|
[47031] | 201 | /* Calculate minimum height-hint: */
|
---|
| 202 | int iMinimumHeightHint = 0;
|
---|
| 203 | {
|
---|
| 204 | /* Take into account layout: */
|
---|
| 205 | iMinimumHeightHint += 2 * m_iLayoutMargin;
|
---|
[68807] | 206 | iMinimumHeightHint += m_iLayoutSpacing;
|
---|
[47031] | 207 | {
|
---|
| 208 | /* Take into account widgets: */
|
---|
[68540] | 209 | const int iTextPaneHeight = m_pMessagePane->minimumSizeHint().height();
|
---|
[47031] | 210 | const int iButtonBoxHeight = m_pButtonPane->minimumSizeHint().height();
|
---|
| 211 | iMinimumHeightHint += qMax(iTextPaneHeight, iButtonBoxHeight);
|
---|
[68540] | 212 | /* Add the height of details-pane only if it is visible: */
|
---|
| 213 | if (m_pDetailsPane->isVisible())
|
---|
| 214 | iMinimumHeightHint += m_pDetailsPane->minimumSizeHint().height();
|
---|
[47031] | 215 | }
|
---|
| 216 | }
|
---|
| 217 |
|
---|
| 218 | /* Compose minimum size-hints: */
|
---|
| 219 | m_hiddenSizeHint = QSize(iMinimumWidthHint, 1);
|
---|
| 220 | m_shownSizeHint = QSize(iMinimumWidthHint, iMinimumHeightHint);
|
---|
| 221 | m_minimumSizeHint = m_fShown ? m_shownSizeHint : m_hiddenSizeHint;
|
---|
| 222 |
|
---|
| 223 | /* Update 'show/hide' animation: */
|
---|
| 224 | if (m_pShowAnimation)
|
---|
| 225 | m_pShowAnimation->update();
|
---|
| 226 |
|
---|
[47014] | 227 | /* Notify parent popup-stack: */
|
---|
| 228 | emit sigSizeHintChanged();
|
---|
| 229 | }
|
---|
| 230 |
|
---|
[45658] | 231 | void UIPopupPane::sltButtonClicked(int iButtonID)
|
---|
| 232 | {
|
---|
[47013] | 233 | /* Complete popup with corresponding code: */
|
---|
[50138] | 234 | done(iButtonID);
|
---|
[45658] | 235 | }
|
---|
| 236 |
|
---|
[45490] | 237 | void UIPopupPane::prepare()
|
---|
| 238 | {
|
---|
[47455] | 239 | /* Prepare this: */
|
---|
| 240 | installEventFilter(this);
|
---|
| 241 | /* Prepare background: */
|
---|
| 242 | prepareBackground();
|
---|
[45468] | 243 | /* Prepare content: */
|
---|
| 244 | prepareContent();
|
---|
[47013] | 245 | /* Prepare animation: */
|
---|
| 246 | prepareAnimation();
|
---|
[47014] | 247 |
|
---|
[47031] | 248 | /* Update size-hint: */
|
---|
| 249 | sltUpdateSizeHint();
|
---|
[45468] | 250 | }
|
---|
| 251 |
|
---|
[47455] | 252 | void UIPopupPane::prepareBackground()
|
---|
| 253 | {
|
---|
| 254 | /* Prepare palette: */
|
---|
[88862] | 255 | QPalette pal = QApplication::palette();
|
---|
[47455] | 256 | pal.setColor(QPalette::Window, QApplication::palette().color(QPalette::Window));
|
---|
| 257 | setPalette(pal);
|
---|
| 258 | }
|
---|
| 259 |
|
---|
[45605] | 260 | void UIPopupPane::prepareContent()
|
---|
[45468] | 261 | {
|
---|
[68540] | 262 | /* Create message-pane: */
|
---|
| 263 | m_pMessagePane = new UIPopupPaneMessage(this, m_strMessage, m_fFocused);
|
---|
[45605] | 264 | {
|
---|
[68540] | 265 | /* Configure message-pane: */
|
---|
[80914] | 266 | connect(this, &UIPopupPane::sigProposePaneWidth, m_pMessagePane, &UIPopupPaneMessage::sltHandleProposalForWidth);
|
---|
| 267 | connect(m_pMessagePane, &UIPopupPaneMessage::sigSizeHintChanged, this, &UIPopupPane::sltUpdateSizeHint);
|
---|
[68540] | 268 | m_pMessagePane->installEventFilter(this);
|
---|
[45605] | 269 | }
|
---|
[47013] | 270 |
|
---|
[45662] | 271 | /* Create button-box: */
|
---|
| 272 | m_pButtonPane = new UIPopupPaneButtonPane(this);
|
---|
| 273 | {
|
---|
[68540] | 274 | /* Configure button-box: */
|
---|
[80914] | 275 | connect(m_pButtonPane, &UIPopupPaneButtonPane::sigButtonClicked, this, &UIPopupPane::sltButtonClicked);
|
---|
[45662] | 276 | m_pButtonPane->installEventFilter(this);
|
---|
| 277 | m_pButtonPane->setButtons(m_buttonDescriptions);
|
---|
| 278 | }
|
---|
| 279 |
|
---|
[68540] | 280 | /* Create details-pane: */
|
---|
| 281 | m_pDetailsPane = new UIPopupPaneDetails(this, prepareDetailsText(), m_fFocused);
|
---|
| 282 | {
|
---|
| 283 | /* Configure details-pane: */
|
---|
| 284 | connect(this, &UIPopupPane::sigProposePaneWidth, m_pDetailsPane, &UIPopupPaneDetails::sltHandleProposalForWidth);
|
---|
| 285 | connect(this, &UIPopupPane::sigProposeDetailsPaneHeight, m_pDetailsPane, &UIPopupPaneDetails::sltHandleProposalForHeight);
|
---|
| 286 | connect(m_pDetailsPane, &UIPopupPaneDetails::sigSizeHintChanged, this, &UIPopupPane::sltUpdateSizeHint);
|
---|
| 287 | m_pDetailsPane->installEventFilter(this);
|
---|
| 288 | }
|
---|
| 289 |
|
---|
[45662] | 290 | /* Prepare focus rules: */
|
---|
| 291 | setFocusPolicy(Qt::StrongFocus);
|
---|
[68540] | 292 | m_pMessagePane->setFocusPolicy(Qt::StrongFocus);
|
---|
[45662] | 293 | m_pButtonPane->setFocusPolicy(Qt::StrongFocus);
|
---|
[68540] | 294 | m_pDetailsPane->setFocusPolicy(Qt::StrongFocus);
|
---|
[45662] | 295 | setFocusProxy(m_pButtonPane);
|
---|
[68540] | 296 | m_pMessagePane->setFocusProxy(m_pButtonPane);
|
---|
| 297 | m_pDetailsPane->setFocusProxy(m_pButtonPane);
|
---|
[47108] | 298 |
|
---|
| 299 | /* Translate UI finally: */
|
---|
[104358] | 300 | sltRetranslateUI();
|
---|
| 301 | connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
|
---|
| 302 | this, &UIPopupPane::sltRetranslateUI);
|
---|
[45468] | 303 | }
|
---|
| 304 |
|
---|
[47013] | 305 | void UIPopupPane::prepareAnimation()
|
---|
| 306 | {
|
---|
[47016] | 307 | /* Install 'show' animation for 'minimumSizeHint' property: */
|
---|
| 308 | connect(this, SIGNAL(sigToShow()), this, SIGNAL(sigShow()), Qt::QueuedConnection);
|
---|
| 309 | m_pShowAnimation = UIAnimation::installPropertyAnimation(this, "minimumSizeHint", "hiddenSizeHint", "shownSizeHint",
|
---|
| 310 | SIGNAL(sigShow()), SIGNAL(sigHide()));
|
---|
[80914] | 311 | connect(m_pShowAnimation, &UIAnimation::sigStateEnteredFinal, this, &UIPopupPane::sltMarkAsShown);
|
---|
[47016] | 312 |
|
---|
[47013] | 313 | /* Install 'hover' animation for 'opacity' property: */
|
---|
| 314 | UIAnimation::installPropertyAnimation(this, "opacity", "defaultOpacity", "hoveredOpacity",
|
---|
[47694] | 315 | SIGNAL(sigHoverEnter()), SIGNAL(sigHoverLeave()), m_fHovered);
|
---|
[47013] | 316 | }
|
---|
| 317 |
|
---|
[104358] | 318 | void UIPopupPane::sltRetranslateUI()
|
---|
[47108] | 319 | {
|
---|
| 320 | /* Translate tool-tips: */
|
---|
| 321 | retranslateToolTips();
|
---|
| 322 | }
|
---|
| 323 |
|
---|
| 324 | void UIPopupPane::retranslateToolTips()
|
---|
| 325 | {
|
---|
[68540] | 326 | /* Translate pane & message-pane tool-tips: */
|
---|
[47108] | 327 | if (m_fFocused)
|
---|
| 328 | {
|
---|
| 329 | setToolTip(QString());
|
---|
[68540] | 330 | m_pMessagePane->setToolTip(QString());
|
---|
[47108] | 331 | }
|
---|
| 332 | else
|
---|
| 333 | {
|
---|
| 334 | setToolTip(QApplication::translate("UIPopupCenter", "Click for full details"));
|
---|
[68540] | 335 | m_pMessagePane->setToolTip(QApplication::translate("UIPopupCenter", "Click for full details"));
|
---|
[47108] | 336 | }
|
---|
| 337 | }
|
---|
| 338 |
|
---|
[71490] | 339 | bool UIPopupPane::eventFilter(QObject *pObject, QEvent *pEvent)
|
---|
[45468] | 340 | {
|
---|
[78007] | 341 | /* Handle events for allowed widgets only: */
|
---|
| 342 | if ( pObject != this
|
---|
| 343 | && pObject != m_pMessagePane
|
---|
| 344 | && pObject != m_pButtonPane
|
---|
| 345 | && pObject != m_pDetailsPane)
|
---|
[104358] | 346 | return QWidget::eventFilter(pObject, pEvent);
|
---|
[78007] | 347 |
|
---|
[45658] | 348 | /* Depending on event-type: */
|
---|
| 349 | switch (pEvent->type())
|
---|
[45522] | 350 | {
|
---|
[45658] | 351 | /* Something is hovered: */
|
---|
| 352 | case QEvent::HoverEnter:
|
---|
| 353 | case QEvent::Enter:
|
---|
[45522] | 354 | {
|
---|
[46985] | 355 | /* Hover pane if not yet hovered: */
|
---|
[45658] | 356 | if (!m_fHovered)
|
---|
[45522] | 357 | {
|
---|
[45658] | 358 | m_fHovered = true;
|
---|
| 359 | emit sigHoverEnter();
|
---|
[45522] | 360 | }
|
---|
[45658] | 361 | break;
|
---|
| 362 | }
|
---|
| 363 | /* Nothing is hovered: */
|
---|
| 364 | case QEvent::Leave:
|
---|
| 365 | {
|
---|
[46985] | 366 | /* Unhover pane if hovered but not focused: */
|
---|
[71490] | 367 | if (pObject == this && m_fHovered && !m_fFocused)
|
---|
[45522] | 368 | {
|
---|
[45658] | 369 | m_fHovered = false;
|
---|
| 370 | emit sigHoverLeave();
|
---|
[45522] | 371 | }
|
---|
[45658] | 372 | break;
|
---|
| 373 | }
|
---|
[47455] | 374 | /* Pane is clicked with mouse: */
|
---|
| 375 | case QEvent::MouseButtonPress:
|
---|
[45658] | 376 | {
|
---|
[46985] | 377 | /* Focus pane if not focused: */
|
---|
[45658] | 378 | if (!m_fFocused)
|
---|
[45588] | 379 | {
|
---|
[45658] | 380 | m_fFocused = true;
|
---|
| 381 | emit sigFocusEnter();
|
---|
[47030] | 382 | /* Hover pane if not hovered: */
|
---|
| 383 | if (!m_fHovered)
|
---|
| 384 | {
|
---|
| 385 | m_fHovered = true;
|
---|
| 386 | emit sigHoverEnter();
|
---|
| 387 | }
|
---|
[47108] | 388 | /* Translate tool-tips: */
|
---|
| 389 | retranslateToolTips();
|
---|
[45588] | 390 | }
|
---|
[45658] | 391 | break;
|
---|
| 392 | }
|
---|
[46985] | 393 | /* Pane is unfocused: */
|
---|
[45658] | 394 | case QEvent::FocusOut:
|
---|
| 395 | {
|
---|
[47030] | 396 | /* Unhocus pane if focused: */
|
---|
[47694] | 397 | if (m_fCanLooseFocus && m_fFocused)
|
---|
[45588] | 398 | {
|
---|
[45658] | 399 | m_fFocused = false;
|
---|
| 400 | emit sigFocusLeave();
|
---|
[47030] | 401 | /* Unhover pane if hovered: */
|
---|
| 402 | if (m_fHovered)
|
---|
| 403 | {
|
---|
| 404 | m_fHovered = false;
|
---|
| 405 | emit sigHoverLeave();
|
---|
| 406 | }
|
---|
[47108] | 407 | /* Translate tool-tips: */
|
---|
| 408 | retranslateToolTips();
|
---|
[45588] | 409 | }
|
---|
[45658] | 410 | break;
|
---|
[45522] | 411 | }
|
---|
[45658] | 412 | /* Default case: */
|
---|
| 413 | default: break;
|
---|
[45522] | 414 | }
|
---|
[78007] | 415 |
|
---|
| 416 | /* Call to base-class: */
|
---|
[104358] | 417 | return QWidget::eventFilter(pObject, pEvent);
|
---|
[45468] | 418 | }
|
---|
| 419 |
|
---|
[47016] | 420 | void UIPopupPane::showEvent(QShowEvent *pEvent)
|
---|
| 421 | {
|
---|
| 422 | /* Call to base-class: */
|
---|
| 423 | QWidget::showEvent(pEvent);
|
---|
| 424 |
|
---|
| 425 | /* Polish border: */
|
---|
| 426 | if (m_fPolished)
|
---|
| 427 | return;
|
---|
| 428 | m_fPolished = true;
|
---|
| 429 |
|
---|
| 430 | /* Call to polish event: */
|
---|
| 431 | polishEvent(pEvent);
|
---|
| 432 | }
|
---|
| 433 |
|
---|
[71490] | 434 | void UIPopupPane::polishEvent(QShowEvent *)
|
---|
[47016] | 435 | {
|
---|
[47949] | 436 | /* Focus if marked as 'focused': */
|
---|
| 437 | if (m_fFocused)
|
---|
| 438 | setFocus();
|
---|
| 439 |
|
---|
[47016] | 440 | /* Emit signal to start *show* animation: */
|
---|
| 441 | emit sigToShow();
|
---|
| 442 | }
|
---|
| 443 |
|
---|
[71490] | 444 | void UIPopupPane::paintEvent(QPaintEvent *)
|
---|
[45468] | 445 | {
|
---|
[47634] | 446 | /* Compose painting rectangle,
|
---|
| 447 | * Shifts are required for the antialiasing support: */
|
---|
| 448 | const QRect rect(1, 1, width() - 2, height() - 2);
|
---|
[45490] | 449 |
|
---|
| 450 | /* Create painter: */
|
---|
| 451 | QPainter painter(this);
|
---|
| 452 | painter.setRenderHint(QPainter::Antialiasing);
|
---|
| 453 |
|
---|
[47013] | 454 | /* Configure clipping: */
|
---|
| 455 | configureClipping(rect, painter);
|
---|
| 456 |
|
---|
| 457 | /* Paint background: */
|
---|
| 458 | paintBackground(rect, painter);
|
---|
[47633] | 459 |
|
---|
| 460 | /* Paint frame: */
|
---|
| 461 | paintFrame(painter);
|
---|
[47013] | 462 | }
|
---|
| 463 |
|
---|
| 464 | void UIPopupPane::configureClipping(const QRect &rect, QPainter &painter)
|
---|
| 465 | {
|
---|
| 466 | /* Configure clipping: */
|
---|
[45490] | 467 | QPainterPath path;
|
---|
[45586] | 468 | int iDiameter = 6;
|
---|
[45490] | 469 | QSizeF arcSize(2 * iDiameter, 2 * iDiameter);
|
---|
[47634] | 470 | path.moveTo(rect.x() + iDiameter, rect.y());
|
---|
[45490] | 471 | path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-iDiameter, 0), 90, 90);
|
---|
[47634] | 472 | path.lineTo(path.currentPosition().x(), rect.y() + rect.height() - iDiameter);
|
---|
[45490] | 473 | path.arcTo(QRectF(path.currentPosition(), arcSize).translated(0, -iDiameter), 180, 90);
|
---|
[47634] | 474 | path.lineTo(rect.x() + rect.width() - iDiameter, path.currentPosition().y());
|
---|
[45490] | 475 | path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-iDiameter, -2 * iDiameter), 270, 90);
|
---|
[47634] | 476 | path.lineTo(path.currentPosition().x(), rect.y() + iDiameter);
|
---|
[45490] | 477 | path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-2 * iDiameter, -iDiameter), 0, 90);
|
---|
| 478 | path.closeSubpath();
|
---|
| 479 | painter.setClipPath(path);
|
---|
[47013] | 480 | }
|
---|
[45490] | 481 |
|
---|
[47013] | 482 | void UIPopupPane::paintBackground(const QRect &rect, QPainter &painter)
|
---|
| 483 | {
|
---|
| 484 | /* Paint background: */
|
---|
[45490] | 485 | QColor currentColor(palette().color(QPalette::Window));
|
---|
[45586] | 486 | QColor newColor1(currentColor.red(), currentColor.green(), currentColor.blue(), opacity());
|
---|
| 487 | QColor newColor2 = newColor1.darker(115);
|
---|
[47756] | 488 | QLinearGradient headerGradient(rect.topLeft(), rect.bottomLeft());
|
---|
[45586] | 489 | headerGradient.setColorAt(0, newColor1);
|
---|
| 490 | headerGradient.setColorAt(1, newColor2);
|
---|
| 491 | painter.fillRect(rect, headerGradient);
|
---|
[45490] | 492 | }
|
---|
| 493 |
|
---|
[47633] | 494 | void UIPopupPane::paintFrame(QPainter &painter)
|
---|
| 495 | {
|
---|
| 496 | /* Paint frame: */
|
---|
| 497 | QColor currentColor(palette().color(QPalette::Window).darker(150));
|
---|
| 498 | QPainterPath path = painter.clipPath();
|
---|
| 499 | painter.setClipping(false);
|
---|
| 500 | painter.strokePath(path, currentColor);
|
---|
| 501 | }
|
---|
| 502 |
|
---|
[45691] | 503 | void UIPopupPane::done(int iResultCode)
|
---|
[45662] | 504 | {
|
---|
| 505 | /* Notify listeners: */
|
---|
[45691] | 506 | emit sigDone(iResultCode);
|
---|
[45662] | 507 | }
|
---|
| 508 |
|
---|
[68540] | 509 | QString UIPopupPane::prepareDetailsText() const
|
---|
| 510 | {
|
---|
| 511 | if (m_strDetails.isEmpty())
|
---|
| 512 | return QString();
|
---|
| 513 |
|
---|
| 514 | QStringPairList aDetailsList;
|
---|
| 515 | prepareDetailsList(aDetailsList);
|
---|
| 516 | if (aDetailsList.isEmpty())
|
---|
| 517 | return QString();
|
---|
| 518 |
|
---|
| 519 | if (aDetailsList.size() == 1)
|
---|
| 520 | return tr("<p><b>Details:</b>") + m_strDetails + "</p>";
|
---|
| 521 |
|
---|
| 522 | QString strResultText;
|
---|
| 523 | for (int iListIdx = 0; iListIdx < aDetailsList.size(); ++iListIdx)
|
---|
| 524 | {
|
---|
| 525 | strResultText += tr("<p><b>Details:</b> (%1 of %2)").arg(iListIdx + 1).arg(aDetailsList.size());
|
---|
| 526 | const QString strFirstPart = aDetailsList.at(iListIdx).first;
|
---|
| 527 | const QString strSecondPart = aDetailsList.at(iListIdx).second;
|
---|
| 528 | if (strFirstPart.isEmpty())
|
---|
| 529 | strResultText += strSecondPart + "</p>";
|
---|
| 530 | else
|
---|
| 531 | strResultText += QString("%1<br>%2").arg(strFirstPart, strSecondPart) + "</p>";
|
---|
| 532 | }
|
---|
| 533 | return strResultText;
|
---|
| 534 | }
|
---|
| 535 |
|
---|
| 536 | void UIPopupPane::prepareDetailsList(QStringPairList &aDetailsList) const
|
---|
| 537 | {
|
---|
| 538 | if (m_strDetails.isEmpty())
|
---|
| 539 | return;
|
---|
| 540 |
|
---|
| 541 | /* Split details into paragraphs: */
|
---|
[93982] | 542 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
---|
| 543 | QStringList aParagraphs(m_strDetails.split("<!--EOP-->", Qt::SkipEmptyParts));
|
---|
| 544 | #else
|
---|
[68540] | 545 | QStringList aParagraphs(m_strDetails.split("<!--EOP-->", QString::SkipEmptyParts));
|
---|
[93982] | 546 | #endif
|
---|
[68540] | 547 | /* Make sure details-text has at least one paragraph: */
|
---|
| 548 | AssertReturnVoid(!aParagraphs.isEmpty());
|
---|
| 549 |
|
---|
| 550 | /* Enumerate all the paragraphs: */
|
---|
| 551 | foreach (const QString &strParagraph, aParagraphs)
|
---|
| 552 | {
|
---|
| 553 | /* Split each paragraph into pairs: */
|
---|
[93982] | 554 | #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
---|
| 555 | QStringList aParts(strParagraph.split("<!--EOM-->", Qt::KeepEmptyParts));
|
---|
| 556 | #else
|
---|
[68540] | 557 | QStringList aParts(strParagraph.split("<!--EOM-->", QString::KeepEmptyParts));
|
---|
[93982] | 558 | #endif
|
---|
[68540] | 559 | /* Make sure each paragraph consist of 2 parts: */
|
---|
| 560 | AssertReturnVoid(aParts.size() == 2);
|
---|
| 561 | /* Append each pair into details-list: */
|
---|
| 562 | aDetailsList << QStringPair(aParts.at(0), aParts.at(1));
|
---|
| 563 | }
|
---|
| 564 | }
|
---|