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