diff --git a/app/lattecorona.cpp b/app/lattecorona.cpp index 3dbba2e8..6696afaf 100644 --- a/app/lattecorona.cpp +++ b/app/lattecorona.cpp @@ -1,1038 +1,1055 @@ /* * 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 "importer.h" #include "lattedockadaptor.h" #include "launcherssignals.h" #include "layoutmanager.h" #include "screenpool.h" #include "indicator/factory.h" #include "layout/activelayout.h" #include "layout/genericlayout.h" +#include "layout/toplayout.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_layoutManager(new LayoutManager(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_layoutManager->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_layoutManager->memoryUsage() == Types::SingleLayout) { cleanConfig(); } qDebug() << "Latte Corona - unload: containments ..."; m_layoutManager->unload(); m_wm->deleteLater(); m_globalShortcuts->deleteLater(); m_layoutManager->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) { disconnect(m_activityConsumer, &KActivities::Consumer::serviceStatusChanged, this, &Corona::load); m_layoutManager->load(); m_activitiesStarting = false; 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_layoutManager->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_layoutManager->layoutExists(loadLayoutName)) { loadLayoutName = m_layoutManager->defaultLayoutName(); m_layoutManager->importDefaultLayout(false); } } else if (m_defaultLayoutOnStartup) { loadLayoutName = m_layoutManager->importer()->uniqueLayoutName(m_layoutManager->defaultLayoutName()); m_layoutManager->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_layoutManager->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; } LayoutManager *Corona::layoutManager() const { return m_layoutManager; } AbstractWindowInterface *Corona::wm() const { return m_wm; } Indicator::Factory *Corona::indicatorFactory() const { return m_indicatorFactory; } 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->knownIds().contains(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->knownIds().contains(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::ActiveLayout *currentLayout = m_layoutManager->currentLayout(); views = currentLayout->latteViews(); } else { - Latte::ActiveLayout *activeLayout = m_layoutManager->activeLayout(forLayout); - views = activeLayout->latteViews(); + Layout::GenericLayout *generic = m_layoutManager->activeLayout(forLayout); + + if (!generic) { + //! identify best active layout to be used for metrics + //! active layouts are always considering their top layouts + //! for their metrics + TopLayout *topLayout = m_layoutManager->topLayout(forLayout); + + if (topLayout) { + generic = topLayout->currentActiveLayout(); + } + } + + if (!generic) { + generic = m_layoutManager->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() - view->effects()->innerShadow(); // 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->knownIds().contains(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::ActiveLayout *currentLayout = m_layoutManager->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_layoutManager->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_layoutManager->hideLatteSettingsDialog(); m_layoutManager->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::ActiveLayout *currentLayout = m_layoutManager->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::ActiveLayout *currentLayout = m_layoutManager->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_layoutManager->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_layoutManager->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_layoutManager->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_layoutManager->switchToLayout(layout); } void Corona::showSettingsWindow(int page) { Types::LatteConfigPage p = Types::LayoutPage; if (page >= Types::LayoutPage && page <= Types::PreferencesPage) { p = static_cast(page); } m_layoutManager->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::ActiveLayout *currentLayout = m_layoutManager->currentLayout(); if (currentLayout) { viewType = currentLayout->latteViewType(m_contextMenuViewId); } data << QString::number((int)m_layoutManager->memoryUsage()); data << m_layoutManager->currentLayoutName(); data << QString::number((int)viewType); for(const auto &layoutName : m_layoutManager->menuLayouts()) { if (m_layoutManager->activeLayout(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(); } } diff --git a/app/layout/activelayout.cpp b/app/layout/activelayout.cpp index 21c6771b..3df64cc8 100644 --- a/app/layout/activelayout.cpp +++ b/app/layout/activelayout.cpp @@ -1,454 +1,450 @@ /* * 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 "activelayout.h" // local #include "toplayout.h" #include "../lattecorona.h" #include "../layoutmanager.h" #include "../screenpool.h" #include "../settings/universalsettings.h" #include "../view/view.h" #include "../../liblatte2/types.h" // Qt #include // KDE #include #include namespace Latte { ActiveLayout::ActiveLayout(QObject *parent, QString layoutFile, QString assignedName) : Layout::GenericLayout(parent, layoutFile, assignedName) { if (m_loadedCorrectly) { loadConfig(); init(); } } ActiveLayout::~ActiveLayout() { if (!m_layoutFile.isEmpty()) { m_layoutGroup.sync(); } } void ActiveLayout::unloadContainments() { Layout::GenericLayout::unloadContainments(); if (m_topLayout) { disconnect(m_topLayout, &Layout::GenericLayout::viewsCountChanged, this, &Layout::GenericLayout::viewsCountChanged); m_topLayout->removeActiveLayout(this); } } void ActiveLayout::init() { connect(this, &ActiveLayout::activitiesChanged, this, &ActiveLayout::saveConfig); connect(this, &ActiveLayout::disableBordersForMaximizedWindowsChanged, this, &ActiveLayout::saveConfig); connect(this, &ActiveLayout::showInMenuChanged, this, &ActiveLayout::saveConfig); connect(this, &ActiveLayout::topLayoutNameChanged, this, &ActiveLayout::saveConfig); } void ActiveLayout::initToCorona(Latte::Corona *corona) { if (GenericLayout::initToCorona(corona)) { connect(m_corona->universalSettings(), &UniversalSettings::canDisableBordersChanged, this, [&]() { if (m_corona->universalSettings()->canDisableBorders()) { kwin_setDisabledMaximizedBorders(disableBordersForMaximizedWindows()); } else { kwin_setDisabledMaximizedBorders(false); } }); if (m_corona->layoutManager()->memoryUsage() == Types::SingleLayout && m_corona->universalSettings()->canDisableBorders()) { kwin_setDisabledMaximizedBorders(disableBordersForMaximizedWindows()); } else if (m_corona->layoutManager()->memoryUsage() == Types::MultipleLayouts) { connect(m_corona->layoutManager(), &LayoutManager::currentLayoutNameChanged, this, [&]() { if (m_corona->universalSettings()->canDisableBorders() && m_corona->layoutManager()->currentLayoutName() == name()) { kwin_setDisabledMaximizedBorders(disableBordersForMaximizedWindows()); } }); } //! Request the TopLayout in case there is one and Latte is functioning in MultipleLayouts mode if (m_corona->layoutManager()->memoryUsage() == Types::MultipleLayouts && !m_topLayoutName.isEmpty()) { if (m_corona->layoutManager()->assignActiveToTopLayout(this, m_topLayoutName)) { setTopLayout(m_corona->layoutManager()->topLayout(m_topLayoutName)); } } } } bool ActiveLayout::disableBordersForMaximizedWindows() const { return m_disableBordersForMaximizedWindows; } void ActiveLayout::setDisableBordersForMaximizedWindows(bool disable) { if (m_disableBordersForMaximizedWindows == disable) { return; } m_disableBordersForMaximizedWindows = disable; kwin_setDisabledMaximizedBorders(disable); emit disableBordersForMaximizedWindowsChanged(); } bool ActiveLayout::kwin_disabledMaximizedBorders() const { //! Identify Plasma Desktop version QProcess process; process.start("kreadconfig5 --file kwinrc --group Windows --key BorderlessMaximizedWindows"); process.waitForFinished(); QString output(process.readAllStandardOutput()); output = output.remove("\n"); return (output == "true"); } void ActiveLayout::kwin_setDisabledMaximizedBorders(bool disable) { if (kwin_disabledMaximizedBorders() == disable) { return; } QString disableText = disable ? "true" : "false"; QProcess process; QString commandStr = "kwriteconfig5 --file kwinrc --group Windows --key BorderlessMaximizedWindows --type bool " + disableText; process.start(commandStr); process.waitForFinished(); QDBusInterface iface("org.kde.KWin", "/KWin", "", QDBusConnection::sessionBus()); if (iface.isValid()) { iface.call("reconfigure"); } } bool ActiveLayout::showInMenu() const { return m_showInMenu; } void ActiveLayout::setShowInMenu(bool show) { if (m_showInMenu == show) { return; } m_showInMenu = show; emit showInMenuChanged(); } QStringList ActiveLayout::activities() const { return m_activities; } void ActiveLayout::setActivities(QStringList activities) { if (m_activities == activities) { return; } m_activities = activities; emit activitiesChanged(); } QString ActiveLayout::topLayoutName() const { return m_topLayoutName; } void ActiveLayout::setTopLayoutName(QString name) { if (m_topLayoutName == name) { return; } m_topLayoutName = name; emit topLayoutNameChanged(); } void ActiveLayout::setTopLayout(TopLayout *layout) { if (m_topLayout == layout) { return; } disconnect(m_topLayout, &Layout::GenericLayout::viewsCountChanged, this, &Layout::GenericLayout::viewsCountChanged); m_topLayout = layout; connect(m_topLayout, &Layout::GenericLayout::viewsCountChanged, this, &Layout::GenericLayout::viewsCountChanged); emit viewsCountChanged(); } bool ActiveLayout::isActiveLayout() const { if (!m_corona) { return false; } ActiveLayout *activeLayout = m_corona->layoutManager()->activeLayout(m_layoutName); if (activeLayout) { return true; } else { return false; } } bool ActiveLayout::isOriginalLayout() const { return m_layoutName != MultipleLayoutsName; } void ActiveLayout::loadConfig() { m_disableBordersForMaximizedWindows = m_layoutGroup.readEntry("disableBordersForMaximizedWindows", false); m_showInMenu = m_layoutGroup.readEntry("showInMenu", false); m_topLayoutName = m_layoutGroup.readEntry("topLayoutName", QString()); m_activities = m_layoutGroup.readEntry("activities", QStringList()); emit activitiesChanged(); } //! OVERRIDES void ActiveLayout::saveConfig() { qDebug() << "active layout is saving... for layout:" << m_layoutName; m_layoutGroup.writeEntry("showInMenu", m_showInMenu); m_layoutGroup.writeEntry("disableBordersForMaximizedWindows", m_disableBordersForMaximizedWindows); m_layoutGroup.writeEntry("topLayoutName", m_topLayoutName); m_layoutGroup.writeEntry("activities", m_activities); m_layoutGroup.sync(); } const QStringList ActiveLayout::appliedActivities() { if (!m_corona) { return {}; } if (m_corona->layoutManager()->memoryUsage() == Types::SingleLayout) { return {"0"}; } else if (m_corona->layoutManager()->memoryUsage() == Types::MultipleLayouts) { if (m_activities.isEmpty()) { return m_corona->layoutManager()->orphanedActivities(); } else { return m_activities; } } else { return {"0"}; } } QList ActiveLayout::latteViews() { if (m_topLayout) { QList views = Layout::GenericLayout::latteViews(); views << m_topLayout->latteViews(); return views; } return Layout::GenericLayout::latteViews(); } int ActiveLayout::viewsCount(int screen) const { if (!m_corona) { return 0; } int views = Layout::GenericLayout::viewsCount(screen); if (m_topLayout) { QScreen *scr = m_corona->screenPool()->screenForId(screen); for (const auto view : m_topLayout->latteViews()) { if (view && view->screen() == scr && !view->containment()->destroyed()) { ++views; } } } return views; } int ActiveLayout::viewsCount(QScreen *screen) const { if (!m_corona) { return 0; } int views = Layout::GenericLayout::viewsCount(screen); if (m_topLayout) { for (const auto view : m_topLayout->latteViews()) { if (view && view->screen() == screen && !view->containment()->destroyed()) { ++views; } } } return views; } int ActiveLayout::viewsCount() const { if (!m_corona) { return 0; } int views = Layout::GenericLayout::viewsCount(); if (m_topLayout) { for (const auto view : m_topLayout->latteViews()) { if (view && view->containment() && !view->containment()->destroyed()) { ++views; } } } return views; } QList ActiveLayout::availableEdgesForView(QScreen *scr, Latte::View *forView) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } edges = Layout::GenericLayout::availableEdgesForView(scr, forView); if (m_topLayout) { for (const auto view : m_topLayout->latteViews()) { //! make sure that availabe edges takes into account only views that should be excluded, //! this is why the forView should not be excluded if (view && view != forView && view->positioner()->currentScreenName() == scr->name()) { edges.removeOne(view->location()); } } } return edges; } QList ActiveLayout::freeEdges(QScreen *scr) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } edges = Layout::GenericLayout::freeEdges(scr); if (m_topLayout) { for (const auto view : m_topLayout->latteViews()) { if (view && view->positioner()->currentScreenName() == scr->name()) { edges.removeOne(view->location()); } } } return edges; } QList ActiveLayout::freeEdges(int screen) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } edges = Layout::GenericLayout::freeEdges(screen); QScreen *scr = m_corona->screenPool()->screenForId(screen); if (m_topLayout) { for (const auto view : m_topLayout->latteViews()) { if (view && scr && view->positioner()->currentScreenName() == scr->name()) { edges.removeOne(view->location()); } } } return edges; } QList ActiveLayout::sortedLatteViews(QList views) { - QList combined = latteViews(); + QList vws = latteViews(); - if (m_topLayout) { - combined << m_topLayout->latteViews(); - } - - return Layout::GenericLayout::sortedLatteViews(combined); + return Layout::GenericLayout::sortedLatteViews(vws); } QList ActiveLayout::viewsWithPlasmaShortcuts() { QList combined = Layout::GenericLayout::viewsWithPlasmaShortcuts(); if (m_topLayout) { combined << m_topLayout->viewsWithPlasmaShortcuts(); } return combined; } void ActiveLayout::syncLatteViewsToScreens(Layout::ViewsMap *occupiedMap) { if (m_topLayout) { Layout::ViewsMap map = m_topLayout->validViewsMap(); Layout::GenericLayout::syncLatteViewsToScreens(&map); } else { Layout::GenericLayout::syncLatteViewsToScreens(); } } } diff --git a/app/layout/toplayout.cpp b/app/layout/toplayout.cpp index 44383e88..1e7773cc 100644 --- a/app/layout/toplayout.cpp +++ b/app/layout/toplayout.cpp @@ -1,218 +1,239 @@ /* * 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 "toplayout.h" // local #include "activelayout.h" #include "../lattecorona.h" +#include "../layoutmanager.h" #include "../screenpool.h" #include "../view/view.h" namespace Latte { TopLayout::TopLayout(ActiveLayout *assigned, QObject *parent, QString layoutFile, QString layoutName) : Layout::GenericLayout (parent, layoutFile, layoutName) { initToCorona(assigned->corona()); + + connect(m_corona->layoutManager(), &LayoutManager::currentLayoutNameChanged, this, &TopLayout::updateLastUsedActiveLayout); + addActiveLayout(assigned); + updateLastUsedActiveLayout(); } TopLayout::~TopLayout() { } bool TopLayout::isCurrent() const { for (const auto &layout : m_activeLayouts) { if (layout->isCurrent()) { return true; } } return false; } const QStringList TopLayout::appliedActivities() { if (!m_corona) { return {}; } QStringList activities; for (const auto &layout : m_activeLayouts) { activities << layout->appliedActivities(); } return activities; } +void TopLayout::updateLastUsedActiveLayout() +{ + for (const auto &layout : m_activeLayouts) { + if (layout->isCurrent()) { + m_lastUsedActiveLayout = layout->name(); + break; + } + } +} + ActiveLayout *TopLayout::currentActiveLayout() const { + //! first the current active one for (const auto &layout : m_activeLayouts) { if (layout->isCurrent()) { return layout; } } + //! the last used + for (const auto &layout : m_activeLayouts) { + if (layout->name() == m_lastUsedActiveLayout) { + return layout; + } + } + return nullptr; } void TopLayout::addActiveLayout(ActiveLayout *layout) { if (layout != nullptr && !m_activeLayouts.contains(layout)) { m_activeLayouts.append(layout); connect(layout, &GenericLayout::activitiesChanged, this, &GenericLayout::activitiesChanged); emit activitiesChanged(); emit viewsCountChanged(); updateLastUsedActivity(); } } void TopLayout::removeActiveLayout(ActiveLayout *layout) { if (m_activeLayouts.contains(layout)) { qDebug() << "TOPLAYOUT <" << name() << "> : Removing active layout, " << layout->name(); m_activeLayouts.removeAll(layout); disconnect(layout, &GenericLayout::activitiesChanged, this, &GenericLayout::activitiesChanged); emit activitiesChanged(); //! viewsCount signal is not needed to be trigerred here because //! in such case the views number has not been changed for the rest //! active layouts } } //! OVERRIDE int TopLayout::viewsCount(int screen) const { if (!m_corona) { return 0; } ActiveLayout *current = currentActiveLayout(); if (current) { return current->viewsCount(screen); } return Layout::GenericLayout::viewsCount(screen); } int TopLayout::viewsCount(QScreen *screen) const { if (!m_corona) { return 0; } ActiveLayout *current = currentActiveLayout(); if (current) { return current->viewsCount(screen); } return Layout::GenericLayout::viewsCount(screen);; } int TopLayout::viewsCount() const { if (!m_corona) { return 0; } ActiveLayout *current = currentActiveLayout(); if (current) { return current->viewsCount(); } return Layout::GenericLayout::viewsCount(); } QList TopLayout::availableEdgesForView(QScreen *scr, Latte::View *forView) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } return Layout::GenericLayout::availableEdgesForView(scr, forView); } QList TopLayout::freeEdges(QScreen *scr) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } ActiveLayout *current = currentActiveLayout(); if (current) { return current->freeEdges(scr); } return Layout::GenericLayout::freeEdges(scr); } QList TopLayout::freeEdges(int screen) const { using Plasma::Types; QList edges{Types::BottomEdge, Types::LeftEdge, Types::TopEdge, Types::RightEdge}; if (!m_corona) { return edges; } ActiveLayout *current = currentActiveLayout(); if (current) { return current->freeEdges(screen); } return Layout::GenericLayout::freeEdges(screen); } QList TopLayout::sortedLatteViews(QList views) { - QList combined = latteViews(); - ActiveLayout *current = currentActiveLayout(); if (current) { - return current->latteViews(); + return current->sortedLatteViews(); } return Layout::GenericLayout::sortedLatteViews(); } } diff --git a/app/layout/toplayout.h b/app/layout/toplayout.h index da505dcf..7c8495e2 100644 --- a/app/layout/toplayout.h +++ b/app/layout/toplayout.h @@ -1,80 +1,83 @@ /* * 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 TOPLAYOUT_H #define TOPLAYOUT_H // local #include "genericlayout.h" // Qt #include namespace Latte { class ActiveLayout; } namespace Latte { //! TopLayout is a layout that exists only as long as it belongs to one or //! more ActiveLayout(s). It is a layer above an active or more layouts and can //! be used from ActiveLayouts to share Latte:View(s) . Much of its functionality //! is provided by the ActiveLayouts it belongs to. For example the activities //! that its views should be shown is identified only from the active layouts //! it belongs to class TopLayout : public Layout::GenericLayout { public: TopLayout(ActiveLayout *assigned, QObject *parent, QString layoutFile, QString layoutName = QString()); ~TopLayout() override; const QStringList appliedActivities(); + ActiveLayout *currentActiveLayout() const; //! OVERRIDE GeneralLayout implementations bool isCurrent() const override; int viewsCount(int screen) const override; int viewsCount(QScreen *screen) const override; int viewsCount() const override; //! Available edges for specific view in that screen QList availableEdgesForView(QScreen *scr, Latte::View *forView) const override; //! All free edges in that screen QList freeEdges(QScreen *scr) const override; QList freeEdges(int screen) const override; QList sortedLatteViews(QList views = QList()) override; public slots: void addActiveLayout(ActiveLayout *layout); void removeActiveLayout(ActiveLayout *layout); -private: - ActiveLayout *currentActiveLayout() const; +private slots: + void updateLastUsedActiveLayout(); private: + QString m_lastUsedActiveLayout; + QList m_activeLayouts; }; } #endif //TOPLAYOUT_H diff --git a/shell/package/contents/configuration/LatteDockConfiguration.qml b/shell/package/contents/configuration/LatteDockConfiguration.qml index b19fc8ee..12cef454 100644 --- a/shell/package/contents/configuration/LatteDockConfiguration.qml +++ b/shell/package/contents/configuration/LatteDockConfiguration.qml @@ -1,638 +1,638 @@ /* * 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 . */ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import QtQuick.Window 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.components 3.0 as PlasmaComponents3 import org.kde.plasma.extras 2.0 as PlasmaExtras import QtQuick.Controls.Styles.Plasma 2.0 as Styles import org.kde.plasma.plasmoid 2.0 import org.kde.kquickcontrolsaddons 2.0 as KQuickControlAddons import org.kde.latte 0.2 as Latte import org.kde.latte.components 1.0 as LatteComponents import "pages" as Pages import "../controls" as LatteExtraControls FocusScope { id: dialog readonly property bool basicLevel: viewConfig.complexity === Latte.Types.BasicSettings readonly property bool advancedLevel: viewConfig.complexity === Latte.Types.AdvancedSettings readonly property bool expertLevel: viewConfig.complexity === Latte.Types.ExpertSettings readonly property bool highLevel: advancedLevel || expertLevel //! max size based on screen resolution //! TODO: if we can access availableScreenGeometry.height this can be improved, currently //! we use 100px. or 50px. in order to give space for othe views to be shown and to have also //! some space around the settings window property int maxHeight: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? viewConfig.availableScreenGeometry.height - (latteView.editThickness - latteView.normalThickness) - 16 : viewConfig.availableScreenGeometry.height - 2 * units.largeSpacing property int maxWidth: 0.6 * latteView.screenGeometry.width //! propose size based on font size property int proposedWidth: 0.84 * proposedHeight + units.smallSpacing * 2 property int proposedHeight: 36 * theme.mSize(theme.defaultFont).height //! user set scales based on its preference, e.g. 96% of the proposed size property int userScaleWidth: plasmoid.configuration.windowWidthScale property int userScaleHeight: plasmoid.configuration.windowHeightScale //! chosen size to be applied, if the user has set or not a different scale for the settings window property int chosenWidth: userScaleWidth !== 100 ? (userScaleWidth/100) * proposedWidth : proposedWidth property int chosenHeight: userScaleHeight !== 100 ? (userScaleHeight/100) * heightLevel * proposedHeight : heightLevel * proposedHeight readonly property real heightLevel: (dialog.expertLevel ? 100 : 1) onHeightChanged: viewConfig.syncGeometry(); //! applied size in order to not be out of boundaries //! width can be between 200px - maxWidth //! height can be between 400px - maxHeight property int appliedWidth: Math.min(maxWidth, Math.max(200, chosenWidth)) property int appliedHeight: Math.min(maxHeight, Math.max(400, chosenHeight)) width: appliedWidth height: appliedHeight Layout.minimumWidth: width Layout.minimumHeight: height LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true readonly property bool viewIsPanel: latteView.type === Latte.Types.PanelView property bool panelIsVertical: plasmoid.formFactor === PlasmaCore.Types.Vertical property int subGroupSpacing: units.largeSpacing + units.smallSpacing * 1.5 property color bC: theme.backgroundColor property color transparentBackgroundColor: Qt.rgba(bC.r, bC.g, bC.b, 0.7) onHighLevelChanged: { //! switch to appearancePage when effectsPage becomes hidden because //! advancedLevel was disabled by the user if (!highLevel && tabGroup.currentTab === effectsPage) { tabGroup.currentTab = appearancePage; tabBar.currentTab = appearanceTabBtn; } } PlasmaCore.FrameSvgItem{ anchors.fill: parent imagePath: "dialogs/background" enabledBorders: viewConfig.enabledBorders } MouseArea{ id: backgroundMouseArea anchors.fill: parent hoverEnabled: true property bool blockWheel: false property bool wheelTriggeredOnce: false property int scaleStep: 4 onContainsMouseChanged: { if (!containsMouse) { wheelTriggeredOnce = false; } } onWheel: { if (blockWheel || !(wheel.modifiers & Qt.MetaModifier)){ return; } blockWheel = true; wheelTriggeredOnce = true; scrollDelayer.start(); var angle = wheel.angleDelta.y / 8; //positive direction if (angle > 12) { plasmoid.configuration.windowWidthScale = plasmoid.configuration.windowWidthScale + scaleStep; plasmoid.configuration.windowHeightScale = plasmoid.configuration.windowHeightScale + scaleStep; viewConfig.syncGeometry(); //negative direction } else if (angle < -12) { plasmoid.configuration.windowWidthScale = plasmoid.configuration.windowWidthScale - scaleStep; plasmoid.configuration.windowHeightScale = plasmoid.configuration.windowHeightScale - scaleStep; viewConfig.syncGeometry(); } } } PlasmaComponents.Label{ anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter text: i18nc("view settings window scale","Window scale at %0%").arg(userScaleWidth) visible: backgroundMouseArea.containsMouse && backgroundMouseArea.wheelTriggeredOnce } //! A timer is needed in order to handle also touchpads that probably //! send too many signals very fast. This way the signals per sec are limited. //! The user needs to have a steady normal scroll in order to not //! notice a annoying delay Timer{ id: scrollDelayer interval: 75 onTriggered: backgroundMouseArea.blockWheel = false; } ColumnLayout { id: content Layout.minimumWidth: width Layout.minimumHeight: calculatedHeight Layout.preferredWidth: width Layout.preferredHeight: calculatedHeight width: (dialog.appliedWidth - units.smallSpacing * 2) anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top spacing: units.smallSpacing property int calculatedHeight: header.height + headerSpacer.height+ tabBar.height + pagesBackground.height + actionButtons.height + spacing * 3 Keys.onPressed: { if (event.key === Qt.Key_Escape) { viewConfig.hideConfigWindow(); } } Component.onCompleted: forceActiveFocus(); RowLayout { id: header Layout.fillWidth: true spacing: 0 Item { Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillWidth: false Layout.topMargin: units.smallSpacing Layout.preferredWidth: width Layout.preferredHeight: height width: Qt.application.layoutDirection !== Qt.RightToLeft ? logo.width + latteTxt.width + units.smallSpacing : logo.width + units.smallSpacing height: logo.height LatteComponents.ToolTip{ parent: logo text: i18n("Open Latte settings window") visible: aboutMouseArea.containsMouse delay: 7 * units.longDuration } Latte.IconItem { id: logo width: Math.round(1.4 * latteTxtMetrics.font.pixelSize) height: width smooth: true source: "latte-dock" // animated: true usesPlasmaTheme: false active: aboutMouseArea.containsMouse } PlasmaComponents.Label { id: latteTxtMetrics text: "Latte" width: 0 font.pointSize: 2 * theme.defaultFont.pointSize visible: false } PlasmaCore.SvgItem{ id: latteTxt width: 2.2 * height height: 0.4 * latteTxtMetrics.font.pixelSize visible: Qt.application.layoutDirection !== Qt.RightToLeft anchors.left: logo.right anchors.verticalCenter: logo.verticalCenter svg: PlasmaCore.Svg{ imagePath: universalSettings.trademarkIconPath() } } MouseArea { id: aboutMouseArea acceptedButtons: Qt.LeftButton anchors.fill: parent hoverEnabled: true readonly property int preferencesPage: Latte.Types.PreferencesPage onClicked: layoutManager.showLatteSettingsDialog(preferencesPage) } Rectangle { anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 2 width: parent.width + 4 height: 2 color: theme.highlightColor visible: aboutMouseArea.containsMouse } } Item{ id: headerSpacer Layout.minimumHeight: complexitySettings.height + 2*units.smallSpacing } ColumnLayout { PlasmaComponents.ToolButton { id: pinButton Layout.fillWidth: false Layout.fillHeight: false Layout.preferredWidth: width Layout.preferredHeight: height Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.bottomMargin: units.smallSpacing //!avoid editMode box shadow Layout.topMargin: units.smallSpacing * 2 Layout.rightMargin: units.smallSpacing iconSource: "window-pin" checkable: true width: Math.round(units.gridUnit * 1.25) height: width property bool inStartup: true onClicked: { plasmoid.configuration.configurationSticker = checked viewConfig.setSticker(checked) } Component.onCompleted: { checked = plasmoid.configuration.configurationSticker viewConfig.setSticker(plasmoid.configuration.configurationSticker) } } RowLayout { id: complexitySettings Layout.fillWidth: true Layout.rightMargin: units.smallSpacing * 2 Layout.alignment: Qt.AlignRight | Qt.AlignTop function switchMode() { if (dialog.basicLevel || dialog.advancedLevel) { viewConfig.complexity = Latte.Types.ExpertSettings; } else if (dialog.expertLevel) { viewConfig.complexity = Latte.Types.BasicSettings; } } PlasmaComponents.Label { Layout.fillWidth: true Layout.alignment: Qt.AlignRight } PlasmaComponents.Label { id: complexityLbl Layout.alignment: Qt.AlignRight // opacity: dialog.basicLevel ? basicOpacity : 1 //! TODO: the term here is not accurate because the expert settings mode //! is used currently. In the future this term will be rethought if //! it must remain or be changed text: i18nc("advanced settings", "Advanced") readonly property real textColorBrightness: colorBrightness(theme.textColor) readonly property real basicOpacity: textColorBrightness > 127 ? 0.7 : 0.3 color: { if (dialog.basicLevel) { return textColorBrightness > 127 ? Qt.darker(theme.textColor, 1.4) : Qt.lighter(theme.textColor, 2.8); } return theme.textColor; } function colorBrightness(color) { return colorBrightnessFromRGB(color.r * 255, color.g * 255, color.b * 255); } // formula for brightness according to: // https://www.w3.org/TR/AERT/#color-contrast function colorBrightnessFromRGB(r, g, b) { return (r * 299 + g * 587 + b * 114) / 1000 } MouseArea { id: complexityMouseArea anchors.fill: parent hoverEnabled: true onClicked: { complexitySettings.switchMode(); } } } LatteComponents.Switch { id: complexitySwitch checked: dialog.expertLevel onPressedChanged: { if(pressed){ complexitySettings.switchMode(); } } } } } } PlasmaComponents.TabBar { id: tabBar Layout.fillWidth: true Layout.maximumWidth: (dialog.appliedWidth - units.smallSpacing * 2) PlasmaComponents.TabButton { id: behaviorTabBtn text: i18n("Behavior") tab: behaviorPage } PlasmaComponents.TabButton { id: appearanceTabBtn text: i18n("Appearance") tab: appearancePage } PlasmaComponents.TabButton { id: effectsTabBtn text: i18n("Effects") tab: effectsPage visible: dialog.highLevel } PlasmaComponents.TabButton { id: tasksTabBtn text: i18n("Tasks") tab: tasksPage visible: latteView.latteTasksArePresent } } Rectangle { id: pagesBackground Layout.fillWidth: true Layout.fillHeight: false Layout.minimumWidth: dialog.appliedWidth - units.smallSpacing * 4 Layout.minimumHeight: height Layout.maximumHeight: height width: dialog.appliedWidth - units.smallSpacing * 3 height: availableFreeHeight + units.smallSpacing * 4 color: transparentBackgroundColor border.width: 1 border.color: theme.backgroundColor //fix the height binding loop when showing the configuration window property int availableFreeHeight: dialog.appliedHeight - header.height - headerSpacer.height - tabBar.height - actionButtons.height - 2 * units.smallSpacing PlasmaExtras.ScrollArea { id: scrollArea anchors.fill: parent verticalScrollBarPolicy: Qt.ScrollBarAsNeeded horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff flickableItem.flickableDirection: Flickable.VerticalFlick PlasmaComponents.TabGroup { id: tabGroup width: currentTab.Layout.maximumWidth height: currentTab.Layout.maximumHeight Pages.BehaviorConfig { id: behaviorPage } Pages.AppearanceConfig { id: appearancePage } Pages.EffectsConfig { id: effectsPage } Pages.TasksConfig { id: tasksPage } } } } RowLayout { id: actionButtons Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom spacing: units.largeSpacing Connections{ target: latteView.managedLayout onViewsCountChanged: actionButtons.updateEnabled(); } function updateEnabled() { var screenFreeEdges = latteView.managedLayout.qmlFreeEdges(latteView.positioner.currentScreenId); - actionsComboBtn.buttonEnabled = latteView.managedLayout.viewsCount<4 && screenFreeEdges.length > 0 + actionsComboBtn.buttonEnabled = screenFreeEdges.length > 0; if (actionsModel.count > 0) { actionsModel.get(0).enabled = actionsComboBtn.buttonEnabled; } removeView.enabled = latteView.managedLayout.viewsCount>1 /*&& !(latteView.managedLayout.viewsWithTasks()===1 && latteView.tasksPresent())*/ } LatteComponents.ComboBoxButton { id: actionsComboBtn Layout.fillWidth: true implicitWidth: removeView.implicitWidth implicitHeight: removeView.implicitHeight buttonEnabled: true buttonText: i18n("New Dock") buttonIconSource: "list-add" buttonToolTip: i18n("Add a new dock") comboBoxEnabled: true comboBoxBlankSpaceForEmptyIcons: true comboBoxPopUpAlignRight: Qt.application.layoutDirection === Qt.RightToLeft comboBoxEnabledRole: "enabled" comboBoxTextRole: "name" comboBoxIconRole: "icon" comboBoxMinimumPopUpWidth: actionsModel.count > 1 ? dialog.width / 2 : 150 property var activeLayoutsNames; Component.onCompleted: { comboBox.model = actionsModel; actionButtons.updateEnabled(); } ListModel { id: actionsModel } Connections{ target: actionsComboBtn.comboBox Component.onCompleted:{ actionsComboBtn.addModel(); actionButtons.updateEnabled(); } onActivated: { if (index==0) { latteView.copyView(); } else if (index>=1) { latteView.positioner.hideDockDuringMovingToLayout(activeLayoutsNames[index-1]); } actionsComboBtn.comboBox.currentIndex = -1; } onEnabledChanged: { if (enabled) { actionsComboBtn.addModel(); } else { actionsComboBtn.emptyModel(); } } } Connections{ target: actionsComboBtn.button onClicked: latteView.managedLayout.addNewView(); } Connections{ target: latteView onTypeChanged: actionsComboBtn.updateCopyText() } function addModel() { actionsModel.clear(); var copy = {actionId: 'copy:', enabled: true, name: '', icon: 'edit-copy'}; actionsModel.append(copy); updateCopyText(); var tempActiveLayouts = layoutManager.activeLayoutsNames(); var currentLayoutIndex = tempActiveLayouts.indexOf(latteView.managedLayout.name); tempActiveLayouts.splice(currentLayoutIndex,1); if (tempActiveLayouts.length > 0) { activeLayoutsNames = tempActiveLayouts; var iconArrow = Qt.application.layoutDirection === Qt.RightToLeft ? 'arrow-left' : 'arrow-right'; for(var i=0; i 1 ? 1 : 0 tooltip: i18n("Remove current dock") onClicked: latteView.removeView() } PlasmaComponents.Button { id: closeButton Layout.fillWidth: true text: i18n("Close") iconSource: "dialog-close" tooltip: i18n("Close settings window") onClicked: viewConfig.hideConfigWindow(); } } } //! HACK FOR X11 environments //! show an inner shadow similar to Latte::View editShadow in order to //! not break the visual user experience LatteExtraControls.InnerShadow{ width: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? dialog.width + 2*shadowSize : shadowSize height: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? shadowSize : dialog.height + 2*shadowSize shadowSize: latteView.effects.editShadow shadowOpacity: Math.max(0.35, maxOpacity) shadowDirection: plasmoid.location visible: !Latte.WindowSystem.isPlatformWayland && Latte.WindowSystem.compositingActive && latteView.effects.settingsMaskSubtracted readonly property real maxOpacity: Latte.WindowSystem.compositingActive && !plasmoid.configuration.inConfigureAppletsMode ? plasmoid.configuration.editBackgroundOpacity : 1 } }