~ubuntu-branches/ubuntu/oneiric/partitionmanager/oneiric

« back to all changes in this revision

Viewing changes to src/gui/progressdialog.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Anthony Mercatante
  • Date: 2009-01-23 17:57:36 UTC
  • Revision ID: james.westby@ubuntu.com-20090123175736-2ltrhgg3m55dokbm
Tags: upstream-1.0.0~beta1a
ImportĀ upstreamĀ versionĀ 1.0.0~beta1a

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***************************************************************************
 
2
 *   Copyright (C) 2008 by Volker Lanz <vl@fidra.de>                       *
 
3
 *                                                                         *
 
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.                                   *
 
8
 *                                                                         *
 
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.                          *
 
13
 *                                                                         *
 
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
 ***************************************************************************/
 
19
 
 
20
#include "gui/progressdialog.h"
 
21
 
 
22
#include "gui/progressdialogwidget.h"
 
23
#include "gui/progressdetailswidget.h"
 
24
 
 
25
#include "core/operationrunner.h"
 
26
 
 
27
#include "ops/operation.h"
 
28
 
 
29
#include "jobs/job.h"
 
30
 
 
31
#include "util/report.h"
 
32
 
 
33
#include <QCloseEvent>
 
34
#include <QTime>
 
35
#include <QFont>
 
36
#include <QKeyEvent>
 
37
#include <QFile>
 
38
 
 
39
#include <kapplication.h>
 
40
#include <kdebug.h>
 
41
#include <kmessagebox.h>
 
42
#include <kfiledialog.h>
 
43
#include <krun.h>
 
44
#include <ktemporaryfile.h>
 
45
#include <kaboutdata.h>
 
46
#include <ktextedit.h>
 
47
 
 
48
const QString ProgressDialog::m_TimeFormat = "hh:mm:ss";
 
49
 
 
50
/** Creates a new ProgressDialog
 
51
        @param parent pointer to the parent widget
 
52
        @param orunner the OperationRunner whose progress this dialog is showing
 
53
*/
 
54
ProgressDialog::ProgressDialog(QWidget* parent, OperationRunner& orunner) :
 
55
        KDialog(parent),
 
56
        m_ProgressDialogWidget(new ProgressDialogWidget(this)),
 
57
        m_ProgressDetailsWidget(new ProgressDetailsWidget(this)),
 
58
        m_OperationRunner(orunner),
 
59
        m_Report(NULL),
 
60
        m_SavedParentTitle(parent->windowTitle()),
 
61
        m_Timer(this),
 
62
        m_Time(),
 
63
        m_CurrentOpItem(NULL),
 
64
        m_CurrentJobItem(NULL)
 
65
{
 
66
        setMainWidget(&dialogWidget());
 
67
        setDetailsWidget(&detailsWidget());
 
68
 
 
69
        showButtonSeparator(true);
 
70
        setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Details);
 
71
 
 
72
        dialogWidget().treeTasks().setColumnWidth(0, width() * 0.8);
 
73
 
 
74
        setupConnections();
 
75
 
 
76
        restoreDialogSize(KConfigGroup(KGlobal::config(), "progressDialog"));
 
77
}
 
78
 
 
79
/** Destroys a ProgressDialog */
 
80
ProgressDialog::~ProgressDialog()
 
81
{
 
82
        KConfigGroup kcg(KGlobal::config(), "progressDialog");
 
83
        saveDialogSize(kcg);
 
84
        delete m_Report;
 
85
}
 
86
 
 
87
void ProgressDialog::setupConnections()
 
88
{
 
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()));
 
98
}
 
99
 
 
100
/** Shows the dialog */
 
101
void ProgressDialog::show()
 
102
{
 
103
        foreach (QWidget* w, kapp->topLevelWidgets())
 
104
                w->setEnabled(false);
 
105
 
 
106
        setEnabled(true);
 
107
 
 
108
        setStatus(i18nc("@info:progress", "Setting up..."));
 
109
 
 
110
        resetReport();
 
111
 
 
112
        dialogWidget().progressTotal().setRange(0, operationRunner().numJobs());
 
113
        dialogWidget().progressTotal().setValue(0);
 
114
 
 
115
        dialogWidget().treeTasks().clear();
 
116
        showButton(KDialog::Ok, false);
 
117
        showButton(KDialog::Cancel, true);
 
118
 
 
119
        timer().start(1000);
 
120
        time().start();
 
121
 
 
122
        setLastReportUpdate(0);
 
123
 
 
124
        onSecondElapsed(); // resets the total time output label
 
125
 
 
126
        KDialog::show();
 
127
}
 
128
 
 
129
void ProgressDialog::resetReport()
 
130
{
 
131
        delete m_Report;
 
132
        m_Report = new Report(NULL);
 
133
 
 
134
        detailsWidget().editReport().clear();
 
135
        detailsWidget().editReport().setCursorWidth(0);
 
136
        detailsWidget().buttonSave().setEnabled(false);
 
137
        detailsWidget().buttonBrowser().setEnabled(false);
 
138
 
 
139
        connect(&report(), SIGNAL(outputChanged()), SLOT(updateReport()));
 
140
}
 
