diff --git a/app/layouts/synchronizer.cpp b/app/layouts/synchronizer.cpp index 55c429a4..49ab242d 100644 --- a/app/layouts/synchronizer.cpp +++ b/app/layouts/synchronizer.cpp @@ -1,987 +1,987 @@ /* * 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 // Plasma #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; } Latte::View *Synchronizer::viewForContainment(Plasma::Containment *containment) { for (auto layout : m_centralLayouts) { Latte::View *view = layout->viewForContainment(containment); if (view) { return view; } } for (auto layout : m_sharedLayouts) { Latte::View *view = layout->viewForContainment(containment); if (view) { return view; } } 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 + //! IMPORTANT: DON'T 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, QStringList &deprecatedShares) { if (m_manager->memoryUsage() != Types::MultipleLayouts) { return; } qDebug() << " CURRENT SHARES MAP :: " << sharesMap; qDebug() << " DEPRECATED SHARES :: " << deprecatedShares; QHash unassign; //! CENTRAL (inactive) layouts that must update their SharedLayoutName because they //! were unassigned from a Shared Layout for (const auto &share : deprecatedShares) { CentralLayout *central = centralLayout(share); if (!central) { //! Central Layout is not loaded CentralLayout centralInStorage(this, Importer::layoutFilePath(share)); centralInStorage.setSharedLayoutName(QString()); } } //! 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; } } } else { //! Central Layout is not loaded CentralLayout centralInStorage(this, Importer::layoutFilePath(centralName)); centralInStorage.setSharedLayoutName(i.key()); } } } //! 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/plasma/extended/theme.cpp b/app/plasma/extended/theme.cpp index 1792e3fe..2bc33330 100644 --- a/app/plasma/extended/theme.cpp +++ b/app/plasma/extended/theme.cpp @@ -1,632 +1,632 @@ /* * Copyright 2018 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 "theme.h" // local #include "lattecorona.h" #include "../../layouts/importer.h" #include "../../view/panelshadows_p.h" #include "../../wm/schemecolors.h" #include "../../../liblatte2/commontools.h" // Qt #include #include #include // KDE #include #include #include // X11 #include #define DEFAULTCOLORSCHEME "default.colors" #define REVERSEDCOLORSCHEME "reversed.colors" namespace Latte { namespace PlasmaExtended { Theme::Theme(KSharedConfig::Ptr config, QObject *parent) : QObject(parent), m_themeGroup(KConfigGroup(config, QStringLiteral("PlasmaThemeExtended"))) { m_corona = qobject_cast(parent); //! compositing tracking if (KWindowSystem::isPlatformWayland()) { //! TODO: Wayland compositing active m_compositing = true; } else { connect(KWindowSystem::self(), &KWindowSystem::compositingChanged , this, [&](bool enabled) { if (m_compositing == enabled) return; m_compositing = enabled; emit compositingChanged(); }); m_compositing = KWindowSystem::compositingActive(); } //! loadConfig(); connect(this, &Theme::compositingChanged, this, &Theme::roundnessChanged); connect(this, &Theme::outlineWidthChanged, this, &Theme::saveConfig); connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::hasShadowChanged); connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::load); connect(&m_theme, &Plasma::Theme::themeChanged, this, &Theme::themeChanged); } void Theme::load() { loadThemePaths(); loadRoundness(); } Theme::~Theme() { saveConfig(); m_defaultScheme->deleteLater(); m_reversedScheme->deleteLater(); } bool Theme::hasShadow() const { return PanelShadows::self()->enabled(); } bool Theme::isLightTheme() const { return m_isLightTheme; } bool Theme::isDarkTheme() const { return !m_isLightTheme; } int Theme::bottomEdgeRoundness() const { return m_bottomEdgeRoundness; } int Theme::leftEdgeRoundness() const { return m_leftEdgeRoundness; } int Theme::topEdgeRoundness() const { return m_topEdgeRoundness; } int Theme::rightEdgeRoundness() const { return m_rightEdgeRoundness; } int Theme::outlineWidth() const { return m_outlineWidth; } void Theme::setOutlineWidth(int width) { if (m_outlineWidth == width) { return; } m_outlineWidth = width; emit outlineWidthChanged(); } float Theme::bottomEdgeMaxOpacity() const { return m_bottomEdgeMaxOpacity; } float Theme::leftEdgeMaxOpacity() const { return m_leftEdgeMaxOpacity; } float Theme::topEdgeMaxOpacity() const { return m_topEdgeMaxOpacity; } float Theme::rightEdgeMaxOpacity() const { return m_rightEdgeMaxOpacity; } WindowSystem::SchemeColors *Theme::defaultTheme() const { return m_defaultScheme; } WindowSystem::SchemeColors *Theme::lightTheme() const { return m_isLightTheme ? m_defaultScheme : m_reversedScheme; } WindowSystem::SchemeColors *Theme::darkTheme() const { return !m_isLightTheme ? m_defaultScheme : m_reversedScheme; } void Theme::setOriginalSchemeFile(const QString &file) { if (m_originalSchemePath == file) { return; } m_originalSchemePath = file; qDebug() << "plasma theme original colors ::: " << m_originalSchemePath; updateDefaultScheme(); updateReversedScheme(); loadThemeLightness(); emit themeChanged(); } //! WM records need to be updated based on the colors that //! plasma will use in order to be consistent. Such an example //! are the Breeze color schemes that have different values for //! WM and the plasma theme records void Theme::updateDefaultScheme() { QString defaultFilePath = m_extendedThemeDir.path() + "/" + DEFAULTCOLORSCHEME; if (QFileInfo(defaultFilePath).exists()) { QFile(defaultFilePath).remove(); } QFile(m_originalSchemePath).copy(defaultFilePath); m_defaultSchemePath = defaultFilePath; updateDefaultSchemeValues(); if (m_defaultScheme) { disconnect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness); m_defaultScheme->deleteLater(); } m_defaultScheme = new WindowSystem::SchemeColors(this, m_defaultSchemePath, true); connect(m_defaultScheme, &WindowSystem::SchemeColors::colorsChanged, this, &Theme::loadThemeLightness); qDebug() << "plasma theme default colors ::: " << m_defaultSchemePath; } void Theme::updateDefaultSchemeValues() { //! update WM values based on original scheme KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath); KSharedConfigPtr defaultPtr = KSharedConfig::openConfig(m_defaultSchemePath); if (originalPtr && defaultPtr) { KConfigGroup originalViewGroup(originalPtr, "Colors:View"); KConfigGroup defaultWMGroup(defaultPtr, "WM"); defaultWMGroup.writeEntry("activeBackground", originalViewGroup.readEntry("BackgroundNormal", QColor())); defaultWMGroup.writeEntry("activeForeground", originalViewGroup.readEntry("ForegroundNormal", QColor())); defaultWMGroup.sync(); } } void Theme::updateReversedScheme() { QString reversedFilePath = m_extendedThemeDir.path() + "/" + REVERSEDCOLORSCHEME; if (QFileInfo(reversedFilePath).exists()) { QFile(reversedFilePath).remove(); } QFile(m_originalSchemePath).copy(reversedFilePath); m_reversedSchemePath = reversedFilePath; updateReversedSchemeValues(); if (m_reversedScheme) { m_reversedScheme->deleteLater(); } m_reversedScheme = new WindowSystem::SchemeColors(this, m_reversedSchemePath, true); qDebug() << "plasma theme reversed colors ::: " << m_reversedSchemePath; } void Theme::updateReversedSchemeValues() { //! reverse values based on original scheme KSharedConfigPtr originalPtr = KSharedConfig::openConfig(m_originalSchemePath); KSharedConfigPtr reversedPtr = KSharedConfig::openConfig(m_reversedSchemePath); if (originalPtr && reversedPtr) { for (const auto &groupName : reversedPtr->groupList()) { if (groupName != "Colors:Button" && groupName != "Colors:Selection") { KConfigGroup reversedGroup(reversedPtr, groupName); if (reversedGroup.keyList().contains("BackgroundNormal") && reversedGroup.keyList().contains("ForegroundNormal")) { //! reverse usual text/background values KConfigGroup originalGroup(originalPtr, groupName); reversedGroup.writeEntry("BackgroundNormal", originalGroup.readEntry("ForegroundNormal", QColor())); reversedGroup.writeEntry("ForegroundNormal", originalGroup.readEntry("BackgroundNormal", QColor())); reversedGroup.sync(); } } } //! update WM group KConfigGroup reversedWMGroup(reversedPtr, "WM"); KConfigGroup originalViewGroup(originalPtr, "Colors:View"); if (reversedWMGroup.keyList().contains("activeBackground") && reversedWMGroup.keyList().contains("activeForeground") && reversedWMGroup.keyList().contains("inactiveBackground") && reversedWMGroup.keyList().contains("inactiveForeground")) { //! reverse usual wm titlebar values KConfigGroup originalGroup(originalPtr, "WM"); reversedWMGroup.writeEntry("activeBackground", originalViewGroup.readEntry("ForegroundNormal", QColor())); reversedWMGroup.writeEntry("activeForeground", originalViewGroup.readEntry("BackgroundNormal", QColor())); reversedWMGroup.writeEntry("inactiveBackground", originalGroup.readEntry("inactiveForeground", QColor())); reversedWMGroup.writeEntry("inactiveForeground", originalGroup.readEntry("inactiveBackground", QColor())); reversedWMGroup.sync(); } if (reversedWMGroup.keyList().contains("activeBlend") && reversedWMGroup.keyList().contains("inactiveBlend")) { KConfigGroup originalGroup(originalPtr, "WM"); reversedWMGroup.writeEntry("activeBlend", originalGroup.readEntry("inactiveBlend", QColor())); reversedWMGroup.writeEntry("inactiveBlend", originalGroup.readEntry("activeBlend", QColor())); reversedWMGroup.sync(); } //! update scheme name QString originalSchemeName = WindowSystem::SchemeColors::schemeName(m_originalSchemePath); KConfigGroup generalGroup(reversedPtr, "General"); generalGroup.writeEntry("Name", originalSchemeName + "_reversed"); generalGroup.sync(); } } int Theme::roundness(const QImage &svgImage, Plasma::Types::Location edge) { int discovRow = (edge == Plasma::Types::TopEdge ? svgImage.height()-1 : 0); int discovCol = (edge == Plasma::Types::LeftEdge ? svgImage.width()-1 : 0); int round{0}; int maxOpacity = qMin(qAlpha(svgImage.pixel(49,0)), 200); if (edge == Plasma::Types::BottomEdge) { m_bottomEdgeMaxOpacity = (float)maxOpacity / (float)255; } else if (edge == Plasma::Types::LeftEdge) { m_leftEdgeMaxOpacity = (float)maxOpacity / (float)255; } else if (edge == Plasma::Types::TopEdge) { m_topEdgeMaxOpacity = (float)maxOpacity / (float)255; } else if (edge == Plasma::Types::RightEdge) { m_rightEdgeMaxOpacity = (float)maxOpacity / (float)255; } if (edge == Plasma::Types::BottomEdge || edge == Plasma::Types::RightEdge || edge == Plasma::Types::TopEdge) { //! TOPLEFT corner //! first LEFT pixel found QRgb *line = (QRgb *)svgImage.scanLine(discovRow); for (int col=0; col<50; ++col) { QRgb pixelData = line[col]; if (qAlpha(pixelData) < maxOpacity) { discovCol++; round++; } else { break; } } } else if (edge == Plasma::Types::LeftEdge) { //! it should be TOPRIGHT corner in that case //! first RIGHT pixel found QRgb *line = (QRgb *)svgImage.scanLine(discovRow); for (int col=99; col>50; --col) { QRgb pixelData = line[col]; if (qAlpha(pixelData) < maxOpacity) { discovCol--; round++; } else { break; } } } - //! this needs investigation (the x2) I dont know if it is really needed + //! this needs investigation (the x2) I don't know if it is really needed //! but it gives me the impression that returns better results return round; ///**2*/; } void Theme::loadCompositingRoundness() { Plasma::FrameSvg *svg = new Plasma::FrameSvg(this); svg->setImagePath(QStringLiteral("widgets/panel-background")); svg->setEnabledBorders(Plasma::FrameSvg::AllBorders); svg->resizeFrame(QSize(100,100)); //! New approach QPixmap pxm = svg->framePixmap(); //! bottom roundness if (svg->hasElementPrefix("south")) { svg->setElementPrefix("south"); pxm = svg->framePixmap(); } else { svg->setElementPrefix(""); pxm = svg->framePixmap(); } m_bottomEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::BottomEdge); //! left roundness if (svg->hasElementPrefix("west")) { svg->setElementPrefix("west"); pxm = svg->framePixmap(); } else { svg->setElementPrefix(""); pxm = svg->framePixmap(); } m_leftEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::LeftEdge); //! top roundness if (svg->hasElementPrefix("north")) { svg->setElementPrefix("north"); pxm = svg->framePixmap(); } else { svg->setElementPrefix(""); pxm = svg->framePixmap(); } m_topEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::TopEdge); //! right roundness if (svg->hasElementPrefix("east")) { svg->setElementPrefix("east"); pxm = svg->framePixmap(); } else { svg->setElementPrefix(""); pxm = svg->framePixmap(); } m_rightEdgeRoundness = roundness(pxm.toImage(), Plasma::Types::RightEdge); /* qDebug() << " COMPOSITING MASK ::: " << svg->mask(); qDebug() << " COMPOSITING MASK BOUNDING RECT ::: " << svg->mask().boundingRect();*/ qDebug() << " COMPOSITING ROUNDNESS ::: " << m_bottomEdgeRoundness << " _ " << m_leftEdgeRoundness << " _ " << m_topEdgeRoundness << " _ " << m_rightEdgeRoundness; svg->deleteLater(); } void Theme::loadRoundness() { loadCompositingRoundness(); emit maxOpacityChanged(); emit roundnessChanged(); } void Theme::loadThemePaths() { m_themePath = Layouts::Importer::standardPath("plasma/desktoptheme/" + m_theme.themeName()); if (QDir(m_themePath+"/widgets").exists()) { m_themeWidgetsPath = m_themePath + "/widgets"; } else { m_themeWidgetsPath = Layouts::Importer::standardPath("plasma/desktoptheme/default/widgets"); } qDebug() << "current plasma theme ::: " << m_theme.themeName(); qDebug() << "theme path ::: " << m_themePath; qDebug() << "theme widgets path ::: " << m_themeWidgetsPath; //! clear kde connections for (auto &c : m_kdeConnections) { disconnect(c); } //! assign color schemes QString themeColorScheme = m_themePath + "/colors"; if (QFileInfo(themeColorScheme).exists()) { setOriginalSchemeFile(themeColorScheme); } else { //! when plasma theme uses the kde colors //! we track when kde color scheme is changing QString kdeSettingsFile = QDir::homePath() + "/.config/kdeglobals"; KDirWatch::self()->addFile(kdeSettingsFile); m_kdeConnections[0] = connect(KDirWatch::self(), &KDirWatch::dirty, this, [ &, kdeSettingsFile](const QString & path) { if (path == kdeSettingsFile) { this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); } }); m_kdeConnections[1] = connect(KDirWatch::self(), &KDirWatch::created, this, [ &, kdeSettingsFile](const QString & path) { if (path == kdeSettingsFile) { this->setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); } }); setOriginalSchemeFile(WindowSystem::SchemeColors::possibleSchemeFile("kdeglobals")); } //! this is probably not needed at all in order to provide full transparency for all //! plasma themes, so we disable it in order to confirm from user testing //! that it is not needed at all //parseThemeSvgFiles(); } void Theme::parseThemeSvgFiles() { QString origBackgroundSvgFile; QString curBackgroundSvgFile = m_extendedThemeDir.path()+"/widgets/panel-background.svg"; if (QFileInfo(curBackgroundSvgFile).exists()) { QDir(m_extendedThemeDir.path()+"/widgets").remove("panel-background.svg"); } if (!QDir(m_extendedThemeDir.path()+"/widgets").exists()) { QDir(m_extendedThemeDir.path()).mkdir("widgets"); } if (QFileInfo(m_themeWidgetsPath+"/panel-background.svg").exists()) { origBackgroundSvgFile = m_themeWidgetsPath+"/panel-background.svg"; QFile(origBackgroundSvgFile).copy(curBackgroundSvgFile); } else if (QFileInfo(m_themeWidgetsPath+"/panel-background.svgz").exists()) { origBackgroundSvgFile = m_themeWidgetsPath+"/panel-background.svgz"; QString tempBackFile = m_extendedThemeDir.path()+"/widgets/panel-background.svg.gz"; QFile(origBackgroundSvgFile).copy(tempBackFile); //! Identify Plasma Desktop version QProcess process; process.start("gzip -d " + tempBackFile); process.waitForFinished(); QString output(process.readAllStandardOutput()); qDebug() << "plasma theme, background extraction output ::: " << output; qDebug() << "plasma theme, original background svg file was decompressed..."; } if (QFileInfo(curBackgroundSvgFile).exists()) { qDebug() << "plasma theme, panel background ::: " << curBackgroundSvgFile; } else { qDebug() << "plasma theme, panel background ::: was not found..."; } //! Find panel-background transparency QFile svgFile(curBackgroundSvgFile); QString styleSvgStr; if (svgFile.open(QIODevice::ReadOnly)) { QTextStream in(&svgFile); bool centerIdFound{false}; bool styleFound{false}; while (!in.atEnd() && !styleFound) { QString line = in.readLine(); //! each time a rect starts then style can be reset if (line.contains("")) { break; } } svgFile.close(); } if (!styleSvgStr.isEmpty()) { int styleInd = styleSvgStr.indexOf("style="); QString cleanedStr = styleSvgStr.remove(0, styleInd+7); int endInd = cleanedStr.indexOf("\""); styleSvgStr = cleanedStr.mid(0,endInd); QStringList styleValues = styleSvgStr.split(";"); // qDebug() << "plasma theme, discovered svg style ::: " << styleValues; float opacity{1}; float fillOpacity{1}; for (QString &value : styleValues) { if (value.startsWith("opacity:")) { opacity = value.remove(0,8).toFloat(); } if (value.startsWith("fill-opacity:")) { fillOpacity = value.remove(0,13).toFloat(); } } // m_backgroundMaxOpacity = opacity * fillOpacity; // qDebug() << "plasma theme opacity :: " << m_backgroundMaxOpacity << " from : " << opacity << " * " << fillOpacity; } // emit backgroundMaxOpacityChanged(); } void Theme::loadThemeLightness() { float textColorLum = Latte::colorLumina(m_defaultScheme->textColor()); float backColorLum = Latte::colorLumina(m_defaultScheme->backgroundColor()); if (backColorLum > textColorLum) { m_isLightTheme = true; } else { m_isLightTheme = false; } if (m_isLightTheme) { qDebug() << "Plasma theme is light..."; } else { qDebug() << "Plasma theme is dark..."; } } void Theme::loadConfig() { setOutlineWidth(m_themeGroup.readEntry("outlineWidth", 1)); } void Theme::saveConfig() { m_themeGroup.writeEntry("outlineWidth", m_outlineWidth); m_themeGroup.sync(); } } } diff --git a/app/view/contextmenu.cpp b/app/view/contextmenu.cpp index 81d7d57c..4e313fb6 100644 --- a/app/view/contextmenu.cpp +++ b/app/view/contextmenu.cpp @@ -1,471 +1,471 @@ /* * Copyright 2018 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 "contextmenu.h" // local #include "view.h" #include "visibilitymanager.h" #include "../lattecorona.h" // Qt #include #include // KDE #include #include #include // Plasma #include #include #include #include #include namespace Latte { namespace ViewPart { ContextMenu::ContextMenu(Latte::View *view) : QObject(view), m_latteView(view) { } ContextMenu::~ContextMenu() { } QMenu *ContextMenu::menu() { return m_contextMenu; } void ContextMenu::menuAboutToHide() { if (!m_latteView) { return; } m_contextMenu = 0; if (!m_latteView->containment()->isUserConfiguring()) { m_latteView->visibility()->setBlockHiding(false); } emit menuChanged(); } bool ContextMenu::mousePressEvent(QMouseEvent *event) { //qDebug() << "Step -1 ..."; if (!event || !m_latteView->containment()) { return false; } //qDebug() << "Step 0..."; //even if the menu is executed synchronously, other events may be processed //by the qml incubator when plasma is loading, so we need to guard there if (m_contextMenu) { //qDebug() << "Step 0.5 ..."; m_contextMenu->close(); m_contextMenu = 0; emit menuChanged(); // PlasmaQuick::ContainmentView::mousePressEvent(event); return false; } //qDebug() << "1 ..."; QString trigger = Plasma::ContainmentActions::eventToString(event); if (trigger == "RightButton;NoModifier") { Plasma::ContainmentActions *plugin = m_latteView->containment()->containmentActions().value(trigger); if (!plugin || plugin->contextualActions().isEmpty()) { event->setAccepted(false); return false; } //qDebug() << "2 ..."; //the plugin can be a single action or a context menu //Don't have an action list? execute as single action //and set the event position as action data /*if (plugin->contextualActions().length() == 1) { QAction *action = plugin->contextualActions().at(0); action->setData(event->pos()); action->trigger(); event->accept(); return; }*/ //FIXME: very inefficient appletAt() implementation Plasma::Applet *applet = 0; bool inSystray = false; //! initialize the appletContainsMethod on the first right click if (!m_appletContainsMethod.isValid()) { updateAppletContainsMethod(); } for (const Plasma::Applet *appletTemp : m_latteView->containment()->applets()) { PlasmaQuick::AppletQuickItem *ai = appletTemp->property("_plasma_graphicObject").value(); bool appletContainsMouse = false; if (m_appletContainsMethod.isValid()) { QVariant retVal; m_appletContainsMethod.invoke(m_appletContainsMethodItem, Qt::DirectConnection, Q_RETURN_ARG(QVariant, retVal) , Q_ARG(QVariant, appletTemp->id()), Q_ARG(QVariant, event->pos())); appletContainsMouse = retVal.toBool(); } else { appletContainsMouse = ai->contains(ai->mapFromItem(m_latteView->contentItem(), event->pos())); } if (ai && ai->isVisible() && appletContainsMouse) { applet = ai->applet(); KPluginMetaData meta = applet->kPackage().metadata(); //Try to find applets inside a systray if (meta.pluginId() == "org.kde.plasma.systemtray" || meta.pluginId() == "org.nomad.systemtray") { auto systrayId = applet->config().readEntry("SystrayContainmentId"); applet = 0; inSystray = true; Plasma::Containment *cont = containmentById(systrayId.toInt()); if (cont) { for (const Plasma::Applet *appletCont : cont->applets()) { PlasmaQuick::AppletQuickItem *ai2 = appletCont->property("_plasma_graphicObject").value(); if (ai2 && ai2->isVisible() && ai2->contains(ai2->mapFromItem(m_latteView->contentItem(), event->pos()))) { applet = ai2->applet(); break; } } } break; } else { ai = 0; } } } if (!applet && !inSystray) { applet = m_latteView->containment(); } //qDebug() << "3 ..."; if (applet) { const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); //qDebug() << "3.5 ..."; if (!provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { //qDebug() << "4..."; QMenu *desktopMenu = new QMenu; //this is a workaround where Qt now creates the menu widget //in .exec before oxygen can polish it and set the following attribute desktopMenu->setAttribute(Qt::WA_TranslucentBackground); //end workaround if (desktopMenu->winId()) { desktopMenu->windowHandle()->setTransientParent(m_latteView); } desktopMenu->setAttribute(Qt::WA_DeleteOnClose); m_contextMenu = desktopMenu; //! deprecated old code that can be removed if the following plasma approach doesn't //! create any issues with context menu creation in Latte /*if (m_latteView->mouseGrabberItem()) { //workaround, this fixes for me most of the right click menu behavior m_latteView->mouseGrabberItem()->ungrabMouse(); return; }*/ //!plasma official code - //this is a workaround where Qt will fail to realise a mouse has been released + //this is a workaround where Qt will fail to realize a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [this]() { if (m_latteView->mouseGrabberItem()) { m_latteView->mouseGrabberItem()->ungrabMouse(); } }; //pre 5.8.0 QQuickWindow code is "item->grabMouse(); sendEvent(item, mouseEvent)" //post 5.8.0 QQuickWindow code is sendEvent(item, mouseEvent); item->grabMouse() if (QVersionNumber::fromString(qVersion()) > QVersionNumber(5, 8, 0)) { QTimer::singleShot(0, this, ungrabMouseHack); } else { ungrabMouseHack(); } //end workaround //!end of plasma official code(workaround) //qDebug() << "5 ..."; if (applet && applet != m_latteView->containment()) { //qDebug() << "5.3 ..."; emit applet->contextualActionsAboutToShow(); addAppletActions(desktopMenu, applet, event); } else { //qDebug() << "5.6 ..."; emit m_latteView->containment()->contextualActionsAboutToShow(); addContainmentActions(desktopMenu, event); } //this is a workaround where Qt now creates the menu widget //in .exec before oxygen can polish it and set the following attribute desktopMenu->setAttribute(Qt::WA_TranslucentBackground); //end workaround QPoint pos = event->globalPos(); if (applet) { //qDebug() << "6 ..."; desktopMenu->adjustSize(); if (m_latteView->screen()) { const QRect scr = m_latteView->screen()->geometry(); int smallStep = 3; int x = event->globalPos().x() + smallStep; int y = event->globalPos().y() + smallStep; //qDebug()<globalPos().x() > scr.center().x()) { x = event->globalPos().x() - desktopMenu->width() - smallStep; } if (event->globalPos().y() > scr.center().y()) { y = event->globalPos().y() - desktopMenu->height() - smallStep; } pos = QPoint(x, y); } } //qDebug() << "7..."; if (desktopMenu->isEmpty()) { //qDebug() << "7.5 ..."; delete desktopMenu; event->accept(); return false; } connect(desktopMenu, SIGNAL(aboutToHide()), this, SLOT(menuAboutToHide())); m_latteView->visibility()->setBlockHiding(true); desktopMenu->popup(pos); event->setAccepted(true); emit menuChanged(); return false; } //qDebug() << "8 ..."; } //qDebug() << "9 ..."; } //qDebug() << "10 ..."; emit menuChanged(); return true; // PlasmaQuick::ContainmentView::mousePressEvent(event); } //! update the appletContainsPos method from Panel view void ContextMenu::updateAppletContainsMethod() { for (QQuickItem *item : m_latteView->contentItem()->childItems()) { if (auto *metaObject = item->metaObject()) { // not using QMetaObject::invokeMethod to avoid warnings when calling // this on applets that don't have it or other child items since this // is pretty much trial and error. // Also, "var" arguments are treated as QVariant in QMetaObject int methodIndex = metaObject->indexOfMethod("appletContainsPos(QVariant,QVariant)"); if (methodIndex == -1) { continue; } m_appletContainsMethod = metaObject->method(methodIndex); m_appletContainsMethodItem = item; } } } void ContextMenu::addAppletActions(QMenu *desktopMenu, Plasma::Applet *applet, QEvent *event) { if (!m_latteView->containment()) { return; } for (QAction *action : applet->contextualActions()) { if (action) { desktopMenu->addAction(action); } } if (!applet->failedToLaunch()) { QAction *runAssociatedApplication = applet->actions()->action(QStringLiteral("run associated application")); if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { desktopMenu->addAction(runAssociatedApplication); } QAction *configureApplet = applet->actions()->action(QStringLiteral("configure")); if (configureApplet && configureApplet->isEnabled()) { desktopMenu->addAction(configureApplet); } QAction *appletAlternatives = applet->actions()->action(QStringLiteral("alternatives")); if (appletAlternatives && appletAlternatives->isEnabled() && m_latteView->containment()->isUserConfiguring()) { desktopMenu->addAction(appletAlternatives); } } QAction *containmentAction = desktopMenu->menuAction(); containmentAction->setText(i18nc("%1 is the name of the containment", "%1 Options", m_latteView->containment()->title())); addContainmentActions(containmentAction->menu(), event); if (!containmentAction->menu()->isEmpty()) { int enabled = 0; //count number of real actions QListIterator actionsIt(containmentAction->menu()->actions()); while (enabled < 3 && actionsIt.hasNext()) { QAction *action = actionsIt.next(); if (action->isVisible() && !action->isSeparator()) { ++enabled; } } desktopMenu->addSeparator(); if (enabled) { //if there is only one, don't create a submenu // if (enabled < 2) { for (QAction *action : containmentAction->menu()->actions()) { if (action && action->isVisible()) { desktopMenu->addAction(action); } } // } else { // desktopMenu->addMenu(containmentMenu); // } } } if (m_latteView->containment()->immutability() == Plasma::Types::Mutable && (m_latteView->containment()->containmentType() != Plasma::Types::PanelContainment || m_latteView->containment()->isUserConfiguring())) { QAction *closeApplet = applet->actions()->action(QStringLiteral("remove")); //qDebug() << "checking for removal" << closeApplet; if (closeApplet) { if (!desktopMenu->isEmpty()) { desktopMenu->addSeparator(); } //qDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible(); desktopMenu->addAction(closeApplet); } } } void ContextMenu::addContainmentActions(QMenu *desktopMenu, QEvent *event) { if (!m_latteView->containment()) { return; } if (m_latteView->containment()->corona()->immutability() != Plasma::Types::Mutable && !KAuthorized::authorizeAction(QStringLiteral("plasma/containment_actions"))) { //qDebug() << "immutability"; return; } //this is what ContainmentPrivate::prepareContainmentActions was const QString trigger = Plasma::ContainmentActions::eventToString(event); //"RightButton;NoModifier" Plasma::ContainmentActions *plugin = m_latteView->containment()->containmentActions().value(trigger); if (!plugin) { return; } if (plugin->containment() != m_latteView->containment()) { plugin->setContainment(m_latteView->containment()); // now configure it KConfigGroup cfg(m_latteView->containment()->corona()->config(), "ActionPlugins"); cfg = KConfigGroup(&cfg, QString::number(m_latteView->containment()->containmentType())); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } QList actions = plugin->contextualActions(); for (const QAction *act : actions) { if (act->menu()) { //this is a workaround where Qt now creates the menu widget //in .exec before oxygen can polish it and set the following attribute act->menu()->setAttribute(Qt::WA_TranslucentBackground); //end workaround if (act->menu()->winId()) { act->menu()->windowHandle()->setTransientParent(m_latteView); } } } desktopMenu->addActions(actions); return; } Plasma::Containment *ContextMenu::containmentById(uint id) { for (const auto containment : m_latteView->corona()->containments()) { if (id == containment->id()) { return containment; } } return 0; } } } diff --git a/app/wm/tracker/windowstracker.cpp b/app/wm/tracker/windowstracker.cpp index b863da8a..3fc6cc1e 100644 --- a/app/wm/tracker/windowstracker.cpp +++ b/app/wm/tracker/windowstracker.cpp @@ -1,1022 +1,1022 @@ /* * 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 "windowstracker.h" // local #include "lastactivewindow.h" #include "schemes.h" #include "trackedlayoutinfo.h" #include "trackedviewinfo.h" #include "../abstractwindowinterface.h" #include "../schemecolors.h" #include "../../lattecorona.h" #include "../../layout/genericlayout.h" #include "../../layouts/manager.h" #include "../../view/view.h" #include "../../view/positioner.h" #include "../../../liblatte2/types.h" namespace Latte { namespace WindowSystem { namespace Tracker { Windows::Windows(AbstractWindowInterface *parent) : QObject(parent) { m_wm = parent; m_extraViewHintsTimer.setInterval(600); m_extraViewHintsTimer.setSingleShot(true); connect(&m_extraViewHintsTimer, &QTimer::timeout, this, &Windows::updateExtraViewHints); init(); } Windows::~Windows() { //! clear all the m_views tracking information for (QHash::iterator i=m_views.begin(); i!=m_views.end(); ++i) { i.value()->deleteLater(); m_views[i.key()] = nullptr; } m_views.clear(); //! clear all the m_layouts tracking layouts for (QHash::iterator i=m_layouts.begin(); i!=m_layouts.end(); ++i) { i.value()->deleteLater(); m_layouts[i.key()] = nullptr; } m_layouts.clear(); } void Windows::init() { connect(m_wm->corona(), &Plasma::Corona::availableScreenRectChanged, this, &Windows::updateAvailableScreenGeometries); connect(m_wm, &AbstractWindowInterface::windowChanged, this, [&](WindowId wid) { m_windows[wid] = m_wm->requestInfo(wid); updateAllHints(); emit windowChanged(wid); }); connect(m_wm, &AbstractWindowInterface::windowRemoved, this, [&](WindowId wid) { m_windows.remove(wid); updateAllHints(); emit windowRemoved(wid); }); connect(m_wm, &AbstractWindowInterface::windowAdded, this, [&](WindowId wid) { if (!m_windows.contains(wid)) { m_windows.insert(wid, m_wm->requestInfo(wid)); } updateAllHints(); }); connect(m_wm, &AbstractWindowInterface::activeWindowChanged, this, [&](WindowId wid) { //! for some reason this is needed in order to update properly activeness values //! when the active window changes the previous active windows should be also updated for (const auto view : m_views.keys()) { WindowId lastWinId = m_views[view]->lastActiveWindow()->winId(); if ((lastWinId) != wid && m_windows.contains(lastWinId)) { m_windows[lastWinId] = m_wm->requestInfo(lastWinId); } } m_windows[wid] = m_wm->requestInfo(wid); updateAllHints(); emit activeWindowChanged(wid); }); connect(m_wm, &AbstractWindowInterface::currentDesktopChanged, this, [&] { updateAllHints(); }); connect(m_wm, &AbstractWindowInterface::currentActivityChanged, this, [&] { if (m_wm->corona()->layoutsManager()->memoryUsage() == Types::MultipleLayouts) { //! this is needed in MultipleLayouts because there is a chance that multiple //! layouts are providing different available screen geometries in different Activities updateAvailableScreenGeometries(); } updateAllHints(); }); } void Windows::initLayoutHints(Latte::Layout::GenericLayout *layout) { if (!m_layouts.contains(layout)) { return; } setActiveWindowMaximized(layout, false); setExistsWindowActive(layout, false); setExistsWindowMaximized(layout, false); setActiveWindowScheme(layout, nullptr); } void Windows::initViewHints(Latte::View *view) { if (!m_views.contains(view)) { return; } setActiveWindowMaximized(view, false); setActiveWindowTouching(view, false); setExistsWindowActive(view, false); setExistsWindowTouching(view, false); setExistsWindowMaximized(view, false); setIsTouchingBusyVerticalView(view, false); setActiveWindowScheme(view, nullptr); setTouchingWindowScheme(view, nullptr); } AbstractWindowInterface *Windows::wm() { return m_wm; } void Windows::addView(Latte::View *view) { if (m_views.contains(view)) { return; } m_views[view] = new TrackedViewInfo(this, view); updateAvailableScreenGeometries(); //! Consider Layouts addRelevantLayout(view); connect(view, &Latte::View::layoutChanged, this, [&, view]() { addRelevantLayout(view); }); connect(view, &Latte::View::isTouchingBottomViewAndIsBusy, this, &Windows::updateExtraViewHints); connect(view, &Latte::View::isTouchingTopViewAndIsBusy, this, &Windows::updateExtraViewHints); updateAllHints(); emit informationAnnounced(view); } void Windows::removeView(Latte::View *view) { if (!m_views.contains(view)) { return; } m_views[view]->deleteLater(); m_views.remove(view); updateRelevantLayouts(); } void Windows::addRelevantLayout(Latte::View *view) { if (view->layout() && !m_layouts.contains(view->layout())) { m_layouts[view->layout()] = new TrackedLayoutInfo(this, view->layout()); updateRelevantLayouts(); updateHints(view->layout()); emit informationAnnouncedForLayout(view->layout()); } } void Windows::updateRelevantLayouts() { QList orphanedLayouts; - //! REMOVE Orphaned Relevant layouts that have been removed or they dont contain any Views anymore + //! REMOVE Orphaned Relevant layouts that have been removed or they don't contain any Views anymore for (QHash::iterator i=m_layouts.begin(); i!=m_layouts.end(); ++i) { bool hasView{false}; for (QHash::iterator j=m_views.begin(); j!=m_views.end(); ++j) { if (j.key() && i.key() && i.key() == j.key()->layout()) { hasView = true; break; } } if (!hasView) { if (i.value()) { i.value()->deleteLater(); } orphanedLayouts << i.key(); } } for(const auto &layout : orphanedLayouts) { m_layouts.remove(layout); } //! UPDATE Enabled layout window tracking based on the Views that are requesting windows tracking for (QHash::iterator i=m_layouts.begin(); i!=m_layouts.end(); ++i) { bool hasViewEnabled{false}; for (QHash::iterator j=m_views.begin(); j!=m_views.end(); ++j) { if (i.key() == j.key()->layout() && j.value()->enabled()) { hasViewEnabled = true; break; } } if (i.value()) { i.value()->setEnabled(hasViewEnabled); if (!hasViewEnabled) { initLayoutHints(i.key()); } } } } //! Views Properties And Hints bool Windows::enabled(Latte::View *view) { if (!m_views.contains(view)) { return false; } return m_views[view]->enabled(); } void Windows::setEnabled(Latte::View *view, const bool enabled) { if (!m_views.contains(view) || m_views[view]->enabled() == enabled) { return; } m_views[view]->setEnabled(enabled); if (enabled) { updateHints(view); } else { initViewHints(view); } updateRelevantLayouts(); emit enabledChanged(view); } bool Windows::activeWindowMaximized(Latte::View *view) const { if (!m_views.contains(view)) { return false; } return m_views[view]->activeWindowMaximized(); } void Windows::setActiveWindowMaximized(Latte::View *view, bool activeMaximized) { if (!m_views.contains(view) || m_views[view]->activeWindowMaximized() == activeMaximized) { return; } m_views[view]->setActiveWindowMaximized(activeMaximized); emit activeWindowMaximizedChanged(view); } bool Windows::activeWindowTouching(Latte::View *view) const { if (!m_views.contains(view)) { return false; } return m_views[view]->activeWindowTouching(); } void Windows::setActiveWindowTouching(Latte::View *view, bool activeTouching) { if (!m_views.contains(view) || m_views[view]->activeWindowTouching() == activeTouching) { return; } m_views[view]->setActiveWindowTouching(activeTouching); emit activeWindowTouchingChanged(view); } bool Windows::existsWindowActive(Latte::View *view) const { if (!m_views.contains(view)) { return false; } return m_views[view]->existsWindowActive(); } void Windows::setExistsWindowActive(Latte::View *view, bool windowActive) { if (!m_views.contains(view) || m_views[view]->existsWindowActive() == windowActive) { return; } m_views[view]->setExistsWindowActive(windowActive); emit existsWindowActiveChanged(view); } bool Windows::existsWindowMaximized(Latte::View *view) const { if (!m_views.contains(view)) { return false; } return m_views[view]->existsWindowMaximized(); } void Windows::setExistsWindowMaximized(Latte::View *view, bool windowMaximized) { if (!m_views.contains(view) || m_views[view]->existsWindowMaximized() == windowMaximized) { return; } m_views[view]->setExistsWindowMaximized(windowMaximized); emit existsWindowMaximizedChanged(view); } bool Windows::existsWindowTouching(Latte::View *view) const { if (!m_views.contains(view)) { return false; } return m_views[view]->existsWindowTouching(); } void Windows::setExistsWindowTouching(Latte::View *view, bool windowTouching) { if (!m_views.contains(view) || m_views[view]->existsWindowTouching() == windowTouching) { return; } m_views[view]->setExistsWindowTouching(windowTouching); emit existsWindowTouchingChanged(view); } bool Windows::isTouchingBusyVerticalView(Latte::View *view) const { if (!m_views.contains(view)) { return false; } return m_views[view]->isTouchingBusyVerticalView(); } void Windows::setIsTouchingBusyVerticalView(Latte::View *view, bool viewTouching) { if (!m_views.contains(view) || m_views[view]->isTouchingBusyVerticalView() == viewTouching) { return; } m_views[view]->setIsTouchingBusyVerticalView(viewTouching); emit isTouchingBusyVerticalViewChanged(view); } SchemeColors *Windows::activeWindowScheme(Latte::View *view) const { if (!m_views.contains(view)) { return nullptr; } return m_views[view]->activeWindowScheme(); } void Windows::setActiveWindowScheme(Latte::View *view, WindowSystem::SchemeColors *scheme) { if (!m_views.contains(view) || m_views[view]->activeWindowScheme() == scheme) { return; } m_views[view]->setActiveWindowScheme(scheme); emit activeWindowSchemeChanged(view); } SchemeColors *Windows::touchingWindowScheme(Latte::View *view) const { if (!m_views.contains(view)) { return nullptr; } return m_views[view]->touchingWindowScheme(); } void Windows::setTouchingWindowScheme(Latte::View *view, WindowSystem::SchemeColors *scheme) { if (!m_views.contains(view) || m_views[view]->touchingWindowScheme() == scheme) { return; } m_views[view]->setTouchingWindowScheme(scheme); emit touchingWindowSchemeChanged(view); } LastActiveWindow *Windows::lastActiveWindow(Latte::View *view) { if (!m_views.contains(view)) { return nullptr; } return m_views[view]->lastActiveWindow(); } //! Layouts bool Windows::enabled(Latte::Layout::GenericLayout *layout) { if (!m_layouts.contains(layout)) { return false; } return m_layouts[layout]->enabled(); } bool Windows::activeWindowMaximized(Latte::Layout::GenericLayout *layout) const { if (!m_layouts.contains(layout)) { return false; } return m_layouts[layout]->activeWindowMaximized(); } void Windows::setActiveWindowMaximized(Latte::Layout::GenericLayout *layout, bool activeMaximized) { if (!m_layouts.contains(layout) || m_layouts[layout]->activeWindowMaximized() == activeMaximized) { return; } m_layouts[layout]->setActiveWindowMaximized(activeMaximized); emit activeWindowMaximizedChangedForLayout(layout); } bool Windows::existsWindowActive(Latte::Layout::GenericLayout *layout) const { if (!m_layouts.contains(layout)) { return false; } return m_layouts[layout]->existsWindowActive(); } void Windows::setExistsWindowActive(Latte::Layout::GenericLayout *layout, bool windowActive) { if (!m_layouts.contains(layout) || m_layouts[layout]->existsWindowActive() == windowActive) { return; } m_layouts[layout]->setExistsWindowActive(windowActive); emit existsWindowActiveChangedForLayout(layout); } bool Windows::existsWindowMaximized(Latte::Layout::GenericLayout *layout) const { if (!m_layouts.contains(layout)) { return false; } return m_layouts[layout]->existsWindowMaximized(); } void Windows::setExistsWindowMaximized(Latte::Layout::GenericLayout *layout, bool windowMaximized) { if (!m_layouts.contains(layout) || m_layouts[layout]->existsWindowMaximized() == windowMaximized) { return; } m_layouts[layout]->setExistsWindowMaximized(windowMaximized); emit existsWindowMaximizedChangedForLayout(layout); } SchemeColors *Windows::activeWindowScheme(Latte::Layout::GenericLayout *layout) const { if (!m_layouts.contains(layout)) { return nullptr; } return m_layouts[layout]->activeWindowScheme(); } void Windows::setActiveWindowScheme(Latte::Layout::GenericLayout *layout, WindowSystem::SchemeColors *scheme) { if (!m_layouts.contains(layout) || m_layouts[layout]->activeWindowScheme() == scheme) { return; } m_layouts[layout]->setActiveWindowScheme(scheme); emit activeWindowSchemeChangedForLayout(layout); } LastActiveWindow *Windows::lastActiveWindow(Latte::Layout::GenericLayout *layout) { if (!m_layouts.contains(layout)) { return nullptr; } return m_layouts[layout]->lastActiveWindow(); } //! Windows bool Windows::isValidFor(const WindowId &wid) const { if (!m_windows.contains(wid)) { return false; } return m_windows[wid].isValid() && !m_windows[wid].isPlasmaDesktop(); } QIcon Windows::iconFor(const WindowId &wid) { if (!m_windows.contains(wid)) { return QIcon(); } if (m_windows[wid].icon().isNull()) { AppData data = m_wm->appDataFor(wid); QIcon icon = data.icon; if (icon.isNull()) { icon = m_wm->iconFor(wid); } m_windows[wid].setIcon(icon); return icon; } return m_windows[wid].icon(); } QString Windows::appNameFor(const WindowId &wid) { if (!m_windows.contains(wid)) { return QString(); } if (m_windows[wid].appName().isEmpty()) { AppData data = m_wm->appDataFor(wid); m_windows[wid].setAppName(data.name); return data.name; } return m_windows[wid].appName(); } WindowInfoWrap Windows::infoFor(const WindowId &wid) const { if (!m_windows.contains(wid)) { return WindowInfoWrap(); } return m_windows[wid]; } //! Windows Criteria Functions bool Windows::inCurrentDesktopActivity(const WindowInfoWrap &winfo) { return (winfo.isValid() && winfo.isOnDesktop(m_wm->currentDesktop()) && winfo.isOnActivity(m_wm->currentActivity())); } bool Windows::intersects(Latte::View *view, const WindowInfoWrap &winfo) { return (!winfo.isMinimized() && !winfo.isShaded() && winfo.geometry().intersects(view->absoluteGeometry())); } bool Windows::isActive(const WindowInfoWrap &winfo) { return (winfo.isValid() && winfo.isActive() && !winfo.isPlasmaDesktop() && !winfo.isMinimized()); } bool Windows::isActiveInViewScreen(Latte::View *view, const WindowInfoWrap &winfo) { return (winfo.isValid() && winfo.isActive() && !winfo.isPlasmaDesktop() && !winfo.isMinimized() && m_views[view]->availableScreenGeometry().contains(winfo.geometry().center())); } bool Windows::isMaximizedInViewScreen(Latte::View *view, const WindowInfoWrap &winfo) { auto viewIntersectsMaxVert = [&]() noexcept -> bool { return ((winfo.isMaxVert() || (view->screen() && view->screen()->availableSize().height() <= winfo.geometry().height())) && intersects(view, winfo)); }; auto viewIntersectsMaxHoriz = [&]() noexcept -> bool { return ((winfo.isMaxHoriz() || (view->screen() && view->screen()->availableSize().width() <= winfo.geometry().width())) && intersects(view, winfo)); }; //! updated implementation to identify the screen that the maximized window is present //! in order to avoid: https://bugs.kde.org/show_bug.cgi?id=397700 return (winfo.isValid() && !winfo.isPlasmaDesktop() && !winfo.isMinimized() && (winfo.isMaximized() || viewIntersectsMaxVert() || viewIntersectsMaxHoriz()) && m_views[view]->availableScreenGeometry().contains(winfo.geometry().center())); } bool Windows::isTouchingView(Latte::View *view, const WindowSystem::WindowInfoWrap &winfo) { return (winfo.isValid() && !winfo.isPlasmaDesktop() && intersects(view, winfo)); } bool Windows::isTouchingViewEdge(Latte::View *view, const WindowInfoWrap &winfo) { if (winfo.isValid() && !winfo.isPlasmaDesktop() && !winfo.isMinimized()) { bool inViewThicknessEdge{false}; bool inViewLengthBoundaries{false}; QRect screenGeometry = view->screenGeometry(); bool inCurrentScreen{screenGeometry.contains(winfo.geometry().topLeft()) || screenGeometry.contains(winfo.geometry().bottomRight())}; if (inCurrentScreen) { if (view->location() == Plasma::Types::TopEdge) { inViewThicknessEdge = (winfo.geometry().y() == view->absoluteGeometry().bottom() + 1); } else if (view->location() == Plasma::Types::BottomEdge) { inViewThicknessEdge = (winfo.geometry().bottom() == view->absoluteGeometry().top() - 1); } else if (view->location() == Plasma::Types::LeftEdge) { inViewThicknessEdge = (winfo.geometry().x() == view->absoluteGeometry().right() + 1); } else if (view->location() == Plasma::Types::RightEdge) { inViewThicknessEdge = (winfo.geometry().right() == view->absoluteGeometry().left() - 1); } if (view->formFactor() == Plasma::Types::Horizontal) { int yCenter = view->absoluteGeometry().center().y(); QPoint leftChecker(winfo.geometry().left(), yCenter); QPoint rightChecker(winfo.geometry().right(), yCenter); bool fulloverlap = (winfo.geometry().left()<=view->absoluteGeometry().left()) && (winfo.geometry().right()>=view->absoluteGeometry().right()); inViewLengthBoundaries = fulloverlap || view->absoluteGeometry().contains(leftChecker) || view->absoluteGeometry().contains(rightChecker); } else if (view->formFactor() == Plasma::Types::Vertical) { int xCenter = view->absoluteGeometry().center().x(); QPoint topChecker(xCenter, winfo.geometry().top()); QPoint bottomChecker(xCenter, winfo.geometry().bottom()); bool fulloverlap = (winfo.geometry().top()<=view->absoluteGeometry().top()) && (winfo.geometry().bottom()>=view->absoluteGeometry().bottom()); inViewLengthBoundaries = fulloverlap || view->absoluteGeometry().contains(topChecker) || view->absoluteGeometry().contains(bottomChecker); } } return (inViewThicknessEdge && inViewLengthBoundaries); } return false; } void Windows::cleanupFaultyWindows() { for (const auto &key : m_windows.keys()) { auto winfo = m_windows[key]; //! garbage windows removing if (winfo.wid()<=0 || winfo.geometry() == QRect(0, 0, 0, 0)) { //qDebug() << "Faulty Geometry ::: " << winfo.wid(); m_windows.remove(key); } } } void Windows::updateAvailableScreenGeometries() { for (const auto view : m_views.keys()) { if (m_views[view]->enabled()) { int currentScrId = view->positioner()->currentScreenId(); QRect tempAvailableScreenGeometry = m_wm->corona()->availableScreenRectWithCriteria(currentScrId, {Types::AlwaysVisible}, {}); if (tempAvailableScreenGeometry != m_views[view]->availableScreenGeometry()) { m_views[view]->setAvailableScreenGeometry(tempAvailableScreenGeometry); updateHints(view); } } } } void Windows::setPlasmaDesktop(WindowId wid) { if (!m_windows.contains(wid)) { return; } if (!m_windows[wid].isPlasmaDesktop()) { m_windows[wid].setIsPlasmaDesktop(true); qDebug() << " plasmashell updated..."; updateAllHints(); } } void Windows::updateAllHints() { for (const auto view : m_views.keys()) { updateHints(view); } for (const auto layout : m_layouts.keys()) { updateHints(layout); } if (!m_extraViewHintsTimer.isActive()) { m_extraViewHintsTimer.start(); } } void Windows::updateExtraViewHints() { for (const auto horView : m_views.keys()) { if (!m_views.contains(horView) || !m_views[horView]->enabled() || !m_views[horView]->isTrackingCurrentActivity()) { continue; } if (horView->formFactor() == Plasma::Types::Horizontal) { bool touchingBusyVerticalView{false}; for (const auto verView : m_views.keys()) { if (!m_views.contains(verView) || !m_views[verView]->enabled() || !m_views[verView]->isTrackingCurrentActivity()) { continue; } bool sameScreen = (verView->positioner()->currentScreenId() == horView->positioner()->currentScreenId()); if (verView->formFactor() == Plasma::Types::Vertical && sameScreen) { bool topTouch = verView->isTouchingTopViewAndIsBusy() && horView->location() == Plasma::Types::TopEdge; bool bottomTouch = verView->isTouchingBottomViewAndIsBusy() && horView->location() == Plasma::Types::BottomEdge; if (topTouch || bottomTouch) { touchingBusyVerticalView = true; break; } } } //qDebug() << " Touching Busy Vertical View :: " << horView->location() << " - " << horView->positioner()->currentScreenId() << " :: " << touchingBusyVerticalView; setIsTouchingBusyVerticalView(horView, touchingBusyVerticalView); } } } void Windows::updateHints(Latte::View *view) { if (!m_views.contains(view) || !m_views[view]->enabled() || !m_views[view]->isTrackingCurrentActivity()) { return; } bool foundActive{false}; bool foundActiveInCurScreen{false}; bool foundActiveTouchInCurScreen{false}; bool foundTouchInCurScreen{false}; bool foundMaximizedInCurScreen{false}; bool foundActiveGroupTouchInCurScreen{false}; //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0), //! maybe a garbage collector here is a good idea!!! bool existsFaultyWindow{false}; WindowId maxWinId; WindowId activeWinId; WindowId touchWinId; WindowId activeTouchWinId; //! First Pass for (const auto &winfo : m_windows) { if (!existsFaultyWindow && (winfo.wid()<=0 || winfo.geometry() == QRect(0, 0, 0, 0))) { existsFaultyWindow = true; } if (winfo.isPlasmaDesktop() || !inCurrentDesktopActivity(winfo)) { continue; } if (isActive(winfo)) { foundActive = true; } if (isActiveInViewScreen(view, winfo)) { foundActiveInCurScreen = true; activeWinId = winfo.wid(); } if (isTouchingViewEdge(view, winfo) || isTouchingView(view, winfo)) { if (winfo.isActive()) { //qDebug() << " ACTIVE-TOUCH :: " << winfo.wid() << " _ " << winfo.appName() << " _ " << winfo.geometry() << " _ " << winfo.display(); foundActiveTouchInCurScreen = true; activeTouchWinId = winfo.wid(); if (isMaximizedInViewScreen(view, winfo)) { //! active maximized windows have higher priority than the rest maximized windows foundMaximizedInCurScreen = true; maxWinId = winfo.wid(); } } else { //qDebug() << " TOUCH :: " << winfo.wid() << " _ " << winfo.appName() << " _ " << winfo.geometry() << " _ " << winfo.display(); foundTouchInCurScreen = true; touchWinId = winfo.wid(); } if (!foundMaximizedInCurScreen && isMaximizedInViewScreen(view, winfo)) { foundMaximizedInCurScreen = true; maxWinId = winfo.wid(); } } //qDebug() << "window geometry ::: " << winfo.geometry(); } if (existsFaultyWindow) { cleanupFaultyWindows(); } //! PASS 2 if (foundActiveInCurScreen && !foundActiveTouchInCurScreen) { //! Second Pass to track also Child windows if needed //qDebug() << "Windows Array..."; //for (const auto &winfo : m_windows) { // qDebug() << " - " << winfo.wid() << " - " << winfo.isValid() << " - " << winfo.display() << " - " << winfo.geometry() << " parent : " << winfo.parentId(); //} //qDebug() << " - - - - - "; WindowInfoWrap activeInfo = m_windows[activeWinId]; WindowId mainWindowId = activeInfo.isChildWindow() ? activeInfo.parentId() : activeWinId; for (const auto &winfo : m_windows) { if (winfo.isPlasmaDesktop() || !inCurrentDesktopActivity(winfo)) { continue; } bool inActiveGroup = (winfo.wid() == mainWindowId || winfo.parentId() == mainWindowId); //! consider only windows that belong to active window group meaning the main window //! and its children if (!inActiveGroup) { continue; } if (isTouchingViewEdge(view, winfo) || isTouchingView(view, winfo)) { foundActiveGroupTouchInCurScreen = true; break; } } } //! HACK: KWin Effects such as ShowDesktop have no way to be identified and as such //! create issues with identifying properly touching and maximized windows. BUT when //! they are enabled then NO ACTIVE window is found. This is a way to identify these //! effects trigerring and disable the touch flags. //! BUG: 404483 //! Disabled because it has fault identifications, e.g. when a window is maximized and //! Latte or Plasma are showing their View settings //foundMaximizedInCurScreen = foundMaximizedInCurScreen && foundActive; //foundTouchInCurScreen = foundTouchInCurScreen && foundActive; //! assign flags setExistsWindowActive(view, foundActiveInCurScreen); setActiveWindowTouching(view, foundActiveTouchInCurScreen || foundActiveGroupTouchInCurScreen); setActiveWindowMaximized(view, (maxWinId.toInt()>0 && (maxWinId == activeTouchWinId))); setExistsWindowMaximized(view, foundMaximizedInCurScreen); setExistsWindowTouching(view, (foundTouchInCurScreen || foundActiveTouchInCurScreen || foundActiveGroupTouchInCurScreen)); //! update color schemes for active and touching windows setActiveWindowScheme(view, (foundActiveInCurScreen ? m_wm->schemesTracker()->schemeForWindow(activeWinId) : nullptr)); if (foundActiveTouchInCurScreen) { setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(activeTouchWinId)); } else if (foundMaximizedInCurScreen) { setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(maxWinId)); } else if (foundTouchInCurScreen) { setTouchingWindowScheme(view, m_wm->schemesTracker()->schemeForWindow(touchWinId)); } else { setTouchingWindowScheme(view, nullptr); } //! update LastActiveWindow if (foundActiveInCurScreen) { m_views[view]->setActiveWindow(activeWinId); } //! Debug //qDebug() << " -- TRACKING REPORT --"; //qDebug() << "TRACKING | SCREEN: " << view->positioner()->currentScreenId() << " , EDGE:" << view->location() << " , ENABLED:" << enabled(view); //qDebug() << "TRACKING | activeWindowTouching: " << foundActiveTouchInCurScreen << " ,activeWindowMaximized: " << activeWindowMaximized(view); //qDebug() << "TRACKING | existsWindowActive: " << foundActiveInCurScreen << " , existsWindowMaximized:" << existsWindowMaximized(view) // << " , existsWindowTouching:"<enabled() || !m_layouts[layout]->isTrackingCurrentActivity()) { return; } bool foundActive{false}; bool foundActiveMaximized{false}; bool foundMaximized{false}; //! the notification window is not sending a remove signal and creates windows of geometry (0x0 0,0), //! maybe a garbage collector here is a good idea!!! bool existsFaultyWindow{false}; WindowId activeWinId; WindowId maxWinId; for (const auto &winfo : m_windows) { if (!existsFaultyWindow && (winfo.wid()<=0 || winfo.geometry() == QRect(0, 0, 0, 0))) { existsFaultyWindow = true; } if (winfo.isPlasmaDesktop() || !inCurrentDesktopActivity(winfo)) { continue; } if (isActive(winfo)) { foundActive = true; activeWinId = winfo.wid(); if (winfo.isMaximized() && !winfo.isMinimized()) { foundActiveMaximized = true; maxWinId = winfo.wid(); } } if (!foundActiveMaximized && winfo.isMaximized() && !winfo.isMinimized()) { foundMaximized = true; maxWinId = winfo.wid(); } //qDebug() << "window geometry ::: " << winfo.geometry(); } if (existsFaultyWindow) { cleanupFaultyWindows(); } //! HACK: KWin Effects such as ShowDesktop have no way to be identified and as such //! create issues with identifying properly touching and maximized windows. BUT when //! they are enabled then NO ACTIVE window is found. This is a way to identify these //! effects trigerring and disable the touch flags. //! BUG: 404483 //! Disabled because it has fault identifications, e.g. when a window is maximized and //! Latte or Plasma are showing their View settings //foundMaximizedInCurScreen = foundMaximizedInCurScreen && foundActive; //foundTouchInCurScreen = foundTouchInCurScreen && foundActive; //! assign flags setExistsWindowActive(layout, foundActive); setActiveWindowMaximized(layout, foundActiveMaximized); setExistsWindowMaximized(layout, foundActiveMaximized || foundMaximized); //! update color schemes for active and touching windows setActiveWindowScheme(layout, (foundActive ? m_wm->schemesTracker()->schemeForWindow(activeWinId) : nullptr)); //! update LastActiveWindow if (foundActive) { m_layouts[layout]->setActiveWindow(activeWinId); } //! Debug //qDebug() << " -- TRACKING REPORT --"; //qDebug() << "TRACKING | SCREEN: " << view->positioner()->currentScreenId() << " , EDGE:" << view->location() << " , ENABLED:" << enabled(view); //qDebug() << "TRACKING | activeWindowTouching: " << foundActiveTouchInCurScreen << " ,activeWindowMaximized: " << activeWindowMaximized(view); //qDebug() << "TRACKING | existsWindowActive: " << foundActiveInCurScreen << " , existsWindowMaximized:" << existsWindowMaximized(view) // << " , existsWindowTouching:"< * * 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 org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.latte 0.2 as Latte import "../../code/ColorizerTools.js" as ColorizerTools Loader{ id: manager //! the loader loads the backgroundTracker component active: root.themeColors === Latte.Types.SmartThemeColors readonly property bool backgroundIsBusy: item ? item.isBusy : false readonly property real originalThemeTextColorBrightness: ColorizerTools.colorBrightness(theme.textColor) readonly property color originalLightTextColor: originalThemeTextColorBrightness > 127.5 ? theme.textColor : theme.backgroundColor readonly property real themeTextColorBrightness: ColorizerTools.colorBrightness(textColor) readonly property real backgroundColorBrightness: ColorizerTools.colorBrightness(backgroundColor) readonly property color minimizedDotColor: themeTextColorBrightness > 127.5 ? Qt.darker(textColor, 1.7) : Qt.lighter(textColor, 8) readonly property color focusGlowColor: Qt.hsva(buttonFocusColor.h, buttonFocusColor.s, 1.0, 1.0) readonly property color outlineColorBase: backgroundColor readonly property real outlineColorBaseBrightness: ColorizerTools.colorBrightness(outlineColorBase) readonly property color outlineColor: { if (!root.panelOutline) { return backgroundColor; } if (outlineColorBaseBrightness > 127.5) { return Qt.darker(outlineColorBase, 1.5); } else { return Qt.lighter(outlineColorBase, 2.2); } } readonly property bool editModeTextColorIsBright: ColorizerTools.colorBrightness(editModeTextColor) > 127.5 readonly property color editModeTextColor: latteView && latteView.layout ? latteView.layout.textColor : "white" readonly property bool mustBeShown: (applyTheme && applyTheme !== theme) || (root.inConfigureAppletsMode && (root.themeColors === Latte.Types.SmartThemeColors)) readonly property real currentBackgroundBrightness: item ? item.currentBrightness : -1000 readonly property bool applyingWindowColors: (root.windowColors === Latte.Types.ActiveWindowColors && latteView && latteView.windowsTracker && selectedWindowsTracker.activeWindowScheme) || (root.windowColors === Latte.Types.TouchingWindowColors && latteView && latteView.windowsTracker && latteView.windowsTracker.currentScreen.touchingWindowScheme) property QtObject applyTheme: { if (latteView && latteView.windowsTracker && !root.hasExpandedApplet) { if (root.windowColors === Latte.Types.ActiveWindowColors && selectedWindowsTracker.activeWindowScheme) { return selectedWindowsTracker.activeWindowScheme; } if (root.windowColors === Latte.Types.TouchingWindowColors && latteView.windowsTracker.currentScreen.touchingWindowScheme) { //! we must track touching windows and when they are not ative //! the active window scheme is used for convenience if (latteView.windowsTracker.currentScreen.existsWindowTouching && !latteView.windowsTracker.currentScreen.activeWindowTouching && latteView.windowsTracker.currentScreen.activeWindowScheme) { return latteView.windowsTracker.currentScreen.activeWindowScheme; } return latteView.windowsTracker.currentScreen.touchingWindowScheme; } } if (themeExtended) { if (root.userShowPanelBackground && root.plasmaBackgroundForPopups && root.hasExpandedApplet /*for expanded popups when it is enabled*/ || root.plasmaStyleBusyForTouchingBusyVerticalView || (root.themeColors === Latte.Types.SmartThemeColors /*for Smart theming that Windows colors are not used and the user wants solidness at some cases*/ && root.windowColors === Latte.Types.NoneWindowColors && root.forceSolidPanel) ) { /* plasma style*/ return theme; } if (root.themeColors === Latte.Types.ReverseThemeColors) { return themeExtended.isLightTheme ? themeExtended.darkTheme : themeExtended.lightTheme; } if (root.themeColors === Latte.Types.SmartThemeColors) { //! Smart Colors Case if (!root.forcePanelForBusyBackground) { //! simple case that not a busy background is applied return currentBackgroundBrightness > 127.5 ? themeExtended.lightTheme : themeExtended.darkTheme; } else { //! Smart + Busy background case var themeContrastedTextColor = currentBackgroundBrightness > 127.5 ? themeExtended.lightTheme : themeExtended.darkTheme; var themeContrastedBackground = currentBackgroundBrightness > 127.5 ? themeExtended.darkTheme : themeExtended.lightTheme; if (root.panelTransparency < 35) { //! textColor should be better to provide the needed contrast return themeContrastedTextColor; } else if (root.panelTransparency >= 35 && root.panelTransparency <= 70) { //! provide a dark case scenario at all cases return themeExtended.darkTheme; } else { - //! default plasma theme shoud be better for panel transparency > 70 + //! default plasma theme should be better for panel transparency > 70 return theme; } } } } return theme; } property color applyColor: textColor readonly property color backgroundColor:applyTheme.backgroundColor readonly property color textColor: { if (latteView && latteView.layout && root.inConfigureAppletsMode && Latte.WindowSystem.compositingActive && root.panelTransparency<40 && (root.themeColors === Latte.Types.SmartThemeColors)) { return latteView.layout.textColor; } return applyTheme.textColor; } readonly property color inactiveBackgroundColor: applyTheme === theme ? theme.backgroundColor : applyTheme.inactiveBackgroundColor readonly property color inactiveTextColor: applyTheme === theme ? theme.textColor : applyTheme.inactiveTextColor readonly property color highlightColor: applyTheme.highlightColor readonly property color highlightedTextColor: applyTheme.highlightedTextColor readonly property color positiveTextColor: applyTheme.positiveTextColor readonly property color neutralTextColor: applyTheme.neutralTextColor readonly property color negativeTextColor: applyTheme.negativeTextColor readonly property color buttonTextColor: applyTheme.buttonTextColor readonly property color buttonBackgroundColor: applyTheme.buttonBackgroundColor readonly property color buttonHoverColor: applyTheme.buttonHoverColor readonly property color buttonFocusColor: applyTheme.buttonFocusColor readonly property string scheme: { if (root.inConfigureAppletsMode && (root.themeColors === Latte.Types.SmartThemeColors)) { if (!Latte.WindowSystem.compositingActive && applyTheme !== theme) { return applyTheme.schemeFile; } //! in edit mode (that is shown the edit visual without opacity) //! take care the applets that need a proper color scheme to paint themselves if ((editModeTextColorIsBright && themeExtended.isLightTheme) || (!editModeTextColorIsBright && !themeExtended.isLightTheme)) { if (themeExtended.darkTheme === themeExtended.defaultTheme) { console.log("light theme..." + themeExtended.isLightTheme); return themeExtended.lightTheme.schemeFile; } else { console.log("dark theme..." + themeExtended.isLightTheme); return themeExtended.darkTheme.schemeFile; } } else { console.log("default theme... : " + themeExtended.isLightTheme); return themeExtended.defaultTheme.schemeFile; } } if (applyTheme===theme || !mustBeShown) { if (themeExtended) { return themeExtended.defaultTheme.schemeFile; } else { return "kdeglobals"; } } return applyTheme.schemeFile; } sourceComponent: Latte.BackgroundTracker { activity: viewLayout ? viewLayout.lastUsedActivity : "" location: plasmoid.location screenName: latteView && latteView.positioner ? latteView.positioner.currentScreenName : "" } } diff --git a/indicators/org.kde.latte.plasma/package/ui/FrontLayer.qml b/indicators/org.kde.latte.plasma/package/ui/FrontLayer.qml index 9e297721..5c8bc97c 100644 --- a/indicators/org.kde.latte.plasma/package/ui/FrontLayer.qml +++ b/indicators/org.kde.latte.plasma/package/ui/FrontLayer.qml @@ -1,265 +1,265 @@ /* * 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 . */ import QtQuick 2.7 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.latte 0.2 as Latte Item { anchors.fill: parent Item { id: relevantItem anchors.fill: parent clip: true Item{ id: clickedCenter readonly property int center: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? parent.width/2 : parent.height/2 states:[ State { name: "bottom" when: (plasmoid.location === PlasmaCore.Types.BottomEdge) AnchorChanges { target: clickedCenter anchors{ top:undefined; bottom:parent.bottom; left:undefined; right:undefined; horizontalCenter:parent.horizontalCenter; verticalCenter:undefined} } }, State { name: "left" when: (plasmoid.location === PlasmaCore.Types.LeftEdge) AnchorChanges { target: clickedCenter anchors{ top:undefined; bottom:undefined; left:parent.left; right:undefined; horizontalCenter:undefined; verticalCenter:parent.verticalCenter} } }, State { name: "right" when: (plasmoid.location === PlasmaCore.Types.RightEdge) AnchorChanges { target: clickedCenter anchors{ top:undefined; bottom:undefined; left:undefined; right:parent.right; horizontalCenter:undefined; verticalCenter:parent.verticalCenter} } }, State { name: "top" when: (plasmoid.location === PlasmaCore.Types.TopEdge) AnchorChanges { target: clickedCenter anchors{ top:parent.top; bottom:undefined; left:undefined; right:undefined; horizontalCenter:parent.horizontalCenter; verticalCenter:undefined} } } ] } Rectangle { id: clickedRectangle anchors.centerIn: clickedCenter opacity: 0 radius: width/2 height: width color: theme.highlightColor } } SequentialAnimation { id: clickedAnimation ScriptAction{ script: { clickedRectangle.width = 0; clickedRectangle.opacity = 0.75; clickedRectangle.anchors.rightMargin = 0; clickedRectangle.anchors.leftMargin = 0; clickedRectangle.anchors.topMargin = 0; clickedRectangle.anchors.bottomMargin = 0; clickedRectangle.anchors.horizontalCenterOffset = 0; clickedRectangle.anchors.verticalCenterOffset = 0; } } ParallelAnimation{ PropertyAnimation { target: clickedRectangle property: "width" - //! Dont animate above for length + //! Don't animate above for length to: maxLength * multiplier duration: 700 easing.type: Easing.Linear readonly property int multiplier: indicator.scaleFactor * 2 readonly property int maxLength: Math.min(indicator.currentIconSize*10, Math.max(relevantItem.width, relevantItem.height)) } PropertyAnimation { target: clickedRectangle property: "opacity" to: 0 duration: 700 easing.type: Easing.Linear } } } Connections { target: level enabled: root.clickedAnimationEnabled onMousePressed: { var fixedX = 0; var fixedY = 0; if (plasmoid.formFactor === PlasmaCore.Types.Horizontal) { fixedX = x - clickedCenter.center; } else { if (plasmoid.location === PlasmaCore.Types.RightEdge) { fixedX = relevantItem.width - x; } else { fixedX = x; } } if (plasmoid.formFactor === PlasmaCore.Types.Vertical) { fixedY = y - clickedCenter.center; } else { if (plasmoid.location === PlasmaCore.Types.BottomEdge) { fixedY = relevantItem.height - y; } else { fixedY = y; } } if (plasmoid.formFactor === PlasmaCore.Types.Horizontal) { clickedCenter.anchors.horizontalCenterOffset = fixedX; } else { if (plasmoid.location === PlasmaCore.Types.LeftEdge) { clickedCenter.anchors.leftMargin = fixedX; } else if (plasmoid.location === PlasmaCore.Types.RightEdge) { clickedCenter.anchors.rightMargin = fixedX; } } if (plasmoid.formFactor === PlasmaCore.Types.Vertical) { clickedCenter.anchors.verticalCenterOffset = fixedY; } else { if (plasmoid.location === PlasmaCore.Types.BottomEdge) { clickedCenter.anchors.bottomMargin = fixedY; } else if (plasmoid.location === PlasmaCore.Types.TopEdge) { clickedCenter.anchors.topMargin = fixedY; } } clickedCenter.anchors.verticalCenterOffset = fixedY; clickedAnimation.start(); } } Loader { anchors.fill: parent visible: !indicator.isApplet && indicator.isGroup sourceComponent: Item{ anchors.fill: parent Item { id: iconBox anchors.centerIn: parent width: indicator.currentIconSize height: width } PlasmaCore.SvgItem { id: arrow implicitWidth: 0.25 * iconBox.width implicitHeight: implicitWidth svg: groupSvg elementId: elementForLocation(plasmoid.location) readonly property QtObject groupSvg: indicator.resources.svgs.length > 0 ? indicator.resources.svgs[0] : null function elementForLocation(location) { switch (location) { case PlasmaCore.Types.LeftEdge: return "group-expander-left"; case PlasmaCore.Types.TopEdge: return "group-expander-top"; case PlasmaCore.Types.RightEdge: return "group-expander-right"; case PlasmaCore.Types.BottomEdge: default: return "group-expander-bottom"; } } } states: [ State { name: "bottom" when: plasmoid.location === PlasmaCore.Types.BottomEdge AnchorChanges { target: arrow anchors.top: undefined; anchors.left: undefined; anchors.right: undefined; anchors.bottom: arrow.parent.bottom; anchors.horizontalCenter: iconBox.horizontalCenter; anchors.verticalCenter: undefined; } }, State { name: "top" when: plasmoid.location === PlasmaCore.Types.TopEdge AnchorChanges { target: arrow anchors.top: arrow.parent.top; anchors.left: undefined; anchors.right: undefined; anchors.bottom: undefined; anchors.horizontalCenter: iconBox.horizontalCenter; anchors.verticalCenter: undefined; } }, State { name: "left" when: plasmoid.location === PlasmaCore.Types.LeftEdge AnchorChanges { target: arrow anchors.top: undefined; anchors.left: arrow.parent.left; anchors.right: undefined; anchors.bottom: undefined; anchors.horizontalCenter: undefined; anchors.verticalCenter: iconBox.verticalCenter; } }, State { name: "right" when: plasmoid.location === PlasmaCore.Types.RightEdge AnchorChanges { target: arrow anchors.top: undefined; anchors.left: undefined; anchors.right: arrow.parent.right; anchors.bottom: undefined; anchors.horizontalCenter: undefined; anchors.verticalCenter: iconBox.verticalCenter; } } ] } } } diff --git a/liblatte2/extras.h b/liblatte2/extras.h index 53756b56..29f9c43e 100644 --- a/liblatte2/extras.h +++ b/liblatte2/extras.h @@ -1,83 +1,102 @@ +/* +* Copyright 2018 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 EXTRAS_H #define EXTRAS_H // local #include "../liblatte2/config-latte-lib.h" // C++ #include #include #include #include // Qt #include #include #include #include #include #include // Plasma #include //! There are gcc versions that don't support yet that function even though they //! publish themselves as C++14 compatible. Such a case is gcc 4.8.x that openSUSE //! LEAP 42.2-3 is using. By enabling this flag such systems can be build correctly. #if ENABLE_MAKE_UNIQUE namespace std { template unique_ptr make_unique(Args &&... args) { return std::unique_ptr(new T(std::forward(args)...)); } } #endif /*! * @brief convert a QRect to a QString with format `(, ) x` */ inline QString qRectToStr(const QRect &r) { return "(" % QString::number(r.x()) % ", " % QString::number(r.y()) % ") " % QString::number(r.width()) % "x" % QString::number(r.height()); } /*! * @brief convert a `Q_ENUM` to c-string */ template inline const char *qEnumToStr(EnumType value) { return QMetaEnum::fromType().valueToKey(value); } /*! * @brief convert a `Q_ENUMS` of `Plasma::Types::Location` to c-string */ inline const char *qEnumToStr(Plasma::Types::Location Enum) { static const int Index = Plasma::Types::staticMetaObject.indexOfEnumerator("Location"); return Plasma::Types::staticMetaObject.enumerator(Index).valueToKey(Enum); } /*! * @brief convert a `Q_ENUMS` of `Plasma::Types::FormFactor` to c-string */ inline const char *qEnumToStr(Plasma::Types::FormFactor Enum) { static const int Index = Plasma::Types::staticMetaObject.indexOfEnumerator("FormFactor"); return Plasma::Types::staticMetaObject.enumerator(Index).valueToKey(Enum); } /*! * @brief machine epsilon */ template typename std::enable_if < !std::is_integral(), bool >::type almost_equal(T x, T y, int ulp) { return std::abs(x - y) < std::numeric_limits::epsilon() * std::abs(x + y) * ulp || std::abs(x - y) < std::numeric_limits::min(); } #endif // EXTRAS_H diff --git a/liblatte2/plasma/extended/backgroundcache.cpp b/liblatte2/plasma/extended/backgroundcache.cpp index e7e06bd6..8aa3952e 100644 --- a/liblatte2/plasma/extended/backgroundcache.cpp +++ b/liblatte2/plasma/extended/backgroundcache.cpp @@ -1,474 +1,474 @@ /* * Copyright 2018 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 "backgroundcache.h" // local #include "../../commontools.h" // Qt #include #include #include #include #include #include // Plasma #include // KDE #include #include #define MAXHASHSIZE 300 #define PLASMACONFIG "plasma-org.kde.plasma.desktop-appletsrc" #define DEFAULTWALLPAPER "wallpapers/Next/contents/images/1920x1080.png" namespace Latte{ namespace PlasmaExtended { BackgroundCache::BackgroundCache(QObject *parent) : QObject(parent), m_initialized(false), m_plasmaConfig(KSharedConfig::openConfig(PLASMACONFIG)) { const auto configFile = QStandardPaths::writableLocation( QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + PLASMACONFIG; m_defaultWallpaperPath = Latte::standardPath(DEFAULTWALLPAPER); qDebug() << "Default Wallpaper path ::: " << m_defaultWallpaperPath; KDirWatch::self()->addFile(configFile); connect(KDirWatch::self(), &KDirWatch::dirty, this, &BackgroundCache::settingsFileChanged); connect(KDirWatch::self(), &KDirWatch::created, this, &BackgroundCache::settingsFileChanged); if (!m_pool) { m_pool = new ScreenPool(this); } reload(); } BackgroundCache::~BackgroundCache() { if (m_pool) { m_pool->deleteLater(); } } BackgroundCache *BackgroundCache::self() { static BackgroundCache cache; return &cache; } void BackgroundCache::settingsFileChanged(const QString &file) { if (!file.endsWith(PLASMACONFIG)) { return; } if (m_initialized) { m_plasmaConfig->reparseConfiguration(); reload(); } } QString BackgroundCache::backgroundFromConfig(const KConfigGroup &config, QString wallpaperPlugin) const { auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General"); if (wallpaperConfig.hasKey("Image")) { // Trying for the wallpaper auto wallpaper = wallpaperConfig.readEntry("Image", QString()); if (!wallpaper.isEmpty()) { return wallpaper; } } if (wallpaperConfig.hasKey("Color")) { auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0)); return backgroundColor.name(); } return QString(); } bool BackgroundCache::isDesktopContainment(const KConfigGroup &containment) const { const auto type = containment.readEntry("plugin", QString()); if (type == "org.kde.desktopcontainment" || type == "org.kde.plasma.folder" ) { return true; } return false; } void BackgroundCache::reload() { // Traversing through all containments in search for // containments that define activities in plasma KConfigGroup plasmaConfigContainments = m_plasmaConfig->group("Containments"); //!activityId and screen names for which their background was updated QHash> updates; for (const auto &containmentId : plasmaConfigContainments.groupList()) { const auto containment = plasmaConfigContainments.group(containmentId); const auto wallpaperPlugin = containment.readEntry("wallpaperplugin", QString()); const auto lastScreen = containment.readEntry("lastScreen", 0); const auto activity = containment.readEntry("activityId", QString()); //! Ignore the containment if the activity is not defined or //! the containment is not a plasma desktop if (activity.isEmpty() || !isDesktopContainment(containment)) continue; const auto returnedBackground = backgroundFromConfig(containment, wallpaperPlugin); QString background = returnedBackground; if (background.startsWith("file://")) { background = returnedBackground.mid(7); } QString screenName = m_pool->connector(lastScreen); //! Take case of broadcasted backgrounds, when their plugin is changed they should be disabled if (pluginExistsFor(activity,screenName) && m_plugins[activity][screenName] != wallpaperPlugin && backgroundIsBroadcasted(activity, screenName)){ //! in such case the Desktop changed wallpaper plugin and the broadcasted wallpapers should be removed setBroadcastedBackgroundsEnabled(activity, screenName, false); } m_plugins[activity][screenName] = wallpaperPlugin; if (background.isEmpty() || backgroundIsBroadcasted(activity, screenName)) { continue; } if(!m_backgrounds.contains(activity) || !m_backgrounds[activity].contains(screenName) || m_backgrounds[activity][screenName] != background) { updates[activity].append(screenName); } m_backgrounds[activity][screenName] = background; } m_initialized = true; for (const auto &activity : updates.keys()) { for (const auto &screen : updates[activity]) { emit backgroundChanged(activity, screen); } } } QString BackgroundCache::background(QString activity, QString screen) { if (m_backgrounds.contains(activity) && m_backgrounds[activity].contains(screen)) { return m_backgrounds[activity][screen]; } else { return m_defaultWallpaperPath; } } bool BackgroundCache::busyFor(QString activity, QString screen, Plasma::Types::Location location) { QString assignedBackground = background(activity, screen); if (!assignedBackground.isEmpty()) { return busyForFile(assignedBackground, location); } return false; } float BackgroundCache::brightnessFor(QString activity, QString screen, Plasma::Types::Location location) { QString assignedBackground = background(activity, screen); if (!assignedBackground.isEmpty()) { return brightnessForFile(assignedBackground, location); } return -1000; } float BackgroundCache::brightnessFromArea(QImage &image, int firstRow, int firstColumn, int endRow, int endColumn) { float areaBrightness = -1000; if (image.format() != QImage::Format_Invalid) { for (int row = firstRow; row < endRow; ++row) { QRgb *line = (QRgb *)image.scanLine(row); for (int col = firstColumn; col < endColumn ; ++col) { QRgb pixelData = line[col]; float pixelBrightness = Latte::colorBrightness(pixelData); areaBrightness = (areaBrightness == -1000) ? pixelBrightness : (areaBrightness + pixelBrightness); } } float areaSize = (endRow - firstRow) * (endColumn - firstColumn); areaBrightness = areaBrightness / areaSize; } return areaBrightness; } bool BackgroundCache::areaIsBusy(float bright1, float bright2) { bool bright1IsLight = bright1>=123; bool bright2IsLight = bright2>=123; bool inBounds = bright1>=0 && bright2<=255 && bright2>=0 && bright2<=255; return !inBounds || bright1IsLight != bright2IsLight; } //! In order to calculate the brightness and busy hints for specific image //! the code is doing the following. It is not needed to calculate these values //! for the entire image that would also be cpu costly. The function takes //! the location of the area in the image for which we are interested. -//! The area is splitted in ten different Tiles and for each one its brightness +//! The area is split in ten different Tiles and for each one its brightness //! is computed. The brightness average from these tiles provides the entire //! area brightness. In order to indicate if this area is busy or not we //! compare the minimum and the maximum values of brightness from these //! tiles. If the difference it too big then the area is busy void BackgroundCache::updateImageCalculations(QString imageFile, Plasma::Types::Location location) { if (m_hintsCache.size() > MAXHASHSIZE) { cleanupHashes(); } //! if it is a local image QImage image(imageFile); if (image.format() != QImage::Format_Invalid) { float brightness{-1000}; float maxBrightness{0}; float minBrightness{255}; bool vertical = (location == Plasma::Types::LeftEdge || location == Plasma::Types::RightEdge) ? true : false; int imageLength = !vertical ? image.width() : image.height(); int tiles{qMin(10,imageLength)}; //! 24px. should be enough because the views are always snapped to edges int tileThickness = !vertical ? qMin(24,image.height()) : qMin(24,image.width()); int tileLength = imageLength / tiles ; int tileWidth = !vertical ? tileLength : tileThickness; int tileHeight = !vertical ? tileThickness : tileLength; float factor = ((float)100/tiles)/100; QList subBrightness; qDebug() << "------------ -- Image Calculations -- --------------" ; qDebug() << "Hints for Background image | " << imageFile; qDebug() << "Hints for Background image | Edge: " << location << ", Image size: " << image.width() << "x" << image.height() << ", Tiles: " << tiles << ", subsize: " << tileWidth << "x" << tileHeight; //! Iterating algorigthm int firstRow = 0; int firstColumn = 0; int endRow = 0; int endColumn = 0; //! horizontal tiles calculations if (location == Plasma::Types::TopEdge) { firstRow = 0; endRow = tileThickness; } else if (location == Plasma::Types::BottomEdge) { firstRow = image.height() - tileThickness - 1; endRow = image.height() - 1; } if (!vertical) { for (int i=1; i<=tiles; ++i) { float subFactor = ((float)i) * factor; firstColumn = endColumn+1; endColumn = (subFactor*imageLength) - 1; endColumn = qMin(endColumn, imageLength-1); int tempBrightness = brightnessFromArea(image, firstRow, firstColumn, endRow, endColumn); qDebug() << " Tile considering horizontal << (" << firstColumn << "," << firstRow << ") - (" << endColumn << "," << endRow << "), subfactor: " << subFactor << ", brightness: " << tempBrightness; subBrightness.append(tempBrightness); if (tempBrightness > maxBrightness) { maxBrightness = tempBrightness; } if (tempBrightness < minBrightness) { minBrightness = tempBrightness; } } } //! vertical tiles calculations if (location == Plasma::Types::LeftEdge) { firstColumn = 0; endColumn = tileThickness; } else if (location == Plasma::Types::RightEdge) { firstColumn = image.width() - 1 - tileThickness; endColumn = image.width() - 1; } if (vertical) { for (int i=1; i<=tiles; ++i) { float subFactor = ((float)i) * factor; firstRow = endRow+1; endRow = (subFactor*imageLength) - 1; endRow = qMin(endRow, imageLength-1); int tempBrightness = brightnessFromArea(image, firstRow, firstColumn, endRow, endColumn); qDebug() << " Tile considering vertical << (" << firstColumn << "," << firstRow << ") - (" << endColumn << "," << endRow << "), subfactor: " << subFactor << ", brightness: " << tempBrightness; subBrightness.append(tempBrightness); if (tempBrightness > maxBrightness) { maxBrightness = tempBrightness; } if (tempBrightness < minBrightness) { minBrightness = tempBrightness; } } } //! compute total brightness for this area float subBrightnessSum = 0; for (int i=0; i(); } m_broadcasted[activity].append(screen); } else if (!enabled && backgroundIsBroadcasted(activity, screen)) { m_broadcasted[activity].removeAll(screen); if (m_broadcasted[activity].isEmpty()) { m_broadcasted.remove(activity); } reload(); } } bool BackgroundCache::backgroundIsBroadcasted(QString activity, QString screenName) { return m_broadcasted.contains(activity) && m_broadcasted[activity].contains(screenName); } bool BackgroundCache::pluginExistsFor(QString activity, QString screenName) { return m_plugins.contains(activity) && m_plugins[activity].contains(screenName); } } } diff --git a/plasmoid/package/contents/ui/task/IconItem.qml b/plasmoid/package/contents/ui/task/IconItem.qml index 3e1ea2f0..9b24856e 100644 --- a/plasmoid/package/contents/ui/task/IconItem.qml +++ b/plasmoid/package/contents/ui/task/IconItem.qml @@ -1,808 +1,808 @@ /* * 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.4 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet 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 "animations" as TaskAnimations //I am using KQuickControlAddons.QIconItem even though onExit it triggers the following error //QObject::~QObject: Timers cannot be stopped from another thread //but it increases performance almost to double during animation Item{ id: taskIcon width: wrapper.regulatorWidth height: wrapper.regulatorHeight //big interval to show shadows only after all the crappy adds and removes of tasks //have happened property bool firstDrawed: true property bool toBeDestroyed: false // three intervals in order to create the necessary buffers from the // PlasmaCore.IconItem, one big interval for the first creation of the // plasmoid, a second one for the first creation of a task and a small one // for simple updates. // This is done before especially on initialization stage some visuals // are not ready and empty buffers are created //property int firstDrawedInterval: root.initializationStep ? 2000 : 1000 // property int shadowInterval: firstDrawed ? firstDrawedInterval : 250 property int shadowInterval: firstDrawed ? 1000 : 250 property int shadowSize : root.appShadowSize readonly property bool smartLauncherEnabled: ((taskItem.isStartup === false) && (root.showInfoBadge || root.showProgressBadge)) readonly property variant iconDecoration: decoration readonly property color backgroundColor: iconImageBuffer.backgroundColor readonly property color glowColor: iconImageBuffer.glowColor property QtObject buffers: null property QtObject smartLauncherItem: null property Item visualIconItem: iconImageBuffer property Item titleTooltipVisualParent: titleTooltipParent property Item previewsTootipVisualParent: previewsTooltipParent /* Rectangle{ anchors.fill: parent border.width: 1 border.color: "green" color: "transparent" } */ onSmartLauncherEnabledChanged: { if (smartLauncherEnabled && !smartLauncherItem) { var smartLauncher = Qt.createQmlObject( " import org.kde.plasma.private.taskmanager 0.1 as TaskManagerApplet; TaskManagerApplet.SmartLauncherItem { }", taskIcon); smartLauncher.launcherUrl = Qt.binding(function() { return taskItem.launcherUrlWithIcon; }); smartLauncherItem = smartLauncher; } else if (!smartLauncherEnabled && smartLauncherItem) { smartLauncherItem.destroy(); smartLauncherItem = null; } } Rectangle{ id: draggedRectangle width: taskItem.isSeparator ? parent.width + 1 : iconImageBuffer.width+1 height: taskItem.isSeparator ? parent.height + 1 : iconImageBuffer.height+1 anchors.centerIn: iconGraphic opacity: 0 radius: 3 anchors.margins: 5 property color tempColor: theme.highlightColor color: tempColor border.width: 1 border.color: theme.highlightColor onTempColorChanged: tempColor.a = 0.35; } TitleTooltipParent{ id: titleTooltipParent thickness: root.zoomFactor * (root.iconSize + root.thickMargins) } TitleTooltipParent{ id: previewsTooltipParent thickness: root.zoomFactor * (root.iconSize + root.thickMargins) + 1 } // KQuickControlAddons.QIconItem{ Item{ id: iconGraphic width: parent.width height: parent.height //fix bug #478, when changing form factor sometimes the tasks are not positioned //correctly, in such case we make a fast reinitialization for the sizes Connections { target: plasmoid onFormFactorChanged:{ taskItem.inAddRemoveAnimation = false; wrapper.mScale = 1.01; wrapper.tempScaleWidth = 1.01; wrapper.tempScaleHeight = 1.01; wrapper.mScale = 1; wrapper.tempScaleWidth = 1; wrapper.tempScaleHeight = 1; } } Latte.IconItem{ id: iconImageBuffer anchors.centerIn: parent width: Math.round(newTempSize) height: Math.round(width) source: decoration smooth: root.zoomFactor === 1 ? true : false providesColors: indicators ? indicators.info.needsIconColors : false opacity: root.enableShadows && taskWithShadow.active && (taskWithShadow.item.status === ShaderEffect.Compiled) ? 0 : 1 visible: !taskItem.isSeparator && !badgesLoader.active onValidChanged: { if (!valid && (source === decoration || source === "unknown")) { source = "application-x-executable"; } } //! try to show the correct icon when a window is removed... libtaskmanager when a window is removed //! sends an unknown pixmap as icon Connections { target: taskItem onInRemoveStageChanged: { if (taskItem.inRemoveStage && iconImageBuffer.lastValidSourceName !== "") { iconImageBuffer.source = iconImageBuffer.lastValidSourceName; } } } property int zoomedSize: root.zoomFactor * root.iconSize property real basicScalingWidth : wrapper.inTempScaling ? (root.iconSize * wrapper.scaleWidth) : root.iconSize * wrapper.mScale property real basicScalingHeight : wrapper.inTempScaling ? (root.iconSize * wrapper.scaleHeight) : root.iconSize * wrapper.mScale property real newTempSize: { if (wrapper.opacity == 1) return Math.min(basicScalingWidth, basicScalingHeight) else return Math.max(basicScalingWidth, basicScalingHeight) } //! Latte Side Painting-style if the user chose it Loader{ anchors.fill: iconImageBuffer active: plasmoid.configuration.forceMonochromaticIcons sourceComponent: ColorOverlay { anchors.fill: parent color: latteBridge ? latteBridge.palette.textColor : "transparent" source: iconImageBuffer } } //! Latte Side Painting-style if the user chose it ///states for launcher animation states: [ State{ name: "*" when: !launcherAnimation.running && !newWindowAnimation.running && !taskItem.inAddRemoveAnimation && !fastRestoreAnimation.running AnchorChanges{ target:iconImageBuffer; anchors.horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined; anchors.verticalCenter: root.vertical ? parent.verticalCenter : undefined; anchors.right: root.position === PlasmaCore.Types.RightPositioned ? parent.right : undefined; anchors.left: root.position === PlasmaCore.Types.LeftPositioned ? parent.left : undefined; anchors.top: root.position === PlasmaCore.Types.TopPositioned ? parent.top : undefined; anchors.bottom: root.position === PlasmaCore.Types.BottomPositioned ? parent.bottom : undefined; } }, State{ name: "inAddRemoveAnimation" when: taskItem.inAddRemoveAnimation AnchorChanges{ target:iconImageBuffer; anchors.horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined; anchors.verticalCenter: root.vertical ? parent.verticalCenter : undefined; anchors.right: root.position === PlasmaCore.Types.LeftPositioned ? parent.right : undefined; anchors.left: root.position === PlasmaCore.Types.RightPositioned ? parent.left : undefined; anchors.top: root.position === PlasmaCore.Types.BottomPositioned ? parent.top : undefined; anchors.bottom: root.position === PlasmaCore.Types.TopPositioned ? parent.bottom : undefined; } }, State{ name: "animating" when: (launcherAnimation.running || newWindowAnimation.running || fastRestoreAnimation.running) && !taskItem.inAddRemoveAnimation AnchorChanges{ target:iconImageBuffer; anchors.horizontalCenter: !root.vertical ? parent.horizontalCenter : undefined; anchors.verticalCenter: root.vertical ? parent.verticalCenter : undefined; anchors.right: root.position === PlasmaCore.Types.LeftPositioned ? parent.right : undefined; anchors.left: root.position === PlasmaCore.Types.RightPositioned ? parent.left : undefined; anchors.top: root.position === PlasmaCore.Types.BottomPositioned ? parent.top : undefined; anchors.bottom: root.position === PlasmaCore.Types.TopPositioned ? parent.bottom : undefined; } } ] ///transitions, basic for the anchor changes transitions: [ Transition{ from: "animating" to: "*" enabled: !fastRestoreAnimation.running && !taskItem.inMimicParabolicAnimation AnchorAnimation { duration: 1.5*root.durationTime*units.longDuration } } ] } //IconImageBuffer //! Shadows Loader{ id: taskWithShadow anchors.fill: iconImageBuffer active: root.enableShadows && !taskItem.isSeparator sourceComponent: DropShadow{ anchors.fill: parent color: root.appShadowColor fast: true samples: 2 * radius source: badgesLoader.active ? badgesLoader.item : iconImageBuffer radius: root.appShadowSize verticalOffset: 2 } } //! Shadows //! Combined Loader for Progress and Audio badges masks Loader{ id: badgesLoader anchors.fill: iconImageBuffer active: activateProgress > 0 asynchronous: true opacity: stateColorizer.opacity > 0 ? 0 : 1 property real activateProgress: showInfo || showProgress || showAudio ? 1 : 0 property bool showInfo: (root.showInfoBadge && taskIcon.smartLauncherItem && !taskItem.isSeparator && (taskIcon.smartLauncherItem.countVisible || taskItem.badgeIndicator > 0)) property bool showProgress: root.showProgressBadge && taskIcon.smartLauncherItem && !taskItem.isSeparator && taskIcon.smartLauncherItem.progressVisible property bool showAudio: (root.showAudioBadge && taskItem.hasAudioStream && taskItem.playingAudio && !taskItem.isSeparator) && !shortcutBadge.active Behavior on activateProgress { NumberAnimation { duration: root.durationTime*2*units.longDuration } } sourceComponent: Item{ ShaderEffect { id: iconOverlay enabled: false anchors.fill: parent property var source: ShaderEffectSource { sourceItem: Latte.IconItem{ width: iconImageBuffer.width height: iconImageBuffer.height source: iconImageBuffer.source Loader{ anchors.fill: parent active: plasmoid.configuration.forceMonochromaticIcons sourceComponent: ColorOverlay { anchors.fill: parent color: latteBridge ? latteBridge.palette.textColor : "transparent" source: iconImageBuffer } } } } property var mask: ShaderEffectSource { sourceItem: Item{ LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft && !root.vertical LayoutMirroring.childrenInherit: true width: iconImageBuffer.width height: iconImageBuffer.height Rectangle{ id: maskRect width: Math.max(badgeVisualsLoader.infoBadgeWidth, parent.width / 2) height: parent.height / 2 radius: parent.height visible: badgesLoader.showInfo || badgesLoader.showProgress //! Removes any remainings from the icon around the roundness at the corner Rectangle{ id: maskCorner width: parent.width/2 height: parent.height/2 } states: [ State { name: "default" when: (plasmoid.location !== PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } AnchorChanges { target: maskCorner anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } }, State { name: "right" when: (plasmoid.location === PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } AnchorChanges { target: maskCorner anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } } ] } // progressMask Rectangle{ id: maskRect2 width: parent.width/2 height: width radius: width visible: badgesLoader.showAudio Rectangle{ id: maskCorner2 width:parent.width/2 height:parent.height/2 } states: [ State { name: "default" when: (plasmoid.location !== PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect2 anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } AnchorChanges { target: maskCorner2 anchors{ top:parent.top; bottom:undefined; left:parent.left; right:undefined;} } }, State { name: "right" when: (plasmoid.location === PlasmaCore.Types.RightEdge) AnchorChanges { target: maskRect2 anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } AnchorChanges { target: maskCorner2 anchors{ top:parent.top; bottom:undefined; left:undefined; right:parent.right;} } } ] } // audio mask } hideSource: true live: true } //end of mask supportsAtlasTextures: true fragmentShader: " varying highp vec2 qt_TexCoord0; uniform highp float qt_Opacity; uniform lowp sampler2D source; uniform lowp sampler2D mask; void main() { gl_FragColor = texture2D(source, qt_TexCoord0.st) * (1.0 - (texture2D(mask, qt_TexCoord0.st).a)) * qt_Opacity; } " } //end of sourceComponent } } ////! //! START: Badges Visuals //! the badges visual get out from iconGraphic in order to be able to draw shadows that //! extend beyond the iconGraphic boundaries Loader { id: badgeVisualsLoader anchors.fill: iconImageBuffer active: badgesLoader.active readonly property int infoBadgeWidth: active ? publishedInfoBadgeWidth : 0 property int publishedInfoBadgeWidth: 0 sourceComponent: Item { ProgressOverlay{ id: infoBadge anchors.right: parent.right anchors.top: parent.top width: Math.max(parent.width, contentWidth) height: parent.height opacity: badgesLoader.activateProgress visible: badgesLoader.showInfo || badgesLoader.showProgress layer.enabled: root.enableShadows layer.effect: DropShadow { color: root.appShadowColor fast: true samples: 2 * radius source: infoBadge radius: root.appShadowSize verticalOffset: 2 } } AudioStream{ id: audioStreamBadge anchors.fill: parent opacity: badgesLoader.activateProgress visible: badgesLoader.showAudio layer.enabled: root.enableShadows layer.effect: DropShadow { color: root.appShadowColor fast: true samples: 2 * radius source: audioStreamBadge radius: root.appShadowSize verticalOffset: 2 } } Binding { target: badgeVisualsLoader property: "publishedInfoBadgeWidth" value: infoBadge.contentWidth } } } //! GREY-ing the information badges when the task is dragged //! moved out of badgeVisualsLoader in order to avoid crashes //! when the latte view is removed Loader { width: iconImageBuffer.width height: iconImageBuffer.height anchors.centerIn: iconImageBuffer active: badgeVisualsLoader.active sourceComponent: Colorize{ source: badgeVisualsLoader.item //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when zoomFactor === 1 - //! I dont know enabling cached=true helps, but it does. + //! I don't know enabling cached=true helps, but it does. cached: true opacity: stateColorizer.opacity hue: stateColorizer.hue saturation: stateColorizer.saturation lightness: stateColorizer.lightness } } //! END: Badges Visuals //! Effects Colorize{ id: stateColorizer anchors.centerIn: iconImageBuffer width: iconImageBuffer.width height: iconImageBuffer.height //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when zoomFactor === 1 - //! I dont know enabling cached=true helps, but it does. + //! I don't know enabling cached=true helps, but it does. cached: true source: badgesLoader.active ? badgesLoader : iconImageBuffer visible: !isSeparator opacity:0 hue:0 saturation:0 lightness:0 } BrightnessContrast{ id:hoveredImage anchors.centerIn: iconImageBuffer width: iconImageBuffer.width height: iconImageBuffer.height //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when zoomFactor === 1 - //! I dont know enabling cached=true helps, but it does. + //! I don't know enabling cached=true helps, but it does. cached: true source: badgesLoader.active ? badgesLoader : iconImageBuffer visible: !isSeparator opacity: taskItem.containsMouse && !clickedAnimation.running && !indicators.info.providesHoveredAnimation ? 1 : 0 brightness: 0.30 contrast: 0.1 Behavior on opacity { NumberAnimation { duration: root.durationTime*units.longDuration } } } BrightnessContrast { id: brightnessTaskEffect anchors.centerIn: iconImageBuffer width: iconImageBuffer.width height: iconImageBuffer.height //! HACK TO AVOID PIXELIZATION //! WORKAROUND: When Effects are enabled e.g. BrightnessContrast, Colorize etc. //! the icon appears pixelated. It is even most notable when zoomFactor === 1 - //! I dont know enabling cached=true helps, but it does. + //! I don't know enabling cached=true helps, but it does. cached: true source: badgesLoader.active ? badgesLoader : iconImageBuffer visible: clickedAnimation.running && !isSeparator } //! Effects ShortcutBadge{ id: shortcutBadge } } Loader { id: dropFilesVisual active: applyOpacity>0 width: !root.vertical ? length : thickness height: !root.vertical ? thickness : length anchors.centerIn: parent readonly property int length: root.iconSize + root.lengthMargins readonly property int thickness: root.iconSize + root.thickMargins readonly property real applyOpacity: root.dropNewLauncher && !mouseHandler.onlyLaunchers && (root.dragSource == null) && (mouseHandler.hoveredItem === taskItem) ? 0.7 : 0 sourceComponent: LatteComponents.AddItem { anchors.fill: parent backgroundOpacity: dropFilesVisual.applyOpacity } } Component.onDestruction: { taskIcon.toBeDestroyed = true; if(removingAnimation.removingItem) removingAnimation.removingItem.destroy(); } Connections{ target: taskItem onInAttentionChanged:{ if (!taskItem.inAttention && newWindowAnimation.running && taskItem.inAttentionAnimation) { newWindowAnimation.pause(); fastRestoreAnimation.start(); } } } ///// Animations ///// TaskAnimations.ClickedAnimation { id: clickedAnimation } TaskAnimations.LauncherAnimation { id:launcherAnimation } TaskAnimations.NewWindowAnimation { id: newWindowAnimation } TaskAnimations.RemoveWindowFromGroupAnimation { id: removingAnimation } TaskAnimations.FastRestoreAnimation { id: fastRestoreAnimation } //////////// States //////////////////// states: [ State{ name: "*" when: !taskItem.isDragged }, State{ name: "isDragged" when: ( (taskItem.isDragged) && (!root.inConfigureAppletsMode) ) } ] //////////// Transitions ////////////// transitions: [ Transition{ id: isDraggedTransition to: "isDragged" property int speed: root.durationTime*units.longDuration SequentialAnimation{ ScriptAction{ script: { icList.directRender = false; if(latteView) { latteView.globalDirectRender=false; } taskItem.inBlockingAnimation = true; root.clearZoom(); } } PropertyAnimation { target: wrapper property: "mScale" to: 1 + ((root.zoomFactor - 1) / 3) duration: isDraggedTransition.speed / 2 easing.type: Easing.OutQuad } ParallelAnimation{ PropertyAnimation { target: draggedRectangle property: "opacity" to: 1 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: iconImageBuffer property: "opacity" to: 0 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: stateColorizer property: "opacity" to: taskItem.isSeparator ? 0 : 1 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } } } onRunningChanged: { if(running){ taskItem.animationStarted(); //root.animations++; parabolicManager.clearTasksGreaterThan(index); parabolicManager.clearTasksLowerThan(index); if (latteView){ latteView.parabolicManager.clearAppletsGreaterThan(latteView.latteAppletPos); latteView.parabolicManager.clearAppletsLowerThan(latteView.latteAppletPos); } } } }, Transition{ id: defaultTransition from: "isDragged" to: "*" property int speed: root.durationTime*units.longDuration SequentialAnimation{ ScriptAction{ script: { icList.directRender = false; if(latteView) { latteView.globalDirectRender=false; } } } ParallelAnimation{ PropertyAnimation { target: draggedRectangle property: "opacity" to: 0 duration: defaultTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: iconImageBuffer property: "opacity" to: 1 duration: defaultTransition.speed easing.type: Easing.OutQuad } PropertyAnimation { target: stateColorizer property: "opacity" to: 0 duration: isDraggedTransition.speed easing.type: Easing.OutQuad } } /* PropertyAnimation { target: wrapper property: "mScale" to: 1; duration: isDraggedTransition.speed easing.type: Easing.OutQuad }*/ ScriptAction{ script: { taskItem.inBlockingAnimation = false; } } } onRunningChanged: { if(!running){ var halfZoom = 1 + ((root.zoomFactor - 1) / 2); wrapper.calculateScales((root.iconSize+root.thickMargins)/2); taskItem.animationEnded(); // root.animations--; } } } ] }// Icon Item