diff --git a/app/lattecorona.cpp b/app/lattecorona.cpp index 9914f062..25c9b8ce 100644 --- a/app/lattecorona.cpp +++ b/app/lattecorona.cpp @@ -1,1061 +1,1061 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * 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 . */ #include "lattecorona.h" // local #include "alternativeshelper.h" #include "lattedockadaptor.h" #include "screenpool.h" #include "indicator/factory.h" #include "layout/centrallayout.h" #include "layout/genericlayout.h" #include "layout/sharedlayout.h" #include "layouts/importer.h" #include "layouts/manager.h" #include "layouts/launcherssignals.h" #include "shortcuts/globalshortcuts.h" #include "package/lattepackage.h" #include "plasma/extended/screenpool.h" #include "plasma/extended/theme.h" #include "settings/universalsettings.h" #include "view/view.h" #include "wm/abstractwindowinterface.h" #include "wm/waylandinterface.h" #include "wm/xwindowinterface.h" // Qt #include #include #include #include #include #include #include #include #include // Plasma #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Latte { Corona::Corona(bool defaultLayoutOnStartup, QString layoutNameOnStartUp, int userSetMemoryUsage, QObject *parent) : Plasma::Corona(parent), m_defaultLayoutOnStartup(defaultLayoutOnStartup), m_userSetMemoryUsage(userSetMemoryUsage), m_layoutNameOnStartUp(layoutNameOnStartUp), m_activityConsumer(new KActivities::Consumer(this)), m_screenPool(new ScreenPool(KSharedConfig::openConfig(), this)), m_indicatorFactory(new Indicator::Factory(this)), m_universalSettings(new UniversalSettings(KSharedConfig::openConfig(), this)), m_globalShortcuts(new GlobalShortcuts(this)), m_plasmaScreenPool(new PlasmaExtended::ScreenPool(this)), - m_themeExtended(new PlasmaExtended::Theme(KSharedConfig::openConfig(), this)) + m_themeExtended(new PlasmaExtended::Theme(KSharedConfig::openConfig(), this)), + m_layoutsManager(new Layouts::Manager(this)) { //! create the window manager if (KWindowSystem::isPlatformWayland()) { m_wm = new WaylandInterface(this); } else { m_wm = new XWindowInterface(this); } setupWaylandIntegration(); KPackage::Package package(new Latte::Package(this)); m_screenPool->load(); if (!package.isValid()) { qWarning() << staticMetaObject.className() << "the package" << package.metadata().rawData() << "is invalid!"; return; } else { qDebug() << staticMetaObject.className() << "the package" << package.metadata().rawData() << "is valid!"; } setKPackage(package); //! universal settings / extendedtheme must be loaded after the package has been set m_universalSettings->load(); m_themeExtended->load(); qmlRegisterTypes(); if (m_activityConsumer && (m_activityConsumer->serviceStatus() == KActivities::Consumer::Running)) { load(); } connect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &Corona::load); m_viewsScreenSyncTimer.setSingleShot(true); m_viewsScreenSyncTimer.setInterval(m_universalSettings->screenTrackerInterval()); connect(&m_viewsScreenSyncTimer, &QTimer::timeout, this, &Corona::syncLatteViewsToScreens); connect(m_universalSettings, &UniversalSettings::screenTrackerIntervalChanged, this, [this]() { m_viewsScreenSyncTimer.setInterval(m_universalSettings->screenTrackerInterval()); }); //! initialize the background tracer for broadcasted backgrounds m_backgroundTracer = new KDeclarative::QmlObjectSharedEngine(this); m_backgroundTracer->setInitializationDelayed(true); m_backgroundTracer->setSource(kPackage().filePath("backgroundTracer")); m_backgroundTracer->completeInitialization(); //! Dbus adaptor initialization new LatteDockAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/Latte"), this); } Corona::~Corona() { //! BEGIN: Give the time to slide-out views when closing m_layoutsManager->hideAllViews(); //! Don't delay the destruction under wayland in any case //! because it creates a crash with kwin effects //! https://bugs.kde.org/show_bug.cgi?id=392890 if (!KWindowSystem::isPlatformWayland()) { QTimer::singleShot(400, [this]() { m_quitTimedEnded = true; }); while (!m_quitTimedEnded) { QGuiApplication::processEvents(QEventLoop::AllEvents, 50); } } //! END: slide-out views when closing m_viewsScreenSyncTimer.stop(); if (m_layoutsManager->memoryUsage() == Types::SingleLayout) { cleanConfig(); } qDebug() << "Latte Corona - unload: containments ..."; m_layoutsManager->unload(); m_wm->deleteLater(); m_globalShortcuts->deleteLater(); m_layoutsManager->deleteLater(); m_screenPool->deleteLater(); m_universalSettings->deleteLater(); m_plasmaScreenPool->deleteLater(); m_backgroundTracer->deleteLater(); m_themeExtended->deleteLater(); m_indicatorFactory->deleteLater(); disconnect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &Corona::load); delete m_activityConsumer; qDebug() << "Latte Corona - deleted..."; } void Corona::load() { if (m_activityConsumer && (m_activityConsumer->serviceStatus() == KActivities::Consumer::Running) && m_activitiesStarting) { m_activitiesStarting = false; disconnect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &Corona::load); - m_layoutsManager = new Layouts::Manager(this); m_layoutsManager->load(); connect(this, &Corona::availableScreenRectChangedFrom, this, &Plasma::Corona::availableScreenRectChanged); connect(this, &Corona::availableScreenRegionChangedFrom, this, &Plasma::Corona::availableScreenRegionChanged); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Corona::primaryOutputChanged, Qt::UniqueConnection); connect(QApplication::desktop(), &QDesktopWidget::screenCountChanged, this, &Corona::screenCountChanged); connect(m_screenPool, &ScreenPool::primaryPoolChanged, this, &Corona::screenCountChanged); QString assignedLayout = m_layoutsManager->shouldSwitchToLayout(m_activityConsumer->currentActivity()); QString loadLayoutName = ""; if (!m_defaultLayoutOnStartup && m_layoutNameOnStartUp.isEmpty()) { if (!assignedLayout.isEmpty() && assignedLayout != m_universalSettings->currentLayoutName()) { loadLayoutName = assignedLayout; } else { loadLayoutName = m_universalSettings->currentLayoutName(); } if (!m_layoutsManager->layoutExists(loadLayoutName)) { loadLayoutName = m_layoutsManager->defaultLayoutName(); m_layoutsManager->importDefaultLayout(false); } } else if (m_defaultLayoutOnStartup) { loadLayoutName = m_layoutsManager->importer()->uniqueLayoutName(m_layoutsManager->defaultLayoutName()); m_layoutsManager->importDefaultLayout(true); } else { loadLayoutName = m_layoutNameOnStartUp; } if (m_userSetMemoryUsage != -1 && !KWindowSystem::isPlatformWayland()) { Types::LayoutsMemoryUsage usage = static_cast(m_userSetMemoryUsage); m_universalSettings->setLayoutsMemoryUsage(usage); } if (KWindowSystem::isPlatformWayland()) { m_universalSettings->setLayoutsMemoryUsage(Types::SingleLayout); } m_layoutsManager->loadLayoutOnStartup(loadLayoutName); //! load screens signals such screenGeometryChanged in order to support //! plasmoid.screenGeometry properly for (QScreen *screen : qGuiApp->screens()) { addOutput(screen); } connect(qGuiApp, &QGuiApplication::screenAdded, this, &Corona::addOutput, Qt::UniqueConnection); } } void Corona::unload() { qDebug() << "unload: removing containments..."; while (!containments().isEmpty()) { //deleting a containment will remove it from the list due to QObject::destroyed connect in Corona //this form doesn't crash, while qDeleteAll(containments()) does delete containments().first(); } } void Corona::setupWaylandIntegration() { if (!KWindowSystem::isPlatformWayland()) { return; } using namespace KWayland::Client; auto connection = ConnectionThread::fromApplication(this); if (!connection) { return; } Registry *registry{new Registry(this)}; registry->create(connection); connect(registry, &Registry::plasmaShellAnnounced, this , [this, registry](quint32 name, quint32 version) { m_waylandCorona = registry->createPlasmaShell(name, version, this); }); QObject::connect(registry, &KWayland::Client::Registry::plasmaWindowManagementAnnounced, [this, registry](quint32 name, quint32 version) { KWayland::Client::PlasmaWindowManagement *pwm = registry->createPlasmaWindowManagement(name, version, this); WaylandInterface *wI = qobject_cast(m_wm); if (wI) { wI->initWindowManagement(pwm); } }); registry->setup(); connection->roundtrip(); } KActivities::Consumer *Corona::activityConsumer() const { return m_activityConsumer; } KWayland::Client::PlasmaShell *Corona::waylandCoronaInterface() const { return m_waylandCorona; } void Corona::cleanConfig() { auto containmentsEntries = config()->group("Containments"); bool changed = false; for(const auto &cId : containmentsEntries.groupList()) { if (!containmentExists(cId.toUInt())) { //cleanup obsolete containments containmentsEntries.group(cId).deleteGroup(); changed = true; qDebug() << "obsolete containment configuration deleted:" << cId; } else { //cleanup obsolete applets of running containments auto appletsEntries = containmentsEntries.group(cId).group("Applets"); for(const auto &appletId : appletsEntries.groupList()) { if (!appletExists(cId.toUInt(), appletId.toUInt())) { appletsEntries.group(appletId).deleteGroup(); changed = true; qDebug() << "obsolete applet configuration deleted:" << appletId; } } } } if (changed) { config()->sync(); qDebug() << "configuration file cleaned..."; } } bool Corona::containmentExists(uint id) const { for(const auto containment : containments()) { if (id == containment->id()) { return true; } } return false; } bool Corona::appletExists(uint containmentId, uint appletId) const { Plasma::Containment *containment = nullptr; for(const auto cont : containments()) { if (containmentId == cont->id()) { containment = cont; break; } } if (!containment) { return false; } for(const auto applet : containment->applets()) { if (applet->id() == appletId) { return true; } } return false; } KActivities::Consumer *Corona::activitiesConsumer() const { return m_activityConsumer; } GlobalShortcuts *Corona::globalShortcuts() const { return m_globalShortcuts; } ScreenPool *Corona::screenPool() const { return m_screenPool; } UniversalSettings *Corona::universalSettings() const { return m_universalSettings; } AbstractWindowInterface *Corona::wm() const { return m_wm; } Indicator::Factory *Corona::indicatorFactory() const { return m_indicatorFactory; } Layouts::Manager *Corona::layoutsManager() const { return m_layoutsManager; } PlasmaExtended::ScreenPool *Corona::plasmaScreenPool() const { return m_plasmaScreenPool; } PlasmaExtended::Theme *Corona::themeExtended() const { return m_themeExtended; } int Corona::numScreens() const { return qGuiApp->screens().count(); } QRect Corona::screenGeometry(int id) const { const auto screens = qGuiApp->screens(); const QScreen *screen{qGuiApp->primaryScreen()}; QString screenName; if (m_screenPool->hasId(id)) { screenName = m_screenPool->connector(id); } for(const auto scr : screens) { if (scr->name() == screenName) { screen = scr; break; } } return screen->geometry(); } QRegion Corona::availableScreenRegion(int id) const { return availableScreenRegionWithCriteria(id); } QRegion Corona::availableScreenRegionWithCriteria(int id, QString forLayout) const { const auto screens = qGuiApp->screens(); const QScreen *screen{qGuiApp->primaryScreen()}; QString screenName; if (m_screenPool->hasId(id)) { screenName = m_screenPool->connector(id); } for(auto scr : screens) { if (scr->name() == screenName) { screen = scr; break; } } if (!screen) return QRegion(); QList views; if (forLayout.isEmpty()) { Latte::CentralLayout *currentLayout = m_layoutsManager->currentLayout(); views = currentLayout->latteViews(); } else { Layout::GenericLayout *generic = m_layoutsManager->centralLayout(forLayout); if (!generic) { //! Identify best active layout to be used for metrics calculations. //! Active layouts are always take into account their shared layouts for their metrics SharedLayout *sharedLayout = m_layoutsManager->sharedLayout(forLayout); if (sharedLayout) { generic = sharedLayout->currentCentralLayout(); } } if (!generic) { generic = m_layoutsManager->currentLayout(); } views = generic->latteViews(); } QRegion available(screen->geometry()); for (const auto *view : views) { if (view && view->containment() && view->screen() == screen && view->visibility() && (view->visibility()->mode() != Latte::Types::AutoHide)) { int realThickness = view->normalThickness(); // Usually availableScreenRect is used by the desktop, // but Latte don't have desktop, then here just // need calculate available space for top and bottom location, // because the left and right are those who dodge others views switch (view->location()) { case Plasma::Types::TopEdge: if (view->behaveAsPlasmaPanel()) { available -= view->geometry(); } else { QRect realGeometry; int realWidth = view->maxLength() * view->width(); switch (view->alignment()) { case Latte::Types::Left: realGeometry = QRect(view->x(), view->y(), realWidth, realThickness); break; case Latte::Types::Center: case Latte::Types::Justify: realGeometry = QRect(qMax(view->geometry().x(), view->geometry().center().x() - realWidth / 2), view->y(), realWidth, realThickness); break; case Latte::Types::Right: realGeometry = QRect(view->geometry().right() - realWidth + 1, view->y(), realWidth, realThickness); break; } available -= realGeometry; } break; case Plasma::Types::BottomEdge: if (view->behaveAsPlasmaPanel()) { available -= view->geometry(); } else { QRect realGeometry; int realWidth = view->maxLength() * view->width(); int realY = view->geometry().bottom() - realThickness + 1; switch (view->alignment()) { case Latte::Types::Left: realGeometry = QRect(view->x(), realY, realWidth, realThickness); break; case Latte::Types::Center: case Latte::Types::Justify: realGeometry = QRect(qMax(view->geometry().x(), view->geometry().center().x() - realWidth / 2), realY, realWidth, realThickness); break; case Latte::Types::Right: realGeometry = QRect(view->geometry().right() - realWidth + 1, realY, realWidth, realThickness); break; } available -= realGeometry; } break; default: //! bypass clang warnings break; } } } /*qDebug() << "::::: FREE AREAS :::::"; for (int i = 0; i < available.rectCount(); ++i) { qDebug() << available.rects().at(i); } qDebug() << "::::: END OF FREE AREAS :::::";*/ return available; } QRect Corona::availableScreenRect(int id) const { return availableScreenRectWithCriteria(id); } QRect Corona::availableScreenRectWithCriteria(int id, QList modes, QList edges) const { const auto screens = qGuiApp->screens(); const QScreen *screen{qGuiApp->primaryScreen()}; if (m_screenPool->hasId(id)) { QString scrName = m_screenPool->connector(id); for(const auto scr : screens) { if (scr->name() == scrName) { screen = scr; break; } } } if (!screen) return {}; bool allModes = modes.isEmpty(); bool allEdges = edges.isEmpty(); auto available = screen->geometry(); Latte::CentralLayout *currentLayout = m_layoutsManager->currentLayout(); QList views; if (currentLayout) { views = currentLayout->latteViews(); } for (const auto *view : views) { if (view && view->containment() && view->screen() == screen && ((allEdges || edges.contains(view->location())) && (allModes || (view->visibility() && modes.contains(view->visibility()->mode()))))) { // Usually availableScreenRect is used by the desktop, // but Latte don't have desktop, then here just // need calculate available space for top and bottom location, // because the left and right are those who dodge others docks switch (view->location()) { case Plasma::Types::TopEdge: available.setTop(view->y() + view->normalThickness()); break; case Plasma::Types::BottomEdge: available.setBottom(view->y() + view->height() - view->normalThickness()); break; case Plasma::Types::LeftEdge: available.setLeft(view->x() + view->normalThickness()); break; case Plasma::Types::RightEdge: available.setRight(view->x() + view->width() - view->normalThickness()); break; default: //! bypass clang warnings break; } } } return available; } void Corona::addOutput(QScreen *screen) { Q_ASSERT(screen); int id = m_screenPool->id(screen->name()); if (id == -1) { int newId = m_screenPool->firstAvailableId(); m_screenPool->insertScreenMapping(newId, screen->name()); } connect(screen, &QScreen::geometryChanged, this, [ = ]() { const int id = m_screenPool->id(screen->name()); if (id >= 0) { emit screenGeometryChanged(id); emit availableScreenRegionChanged(); emit availableScreenRectChanged(); } }); emit availableScreenRectChanged(); emit screenAdded(m_screenPool->id(screen->name())); } void Corona::primaryOutputChanged() { m_viewsScreenSyncTimer.start(); } void Corona::screenRemoved(QScreen *screen) { Q_ASSERT(screen); } void Corona::screenCountChanged() { m_viewsScreenSyncTimer.start(); } //! the central functions that updates loading/unloading latteviews //! concerning screen changed (for multi-screen setups mainly) void Corona::syncLatteViewsToScreens() { m_layoutsManager->syncLatteViewsToScreens(); } int Corona::primaryScreenId() const { return m_screenPool->id(qGuiApp->primaryScreen()->name()); } void Corona::closeApplication() { //! this code must be called asynchronously because it is called //! also from qml (Settings window). QTimer::singleShot(5, [this]() { m_layoutsManager->hideLatteSettingsDialog(); m_layoutsManager->hideAllViews(); }); //! give the time for the views to hide themselves QTimer::singleShot(500, [this]() { qGuiApp->quit(); }); } void Corona::aboutApplication() { if (aboutDialog) { aboutDialog->hide(); aboutDialog->deleteLater(); } aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData()); connect(aboutDialog.data(), &QDialog::finished, aboutDialog.data(), &QObject::deleteLater); m_wm->skipTaskBar(*aboutDialog); m_wm->setKeepAbove(*aboutDialog, true); aboutDialog->show(); } int Corona::screenForContainment(const Plasma::Containment *containment) const { //FIXME: indexOf is not a proper way to support multi-screen // as for environment to environment the indexes change // also there is the following issue triggered // from latteView adaptToScreen() // // in a multi-screen environment that // primary screen is not set to 0 it was // created an endless showing loop at // startup (catch-up race) between // screen:0 and primaryScreen //case in which this containment is child of an applet, hello systray :) if (Plasma::Applet *parentApplet = qobject_cast(containment->parent())) { if (Plasma::Containment *cont = parentApplet->containment()) { return screenForContainment(cont); } else { return -1; } } Latte::CentralLayout *currentLayout = m_layoutsManager->currentLayout(); Latte::View *view = currentLayout->viewForContainment(containment); if (view && view->screen()) { return m_screenPool->id(view->screen()->name()); } //Failed? fallback on lastScreen() //lastScreen() is the correct screen for panels //It is also correct for desktops *that have the correct activity()* //a containment with lastScreen() == 0 but another activity, //won't be associated to a screen // qDebug() << "ShellCorona screenForContainment: " << containment << " Last screen is " << containment->lastScreen(); for (auto screen : qGuiApp->screens()) { // containment->lastScreen() == m_screenPool->id(screen->name()) to check if the lastScreen refers to a screen that exists/it's known if (containment->lastScreen() == m_screenPool->id(screen->name()) && (containment->activity() == m_activityConsumer->currentActivity() || containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) { return containment->lastScreen(); } } return -1; } void Corona::showAlternativesForApplet(Plasma::Applet *applet) { const QString alternativesQML = kPackage().filePath("appletalternativesui"); if (alternativesQML.isEmpty()) { return; } Latte::CentralLayout *currentLayout = m_layoutsManager->currentLayout(); Latte::View *latteView = currentLayout->viewForContainment(applet->containment()); KDeclarative::QmlObjectSharedEngine *qmlObj{nullptr}; if (latteView) { latteView->setAlternativesIsShown(true); qmlObj = new KDeclarative::QmlObjectSharedEngine(latteView); } else { qmlObj = new KDeclarative::QmlObjectSharedEngine(this); } qmlObj->setInitializationDelayed(true); qmlObj->setSource(QUrl::fromLocalFile(alternativesQML)); AlternativesHelper *helper = new AlternativesHelper(applet, qmlObj); qmlObj->rootContext()->setContextProperty(QStringLiteral("alternativesHelper"), helper); m_alternativesObjects << qmlObj; qmlObj->completeInitialization(); //! Alternative dialog signals connect(helper, &QObject::destroyed, this, [latteView]() { latteView->setAlternativesIsShown(false); }); connect(qmlObj->rootObject(), SIGNAL(visibleChanged(bool)), this, SLOT(alternativesVisibilityChanged(bool))); connect(applet, &Plasma::Applet::destroyedChanged, this, [this, qmlObj](bool destroyed) { if (!destroyed) { return; } QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObjectSharedEngine *obj = it.next(); if (obj == qmlObj) { it.remove(); obj->deleteLater(); } } }); } void Corona::alternativesVisibilityChanged(bool visible) { if (visible) { return; } QObject *root = sender(); QMutableListIterator it(m_alternativesObjects); while (it.hasNext()) { KDeclarative::QmlObjectSharedEngine *obj = it.next(); if (obj->rootObject() == root) { it.remove(); obj->deleteLater(); } } } void Corona::addViewForLayout(QString layoutName) { qDebug() << "loading default layout"; //! Setting mutable for create a containment setImmutability(Plasma::Types::Mutable); QVariantList args; auto defaultContainment = createContainmentDelayed("org.kde.latte.containment", args); defaultContainment->setContainmentType(Plasma::Types::PanelContainment); defaultContainment->init(); if (!defaultContainment || !defaultContainment->kPackage().isValid()) { qWarning() << "the requested containment plugin can not be located or loaded"; return; } auto config = defaultContainment->config(); defaultContainment->restore(config); using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; Layout::GenericLayout *currentLayout = m_layoutsManager->layout(layoutName); if (currentLayout) { edges = currentLayout->freeEdges(defaultContainment->screen()); } if ((edges.count() > 0)) { defaultContainment->setLocation(edges.at(0)); } else { defaultContainment->setLocation(Plasma::Types::BottomEdge); } if (m_layoutsManager->memoryUsage() == Latte::Types::MultipleLayouts) { config.writeEntry("layoutId", layoutName); } defaultContainment->updateConstraints(Plasma::Types::StartupCompletedConstraint); defaultContainment->save(config); requestConfigSync(); defaultContainment->flushPendingConstraintsEvents(); emit containmentAdded(defaultContainment); emit containmentCreated(defaultContainment); defaultContainment->createApplet(QStringLiteral("org.kde.latte.plasmoid")); defaultContainment->createApplet(QStringLiteral("org.kde.plasma.analogclock")); } void Corona::loadDefaultLayout() { addViewForLayout(m_layoutsManager->currentLayoutName()); } QStringList Corona::containmentsIds() { QStringList ids; for(const auto containment : containments()) { ids << QString::number(containment->id()); } return ids; } QStringList Corona::appletsIds() { QStringList ids; for(const auto containment : containments()) { auto applets = containment->config().group("Applets"); ids << applets.groupList(); } return ids; } //! Activate launcher menu through dbus interface void Corona::activateLauncherMenu() { m_globalShortcuts->activateLauncherMenu(); } void Corona::windowColorScheme(QString windowIdAndScheme) { int firstSlash = windowIdAndScheme.indexOf("-"); QString windowIdStr = windowIdAndScheme.mid(0, firstSlash); QString schemeStr = windowIdAndScheme.mid(firstSlash + 1); if (KWindowSystem::isPlatformWayland()) { QTimer::singleShot(200, [this, schemeStr]() { //! [Wayland Case] - give the time to be informed correctly for the active window id //! otherwise the active window id may not be the same with the one trigerred //! the color scheme dbus signal QString windowIdStr = m_wm->activeWindow().toString(); m_wm->setColorSchemeForWindow(windowIdStr.toUInt(), schemeStr); }); } else { m_wm->setColorSchemeForWindow(windowIdStr.toUInt(), schemeStr); } } //! update badge for specific view item void Corona::updateDockItemBadge(QString identifier, QString value) { m_globalShortcuts->updateViewItemBadge(identifier, value); } void Corona::switchToLayout(QString layout) { m_layoutsManager->switchToLayout(layout); } void Corona::showSettingsWindow(int page) { Types::LatteConfigPage p = Types::LayoutPage; if (page >= Types::LayoutPage && page <= Types::PreferencesPage) { p = static_cast(page); } m_layoutsManager->showLatteSettingsDialog(p); } void Corona::setContextMenuView(int id) { //! set context menu view id m_contextMenuViewId = id; } QStringList Corona::contextMenuData() { QStringList data; Types::ViewType viewType{Types::DockView}; Latte::CentralLayout *currentLayout = m_layoutsManager->currentLayout(); if (currentLayout) { viewType = currentLayout->latteViewType(m_contextMenuViewId); } data << QString::number((int)m_layoutsManager->memoryUsage()); data << m_layoutsManager->currentLayoutName(); data << QString::number((int)viewType); for(const auto &layoutName : m_layoutsManager->menuLayouts()) { if (m_layoutsManager->centralLayout(layoutName)) { data << QString("1," + layoutName); } else { data << QString("0," + layoutName); } } //! reset context menu view id m_contextMenuViewId = -1; return data; } void Corona::setBackgroundFromBroadcast(QString activity, QString screenName, QString filename) { if (filename.startsWith("file://")) { filename = filename.remove(0,7); } QMetaObject::invokeMethod(m_backgroundTracer->rootObject(), "setBackgroundFromBroadcast", Q_ARG(QVariant, activity), Q_ARG(QVariant, screenName), Q_ARG(QVariant, filename)); } void Corona::setBroadcastedBackgroundsEnabled(QString activity, QString screenName, bool enabled) { QMetaObject::invokeMethod(m_backgroundTracer->rootObject(), "setBroadcastedBackgroundsEnabled", Q_ARG(QVariant, activity), Q_ARG(QVariant, screenName), Q_ARG(QVariant, enabled)); } inline void Corona::qmlRegisterTypes() const { qmlRegisterType(); qmlRegisterType(); } } diff --git a/app/layouts/manager.cpp b/app/layouts/manager.cpp index 210df009..3294ae5f 100644 --- a/app/layouts/manager.cpp +++ b/app/layouts/manager.cpp @@ -1,550 +1,551 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * 2019 Michail Vourlakos * * 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 . */ #include "manager.h" // local #include "importer.h" #include "launcherssignals.h" #include "../infoview.h" #include "../screenpool.h" #include "../layout/abstractlayout.h" #include "../layout/centrallayout.h" #include "../layout/genericlayout.h" #include "../layout/sharedlayout.h" #include "../settings/settingsdialog.h" #include "../settings/universalsettings.h" #include "../view/view.h" // Qt #include #include #include #include #include // KDE #include #include namespace Latte { namespace Layouts { const int MultipleLayoutsPresetId = 10; Manager::Manager(QObject *parent) : QObject(parent), m_importer(new Importer(this)), - m_launchersSignals(new LaunchersSignals(this)), - m_synchronizer(new Synchronizer(this)) + m_launchersSignals(new LaunchersSignals(this)) { m_corona = qobject_cast(parent); + //! needs to be created AFTER corona assignment + m_synchronizer = new Synchronizer(this); if (m_corona) { connect(m_corona->universalSettings(), &UniversalSettings::currentLayoutNameChanged, this, &Manager::currentLayoutNameChanged); connect(m_synchronizer, &Synchronizer::centralLayoutsChanged, this, &Manager::centralLayoutsChanged); connect(m_synchronizer, &Synchronizer::currentLayoutNameChanged, this, &Manager::currentLayoutNameChanged); connect(m_synchronizer, &Synchronizer::currentLayoutIsSwitching, this, &Manager::currentLayoutIsSwitching); connect(m_synchronizer, &Synchronizer::layoutsChanged, this, &Manager::layoutsChanged); connect(m_synchronizer, &Synchronizer::menuLayoutsChanged, this, &Manager::menuLayoutsChanged); } } Manager::~Manager() { m_importer->deleteLater(); m_launchersSignals->deleteLater(); //! no needed because Latte:Corona is calling it at better place // unload(); m_synchronizer->deleteLater(); } void Manager::load() { m_presetsPaths.clear(); int configVer = m_corona->universalSettings()->version(); qDebug() << "Universal Settings version : " << configVer; if (configVer < 2 && QFile(QDir::homePath() + "/.config/lattedockrc").exists()) { qDebug() << "Latte must update its configuration..."; m_importer->updateOldConfiguration(); importPresets(false); } else if (!QFile(QDir::homePath() + "/.config/lattedockrc").exists()) { //startup create what is necessary.... QDir layoutDir(QDir::homePath() + "/.config/latte"); if (!layoutDir.exists()) { QDir(QDir::homePath() + "/.config").mkdir("latte"); } newLayout(i18n("My Layout")); importPresets(false); m_corona->universalSettings()->setCurrentLayoutName(i18n("My Layout")); m_corona->universalSettings()->setVersion(2); } //! Check if the multiple-layouts hidden file is present, add it if it isnt if (!QFile(QDir::homePath() + "/.config/latte/" + Layout::AbstractLayout::MultipleLayoutsName + ".layout.latte").exists()) { importPreset(MultipleLayoutsPresetId, false); } qDebug() << "Latte is loading its layouts..."; m_presetsPaths.append(m_corona->kPackage().filePath("preset1")); m_presetsPaths.append(m_corona->kPackage().filePath("preset2")); m_presetsPaths.append(m_corona->kPackage().filePath("preset3")); m_presetsPaths.append(m_corona->kPackage().filePath("preset4")); m_synchronizer->loadLayouts(); } void Manager::unload() { m_synchronizer->unloadLayouts(); //! Remove no-needed temp files QString temp1File = QDir::homePath() + "/.config/lattedock.copy1.bak"; QString temp2File = QDir::homePath() + "/.config/lattedock.copy2.bak"; QFile file1(temp1File); QFile file2(temp2File); if (file1.exists()) { file1.remove(); } if (file2.exists()) { file2.remove(); } } Latte::Corona *Manager::corona() { return m_corona; } Importer *Manager::importer() { return m_importer; } LaunchersSignals *Manager::launchersSignals() const { return m_launchersSignals; } Synchronizer *Manager::synchronizer() const { return m_synchronizer; } QString Manager::currentLayoutName() const { return m_synchronizer->currentLayoutName(); } QString Manager::defaultLayoutName() const { QByteArray presetNameOrig = QString("preset" + QString::number(1)).toUtf8(); QString presetPath = m_corona->kPackage().filePath(presetNameOrig); QString presetName = CentralLayout::layoutName(presetPath); QByteArray presetNameChars = presetName.toUtf8(); presetName = i18n(presetNameChars); return presetName; } bool Manager::layoutExists(QString layoutName) const { return m_synchronizer->layoutExists(layoutName); } QStringList Manager::layouts() const { return m_synchronizer->layouts(); } QStringList Manager::menuLayouts() const { return m_synchronizer->menuLayouts(); } void Manager::setMenuLayouts(QStringList layouts) { m_synchronizer->setMenuLayouts(layouts); } QStringList Manager::activities() { return m_synchronizer->activities(); } QStringList Manager::runningActivities() { return m_synchronizer->runningActivities(); } QStringList Manager::orphanedActivities() { return m_synchronizer->orphanedActivities(); } QStringList Manager::presetsPaths() const { return m_presetsPaths; } Types::LayoutsMemoryUsage Manager::memoryUsage() const { return m_corona->universalSettings()->layoutsMemoryUsage(); } int Manager::layoutsMemoryUsage() { return (int)m_corona->universalSettings()->layoutsMemoryUsage(); } void Manager::setMemoryUsage(Types::LayoutsMemoryUsage memoryUsage) { m_corona->universalSettings()->setLayoutsMemoryUsage(memoryUsage); } bool Manager::latteViewExists(Latte::View *view) const { return m_synchronizer->latteViewExists(view); } QStringList Manager::centralLayoutsNames() { return m_synchronizer->centralLayoutsNames(); } QStringList Manager::sharedLayoutsNames() { return m_synchronizer->sharedLayoutsNames(); } QStringList Manager::storedSharedLayouts() const { return m_synchronizer->storedSharedLayouts(); } Layout::GenericLayout *Manager::layout(QString id) const { return m_synchronizer->layout(id); } CentralLayout *Manager::centralLayout(QString id) const { return m_synchronizer->centralLayout(id); } int Manager::centralLayoutPos(QString id) const { return m_synchronizer->centralLayoutPos(id); } SharedLayout *Manager::sharedLayout(QString id) const { return m_synchronizer->sharedLayout(id); } bool Manager::registerAtSharedLayout(CentralLayout *central, QString id) { return m_synchronizer->registerAtSharedLayout(central, id); } CentralLayout *Manager::currentLayout() const { return m_synchronizer->currentLayout(); } bool Manager::switchToLayout(QString layoutName, int previousMemoryUsage) { return m_synchronizer->switchToLayout(layoutName, previousMemoryUsage); } QString Manager::shouldSwitchToLayout(QString activityId) { return m_synchronizer->shouldSwitchToLayout(activityId); } void Manager::loadLayoutOnStartup(QString layoutName) { QStringList layouts = m_importer->checkRepairMultipleLayoutsLinkedFile(); //! Latte didn't close correctly, maybe a crash if (layouts.size() > 0) { QMessageBox *msg = new QMessageBox(); msg->setAttribute(Qt::WA_DeleteOnClose); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Multiple Layouts Warning")); msg->setText(i18n("Latte did not close properly in the previous session. The following layout(s) [%0] were updated for consistency!!!").arg(layouts.join(","))); msg->setStandardButtons(QMessageBox::Ok); msg->open(); } m_synchronizer->switchToLayout(layoutName); } void Manager::loadLatteLayout(QString layoutPath) { qDebug() << " -------------------------------------------------------------------- "; qDebug() << " -------------------------------------------------------------------- "; if (m_corona->containments().size() > 0) { qDebug() << "LOAD LATTE LAYOUT ::: There are still containments present !!!! :: " << m_corona->containments().size(); } if (!layoutPath.isEmpty() && m_corona->containments().size() == 0) { cleanupOnStartup(layoutPath); qDebug() << "LOADING CORONA LAYOUT:" << layoutPath; m_corona->loadLayout(layoutPath); } } void Manager::cleanupOnStartup(QString path) { KSharedConfigPtr filePtr = KSharedConfig::openConfig(path); KConfigGroup actionGroups = KConfigGroup(filePtr, "ActionPlugins"); QStringList deprecatedActionGroup; for (const auto &actId : actionGroups.groupList()) { QString pluginId = actionGroups.group(actId).readEntry("RightButton;NoModifier", ""); if (pluginId == "org.kde.contextmenu") { deprecatedActionGroup << actId; } } for (const auto &pId : deprecatedActionGroup) { qDebug() << "!!!!!!!!!!!!!!!! !!!!!!!!!!!! !!!!!!! REMOVING :::: " << pId; actionGroups.group(pId).deleteGroup(); } KConfigGroup containmentGroups = KConfigGroup(filePtr, "Containments"); QStringList removeContaimentsList; for (const auto &cId : containmentGroups.groupList()) { QString pluginId = containmentGroups.group(cId).readEntry("plugin", ""); if (pluginId == "org.kde.desktopcontainment") { //!must remove ghost containments first removeContaimentsList << cId; } } for (const auto &cId : removeContaimentsList) { containmentGroups.group(cId).deleteGroup(); } actionGroups.sync(); containmentGroups.sync(); } void Manager::showAboutDialog() { m_corona->aboutApplication(); } void Manager::hideAllViews() { m_synchronizer->hideAllViews(); } void Manager::pauseLayout(QString layoutName) { m_synchronizer->pauseLayout(layoutName); } void Manager::syncActiveLayoutsToOriginalFiles() { m_synchronizer->syncActiveLayoutsToOriginalFiles(); } void Manager::clearUnloadedContainmentsFromLinkedFile(QStringList containmentsIds, bool bypassChecks) { if (!m_corona || (memoryUsage() == Types::SingleLayout && !bypassChecks)) { return; } auto containments = m_corona->config()->group("Containments"); for (const auto &conId : containmentsIds) { qDebug() << "unloads ::: " << conId; KConfigGroup containment = containments.group(conId); containment.deleteGroup(); } containments.sync(); } void Manager::syncLatteViewsToScreens() { m_synchronizer->syncLatteViewsToScreens(); } QString Manager::newLayout(QString layoutName, QString preset) { QDir layoutDir(QDir::homePath() + "/.config/latte"); QStringList filter; filter.append(QString(layoutName + "*.layout.latte")); QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks); //! if the newLayout already exists provide a newName that doesn't if (files.count() >= 1) { int newCounter = files.count() + 1; layoutName = layoutName + "-" + QString::number(newCounter); } QString newLayoutPath = layoutDir.absolutePath() + "/" + layoutName + ".layout.latte"; qDebug() << "adding layout : " << layoutName << " based on preset:" << preset; if (preset == i18n("Default") && !QFile(newLayoutPath).exists()) { qDebug() << "adding layout : succeed"; QFile(m_corona->kPackage().filePath("preset1")).copy(newLayoutPath); } return newLayoutPath; } void Manager::importDefaultLayout(bool newInstanceIfPresent) { importPreset(1, newInstanceIfPresent); if (newInstanceIfPresent) { m_synchronizer->loadLayouts(); } } void Manager::importPresets(bool includeDefault) { int start = 1; if (!includeDefault) { start = 2; } for (int i = start; i <= 4; ++i) { importPreset(i, false); } } void Manager::importPreset(int presetNo, bool newInstanceIfPresent) { QDir configDir(QDir::homePath() + "/.config"); if (!QDir(configDir.absolutePath() + "/latte").exists()) { configDir.mkdir("latte"); } QByteArray presetNameOrig = QString("preset" + QString::number(presetNo)).toUtf8(); QString presetPath = m_corona->kPackage().filePath(presetNameOrig); QString presetName = Layout::AbstractLayout::layoutName(presetPath); QByteArray presetNameChars = presetName.toUtf8(); presetName = i18n(presetNameChars); //! hide the multiple layouts layout file from user presetName = (presetNo == MultipleLayoutsPresetId) ? "." + presetName : presetName; QString newLayoutFile = ""; if (newInstanceIfPresent) { newLayoutFile = QDir::homePath() + "/.config/latte/" + m_importer->uniqueLayoutName(presetName) + ".layout.latte"; } else { newLayoutFile = QDir::homePath() + "/.config/latte/" + presetName + ".layout.latte"; } if (!QFile(newLayoutFile).exists()) { QFile(presetPath).copy(newLayoutFile); QFileInfo newFileInfo(newLayoutFile); if (newFileInfo.exists() && !newFileInfo.isWritable()) { QFile(newLayoutFile).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } } } void Manager::showLatteSettingsDialog(int page) { if (!m_latteSettingsDialog) { m_latteSettingsDialog = new SettingsDialog(nullptr, m_corona); } m_latteSettingsDialog->show(); if (m_latteSettingsDialog->isMinimized()) { m_latteSettingsDialog->showNormal(); } Types::LatteConfigPage configPage = static_cast(page); m_latteSettingsDialog->setCurrentPage(configPage); m_latteSettingsDialog->activateWindow(); } void Manager::hideLatteSettingsDialog() { if (m_latteSettingsDialog) { m_latteSettingsDialog->deleteLater(); m_latteSettingsDialog = nullptr; } } void Manager::showInfoWindow(QString info, int duration, QStringList activities) { for (const auto screen : qGuiApp->screens()) { InfoView *infoView = new InfoView(m_corona, info, screen); infoView->show(); infoView->setOnActivities(activities); QTimer::singleShot(duration, [this, infoView]() { infoView->deleteLater(); }); } } //! it is used just in order to provide translations for the presets void Manager::ghostForTranslatedPresets() { QString preset1 = i18n("Default"); QString preset2 = i18n("Plasma"); QString preset3 = i18n("Unity"); QString preset4 = i18n("Extended"); } } } diff --git a/app/layouts/synchronizer.cpp b/app/layouts/synchronizer.cpp index 9ade660b..a37cc0d2 100644 --- a/app/layouts/synchronizer.cpp +++ b/app/layouts/synchronizer.cpp @@ -1,868 +1,947 @@ /* * Copyright 2019 Michail Vourlakos * * 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 . */ #include "synchronizer.h" //! local #include "importer.h" #include "manager.h" #include "../lattecorona.h" #include "../layout/centrallayout.h" #include "../layout/genericlayout.h" #include "../layout/sharedlayout.h" #include "../settings/universalsettings.h" #include "../view/view.h" // Qt #include #include // KDE #include #include namespace Latte { namespace Layouts { Synchronizer::Synchronizer(QObject *parent) : QObject(parent), m_activitiesController(new KActivities::Controller) { m_manager = qobject_cast(parent); //! Dynamic Switching m_dynamicSwitchTimer.setSingleShot(true); updateDynamicSwitchInterval(); connect(m_manager->corona()->universalSettings(), &UniversalSettings::showInfoWindowChanged, this, &Synchronizer::updateDynamicSwitchInterval); connect(&m_dynamicSwitchTimer, &QTimer::timeout, this, &Synchronizer::confirmDynamicSwitch); //! KActivities tracking connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged, this, &Synchronizer::currentActivityChanged); connect(m_manager->corona()->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged, this, [&]() { if (m_manager->memoryUsage() == Types::MultipleLayouts) { syncMultipleLayoutsToActivities(); } }); } Synchronizer::~Synchronizer() { m_activitiesController->deleteLater(); } bool Synchronizer::latteViewExists(Latte::View *view) const { for (const auto layout : m_centralLayouts) { for (const auto &v : layout->latteViews()) { if (v == view) { return true; } } } return false; } bool Synchronizer::layoutExists(QString layoutName) const { return m_layouts.contains(layoutName); } bool Synchronizer::layoutIsAssigned(QString layoutName) { QHashIterator i(m_assignedLayouts); while (i.hasNext()) { i.next(); if (i.value() == layoutName) { return true; } } return false; } +bool Synchronizer::mapHasRecord(const QString &record, SharesMap &map) +{ + for (SharesMap::iterator i=map.begin(); i!=map.end(); ++i) { + if (i.value().contains(record)) { + return true; + } + } + + return false; +} + bool Synchronizer::registerAtSharedLayout(CentralLayout *central, QString id) { if (m_manager->memoryUsage() == Types::SingleLayout || centralLayout(id)) { //! if memory is functioning to SINGLE mode OR shared layout has already //! been loaded as CentralLayout return false; } for (int i = 0; i < m_sharedLayouts.size(); ++i) { SharedLayout *layout = m_sharedLayouts.at(i); if (layout->name() == id) { layout->addCentralLayout(central); return true; } } //! If SharedLayout was not found, we must create it SharedLayout *top = new SharedLayout(central, this, Importer::layoutFilePath(id)); m_sharedLayouts.append(top); top->importToCorona(); connect(top, &SharedLayout::layoutDestroyed, this, &Synchronizer::unloadSharedLayout); return true; } int Synchronizer::centralLayoutPos(QString id) const { for (int i = 0; i < m_centralLayouts.size(); ++i) { CentralLayout *layout = m_centralLayouts.at(i); if (layout->name() == id) { return i; } } return -1; } QString Synchronizer::currentLayoutName() const { if (m_manager->memoryUsage() == Types::SingleLayout) { return m_manager->corona()->universalSettings()->currentLayoutName(); } else if (m_manager->memoryUsage() == Types::MultipleLayouts) { return currentLayoutNameInMultiEnvironment(); } return QString(); } QString Synchronizer::currentLayoutNameInMultiEnvironment() const { return m_currentLayoutNameInMultiEnvironment; } QString Synchronizer::layoutPath(QString layoutName) { QString path = QDir::homePath() + "/.config/latte/" + layoutName + ".layout.latte"; if (!QFile(path).exists()) { path = ""; } return path; } QStringList Synchronizer::activities() { return m_manager->corona()->activitiesConsumer()->activities(); } QStringList Synchronizer::runningActivities() { return m_manager->corona()->activitiesConsumer()->runningActivities(); } QStringList Synchronizer::orphanedActivities() { QStringList orphans; for (const auto &activity : activities()) { if (m_assignedLayouts[activity].isEmpty()) { orphans.append(activity); } } return orphans; } QStringList Synchronizer::centralLayoutsNames() { QStringList names; if (m_manager->memoryUsage() == Types::SingleLayout) { names << currentLayoutName(); } else { for (int i = 0; i < m_centralLayouts.size(); ++i) { CentralLayout *layout = m_centralLayouts.at(i); names << layout->name(); } } return names; } QStringList Synchronizer::layouts() const { return m_layouts; } QStringList Synchronizer::menuLayouts() const { QStringList fixedMenuLayouts = m_menuLayouts; //! in case the current layout isnt checked to be shown in the menus //! we must add it on top if (!fixedMenuLayouts.contains(currentLayoutName()) && m_manager->memoryUsage() == Types::SingleLayout) { fixedMenuLayouts.prepend(currentLayoutName()); } else if (m_manager->memoryUsage() == Types::MultipleLayouts) { for (const auto layout : m_centralLayouts) { if (!fixedMenuLayouts.contains(layout->name())) { fixedMenuLayouts.prepend(layout->name()); } } } return fixedMenuLayouts; } void Synchronizer::setMenuLayouts(QStringList layouts) { if (m_menuLayouts == layouts) { return; } m_menuLayouts = layouts; emit menuLayoutsChanged(); } QString Synchronizer::shouldSwitchToLayout(QString activityId) { if (m_assignedLayouts.contains(activityId) && m_assignedLayouts[activityId] != currentLayoutName()) { return m_assignedLayouts[activityId]; } else if (!m_assignedLayouts.contains(activityId) && !m_manager->corona()->universalSettings()->lastNonAssignedLayoutName().isEmpty() && m_manager->corona()->universalSettings()->lastNonAssignedLayoutName() != currentLayoutName()) { return m_manager->corona()->universalSettings()->lastNonAssignedLayoutName(); } return QString(); } QStringList Synchronizer::sharedLayoutsNames() { QStringList names; for (int i = 0; i < m_sharedLayouts.size(); ++i) { SharedLayout *layout = m_sharedLayouts.at(i); names << layout->name(); } return names; } QStringList Synchronizer::storedSharedLayouts() const { return m_sharedLayoutIds; } QStringList Synchronizer::validActivities(QStringList currentList) { QStringList validIds; for (const auto &activity : currentList) { if (activities().contains(activity)) { validIds.append(activity); } } return validIds; } CentralLayout *Synchronizer::centralLayout(QString id) const { for (int i = 0; i < m_centralLayouts.size(); ++i) { CentralLayout *layout = m_centralLayouts.at(i); if (layout->name() == id) { return layout; } } return nullptr; } CentralLayout *Synchronizer::currentLayout() const { if (m_manager->memoryUsage() == Types::SingleLayout) { return m_centralLayouts.at(0); } else { for (auto layout : m_centralLayouts) { if (layout->activities().contains(m_manager->corona()->activitiesConsumer()->currentActivity())) { return layout; } } for (auto layout : m_centralLayouts) { if (layout->activities().isEmpty()) { return layout; } } } return nullptr; } Layout::GenericLayout *Synchronizer::layout(QString id) const { Layout::GenericLayout *l = centralLayout(id); if (!l) { l = sharedLayout(id); } return l; } SharedLayout *Synchronizer::sharedLayout(QString id) const { for (int i = 0; i < m_sharedLayouts.size(); ++i) { SharedLayout *layout = m_sharedLayouts.at(i); if (layout->name() == id) { return layout; } } return nullptr; } void Synchronizer::addLayout(CentralLayout *layout) { if (!m_centralLayouts.contains(layout)) { m_centralLayouts.append(layout); layout->initToCorona(m_manager->corona()); } } void Synchronizer::clearSharedLayoutsFromCentralLists() { QStringList unassign; for(const QString &name : m_sharedLayoutIds) { //! remove from ContextMenu m_menuLayouts.removeAll(name); //! remove from layouts assigned to activities QHashIterator i(m_assignedLayouts); while (i.hasNext()) { i.next(); if (i.value() == name) { unassign << i.key(); } } } for(const QString &activity : unassign) { m_assignedLayouts.remove(activity); } } void Synchronizer::confirmDynamicSwitch() { QString tempShouldSwitch = shouldSwitchToLayout(m_manager->corona()->activitiesConsumer()->currentActivity()); if (tempShouldSwitch.isEmpty()) { return; } if (m_shouldSwitchToLayout == tempShouldSwitch && m_shouldSwitchToLayout != currentLayoutName()) { qDebug() << "dynamic switch to layout :: " << m_shouldSwitchToLayout; emit currentLayoutIsSwitching(currentLayoutName()); if (m_manager->corona()->universalSettings()->showInfoWindow()) { m_manager->showInfoWindow(i18n("Switching to layout %0 ...").arg(m_shouldSwitchToLayout), 4000); } QTimer::singleShot(500, [this, tempShouldSwitch]() { switchToLayout(tempShouldSwitch); }); } else { m_shouldSwitchToLayout = tempShouldSwitch; m_dynamicSwitchTimer.start(); } } void Synchronizer::currentActivityChanged(const QString &id) { if (m_manager->memoryUsage() == Types::SingleLayout) { qDebug() << "activity changed :: " << id; m_shouldSwitchToLayout = shouldSwitchToLayout(id); m_dynamicSwitchTimer.start(); } else if (m_manager->memoryUsage() == Types::MultipleLayouts) { updateCurrentLayoutNameInMultiEnvironment(); } } void Synchronizer::hideAllViews() { for (const auto layout : m_centralLayouts) { emit currentLayoutIsSwitching(layout->name()); } } void Synchronizer::pauseLayout(QString layoutName) { if (m_manager->memoryUsage() == Types::MultipleLayouts) { CentralLayout *layout = centralLayout(layoutName); if (layout && !layout->activities().isEmpty()) { int i = 0; for (const auto &activityId : layout->activities()) { //! Stopping the activities must be done asynchronous because otherwise //! the activity manager cant close multiple activities QTimer::singleShot(i * 1000, [this, activityId]() { m_activitiesController->stopActivity(activityId); }); i = i + 1; } } } } void Synchronizer::syncActiveLayoutsToOriginalFiles() { if (m_manager->memoryUsage() == Types::MultipleLayouts) { for (const auto layout : m_centralLayouts) { layout->syncToLayoutFile(); } for (const auto layout : m_sharedLayouts) { layout->syncToLayoutFile(); } } } void Synchronizer::syncLatteViewsToScreens() { for (const auto layout : m_sharedLayouts) { layout->syncLatteViewsToScreens(); } for (const auto layout : m_centralLayouts) { layout->syncLatteViewsToScreens(); } } void Synchronizer::unloadCentralLayout(CentralLayout *layout) { int pos = m_centralLayouts.indexOf(layout); if (pos>=0) { CentralLayout *central = m_centralLayouts.takeAt(0); if (m_multipleModeInitialized) { central->syncToLayoutFile(true); } central->unloadContainments(); central->unloadLatteViews(); if (m_multipleModeInitialized) { m_manager->clearUnloadedContainmentsFromLinkedFile(central->unloadedContainmentsIds(), true); } delete central; } } void Synchronizer::unloadSharedLayout(SharedLayout *layout) { if (m_sharedLayouts.contains(layout)) { disconnect(layout, &SharedLayout::layoutDestroyed, this, &Synchronizer::unloadSharedLayout); int pos = m_sharedLayouts.indexOf(layout); SharedLayout *shared = m_sharedLayouts.takeAt(pos); shared->syncToLayoutFile(true); shared->unloadContainments(); shared->unloadLatteViews(); m_manager->clearUnloadedContainmentsFromLinkedFile(shared->unloadedContainmentsIds(), true); delete layout; } } void Synchronizer::loadLayouts() { m_layouts.clear(); m_menuLayouts.clear(); m_assignedLayouts.clear(); m_sharedLayoutIds.clear(); QDir layoutDir(QDir::homePath() + "/.config/latte"); QStringList filter; filter.append(QString("*.layout.latte")); QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks); for (const auto &layout : files) { if (layout.contains(Layout::AbstractLayout::MultipleLayoutsName)) { //! IMPORTANT: DONT ADD MultipleLayouts hidden file in layouts list continue; } CentralLayout centralLayout(this, layoutDir.absolutePath() + "/" + layout); QStringList validActivityIds = validActivities(centralLayout.activities()); centralLayout.setActivities(validActivityIds); for (const auto &activity : validActivityIds) { m_assignedLayouts[activity] = centralLayout.name(); } m_layouts.append(centralLayout.name()); if (centralLayout.showInMenu()) { m_menuLayouts.append(centralLayout.name()); } QString sharedName = centralLayout.sharedLayoutName(); if (!sharedName.isEmpty() && !m_sharedLayoutIds.contains(sharedName)) { m_sharedLayoutIds << sharedName; } } //! Shared Layouts should not be used for Activities->Layouts assignments or published lists clearSharedLayoutsFromCentralLists(); emit layoutsChanged(); emit menuLayoutsChanged(); } void Synchronizer::unloadLayouts() { //! Unload all CentralLayouts while (!m_centralLayouts.isEmpty()) { CentralLayout *layout = m_centralLayouts.at(0); unloadCentralLayout(layout); } m_multipleModeInitialized = false; } void Synchronizer::updateCurrentLayoutNameInMultiEnvironment() { for (const auto layout : m_centralLayouts) { if (layout->activities().contains(m_manager->corona()->activitiesConsumer()->currentActivity())) { m_currentLayoutNameInMultiEnvironment = layout->name(); emit currentLayoutNameChanged(); return; } } for (const auto layout : m_centralLayouts) { if (layout->activities().isEmpty()) { m_currentLayoutNameInMultiEnvironment = layout->name(); emit currentLayoutNameChanged(); return; } } } void Synchronizer::updateDynamicSwitchInterval() { if (m_manager->corona()->universalSettings()->showInfoWindow()) { m_dynamicSwitchTimer.setInterval(1800); } else { m_dynamicSwitchTimer.setInterval(2300); } } bool Synchronizer::switchToLayout(QString layoutName, int previousMemoryUsage) { if (m_centralLayouts.size() > 0 && currentLayoutName() == layoutName && previousMemoryUsage == -1) { return false; } //! First Check If that Layout is already present and in that case //! we can just switch to the proper Activity if (m_manager->memoryUsage() == Types::MultipleLayouts && previousMemoryUsage == -1) { CentralLayout *layout = centralLayout(layoutName); if (layout) { QStringList appliedActivities = layout->appliedActivities(); QString nextActivity = !layout->lastUsedActivity().isEmpty() ? layout->lastUsedActivity() : appliedActivities[0]; //! it means we are at a foreign activity if (!appliedActivities.contains(m_manager->corona()->activitiesConsumer()->currentActivity())) { m_activitiesController->setCurrentActivity(nextActivity); return true; } } } //! When going from memory usage to different memory usage we first //! send the layouts that will be changed. This signal creates the //! nice animation that hides these docks/panels if (previousMemoryUsage != -1) { for (const auto layout : m_centralLayouts) { emit currentLayoutIsSwitching(layout->name()); } for (const auto layout : m_sharedLayouts) { emit currentLayoutIsSwitching(layout->name()); } } QString lPath = layoutPath(layoutName); if (lPath.isEmpty() && layoutName == i18n("Alternative")) { lPath = m_manager->newLayout(i18n("Alternative"), i18n("Default")); } if (!lPath.isEmpty()) { if (m_manager->memoryUsage() == Types::SingleLayout) { // emit currentLayoutIsSwitching(currentLayoutName()); } else if (m_manager->memoryUsage() == Types::MultipleLayouts && layoutName != Layout::AbstractLayout::MultipleLayoutsName) { CentralLayout toLayout(this, lPath); QStringList toActivities = toLayout.activities(); CentralLayout *centralForOrphans{nullptr}; for (const auto fromLayout : m_centralLayouts) { if (fromLayout->activities().isEmpty()) { centralForOrphans = fromLayout; break; } } if (toActivities.isEmpty() && centralForOrphans && (toLayout.name() != centralForOrphans->name())) { emit currentLayoutIsSwitching(centralForOrphans->name()); } } //! this code must be called asynchronously because it is called //! also from qml (Tasks plasmoid). This change fixes a very important //! crash when switching sessions through the Tasks plasmoid Context menu //! Latte was unstable and was crashing very often during changing //! sessions. QTimer::singleShot(350, [this, layoutName, lPath, previousMemoryUsage]() { qDebug() << layoutName << " - " << lPath; QString fixedLPath = lPath; QString fixedLayoutName = layoutName; bool initializingMultipleLayouts{false}; if (m_manager->memoryUsage() == Types::MultipleLayouts && !m_multipleModeInitialized) { initializingMultipleLayouts = true; } if (m_manager->memoryUsage() == Types::SingleLayout || initializingMultipleLayouts || previousMemoryUsage == Types::MultipleLayouts) { unloadLayouts(); if (initializingMultipleLayouts) { fixedLayoutName = QString(Layout::AbstractLayout::MultipleLayoutsName); fixedLPath = layoutPath(fixedLayoutName); } if (fixedLayoutName != Layout::AbstractLayout::MultipleLayoutsName) { CentralLayout *newLayout = new CentralLayout(this, fixedLPath, fixedLayoutName); addLayout(newLayout); } m_manager->loadLatteLayout(fixedLPath); if (initializingMultipleLayouts) { m_multipleModeInitialized = true; } emit centralLayoutsChanged(); } if (m_manager->memoryUsage() == Types::MultipleLayouts) { if (!initializingMultipleLayouts && !centralLayout(layoutName)) { //! When we are in Multiple Layouts Environment and the user activates //! a Layout that is assigned to specific activities but this //! layout isnt loaded (this means neither of its activities are running) //! is such case we just activate these Activities CentralLayout layout(this, Importer::layoutFilePath(layoutName)); int i = 0; bool lastUsedActivityFound{false}; QString lastUsedActivity = layout.lastUsedActivity(); bool orphanedLayout = !layoutIsAssigned(layoutName); QStringList assignedActivities = orphanedLayout ? orphanedActivities() : layout.activities(); if (!orphanedLayout) { for (const auto &assignedActivity : assignedActivities) { //! Starting the activities must be done asynchronous because otherwise //! the activity manager cant close multiple activities QTimer::singleShot(i * 1000, [this, assignedActivity, lastUsedActivity]() { m_activitiesController->startActivity(assignedActivity); if (lastUsedActivity == assignedActivity) { m_activitiesController->setCurrentActivity(lastUsedActivity); } }); if (lastUsedActivity == assignedActivity) { lastUsedActivityFound = true; } i = i + 1; } } else { //! orphaned layout for (const auto &assignedActivity : assignedActivities) { if (lastUsedActivity == assignedActivity) { lastUsedActivityFound = true; } } if ((!lastUsedActivityFound && assignedActivities.count() == 0) || !assignedActivities.contains(m_manager->corona()->activitiesConsumer()->currentActivity())) { //! Starting the activities must be done asynchronous because otherwise //! the activity manager cant close multiple activities QTimer::singleShot(1000, [this, lastUsedActivity, lastUsedActivityFound]() { m_activitiesController->startActivity(lastUsedActivity); m_activitiesController->setCurrentActivity(lastUsedActivity); }); } } if (orphanedLayout) { syncMultipleLayoutsToActivities(layoutName); } else if (!orphanedLayout && !lastUsedActivityFound) { m_activitiesController->setCurrentActivity(layout.activities()[0]); } } else { syncMultipleLayoutsToActivities(layoutName); } } m_manager->corona()->universalSettings()->setCurrentLayoutName(layoutName); if (!layoutIsAssigned(layoutName)) { m_manager->corona()->universalSettings()->setLastNonAssignedLayoutName(layoutName); } }); } else { qDebug() << "Layout : " << layoutName << " was not found..."; } return true; } void Synchronizer::syncMultipleLayoutsToActivities(QString layoutForOrphans) { qDebug() << " ---- --------- ------ syncMultipleLayoutsToActivities ------- "; qDebug() << " ---- --------- ------ ------------------------------- ------- "; QStringList layoutsToUnload; QStringList layoutsToLoad; bool allRunningActivitiesWillBeReserved{true}; if (layoutForOrphans.isEmpty() || m_assignedLayouts.values().contains(layoutForOrphans)) { layoutForOrphans = m_manager->corona()->universalSettings()->lastNonAssignedLayoutName(); } for (const auto &activity : runningActivities()) { if (!m_assignedLayouts[activity].isEmpty()) { if (!layoutsToLoad.contains(m_assignedLayouts[activity])) { layoutsToLoad.append(m_assignedLayouts[activity]); } } else { allRunningActivitiesWillBeReserved = false; } } for (const auto layout : m_centralLayouts) { QString tempLayoutName; if (!layoutsToLoad.contains(layout->name()) && layout->name() != layoutForOrphans) { tempLayoutName = layout->name(); } else if (layout->activities().isEmpty() && allRunningActivitiesWillBeReserved) { //! in such case the layout for the orphaned must be unloaded tempLayoutName = layout->name(); } if (!tempLayoutName.isEmpty() && !layoutsToUnload.contains(tempLayoutName)) { layoutsToUnload << tempLayoutName; } } //! Unload no needed Layouts for (const auto &layoutName : layoutsToUnload) { CentralLayout *layout = centralLayout(layoutName); int posLayout = centralLayoutPos(layoutName); if (posLayout >= 0) { qDebug() << "REMOVING LAYOUT ::::: " << layoutName; m_centralLayouts.removeAt(posLayout); layout->syncToLayoutFile(true); layout->unloadContainments(); layout->unloadLatteViews(); m_manager->clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds()); delete layout; } } //! Add Layout for orphan activities if (!allRunningActivitiesWillBeReserved) { if (!centralLayout(layoutForOrphans)) { CentralLayout *newLayout = new CentralLayout(this, layoutPath(layoutForOrphans), layoutForOrphans); if (newLayout) { qDebug() << "ACTIVATING ORPHANED LAYOUT ::::: " << layoutForOrphans; addLayout(newLayout); newLayout->importToCorona(); } } } //! Add needed Layouts based on Activities for (const auto &layoutName : layoutsToLoad) { if (!centralLayout(layoutName)) { CentralLayout *newLayout = new CentralLayout(this, QString(layoutPath(layoutName)), layoutName); if (newLayout) { qDebug() << "ACTIVATING LAYOUT ::::: " << layoutName; addLayout(newLayout); newLayout->importToCorona(); if (m_manager->corona()->universalSettings()->showInfoWindow()) { m_manager->showInfoWindow(i18n("Activating layout: %0 ...").arg(newLayout->name()), 5000, newLayout->appliedActivities()); } } } } updateCurrentLayoutNameInMultiEnvironment(); emit centralLayoutsChanged(); } +void Synchronizer::syncActiveShares(SharesMap &sharesMap) +{ + if (m_manager->memoryUsage() != Types::MultipleLayouts) { + return; + } + + qDebug() << " CURRENT SHARES MAP :: " << sharesMap; + + QHash unassign; + + //! CENTRAL (active) layouts that will become SHARED must be unloaded first + for (SharesMap::iterator i=sharesMap.begin(); i!=sharesMap.end(); ++i) { + CentralLayout *central = centralLayout(i.key()); + if (central) { + unloadCentralLayout(central); + } + } + + //! CENTRAL (active) layouts that update their (active) SHARED layouts + //! AND load SHARED layouts that are NOT ACTIVE + for (SharesMap::iterator i=sharesMap.begin(); i!=sharesMap.end(); ++i) { + SharedLayout *shared = sharedLayout(i.key()); + qDebug() << " SHARED :: " << i.key(); + for (const auto ¢ralName : i.value()) { + CentralLayout *central = centralLayout(centralName); + qDebug() << " CENTRAL NAME :: " << centralName; + if (central) { + //! Assign this Central Layout at a different Shared Layout + SharedLayout *oldShared = central->sharedLayout(); + + if (!shared) { + //Shared not loaded and it must be loaded before proceed + registerAtSharedLayout(central, i.key()); + shared = sharedLayout(i.key()); + } + + if (shared != oldShared) { + shared->addCentralLayout(central); + central->setSharedLayout(shared); + if (oldShared) { + //! CENTRAL layout that changed from one ACTIVESHARED layout to another + unassign[central] = shared; + } + } + } + } + } + + //! CENTRAL Layouts that wont have any SHARED Layout any more + for (const auto ¢ralName : centralLayoutsNames()) { + if (!mapHasRecord(centralName, sharesMap)) { + CentralLayout *central = centralLayout(centralName); + if (central && central->sharedLayout()) { + central->sharedLayout()->removeCentralLayout(central); + central->setSharedLayoutName(QString()); + central->setSharedLayout(nullptr); + } + } + } + + //! Unassing from Shared Layouts Central ones that are not assigned any more + //! IMPORTANT: This must be done after all the ASSIGNMENTS in order to avoid + //! to unload a SharedLayout that it should not + for (QHash::iterator i=unassign.begin(); i!=unassign.end(); ++i) { + i.value()->removeCentralLayout(i.key()); + } +} + } } // end of namespace diff --git a/app/layouts/synchronizer.h b/app/layouts/synchronizer.h index c2055574..1e89d500 100644 --- a/app/layouts/synchronizer.h +++ b/app/layouts/synchronizer.h @@ -1,146 +1,152 @@ /* * Copyright 2019 Michail Vourlakos * * 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 . */ #ifndef LAYOUTSSYNCHRONIZER_H #define LAYOUTSSYNCHRONIZER_H // Qt #include #include #include namespace Latte { class CentralLayout; class SharedLayout; class View; namespace Layout{ class GenericLayout; } namespace Layouts { class Manager; } } namespace KActivities { class Controller; } namespace Latte { namespace Layouts { +//! This is a Shares map in the following structure: +//! SHARED LAYOUT NAME -> CENTRAL LAYOUT NAMES acting as SHARES +typedef QHash SharesMap; + class Synchronizer : public QObject { Q_OBJECT public: Synchronizer(QObject *parent); ~Synchronizer() override; void loadLayouts(); void unloadLayouts(); void hideAllViews(); void pauseLayout(QString layoutName); void syncActiveLayoutsToOriginalFiles(); void syncLatteViewsToScreens(); void syncMultipleLayoutsToActivities(QString layoutForOrphans = QString()); + void syncActiveShares(SharesMap &sharesMap); bool latteViewExists(Latte::View *view) const; bool layoutExists(QString layoutName) const; bool registerAtSharedLayout(CentralLayout *central, QString id); //! switch to specified layout, default previousMemoryUsage means that it didn't change bool switchToLayout(QString layoutName, int previousMemoryUsage = -1); int centralLayoutPos(QString id) const; QString currentLayoutName() const; QString currentLayoutNameInMultiEnvironment() const; QString shouldSwitchToLayout(QString activityId); QStringList centralLayoutsNames(); QStringList layouts() const; QStringList menuLayouts() const; void setMenuLayouts(QStringList layouts); QStringList sharedLayoutsNames(); QStringList storedSharedLayouts() const; QStringList activities(); QStringList runningActivities(); QStringList orphanedActivities(); //! These are activities that haven't been assigned to specific layout CentralLayout *currentLayout() const; CentralLayout *centralLayout(QString id) const; SharedLayout *sharedLayout(QString id) const; Layout::GenericLayout *layout(QString id) const; signals: void centralLayoutsChanged(); void currentLayoutNameChanged(); void layoutsChanged(); void menuLayoutsChanged(); void runningActicitiesChanged(); void currentLayoutIsSwitching(QString layoutName); private slots: void confirmDynamicSwitch(); void updateDynamicSwitchInterval(); void updateCurrentLayoutNameInMultiEnvironment(); void currentActivityChanged(const QString &id); private: void clearSharedLayoutsFromCentralLists(); void addLayout(CentralLayout *layout); void unloadCentralLayout(CentralLayout *layout); void unloadSharedLayout(SharedLayout *layout); bool layoutIsAssigned(QString layoutName); + bool mapHasRecord(const QString &record, SharesMap &map); QString layoutPath(QString layoutName); QStringList validActivities(QStringList currentList); private: bool m_multipleModeInitialized{false}; QString m_currentLayoutNameInMultiEnvironment; QString m_shouldSwitchToLayout; QStringList m_layouts; QStringList m_menuLayouts; QStringList m_sharedLayoutIds; QHash m_assignedLayouts; QTimer m_dynamicSwitchTimer; QList m_centralLayouts; QList m_sharedLayouts; Layouts::Manager *m_manager; KActivities::Controller *m_activitiesController; }; } } #endif diff --git a/app/settings/settingsdialog.cpp b/app/settings/settingsdialog.cpp index c4ff7633..8689a4da 100644 --- a/app/settings/settingsdialog.cpp +++ b/app/settings/settingsdialog.cpp @@ -1,2015 +1,1954 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * 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 . * */ #include "settingsdialog.h" // local #include "universalsettings.h" #include "ui_settingsdialog.h" #include "../lattecorona.h" #include "../layout/genericlayout.h" #include "../layout/centrallayout.h" #include "../layout/sharedlayout.h" #include "../layouts/importer.h" #include "../layouts/manager.h" #include "../layouts/synchronizer.h" #include "../liblatte2/types.h" #include "../plasma/extended/theme.h" #include "delegates/activitiesdelegate.h" #include "delegates/checkboxdelegate.h" #include "delegates/colorcmbboxdelegate.h" #include "delegates/layoutnamedelegate.h" #include "delegates/shareddelegate.h" // Qt #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include namespace Latte { const int IDCOLUMN = 0; const int HIDDENTEXTCOLUMN = 1; const int COLORCOLUMN = 2; const int NAMECOLUMN = 3; const int MENUCOLUMN = 4; const int BORDERSCOLUMN = 5; const int ACTIVITYCOLUMN = 6; const int SHAREDCOLUMN = 7; const int SCREENTRACKERDEFAULTVALUE = 2500; const int OUTLINEDEFAULTWIDTH = 1; const QChar CheckMark{0x2714}; SettingsDialog::SettingsDialog(QWidget *parent, Latte::Corona *corona) : QDialog(parent), ui(new Ui::SettingsDialog), m_corona(corona) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose, true); setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); resize(m_corona->universalSettings()->layoutsWindowSize()); connect(ui->buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked , this, &SettingsDialog::apply); connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked , this, &SettingsDialog::restoreDefaults); m_model = new QStandardItemModel(m_corona->layoutsManager()->layouts().count(), 6, this); ui->layoutsView->setModel(m_model); ui->layoutsView->horizontalHeader()->setStretchLastSection(true); ui->layoutsView->verticalHeader()->setVisible(false); connect(m_corona->layoutsManager(), &Layouts::Manager::currentLayoutNameChanged, this, &SettingsDialog::layoutsChanged); connect(m_corona->layoutsManager(), &Layouts::Manager::centralLayoutsChanged, this, &SettingsDialog::layoutsChanged); QString iconsPath(m_corona->kPackage().path() + "../../plasmoids/org.kde.latte.containment/contents/icons/"); //!find the available colors QDir layoutDir(iconsPath); QStringList filter; filter.append(QString("*print.jpg")); QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks); QStringList colors; for (auto &file : files) { int colorEnd = file.lastIndexOf("print.jpg"); QString color = file.remove(colorEnd, 9); colors.append(color); } ui->layoutsView->setItemDelegateForColumn(NAMECOLUMN, new LayoutNameDelegate(this)); ui->layoutsView->setItemDelegateForColumn(COLORCOLUMN, new ColorCmbBoxDelegate(this, iconsPath, colors)); ui->layoutsView->setItemDelegateForColumn(MENUCOLUMN, new CheckBoxDelegate(this)); ui->layoutsView->setItemDelegateForColumn(BORDERSCOLUMN, new CheckBoxDelegate(this)); ui->layoutsView->setItemDelegateForColumn(ACTIVITYCOLUMN, new ActivitiesDelegate(this)); ui->layoutsView->setItemDelegateForColumn(SHAREDCOLUMN, new SharedDelegate(this)); m_inMemoryButtons = new QButtonGroup(this); m_inMemoryButtons->addButton(ui->singleToolBtn, Latte::Types::SingleLayout); m_inMemoryButtons->addButton(ui->multipleToolBtn, Latte::Types::MultipleLayouts); m_inMemoryButtons->setExclusive(true); if (KWindowSystem::isPlatformWayland()) { m_inMemoryButtons->button(Latte::Types::MultipleLayouts)->setEnabled(false); } m_mouseSensitivityButtons = new QButtonGroup(this); m_mouseSensitivityButtons->addButton(ui->lowSensitivityBtn, Latte::Types::LowSensitivity); m_mouseSensitivityButtons->addButton(ui->mediumSensitivityBtn, Latte::Types::MediumSensitivity); m_mouseSensitivityButtons->addButton(ui->highSensitivityBtn, Latte::Types::HighSensitivity); m_mouseSensitivityButtons->setExclusive(true); ui->screenTrackerSpinBox->setValue(m_corona->universalSettings()->screenTrackerInterval()); ui->outlineSpinBox->setValue(m_corona->themeExtended()->outlineWidth()); //! About Menu QMenuBar *menuBar = new QMenuBar(this); // QMenuBar *rightAlignedMenuBar = new QMenuBar(menuBar); layout()->setMenuBar(menuBar); //menuBar->setCornerWidget(rightAlignedMenuBar); QMenu *fileMenu = new QMenu(i18n("File"), menuBar); menuBar->addMenu(fileMenu); QMenu *helpMenu = new QMenu(i18n("Help"), menuBar); //rightAlignedMenuBar->addMenu(helpMenu); menuBar->addMenu(helpMenu); QAction *quitAction = fileMenu->addAction(i18n("Quit Latte")); quitAction->setIcon(QIcon::fromTheme("application-exit")); quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q)); QAction *aboutAction = helpMenu->addAction(i18n("About Latte")); aboutAction->setIcon(QIcon::fromTheme("latte-dock")); //! RTL support for labels in preferences if (qApp->layoutDirection() == Qt::RightToLeft) { ui->behaviorLbl->setAlignment(Qt::AlignRight | Qt::AlignTop); ui->mouseSensetivityLbl->setAlignment(Qt::AlignRight | Qt::AlignTop); ui->delayLbl->setAlignment(Qt::AlignRight | Qt::AlignTop); } loadSettings(); //! SIGNALS connect(m_model, &QStandardItemModel::itemChanged, this, &SettingsDialog::itemChanged); connect(ui->layoutsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [&]() { updatePerLayoutButtonsState(); updateApplyButtonsState(); }); connect(m_inMemoryButtons, static_cast(&QButtonGroup::buttonToggled), [ = ](int id, bool checked) { updateApplyButtonsState(); updateSharedLayoutsStates(); updateSharedLayoutsUiElements(); }); connect(m_mouseSensitivityButtons, static_cast(&QButtonGroup::buttonToggled), [ = ](int id, bool checked) { updateApplyButtonsState(); }); connect(ui->screenTrackerSpinBox, QOverload::of(&QSpinBox::valueChanged), [ = ](int i) { updateApplyButtonsState(); }); connect(ui->outlineSpinBox, QOverload::of(&QSpinBox::valueChanged), [ = ](int i) { updateApplyButtonsState(); }); connect(ui->autostartChkBox, &QCheckBox::stateChanged, this, &SettingsDialog::updateApplyButtonsState); connect(ui->metaPressChkBox, &QCheckBox::stateChanged, this, &SettingsDialog::updateApplyButtonsState); connect(ui->metaPressHoldChkBox, &QCheckBox::stateChanged, this, &SettingsDialog::updateApplyButtonsState); connect(ui->infoWindowChkBox, &QCheckBox::stateChanged, this, &SettingsDialog::updateApplyButtonsState); connect(ui->tabWidget, &QTabWidget::currentChanged, this, &SettingsDialog::updateApplyButtonsState); connect(ui->noBordersForMaximizedChkBox, &QCheckBox::stateChanged, this, [&]() { bool noBordersForMaximized = ui->noBordersForMaximizedChkBox->isChecked(); if (noBordersForMaximized) { ui->layoutsView->setColumnHidden(BORDERSCOLUMN, false); } else { ui->layoutsView->setColumnHidden(BORDERSCOLUMN, true); } updateApplyButtonsState(); }); connect(aboutAction, &QAction::triggered, m_corona, &Latte::Corona::aboutApplication); connect(quitAction, &QAction::triggered, m_corona, &Latte::Corona::closeApplication); //! update all layouts view when runningActivities changed. This way we update immediately //! the running Activities in Activities checkboxes which are shown as bold connect(m_corona->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged, this, [&]() { ui->layoutsView->update(); }); blockDeleteOnActivityStopped(); } SettingsDialog::~SettingsDialog() { qDebug() << Q_FUNC_INFO; qDeleteAll(m_layouts); if (m_model) { delete m_model; } if (m_corona && m_corona->universalSettings()) { m_corona->universalSettings()->setLayoutsWindowSize(size()); QStringList columnWidths; columnWidths << QString::number(ui->layoutsView->columnWidth(COLORCOLUMN)); columnWidths << QString::number(ui->layoutsView->columnWidth(NAMECOLUMN)); columnWidths << QString::number(ui->layoutsView->columnWidth(MENUCOLUMN)); columnWidths << QString::number(ui->layoutsView->columnWidth(BORDERSCOLUMN)); Latte::Types::LayoutsMemoryUsage inMemoryOption = static_cast(m_inMemoryButtons->checkedId()); if (inMemoryOption == Latte::Types::MultipleLayouts) { columnWidths << QString::number(ui->layoutsView->columnWidth(ACTIVITYCOLUMN)); } else { //! In Single Mode, keed recorded value for ACTIVITYCOLUMN QStringList currentWidths = m_corona->universalSettings()->layoutsColumnWidths(); if (currentWidths.count()>=5) { columnWidths << currentWidths[4]; } } m_corona->universalSettings()->setLayoutsColumnWidths(columnWidths); } m_inMemoryButtons->deleteLater(); m_mouseSensitivityButtons->deleteLater(); for (const auto &tempDir : m_tempDirectories) { QDir tDir(tempDir); if (tDir.exists() && tempDir.startsWith("/tmp/")) { tDir.removeRecursively(); } } } void SettingsDialog::blockDeleteOnActivityStopped() { connect(m_corona->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged, this, [&]() { m_blockDeleteOnReject = true; m_activityClosedTimer.start(); }); m_activityClosedTimer.setSingleShot(true); m_activityClosedTimer.setInterval(500); connect(&m_activityClosedTimer, &QTimer::timeout, this, [&]() { m_blockDeleteOnReject = false; }); } QStringList SettingsDialog::activities() { return m_corona->layoutsManager()->activities(); } QStringList SettingsDialog::availableActivities() { return m_availableActivities; } QStringList SettingsDialog::availableSharesFor(int row) { QStringList availables; QStringList regs; for (int i = 0; i < m_model->rowCount(); ++i) { QString id = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); QStringList shares = m_model->data(m_model->index(i, SHAREDCOLUMN), Qt::UserRole).toStringList(); if (i != row) { if (shares.isEmpty()) { availables << id; } else { regs << shares; } } } for (const auto r : regs) { availables.removeAll(r); } return availables; } void SettingsDialog::setCurrentPage(Types::LatteConfigPage page) { if (page == Types::LayoutPage) { ui->tabWidget->setCurrentIndex(0); } else if (page == Types::PreferencesPage) { ui->tabWidget->setCurrentIndex(1); } } void SettingsDialog::on_newButton_clicked() { qDebug() << Q_FUNC_INFO; //! find Default preset path for (const auto &preset : m_corona->layoutsManager()->presetsPaths()) { QString presetName = CentralLayout::layoutName(preset); if (presetName == "Default") { QByteArray presetNameChars = presetName.toUtf8(); const char *prset_str = presetNameChars.data(); presetName = uniqueLayoutName(i18n(prset_str)); addLayoutForFile(preset, presetName, true, false); break; } } } void SettingsDialog::on_copyButton_clicked() { qDebug() << Q_FUNC_INFO; int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } //! Update original layout before copying if this layout is active if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { QString lName = (m_model->data(m_model->index(row, NAMECOLUMN), Qt::DisplayRole)).toString(); Layout::GenericLayout *generic = m_corona->layoutsManager()->layout(lName); if (generic) { generic->syncToLayoutFile(); } } QString tempDir = uniqueTempDirectory(); QString id = m_model->data(m_model->index(row, IDCOLUMN), Qt::DisplayRole).toString(); QString color = m_model->data(m_model->index(row, COLORCOLUMN), Qt::BackgroundRole).toString(); QString textColor = m_model->data(m_model->index(row, COLORCOLUMN), Qt::UserRole).toString(); QString layoutName = uniqueLayoutName(m_model->data(m_model->index(row, NAMECOLUMN), Qt::DisplayRole).toString()); bool menu = m_model->data(m_model->index(row, MENUCOLUMN), Qt::DisplayRole).toString() == CheckMark; bool disabledBorders = m_model->data(m_model->index(row, BORDERSCOLUMN), Qt::DisplayRole).toString() == CheckMark; QString copiedId = tempDir + "/" + layoutName + ".layout.latte"; QFile(id).copy(copiedId); QFileInfo newFileInfo(copiedId); if (newFileInfo.exists() && !newFileInfo.isWritable()) { QFile(copiedId).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } CentralLayout *settings = new CentralLayout(this, copiedId); m_layouts[copiedId] = settings; insertLayoutInfoAtRow(row + 1, copiedId, color, textColor, layoutName, menu, disabledBorders, QStringList(), false); ui->layoutsView->selectRow(row + 1); } void SettingsDialog::on_downloadButton_clicked() { qDebug() << Q_FUNC_INFO; KNS3::DownloadDialog dialog(QStringLiteral("latte-layouts.knsrc"), this); dialog.resize(m_corona->universalSettings()->downloadWindowSize()); dialog.exec(); bool layoutAdded{false}; if (!dialog.changedEntries().isEmpty() || !dialog.installedEntries().isEmpty()) { for (const auto &entry : dialog.installedEntries()) { for (const auto &entryFile : entry.installedFiles()) { Layouts::Importer::LatteFileVersion version = Layouts::Importer::fileVersion(entryFile); if (version == Layouts::Importer::LayoutVersion2) { layoutAdded = true; addLayoutForFile(entryFile); break; } } } } m_corona->universalSettings()->setDownloadWindowSize(dialog.size()); if (layoutAdded) { apply(); } } void SettingsDialog::on_removeButton_clicked() { qDebug() << Q_FUNC_INFO; int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } QString layoutName = m_model->data(m_model->index(row, NAMECOLUMN), Qt::DisplayRole).toString(); if (m_corona->layoutsManager()->centralLayout(layoutName)) { return; } m_model->removeRow(row); updateApplyButtonsState(); row = qMax(row - 1, 0); ui->layoutsView->selectRow(row); } void SettingsDialog::on_lockedButton_clicked() { qDebug() << Q_FUNC_INFO; int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } bool lockedModel = m_model->data(m_model->index(row, NAMECOLUMN), Qt::UserRole).toBool(); m_model->setData(m_model->index(row, NAMECOLUMN), QVariant(!lockedModel), Qt::UserRole); updatePerLayoutButtonsState(); updateApplyButtonsState(); } void SettingsDialog::on_sharedButton_clicked() { qDebug() << Q_FUNC_INFO; int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } if (isShared(row)) { m_model->setData(m_model->index(row, SHAREDCOLUMN), QStringList(), Qt::UserRole); } else { bool assigned{false}; QStringList assignedList; QStringList availableShares = availableSharesFor(row); for (const auto &id : availableShares) { QString name = nameForId(id); if (m_corona->layoutsManager()->layout(name)) { assignedList << id; m_model->setData(m_model->index(row, SHAREDCOLUMN), assignedList, Qt::UserRole); assigned = true; break; } } if (!assigned && availableShares.count()>0) { assignedList << availableShares[0]; m_model->setData(m_model->index(row, SHAREDCOLUMN), assignedList, Qt::UserRole); assigned = true; } } updatePerLayoutButtonsState(); updateApplyButtonsState(); } void SettingsDialog::on_importButton_clicked() { qDebug() << Q_FUNC_INFO; QFileDialog *fileDialog = new QFileDialog(this, i18nc("import layout/configuration", "Import Layout/Configuration") , QDir::homePath() , QStringLiteral("layout.latte")); fileDialog->setFileMode(QFileDialog::AnyFile); fileDialog->setAcceptMode(QFileDialog::AcceptOpen); fileDialog->setDefaultSuffix("layout.latte"); QStringList filters; filters << QString(i18nc("import latte layout", "Latte Dock Layout file v0.2") + "(*.layout.latte)") << QString(i18nc("import latte layouts/configuration", "Latte Dock Full Configuration file (v0.1, v0.2)") + "(*.latterc)"); fileDialog->setNameFilters(filters); connect(fileDialog, &QFileDialog::finished , fileDialog, &QFileDialog::deleteLater); connect(fileDialog, &QFileDialog::fileSelected , this, [&](const QString & file) { Layouts::Importer::LatteFileVersion version = Layouts::Importer::fileVersion(file); qDebug() << "VERSION :::: " << version; if (version == Layouts::Importer::LayoutVersion2) { addLayoutForFile(file); } else if (version == Layouts::Importer::ConfigVersion1) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Import: Configuration file version v0.1")); msg->setText( i18n("You are going to import an old version v0.1 configuration file.
Be careful, importing the entire configuration will erase all your current configuration!!!