141
 
 
142
void ProgressDialog::closeEvent(QCloseEvent* e)
 
143
{
 
144
        e->ignore();
 
145
        slotButtonClicked(operationRunner().isRunning() ? KDialog::Cancel : KDialog::Ok);
 
146
}
 
147
 
 
148
void ProgressDialog::slotButtonClicked(int button)
 
149
{
 
150
        if (button == KDialog::Details)
 
151
        {
 
152
                KDialog::slotButtonClicked(button);
 
153
                updateReport(true);
 
154
                return;
 
155
        }
 
156
 
 
157
        if (button == KDialog::Cancel && operationRunner().isRunning())
 
158
        {
 
159
                // only cancel once
 
160
                if (operationRunner().isCancelling())
 
161
                        return;
 
162
 
 
163
                KApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
 
164
 
 
165
                enableButtonCancel(false);
 
166
                setStatus(i18nc("@info:progress", "Waiting for operation to finish..."));
 
167
                repaint();
 
168
                dialogWidget().repaint();
 
169
 
 
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);
 
174
 
 
175
                KApplication::restoreOverrideCursor();
 
176
 
 
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();
 
181
 
 
182
                operationRunner().suspendMutex().unlock();
 
183
 
 
184
                return;
 
185
        }
 
186
 
 
187
        foreach (QWidget* w, kapp->topLevelWidgets())
 
188
                w->setEnabled(true);
 
189
 
 
190
        parentWidget()->setWindowTitle(savedParentTitle());
 
191
 
 
192
        KDialog::accept();
 
193
}
 
194
 
 
195
void ProgressDialog::onAllOpsFinished()
 
196
{
 
197
        allOpsDone(i18nc("@info:progress", "All operations successfully finished."));
 
198
}
 
199
 
 
200
void ProgressDialog::onAllOpsCancelled()
 
201
{
 
202
        allOpsDone(i18nc("@info:progress", "Operations cancelled."));
 
203
}
 
204
 
 
205
void ProgressDialog::onAllOpsError()
 
206
{
 
207
        allOpsDone(i18nc("@info:progress", "There were errors while applying operations. Aborted."));
 
208
}
 
209
 
 
210
void ProgressDialog::allOpsDone(const QString& msg)
 
211
{
 
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);
 
217
        timer().stop();
 
218
        updateReport(true);
 
219
 
 
220
        setStatus(msg);
 
221
}
 
222
 
 
223
void ProgressDialog::updateReport(bool force)
 
224
{
 
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))
 
231
        {
 
232
                detailsWidget().editReport().setHtml("<html><body>" + report().toHtml() + "</body></html>");
 
233
                detailsWidget().editReport().moveCursor(QTextCursor::End);
 
234
                detailsWidget().editReport().ensureCursorVisible();
 
235
 
 
236
                setLastReportUpdate(time().elapsed());
 
237
        }
 
238
}
 
239
 
 
240
void ProgressDialog::onOpStarted(int num, Operation* op)
 
241
{
 
242
        addTaskOutput(num, *op);
 
243
        setStatus(op->description());
 
244
 
 
245
        dialogWidget().progressSub().setValue(0);
 
246
        dialogWidget().progressSub().setRange(0, op->totalProgress());
 
247
 
 
248
        connect(op, SIGNAL(jobStarted(Job*, Operation*)), SLOT(onJobStarted(Job*, Operation*)));
 
249
        connect(op, SIGNAL(jobFinished(Job*, Operation*)), SLOT(onJobFinished(Job*, Operation*)));
 
250
}
 
251
 
 
252
void ProgressDialog::onJobStarted(Job* job, Operation* op)
 
253
{
 
254
        for (qint32 i = 0; i < dialogWidget().treeTasks().topLevelItemCount(); i++)
 
255
        {
 
256
                QTreeWidgetItem* item = dialogWidget().treeTasks().topLevelItem(i);
 
257
 
 
258
                if (item == NULL || reinterpret_cast<const Operation*>(item->data(0, Qt::UserRole).toULongLong()) != op)
 
259
                        continue;
 
260
 
 
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);
 
268
                break;
 
269
        }
 
270
}
 
271
 
 
272
void ProgressDialog::onJobFinished(Job* job, Operation* op)
 
273
{
 
274
        if (currentJobItem())
 
275
                currentJobItem()->setIcon(0, job->statusIcon());
 
276
 
 
277
        setCurrentJobItem(NULL);
 
278
 
 
279
        const int current = dialogWidget().progressTotal().value();
 
280
        dialogWidget().progressTotal().setValue(current + 1);
 
281
 
 
282
        setParentTitle(op->description());
 
283
        updateReport(true);
 
284
}
 
285
 
 
286
void ProgressDialog::onOpFinished(int num, Operation* op)
 
