From 84d4d4cef7e3537c5c652050cdbefa4f05d655e9 Mon Sep 17 00:00:00 2001 From: Michail Vourlakos Date: Sat, 16 Jan 2021 19:12:42 +0200 Subject: [PATCH] provide latte internal widget explorer --- app/package/lattepackage.cpp | 1 + app/view/settings/CMakeLists.txt | 1 + app/view/settings/subconfigview.h | 4 +- app/view/settings/viewsettingsfactory.cpp | 12 + app/view/settings/viewsettingsfactory.h | 3 + app/view/settings/widgetexplorerview.cpp | 287 +++++++++++++ app/view/settings/widgetexplorerview.h | 105 +++++ app/view/view.cpp | 12 + app/view/view.h | 1 + containmentactions/contextmenu/menu.cpp | 13 +- containmentactions/contextmenu/menu.h | 3 +- .../package/contents/views/AppletDelegate.qml | 236 +++++++++++ .../package/contents/views/WidgetExplorer.qml | 389 ++++++++++++++++++ 13 files changed, 1061 insertions(+), 6 deletions(-) create mode 100644 app/view/settings/widgetexplorerview.cpp create mode 100644 app/view/settings/widgetexplorerview.h create mode 100644 shell/package/contents/views/AppletDelegate.qml create mode 100644 shell/package/contents/views/WidgetExplorer.qml diff --git a/app/package/lattepackage.cpp b/app/package/lattepackage.cpp index c9bcbbb26..4bb1e8a9d 100644 --- a/app/package/lattepackage.cpp +++ b/app/package/lattepackage.cpp @@ -45,6 +45,7 @@ void Package::initPackage(KPackage::Package *package) package->setPath("org.kde.latte.shell"); package->addFileDefinition("defaults", QStringLiteral("defaults"), i18n("Latte Dock defaults")); package->addFileDefinition("lattedockui", QStringLiteral("views/Panel.qml"), i18n("Latte Dock panel")); + package->addFileDefinition("widgetexplorerui", QStringLiteral("views/WidgetExplorer.qml"), i18n("Widget Explorer")); //Configuration package->addFileDefinition("lattedockconfigurationui", QStringLiteral("configuration/LatteDockConfiguration.qml"), i18n("Dock configuration UI")); package->addFileDefinition("lattedocksecondaryconfigurationui", QStringLiteral("configuration/LatteDockSecondaryConfiguration.qml"), i18n("Dock secondary configuration UI")); diff --git a/app/view/settings/CMakeLists.txt b/app/view/settings/CMakeLists.txt index 3d67a5112..82f21094c 100644 --- a/app/view/settings/CMakeLists.txt +++ b/app/view/settings/CMakeLists.txt @@ -6,5 +6,6 @@ set(lattedock-app_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/secondaryconfigview.cpp ${CMAKE_CURRENT_SOURCE_DIR}/subconfigview.cpp ${CMAKE_CURRENT_SOURCE_DIR}/viewsettingsfactory.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/widgetexplorerview.cpp PARENT_SCOPE ) diff --git a/app/view/settings/subconfigview.h b/app/view/settings/subconfigview.h index 4611bbbcd..8cd7ece6f 100644 --- a/app/view/settings/subconfigview.h +++ b/app/view/settings/subconfigview.h @@ -78,7 +78,7 @@ signals: void enabledBordersChanged(); protected: - void syncSlideEffect(); + virtual void syncSlideEffect(); virtual void init(); virtual void initParentView(Latte::View *view); @@ -87,7 +87,7 @@ protected: void showEvent(QShowEvent *ev) override; bool event(QEvent *e) override; - Qt::WindowFlags wFlags() const; + virtual Qt::WindowFlags wFlags() const; protected: bool m_isNormalWindow{true}; diff --git a/app/view/settings/viewsettingsfactory.cpp b/app/view/settings/viewsettingsfactory.cpp index 1b6afda4f..b696dcd74 100644 --- a/app/view/settings/viewsettingsfactory.cpp +++ b/app/view/settings/viewsettingsfactory.cpp @@ -20,6 +20,7 @@ #include "viewsettingsfactory.h" #include "primaryconfigview.h" +#include "widgetexplorerview.h" #include "../view.h" // Plasma @@ -81,5 +82,16 @@ ViewPart::PrimaryConfigView *ViewSettingsFactory::primaryConfigView(Latte::View return m_primaryConfigView; } +ViewPart::WidgetExplorerView *ViewSettingsFactory::widgetExplorerView(Latte::View *view) +{ + if (!m_widgetExplorerView) { + m_widgetExplorerView = new ViewPart::WidgetExplorerView(view); + } else { + m_widgetExplorerView->setParentView(view); + } + + return m_widgetExplorerView; +} + } diff --git a/app/view/settings/viewsettingsfactory.h b/app/view/settings/viewsettingsfactory.h index c1525492b..799ca4f95 100644 --- a/app/view/settings/viewsettingsfactory.h +++ b/app/view/settings/viewsettingsfactory.h @@ -34,6 +34,7 @@ class View; namespace ViewPart { class PrimaryConfigView; +class WidgetExplorerView; } } @@ -55,9 +56,11 @@ public: Plasma::Containment *lastContainment(); ViewPart::PrimaryConfigView *primaryConfigView(Latte::View *view); + ViewPart::WidgetExplorerView *widgetExplorerView(Latte::View *view); private: QPointer m_primaryConfigView; + QPointer m_widgetExplorerView; QPointer m_lastContainment; }; diff --git a/app/view/settings/widgetexplorerview.cpp b/app/view/settings/widgetexplorerview.cpp new file mode 100644 index 000000000..a03764ddf --- /dev/null +++ b/app/view/settings/widgetexplorerview.cpp @@ -0,0 +1,287 @@ +/* +* Copyright 2021 Michail Vourlakos +* +* This file is part of Latte-Dock +* +* Latte-Dock is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* Latte-Dock is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "widgetexplorerview.h" + +// local +#include "../panelshadows_p.h" +#include "../view.h" +#include "../../lattecorona.h" +#include "../../wm/abstractwindowinterface.h" + +// Qt +#include +#include + +// KDE +#include +#include +#include + +// Plasma +#include + +namespace Latte { +namespace ViewPart { + +WidgetExplorerView::WidgetExplorerView(Latte::View *view) + : SubConfigView(view, QString("#widgetexplorerview#"), true) +{ + setResizeMode(QQuickView::SizeRootObjectToView); + + connect(this, &QQuickView::widthChanged, this, &WidgetExplorerView::updateEffects); + connect(this, &QQuickView::heightChanged, this, &WidgetExplorerView::updateEffects); + + connect(this, &QQuickView::statusChanged, [&](QQuickView::Status status) { + if (status == QQuickView::Ready) { + updateEffects(); + } + }); + + setParentView(view); + init(); +} + +void WidgetExplorerView::init() +{ + SubConfigView::init(); + + QByteArray tempFilePath = "widgetexplorerui"; + + updateEnabledBorders(); + + auto source = QUrl::fromLocalFile(m_latteView->containment()->corona()->kPackage().filePath(tempFilePath)); + setSource(source); + syncGeometry(); +} + +bool WidgetExplorerView::hideOnWindowDeactivate() const +{ + return m_hideOnWindowDeactivate; +} + +void WidgetExplorerView::setHideOnWindowDeactivate(bool hide) +{ + if (m_hideOnWindowDeactivate == hide) { + return; + } + + m_hideOnWindowDeactivate = hide; + emit hideOnWindowDeactivateChanged(); +} + +Qt::WindowFlags WidgetExplorerView::wFlags() const +{ + return (flags() | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); +} + +QRect WidgetExplorerView::geometryWhenVisible() const +{ + return m_geometryWhenVisible; +} + +void WidgetExplorerView::initParentView(Latte::View *view) +{ + SubConfigView::initParentView(view); + + rootContext()->setContextProperty(QStringLiteral("containmentFromView"), m_latteView->containment()); + + updateEnabledBorders(); + syncGeometry(); +} + +QRect WidgetExplorerView::availableScreenGeometry() const +{ + int currentScrId = m_latteView->positioner()->currentScreenId(); + + QList ignoreModes{Latte::Types::SidebarOnDemand,Latte::Types::SidebarAutoHide}; + + if (m_latteView->visibility() && m_latteView->visibility()->isSidebar()) { + ignoreModes.removeAll(Latte::Types::SidebarOnDemand); + ignoreModes.removeAll(Latte::Types::SidebarAutoHide); + } + + QString activityid = m_latteView->layout()->lastUsedActivity(); + + return m_corona->availableScreenRectWithCriteria(currentScrId, activityid, ignoreModes, {}, false, true); +} + +void WidgetExplorerView::syncGeometry() +{ + if (!m_latteView || !m_latteView->layout() || !m_latteView->containment() || !rootObject()) { + return; + } + const QSize size(rootObject()->width(), rootObject()->height()); + auto availGeometry = availableScreenGeometry(); + + int margin = availGeometry.height() == m_latteView->screenGeometry().height() ? 100 : 0; + auto geometry = QRect(availGeometry.x(), availGeometry.y(), size.width(), availGeometry.height()-margin); + + updateEnabledBorders(); + + if (m_geometryWhenVisible == geometry) { + return; + } + + m_geometryWhenVisible = geometry; + + setPosition(geometry.topLeft()); + + if (m_shellSurface) { + m_shellSurface->setPosition(geometry.topLeft()); + } + + setMaximumSize(geometry.size()); + setMinimumSize(geometry.size()); + resize(geometry.size()); +} + +void WidgetExplorerView::showEvent(QShowEvent *ev) +{ + if (m_shellSurface) { + //! under wayland it needs to be set again after its hiding + m_shellSurface->setPosition(m_geometryWhenVisible.topLeft()); + } + + SubConfigView::showEvent(ev); + + if (!m_latteView) { + return; + } + + syncGeometry(); + + requestActivate(); + + m_screenSyncTimer.start(); + QTimer::singleShot(400, this, &WidgetExplorerView::syncGeometry); + + emit showSignal(); +} + +void WidgetExplorerView::focusOutEvent(QFocusEvent *ev) +{ + Q_UNUSED(ev); + + if (!m_latteView) { + return; + } + + hideConfigWindow(); +} + +void WidgetExplorerView::updateEffects() +{ + //! Don't apply any effect before the wayland surface is created under wayland + //! https://bugs.kde.org/show_bug.cgi?id=392890 + if (KWindowSystem::isPlatformWayland() && !m_shellSurface) { + return; + } + + if (!m_background) { + m_background = new Plasma::FrameSvg(this); + } + + if (m_background->imagePath() != "dialogs/background") { + m_background->setImagePath(QStringLiteral("dialogs/background")); + } + + m_background->setEnabledBorders(m_enabledBorders); + m_background->resizeFrame(size()); + + QRegion mask = m_background->mask(); + + QRegion fixedMask = mask.isNull() ? QRegion(QRect(0,0,width(),height())) : mask; + + if (!fixedMask.isEmpty()) { + setMask(fixedMask); + } else { + setMask(QRegion()); + } + + if (KWindowSystem::compositingActive()) { + KWindowEffects::enableBlurBehind(winId(), true, fixedMask); + } else { + KWindowEffects::enableBlurBehind(winId(), false); + } +} + +void WidgetExplorerView::hideConfigWindow() +{ + if (!m_hideOnWindowDeactivate) { + return; + } + + if (m_shellSurface) { + //!NOTE: Avoid crash in wayland environment with qt5.9 + close(); + } else { + hide(); + } +} + +void WidgetExplorerView::syncSlideEffect() +{ + if (!m_latteView || !m_latteView->containment()) { + return; + } + + auto slideLocation = WindowSystem::AbstractWindowInterface::Slide::Left; + + m_corona->wm()->slideWindow(*this, slideLocation); +} + +//!BEGIN borders +void WidgetExplorerView::updateEnabledBorders() +{ + if (!this->screen()) { + return; + } + + Plasma::FrameSvg::EnabledBorders borders = Plasma::FrameSvg::AllBorders; + + if (!m_geometryWhenVisible.isEmpty()) { + if (m_geometryWhenVisible.x() == m_latteView->screenGeometry().x()) { + borders &= ~Plasma::FrameSvg::LeftBorder; + } + + if (m_geometryWhenVisible.y() == m_latteView->screenGeometry().y()) { + borders &= ~Plasma::FrameSvg::TopBorder; + } + + if (m_geometryWhenVisible.height() == m_latteView->screenGeometry().height()) { + borders &= ~Plasma::FrameSvg::BottomBorder; + } + } + + if (m_enabledBorders != borders) { + if (isVisible()) { + m_enabledBorders = borders; + } + m_corona->dialogShadows()->addWindow(this, m_enabledBorders); + + emit enabledBordersChanged(); + } +} + +//!END borders + +} +} + diff --git a/app/view/settings/widgetexplorerview.h b/app/view/settings/widgetexplorerview.h new file mode 100644 index 000000000..31faebb0a --- /dev/null +++ b/app/view/settings/widgetexplorerview.h @@ -0,0 +1,105 @@ +/* +* Copyright 2021 Michail Vourlakos +* +* This file is part of Latte-Dock +* +* Latte-Dock is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License as +* published by the Free Software Foundation; either version 2 of +* the License, or (at your option) any later version. +* +* Latte-Dock is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#ifndef WIDGETEXPLORERVIEW_H +#define WIDGETEXPLORERVIEW_H + +// local +#include "subconfigview.h" + +//Qt +#include +#include +#include +#include + +// Plasma +#include +#include + + +namespace Plasma { +class Applet; +class Containment; +class FrameSvg; +class Types; +} + +namespace KWayland { +namespace Client { +class PlasmaShellSurface; +} +} + +namespace Latte { +class Corona; +class View; +} + +namespace Latte { +namespace ViewPart { + +class WidgetExplorerView : public SubConfigView +{ + Q_OBJECT + Q_PROPERTY(bool hideOnWindowDeactivate READ hideOnWindowDeactivate WRITE setHideOnWindowDeactivate NOTIFY hideOnWindowDeactivateChanged) + +public: + WidgetExplorerView(Latte::View *view); + + bool hideOnWindowDeactivate() const; + void setHideOnWindowDeactivate(bool hide); + + QRect geometryWhenVisible() const; + +public slots: + Q_INVOKABLE void hideConfigWindow(); + Q_INVOKABLE void syncGeometry() override; + Q_INVOKABLE void updateEffects(); + +signals: + void hideOnWindowDeactivateChanged(); + void showSignal(); + +protected: + void showEvent(QShowEvent *ev) override; + void syncSlideEffect() override; + void focusOutEvent(QFocusEvent *ev) override; + + void init() override; + void initParentView(Latte::View *view) override; + void updateEnabledBorders() override; + + Qt::WindowFlags wFlags() const override; + +private: + QRect availableScreenGeometry() const; + +private: + bool m_hideOnWindowDeactivate{true}; + QRect m_geometryWhenVisible; + + //only for the mask on disabled compositing, not to actually paint + Plasma::FrameSvg *m_background{nullptr}; +}; + +} +} +#endif //WIDGETEXPLORERVIEW_H + diff --git a/app/view/view.cpp b/app/view/view.cpp index 82d91e644..464880708 100644 --- a/app/view/view.cpp +++ b/app/view/view.cpp @@ -28,6 +28,7 @@ #include "settings/primaryconfigview.h" #include "settings/secondaryconfigview.h" #include "settings/viewsettingsfactory.h" +#include "settings/widgetexplorerview.h" #include "../apptypes.h" #include "../lattecorona.h" #include "../data/layoutdata.h" @@ -178,6 +179,7 @@ View::View(Plasma::Corona *corona, QScreen *targetScreen, bool byPassWM) } connect(this->containment(), SIGNAL(statusChanged(Plasma::Types::ItemStatus)), SLOT(statusChanged(Plasma::Types::ItemStatus))); + connect(this->containment(), &Plasma::Containment::showAddWidgetsInterface, this, &View::showWidgetExplorer); connect(this->containment(), &Plasma::Containment::userConfiguringChanged, this, [&]() { emit inEditModeChanged(); }); @@ -523,6 +525,16 @@ void View::showConfigurationInterface(Plasma::Applet *applet) } } +void View::showWidgetExplorer(const QPointF &point) +{ + auto widgetExplorerView = m_corona->viewSettingsFactory()->widgetExplorerView(this); + + if (!widgetExplorerView->isVisible()) { + // widgetExplorerView->syncSlideEffect(); + widgetExplorerView->showAfter(400); + } +} + QRect View::localGeometry() const { return m_localGeometry; diff --git a/app/view/view.h b/app/view/view.h index e58993ee8..d37402482 100644 --- a/app/view/view.h +++ b/app/view/view.h @@ -279,6 +279,7 @@ public slots: protected slots: void showConfigurationInterface(Plasma::Applet *applet) override; + void showWidgetExplorer(const QPointF &point); protected: bool event(QEvent *ev) override; diff --git a/containmentactions/contextmenu/menu.cpp b/containmentactions/contextmenu/menu.cpp index 6d8f632a3..c034f2d68 100644 --- a/containmentactions/contextmenu/menu.cpp +++ b/containmentactions/contextmenu/menu.cpp @@ -88,14 +88,15 @@ void Menu::makeActions() }); m_addWidgetsAction = new QAction(QIcon::fromTheme("list-add"), i18n("&Add Widgets..."), this); - m_addWidgetsAction->setStatusTip(i18n("Show Plasma Widget Explorer")); - connect(m_addWidgetsAction, &QAction::triggered, [ = ]() { + m_addWidgetsAction->setStatusTip(i18n("Show Widget Explorer")); + connect(m_addWidgetsAction, &QAction::triggered, this, &Menu::requestWidgetExplorer); + /*connect(m_addWidgetsAction, &QAction::triggered, [ = ]() { QDBusInterface iface("org.kde.plasmashell", "/PlasmaShell", "", QDBusConnection::sessionBus()); if (iface.isValid()) { iface.call("toggleWidgetExplorer"); } - }); + });*/ m_configureAction = new QAction(QIcon::fromTheme("document-edit"), i18nc("view settings window", "View &Settings..."), this); connect(m_configureAction, &QAction::triggered, this, &Menu::requestConfiguration); @@ -137,6 +138,12 @@ void Menu::requestConfiguration() } } +void Menu::requestWidgetExplorer() +{ + if (this->containment()) { + emit this->containment()->showAddWidgetsInterface(QPointF()); + } +} QList Menu::contextualActions() { diff --git a/containmentactions/contextmenu/menu.h b/containmentactions/contextmenu/menu.h index c73f690a1..fcb937c16 100644 --- a/containmentactions/contextmenu/menu.h +++ b/containmentactions/contextmenu/menu.h @@ -43,8 +43,9 @@ public: private Q_SLOTS: void makeActions(); void populateLayouts(); - void requestConfiguration(); void quitApplication(); + void requestConfiguration(); + void requestWidgetExplorer(); void switchToLayout(QAction *action); diff --git a/shell/package/contents/views/AppletDelegate.qml b/shell/package/contents/views/AppletDelegate.qml new file mode 100644 index 000000000..0248ea094 --- /dev/null +++ b/shell/package/contents/views/AppletDelegate.qml @@ -0,0 +1,236 @@ +/* + * Copyright 2011 Marco Martin + * Copyright 2015 Kai Uwe Broulik + * Copyright 2021 Michail Vourlakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.4 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.draganddrop 2.0 + +Item { + id: delegate + + readonly property string pluginName: model.pluginName + readonly property bool pendingUninstall: pendingUninstallTimer.applets.indexOf(pluginName) > -1 + + width: list.cellWidth + height: list.cellHeight + + DragArea { + anchors.fill: parent + supportedActions: Qt.MoveAction | Qt.LinkAction + //onDragStarted: tooltipDialog.visible = false + delegateImage: decoration + enabled: !delegate.pendingUninstall + mimeData { + source: parent + } + Component.onCompleted: mimeData.setData("text/x-plasmoidservicename", pluginName) + + onDragStarted: { + kwindowsystem.showingDesktop = true; + main.draggingWidget = true; + } + onDrop: { + main.draggingWidget = false; + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + onDoubleClicked: { + if (!delegate.pendingUninstall) { + widgetExplorer.addApplet(pluginName) + } + } + onEntered: delegate.GridView.view.currentIndex = index + onExited: delegate.GridView.view.currentIndex = - 1 + } + + ColumnLayout { + id: mainLayout + spacing: units.smallSpacing + anchors { + left: parent.left + right: parent.right + //bottom: parent.bottom + margins: units.smallSpacing * 2 + rightMargin: units.smallSpacing * 2 // don't cram the text to the border too much + top: parent.top + } + + Item { + id: iconContainer + width: units.iconSizes.enormous + height: width + Layout.alignment: Qt.AlignHCenter + opacity: delegate.pendingUninstall ? 0.6 : 1 + Behavior on opacity { + OpacityAnimator { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } + + Item { + id: iconWidget + anchors.fill: parent + PlasmaCore.IconItem { + anchors.fill: parent + source: model.decoration + visible: model.screenshot === "" + } + Image { + width: units.iconSizes.enormous + height: width + anchors.fill: parent + fillMode: Image.PreserveAspectFit + source: model.screenshot + } + } + + Item { + id: badgeMask + anchors.fill: parent + + Rectangle { + x: Math.round(-units.smallSpacing * 1.5 / 2) + y: x + width: runningBadge.width + Math.round(units.smallSpacing * 1.5) + height: width + radius: height + visible: running && delegate.GridView.isCurrentItem + } + } + + Rectangle { + id: runningBadge + width: height + height: Math.round(theme.mSize(countLabel.font).height * 1.3) + radius: height + color: theme.highlightColor + visible: running && delegate.GridView.isCurrentItem + onVisibleChanged: maskShaderSource.scheduleUpdate() + + PlasmaComponents.Label { + id: countLabel + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: running + } + } + + ShaderEffect { + anchors.fill: parent + property var source: ShaderEffectSource { + sourceItem: iconWidget + hideSource: true + live: false + } + property var mask: ShaderEffectSource { + id: maskShaderSource + sourceItem: badgeMask + hideSource: true + live: false + } + + supportsAtlasTextures: true + + fragmentShader: " + varying highp vec2 qt_TexCoord0; + uniform highp float qt_Opacity; + uniform lowp sampler2D source; + uniform lowp sampler2D mask; + void main() { + gl_FragColor = texture2D(source, qt_TexCoord0.st) * (1.0 - (texture2D(mask, qt_TexCoord0.st).a)) * qt_Opacity; + } + " + } + + PlasmaComponents.ToolButton { + id: uninstallButton + anchors { + top: parent.top + right: parent.right + } + iconSource: delegate.pendingUninstall ? "edit-undo" : "edit-delete" + // we don't really "undo" anything but we'll pretend to the user that we do + tooltip: delegate.pendingUninstall ? i18nd("plasma_shell_org.kde.plasma.desktop", "Undo uninstall") + : i18nd("plasma_shell_org.kde.plasma.desktop", "Uninstall widget") + flat: false + visible: model.local && delegate.GridView.isCurrentItem + + onHoveredChanged: { + if (hovered) { + // hovering the uninstall button triggers onExited of the main mousearea + delegate.GridView.view.currentIndex = index + } + } + + onClicked: { + var pending = installTimer.applets + if (delegate.pendingUninstall) { + var index = pending.indexOf(pluginName) + if (index > -1) { + pending.splice(index, 1) + } + } else { + pending.push(pluginName) + } + pendingUninstallTimer.applets = pending + + if (pending.length) { + pendingUninstallTimer.restart() + } else { + pendingUninstallTimer.stop() + } + } + } + } + PlasmaExtras.Heading { + id: heading + Layout.fillWidth: true + level: 4 + text: model.name + elide: Text.ElideRight + wrapMode: Text.WordWrap + maximumLineCount: 2 + lineHeight: 0.95 + horizontalAlignment: Text.AlignHCenter + } + PlasmaComponents.Label { + Layout.fillWidth: true + // otherwise causes binding loop due to the way the Plasma sets the height + height: implicitHeight + text: model.description + font: theme.smallestFont + wrapMode: Text.WordWrap + elide: Text.ElideRight + maximumLineCount: heading.lineCount === 1 ? 3 : 2 + horizontalAlignment: Text.AlignHCenter + } + } + } +} diff --git a/shell/package/contents/views/WidgetExplorer.qml b/shell/package/contents/views/WidgetExplorer.qml new file mode 100644 index 000000000..d8d475803 --- /dev/null +++ b/shell/package/contents/views/WidgetExplorer.qml @@ -0,0 +1,389 @@ +/* + * Copyright 2011 Marco Martin + * Copyright 2021 Michail Vourlakos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +import QtQuick 2.7 +import QtQuick.Controls 2.5 as QQC2 + +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras +import org.kde.kquickcontrolsaddons 2.0 +import org.kde.kwindowsystem 1.0 + +import QtQuick.Window 2.1 +import QtQuick.Layouts 1.1 + +import org.kde.plasma.private.shell 2.0 as PlasmaShell + +Item { + id: main + width: Math.max(heading.paintedWidth, units.iconSizes.enormous * 3 + units.smallSpacing * 4 + units.gridUnit * 2) + // height: 800//Screen.height + + opacity: draggingWidget ? 0.3 : 1 + + //property QtObject containment + + //external drop events can cause a raise event causing us to lose focus and + //therefore get deleted whilst we are still in a drag exec() + //this is a clue to the owning dialog that hideOnWindowDeactivate should be deleted + //See https://bugs.kde.org/show_bug.cgi?id=332733 + property bool preventWindowHide: draggingWidget + || categoriesDialog.status !== PlasmaComponents.DialogStatus.Closed + || getWidgetsDialog.status !== PlasmaComponents.DialogStatus.Closed + + property bool outputOnly: draggingWidget + + property Item categoryButton + + property bool draggingWidget: false + + signal closed(); + + onClosed: { + if (main.preventWindowHide) { + return; + } + + viewConfig.hideConfigWindow(); + } + + onVisibleChanged: { + if (!visible) { + kwindowsystem.showingDesktop = false + } + } + + Component.onDestruction: { + if (pendingUninstallTimer.running) { + // we're not being destroyed so at least reset the filters + widgetExplorer.widgetsModel.filterQuery = "" + widgetExplorer.widgetsModel.filterType = "" + widgetExplorer.widgetsModel.searchTerm = "" + } + } + + function addCurrentApplet() { + var pluginName = list.currentItem ? list.currentItem.pluginName : "" + if (pluginName) { + widgetExplorer.addApplet(pluginName) + } + } + + KWindowSystem { + id: kwindowsystem + } + + QQC2.Action { + shortcut: "Escape" + onTriggered: { + if (searchInput.length > 0) { + searchInput.text = "" + } else { + main.closed(); + } + } + } + + QQC2.Action { + shortcut: "Up" + onTriggered: list.currentIndex = (list.count + list.currentIndex - 1) % list.count + } + + QQC2.Action { + shortcut: "Down" + onTriggered: list.currentIndex = (list.currentIndex + 1) % list.count + } + + QQC2.Action { + shortcut: "Enter" + onTriggered: addCurrentApplet() + } + + QQC2.Action { + shortcut: "Return" + onTriggered: addCurrentApplet() + } + + PlasmaCore.FrameSvgItem{ + id: backgroundFrameSvgItem + anchors.fill: parent + imagePath: "dialogs/background" + enabledBorders: viewConfig.enabledBorders + + onEnabledBordersChanged: viewConfig.updateEffects() + Component.onCompleted: viewConfig.updateEffects() + } + + + PlasmaShell.WidgetExplorer { + id:widgetExplorer + //view: desktop + containment: containmentFromView + onShouldClose: main.closed(); + } + + + PlasmaComponents.ModelContextMenu { + id: categoriesDialog + visualParent: categoryButton + // model set on first invocation + + onClicked: { + list.contentX = 0 + list.contentY = 0 + categoryButton.text = (model.filterData ? model.display : i18nd("plasma_shell_org.kde.plasma.desktop", "All Widgets")) + widgetExplorer.widgetsModel.filterQuery = model.filterData + widgetExplorer.widgetsModel.filterType = model.filterType + } + } + + PlasmaComponents.ModelContextMenu { + id: getWidgetsDialog + visualParent: getWidgetsButton + placement: PlasmaCore.Types.TopPosedLeftAlignedPopup + // model set on first invocation + onClicked: model.trigger() + } + /* + PlasmaCore.Dialog { + id: tooltipDialog + property Item appletDelegate + location: PlasmaCore.Types.RightEdge //actually we want this to be the opposite location of the explorer itself + type: PlasmaCore.Dialog.Tooltip + flags:Qt.Window|Qt.WindowStaysOnTopHint|Qt.X11BypassWindowManagerHint + onAppletDelegateChanged: { + if (!appletDelegate) { + toolTipHideTimer.restart() + toolTipShowTimer.running = false + } else if (tooltipDialog.visible) { + tooltipDialog.visualParent = appletDelegate + } else { + tooltipDialog.visualParent = appletDelegate + toolTipShowTimer.restart() + toolTipHideTimer.running = false + } + } + mainItem: Tooltip { id: tooltipWidget } + Behavior on y { + NumberAnimation { duration: units.longDuration } + } + } + Timer { + id: toolTipShowTimer + interval: 500 + repeat: false + onTriggered: { + tooltipDialog.visible = true + } + } + Timer { + id: toolTipHideTimer + interval: 1000 + repeat: false + onTriggered: tooltipDialog.visible = false + } + */ + + + PlasmaExtras.PlasmoidHeading { + id: topArea + implicitWidth: header.implicitWidth + implicitHeight: header.implicitHeight + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + ColumnLayout { + id: header + anchors.fill: parent + + RowLayout { + PlasmaExtras.Heading { + id: heading + level: 1 + text: i18nd("plasma_shell_org.kde.plasma.desktop", "Widgets") + elide: Text.ElideRight + + Layout.fillWidth: true + } + PlasmaComponents.ToolButton { + id: getWidgetsButton + iconSource: "get-hot-new-stuff" + text: i18nd("plasma_shell_org.kde.plasma.desktop", "Get New Widgets...") + onClicked: { + getWidgetsDialog.model = widgetExplorer.widgetsMenuActions + getWidgetsDialog.openRelative() + } + } + PlasmaComponents.ToolButton { + id: closeButton + iconSource: "window-close" + onClicked: main.closed() + } + } + + RowLayout { + PlasmaComponents.TextField { + id: searchInput + Layout.fillWidth: true + clearButtonShown: true + placeholderText: i18nd("plasma_shell_org.kde.plasma.desktop", "Search...") + onTextChanged: { + list.positionViewAtBeginning() + list.currentIndex = -1 + widgetExplorer.widgetsModel.searchTerm = text + } + + Component.onCompleted: forceActiveFocus() + } + PlasmaComponents.ToolButton { + id: categoryButton + tooltip: i18nd("plasma_shell_org.kde.plasma.desktop", "Categories") + text: i18nd("plasma_shell_org.kde.plasma.desktop", "All Widgets") + iconSource: "view-filter" + onClicked: { + categoriesDialog.model = widgetExplorer.filterModel + categoriesDialog.open(0, categoryButton.height) + } + } + } + + Item { + height: units.smallSpacing + } + } + } + + Timer { + id: setModelTimer + interval: 20 + running: true + onTriggered: list.model = widgetExplorer.widgetsModel + } + + PlasmaExtras.ScrollArea { + anchors { + top: topArea.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn + + // hide the flickering by fading in nicely + opacity: setModelTimer.running ? 0 : 1 + Behavior on opacity { + OpacityAnimator { + duration: units.longDuration + easing.type: Easing.InOutQuad + } + } + + GridView { + id: list + + // model set delayed by Timer above + + activeFocusOnTab: true + keyNavigationWraps: true + cellWidth: Math.floor((width - units.smallSpacing) / 3) + cellHeight: cellWidth + units.gridUnit * 4 + units.smallSpacing * 2 + + delegate: AppletDelegate {} + highlight: PlasmaComponents.Highlight {} + highlightMoveDuration: 0 + //highlightResizeDuration: 0 + + //slide in to view from the left + add: Transition { + NumberAnimation { + properties: "x" + from: -list.width + duration: units.shortDuration + } + } + + //slide out of view to the right + remove: Transition { + NumberAnimation { + properties: "x" + to: list.width + duration: units.shortDuration + } + } + + //if we are adding other items into the view use the same animation as normal adding + //this makes everything slide in together + //if we make it move everything ends up weird + addDisplaced: list.add + + //moved due to filtering + displaced: Transition { + NumberAnimation { + properties: "x,y" + duration: units.shortDuration + } + } + + PlasmaExtras.Heading { + anchors.fill: parent + anchors.margins: units.largeSpacing + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + wrapMode: Text.WordWrap + level: 2 + text: searchInput.text.length > 0 ? i18n("No widgets matched the search terms") : i18n("No widgets available") + enabled: false + visible: list.count == 0 + } + } + } + + //! Bindings + Binding{ + target: viewConfig + property: "hideOnWindowDeactivate" + value: !main.preventWindowHide + } + + //! Timers + Timer { + id: pendingUninstallTimer + // keeps track of the applets the user wants to uninstall + property var applets: [] + + interval: 60000 // one minute + onTriggered: { + for (var i = 0, length = applets.length; i < length; ++i) { + widgetExplorer.uninstall(applets[i]) + } + applets = [] + + /*if (sidePanelStack.state !== "widgetExplorer" && widgetExplorer) { + widgetExplorer.destroy() + widgetExplorer = null + }*/ + } + } +}