File indexing completed on 2024-04-28 05:35:58

0001 /*
0002     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "panelconfigview.h"
0008 #include "config-X11.h"
0009 #include "panelshadows_p.h"
0010 #include "panelview.h"
0011 #include "shellcorona.h"
0012 
0013 #include <LayerShellQt/Window>
0014 
0015 #include <QAction>
0016 #include <QDebug>
0017 #include <QDir>
0018 #include <QQmlComponent>
0019 #include <QQmlContext>
0020 #include <QQmlEngine>
0021 #include <QScreen>
0022 
0023 #include <KWindowSystem>
0024 #include <plasmaquick/popupplasmawindow.h>
0025 #include <qnamespace.h>
0026 #if HAVE_X11
0027 #include <KX11Extras>
0028 #endif
0029 #include <klocalizedstring.h>
0030 #include <kwindoweffects.h>
0031 
0032 #include <Plasma/Containment>
0033 #include <Plasma/PluginLoader>
0034 
0035 #include <KWayland/Client/plasmashell.h>
0036 #include <KWayland/Client/surface.h>
0037 
0038 #include <chrono>
0039 
0040 using namespace std::chrono_literals;
0041 using namespace Qt::StringLiterals;
0042 
0043 PanelRulerView::PanelRulerView(Plasma::Containment *containment, PanelView *panelView, PanelConfigView *mainConfigView)
0044     : PlasmaWindow()
0045     , m_containment(containment)
0046     , m_panelView(panelView)
0047     , m_mainConfigView(mainConfigView)
0048 {
0049     if (KWindowSystem::isPlatformWayland()) {
0050         m_layerWindow = LayerShellQt::Window::get(this);
0051         m_layerWindow->setLayer(LayerShellQt::Window::LayerTop);
0052         m_layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand);
0053         m_layerWindow->setScope(QStringLiteral("dock"));
0054         m_layerWindow->setCloseOnDismissed(false);
0055     }
0056     setScreen(m_panelView->screen());
0057 
0058     connect(this, &PanelRulerView::mainItemChanged, this, &PanelRulerView::syncPanelLocation);
0059 }
0060 
0061 PanelRulerView::~PanelRulerView()
0062 {
0063 }
0064 
0065 void PanelRulerView::syncPanelLocation()
0066 {
0067     if (!mainItem()) {
0068         return;
0069     }
0070     const QRect available = m_containment->corona()->availableScreenRect(m_containment->screen());
0071 
0072     switch (m_containment->location()) {
0073     case Plasma::Types::TopEdge:
0074         setBorders(Qt::BottomEdge);
0075         break;
0076     case Plasma::Types::LeftEdge:
0077         setBorders(Qt::RightEdge);
0078         break;
0079     case Plasma::Types::RightEdge:
0080         setBorders(Qt::LeftEdge);
0081         break;
0082     case Plasma::Types::BottomEdge:
0083     default:
0084         setBorders(Qt::TopEdge);
0085     }
0086 
0087     switch (m_containment->location()) {
0088     case Plasma::Types::LeftEdge:
0089     case Plasma::Types::RightEdge:
0090         setMaximumWidth(mainItem()->implicitWidth());
0091         setWidth(mainItem()->implicitWidth());
0092         setMaximumHeight(available.height());
0093         setHeight(available.height());
0094         break;
0095     case Plasma::Types::TopEdge:
0096     case Plasma::Types::BottomEdge:
0097     default:
0098         setMaximumWidth(available.width());
0099         setWidth(available.width());
0100         setMaximumHeight(mainItem()->implicitHeight());
0101         setHeight(mainItem()->implicitHeight());
0102         break;
0103     }
0104 
0105     if (KWindowSystem::isPlatformX11()) {
0106         KX11Extras::setType(winId(), NET::Dock);
0107         KX11Extras::setState(winId(), NET::KeepAbove);
0108         switch (m_containment->location()) {
0109         case Plasma::Types::TopEdge:
0110             setPosition(available.topLeft() + screen()->geometry().topLeft());
0111             break;
0112         case Plasma::Types::LeftEdge:
0113             setPosition(available.topLeft() + screen()->geometry().topLeft());
0114             break;
0115         case Plasma::Types::RightEdge:
0116             setPosition(available.topLeft() + screen()->geometry().topRight() - QPoint(width(), 0));
0117             break;
0118         case Plasma::Types::BottomEdge:
0119         default:
0120             setPosition(available.bottomLeft() + screen()->geometry().topLeft() - QPoint(0, height()));
0121         }
0122     } else if (m_layerWindow) {
0123         m_layerWindow->setKeyboardInteractivity(LayerShellQt::Window::KeyboardInteractivityOnDemand);
0124         LayerShellQt::Window::Anchors anchors;
0125 
0126         switch (m_containment->location()) {
0127         case Plasma::Types::TopEdge:
0128             anchors.setFlag(LayerShellQt::Window::AnchorTop);
0129             break;
0130         case Plasma::Types::LeftEdge:
0131             anchors.setFlag(LayerShellQt::Window::AnchorLeft);
0132             break;
0133         case Plasma::Types::RightEdge:
0134             anchors.setFlag(LayerShellQt::Window::AnchorRight);
0135             break;
0136         case Plasma::Types::BottomEdge:
0137         default:
0138             anchors.setFlag(LayerShellQt::Window::AnchorBottom);
0139             break;
0140         }
0141 
0142         if (m_containment->formFactor() == Plasma::Types::Horizontal) {
0143             switch (m_panelView->alignment()) {
0144             case Qt::AlignLeft:
0145                 anchors.setFlag(LayerShellQt::Window::AnchorLeft);
0146                 break;
0147             case Qt::AlignCenter:
0148                 break;
0149             case Qt::AlignRight:
0150                 anchors.setFlag(LayerShellQt::Window::AnchorRight);
0151                 break;
0152             }
0153         } else {
0154             switch (m_panelView->alignment()) {
0155             case Qt::AlignLeft:
0156                 anchors.setFlag(LayerShellQt::Window::AnchorTop);
0157                 break;
0158             case Qt::AlignCenter:
0159                 break;
0160             case Qt::AlignRight:
0161                 anchors.setFlag(LayerShellQt::Window::AnchorBottom);
0162                 break;
0163             }
0164         }
0165 
0166         // m_layerWindow->setMargins(margins);
0167         m_layerWindow->setAnchors(anchors);
0168 
0169         requestUpdate();
0170     }
0171 }
0172 
0173 void PanelRulerView::showEvent(QShowEvent *ev)
0174 {
0175     syncPanelLocation();
0176     PlasmaWindow::showEvent(ev);
0177 }
0178 
0179 void PanelRulerView::focusOutEvent(QFocusEvent *ev)
0180 {
0181     QWindow *focusWindow = QGuiApplication::focusWindow();
0182 
0183     if (focusWindow
0184         && ((focusWindow->flags().testFlag(Qt::Popup)) || focusWindow->objectName() == QLatin1String("QMenuClassWindow") || focusWindow == m_mainConfigView)) {
0185         return;
0186     }
0187 
0188     m_mainConfigView->focusVisibilityCheck(focusWindow);
0189 }
0190 
0191 //////////////////////////////PanelConfigView
0192 PanelConfigView::PanelConfigView(Plasma::Containment *containment, PanelView *panelView)
0193     : PlasmaQuick::PopupPlasmaWindow()
0194     , m_containment(containment)
0195     , m_panelView(panelView)
0196     , m_sharedQmlEngine(std::make_unique<PlasmaQuick::SharedQmlEngine>(this))
0197 {
0198     connect(panelView, &QObject::destroyed, this, &QObject::deleteLater);
0199 
0200     setScreen(panelView->screen());
0201 
0202     connect(panelView, &QWindow::screenChanged, &m_screenSyncTimer, QOverload<>::of(&QTimer::start));
0203     m_screenSyncTimer.setSingleShot(true);
0204     m_screenSyncTimer.setInterval(150ms);
0205     connect(&m_screenSyncTimer, &QTimer::timeout, [this, panelView]() {
0206         setScreen(panelView->screen());
0207         syncGeometry();
0208     });
0209 
0210     m_sharedQmlEngine->rootContext()->setContextProperties({
0211         QQmlContext::PropertyPair{u"plasmoid"_s, QVariant::fromValue(containment)},
0212         QQmlContext::PropertyPair{u"panel"_s, QVariant::fromValue(panelView)},
0213         QQmlContext::PropertyPair{u"configDialog"_s, QVariant::fromValue(this)},
0214     });
0215     connect(containment, &Plasma::Containment::destroyedChanged, this, &QObject::deleteLater);
0216     connect(containment, &Plasma::Containment::formFactorChanged, this, &PanelConfigView::syncGeometry);
0217     connect(containment, &Plasma::Containment::locationChanged, this, &PanelConfigView::syncGeometry);
0218 
0219     connect(panelView, &PanelView::lengthChanged, this, &PanelConfigView::syncGeometry);
0220     connect(panelView, &PanelView::geometryChanged, this, &PanelConfigView::syncGeometry);
0221     connect(panelView, &PanelView::thicknessChanged, this, &PanelConfigView::syncGeometry);
0222     connect(m_containment->corona(), &Plasma::Corona::editModeChanged, this, [this](bool edit) {
0223         if (!edit) {
0224             hide();
0225         }
0226     });
0227 
0228     setMargin(4);
0229     m_focusWindow = qApp->focusWindow();
0230 }
0231 
0232 PanelConfigView::~PanelConfigView()
0233 {
0234 }
0235 
0236 void PanelConfigView::init()
0237 {
0238     m_sharedQmlEngine->setInitializationDelayed(true);
0239     m_sharedQmlEngine->setSource(m_containment->corona()->kPackage().fileUrl("panelconfigurationui"));
0240     m_sharedQmlEngine->completeInitialization({{QStringLiteral("panelConfiguration"), QVariant::fromValue(this)}});
0241     setMainItem(qobject_cast<QQuickItem *>(m_sharedQmlEngine->rootObject()));
0242     if (mainItem()) {
0243         if (m_panelRulerView) {
0244             QQuickItem *ruler = mainItem()->property("panelRuler").value<QQuickItem *>();
0245             m_panelRulerView->setMainItem(ruler);
0246             m_panelRulerView->syncPanelLocation();
0247         }
0248         auto syncSize = [this] {
0249             resize(mainItem()->implicitWidth() + leftPadding() + rightPadding(), mainItem()->implicitHeight() + topPadding() + bottomPadding());
0250         };
0251         connect(mainItem(), &QQuickItem::implicitWidthChanged, this, syncSize);
0252         syncSize();
0253         mainItem()->setVisible(true);
0254     }
0255     syncGeometry();
0256 }
0257 
0258 void PanelConfigView::showAddWidgetDialog()
0259 {
0260     QAction *addWidgetAction = m_containment->internalAction(QStringLiteral("add widgets"));
0261     if (addWidgetAction) {
0262         addWidgetAction->trigger();
0263     }
0264 }
0265 
0266 void PanelConfigView::addPanelSpacer()
0267 {
0268     ShellCorona *c = qobject_cast<ShellCorona *>(m_containment->corona());
0269     if (!c) {
0270         return;
0271     }
0272     // Add a spacer at the end *except* if there is exactly one spacer already
0273     // this to trigger the panel centering mode of the spacer in a slightly more discoverable way
0274     c->evaluateScript(QStringLiteral("panel = panelById(") + QString::number(m_containment->id())
0275                       + QStringLiteral(");"
0276                                        "var spacers = panel.widgets(\"org.kde.plasma.panelspacer\");"
0277                                        "if (spacers.length === 1) {"
0278                                        "    panel.addWidget(\"org.kde.plasma.panelspacer\", 0,0,1,1);"
0279                                        "} else {"
0280                                        "    panel.addWidget(\"org.kde.plasma.panelspacer\");"
0281                                        "}"));
0282 }
0283 
0284 void PanelConfigView::syncGeometry()
0285 {
0286     switch (m_containment->location()) {
0287     case Plasma::Types::TopEdge:
0288         setPopupDirection(Qt::BottomEdge);
0289         break;
0290     case Plasma::Types::LeftEdge:
0291         setPopupDirection(Qt::RightEdge);
0292         break;
0293     case Plasma::Types::RightEdge:
0294         setPopupDirection(Qt::LeftEdge);
0295         break;
0296     case Plasma::Types::BottomEdge:
0297     default:
0298         setPopupDirection(Qt::TopEdge);
0299     }
0300     queuePositionUpdate();
0301     update();
0302     if (m_panelRulerView) {
0303         m_panelRulerView->syncPanelLocation();
0304     }
0305 }
0306 
0307 void PanelConfigView::keyPressEvent(QKeyEvent *ev)
0308 {
0309     QQuickWindow::keyPressEvent(ev);
0310     if (ev->isAccepted()) {
0311         return;
0312     }
0313 
0314     if (ev->matches(QKeySequence::Cancel)) {
0315         ev->accept();
0316         hide();
0317     }
0318 }
0319 
0320 void PanelConfigView::showEvent(QShowEvent *ev)
0321 {
0322     if (m_containment) {
0323         m_containment->setUserConfiguring(true);
0324     }
0325     PopupPlasmaWindow::showEvent(ev);
0326 }
0327 
0328 void PanelConfigView::hideEvent(QHideEvent *ev)
0329 {
0330     PopupPlasmaWindow::hideEvent(ev);
0331 
0332     if (m_containment) {
0333         m_containment->setUserConfiguring(false);
0334     }
0335     deleteLater();
0336 }
0337 
0338 void PanelConfigView::focusVisibilityCheck(QWindow *focusWindow)
0339 {
0340     QWindow *oldFocusWindow = m_focusWindow;
0341     m_focusWindow = focusWindow;
0342 
0343     if (!focusWindow) {
0344         hide();
0345         return;
0346     }
0347     if (focusWindow == this || (m_panelRulerView && focusWindow == m_panelRulerView.get())) {
0348         return;
0349     }
0350     if (auto popup = qobject_cast<const PopupPlasmaWindow *>(focusWindow)) {
0351         if (auto parent = popup->visualParent(); parent && parent->window() == m_panelView) {
0352             return;
0353         }
0354     }
0355     if (auto popup = qobject_cast<const PopupPlasmaWindow *>(oldFocusWindow); popup && oldFocusWindow != this) {
0356         requestActivate();
0357         return;
0358     }
0359     // Don't close if the user is directly manipulating the panel
0360     if (m_panelView->mouseGrabberItem()) {
0361         return;
0362     }
0363     hide();
0364 }
0365 
0366 void PanelConfigView::focusInEvent(QFocusEvent *ev)
0367 {
0368     connect(qApp, &QGuiApplication::focusWindowChanged, this, &PanelConfigView::focusVisibilityCheck, Qt::UniqueConnection);
0369     PopupPlasmaWindow::focusInEvent(ev);
0370 }
0371 
0372 void PanelConfigView::setVisibilityMode(PanelView::VisibilityMode mode)
0373 {
0374     m_panelView->setVisibilityMode(mode);
0375     Q_EMIT visibilityModeChanged();
0376 }
0377 
0378 PanelView::VisibilityMode PanelConfigView::visibilityMode() const
0379 {
0380     return m_panelView->visibilityMode();
0381 }
0382 
0383 void PanelConfigView::setOpacityMode(PanelView::OpacityMode mode)
0384 {
0385     m_panelView->setOpacityMode(mode);
0386     Q_EMIT opacityModeChanged();
0387 }
0388 
0389 PanelView::OpacityMode PanelConfigView::opacityMode() const
0390 {
0391     return m_panelView->opacityMode();
0392 }
0393 
0394 KSvg::FrameSvg::EnabledBorders PanelConfigView::enabledBorders() const
0395 {
0396     return m_enabledBorders;
0397 }
0398 
0399 PanelRulerView *PanelConfigView::panelRulerView()
0400 {
0401     if (!m_panelRulerView) {
0402         m_panelRulerView = std::make_unique<PanelRulerView>(m_containment, m_panelView, this);
0403         // It's a queued connection because m_panelRulerView needs a bit to have the proper size after visibleChanged is emitted
0404         connect(
0405             m_panelRulerView.get(),
0406             &PanelRulerView::visibleChanged,
0407             this,
0408             [this](bool visible) {
0409                 if (visible) {
0410                     setMargin(std::min(m_panelRulerView->width(), m_panelRulerView->height()) + 4);
0411                 } else {
0412                     setMargin(4);
0413                 }
0414             },
0415             Qt::QueuedConnection);
0416     }
0417 
0418     if (mainItem()) {
0419         QQuickItem *ruler = mainItem()->property("panelRuler").value<QQuickItem *>();
0420         m_panelRulerView->setMainItem(ruler);
0421         m_panelRulerView->syncPanelLocation();
0422     }
0423     return m_panelRulerView.get();
0424 }
0425 
0426 #include "moc_panelconfigview.cpp"