1 /*
2     SPDX-FileCopyrightText: 2021 Michail Vourlakos <mvourlakos@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "viewshandler.h"
7 
8 // local
9 #include "ui_viewsdialog.h"
10 #include "viewscontroller.h"
11 #include "viewsdialog.h"
12 #include "viewstableview.h"
13 #include "../exporttemplatedialog/exporttemplatedialog.h"
14 #include "../settingsdialog/layoutscontroller.h"
15 #include "../settingsdialog/layoutsmodel.h"
16 #include "../settingsdialog/delegates/layoutcmbitemdelegate.h"
17 #include "../../data/layoutstable.h"
18 #include "../../data/genericbasictable.h"
19 #include "../../data/viewstable.h"
20 #include "../../lattecorona.h"
21 #include "../../layout/abstractlayout.h"
22 #include "../../layout/centrallayout.h"
23 #include "../../layouts/manager.h"
24 #include "../../layouts/storage.h"
25 #include "../../layouts/synchronizer.h"
26 #include "../../templates/templatesmanager.h"
27 #include "../../tools/commontools.h"
28 
29 // Qt
30 #include <QFileDialog>
31 
32 // KDE
33 #include <KLocalizedString>
34 #include <KStandardGuiItem>
35 #include <KIO/OpenFileManagerWindowJob>
36 
37 namespace Latte {
38 namespace Settings {
39 namespace Handler {
40 
ViewsHandler(Dialog::ViewsDialog * dialog)41 ViewsHandler::ViewsHandler(Dialog::ViewsDialog *dialog)
42     : Generic(dialog),
43       m_dialog(dialog),
44       m_ui(m_dialog->ui())
45 {
46     m_viewsController = new Settings::Controller::Views(this);
47 
48     init();
49 }
50 
~ViewsHandler()51 ViewsHandler::~ViewsHandler()
52 {
53 }
54 
init()55 void ViewsHandler::init()
56 {
57     //! Layouts
58     m_layoutsProxyModel = new QSortFilterProxyModel(this);
59     m_layoutsProxyModel->setSourceModel(m_dialog->layoutsController()->baseModel());
60     m_layoutsProxyModel->setSortRole(Model::Layouts::SORTINGROLE);
61     m_layoutsProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
62     m_layoutsProxyModel->sort(Model::Layouts::NAMECOLUMN, Qt::AscendingOrder);
63 
64     m_ui->layoutsCmb->setModel(m_layoutsProxyModel);
65     m_ui->layoutsCmb->setModelColumn(Model::Layouts::NAMECOLUMN);
66     m_ui->layoutsCmb->setItemDelegate(new Settings::Layout::Delegate::LayoutCmbItemDelegate(this));
67 
68     //! New Button
69     m_newViewAction = new QAction(i18nc("new view", "&New"), this);
70     m_newViewAction->setToolTip(i18n("New dock or panel"));
71     m_newViewAction->setIcon(QIcon::fromTheme("add"));
72     m_newViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N));
73     connectActionWithButton(m_ui->newBtn, m_newViewAction);
74     connect(m_newViewAction, &QAction::triggered, m_ui->newBtn, &QPushButton::showMenu);
75 
76     initViewTemplatesSubMenu();
77     m_newViewAction->setMenu(m_viewTemplatesSubMenu);
78     m_ui->newBtn->setMenu(m_viewTemplatesSubMenu);
79 
80     connect(corona()->templatesManager(), &Latte::Templates::Manager::viewTemplatesChanged, this, &ViewsHandler::initViewTemplatesSubMenu);
81 
82     //! Duplicate Button
83     m_duplicateViewAction = new QAction(i18nc("duplicate dock or panel", "&Duplicate"), this);
84     m_duplicateViewAction->setToolTip(i18n("Duplicate selected dock or panel"));
85     m_duplicateViewAction->setIcon(QIcon::fromTheme("edit-copy"));
86     m_duplicateViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
87     connectActionWithButton(m_ui->duplicateBtn, m_duplicateViewAction);
88     connect(m_duplicateViewAction, &QAction::triggered, m_viewsController, &Controller::Views::duplicateSelectedViews);
89 
90     //! Remove Button
91     m_removeViewAction = new QAction(i18nc("remove layout", "Remove"), m_ui->removeBtn);
92     m_removeViewAction->setToolTip(i18n("Remove selected view"));
93     m_removeViewAction->setIcon(QIcon::fromTheme("delete"));
94     m_removeViewAction->setShortcut(QKeySequence(Qt::Key_Delete));
95     connectActionWithButton(m_ui->removeBtn, m_removeViewAction);
96     connect(m_removeViewAction, &QAction::triggered, this, &ViewsHandler::removeSelectedViews);
97     m_ui->removeBtn->addAction(m_removeViewAction); //this is needed in order to be triggered properly
98 
99     //! Import
100     m_importViewAction =new QAction(i18nc("import dock/panel","&Import..."));
101     m_duplicateViewAction->setToolTip(i18n("Import dock or panel from local file"));
102     m_importViewAction->setIcon(QIcon::fromTheme("document-import"));
103     m_importViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I));
104     connectActionWithButton(m_ui->importBtn, m_importViewAction);
105     connect(m_importViewAction, &QAction::triggered, this, &ViewsHandler::importView);
106 
107     //! Export
108     m_exportViewAction = new QAction(i18nc("export layout", "&Export"), this);
109     m_exportViewAction->setToolTip(i18n("Export selected dock or panel at your system"));
110     m_exportViewAction->setIcon(QIcon::fromTheme("document-export"));
111     m_exportViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_E));
112     connectActionWithButton(m_ui->exportBtn, m_exportViewAction);
113     connect(m_exportViewAction, &QAction::triggered, m_ui->exportBtn, &QPushButton::showMenu);
114 
115     initViewExportSubMenu();
116     m_exportViewAction->setMenu(m_viewExportSubMenu);
117     m_ui->exportBtn->setMenu(m_viewExportSubMenu);
118 
119     //! signals
120     connect(this, &ViewsHandler::currentLayoutChanged, this, &ViewsHandler::reload);
121 
122     reload();
123     m_lastConfirmedLayoutIndex =m_ui->layoutsCmb->currentIndex();
124 
125     emit currentLayoutChanged();
126 
127     //! connect layout combobox after the selected layout has been loaded
128     connect(m_ui->layoutsCmb, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewsHandler::onCurrentLayoutIndexChanged);
129 
130     //!
131     connect(m_viewsController, &Settings::Controller::Views::dataChanged, this, &ViewsHandler::dataChanged);
132 
133     connect(m_ui->viewsTable, &View::ViewsTableView::selectionsChanged, this, &ViewsHandler::onSelectionChanged);
134 
135     onSelectionChanged();
136 }
137 
initViewTemplatesSubMenu()138 void ViewsHandler::initViewTemplatesSubMenu()
139 {
140     if (!m_viewTemplatesSubMenu) {
141         m_viewTemplatesSubMenu = new QMenu(m_ui->newBtn);
142         m_viewTemplatesSubMenu->setMinimumWidth(m_ui->newBtn->width() * 2);
143     } else {
144         m_viewTemplatesSubMenu->clear();
145     }
146 
147     /*Add View Templates for New Action*/
148     Data::GenericBasicTable templates = corona()->templatesManager()->viewTemplates();
149 
150     bool customtemplateseparatoradded{false};
151 
152     for (int i=0; i<templates.rowCount(); ++i) {
153         if (!customtemplateseparatoradded && templates[i].id.startsWith(QDir::homePath())) {
154             m_viewTemplatesSubMenu->addSeparator();
155             customtemplateseparatoradded = true;
156         }
157 
158         QAction *newview = m_viewTemplatesSubMenu->addAction(templates[i].name);
159         newview->setIcon(QIcon::fromTheme("document-new"));
160 
161         Data::Generic templateData = templates[i];
162 
163         connect(newview, &QAction::triggered, this, [&, templateData]() {
164             newView(templateData);
165         });
166     }
167 
168     if (templates.rowCount() > 0) {
169         QAction *openTemplatesDirectory = m_viewTemplatesSubMenu->addAction(i18n("Templates..."));
170         openTemplatesDirectory->setToolTip(i18n("Open templates directory"));
171         openTemplatesDirectory->setIcon(QIcon::fromTheme("edit"));
172 
173         connect(openTemplatesDirectory, &QAction::triggered, this, [&]() {
174             KIO::highlightInFileManager({QString(Latte::configPath() + "/latte/templates/Dock.view.latte")});
175         });
176     }
177 }
178 
initViewExportSubMenu()179 void ViewsHandler::initViewExportSubMenu()
180 {
181     if (!m_viewExportSubMenu) {
182         m_viewExportSubMenu = new QMenu(m_ui->exportBtn);
183         m_viewExportSubMenu->setMinimumWidth(m_ui->exportBtn->width() * 2);
184     } else {
185         m_viewExportSubMenu->clear();
186     }
187 
188     QAction *exportforbackup = m_viewExportSubMenu->addAction(i18nc("export for backup","&Export For Backup..."));
189     exportforbackup->setIcon(QIcon::fromTheme("document-export"));
190     exportforbackup->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT  + Qt::Key_E));
191     connect(exportforbackup, &QAction::triggered, this, &ViewsHandler::exportViewForBackup);
192 
193     QAction *exportastemplate = m_viewExportSubMenu->addAction(i18nc("export as template","Export As &Template..."));
194     exportastemplate->setIcon(QIcon::fromTheme("document-export"));
195     exportastemplate->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT  + Qt::Key_T));
196     connect(exportastemplate, &QAction::triggered, this, &ViewsHandler::exportViewAsTemplate);
197 }
198 
reload()199 void ViewsHandler::reload()
200 {
201     o_data = m_dialog->layoutsController()->selectedLayoutCurrentData();
202     o_data.views = m_dialog->layoutsController()->selectedLayoutViews();
203 
204     Latte::Data::LayoutIcon icon = m_dialog->layoutsController()->selectedLayoutIcon();
205 
206     m_ui->layoutsCmb->setCurrentText(o_data.name);
207     m_ui->layoutsCmb->setLayoutIcon(icon);
208 
209     loadLayout(o_data);
210 }
211 
corona() const212 Latte::Corona *ViewsHandler::corona() const
213 {
214     return m_dialog->corona();
215 }
216 
ui() const217 Ui::ViewsDialog *ViewsHandler::ui() const
218 {
219     return m_ui;
220 }
221 
layoutsController() const222 Settings::Controller::Layouts *ViewsHandler::layoutsController() const
223 {
224     return m_dialog->layoutsController();
225 }
226 
loadLayout(const Latte::Data::Layout & data)227 void ViewsHandler::loadLayout(const Latte::Data::Layout &data)
228 {
229     updateWindowTitle();
230 }
231 
currentData() const232 Latte::Data::Layout ViewsHandler::currentData() const
233 {
234     return o_data;
235 }
236 
originalData() const237 Latte::Data::Layout ViewsHandler::originalData() const
238 {
239     return m_dialog->layoutsController()->selectedLayoutOriginalData();
240 }
241 
hasChangedData() const242 bool ViewsHandler::hasChangedData() const
243 {
244     return m_viewsController->hasChangedData();
245 }
246 
inDefaultValues() const247 bool ViewsHandler::inDefaultValues() const
248 {
249     //nothing special
250     return true;
251 }
252 
isSelectedLayoutOriginal() const253 bool ViewsHandler::isSelectedLayoutOriginal() const
254 {
255     return m_dialog->layoutsController()->isSelectedLayoutOriginal();
256 }
257 
reset()258 void ViewsHandler::reset()
259 {
260     m_viewsController->reset();
261 }
262 
resetDefaults()263 void ViewsHandler::resetDefaults()
264 {
265     //do nothing
266 }
267 
save()268 void ViewsHandler::save()
269 {
270     int viewsforremoval = m_viewsController->viewsForRemovalCount();
271 
272     if (viewsforremoval <=0 || removalConfirmation(viewsforremoval) == KMessageBox::Yes) {
273         m_viewsController->save();
274     }
275 }
276 
storedView(const QString & viewId)277 QString ViewsHandler::storedView(const QString &viewId)
278 {
279     Latte::Data::View viewdata = m_viewsController->currentData(viewId);
280 
281     if (!viewdata.isValid()) {
282         return QString();
283     }
284 
285     if (viewdata.isCreated()) {
286         CentralLayout *central = m_dialog->layoutsController()->centralLayout(currentData().id);
287         return central->storedView(viewdata.id.toInt());
288     } else if (viewdata.hasViewTemplateOrigin() || viewdata.hasLayoutOrigin()) {
289         return viewdata.originFile();
290     }
291 
292     return QString();
293 }
294 
newView(const Data::Generic & templateData)295 void ViewsHandler::newView(const Data::Generic &templateData)
296 {
297     Data::ViewsTable views = Latte::Layouts::Storage::self()->views(templateData.id);
298 
299     if (views.rowCount() > 0) {
300         Data::View viewfromtemplate = views[0];
301         viewfromtemplate.setState(Data::View::OriginFromViewTemplate, templateData.id);
302         viewfromtemplate.name = templateData.name;
303         Data::View newview = m_viewsController->appendViewFromViewTemplate(viewfromtemplate);
304 
305         showInlineMessage(i18nc("settings:dock/panel added successfully","<b>%1</b> added successfully...", newview.name),
306                           KMessageWidget::Positive);
307     }
308 }
309 
removeSelectedViews()310 void ViewsHandler::removeSelectedViews()
311 {
312     qDebug() << Q_FUNC_INFO;
313 
314     if (!m_removeViewAction->isEnabled() || !m_viewsController->hasSelectedView()) {
315         return;
316     }
317 
318     m_viewsController->removeSelectedViews();
319 }
320 
exportViewForBackup()321 void ViewsHandler::exportViewForBackup()
322 {
323     if (!m_viewsController->hasSelectedView()) {
324         return;
325     }
326 
327     if (m_viewsController->selectedViewsCount() > 1) {
328         showInlineMessage(i18n("<b>Export</b> functionality is supported only for one dock or panel each time."),
329                           KMessageWidget::Warning,
330                           false);
331         return;
332     }
333 
334     Data::ViewsTable views =  m_viewsController->selectedViewsCurrentData();
335 
336     if (views.rowCount() != 1) {
337         return;
338     }
339 
340     QString temporiginfile = storedView(views[0].id);
341 
342     QFileDialog *exportFileDialog = new QFileDialog(m_dialog, i18n("Export Dock/Panel For Backup"), QDir::homePath(), QStringLiteral("view.latte"));
343 
344     exportFileDialog->setLabelText(QFileDialog::Accept, i18nc("export view","Export"));
345     exportFileDialog->setFileMode(QFileDialog::AnyFile);
346     exportFileDialog->setAcceptMode(QFileDialog::AcceptSave);
347     exportFileDialog->setDefaultSuffix("view.latte");
348 
349     QStringList filters;
350     QString filter1(i18nc("export view", "Latte Dock/Panel file v0.2") + "(*.view.latte)");
351 
352     filters << filter1;
353 
354     exportFileDialog->setNameFilters(filters);
355 
356     connect(exportFileDialog, &QFileDialog::finished, exportFileDialog, &QFileDialog::deleteLater);
357 
358     connect(exportFileDialog, &QFileDialog::fileSelected, this, [&, temporiginfile](const QString & file) {
359         auto showExportViewError = [this](const QString &destinationfile) {
360             showInlineMessage(i18nc("settings:view export fail","Export in file <b>%1</b> <b>failed</b>...", QFileInfo(destinationfile).fileName()),
361                               KMessageWidget::Error,
362                               true);
363         };
364 
365         if (QFile::exists(file) && !QFile::remove(file)) {
366             showExportViewError(file);
367             return;
368         }
369 
370         if (file.endsWith(".view.latte")) {
371             if (!QFile(temporiginfile).copy(file)) {
372                 showExportViewError(file);
373                 return;
374             }
375 
376             QAction *openUrlAction = new QAction(i18n("Open Location..."), this);
377             openUrlAction->setData(file);
378             QList<QAction *> actions;
379             actions << openUrlAction;
380 
381             connect(openUrlAction, &QAction::triggered, this, [&, openUrlAction]() {
382                 QString file = openUrlAction->data().toString();
383 
384                 if (!file.isEmpty()) {
385                     KIO::highlightInFileManager({file});
386                 }
387             });
388 
389             showInlineMessage(i18nc("settings:view export success","Export in file <b>%1</b> succeeded...", QFileInfo(file).fileName()),
390                               KMessageWidget::Positive,
391                               false,
392                               actions);
393         }
394     });
395 
396     exportFileDialog->open();
397     exportFileDialog->selectFile(views[0].name);
398 }
399 
exportViewAsTemplate()400 void ViewsHandler::exportViewAsTemplate()
401 {
402     if (!m_viewsController->hasSelectedView()) {
403         return;
404     }
405 
406     if (m_viewsController->selectedViewsCount() > 1) {
407         showInlineMessage(i18n("<b>Export</b> functionality is supported only for one dock or panel each time."),
408                           KMessageWidget::Warning,
409                           false);
410         return;
411     }
412 
413     Data::ViewsTable views =  m_viewsController->selectedViewsCurrentData();
414 
415     if (views.rowCount() != 1) {
416         return;
417     }
418 
419     Data::View exportview = views[0];
420     exportview.id = storedView(views[0].id);
421     exportview.setState(Data::View::OriginFromLayout, exportview.id, currentData().name, views[0].id);
422 
423     Dialog::ExportTemplateDialog *exportdlg = new Dialog::ExportTemplateDialog(m_dialog, exportview);
424     exportdlg->exec();
425 }
426 
importView()427 void ViewsHandler::importView()
428 {
429     qDebug() << Q_FUNC_INFO;
430 
431     QFileDialog *importFileDialog = new QFileDialog(m_dialog, i18nc("import dock/panel", "Import Dock/Panel"), QDir::homePath(), QStringLiteral("view.latte"));
432 
433     importFileDialog->setWindowIcon(QIcon::fromTheme("document-import"));
434     importFileDialog->setLabelText(QFileDialog::Accept, i18n("Import"));
435     importFileDialog->setFileMode(QFileDialog::AnyFile);
436     importFileDialog->setAcceptMode(QFileDialog::AcceptOpen);
437     importFileDialog->setDefaultSuffix("view.latte");
438 
439     QStringList filters;
440     filters << QString(i18nc("import dock panel", "Latte Dock or Panel file v0.2") + "(*.view.latte)");
441     importFileDialog->setNameFilters(filters);
442 
443     connect(importFileDialog, &QFileDialog::finished, importFileDialog, &QFileDialog::deleteLater);
444 
445     connect(importFileDialog, &QFileDialog::fileSelected, this, [&](const QString & file) {
446         Data::Generic templatedata;
447         templatedata.id = file;
448         templatedata.name = QFileInfo(file).fileName();
449         templatedata.name = templatedata.name.remove(".view.latte");
450         newView(templatedata);
451     });
452 
453     importFileDialog->open();
454 }
455 
onCurrentLayoutIndexChanged(int row)456 void ViewsHandler::onCurrentLayoutIndexChanged(int row)
457 {
458     bool switchtonewlayout{false};
459 
460     if (m_lastConfirmedLayoutIndex != row) {
461         if (hasChangedData()) { //new layout was chosen but there are changes
462             KMessageBox::ButtonCode result = saveChangesConfirmation();
463 
464             if (result == KMessageBox::Yes) {
465                 int removalviews = m_viewsController->viewsForRemovalCount();
466                 KMessageBox::ButtonCode removalresponse = removalConfirmation(removalviews);
467 
468                 if (removalresponse == KMessageBox::Yes) {
469                     switchtonewlayout = true;
470                     m_lastConfirmedLayoutIndex = row;
471                     m_viewsController->save();
472                 } else {
473                     //do nothing
474                 }
475             } else if (result == KMessageBox::No) {
476                 switchtonewlayout = true;
477                 m_lastConfirmedLayoutIndex = row;
478             } else if (result == KMessageBox::Cancel) {
479                 //do nothing
480             }
481         } else { //new layout was chosen and there are no changes
482             switchtonewlayout = true;
483             m_lastConfirmedLayoutIndex = row;
484         }
485     }
486 
487     if (switchtonewlayout) {
488         m_dialog->deleteInlineMessages();
489         QString layoutId = m_layoutsProxyModel->data(m_layoutsProxyModel->index(row, Model::Layouts::IDCOLUMN), Qt::UserRole).toString();
490         m_dialog->layoutsController()->selectRow(layoutId);
491         reload();
492         emit currentLayoutChanged();
493     } else {
494         //! reset combobox index
495         m_ui->layoutsCmb->setCurrentText(o_data.name);
496     }
497 }
498 
onSelectionChanged()499 void ViewsHandler::onSelectionChanged()
500 {
501     bool hasselected = m_viewsController->hasSelectedView();
502 
503     setTwinProperty(m_duplicateViewAction, TWINENABLED, hasselected);
504     setTwinProperty(m_removeViewAction, TWINENABLED, hasselected);
505     setTwinProperty(m_exportViewAction, TWINENABLED, hasselected);
506     m_viewExportSubMenu->setEnabled(hasselected);
507 }
508 
updateWindowTitle()509 void ViewsHandler::updateWindowTitle()
510 {
511     m_dialog->setWindowTitle(i18nc("<layout name> Docks/Panels",
512                                    "%1 Docks/Panels",
513                                    m_ui->layoutsCmb->currentText()));
514 }
515 
removalConfirmation(const int & viewsCount)516 KMessageBox::ButtonCode ViewsHandler::removalConfirmation(const int &viewsCount)
517 {
518     if (viewsCount<=0) {
519         return KMessageBox::No;
520     }
521 
522     if (hasChangedData() && viewsCount>0) {
523         return KMessageBox::warningYesNo(m_dialog,
524                                          i18np("You are going to <b>remove 1</b> dock or panel completely from your layout.<br/>Would you like to continue?",
525                                                "You are going to <b>remove %1</b> docks and panels completely from your layout.<br/>Would you like to continue?",
526                                                viewsCount),
527                                          i18n("Approve Removal"));
528     }
529 
530     return KMessageBox::No;
531 }
532 
saveChangesConfirmation()533 KMessageBox::ButtonCode ViewsHandler::saveChangesConfirmation()
534 {
535     if (hasChangedData()) {
536         QString layoutName = o_data.name;
537         QString saveChangesText = i18n("The settings of <b>%1</b> layout have changed.<br/>Do you want to apply the changes <b>now</b> or discard them?", layoutName);
538 
539         return m_dialog->saveChangesConfirmation(saveChangesText);
540     }
541 
542     return KMessageBox::Cancel;
543 }
544 
545 }
546 }
547 }
548