1
/***************************************************************************
2
* Copyright (C) 2008 by Volker Lanz <vl@fidra.de> *
4
* This program is free software; you can redistribute it and/or modify *
5
* it under the terms of the GNU General Public License as published by *
6
* the Free Software Foundation; either version 2 of the License, or *
7
* (at your option) any later version. *
9
* This program is distributed in the hope that it will be useful, *
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12
* GNU General Public License for more details. *
14
* You should have received a copy of the GNU General Public License *
15
* along with this program; if not, write to the *
16
* Free Software Foundation, Inc., *
17
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18
***************************************************************************/
20
#include "gui/progressdialog.h"
22
#include "gui/progressdialogwidget.h"
23
#include "gui/progressdetailswidget.h"
25
#include "core/operationrunner.h"
27
#include "ops/operation.h"
31
#include "util/report.h"
33
#include <QCloseEvent>
39
#include <kapplication.h>
41
#include <kmessagebox.h>
42
#include <kfiledialog.h>
44
#include <ktemporaryfile.h>
45
#include <kaboutdata.h>
46
#include <ktextedit.h>
48
const QString ProgressDialog::m_TimeFormat = "hh:mm:ss";
50
/** Creates a new ProgressDialog
51
@param parent pointer to the parent widget
52
@param orunner the OperationRunner whose progress this dialog is showing
54
ProgressDialog::ProgressDialog(QWidget* parent, OperationRunner& orunner) :
56
m_ProgressDialogWidget(new ProgressDialogWidget(this)),
57
m_ProgressDetailsWidget(new ProgressDetailsWidget(this)),
58
m_OperationRunner(orunner),
60
m_SavedParentTitle(parent->windowTitle()),
63
m_CurrentOpItem(NULL),
64
m_CurrentJobItem(NULL)
66
setMainWidget(&dialogWidget());
67
setDetailsWidget(&detailsWidget());
69
showButtonSeparator(true);
70
setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Details);
72
dialogWidget().treeTasks().setColumnWidth(0, width() * 0.8);
76
restoreDialogSize(KConfigGroup(KGlobal::config(), "progressDialog"));
79
/** Destroys a ProgressDialog */
80
ProgressDialog::~ProgressDialog()
82
KConfigGroup kcg(KGlobal::config(), "progressDialog");
87
void ProgressDialog::setupConnections()
89
connect(&operationRunner(), SIGNAL(progressSub(int)), &dialogWidget().progressSub(), SLOT(setValue(int)));
90
connect(&operationRunner(), SIGNAL(finished()), SLOT(onAllOpsFinished()));
91
connect(&operationRunner(), SIGNAL(cancelled()), SLOT(onAllOpsCancelled()));
92
connect(&operationRunner(), SIGNAL(error()), SLOT(onAllOpsError()));
93
connect(&operationRunner(), SIGNAL(opStarted(int, Operation*)), SLOT(onOpStarted(int, Operation*)));
94
connect(&operationRunner(), SIGNAL(opFinished(int, Operation*)), SLOT(onOpFinished(int, Operation*)));
95
connect(&timer(), SIGNAL(timeout()), SLOT(onSecondElapsed()));
96
connect(&detailsWidget().buttonSave(), SIGNAL(clicked()), SLOT(saveReport()));
97
connect(&detailsWidget().buttonBrowser(), SIGNAL(clicked()), SLOT(browserReport()));
100
/** Shows the dialog */
101
void ProgressDialog::show()
103
foreach (QWidget* w, kapp->topLevelWidgets())
104
w->setEnabled(false);
108
setStatus(i18nc("@info:progress", "Setting up..."));
112
dialogWidget().progressTotal().setRange(0, operationRunner().numJobs());
113
dialogWidget().progressTotal().setValue(0);
115
dialogWidget().treeTasks().clear();
116
showButton(KDialog::Ok, false);
117
showButton(KDialog::Cancel, true);
122
setLastReportUpdate(0);
124
onSecondElapsed(); // resets the total time output label
129
void ProgressDialog::resetReport()
132
m_Report = new Report(NULL);
134
detailsWidget().editReport().clear();
135
detailsWidget().editReport().setCursorWidth(0);
136
detailsWidget().buttonSave().setEnabled(false);
137
detailsWidget().buttonBrowser().setEnabled(false);
139
connect(&report(), SIGNAL(outputChanged()), SLOT(updateReport()));
142
void ProgressDialog::closeEvent(QCloseEvent* e)
145
slotButtonClicked(operationRunner().isRunning() ? KDialog::Cancel : KDialog::Ok);
148
void ProgressDialog::slotButtonClicked(int button)
150
if (button == KDialog::Details)
152
KDialog::slotButtonClicked(button);
157
if (button == KDialog::Cancel && operationRunner().isRunning())
160
if (operationRunner().isCancelling())
163
KApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
165
enableButtonCancel(false);
166
setStatus(i18nc("@info:progress", "Waiting for operation to finish..."));
168
dialogWidget().repaint();
170
// suspend the runner, so it doesn't happily carry on while the user decides
171
// if he really wants to cancel
172
operationRunner().suspendMutex().lock();
173
enableButtonCancel(true);
175
KApplication::restoreOverrideCursor();
177
if (KMessageBox::questionYesNo(this, i18nc("@info", "Do you really want to cancel?"), i18nc("@title:window", "Cancel Running Operations"), KGuiItem(i18nc("@action:button", "Yes, Cancel Operations")), KStandardGuiItem::no()) == KMessageBox::Yes)
178
// in the meantime while we were showing the messagebox, the runner might have finished.
179
if (operationRunner().isRunning())
180
operationRunner().cancel();
182
operationRunner().suspendMutex().unlock();
187
foreach (QWidget* w, kapp->topLevelWidgets())
190
parentWidget()->setWindowTitle(savedParentTitle());
195
void ProgressDialog::onAllOpsFinished()
197
allOpsDone(i18nc("@info:progress", "All operations successfully finished."));
200
void ProgressDialog::onAllOpsCancelled()
202
allOpsDone(i18nc("@info:progress", "Operations cancelled."));
205
void ProgressDialog::onAllOpsError()
207
allOpsDone(i18nc("@info:progress", "There were errors while applying operations. Aborted."));
210
void ProgressDialog::allOpsDone(const QString& msg)
212
dialogWidget().progressTotal().setValue(operationRunner().numJobs());
213
showButton(KDialog::Cancel, false);
214
showButton(KDialog::Ok, true);
215
detailsWidget().buttonSave().setEnabled(true);
216
detailsWidget().buttonBrowser().setEnabled(true);
223
void ProgressDialog::updateReport(bool force)
225
// Rendering the HTML in the KTextEdit is extremely expensive. So make sure not to do that
226
// unnecessarily and not too often:
227
// (1) If the widget isn't visible, don't update.
228
// (2) Also don't update if the last update was n msecs ago, BUT
229
// (3) DO update if we're being forced to.
230
if (force || (detailsWidget().isVisible() && time().elapsed() - lastReportUpdate() > 2000))
232
detailsWidget().editReport().setHtml("<html><body>" + report().toHtml() + "</body></html>");
233
detailsWidget().editReport().moveCursor(QTextCursor::End);
234
detailsWidget().editReport().ensureCursorVisible();
236
setLastReportUpdate(time().elapsed());
240
void ProgressDialog::onOpStarted(int num, Operation* op)
242
addTaskOutput(num, *op);
243
setStatus(op->description());
245
dialogWidget().progressSub().setValue(0);
246
dialogWidget().progressSub().setRange(0, op->totalProgress());
248
connect(op, SIGNAL(jobStarted(Job*, Operation*)), SLOT(onJobStarted(Job*, Operation*)));
249
connect(op, SIGNAL(jobFinished(Job*, Operation*)), SLOT(onJobFinished(Job*, Operation*)));
252
void ProgressDialog::onJobStarted(Job* job, Operation* op)
254
for (qint32 i = 0; i < dialogWidget().treeTasks().topLevelItemCount(); i++)
256
QTreeWidgetItem* item = dialogWidget().treeTasks().topLevelItem(i);
258
if (item == NULL || reinterpret_cast<const Operation*>(item->data(0, Qt::UserRole).toULongLong()) != op)
261
QTreeWidgetItem* child = new QTreeWidgetItem();
262
child->setText(0, job->description());
263
child->setIcon(0, job->statusIcon());
264
child->setText(1, QTime(0, 0).toString(timeFormat()));
265
item->addChild(child);
266
dialogWidget().treeTasks().scrollToBottom();
267
setCurrentJobItem(child);
272
void ProgressDialog::onJobFinished(Job* job, Operation* op)
274
if (currentJobItem())
275
currentJobItem()->setIcon(0, job->statusIcon());
277
setCurrentJobItem(NULL);
279
const int current = dialogWidget().progressTotal().value();
280
dialogWidget().progressTotal().setValue(current + 1);
282
setParentTitle(op->description());
286
void ProgressDialog::onOpFinished(int num, Operation* op)
290
currentOpItem()->setText(0, opDesc(num, *op));
291
currentOpItem()->setIcon(0, op->statusIcon());
294
setCurrentOpItem(NULL);
296
setStatus(op->description());
298
dialogWidget().progressSub().setValue(op->totalProgress());
302
void ProgressDialog::setParentTitle(const QString& s)
304
const int percent = dialogWidget().progressTotal().value() * 100 / dialogWidget().progressTotal().maximum();
305
parentWidget()->setWindowTitle(QString::number(percent) + "% - " + s + " - " + savedParentTitle());
308
void ProgressDialog::setStatus(const QString& s)
311
dialogWidget().status().setText(s);
316
QString ProgressDialog::opDesc(int num, const Operation& op) const
318
return i18nc("@info:progress", "[%1/%2] - %3: %4", num, operationRunner().numOperations(), op.statusText(), op.description());
321
void ProgressDialog::addTaskOutput(int num, const Operation& op)
323
QTreeWidgetItem* item = new QTreeWidgetItem();
324
item->setIcon(0, op.statusIcon());
325
item->setText(0, opDesc(num, op));
326
item->setText(1, QTime(0, 0).toString(timeFormat()));
329
f.setWeight(QFont::Bold);
333
item->setData(0, Qt::UserRole, reinterpret_cast<const qulonglong>(&op));
334
dialogWidget().treeTasks().addTopLevelItem(item);
335
dialogWidget().treeTasks().scrollToBottom();
336
setCurrentOpItem(item);
339
void ProgressDialog::onSecondElapsed()
341
if (currentJobItem())
343
QTime t = QTime::fromString(currentJobItem()->text(1), timeFormat()).addSecs(1);
344
currentJobItem()->setText(1, t.toString(timeFormat()));
349
QTime t = QTime::fromString(currentOpItem()->text(1), timeFormat()).addSecs(1);
350
currentOpItem()->setText(1, t.toString(timeFormat()));;
353
const QTime outputTime = QTime(0, 0).addMSecs(time().elapsed());
354
dialogWidget().totalTime().setText(i18nc("@info:progress", "Total Time: %1", outputTime.toString(timeFormat())));
357
void ProgressDialog::keyPressEvent(QKeyEvent* e)
365
if (isButtonEnabled(KDialog::Ok))
366
slotButtonClicked(KDialog::Ok);
370
slotButtonClicked(isButtonEnabled(KDialog::Cancel) ? KDialog::Cancel : KDialog::Ok);
378
void ProgressDialog::saveReport()
380
QString fileName = KFileDialog::getSaveFileName(KUrl("kfiledialog://saveReport"));
382
if (fileName.isEmpty())
385
if (!QFile::exists(fileName) || KMessageBox::warningContinueCancel(this, i18nc("@info", "Do you want to overwrite the existing file <filename>%1</filename>?", fileName), i18nc("@title:window", "Overwrite Existing File?"), KGuiItem(i18nc("@action:button", "&Overwrite File")), KStandardGuiItem::cancel()) == KMessageBox::Continue)
387
QFile file(fileName);
389
if (file.open(QIODevice::WriteOnly | QIODevice::Truncate))
391
file.write(Report::htmlHeader().toUtf8());
392
file.write(report().toHtml().toUtf8());
393
file.write(Report::htmlFooter().toUtf8());
398
KMessageBox::sorry(this, i18nc("@info", "Could not open file <filename>%1</filename> for writing.", fileName), i18nc("@title:window", "Could Not Save Report."));
402
void ProgressDialog::browserReport()
406
// Make sure the temp file is created somewhere another user can read it: KRun::runUrl() will open
407
// the file as the logged in user, not as the user running our application.
408
file.setFileTemplate("/tmp/" + KGlobal::mainComponent().aboutData()->appName() + "-XXXXXX.html");
409
file.setAutoRemove(false);
413
file.write(Report::htmlHeader().toUtf8());
414
file.write(report().toHtml().toUtf8());
415
file.write(Report::htmlFooter().toUtf8());
417
// set the temp file's permission for everyone to read it.
418
file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther);
420
if (!KRun::runUrl(file.fileName(), "text/html", this, true))
421
KMessageBox::sorry(this, i18nc("@info", "The configured external browser could not be run. Please check your settings."), i18nc("@title:window", "Could Not Launch Browser."));
424
KMessageBox::sorry(this, i18nc("@info", "Could not create temporary file <filename>%1</filename> for writing.", file.fileName()), i18nc("@title:window", "Could Not Launch Browser."));