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