File indexing completed on 2024-05-05 09:56:38
0001 /* 0002 SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include <config-plasma.h> 0008 0009 #include "autohidescreenedge.h" 0010 #include "debug.h" 0011 #include "panelconfigview.h" 0012 #include "panelshadows_p.h" 0013 #include "panelview.h" 0014 #include "shellcorona.h" 0015 0016 #include <QAction> 0017 #include <QApplication> 0018 #include <QDebug> 0019 #include <QGuiApplication> 0020 #include <QQmlContext> 0021 #include <QQmlEngine> 0022 #include <QRegularExpression> 0023 #include <QScreen> 0024 0025 #include <KX11Extras> 0026 #include <kwindoweffects.h> 0027 #include <kwindowsystem.h> 0028 0029 #include <Plasma/Containment> 0030 #include <PlasmaQuick/AppletQuickItem> 0031 0032 #include <LayerShellQt/Window> 0033 0034 #if HAVE_X11 0035 #include <NETWM> 0036 #include <private/qtx11extras_p.h> 0037 #include <qpa/qplatformwindow_p.h> 0038 #endif 0039 #include <chrono> 0040 0041 using namespace std::chrono_literals; 0042 using namespace Qt::StringLiterals; 0043 0044 static const int MINSIZE = 10; 0045 0046 PanelView::PanelView(ShellCorona *corona, QScreen *targetScreen, QWindow *parent) 0047 : PlasmaQuick::ContainmentView(corona, parent) 0048 , m_offset(0) 0049 , m_maxLength(0) 0050 , m_minLength(0) 0051 , m_contentLength(0) 0052 , m_distance(0) 0053 , m_thickness(30) 0054 , m_minDrawingWidth(0) 0055 , m_minDrawingHeight(0) 0056 , m_initCompleted(false) 0057 , m_floating(false) 0058 , m_alignment(Qt::AlignCenter) 0059 , m_corona(corona) 0060 , m_visibilityMode(NormalPanel) 0061 , m_opacityMode(Adaptive) 0062 , m_lengthMode(FillAvailable) 0063 , m_backgroundHints(Plasma::Types::StandardBackground) 0064 { 0065 if (KWindowSystem::isPlatformWayland()) { 0066 m_layerWindow = LayerShellQt::Window::get(this); 0067 m_layerWindow->setLayer(LayerShellQt::Window::LayerTop); 0068 m_layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityNone); 0069 m_layerWindow->setScope(QStringLiteral("dock")); 0070 m_layerWindow->setCloseOnDismissed(false); 0071 } 0072 if (targetScreen) { 0073 setPosition(targetScreen->geometry().center()); 0074 setScreenToFollow(targetScreen); 0075 setScreen(targetScreen); 0076 } 0077 setResizeMode(QuickViewSharedEngine::SizeRootObjectToView); 0078 setColor(QColor(Qt::transparent)); 0079 setFlags(Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); 0080 updateAdaptiveOpacityEnabled(); 0081 0082 connect(&m_theme, &Plasma::Theme::themeChanged, this, &PanelView::updateMask); 0083 connect(&m_theme, &Plasma::Theme::themeChanged, this, &PanelView::updateAdaptiveOpacityEnabled); 0084 connect(this, &PanelView::backgroundHintsChanged, this, &PanelView::updateMask); 0085 connect(this, &PanelView::backgroundHintsChanged, this, &PanelView::updateEnabledBorders); 0086 // TODO: add finished/componentComplete signal to QuickViewSharedEngine, 0087 // so we exactly know when rootobject is available 0088 connect(this, &QuickViewSharedEngine::statusChanged, this, &PanelView::handleQmlStatusChange); 0089 0090 m_unhideTimer.setSingleShot(true); 0091 m_unhideTimer.setInterval(500ms); 0092 connect(&m_unhideTimer, &QTimer::timeout, this, &PanelView::restoreAutoHide); 0093 0094 m_lastScreen = targetScreen; 0095 connect(this, &PanelView::locationChanged, this, &PanelView::restore); 0096 connect(this, &PanelView::containmentChanged, this, &PanelView::refreshContainment); 0097 0098 // FEATURE 352476: cancel focus on the panel when clicking outside 0099 connect(this, &PanelView::activeFocusItemChanged, this, [this] { 0100 if (containment()->status() == Plasma::Types::AcceptingInputStatus && !activeFocusItem()) { 0101 // BUG 454729: avoid switching to PassiveStatus in keyboard navigation 0102 containment()->setStatus(Plasma::Types::ActiveStatus); 0103 } 0104 }); 0105 0106 if (!m_corona->kPackage().isValid()) { 0107 qCWarning(PLASMASHELL) << "Invalid home screen package"; 0108 } 0109 0110 m_strutsTimer.setSingleShot(true); 0111 connect(&m_strutsTimer, &QTimer::timeout, this, &PanelView::updateExclusiveZone); 0112 0113 // Register enums 0114 qmlRegisterUncreatableMetaObject(PanelView::staticMetaObject, "org.kde.plasma.shell.panel", 0, 1, "Global", QStringLiteral("Error: only enums")); 0115 0116 qmlRegisterAnonymousType<QScreen>("", 1); 0117 rootContext()->setContextProperty(QStringLiteral("panel"), this); 0118 setSource(m_corona->kPackage().fileUrl("views", QStringLiteral("Panel.qml"))); 0119 updatePadding(); 0120 updateFloating(); 0121 updateTouchingWindow(); 0122 } 0123 0124 PanelView::~PanelView() 0125 { 0126 if (containment()) { 0127 m_corona->requestApplicationConfigSync(); 0128 } 0129 } 0130 0131 KConfigGroup PanelView::panelConfig(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen) 0132 { 0133 if (!containment || !screen) { 0134 return KConfigGroup(); 0135 } 0136 KConfigGroup views(corona->applicationConfig(), u"PlasmaViews"_s); 0137 views = KConfigGroup(&views, QStringLiteral("Panel %1").arg(containment->id())); 0138 0139 if (containment->formFactor() == Plasma::Types::Vertical) { 0140 return KConfigGroup(&views, QStringLiteral("Vertical") + QString::number(screen->size().height())); 0141 // treat everything else as horizontal 0142 } else { 0143 return KConfigGroup(&views, QStringLiteral("Horizontal") + QString::number(screen->size().width())); 0144 } 0145 } 0146 0147 KConfigGroup PanelView::panelConfigDefaults(ShellCorona *corona, Plasma::Containment *containment, QScreen *screen) 0148 { 0149 if (!containment || !screen) { 0150 return KConfigGroup(); 0151 } 0152 0153 KConfigGroup views(corona->applicationConfig(), u"PlasmaViews"_s); 0154 views = KConfigGroup(&views, QStringLiteral("Panel %1").arg(containment->id())); 0155 0156 return KConfigGroup(&views, QStringLiteral("Defaults")); 0157 } 0158 0159 int PanelView::readConfigValueWithFallBack(const QString &key, int defaultValue) 0160 { 0161 int value = config().readEntry(key, configDefaults().readEntry(key, defaultValue)); 0162 return value; 0163 } 0164 0165 KConfigGroup PanelView::config() const 0166 { 0167 return panelConfig(m_corona, containment(), m_screenToFollow); 0168 } 0169 0170 KConfigGroup PanelView::configDefaults() const 0171 { 0172 return panelConfigDefaults(m_corona, containment(), m_screenToFollow); 0173 } 0174 0175 Q_INVOKABLE QString PanelView::fileFromPackage(const QString &key, const QString &fileName) 0176 { 0177 return corona()->kPackage().filePath(key.toUtf8(), fileName); 0178 } 0179 0180 void PanelView::maximize() 0181 { 0182 int length; 0183 if (containment()->formFactor() == Plasma::Types::Vertical) { 0184 length = m_screenToFollow->size().height(); 0185 } else { 0186 length = m_screenToFollow->size().width(); 0187 } 0188 setOffset(0); 0189 setMinimumLength(length); 0190 setMaximumLength(length); 0191 } 0192 0193 Qt::Alignment PanelView::alignment() const 0194 { 0195 return m_alignment; 0196 } 0197 0198 void PanelView::setAlignment(Qt::Alignment alignment) 0199 { 0200 if (m_alignment == alignment) { 0201 return; 0202 } 0203 0204 m_alignment = alignment; 0205 // alignment is not resolution dependent, doesn't save to Defaults 0206 config().parent().writeEntry("alignment", (int)m_alignment); 0207 Q_EMIT alignmentChanged(); 0208 positionPanel(); 0209 } 0210 0211 int PanelView::offset() const 0212 { 0213 return m_offset; 0214 } 0215 0216 void PanelView::setOffset(int offset) 0217 { 0218 if (m_offset == offset) { 0219 return; 0220 } 0221 0222 if (formFactor() == Plasma::Types::Vertical) { 0223 if (offset + m_maxLength > m_screenToFollow->size().height()) { 0224 setMaximumLength(-m_offset + m_screenToFollow->size().height()); 0225 } 0226 } else { 0227 if (offset + m_maxLength > m_screenToFollow->size().width()) { 0228 setMaximumLength(-m_offset + m_screenToFollow->size().width()); 0229 } 0230 } 0231 0232 m_offset = offset; 0233 config().writeEntry("offset", m_offset); 0234 configDefaults().writeEntry("offset", m_offset); 0235 positionPanel(); 0236 Q_EMIT offsetChanged(); 0237 m_corona->requestApplicationConfigSync(); 0238 Q_EMIT m_corona->availableScreenRegionChanged(containment()->screen()); 0239 } 0240 0241 int PanelView::thickness() const 0242 { 0243 return m_thickness; 0244 } 0245 0246 int PanelView::totalThickness() const 0247 { 0248 switch (containment()->location()) { 0249 case Plasma::Types::TopEdge: 0250 return thickness() + m_topFloatingPadding + m_bottomFloatingPadding; 0251 case Plasma::Types::LeftEdge: 0252 return thickness() + m_leftFloatingPadding + m_rightFloatingPadding; 0253 case Plasma::Types::RightEdge: 0254 return thickness() + m_rightFloatingPadding + m_leftFloatingPadding; 0255 case Plasma::Types::BottomEdge: 0256 return thickness() + m_bottomFloatingPadding + m_topFloatingPadding; 0257 default: 0258 return thickness(); 0259 } 0260 } 0261 0262 void PanelView::setThickness(int value) 0263 { 0264 if (value < minThickness()) { 0265 value = minThickness(); 0266 } 0267 if (value == thickness()) { 0268 return; 0269 } 0270 0271 m_thickness = value; 0272 Q_EMIT thicknessChanged(); 0273 0274 // For thickness, always use the default thickness value for horizontal/vertical panel 0275 configDefaults().writeEntry("thickness", value); 0276 m_corona->requestApplicationConfigSync(); 0277 resizePanel(); 0278 } 0279 0280 int PanelView::length() const 0281 { 0282 return qMax(1, m_contentLength); 0283 } 0284 0285 void PanelView::setLength(int value) 0286 { 0287 if (value == m_contentLength) { 0288 return; 0289 } 0290 0291 m_contentLength = value; 0292 0293 resizePanel(); 0294 } 0295 0296 int PanelView::maximumLength() const 0297 { 0298 return m_maxLength; 0299 } 0300 0301 void PanelView::setMaximumLength(int length) 0302 { 0303 if (length == m_maxLength) { 0304 return; 0305 } 0306 0307 if (m_minLength > length) { 0308 setMinimumLength(length); 0309 } 0310 0311 config().writeEntry("maxLength", length); 0312 configDefaults().writeEntry("maxLength", length); 0313 m_maxLength = length; 0314 Q_EMIT maximumLengthChanged(); 0315 m_corona->requestApplicationConfigSync(); 0316 0317 resizePanel(); 0318 } 0319 0320 int PanelView::minimumLength() const 0321 { 0322 return m_minLength; 0323 } 0324 0325 void PanelView::setMinimumLength(int length) 0326 { 0327 if (length == m_minLength) { 0328 return; 0329 } 0330 0331 if (m_maxLength < length) { 0332 setMaximumLength(length); 0333 } 0334 0335 config().writeEntry("minLength", length); 0336 configDefaults().writeEntry("minLength", length); 0337 m_minLength = length; 0338 Q_EMIT minimumLengthChanged(); 0339 m_corona->requestApplicationConfigSync(); 0340 0341 resizePanel(); 0342 } 0343 0344 int PanelView::distance() const 0345 { 0346 return m_distance; 0347 } 0348 0349 void PanelView::setDistance(int dist) 0350 { 0351 if (m_distance == dist) { 0352 return; 0353 } 0354 0355 m_distance = dist; 0356 Q_EMIT distanceChanged(); 0357 positionPanel(); 0358 } 0359 0360 bool PanelView::floating() const 0361 { 0362 return m_floating; 0363 } 0364 0365 void PanelView::setFloating(bool floating) 0366 { 0367 if (m_floating == floating) { 0368 return; 0369 } 0370 0371 m_floating = floating; 0372 if (config().isValid() && config().parent().isValid()) { 0373 config().parent().writeEntry("floating", (int)floating); 0374 m_corona->requestApplicationConfigSync(); 0375 } 0376 Q_EMIT floatingChanged(); 0377 0378 updateFloating(); 0379 updateEnabledBorders(); 0380 updateMask(); 0381 } 0382 0383 int PanelView::minThickness() const 0384 { 0385 if (formFactor() == Plasma::Types::Vertical) { 0386 return m_minDrawingWidth; 0387 } else if (formFactor() == Plasma::Types::Horizontal) { 0388 return m_minDrawingHeight; 0389 } 0390 return 0; 0391 } 0392 0393 Plasma::Types::BackgroundHints PanelView::backgroundHints() const 0394 { 0395 return m_backgroundHints; 0396 } 0397 0398 void PanelView::setBackgroundHints(Plasma::Types::BackgroundHints hint) 0399 { 0400 if (m_backgroundHints == hint) { 0401 return; 0402 } 0403 0404 m_backgroundHints = hint; 0405 0406 Q_EMIT backgroundHintsChanged(); 0407 } 0408 0409 KSvg::FrameSvg::EnabledBorders PanelView::enabledBorders() const 0410 { 0411 return m_enabledBorders; 0412 } 0413 0414 void PanelView::setVisibilityMode(PanelView::VisibilityMode mode) 0415 { 0416 if (m_visibilityMode == mode) { 0417 return; 0418 } 0419 0420 m_visibilityMode = mode; 0421 0422 disconnect(containment(), &Plasma::Applet::activated, this, &PanelView::showTemporarily); 0423 if (edgeActivated()) { 0424 connect(containment(), &Plasma::Applet::activated, this, &PanelView::showTemporarily); 0425 } 0426 0427 if (config().isValid() && config().parent().isValid()) { 0428 // panelVisibility is not resolution dependent, don't write to Defaults 0429 config().parent().writeEntry("panelVisibility", (int)mode); 0430 m_corona->requestApplicationConfigSync(); 0431 } 0432 0433 updateExclusiveZone(); 0434 0435 Q_EMIT visibilityModeChanged(); 0436 0437 restoreAutoHide(); 0438 } 0439 0440 PanelView::VisibilityMode PanelView::visibilityMode() const 0441 { 0442 return m_visibilityMode; 0443 } 0444 0445 PanelView::OpacityMode PanelView::opacityMode() const 0446 { 0447 if (!m_theme.adaptiveTransparencyEnabled()) { 0448 return PanelView::Translucent; 0449 } 0450 return m_opacityMode; 0451 } 0452 0453 PanelView::LengthMode PanelView::lengthMode() const 0454 { 0455 return m_lengthMode; 0456 } 0457 0458 bool PanelView::adaptiveOpacityEnabled() 0459 { 0460 return m_theme.adaptiveTransparencyEnabled(); 0461 } 0462 0463 void PanelView::setOpacityMode(PanelView::OpacityMode mode) 0464 { 0465 if (m_opacityMode != mode) { 0466 m_opacityMode = mode; 0467 if (config().isValid() && config().parent().isValid()) { 0468 config().parent().writeEntry("panelOpacity", (int)mode); 0469 m_corona->requestApplicationConfigSync(); 0470 } 0471 Q_EMIT opacityModeChanged(); 0472 } 0473 } 0474 0475 void PanelView::setLengthMode(PanelView::LengthMode mode) 0476 { 0477 if (m_lengthMode != mode) { 0478 m_lengthMode = mode; 0479 if (config().isValid() && config().parent().isValid()) { 0480 config().parent().writeEntry("panelLengthMode", (int)mode); 0481 m_corona->requestApplicationConfigSync(); 0482 } 0483 Q_EMIT lengthModeChanged(); 0484 positionPanel(); 0485 Q_EMIT m_corona->availableScreenRegionChanged(containment()->screen()); 0486 resizePanel(); 0487 } 0488 } 0489 0490 void PanelView::updateAdaptiveOpacityEnabled() 0491 { 0492 Q_EMIT opacityModeChanged(); 0493 Q_EMIT adaptiveOpacityEnabledChanged(); 0494 } 0495 0496 void PanelView::positionPanel() 0497 { 0498 if (!containment()) { 0499 return; 0500 } 0501 0502 if (!m_initCompleted) { 0503 return; 0504 } 0505 0506 KWindowEffects::SlideFromLocation slideLocation = KWindowEffects::NoEdge; 0507 0508 switch (containment()->location()) { 0509 case Plasma::Types::TopEdge: 0510 containment()->setFormFactor(Plasma::Types::Horizontal); 0511 slideLocation = KWindowEffects::TopEdge; 0512 break; 0513 0514 case Plasma::Types::LeftEdge: 0515 containment()->setFormFactor(Plasma::Types::Vertical); 0516 slideLocation = KWindowEffects::LeftEdge; 0517 break; 0518 0519 case Plasma::Types::RightEdge: 0520 containment()->setFormFactor(Plasma::Types::Vertical); 0521 slideLocation = KWindowEffects::RightEdge; 0522 break; 0523 0524 case Plasma::Types::BottomEdge: 0525 default: 0526 containment()->setFormFactor(Plasma::Types::Horizontal); 0527 slideLocation = KWindowEffects::BottomEdge; 0528 break; 0529 } 0530 0531 if (m_layerWindow) { 0532 QMargins margins; 0533 LayerShellQt::Window::Anchors anchors; 0534 LayerShellQt::Window::Anchor edge; 0535 0536 switch (containment()->location()) { 0537 case Plasma::Types::TopEdge: 0538 anchors.setFlag(LayerShellQt::Window::AnchorTop); 0539 margins.setTop((-m_topFloatingPadding - m_bottomFloatingPadding) * (1 - m_floatingness)); 0540 edge = LayerShellQt::Window::AnchorTop; 0541 break; 0542 case Plasma::Types::LeftEdge: 0543 anchors.setFlag(LayerShellQt::Window::AnchorLeft); 0544 margins.setLeft((-m_rightFloatingPadding - m_leftFloatingPadding) * (1 - m_floatingness)); 0545 edge = LayerShellQt::Window::AnchorLeft; 0546 break; 0547 case Plasma::Types::RightEdge: 0548 anchors.setFlag(LayerShellQt::Window::AnchorRight); 0549 margins.setRight((-m_rightFloatingPadding - m_leftFloatingPadding) * (1 - m_floatingness)); 0550 edge = LayerShellQt::Window::AnchorRight; 0551 break; 0552 case Plasma::Types::BottomEdge: 0553 default: 0554 anchors.setFlag(LayerShellQt::Window::AnchorBottom); 0555 margins.setBottom((-m_topFloatingPadding - m_bottomFloatingPadding) * (1 - m_floatingness)); 0556 edge = LayerShellQt::Window::AnchorBottom; 0557 break; 0558 } 0559 0560 if (formFactor() == Plasma::Types::Horizontal) { 0561 switch (m_alignment) { 0562 case Qt::AlignLeft: 0563 anchors.setFlag(LayerShellQt::Window::AnchorLeft); 0564 if (m_lengthMode == PanelView::LengthMode::Custom) { 0565 margins.setLeft(margins.left() + m_offset); 0566 } 0567 break; 0568 case Qt::AlignCenter: 0569 break; 0570 case Qt::AlignRight: 0571 anchors.setFlag(LayerShellQt::Window::AnchorRight); 0572 if (m_lengthMode == PanelView::LengthMode::Custom) { 0573 margins.setRight(margins.right() + m_offset); 0574 } 0575 break; 0576 } 0577 if (m_lengthMode == PanelView::LengthMode::FillAvailable) { 0578 anchors.setFlag(LayerShellQt::Window::AnchorLeft); 0579 anchors.setFlag(LayerShellQt::Window::AnchorRight); 0580 } 0581 } else { 0582 switch (m_alignment) { 0583 case Qt::AlignLeft: 0584 anchors.setFlag(LayerShellQt::Window::AnchorTop); 0585 if (m_lengthMode == PanelView::LengthMode::Custom) { 0586 margins.setTop(margins.top() + m_offset); 0587 } 0588 break; 0589 case Qt::AlignCenter: 0590 break; 0591 case Qt::AlignRight: 0592 anchors.setFlag(LayerShellQt::Window::AnchorBottom); 0593 if (m_lengthMode == PanelView::LengthMode::Custom) { 0594 margins.setBottom(margins.bottom() + m_offset); 0595 } 0596 break; 0597 } 0598 if (m_lengthMode == PanelView::LengthMode::FillAvailable) { 0599 anchors.setFlag(LayerShellQt::Window::AnchorTop); 0600 anchors.setFlag(LayerShellQt::Window::AnchorBottom); 0601 } 0602 } 0603 0604 m_layerWindow->setAnchors(anchors); 0605 m_layerWindow->setExclusiveEdge(edge); 0606 m_layerWindow->setMargins(margins); 0607 0608 updateMask(); 0609 requestUpdate(); 0610 } 0611 0612 // TODO: Make it X11-specific. It's still relevant on wayland because of popup positioning. 0613 const QPoint pos = geometryByDistance(m_distance).topLeft(); 0614 setPosition(pos); 0615 Q_EMIT geometryChanged(); 0616 0617 KWindowEffects::slideWindow(this, slideLocation, -1); 0618 } 0619 0620 QRect PanelView::geometryByDistance(int distance) const 0621 { 0622 if (!containment() || !m_screenToFollow) { 0623 return QRect(); 0624 } 0625 0626 QScreen *s = m_screenToFollow; 0627 const QRect screenGeometry = s->geometry(); 0628 QRect r(QPoint(0, 0), formFactor() == Plasma::Types::Vertical ? QSize(totalThickness(), height()) : QSize(width(), totalThickness())); 0629 0630 int currentOffset = 0; 0631 if (m_lengthMode == PanelView::LengthMode::Custom) { 0632 currentOffset = m_offset; 0633 } 0634 0635 if (formFactor() == Plasma::Types::Horizontal) { 0636 switch (m_alignment) { 0637 case Qt::AlignCenter: 0638 r.moveCenter(screenGeometry.center() + QPoint(currentOffset, 0)); 0639 break; 0640 case Qt::AlignRight: 0641 r.moveRight(screenGeometry.right() - currentOffset); 0642 break; 0643 case Qt::AlignLeft: 0644 default: 0645 r.moveLeft(screenGeometry.left() + currentOffset); 0646 } 0647 } else { 0648 switch (m_alignment) { 0649 case Qt::AlignCenter: 0650 r.moveCenter(screenGeometry.center() + QPoint(0, currentOffset)); 0651 break; 0652 case Qt::AlignRight: 0653 r.moveBottom(screenGeometry.bottom() - currentOffset); 0654 break; 0655 case Qt::AlignLeft: 0656 default: 0657 r.moveTop(screenGeometry.top() + currentOffset); 0658 } 0659 } 0660 0661 switch (containment()->location()) { 0662 case Plasma::Types::TopEdge: 0663 r.moveTop(screenGeometry.top() + distance + (-m_topFloatingPadding - m_bottomFloatingPadding) * (1 - m_floatingness)); 0664 break; 0665 0666 case Plasma::Types::LeftEdge: 0667 r.moveLeft(screenGeometry.left() + distance + (-m_rightFloatingPadding - m_leftFloatingPadding) * (1 - m_floatingness)); 0668 break; 0669 0670 case Plasma::Types::RightEdge: 0671 r.moveRight(screenGeometry.right() - distance - (-m_rightFloatingPadding - m_leftFloatingPadding) * (1 - m_floatingness)); 0672 break; 0673 0674 case Plasma::Types::BottomEdge: 0675 default: 0676 r.moveBottom(screenGeometry.bottom() - distance - (-m_topFloatingPadding - m_bottomFloatingPadding) * (1 - m_floatingness)); 0677 } 0678 return r; 0679 } 0680 0681 void PanelView::resizePanel() 0682 { 0683 if (!m_initCompleted) { 0684 return; 0685 } 0686 0687 // On Wayland when a screen is disconnected and the panel is migrating to a newscreen 0688 // it can happen a moment where the qscreen gets destroyed before it gets reassigned 0689 // to the new screen 0690 if (!m_screenToFollow) { 0691 return; 0692 } 0693 0694 QSize s; 0695 if (m_lengthMode == PanelView::LengthMode::FillAvailable) { 0696 if (formFactor() == Plasma::Types::Vertical) { 0697 s = QSize(totalThickness(), m_screenToFollow->size().height()); 0698 } else { 0699 s = QSize(m_screenToFollow->size().width(), totalThickness()); 0700 } 0701 setMinimumSize(QSize(totalThickness(), totalThickness())); 0702 setMaximumSize(s); 0703 resize(s); 0704 return; 0705 } else if (m_lengthMode == PanelView::LengthMode::FitContent) { 0706 if (formFactor() == Plasma::Types::Vertical) { 0707 s = QSize( 0708 totalThickness(), 0709 std::max(totalThickness(), std::min(m_screenToFollow->size().height(), m_contentLength + m_topFloatingPadding + m_bottomFloatingPadding))); 0710 } else { 0711 s = QSize(std::max(totalThickness(), std::min(m_screenToFollow->size().width(), m_contentLength + m_leftFloatingPadding + m_rightFloatingPadding)), 0712 totalThickness()); 0713 } 0714 setMinimumSize(QSize(10, 10)); 0715 setMaximumSize(s); 0716 resize(s); 0717 return; 0718 } 0719 0720 QSize targetSize; 0721 QSize targetMinSize; 0722 QSize targetMaxSize; 0723 bool minFirst = false; 0724 0725 if (formFactor() == Plasma::Types::Vertical) { 0726 const int minSize = qMax(MINSIZE, m_minLength); 0727 int maxSize = qMin(m_maxLength, m_screenToFollow->size().height() - m_offset); 0728 maxSize = qMax(minSize, maxSize); 0729 targetMinSize = QSize(totalThickness(), minSize); 0730 targetMaxSize = QSize(totalThickness(), maxSize); 0731 targetSize = QSize(totalThickness(), std::clamp(m_contentLength + m_topFloatingPadding + m_bottomFloatingPadding, minSize, maxSize)); 0732 minFirst = maximumSize().height() < minSize; 0733 } else { 0734 const int minSize = qMax(MINSIZE, m_minLength); 0735 int maxSize = qMin(m_maxLength, m_screenToFollow->size().width() - m_offset); 0736 maxSize = qMax(minSize, maxSize); 0737 targetMinSize = QSize(minSize, totalThickness()); 0738 targetMaxSize = QSize(maxSize, totalThickness()); 0739 targetSize = QSize(std::clamp(m_contentLength + m_leftFloatingPadding + m_rightFloatingPadding, minSize, maxSize), totalThickness()); 0740 minFirst = maximumSize().width() < minSize; 0741 } 0742 0743 // Make sure we don't hit Q_ASSERT(!(max < min)) in qBound 0744 if (minFirst) { 0745 if (minimumSize() != targetMinSize) { 0746 setMinimumSize(targetMinSize); 0747 } 0748 if (maximumSize() != targetMaxSize) { 0749 setMaximumSize(targetMaxSize); 0750 } 0751 } else { 0752 if (maximumSize() != targetMaxSize) { 0753 setMaximumSize(targetMaxSize); 0754 } 0755 if (minimumSize() != targetMinSize) { 0756 setMinimumSize(targetMinSize); 0757 } 0758 } 0759 if (size() != targetSize) { 0760 Q_EMIT geometryChanged(); 0761 resize(targetSize); 0762 } 0763 0764 // position will be updated implicitly from resizeEvent 0765 } 0766 0767 void PanelView::restore() 0768 { 0769 KConfigGroup panelConfig = config(); 0770 if (!panelConfig.isValid()) { 0771 return; 0772 } 0773 0774 // All the defaults are based on whatever are the current values 0775 // so won't be weirdly reset after screen resolution change 0776 0777 // alignment is not resolution dependent 0778 // but if fails read it from the resolution dependent one as 0779 // the place for this config key is changed in Plasma 5.9 0780 // Do NOT use readConfigValueWithFallBack 0781 setAlignment((Qt::Alignment)panelConfig.parent().readEntry<int>("alignment", panelConfig.readEntry<int>("alignment", m_alignment))); 0782 0783 // All the other values are read from screen independent values, 0784 // but fallback on the screen independent section, as is the only place 0785 // is safe to directly write during plasma startup, as there can be 0786 // resolution changes 0787 m_offset = readConfigValueWithFallBack("offset", m_offset); 0788 if (m_alignment == Qt::AlignCenter) { 0789 m_offset = 0; 0790 } else { 0791 m_offset = qMax(0, m_offset); 0792 } 0793 0794 setFloating((bool)config().parent().readEntry<int>("floating", configDefaults().parent().readEntry<int>("floating", true))); 0795 setThickness(configDefaults().readEntry("thickness", m_thickness)); 0796 0797 const QSize screenSize = m_screenToFollow->size(); 0798 setMinimumSize(QSize(-1, -1)); 0799 // FIXME: an invalid size doesn't work with QWindows 0800 setMaximumSize(screenSize); 0801 0802 m_initCompleted = true; 0803 positionPanel(); 0804 0805 const int side = containment()->formFactor() == Plasma::Types::Vertical ? screenSize.height() : screenSize.width(); 0806 const int maxSize = side - m_offset; 0807 m_maxLength = qBound<int>(MINSIZE, readConfigValueWithFallBack("maxLength", side), maxSize); 0808 m_minLength = qBound<int>(MINSIZE, readConfigValueWithFallBack("minLength", side), maxSize); 0809 0810 // panelVisibility is not resolution dependent 0811 // but if fails read it from the resolution dependent one as 0812 // the place for this config key is changed in Plasma 5.9 0813 // Do NOT use readConfigValueWithFallBack 0814 setVisibilityMode((VisibilityMode)panelConfig.parent().readEntry<int>("panelVisibility", panelConfig.readEntry<int>("panelVisibility", (int)NormalPanel))); 0815 setOpacityMode((OpacityMode)config().parent().readEntry<int>("panelOpacity", 0816 configDefaults().parent().readEntry<int>("panelOpacity", PanelView::OpacityMode::Adaptive))); 0817 setLengthMode( 0818 (LengthMode)config().parent().readEntry<int>("panelLengthMode", 0819 configDefaults().parent().readEntry<int>("panelLengthMode", PanelView::LengthMode::FillAvailable))); 0820 resizePanel(); 0821 0822 Q_EMIT maximumLengthChanged(); 0823 Q_EMIT minimumLengthChanged(); 0824 Q_EMIT offsetChanged(); 0825 Q_EMIT alignmentChanged(); 0826 } 0827 0828 void PanelView::showConfigurationInterface(Plasma::Applet *applet) 0829 { 0830 if (!applet || !applet->containment()) { 0831 return; 0832 } 0833 0834 Plasma::Containment *cont = qobject_cast<Plasma::Containment *>(applet); 0835 0836 const bool isPanelConfig = (cont && cont == containment() && cont->isContainment()); 0837 0838 if (isPanelConfig) { 0839 if (m_panelConfigView && m_panelConfigView->isVisible()) { 0840 m_panelConfigView->hide(); 0841 cont->corona()->setEditMode(false); 0842 } else if (m_panelConfigView) { 0843 m_panelConfigView->show(); 0844 m_panelConfigView->requestActivate(); 0845 } else { 0846 PanelConfigView *configView = new PanelConfigView(cont, this); 0847 m_panelConfigView = configView; 0848 configView->setVisualParent(contentItem()); 0849 configView->init(); 0850 configView->show(); 0851 configView->requestActivate(); 0852 } 0853 } else { 0854 if (m_appletConfigView && m_appletConfigView->applet() == applet) { 0855 m_appletConfigView->show(); 0856 m_appletConfigView->requestActivate(); 0857 return; 0858 } else if (m_appletConfigView) { 0859 m_appletConfigView->deleteLater(); // BUG 476968 0860 } 0861 m_appletConfigView = new PlasmaQuick::ConfigView(applet); 0862 m_appletConfigView->init(); 0863 m_appletConfigView->show(); 0864 m_appletConfigView->requestActivate(); 0865 } 0866 } 0867 0868 void PanelView::restoreAutoHide() 0869 { 0870 bool autoHide = true; 0871 disconnect(m_transientWindowVisibleWatcher); 0872 0873 if (!edgeActivated()) { 0874 autoHide = false; 0875 } else if (m_containsMouse) { 0876 autoHide = false; 0877 } else if (containment() && containment()->isUserConfiguring()) { 0878 autoHide = false; 0879 } else if (containment() && containment()->status() >= Plasma::Types::NeedsAttentionStatus && containment()->status() != Plasma::Types::HiddenStatus) { 0880 autoHide = false; 0881 } else { 0882 for (QWindow *window : qApp->topLevelWindows()) { 0883 if (window->transientParent() == this && window->isVisible()) { 0884 m_transientWindowVisibleWatcher = connect(window, &QWindow::visibleChanged, this, &PanelView::restoreAutoHide); 0885 autoHide = false; 0886 break; // if multiple are open, we will re-evaluate this expression after the first closes 0887 } 0888 } 0889 } 0890 0891 setAutoHideEnabled(autoHide); 0892 } 0893 0894 void PanelView::setAutoHideEnabled(bool enabled) 0895 { 0896 if (m_visibilityMode == VisibilityMode::AutoHide || (m_visibilityMode == VisibilityMode::DodgeWindows && m_touchingWindow)) { 0897 if (!m_autoHideScreenEdge) { 0898 m_autoHideScreenEdge = AutoHideScreenEdge::create(this); 0899 } 0900 if (enabled) { 0901 m_autoHideScreenEdge->activate(); 0902 } else { 0903 m_autoHideScreenEdge->deactivate(); 0904 } 0905 } else { 0906 delete m_autoHideScreenEdge; 0907 m_autoHideScreenEdge = nullptr; 0908 } 0909 } 0910 0911 void PanelView::resizeEvent(QResizeEvent *ev) 0912 { 0913 updateEnabledBorders(); 0914 // don't setGeometry() to make really sure we aren't doing a resize loop 0915 if (m_screenToFollow && containment()) { 0916 // TODO: Make it X11-specific. It's still relevant on wayland because of popup positioning. 0917 const QPoint pos = geometryByDistance(m_distance).topLeft(); 0918 setPosition(pos); 0919 0920 m_strutsTimer.start(STRUTSTIMERDELAY); 0921 Q_EMIT m_corona->availableScreenRegionChanged(containment()->screen()); 0922 } 0923 0924 PlasmaQuick::ContainmentView::resizeEvent(ev); 0925 } 0926 0927 void PanelView::moveEvent(QMoveEvent *ev) 0928 { 0929 updateEnabledBorders(); 0930 m_strutsTimer.start(STRUTSTIMERDELAY); 0931 PlasmaQuick::ContainmentView::moveEvent(ev); 0932 if (m_screenToFollow && !m_screenToFollow->geometry().contains(geometry())) { 0933 positionPanel(); 0934 } 0935 } 0936 0937 void PanelView::keyPressEvent(QKeyEvent *event) 0938 { 0939 // Press escape key to cancel focus on the panel 0940 if (event->key() == Qt::Key_Escape) { 0941 if (containment()->status() == Plasma::Types::AcceptingInputStatus) { 0942 containment()->setStatus(Plasma::Types::PassiveStatus); 0943 } 0944 // No return for Wayland 0945 } 0946 0947 PlasmaQuick::ContainmentView::keyPressEvent(event); 0948 if (event->isAccepted()) { 0949 return; 0950 } 0951 0952 // Catch arrows keyPress to have same behavior as tab/backtab 0953 if ((event->key() == Qt::Key_Right && qApp->layoutDirection() == Qt::LeftToRight) 0954 || (event->key() == Qt::Key_Left && qApp->layoutDirection() != Qt::LeftToRight) || event->key() == Qt::Key_Down) { 0955 event->accept(); 0956 QKeyEvent tabEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier); 0957 qApp->sendEvent((QObject *)this, (QEvent *)&tabEvent); 0958 return; 0959 } else if ((event->key() == Qt::Key_Right && qApp->layoutDirection() != Qt::LeftToRight) 0960 || (event->key() == Qt::Key_Left && qApp->layoutDirection() == Qt::LeftToRight) || event->key() == Qt::Key_Up) { 0961 event->accept(); 0962 QKeyEvent backtabEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier); 0963 qApp->sendEvent((QObject *)this, (QEvent *)&backtabEvent); 0964 return; 0965 } 0966 } 0967 0968 void PanelView::integrateScreen() 0969 { 0970 updateMask(); 0971 #if HAVE_X11 0972 if (KWindowSystem::isPlatformX11()) { 0973 KX11Extras::setOnAllDesktops(winId(), true); 0974 KX11Extras::setType(winId(), NET::Dock); 0975 // QXcbWindow isn't installed and thus inaccessible to us, but it does read this magic property... 0976 setProperty("_q_xcb_wm_window_type", QNativeInterface::Private::QXcbWindow::Dock); 0977 } 0978 #endif 0979 setVisibilityMode(m_visibilityMode); 0980 0981 if (containment()) { 0982 containment()->reactToScreenChange(); 0983 } 0984 } 0985 0986 void PanelView::showEvent(QShowEvent *event) 0987 { 0988 PlasmaQuick::ContainmentView::showEvent(event); 0989 0990 integrateScreen(); 0991 } 0992 0993 void PanelView::setScreenToFollow(QScreen *screen) 0994 { 0995 if (screen == m_screenToFollow) { 0996 return; 0997 } 0998 0999 if (!screen) { 1000 return; 1001 } 1002 1003 // layer surfaces can't be moved between outputs, so hide and show the window on a new output 1004 const bool remap = m_layerWindow && isVisible(); 1005 if (remap) { 1006 setVisible(false); 1007 } 1008 1009 if (!m_screenToFollow.isNull()) { 1010 // disconnect from old screen 1011 disconnect(m_screenToFollow, &QScreen::virtualGeometryChanged, this, &PanelView::updateExclusiveZone); 1012 disconnect(m_screenToFollow, &QScreen::geometryChanged, this, &PanelView::restore); 1013 } 1014 1015 connect(screen, &QScreen::virtualGeometryChanged, this, &PanelView::updateExclusiveZone, Qt::UniqueConnection); 1016 connect(screen, &QScreen::geometryChanged, this, &PanelView::restore, Qt::UniqueConnection); 1017 1018 /*connect(screen, &QObject::destroyed, this, [this]() { 1019 if (PanelView::screen()) { 1020 m_screenToFollow = PanelView::screen(); 1021 adaptToScreen(); 1022 } 1023 });*/ 1024 1025 m_screenToFollow = screen; 1026 1027 setScreen(screen); 1028 adaptToScreen(); 1029 1030 if (remap) { 1031 setVisible(true); 1032 } 1033 } 1034 1035 QScreen *PanelView::screenToFollow() const 1036 { 1037 return m_screenToFollow; 1038 } 1039 1040 void PanelView::adaptToScreen() 1041 { 1042 Q_EMIT screenToFollowChanged(m_screenToFollow); 1043 m_lastScreen = m_screenToFollow; 1044 1045 if (!m_screenToFollow) { 1046 return; 1047 } 1048 1049 integrateScreen(); 1050 restore(); 1051 } 1052 1053 bool PanelView::event(QEvent *e) 1054 { 1055 switch (e->type()) { 1056 case QEvent::Enter: 1057 m_containsMouse = true; 1058 if (edgeActivated()) { 1059 m_unhideTimer.stop(); 1060 } 1061 break; 1062 1063 case QEvent::Leave: 1064 m_containsMouse = false; 1065 if (edgeActivated()) { 1066 m_unhideTimer.start(); 1067 } 1068 break; 1069 1070 /*Fitt's law: if the containment has margins, and the mouse cursor clicked 1071 * on the mouse edge, forward the click in the containment boundaries 1072 */ 1073 1074 case QEvent::MouseMove: 1075 case QEvent::MouseButtonPress: 1076 case QEvent::MouseButtonRelease: { 1077 QMouseEvent *me = static_cast<QMouseEvent *>(e); 1078 1079 // first, don't mess with position if the cursor is actually outside the view: 1080 // somebody is doing a click and drag that must not break when the cursor i outside 1081 if (geometry().contains(QCursor::pos(screenToFollow()))) { 1082 if (!containmentContainsPosition(me->scenePosition()) && !m_fakeEventPending) { 1083 QMouseEvent me2(me->type(), 1084 positionAdjustedForContainment(me->scenePosition()), 1085 positionAdjustedForContainment(me->scenePosition()), 1086 positionAdjustedForContainment(me->scenePosition()) + position(), 1087 me->button(), 1088 me->buttons(), 1089 me->modifiers()); 1090 me2.setTimestamp(me->timestamp()); 1091 1092 m_fakeEventPending = true; 1093 QCoreApplication::sendEvent(this, &me2); 1094 m_fakeEventPending = false; 1095 return true; 1096 } 1097 } else { 1098 // default handling if current mouse position is outside the panel 1099 return ContainmentView::event(e); 1100 } 1101 break; 1102 } 1103 1104 case QEvent::Wheel: { 1105 QWheelEvent *we = static_cast<QWheelEvent *>(e); 1106 1107 if (!containmentContainsPosition(we->position()) && !m_fakeEventPending) { 1108 QWheelEvent we2(positionAdjustedForContainment(we->position()), 1109 positionAdjustedForContainment(we->position()) + position(), 1110 we->pixelDelta(), 1111 we->angleDelta(), 1112 we->buttons(), 1113 we->modifiers(), 1114 we->phase(), 1115 we->inverted()); 1116 we2.setTimestamp(we->timestamp()); 1117 1118 m_fakeEventPending = true; 1119 QCoreApplication::sendEvent(this, &we2); 1120 m_fakeEventPending = false; 1121 return true; 1122 } 1123 break; 1124 } 1125 1126 case QEvent::DragEnter: { 1127 QDragEnterEvent *de = static_cast<QDragEnterEvent *>(e); 1128 if (!containmentContainsPosition(de->position()) && !m_fakeEventPending) { 1129 QDragEnterEvent de2(positionAdjustedForContainment(de->position()).toPoint(), 1130 de->possibleActions(), 1131 de->mimeData(), 1132 de->buttons(), 1133 de->modifiers()); 1134 1135 m_fakeEventPending = true; 1136 QCoreApplication::sendEvent(this, &de2); 1137 m_fakeEventPending = false; 1138 return true; 1139 } 1140 break; 1141 } 1142 // DragLeave just works 1143 case QEvent::DragLeave: 1144 break; 1145 case QEvent::DragMove: { 1146 QDragMoveEvent *de = static_cast<QDragMoveEvent *>(e); 1147 if (!containmentContainsPosition(de->position()) && !m_fakeEventPending) { 1148 QDragMoveEvent de2(positionAdjustedForContainment(de->position()).toPoint(), de->possibleActions(), de->mimeData(), de->buttons(), de->modifiers()); 1149 1150 m_fakeEventPending = true; 1151 QCoreApplication::sendEvent(this, &de2); 1152 m_fakeEventPending = false; 1153 return true; 1154 } 1155 break; 1156 } 1157 case QEvent::Drop: { 1158 QDropEvent *de = static_cast<QDropEvent *>(e); 1159 if (!containmentContainsPosition(de->position()) && !m_fakeEventPending) { 1160 QDropEvent de2(positionAdjustedForContainment(de->position()).toPoint(), de->possibleActions(), de->mimeData(), de->buttons(), de->modifiers()); 1161 1162 m_fakeEventPending = true; 1163 QCoreApplication::sendEvent(this, &de2); 1164 m_fakeEventPending = false; 1165 return true; 1166 } 1167 break; 1168 } 1169 1170 case QEvent::Hide: { 1171 m_containsMouse = false; 1172 break; 1173 } 1174 default: 1175 break; 1176 } 1177 1178 return ContainmentView::event(e); 1179 } 1180 1181 bool PanelView::containmentContainsPosition(const QPointF &point) const 1182 { 1183 QQuickItem *containmentItem = PlasmaQuick::AppletQuickItem::itemForApplet(containment()); 1184 1185 if (!containmentItem) { 1186 return false; 1187 } 1188 1189 return QRectF(containmentItem->mapToScene(QPoint(m_leftPadding, m_topPadding)), 1190 QSizeF(containmentItem->width() - m_leftPadding - m_rightPadding, containmentItem->height() - m_topPadding - m_bottomPadding)) 1191 .contains(point); 1192 } 1193 1194 QPointF PanelView::positionAdjustedForContainment(const QPointF &point) const 1195 { 1196 QQuickItem *containmentItem = PlasmaQuick::AppletQuickItem::itemForApplet(containment()); 1197 1198 if (!containmentItem) { 1199 return point; 1200 } 1201 1202 QRectF containmentRect(containmentItem->mapToScene(QPoint(0, 0)), QSizeF(containmentItem->width(), containmentItem->height())); 1203 1204 // We are removing 1 to the e.g. containmentRect.right() - m_rightPadding because the last pixel would otherwise 1205 // the first one in the margin, and thus the mouse event would be discarded. Instead, the first pixel given by 1206 // containmentRect.left() + m_leftPadding the first one *not* in the margin, so it work. 1207 return QPointF(qBound(containmentRect.left() + m_leftPadding, point.x(), containmentRect.right() - m_rightPadding - 1), 1208 qBound(containmentRect.top() + m_topPadding, point.y(), containmentRect.bottom() - m_bottomPadding - 1)); 1209 } 1210 1211 void PanelView::updateMask() 1212 { 1213 if (m_backgroundHints == Plasma::Types::NoBackground) { 1214 KWindowEffects::enableBlurBehind(this, false); 1215 KWindowEffects::enableBackgroundContrast(this, false); 1216 setMask(QRegion()); 1217 } else { 1218 QRegion mask; 1219 1220 QQuickItem *rootObject = this->rootObject(); 1221 if (rootObject) { 1222 const QVariant maskProperty = rootObject->property("panelMask"); 1223 if (static_cast<QMetaType::Type>(maskProperty.typeId()) == QMetaType::QRegion) { 1224 mask = maskProperty.value<QRegion>(); 1225 mask.translate(rootObject->property("maskOffsetX").toInt(), rootObject->property("maskOffsetY").toInt()); 1226 } 1227 } 1228 // We use mask for graphical effect which tightly covers the panel 1229 // For the input region (QWindow::mask) screenPanelRect includes area around the floating 1230 // panel in order to respect Fitt's law 1231 KWindowEffects::enableBlurBehind(this, m_theme.blurBehindEnabled(), mask); 1232 KWindowEffects::enableBackgroundContrast(this, 1233 m_theme.backgroundContrastEnabled(), 1234 m_theme.backgroundContrast(), 1235 m_theme.backgroundIntensity(), 1236 m_theme.backgroundSaturation(), 1237 mask); 1238 1239 if (!KWindowSystem::isPlatformX11() || KX11Extras::compositingActive()) { 1240 QRect screenPanelRect = geometry().intersected(screen()->geometry()); 1241 screenPanelRect.moveTo(mapFromGlobal(screenPanelRect.topLeft())); 1242 setMask(screenPanelRect); 1243 } else { 1244 setMask(mask); 1245 } 1246 } 1247 } 1248 1249 bool PanelView::canSetStrut() const 1250 { 1251 #if HAVE_X11 1252 if (!KWindowSystem::isPlatformX11()) { 1253 return true; 1254 } 1255 // read the wm name, need to do this every time which means a roundtrip unfortunately 1256 // but WM might have changed 1257 NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck); 1258 if (qstricmp(rootInfo.wmName(), "KWin") == 0) { 1259 // KWin since 5.7 can handle this fine, so only exclude for other window managers 1260 return true; 1261 } 1262 1263 const QRect thisScreen = screen()->geometry(); 1264 const int numScreens = corona()->numScreens(); 1265 if (numScreens < 2) { 1266 return true; 1267 } 1268 1269 // Extended struts against a screen edge near to another screen are really harmful, so windows maximized under the panel is a lesser pain 1270 // TODO: force "windows can cover" in those cases? 1271 const auto screenIds = m_corona->screenIds(); 1272 for (int id : screenIds) { 1273 if (id == containment()->screen()) { 1274 continue; 1275 } 1276 1277 const QRect otherScreen = corona()->screenGeometry(id); 1278 if (!otherScreen.isValid()) { 1279 continue; 1280 } 1281 1282 switch (location()) { 1283 case Plasma::Types::TopEdge: 1284 if (otherScreen.bottom() <= thisScreen.top()) { 1285 return false; 1286 } 1287 break; 1288 case Plasma::Types::BottomEdge: 1289 if (otherScreen.top() >= thisScreen.bottom()) { 1290 return false; 1291 } 1292 break; 1293 case Plasma::Types::RightEdge: 1294 if (otherScreen.left() >= thisScreen.right()) { 1295 return false; 1296 } 1297 break; 1298 case Plasma::Types::LeftEdge: 1299 if (otherScreen.right() <= thisScreen.left()) { 1300 return false; 1301 } 1302 break; 1303 default: 1304 return false; 1305 } 1306 } 1307 return true; 1308 #else 1309 return true; 1310 #endif 1311 } 1312 1313 void PanelView::updateExclusiveZone() 1314 { 1315 auto exclusiveMargin = [this] { 1316 switch (location()) { 1317 case Plasma::Types::TopEdge: 1318 return m_layerWindow->margins().top(); 1319 break; 1320 case Plasma::Types::LeftEdge: 1321 return m_layerWindow->margins().left(); 1322 break; 1323 case Plasma::Types::RightEdge: 1324 return m_layerWindow->margins().right(); 1325 break; 1326 case Plasma::Types::BottomEdge: 1327 default: 1328 return m_layerWindow->margins().bottom(); 1329 break; 1330 } 1331 }; 1332 1333 if (containment() && containment()->isUserConfiguring() && m_layerWindow && m_layerWindow->exclusionZone() == 0) { 1334 // We set the exclusive zone to make sure the ruler does not 1335 // overlap with the panel regardless of the visibility mode; 1336 // this won't be updated anymore as long as we are within 1337 // the panel configuration. 1338 m_layerWindow->setExclusiveZone(thickness() - exclusiveMargin()); 1339 } 1340 if (!containment() || containment()->isUserConfiguring() || !m_screenToFollow) { 1341 return; 1342 } 1343 1344 if (KWindowSystem::isPlatformWayland()) { 1345 switch (m_visibilityMode) { 1346 case NormalPanel: 1347 m_layerWindow->setExclusiveZone(thickness() - exclusiveMargin()); 1348 break; 1349 case AutoHide: 1350 case DodgeWindows: 1351 case WindowsGoBelow: 1352 m_layerWindow->setExclusiveZone(0); 1353 break; 1354 } 1355 requestUpdate(); 1356 } else { 1357 #if HAVE_X11 1358 qreal top_width = 0, top_start = 0, top_end = 0; 1359 qreal bottom_width = 0, bottom_start = 0, bottom_end = 0; 1360 qreal right_width = 0, right_start = 0, right_end = 0; 1361 qreal left_width = 0, left_start = 0, left_end = 0; 1362 1363 if (m_visibilityMode == NormalPanel) { 1364 if (!canSetStrut()) { 1365 KX11Extras::setExtendedStrut(winId(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); 1366 return; 1367 } 1368 1369 // When setting struts, all arguments must belong to the logical coordinates. 1370 const double devicePixelRatio = m_screenToFollow->devicePixelRatio(); 1371 const QRectF thisScreen{m_screenToFollow->geometry().topLeft().toPointF() / devicePixelRatio, m_screenToFollow->geometry().size()}; 1372 // extended struts are to the combined screen geoms, not the single screen 1373 QRectF wholeScreen; 1374 for (const auto screens = qGuiApp->screens(); auto screen : screens) { 1375 const QRectF geometry = screen->geometry().toRectF(); 1376 wholeScreen |= QRectF(geometry.topLeft() / devicePixelRatio, geometry.size()); 1377 } 1378 1379 const qreal offset = 1 / devicePixelRatio; // To make sure strut is only in a screen 1380 1381 switch (location()) { 1382 case Plasma::Types::TopEdge: { 1383 const qreal topOffset = thisScreen.top(); 1384 top_width = thickness() + topOffset; 1385 top_start = x() / devicePixelRatio; 1386 top_end = top_start + width() - offset; 1387 // qDebug() << "setting top edge to" << top_width << top_start << top_end; 1388 break; 1389 } 1390 1391 case Plasma::Types::BottomEdge: { 1392 const qreal bottomOffset = wholeScreen.bottom() - thisScreen.bottom(); 1393 bottom_width = thickness() + bottomOffset; 1394 bottom_start = x() / devicePixelRatio; 1395 bottom_end = bottom_start + width() - offset; 1396 // qDebug() << "setting bottom edge to" << bottom_width << bottom_start << bottom_end; 1397 break; 1398 } 1399 1400 case Plasma::Types::RightEdge: { 1401 const qreal rightOffset = wholeScreen.right() - thisScreen.right(); 1402 right_width = thickness() + rightOffset; 1403 right_start = y() / devicePixelRatio; 1404 right_end = right_start + height() - offset; 1405 // qDebug() << "setting right edge to" << right_width << right_start << right_end; 1406 break; 1407 } 1408 1409 case Plasma::Types::LeftEdge: { 1410 const qreal leftOffset = thisScreen.x(); 1411 left_width = thickness() + leftOffset; 1412 left_start = y() / devicePixelRatio; 1413 left_end = left_start + height() - offset; 1414 // qDebug() << "setting left edge to" << left_width << left_start << left_end; 1415 break; 1416 } 1417 1418 default: 1419 // qDebug() << "where are we?"; 1420 break; 1421 } 1422 } 1423 1424 KX11Extras::setExtendedStrut(winId(), 1425 left_width, 1426 left_start, 1427 left_end, 1428 right_width, 1429 right_start, 1430 right_end, 1431 top_width, 1432 top_start, 1433 top_end, 1434 bottom_width, 1435 bottom_start, 1436 bottom_end); 1437 #endif 1438 } 1439 } 1440 1441 void PanelView::refreshContainment() 1442 { 1443 restore(); 1444 connect(containment(), &Plasma::Containment::userConfiguringChanged, this, [this](bool configuring) { 1445 if (configuring) { 1446 showTemporarily(); 1447 } else { 1448 m_unhideTimer.start(); 1449 updateExclusiveZone(); 1450 } 1451 }); 1452 1453 connect(containment(), &Plasma::Applet::statusChanged, this, &PanelView::refreshStatus); 1454 connect(containment(), &Plasma::Applet::appletDeleted, this, [this] { 1455 // containment()->destroyed() is true only when the user deleted it 1456 // so the config is to be thrown away, not during shutdown 1457 if (containment()->destroyed()) { 1458 KConfigGroup views(m_corona->applicationConfig(), u"PlasmaViews"_s); 1459 for (auto grp : views.groupList()) { 1460 if (grp.contains(QRegularExpression(QStringLiteral("Panel %1$").arg(QString::number(containment()->id()))))) { 1461 qDebug() << "Panel" << containment()->id() << "removed by user"; 1462 views.deleteGroup(grp); 1463 } 1464 views.sync(); 1465 } 1466 } 1467 }); 1468 connect(containment(), &Plasma::Applet::userConfiguringChanged, this, &PanelView::updateExclusiveZone); 1469 } 1470 1471 void PanelView::handleQmlStatusChange(QQmlComponent::Status status) 1472 { 1473 if (status != QQmlComponent::Ready) { 1474 return; 1475 } 1476 1477 QQuickItem *rootObject = this->rootObject(); 1478 if (rootObject) { 1479 disconnect(this, &QuickViewSharedEngine::statusChanged, this, &PanelView::handleQmlStatusChange); 1480 1481 updatePadding(); 1482 updateFloating(); 1483 const int paddingSignal = rootObject->metaObject()->indexOfSignal("bottomPaddingChanged()"); 1484 const int floatingSignal = rootObject->metaObject()->indexOfSignal("floatingnessChanged()"); 1485 const int touchingWindowSignal = rootObject->metaObject()->indexOfSignal("touchingWindowChanged()"); 1486 1487 if (paddingSignal >= 0) { 1488 connect(rootObject, SIGNAL(bottomPaddingChanged()), this, SLOT(updatePadding())); 1489 connect(rootObject, SIGNAL(topPaddingChanged()), this, SLOT(updatePadding())); 1490 connect(rootObject, SIGNAL(rightPaddingChanged()), this, SLOT(updatePadding())); 1491 connect(rootObject, SIGNAL(leftPaddingChanged()), this, SLOT(updatePadding())); 1492 } 1493 if (floatingSignal >= 0) { 1494 connect(rootObject, SIGNAL(minPanelHeightChanged()), this, SLOT(updatePadding())); 1495 connect(rootObject, SIGNAL(minPanelWidthChanged()), this, SLOT(updatePadding())); 1496 connect(rootObject, SIGNAL(floatingnessChanged()), this, SLOT(updateFloating())); 1497 connect(rootObject, SIGNAL(hasShadowsChanged()), this, SLOT(updateShadows())); 1498 connect(rootObject, SIGNAL(maskOffsetXChanged()), this, SLOT(updateMask())); 1499 connect(rootObject, SIGNAL(maskOffsetYChanged()), this, SLOT(updateMask())); 1500 } 1501 1502 const QVariant maskProperty = rootObject->property("panelMask"); 1503 if (static_cast<QMetaType::Type>(maskProperty.typeId()) == QMetaType::QRegion) { 1504 connect(rootObject, SIGNAL(panelMaskChanged()), this, SLOT(updateMask())); 1505 updateMask(); 1506 } 1507 if (touchingWindowSignal >= 0) { 1508 connect(rootObject, SIGNAL(touchingWindowChanged()), this, SLOT(updateTouchingWindow())); 1509 } 1510 updateTouchingWindow(); 1511 } 1512 } 1513 1514 void PanelView::refreshStatus(Plasma::Types::ItemStatus status) 1515 { 1516 if (status == Plasma::Types::NeedsAttentionStatus) { 1517 showTemporarily(); 1518 setFlags(flags() | Qt::WindowDoesNotAcceptFocus); 1519 if (isActive()) { 1520 m_corona->restorePreviousWindow(); 1521 } 1522 if (m_layerWindow) { 1523 m_layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityNone); 1524 requestUpdate(); 1525 } 1526 } else if (status == Plasma::Types::AcceptingInputStatus) { 1527 m_corona->savePreviousWindow(); 1528 setFlags(flags() & ~Qt::WindowDoesNotAcceptFocus); 1529 1530 if (KWindowSystem::isPlatformX11()) { 1531 KX11Extras::forceActiveWindow(winId()); 1532 } else { 1533 showTemporarily(); 1534 } 1535 1536 if (m_layerWindow) { 1537 m_layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand); 1538 requestUpdate(); 1539 } 1540 1541 const auto nextItem = rootObject()->nextItemInFocusChain(); 1542 if (nextItem) { 1543 nextItem->forceActiveFocus(Qt::TabFocusReason); 1544 } else { 1545 containment()->setStatus(Plasma::Types::PassiveStatus); 1546 } 1547 } else { 1548 if (status == Plasma::Types::PassiveStatus) { 1549 m_corona->restorePreviousWindow(); 1550 } else if (status == Plasma::Types::ActiveStatus) { 1551 m_corona->clearPreviousWindow(); 1552 } 1553 1554 restoreAutoHide(); 1555 setFlags(flags() | Qt::WindowDoesNotAcceptFocus); 1556 if (m_layerWindow) { 1557 m_layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityNone); 1558 requestUpdate(); 1559 } 1560 } 1561 } 1562 1563 void PanelView::showTemporarily() 1564 { 1565 setAutoHideEnabled(false); 1566 1567 QTimer *t = new QTimer(this); 1568 t->setSingleShot(true); 1569 t->setInterval(3s); 1570 connect(t, &QTimer::timeout, this, &PanelView::restoreAutoHide); 1571 connect(t, &QTimer::timeout, t, &QObject::deleteLater); 1572 t->start(); 1573 } 1574 1575 void PanelView::updateTouchingWindow() 1576 { 1577 if (!rootObject()) { 1578 return; 1579 } 1580 m_touchingWindow = rootObject()->property("touchingWindow").toBool(); 1581 restoreAutoHide(); 1582 } 1583 1584 void PanelView::screenDestroyed(QObject *) 1585 { 1586 // NOTE: this is overriding the screen destroyed slot, we need to do this because 1587 // otherwise Qt goes mental and starts moving our panels. See: 1588 // https://codereview.qt-project.org/#/c/88351/ 1589 // if(screen == this->m_screenToFollow) { 1590 // DO NOTHING, panels are moved by ::readaptToScreen 1591 // } 1592 } 1593 1594 bool PanelView::edgeActivated() const 1595 { 1596 return m_visibilityMode == PanelView::AutoHide || m_visibilityMode == PanelView::DodgeWindows; 1597 } 1598 1599 void PanelView::updateEnabledBorders() 1600 { 1601 KSvg::FrameSvg::EnabledBorders borders = KSvg::FrameSvg::AllBorders; 1602 if (m_backgroundHints == Plasma::Types::NoBackground) { 1603 borders = KSvg::FrameSvg::NoBorder; 1604 } else { 1605 switch (location()) { 1606 case Plasma::Types::TopEdge: 1607 borders &= ~KSvg::FrameSvg::TopBorder; 1608 break; 1609 case Plasma::Types::LeftEdge: 1610 borders &= ~KSvg::FrameSvg::LeftBorder; 1611 break; 1612 case Plasma::Types::RightEdge: 1613 borders &= ~KSvg::FrameSvg::RightBorder; 1614 break; 1615 case Plasma::Types::BottomEdge: 1616 borders &= ~KSvg::FrameSvg::BottomBorder; 1617 break; 1618 default: 1619 break; 1620 } 1621 1622 if (m_screenToFollow) { 1623 if (x() <= m_screenToFollow->geometry().x()) { 1624 borders &= ~KSvg::FrameSvg::LeftBorder; 1625 } 1626 if (x() + width() >= m_screenToFollow->geometry().x() + m_screenToFollow->geometry().width()) { 1627 borders &= ~KSvg::FrameSvg::RightBorder; 1628 } 1629 if (y() <= m_screenToFollow->geometry().y()) { 1630 borders &= ~KSvg::FrameSvg::TopBorder; 1631 } 1632 if (y() + height() >= m_screenToFollow->geometry().y() + m_screenToFollow->geometry().height()) { 1633 borders &= ~KSvg::FrameSvg::BottomBorder; 1634 } 1635 } 1636 } 1637 1638 if (m_enabledBorders != borders) { 1639 PanelShadows::self()->setEnabledBorders(this, borders); 1640 m_enabledBorders = borders; 1641 Q_EMIT enabledBordersChanged(); 1642 } 1643 } 1644 1645 void PanelView::updatePadding() 1646 { 1647 if (!rootObject()) 1648 return; 1649 m_leftPadding = rootObject()->property("leftPadding").toInt(); 1650 m_rightPadding = rootObject()->property("rightPadding").toInt(); 1651 m_topPadding = rootObject()->property("topPadding").toInt(); 1652 m_bottomPadding = rootObject()->property("bottomPadding").toInt(); 1653 m_minDrawingHeight = rootObject()->property("minPanelHeight").toInt(); 1654 m_minDrawingWidth = rootObject()->property("minPanelWidth").toInt(); 1655 Q_EMIT minThicknessChanged(); 1656 setThickness(m_thickness); 1657 } 1658 1659 void PanelView::updateShadows() 1660 { 1661 if (!rootObject()) { 1662 return; 1663 } 1664 bool hasShadows = rootObject()->property("hasShadows").toBool(); 1665 if (hasShadows) { 1666 PanelShadows::self()->addWindow(this, enabledBorders()); 1667 } else { 1668 PanelShadows::self()->removeWindow(this); 1669 } 1670 } 1671 1672 void PanelView::updateFloating() 1673 { 1674 if (!rootObject()) { 1675 return; 1676 } 1677 m_floatingness = rootObject()->property("floatingness").toFloat(); 1678 m_leftFloatingPadding = rootObject()->property("fixedLeftFloatingPadding").toInt(); 1679 m_rightFloatingPadding = rootObject()->property("fixedRightFloatingPadding").toInt(); 1680 m_topFloatingPadding = rootObject()->property("fixedTopFloatingPadding").toInt(); 1681 m_bottomFloatingPadding = rootObject()->property("fixedBottomFloatingPadding").toInt(); 1682 1683 resizePanel(); 1684 positionPanel(); 1685 updateExclusiveZone(); 1686 updateMask(); 1687 updateShadows(); 1688 } 1689 1690 #include "moc_panelview.cpp"