/* * Copyright (C) 2007 by Ivan Cukic * Copyright (C) 2009 by Ana CecĂ­lia Martins * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, as published by the * Free Software Foundation * * 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/Lesser 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. */ #include "widgetexplorer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Plasma { // hardcoded but ok because the widget is not resizable, that implies issue with big font sizes tho static const int s_margin = 4; static const QSizeF s_appletframesize = QSize(300, 100); static const QSizeF s_appleticonsize = QSize(86, 86); static const int s_filterwidth = 305; static const int s_dragpixmapsize = 64; static const int s_appletactiveiconsize = 22; // the default Plasma::Corona MIME type, see: // kdelibs/plasma/corona.cpp static const QString s_coronamimetype = QString::fromLatin1("text/x-plasmoidservicename"); static Qt::Orientation kOrientationForLocation(const Plasma::Location location) { switch (location) { case Plasma::Location::LeftEdge: case Plasma::Location::RightEdge: { return Qt::Vertical; } default: { return Qt::Horizontal; } } Q_UNREACHABLE(); } // similar to Plasma::Applet::view(), the important thing is to get a window (preferably active // one) static QGraphicsView* kSceneWindow(const QGraphicsScene *scene) { if (!scene) { kWarning() << "No WidgetExplorer scene"; return nullptr; } foreach (QGraphicsView *view, scene->views()) { if (view->isActiveWindow()) { return view; } } return nullptr; } class AppletIcon : public Plasma::IconWidget { Q_OBJECT public: AppletIcon(QGraphicsItem *parent, const KPluginInfo &appletInfo); protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) final; void mouseMoveEvent(QGraphicsSceneMouseEvent *event) final; private: KPluginInfo m_appletinfo; QPointF m_dragstartpos; }; AppletIcon::AppletIcon(QGraphicsItem *parent, const KPluginInfo &appletInfo) : Plasma::IconWidget(parent), m_appletinfo(appletInfo) { } void AppletIcon::mousePressEvent(QGraphicsSceneMouseEvent *event) { m_dragstartpos = event->pos(); // don't propagate event->accept(); } void AppletIcon::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if (event->buttons() & Qt::LeftButton && (event->pos() - m_dragstartpos).manhattanLength() > KGlobalSettings::dndEventDelay()) { QGraphicsView* sceneview = kSceneWindow(scene()); QDrag* drag = new QDrag(sceneview ? sceneview : qApp->activeWindow()); QMimeData* mimedata = new QMimeData(); mimedata->setData(s_coronamimetype, m_appletinfo.pluginName().toUtf8()); drag->setMimeData(mimedata); drag->setPixmap(KIcon(m_appletinfo.icon()).pixmap(s_dragpixmapsize)); static const int halfdragpixmapsize = (s_dragpixmapsize / 2); drag->setHotSpot(QPoint(halfdragpixmapsize, halfdragpixmapsize)); setPressed(true); drag->exec(); setPressed(false); } } class AppletFrame : public Plasma::Frame { Q_OBJECT public: AppletFrame(QGraphicsWidget *parent, const KPluginInfo &appletInfo); KPluginInfo pluginInfo() const; void setRunning(const bool isrunning); Q_SIGNALS: void addApplet(const QString &applet); void removeApplet(const QString &applet); private Q_SLOTS: void slotAddApplet(); void slotRemoveApplet(); void slotUpdateFonts(); private: KPluginInfo m_appletinfo; AppletIcon* m_appleticon; Plasma::Label* m_appletname; Plasma::IconWidget* m_appletactive; Plasma::Label* m_appletcomment; }; AppletFrame::AppletFrame(QGraphicsWidget *parent, const KPluginInfo &appletInfo) : Plasma::Frame(parent), m_appletinfo(appletInfo), m_appleticon(nullptr), m_appletname(nullptr), m_appletactive(nullptr), m_appletcomment(nullptr) { setFrameShadow(Plasma::Frame::Sunken); QGraphicsGridLayout* appletLayout = new QGraphicsGridLayout(this); m_appleticon = new AppletIcon(this, appletInfo); m_appleticon->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_appleticon->setMinimumSize(s_appleticonsize); m_appleticon->setMaximumSize(s_appleticonsize); m_appleticon->setIcon(appletInfo.icon()); // multiple applet instances can be added thus not conditional m_appleticon->setToolTip(i18n("Double-click or drag to add this applet.")); connect( m_appleticon, SIGNAL(doubleClicked()), this, SLOT(slotAddApplet()) ); appletLayout->addItem(m_appleticon, 0, 0, 2, 1); m_appletname = new Plasma::Label(this); m_appletname->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); QFont appletNameFont = KGlobalSettings::generalFont(); appletNameFont.setBold(true); m_appletname->setFont(appletNameFont); m_appletname->setText(appletInfo.name()); appletLayout->addItem(m_appletname, 0, 1); m_appletactive = new Plasma::IconWidget(this); m_appletactive->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); m_appletactive->setMaximumSize(s_appletactiveiconsize, s_appletactiveiconsize); m_appletactive->setIcon(KIcon()); // all applet instances are removed m_appletactive->setToolTip(i18n("Click to remove this applet.")); connect( m_appletactive, SIGNAL(clicked()), this, SLOT(slotRemoveApplet()) ); appletLayout->addItem(m_appletactive, 0, 2); m_appletcomment = new Plasma::Label(this); m_appletcomment->setAlignment(Qt::AlignLeft | Qt::AlignTop); m_appletcomment->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_appletcomment->setFont(KGlobalSettings::smallestReadableFont()); m_appletcomment->setText(appletInfo.comment()); appletLayout->addItem(m_appletcomment, 1, 1, 1, 2); setLayout(appletLayout); connect( KGlobalSettings::self(), SIGNAL(kdisplayFontChanged()), this, SLOT(slotUpdateFonts()) ); } KPluginInfo AppletFrame::pluginInfo() const { return m_appletinfo; } void AppletFrame::setRunning(const bool isrunning) { m_appletactive->setIcon(isrunning ? KIcon("edit-delete") : KIcon()); } void AppletFrame::slotAddApplet() { m_appleticon->setPressed(false); emit addApplet(m_appletinfo.pluginName()); } void AppletFrame::slotRemoveApplet() { m_appletactive->setPressed(false); emit removeApplet(m_appletinfo.pluginName()); } void AppletFrame::slotUpdateFonts() { QFont appletNameFont = KGlobalSettings::generalFont(); appletNameFont.setBold(true); m_appletname->setFont(appletNameFont); m_appletcomment->setFont(KGlobalSettings::smallestReadableFont()); } class WidgetExplorerPrivate { public: WidgetExplorerPrivate(WidgetExplorer *w) : q(w), containment(nullptr), mainLayout(nullptr), filterEdit(nullptr), topSpacer(nullptr), categoriesButton(nullptr), categoriesMenu(nullptr), closeButton(nullptr), scrollWidget(nullptr), appletsWidget(nullptr), appletsLayout(nullptr), appletsPlaceholder(nullptr) { } void init(const Plasma::Location loc); void updateApplets(); void updateRunningApplets(); void filterApplets(const QString &text); void updateOrientation(const Qt::Orientation orientation); void _k_appletAdded(Plasma::Applet *applet); void _k_appletRemoved(Plasma::Applet *applet); void _k_containmentDestroyed(); void _k_immutabilityChanged(const Plasma::ImmutabilityType type); void _k_textChanged(const QString &text); void _k_closePressed(); void _k_categoriesClicked(); void _k_menuTriggered(QAction *action); void _k_menuAboutToHide(); void _k_addApplet(const QString &pluginName); void _k_removeApplet(const QString &pluginName); void _k_databaseChanged(const QStringList &resources); Plasma::Location location; WidgetExplorer* q; Plasma::Containment* containment; QMutex mutex; QGraphicsGridLayout* mainLayout; Plasma::LineEdit* filterEdit; Plasma::Label* topSpacer; Plasma::ToolButton* categoriesButton; QMenu* categoriesMenu; Plasma::ToolButton* closeButton; Plasma::ScrollWidget* scrollWidget; QGraphicsWidget* appletsWidget; QGraphicsLinearLayout* appletsLayout; QList appletFrames; Plasma::Label* appletsPlaceholder; QMap runningApplets; }; void WidgetExplorerPrivate::init(const Plasma::Location loc) { q->setFocusPolicy(Qt::StrongFocus); const Qt::Orientation orientation = kOrientationForLocation(location); location = loc; mainLayout = new QGraphicsGridLayout(q); mainLayout->setContentsMargins(s_margin, s_margin, s_margin, s_margin); mainLayout->setSpacing(s_margin); filterEdit = new Plasma::LineEdit(q); filterEdit->setClickMessage(i18n("Enter search term...")); filterEdit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); QSizeF filterEditMinimumSize = filterEdit->minimumSize(); filterEditMinimumSize.setWidth(s_filterwidth); filterEdit->setMinimumSize(filterEditMinimumSize); filterEdit->setMaximumSize(filterEditMinimumSize); q->setFocusProxy(filterEdit); q->connect( filterEdit, SIGNAL(textChanged(QString)), q, SLOT(_k_textChanged(QString)) ); mainLayout->addItem(filterEdit, 0, 0); topSpacer = new Plasma::Label(q); topSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); topSpacer->setMinimumSize(0, 0); mainLayout->addItem(topSpacer, 0, 1); categoriesButton = new Plasma::ToolButton(q); categoriesButton->setText(i18n("All Widgets")); q->connect( categoriesButton, SIGNAL(clicked()), q, SLOT(_k_categoriesClicked()) ); categoriesMenu = new QMenu(); q->connect( categoriesMenu, SIGNAL(triggered(QAction*)), q, SLOT(_k_menuTriggered(QAction*)) ); q->connect( categoriesMenu, SIGNAL(aboutToHide()), q, SLOT(_k_menuAboutToHide()) ); mainLayout->addItem(categoriesButton, 0, 2); closeButton = new Plasma::ToolButton(q); closeButton->setIcon(KIcon("window-close")); q->connect( closeButton, SIGNAL(pressed()), q, SLOT(_k_closePressed()) ); mainLayout->addItem(closeButton, 0, 3); scrollWidget = new Plasma::ScrollWidget(q); scrollWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollWidget->setOverShoot(false); scrollWidget->setOverflowBordersVisible(false); scrollWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); appletsWidget = new QGraphicsWidget(scrollWidget); appletsWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); appletsLayout = new QGraphicsLinearLayout(orientation, appletsWidget); appletsPlaceholder = new Plasma::Label(appletsWidget); appletsPlaceholder->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); appletsPlaceholder->setAlignment(Qt::AlignCenter); appletsPlaceholder->setText(i18n("No applets found.")); appletsLayout->addItem(appletsPlaceholder); appletsWidget->setLayout(appletsLayout); scrollWidget->setWidget(appletsWidget); mainLayout->addItem(scrollWidget, 1, 0, 1, 4); updateOrientation(orientation); q->setLayout(mainLayout); q->connect( KSycoca::self(), SIGNAL(databaseChanged(QStringList)), q, SLOT(_k_databaseChanged(QStringList)) ); } void WidgetExplorerPrivate::updateApplets() { filterEdit->setEnabled(false); QMutexLocker locker(&mutex); foreach (AppletFrame* appletFrame, appletFrames) { appletsLayout->removeItem(appletFrame); } qDeleteAll(appletFrames); appletFrames.clear(); QStringList appletCategories; bool hasapplets = false; const QString containmentsCategory = i18n("Containments"); foreach (const KPluginInfo &appletInfo, Plasma::Applet::listAppletInfo()) { const QString appletCategory = appletInfo.category(); if (appletInfo.property("NoDisplay").toBool() || appletCategory == containmentsCategory) { continue; } if (!appletCategory.isEmpty() && !appletCategories.contains(appletCategory)) { appletCategories.append(appletCategory); } hasapplets = true; AppletFrame* appletFrame = new AppletFrame(appletsWidget, appletInfo); appletFrame->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); appletFrame->setMinimumSize(s_appletframesize); appletFrame->setPreferredSize(s_appletframesize); q->connect( appletFrame, SIGNAL(addApplet(QString)), q, SLOT(_k_addApplet(QString)) ); q->connect( appletFrame, SIGNAL(removeApplet(QString)), q, SLOT(_k_removeApplet(QString)) ); appletsLayout->addItem(appletFrame); appletFrames.append(appletFrame); } appletsPlaceholder->setVisible(!hasapplets); categoriesMenu->clear(); categoriesMenu->addAction(i18n("All Widgets")); categoriesMenu->addAction(i18n("Running")); categoriesMenu->addSeparator(); foreach (const QString &appletCategory, appletCategories) { categoriesMenu->addAction(appletCategory); } filterEdit->setEnabled(true); } void WidgetExplorerPrivate::updateRunningApplets() { QMutexLocker locker(&mutex); const QStringList running = runningApplets.values(); foreach (AppletFrame* appletFrame, appletFrames) { appletFrame->setRunning(running.contains(appletFrame->pluginInfo().pluginName())); } } void WidgetExplorerPrivate::filterApplets(const QString &text) { QMutexLocker locker(&mutex); const QString categoriesButtonText = categoriesButton->text(); const bool allwidgets = (categoriesButtonText == i18n("All Widgets")); const bool onlyrunning = (categoriesButtonText == i18n("Running")); const QStringList running = runningApplets.values(); bool hasapplets = false; foreach (AppletFrame* appletFrame, appletFrames) { appletFrame->setVisible(false); const KPluginInfo appletInfo = appletFrame->pluginInfo(); const QString appletPluginName = appletInfo.pluginName(); if (onlyrunning && !running.contains(appletPluginName)) { continue; } if (!onlyrunning && !allwidgets && categoriesButtonText != appletInfo.category()) { continue; } if (!text.isEmpty()) { if (appletInfo.name().contains(text, Qt::CaseInsensitive) || appletPluginName.contains(text, Qt::CaseInsensitive) || appletInfo.comment().contains(text, Qt::CaseInsensitive)) { appletFrame->setVisible(true); hasapplets = true; } } else { appletFrame->setVisible(true); hasapplets = true; } } appletsPlaceholder->setVisible(!hasapplets); appletsPlaceholder->setText( // NOTE: this uses translation from: // kdelibs/kdeui/findreplace/kfind.cpp text.isEmpty() ? i18n("No applets found.") : i18n("No matches found for '%1'.", text) ); appletsWidget->adjustSize(); } void WidgetExplorerPrivate::updateOrientation(const Qt::Orientation orientation) { appletsLayout->setOrientation(orientation); topSpacer->setVisible(false); mainLayout->removeItem(topSpacer); categoriesButton->setVisible(false); mainLayout->removeItem(categoriesButton); if (orientation == Qt::Horizontal) { filterEdit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); mainLayout->addItem(topSpacer, 0, 1); topSpacer->setVisible(true); mainLayout->addItem(categoriesButton, 0, 2); categoriesButton->setVisible(true); } else { filterEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); // TODO: put the categories button bellow the filter and close button } } void WidgetExplorerPrivate::_k_textChanged(const QString &text) { filterApplets(text); } void WidgetExplorerPrivate::_k_categoriesClicked() { categoriesButton->setDown(true); categoriesMenu->exec(QCursor::pos()); } void WidgetExplorerPrivate::_k_menuTriggered(QAction *action) { categoriesButton->setText(action->text()); categoriesButton->setDown(false); filterApplets(filterEdit->text()); } void WidgetExplorerPrivate::_k_menuAboutToHide() { categoriesButton->setDown(false); } void WidgetExplorerPrivate::_k_closePressed() { emit q->closeClicked(); } void WidgetExplorerPrivate::_k_addApplet(const QString &pluginName) { if (!containment) { return; } containment->addApplet(pluginName); } void WidgetExplorerPrivate::_k_removeApplet(const QString &pluginName) { if (!containment) { return; } Plasma::Corona *corona = containment->corona(); if (!corona) { return; } QList containments = corona->containments(); foreach (Containment *containment, containments) { foreach (Applet *applet, containment->applets()) { if (applet->pluginName() == pluginName) { applet->destroy(); } } } } void WidgetExplorerPrivate::_k_databaseChanged(const QStringList &resources) { if (resources.contains("services")) { updateApplets(); filterApplets(filterEdit->text()); } } void WidgetExplorerPrivate::_k_appletAdded(Plasma::Applet *applet) { runningApplets.insert(applet, applet->pluginName()); updateRunningApplets(); filterApplets(filterEdit->text()); } void WidgetExplorerPrivate::_k_appletRemoved(Plasma::Applet *applet) { runningApplets.remove(applet); updateRunningApplets(); filterApplets(filterEdit->text()); } void WidgetExplorerPrivate::_k_containmentDestroyed() { q->setContainment(nullptr); } void WidgetExplorerPrivate::_k_immutabilityChanged(const Plasma::ImmutabilityType type) { if (type != Plasma::Mutable) { emit q->closeClicked(); } } WidgetExplorer::WidgetExplorer(const Plasma::Location loc, QGraphicsItem *parent) : QGraphicsWidget(parent), d(new WidgetExplorerPrivate(this)) { d->init(loc); } WidgetExplorer::WidgetExplorer(QGraphicsItem *parent) : QGraphicsWidget(parent), d(new WidgetExplorerPrivate(this)) { d->init(Plasma::BottomEdge); } WidgetExplorer::~WidgetExplorer() { if (d->containment) { d->containment->disconnect(this); } foreach (AppletFrame* appletFrame, d->appletFrames) { d->appletsLayout->removeItem(appletFrame); } qDeleteAll(d->appletFrames); d->appletFrames.clear(); d->appletsLayout->removeItem(d->appletsPlaceholder); delete d->appletsPlaceholder; d->mainLayout->removeItem(d->filterEdit); delete d->filterEdit; d->mainLayout->removeItem(d->topSpacer); delete d->topSpacer; d->mainLayout->removeItem(d->categoriesButton); delete d->categoriesButton; delete d->categoriesMenu; d->mainLayout->removeItem(d->closeButton); delete d->closeButton; delete d->appletsWidget; delete d; } void WidgetExplorer::setLocation(const Plasma::Location loc) { d->location = loc; d->updateOrientation(kOrientationForLocation(loc)); emit locationChanged(loc); } Plasma::Location WidgetExplorer::location() const { return d->location; } void WidgetExplorer::setContainment(Plasma::Containment *containment) { if (d->containment != containment) { if (d->containment) { d->containment->disconnect(this); } d->runningApplets.clear(); d->containment = containment; if (d->containment) { connect( d->containment, SIGNAL(destroyed(QObject*)), this, SLOT(_k_containmentDestroyed()) ); connect( d->containment, SIGNAL(immutabilityChanged(Plasma::ImmutabilityType)), this, SLOT(_k_immutabilityChanged(Plasma::ImmutabilityType)) ); setLocation(containment->location()); } if (containment) { Plasma::Corona *corona = containment->corona(); if (corona) { QList containments = corona->containments(); foreach (Containment *containment, containments) { connect( containment, SIGNAL(appletAdded(Plasma::Applet*,QPointF)), this, SLOT(_k_appletAdded(Plasma::Applet*)) ); connect( containment, SIGNAL(appletRemoved(Plasma::Applet*)), this, SLOT(_k_appletRemoved(Plasma::Applet*)) ); foreach (Applet *applet, containment->applets()) { d->runningApplets.insert(applet, applet->pluginName()); } } } } d->updateApplets(); d->updateRunningApplets(); d->filterApplets(d->filterEdit->text()); } } Containment* WidgetExplorer::containment() const { return d->containment; } void WidgetExplorer::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { emit closeClicked(); event->accept(); return; } // arrow keys go to the scroll widget switch (event->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: { QApplication::sendEvent(d->scrollWidget, event); return; } } // if the scroll area or any other widget steals the focus the focus goes back to the filter // widget tho if (!d->filterEdit->hasFocus()) { d->filterEdit->setFocus(); } QApplication::sendEvent(d->filterEdit->nativeWidget(), event); } } // namespace Plasma #include "moc_widgetexplorer.cpp" #include "widgetexplorer.moc"