VirtualBox

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

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

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

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.9 KB
RevLine 
[26715]1/* $Id: UIProgressDialog.cpp 104358 2024-04-18 05:33:40Z vboxsync $ */
[25178]2/** @file
[52727]3 * VBox Qt GUI - UIProgressDialog class implementation.
[25178]4 */
5
6/*
[98103]7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
[25178]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
[25178]26 */
27
[41587]28/* Qt includes: */
[104358]29#include <QApplication>
[76606]30#include <QCloseEvent>
31#include <QEventLoop>
32#include <QProgressBar>
33#include <QTime>
34#include <QTimer>
35#include <QVBoxLayout>
[41587]36
37/* GUI includes: */
[76606]38#include "QIDialogButtonBox.h"
39#include "QILabel.h"
40#include "UIErrorString.h"
41#include "UIExtraDataManager.h"
42#include "UIMainEventListener.h"
43#include "UIModalWindowManager.h"
44#include "UIProgressDialog.h"
45#include "UIProgressEventHandler.h"
46#include "UISpecialControls.h"
[90967]47#include "UITranslator.h"
[104358]48#include "UITranslationEventListener.h"
[76606]49#ifdef VBOX_WS_MAC
50# include "VBoxUtils-darwin.h"
51#endif
[25178]52
[41587]53/* COM includes: */
[76606]54#include "CEventListener.h"
55#include "CEventSource.h"
56#include "CProgress.h"
[25178]57
[52730]58
[34479]59const char *UIProgressDialog::m_spcszOpDescTpl = "%1 ... (%2/%3)";
[25178]60
[71578]61UIProgressDialog::UIProgressDialog(CProgress &comProgress,
[34479]62 const QString &strTitle,
63 QPixmap *pImage /* = 0 */,
64 int cMinDuration /* = 2000 */,
65 QWidget *pParent /* = 0 */)
[104358]66 : QIDialog(pParent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint)
[71578]67 , m_comProgress(comProgress)
[69000]68 , m_strTitle(strTitle)
69 , m_pImage(pImage)
70 , m_cMinDuration(cMinDuration)
[69006]71 , m_fLegacyHandling(gEDataManager->legacyProgressHandlingRequested())
[68999]72 , m_pLabelImage(0)
73 , m_pLabelDescription(0)
74 , m_pProgressBar(0)
75 , m_pButtonCancel(0)
76 , m_pLabelEta(0)
77 , m_cOperations(m_comProgress.GetOperationCount())
78 , m_uCurrentOperation(m_comProgress.GetOperation() + 1)
[78969]79 , m_uCurrentOperationWeight(m_comProgress.GetOperationWeight())
[69000]80 , m_fCancelEnabled(false)
[43857]81 , m_fEnded(false)
[69006]82 , m_pEventHandler(0)
[25178]83{
[69000]84 /* Prepare: */
85 prepare();
86}
87
88UIProgressDialog::~UIProgressDialog()
89{
90 /* Cleanup: */
91 cleanup();
92}
93
[104358]94void UIProgressDialog::sltRetranslateUI()
[69000]95{
96 m_pButtonCancel->setText(tr("&Cancel"));
97 m_pButtonCancel->setToolTip(tr("Cancel the current operation"));
98}
99
100int UIProgressDialog::run(int cRefreshInterval)
101{
[69005]102 /* Make sure progress hasn't finished already: */
103 if (!m_comProgress.isOk() || m_comProgress.GetCompleted())
[69000]104 {
[69005]105 /* Progress completed? */
106 if (m_comProgress.isOk())
107 return Accepted;
108 /* Or aborted? */
109 else
110 return Rejected;
111 }
[69000]112
[69006]113 /* Start refresh timer (if necessary): */
114 int id = 0;
115 if (m_fLegacyHandling)
116 id = startTimer(cRefreshInterval);
[69005]117
118 /* Set busy cursor.
119 * We don't do this on the Mac, cause regarding the design rules of
120 * Apple there is no busy window behavior. A window should always be
121 * responsive and it is in our case (We show the progress dialog bar). */
[69000]122#ifndef VBOX_WS_MAC
[69005]123 if (m_fCancelEnabled)
124 QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
125 else
126 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
[69000]127#endif /* VBOX_WS_MAC */
128
[69005]129 /* Create a local event-loop: */
130 {
131 /* Guard ourself for the case
132 * we destroyed ourself in our event-loop: */
133 QPointer<UIProgressDialog> guard = this;
[69000]134
[69005]135 /* Holds the modal loop, but don't show the window immediately: */
136 execute(false);
[69000]137
[69005]138 /* Are we still valid? */
139 if (guard.isNull())
140 return Rejected;
141 }
[69000]142
[69006]143 /* Kill refresh timer (if necessary): */
144 if (m_fLegacyHandling)
145 killTimer(id);
[69000]146
147#ifndef VBOX_WS_MAC
[69005]148 /* Reset the busy cursor */
149 QApplication::restoreOverrideCursor();
[69000]150#endif /* VBOX_WS_MAC */
151
[69005]152 return result();
[69000]153}
154
155void UIProgressDialog::show()
156{
157 /* We should not show progress-dialog
158 * if it was already finalized but not yet closed.
159 * This could happens in case of some other
160 * modal dialog prevents our event-loop from
161 * being exit overlapping 'this'. */
162 if (!m_fEnded)
163 QIDialog::show();
164}
165
166void UIProgressDialog::reject()
167{
168 if (m_fCancelEnabled)
169 sltCancelOperation();
170}
171
[71578]172void UIProgressDialog::timerEvent(QTimerEvent *)
[69000]173{
174 /* Call the timer event handling delegate: */
175 handleTimerEvent();
176}
177
178void UIProgressDialog::closeEvent(QCloseEvent *pEvent)
179{
180 if (m_fCancelEnabled)
181 sltCancelOperation();
182 else
183 pEvent->ignore();
184}
185
[74878]186void UIProgressDialog::sltHandleProgressPercentageChange(const QUuid &, const int iPercent)
[69006]187{
188 /* New mode only: */
189 AssertReturnVoid(!m_fLegacyHandling);
190
191 /* Update progress: */
192 updateProgressState();
193 updateProgressPercentage(iPercent);
194}
195
[74878]196void UIProgressDialog::sltHandleProgressTaskComplete(const QUuid &)
[69006]197{
198 /* New mode only: */
199 AssertReturnVoid(!m_fLegacyHandling);
200
201 /* If progress-dialog is not yet ended but progress is aborted or completed: */
202 if (!m_fEnded && (!m_comProgress.isOk() || m_comProgress.GetCompleted()))
203 {
204 /* Set progress to 100%: */
205 updateProgressPercentage(100);
206
207 /* Try to close the dialog: */
208 closeProgressDialog();
209 }
210}
211
212void UIProgressDialog::sltHandleWindowStackChange()
213{
214 /* If progress-dialog is not yet ended but progress is aborted or completed: */
215 if (!m_fEnded && (!m_comProgress.isOk() || m_comProgress.GetCompleted()))
216 {
217 /* Try to close the dialog: */
218 closeProgressDialog();
219 }
220}
221
[69000]222void UIProgressDialog::sltCancelOperation()
223{
224 m_pButtonCancel->setEnabled(false);
225 m_comProgress.Cancel();
226}
227
228void UIProgressDialog::prepare()
229{
[43857]230 /* Setup dialog: */
[84029]231 if (m_strTitle.isNull())
232 setWindowTitle(m_comProgress.GetDescription());
233 else
234 setWindowTitle(QString("%1: %2").arg(m_strTitle, m_comProgress.GetDescription()));
[43857]235 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
[68999]236#ifdef VBOX_WS_MAC
237 ::darwinSetHidesAllTitleButtons(this);
238#endif
[25178]239
[69006]240 /* Make sure dialog is handling window stack changes: */
241 connect(&windowManager(), &UIModalWindowManager::sigStackChanged,
242 this, &UIProgressDialog::sltHandleWindowStackChange);
243
[69000]244 /* Prepare: */
[69006]245 prepareEventHandler();
[69000]246 prepareWidgets();
247}
248
[69006]249void UIProgressDialog::prepareEventHandler()
250{
251 if (!m_fLegacyHandling)
252 {
253 /* Create CProgress event handler: */
254 m_pEventHandler = new UIProgressEventHandler(this, m_comProgress);
255 connect(m_pEventHandler, &UIProgressEventHandler::sigProgressPercentageChange,
256 this, &UIProgressDialog::sltHandleProgressPercentageChange);
257 connect(m_pEventHandler, &UIProgressEventHandler::sigProgressTaskComplete,
258 this, &UIProgressDialog::sltHandleProgressTaskComplete);
259 }
260}
261
[69000]262void UIProgressDialog::prepareWidgets()
263{
[43857]264 /* Create main layout: */
265 QHBoxLayout *pMainLayout = new QHBoxLayout(this);
[69000]266 AssertPtrReturnVoid(pMainLayout);
[68999]267 {
268 /* Configure layout: */
[60362]269#ifdef VBOX_WS_MAC
[69000]270 if (m_pImage)
[68999]271 pMainLayout->setContentsMargins(30, 15, 30, 15);
272 else
273 pMainLayout->setContentsMargins(6, 6, 6, 6);
[68998]274#endif
[25178]275
[68999]276 /* If there is image: */
[69000]277 if (m_pImage)
[68999]278 {
279 /* Create image label: */
280 m_pLabelImage = new QLabel;
[69000]281 AssertPtrReturnVoid(m_pLabelImage);
[68999]282 {
283 /* Configure label: */
[69000]284 m_pLabelImage->setPixmap(*m_pImage);
[25178]285
[68999]286 /* Add into layout: */
287 pMainLayout->addWidget(m_pLabelImage);
288 }
289 }
[43857]290
[68999]291 /* Create description layout: */
292 QVBoxLayout *pDescriptionLayout = new QVBoxLayout;
[69000]293 AssertPtrReturnVoid(pDescriptionLayout);
[68999]294 {
295 /* Configure layout: */
[93995]296 pDescriptionLayout->setContentsMargins(0, 0, 0, 0);
[43857]297
[68999]298 /* Add stretch: */
299 pDescriptionLayout->addStretch(1);
[25178]300
[68999]301 /* Create description label: */
302 m_pLabelDescription = new QILabel;
[69000]303 AssertPtrReturnVoid(m_pLabelDescription);
[68999]304 {
305 /* Configure label: */
306 if (m_cOperations > 1)
307 m_pLabelDescription->setText(QString(m_spcszOpDescTpl)
308 .arg(m_comProgress.GetOperationDescription())
309 .arg(m_uCurrentOperation).arg(m_cOperations));
310 else
311 m_pLabelDescription->setText(QString("%1 ...")
312 .arg(m_comProgress.GetOperationDescription()));
[25178]313
[68999]314 /* Add into layout: */
315 pDescriptionLayout->addWidget(m_pLabelDescription, 0, Qt::AlignHCenter);
316 }
[25178]317
[68999]318 /* Create proggress layout: */
319 QHBoxLayout *pProgressLayout = new QHBoxLayout;
[69000]320 AssertPtrReturnVoid(pProgressLayout);
[68999]321 {
322 /* Configure layout: */
[93995]323 pProgressLayout->setContentsMargins(0, 0, 0, 0);
[34479]324
[68999]325 /* Create progress-bar: */
326 m_pProgressBar = new QProgressBar;
[69000]327 AssertPtrReturnVoid(m_pProgressBar);
[68999]328 {
329 /* Configure progress-bar: */
[78969]330 // WORKAROUND:
331 // Based on agreement implemented in r131088 and r131090,
332 // if progress has just one operation with weight equal to 1,
333 // we should make it "infinite" by setting maximum to minimum.
[97765]334 // But be aware that this can and will be overridden by
335 // updated progress percentage if it's changing.
336 if (m_cOperations == 1 && m_uCurrentOperationWeight == 1)
[78969]337 m_pProgressBar->setMaximum(0);
338 else
339 m_pProgressBar->setMaximum(100);
[68999]340 m_pProgressBar->setValue(0);
341
342 /* Add into layout: */
343 pProgressLayout->addWidget(m_pProgressBar, 0, Qt::AlignVCenter);
344 }
345
346 /* Create cancel button: */
347 m_pButtonCancel = new UIMiniCancelButton;
[69000]348 AssertPtrReturnVoid(m_pButtonCancel);
[68999]349 {
350 /* Configure cancel button: */
351 m_fCancelEnabled = m_comProgress.GetCancelable();
352 m_pButtonCancel->setEnabled(m_fCancelEnabled);
353 m_pButtonCancel->setFocusPolicy(Qt::ClickFocus);
[80955]354 connect(m_pButtonCancel, &UIMiniCancelButton::clicked, this, &UIProgressDialog::sltCancelOperation);
[68999]355
356 /* Add into layout: */
357 pProgressLayout->addWidget(m_pButtonCancel, 0, Qt::AlignVCenter);
358 }
359
360 /* Add into layout: */
361 pDescriptionLayout->addLayout(pProgressLayout);
362 }
363
364 /* Create estimation label: */
365 m_pLabelEta = new QILabel;
366 {
367 /* Add into layout: */
368 pDescriptionLayout->addWidget(m_pLabelEta, 0, Qt::AlignLeft | Qt::AlignVCenter);
369 }
370
371 /* Add stretch: */
372 pDescriptionLayout->addStretch(1);
373
374 /* Add into layout: */
375 pMainLayout->addLayout(pDescriptionLayout);
376 }
377 }
378
[43857]379 /* Translate finally: */
[104358]380 sltRetranslateUI();
381 connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
382 this, &UIProgressDialog::sltRetranslateUI);
[25178]383
384 /* The progress dialog will be shown automatically after
385 * the duration is over if progress is not finished yet. */
[69000]386 QTimer::singleShot(m_cMinDuration, this, SLOT(show()));
[25178]387}
388
[69000]389void UIProgressDialog::cleanupWidgets()
[55696]390{
[69000]391 /* Nothing for now. */
392}
393
[69006]394void UIProgressDialog::cleanupEventHandler()
395{
396 if (!m_fLegacyHandling)
397 {
398 /* Destroy CProgress event handler: */
399 delete m_pEventHandler;
400 m_pEventHandler = 0;
401 }
402}
403
[69000]404void UIProgressDialog::cleanup()
405{
[55696]406 /* Wait for CProgress to complete: */
[68999]407 m_comProgress.WaitForCompletion(-1);
[68998]408
[55696]409 /* Call the timer event handling delegate: */
[69006]410 if (m_fLegacyHandling)
411 handleTimerEvent();
[55696]412
[69000]413 /* Cleanup: */
[69006]414 cleanupEventHandler();
[69000]415 cleanupWidgets();
[25178]416}
417
[69000]418void UIProgressDialog::updateProgressState()
[25178]419{
[69000]420 /* Mark progress canceled if so: */
421 if (m_comProgress.GetCanceled())
422 m_pLabelEta->setText(tr("Canceling..."));
423 /* Update the progress dialog otherwise: */
[55696]424 else
[25178]425 {
[68998]426 /* Update ETA: */
[68999]427 const long iNewTime = m_comProgress.GetTimeRemaining();
428 long iSeconds;
429 long iMinutes;
430 long iHours;
431 long iDays;
[25178]432
[68999]433 iSeconds = iNewTime < 0 ? 0 : iNewTime;
434 iMinutes = iSeconds / 60;
435 iSeconds -= iMinutes * 60;
436 iHours = iMinutes / 60;
437 iMinutes -= iHours * 60;
438 iDays = iHours / 24;
439 iHours -= iDays * 24;
[25178]440
[90967]441 const QString strDays = UITranslator::daysToString(iDays);
442 const QString strHours = UITranslator::hoursToString(iHours);
443 const QString strMinutes = UITranslator::minutesToString(iMinutes);
444 const QString strSeconds = UITranslator::secondsToString(iSeconds);
[25178]445
[68999]446 const QString strTwoComp = tr("%1, %2 remaining", "You may wish to translate this more like \"Time remaining: %1, %2\"");
447 const QString strOneComp = tr("%1 remaining", "You may wish to translate this more like \"Time remaining: %1\"");
[25178]448
[68999]449 if (iDays > 1 && iHours > 0)
450 m_pLabelEta->setText(strTwoComp.arg(strDays).arg(strHours));
451 else if (iDays > 1)
452 m_pLabelEta->setText(strOneComp.arg(strDays));
453 else if (iDays > 0 && iHours > 0)
454 m_pLabelEta->setText(strTwoComp.arg(strDays).arg(strHours));
455 else if (iDays > 0 && iMinutes > 5)
456 m_pLabelEta->setText(strTwoComp.arg(strDays).arg(strMinutes));
457 else if (iDays > 0)
458 m_pLabelEta->setText(strOneComp.arg(strDays));
459 else if (iHours > 2)
460 m_pLabelEta->setText(strOneComp.arg(strHours));
461 else if (iHours > 0 && iMinutes > 0)
462 m_pLabelEta->setText(strTwoComp.arg(strHours).arg(strMinutes));
463 else if (iHours > 0)
464 m_pLabelEta->setText(strOneComp.arg(strHours));
465 else if (iMinutes > 2)
466 m_pLabelEta->setText(strOneComp.arg(strMinutes));
467 else if (iMinutes > 0 && iSeconds > 5)
468 m_pLabelEta->setText(strTwoComp.arg(strMinutes).arg(strSeconds));
469 else if (iMinutes > 0)
470 m_pLabelEta->setText(strOneComp.arg(strMinutes));
471 else if (iSeconds > 5)
472 m_pLabelEta->setText(strOneComp.arg(strSeconds));
473 else if (iSeconds > 0)
474 m_pLabelEta->setText(tr("A few seconds remaining"));
[25178]475 else
[68999]476 m_pLabelEta->clear();
[25178]477
[68998]478 /* Then operation text (if changed): */
[68999]479 ulong uNewOp = m_comProgress.GetOperation() + 1;
480 if (uNewOp != m_uCurrentOperation)
[25178]481 {
[68999]482 m_uCurrentOperation = uNewOp;
[78969]483 m_uCurrentOperationWeight = m_comProgress.GetOperationWeight();
[68999]484 m_pLabelDescription->setText(QString(m_spcszOpDescTpl)
485 .arg(m_comProgress.GetOperationDescription())
486 .arg(m_uCurrentOperation).arg(m_cOperations));
[25178]487 }
[43855]488
489 /* Then cancel button: */
[68999]490 m_fCancelEnabled = m_comProgress.GetCancelable();
491 m_pButtonCancel->setEnabled(m_fCancelEnabled);
[69000]492 }
493}
[56495]494
[69000]495void UIProgressDialog::updateProgressPercentage(int iPercent /* = -1 */)
496{
[97765]497 /* Handle default call: */
[69000]498 if (iPercent == -1)
499 iPercent = m_comProgress.GetPercent();
[97765]500
501 /* Make sure percentage is reflected properly
502 * if progress was "infinite" initially: */
503 if ( m_pProgressBar->maximum() == 0
504 && iPercent > 0 && iPercent < 100)
505 m_pProgressBar->setMaximum(100);
506
507 /* Update operation percentage: */
[69000]508 m_pProgressBar->setValue(iPercent);
509
510 /* Notify listeners about the operation progress update: */
511 emit sigProgressChange(m_cOperations, m_comProgress.GetOperationDescription(),
512 m_comProgress.GetOperation() + 1, iPercent);
513}
514
[69004]515void UIProgressDialog::closeProgressDialog()
516{
517 /* If window is on the top of the stack: */
518 if (windowManager().isWindowOnTheTopOfTheModalWindowStack(this))
519 {
520 /* Progress completed? */
521 if (m_comProgress.isOk())
522 done(Accepted);
523 /* Or aborted? */
524 else
525 done(Rejected);
526
527 /* Mark progress-dialog finished: */
528 m_fEnded = true;
529 }
530}
531
[69000]532void UIProgressDialog::handleTimerEvent()
533{
[69006]534 /* Old mode only: */
535 AssertReturnVoid(m_fLegacyHandling);
536
[69004]537 /* If progress-dialog is ended: */
538 if (m_fEnded)
[69000]539 {
[69004]540 // WORKAROUND:
541 // We should hide progress-dialog if it was already ended but not yet closed. This could happen
542 // in case if some other modal dialog prevents our event-loop from being exit overlapping 'this'.
543 /* If window is on the top of the stack and still shown: */
544 if (!isHidden() && windowManager().isWindowOnTheTopOfTheModalWindowStack(this))
545 hide();
546
[69000]547 return;
[42261]548 }
[69000]549
[69004]550 /* If progress-dialog is not yet ended but progress is aborted or completed: */
551 if (!m_comProgress.isOk() || m_comProgress.GetCompleted())
[69000]552 {
[69004]553 /* Set progress to 100%: */
[69005]554 updateProgressPercentage(100);
[69000]555
[69004]556 /* Try to close the dialog: */
557 return closeProgressDialog();
[69000]558 }
559
560 /* Update progress: */
561 updateProgressState();
562 updateProgressPercentage();
[25178]563}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use