Alternative, you can import safely from this file
only the contained layouts...
")); msg->setStandardButtons(QMessageBox::Cancel); QPushButton *fullBtn = new QPushButton(msg); QPushButton *layoutsBtn = new QPushButton(msg); fullBtn->setText(i18nc("import full configuration", "Full Configuration")); fullBtn->setIcon(QIcon::fromTheme("settings")); layoutsBtn->setText(i18nc("import only the layouts", "Only Layouts")); layoutsBtn->setIcon(QIcon::fromTheme("user-identity")); msg->addButton(fullBtn, QMessageBox::AcceptRole); msg->addButton(layoutsBtn, QMessageBox::AcceptRole); msg->setDefaultButton(layoutsBtn); connect(msg, &QMessageBox::finished, msg, &QMessageBox::deleteLater); msg->open(); connect(layoutsBtn, &QPushButton::clicked , this, [ &, file](bool check) { importLayoutsFromV1ConfigFile(file); }); connect(fullBtn, &QPushButton::clicked , this, [ &, file](bool check) { //!NOTE: Restart latte for import the new configuration QProcess::startDetached(qGuiApp->applicationFilePath() + " --import-full \"" + file + "\""); qGuiApp->exit(); }); } else if (version == Layouts::Importer::ConfigVersion2) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Import: Configuration file version v0.2")); msg->setText( i18n("You are going to import a v0.2 configuration file.
Be careful, importing will erase all your current configuration!!!

