diff --git a/plugin-statusnotifier/CMakeLists.txt b/plugin-statusnotifier/CMakeLists.txt index 82970d9e3..87369cd2a 100644 --- a/plugin-statusnotifier/CMakeLists.txt +++ b/plugin-statusnotifier/CMakeLists.txt @@ -6,6 +6,7 @@ find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Concurrent) set(HEADERS statusnotifier.h + statusnotifierconfiguration.h dbustypes.h statusnotifierbutton.h statusnotifieriteminterface.h @@ -16,6 +17,7 @@ set(HEADERS set(SOURCES statusnotifier.cpp + statusnotifierconfiguration.cpp dbustypes.cpp statusnotifierbutton.cpp statusnotifieriteminterface.cpp @@ -24,6 +26,10 @@ set(SOURCES sniasync.cpp ) +set(UIS + statusnotifierconfiguration.ui +) + qt5_add_dbus_adaptor(DBUS_SOURCES org.kde.StatusNotifierItem.xml statusnotifieriteminterface.h diff --git a/plugin-statusnotifier/statusnotifier.cpp b/plugin-statusnotifier/statusnotifier.cpp index 0ece886b8..3d3aaa0cf 100644 --- a/plugin-statusnotifier/statusnotifier.cpp +++ b/plugin-statusnotifier/statusnotifier.cpp @@ -35,6 +35,13 @@ StatusNotifier::StatusNotifier(const ILXQtPanelPluginStartupInfo &startupInfo) : m_widget = new StatusNotifierWidget(this); } +QDialog *StatusNotifier::configureDialog() +{ + auto dialog = new StatusNotifierConfiguration(settings()); + dialog->addItems(m_widget->itemTitles()); + return dialog; +} + void StatusNotifier::realign() { m_widget->realign(); diff --git a/plugin-statusnotifier/statusnotifier.h b/plugin-statusnotifier/statusnotifier.h index 80572e2ea..a64b32b9b 100644 --- a/plugin-statusnotifier/statusnotifier.h +++ b/plugin-statusnotifier/statusnotifier.h @@ -31,6 +31,7 @@ #include "../panel/ilxqtpanelplugin.h" #include "statusnotifierwidget.h" +#include "statusnotifierconfiguration.h" class StatusNotifier : public QObject, public ILXQtPanelPlugin { @@ -41,9 +42,13 @@ class StatusNotifier : public QObject, public ILXQtPanelPlugin bool isSeparate() const { return true; } void realign(); QString themeId() const { return QStringLiteral("StatusNotifier"); } - virtual Flags flags() const { return SingleInstance | NeedsHandle; } + virtual Flags flags() const { return SingleInstance | HaveConfigDialog | NeedsHandle; } QWidget *widget() { return m_widget; } + QDialog *configureDialog() override; + + void settingsChanged() { m_widget->settingsChanged(); } + private: StatusNotifierWidget *m_widget; }; diff --git a/plugin-statusnotifier/statusnotifierbutton.cpp b/plugin-statusnotifier/statusnotifierbutton.cpp index 77f261d15..f3161b96e 100644 --- a/plugin-statusnotifier/statusnotifierbutton.cpp +++ b/plugin-statusnotifier/statusnotifierbutton.cpp @@ -58,7 +58,8 @@ StatusNotifierButton::StatusNotifierButton(QString service, QString objectPath, mMenu(nullptr), mStatus(Passive), mFallbackIcon(QIcon::fromTheme(QLatin1String("application-x-executable"))), - mPlugin(plugin) + mPlugin(plugin), + mAutoHide(false) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAutoRaise(true); @@ -70,6 +71,15 @@ StatusNotifierButton::StatusNotifierButton(QString service, QString objectPath, connect(interface, &SniAsync::NewToolTip, this, &StatusNotifierButton::newToolTip); connect(interface, &SniAsync::NewStatus, this, &StatusNotifierButton::newStatus); + // get the title only at the start because that title is used + // for deciding about (auto-)hiding + interface->propertyGetAsync(QLatin1String("Title"), [this] (QString value) { + mTitle = value; + QTimer::singleShot(0, this, [this]() { // wait for the c-tor + Q_EMIT titleFound(mTitle); + }); + }); + interface->propertyGetAsync(QLatin1String("Menu"), [this] (QDBusObjectPath path) { if (!path.path().isEmpty()) { @@ -90,6 +100,14 @@ StatusNotifierButton::StatusNotifierButton(QString service, QString objectPath, }); newToolTip(); + + // The timer that hides an auto-hiding button after it gets attention: + mHideTimer.setSingleShot(true); + mHideTimer.setInterval(300000); + connect(&mHideTimer, &QTimer::timeout, this, [this] { + hide(); + Q_EMIT attentionChanged(); + }); } StatusNotifierButton::~StatusNotifierButton() @@ -99,6 +117,9 @@ StatusNotifierButton::~StatusNotifierButton() void StatusNotifierButton::newIcon() { + if (!icon().isNull() && icon().name() != QLatin1String("application-x-executable")) + onNeedingAttention(); + interface->propertyGetAsync(QLatin1String("IconThemePath"), [this] (QString value) { refetchIcon(Passive, value); }); @@ -106,6 +127,8 @@ void StatusNotifierButton::newIcon() void StatusNotifierButton::newOverlayIcon() { + onNeedingAttention(); + interface->propertyGetAsync(QLatin1String("IconThemePath"), [this] (QString value) { refetchIcon(Active, value); }); @@ -113,6 +136,8 @@ void StatusNotifierButton::newOverlayIcon() void StatusNotifierButton::newAttentionIcon() { + onNeedingAttention(); + interface->propertyGetAsync(QLatin1String("IconThemePath"), [this] (QString value) { refetchIcon(NeedsAttention, value); }); @@ -255,6 +280,8 @@ void StatusNotifierButton::newStatus(QString status) return; mStatus = newStatus; + if (mStatus == NeedsAttention) + onNeedingAttention(); resetIcon(); } @@ -303,3 +330,31 @@ void StatusNotifierButton::resetIcon() else setIcon(mFallbackIcon); } + +void StatusNotifierButton::setAutoHide(bool autoHide, int minutes, bool forcedVisible) +{ + if (autoHide) + mHideTimer.setInterval(qBound(1, minutes, 60) * 60000); + if (mAutoHide != autoHide) + { + mAutoHide = autoHide; + setVisible(!mAutoHide || forcedVisible); + if (!mAutoHide) + mHideTimer.stop(); + } +} + +void StatusNotifierButton::onNeedingAttention() +{ + if (mAutoHide) + { + show(); + mHideTimer.start(); + Q_EMIT attentionChanged(); + } +} + +bool StatusNotifierButton::hasAttention() const +{ + return mHideTimer.isActive(); +} diff --git a/plugin-statusnotifier/statusnotifierbutton.h b/plugin-statusnotifier/statusnotifierbutton.h index fc248f747..3a14213a0 100644 --- a/plugin-statusnotifier/statusnotifierbutton.h +++ b/plugin-statusnotifier/statusnotifierbutton.h @@ -36,6 +36,7 @@ #include #include #include +#include #if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) template inline T qFromUnaligned(const uchar *src) @@ -63,6 +64,16 @@ class StatusNotifierButton : public QToolButton Passive, Active, NeedsAttention }; + QString title() const { + return mTitle; + } + bool hasAttention() const; + void setAutoHide(bool autoHide, int minutes = 5, bool forcedVisible = false); + +signals: + void titleFound(const QString &title); + void attentionChanged(); + public slots: void newIcon(); void newAttentionIcon(); @@ -71,6 +82,8 @@ public slots: void newStatus(QString status); private: + void onNeedingAttention(); + SniAsync *interface; QMenu *mMenu; Status mStatus; @@ -79,6 +92,10 @@ public slots: ILXQtPanelPlugin* mPlugin; + QString mTitle; + bool mAutoHide; + QTimer mHideTimer; + protected: void contextMenuEvent(QContextMenuEvent * event); void mouseReleaseEvent(QMouseEvent *event); diff --git a/plugin-statusnotifier/statusnotifierconfiguration.cpp b/plugin-statusnotifier/statusnotifierconfiguration.cpp new file mode 100644 index 000000000..f3d480fae --- /dev/null +++ b/plugin-statusnotifier/statusnotifierconfiguration.cpp @@ -0,0 +1,115 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2020 LXQt team + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#include "statusnotifierconfiguration.h" +#include "ui_statusnotifierconfiguration.h" +#include +#include + +StatusNotifierConfiguration::StatusNotifierConfiguration(PluginSettings *settings, QWidget *parent): + LXQtPanelPluginConfigDialog(settings, parent), + ui(new Ui::StatusNotifierConfiguration) +{ + setAttribute(Qt::WA_DeleteOnClose); + setObjectName(QStringLiteral("StatusNotifierConfigurationWindow")); + ui->setupUi(this); + + if (QPushButton *closeBtn = ui->buttons->button(QDialogButtonBox::Close)) + closeBtn->setDefault(true); + connect(ui->buttons, &QDialogButtonBox::clicked, this, &StatusNotifierConfiguration::dialogButtonsAction); + + ui->tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + ui->tableWidget->horizontalHeader()->setSectionsClickable(false); + ui->tableWidget->sortByColumn(0, Qt::AscendingOrder); + + loadSettings(); + + connect(ui->attentionSB, &QAbstractSpinBox::editingFinished, this, &StatusNotifierConfiguration::saveSettings); +} + +StatusNotifierConfiguration::~StatusNotifierConfiguration() +{ + delete ui; +} + +void StatusNotifierConfiguration::loadSettings() +{ + ui->attentionSB->setValue(settings().value(QStringLiteral("attentionPeriod"), 5).toInt()); + mAutoHideList = settings().value(QStringLiteral("autoHideList")).toStringList(); + mHideList = settings().value(QStringLiteral("hideList")).toStringList(); +} + +void StatusNotifierConfiguration::saveSettings() +{ + settings().setValue(QStringLiteral("attentionPeriod"), ui->attentionSB->value()); + settings().setValue(QStringLiteral("autoHideList"), mAutoHideList); + settings().setValue(QStringLiteral("hideList"), mHideList); +} + +void StatusNotifierConfiguration::addItems(const QStringList &items) +{ + ui->tableWidget->setRowCount(items.size()); + ui->tableWidget->setSortingEnabled(false); + int index = 0; + for (const auto &item : items) + { + // first column + QTableWidgetItem *widgetItem = new QTableWidgetItem(item); + widgetItem->setFlags(widgetItem->flags() & ~Qt::ItemIsEditable & ~Qt::ItemIsSelectable); + ui->tableWidget->setItem(index, 0, widgetItem); + // second column + QComboBox *cb = new QComboBox(); + cb->addItems(QStringList() << tr("Always show") << tr("Auto-hide") << tr("Always hide")); + if (mAutoHideList.contains(item)) + cb->setCurrentIndex(1); + else if (mHideList.contains(item)) + cb->setCurrentIndex(2); + connect(cb, QOverload::of(&QComboBox::currentIndexChanged), this, [this, item] (int indx) { + if (indx == 0) + { + mAutoHideList.removeAll(item); + mHideList.removeAll(item); + } + else if (indx == 1) + { + mHideList.removeAll(item); + if (!mAutoHideList.contains(item)) + mAutoHideList << item; + } + else if (indx == 2) + { + mAutoHideList.removeAll(item); + if (!mHideList.contains(item)) + mHideList << item; + } + saveSettings(); + }); + ui->tableWidget->setCellWidget(index, 1, cb); + ++ index; + } + ui->tableWidget->setSortingEnabled(true); + ui->tableWidget->horizontalHeader()->setSortIndicatorShown(false); + ui->tableWidget->setCurrentCell(0, 1); +} diff --git a/plugin-statusnotifier/statusnotifierconfiguration.h b/plugin-statusnotifier/statusnotifierconfiguration.h new file mode 100644 index 000000000..c3379f672 --- /dev/null +++ b/plugin-statusnotifier/statusnotifierconfiguration.h @@ -0,0 +1,58 @@ +/* BEGIN_COMMON_COPYRIGHT_HEADER + * (c)LGPL2+ + * + * LXQt - a lightweight, Qt based, desktop toolset + * https://lxqt.org + * + * Copyright: 2020 LXQt team + * + * This program or library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * 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 + * Lesser General Public License for more details. + + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * END_COMMON_COPYRIGHT_HEADER */ + +#ifndef STATUSNOTIFIERCONFIGURATION_H +#define STATUSNOTIFIERCONFIGURATION_H + +#include "../panel/lxqtpanelpluginconfigdialog.h" +#include "../panel/pluginsettings.h" + +namespace Ui { + class StatusNotifierConfiguration; +} + +class StatusNotifierConfiguration : public LXQtPanelPluginConfigDialog +{ + Q_OBJECT + +public: + explicit StatusNotifierConfiguration(PluginSettings *settings, QWidget *parent = nullptr); + ~StatusNotifierConfiguration(); + + void addItems(const QStringList &items); + +private: + Ui::StatusNotifierConfiguration *ui; + + QStringList mAutoHideList; + QStringList mHideList; + + void loadSettings(); + +private slots: + void saveSettings(); +}; + +#endif // STATUSNOTIFIERCONFIGURATION_H diff --git a/plugin-statusnotifier/statusnotifierconfiguration.ui b/plugin-statusnotifier/statusnotifierconfiguration.ui new file mode 100644 index 000000000..eb21ff079 --- /dev/null +++ b/plugin-statusnotifier/statusnotifierconfiguration.ui @@ -0,0 +1,145 @@ + + + StatusNotifierConfiguration + + + + 0 + 0 + 400 + 400 + + + + Status Notifier Settings + + + + 5 + + + + + 5 + + + + + An auto-hiding item will remain visible for this period if it needs attention. + + + Attention period: + + + + + + + An auto-hiding item will remain visible for this period if it needs attention. + + + minute(s) + + + 1 + + + 60 + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 5 + 5 + + + + + + + + + + Change visibility of items + + + + + + QAbstractItemView::SingleSelection + + + false + + + + Item + + + + + Visibility + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Reset + + + + + + + + + buttons + accepted() + StatusNotifierConfiguration + accept() + + + 262 + 496 + + + 157 + 274 + + + + + buttons + rejected() + StatusNotifierConfiguration + reject() + + + 330 + 496 + + + 286 + 274 + + + + + diff --git a/plugin-statusnotifier/statusnotifierwidget.cpp b/plugin-statusnotifier/statusnotifierwidget.cpp index f8667241d..6a4416b8b 100644 --- a/plugin-statusnotifier/statusnotifierwidget.cpp +++ b/plugin-statusnotifier/statusnotifierwidget.cpp @@ -32,14 +32,55 @@ #include #include #include +#include "../panel/pluginsettings.h" #include "../panel/ilxqtpanelplugin.h" StatusNotifierWidget::StatusNotifierWidget(ILXQtPanelPlugin *plugin, QWidget *parent) : QWidget(parent), - mPlugin(plugin) + mPlugin(plugin), + mAttentionPeriod(5), + mForceVisible(false) { setLayout(new LXQt::GridLayout(this)); + // The button that shows all hidden items: + mShowBtn = new QToolButton(this); + mShowBtn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + mShowBtn->setAutoRaise(true); + mShowBtn->setToolButtonStyle(Qt::ToolButtonIconOnly); + mShowBtn->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); + layout()->addWidget(mShowBtn); + mShowBtn->hide(); + connect(mShowBtn, &QAbstractButton::released, [this] { + if (mForceVisible) + return; // all items are visible; nothing to do + mHideTimer.stop(); + mForceVisible = true; + const auto allButtons = findChildren(QString(), Qt::FindDirectChildrenOnly); + for (const auto &btn : allButtons) + btn->show(); + }); + + settingsChanged(); + + // The timer that hides (auto-)hidden items after 2 seconds: + mHideTimer.setSingleShot(true); + mHideTimer.setInterval(2000); + connect(&mHideTimer, &QTimer::timeout, this, [this] { + mForceVisible = false; + const auto allButtons = findChildren(QString(), Qt::FindDirectChildrenOnly); + for (const auto &btn : allButtons) + { + if (btn->hasAttention() + || (!mAutoHideList.contains(btn->title()) + && !mHideList.contains(btn->title()))) + { + continue; + } + btn->hide(); + } + }); + QFutureWatcher * future_watcher = new QFutureWatcher; connect(future_watcher, &QFutureWatcher::finished, this, [this, future_watcher] { @@ -70,7 +111,6 @@ StatusNotifierWidget::StatusNotifierWidget(ILXQtPanelPlugin *plugin, QWidget *pa future_watcher->setFuture(future); realign(); - } StatusNotifierWidget::~StatusNotifierWidget() @@ -78,6 +118,17 @@ StatusNotifierWidget::~StatusNotifierWidget() delete mWatcher; } +void StatusNotifierWidget::leaveEvent(QEvent * /*event*/) +{ + if (mForceVisible) + mHideTimer.start(); +} + +void StatusNotifierWidget::enterEvent(QEvent * /*event*/) +{ + mHideTimer.stop(); +} + void StatusNotifierWidget::itemAdded(QString serviceAndPath) { int slash = serviceAndPath.indexOf(QLatin1Char('/')); @@ -88,6 +139,51 @@ void StatusNotifierWidget::itemAdded(QString serviceAndPath) mServices.insert(serviceAndPath, button); layout()->addWidget(button); button->show(); + + // show/hide the added item appropriately and show mShowBtn if needed + connect(button, &StatusNotifierButton::titleFound, [this, button] (const QString &title) { + mItemTitles << title; + if (mAutoHideList.contains(title)) + { + mShowBtn->show(); + button->setAutoHide(true, mAttentionPeriod, mForceVisible); + } + else if (mHideList.contains(title)) + { + mShowBtn->show(); + button->setAutoHide(false); + if (!mForceVisible) + button->hide(); + } + }); + // show/hide mShowBtn if needed whenever an item gets or loses attention + connect(button, &StatusNotifierButton::attentionChanged, [this, button] { + if (button->hasAttention()) + { + if (mShowBtn->isVisible()) + { + const auto allButtons = findChildren(QString(), Qt::FindDirectChildrenOnly); + for (const auto &btn : allButtons) + { + if (!btn->isVisible() + // or shown only because mShowBtn was clicked + || (mForceVisible && !btn->hasAttention() + && (mAutoHideList.contains(btn->title()) + || mHideList.contains(btn->title())))) + { + return; + } + } + // there is no item in the hiding list and all auto-hiding items have attention; + // so, mShowBtn has no job + mHideTimer.stop(); + mForceVisible = false; + mShowBtn->hide(); + } + } + else // the auto-hiding item lost attention + mShowBtn->show(); + }); } void StatusNotifierWidget::itemRemoved(const QString &serviceAndPath) @@ -95,8 +191,69 @@ void StatusNotifierWidget::itemRemoved(const QString &serviceAndPath) StatusNotifierButton *button = mServices.value(serviceAndPath, nullptr); if (button) { + mItemTitles.removeOne(button->title()); + if (mShowBtn->isVisible()) + { // hide mShowBtn if no (auto-)hidden item remains + bool showBtn = false; + for (const auto &name : qAsConst(mItemTitles)) + { + if (mAutoHideList.contains(name) || mHideList.contains(name)) + { + showBtn = true; + break; + } + } + if (!showBtn) + { + mHideTimer.stop(); + mForceVisible = false; + mShowBtn->hide(); + } + } button->deleteLater(); layout()->removeWidget(button); + mServices.remove(serviceAndPath); + } +} + +void StatusNotifierWidget::settingsChanged() +{ + mAttentionPeriod = mPlugin->settings()->value(QStringLiteral("attentionPeriod"), 5).toInt(); + mAutoHideList = mPlugin->settings()->value(QStringLiteral("autoHideList")).toStringList(); + mHideList = mPlugin->settings()->value(QStringLiteral("hideList")).toStringList(); + + // show/hide items as well as showBtn appropriately + const auto allButtons = findChildren(QString(), Qt::FindDirectChildrenOnly); + bool showBtn = false; + for (const auto &btn : allButtons) + { + if (mAutoHideList.contains(btn->title())) + { + btn->setAutoHide(true, mAttentionPeriod); + if (!btn->isVisible() + // or shown only because mShowBtn was clicked + || !btn->hasAttention()) + { + showBtn = true; + } + } + else if (mHideList.contains(btn->title())) + { + showBtn = true; + btn->setAutoHide(false); + btn->hide(); + } + else + { + btn->setAutoHide(false); + btn->show(); // may have been in mHideList before + } + } + mShowBtn->setVisible(showBtn); + if (!showBtn) + { + mHideTimer.stop(); + mForceVisible = false; } } @@ -119,3 +276,10 @@ void StatusNotifierWidget::realign() layout->setEnabled(true); } + +QStringList StatusNotifierWidget::itemTitles() const +{ + QStringList names = mItemTitles; + names.removeDuplicates(); + return names; +} diff --git a/plugin-statusnotifier/statusnotifierwidget.h b/plugin-statusnotifier/statusnotifierwidget.h index 5852bf177..28d2dcd70 100644 --- a/plugin-statusnotifier/statusnotifierwidget.h +++ b/plugin-statusnotifier/statusnotifierwidget.h @@ -30,6 +30,7 @@ #define STATUSNOTIFIERWIDGET_H #include +#include #include @@ -44,6 +45,9 @@ class StatusNotifierWidget : public QWidget StatusNotifierWidget(ILXQtPanelPlugin *plugin, QWidget *parent = nullptr); ~StatusNotifierWidget(); + void settingsChanged(); + QStringList itemTitles() const; + signals: public slots: @@ -52,11 +56,24 @@ public slots: void realign(); +protected: + void leaveEvent(QEvent *event) override; + void enterEvent(QEvent *event) override; + private: ILXQtPanelPlugin *mPlugin; StatusNotifierWatcher *mWatcher; + QTimer mHideTimer; + QHash mServices; + + QStringList mItemTitles; + QStringList mAutoHideList; + QStringList mHideList; + QToolButton *mShowBtn; + int mAttentionPeriod; + bool mForceVisible; }; #endif // STATUSNOTIFIERWIDGET_H