File indexing completed on 2025-01-12 08:02:37
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 " • <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(" 1. Activate this layout and restart Latte<br/>"); 0680 message += i18n(" 2. Remove the mentioned applets from your layout<br/>"); 0681 message += i18n(" 3. Update manually the applets id when the layout is <b>not active</b><br/>"); 0682 message += i18n(" 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 " • <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 " • <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(" 1. Update manually the subcontainment id inside pseudo applet settings when the layout is <b>not active</b><br/>"); 0736 message += i18n(" 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 " • <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 " • <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(" 1. Update manually the containments or applets id when the layout is <b>not active</b><br/>"); 0791 message += i18n(" 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 " • <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(" 1. Click <b>Repair</b> button in order to remove orphaned subcontainments<br/>"); 0830 message += i18n(" 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