Would you like to proceed?")); msg->setStandardButtons(QMessageBox::Yes | QMessageBox::No); msg->setDefaultButton(QMessageBox::No); connect(msg, &QMessageBox::finished, this, [ &, msg, file](int result) { if (result == QMessageBox::Yes) { //!NOTE: Restart latte for import the new configuration msg->deleteLater(); QProcess::startDetached(qGuiApp->applicationFilePath() + " --import-full \"" + file + "\""); qGuiApp->exit(); } }); msg->open(); } }); fileDialog->open(); } bool SettingsDialog::importLayoutsFromV1ConfigFile(QString file) { KTar archive(file, QStringLiteral("application/x-tar")); archive.open(QIODevice::ReadOnly); //! if the file isnt a tar archive if (archive.isOpen()) { QDir tempDir{uniqueTempDirectory()}; const auto archiveRootDir = archive.directory(); for (const auto &name : archiveRootDir->entries()) { auto fileEntry = archiveRootDir->file(name); fileEntry->copyTo(tempDir.absolutePath()); } QString name = Layouts::Importer::nameOfConfigFile(file); QString applets(tempDir.absolutePath() + "/" + "lattedock-appletsrc"); if (QFile(applets).exists()) { if (m_corona->layoutsManager()->importer()->importOldLayout(applets, name, false, tempDir.absolutePath())) { addLayoutForFile(tempDir.absolutePath() + "/" + name + ".layout.latte", name, false); } QString alternativeName = name + "-" + i18nc("layout", "Alternative"); if (m_corona->layoutsManager()->importer()->importOldLayout(applets, alternativeName, false, tempDir.absolutePath())) { addLayoutForFile(tempDir.absolutePath() + "/" + alternativeName + ".layout.latte", alternativeName, false); } } return true; } return false; } void SettingsDialog::on_exportButton_clicked() { int row = ui->layoutsView->currentIndex().row(); if (row < 0) { return; } QString layoutExported = m_model->data(m_model->index(row, IDCOLUMN), Qt::DisplayRole).toString(); //! Update ALL active original layouts before exporting, //! this is needed because the export method can export also the full configuration qDebug() << Q_FUNC_INFO; m_corona->layoutsManager()->syncActiveLayoutsToOriginalFiles(); QFileDialog *fileDialog = new QFileDialog(this, i18nc("export layout/configuration", "Export Layout/Configuration") , QDir::homePath(), QStringLiteral("layout.latte")); fileDialog->setFileMode(QFileDialog::AnyFile); fileDialog->setAcceptMode(QFileDialog::AcceptSave); fileDialog->setDefaultSuffix("layout.latte"); QStringList filters; QString filter1(i18nc("export layout", "Latte Dock Layout file v0.2") + "(*.layout.latte)"); QString filter2(i18nc("export full configuration", "Latte Dock Full Configuration file v0.2") + "(*.latterc)"); filters << filter1 << filter2; fileDialog->setNameFilters(filters); connect(fileDialog, &QFileDialog::finished , fileDialog, &QFileDialog::deleteLater); connect(fileDialog, &QFileDialog::fileSelected , this, [ &, layoutExported](const QString & file) { auto showNotificationError = []() { auto notification = new KNotification("export-fail", KNotification::CloseOnTimeout); notification->setText(i18nc("export layout", "Failed to export layout")); notification->sendEvent(); }; if (QFile::exists(file) && !QFile::remove(file)) { showNotificationError(); return; } if (file.endsWith(".layout.latte")) { if (!QFile(layoutExported).copy(file)) { showNotificationError(); return; } QFileInfo newFileInfo(file); if (newFileInfo.exists() && !newFileInfo.isWritable()) { QFile(file).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } CentralLayout layoutS(this, file); layoutS.setActivities(QStringList()); layoutS.clearLastUsedActivity(); //NOTE: The pointer is automatically deleted when the event is closed auto notification = new KNotification("export-done", KNotification::CloseOnTimeout); notification->setActions({i18nc("export layout", "Open location")}); notification->setText(i18nc("export layout", "Layout exported successfully")); connect(notification, &KNotification::action1Activated , this, [file]() { QDesktopServices::openUrl({QFileInfo(file).canonicalPath()}); }); notification->sendEvent(); } else if (file.endsWith(".latterc")) { auto showNotificationError = []() { auto notification = new KNotification("export-fail", KNotification::CloseOnTimeout); notification->setText(i18nc("import/export config", "Failed to export configuration")); notification->sendEvent(); }; if (m_corona->layoutsManager()->importer()->exportFullConfiguration(file)) { auto notification = new KNotification("export-done", KNotification::CloseOnTimeout); notification->setActions({i18nc("import/export config", "Open location")}); notification->setText(i18nc("import/export config", "Full Configuration exported successfully")); connect(notification, &KNotification::action1Activated , this, [file]() { QDesktopServices::openUrl({QFileInfo(file).canonicalPath()}); }); notification->sendEvent(); } else { showNotificationError(); } } }); fileDialog->open(); } void SettingsDialog::requestImagesDialog(int row) { QStringList mimeTypeFilters; mimeTypeFilters << "image/jpeg" // will show "JPEG image (*.jpeg *.jpg) << "image/png"; // will show "PNG image (*.png)" QFileDialog dialog(this); dialog.setMimeTypeFilters(mimeTypeFilters); QString background = m_model->data(m_model->index(row, COLORCOLUMN), Qt::BackgroundRole).toString(); if (background.startsWith("/") && QFileInfo(background).exists()) { dialog.setDirectory(QFileInfo(background).absolutePath()); dialog.selectFile(background); } if (dialog.exec()) { QStringList files = dialog.selectedFiles(); if (files.count() > 0) { m_model->setData(m_model->index(row, COLORCOLUMN), files[0], Qt::BackgroundRole); } } } void SettingsDialog::requestColorsDialog(int row) { QColorDialog dialog(this); QString textColor = m_model->data(m_model->index(row, COLORCOLUMN), Qt::UserRole).toString(); dialog.setCurrentColor(QColor(textColor)); if (dialog.exec()) { qDebug() << dialog.selectedColor().name(); m_model->setData(m_model->index(row, COLORCOLUMN), dialog.selectedColor().name(), Qt::UserRole); } } void SettingsDialog::accept() { qDebug() << Q_FUNC_INFO; if (saveAllChanges()) { deleteLater(); } } void SettingsDialog::reject() { qDebug() << Q_FUNC_INFO; if (!m_blockDeleteOnReject) { deleteLater(); } } void SettingsDialog::apply() { qDebug() << Q_FUNC_INFO; saveAllChanges(); o_settings = currentSettings(); o_settingsLayouts = currentLayoutsSettings(); updateApplyButtonsState(); updatePerLayoutButtonsState(); } void SettingsDialog::restoreDefaults() { qDebug() << Q_FUNC_INFO; if (ui->tabWidget->currentIndex() == 0) { //! Default layouts missing from layouts list for (const auto &preset : m_corona->layoutsManager()->presetsPaths()) { QString presetName = CentralLayout::layoutName(preset); QByteArray presetNameChars = presetName.toUtf8(); const char *prset_str = presetNameChars.data(); presetName = i18n(prset_str); if (!nameExistsInModel(presetName)) { addLayoutForFile(preset, presetName); } } } else if (ui->tabWidget->currentIndex() == 1) { //! Defaults for general Latte settings ui->autostartChkBox->setChecked(true); ui->infoWindowChkBox->setChecked(true); ui->metaPressChkBox->setChecked(false); ui->metaPressHoldChkBox->setChecked(true); ui->noBordersForMaximizedChkBox->setChecked(false); ui->highSensitivityBtn->setChecked(true); ui->screenTrackerSpinBox->setValue(SCREENTRACKERDEFAULTVALUE); ui->outlineSpinBox->setValue(OUTLINEDEFAULTWIDTH); } } void SettingsDialog::addLayoutForFile(QString file, QString layoutName, bool newTempDirectory, bool showNotification) { if (layoutName.isEmpty()) { layoutName = CentralLayout::layoutName(file); } QString copiedId; if (newTempDirectory) { QString tempDir = uniqueTempDirectory(); copiedId = tempDir + "/" + layoutName + ".layout.latte"; QFile(file).copy(copiedId); } else { copiedId = file; } QFileInfo newFileInfo(copiedId); if (newFileInfo.exists() && !newFileInfo.isWritable()) { QFile(copiedId).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } if (m_layouts.contains(copiedId)) { CentralLayout *oldSettings = m_layouts.take(copiedId); delete oldSettings; } CentralLayout *settings = new CentralLayout(this, copiedId); m_layouts[copiedId] = settings; QString id = copiedId; QString color = settings->color(); QString textColor = settings->textColor(); QString background = settings->background(); bool menu = settings->showInMenu(); bool disabledBorders = settings->disableBordersForMaximizedWindows(); bool locked = !settings->isWritable(); layoutName = uniqueLayoutName(layoutName); int row = ascendingRowFor(layoutName); if (background.isEmpty()) { insertLayoutInfoAtRow(row, copiedId, color, QString(), layoutName, menu, disabledBorders, QStringList(), locked); } else { insertLayoutInfoAtRow(row, copiedId, background, textColor, layoutName, menu, disabledBorders, QStringList(), locked); } ui->layoutsView->selectRow(row); if (showNotification) { //NOTE: The pointer is automatically deleted when the event is closed auto notification = new KNotification("import-done", KNotification::CloseOnTimeout); notification->setText(i18nc("import-done", "Layout: %0 imported successfully
").arg(layoutName)); notification->sendEvent(); } } void SettingsDialog::loadSettings() { m_initLayoutPaths.clear(); m_model->clear(); m_sharesMap.clear(); int i = 0; QStringList brokenLayouts; if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { m_corona->layoutsManager()->syncActiveLayoutsToOriginalFiles(); } for (const auto layout : m_corona->layoutsManager()->layouts()) { QString layoutPath = QDir::homePath() + "/.config/latte/" + layout + ".layout.latte"; m_initLayoutPaths.append(layoutPath); CentralLayout *central = new CentralLayout(this, layoutPath); m_layouts[layoutPath] = central; QString background = central->background(); //! add central layout properties if (background.isEmpty()) { insertLayoutInfoAtRow(i, layoutPath, central->color(), QString(), central->name(), central->showInMenu(), central->disableBordersForMaximizedWindows(), central->activities(), !central->isWritable()); } else { insertLayoutInfoAtRow(i, layoutPath, background, central->textColor(), central->name(), central->showInMenu(), central->disableBordersForMaximizedWindows(), central->activities(), !central->isWritable()); } //! create initial SHARES maps QString shared = central->sharedLayoutName(); if (!shared.isEmpty()) { m_sharesMap[shared].append(layoutPath); } qDebug() << "counter:" << i << " total:" << m_model->rowCount(); i++; if (central->name() == m_corona->layoutsManager()->currentLayoutName()) { ui->layoutsView->selectRow(i - 1); } Layout::GenericLayout *generic = m_corona->layoutsManager()->layout(central->name()); if ((generic && generic->layoutIsBroken()) || (!generic && central->layoutIsBroken())) { brokenLayouts.append(central->name()); } } //! update SHARES map keys in order to use the #settingsid(s) QStringList forremoval; //! remove these records after updating for (QHash::iterator i=m_sharesMap.begin(); i!=m_sharesMap.end(); ++i) { forremoval << i.key(); } //! update keys for (QHash::iterator i=m_sharesMap.begin(); i!=m_sharesMap.end(); ++i) { QString shareid = idForRow(rowForName(i.key())); m_sharesMap[shareid] = i.value(); } //! remove deprecated keys for (const auto &key : forremoval) { m_sharesMap.remove(key); } qDebug() << "SHARES MAP ::: " << m_sharesMap; for (QHash::iterator i=m_sharesMap.begin(); i!=m_sharesMap.end(); ++i) { int sharedPos = rowForId(i.key()); if (sharedPos >= 0) { m_model->setData(m_model->index(sharedPos, SHAREDCOLUMN), i.value(), Qt::UserRole); } } recalculateAvailableActivities(); m_model->setHorizontalHeaderItem(IDCOLUMN, new QStandardItem(QString("#path"))); m_model->setHorizontalHeaderItem(COLORCOLUMN, new QStandardItem(QIcon::fromTheme("games-config-background"), QString(i18nc("column for layout background", "Background")))); m_model->setHorizontalHeaderItem(NAMECOLUMN, new QStandardItem(QString(i18nc("column for layout name", "Name")))); m_model->setHorizontalHeaderItem(MENUCOLUMN, new QStandardItem(QString(i18nc("column for layout to show in menu", "In Menu")))); m_model->setHorizontalHeaderItem(BORDERSCOLUMN, new QStandardItem(QString(i18nc("column for layout to hide borders for maximized windows", "Borderless")))); m_model->setHorizontalHeaderItem(ACTIVITYCOLUMN, new QStandardItem(QIcon::fromTheme("preferences-activities"), QString(i18nc("column for layout to show which activities is assigned to", "Activities")))); m_model->setHorizontalHeaderItem(SHAREDCOLUMN, new QStandardItem(QIcon::fromTheme("document-share"), QString(i18nc("column for shared layout to show which layouts is assigned to", "Shared To")))); //! this line should be commented for debugging layouts window functionality ui->layoutsView->setColumnHidden(IDCOLUMN, true); ui->layoutsView->setColumnHidden(HIDDENTEXTCOLUMN, true); if (m_corona->universalSettings()->canDisableBorders()) { ui->layoutsView->setColumnHidden(BORDERSCOLUMN, false); } else { ui->layoutsView->setColumnHidden(BORDERSCOLUMN, true); } ui->layoutsView->resizeColumnsToContents(); QStringList columnWidths = m_corona->universalSettings()->layoutsColumnWidths(); if (!columnWidths.isEmpty()) { for (int i=0; ilayoutsView->setColumnWidth(COLORCOLUMN+i, columnWidths[i].toInt()); } } if (m_corona->layoutsManager()->memoryUsage() == Types::SingleLayout) { ui->singleToolBtn->setChecked(true); } else if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { ui->multipleToolBtn->setChecked(true); } updatePerLayoutButtonsState(); updateSharedLayoutsStates(); ui->autostartChkBox->setChecked(m_corona->universalSettings()->autostart()); ui->infoWindowChkBox->setChecked(m_corona->universalSettings()->showInfoWindow()); ui->metaPressChkBox->setChecked(m_corona->universalSettings()->metaForwardedToLatte()); ui->metaPressHoldChkBox->setChecked(m_corona->universalSettings()->metaPressAndHoldEnabled()); ui->noBordersForMaximizedChkBox->setChecked(m_corona->universalSettings()->canDisableBorders()); if (m_corona->universalSettings()->mouseSensitivity() == Types::LowSensitivity) { ui->lowSensitivityBtn->setChecked(true); } else if (m_corona->universalSettings()->mouseSensitivity() == Types::MediumSensitivity) { ui->mediumSensitivityBtn->setChecked(true); } else if (m_corona->universalSettings()->mouseSensitivity() == Types::HighSensitivity) { ui->highSensitivityBtn->setChecked(true); } o_settings = currentSettings(); o_settingsLayouts = currentLayoutsSettings(); updateApplyButtonsState(); updateSharedLayoutsUiElements(); //! there are broken layouts and the user must be informed! if (brokenLayouts.count() > 0) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Layout Warning")); msg->setText(i18n("The layout(s) %0 have broken configuration!!! Please remove them to improve the system stability...").arg(brokenLayouts.join(","))); msg->setStandardButtons(QMessageBox::Ok); msg->open(); } } QList SettingsDialog::currentSettings() { QList settings; settings << m_inMemoryButtons->checkedId(); settings << (int)ui->autostartChkBox->isChecked(); settings << (int)ui->infoWindowChkBox->isChecked(); settings << (int)ui->metaPressChkBox->isChecked(); settings << (int)ui->metaPressHoldChkBox->isChecked(); settings << (int)ui->noBordersForMaximizedChkBox->isChecked(); settings << m_mouseSensitivityButtons->checkedId(); settings << ui->screenTrackerSpinBox->value(); settings << ui->outlineSpinBox->value(); settings << m_model->rowCount(); return settings; } QStringList SettingsDialog::currentLayoutsSettings() { QStringList layoutSettings; for (int i = 0; i < m_model->rowCount(); ++i) { QString id = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); QString color = m_model->data(m_model->index(i, COLORCOLUMN), Qt::BackgroundRole).toString(); QString textColor = m_model->data(m_model->index(i, COLORCOLUMN), Qt::UserRole).toString(); QString name = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); bool locked = m_model->data(m_model->index(i, NAMECOLUMN), Qt::UserRole).toBool(); bool menu = m_model->data(m_model->index(i, MENUCOLUMN), Qt::DisplayRole).toString() == CheckMark; bool borders = m_model->data(m_model->index(i, BORDERSCOLUMN), Qt::DisplayRole).toString() == CheckMark; QStringList lActivities = m_model->data(m_model->index(i, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); QStringList shares = m_model->data(m_model->index(i, SHAREDCOLUMN), Qt::UserRole).toStringList(); layoutSettings << id; layoutSettings << color; layoutSettings << textColor; layoutSettings << name; layoutSettings << QString::number((int)locked); layoutSettings << QString::number((int)menu); layoutSettings << QString::number((int)borders); layoutSettings << lActivities; layoutSettings << shares; } return layoutSettings; } void SettingsDialog::insertLayoutInfoAtRow(int row, QString path, QString color, QString textColor, QString name, bool menu, bool disabledBorders, QStringList activities, bool locked) { QStandardItem *pathItem = new QStandardItem(path); QStandardItem *hiddenTextItem = new QStandardItem(); QStandardItem *colorItem = new QStandardItem(); colorItem->setSelectable(false); QStandardItem *nameItem = new QStandardItem(name); nameItem->setTextAlignment(Qt::AlignCenter); QStandardItem *menuItem = new QStandardItem(); menuItem->setEditable(false); menuItem->setSelectable(true); menuItem->setText(menu ? CheckMark : QString()); menuItem->setTextAlignment(Qt::AlignCenter); QStandardItem *bordersItem = new QStandardItem(); bordersItem->setEditable(false); bordersItem->setSelectable(true); bordersItem->setText(disabledBorders ? CheckMark : QString()); bordersItem->setTextAlignment(Qt::AlignCenter); QStandardItem *activitiesItem = new QStandardItem(activities.join(",")); QStandardItem *sharesItem = new QStandardItem(); QList items; items.append(pathItem); items.append(hiddenTextItem); items.append(colorItem); items.append(nameItem); items.append(menuItem); items.append(bordersItem); items.append(activitiesItem); items.append(sharesItem); if (row > m_model->rowCount() - 1) { m_model->appendRow(items); row = m_model->rowCount() - 1; qDebug() << "append row at:" << row << " rows:" << m_model->rowCount(); } else { m_model->insertRow(row, items); qDebug() << "insert row at:" << row << " rows:" << m_model->rowCount(); } m_model->setData(m_model->index(row, IDCOLUMN), path, Qt::DisplayRole); m_model->setData(m_model->index(row, COLORCOLUMN), color, Qt::BackgroundRole); m_model->setData(m_model->index(row, COLORCOLUMN), textColor, Qt::UserRole); QFont font; if (m_corona->layoutsManager()->layout(name)) { font.setBold(true); } else { font.setBold(false); } if (path.startsWith("/tmp/")) { font.setItalic(true); } else { font.setItalic(false); } m_model->setData(m_model->index(row, NAMECOLUMN), QVariant(name), Qt::DisplayRole); m_model->setData(m_model->index(row, NAMECOLUMN), font, Qt::FontRole); m_model->setData(m_model->index(row, NAMECOLUMN), QVariant(locked), Qt::UserRole); m_model->setData(m_model->index(row, ACTIVITYCOLUMN), activities, Qt::UserRole); } void SettingsDialog::on_switchButton_clicked() { if (ui->buttonBox->button(QDialogButtonBox::Apply)->isEnabled()) { //! thus there are changes in the settings QString lName; QStringList lActivities; if (m_inMemoryButtons->checkedId() == Latte::Types::MultipleLayouts) { lName = m_model->data(m_model->index(ui->layoutsView->currentIndex().row(), NAMECOLUMN), Qt::DisplayRole).toString(); lActivities = m_model->data(m_model->index(ui->layoutsView->currentIndex().row(), ACTIVITYCOLUMN), Qt::UserRole).toStringList(); } apply(); if (!lName.isEmpty() && !lActivities.isEmpty()) { //! an activities-assigned layout is chosen and at the same time we are moving //! to multiple layouts state m_corona->layoutsManager()->switchToLayout(lName); } } else { QVariant value = m_model->data(m_model->index(ui->layoutsView->currentIndex().row(), NAMECOLUMN), Qt::DisplayRole); if (value.isValid()) { m_corona->layoutsManager()->switchToLayout(value.toString()); } else { qDebug() << "not valid layout"; } } updatePerLayoutButtonsState(); } void SettingsDialog::on_pauseButton_clicked() { ui->pauseButton->setEnabled(false); QString id = m_model->data(m_model->index(ui->layoutsView->currentIndex().row(), IDCOLUMN), Qt::DisplayRole).toString(); CentralLayout *layout = m_layouts[id]; if (layout) { m_corona->layoutsManager()->pauseLayout(layout->name()); } } void SettingsDialog::layoutsChanged() { for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex nameIndex = m_model->index(i, NAMECOLUMN); QVariant value = m_model->data(nameIndex); if (value.isValid()) { QString name = value.toString(); QFont font; if (m_corona->layoutsManager()->currentLayoutName() == name) { font.setBold(true); // ui->layoutsView->selectRow(i); } else { Layout::GenericLayout *layout = m_corona->layoutsManager()->layout(name); if (layout && (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts)) { font.setBold(true); } else { font.setBold(false); } } m_model->setData(nameIndex, font, Qt::FontRole); } } } void SettingsDialog::itemChanged(QStandardItem *item) { updatePerLayoutButtonsState(); if (item->column() == ACTIVITYCOLUMN) { //! recalculate the available activities recalculateAvailableActivities(); } else if (item->column() == NAMECOLUMN) { int currentRow = ui->layoutsView->currentIndex().row(); QString id = m_model->data(m_model->index(currentRow, IDCOLUMN), Qt::DisplayRole).toString(); QString name = m_model->data(m_model->index(currentRow, NAMECOLUMN), Qt::DisplayRole).toString(); QFont font = qvariant_cast(m_model->data(m_model->index(currentRow, NAMECOLUMN), Qt::FontRole)); if (m_corona->layoutsManager()->layout(m_layouts[id]->name())) { font.setBold(true); } else { font.setBold(false); } if (m_layouts[id]->name() != name) { font.setItalic(true); } else { font.setItalic(false); } m_model->setData(m_model->index(currentRow, NAMECOLUMN), font, Qt::FontRole); } else if (item->column() == SHAREDCOLUMN) { updateSharedLayoutsStates(); } updateApplyButtonsState(); } void SettingsDialog::updateApplyButtonsState() { bool changed{false}; //! Ok, Apply Buttons if ((o_settings != currentSettings()) || (o_settingsLayouts != currentLayoutsSettings())) { changed = true; } if (changed) { ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true); } else { //ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); } //! RestoreDefaults Button if (ui->tabWidget->currentIndex() == 0) { //! Check Default layouts missing from layouts list bool layoutMissing{false}; for (const auto &preset : m_corona->layoutsManager()->presetsPaths()) { QString presetName = CentralLayout::layoutName(preset); QByteArray presetNameChars = presetName.toUtf8(); const char *prset_str = presetNameChars.data(); presetName = i18n(prset_str); if (!nameExistsInModel(presetName)) { layoutMissing = true; break; } } if (layoutMissing) { ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); } else { ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); } } else if (ui->tabWidget->currentIndex() == 1) { //! Defaults for general Latte settings if (!ui->autostartChkBox->isChecked() || ui->metaPressChkBox->isChecked() || !ui->metaPressHoldChkBox->isChecked() || !ui->infoWindowChkBox->isChecked() || ui->noBordersForMaximizedChkBox->isChecked() || !ui->highSensitivityBtn->isChecked() || ui->screenTrackerSpinBox->value() != SCREENTRACKERDEFAULTVALUE || ui->outlineSpinBox->value() != OUTLINEDEFAULTWIDTH ) { ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(true); } else { ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setEnabled(false); } } } void SettingsDialog::updatePerLayoutButtonsState() { int currentRow = ui->layoutsView->currentIndex().row(); QString id = m_model->data(m_model->index(currentRow, IDCOLUMN), Qt::DisplayRole).toString(); QString nameInModel = m_model->data(m_model->index(currentRow, NAMECOLUMN), Qt::DisplayRole).toString(); QString originalName = m_layouts.contains(id) ? m_layouts[id]->name() : ""; bool lockedInModel = m_model->data(m_model->index(currentRow, NAMECOLUMN), Qt::UserRole).toBool(); bool sharedInModel = !m_model->data(m_model->index(currentRow, SHAREDCOLUMN), Qt::UserRole).toStringList().isEmpty(); //! Switch Button if (id.startsWith("/tmp/") || originalName != nameInModel) { ui->switchButton->setEnabled(false); } else { ui->switchButton->setEnabled(true); } //! Pause Button if (m_corona->layoutsManager()->memoryUsage() == Types::SingleLayout) { ui->pauseButton->setVisible(false); } else if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { ui->pauseButton->setVisible(true); QStringList lActivities = m_model->data(m_model->index(currentRow, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); Latte::CentralLayout *layout = m_layouts[id]; if (!lActivities.isEmpty() && layout && m_corona->layoutsManager()->centralLayout(layout->name())) { ui->pauseButton->setEnabled(true); } else { ui->pauseButton->setEnabled(false); } } //! Remove Layout Button if (originalName != nameInModel || (originalName == m_corona->layoutsManager()->currentLayoutName()) || (m_corona->layoutsManager()->centralLayout(originalName)) || lockedInModel) { ui->removeButton->setEnabled(false); } else { ui->removeButton->setEnabled(true); } //! Layout Locked Button if (lockedInModel) { ui->lockedButton->setChecked(true); } else { ui->lockedButton->setChecked(false); } //! Layout Shared Button if (sharedInModel) { ui->sharedButton->setChecked(true); } else { ui->sharedButton->setChecked(false); } } void SettingsDialog::updateSharedLayoutsStates() { bool inMultiple{inMultipleLayoutsLook()}; for (int i = 0; i < m_model->rowCount(); ++i) { QStringList shares = m_model->data(m_model->index(i, SHAREDCOLUMN), Qt::UserRole).toStringList(); if (shares.isEmpty() || !inMultiple) { QStandardItem *item = m_model->item(i, MENUCOLUMN); item->setEnabled(true); item = m_model->item(i, BORDERSCOLUMN); item->setEnabled(true); item = m_model->item(i, ACTIVITYCOLUMN); item->setEnabled(true); } else { QStandardItem *item = m_model->item(i, MENUCOLUMN); item->setEnabled(false); item = m_model->item(i, BORDERSCOLUMN); item->setEnabled(false); item = m_model->item(i, ACTIVITYCOLUMN); item->setEnabled(false); } //! refresh LayoutName QStandardItem *nameItem = m_model->item(i, NAMECOLUMN); nameItem->setEnabled(false); nameItem->setEnabled(true); } } void SettingsDialog::updateSharedLayoutsUiElements() { //! UI Elements that need to be enabled/disabled Latte::Types::LayoutsMemoryUsage inMemoryOption = static_cast(m_inMemoryButtons->checkedId()); if (inMemoryOption == Latte::Types::MultipleLayouts) { ui->layoutsView->setColumnHidden(SHAREDCOLUMN, false); ui->sharedButton->setVisible(true); //! column widths QStringList cWidths = m_corona->universalSettings()->layoutsColumnWidths(); if (cWidths.count()>=5) { ui->layoutsView->setColumnWidth(ACTIVITYCOLUMN, cWidths[4].toInt()); } } else { ui->layoutsView->setColumnHidden(SHAREDCOLUMN, true); ui->sharedButton->setVisible(false); } } void SettingsDialog::recalculateAvailableActivities() { QStringList tempActivities = m_corona->layoutsManager()->activities(); for (int i = 0; i < m_model->rowCount(); ++i) { QStringList assigned = m_model->data(m_model->index(i, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); for (const auto &activity : assigned) { if (tempActivities.contains(activity)) { tempActivities.removeAll(activity); } } } m_availableActivities = tempActivities; } bool SettingsDialog::dataAreAccepted() { for (int i = 0; i < m_model->rowCount(); ++i) { QString layout1 = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); for (int j = i + 1; j < m_model->rowCount(); ++j) { QString temp = m_model->data(m_model->index(j, NAMECOLUMN), Qt::DisplayRole).toString(); //!same layout name exists again if (layout1 == temp) { auto msg = new QMessageBox(this); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Layout Warning")); msg->setText(i18n("There are layouts with the same name, that is not permitted!!! Please update these names to re-apply the changes...")); msg->setStandardButtons(QMessageBox::Ok); connect(msg, &QMessageBox::finished, this, [ &, i, j](int result) { QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::ClearAndSelect; QModelIndex indexBase = m_model->index(i, NAMECOLUMN); ui->layoutsView->selectionModel()->select(indexBase, flags); QModelIndex indexOccurence = m_model->index(j, NAMECOLUMN); ui->layoutsView->edit(indexOccurence); }); msg->open(); return false; } } } return true; } bool SettingsDialog::saveAllChanges() { if (!dataAreAccepted()) { return false; } //! Update universal settings Latte::Types::MouseSensitivity sensitivity = static_cast(m_mouseSensitivityButtons->checkedId()); bool autostart = ui->autostartChkBox->isChecked(); bool forwardMetaPress = ui->metaPressChkBox->isChecked(); bool metaPressAndHold = ui->metaPressHoldChkBox->isChecked(); bool showInfoWindow = ui->infoWindowChkBox->isChecked(); bool noBordersForMaximized = ui->noBordersForMaximizedChkBox->isChecked(); m_corona->universalSettings()->setMouseSensitivity(sensitivity); m_corona->universalSettings()->setAutostart(autostart); m_corona->universalSettings()->forwardMetaToLatte(forwardMetaPress); m_corona->universalSettings()->setMetaPressAndHoldEnabled(metaPressAndHold); m_corona->universalSettings()->setShowInfoWindow(showInfoWindow); m_corona->universalSettings()->setCanDisableBorders(noBordersForMaximized); m_corona->universalSettings()->setScreenTrackerInterval(ui->screenTrackerSpinBox->value()); m_corona->themeExtended()->setOutlineWidth(ui->outlineSpinBox->value()); //! Update Layouts QStringList knownActivities = activities(); QTemporaryDir layoutTempDir; qDebug() << "Temporary Directory ::: " << layoutTempDir.path(); QStringList fromRenamePaths; QStringList toRenamePaths; QStringList toRenameNames; QString switchToLayout; QHash activeLayoutsToRename; //! remove layouts that have been removed from the user for (const auto &initLayout : m_initLayoutPaths) { if (!idExistsInModel(initLayout)) { QFile(initLayout).remove(); if (m_layouts.contains(initLayout)) { CentralLayout *removedLayout = m_layouts.take(initLayout); delete removedLayout; } } } for (int i = 0; i < m_model->rowCount(); ++i) { QString id = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); QString color = m_model->data(m_model->index(i, COLORCOLUMN), Qt::BackgroundRole).toString(); QString textColor = m_model->data(m_model->index(i, COLORCOLUMN), Qt::UserRole).toString(); QString name = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); bool locked = m_model->data(m_model->index(i, NAMECOLUMN), Qt::UserRole).toBool(); bool menu = m_model->data(m_model->index(i, MENUCOLUMN), Qt::DisplayRole).toString() == CheckMark; bool disabledBorders = m_model->data(m_model->index(i, BORDERSCOLUMN), Qt::DisplayRole).toString() == CheckMark; QStringList lActivities = m_model->data(m_model->index(i, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); QStringList cleanedActivities; //!update only activities that are valid for (const auto &activity : lActivities) { if (knownActivities.contains(activity)) { cleanedActivities.append(activity); } } //qDebug() << i << ". " << id << " - " << color << " - " << name << " - " << menu << " - " << lActivities; //! update the generic parts of the layouts Layout::GenericLayout *genericActive= m_corona->layoutsManager()->layout(m_layouts[id]->name()); Layout::GenericLayout *generic = genericActive ? genericActive : m_layouts[id]; //! unlock read-only layout if (!generic->isWritable()) { generic->unlock(); } if (color.startsWith("/")) { //it is image file in such case if (color != generic->background()) { generic->setBackground(color); } if (generic->textColor() != textColor) { generic->setTextColor(textColor); } } else { if (color != generic->color()) { generic->setColor(color); generic->setBackground(QString()); generic->setTextColor(QString()); } } //! update only the Central-specific layout parts CentralLayout *centralActive= m_corona->layoutsManager()->centralLayout(m_layouts[id]->name()); CentralLayout *central = centralActive ? centralActive : m_layouts[id]; if (central->showInMenu() != menu) { central->setShowInMenu(menu); } if (central->disableBordersForMaximizedWindows() != disabledBorders) { central->setDisableBordersForMaximizedWindows(disabledBorders); } if (central->activities() != cleanedActivities) { central->setActivities(cleanedActivities); } //! If the layout name changed OR the layout path is a temporary one if (generic->name() != name || (id.startsWith("/tmp/"))) { //! If the layout is Active in MultipleLayouts if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts && generic->isActive()) { qDebug() << " Active Layout Should Be Renamed From : " << generic->name() << " TO :: " << name; activeLayoutsToRename[name] = generic; } QString tempFile = layoutTempDir.path() + "/" + QString(generic->name() + ".layout.latte"); qDebug() << "new temp file ::: " << tempFile; if ((m_corona->layoutsManager()->memoryUsage() == Types::SingleLayout) && (generic->name() == m_corona->layoutsManager()->currentLayoutName())) { switchToLayout = name; } generic = m_layouts.take(id); delete generic; QFile(id).rename(tempFile); fromRenamePaths.append(id); toRenamePaths.append(tempFile); toRenameNames.append(name); } } //! this is necessary in case two layouts have to swap names //! so we copy first the layouts in a temp directory and afterwards all //! together we move them in the official layout directory for (int i = 0; i < toRenamePaths.count(); ++i) { QString newFile = QDir::homePath() + "/.config/latte/" + toRenameNames[i] + ".layout.latte"; QFile(toRenamePaths[i]).rename(newFile); CentralLayout *nLayout = new CentralLayout(this, newFile); m_layouts[newFile] = nLayout; //! updating the #SETTINGSID in the model for the layout that was renamed for (int j = 0; j < m_model->rowCount(); ++j) { QString tId = m_model->data(m_model->index(j, IDCOLUMN), Qt::DisplayRole).toString(); if (tId == fromRenamePaths[i]) { m_model->setData(m_model->index(j, IDCOLUMN), newFile, Qt::DisplayRole); m_initLayoutPaths.append(newFile); QFont font = qvariant_cast(m_model->data(m_model->index(j, NAMECOLUMN), Qt::FontRole)); font.setItalic(false); m_model->setData(m_model->index(j, NAMECOLUMN), font, Qt::FontRole); } } } QString orphanedLayout; if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { for (const auto &newLayoutName : activeLayoutsToRename.keys()) { Layout::GenericLayout *layout = activeLayoutsToRename[newLayoutName]; qDebug() << " Active Layout of Type: " << layout->type() << " Is Renamed From : " << activeLayoutsToRename[newLayoutName]->name() << " TO :: " << newLayoutName; layout->renameLayout(newLayoutName); if (layout->type() == Layout::Type::Central) { CentralLayout *central = qobject_cast(layout); if (central->activities().isEmpty()) { //! that means it is an active layout for orphaned Activities orphanedLayout = newLayoutName; } } //! broadcast the name change int row = rowForName(newLayoutName); QStandardItem *item = m_model->item(row, NAMECOLUMN); if (item) { emit itemChanged(item); } } } //! lock layouts in the end when the user has chosen it for (int i = 0; i < m_model->rowCount(); ++i) { QString id = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); QString name = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); bool locked = m_model->data(m_model->index(i, NAMECOLUMN), Qt::UserRole).toBool(); Layout::GenericLayout *generic = m_corona->layoutsManager()->layout(m_layouts[id]->name()); Layout::GenericLayout *layout = generic ? generic : m_layouts[id]; if (layout && locked && layout->isWritable()) { layout->lock(); } } //! update SharedLayouts that are Active - if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { - updateActiveShares(); - } + syncActiveShares(); //! reload layouts in layoutsmanager m_corona->layoutsManager()->synchronizer()->loadLayouts(); //! send to layout manager in which layout to switch Latte::Types::LayoutsMemoryUsage inMemoryOption = static_cast(m_inMemoryButtons->checkedId()); if (m_corona->layoutsManager()->memoryUsage() != inMemoryOption) { Types::LayoutsMemoryUsage previousMemoryUsage = m_corona->layoutsManager()->memoryUsage(); m_corona->layoutsManager()->setMemoryUsage(inMemoryOption); QVariant value = m_model->data(m_model->index(ui->layoutsView->currentIndex().row(), NAMECOLUMN), Qt::DisplayRole); QString layoutName = value.toString(); m_corona->layoutsManager()->switchToLayout(layoutName, previousMemoryUsage); } else { if (!switchToLayout.isEmpty()) { m_corona->layoutsManager()->switchToLayout(switchToLayout); } else if (m_corona->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { m_corona->layoutsManager()->synchronizer()->syncMultipleLayoutsToActivities(orphanedLayout); } } return true; } -void SettingsDialog::updateActiveShares() +void SettingsDialog::syncActiveShares() { - QHash currentSharesMap; + if (m_corona->layoutsManager()->memoryUsage() != Types::MultipleLayouts) { + return; + } + + QHash currentSharesIdMap; + Layouts::SharesMap currentSharesNamesMap; for (int i = 0; i < m_model->rowCount(); ++i) { if (isShared(i)) { QString id = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); - QStringList shares = m_model->data(m_model->index(i, SHAREDCOLUMN), Qt::UserRole).toStringList(); - currentSharesMap[id] = shares; - } - } - - qDebug() << " CURRENT SHARES MAP :: " << currentSharesMap; + QString name = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); + QStringList shareIds = m_model->data(m_model->index(i, SHAREDCOLUMN), Qt::UserRole).toStringList(); + QStringList shareNames; - QHash unassign; - - //! CENTRAL (active) layouts that will become SHARED must be unloaded first - for (QHash::iterator i=currentSharesMap.begin(); i!=currentSharesMap.end(); ++i) { - CentralLayout *central = m_corona->layoutsManager()->centralLayout(nameForId(i.key())); - if (central) { - //IMPORTANT !!!!! REENABLE WHEN THE CODE IS MOVED!!!! - //m_corona->layoutsManager()->synchronizer()->unloadCentralLayout(central); - } - } - - //! CENTRAL (active) layouts that update their (active) SHARED layouts - //! AND load SHARED layouts that are NOT ACTIVE - for (QHash::iterator i=currentSharesMap.begin(); i!=currentSharesMap.end(); ++i) { - SharedLayout *shared = m_corona->layoutsManager()->sharedLayout(nameForId(i.key())); - qDebug() << " SHARED :: " << nameForId(i.key()); - for (const auto ¢ralId : i.value()) { - CentralLayout *central = m_corona->layoutsManager()->centralLayout(nameForId(centralId)); - qDebug() << " CENTRAL NAME :: " << nameForId(centralId); - if (central) { - //! Assign this Central Layout at a different Shared Layout - SharedLayout *oldShared = central->sharedLayout(); - - if (!shared) { - //Shared not loaded and it must be loaded before proceed - m_corona->layoutsManager()->registerAtSharedLayout(central, nameForId(i.key())); - shared = m_corona->layoutsManager()->sharedLayout(nameForId(i.key())); - } - - if (shared != oldShared) { - shared->addCentralLayout(central); - central->setSharedLayout(shared); - if (oldShared) { - //! CENTRAL layout that changed from one ACTIVESHARED layout to another - unassign[central] = shared; - } - } + for (const auto &shareid : shareIds) { + QString shareName = nameForId(shareid); + shareNames << shareName; } - } - } - //! CENTRAL Layouts that wont have any SHARED Layout any more - for (QHash::iterator i=m_sharesMap.begin(); i!=m_sharesMap.end(); ++i) { - for (const auto ¢ralId : i.value()) { - if (!mapHasRecord(centralId, currentSharesMap)) { - CentralLayout *central = m_corona->layoutsManager()->centralLayout(nameForId(centralId)); - if (central && central->sharedLayout()) { - central->sharedLayout()->removeCentralLayout(central); - central->setSharedLayoutName(QString()); - central->setSharedLayout(nullptr); - } - } + currentSharesIdMap[id] = shareIds; + currentSharesNamesMap[name] = shareNames; } } - //! Unassing from Shared Layouts Central ones that are not assigned any more - //! IMPORTANT: This must be done after all the ASSIGNMENTS in order to avoid - //! to unload a SharedLayout that it should not - for (QHash::iterator i=unassign.begin(); i!=unassign.end(); ++i) { - i.value()->removeCentralLayout(i.key()); - } + qDebug() << " CURRENT SHARES ID MAP :: " << currentSharesIdMap; + m_corona->layoutsManager()->synchronizer()->syncActiveShares(currentSharesNamesMap); - //! TODO : (active) SharedLayouts that become Active should be unloaded first m_sharesMap.clear(); - m_sharesMap = currentSharesMap; + m_sharesMap = currentSharesIdMap; } void SettingsDialog::addActivityInCurrent(const QString &activityId) { int currentRow = ui->layoutsView->currentIndex().row(); QStringList activities = m_model->data(m_model->index(currentRow, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); if (!activities.contains(activityId)) { activities << activityId; m_model->setData(m_model->index(currentRow, ACTIVITYCOLUMN), activities, Qt::UserRole); } } void SettingsDialog::removeActivityFromCurrent(const QString &activityId) { int currentRow = ui->layoutsView->currentIndex().row(); QStringList activities = m_model->data(m_model->index(currentRow, ACTIVITYCOLUMN), Qt::UserRole).toStringList(); if (activities.contains(activityId)) { activities.removeAll(activityId); m_model->setData(m_model->index(currentRow, ACTIVITYCOLUMN), activities, Qt::UserRole); } } void SettingsDialog::addShareInCurrent(const QString &layoutId) { int currentRow = ui->layoutsView->currentIndex().row(); QStringList shares = m_model->data(m_model->index(currentRow, SHAREDCOLUMN), Qt::UserRole).toStringList(); if (!shares.contains(layoutId)) { shares << layoutId; m_model->setData(m_model->index(currentRow, SHAREDCOLUMN), shares, Qt::UserRole); } } void SettingsDialog::removeShareFromCurrent(const QString &layoutId) { int currentRow = ui->layoutsView->currentIndex().row(); QStringList shares = m_model->data(m_model->index(currentRow, SHAREDCOLUMN), Qt::UserRole).toStringList(); if (shares.contains(layoutId)) { shares.removeAll(layoutId); m_model->setData(m_model->index(currentRow, SHAREDCOLUMN), shares, Qt::UserRole); } } bool SettingsDialog::idExistsInModel(QString id) { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowId = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); if (rowId == id) { return true; } } return false; } -bool SettingsDialog::mapHasRecord(const QString &record, QHash &map) -{ - for (QHash::iterator i=map.begin(); i!=map.end(); ++i) { - if (i.value().contains(record)) { - return true; - } - } - - return false; -} - bool SettingsDialog::nameExistsInModel(QString name) { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowName = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); if (rowName == name) { return true; } } return false; } bool SettingsDialog::inMultipleLayoutsLook() const { Latte::Types::LayoutsMemoryUsage inMemoryOption = static_cast(m_inMemoryButtons->checkedId()); return inMemoryOption == Latte::Types::MultipleLayouts; } bool SettingsDialog::isActive(QString layoutName) const { return (m_corona->layoutsManager()->layout(layoutName) != nullptr); } bool SettingsDialog::isMenuCell(int column) const { return column == MENUCOLUMN; } bool SettingsDialog::isShared(int row) const { if (row >=0 ) { QStringList shares = m_model->data(m_model->index(row, SHAREDCOLUMN), Qt::UserRole).toStringList(); if (!shares.isEmpty()) { return true; } } return false; } int SettingsDialog::ascendingRowFor(QString name) { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowName = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); if (rowName.toUpper() > name.toUpper()) { return i; } } return m_model->rowCount(); } int SettingsDialog::rowForId(QString id) const { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowId = m_model->data(m_model->index(i, IDCOLUMN), Qt::DisplayRole).toString(); if (rowId == id) { return i; } } return -1; } int SettingsDialog::rowForName(QString layoutName) const { for (int i = 0; i < m_model->rowCount(); ++i) { QString rowName = m_model->data(m_model->index(i, NAMECOLUMN), Qt::DisplayRole).toString(); if (rowName == layoutName) { return i; } } return -1; } QString SettingsDialog::idForRow(int row) const { return m_model->data(m_model->index(row, IDCOLUMN), Qt::DisplayRole).toString(); } QString SettingsDialog::nameForId(QString id) const { int row = rowForId(id); return m_model->data(m_model->index(row, NAMECOLUMN), Qt::DisplayRole).toString(); } QString SettingsDialog::uniqueTempDirectory() { QTemporaryDir tempDir; tempDir.setAutoRemove(false); m_tempDirectories.append(tempDir.path()); return tempDir.path(); } QString SettingsDialog::uniqueLayoutName(QString name) { int pos_ = name.lastIndexOf(QRegExp(QString("[-][0-9]+"))); if (nameExistsInModel(name) && pos_ > 0) { name = name.left(pos_); } int i = 2; QString namePart = name; while (nameExistsInModel(name)) { name = namePart + "-" + QString::number(i); i++; } return name; } }//end of namespace diff --git a/app/settings/settingsdialog.h b/app/settings/settingsdialog.h index 1ab0a334..dea065c5 100644 --- a/app/settings/settingsdialog.h +++ b/app/settings/settingsdialog.h @@ -1,161 +1,160 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * 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 . * */ #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H // local #include "../liblatte2/types.h" // Qt #include #include #include #include #include #include namespace Ui { class SettingsDialog; } namespace KActivities { class Controller; } namespace Latte { class Corona; class CentralLayout; } namespace Latte { class SettingsDialog : public QDialog { Q_OBJECT public: SettingsDialog(QWidget *parent, Latte::Corona *corona); ~SettingsDialog(); void setCurrentPage(Types::LatteConfigPage page); void addActivityInCurrent(const QString &activityId); void removeActivityFromCurrent(const QString &activityId); void addShareInCurrent(const QString &layoutId); void removeShareFromCurrent(const QString &layoutId); bool inMultipleLayoutsLook() const; bool isActive(QString layoutName) const; bool isShared(int row) const; bool isMenuCell(int column) const; QString nameForId(QString id) const; QString idForRow(int row) const; QStringList activities(); QStringList availableActivities(); QStringList availableSharesFor(int row); void requestImagesDialog(int row); void requestColorsDialog(int row); private slots: // auto connections void on_newButton_clicked(); void on_copyButton_clicked(); void on_downloadButton_clicked(); void on_lockedButton_clicked(); void on_pauseButton_clicked(); void on_removeButton_clicked(); void on_sharedButton_clicked(); void on_switchButton_clicked(); void on_importButton_clicked(); void on_exportButton_clicked(); void accept() override; void reject() override; void apply(); void restoreDefaults(); void updatePerLayoutButtonsState(); void layoutsChanged(); void itemChanged(QStandardItem *item); private: void addLayoutForFile(QString file, QString layoutName = QString(), bool newTempDirectory = true, bool showNotification = true); //! When an activity is closed for some reason the window manager hides and reshows //! the windows. This function prevents this because we don't want to delete the window //! on reject in such case. void blockDeleteOnActivityStopped(); void loadSettings(); void recalculateAvailableActivities(); void insertLayoutInfoAtRow(int row, QString path, QString color, QString textColor, QString name, bool menu, bool disabledBorders, QStringList activities, bool locked = false); void updateApplyButtonsState(); void updateSharedLayoutsStates(); void updateSharedLayoutsUiElements(); - void updateActiveShares(); + void syncActiveShares(); bool dataAreAccepted(); bool idExistsInModel(QString id); bool importLayoutsFromV1ConfigFile(QString file); - bool mapHasRecord(const QString &record, QHash &map); bool nameExistsInModel(QString name); bool saveAllChanges(); int rowForId(QString id) const; int rowForName(QString layoutName) const; int ascendingRowFor(QString name); QString uniqueTempDirectory(); QString uniqueLayoutName(QString name); QList currentSettings(); QStringList currentLayoutsSettings(); private: QStringList m_availableActivities; QStringList m_tempDirectories; QStringList m_initLayoutPaths; QButtonGroup *m_inMemoryButtons; QButtonGroup *m_mouseSensitivityButtons; QTimer m_activityClosedTimer; bool m_blockDeleteOnReject{false}; Latte::Corona *m_corona{nullptr}; QStandardItemModel *m_model{nullptr}; Ui::SettingsDialog *ui; //! SharedLayout #settingsid, Shares #settingsid QHash m_sharesMap; QHash m_layouts; QList o_settings; QStringList o_settingsLayouts; }; } #endif // SETTINGSDIALOG_H