File indexing completed on 2024-04-21 05:31:00

0001 /*
0002     SPDX-FileCopyrightText: 2021 Michail Vourlakos <mvourlakos@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "viewscontroller.h"
0007 
0008 // local
0009 #include <config-latte.h>
0010 #include "ui_viewsdialog.h"
0011 #include "viewsdialog.h"
0012 #include "viewshandler.h"
0013 #include "viewsmodel.h"
0014 #include "viewstableview.h"
0015 #include "delegates/namedelegate.h"
0016 #include "delegates/singleoptiondelegate.h"
0017 #include "delegates/singletextdelegate.h"
0018 #include "../generic/generictools.h"
0019 #include "../settingsdialog/templateskeeper.h"
0020 #include "../../data/errorinformationdata.h"
0021 #include "../../layout/genericlayout.h"
0022 #include "../../layout/centrallayout.h"
0023 #include "../../layouts/manager.h"
0024 #include "../../layouts/synchronizer.h"
0025 #include "../../view/view.h"
0026 
0027 // Qt
0028 #include <QHeaderView>
0029 #include <QItemSelection>
0030 
0031 // KDE
0032 #include <KMessageWidget>
0033 #include <KSharedConfig>
0034 #include <KIO/OpenUrlJob>
0035 
0036 
0037 namespace Latte {
0038 namespace Settings {
0039 namespace Controller {
0040 
0041 
0042 Views::Views(Settings::Handler::ViewsHandler *parent)
0043     : QObject(parent),
0044       m_handler(parent),
0045       m_model(new Model::Views(this, m_handler->corona())),
0046       m_proxyModel(new QSortFilterProxyModel(this)),
0047       m_view(m_handler->ui()->viewsTable),
0048       m_storage(KConfigGroup(KSharedConfig::openConfig(),"LatteSettingsDialog").group("ViewsDialog"))
0049 {
0050     loadConfig();
0051     m_proxyModel->setSourceModel(m_model);
0052 
0053     connect(m_model, &QAbstractItemModel::dataChanged, this, &Views::dataChanged);
0054     connect(m_model, &Model::Views::rowsInserted, this, &Views::dataChanged);
0055     connect(m_model, &Model::Views::rowsRemoved, this, &Views::dataChanged);
0056 
0057     connect(m_handler, &Handler::ViewsHandler::currentLayoutChanged, this, &Views::onCurrentLayoutChanged);
0058 
0059     init();
0060 }
0061 
0062 Views::~Views()
0063 {
0064     saveConfig();
0065 }
0066 
0067 QAbstractItemModel *Views::proxyModel() const
0068 {
0069     return m_proxyModel;
0070 }
0071 
0072 QAbstractItemModel *Views::baseModel() const
0073 {
0074     return m_model;
0075 }
0076 
0077 QTableView *Views::view() const
0078 {
0079     return m_view;
0080 }
0081 
0082 void Views::init()
0083 {
0084     m_view->setModel(m_proxyModel);
0085     //m_view->setHorizontalHeader(m_headerView);
0086     m_view->verticalHeader()->setVisible(false);
0087     m_view->setSortingEnabled(true);
0088 
0089     m_proxyModel->setSortRole(Model::Views::SORTINGROLE);
0090     m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0091 
0092     m_view->sortByColumn(m_viewSortColumn, m_viewSortOrder);
0093 
0094     m_view->setItemDelegateForColumn(Model::Views::IDCOLUMN, new Settings::View::Delegate::SingleText(this));
0095     m_view->setItemDelegateForColumn(Model::Views::NAMECOLUMN, new Settings::View::Delegate::NameDelegate(this));
0096     m_view->setItemDelegateForColumn(Model::Views::SCREENCOLUMN, new Settings::View::Delegate::SingleOption(this));
0097     m_view->setItemDelegateForColumn(Model::Views::EDGECOLUMN, new Settings::View::Delegate::SingleOption(this));
0098     m_view->setItemDelegateForColumn(Model::Views::ALIGNMENTCOLUMN, new Settings::View::Delegate::SingleOption(this));
0099     m_view->setItemDelegateForColumn(Model::Views::SUBCONTAINMENTSCOLUMN, new Settings::View::Delegate::SingleText(this));
0100 
0101     applyColumnWidths();
0102 
0103     m_cutAction = new QAction(QIcon::fromTheme("edit-cut"), i18n("Cut"), m_view);
0104     m_cutAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X));
0105     connect(m_cutAction, &QAction::triggered, this, &Views::cutSelectedViews);
0106 
0107     m_copyAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Copy"), m_view);
0108     m_copyAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
0109     connect(m_copyAction, &QAction::triggered, this, &Views::copySelectedViews);
0110 
0111     m_pasteAction = new QAction(QIcon::fromTheme("edit-paste"), i18n("Paste"), m_view);
0112     m_pasteAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V));
0113     connect(m_pasteAction, &QAction::triggered, this, &Views::pasteSelectedViews);
0114 
0115     m_duplicateAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Duplicate Here"), m_view);
0116     m_duplicateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
0117     connect(m_duplicateAction, &QAction::triggered, this, &Views::duplicateSelectedViews);
0118 
0119     m_view->addAction(m_cutAction);
0120     m_view->addAction(m_copyAction);
0121     m_view->addAction(m_duplicateAction);
0122     m_view->addAction(m_pasteAction);
0123 
0124     onSelectionsChanged();
0125 
0126     connect(m_view, &View::ViewsTableView::selectionsChanged, this, &Views::onSelectionsChanged);
0127     connect(m_view, &QObject::destroyed, this, &Views::storeColumnWidths);
0128 
0129     connect(m_view->horizontalHeader(), &QObject::destroyed, this, [&]() {
0130         m_viewSortColumn = m_view->horizontalHeader()->sortIndicatorSection();
0131         m_viewSortOrder = m_view->horizontalHeader()->sortIndicatorOrder();
0132     });
0133 }
0134 
0135 void Views::reset()
0136 {
0137     m_model->resetData();
0138 
0139     //! Clear any templates keeper data in order to produce reupdates if needed
0140     m_handler->layoutsController()->templatesKeeper()->clear();
0141 }
0142 
0143 bool Views::hasChangedData() const
0144 {
0145     return m_model->hasChangedData();
0146 }
0147 
0148 bool Views::hasSelectedView() const
0149 {
0150     return m_view->selectionModel()->hasSelection();
0151 }
0152 
0153 int Views::selectedViewsCount() const
0154 {
0155     return m_view->selectionModel()->selectedRows(Model::Views::IDCOLUMN).count();
0156 }
0157 
0158 int Views::rowForId(QString id) const
0159 {
0160     for (int i = 0; i < m_proxyModel->rowCount(); ++i) {
0161         QString rowId = m_proxyModel->data(m_proxyModel->index(i, Model::Views::IDCOLUMN), Qt::UserRole).toString();
0162 
0163         if (rowId == id) {
0164             return i;
0165         }
0166     }
0167 
0168     return -1;
0169 }
0170 
0171 const Data::ViewsTable Views::selectedViewsCurrentData() const
0172 {
0173     Data::ViewsTable selectedviews;
0174 
0175     if (!hasSelectedView()) {
0176         return selectedviews;
0177     }
0178 
0179     QModelIndexList layoutidindexes = m_view->selectionModel()->selectedRows(Model::Views::IDCOLUMN);
0180 
0181     for(int i=0; i<layoutidindexes.count(); ++i) {
0182         QString selectedid = layoutidindexes[i].data(Qt::UserRole).toString();
0183         selectedviews <<  m_model->currentData(selectedid);
0184     }
0185 
0186     return selectedviews;
0187 }
0188 
0189 const Latte::Data::View Views::appendViewFromViewTemplate(const Data::View &view)
0190 {
0191     Data::View newview = view;
0192     newview.name = uniqueViewName(view.name);
0193     m_model->appendTemporaryView(newview);
0194     return newview;
0195 }
0196 
0197 const Latte::Data::View Views::currentData(const QString &id)
0198 {
0199     return m_model->currentData(id);
0200 }
0201 
0202 Data::ViewsTable Views::selectedViewsForClipboard()
0203 {
0204     Data::ViewsTable clipboardviews;
0205     if (!hasSelectedView()) {
0206         return clipboardviews;
0207     }
0208 
0209     Data::ViewsTable selectedviews = selectedViewsCurrentData();
0210     Latte::Data::Layout currentlayout = m_handler->currentData();
0211 
0212     for(int i=0; i<selectedviews.rowCount(); ++i) {
0213         if (selectedviews[i].state() == Data::View::IsInvalid) {
0214             continue;
0215         }
0216 
0217         Latte::Data::View copiedview = selectedviews[i];
0218 
0219         if (selectedviews[i].state() == Data::View::IsCreated) {
0220             QString storedviewpath = m_handler->layoutsController()->templatesKeeper()->storedView(currentlayout.id, selectedviews[i].id);
0221             copiedview.setState(Data::View::OriginFromLayout, storedviewpath, currentlayout.id, selectedviews[i].id);
0222         } else if (selectedviews[i].state() == Data::View::OriginFromViewTemplate) {
0223             copiedview.setState(Data::View::OriginFromViewTemplate, selectedviews[i].originFile(), currentlayout.id, selectedviews[i].id);
0224         } else if (selectedviews[i].state() == Data::View::OriginFromLayout) {
0225             //! is already in valid values
0226         }
0227 
0228         copiedview.isActive = false;
0229         clipboardviews << copiedview;
0230 
0231     }
0232 
0233     return clipboardviews;
0234 }
0235 
0236 void Views::copySelectedViews()
0237 {
0238     qDebug() << Q_FUNC_INFO;
0239 
0240     if (!hasSelectedView()) {
0241         return;
0242     }
0243 
0244     //! reset cut substates for views
0245     Data::ViewsTable currentviews = m_model->currentViewsData();
0246     for (int i=0; i<currentviews.rowCount(); ++i) {
0247         Data::View cview = currentviews[i];
0248         cview.isMoveOrigin = false;
0249         m_model->updateCurrentView(cview.id, cview);
0250     }
0251 
0252     Data::ViewsTable clipboardviews = selectedViewsForClipboard();
0253 
0254     //! reset cut substates for views
0255     for (int i=0; i<clipboardviews.rowCount(); ++i) {
0256         clipboardviews[i].isMoveOrigin = false;
0257 
0258         /*   Data::View tempview = m_model->currentData(clipboardviews[i].id);
0259         tempview.isMoveOrigin = false;
0260         m_model->updateCurrentView(tempview.id, tempview);*/
0261     }
0262 
0263     m_handler->layoutsController()->templatesKeeper()->setClipboardContents(clipboardviews);
0264 }
0265 
0266 void Views::cutSelectedViews()
0267 {
0268     qDebug() << Q_FUNC_INFO;
0269 
0270     if (!hasSelectedView()) {
0271         return;
0272     }
0273 
0274     //! reset previous move records
0275     Data::ViewsTable currentviews = m_model->currentViewsData();
0276     for (int i=0; i<currentviews.rowCount(); ++i) {
0277         Data::View cview = currentviews[i];
0278         cview.isMoveOrigin = false;
0279         m_model->updateCurrentView(cview.id, cview);
0280     }
0281 
0282     Data::ViewsTable clipboardviews = selectedViewsForClipboard();
0283 
0284     //! activate cut substates for views
0285     for (int i=0; i<clipboardviews.rowCount(); ++i) {
0286         clipboardviews[i].isMoveOrigin = true;
0287 
0288         Data::View tempview = m_model->currentData(clipboardviews[i].id);
0289         tempview.isMoveOrigin = true;
0290         m_model->updateCurrentView(tempview.id, tempview);
0291     }
0292 
0293     m_handler->layoutsController()->templatesKeeper()->setClipboardContents(clipboardviews);
0294 }
0295 
0296 void Views::pasteSelectedViews()
0297 {
0298     Data::ViewsTable clipboardviews = m_handler->layoutsController()->templatesKeeper()->clipboardContents();
0299     Latte::Data::Layout currentlayout = m_handler->currentData();
0300 
0301     bool hascurrentlayoutcuttedviews{false};
0302 
0303     for(int i=0; i<clipboardviews.rowCount(); ++i) {
0304         if (clipboardviews[i].isMoveOrigin && clipboardviews[i].originLayout() == currentlayout.id) {
0305             hascurrentlayoutcuttedviews = true;
0306             continue;
0307         }
0308 
0309         if (clipboardviews[i].isMoveOrigin) {
0310             //! update cut flags only for real cutted view and not for copied one
0311             clipboardviews[i].isMoveOrigin = false;
0312             clipboardviews[i].isMoveDestination = true;
0313         }
0314 
0315         appendViewFromViewTemplate(clipboardviews[i]);
0316     }
0317 
0318     if (hascurrentlayoutcuttedviews) {
0319         m_handler->showInlineMessage(i18n("Docks and panels from <b>Paste</b> action are already present in current layout"),
0320                                      KMessageWidget::Warning);
0321     }
0322 }
0323 
0324 void Views::duplicateSelectedViews()
0325 {
0326     qDebug() << Q_FUNC_INFO;
0327 
0328     if (!hasSelectedView()) {
0329         return;
0330     }
0331 
0332     Data::ViewsTable selectedviews = selectedViewsCurrentData();
0333     Latte::Data::Layout currentlayout = m_handler->currentData();
0334 
0335     for(int i=0; i<selectedviews.rowCount(); ++i) {
0336         if (selectedviews[i].state() == Data::View::IsCreated) {
0337             QString storedviewpath = m_handler->layoutsController()->templatesKeeper()->storedView(currentlayout.id, selectedviews[i].id);
0338             Latte::Data::View duplicatedview = selectedviews[i];
0339             duplicatedview.setState(Data::View::OriginFromLayout, storedviewpath, currentlayout.id, selectedviews[i].id);
0340             duplicatedview.isActive = false;
0341             appendViewFromViewTemplate(duplicatedview);
0342         } else if (selectedviews[i].state() == Data::View::OriginFromViewTemplate
0343                    || selectedviews[i].state() == Data::View::OriginFromLayout) {
0344             Latte::Data::View duplicatedview = selectedviews[i];
0345             duplicatedview.isActive = false;
0346             appendViewFromViewTemplate(duplicatedview);
0347         }
0348     }
0349 }
0350 
0351 void Views::removeSelectedViews()
0352 {
0353     if (!hasSelectedView()) {
0354         return;
0355     }
0356 
0357     Data::ViewsTable selectedviews = selectedViewsCurrentData();;
0358 
0359     int selectionheadrow = m_model->rowForId(selectedviews[0].id);
0360 
0361     for (int i=0; i<selectedviews.rowCount(); ++i) {
0362         m_model->removeView(selectedviews[i].id);
0363     }
0364 
0365     m_view->selectRow(qBound(0, selectionheadrow, m_model->rowCount()-1));
0366 }
0367 
0368 void Views::selectRow(const QString &id)
0369 {
0370     m_view->selectRow(rowForId(id));
0371 }
0372 
0373 void Views::onCurrentLayoutChanged()
0374 {   
0375     Data::Layout currentlayoutdata = m_handler->currentData();
0376 
0377     Data::ViewsTable clipboardviews = m_handler->layoutsController()->templatesKeeper()->clipboardContents();
0378 
0379     if (!clipboardviews.isEmpty()) {
0380         //! clipboarded views needs to update the relevant flags to loaded views
0381         for (int i=0; i<currentlayoutdata.views.rowCount(); ++i) {
0382             QString vid = currentlayoutdata.views[i].id;
0383 
0384             if (!clipboardviews.containsId(vid)) {
0385                 continue;
0386             }
0387 
0388             if (clipboardviews[vid].isMoveOrigin && (clipboardviews[vid].originLayout() == currentlayoutdata.id)) {
0389                 currentlayoutdata.views[vid].isMoveOrigin = true;
0390             }
0391         }
0392     }
0393 
0394     m_model->setOriginalData(currentlayoutdata.views);
0395 
0396     //! track viewscountchanged signal for current active layout scenario
0397     for (const auto &var : m_currentLayoutConnections) {
0398         QObject::disconnect(var);
0399     }
0400 
0401     Latte::CentralLayout *currentlayout = m_handler->layoutsController()->centralLayout(currentlayoutdata.id);
0402 
0403     if (currentlayout && currentlayout->isActive()) {
0404         m_currentLayoutConnections << connect(currentlayout, &Layout::GenericLayout::viewsCountChanged, this, [&, currentlayout](){
0405             m_model->updateActiveStatesBasedOn(currentlayout);
0406         });
0407     }
0408 
0409     messagesForErrorsWarnings(currentlayout);
0410 }
0411 
0412 void Views::onSelectionsChanged()
0413 {
0414     bool hasselectedview = hasSelectedView();
0415 
0416     m_cutAction->setVisible(hasselectedview);
0417     m_copyAction->setVisible(hasselectedview);
0418     m_duplicateAction->setVisible(hasselectedview);
0419     m_pasteAction->setEnabled(m_handler->layoutsController()->templatesKeeper()->hasClipboardContents());
0420 }
0421 
0422 int Views::viewsForRemovalCount() const
0423 {
0424     if (!hasChangedData()) {
0425         return 0;
0426     }
0427 
0428     Latte::Data::ViewsTable originalViews = m_model->originalViewsData();
0429     Latte::Data::ViewsTable currentViews = m_model->currentViewsData();
0430     Latte::Data::ViewsTable removedViews = originalViews.subtracted(currentViews);
0431 
0432     return removedViews.rowCount();
0433 }
0434 
0435 bool Views::hasValidOriginView(const Data::View &view)
0436 {
0437     bool viewidisinteger{true};
0438     int vid_int = view.originView().toInt(&viewidisinteger);
0439     QString vid_str = view.originView();
0440 
0441     if (vid_str.isEmpty() || !viewidisinteger || vid_int<=0) {
0442         return false;
0443     }
0444 
0445     return true;
0446 }
0447 
0448 CentralLayout *Views::originLayout(const Data::View &view)
0449 {
0450     QString origincurrentid = view.originLayout();
0451     Data::Layout originlayoutdata = m_handler->layoutsController()->originalData(origincurrentid);
0452 
0453     Latte::CentralLayout *originactive = m_handler->layoutsController()->isLayoutOriginal(origincurrentid) ?
0454                 m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(originlayoutdata.name) : nullptr;
0455 
0456     return originactive;
0457 }
0458 
0459 void Views::updateDoubledMoveDestinationRows() {
0460     //! only one isMoveDestination should exist for each unique move isMoveOrigin case
0461     //! all the rest that have been created through Cut/Paste or Duplicate options should become
0462     //! simple OriginFromViewTemplate cases
0463 
0464     for (int i=0; i<m_model->rowCount(); ++i) {
0465         Data::View baseview = m_model->at(i);
0466 
0467         if (!baseview.isMoveDestination || baseview.state()!=Data::View::OriginFromLayout) {
0468             continue;
0469         }
0470 
0471         for (int j=i+1; j<m_model->rowCount(); ++j) {
0472             Data::View subsequentview = m_model->at(j);
0473 
0474             if (subsequentview.isMoveDestination
0475                     && subsequentview.state() == Data::View::OriginFromLayout
0476                     && subsequentview.originFile() == baseview.originFile()
0477                     && subsequentview.originLayout() == baseview.originLayout()
0478                     && subsequentview.originView() == baseview.originView()) {
0479                 //! this is a subsequent view that needs to be updated properly
0480                 subsequentview.isMoveDestination = false;
0481                 subsequentview.isMoveOrigin = false;
0482                 subsequentview.setState(Data::View::OriginFromViewTemplate, subsequentview.originFile(), QString(), QString());
0483                 m_model->updateCurrentView(subsequentview.id, subsequentview);
0484             }
0485         }
0486     }
0487 }
0488 
0489 void Views::messagesForErrorsWarnings(const Latte::CentralLayout *centralLayout, const bool &showNoErrorsMessage)
0490 {
0491     if (!centralLayout) {
0492         return;
0493     }
0494 
0495     Data::Layout currentdata = centralLayout->data();
0496 
0497     m_model->clearErrorsAndWarnings();
0498 
0499     //! warnings
0500     if (currentdata.warnings > 0) {
0501         Data::WarningsList warnings = centralLayout->warnings();
0502 
0503         // show warnings
0504         for (int i=0; i< warnings.count(); ++i) {
0505             if (warnings[i].id == Data::Warning::ORPHANEDSUBCONTAINMENT) {
0506                 messageForWarningOrphanedSubContainments(warnings[i]);
0507             } else if (warnings[i].id == Data::Warning::APPLETANDCONTAINMENTWITHSAMEID) {
0508                 messageForWarningAppletAndContainmentWithSameId(warnings[i]);
0509             }
0510         }
0511 
0512         // count warnings per view
0513         for (int i=0; i<warnings.count(); ++i) {
0514             for (int j=0; j<warnings[i].information.rowCount(); ++j) {
0515                 if (!warnings[i].information[j].containment.isValid()) {
0516                     continue;
0517                 }
0518 
0519                 QString cid = warnings[i].information[j].containment.storageId;
0520                 Data::View view = m_model->currentData(cid);
0521                 if (!view.isValid()) {
0522                     //! one step back from subcontainment to view in order to find the influenced view id
0523                     cid = m_model->viewForSubContainment(cid);
0524                     view = m_model->currentData(cid);
0525                 }
0526 
0527                 if (view.isValid()) {
0528                     view.warnings++;
0529                     m_model->updateCurrentView(cid, view);
0530                 }
0531             }
0532         }
0533     }
0534 
0535     //! errors
0536     if (currentdata.errors > 0) {
0537         Data::ErrorsList errors = centralLayout->errors();
0538 
0539         // show errors
0540         for (int i=0; i< errors.count(); ++i) {
0541             if (errors[i].id == Data::Error::APPLETSWITHSAMEID) {
0542                 messageForErrorAppletsWithSameId(errors[i]);
0543             } else if (errors[i].id == Data::Error::ORPHANEDPARENTAPPLETOFSUBCONTAINMENT) {
0544                 messageForErrorOrphanedParentAppletOfSubContainment(errors[i]);
0545             }
0546         }
0547 
0548         // count errors per view
0549         for (int i=0; i<errors.count(); ++i) {
0550             for (int j=0; j<errors[i].information.rowCount(); ++j) {
0551                 if (!errors[i].information[j].containment.isValid()) {
0552                     continue;
0553                 }
0554 
0555                 QString cid = errors[i].information[j].containment.storageId;
0556                 Data::View view = m_model->currentData(cid);
0557                 if (!view.isValid()) {
0558                     //! one step back from subcontainment to view in order to find the influenced view id
0559                     cid = m_model->viewForSubContainment(cid);
0560                     view = m_model->currentData(cid);
0561                 }
0562 
0563                 if (view.isValid()) {
0564                     view.errors++;
0565                     m_model->updateCurrentView(cid, view);
0566                 }
0567             }
0568         }
0569     }
0570 
0571     m_handler->layoutsController()->setLayoutCurrentErrorsWarnings(currentdata.id, currentdata.errors, currentdata.warnings);
0572 
0573     if (showNoErrorsMessage && currentdata.errors == 0 && currentdata.warnings == 0) {
0574         m_handler->showInlineMessage(i18n("Really nice! You are good to go, your layout does not report any errors or warnings."),
0575                                      KMessageWidget::Positive,
0576                                      false);
0577     }
0578 
0579 }
0580 
0581 void Views::showDefaultPersistentErrorWarningInlineMessage(const QString &messageText,
0582                                                            const KMessageWidget::MessageType &messageType,
0583                                                            QList<QAction *> extraActions,
0584                                                            const bool &showOpenLayoutAction)
0585 {
0586     QList<QAction *> actions;
0587     actions << extraActions;
0588 
0589     if (showOpenLayoutAction) {
0590         Data::Layout currentlayout = m_handler->currentData();
0591 
0592         //! add default action to open layout
0593         QAction *openlayoutaction = new QAction(i18n("Edit Layout"), this);
0594         openlayoutaction->setEnabled(!currentlayout.isActive);
0595         openlayoutaction->setIcon(QIcon::fromTheme("document-edit"));
0596         openlayoutaction->setData(currentlayout.id);
0597         actions << openlayoutaction;
0598 
0599         connect(openlayoutaction, &QAction::triggered, this, [&, openlayoutaction]() {
0600             QString file = openlayoutaction->data().toString();
0601 
0602             if (!file.isEmpty()) {
0603                 auto job = new KIO::OpenUrlJob(QUrl::fromLocalFile(file), QStringLiteral("text/plain"), this);
0604                 job->start();
0605                 showDefaultInlineMessageValidator();
0606             }
0607         });
0608     }
0609 
0610     //! show message
0611     m_handler->showInlineMessage(messageText,
0612                                  messageType,
0613                                  true,
0614                                  actions);
0615 }
0616 
0617 void Views::showDefaultInlineMessageValidator()
0618 {
0619     Data::Layout currentlayout = m_handler->currentData();
0620 
0621     //! add default action to open layout
0622     QAction *validateaction = new QAction(i18n("Validate"), this);
0623     validateaction->setIcon(QIcon::fromTheme("view-refresh"));
0624     validateaction->setData(currentlayout.id);
0625 
0626     QList<QAction *> actions;
0627     actions << validateaction;
0628 
0629     connect(validateaction, &QAction::triggered, this, [&, currentlayout]() {
0630 
0631         auto centrallayout = m_handler->layoutsController()->centralLayout(currentlayout.id);
0632         if (centrallayout && !centrallayout->isActive()) {
0633             KSharedConfigPtr lFile = KSharedConfig::openConfig(centrallayout->file());
0634             //! update configuration with latest changes
0635             lFile->reparseConfiguration();
0636         }
0637 
0638         messagesForErrorsWarnings(centrallayout, true);
0639     });
0640 
0641     QString messagetext = i18n("After you have made your layout file changes, please click <b>Validate</b> to confirm them.");
0642 
0643     //! show message
0644     m_handler->showInlineMessage(messagetext,
0645                                  KMessageWidget::Warning,
0646                                  true,
0647                                  actions);
0648 }
0649 
0650 void Views::messageForErrorAppletsWithSameId(const Data::Error &error)
0651 {
0652     if (error.id != Data::Error::APPLETSWITHSAMEID) {
0653         return;
0654     }
0655 
0656     //! construct message
0657     QString message = i18nc("error id and title", "<b>Error #%1: %2</b> <br/>",error.id, error.name);
0658     message += "<br/>";
0659     message += i18n("In your layout there are two or more applets with same id. Such situation can create crashes, abnormal behavior and data loss when you activate and use this layout.<br/>");
0660 
0661     message += "<br/>";
0662     message += i18n("<b>Applets:</b><br/>");
0663     for (int i=0; i<error.information.rowCount(); ++i) {
0664         QString appletname = error.information[i].applet.visibleName();
0665         QString appletstorageid = error.information[i].applet.storageId;
0666         QString viewname = visibleViewName(error.information[i].containment.storageId);
0667         QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
0668         QString containmentstorageid = error.information[i].containment.storageId;
0669         message += i18nc("applets with same id error, applet name, applet id, containment name, containment id",
0670                          "&nbsp;&nbsp;• <b>%1</b> [#%2] inside  <b>%3</b> [#%4]<br/>",
0671                          appletname,
0672                          appletstorageid,
0673                          containmentname,
0674                          containmentstorageid);
0675     }
0676 
0677     message += "<br/>";
0678     message += i18n("<b>Possible Solutions:</b><br/>");
0679     message += i18n("&nbsp;&nbsp;1. Activate this layout and restart Latte<br/>");
0680     message += i18n("&nbsp;&nbsp;2. Remove the mentioned applets from your layout<br/>");
0681     message += i18n("&nbsp;&nbsp;3. Update manually the applets id when the layout is <b>not active</b><br/>");
0682     message += i18n("&nbsp;&nbsp;4. Remove this layout totally<br/>");
0683 
0684     showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Error);
0685 }
0686 
0687 void Views::messageForErrorOrphanedParentAppletOfSubContainment(const Data::Error &error)
0688 {
0689     if (error.id != Data::Error::ORPHANEDPARENTAPPLETOFSUBCONTAINMENT) {
0690         return;
0691     }
0692 
0693     //! construct message
0694     QString message = i18nc("error id and title", "<b>Error #%1: %2</b> <br/><br/>", error.id, error.name);
0695     message += i18n("In your layout there are orphaned pseudo applets that link to unexistent subcontainments. Such case is for example a systemtray that has lost connection with its child applets. Such situation can create crashes, abnormal behavior and data loss when you activate and use this layout.<br/>");
0696 
0697     message += "<br/>";
0698     message += i18n("<b>Pseudo Applets:</b><br/>");
0699     for (int i=0; i<error.information.rowCount(); ++i) {
0700         if (!error.information[i].applet.isValid()) {
0701             continue;
0702         }
0703 
0704         QString appletname = error.information[i].applet.visibleName();
0705         QString appletstorageid = error.information[i].applet.storageId;
0706         QString viewname = visibleViewName(error.information[i].containment.storageId);
0707         QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
0708         QString containmentstorageid = error.information[i].containment.storageId;
0709         message += i18nc("orphaned pseudo applets, applet name, applet id, containment name, containment id",
0710                          "&nbsp;&nbsp;• <b>%1</b> [#%2] inside  <b>%3</b> [#%4]<br/>",
0711                          appletname,
0712                          appletstorageid,
0713                          containmentname,
0714                          containmentstorageid);
0715     }
0716 
0717     message += "<br/>";
0718     message += i18n("<b>Orphaned Subcontainments:</b><br/>");
0719     for (int i=0; i<error.information.rowCount(); ++i) {
0720         if (error.information[i].applet.isValid()) {
0721             continue;
0722         }
0723 
0724         QString viewname = visibleViewName(error.information[i].containment.storageId);
0725         QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
0726         QString containmentstorageid = error.information[i].containment.storageId;
0727         message += i18nc("orphaned subcontainments, containment name, containment id",
0728                          "&nbsp;&nbsp;• <b>%1</b> [#%2] <br/>",
0729                          containmentname,
0730                          containmentstorageid);
0731     }
0732 
0733     message += "<br/>";
0734     message += i18n("<b>Possible Solutions:</b><br/>");
0735     message += i18n("&nbsp;&nbsp;1. Update manually the subcontainment id inside pseudo applet settings when the layout is <b>not active</b><br/>");
0736     message += i18n("&nbsp;&nbsp;2. Remove this layout totally<br/>");
0737 
0738     //! show message
0739     showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Error);
0740 }
0741 
0742 void Views::messageForWarningAppletAndContainmentWithSameId(const Data::Warning &warning)
0743 {
0744     if (warning.id != Data::Warning::APPLETANDCONTAINMENTWITHSAMEID) {
0745         return;
0746     }
0747 
0748     //! construct message
0749     QString message = i18nc("warning id and title", "<b>Warning #%1: %2</b> <br/><br/>", warning.id, warning.name);
0750     message += i18n("In your layout there are applets and containments with the same id. Such situation is not dangerous but it should not occur.<br/>");
0751 
0752     message += "<br/>";
0753     message += i18n("<b>Applets:</b><br/>");
0754     for (int i=0; i<warning.information.rowCount(); ++i) {
0755         if (!warning.information[i].applet.isValid()) {
0756             continue;
0757         }
0758 
0759         QString appletname = warning.information[i].applet.visibleName();
0760         QString appletstorageid = warning.information[i].applet.storageId;
0761         QString viewname = visibleViewName(warning.information[i].containment.storageId);
0762         QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
0763         QString containmentstorageid = warning.information[i].containment.storageId;
0764         message += i18nc("applets, applet name, applet id, containment name, containment id",
0765                          "&nbsp;&nbsp;• <b>%1</b> [#%2] inside  <b>%3</b> [#%4]<br/>",
0766                          appletname,
0767                          appletstorageid,
0768                          containmentname,
0769                          containmentstorageid);
0770     }
0771 
0772     message += "<br/>";
0773     message += i18n("<b>Containments:</b><br/>");
0774     for (int i=0; i<warning.information.rowCount(); ++i) {
0775         if (warning.information[i].applet.isValid()) {
0776             continue;
0777         }
0778 
0779         QString viewname = visibleViewName(warning.information[i].containment.storageId);
0780         QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
0781         QString containmentstorageid = warning.information[i].containment.storageId;
0782         message += i18nc("containments, containment name, containment id",
0783                          "&nbsp;&nbsp;• <b>%1</b> [#%2] <br/>",
0784                          containmentname,
0785                          containmentstorageid);
0786     }
0787 
0788     message += "<br/>";
0789     message += i18n("<b>Possible Solutions:</b><br/>");
0790     message += i18n("&nbsp;&nbsp;1. Update manually the containments or applets id when the layout is <b>not active</b><br/>");
0791     message += i18n("&nbsp;&nbsp;2. Remove any of the containments or applets that conflict with each other<br/>");
0792 
0793     //! show message
0794     showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Warning);
0795 }
0796 
0797 void Views::messageForWarningOrphanedSubContainments(const Data::Warning &warning)
0798 {
0799     if (warning.id != Data::Warning::ORPHANEDSUBCONTAINMENT) {
0800         return;
0801     }
0802 
0803     QList<int> orphaned;
0804 
0805     //! construct message
0806     QString message = i18nc("warning id and title", "<b>Warning #%1: %2</b> <br/><br/>", warning.id, warning.name);
0807     message += i18n("In your layout there are orphaned subcontainments that are not used by any dock or panel. Such situation is not dangerous but it is advised to remove them in order to reduce memory usage.<br/>");
0808 
0809     message += "<br/>";
0810     message += i18n("<b>Orphaned Subcontainments:</b><br/>");
0811     for (int i=0; i<warning.information.rowCount(); ++i) {
0812         if (warning.information[i].applet.isValid()) {
0813             continue;
0814         }
0815 
0816         QString viewname = visibleViewName(warning.information[i].containment.storageId);
0817         QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
0818         QString containmentstorageid = warning.information[i].containment.storageId;
0819         message += i18nc("orphaned subcontainments, containment name, containment id",
0820                          "&nbsp;&nbsp;• <b>%1</b> [#%2] <br/>",
0821                          containmentname,
0822                          containmentstorageid);
0823 
0824         orphaned << warning.information[i].containment.storageId.toInt();
0825     }
0826 
0827     message += "<br/>";
0828     message += i18n("<b>Possible Solutions:</b><br/>");
0829     message += i18n("&nbsp;&nbsp;1. Click <b>Repair</b> button in order to remove orphaned subcontainments<br/>");
0830     message += i18n("&nbsp;&nbsp;2. Remove manually orphaned subcontainments when the layout is <b>not active</b><br/>");
0831 
0832     //! add extra repair action
0833     QAction *repairlayoutaction = new QAction(i18n("Repair"), this);
0834     repairlayoutaction->setIcon(QIcon::fromTheme("dialog-yes"));
0835     QList<QAction *> extraactions;
0836     extraactions << repairlayoutaction;
0837 
0838     Latte::Data::Layout currentlayout = m_handler->currentData();
0839 
0840     connect(repairlayoutaction, &QAction::triggered, this, [&, currentlayout, orphaned]() {
0841         auto centrallayout = m_handler->layoutsController()->centralLayout(currentlayout.id);
0842 
0843         for (int i=0; i<orphaned.count(); ++i) {
0844             centrallayout->removeOrphanedSubContainment(orphaned[i]);
0845         }
0846 
0847         messagesForErrorsWarnings(centrallayout, true);
0848     });
0849 
0850     //! show message
0851     showDefaultPersistentErrorWarningInlineMessage(message,
0852                                                    KMessageWidget::Warning,
0853                                                    extraactions);
0854 }
0855 
0856 void Views::save()
0857 {
0858     //! when this function is called we consider that removal has already been approved
0859     updateDoubledMoveDestinationRows();
0860 
0861     Latte::Data::Layout originallayout = m_handler->originalData();
0862     Latte::Data::Layout currentlayout = m_handler->currentData();
0863     Latte::CentralLayout *central = m_handler->layoutsController()->centralLayout(currentlayout.id);
0864 
0865     //! views in model
0866     Latte::Data::ViewsTable originalViews = m_model->originalViewsData();
0867     Latte::Data::ViewsTable currentViews = m_model->currentViewsData();
0868     Latte::Data::ViewsTable alteredViews = m_model->alteredViews();
0869     Latte::Data::ViewsTable newViews = m_model->newViews();
0870 
0871     QHash<QString, Data::View> newviewsresponses;
0872     QHash<QString, Data::View> cuttedpastedviews;
0873     QHash<QString, Data::View> cuttedpastedactiveviews;
0874 
0875     m_debugSaveCall++;
0876     qDebug() << "org.kde.latte ViewsDialog::save() call: " << m_debugSaveCall << "-------- ";
0877 
0878     //! add new views that are accepted
0879     for(int i=0; i<newViews.rowCount(); ++i){
0880         if (newViews[i].isMoveDestination) {
0881             CentralLayout *originActive = originLayout(newViews[i]);
0882             bool inmovebetweenactivelayouts = central->isActive() && originActive && central != originActive && hasValidOriginView(newViews[i]);
0883 
0884             if (inmovebetweenactivelayouts) {
0885                 cuttedpastedactiveviews[newViews[i].id] = newViews[i];
0886                 continue;
0887             }
0888 
0889             cuttedpastedviews[newViews[i].id] = newViews[i];
0890         }
0891 
0892         if (newViews[i].state() == Data::View::OriginFromViewTemplate) {
0893             Data::View addedview = central->newView(newViews[i]);
0894 
0895             newviewsresponses[newViews[i].id] = addedview;
0896         } else if (newViews[i].state() == Data::View::OriginFromLayout) {
0897             Data::View adjustedview = newViews[i];
0898             adjustedview.setState(Data::View::OriginFromViewTemplate, newViews[i].originFile(), QString(), QString());
0899             Data::View addedview = central->newView(adjustedview);
0900 
0901             newviewsresponses[newViews[i].id] = addedview;
0902         }
0903     }
0904 
0905     //! update altered views
0906     for (int i=0; i<alteredViews.rowCount(); ++i) {
0907         if (alteredViews[i].state() == Data::View::IsCreated && !alteredViews[i].isMoveOrigin) {
0908             qDebug() << "org.kde.latte ViewsDialog::save() updating altered view :: " << alteredViews[i];
0909             central->updateView(alteredViews[i]);
0910         }
0911     }
0912 
0913     //! remove deprecated views that have been removed from user
0914     Latte::Data::ViewsTable removedViews = originalViews.subtracted(currentViews);
0915 
0916     for (int i=0; i<removedViews.rowCount(); ++i) {
0917         qDebug() << "org.kde.latte ViewsDialog::save() real removing view :: " << removedViews[i];
0918         central->removeView(removedViews[i]);
0919     }
0920 
0921     //! remove deprecated views from external layouts that must be removed because of Cut->Paste Action
0922     for(const auto vid: cuttedpastedviews.keys()){
0923         bool viewidisinteger{true};
0924         int vid_int = cuttedpastedviews[vid].originView().toInt(&viewidisinteger);
0925         QString vid_str = cuttedpastedviews[vid].originView();
0926 
0927         if (vid_str.isEmpty() || !viewidisinteger || vid_int<=0) {
0928             //! ignore origin views that have not been created already
0929             continue;
0930         }
0931 
0932         qDebug() << "org.kde.latte ViewsDialog::save() removing cut-pasted view :: " << cuttedpastedviews[vid];
0933 
0934         //! Be Careful: Remove deprecated views from Cut->Paste Action
0935         QString origincurrentid = cuttedpastedviews[vid].originLayout();
0936         Data::Layout originlayout = m_handler->layoutsController()->originalData(origincurrentid);
0937         Latte::CentralLayout *origin = m_handler->layoutsController()->centralLayout(originlayout.id);
0938 
0939         Data::ViewsTable originviews = Latte::Layouts::Storage::self()->views(origin);
0940 
0941         if (originviews.containsId(vid_str)) {
0942             origin->removeView(originviews[vid_str]);
0943         }
0944     }
0945 
0946     //! move active views between different active layouts
0947     for (const auto vid: cuttedpastedactiveviews.keys()) {
0948         Data::View pastedactiveview = cuttedpastedactiveviews[vid];
0949         uint originviewid = pastedactiveview.originView().toUInt();
0950         CentralLayout *origin = originLayout(pastedactiveview);
0951         QString originlayoutname = origin->name();
0952         QString destinationlayoutname = originallayout.name;
0953 
0954         auto view = origin->viewForContainment(originviewid);
0955 
0956         QString tempviewid = pastedactiveview.id;
0957         pastedactiveview.id = QString::number(originviewid);
0958 
0959         qDebug() << "org.kde.latte ViewsDialog::save() move to another layout cutted-pasted active view :: " << pastedactiveview;
0960 
0961         if (view) {
0962             //! onscreen_view->onscreen_view
0963             //! onscreen_view->offscreen_view
0964             pastedactiveview.setState(pastedactiveview.state(), pastedactiveview.originFile(), destinationlayoutname, pastedactiveview.originView());
0965             origin->updateView(pastedactiveview);
0966         } else {
0967             //! offscreen_view->onscreen_view
0968             m_handler->corona()->layoutsManager()->moveView(originlayoutname, originviewid, destinationlayoutname);
0969             //!is needed in order for layout to not trigger another move
0970             pastedactiveview.setState(Data::View::IsCreated, QString(), QString(), QString());
0971             central->updateView(pastedactiveview);
0972         }
0973 
0974         pastedactiveview.setState(Data::View::IsCreated, QString(), QString(), QString());
0975         newviewsresponses[tempviewid] = pastedactiveview;
0976     }
0977 
0978     //! update
0979     if ((removedViews.rowCount() > 0) || (newViews.rowCount() > 0)) {
0980         m_handler->corona()->layoutsManager()->synchronizer()->syncActiveLayoutsToOriginalFiles();
0981     }
0982 
0983     //! update model for newly added views
0984     for (const auto vid: newviewsresponses.keys()) {
0985         m_model->setOriginalView(vid, newviewsresponses[vid]);
0986     }
0987 
0988     //! update all table with latest data and make the original one
0989     currentViews = m_model->currentViewsData();
0990     m_model->setOriginalData(currentViews);
0991 
0992     //! update model activeness
0993     if (central->isActive()) {
0994         m_model->updateActiveStatesBasedOn(central);
0995     }
0996 
0997     //! Clear any templates keeper data in order to produce reupdates if needed
0998     m_handler->layoutsController()->templatesKeeper()->clear();
0999 }
1000 
1001 QString Views::uniqueViewName(QString name)
1002 {
1003     if (name.isEmpty()) {
1004         return name;
1005     }
1006 
1007     int pos_ = name.lastIndexOf(QRegExp(QString(" - [0-9]+")));
1008 
1009     if (m_model->containsCurrentName(name) && pos_ > 0) {
1010         name = name.left(pos_);
1011     }
1012 
1013     int i = 2;
1014 
1015     QString namePart = name;
1016 
1017     while (m_model->containsCurrentName(name)) {
1018         name = namePart + " - " + QString::number(i);
1019         i++;
1020     }
1021 
1022     return name;
1023 }
1024 
1025 QString Views::visibleViewName(const QString &id) const
1026 {
1027     if (id.isEmpty()) {
1028         return QString();
1029     }
1030 
1031     Data::View view = m_model->currentData(id);
1032 
1033     if (view.isValid()) {
1034         return view.name;
1035     }
1036 
1037     return QString();
1038 
1039 }
1040 
1041 void Views::applyColumnWidths()
1042 {
1043     m_view->horizontalHeader()->setSectionResizeMode(Model::Views::SUBCONTAINMENTSCOLUMN, QHeaderView::Stretch);
1044 
1045     if (m_viewColumnWidths.count()<(Model::Views::columnCount()-1)) {
1046         return;
1047     }
1048 
1049     m_view->setColumnWidth(Model::Views::IDCOLUMN, m_viewColumnWidths[0].toInt());
1050     m_view->setColumnWidth(Model::Views::NAMECOLUMN, m_viewColumnWidths[1].toInt());
1051     m_view->setColumnWidth(Model::Views::SCREENCOLUMN, m_viewColumnWidths[2].toInt());
1052     m_view->setColumnWidth(Model::Views::EDGECOLUMN, m_viewColumnWidths[3].toInt());
1053     m_view->setColumnWidth(Model::Views::ALIGNMENTCOLUMN, m_viewColumnWidths[4].toInt());
1054 }
1055 
1056 void Views::storeColumnWidths()
1057 {
1058     if (m_viewColumnWidths.isEmpty() || (m_viewColumnWidths.count()<Model::Views::columnCount()-1)) {
1059         m_viewColumnWidths.clear();
1060         for (int i=0; i<Model::Views::columnCount(); ++i) {
1061             m_viewColumnWidths << "";
1062         }
1063     }
1064 
1065     m_viewColumnWidths[0] = QString::number(m_view->columnWidth(Model::Views::IDCOLUMN));
1066     m_viewColumnWidths[1] = QString::number(m_view->columnWidth(Model::Views::NAMECOLUMN));
1067     m_viewColumnWidths[2] = QString::number(m_view->columnWidth(Model::Views::SCREENCOLUMN));
1068     m_viewColumnWidths[3] = QString::number(m_view->columnWidth(Model::Views::EDGECOLUMN));
1069     m_viewColumnWidths[4] = QString::number(m_view->columnWidth(Model::Views::ALIGNMENTCOLUMN));
1070 }
1071 
1072 void Views::loadConfig()
1073 {
1074     QStringList defaultcolumnwidths;
1075     defaultcolumnwidths << QString::number(59) << QString::number(256) << QString::number(142) << QString::number(135) << QString::number(131);
1076 
1077     m_viewColumnWidths = m_storage.readEntry("columnWidths", defaultcolumnwidths);
1078     m_viewSortColumn = m_storage.readEntry("sortColumn", (int)Model::Views::SCREENCOLUMN);
1079     m_viewSortOrder = static_cast<Qt::SortOrder>(m_storage.readEntry("sortOrder", (int)Qt::AscendingOrder));
1080 }
1081 
1082 void Views::saveConfig()
1083 {
1084     m_storage.writeEntry("columnWidths", m_viewColumnWidths);
1085     m_storage.writeEntry("sortColumn", m_viewSortColumn);
1086     m_storage.writeEntry("sortOrder", (int)m_viewSortOrder);
1087 }
1088 
1089 }
1090 }
1091 }
1092