VirtualBox

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

Last change on this file since 100347 was 98103, checked in by vboxsync, 22 months ago

Copyright year updates by scm.

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