1 /*
2     SPDX-FileCopyrightText: 2016 Smith AR <audoban@openmailbox.org>
3     SPDX-FileCopyrightText: 2016 Michail Vourlakos <mvourlakos@gmail.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "primaryconfigview.h"
9 
10 // local
11 #include <config-latte.h>
12 #include "canvasconfigview.h"
13 #include "indicatoruimanager.h"
14 #include "secondaryconfigview.h"
15 #include "../effects.h"
16 #include "../panelshadows_p.h"
17 #include "../view.h"
18 #include "../../lattecorona.h"
19 #include "../../layouts/manager.h"
20 #include "../../layout/genericlayout.h"
21 #include "../../settings/universalsettings.h"
22 #include "../../wm/abstractwindowinterface.h"
23 
24 // Qt
25 #include <QQuickItem>
26 #include <QQmlContext>
27 #include <QQmlEngine>
28 #include <QScreen>
29 
30 // KDE
31 #include <KLocalizedContext>
32 #include <KDeclarative/KDeclarative>
33 #include <KWayland/Client/plasmashell.h>
34 #include <KWayland/Client/surface.h>
35 #include <KWindowEffects>
36 #include <KWindowSystem>
37 
38 // Plasma
39 #include <Plasma/Package>
40 
41 #define CANVASWINDOWINTERVAL 50
42 #define PRIMARYWINDOWINTERVAL 250
43 #define SECONDARYWINDOWINTERVAL 200
44 #define SLIDEOUTINTERVAL 400
45 
46 namespace Latte {
47 namespace ViewPart {
48 
PrimaryConfigView(Latte::View * view)49 PrimaryConfigView::PrimaryConfigView(Latte::View *view)
50     : SubConfigView(view, QString("#primaryconfigview#")),
51       m_indicatorUiManager(new Config::IndicatorUiManager(this))
52 {
53     connect(this, &QQuickWindow::xChanged, this, &PrimaryConfigView::xChanged);
54     connect(this, &QQuickWindow::yChanged, this, &PrimaryConfigView::yChanged);
55 
56     connect(this, &QQuickView::widthChanged, this, &PrimaryConfigView::updateEffects);
57     connect(this, &QQuickView::heightChanged, this, &PrimaryConfigView::updateEffects);
58 
59     connect(this, &PrimaryConfigView::availableScreenGeometryChanged, this, &PrimaryConfigView::syncGeometry);
60 
61     connect(this, &QQuickView::statusChanged, [&](QQuickView::Status status) {
62         if (status == QQuickView::Ready) {
63             updateEffects();
64         }
65     });
66 
67     if (m_corona) {
68         connections << connect(m_corona, &Latte::Corona::raiseViewsTemporaryChanged, this, &PrimaryConfigView::raiseDocksTemporaryChanged);
69         connections << connect(m_corona, &Latte::Corona::availableScreenRectChangedFrom, this, &PrimaryConfigView::updateAvailableScreenGeometry);
70 
71         connections << connect(m_corona->layoutsManager(), &Latte::Layouts::Manager::currentLayoutIsSwitching, this, [this]() {
72             if (isVisible()) {
73                 hideConfigWindow();
74             }
75         });
76 
77         connect(m_corona->universalSettings(), &Latte::UniversalSettings::inAdvancedModeForEditSettingsChanged,
78                 this, &PrimaryConfigView::updateShowInlineProperties);
79         connect(m_corona->universalSettings(), &Latte::UniversalSettings::inAdvancedModeForEditSettingsChanged,
80                 this, &PrimaryConfigView::syncGeometry);
81     }
82 
83     m_availableScreemGeometryTimer.setSingleShot(true);
84     m_availableScreemGeometryTimer.setInterval(250);
85 
86     connections << connect(&m_availableScreemGeometryTimer, &QTimer::timeout, this, [this]() {
87         instantUpdateAvailableScreenGeometry();
88     });
89 
90     setParentView(view);
91     init();
92 }
93 
~PrimaryConfigView()94 PrimaryConfigView::~PrimaryConfigView()
95 {
96     if (m_canvasConfigView) {
97         delete m_canvasConfigView;
98     }
99 
100     if (m_secConfigView) {
101         delete m_secConfigView;
102     }
103 }
104 
init()105 void PrimaryConfigView::init()
106 {
107     SubConfigView::init();
108 
109     QByteArray tempFilePath = "lattedockconfigurationui";
110 
111     auto source = QUrl::fromLocalFile(m_latteView->containment()->corona()->kPackage().filePath(tempFilePath));
112     setSource(source);
113     syncGeometry();
114 }
115 
indicatorUiManager()116 Config::IndicatorUiManager *PrimaryConfigView::indicatorUiManager()
117 {
118     return m_indicatorUiManager;
119 }
120 
setOnActivities(QStringList activities)121 void PrimaryConfigView::setOnActivities(QStringList activities)
122 {
123     m_corona->wm()->setWindowOnActivities(trackedWindowId(), activities);
124 
125     if (m_secConfigView) {
126         m_corona->wm()->setWindowOnActivities(m_secConfigView->trackedWindowId(), activities);
127     }
128 
129     if (m_canvasConfigView) {
130         m_corona->wm()->setWindowOnActivities(m_canvasConfigView->trackedWindowId(), activities);
131     }
132 }
133 
requestActivate()134 void PrimaryConfigView::requestActivate()
135 {
136     if (m_latteView && m_latteView->visibility()) {
137         if (KWindowSystem::isPlatformX11()) {
138             m_latteView->visibility()->setViewOnFrontLayer();
139         } else if (m_shellSurface) {
140             m_corona->wm()->requestActivate(m_latteView->positioner()->trackedWindowId());
141         }
142     }
143 
144     if (m_secConfigView) {
145         m_secConfigView->requestActivate();
146     }
147 
148     SubConfigView::requestActivate();
149 }
150 
showConfigWindow()151 void PrimaryConfigView::showConfigWindow()
152 {
153     if (isVisible()) {
154         return;
155     }
156 
157     if (m_latteView && m_latteView->containment()) {
158         m_latteView->containment()->setUserConfiguring(true);
159     }
160 
161     showAfter(PRIMARYWINDOWINTERVAL);
162     showCanvasWindow();
163     showSecondaryWindow();
164 }
165 
hideConfigWindow()166 void PrimaryConfigView::hideConfigWindow()
167 {
168     if (m_shellSurface) {
169         //!NOTE: Avoid crash in wayland environment with qt5.9
170         close();
171     } else {
172         hide();
173     }
174 
175     hideCanvasWindow();
176     hideSecondaryWindow();
177 }
178 
showCanvasWindow()179 void PrimaryConfigView::showCanvasWindow()
180 {
181     if (!m_canvasConfigView) {
182         m_canvasConfigView = new CanvasConfigView(m_latteView, this);
183     }
184 
185     if (m_canvasConfigView && !m_canvasConfigView->isVisible()){
186         m_canvasConfigView->showAfter(CANVASWINDOWINTERVAL);
187     }
188 }
189 
hideCanvasWindow()190 void PrimaryConfigView::hideCanvasWindow()
191 {
192     if (m_canvasConfigView) {
193         m_canvasConfigView->hideConfigWindow();
194     }
195 }
196 
showSecondaryWindow()197 void PrimaryConfigView::showSecondaryWindow()
198 {
199     bool isValidShowing{m_latteView->formFactor() == Plasma::Types::Horizontal && inAdvancedMode()};
200 
201     if (!isValidShowing) {
202         return;
203     }
204 
205     if (!m_secConfigView) {
206         m_secConfigView = new SecondaryConfigView(m_latteView, this);
207     }
208 
209     if (m_secConfigView && !m_secConfigView->isVisible()){
210         m_secConfigView->showAfter(SECONDARYWINDOWINTERVAL);
211     }
212 }
213 
hideSecondaryWindow()214 void PrimaryConfigView::hideSecondaryWindow()
215 {
216     if (m_secConfigView) {
217         m_secConfigView->hideConfigWindow();
218     }
219 }
220 
setParentView(Latte::View * view,const bool & immediate)221 void PrimaryConfigView::setParentView(Latte::View *view, const bool &immediate)
222 {
223     if (m_latteView == view) {
224         return;
225     }
226 
227     if (m_latteView && !immediate) {
228         hideConfigWindow();
229 
230         //!slide-out delay
231         QTimer::singleShot(SLIDEOUTINTERVAL, [this, view]() {
232             initParentView(view);
233             showConfigWindow();
234         });
235     } else {
236         initParentView(view);
237         showConfigWindow();
238     }
239 }
240 
initParentView(Latte::View * view)241 void PrimaryConfigView::initParentView(Latte::View *view)
242 {
243     setIsReady(false);
244 
245     SubConfigView::initParentView(view);
246 
247     viewconnections << connect(m_latteView, &Latte::View::layoutChanged, this, [this]() {
248         if (m_latteView->layout()) {
249             updateAvailableScreenGeometry();
250         }
251     });
252 
253     viewconnections << connect(m_latteView, &Latte::View::editThicknessChanged, this, [this]() {
254         updateAvailableScreenGeometry();
255     });
256 
257     viewconnections << connect(m_latteView, &Latte::View::maxNormalThicknessChanged, this, [this]() {
258         updateAvailableScreenGeometry();
259     });
260 
261     viewconnections << connect(m_latteView, &Latte::View::locationChanged, this, [this]() {
262         updateAvailableScreenGeometry();
263     });
264 
265     viewconnections << connect(m_latteView->positioner(), &Latte::ViewPart::Positioner::currentScreenChanged, this, [this]() {
266         updateAvailableScreenGeometry();
267     });
268 
269     viewconnections << connect(m_corona->universalSettings(), &Latte::UniversalSettings::inAdvancedModeForEditSettingsChanged, m_latteView, &Latte::View::inSettingsAdvancedModeChanged);
270     viewconnections << connect(m_latteView->containment(), &Plasma::Containment::immutabilityChanged, this, &PrimaryConfigView::immutabilityChanged);
271 
272     m_originalByPassWM = m_latteView->byPassWM();
273     m_originalMode = m_latteView->visibility()->mode();
274 
275     updateEnabledBorders();
276     updateAvailableScreenGeometry();
277     syncGeometry();
278 
279     setIsReady(true);
280 
281     if (m_canvasConfigView) {
282         m_canvasConfigView->setParentView(view);
283     }
284 
285     if (m_secConfigView) {
286         m_secConfigView->setParentView(view);
287     }
288 
289     //! inform view about the current settings level
290     emit m_latteView->inSettingsAdvancedModeChanged();
291 }
292 
instantUpdateAvailableScreenGeometry()293 void PrimaryConfigView::instantUpdateAvailableScreenGeometry()
294 {
295     if (!m_latteView || !m_latteView->positioner()) {
296         return;
297     }
298 
299     int currentScrId = m_latteView->positioner()->currentScreenId();
300 
301     QList<Latte::Types::Visibility> ignoreModes{Latte::Types::SidebarOnDemand,Latte::Types::SidebarAutoHide};
302 
303     if (m_latteView->visibility() && m_latteView->visibility()->isSidebar()) {
304         ignoreModes.removeAll(Latte::Types::SidebarOnDemand);
305         ignoreModes.removeAll(Latte::Types::SidebarAutoHide);
306     }
307 
308     QString activityid = m_latteView->layout()->lastUsedActivity();
309 
310     m_availableScreenGeometry = m_corona->availableScreenRectWithCriteria(currentScrId, activityid, ignoreModes, {}, false, true);
311     emit availableScreenGeometryChanged();
312 }
313 
updateAvailableScreenGeometry(View * origin)314 void PrimaryConfigView::updateAvailableScreenGeometry(View *origin)
315 {
316     if (!m_latteView || !m_latteView->layout() || m_latteView == origin) {
317         return;
318     }
319 
320     if (!m_availableScreemGeometryTimer.isActive()) {
321         m_availableScreemGeometryTimer.start();
322     }
323 }
324 
availableScreenGeometry() const325 QRect PrimaryConfigView::availableScreenGeometry() const
326 {
327     return m_availableScreenGeometry;
328 }
329 
geometryWhenVisible() const330 QRect PrimaryConfigView::geometryWhenVisible() const
331 {
332     return m_geometryWhenVisible;
333 }
334 
syncGeometry()335 void PrimaryConfigView::syncGeometry()
336 {
337     if (!m_latteView || !m_latteView->layout() || !m_latteView->containment() || !rootObject()) {
338         return;
339     }
340 
341     const QSize size(rootObject()->width(), rootObject()->height());
342     const auto location = m_latteView->containment()->location();
343     const auto scrGeometry = m_latteView->screenGeometry();
344     const auto availGeometry = m_availableScreenGeometry;
345     const auto canvasGeometry = m_latteView->positioner()->canvasGeometry();
346 
347     int canvasThickness = m_latteView->formFactor() == Plasma::Types::Vertical ? canvasGeometry.width() : canvasGeometry.height();
348 
349     QPoint position{0, 0};
350 
351     int xPos{0};
352     int yPos{0};
353 
354     switch (m_latteView->formFactor()) {
355     case Plasma::Types::Horizontal: {
356         if (inAdvancedMode()) {
357             if (qApp->isLeftToRight()) {
358                 xPos = availGeometry.x() + availGeometry.width() - size.width();
359             } else {
360                 xPos = availGeometry.x();
361             }
362         } else {
363             xPos = scrGeometry.center().x() - size.width() / 2;
364         }
365 
366         if (location == Plasma::Types::TopEdge) {
367             yPos = scrGeometry.y() + canvasThickness;
368         } else if (location == Plasma::Types::BottomEdge) {
369             yPos = scrGeometry.y() + scrGeometry.height() - canvasThickness - size.height();
370         }
371     }
372         break;
373 
374     case Plasma::Types::Vertical: {
375         if (location == Plasma::Types::LeftEdge) {
376             xPos = scrGeometry.x() + canvasThickness;
377             yPos =  availGeometry.y() + (availGeometry.height() - size.height())/2;
378         } else if (location == Plasma::Types::RightEdge) {
379             xPos = scrGeometry.x() + scrGeometry.width() - canvasThickness - size.width();
380             yPos =  availGeometry.y() + (availGeometry.height() - size.height())/2;
381         }
382     }
383         break;
384 
385     default:
386         qWarning() << "no sync geometry, wrong formFactor";
387         break;
388     }
389 
390     position = {xPos, yPos};
391 
392     updateEnabledBorders();
393 
394     auto geometry = QRect(position.x(), position.y(), size.width(), size.height());
395 
396     QRect winGeometry(x(), y(), width(), height());
397 
398     if (m_geometryWhenVisible == geometry && winGeometry == geometry) {
399         return;
400     }
401 
402     m_geometryWhenVisible = geometry;
403 
404     setPosition(position);
405 
406     if (m_shellSurface) {
407         m_shellSurface->setPosition(position);
408     }
409 
410     setMaximumSize(size);
411     setMinimumSize(size);
412     resize(size);
413 
414     emit m_latteView->configWindowGeometryChanged();
415 }
416 
showEvent(QShowEvent * ev)417 void PrimaryConfigView::showEvent(QShowEvent *ev)
418 {
419     updateAvailableScreenGeometry();
420 
421     if (m_shellSurface) {
422         //! under wayland it needs to be set again after its hiding
423         m_shellSurface->setPosition(m_geometryWhenVisible.topLeft());
424     }
425 
426     SubConfigView::showEvent(ev);
427 
428     if (!m_latteView) {
429         return;
430     }
431 
432     setFlags(wFlags());
433     m_corona->wm()->setViewExtraFlags(this, false, Latte::Types::NormalWindow);
434 
435     syncGeometry();
436 
437     m_screenSyncTimer.start();
438     QTimer::singleShot(400, this, &PrimaryConfigView::syncGeometry);
439 
440     updateShowInlineProperties();
441 
442     showCanvasWindow();
443 
444     emit showSignal();
445 
446     if (m_latteView && m_latteView->layout()) {
447         m_latteView->layout()->setLastConfigViewFor(m_latteView);
448     }
449 }
450 
hideEvent(QHideEvent * ev)451 void PrimaryConfigView::hideEvent(QHideEvent *ev)
452 {
453     if (!m_latteView) {
454         return;
455     }
456 
457     if (m_latteView->containment()) {
458         m_latteView->containment()->setUserConfiguring(false);
459     }
460 
461     const auto mode = m_latteView->visibility()->mode();
462 
463     if ((mode == Types::AlwaysVisible || mode == Types::WindowsGoBelow)
464             && !(m_originalMode == Types::AlwaysVisible || m_originalMode == Types::WindowsGoBelow)) {
465         //! mode changed to AlwaysVisible OR WindowsGoBelow FROM Dodge mode
466         if (m_originalByPassWM) {
467             //! if original by pass is active
468             m_latteView->layout()->recreateView(m_latteView->containment());
469         }
470     } else if (m_latteView->byPassWM() != m_originalByPassWM) {
471         m_latteView->layout()->recreateView(m_latteView->containment());
472     }
473 
474     setVisible(false);
475 }
476 
hasFocus() const477 bool PrimaryConfigView::hasFocus() const
478 {
479     bool primaryHasHocus{isActive()};
480     bool secHasFocus{m_secConfigView && m_secConfigView->isActive()};
481     bool canvasHasFocus{m_canvasConfigView && m_canvasConfigView->isActive()};
482     bool viewHasFocus{m_latteView && (m_latteView->containsMouse() || m_latteView->alternativesIsShown())};
483 
484     return (m_blockFocusLost || viewHasFocus || primaryHasHocus || secHasFocus || canvasHasFocus);
485 }
486 
focusOutEvent(QFocusEvent * ev)487 void PrimaryConfigView::focusOutEvent(QFocusEvent *ev)
488 {
489     Q_UNUSED(ev);
490 
491     if (!m_latteView) {
492         return;
493     }
494 
495     const auto *focusWindow = qGuiApp->focusWindow();
496 
497     if (focusWindow && (focusWindow->flags().testFlag(Qt::Popup)
498                                 || focusWindow->flags().testFlag(Qt::ToolTip))) {
499         return;
500     }
501 
502     if (!hasFocus()) {
503         hideConfigWindow();
504     }
505 }
506 
immutabilityChanged(Plasma::Types::ImmutabilityType type)507 void PrimaryConfigView::immutabilityChanged(Plasma::Types::ImmutabilityType type)
508 {
509     if (type != Plasma::Types::Mutable && isVisible()) {
510         hideConfigWindow();
511     }
512 }
513 
isReady() const514 bool PrimaryConfigView::isReady() const
515 {
516     return m_isReady;
517 }
518 
setIsReady(bool ready)519 void PrimaryConfigView::setIsReady(bool ready)
520 {
521     if (m_isReady == ready) {
522         return;
523     }
524 
525     m_isReady = ready;
526     emit isReadyChanged();
527 }
528 
529 
sticker() const530 bool PrimaryConfigView::sticker() const
531 {
532     return m_blockFocusLost;
533 }
534 
setSticker(bool blockFocusLost)535 void PrimaryConfigView::setSticker(bool blockFocusLost)
536 {
537     if (m_blockFocusLost == blockFocusLost)
538         return;
539 
540     m_blockFocusLost = blockFocusLost;
541 }
542 
showInlineProperties() const543 bool PrimaryConfigView::showInlineProperties() const
544 {
545     return m_showInlineProperties;
546 }
setShowInlineProperties(bool show)547 void PrimaryConfigView::setShowInlineProperties(bool show)
548 {
549     if (m_showInlineProperties == show) {
550         return;
551     }
552 
553     m_showInlineProperties = show;
554     emit showInlinePropertiesChanged();
555 }
556 
updateShowInlineProperties()557 void PrimaryConfigView::updateShowInlineProperties()
558 {
559     if (!m_latteView) {
560         return;
561     }
562 
563     bool showSecWindow{false};
564     bool advancedApprovedSecWindow{false};
565 
566     if (inAdvancedMode() && m_latteView->formFactor() != Plasma::Types::Vertical) {
567         showSecWindow = true;
568         advancedApprovedSecWindow = true;
569     }
570 
571     //! consider screen geometry for showing or not the secondary window
572     if (showSecWindow && !geometryWhenVisible().isNull()) {
573         if (m_secConfigView && m_secConfigView->geometryWhenVisible().intersects(geometryWhenVisible())) {
574             showSecWindow = false;
575         } else if (advancedApprovedSecWindow) {
576             showSecWindow = true;
577         }
578     }
579 
580     if (showSecWindow) {
581         showSecondaryWindow();
582 
583        // QTimer::singleShot(150, m_secConfigView, SLOT(show()));
584         setShowInlineProperties(false);
585     } else {
586         hideSecondaryWindow();
587         setShowInlineProperties(true);
588     }
589 
590     // qDebug() << " showSecWindow:" << showSecWindow << " _ " << " inline:"<< !showSecWindow;
591 }
592 
inAdvancedMode() const593 bool PrimaryConfigView::inAdvancedMode() const
594 {
595     return m_corona->universalSettings()->inAdvancedModeForEditSettings();
596 }
597 
598 //!BEGIN borders
updateEnabledBorders()599 void PrimaryConfigView::updateEnabledBorders()
600 {
601     if (!this->screen()) {
602         return;
603     }
604 
605     Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders;
606 
607     switch (m_latteView->location()) {
608     case Plasma::Types::TopEdge:
609         borders &= m_inReverse ? ~Plasma::FrameSvg::BottomBorder : ~Plasma::FrameSvg::TopBorder;
610         break;
611 
612     case Plasma::Types::LeftEdge:
613         borders &= ~Plasma::FrameSvg::LeftBorder;
614         break;
615 
616     case Plasma::Types::RightEdge:
617         borders &= ~Plasma::FrameSvg::RightBorder;
618         break;
619 
620     case Plasma::Types::BottomEdge:
621         borders &= m_inReverse ? ~Plasma::FrameSvg::TopBorder : ~Plasma::FrameSvg::BottomBorder;
622         break;
623 
624     default:
625         break;
626     }
627 
628     if (m_enabledBorders != borders) {
629         m_enabledBorders = borders;
630 
631         m_corona->dialogShadows()->addWindow(this, m_enabledBorders);
632 
633         emit enabledBordersChanged();
634     }
635 }
636 //!END borders
637 
updateEffects()638 void PrimaryConfigView::updateEffects()
639 {
640     //! Don't apply any effect before the wayland surface is created under wayland
641     //! https://bugs.kde.org/show_bug.cgi?id=392890
642     if (KWindowSystem::isPlatformWayland() && !m_shellSurface) {
643         return;
644     }
645 
646     if (!m_background) {
647         m_background = new Plasma::FrameSvg(this);
648     }
649 
650     if (m_background->imagePath() != "dialogs/background") {
651         m_background->setImagePath(QStringLiteral("dialogs/background"));
652     }
653 
654     m_background->setEnabledBorders(m_enabledBorders);
655     m_background->resizeFrame(size());
656 
657     QRegion mask = m_background->mask();
658 
659     QRegion fixedMask = mask.isNull() ? QRegion(QRect(0,0,width(),height())) : mask;
660 
661     if (!fixedMask.isEmpty()) {
662         setMask(fixedMask);
663     } else {
664         setMask(QRegion());
665     }
666 
667     if (KWindowSystem::compositingActive()) {
668         KWindowEffects::enableBlurBehind(winId(), true, fixedMask);
669     } else {
670         KWindowEffects::enableBlurBehind(winId(), false);
671     }
672 }
673 
674 }
675 }
676 
677