kde-workspace/plasma/applets/launcher/launcher.cpp
Ivailo Monev b193577947 plasma: rework applications tab of launcher applet
v2.0 with a custom navigations bar

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2024-04-15 00:48:47 +03:00

1304 lines
42 KiB
C++

/*
This file is part of the KDE project
Copyright (C) 2024 Ivailo Monev <xakepa10@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2, as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "launcher.h"
#include "kworkspace/kworkspace.h"
#include "kworkspace/kdisplaymanager.h"
#include <QMutex>
#include <QGraphicsLinearLayout>
#include <QGraphicsGridLayout>
#include <QHostInfo>
#include <QDBusInterface>
#include <KUser>
#include <kdbusconnectionpool.h>
#include <KIcon>
#include <KIconLoader>
#include <KStandardDirs>
#include <KSycoca>
#include <KToolInvocation>
#include <KRun>
#include <KBookmarkManager>
#include <KServiceGroup>
#include <KDirWatch>
#include <KDesktopFile>
#include <KRecentDocument>
#include <Solid/PowerManagement>
#include <Plasma/IconWidget>
#include <Plasma/Separator>
#include <Plasma/Label>
#include <Plasma/LineEdit>
#include <Plasma/TabBar>
#include <Plasma/SvgWidget>
#include <Plasma/ToolButton>
#include <Plasma/ScrollWidget>
#include <Plasma/RunnerManager>
#include <KDebug>
// TODO: mime data for drag-n-drop
static const QString s_defaultpopupicon = QString::fromLatin1("start-here-kde");
static const QSizeF s_minimumsize = QSizeF(450, 350);
static const QString s_firsttimeaddress = QString::fromLatin1("_k_firsttime");
static const QStringList s_firsttimeservices = QStringList()
<< QString::fromLatin1("konsole")
<< QString::fromLatin1("dolphin")
<< QString::fromLatin1("systemsettings");
static const QString s_genericicon = QString::fromLatin1("applications-other");
static const QString s_favoriteicon = QString::fromLatin1("bookmarks");
static const QString s_recenticon = QString::fromLatin1("document-open-recent");
static const int s_searchdelay = 500; // ms
static const int s_leavetimeout = 5000; // ms
static QSizeF kIconSize()
{
const int iconsize = KIconLoader::global()->currentSize(KIconLoader::Desktop);
return QSizeF(iconsize, iconsize);
}
static QGraphicsWidget* kMakeSpacer(QGraphicsWidget *parent)
{
QGraphicsWidget* widget = new QGraphicsWidget(parent);
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
widget->setMinimumSize(0, 0);
return widget;
}
static Plasma::IconWidget* kMakeIconWidget(QGraphicsWidget *parent,
const QSizeF &iconsize,
const QString &text,
const QString &infotext,
const QString &icon,
const QString &url)
{
Plasma::IconWidget* iconwidget = new Plasma::IconWidget(parent);
// TODO: actions are not visible when the orientation is horizontal..
iconwidget->setOrientation(Qt::Horizontal);
iconwidget->setMinimumIconSize(iconsize);
iconwidget->setMaximumIconSize(iconsize);
iconwidget->setText(text);
iconwidget->setInfoText(infotext);
iconwidget->setIcon(icon);
iconwidget->setProperty("_k_url", url);
return iconwidget;
}
static Plasma::ScrollWidget* kMakeScrollWidget(QGraphicsWidget *parent)
{
Plasma::ScrollWidget* scrollwidget = new Plasma::ScrollWidget(parent);
scrollwidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// TODO: this really does not work..
scrollwidget->setOverShoot(false);
return scrollwidget;
}
static void kRunService(const QString &entrypath, LauncherApplet* launcherapplet)
{
Q_ASSERT(launcherapplet != nullptr);
launcherapplet->resetState();
KService::Ptr service = KService::serviceByDesktopPath(entrypath);
Q_ASSERT(!service.isNull());
if (!KRun::run(*service.data(), KUrl::List(), nullptr)) {
kWarning() << "could not run" << entrypath;
}
}
static void kRunUrl(const QString &urlpath, LauncherApplet* launcherapplet)
{
Q_ASSERT(launcherapplet != nullptr);
launcherapplet->resetState();
(void)new KRun(KUrl(urlpath), nullptr);
}
static QString kGenericIcon(const QString &name)
{
if (!name.isEmpty()) {
return name;
}
return s_genericicon;
}
static QString kFavoriteIcon(const QString &name)
{
if (!name.isEmpty()) {
return name;
}
return s_favoriteicon;
}
static QString kRecentIcon(const QString &name)
{
if (!name.isEmpty()) {
return name;
}
return s_recenticon;
}
static void kLockScreen()
{
QDBusInterface screensaver(
"org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver",
QDBusConnection::sessionBus()
);
screensaver.call("Lock");
}
static QStringList kAllowedRunners(KConfigGroup configgroup)
{
QStringList result;
foreach (KPluginInfo &plugin, Plasma::RunnerManager::listRunnerInfo()) {
plugin.load(configgroup);
if (plugin.isPluginEnabled()) {
result.append(plugin.pluginName());
}
}
// qDebug() << Q_FUNC_INFO << result << configgroup.name();
return result;
}
class LauncherSearch : public QGraphicsWidget
{
Q_OBJECT
public:
LauncherSearch(QGraphicsWidget *parent, LauncherApplet *launcherapplet);
void setAllowedRunners(const QStringList &runners);
void prepare();
void query(const QString &text);
void finish();
private Q_SLOTS:
void slotUpdateLayout(const QList<Plasma::QueryMatch> &matches);
void slotActivated();
void slotTriggered();
private:
QMutex m_mutex;
LauncherApplet* m_launcherapplet;
QGraphicsLinearLayout* m_layout;
QList<Plasma::IconWidget*> m_iconwidgets;
Plasma::Label* m_label;
Plasma::RunnerManager* m_runnermanager;
};
LauncherSearch::LauncherSearch(QGraphicsWidget *parent, LauncherApplet *launcherapplet)
: QGraphicsWidget(parent),
m_launcherapplet(launcherapplet),
m_layout(nullptr),
m_label(nullptr),
m_runnermanager(nullptr)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_layout = new QGraphicsLinearLayout(Qt::Vertical, this);
setLayout(m_layout);
m_label = new Plasma::Label(this);
m_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_label->setAlignment(Qt::AlignCenter);
m_label->setText(i18n("No matches found"));
m_layout->addItem(m_label);
}
void LauncherSearch::setAllowedRunners(const QStringList &runners)
{
// NOTE: Plasma::RunnerManager basically never unloads, have to re-create it
delete m_runnermanager;
m_runnermanager = new Plasma::RunnerManager(this);
connect(
m_runnermanager, SIGNAL(matchesChanged(QList<Plasma::QueryMatch>)),
this, SLOT(slotUpdateLayout(QList<Plasma::QueryMatch>))
);
m_runnermanager->setAllowedRunners(runners);
}
void LauncherSearch::prepare()
{
// qDebug() << Q_FUNC_INFO;
QMutexLocker locker(&m_mutex);
foreach (Plasma::IconWidget* iconwidget, m_iconwidgets) {
m_layout->removeItem(iconwidget);
}
qDeleteAll(m_iconwidgets);
m_iconwidgets.clear();
m_label->setVisible(true);
adjustSize();
m_runnermanager->reset();
}
void LauncherSearch::query(const QString &text)
{
// qDebug() << Q_FUNC_INFO << text;
m_runnermanager->launchQuery(text);
}
void LauncherSearch::finish()
{
// qDebug() << Q_FUNC_INFO;
m_runnermanager->matchSessionComplete();
}
void LauncherSearch::slotUpdateLayout(const QList<Plasma::QueryMatch> &matches)
{
// qDebug() << Q_FUNC_INFO;
QMutexLocker locker(&m_mutex);
m_label->setVisible(matches.isEmpty());
adjustSize();
const QSizeF iconsize = kIconSize();
foreach (const Plasma::QueryMatch &match, matches) {
// qDebug() << Q_FUNC_INFO << match.text() << match.subtext();
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, match.text(), match.subtext(), QString(), match.id()
);
iconwidget->setIcon(match.icon());
if (match.type() == Plasma::QueryMatch::InformationalMatch) {
iconwidget->setAcceptHoverEvents(false);
iconwidget->setAcceptedMouseButtons(Qt::NoButton);
}
int counter = 1;
if (match.hasConfigurationInterface()) {
QAction* matchconfigaction = new QAction(iconwidget);
matchconfigaction->setText(i18n("Configure"));
matchconfigaction->setIcon(KIcon("preferences-system"));
matchconfigaction->setProperty("_k_id", match.id());
connect(
matchconfigaction, SIGNAL(triggered()),
this, SLOT(slotTriggered())
);
iconwidget->addIconAction(matchconfigaction);
counter++;
// qDebug() << Q_FUNC_INFO << match.id();
}
foreach (QAction* action, m_runnermanager->actionsForMatch(match)) {
iconwidget->addIconAction(action);
counter++;
if (counter >= 4) {
// the limit of Plasma::IconWidget
break;
}
}
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
}
void LauncherSearch::slotActivated()
{
Plasma::IconWidget* iconwidget = qobject_cast<Plasma::IconWidget*>(sender());
const QString iconwidgeturl = iconwidget->property("_k_url").toString();
m_launcherapplet->resetState();
m_runnermanager->run(iconwidgeturl);
}
void LauncherSearch::slotTriggered()
{
QAction* matchconfigaction = qobject_cast<QAction*>(sender());
const QString matchconfigid = matchconfigaction->property("_k_id").toString();
Plasma::AbstractRunner* runner = m_runnermanager->runner(m_runnermanager->runnerName(matchconfigid));
if (!runner) {
kWarning() << "no runner for" << matchconfigid;
return;
}
// TODO: implement
qDebug() << Q_FUNC_INFO << matchconfigid;
}
class LauncherFavorites : public QGraphicsWidget
{
Q_OBJECT
public:
LauncherFavorites(QGraphicsWidget *parent, LauncherApplet* launcherapplet);
private Q_SLOTS:
void slotUpdateLayout();
void slotActivated();
private:
QMutex m_mutex;
LauncherApplet* m_launcherapplet;
QGraphicsLinearLayout* m_layout;
QList<Plasma::IconWidget*> m_iconwidgets;
KBookmarkManager* m_bookmarkmanager;
};
// TODO: context menu to remove
LauncherFavorites::LauncherFavorites(QGraphicsWidget *parent, LauncherApplet* launcherapplet)
: QGraphicsWidget(parent),
m_launcherapplet(launcherapplet),
m_layout(nullptr),
m_bookmarkmanager(nullptr)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_layout = new QGraphicsLinearLayout(Qt::Vertical, this);
setLayout(m_layout);
const QString bookmarfile = KStandardDirs::locateLocal("data", "plasma/bookmarks.xml");
m_bookmarkmanager = KBookmarkManager::managerForFile(bookmarfile, "launcher");
// m_bookmarkmanager->slotEditBookmarks();
slotUpdateLayout();
connect(
m_bookmarkmanager, SIGNAL(changed(QString,QString)),
this, SLOT(slotUpdateLayout())
);
connect(
KSycoca::self(), SIGNAL(databaseChanged(QStringList)),
this, SLOT(slotUpdateLayout())
);
}
void LauncherFavorites::slotUpdateLayout()
{
QMutexLocker locker(&m_mutex);
foreach (Plasma::IconWidget* iconwidget, m_iconwidgets) {
m_layout->removeItem(iconwidget);
}
qDeleteAll(m_iconwidgets);
m_iconwidgets.clear();
adjustSize();
bool isfirsttime = true;
KBookmarkGroup bookmarkgroup = m_bookmarkmanager->root();
// first time gets a special treatment
KBookmark bookmark = bookmarkgroup.first();
while (!bookmark.isNull()) {
if (bookmark.url().url() == s_firsttimeaddress) {
isfirsttime = false;
break;
}
bookmark = bookmarkgroup.next(bookmark);
}
if (isfirsttime) {
bookmark = bookmarkgroup.createNewSeparator();
bookmark.setUrl(s_firsttimeaddress);
bookmark.setDescription("internal bookmark");
foreach (const QString &name, s_firsttimeservices) {
KService::Ptr service = KService::serviceByDesktopName(name);
if (!service.isNull()) {
bookmarkgroup.addBookmark(service->desktopEntryName(), KUrl(service->entryPath()), service->icon());
} else {
kWarning() << "invalid first-time serivce" << name;
}
}
m_bookmarkmanager->emitChanged(bookmarkgroup);
}
const QSizeF iconsize = kIconSize();
bookmark = bookmarkgroup.first();
while (!bookmark.isNull()) {
// qDebug() << Q_FUNC_INFO << bookmark.text() << bookmark.description() << bookmark.icon() << bookmark.url();
if (bookmark.isSeparator()) {
bookmark = bookmarkgroup.next(bookmark);
continue;
}
const QString serviceentrypath = bookmark.url().url();
KService::Ptr service = KService::serviceByDesktopPath(serviceentrypath);
if (service.isNull()) {
service = KService::serviceByDesktopName(bookmark.text());
}
if (service.isNull()) {
kWarning() << "could not find service for" << serviceentrypath;
bookmark = bookmarkgroup.next(bookmark);
continue;
}
// qDebug() << Q_FUNC_INFO << service->entryPath() << service->name() << service->comment();
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, service->name(), service->genericName(), kFavoriteIcon(service->icon()), service->entryPath()
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
bookmark = bookmarkgroup.next(bookmark);
}
}
void LauncherFavorites::slotActivated()
{
Plasma::IconWidget* iconwidget = qobject_cast<Plasma::IconWidget*>(sender());
kRunService(iconwidget->property("_k_url").toString(), m_launcherapplet);
}
class LauncherNavigatorStruct
{
public:
LauncherNavigatorStruct(Plasma::SvgWidget* _svgwidget, Plasma::ToolButton *_toolbutton, const QString &_id)
: svgwidget(_svgwidget), toolbutton(_toolbutton), id(_id)
{
}
Plasma::SvgWidget* svgwidget;
Plasma::ToolButton* toolbutton;
QString id;
};
class LauncherNavigator : public QGraphicsWidget
{
Q_OBJECT
public:
LauncherNavigator(QGraphicsWidget *parent);
void reset();
void addNavigation(const QString &id, const QString &text);
Q_SIGNALS:
void navigate(const QString &id);
private Q_SLOTS:
void slotReleased();
private:
QMutex m_mutex;
QGraphicsLinearLayout* m_layout;
QList<LauncherNavigatorStruct*> m_navigations;
QGraphicsWidget* m_spacer;
};
LauncherNavigator::LauncherNavigator(QGraphicsWidget *parent)
: m_layout(nullptr),
m_spacer(nullptr)
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
m_layout = new QGraphicsLinearLayout(Qt::Horizontal, this);
setLayout(m_layout);
m_spacer = kMakeSpacer(this);
m_layout->addItem(m_spacer);
}
void LauncherNavigator::reset()
{
QMutexLocker locker(&m_mutex);
foreach (LauncherNavigatorStruct *navigation, m_navigations) {
m_layout->removeItem(navigation->toolbutton);
delete navigation->toolbutton;
m_layout->removeItem(navigation->svgwidget);
delete navigation->svgwidget;
}
qDeleteAll(m_navigations);
m_navigations.clear();
if (m_spacer) {
m_layout->removeItem(m_spacer);
delete m_spacer;
m_spacer = nullptr;
}
m_spacer = kMakeSpacer(this);
m_layout->addItem(m_spacer);
}
void LauncherNavigator::addNavigation(const QString &id, const QString &text)
{
// qDebug() << Q_FUNC_INFO << id << text;
QMutexLocker locker(&m_mutex);
Plasma::Svg* svg = new Plasma::Svg(this);
svg->setImagePath("widgets/arrows");
Plasma::SvgWidget* svgwidget = new Plasma::SvgWidget(this);
svgwidget->setElementID("right-arrow");
svgwidget->setSvg(svg);
m_layout->addItem(svgwidget);
Plasma::ToolButton* toolbutton = new Plasma::ToolButton(this);
toolbutton->setText(text);
toolbutton->setProperty("_k_id", id);
m_layout->addItem(toolbutton);
connect(
toolbutton, SIGNAL(released()),
this, SLOT(slotReleased())
);
if (m_spacer) {
m_layout->removeItem(m_spacer);
delete m_spacer;
m_spacer = nullptr;
}
m_spacer = kMakeSpacer(this);
m_layout->addItem(m_spacer);
m_navigations.append(new LauncherNavigatorStruct(svgwidget, toolbutton, id));
}
void LauncherNavigator::slotReleased()
{
Plasma::ToolButton* toolbutton = qobject_cast<Plasma::ToolButton*>(sender());
emit navigate(toolbutton->property("_k_id").toString());
}
// TODO: context menu to add or remove from favorites, animated layout update
class LauncherApplications : public QGraphicsWidget
{
Q_OBJECT
public:
LauncherApplications(QGraphicsWidget *parent, LauncherApplet *launcherapplet);
private Q_SLOTS:
void slotUpdateLayout();
void slotNavigate(const QString &id);
void slotDelayedNavigate();
void slotGroupActivated();
void slotAppActivated();
private:
int serviceCount(KServiceGroup::Ptr servicegroup);
void addGroup(KServiceGroup::Ptr servicegroup);
QMutex m_mutex;
LauncherApplet* m_launcherapplet;
QGraphicsLinearLayout* m_layout;
LauncherNavigator* m_launchernavigator;
QList<Plasma::IconWidget*> m_iconwidgets;
QString m_id;
};
LauncherApplications::LauncherApplications(QGraphicsWidget *parent, LauncherApplet *launcherapplet)
: QGraphicsWidget(parent),
m_launcherapplet(launcherapplet),
m_layout(nullptr),
m_launchernavigator(nullptr)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_layout = new QGraphicsLinearLayout(Qt::Vertical, this);
setLayout(m_layout);
m_launchernavigator = new LauncherNavigator(this);
m_layout->addItem(m_launchernavigator);
slotUpdateLayout();
connect(
m_launchernavigator, SIGNAL(navigate(QString)),
this, SLOT(slotNavigate(QString))
);
connect(
KSycoca::self(), SIGNAL(databaseChanged(QStringList)),
this, SLOT(slotUpdateLayout())
);
}
void LauncherApplications::slotUpdateLayout()
{
QMutexLocker locker(&m_mutex);
foreach (Plasma::IconWidget* iconwidget, m_iconwidgets) {
m_layout->removeItem(iconwidget);
}
qDeleteAll(m_iconwidgets);
m_iconwidgets.clear();
m_launchernavigator->reset();
adjustSize();
m_id.clear();
KServiceGroup::Ptr rootgroup = KServiceGroup::root();
if (!rootgroup.isNull() && rootgroup->isValid()) {
m_id = rootgroup->relPath();
m_launchernavigator->addNavigation(m_id, rootgroup->caption());
addGroup(rootgroup);
}
}
int LauncherApplications::serviceCount(KServiceGroup::Ptr servicegroup)
{
int result = 0;
foreach (const KServiceGroup::Ptr subgroup, servicegroup->groupEntries(KServiceGroup::NoOptions)) {
if (serviceCount(subgroup) > 0) {
result++;
}
}
foreach (const KService::Ptr appservice, servicegroup->serviceEntries(KServiceGroup::NoOptions)) {
if (appservice->noDisplay()) {
kDebug() << "hidden entry" << appservice->name();
continue;
}
result++;
}
return result;
}
void LauncherApplications::addGroup(KServiceGroup::Ptr servicegroup)
{
const QSizeF iconsize = kIconSize();
foreach (const KServiceGroup::Ptr subgroup, servicegroup->groupEntries(KServiceGroup::NoOptions)) {
if (serviceCount(subgroup) > 0) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, subgroup->caption(), subgroup->comment(), kGenericIcon(subgroup->icon()), subgroup->relPath()
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotGroupActivated())
);
}
}
foreach (const KService::Ptr appservice, servicegroup->serviceEntries(KServiceGroup::NoOptions)) {
if (appservice->noDisplay()) {
kDebug() << "hidden entry" << appservice->name();
continue;
}
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, appservice->name(), appservice->comment(), kGenericIcon(appservice->icon()), appservice->entryPath()
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotAppActivated())
);
}
}
void LauncherApplications::slotNavigate(const QString &id)
{
if (id == m_id) {
return;
}
m_id = id;
// delayed because this is connected to widgets that will be destroyed
QTimer::singleShot(100, this, SLOT(slotDelayedNavigate()));
}
void LauncherApplications::slotDelayedNavigate()
{
QMutexLocker locker(&m_mutex);
foreach (Plasma::IconWidget* iconwidget, m_iconwidgets) {
m_layout->removeItem(iconwidget);
}
qDeleteAll(m_iconwidgets);
m_iconwidgets.clear();
m_launchernavigator->reset();
adjustSize();
// qDebug() << Q_FUNC_INFO << m_id;
KServiceGroup::Ptr servicegroup = KServiceGroup::group(m_id);
if (!servicegroup.isNull() && servicegroup->isValid()) {
KServiceGroup::Ptr rootgroup = KServiceGroup::root();
if (!rootgroup.isNull() && rootgroup->isValid()) {
m_launchernavigator->addNavigation(rootgroup->relPath(), rootgroup->caption());
} else {
kWarning() << "root group is not valid";
}
QString groupid;
foreach (const QString &subname, m_id.split(QLatin1Char('/'), QString::SkipEmptyParts)) {
groupid.append(subname);
groupid.append(QLatin1Char('/'));
KServiceGroup::Ptr subgroup = KServiceGroup::group(groupid);
if (subgroup.isNull() || !subgroup->isValid()) {
kWarning() << "invalid subgroup" << subname;
continue;
}
m_launchernavigator->addNavigation(groupid, subgroup->caption());
}
addGroup(servicegroup);
} else {
kWarning() << "invalid group" << m_id;
}
}
void LauncherApplications::slotGroupActivated()
{
Plasma::IconWidget* iconwidget = qobject_cast<Plasma::IconWidget*>(sender());
slotNavigate(iconwidget->property("_k_url").toString());
}
void LauncherApplications::slotAppActivated()
{
Plasma::IconWidget* iconwidget = qobject_cast<Plasma::IconWidget*>(sender());
kRunService(iconwidget->property("_k_url").toString(), m_launcherapplet);
}
class LauncherRecent : public QGraphicsWidget
{
Q_OBJECT
public:
LauncherRecent(QGraphicsWidget *parent, LauncherApplet *launcherapplet);
private Q_SLOTS:
void slotUpdateLayout();
void slotActivated();
private:
QMutex m_mutex;
LauncherApplet* m_launcherapplet;
QGraphicsLinearLayout* m_layout;
QList<Plasma::IconWidget*> m_iconwidgets;
KDirWatch* m_dirwatch;
};
LauncherRecent::LauncherRecent(QGraphicsWidget *parent, LauncherApplet *launcherapplet)
: QGraphicsWidget(parent),
m_launcherapplet(launcherapplet),
m_layout(nullptr),
m_dirwatch(nullptr)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_layout = new QGraphicsLinearLayout(Qt::Vertical, this);
setLayout(m_layout);
m_dirwatch = new KDirWatch(this);
m_dirwatch->addDir(KRecentDocument::recentDocumentDirectory());
slotUpdateLayout();
connect(
m_dirwatch, SIGNAL(dirty(QString)),
this, SLOT(slotUpdateLayout())
);
}
void LauncherRecent::slotUpdateLayout()
{
QMutexLocker locker(&m_mutex);
foreach (Plasma::IconWidget* iconwidget, m_iconwidgets) {
m_layout->removeItem(iconwidget);
}
qDeleteAll(m_iconwidgets);
m_iconwidgets.clear();
adjustSize();
const QSizeF iconsize = kIconSize();
foreach (const QString &recent, KRecentDocument::recentDocuments()) {
KDesktopFile recentfile(recent);
// qDebug() << Q_FUNC_INFO << recentfile.readName() << recentfile.readComment();
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, recentfile.readName(), recentfile.readComment(), kRecentIcon(recentfile.readIcon()), recentfile.readUrl()
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
}
void LauncherRecent::slotActivated()
{
Plasma::IconWidget* iconwidget = qobject_cast<Plasma::IconWidget*>(sender());
kRunUrl(iconwidget->property("_k_url").toString(), m_launcherapplet);
}
class LauncherLeave : public QGraphicsWidget
{
Q_OBJECT
public:
LauncherLeave(QGraphicsWidget *parent);
private Q_SLOTS:
void slotUpdateLayout();
void slotActivated();
void slotTimeout();
private:
QMutex m_mutex;
QGraphicsLinearLayout* m_layout;
QList<Plasma::IconWidget*> m_iconwidgets;
Plasma::Separator* m_systemseparator;
QTimer* m_timer;
bool m_canlock;
bool m_canswitch;
bool m_canreboot;
bool m_canshutdown;
};
LauncherLeave::LauncherLeave(QGraphicsWidget *parent)
: QGraphicsWidget(parent),
m_systemseparator(nullptr),
m_timer(nullptr),
m_canlock(false),
m_canswitch(false),
m_canreboot(false),
m_canshutdown(false)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_layout = new QGraphicsLinearLayout(Qt::Vertical, this);
setLayout(m_layout);
m_timer = new QTimer(this);
m_timer->setInterval(s_leavetimeout);
slotTimeout();
slotUpdateLayout();
connect(
Solid::PowerManagement::notifier(), SIGNAL(supportedSleepStatesChanged()),
this, SLOT(slotUpdateLayout())
);
connect(
m_timer, SIGNAL(timeout()),
this, SLOT(slotTimeout())
);
m_timer->start();
}
void LauncherLeave::slotUpdateLayout()
{
QMutexLocker locker(&m_mutex);
foreach (Plasma::IconWidget* iconwidget, m_iconwidgets) {
m_layout->removeItem(iconwidget);
}
qDeleteAll(m_iconwidgets);
m_iconwidgets.clear();
if (m_systemseparator) {
m_layout->removeItem(m_systemseparator);
delete m_systemseparator;
m_systemseparator = nullptr;
}
adjustSize();
const QSizeF iconsize = kIconSize();
bool hassessionicon = false;
if (m_canlock) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18n("Lock"), i18n("Lock screen"), "system-lock-screen", "lock"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
hassessionicon = true;
}
if (m_canswitch) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18n("Switch user"), i18n("Start a parallel session as a different user"), "system-switch-user", "switch"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
hassessionicon = true;
}
if (hassessionicon) {
m_systemseparator = new Plasma::Separator(this);
m_systemseparator->setOrientation(Qt::Horizontal);
m_layout->addItem(m_systemseparator);
}
const QSet<Solid::PowerManagement::SleepState> sleepsates = Solid::PowerManagement::supportedSleepStates();
if (sleepsates.contains(Solid::PowerManagement::SuspendState)) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18n("Sleep"), i18n("Suspend to RAM"), "system-suspend", "suspendram"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
if (sleepsates.contains(Solid::PowerManagement::HibernateState)) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18n("Hibernate"), i18n("Suspend to disk"), "system-suspend-hibernate", "suspenddisk"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
if (sleepsates.contains(Solid::PowerManagement::HybridSuspendState)) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18n("Hybrid Suspend"), i18n("Hybrid Suspend"), "system-suspend", "suspendhybrid"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
if (m_canreboot) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18nc("Restart computer", "Restart"), i18n("Restart computer"), "system-reboot", "restart"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
if (m_canshutdown) {
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18n("Shut down"), i18n("Turn off computer"), "system-shutdown", "shutdown"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
Plasma::IconWidget* iconwidget = kMakeIconWidget(
this,
iconsize, i18n("Log out"), i18n("End session"), "system-log-out", "logout"
);
m_iconwidgets.append(iconwidget);
m_layout->addItem(iconwidget);
connect(
iconwidget, SIGNAL(activated()),
this, SLOT(slotActivated())
);
}
void LauncherLeave::slotActivated()
{
Plasma::IconWidget* iconwidget = qobject_cast<Plasma::IconWidget*>(sender());
const QString iconwidgeturl = iconwidget->property("_k_url").toString();
if (iconwidgeturl == QLatin1String("lock")) {
kLockScreen();
} else if (iconwidgeturl == QLatin1String("switch")) {
kLockScreen();
KDisplayManager().newSession();
} else if (iconwidgeturl == QLatin1String("suspendram")) {
Solid::PowerManagement::requestSleep(Solid::PowerManagement::SuspendState);
} else if (iconwidgeturl == QLatin1String("suspenddisk")) {
Solid::PowerManagement::requestSleep(Solid::PowerManagement::HibernateState);
} else if (iconwidgeturl == QLatin1String("suspendhybrid")) {
Solid::PowerManagement::requestSleep(Solid::PowerManagement::HybridSuspendState);
} else if (iconwidgeturl == QLatin1String("restart")) {
KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeReboot);
} else if (iconwidgeturl == QLatin1String("shutdown")) {
KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeHalt);
} else if (iconwidgeturl == QLatin1String("logout")) {
KWorkSpace::requestShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeNone);
} else {
Q_ASSERT(false);
kWarning() << "invalid url" << iconwidgeturl;
}
}
void LauncherLeave::slotTimeout()
{
const bool oldcanlock = m_canlock;
const bool oldcanswitch = m_canswitch;
const bool oldcanreboot = m_canreboot;
const bool oldcanshutdown = m_canshutdown;
m_canlock = KDBusConnectionPool::isServiceRegistered("org.freedesktop.ScreenSaver", QDBusConnection::sessionBus());
m_canswitch = KDisplayManager().isSwitchable();
m_canreboot = KWorkSpace::canShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeReboot);
m_canshutdown = KWorkSpace::canShutDown(KWorkSpace::ShutdownConfirmDefault, KWorkSpace::ShutdownTypeHalt);
if (oldcanlock != m_canlock || oldcanswitch != m_canswitch ||
oldcanreboot != m_canreboot || oldcanshutdown != m_canshutdown) {
slotUpdateLayout();
}
}
class LauncherAppletWidget : public QGraphicsWidget
{
Q_OBJECT
public:
LauncherAppletWidget(LauncherApplet* auncherapplet);
void resetSearch();
void setAllowedRunners(const QStringList &runners);
private Q_SLOTS:
void slotSearch(const QString &text);
void slotTimeout();
private:
LauncherApplet* m_launcherapplet;
QGraphicsLinearLayout* m_layout;
QGraphicsLinearLayout* m_toplayout;
Plasma::IconWidget* m_iconwidget;
Plasma::Label* m_label;
Plasma::LineEdit* m_lineedit;
Plasma::TabBar* m_tabbar;
Plasma::ScrollWidget* m_favoritesscrollwidget;
LauncherFavorites* m_favoriteswidget;
Plasma::ScrollWidget* m_applicationsscrollwidget;
LauncherApplications* m_applicationswidget;
Plasma::ScrollWidget* m_recentscrollwidget;
LauncherRecent* m_recentwidget;
Plasma::ScrollWidget* m_leavecrollwidget;
LauncherLeave* m_leavewidget;
Plasma::ScrollWidget* m_searchscrollwidget;
LauncherSearch* m_searchwidget;
QTimer* m_timer;
};
LauncherAppletWidget::LauncherAppletWidget(LauncherApplet* auncherapplet)
: QGraphicsWidget(auncherapplet),
m_launcherapplet(auncherapplet),
m_layout(nullptr),
m_toplayout(nullptr),
m_iconwidget(nullptr),
m_label(nullptr),
m_lineedit(nullptr),
m_tabbar(nullptr),
m_favoritesscrollwidget(nullptr),
m_favoriteswidget(nullptr),
m_applicationsscrollwidget(nullptr),
m_applicationswidget(nullptr),
m_recentscrollwidget(nullptr),
m_recentwidget(nullptr),
m_leavecrollwidget(nullptr),
m_leavewidget(nullptr),
m_searchscrollwidget(nullptr),
m_searchwidget(nullptr),
m_timer(nullptr)
{
m_layout = new QGraphicsLinearLayout(this);
m_layout->setOrientation(Qt::Vertical);
m_toplayout = new QGraphicsLinearLayout(m_layout);
m_toplayout->setOrientation(Qt::Horizontal);
m_toplayout->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_layout->addItem(m_toplayout);
const QString hostname = QHostInfo::localHostName();
KUser user(KUser::UseEffectiveUID);
QString usericon = user.faceIconPath();
if (usericon.isEmpty()) {
usericon = QLatin1String("system-search");
}
m_iconwidget = new Plasma::IconWidget(this);
m_iconwidget->setAcceptHoverEvents(false);
m_iconwidget->setAcceptedMouseButtons(Qt::NoButton);
m_iconwidget->setIcon(usericon);
m_toplayout->addItem(m_iconwidget);
QString usertext;
QString fullusername = user.property(KUser::FullName);
if (fullusername.isEmpty()) {
usertext = i18nc("login name, hostname", "User <b>%1</b> on <b>%2</b>", user.loginName(), hostname);
} else {
usertext = i18nc("full name, login name, hostname", "<b>%1 (%2)</b> on <b>%3</b>", fullusername, user.loginName(), hostname);
}
m_label = new Plasma::Label(this);
m_label->setWordWrap(false);
m_label->setText(usertext);
m_toplayout->addItem(m_label);
m_toplayout->setAlignment(m_label, Qt::AlignCenter);
m_lineedit = new Plasma::LineEdit(this);
m_lineedit->setClickMessage(i18n("Search"));
m_lineedit->setClearButtonShown(true);
m_toplayout->addItem(m_lineedit);
m_toplayout->setAlignment(m_lineedit, Qt::AlignCenter);
setFocusProxy(m_lineedit);
m_tabbar = new Plasma::TabBar(this);
// has not effect..
// m_tabbar->nativeWidget()->setShape(QTabBar::RoundedSouth);
m_favoritesscrollwidget = kMakeScrollWidget(m_tabbar);
m_favoritesscrollwidget->setMinimumSize(s_minimumsize);
m_favoriteswidget = new LauncherFavorites(m_favoritesscrollwidget, m_launcherapplet);
m_favoritesscrollwidget->setWidget(m_favoriteswidget);
m_tabbar->addTab(KIcon(s_favoriteicon), i18n("Favorites"), m_favoritesscrollwidget);
m_applicationsscrollwidget = kMakeScrollWidget(m_tabbar);
m_applicationsscrollwidget->setMinimumSize(s_minimumsize);
m_applicationswidget = new LauncherApplications(m_applicationsscrollwidget, m_launcherapplet);
m_applicationsscrollwidget->setWidget(m_applicationswidget);
m_tabbar->addTab(KIcon(s_genericicon), i18n("Applications"), m_applicationsscrollwidget);
m_recentscrollwidget = kMakeScrollWidget(m_tabbar);
m_recentscrollwidget->setMinimumSize(s_minimumsize);
m_recentwidget = new LauncherRecent(m_recentscrollwidget, m_launcherapplet);
m_recentscrollwidget->setWidget(m_recentwidget);
m_tabbar->addTab(KIcon(s_recenticon), i18n("Recently Used"), m_recentscrollwidget);
m_leavecrollwidget = kMakeScrollWidget(m_tabbar);
m_leavecrollwidget->setMinimumSize(s_minimumsize);
m_leavewidget = new LauncherLeave(m_leavecrollwidget);
m_leavecrollwidget->setWidget(m_leavewidget);
m_tabbar->addTab(KIcon("system-shutdown"), i18n("Leave"), m_leavecrollwidget);
m_layout->addItem(m_tabbar);
// squeeze the icon
m_layout->setStretchFactor(m_tabbar, 100);
m_searchscrollwidget = kMakeScrollWidget(this);
m_searchscrollwidget->setMinimumSize(s_minimumsize);
m_searchscrollwidget->setVisible(false);
m_searchwidget = new LauncherSearch(m_searchscrollwidget, m_launcherapplet);
m_searchscrollwidget->setWidget(m_searchwidget);
m_layout->addItem(m_searchscrollwidget);
connect(
m_lineedit, SIGNAL(textChanged(QString)),
this, SLOT(slotSearch(QString))
);
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
m_timer->setInterval(s_searchdelay);
connect(
m_timer, SIGNAL(timeout()),
this, SLOT(slotTimeout())
);
setLayout(m_layout);
}
void LauncherAppletWidget::resetSearch()
{
m_lineedit->setText(QString());
}
void LauncherAppletWidget::setAllowedRunners(const QStringList &runners)
{
m_searchwidget->setAllowedRunners(runners);
}
void LauncherAppletWidget::slotSearch(const QString &text)
{
const QString query = text.trimmed();
if (query.isEmpty()) {
m_timer->stop();
m_searchwidget->finish();
m_searchscrollwidget->setVisible(false);
m_tabbar->setVisible(true);
return;
}
if (!m_timer->isActive()) {
m_searchwidget->prepare();
}
m_timer->start();
}
void LauncherAppletWidget::slotTimeout()
{
m_searchwidget->query(m_lineedit->text());
m_tabbar->setVisible(false);
m_searchscrollwidget->setVisible(true);
}
LauncherApplet::LauncherApplet(QObject *parent, const QVariantList &args)
: Plasma::PopupApplet(parent, args),
m_launcherwidget(nullptr),
m_editmenuaction(nullptr),
m_selector(nullptr),
m_shareconfig(nullptr)
{
KGlobal::locale()->insertCatalog("plasma_applet_launcher");
setPopupIcon(s_defaultpopupicon);
setAspectRatioMode(Plasma::IgnoreAspectRatio);
setHasConfigurationInterface(true);
m_launcherwidget = new LauncherAppletWidget(this);
setFocusProxy(m_launcherwidget);
}
void LauncherApplet::init()
{
setGlobalShortcut(KShortcut(Qt::ALT+Qt::Key_F2));
m_shareconfig = KSharedConfig::openConfig(globalConfig().config()->name());
m_configgroup = m_shareconfig->group("Plugins");
m_launcherwidget->setAllowedRunners(kAllowedRunners(m_configgroup));
}
QGraphicsWidget* LauncherApplet::graphicsWidget()
{
return m_launcherwidget;
}
QList<QAction*> LauncherApplet::contextualActions()
{
QList<QAction*> result;
const KService::Ptr service = KService::serviceByStorageId("kmenuedit");
if (!service.isNull()) {
if (!m_editmenuaction) {
m_editmenuaction = new QAction(this);
m_editmenuaction->setText(i18n("Edit Applications..."));
connect(
m_editmenuaction, SIGNAL(triggered()),
this, SLOT(slotEditMenu())
);
}
result.append(m_editmenuaction);
}
return result;
}
void LauncherApplet::createConfigurationInterface(KConfigDialog *parent)
{
QWidget *widget = new QWidget();
m_selector = new KPluginSelector(widget);
m_selector->addPlugins(
Plasma::RunnerManager::listRunnerInfo(),
KPluginSelector::ReadConfigFile,
i18n("Available Plugins"), QString(),
m_shareconfig
);
connect(m_selector, SIGNAL(changed(bool)), parent, SLOT(settingsModified()));
parent->addPage(m_selector, i18n("Runners"), "preferences-plugin");
connect(parent, SIGNAL(applyClicked()), this, SLOT(slotConfigAccepted()));
connect(parent, SIGNAL(okClicked()), this, SLOT(slotConfigAccepted()));
}
void LauncherApplet::resetState()
{
hidePopup();
m_launcherwidget->resetSearch();
}
void LauncherApplet::slotEditMenu()
{
if (KToolInvocation::kdeinitExec("kmenuedit") == 0) {
hidePopup();
} else {
showMessage(KIcon("dialog-error"), i18n("Failed to launch menu editor"), Plasma::MessageButton::ButtonOk);
}
}
void LauncherApplet::slotConfigAccepted()
{
Q_ASSERT(m_selector != nullptr);
Q_ASSERT(m_shareconfig != nullptr);
m_selector->save();
m_configgroup.sync();
m_shareconfig->sync();
m_launcherwidget->setAllowedRunners(kAllowedRunners(m_configgroup));
m_launcherwidget->resetSearch();
emit configNeedsSaving();
}
#include "launcher.moc"
#include "moc_launcher.cpp"