287
{
 
288
        if (currentOpItem())
 
289
        {
 
290
                currentOpItem()->setText(0, opDesc(num, *op));
 
291
                currentOpItem()->setIcon(0, op->statusIcon());
 
292
        }
 
293
 
 
294
        setCurrentOpItem(NULL);
 
295
 
 
296
        setStatus(op->description());
 
297
 
 
298
        dialogWidget().progressSub().setValue(op->totalProgress());
 
299
        updateReport(true);
 
300
}
 
301
 
 
302
void ProgressDialog::setParentTitle(const QString& s)
 
303
{
 
304
        const int percent = dialogWidget().progressTotal().value() * 100 / dialogWidget().progressTotal().maximum();
 
305
        parentWidget()->setWindowTitle(QString::number(percent) + "% - " + s + " - " + savedParentTitle());
 
306
}
 
307
 
 
308
void ProgressDialog::setStatus(const QString& s)
 
309
{
 
310
        setCaption(s);
 
311
        dialogWidget().status().setText(s);
 
312
 
 
313
        setParentTitle(s);
 
314
}
 
315
 
 
316
QString ProgressDialog::opDesc(int num, const Operation& op) const
 
317
{
 
318
        return i18nc("@info:progress", "[%1/%2] - %3: %4", num, operationRunner().numOperations(), op.statusText(), op.description());
 
319
}
 
320
 
 
321
void ProgressDialog::addTaskOutput(int num, const Operation& op)
 
322
{
 
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()));
 
327
 
 
328
        QFont f;
 
329
        f.setWeight(QFont::Bold);
 
330
        item->setFont(0, f);
 
331
        item->setFont(1, f);
 
332
 
 
333
        item->setData(0, Qt::UserRole, reinterpret_cast<const qulonglong>(&op));
 
334
        dialogWidget().treeTasks().addTopLevelItem(item);
 
335
        dialogWidget().treeTasks().scrollToBottom();
 
336
        setCurrentOpItem(item);
 
337
}
 
338
 
 
339
void ProgressDialog::onSecondElapsed()
 
340
{
 
341
        if (currentJobItem())
 
342
        {
 
343
                QTime t = QTime::fromString(currentJobItem()->text(1), timeFormat()).addSecs(1);
 
344
                currentJobItem()->setText(1, t.toString(timeFormat()));
 
345
        }
 
346
 
 
347
        if (currentOpItem())
 
348
        {
 
349
                QTime t = QTime::fromString(currentOpItem()->text(1), timeFormat()).addSecs(1);
 
350
                currentOpItem()->setText(1, t.toString(timeFormat()));;
 
351
        }
 
352
 
 
353
        const QTime outputTime = QTime(0, 0).addMSecs(time().elapsed());
 
354
        dialogWidget().totalTime().setText(i18nc("@info:progress", "Total Time: %1", outputTime.toString(timeFormat())));
 
355
}
 
356
 
 
357
void ProgressDialog::keyPressEvent(QKeyEvent* e)
 
358
{
 
359
        e->accept();
 
360
 
 
361
        switch(e->key())
 
362
        {
 
363
                case Qt::Key_Return:
 
364
                case Qt::Key_Enter:
 
365
                        if (isButtonEnabled(KDialog::Ok))
 
366
                                slotButtonClicked(KDialog::Ok);
 
367
                        break;
 
368
 
 
369
                case Qt::Key_Escape:
 
370
                        slotButtonClicked(isButtonEnabled(KDialog::Cancel) ? KDialog::Cancel : KDialog::Ok);
 
371
                        break;
 
372
 
 
373
                default:
 
374
                        break;
 
375
        }
 
376
}
 
377
 
 
378
void ProgressDialog::saveReport()
 
379
{
 
380
        QString fileName = KFileDialog::getSaveFileName(KUrl("kfiledialog://saveReport"));
 
381
 
 
382
        if (fileName.isEmpty())
 
383
                return;
 
384
 
 
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)
 
386
        {
 
387
                QFile file(fileName);
 
388
 
 
389
                if (file.open(QIODevice::WriteOnly | QIODevice::Truncate))
 
390
                {
 
391
                        file.write(Report::htmlHeader().toUtf8());
 
392
                        file.write(report().toHtml().toUtf8());
 
393
                        file.write(Report::htmlFooter().toUtf8());
 
394
 
 
395
                        file.close();
 
396
                }
 
397
                else
 
398
                        KMessageBox::sorry(this, i18nc("@info", "Could not open file <filename>%1</filename> for writing.", fileName), i18nc("@title:window", "Could Not Save Report."));
 
399
        }
 
400
}
 
401
 
 
402
void ProgressDialog::browserReport()
 
403
{
 
404
        KTemporaryFile file;
 
405
 
 
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);
 
410
 
 
411
        if (file.open())
 
412
        {
 
413
                file.write(Report::htmlHeader().toUtf8());
 
414
                file.write(report().toHtml().toUtf8());
 
415
                file.write(Report::htmlFooter().toUtf8());
 
416
 
 
417
                // set the temp file's permission for everyone to read it.
 
418
                file.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadGroup | QFile::ReadOther);
 
419
 
 
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."));
 
422
        }
 
423
        else
 
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."));
 
425
}