Warning, file /plasma/latte-dock/app/settings/settingsdialog/layoutscontroller.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2020 Michail Vourlakos <mvourlakos@gmail.com>
0003     SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005 
0006 #include "layoutscontroller.h"
0007 
0008 // local
0009 #include "ui_settingsdialog.h"
0010 #include "settingsdialog.h"
0011 #include "tablayoutshandler.h"
0012 #include "templateskeeper.h"
0013 #include "delegates/activitiesdelegate.h"
0014 #include "delegates/backgrounddelegate.h"
0015 #include "delegates/checkboxdelegate.h"
0016 #include "delegates/layoutnamedelegate.h"
0017 #include "../universalsettings.h"
0018 #include "../generic/generictools.h"
0019 #include "../../screenpool.h"
0020 #include "../../data/uniqueidinfo.h"
0021 #include "../../layout/centrallayout.h"
0022 #include "../../layouts/importer.h"
0023 #include "../../layouts/manager.h"
0024 #include "../../layouts/synchronizer.h"
0025 #include "../../templates/templatesmanager.h"
0026 
0027 // Qt
0028 #include <QDir>
0029 #include <QFile>
0030 #include <QHeaderView>
0031 #include <QItemSelection>
0032 #include <QStringList>
0033 #include <QTemporaryDir>
0034 #include <QTemporaryFile>
0035 
0036 // KDE
0037 #include <KArchive/KTar>
0038 #include <KArchive/KArchiveEntry>
0039 #include <KArchive/KArchiveDirectory>
0040 #include <KMessageWidget>
0041 
0042 namespace Latte {
0043 namespace Settings {
0044 namespace Controller {
0045 
0046 Layouts::Layouts(Settings::Handler::TabLayouts *parent)
0047     : QObject(parent),
0048       m_handler(parent),
0049       m_model(new Model::Layouts(this, m_handler->corona())),
0050       m_proxyModel(new QSortFilterProxyModel(this)),
0051       m_view(m_handler->ui()->layoutsView),
0052       m_headerView(new Settings::Layouts::HeaderView(Qt::Horizontal, m_handler->dialog())),
0053       m_storage(KConfigGroup(KSharedConfig::openConfig(),"LatteSettingsDialog").group("TabLayouts"))
0054 {   
0055     m_templatesKeeper = new Settings::Part::TemplatesKeeper(this, m_handler->corona());
0056 
0057     loadConfig();
0058     m_proxyModel->setSourceModel(m_model);
0059 
0060     connect(m_handler->corona()->layoutsManager()->synchronizer(), &Latte::Layouts::Synchronizer::newLayoutAdded, this, &Layouts::onLayoutAddedExternally);
0061     connect(m_handler->corona()->layoutsManager()->synchronizer(), &Latte::Layouts::Synchronizer::layoutActivitiesChanged, this, &Layouts::onLayoutActivitiesChangedExternally);
0062 
0063     connect(m_model, &QAbstractItemModel::dataChanged, this, &Layouts::dataChanged);
0064     connect(m_model, &Model::Layouts::rowsInserted, this, &Layouts::dataChanged);
0065     connect(m_model, &Model::Layouts::rowsRemoved, this, &Layouts::dataChanged);
0066 
0067     connect(m_model, &Model::Layouts::nameDuplicated, this, &Layouts::onNameDuplicatedFrom);
0068 
0069     connect(m_headerView, &QObject::destroyed, this, [&]() {
0070         m_viewSortColumn = m_headerView->sortIndicatorSection();
0071         m_viewSortOrder = m_headerView->sortIndicatorOrder();
0072     });
0073 
0074     initView();
0075     initLayouts();
0076 
0077     //! connect them after initialization of the view
0078     connect(m_model, &Model::Layouts::inMultipleModeChanged, this, [&]() {
0079         applyColumnWidths(true);
0080     });
0081 
0082     connect(m_handler->corona()->universalSettings(), &UniversalSettings::canDisableBordersChanged, this, [&]() {
0083         applyColumnWidths(false);
0084     });
0085 }
0086 
0087 Layouts::~Layouts()
0088 {
0089     saveConfig();
0090 
0091     for (const auto &tempDir : m_tempDirectories) {
0092         QDir tDir(tempDir);
0093 
0094         if (tDir.exists() && tempDir.startsWith("/tmp/")) {
0095             tDir.removeRecursively();
0096         }
0097     }
0098 }
0099 
0100 QAbstractItemModel *Layouts::proxyModel() const
0101 {
0102     return m_proxyModel;
0103 }
0104 
0105 QAbstractItemModel *Layouts::baseModel() const
0106 {
0107     return m_model;
0108 }
0109 
0110 QTableView *Layouts::view() const
0111 {
0112     return m_view;
0113 }
0114 
0115 Settings::Part::TemplatesKeeper *Layouts::templatesKeeper() const
0116 {
0117     return m_templatesKeeper;
0118 }
0119 
0120 void Layouts::initView()
0121 {
0122     m_view->setModel(m_proxyModel);
0123     m_view->setHorizontalHeader(m_headerView);
0124     m_view->verticalHeader()->setVisible(false);
0125     m_view->setSortingEnabled(true);
0126 
0127     m_proxyModel->setSortRole(Model::Layouts::SORTINGROLE);
0128     m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0129 
0130     m_view->sortByColumn(m_viewSortColumn, m_viewSortOrder);
0131 
0132     //!find the available colors
0133     m_iconsPath = m_handler->corona()->kPackage().path() + "../../shells/org.kde.latte.shell/contents/images/canvas/";
0134 
0135     QDir layoutDir(m_iconsPath);
0136     QStringList filter;
0137     filter.append(QString("*print.jpg"));
0138     QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks);
0139     QStringList colors;
0140 
0141     for (auto &file : files) {
0142         int colorEnd = file.lastIndexOf("print.jpg");
0143         QString color = file.remove(colorEnd, 9);
0144         colors.append(color);
0145     }
0146 
0147     m_view->setItemDelegateForColumn(Model::Layouts::NAMECOLUMN, new Settings::Layout::Delegate::LayoutName(this));
0148     m_view->setItemDelegateForColumn(Model::Layouts::BACKGROUNDCOLUMN, new Settings::Layout::Delegate::BackgroundDelegate(this));
0149     m_view->setItemDelegateForColumn(Model::Layouts::MENUCOLUMN, new Settings::Layout::Delegate::CheckBox(this));
0150     m_view->setItemDelegateForColumn(Model::Layouts::BORDERSCOLUMN, new Settings::Layout::Delegate::CheckBox(this));
0151     m_view->setItemDelegateForColumn(Model::Layouts::ACTIVITYCOLUMN, new Settings::Layout::Delegate::Activities(this));
0152 
0153     connect(m_view->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &Layouts::onCurrentRowChanged);
0154 
0155     connect(m_view, &QObject::destroyed, this, [&]() {
0156         storeColumnWidths(m_model->inMultipleMode());
0157     });
0158 }
0159 
0160 bool Layouts::hasChangedData() const
0161 {
0162     return m_model->hasChangedData();
0163 }
0164 
0165 bool Layouts::layoutsAreChanged() const
0166 {
0167     return m_model->layoutsAreChanged();
0168 }
0169 
0170 bool Layouts::modeIsChanged() const
0171 {
0172     return m_model-modeIsChanged();
0173 }
0174 
0175 void Layouts::setOriginalInMultipleMode(const bool &inmultiple)
0176 {
0177     m_model->setOriginalInMultipleMode(inmultiple);
0178 }
0179 
0180 bool Layouts::hasSelectedLayout() const
0181 {
0182     return m_view->selectionModel()->hasSelection();
0183 }
0184 
0185 bool Layouts::isLayoutOriginal(const QString &currentLayoutId) const
0186 {
0187     return m_model->originalLayoutsData().containsId(currentLayoutId);
0188 }
0189 
0190 bool Layouts::isSelectedLayoutOriginal() const
0191 {
0192     if (!hasSelectedLayout()) {
0193         return false;
0194     }
0195 
0196     Data::Layout currentData = selectedLayoutCurrentData();
0197 
0198     return m_model->originalLayoutsData().containsId(currentData.id);
0199 }
0200 
0201 QString Layouts::colorPath(const QString color) const
0202 {
0203     QString path = m_iconsPath + color + "print.jpg";
0204 
0205     if (!QFileInfo(path).exists()) {
0206         return m_iconsPath + "blueprint.jpg";
0207     }
0208 
0209     return path;
0210 }
0211 
0212 QString Layouts::iconsPath() const
0213 {
0214     return m_iconsPath;
0215 }
0216 
0217 const Latte::Data::ViewsTable Layouts::selectedLayoutViews()
0218 {
0219     Latte::Data::ViewsTable views;
0220     int selectedRow = m_view->currentIndex().row();
0221 
0222     if (selectedRow < 0) {
0223         return views;
0224     }
0225 
0226     QString selectedId = m_proxyModel->data(m_proxyModel->index(selectedRow, Model::Layouts::IDCOLUMN), Qt::UserRole).toString();
0227     Data::Layout selectedCurrentData = m_model->currentData(selectedId);
0228 
0229     Data::Layout originalSelectedData = selectedLayoutOriginalData();
0230     CentralLayout *central = m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(originalSelectedData.name);
0231 
0232     bool islayoutactive{true};
0233 
0234     if (!central) {
0235         islayoutactive = false;
0236         central = new CentralLayout(this, selectedCurrentData.id);
0237     }
0238 
0239     selectedCurrentData.views = central->viewsTable().onlyOriginals();
0240     selectedCurrentData.views.isInitialized = true;
0241 
0242     if (!islayoutactive) {
0243         central->deleteLater();
0244     }
0245 
0246     return selectedCurrentData.views;
0247 }
0248 
0249 const Latte::Data::LayoutIcon Layouts::selectedLayoutIcon() const
0250 {
0251     int selectedRow = m_view->currentIndex().row();
0252 
0253     if (selectedRow >= 0) {
0254         QString selectedId = m_proxyModel->data(m_proxyModel->index(selectedRow, Model::Layouts::IDCOLUMN), Qt::UserRole).toString();
0255         return m_model->currentLayoutIcon(selectedId);
0256     }
0257 
0258     return Latte::Data::LayoutIcon();
0259 
0260 }
0261 
0262 const Latte::Data::Layout Layouts::selectedLayoutCurrentData() const
0263 {
0264     int selectedRow = m_view->currentIndex().row();
0265     if (selectedRow >= 0) {
0266         QString selectedId = m_proxyModel->data(m_proxyModel->index(selectedRow, Model::Layouts::IDCOLUMN), Qt::UserRole).toString();
0267         return m_model->currentData(selectedId);
0268     } else {
0269         return Latte::Data::Layout();
0270     }
0271 }
0272 
0273 const Latte::Data::Layout Layouts::selectedLayoutOriginalData() const
0274 {
0275     int selectedRow = m_view->currentIndex().row();
0276     QString selectedId = m_proxyModel->data(m_proxyModel->index(selectedRow, Model::Layouts::IDCOLUMN), Qt::UserRole).toString();
0277 
0278     return m_model->originalData(selectedId);
0279 }
0280 
0281 const Latte::Data::Layout Layouts::currentData(const QString &currentLayoutId) const
0282 {
0283     return m_model->currentData(currentLayoutId);
0284 }
0285 
0286 const Latte::Data::Layout Layouts::originalData(const QString &currentLayoutId) const
0287 {
0288     return m_model->originalData(currentLayoutId);
0289 }
0290 
0291 const Latte::Data::ScreensTable Layouts::screensData()
0292 {
0293     Latte::Data::ScreensTable scrtable = m_handler->corona()->screenPool()->screensTable();
0294 
0295     QList<int> expscreens;
0296 
0297     //! update activeness
0298     for (int i=1; i<scrtable.rowCount(); ++i) {
0299         scrtable[i].isActive = m_handler->corona()->screenPool()->isScreenActive(scrtable[i].id.toInt());
0300     }
0301 
0302     //! update removability based on activeness
0303     for (int i=0; i<scrtable.rowCount(); ++i) {
0304         scrtable[i].isRemovable = !scrtable[i].isActive;
0305     }
0306 
0307     //! retrieve all layouts data
0308     Latte::Data::LayoutsTable originalLayouts = m_model->originalLayoutsData();
0309     Latte::Data::LayoutsTable currentLayouts = m_model->currentLayoutsData();
0310     Latte::Data::LayoutsTable removedLayouts = originalLayouts.subtracted(currentLayouts);
0311 
0312     //! temp removed layouts should be considered because they may not be deleted in the end
0313     for (int i=0; i<removedLayouts.rowCount(); ++i) {
0314         CentralLayout *central = centralLayout(removedLayouts[i].id);
0315 
0316         if (!central) {
0317             continue;
0318         }
0319 
0320         QList<int> newexps = central->viewsExplicitScreens();
0321 
0322         expscreens = join(expscreens, newexps);
0323     }
0324 
0325     //! current layouts should be considered
0326     for (int i=0; i<currentLayouts.rowCount(); ++i) {
0327         CentralLayout *central = centralLayout(currentLayouts[i].id);
0328 
0329         if (!central) {
0330             continue;
0331         }
0332 
0333         QList<int> newexps = central->viewsExplicitScreens();
0334 
0335         expscreens = join(expscreens, newexps);
0336     }
0337 
0338     //! discovered explicit screens should be flagged as NOREMOVABLE
0339     for (int i=0; i<expscreens.count(); ++i) {
0340         QString expscridstr = QString::number(expscreens[i]);
0341 
0342         if (scrtable.containsId(expscridstr)) {
0343             scrtable[expscridstr].isRemovable = false;
0344         }
0345     }
0346 
0347     //! Print no-removable screens
0348     /*for (int i=0; i<scrtable.rowCount(); ++i) {
0349         if (!scrtable[i].isRemovable) {
0350             qDebug() <<"org.kde.latte NO REMOVABLE EXP SCREEN ::: " << scrtable[i].id;
0351         }
0352     }*/
0353 
0354     return scrtable;
0355 }
0356 
0357 bool Layouts::inMultipleMode() const
0358 {
0359     return m_model->inMultipleMode();
0360 }
0361 
0362 void Layouts::setInMultipleMode(bool inMultiple)
0363 {
0364     m_model->setInMultipleMode(inMultiple);
0365 }
0366 
0367 CentralLayout *Layouts::centralLayout(const QString &currentLayoutId)
0368 {
0369     Data::Layout originlayoutdata = originalData(currentLayoutId);
0370     auto activelayout = isLayoutOriginal(currentLayoutId) ?
0371                 m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(originlayoutdata.name) : nullptr;
0372 
0373     Latte::CentralLayout *centrallayout = activelayout ? activelayout : new Latte::CentralLayout(this, currentLayoutId);
0374 
0375     return centrallayout;
0376 }
0377 
0378 void Layouts::applyColumnWidths(bool storeValues)
0379 {
0380     bool isLastModeMultiple = !m_model->inMultipleMode();
0381 
0382     //! save previous values
0383     if (storeValues) {
0384         storeColumnWidths(isLastModeMultiple);
0385     }
0386 
0387     if (m_model->inMultipleMode()) {
0388         m_view->horizontalHeader()->setSectionResizeMode(Model::Layouts::ACTIVITYCOLUMN, QHeaderView::Stretch);
0389         m_view->horizontalHeader()->setSectionResizeMode(Model::Layouts::NAMECOLUMN, QHeaderView::Interactive);
0390     } else {
0391         m_view->horizontalHeader()->setSectionResizeMode(Model::Layouts::NAMECOLUMN, QHeaderView::Stretch);
0392         m_view->horizontalHeader()->setSectionResizeMode(Model::Layouts::ACTIVITYCOLUMN, QHeaderView::Interactive);
0393     }
0394 
0395     //! this line should be commented for debugging layouts window functionality
0396     m_view->setColumnHidden(Model::Layouts::IDCOLUMN, true);
0397     m_view->setColumnHidden(Model::Layouts::HIDDENTEXTCOLUMN, true);
0398 
0399     int maxColumns = Model::Layouts::ACTIVITYCOLUMN - Model::Layouts::BACKGROUNDCOLUMN; //4 - multiple
0400 
0401     if (m_handler->corona()->universalSettings()->canDisableBorders()) {
0402         m_view->setColumnHidden(Model::Layouts::BORDERSCOLUMN, false);
0403     } else {
0404         m_view->setColumnHidden(Model::Layouts::BORDERSCOLUMN, true);
0405     }
0406 
0407     if (m_model->inMultipleMode()) {
0408         m_view->setColumnHidden(Model::Layouts::MENUCOLUMN, true);
0409         m_view->setColumnHidden(Model::Layouts::ACTIVITYCOLUMN, false);
0410     } else {
0411         m_view->setColumnHidden(Model::Layouts::MENUCOLUMN, false);
0412         m_view->setColumnHidden(Model::Layouts::ACTIVITYCOLUMN, true);
0413     }
0414 
0415     if (!m_viewColumnWidths.isEmpty()) {
0416         for (int i=0; i<qMin(m_viewColumnWidths.count(), maxColumns); ++i) {
0417             int currentColumn = Model::Layouts::BACKGROUNDCOLUMN+i;
0418 
0419             if ((currentColumn == Model::Layouts::BORDERSCOLUMN && !m_handler->corona()->universalSettings()->canDisableBorders())
0420                     || (currentColumn == Model::Layouts::NAMECOLUMN && isLastModeMultiple)
0421                     || (currentColumn == Model::Layouts::MENUCOLUMN && !isLastModeMultiple)
0422                     || (currentColumn == Model::Layouts::ACTIVITYCOLUMN)) {
0423                 continue;
0424             }
0425 
0426             m_view->setColumnWidth(currentColumn, m_viewColumnWidths[i].toInt());
0427         }
0428     } else {
0429         m_view->resizeColumnsToContents();
0430     }
0431 }
0432 
0433 int Layouts::rowForId(QString id) const
0434 {
0435     for (int i = 0; i < m_proxyModel->rowCount(); ++i) {
0436         QString rowId = m_proxyModel->data(m_proxyModel->index(i, Model::Layouts::IDCOLUMN), Qt::UserRole).toString();
0437 
0438         if (rowId == id) {
0439             return i;
0440         }
0441     }
0442 
0443     return -1;
0444 }
0445 
0446 int Layouts::rowForName(QString layoutName) const
0447 {
0448     for (int i = 0; i < m_proxyModel->rowCount(); ++i) {
0449         QString rowName = m_proxyModel->data(m_proxyModel->index(i, Model::Layouts::NAMECOLUMN), Qt::UserRole).toString();
0450 
0451         if (rowName == layoutName) {
0452             return i;
0453         }
0454     }
0455 
0456     return -1;
0457 }
0458 
0459 QString Layouts::uniqueTempDirectory()
0460 {
0461     QTemporaryDir tempDir;
0462     tempDir.setAutoRemove(false);
0463     m_tempDirectories.append(tempDir.path());
0464 
0465     return tempDir.path();
0466 }
0467 
0468 QString Layouts::uniqueLayoutName(QString name)
0469 {
0470     int pos_ = name.lastIndexOf(QRegExp(QString(" - [0-9]+")));
0471 
0472     if (m_model->containsCurrentName(name) && pos_ > 0) {
0473         name = name.left(pos_);
0474     }
0475 
0476     int i = 2;
0477 
0478     QString namePart = name;
0479 
0480     while (m_model->containsCurrentName(name)) {
0481         name = namePart + " - " + QString::number(i);
0482         i++;
0483     }
0484 
0485     return name;
0486 }
0487 
0488 void Layouts::removeSelected()
0489 {
0490     if (m_view->currentIndex().row() < 0) {
0491         return;
0492     }
0493 
0494     Latte::Data::Layout selectedOriginal = selectedLayoutOriginalData();
0495 
0496     if (m_handler->corona()->layoutsManager()->synchronizer()->layout(selectedOriginal.name)) {
0497         return;
0498     }
0499 
0500     int row = m_view->currentIndex().row();
0501     row = qMin(row, m_proxyModel->rowCount() - 1);
0502     m_view->selectRow(row);
0503 
0504     Latte::Data::Layout selected = selectedLayoutCurrentData();
0505     m_model->removeLayout(selected.id);
0506 }
0507 
0508 void Layouts::toggleEnabledForSelected()
0509 {
0510     if (!hasSelectedLayout()) {
0511         return;
0512     }
0513 
0514     Latte::Data::Layout selected = selectedLayoutCurrentData();
0515 
0516     if (!selected.activities.isEmpty()) {
0517         m_proxyModel->setData(m_proxyModel->index(m_view->currentIndex().row(), Model::Layouts::ACTIVITYCOLUMN), QStringList(), Qt::UserRole);
0518     } else {
0519         QStringList activities;
0520 
0521         bool layoutsenabledonlyinspecificactivities = m_model->hasEnabledLayout()
0522                 && !m_model->hasEnabledLayoutInAllActitivities()
0523                 && !m_model->hasEnabledLayoutInFreeActivities();
0524 
0525         if (m_model->hasEnabledLayoutInCurrentActivity() || layoutsenabledonlyinspecificactivities) {
0526             activities << m_model->currentActivityId();
0527         } else if (m_model->hasEnabledLayoutInFreeActivities()) {
0528             activities << Data::Layout::FREEACTIVITIESID;
0529         } else {
0530             activities << Data::Layout::ALLACTIVITIESID;
0531         }
0532 
0533         m_proxyModel->setData(m_proxyModel->index(m_view->currentIndex().row(), Model::Layouts::ACTIVITYCOLUMN), activities, Qt::UserRole);
0534     }
0535 }
0536 
0537 void Layouts::toggleLockedForSelected()
0538 {
0539     if (!hasSelectedLayout()) {
0540         return;
0541     }
0542 
0543     Latte::Data::Layout selected = selectedLayoutCurrentData();
0544 
0545     m_proxyModel->setData(m_proxyModel->index(m_view->currentIndex().row(), Model::Layouts::NAMECOLUMN), !selected.isLocked, Settings::Model::Layouts::ISLOCKEDROLE);
0546 }
0547 
0548 void Layouts::selectRow(const QString &id)
0549 {    
0550     m_view->selectRow(rowForId(id));
0551 }
0552 
0553 void Layouts::setLayoutProperties(const Latte::Data::Layout &layout)
0554 {
0555     m_model->setLayoutProperties(layout);
0556 }
0557 
0558 QString Layouts::layoutNameForFreeActivities() const
0559 {
0560     return m_model->layoutNameForFreeActivities();
0561 }
0562 
0563 void Layouts::setOriginalLayoutForFreeActivities(const QString &id)
0564 {
0565     m_model->setOriginalLayoutForFreeActivities(id);
0566     emit dataChanged();
0567 }
0568 
0569 void Layouts::initLayouts()
0570 {
0571     m_model->clear();
0572     bool inMultiple{m_handler->corona()->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts};
0573     setInMultipleMode(inMultiple);
0574 
0575     m_handler->corona()->layoutsManager()->synchronizer()->updateLayoutsTable();
0576     Latte::Data::LayoutsTable layouts = m_handler->corona()->layoutsManager()->synchronizer()->layoutsTable();
0577 
0578     //! Send original loaded data to model
0579     m_model->setOriginalInMultipleMode(inMultiple);
0580     m_model->setOriginalData(layouts);
0581 
0582     QStringList currentLayoutNames = m_handler->corona()->layoutsManager()->currentLayoutsNames();
0583     if (currentLayoutNames.count() > 0) {
0584         m_view->selectRow(rowForName(currentLayoutNames[0]));
0585     }
0586 
0587     applyColumnWidths();
0588 
0589     showInitialErrorWarningMessages();
0590 }
0591 
0592 void Layouts::initialMessageForErroredLayouts(const int &count)
0593 {
0594     if (count <= 0) {
0595         return;
0596     }
0597 
0598     m_handler->showInlineMessage(i18ncp("settings:counted layout with errors",
0599                                         "<b>Error:</b> There is <b>1 layout</b> that has reported errors.",
0600                                         "<b>Error:</b> There are <b>%1 layouts</b> that have reported errors.",
0601                                         count),
0602                                      KMessageWidget::Error);
0603 }
0604 
0605 void Layouts::initialMessageForWarningLayouts(const int &count)
0606 {
0607     if (count <= 0) {
0608         return;
0609     }
0610 
0611     m_handler->showInlineMessage(i18ncp("settings:counted layout with warnings",
0612                                         "<b>Warning:</b> There is <b>1 layout</b> that has reported warnings.",
0613                                         "<b>Warning:</b> There are <b>%1 layouts</b> that have reported warnings.",
0614                                         count),
0615                                      KMessageWidget::Warning);
0616 }
0617 
0618 
0619 void Layouts::messageForErroredLayout(const Data::Layout &layout)
0620 {
0621     //! add actions
0622     QAction *examineaction = new QAction(i18n("Examine..."), this);
0623     examineaction->setIcon(QIcon::fromTheme("document-preview"));
0624     examineaction->setData(layout.id);
0625     QList<QAction *> actions;
0626     actions << examineaction;
0627 
0628     connect(examineaction, &QAction::triggered, this, [&, examineaction]() {
0629         QString currentid = examineaction->data().toString();
0630 
0631         if (!currentid.isEmpty()) {
0632             selectRow(currentid);
0633             m_handler->showViewsDialog();
0634         }
0635     });
0636 
0637     if (!layout.hasErrors() && layout.hasWarnings()) {
0638         //! add only warnings first
0639         m_handler->showInlineMessage(i18ncp("settings:named layout with warnings",
0640                                             "<b>Warning: %2</b> layout has reported <b>1 warning</b> that need your attention.",
0641                                             "<b>Warning: %2</b> layout has reported <b>%1 warnings</b> that need your attention.",
0642                                             layout.warnings,
0643                                             layout.name),
0644                                      KMessageWidget::Warning,
0645                                      false,
0646                                      actions);
0647     } else if (layout.hasErrors() && !layout.hasWarnings()) {
0648         //! add errors in the end in order to be read by the user
0649         m_handler->showInlineMessage(i18nc("settings:named layout with errors",
0650                                            "<b>Error: %2</b> layout has reported <b>1 error</b> that you need to repair.",
0651                                            "<b>Error: %2</b> layout has reported <b>%1 errors</b> that you need to repair.",
0652                                            layout.errors,
0653                                            layout.name),
0654                                      KMessageWidget::Error,
0655                                      true,
0656                                      actions);
0657     } else if (layout.hasErrors() && layout.hasWarnings()) {
0658         //! add most important errors in the end in order to be read by the user
0659         QString errorstr = i18ncp("errors count",
0660                                  "1 error",
0661                                  "%1 errors",
0662                                  layout.errors);
0663         QString warningstr = i18ncp("warnings count",
0664                                    "1 warning",
0665                                    "%1 warnings",
0666                                    layout.warnings);
0667 
0668         m_handler->showInlineMessage(i18nc("settings: named layout with %2 errors and %3 warnings",
0669                                            "<b>Error: %1</b> layout has reported <b>%2</b> and <b>%3</b> that you need to repair.",
0670                                            layout.name,
0671                                            errorstr,
0672                                            warningstr),
0673                                      KMessageWidget::Error,
0674                                      true,
0675                                      actions);
0676     }
0677 }
0678 
0679 void Layouts::showInitialErrorWarningMessages()
0680 {
0681     if (!m_hasShownInitialErrorWarningMessages) {
0682         m_hasShownInitialErrorWarningMessages = true;
0683 
0684         Latte::Data::LayoutsTable layouts = m_handler->corona()->layoutsManager()->synchronizer()->layoutsTable();
0685 
0686         int erroredlayouts{0};
0687         int warninglayouts{0};
0688 
0689         for (int i=0; i<layouts.rowCount(); ++i) {
0690             if (layouts[i].hasErrors()) {
0691                 erroredlayouts++;
0692             } else if (layouts[i].hasWarnings()) {
0693                 warninglayouts++;
0694             }
0695         }
0696 
0697         onCurrentRowChanged();
0698         initialMessageForWarningLayouts(warninglayouts);
0699         initialMessageForErroredLayouts(erroredlayouts);
0700     }
0701 }
0702 
0703 void Layouts::onCurrentRowChanged()
0704 {
0705     if (!hasSelectedLayout()) {
0706         return;
0707     }
0708 
0709     if (!m_handler->isViewsDialogVisible()) {
0710         Latte::Data::Layout selectedlayout = selectedLayoutCurrentData();
0711 
0712         if (selectedlayout.hasErrors() || selectedlayout.hasWarnings()) {
0713             messageForErroredLayout(selectedlayout);
0714         }
0715     }
0716 }
0717 
0718 void Layouts::onLayoutActivitiesChangedExternally(const Data::Layout &layout)
0719 {
0720     m_model->setOriginalActivitiesForLayout(layout);
0721 }
0722 
0723 void Layouts::onLayoutAddedExternally(const Data::Layout &layout)
0724 {
0725     m_model->appendOriginalLayout(layout);
0726 }
0727 
0728 void Layouts::setLayoutCurrentErrorsWarnings(const QString &layoutCurrentId, const int &errors, const int &warnings)
0729 {
0730     Latte::Data::Layout layout = m_model->currentData(layoutCurrentId);
0731 
0732     if (!layout.isNull()) {
0733         layout.errors = errors;
0734         layout.warnings = warnings;
0735         setLayoutProperties(layout);
0736     }
0737 
0738 }
0739 
0740 void Layouts::sortByColumn(int column, Qt::SortOrder order)
0741 {
0742     m_view->sortByColumn(column, order);
0743 }
0744 
0745 const Latte::Data::Layout Layouts::addLayoutForFile(QString file, QString layoutName, bool newTempDirectory)
0746 {
0747     if (layoutName.isEmpty()) {
0748         layoutName = CentralLayout::layoutName(file);
0749     }
0750 
0751     layoutName = uniqueLayoutName(layoutName);
0752 
0753     Latte::Data::Layout copied;
0754 
0755     if (newTempDirectory) {
0756         copied.id = uniqueTempDirectory() + "/" + layoutName + ".layout.latte";
0757         QFile(file).copy(copied.id);
0758     } else {
0759         copied.id = file;
0760     }
0761 
0762     QFileInfo newFileInfo(copied.id);
0763 
0764     if (newFileInfo.exists() && !newFileInfo.isWritable()) {
0765         QFile(copied.id).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther);
0766     }
0767 
0768     CentralLayout *settings = new CentralLayout(this, copied.id);
0769 
0770     copied = settings->data();
0771     copied.name = uniqueLayoutName(layoutName);
0772 
0773     m_model->appendLayout(copied);
0774     m_view->selectRow(rowForId(copied.id));
0775 
0776     return copied;
0777 }
0778 
0779 const Latte::Data::Layout Layouts::addLayoutByText(QString rawLayoutText)
0780 {
0781     QTemporaryFile tempFile;
0782     tempFile.open();
0783     QTextStream stream(&tempFile);
0784     stream << rawLayoutText;
0785     stream.flush();
0786     tempFile.close();
0787 
0788     Latte::Data::Layout newLayout = addLayoutForFile(tempFile.fileName(),i18n("Dropped Raw Layout"));
0789 
0790     int selectedRow = m_view->currentIndex().row();
0791     QModelIndex tIndex = m_proxyModel->index(selectedRow, Model::Layouts::NAMECOLUMN);
0792     m_view->edit(tIndex);
0793     
0794     /**Window has to be activated explicitely since the window where the drag
0795      * started would otherwise be the active window. By activating the window
0796        the user can immediately change the name by simply typing.*/
0797     m_handler->dialog()->activateWindow();
0798     
0799     return newLayout;
0800 }
0801 
0802 void Layouts::duplicateSelectedLayout()
0803 {
0804     int row = m_view->currentIndex().row();
0805 
0806     if (row < 0) {
0807         return;
0808     }
0809 
0810     Latte::Data::Layout selectedLayoutCurrent = selectedLayoutCurrentData();
0811     Latte::Data::Layout selectedLayoutOriginal = selectedLayoutOriginalData();
0812     selectedLayoutOriginal = selectedLayoutOriginal.isEmpty() ? selectedLayoutCurrent : selectedLayoutOriginal;
0813 
0814 
0815     //! Update original layout before duplicating if this layout is active
0816     if (m_handler->corona()->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts) {
0817         Latte::CentralLayout *central = m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(selectedLayoutOriginal.name);
0818         if (central) {
0819             central->syncToLayoutFile();
0820         }
0821     }
0822 
0823     Latte::Data::Layout copied = selectedLayoutCurrent;
0824 
0825     copied.name = uniqueLayoutName(selectedLayoutCurrent.name);
0826     copied.id = uniqueTempDirectory() + "/" + copied.name + ".layout.latte";;
0827     copied.isActive = false;
0828     copied.isConsideredActive = false;
0829     copied.isLocked = false;
0830     copied.activities = QStringList();
0831 
0832     QFile(selectedLayoutCurrent.id).copy(copied.id);
0833     QFileInfo newFileInfo(copied.id);
0834 
0835     if (newFileInfo.exists() && !newFileInfo.isWritable()) {
0836         QFile(copied.id).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther);
0837     }
0838 
0839     CentralLayout *settings = new CentralLayout(this, copied.id);
0840     settings->clearLastUsedActivity();
0841     settings->setActivities(QStringList());
0842 
0843     m_model->appendLayout(copied);
0844 
0845     m_view->selectRow(rowForId(copied.id));
0846 }
0847 
0848 bool Layouts::importLayoutsFromV1ConfigFile(QString file)
0849 {
0850     KTar archive(file, QStringLiteral("application/x-tar"));
0851     archive.open(QIODevice::ReadOnly);
0852 
0853     //! if the file isnt a tar archive
0854     if (archive.isOpen()) {
0855         QDir tempDir{uniqueTempDirectory()};
0856 
0857         const auto archiveRootDir = archive.directory();
0858 
0859         for (const auto &name : archiveRootDir->entries()) {
0860             auto fileEntry = archiveRootDir->file(name);
0861             fileEntry->copyTo(tempDir.absolutePath());
0862         }
0863 
0864         QString name = Latte::Layouts::Importer::nameOfConfigFile(file);
0865 
0866         QString applets(tempDir.absolutePath() + "/" + "lattedock-appletsrc");
0867 
0868         if (QFile(applets).exists()) {
0869             QStringList importedlayouts;
0870 
0871             if (m_handler->corona()->layoutsManager()->importer()->importOldLayout(applets, name, false, tempDir.absolutePath())) {
0872                 Latte::Data::Layout imported = addLayoutForFile(tempDir.absolutePath() + "/" + name + ".layout.latte", name);
0873                 importedlayouts << imported.name;
0874             }
0875 
0876             QString alternativeName = name + "-" + i18nc("layout", "Alternative");
0877 
0878             if (m_handler->corona()->layoutsManager()->importer()->importOldLayout(applets, alternativeName, false, tempDir.absolutePath())) {
0879                 Latte::Data::Layout imported = addLayoutForFile(tempDir.absolutePath() + "/" + alternativeName + ".layout.latte", alternativeName, false);
0880                 importedlayouts << imported.name;
0881             }
0882 
0883             if (importedlayouts.count() > 0) {
0884                 m_handler->showInlineMessage(i18np("Layout <b>%2</b> imported successfully...",
0885                                                    "Layouts <b>%2</b> imported successfully...",
0886                                                    importedlayouts.count(),
0887                                                    importedlayouts.join(",")),
0888                         KMessageWidget::Positive);
0889 
0890                 return true;
0891             }
0892         }
0893     }
0894 
0895     return false;
0896 }
0897 
0898 void Layouts::reset()
0899 {
0900     m_model->resetData();
0901     QStringList currentLayoutNames = m_handler->corona()->layoutsManager()->currentLayoutsNames();
0902     if (currentLayoutNames.count() > 0) {
0903         m_view->selectRow(rowForName(currentLayoutNames[0]));
0904     }
0905 
0906     //! Clear any templates keeper data
0907     m_templatesKeeper->clear();
0908 }
0909 
0910 void Layouts::save()
0911 {
0912     //! Update Layouts
0913     QStringList knownActivities = m_handler->corona()->layoutsManager()->synchronizer()->activities();
0914 
0915     QTemporaryDir layoutTempDir;
0916 
0917     qDebug() << "Temporary Directory ::: " << layoutTempDir.path();
0918 
0919     QString switchToLayout;
0920 
0921     QHash<QString, Latte::CentralLayout *> activeLayoutsToRename;
0922 
0923     Latte::Data::LayoutsTable originalLayouts = m_model->originalLayoutsData();
0924     Latte::Data::LayoutsTable currentLayouts = m_model->currentLayoutsData();
0925     Latte::Data::LayoutsTable removedLayouts = originalLayouts.subtracted(currentLayouts);
0926 
0927     //! remove layouts that have been removed from the user
0928     for (int i=0; i<removedLayouts.rowCount(); ++i) {
0929         QFile(removedLayouts[i].id).remove();
0930     }
0931 
0932     QList<Data::UniqueIdInfo> alteredIdsInfo;
0933 
0934     QList<Latte::Data::Layout> alteredLayouts = m_model->alteredLayouts();
0935 
0936     for (int i = 0; i < alteredLayouts.count(); ++i) {
0937         Latte::Data::Layout iLayoutCurrentData = alteredLayouts[i];
0938         Latte::Data::Layout iLayoutOriginalData = m_model->originalData(iLayoutCurrentData.id);
0939         iLayoutOriginalData = iLayoutOriginalData.isEmpty() ? iLayoutCurrentData : iLayoutOriginalData;
0940 
0941         //qDebug() << i << ". " << id << " - " << color << " - " << name << " - " << menu << " - " << lActivities;
0942         //! update the generic parts of the layouts
0943         bool isOriginalLayout = m_model->originalLayoutsData().containsId(iLayoutCurrentData.id);
0944         Latte::CentralLayout *centralActive= isOriginalLayout ? m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(iLayoutOriginalData.name) : nullptr;
0945         Latte::CentralLayout *central = centralActive ? centralActive : new Latte::CentralLayout(this, iLayoutCurrentData.id);
0946 
0947         //! unlock read-only layout
0948         if (!central->isWritable()) {
0949             central->unlock();
0950         }
0951 
0952         //! Icon
0953         central->setIcon(iLayoutCurrentData.icon);
0954 
0955         //! Custom Scheme
0956         central->setSchemeFile(iLayoutCurrentData.schemeFile);
0957 
0958         //! Backgrounds
0959         central->setBackgroundStyle(iLayoutCurrentData.backgroundStyle);
0960         central->setColor(iLayoutCurrentData.color);
0961         central->setCustomBackground(iLayoutCurrentData.background);
0962         central->setCustomTextColor(iLayoutCurrentData.textColor);
0963 
0964         //! Extra Properties
0965         central->setShowInMenu(iLayoutCurrentData.isShownInMenu);
0966         central->setDisableBordersForMaximizedWindows(iLayoutCurrentData.hasDisabledBorders);
0967         central->setPopUpMargin(iLayoutCurrentData.popUpMargin);
0968         central->setActivities(iLayoutCurrentData.activities);
0969 
0970         //! If the layout name changed OR the layout path is a temporary one
0971         if ((iLayoutCurrentData.name != iLayoutOriginalData.name) || iLayoutCurrentData.isTemporary()) {
0972             //! If the layout is Active in MultipleLayouts
0973             if (central->isActive()) {
0974                 qDebug() << " Active Layout Should Be Renamed From : " << central->name() << " TO :: " << iLayoutCurrentData.name;
0975                 activeLayoutsToRename[iLayoutCurrentData.name] = central;
0976             }
0977 
0978             QString tempFile = layoutTempDir.path() + "/" + QString(central->name() + ".layout.latte");
0979             qDebug() << "new temp file ::: " << tempFile;
0980 
0981             QFile(iLayoutCurrentData.id).rename(tempFile);
0982 
0983             Data::UniqueIdInfo idInfo;
0984 
0985             idInfo.oldId = iLayoutCurrentData.id;
0986             idInfo.newId = tempFile;
0987             idInfo.newName = iLayoutCurrentData.name;
0988 
0989             alteredIdsInfo << idInfo;
0990         }
0991     }
0992 
0993     //! this is necessary in case two layouts have to swap names
0994     //! so we copy first the layouts in a temp directory and afterwards all
0995     //! together we move them in the official layout directory
0996     for (int i = 0; i < alteredIdsInfo.count(); ++i) {
0997         Data::UniqueIdInfo idInfo = alteredIdsInfo[i];
0998 
0999         QString newFile = Latte::Layouts::Importer::layoutUserFilePath(idInfo.newName);
1000         QFile(idInfo.newId).rename(newFile);
1001 
1002 
1003         //! updating the #SETTINGSID in the model for the layout that was renamed
1004         for (int j = 0; j < m_model->rowCount(); ++j) {
1005             Latte::Data::Layout jLayout = m_model->at(j);
1006 
1007             if (jLayout.id == idInfo.oldId) {
1008                 m_model->setData(m_model->index(j, Model::Layouts::IDCOLUMN), newFile, Qt::UserRole);
1009             }
1010         }
1011     }
1012 
1013     if (m_handler->corona()->layoutsManager()->memoryUsage() == MemoryUsage::MultipleLayouts) {
1014         for (const auto &newLayoutName : activeLayoutsToRename.keys()) {
1015             Latte::CentralLayout *layoutPtr = activeLayoutsToRename[newLayoutName];
1016             qDebug() << " Active Layout of Type: " << layoutPtr->type() << " Is Renamed From : " << activeLayoutsToRename[newLayoutName]->name() << " TO :: " << newLayoutName;
1017             layoutPtr->renameLayout(newLayoutName);
1018         }
1019     }
1020 
1021     //! lock layouts in the end when the user has chosen it
1022     for (int i = 0; i < alteredLayouts.count(); ++i) {
1023         Latte::Data::Layout layoutCurrentData = alteredLayouts[i];
1024         Latte::Data::Layout layoutOriginalData = m_model->originalData(layoutCurrentData.id);
1025         layoutOriginalData = layoutOriginalData.isEmpty() ? layoutCurrentData : layoutOriginalData;
1026 
1027         Latte::CentralLayout *layoutPtr = m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(layoutOriginalData.name);
1028 
1029         if (!layoutPtr) {
1030             layoutPtr = new Latte::CentralLayout(this, layoutCurrentData.id);
1031         }
1032 
1033         if (layoutCurrentData.isLocked && layoutPtr && layoutPtr->isWritable()) {
1034             layoutPtr->lock();
1035         }
1036     }
1037 
1038     //! send new layouts data in layoutsmanager
1039     m_handler->corona()->layoutsManager()->synchronizer()->setLayoutsTable(currentLayouts);
1040 
1041     //! make sure that there is a layout for free activities
1042     //! send to layout manager in which layout to switch
1043     MemoryUsage::LayoutsMemory inMemoryOption = inMultipleMode() ? Latte::MemoryUsage::MultipleLayouts : Latte::MemoryUsage::SingleLayout;
1044 
1045     if (inMemoryOption == MemoryUsage::SingleLayout) {
1046         bool inrenamingsingleactivelayout = (activeLayoutsToRename.count() > 0);
1047         QString currentSingleLayoutName = m_handler->corona()->universalSettings()->singleModeLayoutName();
1048         QString nextSingleLayoutName = inrenamingsingleactivelayout ? activeLayoutsToRename.keys()[0] : currentSingleLayoutName;
1049 
1050         if (inrenamingsingleactivelayout && !currentLayouts.containsName(currentSingleLayoutName)) {
1051             m_handler->corona()->layoutsManager()->synchronizer()->setIsSingleLayoutInDeprecatedRenaming(true);
1052         }
1053 
1054         m_handler->corona()->layoutsManager()->switchToLayout(nextSingleLayoutName, MemoryUsage::SingleLayout);
1055     }  else {
1056         m_handler->corona()->layoutsManager()->switchToLayout("", MemoryUsage::MultipleLayouts);
1057     }
1058 
1059     m_model->applyData();
1060 
1061     //! Clear any templates keeper data
1062     m_templatesKeeper->clear();
1063 
1064     emit dataChanged();
1065 }
1066 
1067 void Layouts::storeColumnWidths(bool inMultipleMode)
1068 {   
1069     if (m_viewColumnWidths.isEmpty()) {
1070         m_viewColumnWidths << "" << "" << "" << "";
1071     }
1072 
1073     m_viewColumnWidths[0] = QString::number(m_view->columnWidth(Model::Layouts::BACKGROUNDCOLUMN));
1074 
1075     if (inMultipleMode) {
1076         m_viewColumnWidths[1] = QString::number(m_view->columnWidth(Model::Layouts::NAMECOLUMN));
1077     }
1078 
1079     if (!inMultipleMode) {
1080         m_viewColumnWidths[2] = QString::number(m_view->columnWidth(Model::Layouts::MENUCOLUMN));
1081     }
1082 
1083     if (m_handler->corona()->universalSettings()->canDisableBorders()) {
1084         m_viewColumnWidths[3] = QString::number(m_view->columnWidth(Model::Layouts::BORDERSCOLUMN));
1085     }
1086 }
1087 
1088 void Layouts::onNameDuplicatedFrom(const QString &provenId, const QString &trialId)
1089 {
1090     //! duplicated layout name
1091     int pRow = rowForId(provenId);
1092     int tRow = rowForId(trialId);
1093 
1094     int originalRow = m_model->rowForId(provenId);
1095     Latte::Data::Layout provenLayout = m_model->at(originalRow);
1096 
1097     m_handler->showInlineMessage(i18nc("settings: layout name used","Layout <b>%1</b> is already used, please provide a different name...", provenLayout.name),
1098                                  KMessageWidget::Error);
1099 
1100     QModelIndex tIndex = m_proxyModel->index(tRow, Model::Layouts::NAMECOLUMN);
1101 
1102     //! avoid losing focuse
1103     QTimer::singleShot(0, [this, tIndex]() {
1104         m_view->edit(tIndex);
1105     });
1106 }
1107 
1108 QList<int> Layouts::join(const QList<int> &currentRecords, const QList<int> &newRecords)
1109 {
1110     QList<int> result = currentRecords;
1111 
1112     for(int i=0; i<newRecords.count(); ++i) {
1113         if (!result.contains(newRecords[i])) {
1114             result << newRecords[i];
1115         }
1116     }
1117 
1118     return result;
1119 }
1120 
1121 void Layouts::loadConfig()
1122 {
1123     QStringList defaultcolumnwidths;
1124     defaultcolumnwidths << QString::number(48) << QString::number(508) << QString::number(129) << QString::number(104);
1125 
1126     //! new storage
1127     m_viewColumnWidths = m_storage.readEntry("columnWidths", defaultcolumnwidths);
1128     m_viewSortColumn = m_storage.readEntry("sortColumn", (int)Model::Layouts::NAMECOLUMN);
1129     m_viewSortOrder = static_cast<Qt::SortOrder>(m_storage.readEntry("sortOrder", (int)Qt::AscendingOrder));
1130 }
1131 
1132 void Layouts::saveConfig()
1133 {
1134     m_storage.writeEntry("columnWidths", m_viewColumnWidths);
1135     m_storage.writeEntry("sortColumn", m_viewSortColumn);
1136     m_storage.writeEntry("sortOrder", (int)m_viewSortOrder);
1137 }
1138 
1139 }
1140 }
1141 }