From f4ac1436f41e74f2a58e4ef8e7435a358b4461d6 Mon Sep 17 00:00:00 2001
From: Michail Vourlakos <mvourlakos@gmail.com>
Date: Wed, 1 Mar 2017 16:59:04 +0200
Subject: [PATCH] dock containing tasks is protected in multi-screen

--the last dock containing tasks can not be removed
automatic by Latte based on screens heuristics
--on startup Latte checks if a dock containing tasks
will be loaded based on screens associated. If it
doesnt it loads the first dock containing tasks and
puts it on primary screen and setting also its flag
to onPrimary
--on the configuration window when a dock changes from
explicit to primary screen by latte automation the
record of the previous screen is shown correctly
---
 app/dockcorona.cpp                            | 183 +++++++++++++++---
 app/dockcorona.h                              |   6 +
 app/dockview.cpp                              |  31 ++-
 app/dockview.h                                |   2 +-
 .../contents/configuration/BehaviorConfig.qml |  12 ++
 5 files changed, 205 insertions(+), 29 deletions(-)

diff --git a/app/dockcorona.cpp b/app/dockcorona.cpp
index ddb920c25..bfb100cd1 100644
--- a/app/dockcorona.cpp
+++ b/app/dockcorona.cpp
@@ -98,6 +98,9 @@ void DockCorona::load()
 
         m_activitiesStarting = false;
 
+        m_tasksWillBeLoaded =  heuresticForLoadingDockWithTasks();
+        qDebug() << "TASKS WILL BE PRESENT AFTER LOADING ::: " << m_tasksWillBeLoaded;
+
         //  connect(qGuiApp, &QGuiApplication::screenAdded, this, &DockCorona::addOutput, Qt::UniqueConnection);
         connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &DockCorona::primaryOutputChanged, Qt::UniqueConnection);
         //  connect(qGuiApp, &QGuiApplication::screenRemoved, this, &DockCorona::screenRemoved, Qt::UniqueConnection);
@@ -227,13 +230,13 @@ QRect DockCorona::availableScreenRect(int id) const
             // need calculate available space for top and bottom location,
             // because the left and right are those who dodge others docks
             switch (view->location()) {
-                case Plasma::Types::TopEdge:
-                    available.setTopLeft({available.x(), dockRect.bottom()});
-                    break;
+            case Plasma::Types::TopEdge:
+                available.setTopLeft({available.x(), dockRect.bottom()});
+                break;
 
-                case Plasma::Types::BottomEdge:
-                    available.setBottomLeft({available.x(), dockRect.top()});
-                    break;
+            case Plasma::Types::BottomEdge:
+                available.setBottomLeft({available.x(), dockRect.top()});
+                break;
             }
         }
     }
@@ -241,6 +244,19 @@ QRect DockCorona::availableScreenRect(int id) const
     return available;
 }
 
+int DockCorona::noDocksWithTasks() const
+{
+    int result = 0;
+
+    foreach (auto view, m_dockViews) {
+        if (view->tasksPresent()) {
+            result++;
+        }
+    }
+
+    return result;
+}
+
 void DockCorona::addOutput(QScreen *screen)
 {
     Q_ASSERT(screen);
@@ -305,6 +321,40 @@ void DockCorona::syncDockViews()
 
     qDebug() << "removing consideration & updating screen for always on primary docks....";
 
+    //this code trys to find a containment that must not be deleted by
+    //automatic algorithm. Currently the containment with the minimum id
+    //has higher priority
+    int preserveContainmentId{-1};
+    bool dockWithTasksWillBeShown{false};
+
+    foreach (auto view, m_dockViews) {
+        bool found{false};
+        foreach (auto scr, qGuiApp->screens()) {
+            int id = view->containment()->screen();
+            if (id == -1) {
+                id = view->containment()->lastScreen();
+            }
+            if (scr->name() == view->currentScreen()) {
+                found = true;
+                break;
+            }
+        }
+
+        if(found && view->tasksPresent()){
+             dockWithTasksWillBeShown = true;
+        }
+
+        if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment())
+                && !(view->tasksPresent() && noDocksWithTasks() == 1)) { //do not delete last dock containing tasks
+            if (view->tasksPresent()) {
+                if (preserveContainmentId==-1)
+                    preserveContainmentId = view->containment()->id();
+                else if(view->containment()->id() < preserveContainmentId)
+                    preserveContainmentId = view->containment()->id();
+            }
+        }
+    }
+
     foreach (auto view, m_dockViews) {
         bool found{false};
 
@@ -321,10 +371,14 @@ void DockCorona::syncDockViews()
             }
         }
 
