1 //**************************************************************************
2 //   Copyright 2006 - 2017 Martin Koller, kollix@aon.at
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, version 2 of the License
7 //
8 //**************************************************************************
9 #include <MainWindow.hxx>
10 #include <Selector.hxx>
11 #include <Archiver.hxx>
12 #include <MainWidget.hxx>
13 #include <SettingsDialog.hxx>
14 
15 #include <QSplitter>
16 #include <QSpinBox>
17 #include <QToolTip>
18 #include <QTimer>
19 #include <QCursor>
20 #include <QCheckBox>
21 #include <QMenu>
22 #include <QAction>
23 #include <QUrl>
24 #include <QApplication>
25 #include <QFileDialog>
26 #include <QHeaderView>
27 
28 #include <KXMLGUIFactory>
29 #include <KStandardAction>
30 #include <KActionCollection>
31 #include <KToggleAction>
32 #include <KRecentFilesAction>
33 #include <KMessageBox>
34 #include <kio/global.h>
35 #include <KStringHandler>
36 #include <KStatusNotifierItem>
37 #include <KSharedConfig>
38 #include <KConfigGroup>
39 #include <KShortcutsDialog>
40 
41 //#include <iostream>
42 //using namespace std;
43 //--------------------------------------------------------------------------------
44 
MainWindow()45 MainWindow::MainWindow()
46 {
47   new Archiver(this);
48 
49   quitAction = KStandardAction::quit(this, SLOT(maybeQuit()), actionCollection());
50   KStandardAction::keyBindings(guiFactory(), &KXMLGUIFactory::showConfigureShortcutsDialog, actionCollection());
51 
52 
53   QAction *action;
54 
55   action = actionCollection()->addAction(QStringLiteral("newProfile"), this, SLOT(newProfile()));
56   action->setText(i18n("New Profile"));
57   action->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
58 
59   action = actionCollection()->addAction(QStringLiteral("loadProfile"), this, SLOT(loadProfile()));
60   action->setText(i18n("Load Profile..."));
61   action->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
62 
63   action = actionCollection()->addAction(QStringLiteral("saveProfile"), this, SLOT(saveProfile()));
64   action->setText(i18n("Save Profile"));
65   action->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
66 
67   action = actionCollection()->addAction(QStringLiteral("saveProfileAs"), this, SLOT(saveProfileAs()));
68   action->setText(i18n("Save Profile As..."));
69   action->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
70 
71   action = actionCollection()->addAction(QStringLiteral("profileSettings"), this, SLOT(profileSettings()));
72   action->setText(i18n("Profile Settings..."));
73 
74   action = actionCollection()->addAction(QStringLiteral("enableAllMessages"), this, SLOT(enableAllMessages()));
75   action->setText(i18n("Enable All Messages"));
76 
77   KToggleAction *docked = new KToggleAction(i18n("Dock in System Tray"), this);
78   actionCollection()->addAction(QStringLiteral("dockInSysTray"), docked);
79   connect(docked, &QAction::toggled, this, &MainWindow::dockInSysTray);
80 
81   KToggleAction *showHidden = new KToggleAction(i18n("Show Hidden Files"), this);
82   actionCollection()->addAction(QStringLiteral("showHiddenFiles"), showHidden);
83   connect(showHidden, &QAction::toggled, this, &MainWindow::showHiddenFiles);
84 
85   recentFiles = KStandardAction::openRecent(this, SLOT(recentProfileSelected(const QUrl &)), actionCollection());
86   recentFiles->setObjectName(QStringLiteral("recentProfiles"));
87   recentFiles->loadEntries(KSharedConfig::openConfig()->group(""));
88 
89   createGUI();
90 
91   splitter = new QSplitter(Qt::Horizontal, this);
92 
93   selector = new Selector(splitter, actionCollection());
94 
95   mainWidget = new MainWidget(splitter);
96   mainWidget->setSelector(selector);
97   splitter->setCollapsible(splitter->indexOf(mainWidget), false);
98 
99   setCentralWidget(splitter);
100 
101   splitter->restoreState(KSharedConfig::openConfig()->group("geometry").readEntry<QByteArray>("splitter", QByteArray()));
102   selector->header()->restoreState(KSharedConfig::openConfig()->group("geometry").readEntry<QByteArray>("tree", QByteArray()));
103 
104   // save/restore window settings and size
105   setAutoSaveSettings();
106 
107   connect(Archiver::instance, &Archiver::totalFilesChanged, this, &MainWindow::changeSystrayTip);
108   connect(Archiver::instance, &Archiver::logging, this, &MainWindow::loggingSlot);
109   connect(Archiver::instance, &Archiver::inProgress, this, &MainWindow::inProgress);
110 
111   startBackupAction = actionCollection()->addAction(QStringLiteral("startBackup"), mainWidget, SLOT(startBackup()));
112   startBackupAction->setIcon(QIcon::fromTheme(QStringLiteral("kbackup_start")));
113   startBackupAction->setText(i18n("Start Backup"));
114 
115   cancelBackupAction = actionCollection()->addAction(QStringLiteral("cancelBackup"), Archiver::instance, SLOT(cancel()));
116   cancelBackupAction->setText(i18n("Cancel Backup"));
117   cancelBackupAction->setIcon(QIcon::fromTheme(QStringLiteral("kbackup_cancel")));
118   cancelBackupAction->setEnabled(false);
119 
120   showHidden->setChecked(KSharedConfig::openConfig()->group("settings").readEntry<bool>("showHiddenFiles", false));
121   showHiddenFiles(showHidden->isChecked());
122   docked->setChecked(KSharedConfig::openConfig()->group("settings").readEntry<bool>("dockInSysTray", false));
123   dockInSysTray(docked->isChecked());
124 
125   // for convenience, open the tree at the HOME directory
126   selector->openHomeDir();
127 
128   changeSystrayTip();
129 }
130 
131 //--------------------------------------------------------------------------------
132 
runBackup()133 void MainWindow::runBackup()
134 {
135   autorun = true;
136   QTimer::singleShot(0, mainWidget, &MainWidget::startBackup);
137 }
138 
139 //--------------------------------------------------------------------------------
140 
stopAllowed()141 bool MainWindow::stopAllowed()
142 {
143   if ( Archiver::instance->isInProgress() )
144   {
145     if ( KMessageBox::warningYesNo(this,
146             i18n("There is a backup in progress. Do you want to abort it?")) == KMessageBox::No )
147       return false;
148 
149     Archiver::instance->cancel();
150   }
151 
152   KSharedConfig::openConfig()->group("geometry").writeEntry("splitter", splitter->saveState());
153   KSharedConfig::openConfig()->group("geometry").writeEntry("tree", selector->header()->saveState());
154 
155   return true;
156 }
157 
158 //--------------------------------------------------------------------------------
159 
maybeQuit()160 void MainWindow::maybeQuit()
161 {
162   if ( stopAllowed() )
163     qApp->quit();
164 }
165 
166 //--------------------------------------------------------------------------------
167 
queryClose()168 bool MainWindow::queryClose()
169 {
170   if ( qApp->isSavingSession() || !sysTray )
171     return stopAllowed();
172 
173   hide();
174   return false;
175 }
176 
177 //--------------------------------------------------------------------------------
178 
recentProfileSelected(const QUrl & url)179 void MainWindow::recentProfileSelected(const QUrl &url)
180 {
181   loadProfile(url.path());
182 }
183 
184 //--------------------------------------------------------------------------------
185 
loadProfile()186 void MainWindow::loadProfile()
187 {
188   QString fileName = QFileDialog::getOpenFileName(this, i18n("Select Profile"), QString(),
189                                                   i18n("KBackup Profile (*.kbp)"));
190 
191   if ( fileName.isEmpty() ) return;
192 
193   loadProfile(fileName);
194 }
195 
196 //--------------------------------------------------------------------------------
197 
loadProfile(const QString & fileName,bool adaptTreeWidth)198 void MainWindow::loadProfile(const QString &fileName, bool adaptTreeWidth)
199 {
200   QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
201 
202   QStringList includes, excludes;
203   QString error;
204 
205   if ( ! Archiver::instance->loadProfile(fileName, includes, excludes, error) )
206   {
207     QApplication::restoreOverrideCursor();
208 
209     KMessageBox::error(this,
210                 i18n("Could not open profile '%1' for reading: %2",
211                      fileName,
212                      error),
213                 i18n("Open failed"));
214     return;
215   }
216 
217   setLoadedProfile(fileName);
218 
219   // now fill the Selector tree with those settings
220   selector->setBackupList(includes, excludes);
221 
222   mainWidget->getTargetLineEdit()->setText(Archiver::instance->getTarget().toDisplayString(QUrl::PreferLocalFile));
223 
224   if ( adaptTreeWidth )
225     selector->resizeColumnToContents(0);
226 
227   QApplication::restoreOverrideCursor();
228 }
229 
230 //--------------------------------------------------------------------------------
231 
saveProfileAs()232 void MainWindow::saveProfileAs()
233 {
234   QString fileName = QFileDialog::getSaveFileName(this, i18n("Select Profile"), QString(),
235                                                   i18n("KBackup Profile (*.kbp)"));
236 
237   if ( fileName.isEmpty() ) return;
238 
239   saveProfile(fileName);
240 }
241 
242 //--------------------------------------------------------------------------------
243 
saveProfile(QString fileName)244 void MainWindow::saveProfile(QString fileName)
245 {
246   if ( fileName.isEmpty() )
247     fileName = loadedProfile;
248 
249   if ( fileName.isEmpty() )
250   {
251     fileName = QFileDialog::getSaveFileName(this, i18n("Select Profile"), QString(),
252                                             i18n("KBackup Profile (*.kbp)"));
253 
254     if ( fileName.isEmpty() ) return;
255   }
256 
257   QFile file(fileName);
258 
259   if ( file.exists() && (fileName != loadedProfile) )
260   {
261     if ( KMessageBox::warningYesNo(this,
262                 i18n("The profile '%1' does already exist.\n"
263                      "Do you want to overwrite it?",
264                      fileName),
265                 i18n("Profile exists")) == KMessageBox::No )
266       return;
267   }
268 
269   QStringList includes, excludes;
270   selector->getBackupList(includes, excludes);
271   QString error;
272 
273   Archiver::instance->setTarget(QUrl(mainWidget->getTargetLineEdit()->text()));
274 
275   if ( ! Archiver::instance->saveProfile(fileName, includes, excludes, error) )
276   {
277     KMessageBox::error(this,
278                 i18n("Could not open profile '%1' for writing: %2",
279                      fileName,
280                      error),
281                 i18n("Open failed"));
282     return;
283   }
284 
285   setLoadedProfile(fileName);
286 }
287 
288 //--------------------------------------------------------------------------------
289 
profileSettings()290 void MainWindow::profileSettings()
291 {
292   SettingsDialog dialog(this);
293 
294   dialog.ui.prefix->setText(Archiver::instance->getFilePrefix());
295   dialog.setMaxMB(Archiver::instance->getMaxSliceMBs());
296   dialog.ui.numBackups->setValue(Archiver::instance->getKeptBackups());
297   dialog.ui.mediaNeedsChange->setChecked(Archiver::instance->getMediaNeedsChange());
298   dialog.ui.compressFiles->setChecked(Archiver::instance->getCompressFiles());
299   dialog.ui.fullBackupInterval->setValue(Archiver::instance->getFullBackupInterval());
300   dialog.ui.filter->setText(Archiver::instance->getFilter());
301   dialog.ui.dirFilter->setPlainText(Archiver::instance->getDirFilter());
302 
303   if ( dialog.exec() == QDialog::Accepted )
304   {
305     Archiver::instance->setFilePrefix(dialog.ui.prefix->text().trimmed());
306     Archiver::instance->setMaxSliceMBs(dialog.ui.maxSliceSize->value());
307     Archiver::instance->setKeptBackups(dialog.ui.numBackups->value());
308     Archiver::instance->setMediaNeedsChange(dialog.ui.mediaNeedsChange->isChecked());
309     Archiver::instance->setCompressFiles(dialog.ui.compressFiles->isChecked());
310     Archiver::instance->setFullBackupInterval(dialog.ui.fullBackupInterval->value());
311     Archiver::instance->setFilter(dialog.ui.filter->text());
312     Archiver::instance->setDirFilter(dialog.ui.dirFilter->toPlainText());
313   }
314 }
315 
316 //--------------------------------------------------------------------------------
317 
newProfile()318 void MainWindow::newProfile()
319 {
320   Archiver::instance->setFilePrefix(QString());  // back to default
321   Archiver::instance->setMaxSliceMBs(Archiver::UNLIMITED);
322   Archiver::instance->setMediaNeedsChange(true);
323   Archiver::instance->setTarget(QUrl());
324   Archiver::instance->setKeptBackups(Archiver::UNLIMITED);
325   Archiver::instance->setFullBackupInterval(1);
326   Archiver::instance->setFilter(QString());
327   Archiver::instance->setDirFilter(QString());
328 
329   // clear selection
330   QStringList includes, excludes;
331   selector->setBackupList(includes, excludes);
332 
333   mainWidget->getTargetLineEdit()->setText(QString());
334 
335   setLoadedProfile(QString());
336 }
337 
338 //--------------------------------------------------------------------------------
339 
loggingSlot(const QString & message)340 void MainWindow::loggingSlot(const QString &message)
341 {
342   lastLog = message;
343   changeSystrayTip();
344 }
345 
346 //--------------------------------------------------------------------------------
347 
changeSystrayTip()348 void MainWindow::changeSystrayTip()
349 {
350   if ( !sysTray )
351     return;
352 
353   QString text = qApp->applicationDisplayName() + QLatin1String(" - ") +
354                  i18n("Files: %1 Size: %2 MB\n%3",
355                     Archiver::instance->getTotalFiles(),
356                     QString::number(Archiver::instance->getTotalBytes() / 1024.0 / 1024.0, 'f', 2),
357                     KStringHandler::csqueeze(lastLog, 60));
358 
359   sysTray->setToolTip(QStringLiteral("kbackup"), QStringLiteral("kbackup"), text);
360 }
361 
362 //--------------------------------------------------------------------------------
363 
inProgress(bool runs)364 void MainWindow::inProgress(bool runs)
365 {
366   if ( runs )
367   {
368     if ( sysTray )
369     {
370       /*
371       QMovie *movie = KIconLoader::global()->loadMovie("kbackup_runs", KIconLoader::Panel);
372       if ( movie )
373       {
374         sysTray->setMovie(movie);
375         movie->start();
376       }
377       */
378       sysTray->setIconByName(QStringLiteral("kbackup_runs"));
379       sysTray->setStatus(KStatusNotifierItem::Active);
380     }
381 
382     startBackupAction->setEnabled(false);
383     cancelBackupAction->setEnabled(true);
384   }
385   else
386   {
387     if ( sysTray )
388     {
389       /*
390       if ( sysTray->movie() )
391         const_cast<QMovie*>(sysTray->movie())->stop();  // why does it return a const pointer ? :-(
392 
393       sysTray->setIcon(sysTray->loadIcon("kbackup"));
394       */
395       sysTray->setIconByName(QStringLiteral("kbackup"));
396       sysTray->setStatus(KStatusNotifierItem::Passive);
397     }
398 
399     startBackupAction->setEnabled(true);
400     cancelBackupAction->setEnabled(false);
401 
402     if ( autorun )
403       qApp->quit();
404   }
405 }
406 
407 //--------------------------------------------------------------------------------
408 
dockInSysTray(bool checked)409 void MainWindow::dockInSysTray(bool checked)
410 {
411   KSharedConfig::openConfig()->group("settings").writeEntry("dockInSysTray", checked);
412   KSharedConfig::openConfig()->group("settings").sync();
413 
414   if ( checked )
415   {
416     // system tray icon
417     delete sysTray;
418     sysTray = new KStatusNotifierItem(this);
419     sysTray->setStandardActionsEnabled(false);
420 
421     sysTray->contextMenu()->addAction(startBackupAction);
422     sysTray->contextMenu()->addAction(cancelBackupAction);
423     sysTray->contextMenu()->addAction(quitAction);
424 
425     if ( Archiver::instance->isInProgress() )
426     {
427       sysTray->setStatus(KStatusNotifierItem::Active);
428       sysTray->setIconByName(QStringLiteral("kbackup_runs"));
429     }
430     else
431     {
432       sysTray->setStatus(KStatusNotifierItem::Passive);
433       sysTray->setIconByName(QStringLiteral("kbackup"));
434     }
435   }
436   else
437   {
438     delete sysTray;
439     sysTray = nullptr;
440   }
441 }
442 
443 //--------------------------------------------------------------------------------
444 
showHiddenFiles(bool checked)445 void MainWindow::showHiddenFiles(bool checked)
446 {
447   KSharedConfig::openConfig()->group("settings").writeEntry("showHiddenFiles", checked);
448   KSharedConfig::openConfig()->group("settings").sync();
449 
450   selector->setShowHiddenFiles(checked);
451 }
452 
453 //--------------------------------------------------------------------------------
454 
enableAllMessages()455 void MainWindow::enableAllMessages()
456 {
457   KMessageBox::enableAllMessages();
458 }
459 
460 //--------------------------------------------------------------------------------
461 
setLoadedProfile(const QString & name)462 void MainWindow::setLoadedProfile(const QString &name)
463 {
464   loadedProfile = name;
465   Archiver::instance->setLoadedProfile(name);
466   setCaption(name);
467 
468   if ( !name.isEmpty() )
469   {
470     QUrl url;
471     url.setPath(name);
472     recentFiles->addUrl(url);
473     recentFiles->saveEntries(KSharedConfig::openConfig()->group(""));
474     KSharedConfig::openConfig()->group("").sync();
475   }
476 }
477 
478 //--------------------------------------------------------------------------------
479