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