-        if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment())) {
-            qDebug() << "screen Count signal: view must be deleted... for:" << view->currentScreen();
-            auto viewToDelete = m_dockViews.take(view->containment());
-            viewToDelete->deleteLater();
+        if (!found && !view->onPrimary() && (m_dockViews.size() > 1) && m_dockViews.contains(view->containment())
+                && !(view->tasksPresent() && noDocksWithTasks() == 1)) {
+            //do not delete last dock containing tasks
+            if (dockWithTasksWillBeShown || preserveContainmentId != view->containment()->id()) {
+                qDebug() << "screen Count signal: view must be deleted... for:" << view->currentScreen();
+                auto viewToDelete = m_dockViews.take(view->containment());
+                viewToDelete->deleteLater();
+            }
         } else {
             view->reconsiderScreen();
         }
@@ -350,8 +404,8 @@ int DockCorona::docksCount(int screen) const
 
     for (const auto &view : m_dockViews) {
         if (view && view->containment()
-            && view->containment()->screen() == screen
-            && !view->containment()->destroyed()) {
+                && view->containment()->screen() == screen
+                && !view->containment()->destroyed()) {
             ++docks;
         }
     }
@@ -383,7 +437,7 @@ QList<Plasma::Types::Location> DockCorona::freeEdges(QScreen *screen) const
 {
     using Plasma::Types;
     QList<Types::Location> edges{Types::BottomEdge, Types::LeftEdge,
-                                 Types::TopEdge, Types::RightEdge};
+                Types::TopEdge, Types::RightEdge};
 
     for (auto *view : m_dockViews) {
         if (view && view->currentScreen() == screen->name()) {
@@ -398,13 +452,13 @@ QList<Plasma::Types::Location> DockCorona::freeEdges(int screen) const
 {
     using Plasma::Types;
     QList<Types::Location> edges{Types::BottomEdge, Types::LeftEdge,
-                                 Types::TopEdge, Types::RightEdge};
+                Types::TopEdge, Types::RightEdge};
     //when screen=-1 is passed then the primaryScreenid is used
     int fixedScreen = (screen == -1) ? primaryScreenId() : screen;
 
     for (auto *view : m_dockViews) {
         if (view && view->containment()
-            && view->containment()->screen() == fixedScreen) {
+                && view->containment()->screen() == fixedScreen) {
             edges.removeOne(view->location());
         }
     }
@@ -451,8 +505,8 @@ int DockCorona::screenForContainment(const Plasma::Containment *containment) con
     for (auto screen : qGuiApp->screens()) {
         // containment->lastScreen() == m_screenPool->id(screen->name()) to check if the lastScreen refers to a screen that exists/it's known
         if (containment->lastScreen() == m_screenPool->id(screen->name()) &&
-            (containment->activity() == m_activityConsumer->currentActivity() ||
-             containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) {
+                (containment->activity() == m_activityConsumer->currentActivity() ||
+                 containment->containmentType() == Plasma::Types::PanelContainment || containment->containmentType() == Plasma::Types::CustomPanelContainment)) {
             return containment->lastScreen();
         }
     }
@@ -469,10 +523,8 @@ void DockCorona::addDock(Plasma::Containment *containment)
 
     auto metadata = containment->kPackage().metadata();
 
-    if (metadata.pluginId() == "org.kde.plasma.private.systemtray") {
-        if (metadata.pluginId() != "org.kde.latte.containment")
-            return;
-    }
+    if (metadata.pluginId() != "org.kde.latte.containment")
+        return;
 
     for (auto *dock : m_dockViews) {
         if (dock->containment() == containment)
@@ -481,6 +533,13 @@ void DockCorona::addDock(Plasma::Containment *containment)
 
     QScreen *nextScreen{qGuiApp->primaryScreen()};
 
+    bool forceDockLoading = false;
+
+    if (!m_tasksWillBeLoaded && m_firstContainmentWithTasks == containment->id()) {
+        m_tasksWillBeLoaded = true; //this protects by loading more than one dock at startup
+        forceDockLoading = true;
+    }
+
     bool onPrimary = containment->config().readEntry("onPrimary", true);
     int id = containment->screen();
 
@@ -490,7 +549,7 @@ void DockCorona::addDock(Plasma::Containment *containment)
 
     qDebug() << "add dock - containment id : " << id;
 
-    if (id >= 0 && !onPrimary) {
+    if (id >= 0 && !onPrimary && !forceDockLoading) {
         QString connector = m_screenPool->connector(id);
         qDebug() << "add dock - connector : " << connector;
         bool found{false};
@@ -512,6 +571,11 @@ void DockCorona::addDock(Plasma::Containment *containment)
     auto dockView = new DockView(this, nextScreen);
     dockView->init();
     dockView->setContainment(containment);
+
+    if (forceDockLoading) {
+        dockView->setOnPrimary(true);
+    }
+
     connect(containment, &QObject::destroyed, this, &DockCorona::dockContainmentDestroyed);
     connect(containment, &Plasma::Applet::destroyedChanged, this, &DockCorona::destroyedChanged);
     connect(containment, &Plasma::Applet::locationChanged, this, &DockCorona::dockLocationChanged);
@@ -646,6 +710,81 @@ void DockCorona::loadDefaultLayout()
     defaultContainment->createApplet(QStringLiteral("org.kde.plasma.analogclock"));
 }
 
+//! This function figures in the beginning if a dock with tasks
+//! in it will be loaded, if it doesnt then the first dock
+//! availabe with tasks will be loaded not depending on the
+//! screen on which is associated
+bool DockCorona::heuresticForLoadingDockWithTasks()
+{
+    auto containmentsEntries = config()->group("Containments");
+
+    foreach (auto cId, containmentsEntries.groupList()) {
+        QString plugin = containmentsEntries.group(cId).readEntry("plugin");
+
+        if (plugin == "org.kde.latte.containment") {
+            bool onPrimary = containmentsEntries.group(cId).readEntry("onPrimary", true);
+            int lastScreen = containmentsEntries.group(cId).readEntry("lastScreen", -1);
+
+            qDebug() << "containment values: " << onPrimary << " - " << lastScreen;
+
+            auto appletEntries = containmentsEntries.group(cId).group("Applets");
+
+            bool containsTasks = false;
+
+            foreach (auto appId, appletEntries.groupList()) {
+                if (appletEntries.group(appId).readEntry("plugin") == "org.kde.latte.plasmoid") {
+                    containsTasks = true;
+                    break;
+                }
+            }
+
+            if (containsTasks) {
+                m_firstContainmentWithTasks = cId.toInt();
+                if (onPrimary) {
+                    return true;
+                } else {
+                    if (lastScreen >= 0) {
+                        QString connector = m_screenPool->connector(lastScreen);
+
+                        foreach (auto scr, qGuiApp->screens()) {
+                            if (scr && scr->name() == connector) {
+                                return true;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    return false;
+}
+
+//! This function figures if a latte containment contains a
+//! latte tasks plasmoid
+bool DockCorona::containmentContainsTasks(Plasma::Containment *cont)
+{
+    auto containmentsEntries = config()->group("Containments");
+
+    foreach (auto cId, containmentsEntries.groupList()) {
+        QString plugin = containmentsEntries.group(cId).readEntry("plugin");
+
+        if ((plugin == "org.kde.latte.containment") && (cId.toUInt() == cont->id())) {
+            auto appletEntries = containmentsEntries.group(cId).group("Applets");
+
+            foreach (auto appId, appletEntries.groupList()) {
+                if (appletEntries.group(appId).readEntry("plugin") == "org.kde.latte.plasmoid") {
+                    return true;
+                    break;
+                }
+            }
+        }
+    }
+
+    return false;
+}
+
 inline void DockCorona::qmlRegisterTypes() const
 {
     qmlRegisterType<QScreen>();
diff --git a/app/dockcorona.h b/app/dockcorona.h
index 8e0f8037e..0395cb466 100644
--- a/app/dockcorona.h
+++ b/app/dockcorona.h
@@ -58,6 +58,7 @@ public:
     QList<Plasma::Types::Location> freeEdges(QScreen *screen) const;
 
     int docksCount(int screen) const;
+    int noDocksWithTasks() const;
     int screenForContainment(const Plasma::Containment *containment) const override;
 
     void addDock(Plasma::Containment *containment);
@@ -91,11 +92,16 @@ private slots:
 private:
     bool appletExists(uint containmentId, uint appletId) const;
     void cleanConfig();
+    bool containmentContainsTasks(Plasma::Containment *cont);
     bool containmentExists(uint id) const;
+    bool heuresticForLoadingDockWithTasks();
     void qmlRegisterTypes() const;
     int primaryScreenId() const;
 
     bool m_activitiesStarting{true};
+    //this is used to check if a dock with tasks in it will be loaded on startup
+    bool m_tasksWillBeLoaded{false};
+    int m_firstContainmentWithTasks{-1};
 
     QHash<const Plasma::Containment *, DockView *> m_dockViews;
     QHash<const Plasma::Containment *, DockView *> m_waitingDockViews;
diff --git a/app/dockview.cpp b/app/dockview.cpp
index ef825981a..05025f3d9 100644
--- a/app/dockview.cpp
+++ b/app/dockview.cpp
@@ -97,7 +97,7 @@ DockView::DockView(Plasma::Corona *corona, QScreen *targetScreen)
         connect(dockCorona, &DockCorona::dockLocationChanged, this, [&]() {
             //check if an edge has been freed for a primary dock
             //from another screen
-            if(m_onPrimary) {
+            if (m_onPrimary) {
                 m_screenSyncTimer.start();
             }
         });
@@ -196,14 +196,17 @@ bool DockView::setCurrentScreen(const QString id)
     return true;
 }
 
-void DockView::setScreenToFollow(QScreen *screen)
+void DockView::setScreenToFollow(QScreen *screen, bool updateScreenId)
 {
     if (!screen || m_screenToFollow == screen) {
         return;
     }
 
     m_screenToFollow = screen;
-    m_screenToFollowId = screen->name();
+
+    if (updateScreenId) {
+        m_screenToFollowId = screen->name();
+    }
 
     qDebug() << "adapting to screen...";
 
@@ -223,13 +226,29 @@ void DockView::reconsiderScreen()
         qDebug() << "      D, found screen: " << scr->name();
     }
 
-    if (m_onPrimary && screen() != qGuiApp->primaryScreen()) {
-        auto *dockCorona = qobject_cast<DockCorona *>(this->corona());
+    auto *dockCorona = qobject_cast<DockCorona *>(this->corona());
+
+    bool screenExists{false};
+    foreach(auto scr, qGuiApp->screens()){
+        if (m_screenToFollowId == scr->name())
+            screenExists = true;
+    }
 
+    qDebug() << "dock screen exists  ::: "<< screenExists;
+
+    if ((m_onPrimary || (tasksPresent() && dockCorona->noDocksWithTasks() == 1) && !screenExists)
+            && m_screenToFollowId != qGuiApp->primaryScreen()->name()
+            && m_screenToFollow != qGuiApp->primaryScreen()) {
         //change to primary screen only if the specific edge is free
         if (dockCorona->freeEdges(qGuiApp->primaryScreen()).contains(location())) {
             connect(qGuiApp->primaryScreen(), &QScreen::geometryChanged, this, &DockView::screenGeometryChanged);
-            setScreenToFollow(qGuiApp->primaryScreen());
+
+            if (!m_onPrimary && !screenExists && tasksPresent() && (dockCorona->noDocksWithTasks() == 1)) {
+                setScreenToFollow(qGuiApp->primaryScreen(), false);
+            } else {
+                setScreenToFollow(qGuiApp->primaryScreen());
+            }
+
             syncGeometry();
         }
     } else {
diff --git a/app/dockview.h b/app/dockview.h
index 08fe1ae00..930263f7c 100644
--- a/app/dockview.h
+++ b/app/dockview.h
@@ -74,7 +74,7 @@ public:
 
     void init();
 
-    void setScreenToFollow(QScreen *screen);
+    void setScreenToFollow(QScreen *screen, bool updateScreenId = true);
 
     void resizeWindow();
     void syncGeometry();
diff --git a/shell/contents/configuration/BehaviorConfig.qml b/shell/contents/configuration/BehaviorConfig.qml
index b76b357d9..e19c08224 100644
--- a/shell/contents/configuration/BehaviorConfig.qml
+++ b/shell/contents/configuration/BehaviorConfig.qml
@@ -69,6 +69,18 @@ PlasmaComponents.Page {
 
                     screens.push(i18n("On Primary"));
 
+                    //check if the screen exists, it is used in cases Latte is moving
+                    //the dock automatically to primaryScreen in order for the user
+                    //to has always a dock with tasks shown
+                    var screenExists = false
+                    for (var i = 0; i < dock.screens.length; i++) {
+                        if (dock.screens[i].name === dock.currentScreen)
+                            screenExists = true;
+                    }
+
+                    if (!screenExists && !dock.onPrimary)
+                        screens.push(dock.currentScreen);
+
                     for (var i = 0; i < dock.screens.length; i++) {
                         screens.push(dock.screens[i].name)
                     }