made progressbar layout 2 rows, max 6 bars total
[case] / src / fileoperator.cpp
1 // case - file manager for N900
2 // Copyright (C) 2010 Lukas Hrazky <lukkash@email.cz>
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 3 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, see <http://www.gnu.org/licenses/>.
16
17
18 #include "fileoperator.h"
19
20 #include <QtGui>
21 #include <QMessageBox>
22 #include <QVBoxLayout>
23 #include <QMaemo5InformationBox>
24
25 #include "dialog.h"
26 #include "utils.h"
27
28
29 FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
30     QVBoxLayout *layout = new QVBoxLayout;
31     layout->setContentsMargins(0, 0, 0, 0);
32     layout->setSpacing(1);
33     topRow = new QHBoxLayout;
34     topRow->setContentsMargins(0, 0, 0, 0);
35     topRow->setSpacing(1);
36     layout->addLayout(topRow);
37     bottomRow = new QHBoxLayout;
38     bottomRow->setContentsMargins(0, 0, 0, 0);
39     bottomRow->setSpacing(1);
40     layout->addLayout(bottomRow);
41     setLayout(layout);
42
43     qRegisterMetaType<QFileInfo>("QFileInfo");
44     qRegisterMetaType<time_t>("time_t");
45
46     loadOperationIcons(palette(), "delete_small", deleteIcon, inverseDeleteIcon);
47     loadOperationIcons(palette(), "copy_small", copyIcon, inverseCopyIcon);
48     loadOperationIcons(palette(), "move_small", moveIcon, inverseMoveIcon);
49 }
50
51
52 void FileOperator::deleteFiles(const QFileInfoList &files) {
53     if (checkMaxOpsNumber()) return;
54
55     QString title, desc;
56     if (files.size() == 1) {
57         title = tr("Delete file");
58         desc = tr("Are you sure you want to delete %1?")
59             .arg(shortenPath(files[0].absoluteFilePath()));
60     } else {
61         title = tr("Delete files");
62         desc = tr("You are about to delete %1 files. Are you sure you want to continue?").arg(files.size());
63     }
64
65     int confirm = QMessageBox::warning(
66         0,
67         title,
68         desc,
69         QMessageBox::Yes,
70         QMessageBox::No
71     );
72
73     if(confirm == QMessageBox::Yes) {
74         ProgressBar *bar = new ProgressBar(deleteIcon, inverseDeleteIcon);
75         initOperation(new DeleteThread(files), bar);
76     }
77 }
78
79
80 void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
81     if (checkMaxOpsNumber()) return;
82
83     QString title, desc;
84     if (files.size() == 1) {
85         title = tr("Copy file");
86         desc = tr("Are you sure you want to copy %1 to %2?")
87             .arg(shortenPath(files[0].absoluteFilePath()))
88             .arg(shortenPath(destination.absolutePath()));
89     } else {
90         title = tr("Copy files");
91         desc = tr("You are about to copy %1 files to %2. Are you sure you want to continue?")
92             .arg(files.size()).arg(shortenPath(destination.absolutePath()));
93     }
94
95     int confirm = QMessageBox::warning(
96         0,
97         title,
98         desc,
99         QMessageBox::Yes,
100         QMessageBox::No
101     );
102
103     if(confirm == QMessageBox::Yes) {
104         ProgressBar *bar = new ProgressBar(copyIcon, inverseCopyIcon);
105         bar->setBottomTexts(shortenPath(files[0].absolutePath()), shortenPath(destination.absolutePath()));
106         initOperation(new CopyThread(files, destination), bar);
107     }
108 }
109
110
111 void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
112     if (checkMaxOpsNumber()) return;
113
114     // for move we don't wanna move to the same dir
115     if (files[0].absolutePath() == destination.absolutePath()) return;
116
117     QString title, desc;
118     if (files.size() == 1) {
119         title = tr("Move file");
120         desc = tr("Are you sure you want to move %1 to %2?")
121             .arg(shortenPath(files[0].absoluteFilePath()))
122             .arg(shortenPath(destination.absolutePath()));
123     } else {
124         title = tr("Move files");
125         desc = tr("You are about to move %1 files to %2. Are you sure you want to continue?")
126             .arg(files.size()).arg(shortenPath(destination.absolutePath()));
127     }
128
129     int confirm = QMessageBox::warning(
130         0,
131         title,
132         desc,
133         QMessageBox::Yes,
134         QMessageBox::No
135     );
136
137     if(confirm == QMessageBox::Yes) {
138         ProgressBar *bar = new ProgressBar(moveIcon, inverseMoveIcon);
139         bar->setBottomTexts(shortenPath(files[0].absolutePath()), shortenPath(destination.absolutePath()));
140         initOperation(new MoveThread(files, destination), bar);
141     }
142 }
143
144
145 void FileOperator::showErrorPrompt(OperationThread* op,
146     const QString &message,
147     const QString &fileName,
148     const int err)
149 {
150     QMessageBox msgBox;
151     QAbstractButton *cancelButton = msgBox.addButton(QMessageBox::Cancel);
152     QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
153     QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry);
154     QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore);
155     QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole);
156     msgBox.setText(message.arg(shortenPath(fileName)));
157
158     msgBox.exec();
159
160     if (msgBox.clickedButton() == cancelButton) {
161         op->pause = true;
162         op->setResponse(OperationThread::RETRY);
163     } else if (msgBox.clickedButton() == abortButton) {
164         op->setResponse(OperationThread::ABORT);
165     } else if (msgBox.clickedButton() == retryButton) {
166         op->setResponse(OperationThread::RETRY);
167     } else if (msgBox.clickedButton() == ignoreButton) {
168         op->setResponse(OperationThread::IGNORE);
169     } else if (msgBox.clickedButton() == ignoreAllButton) {
170         op->setResponse(OperationThread::IGNORE, true, err);
171     }
172 }
173
174
175 void FileOperator::showOverwritePrompt(
176     OperationThread* op,
177     const QString &fileName,
178     const bool dirOverDir)
179 {
180     Dialog msgBox;
181     QAbstractButton *yesButton = msgBox.addButtonFirst(QDialogButtonBox::Yes);
182     QAbstractButton *yesToAllButton = msgBox.addButtonFirst(QDialogButtonBox::YesToAll);
183     QAbstractButton *noButton = msgBox.addButtonSecond(QDialogButtonBox::No);
184     QAbstractButton *noToAllButton = msgBox.addButtonSecond(QDialogButtonBox::NoToAll);
185     QAbstractButton *abortButton = msgBox.addButtonSecond(tr("Abort"), QDialogButtonBox::DestructiveRole);
186     QAbstractButton *newNameButton = msgBox.addButtonFirst(tr("New Name"), QDialogButtonBox::AcceptRole);
187     QAbstractButton *askButton = 0;
188     QAbstractButton *skipDirButton = 0;
189
190     if (dirOverDir) {
191         msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?")
192             .arg(shortenPath(fileName)));
193         askButton = msgBox.addButtonFirst(tr("Ask"), QDialogButtonBox::AcceptRole);
194         skipDirButton = msgBox.addButtonSecond(tr("Skip"), QDialogButtonBox::NoRole);
195     } else {
196         msgBox.setText(tr("File %1 already exists. Overwrite?").arg(shortenPath(fileName)));
197     }
198
199     msgBox.exec();
200
201     if (msgBox.clickedButton == 0) {
202         op->pause = true;
203         op->setResponse(OperationThread::NONE);
204     } else if (msgBox.clickedButton == abortButton) {
205         op->setResponse(OperationThread::ABORT);
206     } else if (msgBox.clickedButton == yesButton) {
207         op->setResponse(OperationThread::OVERWRITE);
208     } else if (msgBox.clickedButton == yesToAllButton) {
209         op->setResponse(OperationThread::OVERWRITE, true);
210     } else if (msgBox.clickedButton == noButton) {
211         op->setResponse(OperationThread::KEEP);
212     } else if (msgBox.clickedButton == noToAllButton) {
213         op->setResponse(OperationThread::KEEP, true);
214     } else if (msgBox.clickedButton == askButton) {
215         op->setResponse(OperationThread::ASK);
216     } else if (msgBox.clickedButton == newNameButton) {
217         op->setResponse(OperationThread::NONE);
218     } else if (msgBox.clickedButton == skipDirButton) {
219         op->setResponse(OperationThread::SKIP_DIR);
220     }
221 }
222
223
224 void FileOperator::showInputFilenamePrompt(OperationThread* op,
225     const QFileInfo &file,
226     const bool dir)
227 {
228     bool ok;
229     QString prompt, error;
230
231     if (dir) {
232         prompt = tr("Enter the new directory name.");
233     } else {
234         prompt = tr("Enter the new file name.");
235     }
236
237     op->newNameFromDialog = "";
238     QString text = file.fileName();
239
240     while (true) {
241         text = QInputDialog::getText(this, QString(), prompt + error, QLineEdit::Normal, text, &ok);
242
243         if (!ok) break;
244
245         error = "";
246         if (text.contains(QRegExp("[\"*/:<>?\\\\|]"))) {
247             error = "<small><br/><font color = 'red'>" +
248                 tr("The name cannot contain any of the following characters: ") +
249                 "\"*/:&lt;&gt;?\\|</font></small>";
250         } else if (ok && !text.isEmpty()) {
251             QFileInfo info(file.path() + "/" + text);
252             op->newNameFromDialog = info.absoluteFilePath();
253             break;
254         }
255     }
256
257     op->wake();
258 }
259
260
261 void FileOperator::remove(OperationThread* op) {
262     op->wait();
263     ProgressBar *bar = get(op);
264     removeBarFromLayout(bar);
265     opList.removeAll(qMakePair(op, bar));
266     delete op;
267     delete bar;
268 }
269
270
271 void FileOperator::togglePauseOperation(ProgressBar* bar) {
272     OperationThread *op = get(bar);
273
274     if (op->pause) {
275         op->wake();
276     } else {
277         op->pause = true;
278     }
279 }
280
281
282 void FileOperator::abortOperation(ProgressBar* bar) {
283     OperationThread *op = get(bar);
284
285     int confirm = QMessageBox::warning(
286         0,
287         tr("Abort operation"),
288         tr("Are you sure you want to abort the operation?"),
289         QMessageBox::Yes,
290         QMessageBox::No
291     );
292
293     if(confirm == QMessageBox::Yes) {
294         op->abort = true;
295         op->pause = false;
296         op->wake();
297     }
298 }
299
300
301 void FileOperator::initOperation(OperationThread *thread, ProgressBar *bar) {
302     addBarToLayout(bar);
303     opList.append(qMakePair(thread, bar));
304
305     connect(thread, SIGNAL(showErrorPrompt(OperationThread*, const QString&, const QString&, const int)),
306         this, SLOT(showErrorPrompt(OperationThread*, const QString&, const QString&, const int)));
307     connect(thread, SIGNAL(showOverwritePrompt(OperationThread*, const QString&, bool)),
308         this, SLOT(showOverwritePrompt(OperationThread*, const QString&, bool)));
309     connect(thread, SIGNAL(showInputFilenamePrompt(OperationThread*, const QFileInfo&, bool)),
310         this, SLOT(showInputFilenamePrompt(OperationThread*, const QFileInfo&, bool)));
311     connect(thread, SIGNAL(finished(OperationThread*)),
312         this, SLOT(remove(OperationThread*)));
313
314     connect(thread, SIGNAL(totalSizeChanged(int)), bar, SLOT(setMaximum(int)));
315     connect(thread, SIGNAL(progressUpdate(int)), bar, SLOT(updateProgress(int)));
316     connect(thread, SIGNAL(fileNameUpdated(QString)), bar, SLOT(updateMainText(QString)));
317     connect(thread, SIGNAL(operationStarted(time_t)), bar, SLOT(setStartTime(time_t)));
318     connect(thread, SIGNAL(operationPaused()), bar, SLOT(pause()));
319     connect(thread, SIGNAL(operationResumed(time_t)), bar, SLOT(resume(time_t)));
320     connect(thread, SIGNAL(removeAfterCopy()), bar, SLOT(showRemoveNotice()));
321
322     connect(bar, SIGNAL(togglePauseOperation(ProgressBar*)), this, SLOT(togglePauseOperation(ProgressBar*)));
323     connect(bar, SIGNAL(abortOperation(ProgressBar*)), this, SLOT(abortOperation(ProgressBar*)));
324
325     thread->start(QThread::LowestPriority);
326 }
327
328
329 ProgressBar *FileOperator::get(OperationThread *op) const {
330     for (OperationList::const_iterator it = opList.begin(); it != opList.end(); ++it) {
331         if (it->first == op) return it->second;
332     }
333     return 0;
334 }
335
336
337 OperationThread *FileOperator::get(ProgressBar *bar) const {
338     for (OperationList::const_iterator it = opList.begin(); it != opList.end(); ++it) {
339         if (it->second == bar) return it->first;
340     }
341     return 0;
342 }
343
344
345 void FileOperator::addBarToLayout(ProgressBar *bar) {
346     switch (opList.size()) {
347     case 0:
348     case 1:
349     case 2:
350         topRow->addWidget(bar);
351         break;
352     case 4:
353         topRow->addItem(bottomRow->takeAt(0));
354         bottomRow->addWidget(bar);
355         break;
356     case 3:
357         bottomRow->addItem(topRow->takeAt(2));
358     default:
359         bottomRow->addWidget(bar);
360     }
361 }
362
363
364 void FileOperator::removeBarFromLayout(ProgressBar *bar) {
365     int index = topRow->indexOf(bar);
366     if (index != -1) {
367         topRow->takeAt(index);
368         switch (opList.size()) {
369         case 4:
370             topRow->addItem(bottomRow->takeAt(0));
371         case 6:
372             topRow->addItem(bottomRow->takeAt(0));
373             break;
374         }
375     } else {
376         bottomRow->removeWidget(bar);
377         switch (opList.size()) {
378         case 4:
379             topRow->addItem(bottomRow->takeAt(0));
380             break;
381         case 5:
382             bottomRow->insertWidget(0, topRow->takeAt(2)->widget());
383         }
384     }
385 }
386
387
388 bool FileOperator::checkMaxOpsNumber() {
389     if (opList.size() == 6) {
390         QMaemo5InformationBox::information(this, tr("The maximum number of file operations is %1.").arg(6));
391         return true;
392     }
393     return false;
394 }