Skip to content

Commit

Permalink
Implemented auto-hiding for Status Notifier (lxqt#1412)
Browse files Browse the repository at this point in the history
* Implemented auto-hiding for Status Notifier

The items can be auto-hidden or always shown/hidden. The default is "Always show".

If there are (auto-)hidden items, an extra button will appear, which shows all items when clicked; then (auto-)hidden items will be made hidden again after 2 seconds if the the mouse cursor leaves Status Notifier.

The auto-hidden items that need attention are shown for 5 minutes (the interval is configurable). Apart from explicit attention requests, an icon change is also interpreted as an attention request with auto-hiding. That's useful, for example, when a battery or WiFi icon changes.

Closes lxqt/lxqt#1012

* Hide/show status notifier button more appropriately

Hide the status notifier button if all auto-hiding items have attention and there is no item in the hiding list; show it otherwise.

Previously, the button was shown when auto-hiding items existed, even when all of them had attention and no item was in the hiding list.

Also, optimized the code a little.

* Wait for c-tor before announcing the title of a Status Notifier item

Because of a possible race condition.

* Code cleanup for Status Notifier's auto-hiding
  • Loading branch information
tsujan authored Jun 21, 2020
1 parent cd4eac3 commit a21a340
Show file tree
Hide file tree
Showing 10 changed files with 593 additions and 4 deletions.
6 changes: 6 additions & 0 deletions plugin-statusnotifier/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,6 +17,7 @@ set(HEADERS

set(SOURCES
statusnotifier.cpp
statusnotifierconfiguration.cpp
dbustypes.cpp
statusnotifierbutton.cpp
statusnotifieriteminterface.cpp
Expand All @@ -24,6 +26,10 @@ set(SOURCES
sniasync.cpp
)

set(UIS
statusnotifierconfiguration.ui
)

qt5_add_dbus_adaptor(DBUS_SOURCES
org.kde.StatusNotifierItem.xml
statusnotifieriteminterface.h
Expand Down
7 changes: 7 additions & 0 deletions plugin-statusnotifier/statusnotifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
7 changes: 6 additions & 1 deletion plugin-statusnotifier/statusnotifier.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

#include "../panel/ilxqtpanelplugin.h"
#include "statusnotifierwidget.h"
#include "statusnotifierconfiguration.h"

class StatusNotifier : public QObject, public ILXQtPanelPlugin
{
Expand All @@ -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;
};
Expand Down
57 changes: 56 additions & 1 deletion plugin-statusnotifier/statusnotifierbutton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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())
{
Expand All @@ -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()
Expand All @@ -99,20 +117,27 @@ 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);
});
}

void StatusNotifierButton::newOverlayIcon()
{
onNeedingAttention();

interface->propertyGetAsync(QLatin1String("IconThemePath"), [this] (QString value) {
refetchIcon(Active, value);
});
}

void StatusNotifierButton::newAttentionIcon()
{
onNeedingAttention();

interface->propertyGetAsync(QLatin1String("IconThemePath"), [this] (QString value) {
refetchIcon(NeedsAttention, value);
});
Expand Down Expand Up @@ -255,6 +280,8 @@ void StatusNotifierButton::newStatus(QString status)
return;

mStatus = newStatus;
if (mStatus == NeedsAttention)
onNeedingAttention();
resetIcon();
}

Expand Down Expand Up @@ -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();
}
17 changes: 17 additions & 0 deletions plugin-statusnotifier/statusnotifierbutton.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <QToolButton>
#include <QWheelEvent>
#include <QMenu>
#include <QTimer>

#if QT_VERSION < QT_VERSION_CHECK(5, 5, 0)
template <typename T> inline T qFromUnaligned(const uchar *src)
Expand Down Expand Up @@ -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();
Expand All @@ -71,6 +82,8 @@ public slots:
void newStatus(QString status);

private:
void onNeedingAttention();

SniAsync *interface;
QMenu *mMenu;
Status mStatus;
Expand All @@ -79,6 +92,10 @@ public slots:

ILXQtPanelPlugin* mPlugin;

QString mTitle;
bool mAutoHide;
QTimer mHideTimer;

protected:
void contextMenuEvent(QContextMenuEvent * event);
void mouseReleaseEvent(QMouseEvent *event);
Expand Down
115 changes: 115 additions & 0 deletions plugin-statusnotifier/statusnotifierconfiguration.cpp
Original file line number Diff line number Diff line change
@@ -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 <QPushButton>
#include <QComboBox>

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<int>::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);
}
58 changes: 58 additions & 0 deletions plugin-statusnotifier/statusnotifierconfiguration.h
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit a21a340

Please sign in to comment.