File indexing completed on 2024-12-08 08:00:07

0001 /*
0002     SPDX-FileCopyrightText: 2021 Michail Vourlakos <mvourlakos@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "viewshandler.h"
0007 
0008 // local
0009 #include "ui_viewsdialog.h"
0010 #include "viewscontroller.h"
0011 #include "viewsdialog.h"
0012 #include "viewstableview.h"
0013 #include "../exporttemplatedialog/exporttemplatedialog.h"
0014 #include "../settingsdialog/layoutscontroller.h"
0015 #include "../settingsdialog/layoutsmodel.h"
0016 #include "../settingsdialog/delegates/layoutcmbitemdelegate.h"
0017 #include "../../data/layoutstable.h"
0018 #include "../../data/genericbasictable.h"
0019 #include "../../data/viewstable.h"
0020 #include "../../lattecorona.h"
0021 #include "../../layout/abstractlayout.h"
0022 #include "../../layout/centrallayout.h"
0023 #include "../../layouts/manager.h"
0024 #include "../../layouts/storage.h"
0025 #include "../../layouts/synchronizer.h"
0026 #include "../../templates/templatesmanager.h"
0027 #include "../../tools/commontools.h"
0028 
0029 // Qt
0030 #include <QFileDialog>
0031 
0032 // KDE
0033 #include <KLocalizedString>
0034 #include <KStandardGuiItem>
0035 #include <KIO/OpenFileManagerWindowJob>
0036 
0037 namespace Latte {
0038 namespace Settings {
0039 namespace Handler {
0040 
0041 ViewsHandler::ViewsHandler(Dialog::ViewsDialog *dialog)
0042     : Generic(dialog),
0043       m_dialog(dialog),
0044       m_ui(m_dialog->ui())
0045 {
0046     m_viewsController = new Settings::Controller::Views(this);
0047 
0048     init();
0049 }
0050 
0051 ViewsHandler::~ViewsHandler()
0052 {
0053 }
0054 
0055 void ViewsHandler::init()
0056 {
0057     //! Layouts
0058     m_layoutsProxyModel = new QSortFilterProxyModel(this);
0059     m_layoutsProxyModel->setSourceModel(m_dialog->layoutsController()->baseModel());
0060     m_layoutsProxyModel->setSortRole(Model::Layouts::SORTINGROLE);
0061     m_layoutsProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0062     m_layoutsProxyModel->sort(Model::Layouts::NAMECOLUMN, Qt::AscendingOrder);
0063 
0064     m_ui->layoutsCmb->setModel(m_layoutsProxyModel);
0065     m_ui->layoutsCmb->setModelColumn(Model::Layouts::NAMECOLUMN);
0066     m_ui->layoutsCmb->setItemDelegate(new Settings::Layout::Delegate::LayoutCmbItemDelegate(this));
0067 
0068     //! New Button
0069     m_newViewAction = new QAction(i18nc("new view", "&New"), this);
0070     m_newViewAction->setToolTip(i18n("New dock or panel"));
0071     m_newViewAction->setIcon(QIcon::fromTheme("add"));
0072     m_newViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_N));
0073     connectActionWithButton(m_ui->newBtn, m_newViewAction);
0074     connect(m_newViewAction, &QAction::triggered, m_ui->newBtn, &QPushButton::showMenu);
0075 
0076     initViewTemplatesSubMenu();
0077     m_newViewAction->setMenu(m_viewTemplatesSubMenu);
0078     m_ui->newBtn->setMenu(m_viewTemplatesSubMenu);
0079 
0080     connect(corona()->templatesManager(), &Latte::Templates::Manager::viewTemplatesChanged, this, &ViewsHandler::initViewTemplatesSubMenu);
0081 
0082     //! Duplicate Button
0083     m_duplicateViewAction = new QAction(i18nc("duplicate dock or panel", "&Duplicate"), this);
0084     m_duplicateViewAction->setToolTip(i18n("Duplicate selected dock or panel"));
0085     m_duplicateViewAction->setIcon(QIcon::fromTheme("edit-copy"));
0086     m_duplicateViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
0087     connectActionWithButton(m_ui->duplicateBtn, m_duplicateViewAction);
0088     connect(m_duplicateViewAction, &QAction::triggered, m_viewsController, &Controller::Views::duplicateSelectedViews);
0089 
0090     //! Remove Button
0091     m_removeViewAction = new QAction(i18nc("remove layout", "Remove"), m_ui->removeBtn);
0092     m_removeViewAction->setToolTip(i18n("Remove selected view"));
0093     m_removeViewAction->setIcon(QIcon::fromTheme("delete"));
0094     m_removeViewAction->setShortcut(QKeySequence(Qt::Key_Delete));
0095     connectActionWithButton(m_ui->removeBtn, m_removeViewAction);
0096     connect(m_removeViewAction, &QAction::triggered, this, &ViewsHandler::removeSelectedViews);
0097     m_ui->removeBtn->addAction(m_removeViewAction); //this is needed in order to be triggered properly
0098 
0099     //! Import
0100     m_importViewAction =new QAction(i18nc("import dock/panel","&Import..."));
0101     m_duplicateViewAction->setToolTip(i18n("Import dock or panel from local file"));
0102     m_importViewAction->setIcon(QIcon::fromTheme("document-import"));
0103     m_importViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I));
0104     connectActionWithButton(m_ui->importBtn, m_importViewAction);
0105     connect(m_importViewAction, &QAction::triggered, this, &ViewsHandler::importView);
0106 
0107     //! Export
0108     m_exportViewAction = new QAction(i18nc("export layout", "&Export"), this);
0109     m_exportViewAction->setToolTip(i18n("Export selected dock or panel at your system"));
0110     m_exportViewAction->setIcon(QIcon::fromTheme("document-export"));
0111     m_exportViewAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_E));
0112     connectActionWithButton(m_ui->exportBtn, m_exportViewAction);
0113     connect(m_exportViewAction, &QAction::triggered, m_ui->exportBtn, &QPushButton::showMenu);
0114 
0115     initViewExportSubMenu();
0116     m_exportViewAction->setMenu(m_viewExportSubMenu);
0117     m_ui->exportBtn->setMenu(m_viewExportSubMenu);
0118 
0119     //! signals
0120     connect(this, &ViewsHandler::currentLayoutChanged, this, &ViewsHandler::reload);
0121 
0122     reload();
0123     m_lastConfirmedLayoutIndex =m_ui->layoutsCmb->currentIndex();
0124 
0125     emit currentLayoutChanged();
0126 
0127     //! connect layout combobox after the selected layout has been loaded
0128     connect(m_ui->layoutsCmb, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ViewsHandler::onCurrentLayoutIndexChanged);
0129 
0130     //!
0131     connect(m_viewsController, &Settings::Controller::Views::dataChanged, this, &ViewsHandler::dataChanged);
0132 
0133     connect(m_ui->viewsTable, &View::ViewsTableView::selectionsChanged, this, &ViewsHandler::onSelectionChanged);
0134 
0135     onSelectionChanged();
0136 }
0137 
0138 void ViewsHandler::initViewTemplatesSubMenu()
0139 {
0140     if (!m_viewTemplatesSubMenu) {
0141         m_viewTemplatesSubMenu = new QMenu(m_ui->newBtn);
0142         m_viewTemplatesSubMenu->setMinimumWidth(m_ui->newBtn->width() * 2);
0143     } else {
0144         m_viewTemplatesSubMenu->clear();
0145     }
0146 
0147     /*Add View Templates for New Action*/
0148     Data::GenericBasicTable templates = corona()->templatesManager()->viewTemplates();
0149 
0150     bool customtemplateseparatoradded{false};
0151 
0152     for (int i=0; i<templates.rowCount(); ++i) {
0153         if (!customtemplateseparatoradded && templates[i].id.startsWith(QDir::homePath())) {
0154             m_viewTemplatesSubMenu->addSeparator();
0155             customtemplateseparatoradded = true;
0156         }
0157 
0158         QAction *newview = m_viewTemplatesSubMenu->addAction(templates[i].name);
0159         newview->setIcon(QIcon::fromTheme("document-new"));
0160 
0161         Data::Generic templateData = templates[i];
0162 
0163         connect(newview, &QAction::triggered, this, [&, templateData]() {
0164             newView(templateData);
0165         });
0166     }
0167 
0168     if (templates.rowCount() > 0) {
0169         QAction *openTemplatesDirectory = m_viewTemplatesSubMenu->addAction(i18n("Templates..."));
0170         openTemplatesDirectory->setToolTip(i18n("Open templates directory"));
0171         openTemplatesDirectory->setIcon(QIcon::fromTheme("edit"));
0172 
0173         connect(openTemplatesDirectory, &QAction::triggered, this, [&]() {
0174             KIO::highlightInFileManager({QString(Latte::configPath() + "/latte/templates/Dock.view.latte")});
0175         });
0176     }
0177 }
0178 
0179 void ViewsHandler::initViewExportSubMenu()
0180 {
0181     if (!m_viewExportSubMenu) {
0182         m_viewExportSubMenu = new QMenu(m_ui->exportBtn);
0183         m_viewExportSubMenu->setMinimumWidth(m_ui->exportBtn->width() * 2);
0184     } else {
0185         m_viewExportSubMenu->clear();
0186     }
0187 
0188     QAction *exportforbackup = m_viewExportSubMenu->addAction(i18nc("export for backup","&Export For Backup..."));
0189     exportforbackup->setIcon(QIcon::fromTheme("document-export"));
0190     exportforbackup->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT  + Qt::Key_E));
0191     connect(exportforbackup, &QAction::triggered, this, &ViewsHandler::exportViewForBackup);
0192 
0193     QAction *exportastemplate = m_viewExportSubMenu->addAction(i18nc("export as template","Export As &Template..."));
0194     exportastemplate->setIcon(QIcon::fromTheme("document-export"));
0195     exportastemplate->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT  + Qt::Key_T));
0196     connect(exportastemplate, &QAction::triggered, this, &ViewsHandler::exportViewAsTemplate);
0197 }
0198 
0199 void ViewsHandler::reload()
0200 {
0201     o_data = m_dialog->layoutsController()->selectedLayoutCurrentData();
0202     o_data.views = m_dialog->layoutsController()->selectedLayoutViews();
0203 
0204     Latte::Data::LayoutIcon icon = m_dialog->layoutsController()->selectedLayoutIcon();
0205 
0206     m_ui->layoutsCmb->setCurrentText(o_data.name);
0207     m_ui->layoutsCmb->setLayoutIcon(icon);
0208 
0209     loadLayout(o_data);
0210 }
0211 
0212 Latte::Corona *ViewsHandler::corona() const
0213 {
0214     return m_dialog->corona();
0215 }
0216 
0217 Ui::ViewsDialog *ViewsHandler::ui() const
0218 {
0219     return m_ui;
0220 }
0221 
0222 Settings::Controller::Layouts *ViewsHandler::layoutsController() const
0223 {
0224     return m_dialog->layoutsController();
0225 }
0226 
0227 void ViewsHandler::loadLayout(const Latte::Data::Layout &data)
0228 {
0229     updateWindowTitle();
0230 }
0231 
0232 Latte::Data::Layout ViewsHandler::currentData() const
0233 {
0234     return o_data;
0235 }
0236 
0237 Latte::Data::Layout ViewsHandler::originalData() const
0238 {
0239     return m_dialog->layoutsController()->selectedLayoutOriginalData();
0240 }
0241 
0242 bool ViewsHandler::hasChangedData() const
0243 {
0244     return m_viewsController->hasChangedData();
0245 }
0246 
0247 bool ViewsHandler::inDefaultValues() const
0248 {
0249     //nothing special
0250     return true;
0251 }
0252 
0253 bool ViewsHandler::isSelectedLayoutOriginal() const
0254 {
0255     return m_dialog->layoutsController()->isSelectedLayoutOriginal();
0256 }
0257 
0258 void ViewsHandler::reset()
0259 {
0260     m_viewsController->reset();
0261 }
0262 
0263 void ViewsHandler::resetDefaults()
0264 {
0265     //do nothing
0266 }
0267 
0268 void ViewsHandler::save()
0269 {
0270     int viewsforremoval = m_viewsController->viewsForRemovalCount();
0271 
0272     if (viewsforremoval <=0 || removalConfirmation(viewsforremoval) == KMessageBox::Yes) {
0273         m_viewsController->save();
0274     }
0275 }
0276 
0277 QString ViewsHandler::storedView(const QString &viewId)
0278 {
0279     Latte::Data::View viewdata = m_viewsController->currentData(viewId);
0280 
0281     if (!viewdata.isValid()) {
0282         return QString();
0283     }
0284 
0285     if (viewdata.isCreated()) {
0286         CentralLayout *central = m_dialog->layoutsController()->centralLayout(currentData().id);
0287         return central->storedView(viewdata.id.toInt());
0288     } else if (viewdata.hasViewTemplateOrigin() || viewdata.hasLayoutOrigin()) {
0289         return viewdata.originFile();
0290     }
0291 
0292     return QString();
0293 }
0294 
0295 void ViewsHandler::newView(const Data::Generic &templateData)
0296 {
0297     Data::ViewsTable views = Latte::Layouts::Storage::self()->views(templateData.id);
0298 
0299     if (views.rowCount() > 0) {
0300         Data::View viewfromtemplate = views[0];
0301         viewfromtemplate.setState(Data::View::OriginFromViewTemplate, templateData.id);
0302         viewfromtemplate.name = templateData.name;
0303         Data::View newview = m_viewsController->appendViewFromViewTemplate(viewfromtemplate);
0304 
0305         showInlineMessage(i18nc("settings:dock/panel added successfully","<b>%1</b> added successfully...", newview.name),
0306                           KMessageWidget::Positive);
0307     }
0308 }
0309 
0310 void ViewsHandler::removeSelectedViews()
0311 {
0312     qDebug() << Q_FUNC_INFO;
0313 
0314     if (!m_removeViewAction->isEnabled() || !m_viewsController->hasSelectedView()) {
0315         return;
0316     }
0317 
0318     m_viewsController->removeSelectedViews();
0319 }
0320 
0321 void ViewsHandler::exportViewForBackup()
0322 {
0323     if (!m_viewsController->hasSelectedView()) {
0324         return;
0325     }
0326 
0327     if (m_viewsController->selectedViewsCount() > 1) {
0328         showInlineMessage(i18n("<b>Export</b> functionality is supported only for one dock or panel each time."),
0329                           KMessageWidget::Warning,
0330                           false);
0331         return;
0332     }
0333 
0334     Data::ViewsTable views =  m_viewsController->selectedViewsCurrentData();
0335 
0336     if (views.rowCount() != 1) {
0337         return;
0338     }
0339 
0340     QString temporiginfile = storedView(views[0].id);
0341 
0342     QFileDialog *exportFileDialog = new QFileDialog(m_dialog, i18n("Export Dock/Panel For Backup"), QDir::homePath(), QStringLiteral("view.latte"));
0343 
0344     exportFileDialog->setLabelText(QFileDialog::Accept, i18nc("export view","Export"));
0345     exportFileDialog->setFileMode(QFileDialog::AnyFile);
0346     exportFileDialog->setAcceptMode(QFileDialog::AcceptSave);
0347     exportFileDialog->setDefaultSuffix("view.latte");
0348 
0349     QStringList filters;
0350     QString filter1(i18nc("export view", "Latte Dock/Panel file v0.2") + "(*.view.latte)");
0351 
0352     filters << filter1;
0353 
0354     exportFileDialog->setNameFilters(filters);
0355 
0356     connect(exportFileDialog, &QFileDialog::finished, exportFileDialog, &QFileDialog::deleteLater);
0357 
0358     connect(exportFileDialog, &QFileDialog::fileSelected, this, [&, temporiginfile](const QString & file) {
0359         auto showExportViewError = [this](const QString &destinationfile) {
0360             showInlineMessage(i18nc("settings:view export fail","Export in file <b>%1</b> <b>failed</b>...", QFileInfo(destinationfile).fileName()),
0361                               KMessageWidget::Error,
0362                               true);
0363         };
0364 
0365         if (QFile::exists(file) && !QFile::remove(file)) {
0366             showExportViewError(file);
0367             return;
0368         }
0369 
0370         if (file.endsWith(".view.latte")) {
0371             if (!QFile(temporiginfile).copy(file)) {
0372                 showExportViewError(file);
0373                 return;
0374             }
0375 
0376             QAction *openUrlAction = new QAction(i18n("Open Location..."), this);
0377             openUrlAction->setData(file);
0378             QList<QAction *> actions;
0379             actions << openUrlAction;
0380 
0381             connect(openUrlAction, &QAction::triggered, this, [&, openUrlAction]() {
0382                 QString file = openUrlAction->data().toString();
0383 
0384                 if (!file.isEmpty()) {
0385                     KIO::highlightInFileManager({file});
0386                 }
0387             });
0388 
0389             showInlineMessage(i18nc("settings:view export success","Export in file <b>%1</b> succeeded...", QFileInfo(file).fileName()),
0390                               KMessageWidget::Positive,
0391                               false,
0392                               actions);
0393         }
0394     });
0395 
0396     exportFileDialog->open();
0397     exportFileDialog->selectFile(views[0].name);
0398 }
0399 
0400 void ViewsHandler::exportViewAsTemplate()
0401 {
0402     if (!m_viewsController->hasSelectedView()) {
0403         return;
0404     }
0405 
0406     if (m_viewsController->selectedViewsCount() > 1) {
0407         showInlineMessage(i18n("<b>Export</b> functionality is supported only for one dock or panel each time."),
0408                           KMessageWidget::Warning,
0409                           false);
0410         return;
0411     }
0412 
0413     Data::ViewsTable views =  m_viewsController->selectedViewsCurrentData();
0414 
0415     if (views.rowCount() != 1) {
0416         return;
0417     }
0418 
0419     Data::View exportview = views[0];
0420     exportview.id = storedView(views[0].id);
0421     exportview.setState(Data::View::OriginFromLayout, exportview.id, currentData().name, views[0].id);
0422 
0423     Dialog::ExportTemplateDialog *exportdlg = new Dialog::ExportTemplateDialog(m_dialog, exportview);
0424     exportdlg->exec();
0425 }
0426 
0427 void ViewsHandler::importView()
0428 {
0429     qDebug() << Q_FUNC_INFO;
0430 
0431     QFileDialog *importFileDialog = new QFileDialog(m_dialog, i18nc("import dock/panel", "Import Dock/Panel"), QDir::homePath(), QStringLiteral("view.latte"));
0432 
0433     importFileDialog->setWindowIcon(QIcon::fromTheme("document-import"));
0434     importFileDialog->setLabelText(QFileDialog::Accept, i18n("Import"));
0435     importFileDialog->setFileMode(QFileDialog::AnyFile);
0436     importFileDialog->setAcceptMode(QFileDialog::AcceptOpen);
0437     importFileDialog->setDefaultSuffix("view.latte");
0438 
0439     QStringList filters;
0440     filters << QString(i18nc("import dock panel", "Latte Dock or Panel file v0.2") + "(*.view.latte)");
0441     importFileDialog->setNameFilters(filters);
0442 
0443     connect(importFileDialog, &QFileDialog::finished, importFileDialog, &QFileDialog::deleteLater);
0444 
0445     connect(importFileDialog, &QFileDialog::fileSelected, this, [&](const QString & file) {
0446         Data::Generic templatedata;
0447         templatedata.id = file;
0448         templatedata.name = QFileInfo(file).fileName();
0449         templatedata.name = templatedata.name.remove(".view.latte");
0450         newView(templatedata);
0451     });
0452 
0453     importFileDialog->open();
0454 }
0455 
0456 void ViewsHandler::onCurrentLayoutIndexChanged(int row)
0457 {
0458     bool switchtonewlayout{false};
0459 
0460     if (m_lastConfirmedLayoutIndex != row) {
0461         if (hasChangedData()) { //new layout was chosen but there are changes
0462             KMessageBox::ButtonCode result = saveChangesConfirmation();
0463 
0464             if (result == KMessageBox::Yes) {
0465                 int removalviews = m_viewsController->viewsForRemovalCount();
0466                 KMessageBox::ButtonCode removalresponse = removalConfirmation(removalviews);
0467 
0468                 if (removalresponse == KMessageBox::Yes) {
0469                     switchtonewlayout = true;
0470                     m_lastConfirmedLayoutIndex = row;
0471                     m_viewsController->save();
0472                 } else {
0473                     //do nothing
0474                 }
0475             } else if (result == KMessageBox::No) {
0476                 switchtonewlayout = true;
0477                 m_lastConfirmedLayoutIndex = row;
0478             } else if (result == KMessageBox::Cancel) {
0479                 //do nothing
0480             }
0481         } else { //new layout was chosen and there are no changes
0482             switchtonewlayout = true;
0483             m_lastConfirmedLayoutIndex = row;
0484         }
0485     }
0486 
0487     if (switchtonewlayout) {
0488         m_dialog->deleteInlineMessages();
0489         QString layoutId = m_layoutsProxyModel->data(m_layoutsProxyModel->index(row, Model::Layouts::IDCOLUMN), Qt::UserRole).toString();
0490         m_dialog->layoutsController()->selectRow(layoutId);
0491         reload();
0492         emit currentLayoutChanged();
0493     } else {
0494         //! reset combobox index
0495         m_ui->layoutsCmb->setCurrentText(o_data.name);
0496     }
0497 }
0498 
0499 void ViewsHandler::onSelectionChanged()
0500 {
0501     bool hasselected = m_viewsController->hasSelectedView();
0502 
0503     setTwinProperty(m_duplicateViewAction, TWINENABLED, hasselected);
0504     setTwinProperty(m_removeViewAction, TWINENABLED, hasselected);
0505     setTwinProperty(m_exportViewAction, TWINENABLED, hasselected);
0506     m_viewExportSubMenu->setEnabled(hasselected);
0507 }
0508 
0509 void ViewsHandler::updateWindowTitle()
0510 {
0511     m_dialog->setWindowTitle(i18nc("<layout name> Docks/Panels",
0512                                    "%1 Docks/Panels",
0513                                    m_ui->layoutsCmb->currentText()));
0514 }
0515 
0516 KMessageBox::ButtonCode ViewsHandler::removalConfirmation(const int &viewsCount)
0517 {
0518     if (viewsCount<=0) {
0519         return KMessageBox::No;
0520     }
0521 
0522     if (hasChangedData() && viewsCount>0) {
0523         return KMessageBox::warningYesNo(m_dialog,
0524                                          i18np("You are going to <b>remove 1</b> dock or panel completely from your layout.<br/>Would you like to continue?",
0525                                                "You are going to <b>remove %1</b> docks and panels completely from your layout.<br/>Would you like to continue?",
0526                                                viewsCount),
0527                                          i18n("Approve Removal"));
0528     }
0529 
0530     return KMessageBox::No;
0531 }
0532 
0533 KMessageBox::ButtonCode ViewsHandler::saveChangesConfirmation()
0534 {
0535     if (hasChangedData()) {
0536         QString layoutName = o_data.name;
0537         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);
0538 
0539         return m_dialog->saveChangesConfirmation(saveChangesText);
0540     }
0541 
0542     return KMessageBox::Cancel;
0543 }
0544 
0545 }
0546 }
0547 }