You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
latte-dock/app/settings/viewsdialog/viewscontroller.cpp

1116 lines
42 KiB
C++

/*
* Copyright 2021 Michail Vourlakos <mvourlakos@gmail.com>
*
* This file is part of Latte-Dock
*
* Latte-Dock is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* Latte-Dock is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "viewscontroller.h"
// local
#include <config-latte.h>
#include "ui_viewsdialog.h"
#include "viewsdialog.h"
#include "viewshandler.h"
#include "viewsmodel.h"
#include "viewstableview.h"
#include "delegates/namedelegate.h"
#include "delegates/singleoptiondelegate.h"
#include "delegates/singletextdelegate.h"
#include "../generic/generictools.h"
#include "../settingsdialog/templateskeeper.h"
#include "../../data/errorinformationdata.h"
#include "../../layout/genericlayout.h"
#include "../../layout/centrallayout.h"
#include "../../layouts/manager.h"
#include "../../layouts/synchronizer.h"
#include "../../view/view.h"
// Qt
#include <QHeaderView>
#include <QItemSelection>
// KDE
#include <KMessageWidget>
#include <KSharedConfig>
#if KF5_VERSION_MINOR >= 71
#include <KIO/OpenUrlJob>
#else
#include <KRun>
#endif
namespace Latte {
namespace Settings {
namespace Controller {
Views::Views(Settings::Handler::ViewsHandler *parent)
: QObject(parent),
m_handler(parent),
m_model(new Model::Views(this, m_handler->corona())),
m_proxyModel(new QSortFilterProxyModel(this)),
m_view(m_handler->ui()->viewsTable),
m_storage(KConfigGroup(KSharedConfig::openConfig(),"LatteSettingsDialog").group("ViewsDialog"))
{
loadConfig();
m_proxyModel->setSourceModel(m_model);
connect(m_model, &QAbstractItemModel::dataChanged, this, &Views::dataChanged);
connect(m_model, &Model::Views::rowsInserted, this, &Views::dataChanged);
connect(m_model, &Model::Views::rowsRemoved, this, &Views::dataChanged);
connect(m_handler, &Handler::ViewsHandler::currentLayoutChanged, this, &Views::onCurrentLayoutChanged);
init();
}
Views::~Views()
{
saveConfig();
}
QAbstractItemModel *Views::proxyModel() const
{
return m_proxyModel;
}
QAbstractItemModel *Views::baseModel() const
{
return m_model;
}
QTableView *Views::view() const
{
return m_view;
}
void Views::init()
{
m_view->setModel(m_proxyModel);
//m_view->setHorizontalHeader(m_headerView);
m_view->verticalHeader()->setVisible(false);
m_view->setSortingEnabled(true);
m_proxyModel->setSortRole(Model::Views::SORTINGROLE);
m_proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
m_view->sortByColumn(m_viewSortColumn, m_viewSortOrder);
m_view->setItemDelegateForColumn(Model::Views::IDCOLUMN, new Settings::View::Delegate::SingleText(this));
m_view->setItemDelegateForColumn(Model::Views::NAMECOLUMN, new Settings::View::Delegate::NameDelegate(this));
m_view->setItemDelegateForColumn(Model::Views::SCREENCOLUMN, new Settings::View::Delegate::SingleOption(this));
m_view->setItemDelegateForColumn(Model::Views::EDGECOLUMN, new Settings::View::Delegate::SingleOption(this));
m_view->setItemDelegateForColumn(Model::Views::ALIGNMENTCOLUMN, new Settings::View::Delegate::SingleOption(this));
m_view->setItemDelegateForColumn(Model::Views::SUBCONTAINMENTSCOLUMN, new Settings::View::Delegate::SingleText(this));
applyColumnWidths();
m_cutAction = new QAction(QIcon::fromTheme("edit-cut"), i18n("Cut"), m_view);
m_cutAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_X));
connect(m_cutAction, &QAction::triggered, this, &Views::cutSelectedViews);
m_copyAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Copy"), m_view);
m_copyAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_C));
connect(m_copyAction, &QAction::triggered, this, &Views::copySelectedViews);
m_pasteAction = new QAction(QIcon::fromTheme("edit-paste"), i18n("Paste"), m_view);
m_pasteAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_V));
connect(m_pasteAction, &QAction::triggered, this, &Views::pasteSelectedViews);
m_duplicateAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Duplicate Here"), m_view);
m_duplicateAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_D));
connect(m_duplicateAction, &QAction::triggered, this, &Views::duplicateSelectedViews);
m_view->addAction(m_cutAction);
m_view->addAction(m_copyAction);
m_view->addAction(m_duplicateAction);
m_view->addAction(m_pasteAction);
onSelectionsChanged();
connect(m_view, &View::ViewsTableView::selectionsChanged, this, &Views::onSelectionsChanged);
connect(m_view, &QObject::destroyed, this, &Views::storeColumnWidths);
connect(m_view->horizontalHeader(), &QObject::destroyed, this, [&]() {
m_viewSortColumn = m_view->horizontalHeader()->sortIndicatorSection();
m_viewSortOrder = m_view->horizontalHeader()->sortIndicatorOrder();
});
}
void Views::reset()
{
m_model->resetData();
//! Clear any templates keeper data in order to produce reupdates if needed
m_handler->layoutsController()->templatesKeeper()->clear();
}
bool Views::hasChangedData() const
{
return m_model->hasChangedData();
}
bool Views::hasSelectedView() const
{
return m_view->selectionModel()->hasSelection();
}
int Views::selectedViewsCount() const
{
return m_view->selectionModel()->selectedRows(Model::Views::IDCOLUMN).count();
}
int Views::rowForId(QString id) const
{
for (int i = 0; i < m_proxyModel->rowCount(); ++i) {
QString rowId = m_proxyModel->data(m_proxyModel->index(i, Model::Views::IDCOLUMN), Qt::UserRole).toString();
if (rowId == id) {
return i;
}
}
return -1;
}
const Data::ViewsTable Views::selectedViewsCurrentData() const
{
Data::ViewsTable selectedviews;
if (!hasSelectedView()) {
return selectedviews;
}
QModelIndexList layoutidindexes = m_view->selectionModel()->selectedRows(Model::Views::IDCOLUMN);
for(int i=0; i<layoutidindexes.count(); ++i) {
QString selectedid = layoutidindexes[i].data(Qt::UserRole).toString();
selectedviews << m_model->currentData(selectedid);
}
return selectedviews;
}
const Latte::Data::View Views::appendViewFromViewTemplate(const Data::View &view)
{
Data::View newview = view;
newview.name = uniqueViewName(view.name);
m_model->appendTemporaryView(newview);
return newview;
}
const Latte::Data::View Views::currentData(const QString &id)
{
return m_model->currentData(id);
}
Data::ViewsTable Views::selectedViewsForClipboard()
{
Data::ViewsTable clipboardviews;
if (!hasSelectedView()) {
return clipboardviews;
}
Data::ViewsTable selectedviews = selectedViewsCurrentData();
Latte::Data::Layout currentlayout = m_handler->currentData();
for(int i=0; i<selectedviews.rowCount(); ++i) {
if (selectedviews[i].state() == Data::View::IsInvalid) {
continue;
}
Latte::Data::View copiedview = selectedviews[i];
if (selectedviews[i].state() == Data::View::IsCreated) {
QString storedviewpath = m_handler->layoutsController()->templatesKeeper()->storedView(currentlayout.id, selectedviews[i].id);
copiedview.setState(Data::View::OriginFromLayout, storedviewpath, currentlayout.id, selectedviews[i].id);
} else if (selectedviews[i].state() == Data::View::OriginFromViewTemplate) {
copiedview.setState(Data::View::OriginFromViewTemplate, selectedviews[i].originFile(), currentlayout.id, selectedviews[i].id);
} else if (selectedviews[i].state() == Data::View::OriginFromLayout) {
//! is already in valid values
}
copiedview.isActive = false;
clipboardviews << copiedview;
}
return clipboardviews;
}
void Views::copySelectedViews()
{
qDebug() << Q_FUNC_INFO;
if (!hasSelectedView()) {
return;
}
//! reset cut substates for views
Data::ViewsTable currentviews = m_model->currentViewsData();
for (int i=0; i<currentviews.rowCount(); ++i) {
Data::View cview = currentviews[i];
cview.isMoveOrigin = false;
m_model->updateCurrentView(cview.id, cview);
}
Data::ViewsTable clipboardviews = selectedViewsForClipboard();
//! reset cut substates for views
for (int i=0; i<clipboardviews.rowCount(); ++i) {
clipboardviews[i].isMoveOrigin = false;
/* Data::View tempview = m_model->currentData(clipboardviews[i].id);
tempview.isMoveOrigin = false;
m_model->updateCurrentView(tempview.id, tempview);*/
}
m_handler->layoutsController()->templatesKeeper()->setClipboardContents(clipboardviews);
}
void Views::cutSelectedViews()
{
qDebug() << Q_FUNC_INFO;
if (!hasSelectedView()) {
return;
}
//! reset previous move records
Data::ViewsTable currentviews = m_model->currentViewsData();
for (int i=0; i<currentviews.rowCount(); ++i) {
Data::View cview = currentviews[i];
cview.isMoveOrigin = false;
m_model->updateCurrentView(cview.id, cview);
}
Data::ViewsTable clipboardviews = selectedViewsForClipboard();
//! activate cut substates for views
for (int i=0; i<clipboardviews.rowCount(); ++i) {
clipboardviews[i].isMoveOrigin = true;
Data::View tempview = m_model->currentData(clipboardviews[i].id);
tempview.isMoveOrigin = true;
m_model->updateCurrentView(tempview.id, tempview);
}
m_handler->layoutsController()->templatesKeeper()->setClipboardContents(clipboardviews);
}
void Views::pasteSelectedViews()
{
Data::ViewsTable clipboardviews = m_handler->layoutsController()->templatesKeeper()->clipboardContents();
Latte::Data::Layout currentlayout = m_handler->currentData();
bool hascurrentlayoutcuttedviews{false};
for(int i=0; i<clipboardviews.rowCount(); ++i) {
if (clipboardviews[i].isMoveOrigin && clipboardviews[i].originLayout() == currentlayout.id) {
hascurrentlayoutcuttedviews = true;
continue;
}
if (clipboardviews[i].isMoveOrigin) {
//! update cut flags only for real cutted view and not for copied one
clipboardviews[i].isMoveOrigin = false;
clipboardviews[i].isMoveDestination = true;
}
appendViewFromViewTemplate(clipboardviews[i]);
}
if (hascurrentlayoutcuttedviews) {
m_handler->showInlineMessage(i18n("Docks and panels from <b>Paste</b> action are already present in current layout"),
KMessageWidget::Warning);
}
}
void Views::duplicateSelectedViews()
{
qDebug() << Q_FUNC_INFO;
if (!hasSelectedView()) {
return;
}
Data::ViewsTable selectedviews = selectedViewsCurrentData();
Latte::Data::Layout currentlayout = m_handler->currentData();
for(int i=0; i<selectedviews.rowCount(); ++i) {
if (selectedviews[i].state() == Data::View::IsCreated) {
QString storedviewpath = m_handler->layoutsController()->templatesKeeper()->storedView(currentlayout.id, selectedviews[i].id);
Latte::Data::View duplicatedview = selectedviews[i];
duplicatedview.setState(Data::View::OriginFromLayout, storedviewpath, currentlayout.id, selectedviews[i].id);
duplicatedview.isActive = false;
appendViewFromViewTemplate(duplicatedview);
} else if (selectedviews[i].state() == Data::View::OriginFromViewTemplate
|| selectedviews[i].state() == Data::View::OriginFromLayout) {
Latte::Data::View duplicatedview = selectedviews[i];
duplicatedview.isActive = false;
appendViewFromViewTemplate(duplicatedview);
}
}
}
void Views::removeSelectedViews()
{
if (!hasSelectedView()) {
return;
}
Data::ViewsTable selectedviews = selectedViewsCurrentData();;
int selectionheadrow = m_model->rowForId(selectedviews[0].id);
for (int i=0; i<selectedviews.rowCount(); ++i) {
m_model->removeView(selectedviews[i].id);
}
m_view->selectRow(qBound(0, selectionheadrow, m_model->rowCount()-1));
}
void Views::selectRow(const QString &id)
{
m_view->selectRow(rowForId(id));
}
void Views::onCurrentLayoutChanged()
{
Data::Layout currentlayoutdata = m_handler->currentData();
Data::ViewsTable clipboardviews = m_handler->layoutsController()->templatesKeeper()->clipboardContents();
if (!clipboardviews.isEmpty()) {
//! clipboarded views needs to update the relevant flags to loaded views
for (int i=0; i<currentlayoutdata.views.rowCount(); ++i) {
QString vid = currentlayoutdata.views[i].id;
if (!clipboardviews.containsId(vid)) {
continue;
}
if (clipboardviews[vid].isMoveOrigin && (clipboardviews[vid].originLayout() == currentlayoutdata.id)) {
currentlayoutdata.views[vid].isMoveOrigin = true;
}
}
}
m_model->setOriginalData(currentlayoutdata.views);
//! track viewscountchanged signal for current active layout scenario
for (const auto &var : m_currentLayoutConnections) {
QObject::disconnect(var);
}
Latte::CentralLayout *currentlayout = m_handler->centralLayout(currentlayoutdata.id);
if (currentlayout && currentlayout->isActive()) {
m_currentLayoutConnections << connect(currentlayout, &Layout::GenericLayout::viewsCountChanged, this, [&, currentlayout](){
m_model->updateActiveStatesBasedOn(currentlayout);
});
}
messagesForErrorsWarnings(currentlayout);
}
void Views::onSelectionsChanged()
{
bool hasselectedview = hasSelectedView();
m_cutAction->setVisible(hasselectedview);
m_copyAction->setVisible(hasselectedview);
m_duplicateAction->setVisible(hasselectedview);
m_pasteAction->setEnabled(m_handler->layoutsController()->templatesKeeper()->hasClipboardContents());
}
int Views::viewsForRemovalCount() const
{
if (!hasChangedData()) {
return 0;
}
Latte::Data::ViewsTable originalViews = m_model->originalViewsData();
Latte::Data::ViewsTable currentViews = m_model->currentViewsData();
Latte::Data::ViewsTable removedViews = originalViews.subtracted(currentViews);
return removedViews.rowCount();
}
bool Views::hasValidOriginView(const Data::View &view)
{
bool viewidisinteger{true};
int vid_int = view.originView().toInt(&viewidisinteger);
QString vid_str = view.originView();
if (vid_str.isEmpty() || !viewidisinteger || vid_int<=0) {
return false;
}
return true;
}
CentralLayout *Views::originLayout(const Data::View &view)
{
QString origincurrentid = view.originLayout();
Data::Layout originlayoutdata = m_handler->layoutsController()->originalData(origincurrentid);
Latte::CentralLayout *originactive = m_handler->layoutsController()->isLayoutOriginal(origincurrentid) ?
m_handler->corona()->layoutsManager()->synchronizer()->centralLayout(originlayoutdata.name) : nullptr;
return originactive;
}
void Views::updateDoubledMoveDestinationRows() {
//! only one isMoveDestination should exist for each unique move isMoveOrigin case
//! all the rest that have been created through Cut/Paste or Duplicate options should become
//! simple OriginFromViewTemplate cases
for (int i=0; i<m_model->rowCount(); ++i) {
Data::View baseview = m_model->at(i);
if (!baseview.isMoveDestination || baseview.state()!=Data::View::OriginFromLayout) {
continue;
}
for (int j=i+1; j<m_model->rowCount(); ++j) {
Data::View subsequentview = m_model->at(j);
if (subsequentview.isMoveDestination
&& subsequentview.state() == Data::View::OriginFromLayout
&& subsequentview.originFile() == baseview.originFile()
&& subsequentview.originLayout() == baseview.originLayout()
&& subsequentview.originView() == baseview.originView()) {
//! this is a subsequent view that needs to be updated properly
subsequentview.isMoveDestination = false;
subsequentview.isMoveOrigin = false;
subsequentview.setState(Data::View::OriginFromViewTemplate, subsequentview.originFile(), QString(), QString());
m_model->updateCurrentView(subsequentview.id, subsequentview);
}
}
}
}
void Views::messagesForErrorsWarnings(const Latte::CentralLayout *centralLayout, const bool &showNoErrorsMessage)
{
if (!centralLayout) {
return;
}
Data::Layout currentdata = centralLayout->data();
m_model->clearErrorsAndWarnings();
//! warnings
if (currentdata.warnings > 0) {
Data::WarningsList warnings = centralLayout->warnings();
// show warnings
for (int i=0; i< warnings.count(); ++i) {
if (warnings[i].id == Data::Warning::ORPHANEDSUBCONTAINMENT) {
messageForWarningOrphanedSubContainments(warnings[i]);
} else if (warnings[i].id == Data::Warning::APPLETANDCONTAINMENTWITHSAMEID) {
messageForWarningAppletAndContainmentWithSameId(warnings[i]);
}
}
// count warnings per view
for (int i=0; i<warnings.count(); ++i) {
for (int j=0; j<warnings[i].information.rowCount(); ++j) {
if (!warnings[i].information[j].containment.isValid()) {
continue;
}
QString cid = warnings[i].information[j].containment.storageId;
Data::View view = m_model->currentData(cid);
if (!view.isValid()) {
//! one step back from subcontainment to view in order to find the influenced view id
cid = m_model->viewForSubContainment(cid);
view = m_model->currentData(cid);
}
if (view.isValid()) {
view.warnings++;
m_model->updateCurrentView(cid, view);
}
}
}
}
//! errors
if (currentdata.errors > 0) {
Data::ErrorsList errors = centralLayout->errors();
// show errors
for (int i=0; i< errors.count(); ++i) {
if (errors[i].id == Data::Error::APPLETSWITHSAMEID) {
messageForErrorAppletsWithSameId(errors[i]);
} else if (errors[i].id == Data::Error::ORPHANEDPARENTAPPLETOFSUBCONTAINMENT) {
messageForErrorOrphanedParentAppletOfSubContainment(errors[i]);
}
}
// count errors per view
for (int i=0; i<errors.count(); ++i) {
for (int j=0; j<errors[i].information.rowCount(); ++j) {
if (!errors[i].information[j].containment.isValid()) {
continue;
}
QString cid = errors[i].information[j].containment.storageId;
Data::View view = m_model->currentData(cid);
if (!view.isValid()) {
//! one step back from subcontainment to view in order to find the influenced view id
cid = m_model->viewForSubContainment(cid);
view = m_model->currentData(cid);
}
if (view.isValid()) {
view.errors++;
m_model->updateCurrentView(cid, view);
}
}
}
}
m_handler->layoutsController()->setLayoutCurrentErrorsWarnings(currentdata.id, currentdata.errors, currentdata.warnings);
if (showNoErrorsMessage && currentdata.errors == 0 && currentdata.warnings == 0) {
m_handler->showInlineMessage(i18n("Really nice! You are good to go, your layout does not report any errors or warnings."),
KMessageWidget::Positive,
false);
}
}
void Views::showDefaultPersistentErrorWarningInlineMessage(const QString &messageText,
const KMessageWidget::MessageType &messageType,
QList<QAction *> extraActions,
const bool &showOpenLayoutAction)
{
QList<QAction *> actions;
actions << extraActions;
if (showOpenLayoutAction) {
Data::Layout currentlayout = m_handler->currentData();
//! add default action to open layout
QAction *openlayoutaction = new QAction(i18n("Edit Layout"), this);
openlayoutaction->setEnabled(!currentlayout.isActive);
openlayoutaction->setIcon(QIcon::fromTheme("document-edit"));
openlayoutaction->setData(currentlayout.id);
actions << openlayoutaction;
connect(openlayoutaction, &QAction::triggered, this, [&, openlayoutaction]() {
QString file = openlayoutaction->data().toString();
if (!file.isEmpty()) {
#if KF5_VERSION_MINOR >= 71
auto job = new KIO::OpenUrlJob(QUrl::fromLocalFile(file), QStringLiteral("text/plain"), this);
job->start();
#else
KRun::runUrl(QUrl::fromLocalFile(file), QStringLiteral("text/plain"), m_view);
#endif
showDefaultInlineMessageValidator();
}
});
}
//! show message
m_handler->showInlineMessage(messageText,
messageType,
true,
actions);
}
void Views::showDefaultInlineMessageValidator()
{
Data::Layout currentlayout = m_handler->currentData();
//! add default action to open layout
QAction *validateaction = new QAction(i18n("Validate"), this);
validateaction->setIcon(QIcon::fromTheme("view-refresh"));
validateaction->setData(currentlayout.id);
QList<QAction *> actions;
actions << validateaction;
connect(validateaction, &QAction::triggered, this, [&, currentlayout]() {
auto centrallayout = m_handler->centralLayout(currentlayout.id);
if (centrallayout && !centrallayout->isActive()) {
KSharedConfigPtr lFile = KSharedConfig::openConfig(centrallayout->file());
//! update configuration with latest changes
lFile->reparseConfiguration();
}
messagesForErrorsWarnings(centrallayout, true);
});
QString messagetext = i18n("After you have made your layout file changes, please click <b>Validate</b> to confirm them.");
//! show message
m_handler->showInlineMessage(messagetext,
KMessageWidget::Warning,
true,
actions);
}
void Views::messageForErrorAppletsWithSameId(const Data::Error &error)
{
if (error.id != Data::Error::APPLETSWITHSAMEID) {
return;
}
//! construct message
QString message = i18nc("error id and title", "<b>Error #%1: %2</b> <br/>",error.id, error.name);
message += "<br/>";
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/>");
message += "<br/>";
message += i18n("<b>Applets:</b><br/>");
for (int i=0; i<error.information.rowCount(); ++i) {
QString appletname = error.information[i].applet.visibleName();
QString appletstorageid = error.information[i].applet.storageId;
QString viewname = visibleViewName(error.information[i].containment.storageId);
QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
QString containmentstorageid = error.information[i].containment.storageId;
message += i18nc("applets with same id error, applet name, applet id, containment name, containment id",
"&nbsp;&nbsp;• <b>%1</b> [#%2] inside <b>%3</b> [#%4]<br/>",
appletname,
appletstorageid,
containmentname,
containmentstorageid);
}
message += "<br/>";
message += i18n("<b>Possible Solutions:</b><br/>");
message += i18n("&nbsp;&nbsp;1. Activate this layout and restart Latte<br/>");
message += i18n("&nbsp;&nbsp;2. Remove the mentioned applets from your layout<br/>");
message += i18n("&nbsp;&nbsp;3. Update manually the applets id when the layout is <b>not active</b><br/>");
message += i18n("&nbsp;&nbsp;4. Remove this layout totally<br/>");
showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Error);
}
void Views::messageForErrorOrphanedParentAppletOfSubContainment(const Data::Error &error)
{
if (error.id != Data::Error::ORPHANEDPARENTAPPLETOFSUBCONTAINMENT) {
return;
}
//! construct message
QString message = i18nc("error id and title", "<b>Error #%1: %2</b> <br/><br/>", error.id, error.name);
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/>");
message += "<br/>";
message += i18n("<b>Pseudo Applets:</b><br/>");
for (int i=0; i<error.information.rowCount(); ++i) {
if (!error.information[i].applet.isValid()) {
continue;
}
QString appletname = error.information[i].applet.visibleName();
QString appletstorageid = error.information[i].applet.storageId;
QString viewname = visibleViewName(error.information[i].containment.storageId);
QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
QString containmentstorageid = error.information[i].containment.storageId;
message += i18nc("orphaned pseudo applets, applet name, applet id, containment name, containment id",
"&nbsp;&nbsp;• <b>%1</b> [#%2] inside <b>%3</b> [#%4]<br/>",
appletname,
appletstorageid,
containmentname,
containmentstorageid);
}
message += "<br/>";
message += i18n("<b>Orphaned Subcontainments:</b><br/>");
for (int i=0; i<error.information.rowCount(); ++i) {
if (error.information[i].applet.isValid()) {
continue;
}
QString viewname = visibleViewName(error.information[i].containment.storageId);
QString containmentname = viewname.isEmpty() ? error.information[i].containment.visibleName() : viewname;
QString containmentstorageid = error.information[i].containment.storageId;
message += i18nc("orphaned subcontainments, containment name, containment id",
"&nbsp;&nbsp;• <b>%1</b> [#%2] <br/>",
containmentname,
containmentstorageid);
}
message += "<br/>";
message += i18n("<b>Possible Solutions:</b><br/>");
message += i18n("&nbsp;&nbsp;1. Update manually the subcontainment id inside pseudo applet settings when the layout is <b>not active</b><br/>");
message += i18n("&nbsp;&nbsp;2. Remove this layout totally<br/>");
//! show message
showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Error);
}
void Views::messageForWarningAppletAndContainmentWithSameId(const Data::Warning &warning)
{
if (warning.id != Data::Warning::APPLETANDCONTAINMENTWITHSAMEID) {
return;
}
//! construct message
QString message = i18nc("warning id and title", "<b>Warning #%1: %2</b> <br/><br/>", warning.id, warning.name);
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/>");
message += "<br/>";
message += i18n("<b>Applets:</b><br/>");
for (int i=0; i<warning.information.rowCount(); ++i) {
if (!warning.information[i].applet.isValid()) {
continue;
}
QString appletname = warning.information[i].applet.visibleName();
QString appletstorageid = warning.information[i].applet.storageId;
QString viewname = visibleViewName(warning.information[i].containment.storageId);
QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
QString containmentstorageid = warning.information[i].containment.storageId;
message += i18nc("applets, applet name, applet id, containment name, containment id",
"&nbsp;&nbsp;• <b>%1</b> [#%2] inside <b>%3</b> [#%4]<br/>",
appletname,
appletstorageid,
containmentname,
containmentstorageid);
}
message += "<br/>";
message += i18n("<b>Containments:</b><br/>");
for (int i=0; i<warning.information.rowCount(); ++i) {
if (warning.information[i].applet.isValid()) {
continue;
}
QString viewname = visibleViewName(warning.information[i].containment.storageId);
QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
QString containmentstorageid = warning.information[i].containment.storageId;
message += i18nc("containments, containment name, containment id",
"&nbsp;&nbsp;• <b>%1</b> [#%2] <br/>",
containmentname,
containmentstorageid);
}
message += "<br/>";
message += i18n("<b>Possible Solutions:</b><br/>");
message += i18n("&nbsp;&nbsp;1. Update manually the containments or applets id when the layout is <b>not active</b><br/>");
message += i18n("&nbsp;&nbsp;2. Remove any of the containments or applets that conflict with each other<br/>");
//! show message
showDefaultPersistentErrorWarningInlineMessage(message, KMessageWidget::Warning);
}
void Views::messageForWarningOrphanedSubContainments(const Data::Warning &warning)
{
if (warning.id != Data::Warning::ORPHANEDSUBCONTAINMENT) {
return;
}
QList<int> orphaned;
//! construct message
QString message = i18nc("warning id and title", "<b>Warning #%1: %2</b> <br/><br/>", warning.id, warning.name);
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/>");
message += "<br/>";
message += i18n("<b>Orphaned Subcontainments:</b><br/>");
for (int i=0; i<warning.information.rowCount(); ++i) {
if (warning.information[i].applet.isValid()) {
continue;
}
QString viewname = visibleViewName(warning.information[i].containment.storageId);
QString containmentname = viewname.isEmpty() ? warning.information[i].containment.visibleName() : viewname;
QString containmentstorageid = warning.information[i].containment.storageId;
message += i18nc("orphaned subcontainments, containment name, containment id",
"&nbsp;&nbsp;• <b>%1</b> [#%2] <br/>",
containmentname,
containmentstorageid);
orphaned << warning.information[i].containment.storageId.toInt();
}
message += "<br/>";
message += i18n("<b>Possible Solutions:</b><br/>");
message += i18n("&nbsp;&nbsp;1. Click <b>Repair</b> button in order to remove orphaned subcontainments<br/>");
message += i18n("&nbsp;&nbsp;2. Remove manually orphaned subcontainments when the layout is <b>not active</b><br/>");
//! add extra repair action
QAction *repairlayoutaction = new QAction(i18n("Repair"), this);
repairlayoutaction->setIcon(QIcon::fromTheme("dialog-yes"));
QList<QAction *> extraactions;
extraactions << repairlayoutaction;
Latte::Data::Layout currentlayout = m_handler->currentData();
connect(repairlayoutaction, &QAction::triggered, this, [&, currentlayout, orphaned]() {
auto centrallayout = m_handler->centralLayout(currentlayout.id);
for (int i=0; i<orphaned.count(); ++i) {
centrallayout->removeOrphanedSubContainment(orphaned[i]);
}
messagesForErrorsWarnings(centrallayout, true);
});
//! show message
showDefaultPersistentErrorWarningInlineMessage(message,
KMessageWidget::Warning,
extraactions);
}
void Views::save()
{
//! when this function is called we consider that removal has already been approved
updateDoubledMoveDestinationRows();
Latte::Data::Layout originallayout = m_handler->originalData();
Latte::Data::Layout currentlayout = m_handler->currentData();
Latte::CentralLayout *central = m_handler->centralLayout(currentlayout.id);
//! views in model
Latte::Data::ViewsTable originalViews = m_model->originalViewsData();
Latte::Data::ViewsTable currentViews = m_model->currentViewsData();
Latte::Data::ViewsTable alteredViews = m_model->alteredViews();
Latte::Data::ViewsTable newViews = m_model->newViews();
QHash<QString, Data::View> newviewsresponses;
QHash<QString, Data::View> cuttedpastedviews;
QHash<QString, Data::View> cuttedpastedactiveviews;
m_debugSaveCall++;
qDebug() << "org.kde.latte ViewsDialog::save() call: " << m_debugSaveCall << "-------- ";
//! add new views that are accepted
for(int i=0; i<newViews.rowCount(); ++i){
if (newViews[i].isMoveDestination) {
CentralLayout *originActive = originLayout(newViews[i]);
bool inmovebetweenactivelayouts = central->isActive() && originActive && central != originActive && hasValidOriginView(newViews[i]);
if (inmovebetweenactivelayouts) {
cuttedpastedactiveviews[newViews[i].id] = newViews[i];
continue;
}
cuttedpastedviews[newViews[i].id] = newViews[i];
}
if (newViews[i].state() == Data::View::OriginFromViewTemplate) {
Data::View addedview = central->newView(newViews[i]);
newviewsresponses[newViews[i].id] = addedview;
} else if (newViews[i].state() == Data::View::OriginFromLayout) {
Data::View adjustedview = newViews[i];
adjustedview.setState(Data::View::OriginFromViewTemplate, newViews[i].originFile(), QString(), QString());
Data::View addedview = central->newView(adjustedview);
newviewsresponses[newViews[i].id] = addedview;
}
}
//! update altered views
for (int i=0; i<alteredViews.rowCount(); ++i) {
if (alteredViews[i].state() == Data::View::IsCreated && !alteredViews[i].isMoveOrigin) {
qDebug() << "org.kde.latte ViewsDialog::save() updating altered view :: " << alteredViews[i];
central->updateView(alteredViews[i]);
}
}
//! remove deprecated views that have been removed from user
Latte::Data::ViewsTable removedViews = originalViews.subtracted(currentViews);
for (int i=0; i<removedViews.rowCount(); ++i) {
qDebug() << "org.kde.latte ViewsDialog::save() real removing view :: " << removedViews[i];
central->removeView(removedViews[i]);
}
//! remove deprecated views from external layouts that must be removed because of Cut->Paste Action
for(const auto vid: cuttedpastedviews.keys()){
bool viewidisinteger{true};
int vid_int = cuttedpastedviews[vid].originView().toInt(&viewidisinteger);
QString vid_str = cuttedpastedviews[vid].originView();
if (vid_str.isEmpty() || !viewidisinteger || vid_int<=0) {
//! ignore origin views that have not been created already
continue;
}
qDebug() << "org.kde.latte ViewsDialog::save() removing cut-pasted view :: " << cuttedpastedviews[vid];
//! Be Careful: Remove deprecated views from Cut->Paste Action
QString origincurrentid = cuttedpastedviews[vid].originLayout();
Data::Layout originlayout = m_handler->layoutsController()->originalData(origincurrentid);
Latte::CentralLayout *origin = m_handler->centralLayout(originlayout.id);
Data::ViewsTable originviews = Latte::Layouts::Storage::self()->views(origin);
if (originviews.containsId(vid_str)) {
origin->removeView(originviews[vid_str]);
}
}
//! move active views between different active layouts
for (const auto vid: cuttedpastedactiveviews.keys()) {
Data::View pastedactiveview = cuttedpastedactiveviews[vid];
uint originviewid = pastedactiveview.originView().toUInt();
CentralLayout *origin = originLayout(pastedactiveview);
QString originlayoutname = origin->name();
QString destinationlayoutname = originallayout.name;
auto view = origin->viewForContainment(originviewid);
QString tempviewid = pastedactiveview.id;
pastedactiveview.id = QString::number(originviewid);
qDebug() << "org.kde.latte ViewsDialog::save() move to another layout cutted-pasted active view :: " << pastedactiveview;
if (view) {
//! onscreen_view->onscreen_view
//! onscreen_view->offscreen_view
pastedactiveview.setState(pastedactiveview.state(), pastedactiveview.originFile(), destinationlayoutname, pastedactiveview.originView());
origin->updateView(pastedactiveview);
} else {
//! offscreen_view->onscreen_view
m_handler->corona()->layoutsManager()->moveView(originlayoutname, originviewid, destinationlayoutname);
//!is needed in order for layout to not trigger another move
pastedactiveview.setState(Data::View::IsCreated, QString(), QString(), QString());
central->updateView(pastedactiveview);
}
pastedactiveview.setState(Data::View::IsCreated, QString(), QString(), QString());
newviewsresponses[tempviewid] = pastedactiveview;
}
//! update
if ((removedViews.rowCount() > 0) || (newViews.rowCount() > 0)) {
m_handler->corona()->layoutsManager()->synchronizer()->syncActiveLayoutsToOriginalFiles();
}
//! update model for newly added views
for (const auto vid: newviewsresponses.keys()) {
m_model->setOriginalView(vid, newviewsresponses[vid]);
}
//! update all table with latest data and make the original one
currentViews = m_model->currentViewsData();
m_model->setOriginalData(currentViews);
//! update model activeness
if (central->isActive()) {
m_model->updateActiveStatesBasedOn(central);
}
//! Clear any templates keeper data in order to produce reupdates if needed
m_handler->layoutsController()->templatesKeeper()->clear();
}
QString Views::uniqueViewName(QString name)
{
if (name.isEmpty()) {
return name;
}
int pos_ = name.lastIndexOf(QRegExp(QString(" - [0-9]+")));
if (m_model->containsCurrentName(name) && pos_ > 0) {
name = name.left(pos_);
}
int i = 2;
QString namePart = name;
while (m_model->containsCurrentName(name)) {
name = namePart + " - " + QString::number(i);
i++;
}
return name;
}
QString Views::visibleViewName(const QString &id) const
{
if (id.isEmpty()) {
return QString();
}
Data::View view = m_model->currentData(id);
if (view.isValid()) {
return view.name;
}
return QString();
}
void Views::applyColumnWidths()
{
m_view->horizontalHeader()->setSectionResizeMode(Model::Views::SUBCONTAINMENTSCOLUMN, QHeaderView::Stretch);
if (m_viewColumnWidths.count()<(Model::Views::columnCount()-1)) {
return;
}
m_view->setColumnWidth(Model::Views::IDCOLUMN, m_viewColumnWidths[0].toInt());
m_view->setColumnWidth(Model::Views::NAMECOLUMN, m_viewColumnWidths[1].toInt());
m_view->setColumnWidth(Model::Views::SCREENCOLUMN, m_viewColumnWidths[2].toInt());
m_view->setColumnWidth(Model::Views::EDGECOLUMN, m_viewColumnWidths[3].toInt());
m_view->setColumnWidth(Model::Views::ALIGNMENTCOLUMN, m_viewColumnWidths[4].toInt());
}
void Views::storeColumnWidths()
{
if (m_viewColumnWidths.isEmpty() || (m_viewColumnWidths.count()<Model::Views::columnCount()-1)) {
m_viewColumnWidths.clear();
for (int i=0; i<Model::Views::columnCount(); ++i) {
m_viewColumnWidths << "";
}
}
m_viewColumnWidths[0] = QString::number(m_view->columnWidth(Model::Views::IDCOLUMN));
m_viewColumnWidths[1] = QString::number(m_view->columnWidth(Model::Views::NAMECOLUMN));
m_viewColumnWidths[2] = QString::number(m_view->columnWidth(Model::Views::SCREENCOLUMN));
m_viewColumnWidths[3] = QString::number(m_view->columnWidth(Model::Views::EDGECOLUMN));
m_viewColumnWidths[4] = QString::number(m_view->columnWidth(Model::Views::ALIGNMENTCOLUMN));
}
void Views::loadConfig()
{
QStringList defaultcolumnwidths;
defaultcolumnwidths << QString::number(59) << QString::number(256) << QString::number(142) << QString::number(135) << QString::number(131);
m_viewColumnWidths = m_storage.readEntry("columnWidths", defaultcolumnwidths);
m_viewSortColumn = m_storage.readEntry("sortColumn", (int)Model::Views::SCREENCOLUMN);
m_viewSortOrder = static_cast<Qt::SortOrder>(m_storage.readEntry("sortOrder", (int)Qt::AscendingOrder));
}
void Views::saveConfig()
{
m_storage.writeEntry("columnWidths", m_viewColumnWidths);
m_storage.writeEntry("sortColumn", m_viewSortColumn);
m_storage.writeEntry("sortOrder", (int)m_viewSortOrder);
}
}
}
}