diff --git a/app/layoutmanager.cpp b/app/layoutmanager.cpp index 0f46e5cf..29ceabcd 100644 --- a/app/layoutmanager.cpp +++ b/app/layoutmanager.cpp @@ -1,1234 +1,1246 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "layoutmanager.h" // local #include "importer.h" #include "infoview.h" #include "launcherssignals.h" #include "screenpool.h" #include "layout/abstractlayout.h" #include "layout/activelayout.h" #include "layout/genericlayout.h" #include "layout/sharedlayout.h" #include "settings/settingsdialog.h" #include "settings/universalsettings.h" #include "view/view.h" // Qt #include #include #include #include #include // KDE #include #include #include #include namespace Latte { const int MultipleLayoutsPresetId = 10; LayoutManager::LayoutManager(QObject *parent) : QObject(parent), m_importer(new Importer(this)), m_launchersSignals(new LaunchersSignals(this)), m_activitiesController(new KActivities::Controller(this)) { m_corona = qobject_cast(parent); if (m_corona) { connect(m_corona->universalSettings(), &UniversalSettings::currentLayoutNameChanged, this, &LayoutManager::currentLayoutNameChanged); connect(m_corona->universalSettings(), &UniversalSettings::showInfoWindowChanged, this, &LayoutManager::showInfoWindowChanged); m_dynamicSwitchTimer.setSingleShot(true); showInfoWindowChanged(); connect(&m_dynamicSwitchTimer, &QTimer::timeout, this, &LayoutManager::confirmDynamicSwitch); } } LayoutManager::~LayoutManager() { m_importer->deleteLater(); m_launchersSignals->deleteLater(); unload(); m_activitiesController->deleteLater(); } void LayoutManager::load() { int configVer = m_corona->universalSettings()->version(); qDebug() << "Universal Settings version : " << configVer; if (configVer < 2 && QFile(QDir::homePath() + "/.config/lattedockrc").exists()) { qDebug() << "Latte must update its configuration..."; m_importer->updateOldConfiguration(); importPresets(false); } else if (!QFile(QDir::homePath() + "/.config/lattedockrc").exists()) { //startup create what is necessary.... QDir layoutDir(QDir::homePath() + "/.config/latte"); if (!layoutDir.exists()) { QDir(QDir::homePath() + "/.config").mkdir("latte"); } newLayout(i18n("My Layout")); importPresets(false); m_corona->universalSettings()->setCurrentLayoutName(i18n("My Layout")); m_corona->universalSettings()->setVersion(2); } //! Check if the multiple-layouts hidden file is present, add it if it isnt if (!QFile(QDir::homePath() + "/.config/latte/" + Layout::AbstractLayout::MultipleLayoutsName + ".layout.latte").exists()) { importPreset(MultipleLayoutsPresetId, false); } qDebug() << "Latte is loading its layouts..."; connect(m_corona->m_activityConsumer, &KActivities::Consumer::currentActivityChanged, this, &LayoutManager::currentActivityChanged); connect(m_corona->m_activityConsumer, &KActivities::Consumer::runningActivitiesChanged, this, [&]() { if (memoryUsage() == Types::MultipleLayouts) { syncMultipleLayoutsToActivities(); } }); loadLayouts(); } void LayoutManager::unload() { bool multipleMode{activeLayout(Layout::AbstractLayout::MultipleLayoutsName)}; //! Unload all ActiveLayouts while (!m_activeLayouts.isEmpty()) { ActiveLayout *layout = m_activeLayouts.at(0); m_activeLayouts.removeFirst(); if (layout->isOriginalLayout() && multipleMode) { layout->syncToLayoutFile(true); } layout->unloadContainments(); layout->unloadLatteViews(); if (layout->isOriginalLayout() && multipleMode) { clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds(), true); } delete layout; } //! Unload all SharedLayouts while (!m_sharedLayouts.isEmpty()) { SharedLayout *layout = m_sharedLayouts.at(0); m_sharedLayouts.removeFirst(); if (multipleMode) { layout->syncToLayoutFile(true); } layout->unloadContainments(); layout->unloadLatteViews(); if (multipleMode) { clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds(), true); } delete layout; } //! Remove no-needed temp files QString temp1File = QDir::homePath() + "/.config/lattedock.copy1.bak"; QString temp2File = QDir::homePath() + "/.config/lattedock.copy2.bak"; QFile file1(temp1File); QFile file2(temp2File); if (file1.exists()) file1.remove(); if (file2.exists()) file2.remove(); } Latte::Corona *LayoutManager::corona() { return m_corona; } Importer *LayoutManager::importer() { return m_importer; } LaunchersSignals *LayoutManager::launchersSignals() { return m_launchersSignals; } QString LayoutManager::currentLayoutName() const { if (memoryUsage() == Types::SingleLayout) { return m_corona->universalSettings()->currentLayoutName(); } else if (memoryUsage() == Types::MultipleLayouts) { return m_currentLayoutNameInMultiEnvironment; } return QString(); } QString LayoutManager::defaultLayoutName() const { QByteArray presetNameOrig = QString("preset" + QString::number(1)).toUtf8(); QString presetPath = m_corona->kPackage().filePath(presetNameOrig); QString presetName = ActiveLayout::layoutName(presetPath); QByteArray presetNameChars = presetName.toUtf8(); presetName = i18n(presetNameChars); return presetName; } bool LayoutManager::layoutExists(QString layoutName) const { return m_layouts.contains(layoutName); } QStringList LayoutManager::layouts() const { return m_layouts; } QStringList LayoutManager::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()) && memoryUsage() == Types::SingleLayout) { fixedMenuLayouts.prepend(currentLayoutName()); } else if (memoryUsage() == Types::MultipleLayouts) { for (const auto layout : m_activeLayouts) { if (layout->isOriginalLayout() && !fixedMenuLayouts.contains(layout->name())) { fixedMenuLayouts.prepend(layout->name()); } } } return fixedMenuLayouts; } void LayoutManager::setMenuLayouts(QStringList layouts) { if (m_menuLayouts == layouts) { return; } m_menuLayouts = layouts; emit menuLayoutsChanged(); } QStringList LayoutManager::activities() { return m_corona->m_activityConsumer->activities(); } QStringList LayoutManager::runningActivities() { return m_corona->m_activityConsumer->runningActivities(); } QStringList LayoutManager::orphanedActivities() { QStringList orphans; for (const auto &activity : activities()) { if (m_assignedLayouts[activity].isEmpty()) { orphans.append(activity); } } return orphans; } QStringList LayoutManager::presetsPaths() const { return m_presetsPaths; } QString LayoutManager::layoutPath(QString layoutName) { QString path = QDir::homePath() + "/.config/latte/" + layoutName + ".layout.latte"; if (!QFile(path).exists()) { path = ""; } return path; } Types::LayoutsMemoryUsage LayoutManager::memoryUsage() const { return m_corona->universalSettings()->layoutsMemoryUsage(); } int LayoutManager::layoutsMemoryUsage() { return (int)m_corona->universalSettings()->layoutsMemoryUsage(); } void LayoutManager::setMemoryUsage(Types::LayoutsMemoryUsage memoryUsage) { m_corona->universalSettings()->setLayoutsMemoryUsage(memoryUsage); } bool LayoutManager::latteViewExists(Latte::View *view) const { for (const auto layout : m_activeLayouts) { for (const auto &v : layout->latteViews()) { if (v == view) { return true; } } } return false; } QStringList LayoutManager::activeLayoutsNames() { QStringList names; if (memoryUsage() == Types::SingleLayout) { names << currentLayoutName(); } else { for (int i = 0; i < m_activeLayouts.size(); ++i) { ActiveLayout *layout = m_activeLayouts.at(i); if (layout->isOriginalLayout()) { names << layout->name(); } } } return names; } +QStringList LayoutManager::sharedLayoutsNames() +{ + QStringList names; + + for (int i = 0; i < m_sharedLayouts.size(); ++i) { + SharedLayout *layout = m_sharedLayouts.at(i); + names << layout->name(); + } + + return names; +} + Layout::GenericLayout *LayoutManager::layout(QString id) const { Layout::GenericLayout *l = activeLayout(id); if (!l) { l = sharedLayout(id); } return l; } ActiveLayout *LayoutManager::activeLayout(QString id) const { for (int i = 0; i < m_activeLayouts.size(); ++i) { ActiveLayout *layout = m_activeLayouts.at(i); if (layout->name() == id) { return layout; } } return nullptr; } int LayoutManager::activeLayoutPos(QString id) const { for (int i = 0; i < m_activeLayouts.size(); ++i) { ActiveLayout *layout = m_activeLayouts.at(i); if (layout->name() == id) { return i; } } return -1; } SharedLayout *LayoutManager::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; } bool LayoutManager::assignActiveToSharedLayout(ActiveLayout *active, QString id) { if (memoryUsage() == Types::SingleLayout) { return false; } for (int i = 0; i < m_sharedLayouts.size(); ++i) { SharedLayout *layout = m_sharedLayouts.at(i); if (layout->name() == id) { layout->addActiveLayout(active); - // syncLatteViewsToScreens(); + // syncLatteViewsToScreens(); return true; } } //! If SharedLayout was not found, we must create it SharedLayout *top = new SharedLayout(active, this, Importer::layoutFilePath(id)); m_sharedLayouts.append(top); top->importToCorona(); - // syncLatteViewsToScreens(); + // syncLatteViewsToScreens(); return true; } ActiveLayout *LayoutManager::currentLayout() const { if (memoryUsage() == Types::SingleLayout) { return m_activeLayouts.at(0); } else { for (auto layout : m_activeLayouts) { if (layout->activities().contains(m_corona->m_activityConsumer->currentActivity())) { return layout; } } for (auto layout : m_activeLayouts) { if ((layout->name() != Layout::AbstractLayout::MultipleLayoutsName) && (layout->activities().isEmpty())) { return layout; } } } return nullptr; } void LayoutManager::updateCurrentLayoutNameInMultiEnvironment() { for (const auto layout : m_activeLayouts) { if (layout->isOriginalLayout() && layout->activities().contains(m_corona->activitiesConsumer()->currentActivity())) { m_currentLayoutNameInMultiEnvironment = layout->name(); emit currentLayoutNameChanged(); return; } } for (const auto layout : m_activeLayouts) { if (layout->isOriginalLayout() && layout->activities().isEmpty()) { m_currentLayoutNameInMultiEnvironment = layout->name(); emit currentLayoutNameChanged(); return; } } } void LayoutManager::currentActivityChanged(const QString &id) { if (memoryUsage() == Types::SingleLayout) { qDebug() << "activity changed :: " << id; m_shouldSwitchToLayout = shouldSwitchToLayout(id); m_dynamicSwitchTimer.start(); } else if (memoryUsage() == Types::MultipleLayouts) { updateCurrentLayoutNameInMultiEnvironment(); } } void LayoutManager::showInfoWindowChanged() { if (m_corona->universalSettings()->showInfoWindow()) { m_dynamicSwitchTimer.setInterval(1800); } else { m_dynamicSwitchTimer.setInterval(2300); } } QString LayoutManager::shouldSwitchToLayout(QString activityId) { if (m_assignedLayouts.contains(activityId) && m_assignedLayouts[activityId] != currentLayoutName()) { return m_assignedLayouts[activityId]; } else if (!m_assignedLayouts.contains(activityId) && !m_corona->universalSettings()->lastNonAssignedLayoutName().isEmpty() && m_corona->universalSettings()->lastNonAssignedLayoutName() != currentLayoutName()) { return m_corona->universalSettings()->lastNonAssignedLayoutName(); } return QString(); } void LayoutManager::confirmDynamicSwitch() { QString tempShouldSwitch = shouldSwitchToLayout(m_corona->m_activityConsumer->currentActivity()); if (tempShouldSwitch.isEmpty()) { return; } if (m_shouldSwitchToLayout == tempShouldSwitch && m_shouldSwitchToLayout != currentLayoutName()) { qDebug() << "dynamic switch to layout :: " << m_shouldSwitchToLayout; emit currentLayoutIsSwitching(currentLayoutName()); if (m_corona->universalSettings()->showInfoWindow()) { 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 LayoutManager::loadLayouts() { m_layouts.clear(); m_menuLayouts.clear(); m_presetsPaths.clear(); m_assignedLayouts.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) { ActiveLayout layoutSets(this, layoutDir.absolutePath() + "/" + layout); QStringList validActivityIds = validActivities(layoutSets.activities()); layoutSets.setActivities(validActivityIds); for (const auto &activity : validActivityIds) { m_assignedLayouts[activity] = layoutSets.name(); } m_layouts.append(layoutSets.name()); if (layoutSets.showInMenu()) { m_menuLayouts.append(layoutSets.name()); } } m_presetsPaths.append(m_corona->kPackage().filePath("preset1")); m_presetsPaths.append(m_corona->kPackage().filePath("preset2")); m_presetsPaths.append(m_corona->kPackage().filePath("preset3")); m_presetsPaths.append(m_corona->kPackage().filePath("preset4")); emit layoutsChanged(); emit menuLayoutsChanged(); } void LayoutManager::loadLayoutOnStartup(QString layoutName) { // if (memoryUsage() == Types::MultipleLayouts) { QStringList layouts = m_importer->checkRepairMultipleLayoutsLinkedFile(); //! Latte didn't close correctly, maybe a crash if (layouts.size() > 0) { QMessageBox *msg = new QMessageBox(); msg->setAttribute(Qt::WA_DeleteOnClose); msg->setIcon(QMessageBox::Warning); msg->setWindowTitle(i18n("Multiple Layouts Warning")); msg->setText(i18n("Latte did not close properly in the previous session. The following layout(s) [%0] were updated for consistency!!!").arg(layouts.join(","))); msg->setStandardButtons(QMessageBox::Ok); msg->open(); } //} switchToLayout(layoutName); } void LayoutManager::loadLatteLayout(QString layoutPath) { qDebug() << " -------------------------------------------------------------------- "; qDebug() << " -------------------------------------------------------------------- "; if (m_corona->containments().size() > 0) { qDebug() << "LOAD LATTE LAYOUT ::: There are still containments present !!!! :: " << m_corona->containments().size(); } if (!layoutPath.isEmpty() && m_corona->containments().size() == 0) { cleanupOnStartup(layoutPath); qDebug() << "LOADING CORONA LAYOUT:" << layoutPath; m_corona->loadLayout(layoutPath); //! ~~~ ADDING LATTEVIEWS AND ENFORCE LOADING IF TASKS ARENT PRESENT BASED ON SCREENS ~~~ !// //! this is used to record the first dock having tasks in it. It is used //! to specify which dock will be loaded on startup if a case that no "dock //! with tasks" will be loaded otherwise. Currently the older one dock wins /*int firstContainmentWithTasks = -1; //! this is used to check if a dock with tasks in it will be loaded on startup bool tasksWillBeLoaded = heuresticForLoadingDockWithTasks(&firstContainmentWithTasks); qDebug() << "TASKS WILL BE PRESENT AFTER LOADING ::: " << tasksWillBeLoaded; for (const auto containment : m_corona->containments()) { //! forceDockLoading is used when a latte configuration based on the //! current running screens does not provide a dock containing tasks. //! in such case the lowest latte containment containing tasks is loaded //! and it forcefully becomes primary dock if (!tasksWillBeLoaded && firstContainmentWithTasks == containment->id()) { tasksWillBeLoaded = true; //this protects by loading more than one dock at startup addDock(containment, true); } else { addDock(containment); } }*/ } } void LayoutManager::cleanupOnStartup(QString path) { KSharedConfigPtr filePtr = KSharedConfig::openConfig(path); KConfigGroup actionGroups = KConfigGroup(filePtr, "ActionPlugins"); QStringList deprecatedActionGroup; for (const auto &actId : actionGroups.groupList()) { QString pluginId = actionGroups.group(actId).readEntry("RightButton;NoModifier", ""); if (pluginId == "org.kde.contextmenu") { deprecatedActionGroup << actId; } } for (const auto &pId : deprecatedActionGroup) { qDebug() << "!!!!!!!!!!!!!!!! !!!!!!!!!!!! !!!!!!! REMOVING :::: " << pId; actionGroups.group(pId).deleteGroup(); } KConfigGroup containmentGroups = KConfigGroup(filePtr, "Containments"); QStringList removeContaimentsList; for (const auto &cId : containmentGroups.groupList()) { QString pluginId = containmentGroups.group(cId).readEntry("plugin", ""); if (pluginId == "org.kde.desktopcontainment") { //!must remove ghost containments first removeContaimentsList << cId; } } for (const auto &cId : removeContaimentsList) { containmentGroups.group(cId).deleteGroup(); } actionGroups.sync(); containmentGroups.sync(); } void LayoutManager::showAboutDialog() { m_corona->aboutApplication(); } void LayoutManager::importLatteLayout(QString layoutPath) { //! This might not be needed as it is Layout responsibility } void LayoutManager::hideAllViews() { for (const auto layout : m_activeLayouts) { if (layout->isOriginalLayout()) { emit currentLayoutIsSwitching(layout->name()); } } } void LayoutManager::addLayout(ActiveLayout *layout) { if (!m_activeLayouts.contains(layout)) { m_activeLayouts.append(layout); layout->initToCorona(m_corona); } } bool LayoutManager::switchToLayout(QString layoutName, int previousMemoryUsage) { if (m_activeLayouts.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 (memoryUsage() == Types::MultipleLayouts && previousMemoryUsage == -1) { ActiveLayout *layout = activeLayout(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_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_activeLayouts) { if (layout->isOriginalLayout()) { 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 = newLayout(i18n("Alternative"), i18n("Default")); } if (!lPath.isEmpty()) { if (memoryUsage() == Types::SingleLayout) { - // emit currentLayoutIsSwitching(currentLayoutName()); + // emit currentLayoutIsSwitching(currentLayoutName()); } else if (memoryUsage() == Types::MultipleLayouts && layoutName != Layout::AbstractLayout::MultipleLayoutsName) { ActiveLayout toLayout(this, lPath); QStringList toActivities = toLayout.activities(); ActiveLayout *activeForOrphans{nullptr}; for (const auto fromLayout : m_activeLayouts) { if (fromLayout->isOriginalLayout() && fromLayout->activities().isEmpty()) { activeForOrphans = fromLayout; break; } } if (toActivities.isEmpty() && activeForOrphans && (toLayout.name() != activeForOrphans->name())) { emit currentLayoutIsSwitching(activeForOrphans->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 (memoryUsage() == Types::MultipleLayouts && !activeLayout(Layout::AbstractLayout::MultipleLayoutsName)) { initializingMultipleLayouts = true; } if (memoryUsage() == Types::SingleLayout || initializingMultipleLayouts || previousMemoryUsage == Types::MultipleLayouts) { unload(); if (initializingMultipleLayouts) { fixedLayoutName = QString(Layout::AbstractLayout::MultipleLayoutsName); fixedLPath = layoutPath(fixedLayoutName); } ActiveLayout *newLayout = new ActiveLayout(this, fixedLPath, fixedLayoutName); addLayout(newLayout); loadLatteLayout(fixedLPath); emit activeLayoutsChanged(); } if (memoryUsage() == Types::MultipleLayouts) { if (!initializingMultipleLayouts && !activeLayout(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 ActiveLayout 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_corona->m_activityConsumer->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_corona->universalSettings()->setCurrentLayoutName(layoutName); if (!layoutIsAssigned(layoutName)) { m_corona->universalSettings()->setLastNonAssignedLayoutName(layoutName); } }); } else { qDebug() << "Layout : " << layoutName << " was not found..."; } return true; } void LayoutManager::syncMultipleLayoutsToActivities(QString layoutForOrphans) { qDebug() << " ---- --------- ------ syncMultipleLayoutsToActivities ------- "; qDebug() << " ---- --------- ------ ------------------------------- ------- "; QStringList layoutsToUnload; QStringList layoutsToLoad; layoutsToLoad << Layout::AbstractLayout::MultipleLayoutsName; bool allRunningActivitiesWillBeReserved{true}; if (layoutForOrphans.isEmpty() || m_assignedLayouts.values().contains(layoutForOrphans)) { layoutForOrphans = m_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_activeLayouts) { 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) { if (layoutName != Layout::AbstractLayout::MultipleLayoutsName) { ActiveLayout *layout = activeLayout(layoutName); int posLayout = activeLayoutPos(layoutName); if (posLayout >= 0) { qDebug() << "REMOVING LAYOUT ::::: " << layoutName; m_activeLayouts.removeAt(posLayout); if (layout->isOriginalLayout()) { layout->syncToLayoutFile(true); } layout->unloadContainments(); layout->unloadLatteViews(); clearUnloadedContainmentsFromLinkedFile(layout->unloadedContainmentsIds()); delete layout; } } } //! Add Layout for orphan activities if (!allRunningActivitiesWillBeReserved) { if (!activeLayout(layoutForOrphans)) { ActiveLayout *newLayout = new ActiveLayout(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 (!activeLayout(layoutName)) { ActiveLayout *newLayout = new ActiveLayout(this, QString(layoutPath(layoutName)), layoutName); if (newLayout) { qDebug() << "ACTIVATING LAYOUT ::::: " << layoutName; addLayout(newLayout); newLayout->importToCorona(); if (newLayout->isOriginalLayout() && m_corona->universalSettings()->showInfoWindow()) { showInfoWindow(i18n("Activating layout: %0 ...").arg(newLayout->name()), 5000, newLayout->appliedActivities()); } } } } updateCurrentLayoutNameInMultiEnvironment(); emit activeLayoutsChanged(); } void LayoutManager::pauseLayout(QString layoutName) { if (memoryUsage() == Types::MultipleLayouts) { ActiveLayout *layout = activeLayout(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 LayoutManager::syncActiveLayoutsToOriginalFiles() { if (memoryUsage() == Types::MultipleLayouts) { for (const auto layout : m_activeLayouts) { if (layout->isOriginalLayout()) { layout->syncToLayoutFile(); } } } } void LayoutManager::clearUnloadedContainmentsFromLinkedFile(QStringList containmentsIds, bool bypassChecks) { if (!m_corona || (memoryUsage() == Types::SingleLayout && !bypassChecks)) { return; } auto containments = m_corona->config()->group("Containments"); for (const auto &conId : containmentsIds) { qDebug() << "unloads ::: " << conId; KConfigGroup containment = containments.group(conId); containment.deleteGroup(); } containments.sync(); } void LayoutManager::syncLatteViewsToScreens() { for (const auto layout : m_sharedLayouts) { layout->syncLatteViewsToScreens(); } for (const auto layout : m_activeLayouts) { layout->syncLatteViewsToScreens(); } } QString LayoutManager::newLayout(QString layoutName, QString preset) { QDir layoutDir(QDir::homePath() + "/.config/latte"); QStringList filter; filter.append(QString(layoutName + "*.layout.latte")); QStringList files = layoutDir.entryList(filter, QDir::Files | QDir::NoSymLinks); //! if the newLayout already exists provide a newName that doesn't if (files.count() >= 1) { int newCounter = files.count() + 1; layoutName = layoutName + "-" + QString::number(newCounter); } QString newLayoutPath = layoutDir.absolutePath() + "/" + layoutName + ".layout.latte"; qDebug() << "adding layout : " << layoutName << " based on preset:" << preset; if (preset == i18n("Default") && !QFile(newLayoutPath).exists()) { qDebug() << "adding layout : succeed"; QFile(m_corona->kPackage().filePath("preset1")).copy(newLayoutPath); } return newLayoutPath; } //! This function figures in the beginning if a view with tasks //! in it will be loaded taking into account also the screens are present. bool LayoutManager::heuresticForLoadingViewWithTasks(int *firstContainmentWithTasks) { for (const auto containment : m_corona->containments()) { QString plugin = containment->pluginMetaData().pluginId(); if (plugin == "org.kde.latte.containment") { bool onPrimary = containment->config().readEntry("onPrimary", true); int lastScreen = containment->lastScreen(); qDebug() << "containment values: " << onPrimary << " - " << lastScreen; bool containsTasks = false; for (const auto applet : containment->applets()) { const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { containsTasks = true; break; } } if (containsTasks) { *firstContainmentWithTasks = containment->id(); if (onPrimary) { return true; } else { if (m_corona->screenPool()->hasId(lastScreen)) { QString connector = m_corona->screenPool()->connector(lastScreen); for (const auto scr : qGuiApp->screens()) { if (scr && scr->name() == connector) { return true; break; } } } } } } } return false; } void LayoutManager::importDefaultLayout(bool newInstanceIfPresent) { importPreset(1, newInstanceIfPresent); if (newInstanceIfPresent) { loadLayouts(); } } void LayoutManager::importPresets(bool includeDefault) { int start = 1; if (!includeDefault) { start = 2; } for (int i = start; i <= 4; ++i) { importPreset(i, false); } } void LayoutManager::importPreset(int presetNo, bool newInstanceIfPresent) { QDir configDir(QDir::homePath() + "/.config"); if (!QDir(configDir.absolutePath() + "/latte").exists()) { configDir.mkdir("latte"); } QByteArray presetNameOrig = QString("preset" + QString::number(presetNo)).toUtf8(); QString presetPath = m_corona->kPackage().filePath(presetNameOrig); QString presetName = ActiveLayout::layoutName(presetPath); QByteArray presetNameChars = presetName.toUtf8(); presetName = i18n(presetNameChars); //! hide the multiple layouts layout file from user presetName = (presetNo == MultipleLayoutsPresetId) ? "." + presetName : presetName; QString newLayoutFile = ""; if (newInstanceIfPresent) { newLayoutFile = QDir::homePath() + "/.config/latte/" + m_importer->uniqueLayoutName(presetName) + ".layout.latte"; } else { newLayoutFile = QDir::homePath() + "/.config/latte/" + presetName + ".layout.latte"; } if (!QFile(newLayoutFile).exists()) { QFile(presetPath).copy(newLayoutFile); QFileInfo newFileInfo(newLayoutFile); if (newFileInfo.exists() && !newFileInfo.isWritable()) { QFile(newLayoutFile).setPermissions(QFileDevice::ReadUser | QFileDevice::WriteUser | QFileDevice::ReadGroup | QFileDevice::ReadOther); } } } QStringList LayoutManager::validActivities(QStringList currentList) { QStringList validIds; for (const auto &activity : currentList) { if (activities().contains(activity)) { validIds.append(activity); } } return validIds; } bool LayoutManager::layoutIsAssigned(QString layoutName) { QHashIterator i(m_assignedLayouts); while (i.hasNext()) { i.next(); if (i.value() == layoutName) { return true; } } return false; } void LayoutManager::showLatteSettingsDialog(int page) { if (!m_latteSettingsDialog) { m_latteSettingsDialog = new SettingsDialog(nullptr, m_corona); } m_latteSettingsDialog->show(); if (m_latteSettingsDialog->isMinimized()) { m_latteSettingsDialog->showNormal(); } Types::LatteConfigPage configPage = static_cast(page); m_latteSettingsDialog->setCurrentPage(configPage); m_latteSettingsDialog->activateWindow(); } void LayoutManager::hideLatteSettingsDialog() { if (m_latteSettingsDialog) { m_latteSettingsDialog->deleteLater(); m_latteSettingsDialog = nullptr; } } void LayoutManager::showInfoWindow(QString info, int duration, QStringList activities) { for (const auto screen : qGuiApp->screens()) { InfoView *infoView = new InfoView(m_corona, info, screen); infoView->show(); infoView->setOnActivities(activities); QTimer::singleShot(duration, [this, infoView]() { infoView->deleteLater(); }); } } //! it is used just in order to provide translations for the presets void LayoutManager::ghostForTranslatedPresets() { QString preset1 = i18n("Default"); QString preset2 = i18n("Plasma"); QString preset3 = i18n("Unity"); QString preset4 = i18n("Extended"); } } diff --git a/app/layoutmanager.h b/app/layoutmanager.h index bd9d0e7c..29453eb8 100644 --- a/app/layoutmanager.h +++ b/app/layoutmanager.h @@ -1,210 +1,211 @@ /* * Copyright 2017 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef LAYOUTMANAGER_H #define LAYOUTMANAGER_H // local #include "launcherssignals.h" #include "settings/settingsdialog.h" // Qt #include #include #include // KDE #include namespace Plasma { class Containment; class Types; } namespace KActivities { class Controller; } namespace Latte { class Corona; class Importer; class ActiveLayout; class LaunchersSignals; class SharedLayout; class View; namespace Layout { class GenericLayout; } } namespace Latte { //! This class is responsible to manipulate all layouts. //! add,remove,rename, update configurations etc. class LayoutManager : public QObject { Q_OBJECT Q_PROPERTY(QString currentLayoutName READ currentLayoutName NOTIFY currentLayoutNameChanged) Q_PROPERTY(QStringList layouts READ layouts NOTIFY layoutsChanged) Q_PROPERTY(QStringList menuLayouts READ menuLayouts NOTIFY menuLayoutsChanged) Q_PROPERTY(LaunchersSignals *launchersSignals READ launchersSignals NOTIFY launchersSignalsChanged) public: LayoutManager(QObject *parent = nullptr); ~LayoutManager() override; Latte::Corona *corona(); Importer *importer(); void load(); void loadLayoutOnStartup(QString layoutName); void unload(); void hideAllViews(); void pauseLayout(QString layoutName); void syncLatteViewsToScreens(); void syncActiveLayoutsToOriginalFiles(); bool latteViewExists(Latte::View *view) const; bool layoutExists(QString layoutName) const; QString shouldSwitchToLayout(QString activityId); QString currentLayoutName() const; QString defaultLayoutName() const; QStringList layouts() const; QStringList menuLayouts() const; QStringList presetsPaths() const; Types::LayoutsMemoryUsage memoryUsage() const; void setMemoryUsage(Types::LayoutsMemoryUsage memoryUsage); //! returns an active layout with that #id (name), it returns null if such //! layout cant be found ActiveLayout *activeLayout(QString id) const; int activeLayoutPos(QString id) const; SharedLayout *sharedLayout(QString id) const; //! return an active or shared layout with #id (name), it returns null if such //! loaded layout was not found Layout::GenericLayout *layout(QString id) const; //! returns the current and active layout based on activities and user preferences ActiveLayout *currentLayout() const; LaunchersSignals *launchersSignals(); QStringList activities(); QStringList runningActivities(); QStringList orphanedActivities(); //! These are activities that haven't been assigned to specific layout void importDefaultLayout(bool newInstanceIfPresent = false); void importPresets(bool includeDefault = false); bool assignActiveToSharedLayout(ActiveLayout *active, QString id); public slots: void showAboutDialog(); void hideLatteSettingsDialog(); Q_INVOKABLE void showLatteSettingsDialog(int page = Latte::Types::LayoutPage); //! switch to specified layout, default previousMemoryUsage means that it didn't change Q_INVOKABLE bool switchToLayout(QString layoutName, int previousMemoryUsage = -1); Q_INVOKABLE int layoutsMemoryUsage(); //! creates a new layout with layoutName based on the preset Q_INVOKABLE QString newLayout(QString layoutName, QString preset = i18n("Default")); Q_INVOKABLE QStringList activeLayoutsNames(); + Q_INVOKABLE QStringList sharedLayoutsNames(); signals: void activeLayoutsChanged(); void currentLayoutChanged(); void currentLayoutNameChanged(); void launchersSignalsChanged(); void layoutsChanged(); void menuLayoutsChanged(); void currentLayoutIsSwitching(QString layoutName); private slots: void currentActivityChanged(const QString &id); void showInfoWindowChanged(); void syncMultipleLayoutsToActivities(QString layoutForOrphans = QString()); private: void addLayout(ActiveLayout *layout); void cleanupOnStartup(QString path); //!remove deprecated or oldstyle config options void clearUnloadedContainmentsFromLinkedFile(QStringList containmentsIds, bool bypassChecks = false); void confirmDynamicSwitch(); //! it is used just in order to provide translations for the presets void ghostForTranslatedPresets(); //! This function figures in the beginning if a view with tasks //! in it will be loaded taking into account also the screens are present. //! returns true if it will be loaded, false otherwise //! firstContainmentWithTasks = the first containment containing a taskmanager plasmoid bool heuresticForLoadingViewWithTasks(int *firstContainmentWithTasks); void importLatteLayout(QString layoutPath); void importPreset(int presetNo, bool newInstanceIfPresent = false); void loadLatteLayout(QString layoutPath); void loadLayouts(); void setMenuLayouts(QStringList layouts); void showInfoWindow(QString info, int duration, QStringList activities = {"0"}); void updateCurrentLayoutNameInMultiEnvironment(); bool layoutIsAssigned(QString layoutName); QString layoutPath(QString layoutName); QStringList validActivities(QStringList currentList); private: QString m_currentLayoutNameInMultiEnvironment; QString m_shouldSwitchToLayout; QStringList m_layouts; QStringList m_menuLayouts; QStringList m_presetsPaths; QHash m_assignedLayouts; QTimer m_dynamicSwitchTimer; QPointer m_latteSettingsDialog; Latte::Corona *m_corona{nullptr}; Importer *m_importer{nullptr}; LaunchersSignals *m_launchersSignals{nullptr}; QList m_activeLayouts; QList m_sharedLayouts; KActivities::Controller *m_activitiesController; friend class SettingsDialog; }; } #endif // LAYOUTMANAGER_H diff --git a/app/view/view.cpp b/app/view/view.cpp index af0b50e3..a2cf9afd 100644 --- a/app/view/view.cpp +++ b/app/view/view.cpp @@ -1,1221 +1,1221 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "view.h" // local #include "contextmenu.h" #include "effects.h" #include "positioner.h" #include "visibilitymanager.h" #include "settings/primaryconfigview.h" #include "settings/secondaryconfigview.h" #include "../lattecorona.h" #include "../layoutmanager.h" #include "../layout/activelayout.h" #include "../layout/genericlayout.h" #include "../plasma/extended/theme.h" #include "../screenpool.h" #include "../settings/universalsettings.h" #include "../shortcuts/globalshortcuts.h" #include "../shortcuts/shortcutstracker.h" #include "../../liblatte2/extras.h" // Qt #include #include #include #include #include #include #include // KDe #include #include #include #include #include // Plasma #include #include #include namespace Latte { //! both alwaysVisible and byPassWM are passed through corona because //! during the view window creation containment hasn't been set, but these variables //! are needed in order for window flags to be set correctly View::View(Plasma::Corona *corona, QScreen *targetScreen, bool byPassWM) : PlasmaQuick::ContainmentView(corona), m_contextMenu(new ViewPart::ContextMenu(this)), m_effects(new ViewPart::Effects(this)), m_positioner(new ViewPart::Positioner(this)) //needs to be created after Effects because it catches some of its signals { setTitle(corona->kPackage().metadata().name()); setIcon(qGuiApp->windowIcon()); setResizeMode(QuickViewSharedEngine::SizeRootObjectToView); setColor(QColor(Qt::transparent)); setClearBeforeRendering(true); const auto flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::NoDropShadowWindowHint | Qt::WindowDoesNotAcceptFocus; if (byPassWM) { setFlags(flags | Qt::BypassWindowManagerHint); } else { setFlags(flags); } KWindowSystem::setOnAllDesktops(winId(), true); if (targetScreen) m_positioner->setScreenToFollow(targetScreen); else m_positioner->setScreenToFollow(qGuiApp->primaryScreen()); connect(this, &View::containmentChanged , this, [ &, byPassWM]() { qDebug() << "dock view c++ containment changed 1..."; if (!this->containment()) return; qDebug() << "dock view c++ containment changed 2..."; //! First load default values from file restoreConfig(); //! Afterwards override that values in case during creation something different is needed setByPassWM(byPassWM); //! Check the screen assigned to this dock reconsiderScreen(); //! needs to be created before visibility creation because visibility uses it if (!m_windowsTracker) { m_windowsTracker = new ViewPart::WindowsTracker(this); emit windowsTrackerChanged(); } if (!m_visibility) { m_visibility = new ViewPart::VisibilityManager(this); connect(m_visibility, &ViewPart::VisibilityManager::isHiddenChanged, this, [&]() { if (m_visibility->isHidden()) { deactivateApplets(); } }); emit visibilityChanged(); } if (!m_indicator) { m_indicator = new ViewPart::Indicator(this); emit indicatorChanged(); } connect(this->containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), SLOT(statusChanged(Plasma::Types::ItemStatus))); }, Qt::DirectConnection); m_corona = qobject_cast(this->corona()); if (m_corona) { connect(m_corona, &Latte::Corona::viewLocationChanged, this, &View::dockLocationChanged); } } View::~View() { m_inDelete = true; if (m_indicator) { m_indicator->unloadIndicators(); } disconnect(m_corona, &Latte::Corona::availableScreenRectChangedFrom, this, &View::availableScreenRectChangedFrom); disconnect(containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), this, SLOT(statusChanged(Plasma::Types::ItemStatus))); qDebug() << "dock view deleting..."; rootContext()->setContextProperty(QStringLiteral("dock"), nullptr); rootContext()->setContextProperty(QStringLiteral("layoutManager"), nullptr); rootContext()->setContextProperty(QStringLiteral("shortcutsEngine"), nullptr); rootContext()->setContextProperty(QStringLiteral("themeExtended"), nullptr); rootContext()->setContextProperty(QStringLiteral("universalSettings"), nullptr); //! this disconnect does not free up connections correctly when //! latteView is deleted. A crash for this example is the following: //! switch to Alternative Session and disable compositing, //! the signal creating the crash was probably from deleted //! windows. //! this->disconnect(); if (m_configView) { m_configView->setVisible(false);//hide(); } if (m_contextMenu) { delete m_contextMenu; } //needs to be deleted before Effects because it catches some of its signals if (m_positioner) { delete m_positioner; } if (m_effects) { delete m_effects; } if (m_indicator) { delete m_indicator; } if (m_visibility) { delete m_visibility; } if (m_windowsTracker) { delete m_windowsTracker; } } void View::init() { connect(this, &QQuickWindow::xChanged, this, &View::xChanged); connect(this, &QQuickWindow::xChanged, this, &View::updateAbsoluteGeometry); connect(this, &QQuickWindow::yChanged, this, &View::yChanged); connect(this, &QQuickWindow::yChanged, this, &View::updateAbsoluteGeometry); connect(this, &QQuickWindow::widthChanged, this, &View::widthChanged); connect(this, &QQuickWindow::widthChanged, this, &View::updateAbsoluteGeometry); connect(this, &QQuickWindow::heightChanged, this, &View::heightChanged); connect(this, &QQuickWindow::heightChanged, this, &View::updateAbsoluteGeometry); connect(m_corona, &Latte::Corona::availableScreenRectChangedFrom, this, &View::availableScreenRectChangedFrom); connect(this, &View::byPassWMChanged, this, &View::saveConfig); connect(this, &View::isPreferredForShortcutsChanged, this, &View::saveConfig); connect(this, &View::onPrimaryChanged, this, &View::saveConfig); connect(this, &View::typeChanged, this, &View::saveConfig); connect(this, &View::normalThicknessChanged, this, [&]() { emit m_corona->availableScreenRectChangedFrom(this); }); connect(m_effects, &ViewPart::Effects::innerShadowChanged, this, [&]() { emit m_corona->availableScreenRectChangedFrom(this); }); connect(m_positioner, &ViewPart::Positioner::onHideWindowsForSlidingOut, this, &View::hideWindowsForSlidingOut); connect(m_positioner, &ViewPart::Positioner::screenGeometryChanged, this, &View::screenGeometryChanged); connect(m_positioner, &ViewPart::Positioner::windowSizeChanged, this, [&]() { emit m_corona->availableScreenRectChangedFrom(this); }); connect(m_contextMenu, &ViewPart::ContextMenu::menuChanged, this, &View::contextMenuIsShownChanged); ///!!!!! rootContext()->setContextProperty(QStringLiteral("latteView"), this); if (m_corona) { rootContext()->setContextProperty(QStringLiteral("layoutManager"), m_corona->layoutManager()); rootContext()->setContextProperty(QStringLiteral("shortcutsEngine"), m_corona->globalShortcuts()->shortcutsTracker()); rootContext()->setContextProperty(QStringLiteral("themeExtended"), m_corona->themeExtended()); rootContext()->setContextProperty(QStringLiteral("universalSettings"), m_corona->universalSettings()); } setSource(corona()->kPackage().filePath("lattedockui")); // setVisible(true); m_positioner->syncGeometry(); if (!KWindowSystem::isPlatformWayland()) { setVisible(true); } qDebug() << "SOURCE:" << source(); } bool View::inDelete() const { return m_inDelete; } void View::disconnectSensitiveSignals() { disconnect(m_corona, &Latte::Corona::availableScreenRectChangedFrom, this, &View::availableScreenRectChangedFrom); setManagedLayout(nullptr); if (m_windowsTracker) { m_windowsTracker->setEnabled(false); } } void View::availableScreenRectChangedFrom(View *origin) { if (m_inDelete || origin == this) return; if (formFactor() == Plasma::Types::Vertical) { m_positioner->syncGeometry(); } } void View::setupWaylandIntegration() { if (m_shellSurface) return; if (Latte::Corona *c = qobject_cast(corona())) { using namespace KWayland::Client; PlasmaShell *interface {c->waylandCoronaInterface()}; if (!interface) return; Surface *s{Surface::fromWindow(this)}; if (!s) return; m_shellSurface = interface->createSurface(s, this); qDebug() << "WAYLAND dock window surface was created..."; m_shellSurface->setSkipTaskbar(true); m_shellSurface->setRole(PlasmaShellSurface::Role::Panel); m_shellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsGoBelow); } } KWayland::Client::PlasmaShellSurface *View::surface() { return m_shellSurface; } //! the main function which decides if this dock is at the //! correct screen void View::reconsiderScreen() { m_positioner->reconsiderScreen(); } void View::copyView() { m_managedLayout->copyView(containment()); } void View::removeView() { if (m_managedLayout && m_managedLayout->viewsCount() > 1) { QAction *removeAct = this->containment()->actions()->action(QStringLiteral("remove")); if (removeAct) { removeAct->trigger(); } } } bool View::settingsWindowIsShown() { auto configView = qobject_cast(m_configView); return (configView != nullptr); } void View::showSettingsWindow() { if (!settingsWindowIsShown()) { emit m_visibility->mustBeShown(); showConfigurationInterface(containment()); applyActivitiesToWindows(); } } PlasmaQuick::ConfigView *View::configView() { return m_configView; } void View::showConfigurationInterface(Plasma::Applet *applet) { if (!applet || !applet->containment()) return; Plasma::Containment *c = qobject_cast(applet); if (m_configView && c && c->isContainment() && c == this->containment()) { if (m_configView->isVisible()) { m_configView->setVisible(false); //m_configView->hide(); } else { m_configView->setVisible(true); //m_configView->show(); } return; } else if (m_configView) { if (m_configView->applet() == applet) { m_configView->setVisible(true); //m_configView->show(); if (KWindowSystem::isPlatformX11()) { m_configView->requestActivate(); } return; } else { m_configView->setVisible(false); //m_configView->hide(); m_configView->deleteLater(); } } bool delayConfigView = false; if (c && containment() && c->isContainment() && c->id() == this->containment()->id()) { m_configView = new ViewPart::PrimaryConfigView(c, this); delayConfigView = true; } else { m_configView = new PlasmaQuick::ConfigView(applet); } m_configView.data()->init(); if (!delayConfigView) { m_configView->setVisible(true); //m_configView.data()->show(); } else { //add a timer for showing the configuration window the first time it is //created in order to give the containment's layouts the time to //calculate the window's height if (!KWindowSystem::isPlatformWayland()) { QTimer::singleShot(150, m_configView, SLOT(show())); } else { QTimer::singleShot(150, [this]() { m_configView->setVisible(true); }); } } } QRect View::localGeometry() const { return m_localGeometry; } void View::setLocalGeometry(const QRect &geometry) { if (m_localGeometry == geometry) { return; } m_localGeometry = geometry; emit localGeometryChanged(); updateAbsoluteGeometry(); } void View::updateAbsoluteGeometry(bool bypassChecks) { //! there was a -1 in height and width here. The reason of this //! if I remember correctly was related to multi-screen but I cant //! remember exactly the reason, something related to right edge in //! multi screen environment. BUT this was breaking the entire AlwaysVisible //! experience with struts. Removing them in order to restore correct //! behavior and keeping this comment in order to check for //! multi-screen breakage QRect absGeometry {x() + m_localGeometry.x(), y() + m_localGeometry.y() , m_localGeometry.width(), m_localGeometry.height()}; if (m_absoluteGeometry == absGeometry && !bypassChecks) { return; } if (m_absoluteGeometry != absGeometry) { m_absoluteGeometry = absGeometry; emit absoluteGeometryChanged(m_absoluteGeometry); } //! this is needed in order to update correctly the screenGeometries if (visibility() && corona() && visibility()->mode() == Types::AlwaysVisible) { //! main use of BYPASSCKECKS is from Positioner when the view changes screens emit m_corona->availableScreenRectChangedFrom(this); emit m_corona->availableScreenRegionChangedFrom(this); } } void View::statusChanged(Plasma::Types::ItemStatus status) { if (containment()) { if (containment()->status() >= Plasma::Types::NeedsAttentionStatus && containment()->status() != Plasma::Types::HiddenStatus) { setBlockHiding(true); } else if (!containment()->isUserConfiguring()){ setBlockHiding(false); } } } Types::ViewType View::type() const { return m_type; } void View::setType(Types::ViewType type) { if (m_type == type) { return; } m_type = type; emit typeChanged(); } bool View::alternativesIsShown() const { return m_alternativesIsShown; } void View::setAlternativesIsShown(bool show) { if (m_alternativesIsShown == show) { return; } m_alternativesIsShown = show; setBlockHiding(show); emit alternativesIsShownChanged(); } bool View::containsMouse() const { return m_containsMouse; } bool View::contextMenuIsShown() const { if (!m_contextMenu) { return false; } return m_contextMenu->menu(); } int View::currentThickness() const { if (formFactor() == Plasma::Types::Vertical) { return m_effects->mask().isNull() ? width() : m_effects->mask().width() - m_effects->innerShadow(); } else { return m_effects->mask().isNull() ? height() : m_effects->mask().height() - m_effects->innerShadow(); } } int View::normalThickness() const { return m_normalThickness; } void View::setNormalThickness(int thickness) { if (m_normalThickness == thickness) { return; } m_normalThickness = thickness; emit normalThicknessChanged(); } bool View::byPassWM() const { return m_byPassWM; } void View::setByPassWM(bool bypass) { if (m_byPassWM == bypass) { return; } m_byPassWM = bypass; emit byPassWMChanged(); } bool View::behaveAsPlasmaPanel() const { return m_behaveAsPlasmaPanel; } void View::setBehaveAsPlasmaPanel(bool behavior) { if (m_behaveAsPlasmaPanel == behavior) { return; } m_behaveAsPlasmaPanel = behavior; emit behaveAsPlasmaPanelChanged(); } bool View::inEditMode() const { return m_inEditMode; } void View::setInEditMode(bool edit) { if (m_inEditMode == edit) { return; } m_inEditMode = edit; emit inEditModeChanged(); } bool View::isPreferredForShortcuts() const { return m_isPreferredForShortcuts; } void View::setIsPreferredForShortcuts(bool preferred) { if (m_isPreferredForShortcuts == preferred) { return; } m_isPreferredForShortcuts = preferred; emit isPreferredForShortcutsChanged(); if (m_isPreferredForShortcuts && m_managedLayout) { emit m_managedLayout->preferredViewForShortcutsChanged(this); } } bool View::latteTasksArePresent() const { return m_latteTasksArePresent; } void View::setLatteTasksArePresent(bool present) { if (m_latteTasksArePresent == present) { return; } m_latteTasksArePresent = present; emit latteTasksArePresentChanged(); } void View::preferredViewForShortcutsChangedSlot(Latte::View *view) { if (view != this) { setIsPreferredForShortcuts(false); } } bool View::onPrimary() const { return m_onPrimary; } void View::setOnPrimary(bool flag) { if (m_onPrimary == flag) { return; } m_onPrimary = flag; emit onPrimaryChanged(); } float View::maxLength() const { return m_maxLength; } void View::setMaxLength(float length) { if (m_maxLength == length) { return; } m_maxLength = length; emit maxLengthChanged(); } int View::editThickness() const { return m_editThickness; } void View::setEditThickness(int thickness) { if (m_editThickness == thickness) { return; } m_editThickness = thickness; emit editThicknessChanged(); } int View::maxThickness() const { return m_maxThickness; } void View::setMaxThickness(int thickness) { if (m_maxThickness == thickness) return; m_maxThickness = thickness; emit maxThicknessChanged(); } int View::alignment() const { return m_alignment; } void View::setAlignment(int alignment) { Types::Alignment align = static_cast(alignment); if (m_alignment == alignment) { return; } m_alignment = align; emit alignmentChanged(); } QRect View::absoluteGeometry() const { return m_absoluteGeometry; } QRect View::screenGeometry() const { if (this->screen()) { QRect geom = this->screen()->geometry(); return geom; } return QRect(); } int View::offset() const { return m_offset; } void View::setOffset(int offset) { if (m_offset == offset) { return; } m_offset = offset; emit offsetChanged(); } int View::fontPixelSize() const { return m_fontPixelSize; } void View::setFontPixelSize(int size) { if (m_fontPixelSize == size) { return; } m_fontPixelSize = size; emit fontPixelSizeChanged(); } void View::applyActivitiesToWindows() { if (m_visibility && m_managedLayout) { QStringList activities = m_managedLayout->appliedActivities(); m_windowsTracker->setWindowOnActivities(*this, activities); if (m_configView) { m_windowsTracker->setWindowOnActivities(*m_configView, activities); auto configView = qobject_cast(m_configView); if (configView && configView->secondaryWindow()) { m_windowsTracker->setWindowOnActivities(*configView->secondaryWindow(), activities); } } if (m_visibility->supportsKWinEdges()) { m_visibility->applyActivitiesToHiddenWindows(activities); } } } Layout::GenericLayout *View::managedLayout() const { return m_managedLayout; } void View::setManagedLayout(Layout::GenericLayout *layout) { if (m_managedLayout == layout) { return; } // clear mode for (auto &c : connectionsManagedLayout) { disconnect(c); } m_managedLayout = layout; if (m_managedLayout) { //! Sometimes the activity isnt completely ready, by adding a delay //! we try to catch up QTimer::singleShot(100, [this]() { if (m_managedLayout && m_visibility) { qDebug() << "DOCK VIEW FROM LAYOUT ::: " << m_managedLayout->name() << " - activities: " << m_managedLayout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[0] = connect(m_managedLayout, &Layout::GenericLayout::preferredViewForShortcutsChanged, this, &View::preferredViewForShortcutsChangedSlot); connectionsManagedLayout[1] = connect(m_managedLayout, &Layout::GenericLayout::configViewCreated, this, &View::configViewCreated); Latte::Corona *latteCorona = qobject_cast(this->corona()); if (latteCorona->layoutManager()->memoryUsage() == Types::MultipleLayouts) { connectionsManagedLayout[2] = connect(latteCorona->activitiesConsumer(), &KActivities::Consumer::runningActivitiesChanged, this, [&]() { if (m_managedLayout && m_visibility) { qDebug() << "DOCK VIEW FROM LAYOUT (runningActivitiesChanged) ::: " << m_managedLayout->name() << " - activities: " << m_managedLayout->appliedActivities(); applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[3] = connect(m_managedLayout, &Layout::GenericLayout::activitiesChanged, this, [&]() { if (m_managedLayout) { applyActivitiesToWindows(); emit activitiesChanged(); } }); connectionsManagedLayout[4] = connect(latteCorona->layoutManager(), &LayoutManager::layoutsChanged, this, [&]() { if (m_managedLayout) { applyActivitiesToWindows(); emit activitiesChanged(); } }); //!IMPORTANT!!! ::: This fixes a bug when closing an Activity all docks from all Activities are //! disappearing! With this they reappear!!! connectionsManagedLayout[5] = connect(this, &QWindow::visibleChanged, this, [&]() { if (!isVisible() && m_managedLayout) { QTimer::singleShot(100, [this]() { if (m_managedLayout && containment() && !containment()->destroyed()) { setVisible(true); applyActivitiesToWindows(); emit activitiesChanged(); } }); QTimer::singleShot(1500, [this]() { if (m_managedLayout && containment() && !containment()->destroyed()) { setVisible(true); applyActivitiesToWindows(); emit activitiesChanged(); } }); } }); } emit managedLayoutChanged(); } } void View::moveToLayout(QString layoutName) { if (!m_managedLayout) { return; } QList containments = m_managedLayout->unassignFromLayout(this); Latte::Corona *latteCorona = qobject_cast(this->corona()); if (latteCorona && containments.size() > 0) { - Layout::GenericLayout *newLayout = latteCorona->layoutManager()->activeLayout(layoutName); + Layout::GenericLayout *newLayout = latteCorona->layoutManager()->layout(layoutName); if (newLayout) { newLayout->assignToLayout(this, containments); } } } void View::setBlockHiding(bool block) { if (!block) { auto *configView = qobject_cast(m_configView); if (m_alternativesIsShown || (configView && configView->sticker() && configView->isVisible())) { return; } if (m_visibility) { m_visibility->setBlockHiding(false); } } else { if (m_visibility) { m_visibility->setBlockHiding(true); } } } void View::configViewCreated(QQuickView *configView) { if (m_configView && m_configView!=configView) { //! for each layout only one dock should show its configuration windows //! otherwise we could reach a point that because a settings window //! is below another Latte View its options are not reachable auto configDialog = qobject_cast(m_configView); if (configDialog) { configDialog->hideConfigWindow(); } } } void View::hideWindowsForSlidingOut() { setBlockHiding(false); if (m_configView) { auto configDialog = qobject_cast(m_configView); if (configDialog) { configDialog->hideConfigWindow(); } } } //! remove latte tasks plasmoid void View::removeTasksPlasmoid() { if (!tasksPresent() || !containment()) { return; } for (const Plasma::Applet *applet : containment()->applets()) { KPluginMetaData meta = applet->kPackage().metadata(); if (meta.pluginId() == "org.kde.latte.plasmoid") { QAction *closeApplet = applet->actions()->action(QStringLiteral("remove")); if (closeApplet) { closeApplet->trigger(); //! remove only the first found return; } } } } //! check if the tasks plasmoid exist in the dock bool View::tasksPresent() { if (!this->containment()) { return false; } for (const Plasma::Applet *applet : this->containment()->applets()) { const auto &provides = KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides")); if (provides.contains(QLatin1String("org.kde.plasma.multitasking"))) { return true; } } return false; } //!check if the plasmoid with _name_ exists in the midedata bool View::mimeContainsPlasmoid(QMimeData *mimeData, QString name) { if (!mimeData) { return false; } if (mimeData->hasFormat(QStringLiteral("text/x-plasmoidservicename"))) { QString data = mimeData->data(QStringLiteral("text/x-plasmoidservicename")); const QStringList appletNames = data.split('\n', QString::SkipEmptyParts); for (const QString &appletName : appletNames) { if (appletName == name) return true; } } return false; } ViewPart::Effects *View::effects() const { return m_effects; } ViewPart::Indicator *View::indicator() const { return m_indicator; } ViewPart::Positioner *View::positioner() const { return m_positioner; } ViewPart::VisibilityManager *View::visibility() const { return m_visibility; } ViewPart::WindowsTracker *View::windowsTracker() const { return m_windowsTracker; } bool View::event(QEvent *e) { if (!m_inDelete) { emit eventTriggered(e); switch (e->type()) { case QEvent::Enter: m_containsMouse = true; if (m_configView) { ViewPart::PrimaryConfigView *primaryConfigView = qobject_cast(m_configView); if (primaryConfigView) { if (primaryConfigView->secondaryWindow()) { ViewPart::SecondaryConfigView *secConfigView = qobject_cast(primaryConfigView->secondaryWindow()); if (secConfigView) { secConfigView->requestActivate(); } } primaryConfigView->requestActivate(); } } break; case QEvent::Leave: m_containsMouse = false; engine()->trimComponentCache(); break; case QEvent::MouseButtonPress: if (auto mouseEvent = dynamic_cast(e)) { emit mousePressed(mouseEvent->pos(), mouseEvent->button()); } break; case QEvent::MouseButtonRelease: if (auto mouseEvent = dynamic_cast(e)) { emit mouseReleased(mouseEvent->pos(), mouseEvent->button()); } break; case QEvent::PlatformSurface: if (auto pe = dynamic_cast(e)) { switch (pe->surfaceEventType()) { case QPlatformSurfaceEvent::SurfaceCreated: setupWaylandIntegration(); if (m_shellSurface) { m_positioner->syncGeometry(); m_effects->updateShadows(); } break; case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: if (m_shellSurface) { delete m_shellSurface; m_shellSurface = nullptr; qDebug() << "WAYLAND dock window surface was deleted..."; m_effects->clearShadows(); } break; } } break; default: break; } } return ContainmentView::event(e); } void View::deactivateApplets() { if (!containment()) { return; } for (const auto applet : containment()->applets()) { PlasmaQuick::AppletQuickItem *ai = applet->property("_plasma_graphicObject").value(); if (ai) { ai->setExpanded(false); } } } void View::toggleAppletExpanded(const int id) { if (!containment()) { return; } for (const auto applet : containment()->applets()) { if (applet->id() == id) { PlasmaQuick::AppletQuickItem *ai = applet->property("_plasma_graphicObject").value(); if (ai) { if (!ai->isActivationTogglesExpanded()) { ai->setActivationTogglesExpanded(true); } emit applet->activated(); } } } } QVariantList View::containmentActions() { QVariantList actions; /*if (containment()->corona()->immutability() != Plasma::Types::Mutable) { return actions; }*/ //FIXME: the trigger string it should be better to be supported this way //const QString trigger = Plasma::ContainmentActions::eventToString(event); const QString trigger = "RightButton;NoModifier"; Plasma::ContainmentActions *plugin = this->containment()->containmentActions().value(trigger); if (!plugin) { return actions; } if (plugin->containment() != this->containment()) { plugin->setContainment(this->containment()); // now configure it KConfigGroup cfg(this->containment()->corona()->config(), "ActionPlugins"); cfg = KConfigGroup(&cfg, QString::number(this->containment()->containmentType())); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } for (QAction *ac : plugin->contextualActions()) { actions << QVariant::fromValue(ac); } return actions; } void View::disableGrabItemBehavior() { setMouseGrabEnabled(false); } void View::restoreGrabItemBehavior() { if (mouseGrabberItem()) { mouseGrabberItem()->ungrabMouse(); } } bool View::isHighestPriorityView() { if (m_managedLayout) { return this == m_managedLayout->highestPriorityView(); } return false; } //!BEGIN overriding context menus behavior void View::mousePressEvent(QMouseEvent *event) { bool result = m_contextMenu->mousePressEvent(event); emit contextMenuIsShownChanged(); if (result) { PlasmaQuick::ContainmentView::mousePressEvent(event); } } //!END overriding context menus behavior //!BEGIN configuration functions void View::saveConfig() { if (!this->containment()) return; auto config = this->containment()->config(); config.writeEntry("onPrimary", onPrimary()); config.writeEntry("byPassWM", byPassWM()); config.writeEntry("isPreferredForShortcuts", isPreferredForShortcuts()); config.writeEntry("viewType", (int)m_type); config.sync(); } void View::restoreConfig() { if (!this->containment()) return; auto config = this->containment()->config(); m_onPrimary = config.readEntry("onPrimary", true); m_byPassWM = config.readEntry("byPassWM", false); m_isPreferredForShortcuts = config.readEntry("isPreferredForShortcuts", false); //! Send changed signals at the end in order to be sure that saveConfig //! wont rewrite default/invalid values emit onPrimaryChanged(); emit byPassWMChanged(); } //!END configuration functions } //!END namespace diff --git a/shell/package/contents/configuration/LatteDockConfiguration.qml b/shell/package/contents/configuration/LatteDockConfiguration.qml index b521b3b9..387e5d85 100644 --- a/shell/package/contents/configuration/LatteDockConfiguration.qml +++ b/shell/package/contents/configuration/LatteDockConfiguration.qml @@ -1,684 +1,706 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 import QtQuick.Window 2.2 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.components 3.0 as PlasmaComponents3 import org.kde.plasma.extras 2.0 as PlasmaExtras import QtQuick.Controls.Styles.Plasma 2.0 as Styles import org.kde.plasma.plasmoid 2.0 import org.kde.kquickcontrolsaddons 2.0 as KQuickControlAddons import org.kde.latte 0.2 as Latte import org.kde.latte.components 1.0 as LatteComponents import "pages" as Pages import "../controls" as LatteExtraControls FocusScope { id: dialog readonly property bool basicLevel: viewConfig.complexity === Latte.Types.BasicSettings readonly property bool advancedLevel: viewConfig.complexity === Latte.Types.AdvancedSettings readonly property bool expertLevel: viewConfig.complexity === Latte.Types.ExpertSettings readonly property bool highLevel: advancedLevel || expertLevel readonly property bool inConfigureAppletsMode: plasmoid.configuration.inConfigureAppletsMode || !Latte.WindowSystem.compositingActive //! max size based on screen resolution //! TODO: if we can access availableScreenGeometry.height this can be improved, currently //! we use 100px. or 50px. in order to give space for othe views to be shown and to have also //! some space around the settings window property int maxHeight: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? viewConfig.availableScreenGeometry.height - (latteView.editThickness - latteView.normalThickness) - 16 : viewConfig.availableScreenGeometry.height - 2 * units.largeSpacing property int maxWidth: 0.6 * latteView.screenGeometry.width //! propose size based on font size property int proposedWidth: 0.82 * proposedHeight + units.smallSpacing * 2 property int proposedHeight: 35 * theme.mSize(theme.defaultFont).height //! chosen size to be applied, if the user has set or not a different scale for the settings window property int chosenWidth: userScaleWidth !== 1 ? userScaleWidth * proposedWidth : proposedWidth property int chosenHeight: userScaleHeight !== 1 ? userScaleHeight * heightLevel * proposedHeight : heightLevel * proposedHeight readonly property int optionsWidth: appliedWidth - units.smallSpacing * 10 //! user set scales based on its preference, e.g. 96% of the proposed size property real userScaleWidth: 1 property real userScaleHeight: 1 readonly property real heightLevel: (dialog.expertLevel ? 100 : 1) onHeightChanged: viewConfig.syncGeometry(); //! applied size in order to not be out of boundaries //! width can be between 200px - maxWidth //! height can be between 400px - maxHeight property int appliedWidth: Math.min(maxWidth, Math.max(200, chosenWidth)) property int appliedHeight: Math.min(maxHeight, Math.max(400, chosenHeight)) width: appliedWidth height: appliedHeight Layout.minimumWidth: width Layout.minimumHeight: height LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true readonly property bool viewIsPanel: latteView.type === Latte.Types.PanelView property bool panelIsVertical: plasmoid.formFactor === PlasmaCore.Types.Vertical property int subGroupSpacing: units.largeSpacing + units.smallSpacing * 1.5 property color bC: theme.backgroundColor property color transparentBackgroundColor: Qt.rgba(bC.r, bC.g, bC.b, 0.7) onHighLevelChanged: { //! switch to appearancePage when effectsPage becomes hidden because //! advancedLevel was disabled by the user if (!highLevel && tabGroup.currentTab === effectsPage) { tabGroup.currentTab = appearancePage; tabBar.currentTab = appearanceTabBtn; } } Component.onCompleted: { updateScales(); } Connections { target: latteView.positioner onCurrentScreenNameChanged: dialog.updateScales(); } function updateScales() { userScaleWidth = universalSettings.screenWidthScale(latteView.positioner.currentScreenName); userScaleHeight = universalSettings.screenHeightScale(latteView.positioner.currentScreenName); } PlasmaCore.FrameSvgItem{ anchors.fill: parent imagePath: "dialogs/background" enabledBorders: viewConfig.enabledBorders } MouseArea{ id: backgroundMouseArea anchors.fill: parent hoverEnabled: true property bool blockWheel: false property bool updatingWidthScale: false property bool updatingHeightScale: false property bool wheelTriggeredOnce: false property real scaleStep: 0.04 onWheel: { var metaModifier = (wheel.modifiers & Qt.MetaModifier); var ctrlModifier = (wheel.modifiers & Qt.ControlModifier); if (blockWheel || !(metaModifier || ctrlModifier)){ return; } updatingWidthScale = metaModifier || (dialog.expertLevel && ctrlModifier); updatingHeightScale = !dialog.expertLevel && ctrlModifier; blockWheel = true; wheelTriggeredOnce = true; scrollDelayer.start(); var angle = wheel.angleDelta.y / 8; //positive direction if (angle > 12) { var scales; if (updatingWidthScale) { userScaleWidth = userScaleWidth + scaleStep; } if (updatingHeightScale) { userScaleHeight = userScaleHeight + scaleStep; } universalSettings.setScreenScales(latteView.positioner.currentScreenName, userScaleWidth, userScaleHeight); viewConfig.syncGeometry(); //negative direction } else if (angle < -12) { if (updatingWidthScale) { userScaleWidth = userScaleWidth - scaleStep; } if (updatingHeightScale) { userScaleHeight = userScaleHeight - scaleStep; } universalSettings.setScreenScales(latteView.positioner.currentScreenName, userScaleWidth, userScaleHeight); viewConfig.syncGeometry(); } } } PlasmaComponents.Button { id: backgroundMouseAreaTooltip anchors.fill: parent opacity: 0 tooltip: i18n("You can use Ctrl/Meta + Scroll Wheel to alter the window size") onHoveredChanged: { if (!hovered) { backgroundMouseArea.wheelTriggeredOnce = false; } } } PlasmaComponents.Label{ anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter text: backgroundMouseArea.updatingWidthScale ? i18nc("view settings width scale","Width scale at %0%").arg(userScaleWidth * 100) : i18nc("view settings height scale","Height scale at %0%").arg(userScaleHeight * 100) visible: backgroundMouseAreaTooltip.hovered && backgroundMouseArea.wheelTriggeredOnce } //! A timer is needed in order to handle also touchpads that probably //! send too many signals very fast. This way the signals per sec are limited. //! The user needs to have a steady normal scroll in order to not //! notice a annoying delay Timer{ id: scrollDelayer interval: 75 onTriggered: backgroundMouseArea.blockWheel = false; } ColumnLayout { id: content Layout.minimumWidth: width Layout.minimumHeight: calculatedHeight Layout.preferredWidth: width Layout.preferredHeight: calculatedHeight width: (dialog.appliedWidth - units.smallSpacing * 2) anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top spacing: units.smallSpacing property int calculatedHeight: header.height + headerSpacer.height+ tabBar.height + pagesBackground.height + actionButtons.height + spacing * 3 Keys.onPressed: { if (event.key === Qt.Key_Escape) { viewConfig.hideConfigWindow(); } } Component.onCompleted: forceActiveFocus(); RowLayout { id: header Layout.fillWidth: true spacing: 0 Item { Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillWidth: false Layout.topMargin: units.smallSpacing Layout.preferredWidth: width Layout.preferredHeight: height width: Qt.application.layoutDirection !== Qt.RightToLeft ? logo.width + latteTxt.width + units.smallSpacing : logo.width + units.smallSpacing height: logo.height Latte.IconItem { id: logo width: Math.round(1.4 * latteTxtMetrics.font.pixelSize) height: width smooth: true source: "latte-dock" // animated: true usesPlasmaTheme: false active: aboutArea.hovered } PlasmaComponents.Label { id: latteTxtMetrics text: "Latte" width: 0 font.pointSize: 2 * theme.defaultFont.pointSize visible: false } PlasmaCore.SvgItem{ id: latteTxt width: 2.2 * height height: 0.4 * latteTxtMetrics.font.pixelSize visible: Qt.application.layoutDirection !== Qt.RightToLeft anchors.left: logo.right anchors.verticalCenter: logo.verticalCenter svg: PlasmaCore.Svg{ imagePath: universalSettings.trademarkIconPath() } } PlasmaComponents.Button{ id: aboutArea //! Used as tooltip anchors.fill: parent opacity: 0 tooltip: i18n("Open Latte settings window") onPressedChanged: { if (pressed) { layoutManager.showLatteSettingsDialog(Latte.Types.PreferencesPage) } } } Rectangle { anchors.top: parent.bottom anchors.horizontalCenter: parent.horizontalCenter anchors.topMargin: 2 width: parent.width + 4 height: 2 color: theme.highlightColor visible: aboutArea.hovered } } Item{ id: headerSpacer Layout.minimumHeight: complexitySettings.height + 2*units.smallSpacing } ColumnLayout { PlasmaComponents.ToolButton { id: pinButton Layout.fillWidth: false Layout.fillHeight: false Layout.preferredWidth: width Layout.preferredHeight: height Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.bottomMargin: units.smallSpacing //!avoid editMode box shadow Layout.topMargin: units.smallSpacing * 2 Layout.rightMargin: units.smallSpacing iconSource: "window-pin" checkable: true width: Math.round(units.gridUnit * 1.25) height: width property bool inStartup: true onClicked: { plasmoid.configuration.configurationSticker = checked viewConfig.setSticker(checked) } Component.onCompleted: { checked = plasmoid.configuration.configurationSticker viewConfig.setSticker(plasmoid.configuration.configurationSticker) } } RowLayout { id: complexitySettings Layout.fillWidth: true Layout.rightMargin: units.smallSpacing * 2 Layout.alignment: Qt.AlignRight | Qt.AlignTop function switchMode() { if (dialog.basicLevel || dialog.advancedLevel) { viewConfig.complexity = Latte.Types.ExpertSettings; } else if (dialog.expertLevel) { viewConfig.complexity = Latte.Types.BasicSettings; } } PlasmaComponents.Label { Layout.fillWidth: true Layout.alignment: Qt.AlignRight } PlasmaComponents.Label { id: complexityLbl Layout.alignment: Qt.AlignRight // opacity: dialog.basicLevel ? basicOpacity : 1 //! TODO: the term here is not accurate because the expert settings mode //! is used currently. In the future this term will be rethought if //! it must remain or be changed text: i18nc("advanced settings", "Advanced") readonly property real textColorBrightness: colorBrightness(theme.textColor) readonly property real basicOpacity: textColorBrightness > 127 ? 0.7 : 0.3 color: { if (dialog.basicLevel) { return textColorBrightness > 127 ? Qt.darker(theme.textColor, 1.4) : Qt.lighter(theme.textColor, 2.8); } return theme.textColor; } function colorBrightness(color) { return colorBrightnessFromRGB(color.r * 255, color.g * 255, color.b * 255); } // formula for brightness according to: // https://www.w3.org/TR/AERT/#color-contrast function colorBrightnessFromRGB(r, g, b) { return (r * 299 + g * 587 + b * 114) / 1000 } MouseArea { id: complexityMouseArea anchors.fill: parent hoverEnabled: true onClicked: { complexitySettings.switchMode(); } } } LatteComponents.Switch { id: complexitySwitch checked: dialog.expertLevel onPressedChanged: { if(pressed){ complexitySettings.switchMode(); } } } } } } PlasmaComponents.TabBar { id: tabBar Layout.fillWidth: true Layout.maximumWidth: (dialog.appliedWidth - units.smallSpacing * 2) PlasmaComponents.TabButton { id: behaviorTabBtn text: i18n("Behavior") tab: behaviorPage } PlasmaComponents.TabButton { id: appearanceTabBtn text: i18n("Appearance") tab: appearancePage } PlasmaComponents.TabButton { id: effectsTabBtn text: i18n("Effects") tab: effectsPage visible: dialog.highLevel } PlasmaComponents.TabButton { id: tasksTabBtn text: i18n("Tasks") tab: tasksPage visible: latteView.latteTasksArePresent } } Rectangle { id: pagesBackground Layout.fillWidth: true Layout.fillHeight: false Layout.minimumWidth: dialog.appliedWidth - units.smallSpacing * 4 Layout.minimumHeight: height Layout.maximumHeight: height width: dialog.appliedWidth - units.smallSpacing * 3 height: availableFreeHeight + units.smallSpacing * 4 color: transparentBackgroundColor border.width: 1 border.color: theme.backgroundColor //fix the height binding loop when showing the configuration window property int availableFreeHeight: dialog.appliedHeight - header.height - headerSpacer.height - tabBar.height - actionButtons.height - 2 * units.smallSpacing PlasmaExtras.ScrollArea { id: scrollArea anchors.fill: parent verticalScrollBarPolicy: Qt.ScrollBarAsNeeded horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff flickableItem.flickableDirection: Flickable.VerticalFlick PlasmaComponents.TabGroup { id: tabGroup width: currentTab.Layout.maximumWidth height: currentTab.Layout.maximumHeight Pages.BehaviorConfig { id: behaviorPage } Pages.AppearanceConfig { id: appearancePage } Pages.EffectsConfig { id: effectsPage } Pages.TasksConfig { id: tasksPage } } } } RowLayout { id: actionButtons Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom spacing: units.largeSpacing Connections{ target: latteView.managedLayout onViewsCountChanged: actionButtons.updateEnabled(); } function updateEnabled() { var screenFreeEdges = latteView.managedLayout.qmlFreeEdges(latteView.positioner.currentScreenId); actionsComboBtn.buttonEnabled = screenFreeEdges.length > 0; if (actionsModel.count > 0) { actionsModel.get(0).enabled = actionsComboBtn.buttonEnabled; } removeView.enabled = latteView.managedLayout.viewsCount>1 /*&& !(latteView.managedLayout.viewsWithTasks()===1 && latteView.tasksPresent())*/ } LatteComponents.ComboBoxButton { id: actionsComboBtn Layout.fillWidth: true implicitWidth: removeView.implicitWidth implicitHeight: removeView.implicitHeight buttonEnabled: true buttonText: i18n("New Dock") buttonIconSource: "list-add" buttonToolTip: i18n("Add a new dock") comboBoxEnabled: true comboBoxBlankSpaceForEmptyIcons: true comboBoxPopUpAlignRight: Qt.application.layoutDirection === Qt.RightToLeft comboBoxEnabledRole: "enabled" comboBoxTextRole: "name" comboBoxIconRole: "icon" comboBoxMinimumPopUpWidth: actionsModel.count > 1 ? dialog.width / 2 : 150 property var activeLayoutsNames; + property var sharedLayoutsNames; Component.onCompleted: { comboBox.model = actionsModel; actionButtons.updateEnabled(); } ListModel { id: actionsModel } Connections{ target: actionsComboBtn.comboBox Component.onCompleted:{ actionsComboBtn.addModel(); actionButtons.updateEnabled(); } onActivated: { if (index==0) { latteView.copyView(); } else if (index>=1) { - latteView.positioner.hideDockDuringMovingToLayout(activeLayoutsNames[index-1]); + var layouts = actionsComboBtn.sharedLayoutsNames.concat(actionsComboBtn.activeLayoutsNames); + + latteView.positioner.hideDockDuringMovingToLayout(layouts[index-1]); } actionsComboBtn.comboBox.currentIndex = -1; } onEnabledChanged: { if (enabled) { actionsComboBtn.addModel(); } else { actionsComboBtn.emptyModel(); } } } Connections{ target: actionsComboBtn.button onClicked: latteView.managedLayout.addNewView(); } Connections{ target: latteView onTypeChanged: actionsComboBtn.updateCopyText() } function addModel() { actionsModel.clear(); var copy = {actionId: 'copy:', enabled: true, name: '', icon: 'edit-copy'}; actionsModel.append(copy); updateCopyText(); var tempActiveLayouts = layoutManager.activeLayoutsNames(); - var currentLayoutIndex = tempActiveLayouts.indexOf(latteView.managedLayout.name); + var tempSharedLayouts = layoutManager.sharedLayoutsNames(); + + if (tempSharedLayouts.length > 0) { + var curIndex = tempSharedLayouts.indexOf(latteView.managedLayout.name); + if (curIndex >=0) { + tempSharedLayouts.splice(curIndex,1); + } + + sharedLayoutsNames = tempSharedLayouts; + var icon = "document-share"; + + for(var i=0; i 0) { + var curIndex = tempActiveLayouts.indexOf(latteView.managedLayout.name); + if (curIndex >=0) { + tempActiveLayouts.splice(curIndex,1); + } + activeLayoutsNames = tempActiveLayouts; var iconArrow = Qt.application.layoutDirection === Qt.RightToLeft ? 'arrow-left' : 'arrow-right'; for(var i=0; i 1 ? 1 : 0 tooltip: i18n("Remove current dock") onClicked: latteView.removeView() } PlasmaComponents.Button { id: closeButton Layout.fillWidth: true text: i18n("Close") iconSource: "dialog-close" tooltip: i18n("Close settings window") onClicked: viewConfig.hideConfigWindow(); } } } //! HACK FOR X11 environments //! show an inner shadow similar to Latte::View editShadow in order to //! not break the visual user experience LatteExtraControls.InnerShadow{ width: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? dialog.width + 2*shadowSize : shadowSize height: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? shadowSize : dialog.height + 2*shadowSize shadowSize: latteView.effects.editShadow shadowOpacity: Math.max(0.35, maxOpacity) shadowDirection: plasmoid.location visible: !Latte.WindowSystem.isPlatformWayland && Latte.WindowSystem.compositingActive && latteView.effects.settingsMaskSubtracted readonly property real maxOpacity: Latte.WindowSystem.compositingActive && !plasmoid.configuration.inConfigureAppletsMode ? plasmoid.configuration.editBackgroundOpacity : 1 } }