VirtualBox

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

© 2023 Oracle
ContactPrivacy policyTerms of Use