1 /*
2 SPDX-FileCopyrightText: 2021 Michail Vourlakos <mvourlakos@gmail.com>
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "viewscontroller.h"
7
8 // local
9 #include <config-latte.h>
10 #include "ui_viewsdialog.h"
11 #include "viewsdialog.h"
12 #include "viewshandler.h"
13 #include "viewsmodel.h"
14 #include "viewstableview.h"
15 #include "delegates/namedelegate.h"
16 #include "delegates/singleoptiondelegate.h"
17 #include "delegates/singletextdelegate.h"
18 #include "../generic/generictools.h"
19 #include "../settingsdialog/templateskeeper.h"
20 #include "../../data/errorinformationdata.h"
21 #include "../../layout/genericlayout.h"
22 #include "../../layout/centrallayout.h"
23 #include "../../layouts/manager.h"
24 #include "../../layouts/synchronizer.h"
25 #include "../../view/view.h"
26
27 // Qt
28 #include <QHeaderView>
29 #include <QItemSelection>
30
31 // KDE
32 #include <KMessageWidget>
33 #include <KSharedConfig>
34
35 #if KF5_VERSION_MINOR >= 71
36 #include <KIO/OpenUrlJob>
37 #else
38 #include <KRun>
39 #endif
40
41 namespace Latte {
42 namespace Settings {
43 namespace Controller {
44
45
Views(Settings::Handler::ViewsHandler * parent)46 Views::Views(Settings::Handler::ViewsHandler *parent)
47 : QObject(parent),
48 m_handler(parent),
49 m_model(new Model::Views(this, m_handler->corona())),
50 m_proxyModel(new QSortFilterProxyModel(this)),
51 m_view(m_handler->ui()->viewsTable),
52 m_storage(KConfigGroup(KSharedConfig::openConfig(),"LatteSettingsDialog").group("ViewsDialog"))
53 {
54 loadConfig();
55 m_proxyModel->setSourceModel(m_model);
56
57 connect(m_model, &QAbstractItemModel::dataChanged, this, &Views::dataChanged);
58 connect(m_model, &Model::Views::rowsInserted, this, &Views::dataChanged);
59 connect(m_model, &Model::Views::rowsRemoved, this, &Views::dataChanged);
60
61 connect(m_handler, &Handler::ViewsHandler::currentLayoutChanged, this, &Views::onCurrentLayoutChanged);
62
63 init();
64 }
65
~Views()66 Views::~Views()
67 {
68 saveConfig();
69 }
70
proxyModel() const71 QAbstractItemModel *Views::proxyModel() const
72 {
73 return m_proxyModel;
74 }
75
baseModel() const76 QAbstractItemModel *Views::baseModel() const
77 {
78 return m_model;
79 }
80
view() const81 QTableView *Views::view() const
82 {
83 return m_view;
84 }
85
init()86 void Views::init()
87 {
88 m_view->setModel(m_proxyModel);
89 //m_view->setHorizontalHeader(m_headerView);
90 m_view->verticalHeader()->setVisible(false);
91 m_view->setSortingEnabled(true);
92
93 m_proxyModel->setSortRole(Model::Views::SORTINGROLE);
94 m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
95
96 m_view->sortByColumn(m_viewSortColumn, m_viewSortOrder);
97
98 m_view->setItemDelegateForColumn(Model::Views::IDCOLUMN, new Settings::View::Delegate::SingleText(this));
99 m_view->setItemDelegateForColumn(Model::Views::NAMECOLUMN, new Settings::View::Delegate::NameDelegate(this));
100 m_view->setItemDelegateForColumn(Model::Views::SCREENCOLUMN, new Settings::View::Delegate::SingleOption(this));
101 m_view->setItemDelegateForColumn(Model::Views::EDGECOLUMN, new Settings::View::Delegate::SingleOption(this));
102 m_view->setItemDelegateForColumn(Model::Views::ALIGNMENTCOLUMN, new Settings::View::Delegate::SingleOption(this));
103 m_view->setItemDelegateForColumn(Model::Views::SUBCONTAINMENTSCOLUMN, new Settings::View::Delegate::SingleText(this));
104
105 applyColumnWidths();
106
107 m_cutAction = new QAction(QIcon::fromTheme("edit-cut"), i18n("Cut"), m_view);
108 m_cutAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X));
109 connect(m_cutAction, &QAction::triggered, this, &Views::cutSelectedViews);
110
111 m_copyAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Copy"), m_view);
112 m_copyAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
113 connect(m_copyAction, &QAction::triggered, this, &Views::copySelectedViews);
114
115 m_pasteAction = new QAction(QIcon::fromTheme("edit-paste"), i18n("Paste"), m_view);
116 m_pasteAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V));
117 connect(m_pasteAction, &QAction::triggered, this, &Views::pasteSelectedViews);
118
119 m_duplicateAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Duplicate Here"), m_view);
120 m_duplicateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
121 connect(m_duplicateAction, &QAction::triggered, this, &Views::duplicateSelectedViews);
122
123 m_view->addAction(m_cutAction);
124 m_view->addAction(m_copyAction);
125 m_view->addAction(m_duplicateAction);
126 m_view->addAction(m_pasteAction);
127
128 onSelectionsChanged();
129
130 connect(m_view, &View::ViewsTableView::selectionsChanged, this, &Views::onSelectionsChanged);
131 connect(m_view, &QObject::destroyed, this, &Views::storeColumnWidths);
132
133 connect(m_view->horizontalHeader(), &QObject::destroyed, this, [&]() {
134 m_viewSortColumn = m_view->horizontalHeader()->sortIndicatorSection();
135 m_viewSortOrder = m_view->horizontalHeader()->sortIndicatorOrder();
136 });
137 }
138
reset()139 void Views::reset()
140 {
141 m_model->resetData();
142
143 //! Clear any templates keeper data in order to produce reupdates if needed
144 m_handler->layoutsController()->templatesKeeper()->clear();
145 }
146
hasChangedData() const147 bool Views::hasChangedData() const
148 {
149 return m_model->hasChangedData();
150 }
151
hasSelectedView() const152 bool Views::hasSelectedView() const
153 {
154 return m_view->selectionModel()->hasSelection();
155 }
156
selectedViewsCount() const157 int Views::selectedViewsCount() const
158 {
159 return m_view->selectionModel()->selectedRows(Model::Views::IDCOLUMN).count();
160 }
161
rowForId(QString id) const162 int Views::rowForId(QString id) const
163 {
164 for (int i = 0; i < m_proxyModel->rowCount(); ++i) {
165 QString rowId = m_proxyModel->data(m_proxyModel->index(i, Model::Views::IDCOLUMN), Qt::UserRole).toString();
166
167 if (rowId == id) {
168 return i;
169 }
170 }
171
172 return -1;
173 }
174
selectedViewsCurrentData() const175 const Data::ViewsTable Views::selectedViewsCurrentData() const
176 {
177 Data::ViewsTable selectedviews;
178
179 if (!hasSelectedView()) {
180 return selectedviews;
181 }
182
183 QModelIndexList layoutidindexes = m_view->selectionModel()->selectedRows(Model::Views::IDCOLUMN);
184
185 for(int i=0; i<layoutidindexes.count(); ++i) {
186 QString selectedid = layoutidindexes[i].data(Qt::UserRole).toString();
187 selectedviews << m_model->currentData(selectedid);
188 }
189
190 return selectedviews;
191 }
192
appendViewFromViewTemplate(const Data::View & view)193 const Latte::Data::View Views::appendViewFromViewTemplate(const Data::View &view)
194 {
195 Data::View newview = view;
196 newview.name = uniqueViewName(view.name);
197 m_model->appendTemporaryView(newview);
198 return newview;
199 }
200
currentData(const QString & id)201 const Latte::Data::View Views::currentData(const QString &id)
202 {
203 return m_model->currentData(id);
204 }
205
selectedViewsForClipboard()206 Data::ViewsTable Views::selectedViewsForClipboard()
207 {
208 Data::ViewsTable clipboardviews;
209 if (!hasSelectedView()) {
210 return clipboardviews;
211 }
212
213 Data::ViewsTable selectedviews = selectedViewsCurrentData();
214 Latte::Data::Layout currentlayout = m_handler->currentData();
215
216 for(int i=0; i<selectedviews.rowCount(); ++i) {
217 if (selectedviews[i].state() == Data::View::IsInvalid) {
218 continue;
219 }
220
221 Latte::Data::View copiedview = selectedviews[i];
222
223 if (selectedviews[i].state() == Data::View::IsCreated) {
224 QString storedviewpath = m_handler->layoutsController()->templatesKeeper()->storedView(currentlayout.id, selectedviews[i].id);
225 copiedview.setState(Data::View::OriginFromLayout, storedviewpath, currentlayout.id, selectedviews[i].id);
226 } else if (selectedviews[i].state() == Data::View::OriginFromViewTemplate) {
227 copiedview.setState(Data::View::OriginFromViewTemplate, selectedviews[i].originFile(), currentlayout.id, selectedviews[i].id);
228 } else if (selectedviews[i].state() == Data::View::OriginFromLayout) {
229 //! is already in valid values
230 }
231
232 copiedview.isActive = false;
233 clipboardviews << copiedview;
234
235 }
236
237 return clipboardviews;
238 }
239
copySelectedViews()240 void Views::copySelectedViews()
241 {
242 qDebug() << Q_FUNC_INFO;
243
244 if (!hasSelectedView()) {
245 return;
246 }
247
248 //! reset cut substates for views
249 Data::ViewsTable currentviews = m_model->currentViewsData();
250 for (int i=0; i<currentviews.rowCount(); ++i) {
251 Data::View cview = currentviews[i];
252 cview.isMoveOrigin = false;
253 m_model->updateCurrentView(cview.id, cview);
254 }
255
256 Data::ViewsTable clipboardviews = selectedViewsForClipboard();
257
258 //! reset cut substates for views
259 for (int i=0; i<clipboardviews.rowCount(); ++i) {
260 clipboardviews[i].isMoveOrigin = false;
261
262 /* Data::View tempview = m_model->currentData(clipboardviews[i].id);
263 tempview.isMoveOrigin = false;
264 m_model->updateCurrentView(tempview.id, tempview);*/
265 }
266
267 m_handler->layoutsController()->templatesKeeper()->setClipboardContents(clipboardviews);
268 }
269
cutSelectedViews()270 void Views::cutSelectedViews()
271 {
272 qDebug() << Q_FUNC_INFO;
273
274 if (!hasSelectedView()) {
275 return;
276 }
277
278 //! reset previous move records
279 Data::ViewsTable currentviews = m_model->currentViewsData();
280 for (int i=0; i<currentviews.rowCount(); ++i) {
281 Data::View cview = currentviews[i];
282 cview.isMoveOrigin = false;
283 m_model->updateCurrentView(cview.id, cview);
284 }
285
286 Data::ViewsTable clipboardviews = selectedViewsForClipboard();
287
288 //! activate cut substates for views
289 for (int i=0; i<clipboardviews.rowCount(); ++i) {
290 clipboardviews[i].isMoveOrigin = true;
291
292 Data::View tempview = m_model->currentData(clipboardviews[i].id);
293 tempview.isMoveOrigin = true;
294 m_model->updateCurrentView(tempview.id, tempview);
295 }
296
297 m_handler->layoutsController()->templatesKeeper()->setClipboardContents(clipboardviews);
298 }
299
pasteSelectedViews()300 void Views::pasteSelectedViews()
301 {
302 Data::ViewsTable clipboardviews = m_handler->layoutsController()->templatesKeeper()->clipboardContents();
303 Latte::Data::Layout currentlayout = m_handler->currentData();
304
305 bool hascurrentlayoutcuttedviews{false};
306
307 for(int i=0; i<clipboardviews.rowCount(); ++i) {
308 if (clipboardviews[i].isMoveOrigin && clipboardviews[i].originLayout() == currentlayout.id) {
309 hascurrentlayoutcuttedviews = true;
310 continue;
311 }
312
313 if (clipboardviews[i].isMoveOrigin) {
314 //! update cut flags only for real cutted view and not for copied one
315 clipboardviews[i].isMoveOrigin = false;
316 clipboardviews[i].isMoveDestination = true;
317 }
318
319 appendViewFromViewTemplate(clipboardviews[i]);
320 }
321
322 if (hascurrentlayoutcuttedviews) {
323 m_handler->showInlineMessage(i18n("Docks and panels from <b>Paste</b> action are already present in current layout"),
324 KMessageWidget::Warning);
325 }
326 }
327
duplicateSelectedViews()328 void Views::duplicateSelectedViews()
329 {
330 qDebug() << Q_FUNC_INFO;
331
332 if (!hasSelectedView()) {
333 return;
334 }
335
336 Data::ViewsTable selectedviews = selectedViewsCurrentData();
337 Latte::Data::Layout currentlayout = m_handler->currentData();
338
339 for(int i=0; i<selectedviews.rowCount(); ++i) {
340 if (selectedviews[i].state() == Data::View::IsCreated) {
341 QString storedviewpath = m_handler->layoutsController()->templatesKeeper()->storedView(currentlayout.id, selectedviews[i].id);
342 Latte::Data::View duplicatedview = selectedviews[i];
343 duplicatedview.setState(Data::View::OriginFromLayout, storedviewpath, currentlayout.id, selectedviews[i].id);
344 duplicatedview.isActive = false;
345 appendViewFromViewTemplate(duplicatedview);
346 } else if (selectedviews[i].state() == Data::View::OriginFromViewTemplate
347 || selectedviews[i].state() == Data::View::OriginFromLayout) {
348 Latte::Data::View duplicatedview = selectedviews[i];
349 duplicatedview.isActive = false;
350 appendViewFromViewTemplate(duplicatedview);
351 }
352 }
353 }
354
removeSelectedViews()355 void Views::removeSelectedViews()
356 {
357 if (!hasSelectedView()) {
358 return;
359 }
360
361 Data::ViewsTable selectedviews = selectedViewsCurrentData();;
362
363 int selectionheadrow = m_model->rowForId(selectedviews[0].id);
364
365 for (int i=0; i<selectedviews.rowCount(); ++i) {
366 m_model->removeView(selectedviews[i].id);
367 }
368
369 m_view->selectRow(qBound(0, selectionheadrow, m_model->rowCount()-1));
370 }
371
selectRow(const QString & id)372 void Views::selectRow(const QString &id)
373 {
374 m_view->selectRow(rowForId(id));
375 }
376
onCurrentLayoutChanged()377 void Views::onCurrentLayoutChanged()
378 {
379 Data::Layout currentlayoutdata = m_handler->currentData();
380
381 Data::ViewsTable clipboardviews = m_handler->layoutsController()->templatesKeeper()->clipboardContents();
382
383 if (!clipboardviews.isEmpty()) {
384 //! clipboarded views needs to update the relevant flags to loaded views
385 for (int i=0; i<currentlayoutdata.views.rowCount(); ++i) {
386 QString vid = currentlayoutdata.views[i].id;
387
388 if (!clipboardviews.containsId(vid)) {
389 continue;
390 }
391
392 if (clipboardviews[vid].isMoveOrigin && (clipboardviews[vid].originLayout() == currentlayoutdata.id)) {
393 currentlayoutdata.views[vid].isMoveOrigin = true;
394 }
395 }
396 }
397
398 m_model->setOriginalData(currentlayoutdata.views);
399
400 //! track viewscountchanged signal for current active layout scenario
401 for (const auto &var : m_currentLayoutConnections) {
402 QObject::disconnect(var);
403 }
404
405 Latte::CentralLayout *currentlayout = m_handler->layoutsController()->centralLayout(currentlayoutdata.id);
406
407 if (currentlayout && currentlayout->isActive()) {
408 m_currentLayoutConnections << connect(currentlayout, &Layout::GenericLayout::viewsCountChanged, this, [&, currentlayout](){
409 m_model->updateActiveStatesBasedOn(currentlayout);
410 });
411 }
412
413 messagesForErrorsWarnings(currentlayout);
414 }
415
onSelectionsChanged()416 void Views::onSelectionsChanged()
417 {
418 bool hasselectedview = hasSelectedView();
419
420 m_cutAction->setVisible(hasselectedview);
421 m_copyAction->setVisible(hasselectedview);
422 m_duplicateAction->setVisible(hasselectedview);
423 m_pasteAction->setEnabled(m_handler->layoutsController()->templatesKeeper()->hasClipboardContents());
424 }
425
viewsForRemovalCount() const426 int Views::viewsForRemovalCount() const
427 {
428 if (!hasChangedData()) {
429 return 0;
430 }
431
432 Latte::Data::ViewsTable originalViews = m_model->originalViewsData();
433 Latte::Data::ViewsTable currentViews = m_model->currentViewsData();
434 Latte::Data::ViewsTable removedViews = originalViews.subtracted(currentViews);
435
436 return removedViews.rowCount();
437 }
438
hasValidOriginView(const Data::View & view)439 bool Views::hasValidOriginView(const Data::View &view)
440 {
441 bool viewidisinteger{true};
442 int vid_int = view.originView().toInt(&viewidisinteger);
443 QString vid_str = view.originView();
444
445 if (vid_str.isEmpty() || !viewidisinteger || vid_int<=0) {
446 return false;
447 }
448
449 return true;
450 }
451
originLayout(const Data::View & view)452 CentralLayout *Views::originLayout(const Data::View &view)
453 {
454 QString origincurrentid = view.originLayout();
455 Data::Layout originlayoutdata = m_handler->layoutsController()->originalData(origincurrentid);
456
457 Latte::CentralLayout *originactive = m_handler->layoutsController()->isLayoutOriginal(origincurrentid) ?
458 m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(originlayoutdata.name) : nullptr;
459
460 return originactive;
461 }
462
updateDoubledMoveDestinationRows()463 void Views::updateDoubledMoveDestinationRows() {
464 //! only one isMoveDestination should exist for each unique move isMoveOrigin case
465 //! all the rest that have been created through Cut/Paste or Duplicate options should become
466 //! simple OriginFromViewTemplate cases
467
468 for (int i=0; i<m_model->rowCount(); ++i) {
469 Data::View baseview = m_model->at(i);
470
471 if (!baseview.isMoveDestination || baseview.state()!=Data::View::OriginFromLayout) {
472 continue;
473 }
474
475 for (int j=i+1; j<m_model->rowCount(); ++j) {
476 Data::View subsequentview = m_model->at(j);
477
478 if (subsequentview.isMoveDestination
479 && subsequentview.state() == Data::View::OriginFromLayout
480 && subsequentview.originFile() == baseview.originFile()
481 && subsequentview.originLayout() == baseview.originLayout()
482 && subsequentview.originView() == baseview.originView()) {
483 //! this is a subsequent view that needs to be updated properly
484 subsequentview.isMoveDestination = false;
485 subsequentview.isMoveOrigin = false;
486 subsequentview.setState(Data::View::OriginFromViewTemplate, subsequentview.originFile(), QString(), QString());
487 m_model->updateCurrentView(subsequentview.id, subsequentview);
488 }
489 }
490 }
491 }
492
messagesForErrorsWarnings(const Latte::CentralLayout * centralLayout,const bool & showNoErrorsMessage)493 void Views::messagesForErrorsWarnings(const Latte::CentralLayout *centralLayout, const bool &showNoErrorsMessage)
494 {
495 if (!centralLayout) {
496 return;
497 }
498
499 Data::Layout currentdata = centralLayout->data();
500
501 m_model->clearErrorsAndWarnings();
502
503 //! warnings
504 if (currentdata.warnings > 0) {
505 Data::WarningsList warnings = centralLayout->warnings();
506
507 // show warnings
508 for (int i=0; i< warnings.count(); ++i) {
509 if (warnings[i].id == Data::Warning::ORPHANEDSUBCONTAINMENT) {
510 messageForWarningOrphanedSubContainments(warnings[i]);
511 } else if (warnings[i].id == Data::Warning::APPLETANDCONTAINMENTWITHSAMEID) {
512 messageForWarningAppletAndContainmentWithSameId(warnings[i]);
513 }
514 }
515
516 // count warnings per view
517 for (int i=0; i<warnings.count(); ++i) {
518 for (int j=0; j<warnings[i].information.rowCount(); ++j) {
519 if (!warnings[i].information[j].containment.isValid()) {
520 continue;
521 }
522
523 QString cid = warnings[i].information[j].containment.storageId;
524 Data::View view = m_model->currentData(cid);
525 if (!view.isValid()) {
526 //! one step back from subcontainment to view in order to find the influenced view id
527 cid = m_model->viewForSubContainment(cid);
528 view = m_model->currentData(cid);
529 }
530
531 if (view.isValid()) {
532 view.warnings++;
533 m_model->updateCurrentView(cid, view);
534 }
535 }
536 }
537 }
538
539 //! errors
540 if (currentdata.errors > 0) {
541 Data::ErrorsList errors = centralLayout->errors();
542
543 // show errors
544 for (int i=0; i< errors.count(); ++i) {
545 if (errors[i].id == Data::Error::APPLETSWITHSAMEID) {
546 messageForErrorAppletsWithSameId(errors[i]);
547 } else if (errors[i].id == Data::Error::ORPHANEDPARENTAPPLETOFSUBCONTAINMENT) {
548 messageForErrorOrphanedParentAppletOfSubContainment(errors[i]);
549 }
550 }
551
552 // count errors per view
553 for (int i=0; i<errors.count(); ++i) {
554 for (int j=0; j<errors[i].information.rowCount(); ++j) {
555 if (!errors[i].information[j].containment.isValid()) {
556 continue;
557 }
558
559 QString cid = errors[i].information[j].containment.storageId;
560 Data::View view = m_model->currentData(cid);
561 if (!view.isValid()) {
562 //! one step back from subcontainment to view in order to find the influenced view id
563 cid = m_model->viewForSubContainment(cid);
564 view = m_model->currentData(cid);
565 }
566
567 if (view.isValid()) {
568 view.errors++;
569 m_model->updateCurrentView(cid, view);
570 }
571 }
572 }
573 }
574
575 m_handler->layoutsController()->setLayoutCurrentErrorsWarnings(currentdata.id, currentdata.errors, currentdata.warnings);
576
577 if (showNoErrorsMessage && currentdata.errors == 0 && currentdata.warnings == 0) {
578 m_handler->showInlineMessage(i18n("Really nice! You are good to go, your layout does not report any errors or warnings."),
579 KMessageWidget::Positive,
580 false);
581 }
582
583 }
584
showDefaultPersistentErrorWarningInlineMessage(const QString & messageText,const KMessageWidget::MessageType & messageType,QList<QAction * > extraActions,const bool & showOpenLayoutAction)585 void Views::showDefaultPersistentErrorWarningInlineMessage(const QString &messageText,
586 const KMessageWidget::MessageType &messageType,
587 QList<QAction *> extraActions,
588 const bool &showOpenLayoutAction)
589 {
590 QList<QAction *> actions;
591 actions << extraActions;
592
593 if (showOpenLayoutAction) {
594 Data::Layout currentlayout = m_handler->currentData();
595
596 //! add default action to open layout
597 QAction *openlayoutaction = new QAction(i18n("Edit Layout"), this);
598 openlayoutaction->setEnabled(!currentlayout.isActive);
599 openlayoutaction->setIcon(QIcon::fromTheme("document-edit"));
600 openlayoutaction->setData(currentlayout.id);
601 actions << openlayoutaction;
602
603 connect(openlayoutaction, &QAction::triggered, this, [&, openlayoutaction]() {
604 QString file = openlayoutaction->data().toString();
605
606 if (!file.isEmpty()) {
607 #if KF5_VERSION_MINOR >= 71
608 auto job = new KIO::OpenUrlJob(QUrl::fromLocalFile(file), QStringLiteral("text/plain"), this);
609 job->start();
610 #else
611 KRun::runUrl(QUrl::fromLocalFile(file), QStringLiteral("text/plain"), m_view);
612 #endif
613 showDefaultInlineMessageValidator();
614 }
615 });
616 }
617
618 //! show message
619 m_handler->showInlineMessage(messageText,
620 messageType,
621 true,
622 actions);
623 }
624
showDefaultInlineMessageValidator()625 void Views::showDefaultInlineMessageValidator()
626 {
627 Data::Layout currentlayout = m_handler->currentData();
628
629 //! add default action to open layout
630 QAction *validateaction = new QAction(i18n("Validate"), this);
631 validateaction->setIcon(QIcon::fromTheme("view-refresh"));
632 validateaction->setData(currentlayout.id);
633
634 QList<QAction *> actions;
635 actions << validateaction;
636
637 connect(validateaction, &QAction::triggered, this, [&, currentlayout]() {
638
639 auto centrallayout = m_handler->layoutsController()->centralLayout(currentlayout.id);
640 if (centrallayout && !centrallayout->isActive()) {
641 KSharedConfigPtr lFile = KSharedConfig::openConfig(centrallayout->file());
642 //! update configuration with latest changes
643 lFile->reparseConfiguration();
644 }
645
646 messagesForErrorsWarnings(centrallayout, true);
647 });
648
649 QString messagetext = i18n("After you have made your layout file changes, please click <b>Validate</b> to confirm them.");
650
651 //! show message
652 m_handler->showInlineMessage(messagetext,
653 KMessageWidget::Warning,
654 true,
655 actions);
656 }
657
messageForErrorAppletsWithSameId(const Data::Error & error)658 void Views::messageForErrorAppletsWithSameId(const Data::Error &error)
659 {
660 if (error.id != Data::Error::APPLETSWITHSAMEID) {
661 return;
662 }
663
664 //! construct message
665 QString message = i18nc("error id and title", "<b>Error #%1: %2</b> <br/>",error.id, error.name);
666 message += "<br/>";
667 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/>");
668
669 message += "<br/>";
670 message += i18n("<b>Applets:</b><br/>");
671 for (int i=0; i<error.information.rowCount(); ++i) {
672 QString appletname = error.information[i].applet.visibleName();
673 QString appletstorageid = error.information[i].applet.storageId;
674 QString viewname = visibleViewName(error.information[i].containment.storageId);
675 QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
676 QString containmentstorageid = error.information[i].containment.storageId;
677 message += i18nc("applets with same id error, applet name, applet id, containment name, containment id",
678 " • <b>%1</b> [#%2] inside <b>%3</b> [#%4]<br/>",
679 appletname,
680 appletstorageid,
681 containmentname,
682 containmentstorageid);
683 }
684
685 message += "<br/>";
686 message += i18n("<b>Possible Solutions:</b><br/>");
687 message += i18n(" 1. Activate this layout and restart Latte<br/>");
688 message += i18n(" 2. Remove the mentioned applets from your layout<br/>");
689 message += i18n(" 3. Update manually the applets id when the layout is <b>not active</b><br/>");
690 message += i18n(" 4. Remove this layout totally<br/>");
691
692 showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Error);
693 }
694
messageForErrorOrphanedParentAppletOfSubContainment(const Data::Error & error)695 void Views::messageForErrorOrphanedParentAppletOfSubContainment(const Data::Error &error)
696 {
697 if (error.id != Data::Error::ORPHANEDPARENTAPPLETOFSUBCONTAINMENT) {
698 return;
699 }
700
701 //! construct message
702 QString message = i18nc("error id and title", "<b>Error #%1: %2</b> <br/><br/>", error.id, error.name);
703 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/>");
704
705 message += "<br/>";
706 message += i18n("<b>Pseudo Applets:</b><br/>");
707 for (int i=0; i<error.information.rowCount(); ++i) {
708 if (!error.information[i].applet.isValid()) {
709 continue;
710 }
711
712 QString appletname = error.information[i].applet.visibleName();
713 QString appletstorageid = error.information[i].applet.storageId;
714 QString viewname = visibleViewName(error.information[i].containment.storageId);
715 QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
716 QString containmentstorageid = error.information[i].containment.storageId;
717 message += i18nc("orphaned pseudo applets, applet name, applet id, containment name, containment id",
718 " • <b>%1</b> [#%2] inside <b>%3</b> [#%4]<br/>",
719 appletname,
720 appletstorageid,
721 containmentname,
722 containmentstorageid);
723 }
724
725 message += "<br/>";
726 message += i18n("<b>Orphaned Subcontainments:</b><br/>");
727 for (int i=0; i<error.information.rowCount(); ++i) {
728 if (error.information[i].applet.isValid()) {
729 continue;
730 }
731
732 QString viewname = visibleViewName(error.information[i].containment.storageId);
733 QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
734 QString containmentstorageid = error.information[i].containment.storageId;
735 message += i18nc("orphaned subcontainments, containment name, containment id",
736 " • <b>%1</b> [#%2] <br/>",
737 containmentname,
738 containmentstorageid);
739 }
740
741 message += "<br/>";
742 message += i18n("<b>Possible Solutions:</b><br/>");
743 message += i18n(" 1. Update manually the subcontainment id inside pseudo applet settings when the layout is <b>not active</b><br/>");
744 message += i18n(" 2. Remove this layout totally<br/>");
745
746 //! show message
747 showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Error);
748 }
749
messageForWarningAppletAndContainmentWithSameId(const Data::Warning & warning)750 void Views::messageForWarningAppletAndContainmentWithSameId(const Data::Warning &warning)
751 {
752 if (warning.id != Data::Warning::APPLETANDCONTAINMENTWITHSAMEID) {
753 return;
754 }
755
756 //! construct message
757 QString message = i18nc("warning id and title", "<b>Warning #%1: %2</b> <br/><br/>", warning.id, warning.name);
758 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/>");
759
760 message += "<br/>";
761 message += i18n("<b>Applets:</b><br/>");
762 for (int i=0; i<warning.information.rowCount(); ++i) {
763 if (!warning.information[i].applet.isValid()) {
764 continue;
765 }
766
767 QString appletname = warning.information[i].applet.visibleName();
768 QString appletstorageid = warning.information[i].applet.storageId;
769 QString viewname = visibleViewName(warning.information[i].containment.storageId);
770 QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
771 QString containmentstorageid = warning.information[i].containment.storageId;
772 message += i18nc("applets, applet name, applet id, containment name, containment id",
773 " • <b>%1</b> [#%2] inside <b>%3</b> [#%4]<br/>",
774 appletname,
775 appletstorageid,
776 containmentname,
777 containmentstorageid);
778 }
779
780 message += "<br/>";
781 message += i18n("<b>Containments:</b><br/>");
782 for (int i=0; i<warning.information.rowCount(); ++i) {
783 if (warning.information[i].applet.isValid()) {
784 continue;
785 }
786
787 QString viewname = visibleViewName(warning.information[i].containment.storageId);
788 QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
789 QString containmentstorageid = warning.information[i].containment.storageId;
790 message += i18nc("containments, containment name, containment id",
791 " • <b>%1</b> [#%2] <br/>",
792 containmentname,
793 containmentstorageid);
794 }
795
796 message += "<br/>";
797 message += i18n("<b>Possible Solutions:</b><br/>");
798 message += i18n(" 1. Update manually the containments or applets id when the layout is <b>not active</b><br/>");
799 message += i18n(" 2. Remove any of the containments or applets that conflict with each other<br/>");
800
801 //! show message
802 showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Warning);
803 }
804
messageForWarningOrphanedSubContainments(const Data::Warning & warning)805 void Views::messageForWarningOrphanedSubContainments(const Data::Warning &warning)
806 {
807 if (warning.id != Data::Warning::ORPHANEDSUBCONTAINMENT) {
808 return;
809 }
810
811 QList<int> orphaned;
812
813 //! construct message
814 QString message = i18nc("warning id and title", "<b>Warning #%1: %2</b> <br/><br/>", warning.id, warning.name);
815 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/>");
816
817 message += "<br/>";
818 message += i18n("<b>Orphaned Subcontainments:</b><br/>");
819 for (int i=0; i<warning.information.rowCount(); ++i) {
820 if (warning.information[i].applet.isValid()) {
821 continue;
822 }
823
824 QString viewname = visibleViewName(warning.information[i].containment.storageId);
825 QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
826 QString containmentstorageid = warning.information[i].containment.storageId;
827 message += i18nc("orphaned subcontainments, containment name, containment id",
828 " • <b>%1</b> [#%2] <br/>",
829 containmentname,
830 containmentstorageid);
831
832 orphaned << warning.information[i].containment.storageId.toInt();
833 }
834
835 message += "<br/>";
836 message += i18n("<b>Possible Solutions:</b><br/>");
837 message += i18n(" 1. Click <b>Repair</b> button in order to remove orphaned subcontainments<br/>");
838 message += i18n(" 2. Remove manually orphaned subcontainments when the layout is <b>not active</b><br/>");
839
840 //! add extra repair action
841 QAction *repairlayoutaction = new QAction(i18n("Repair"), this);
842 repairlayoutaction->setIcon(QIcon::fromTheme("dialog-yes"));
843 QList<QAction *> extraactions;
844 extraactions << repairlayoutaction;
845
846 Latte::Data::Layout currentlayout = m_handler->currentData();
847
848 connect(repairlayoutaction, &QAction::triggered, this, [&, currentlayout, orphaned]() {
849 auto centrallayout = m_handler->layoutsController()->centralLayout(currentlayout.id);
850
851 for (int i=0; i<orphaned.count(); ++i) {
852 centrallayout->removeOrphanedSubContainment(orphaned[i]);
853 }
854
855 messagesForErrorsWarnings(centrallayout, true);
856 });
857
858 //! show message
859 showDefaultPersistentErrorWarningInlineMessage(message,
860 KMessageWidget::Warning,
861 extraactions);
862 }
863
save()864 void Views::save()
865 {
866 //! when this function is called we consider that removal has already been approved
867 updateDoubledMoveDestinationRows();
868
869 Latte::Data::Layout originallayout = m_handler->originalData();
870 Latte::Data::Layout currentlayout = m_handler->currentData();
871 Latte::CentralLayout *central = m_handler->layoutsController()->centralLayout(currentlayout.id);
872
873 //! views in model
874 Latte::Data::ViewsTable originalViews = m_model->originalViewsData();
875 Latte::Data::ViewsTable currentViews = m_model->currentViewsData();
876 Latte::Data::ViewsTable alteredViews = m_model->alteredViews();
877 Latte::Data::ViewsTable newViews = m_model->newViews();
878
879 QHash<QString, Data::View> newviewsresponses;
880 QHash<QString, Data::View> cuttedpastedviews;
881 QHash<QString, Data::View> cuttedpastedactiveviews;
882
883 m_debugSaveCall++;
884 qDebug() << "org.kde.latte ViewsDialog::save() call: " << m_debugSaveCall << "-------- ";
885
886 //! add new views that are accepted
887 for(int i=0; i<newViews.rowCount(); ++i){
888 if (newViews[i].isMoveDestination) {
889 CentralLayout *originActive = originLayout(newViews[i]);
890 bool inmovebetweenactivelayouts = central->isActive() && originActive && central != originActive && hasValidOriginView(newViews[i]);
891
892 if (inmovebetweenactivelayouts) {
893 cuttedpastedactiveviews[newViews[i].id] = newViews[i];
894 continue;
895 }
896
897 cuttedpastedviews[newViews[i].id] = newViews[i];
898 }
899
900 if (newViews[i].state() == Data::View::OriginFromViewTemplate) {
901 Data::View addedview = central->newView(newViews[i]);
902
903 newviewsresponses[newViews[i].id] = addedview;
904 } else if (newViews[i].state() == Data::View::OriginFromLayout) {
905 Data::View adjustedview = newViews[i];
906 adjustedview.setState(Data::View::OriginFromViewTemplate, newViews[i].originFile(), QString(), QString());
907 Data::View addedview = central->newView(adjustedview);
908
909 newviewsresponses[newViews[i].id] = addedview;
910 }
911 }
912
913 //! update altered views
914 for (int i=0; i<alteredViews.rowCount(); ++i) {
915 if (alteredViews[i].state() == Data::View::IsCreated && !alteredViews[i].isMoveOrigin) {
916 qDebug() << "org.kde.latte ViewsDialog::save() updating altered view :: " << alteredViews[i];
917 central->updateView(alteredViews[i]);
918 }
919 }
920
921 //! remove deprecated views that have been removed from user
922 Latte::Data::ViewsTable removedViews = originalViews.subtracted(currentViews);
923
924 for (int i=0; i<removedViews.rowCount(); ++i) {
925 qDebug() << "org.kde.latte ViewsDialog::save() real removing view :: " << removedViews[i];
926 central->removeView(removedViews[i]);
927 }
928
929 //! remove deprecated views from external layouts that must be removed because of Cut->Paste Action
930 for(const auto vid: cuttedpastedviews.keys()){
931 bool viewidisinteger{true};
932 int vid_int = cuttedpastedviews[vid].originView().toInt(&viewidisinteger);
933 QString vid_str = cuttedpastedviews[vid].originView();
934
935 if (vid_str.isEmpty() || !viewidisinteger || vid_int<=0) {
936 //! ignore origin views that have not been created already
937 continue;
938 }
939
940 qDebug() << "org.kde.latte ViewsDialog::save() removing cut-pasted view :: " << cuttedpastedviews[vid];
941
942 //! Be Careful: Remove deprecated views from Cut->Paste Action
943 QString origincurrentid = cuttedpastedviews[vid].originLayout();
944 Data::Layout originlayout = m_handler->layoutsController()->originalData(origincurrentid);
945 Latte::CentralLayout *origin = m_handler->layoutsController()->centralLayout(originlayout.id);
946
947 Data::ViewsTable originviews = Latte::Layouts::Storage::self()->views(origin);
948
949 if (originviews.containsId(vid_str)) {
950 origin->removeView(originviews[vid_str]);
951 }
952 }
953
954 //! move active views between different active layouts
955 for (const auto vid: cuttedpastedactiveviews.keys()) {
956 Data::View pastedactiveview = cuttedpastedactiveviews[vid];
957 uint originviewid = pastedactiveview.originView().toUInt();
958 CentralLayout *origin = originLayout(pastedactiveview);
959 QString originlayoutname = origin->name();
960 QString destinationlayoutname = originallayout.name;
961
962 auto view = origin->viewForContainment(originviewid);
963
964 QString tempviewid = pastedactiveview.id;
965 pastedactiveview.id = QString::number(originviewid);
966
967 qDebug() << "org.kde.latte ViewsDialog::save() move to another layout cutted-pasted active view :: " << pastedactiveview;
968
969 if (view) {
970 //! onscreen_view->onscreen_view
971 //! onscreen_view->offscreen_view
972 pastedactiveview.setState(pastedactiveview.state(), pastedactiveview.originFile(), destinationlayoutname, pastedactiveview.originView());
973 origin->updateView(pastedactiveview);
974 } else {
975 //! offscreen_view->onscreen_view
976 m_handler->corona()->layoutsManager()->moveView(originlayoutname, originviewid, destinationlayoutname);
977 //!is needed in order for layout to not trigger another move
978 pastedactiveview.setState(Data::View::IsCreated, QString(), QString(), QString());
979 central->updateView(pastedactiveview);
980 }
981
982 pastedactiveview.setState(Data::View::IsCreated, QString(), QString(), QString());
983 newviewsresponses[tempviewid] = pastedactiveview;
984 }
985
986 //! update
987 if ((removedViews.rowCount() > 0) || (newViews.rowCount() > 0)) {
988 m_handler->corona()->layoutsManager()->synchronizer()->syncActiveLayoutsToOriginalFiles();
989 }
990
991 //! update model for newly added views
992 for (const auto vid: newviewsresponses.keys()) {
993 m_model->setOriginalView(vid, newviewsresponses[vid]);
994 }
995
996 //! update all table with latest data and make the original one
997 currentViews = m_model->currentViewsData();
998 m_model->setOriginalData(currentViews);
999
1000 //! update model activeness
1001 if (central->isActive()) {
1002 m_model->updateActiveStatesBasedOn(central);
1003 }
1004
1005 //! Clear any templates keeper data in order to produce reupdates if needed
1006 m_handler->layoutsController()->templatesKeeper()->clear();
1007 }
1008
uniqueViewName(QString name)1009 QString Views::uniqueViewName(QString name)
1010 {
1011 if (name.isEmpty()) {
1012 return name;
1013 }
1014
1015 int pos_ = name.lastIndexOf(QRegExp(QString(" - [0-9]+")));
1016
1017 if (m_model->containsCurrentName(name) && pos_ > 0) {
1018 name = name.left(pos_);
1019 }
1020
1021 int i = 2;
1022
1023 QString namePart = name;
1024
1025 while (m_model->containsCurrentName(name)) {
1026 name = namePart + " - " + QString::number(i);
1027 i++;
1028 }
1029
1030 return name;
1031 }
1032
visibleViewName(const QString & id) const1033 QString Views::visibleViewName(const QString &id) const
1034 {
1035 if (id.isEmpty()) {
1036 return QString();
1037 }
1038
1039 Data::View view = m_model->currentData(id);
1040
1041 if (view.isValid()) {
1042 return view.name;
1043 }
1044
1045 return QString();
1046
1047 }
1048
applyColumnWidths()1049 void Views::applyColumnWidths()
1050 {
1051 m_view->horizontalHeader()->setSectionResizeMode(Model::Views::SUBCONTAINMENTSCOLUMN, QHeaderView::Stretch);
1052
1053 if (m_viewColumnWidths.count()<(Model::Views::columnCount()-1)) {
1054 return;
1055 }
1056
1057 m_view->setColumnWidth(Model::Views::IDCOLUMN, m_viewColumnWidths[0].toInt());
1058 m_view->setColumnWidth(Model::Views::NAMECOLUMN, m_viewColumnWidths[1].toInt());
1059 m_view->setColumnWidth(Model::Views::SCREENCOLUMN, m_viewColumnWidths[2].toInt());
1060 m_view->setColumnWidth(Model::Views::EDGECOLUMN, m_viewColumnWidths[3].toInt());
1061 m_view->setColumnWidth(Model::Views::ALIGNMENTCOLUMN, m_viewColumnWidths[4].toInt());
1062 }
1063
storeColumnWidths()1064 void Views::storeColumnWidths()
1065 {
1066 if (m_viewColumnWidths.isEmpty() || (m_viewColumnWidths.count()<Model::Views::columnCount()-1)) {
1067 m_viewColumnWidths.clear();
1068 for (int i=0; i<Model::Views::columnCount(); ++i) {
1069 m_viewColumnWidths << "";
1070 }
1071 }
1072
1073 m_viewColumnWidths[0] = QString::number(m_view->columnWidth(Model::Views::IDCOLUMN));
1074 m_viewColumnWidths[1] = QString::number(m_view->columnWidth(Model::Views::NAMECOLUMN));
1075 m_viewColumnWidths[2] = QString::number(m_view->columnWidth(Model::Views::SCREENCOLUMN));
1076 m_viewColumnWidths[3] = QString::number(m_view->columnWidth(Model::Views::EDGECOLUMN));
1077 m_viewColumnWidths[4] = QString::number(m_view->columnWidth(Model::Views::ALIGNMENTCOLUMN));
1078 }
1079
loadConfig()1080 void Views::loadConfig()
1081 {
1082 QStringList defaultcolumnwidths;
1083 defaultcolumnwidths << QString::number(59) << QString::number(256) << QString::number(142) << QString::number(135) << QString::number(131);
1084
1085 m_viewColumnWidths = m_storage.readEntry("columnWidths", defaultcolumnwidths);
1086 m_viewSortColumn = m_storage.readEntry("sortColumn", (int)Model::Views::SCREENCOLUMN);
1087 m_viewSortOrder = static_cast<Qt::SortOrder>(m_storage.readEntry("sortOrder", (int)Qt::AscendingOrder));
1088 }
1089
saveConfig()1090 void Views::saveConfig()
1091 {
1092 m_storage.writeEntry("columnWidths", m_viewColumnWidths);
1093 m_storage.writeEntry("sortColumn", m_viewSortColumn);
1094 m_storage.writeEntry("sortOrder", (int)m_viewSortOrder);
1095 }
1096
1097 }
1098 }
1099 }
1100
1101