VirtualBox

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

Last change on this file was 104358, checked in by vboxsync, 4 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
Line 
1/* $Id: UIProgressDialog.cpp 104358 2024-04-18 05:33:40Z 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 <QApplication>
30#include <QCloseEvent>
31#include <QEventLoop>
32#include <QProgressBar>
33#include <QTime>
34#include <QTimer>
35#include <QVBoxLayout>
36
37/* GUI includes: */
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"
47#include "UITranslator.h"
48#include "UITranslationEventListener.h"
49#ifdef VBOX_WS_MAC
50# include "VBoxUtils-darwin.h"
51#endif
52
53/* COM includes: */
54#include "CEventListener.h"
55#include "CEventSource.h"
56#include "CProgress.h"
57
58
59const char *UIProgressDialog::m_spcszOpDescTpl = "%1 ... (%2/%3)";
60
61UIProgressDialog::UIProgressDialog(CProgress &comProgress,
62 const QString &strTitle,
63 QPixmap *pImage /* = 0 */,
64 int cMinDuration /* = 2000 */,
65 QWidget *pParent /* = 0 */)
66 : QIDialog(pParent, Qt::MSWindowsFixedSizeDialogHint | Qt::WindowTitleHint)
67 , m_comProgress(comProgress)
68 , m_strTitle(strTitle)
69 , m_pImage(pImage)
70 , m_cMinDuration(cMinDuration)
71 , m_fLegacyHandling(gEDataManager->legacyProgressHandlingRequested())
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)
79 , m_uCurrentOperationWeight(m_comProgress.GetOperationWeight())
80 , m_fCancelEnabled(false)
81 , m_fEnded(false)
82 , m_pEventHandler(0)
83{
84 /* Prepare: */
85 prepare();
86}
87
88UIProgressDialog::~UIProgressDialog()
89{
90 /* Cleanup: */
91 cleanup();
92}
93
94void UIProgressDialog::sltRetranslateUI()
95{
96 m_pButtonCancel->setText(tr("&Cancel"));
97 m_pButtonCancel->setToolTip(tr("Cancel the current operation"));
98}
99
100int UIProgressDialog::run(int cRefreshInterval)
101{
102 /* Make sure progress hasn't finished already: */
103 if (!m_comProgress.isOk() || m_comProgress.GetCompleted())
104 {
105 /* Progress completed? */
106 if (m_comProgress.isOk())
107 return Accepted;
108 /* Or aborted? */
109 else
110 return Rejected;
111 }
112
113 /* Start refresh timer (if necessary): */
114 int id = 0;
115 if (m_fLegacyHandling)
116 id = startTimer(cRefreshInterval);
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). */
122#ifndef VBOX_WS_MAC
123 if (m_fCancelEnabled)
124 QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
125 else
126 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
127#endif /* VBOX_WS_MAC */
128
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;
134
135 /* Holds the modal loop, but don't show the window immediately: */
136 execute(false);
137
138 /* Are we still valid? */
139 if (guard.isNull())
140 return Rejected;
141 }
142
143 /* Kill refresh timer (if necessary): */
144 if (m_fLegacyHandling)
145 killTimer(id);
146
147#ifndef VBOX_WS_MAC
148 /* Reset the busy cursor */
149 QApplication::restoreOverrideCursor();
150#endif /* VBOX_WS_MAC */
151
152 return result();
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
172void UIProgressDialog::timerEvent(QTimerEvent *)
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
186void UIProgressDialog::sltHandleProgressPercentageChange(const QUuid &, const int iPercent)
187{
188 /* New mode only: */
189 AssertReturnVoid(!m_fLegacyHandling);
190
191 /* Update progress: */
192 updateProgressState();
193 updateProgressPercentage(iPercent);
194}
195
196void UIProgressDialog::sltHandleProgressTaskComplete(const QUuid &)
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
222void UIProgressDialog::sltCancelOperation()
223{
224 m_pButtonCancel->setEnabled(false);
225 m_comProgress.Cancel();
226}
227
228void UIProgressDialog::prepare()
229{
230 /* Setup dialog: */
231 if (m_strTitle.isNull())
232 setWindowTitle(m_comProgress.GetDescription());
233 else
234 setWindowTitle(QString("%1: %2").arg(m_strTitle, m_comProgress.GetDescription()));
235 setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
236#ifdef VBOX_WS_MAC
237 ::darwinSetHidesAllTitleButtons(this);
238#endif
239
240 /* Make sure dialog is handling window stack changes: */
241 connect(&windowManager(), &UIModalWindowManager::sigStackChanged,
242 this, &UIProgressDialog::sltHandleWindowStackChange);
243
244 /* Prepare: */
245 prepareEventHandler();
246 prepareWidgets();
247}
248
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
262void UIProgressDialog::prepareWidgets()
263{
264 /* Create main layout: */
265 QHBoxLayout *pMainLayout = new QHBoxLayout(this);
266 AssertPtrReturnVoid(pMainLayout);
267 {
268 /* Configure layout: */
269#ifdef VBOX_WS_MAC
270 if (m_pImage)
271 pMainLayout->setContentsMargins(30, 15, 30, 15);
272 else
273 pMainLayout->setContentsMargins(6, 6, 6, 6);
274#endif
275
276 /* If there is image: */
277 if (m_pImage)
278 {
279 /* Create image label: */
280 m_pLabelImage = new QLabel;
281 AssertPtrReturnVoid(m_pLabelImage);
282 {
283 /* Configure label: */
284 m_pLabelImage->setPixmap(*m_pImage);
285
286 /* Add into layout: */
287 pMainLayout->addWidget(m_pLabelImage);
288 }
289 }
290
291 /* Create description layout: */
292 QVBoxLayout *pDescriptionLayout = new QVBoxLayout;
293 AssertPtrReturnVoid(pDescriptionLayout);
294 {
295 /* Configure layout: */
296 pDescriptionLayout->setContentsMargins(0, 0, 0, 0);
297
298 /* Add stretch: */
299 pDescriptionLayout->addStretch(1);
300
301 /* Create description label: */
302 m_pLabelDescription = new QILabel;
303 AssertPtrReturnVoid(m_pLabelDescription);
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()));
313
314 /* Add into layout: */
315 pDescriptionLayout->addWidget(m_pLabelDescription, 0, Qt::AlignHCenter);
316 }
317
318 /* Create proggress layout: */
319 QHBoxLayout *pProgressLayout = new QHBoxLayout;
320 AssertPtrReturnVoid(pProgressLayout);
321 {
322 /* Configure layout: */
323 pProgressLayout->setContentsMargins(0, 0, 0, 0);
324
325 /* Create progress-bar: */
326 m_pProgressBar = new QProgressBar;
327 AssertPtrReturnVoid(m_pProgressBar);
328 {
329 /* Configure progress-bar: */
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.
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)
337 m_pProgressBar->setMaximum(0);
338 else
339 m_pProgressBar->setMaximum(100);
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;
348 AssertPtrReturnVoid(m_pButtonCancel);
349 {
350 /* Configure cancel button: */
351 m_fCancelEnabled = m_comProgress.GetCancelable();
352 m_pButtonCancel->setEnabled(m_fCancelEnabled);
353 m_pButtonCancel->setFocusPolicy(Qt::ClickFocus);
354 connect(m_pButtonCancel, &UIMiniCancelButton::clicked, this, &UIProgressDialog::sltCancelOperation);
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
379 /* Translate finally: */
380 sltRetranslateUI();
381 connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
382 this, &UIProgressDialog::sltRetranslateUI);
383
384 /* The progress dialog will be shown automatically after
385 * the duration is over if progress is not finished yet. */
386 QTimer::singleShot(m_cMinDuration, this, SLOT(show()));
387}
388
389void UIProgressDialog::cleanupWidgets()
390{
391 /* Nothing for now. */
392}
393
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
404void UIProgressDialog::cleanup()
405{
406 /* Wait for CProgress to complete: */
407 m_comProgress.WaitForCompletion(-1);
408
409 /* Call the timer event handling delegate: */
410 if (m_fLegacyHandling)
411 handleTimerEvent();
412
413 /* Cleanup: */
414 cleanupEventHandler();
415 cleanupWidgets();
416}
417
418void UIProgressDialog::updateProgressState()
419{
420 /* Mark progress canceled if so: */
421 if (m_comProgress.GetCanceled())
422 m_pLabelEta->setText(tr("Canceling..."));
423 /* Update the progress dialog otherwise: */
424 else
425 {
426 /* Update ETA: */
427 const long iNewTime = m_comProgress.GetTimeRemaining();
428 long iSeconds;
429 long iMinutes;
430 long iHours;
431 long iDays;
432
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;
440
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);
445
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\"");
448
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"));
475 else
476 m_pLabelEta->clear();
477
478 /* Then operation text (if changed): */
479 ulong uNewOp = m_comProgress.GetOperation() + 1;
480 if (uNewOp != m_uCurrentOperation)
481 {
482 m_uCurrentOperation = uNewOp;
483 m_uCurrentOperationWeight = m_comProgress.GetOperationWeight();
484 m_pLabelDescription->setText(QString(m_spcszOpDescTpl)
485 .arg(m_comProgress.GetOperationDescription())
486 .arg(m_uCurrentOperation).arg(m_cOperations));
487 }
488
489 /* Then cancel button: */
490 m_fCancelEnabled = m_comProgress.GetCancelable();
491 m_pButtonCancel->setEnabled(m_fCancelEnabled);
492 }
493}
494
495void UIProgressDialog::updateProgressPercentage(int iPercent /* = -1 */)
496{
497 /* Handle default call: */
498 if (iPercent == -1)
499 iPercent = m_comProgress.GetPercent();
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: */
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
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
532void UIProgressDialog::handleTimerEvent()
533{
534 /* Old mode only: */
535 AssertReturnVoid(m_fLegacyHandling);
536
537 /* If progress-dialog is ended: */
538 if (m_fEnded)
539 {
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
547 return;
548 }
549
550 /* If progress-dialog is not yet ended but progress is aborted or completed: */
551 if (!m_comProgress.isOk() || m_comProgress.GetCompleted())
552 {
553 /* Set progress to 100%: */
554 updateProgressPercentage(100);
555
556 /* Try to close the dialog: */
557 return closeProgressDialog();
558 }
559
560 /* Update progress: */
561 updateProgressState();
562 updateProgressPercentage();
563}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use