1 /*
2     SPDX-FileCopyrightText: 2018 Michail Vourlakos <mvourlakos@gmail.com>
3     SPDX-License-Identifier: GPL-2.0-or-later
4 */
5 
6 #include "positioner.h"
7 
8 // local
9 #include <coretypes.h>
10 #include "effects.h"
11 #include "view.h"
12 #include "visibilitymanager.h"
13 #include "../lattecorona.h"
14 #include "../screenpool.h"
15 #include "../data/screendata.h"
16 #include "../layout/centrallayout.h"
17 #include "../layouts/manager.h"
18 #include "../settings/universalsettings.h"
19 #include "../wm/abstractwindowinterface.h"
20 
21 // Qt
22 #include <QDebug>
23 
24 // KDE
25 #include <KWayland/Client/plasmashell.h>
26 #include <KWayland/Client/surface.h>
27 #include <KWindowSystem>
28 
29 #define RELOCATIONSHOWINGEVENT "viewInRelocationShowing"
30 
31 namespace Latte {
32 namespace ViewPart {
33 
Positioner(Latte::View * parent)34 Positioner::Positioner(Latte::View *parent)
35     : QObject(parent),
36       m_view(parent)
37 {
38     m_screenSyncTimer.setSingleShot(true);
39     m_screenSyncTimer.setInterval(2000);
40     connect(&m_screenSyncTimer, &QTimer::timeout, this, &Positioner::reconsiderScreen);
41 
42     //! under X11 it was identified that windows many times especially under screen changes
43     //! don't end up at the correct position and size. This timer will enforce repositionings
44     //! and resizes every 500ms if the window hasn't end up to correct values and until this
45     //! is achieved
46     m_validateGeometryTimer.setSingleShot(true);
47     m_validateGeometryTimer.setInterval(500);
48     connect(&m_validateGeometryTimer, &QTimer::timeout, this, &Positioner::syncGeometry);
49 
50     //! syncGeometry() function is costly, so now we make sure that is not executed too often
51     m_syncGeometryTimer.setSingleShot(true);
52     m_syncGeometryTimer.setInterval(150);
53     connect(&m_syncGeometryTimer, &QTimer::timeout, this, &Positioner::immediateSyncGeometry);
54 
55     m_corona = qobject_cast<Latte::Corona *>(m_view->corona());
56 
57     if (m_corona) {
58         if (KWindowSystem::isPlatformX11()) {
59             m_trackedWindowId = m_view->winId();
60             m_corona->wm()->registerIgnoredWindow(m_trackedWindowId);
61 
62             connect(m_view, &Latte::View::forcedShown, this, [&]() {
63                 m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId);
64                 m_trackedWindowId = m_view->winId();
65                 m_corona->wm()->registerIgnoredWindow(m_trackedWindowId);
66             });
67         } else {
68             connect(m_view, &QWindow::windowTitleChanged, this, &Positioner::updateWaylandId);
69             connect(m_corona->wm(), &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, &Positioner::updateWaylandId);
70         }
71 
72         connect(m_corona->layoutsManager(), &Layouts::Manager::currentLayoutIsSwitching, this, &Positioner::onCurrentLayoutIsSwitching);
73         /////
74 
75         m_screenSyncTimer.setInterval(qMax(m_corona->universalSettings()->screenTrackerInterval() - 500, 1000));
76         connect(m_corona->universalSettings(), &UniversalSettings::screenTrackerIntervalChanged, this, [&]() {
77             m_screenSyncTimer.setInterval(qMax(m_corona->universalSettings()->screenTrackerInterval() - 500, 1000));
78         });
79 
80         connect(m_corona, &Latte::Corona::viewLocationChanged, this, [&]() {
81             //! check if an edge has been freed for a primary dock
82             //! from another screen
83             if (m_view->onPrimary()) {
84                 m_screenSyncTimer.start();
85             }
86         });
87     }
88 
89     init();
90 }
91 
~Positioner()92 Positioner::~Positioner()
93 {
94     m_inDelete = true;
95     slideOutDuringExit();
96     m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId);
97 
98     m_screenSyncTimer.stop();
99     m_validateGeometryTimer.stop();
100 }
101 
init()102 void Positioner::init()
103 {
104     //! connections
105     connect(this, &Positioner::screenGeometryChanged, this, &Positioner::syncGeometry);
106 
107     connect(this, &Positioner::hidingForRelocationStarted, this, &Positioner::updateInRelocationAnimation);
108     connect(this, &Positioner::showingAfterRelocationFinished, this, &Positioner::updateInRelocationAnimation);
109     connect(this, &Positioner::showingAfterRelocationFinished, this, &Positioner::syncLatteViews);
110     connect(this, &Positioner::startupFinished, this, &Positioner::onStartupFinished);
111 
112     connect(m_view, &Latte::View::onPrimaryChanged, this, &Positioner::syncLatteViews);
113 
114     connect(this, &Positioner::inSlideAnimationChanged, this, [&]() {
115         if (!inSlideAnimation()) {
116             syncGeometry();
117         }
118     });
119 
120     connect(this, &Positioner::isStickedOnTopEdgeChanged, this, [&]() {
121         if (m_view->formFactor() == Plasma::Types::Vertical) {
122             syncGeometry();
123         }
124     });
125 
126     connect(this, &Positioner::isStickedOnBottomEdgeChanged, this, [&]() {
127         if (m_view->formFactor() == Plasma::Types::Vertical) {
128             syncGeometry();
129         }
130     });
131 
132     connect(m_corona->activitiesConsumer(), &KActivities::Consumer::currentActivityChanged, this, [&]() {
133         if (m_view->formFactor() == Plasma::Types::Vertical && m_view->layout() && m_view->layout()->isCurrent()) {
134             syncGeometry();
135         }
136     });
137 
138     connect(this, &Positioner::slideOffsetChanged, this, [&]() {
139         updatePosition(m_lastAvailableScreenRect);
140     });
141 
142     connect(m_view, &QQuickWindow::xChanged, this, &Positioner::validateDockGeometry);
143     connect(m_view, &QQuickWindow::yChanged, this, &Positioner::validateDockGeometry);
144     connect(m_view, &QQuickWindow::widthChanged, this, &Positioner::validateDockGeometry);
145     connect(m_view, &QQuickWindow::heightChanged, this, &Positioner::validateDockGeometry);
146     connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::currentScreenChanged);
147     connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::onScreenChanged);
148 
149     connect(m_view, &Latte::View::behaveAsPlasmaPanelChanged, this, &Positioner::syncGeometry);
150     connect(m_view, &Latte::View::maxThicknessChanged, this, &Positioner::syncGeometry);
151 
152     connect(m_view, &Latte::View::behaveAsPlasmaPanelChanged,  this, [&]() {
153         if (!m_view->behaveAsPlasmaPanel() && m_slideOffset != 0) {
154             m_slideOffset = 0;
155             syncGeometry();
156         }
157     });
158 
159     connect(m_view, &Latte::View::offsetChanged, this, [&]() {
160         updatePosition(m_lastAvailableScreenRect);
161     });
162 
163     connect(m_view, &Latte::View::locationChanged, this, [&]() {
164         updateFormFactor();
165         syncGeometry();
166     });
167 
168     connect(m_view, &Latte::View::editThicknessChanged, this, [&]() {
169         updateCanvasGeometry(m_lastAvailableScreenRect);
170     });
171 
172     connect(m_view, &Latte::View::maxLengthChanged, this, [&]() {
173         if (m_view->behaveAsPlasmaPanel()) {
174             syncGeometry();
175         }
176     });
177 
178     connect(m_view, &Latte::View::normalThicknessChanged, this, [&]() {
179         if (m_view->behaveAsPlasmaPanel()) {
180             syncGeometry();
181         }
182     });
183 
184     connect(m_view, &Latte::View::screenEdgeMarginEnabledChanged, this, [&]() {
185         syncGeometry();
186     });
187 
188     connect(m_view, &Latte::View::screenEdgeMarginChanged, this, [&]() {
189         syncGeometry();
190     });
191 
192     connect(m_view, &View::layoutChanged, this, [&]() {
193         if (m_nextLayoutName.isEmpty() && m_view->layout() && m_view->formFactor() == Plasma::Types::Vertical) {
194             syncGeometry();
195         }
196     });
197 
198     connect(m_view->effects(), &Latte::ViewPart::Effects::drawShadowsChanged, this, [&]() {
199         if (!m_view->behaveAsPlasmaPanel()) {
200             syncGeometry();
201         }
202     });
203 
204     connect(m_view->effects(), &Latte::ViewPart::Effects::innerShadowChanged, this, [&]() {
205         if (m_view->behaveAsPlasmaPanel()) {
206             syncGeometry();
207         }
208     });
209 
210     connect(qGuiApp, &QGuiApplication::screenAdded, this, &Positioner::onScreenChanged);
211     connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Positioner::onScreenChanged);
212 
213     connect(m_view, &Latte::View::visibilityChanged, this, &Positioner::initDelayedSignals);
214 
215     initSignalingForLocationChangeSliding();
216 }
217 
initDelayedSignals()218 void Positioner::initDelayedSignals()
219 {
220     connect(m_view->visibility(), &ViewPart::VisibilityManager::isHiddenChanged, this, [&]() {
221         if (m_view->behaveAsPlasmaPanel() && !m_view->visibility()->isHidden() && qAbs(m_slideOffset)>0) {
222             //! ignore any checks to make sure the panel geometry is up-to-date
223             immediateSyncGeometry();
224         }
225     });
226 }
227 
updateWaylandId()228 void Positioner::updateWaylandId()
229 {
230     QString validTitle = m_view->validTitle();
231     if (validTitle.isEmpty()) {
232         return;
233     }
234 
235     Latte::WindowSystem::WindowId newId = m_corona->wm()->winIdFor("latte-dock", validTitle);
236 
237     if (m_trackedWindowId != newId) {
238         if (!m_trackedWindowId.isNull()) {
239             m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId);
240         }
241 
242         m_trackedWindowId = newId;
243         m_corona->wm()->registerIgnoredWindow(m_trackedWindowId);
244 
245         emit winIdChanged();
246     }
247 }
248 
inRelocationShowing() const249 bool Positioner::inRelocationShowing() const
250 {
251     return m_inRelocationShowing;
252 }
253 
setInRelocationShowing(bool active)254 void Positioner::setInRelocationShowing(bool active)
255 {
256     if (m_inRelocationShowing == active) {
257         return;
258     }
259 
260     m_inRelocationShowing = active;
261 
262     if (m_inRelocationShowing) {
263         m_view->visibility()->addBlockHidingEvent(RELOCATIONSHOWINGEVENT);
264     } else {
265         m_view->visibility()->removeBlockHidingEvent(RELOCATIONSHOWINGEVENT);
266     }
267 }
268 
isOffScreen() const269 bool Positioner::isOffScreen() const
270 {
271     return (m_validGeometry.x()<-500 || m_validGeometry.y()<-500);
272 }
273 
currentScreenId() const274 int Positioner::currentScreenId() const
275 {
276     auto *latteCorona = qobject_cast<Latte::Corona *>(m_view->corona());
277 
278     if (latteCorona) {
279         return latteCorona->screenPool()->id(m_screenNameToFollow);
280     }
281 
282     return -1;
283 }
284 
trackedWindowId()285 Latte::WindowSystem::WindowId Positioner::trackedWindowId()
286 {
287     if (KWindowSystem::isPlatformWayland() && m_trackedWindowId.toInt() <= 0) {
288         updateWaylandId();
289     }
290 
291     return m_trackedWindowId;
292 }
293 
currentScreenName() const294 QString Positioner::currentScreenName() const
295 {
296     return m_screenNameToFollow;
297 }
298 
slideLocation(Plasma::Types::Location location)299 WindowSystem::AbstractWindowInterface::Slide Positioner::slideLocation(Plasma::Types::Location location)
300 {
301     auto slideedge = WindowSystem::AbstractWindowInterface::Slide::None;
302 
303     if (location == Plasma::Types::Floating && m_view->containment()) {
304         location = m_view->containment()->location();
305     }
306 
307     switch (location) {
308     case Plasma::Types::TopEdge:
309         slideedge = WindowSystem::AbstractWindowInterface::Slide::Top;
310         break;
311 
312     case Plasma::Types::RightEdge:
313         slideedge = WindowSystem::AbstractWindowInterface::Slide::Right;
314         break;
315 
316     case Plasma::Types::BottomEdge:
317         slideedge = WindowSystem::AbstractWindowInterface::Slide::Bottom;
318         break;
319 
320     case Plasma::Types::LeftEdge:
321         slideedge = WindowSystem::AbstractWindowInterface::Slide::Left;
322         break;
323 
324     default:
325         qDebug() << staticMetaObject.className() << "wrong location";
326         break;
327     }
328 
329     return slideedge;
330 }
331 
slideOutDuringExit(Plasma::Types::Location location)332 void Positioner::slideOutDuringExit(Plasma::Types::Location location)
333 {
334     if (m_view->isVisible()) {
335         m_corona->wm()->slideWindow(*m_view, slideLocation(location));
336         m_view->setVisible(false);
337     }
338 }
339 
slideInDuringStartup()340 void Positioner::slideInDuringStartup()
341 {
342     m_corona->wm()->slideWindow(*m_view, slideLocation(m_view->containment()->location()));
343 }
344 
onStartupFinished()345 void Positioner::onStartupFinished()
346 {
347     if (m_inStartup) {
348         m_inStartup = false;
349         syncGeometry();
350     }
351 }
352 
onCurrentLayoutIsSwitching(const QString & layoutName)353 void Positioner::onCurrentLayoutIsSwitching(const QString &layoutName)
354 {
355     if (!m_view || !m_view->layout() || m_view->layout()->name() != layoutName || !m_view->isVisible()) {
356         return;
357     }
358 
359     m_inLayoutUnloading = true;
360     slideOutDuringExit();
361 }
362 
setWindowOnActivities(const Latte::WindowSystem::WindowId & wid,const QStringList & activities)363 void Positioner::setWindowOnActivities(const Latte::WindowSystem::WindowId &wid, const QStringList &activities)
364 {
365     m_corona->wm()->setWindowOnActivities(wid, activities);
366 }
367 
syncLatteViews()368 void Positioner::syncLatteViews()
369 {
370     if (m_view->layout()) {
371         //! This is needed in case the edge there are views that must be deleted
372         //! after screen edges changes
373         m_view->layout()->syncLatteViewsToScreens();
374     }
375 }
376 
updateContainmentScreen()377 void Positioner::updateContainmentScreen()
378 {
379     if (m_view->containment()) {
380         m_view->containment()->reactToScreenChange();
381     }
382 }
383 
384 //! this function updates the dock's associated screen.
385 //! updateScreenId = true, update also the m_screenNameToFollow
386 //! updateScreenId = false, do not update the m_screenNameToFollow
387 //! that way an explicit dock can be shown in another screen when
388 //! there isnt a tasks dock running in the system and for that
389 //! dock its first origin screen is stored and that way when
390 //! that screen is reconnected the dock will return to its original
391 //! place
setScreenToFollow(QScreen * scr,bool updateScreenId)392 void Positioner::setScreenToFollow(QScreen *scr, bool updateScreenId)
393 {
394     if (!scr || (scr && (m_screenToFollow == scr) && (m_view->screen() == scr))) {
395         return;
396     }
397 
398     qDebug() << "setScreenToFollow() called for screen:" << scr->name() << " update:" << updateScreenId;
399 
400     m_screenToFollow = scr;
401 
402     if (updateScreenId) {
403         m_screenNameToFollow = scr->name();
404     }
405 
406     qDebug() << "adapting to screen...";
407     m_view->setScreen(scr);
408 
409     updateContainmentScreen();
410 
411     connect(scr, &QScreen::geometryChanged, this, &Positioner::screenGeometryChanged);
412     syncGeometry();
413     m_view->updateAbsoluteGeometry(true);
414     qDebug() << "setScreenToFollow() ended...";
415 
416     emit screenGeometryChanged();
417     emit currentScreenChanged();
418 }
419 
420 //! the main function which decides if this dock is at the
421 //! correct screen
reconsiderScreen()422 void Positioner::reconsiderScreen()
423 {
424     if (m_inDelete) {
425         return;
426     }
427 
428     qDebug() << "reconsiderScreen() called...";
429     qDebug() << "  Delayer  ";
430 
431     for (const auto scr : qGuiApp->screens()) {
432         qDebug() << "      D, found screen: " << scr->name();
433     }
434 
435     bool screenExists{false};
436 
437     //!check if the associated screen is running
438     for (const auto scr : qGuiApp->screens()) {
439         if (m_screenNameToFollow == scr->name()
440                 || (m_view->onPrimary() && scr == qGuiApp->primaryScreen())) {
441             screenExists = true;
442         }
443     }
444 
445     qDebug() << "dock screen exists  ::: " << screenExists;
446 
447     //! 1.a primary dock must be always on the primary screen
448     if (m_view->onPrimary() && (m_screenNameToFollow != qGuiApp->primaryScreen()->name()
449                                 || m_screenToFollow != qGuiApp->primaryScreen()
450                                 || m_view->screen() != qGuiApp->primaryScreen())) {
451         //! case 1
452         qDebug() << "reached case 1: of updating dock primary screen...";
453         setScreenToFollow(qGuiApp->primaryScreen());
454     } else if (!m_view->onPrimary()) {
455         //! 2.an explicit dock must be always on the correct associated screen
456         //! there are cases that window manager misplaces the dock, this function
457         //! ensures that this dock will return at its correct screen
458         for (const auto scr : qGuiApp->screens()) {
459             if (scr && scr->name() == m_screenNameToFollow) {
460                 qDebug() << "reached case 2: updating the explicit screen for dock...";
461                 setScreenToFollow(scr);
462                 break;
463             }
464         }
465     }
466 
467     syncGeometry();
468     qDebug() << "reconsiderScreen() ended...";
469 }
470 
onScreenChanged(QScreen * scr)471 void Positioner::onScreenChanged(QScreen *scr)
472 {
473     m_screenSyncTimer.start();
474 
475     //! this is needed in order to update the struts on screen change
476     //! and even though the geometry has been set correctly the offsets
477     //! of the screen must be updated to the new ones
478     if (m_view->visibility() && m_view->visibility()->mode() == Latte::Types::AlwaysVisible) {
479         m_view->updateAbsoluteGeometry(true);
480     }
481 }
482 
syncGeometry()483 void Positioner::syncGeometry()
484 {
485     if (!(m_view->screen() && m_view->containment()) || m_inDelete || m_slideOffset!=0 || inSlideAnimation()) {
486         return;
487     }
488 
489     qDebug() << "syncGeometry() called...";
490 
491     if (!m_syncGeometryTimer.isActive()) {
492         m_syncGeometryTimer.start();
493     }
494 }
495 
immediateSyncGeometry()496 void Positioner::immediateSyncGeometry()
497 {
498     bool found{false};
499 
500     qDebug() << "immediateSyncGeometry() called...";
501 
502     //! before updating the positioning and geometry of the dock
503     //! we make sure that the dock is at the correct screen
504     if (m_view->screen() != m_screenToFollow) {
505         qDebug() << "Sync Geometry screens inconsistent!!!! ";
506 
507         if (m_screenToFollow) {
508             qDebug() << "Sync Geometry screens inconsistent for m_screenToFollow:" << m_screenToFollow->name() << " dock screen:" << m_view->screen()->name();
509         }
510 
511         if (!m_screenSyncTimer.isActive()) {
512             m_screenSyncTimer.start();
513         }
514     } else {
515         found = true;
516     }
517 
518     //! if the dock isnt at the correct screen the calculations
519     //! are not executed
520     if (found) {
521         //! compute the free screen rectangle for vertical panels only once
522         //! this way the costly QRegion computations are calculated only once
523         //! instead of two times (both inside the resizeWindow and the updatePosition)
524         QRegion freeRegion;;
525         QRect maximumRect;
526         QRect availableScreenRect = m_view->screen()->geometry();
527 
528         if (m_inStartup) {
529             //! paint out-of-screen
530             availableScreenRect = QRect(-9999, -9999, m_view->screen()->geometry().width(), m_view->screen()->geometry().height());
531         }
532 
533         if (m_view->formFactor() == Plasma::Types::Vertical) {
534             QString layoutName = m_view->layout() ? m_view->layout()->name() : QString();
535             auto latteCorona = qobject_cast<Latte::Corona *>(m_view->corona());
536             int fixedScreen = m_view->onPrimary() ? latteCorona->screenPool()->primaryScreenId() : m_view->containment()->screen();
537 
538             QList<Types::Visibility> ignoreModes({Latte::Types::AutoHide,
539                                                   Latte::Types::SidebarOnDemand,
540                                                   Latte::Types::SidebarAutoHide});
541 
542             QList<Plasma::Types::Location> ignoreEdges({Plasma::Types::LeftEdge,
543                                                         Plasma::Types::RightEdge});
544 
545             if (m_isStickedOnTopEdge && m_isStickedOnBottomEdge) {
546                 //! dont send an empty edges array because that means include all screen edges in calculations
547                 ignoreEdges << Plasma::Types::TopEdge;
548                 ignoreEdges << Plasma::Types::BottomEdge;
549             } else {
550                 if (m_isStickedOnTopEdge) {
551                     ignoreEdges << Plasma::Types::TopEdge;
552                 }
553 
554                 if (m_isStickedOnBottomEdge) {
555                     ignoreEdges << Plasma::Types::BottomEdge;
556                 }
557             }
558 
559             QString activityid = m_view->layout() ? m_view->layout()->lastUsedActivity() : QString();
560             if (m_inStartup) {
561                 //! paint out-of-screen
562                 freeRegion = availableScreenRect;
563             } else {
564                 freeRegion = latteCorona->availableScreenRegionWithCriteria(fixedScreen, activityid, ignoreModes, ignoreEdges);
565             }
566 
567             maximumRect = maximumNormalGeometry();
568             QRegion availableRegion = freeRegion.intersected(maximumRect);
569 
570             availableScreenRect = freeRegion.intersected(maximumRect).boundingRect();
571             float area = 0;
572 
573             //! it is used to choose which or the availableRegion rectangles will
574             //! be the one representing dock geometry
575             for (QRegion::const_iterator p_rect=availableRegion.begin(); p_rect!=availableRegion.end(); ++p_rect) {
576                 //! the area of each rectangle in calculated in squares of 50x50
577                 //! this is a way to avoid enourmous numbers for area value
578                 float tempArea = (float)((*p_rect).width() * (*p_rect).height()) / 2500;
579 
580                 if (tempArea > area) {
581                     availableScreenRect = (*p_rect);
582                     area = tempArea;
583                 }
584             }
585 
586             validateTopBottomBorders(availableScreenRect, freeRegion);
587 
588             m_lastAvailableScreenRegion = freeRegion;
589         } else {
590             m_view->effects()->setForceTopBorder(false);
591             m_view->effects()->setForceBottomBorder(false);
592         }
593 
594         m_lastAvailableScreenRect = availableScreenRect;
595 
596         m_view->effects()->updateEnabledBorders();
597 
598         resizeWindow(availableScreenRect);
599         updatePosition(availableScreenRect);
600         updateCanvasGeometry(availableScreenRect);
601 
602         qDebug() << "syncGeometry() calculations for screen: " << m_view->screen()->name() << " _ " << m_view->screen()->geometry();
603         qDebug() << "syncGeometry() calculations for edge: " << m_view->location();
604     }
605 
606     qDebug() << "syncGeometry() ended...";
607 
608     // qDebug() << "dock geometry:" << qRectToStr(geometry());
609 }
610 
validateDockGeometry()611 void Positioner::validateDockGeometry()
612 {
613     if (m_slideOffset==0 && m_view->geometry() != m_validGeometry) {
614         m_validateGeometryTimer.start();
615     }
616 }
617 
canvasGeometry()618 QRect Positioner::canvasGeometry()
619 {
620     return m_canvasGeometry;
621 }
622 
setCanvasGeometry(const QRect & geometry)623 void Positioner::setCanvasGeometry(const QRect &geometry)
624 {
625     if (m_canvasGeometry == geometry) {
626         return;
627     }
628 
629     m_canvasGeometry = geometry;
630     emit canvasGeometryChanged();
631 }
632 
633 
634 //! this is used mainly from vertical panels in order to
635 //! to get the maximum geometry that can be used from the dock
636 //! based on their alignment type and the location dock
maximumNormalGeometry()637 QRect Positioner::maximumNormalGeometry()
638 {
639     int xPos = 0;
640     int yPos = m_view->screen()->geometry().y();;
641     int maxHeight = m_view->screen()->geometry().height();
642     int maxWidth = m_view->maxNormalThickness();
643     QRect maxGeometry;
644     maxGeometry.setRect(0, 0, maxWidth, maxHeight);
645 
646     switch (m_view->location()) {
647     case Plasma::Types::LeftEdge:
648         xPos = m_view->screen()->geometry().x();
649         maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight);
650         break;
651 
652     case Plasma::Types::RightEdge:
653         xPos = m_view->screen()->geometry().right() - maxWidth + 1;
654         maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight);
655         break;
656 
657     default:
658         //! bypass clang warnings
659         break;
660     }
661 
662     return maxGeometry;
663 }
664 
validateTopBottomBorders(QRect availableScreenRect,QRegion availableScreenRegion)665 void Positioner::validateTopBottomBorders(QRect availableScreenRect, QRegion availableScreenRegion)
666 {
667     //! Check if the the top/bottom borders must be drawn also
668     int edgeMargin = qMax(1, m_view->screenEdgeMargin());
669 
670     if (availableScreenRect.top() != m_view->screenGeometry().top()) {
671         //! check top border
672         QRegion fitInRegion = QRect(m_view->screenGeometry().x(), availableScreenRect.y()-1, edgeMargin, 1);
673         QRegion subtracted = fitInRegion.subtracted(availableScreenRegion);
674 
675         if (subtracted.isNull()) {
676             //!FitIn rectangle fits TOTALLY in the free screen region and as such
677             //!the top border should be drawn
678             m_view->effects()->setForceTopBorder(true);
679         } else {
680             m_view->effects()->setForceTopBorder(false);
681         }
682     } else {
683         m_view->effects()->setForceTopBorder(false);
684     }
685 
686     if (availableScreenRect.bottom() != m_view->screenGeometry().bottom()) {
687         //! check top border
688         QRegion fitInRegion = QRect(m_view->screenGeometry().x(), availableScreenRect.bottom()+1, edgeMargin, 1);
689         QRegion subtracted = fitInRegion.subtracted(availableScreenRegion);
690 
691         if (subtracted.isNull()) {
692             //!FitIn rectangle fits TOTALLY in the free screen region and as such
693             //!the BOTTOM border should be drawn
694             m_view->effects()->setForceBottomBorder(true);
695         } else {
696             m_view->effects()->setForceBottomBorder(false);
697         }
698     } else {
699         m_view->effects()->setForceBottomBorder(false);
700     }
701 }
702 
updateCanvasGeometry(QRect availableScreenRect)703 void Positioner::updateCanvasGeometry(QRect availableScreenRect)
704 {
705     if (availableScreenRect.isEmpty()) {
706         return;
707     }
708 
709     QRect canvas;
710     QRect screenGeometry{m_view->screen()->geometry()};
711     int thickness{m_view->editThickness()};
712 
713     if (m_view->formFactor() == Plasma::Types::Vertical) {
714         canvas.setWidth(thickness);
715         canvas.setHeight(availableScreenRect.height());
716     } else {
717         canvas.setWidth(screenGeometry.width());
718         canvas.setHeight(thickness);
719     }
720 
721     switch (m_view->location()) {
722     case Plasma::Types::TopEdge:
723         canvas.moveLeft(screenGeometry.x());
724         canvas.moveTop(screenGeometry.y());
725         break;
726 
727     case Plasma::Types::BottomEdge:
728         canvas.moveLeft(screenGeometry.x());
729         canvas.moveTop(screenGeometry.bottom() - thickness + 1);
730         break;
731 
732     case Plasma::Types::RightEdge:
733         canvas.moveLeft(screenGeometry.right() - thickness + 1);
734         canvas.moveTop(availableScreenRect.y());
735         break;
736 
737     case Plasma::Types::LeftEdge:
738         canvas.moveLeft(availableScreenRect.x());
739         canvas.moveTop(availableScreenRect.y());
740         break;
741 
742     default:
743         qWarning() << "wrong location, couldn't update the canvas config window geometry " << m_view->location();
744     }
745 
746     setCanvasGeometry(canvas);
747 }
748 
updatePosition(QRect availableScreenRect)749 void Positioner::updatePosition(QRect availableScreenRect)
750 {
751     QRect screenGeometry{availableScreenRect};
752     QPoint position;
753     position = {0, 0};
754 
755     const auto gap = [&](int scr_length) -> int {
756         return static_cast<int>(scr_length * m_view->offset());
757     };
758     const auto gapCentered = [&](int scr_length) -> int {
759         return static_cast<int>(scr_length * ((1 - m_view->maxLength()) / 2) + scr_length * m_view->offset());
760     };
761     const auto gapReversed = [&](int scr_length) -> int {
762         return static_cast<int>(scr_length - (scr_length * m_view->maxLength()) - gap(scr_length));
763     };
764 
765     int cleanThickness = m_view->normalThickness() - m_view->effects()->innerShadow();
766 
767     int screenEdgeMargin = m_view->behaveAsPlasmaPanel() ? m_view->screenEdgeMargin() - qAbs(m_slideOffset) : 0;
768 
769     switch (m_view->location()) {
770     case Plasma::Types::TopEdge:
771         if (m_view->behaveAsPlasmaPanel()) {
772             int y = screenGeometry.y() + screenEdgeMargin;
773 
774             if (m_view->alignment() == Latte::Types::Left) {
775                 position = {screenGeometry.x() + gap(screenGeometry.width()), y};
776             } else if (m_view->alignment() == Latte::Types::Right) {
777                 position = {screenGeometry.x() + gapReversed(screenGeometry.width()) + 1, y};
778             } else {
779                 position = {screenGeometry.x() + gapCentered(screenGeometry.width()), y};
780             }
781         } else {
782             position = {screenGeometry.x(), screenGeometry.y()};
783         }
784 
785         break;
786 
787     case Plasma::Types::BottomEdge:
788         if (m_view->behaveAsPlasmaPanel()) {
789             int y = screenGeometry.y() + screenGeometry.height() - cleanThickness - screenEdgeMargin;
790 
791             if (m_view->alignment() == Latte::Types::Left) {
792                 position = {screenGeometry.x() + gap(screenGeometry.width()), y};
793             } else if (m_view->alignment() == Latte::Types::Right) {
794                 position = {screenGeometry.x() + gapReversed(screenGeometry.width()) + 1, y};
795             } else {
796                 position = {screenGeometry.x() + gapCentered(screenGeometry.width()), y};
797             }
798         } else {
799             position = {screenGeometry.x(), screenGeometry.y() + screenGeometry.height() - m_view->height()};
800         }
801 
802         break;
803 
804     case Plasma::Types::RightEdge:
805         if (m_view->behaveAsPlasmaPanel()) {
806             int x = availableScreenRect.right() - cleanThickness + 1 - screenEdgeMargin;
807 
808             if (m_view->alignment() == Latte::Types::Top) {
809                 position = {x, availableScreenRect.y() + gap(availableScreenRect.height())};
810             } else if (m_view->alignment() == Latte::Types::Bottom) {
811                 position = {x, availableScreenRect.y() + gapReversed(availableScreenRect.height()) + 1};
812             } else {
813                 position = {x, availableScreenRect.y() + gapCentered(availableScreenRect.height())};
814             }
815         } else {
816             position = {availableScreenRect.right() - m_view->width() + 1, availableScreenRect.y()};
817         }
818 
819         break;
820 
821     case Plasma::Types::LeftEdge:
822         if (m_view->behaveAsPlasmaPanel()) {
823             int x = availableScreenRect.x() + screenEdgeMargin;
824 
825             if (m_view->alignment() == Latte::Types::Top) {
826                 position = {x, availableScreenRect.y() + gap(availableScreenRect.height())};
827             } else if (m_view->alignment() == Latte::Types::Bottom) {
828                 position = {x, availableScreenRect.y() + gapReversed(availableScreenRect.height()) + 1};
829             } else {
830                 position = {x, availableScreenRect.y() + gapCentered(availableScreenRect.height())};
831             }
832         } else {
833             position = {availableScreenRect.x(), availableScreenRect.y()};
834         }
835 
836         break;
837 
838     default:
839         qWarning() << "wrong location, couldn't update the panel position"
840                    << m_view->location();
841     }
842 
843     if (m_slideOffset == 0 || m_nextScreenEdge != Plasma::Types::Floating /*exactly after relocating and changing screen edge*/) {
844         //! update valid geometry in normal positioning
845         m_validGeometry.moveTopLeft(position);
846     } else {
847         //! when sliding in/out update only the relevant axis for the screen_edge in
848         //! to not mess the calculations and the automatic geometry checkers that
849         //! View::Positioner is using.
850         if (m_view->formFactor() == Plasma::Types::Horizontal) {
851             m_validGeometry.moveLeft(position.x());
852         } else {
853             m_validGeometry.moveTop(position.y());
854         }
855     }
856 
857     m_view->setPosition(position);
858 
859     if (m_view->surface()) {
860         m_view->surface()->setPosition(position);
861     }
862 }
863 
slideOffset() const864 int Positioner::slideOffset() const
865 {
866     return m_slideOffset;
867 }
868 
setSlideOffset(int offset)869 void Positioner::setSlideOffset(int offset)
870 {
871     if (m_slideOffset == offset) {
872         return;
873     }
874 
875     m_slideOffset = offset;
876     emit slideOffsetChanged();
877 }
878 
879 
resizeWindow(QRect availableScreenRect)880 void Positioner::resizeWindow(QRect availableScreenRect)
881 {
882     QSize screenSize = m_view->screen()->size();
883     QSize size = (m_view->formFactor() == Plasma::Types::Vertical) ? QSize(m_view->maxThickness(), availableScreenRect.height()) : QSize(screenSize.width(), m_view->maxThickness());
884 
885     if (m_view->formFactor() == Plasma::Types::Vertical) {
886         //qDebug() << "MAXIMUM RECT :: " << maximumRect << " - AVAILABLE RECT :: " << availableRect;
887         if (m_view->behaveAsPlasmaPanel()) {
888             size.setWidth(m_view->normalThickness());
889             size.setHeight(static_cast<int>(m_view->maxLength() * availableScreenRect.height()));
890         }
891     } else {
892         if (m_view->behaveAsPlasmaPanel()) {
893             size.setWidth(static_cast<int>(m_view->maxLength() * screenSize.width()));
894             size.setHeight(m_view->normalThickness());
895         }
896     }
897 
898     //! protect from invalid window size under wayland
899     size.setWidth(qMax(1, size.width()));
900     size.setHeight(qMax(1, size.height()));
901 
902     m_validGeometry.setSize(size);
903 
904     m_view->setMinimumSize(size);
905     m_view->setMaximumSize(size);
906     m_view->resize(size);
907 
908     if (m_view->formFactor() == Plasma::Types::Horizontal) {
909         emit windowSizeChanged();
910     }
911 }
912 
updateFormFactor()913 void Positioner::updateFormFactor()
914 {
915     if (!m_view->containment())
916         return;
917 
918     switch (m_view->location()) {
919     case Plasma::Types::TopEdge:
920     case Plasma::Types::BottomEdge:
921         m_view->containment()->setFormFactor(Plasma::Types::Horizontal);
922         break;
923 
924     case Plasma::Types::LeftEdge:
925     case Plasma::Types::RightEdge:
926         m_view->containment()->setFormFactor(Plasma::Types::Vertical);
927         break;
928 
929     default:
930         qWarning() << "wrong location, couldn't update the panel position" << m_view->location();
931     }
932 }
933 
onLastRepositionApplyEvent()934 void Positioner::onLastRepositionApplyEvent()
935 {
936     m_view->effects()->setAnimationsBlocked(false);
937     setInRelocationShowing(true);
938     emit showingAfterRelocationFinished();
939     emit edgeChanged();
940 
941     if (m_repositionFromViewSettingsWindow) {
942         m_repositionFromViewSettingsWindow = false;
943         m_view->showSettingsWindow();
944     }
945 }
946 
initSignalingForLocationChangeSliding()947 void Positioner::initSignalingForLocationChangeSliding()
948 {
949     connect(this, &Positioner::hidingForRelocationStarted, this, &Positioner::onHideWindowsForSlidingOut);
950 
951     //! SCREEN_EDGE
952     connect(m_view, &View::locationChanged, this, [&]() {
953         if (m_nextScreenEdge != Plasma::Types::Floating) {
954             bool isrelocationlastevent = isLastHidingRelocationEvent();
955             immediateSyncGeometry();
956             m_nextScreenEdge = Plasma::Types::Floating;
957 
958             //! make sure that View has been repositioned properly in next screen edge and show view afterwards
959             if (isrelocationlastevent) {
960                 QTimer::singleShot(100, [this]() {
961                     onLastRepositionApplyEvent();
962                 });
963             }
964         }
965     });
966 
967     //! SCREEN
968     connect(m_view, &QQuickView::screenChanged, this, [&]() {
969         if (m_nextScreen
970                 && m_nextScreen == m_view->screen()
971                 && m_nextScreen->geometry().contains(m_view->geometry().center())) {
972 
973             bool isrelocationlastevent = isLastHidingRelocationEvent();
974             m_nextScreen = nullptr;
975             m_nextScreenName = "";
976 
977             //! make sure that View has been repositioned properly in next screen and show view afterwards
978             if (isrelocationlastevent) {
979                 QTimer::singleShot(100, [this]() {
980                     onLastRepositionApplyEvent();
981                 });
982             }
983         }
984     });
985 
986     //! LAYOUT
987     connect(m_view, &View::layoutChanged, this, [&]() {
988         if (!m_nextLayoutName.isEmpty() && m_view->layout()) {
989             bool isrelocationlastevent = isLastHidingRelocationEvent();
990             m_nextLayoutName = "";
991 
992             //! make sure that View has been repositioned properly in next layout and show view afterwards
993             if (isrelocationlastevent) {
994                 QTimer::singleShot(100, [this]() {
995                     onLastRepositionApplyEvent();
996                 });
997             }
998         }
999     });
1000 
1001     //! APPLY CHANGES
1002     connect(this, &Positioner::hidingForRelocationFinished, this, [&]() {
1003         //! must be called only if relocation is animated
1004         if (m_repositionIsAnimated) {
1005             m_repositionIsAnimated = false;
1006             m_view->effects()->setAnimationsBlocked(true);
1007         }
1008 
1009         //! LAYOUT
1010         if (!m_nextLayoutName.isEmpty()) {
1011             m_corona->layoutsManager()->moveView(m_view->layout()->name(), m_view->containment()->id(), m_nextLayoutName);
1012         }
1013 
1014         //! SCREEN
1015         if (!m_nextScreenName.isEmpty()) {
1016             bool nextonprimary = (m_nextScreenName == Latte::Data::Screen::ONPRIMARYNAME);
1017             m_nextScreen = qGuiApp->primaryScreen();
1018 
1019             if (!nextonprimary) {
1020                 for (const auto scr : qGuiApp->screens()) {
1021                     if (scr && scr->name() == m_nextScreenName) {
1022                         m_nextScreen = scr;
1023                         break;
1024                     }
1025                 }
1026             }
1027 
1028             m_view->setOnPrimary(nextonprimary);
1029             setScreenToFollow(m_nextScreen);
1030         }
1031 
1032         //! SCREEN_EDGE
1033         if (m_nextScreenEdge != Plasma::Types::Floating) {
1034             m_view->setLocation(m_nextScreenEdge);
1035         }
1036 
1037         //! ALIGNMENT
1038         if (m_nextAlignment != Latte::Types::NoneAlignment && m_nextAlignment != m_view->alignment()) {
1039             m_view->setAlignment(m_nextAlignment);
1040             m_nextAlignment = Latte::Types::NoneAlignment;
1041         }
1042     });
1043 }
1044 
inLayoutUnloading()1045 bool Positioner::inLayoutUnloading()
1046 {
1047     return m_inLayoutUnloading;
1048 }
1049 
inRelocationAnimation()1050 bool Positioner::inRelocationAnimation()
1051 {
1052     return ((m_nextScreenEdge != Plasma::Types::Floating) || !m_nextLayoutName.isEmpty() || !m_nextScreenName.isEmpty());
1053 }
1054 
inSlideAnimation() const1055 bool Positioner::inSlideAnimation() const
1056 {
1057     return m_inSlideAnimation;
1058 }
1059 
setInSlideAnimation(bool active)1060 void Positioner::setInSlideAnimation(bool active)
1061 {
1062     if (m_inSlideAnimation == active) {
1063         return;
1064     }
1065 
1066     m_inSlideAnimation = active;
1067     emit inSlideAnimationChanged();
1068 }
1069 
isCursorInsideView() const1070 bool Positioner::isCursorInsideView() const
1071 {
1072     return m_view->geometry().contains(QCursor::pos(m_screenToFollow));
1073 }
1074 
isStickedOnTopEdge() const1075 bool Positioner::isStickedOnTopEdge() const
1076 {
1077     return m_isStickedOnTopEdge;
1078 }
1079 
setIsStickedOnTopEdge(bool sticked)1080 void Positioner::setIsStickedOnTopEdge(bool sticked)
1081 {
1082     if (m_isStickedOnTopEdge == sticked) {
1083         return;
1084     }
1085 
1086     m_isStickedOnTopEdge = sticked;
1087     emit isStickedOnTopEdgeChanged();
1088 }
1089 
isStickedOnBottomEdge() const1090 bool Positioner::isStickedOnBottomEdge() const
1091 {
1092     return m_isStickedOnBottomEdge;
1093 }
1094 
setIsStickedOnBottomEdge(bool sticked)1095 void Positioner::setIsStickedOnBottomEdge(bool sticked)
1096 {
1097     if (m_isStickedOnBottomEdge == sticked) {
1098         return;
1099     }
1100 
1101     m_isStickedOnBottomEdge = sticked;
1102     emit isStickedOnBottomEdgeChanged();
1103 }
1104 
updateInRelocationAnimation()1105 void Positioner::updateInRelocationAnimation()
1106 {
1107     bool inrelocationanimation = inRelocationAnimation();
1108 
1109     if (m_inRelocationAnimation == inrelocationanimation) {
1110         return;
1111     }
1112 
1113     m_inRelocationAnimation = inrelocationanimation;
1114     emit inRelocationAnimationChanged();
1115 }
1116 
isLastHidingRelocationEvent() const1117 bool Positioner::isLastHidingRelocationEvent() const
1118 {
1119     int events{0};
1120 
1121     if (!m_nextLayoutName.isEmpty()) {
1122         events++;
1123     }
1124 
1125     if (!m_nextScreenName.isEmpty()){
1126         events++;
1127     }
1128 
1129     if (m_nextScreenEdge != Plasma::Types::Floating) {
1130         events++;
1131     }
1132 
1133     return (events <= 1);
1134 }
1135 
setNextLocation(const QString layoutName,const QString screenName,int edge,int alignment)1136 void Positioner::setNextLocation(const QString layoutName, const QString screenName, int edge, int alignment)
1137 {
1138     bool isanimated{false};
1139     bool haschanges{false};
1140 
1141     //! LAYOUT
1142     if (!layoutName.isEmpty()) {
1143         auto layout = m_view->layout();
1144         auto origin = qobject_cast<CentralLayout *>(layout);
1145         auto destination = m_corona->layoutsManager()->synchronizer()->centralLayout(layoutName);
1146 
1147         if (origin && destination && origin!=destination) {
1148             //! Needs to be updated; when the next layout is in the same Visible Workarea
1149             //! with the old one changing layouts should be instant
1150             bool inVisibleWorkarea{origin->lastUsedActivity() == destination->lastUsedActivity()};
1151 
1152             haschanges = true;
1153             m_nextLayoutName = layoutName;
1154 
1155             if (!inVisibleWorkarea) {
1156                 isanimated = true;
1157             }
1158         }
1159     }
1160 
1161     //! SCREEN
1162     if (!screenName.isEmpty()) {
1163         bool nextonprimary = (screenName == Latte::Data::Screen::ONPRIMARYNAME);
1164 
1165         if ( (m_view->onPrimary() && !nextonprimary) /*primary -> explicit*/
1166              || (!m_view->onPrimary() && nextonprimary) /*explicit -> primary*/
1167              || (!m_view->onPrimary() && !nextonprimary && screenName != currentScreenName()) ) { /*explicit -> new_explicit*/
1168 
1169             QString nextscreenname = nextonprimary ? qGuiApp->primaryScreen()->name() : screenName;
1170 
1171             if (currentScreenName() == nextscreenname) {
1172                 m_view->setOnPrimary(nextonprimary);
1173                 updateContainmentScreen();
1174             } else {
1175                 m_nextScreenName = screenName;
1176                 isanimated = true;
1177                 haschanges = true;
1178             }
1179         }
1180     }
1181 
1182     //! SCREEN_EDGE
1183     if (edge != Plasma::Types::Floating) {
1184         if (edge != m_view->location()) {
1185             m_nextScreenEdge = static_cast<Plasma::Types::Location>(edge);
1186             isanimated = true;
1187             haschanges = true;
1188         }
1189     }
1190 
1191     //! ALIGNMENT
1192     if (alignment != Latte::Types::NoneAlignment && m_view->alignment() != alignment) {
1193         m_nextAlignment = static_cast<Latte::Types::Alignment>(alignment);
1194         haschanges = true;
1195     }
1196 
1197     m_repositionIsAnimated = isanimated;
1198     m_repositionFromViewSettingsWindow = m_view->settingsWindowIsShown();
1199 
1200     if (isanimated) {
1201         emit hidingForRelocationStarted();
1202     } else if (haschanges){
1203         emit hidingForRelocationFinished();
1204     }
1205 }
1206 
1207 }
1208 }
1209