Show notification count in tray icon.

This commit is contained in:
Black Hat
2019-07-01 18:19:00 +08:00
parent 858c5a638f
commit 3e0009a069
11 changed files with 251 additions and 26 deletions

View File

@@ -34,6 +34,12 @@ Item {
onNewMessage: if (!window.active && MSettings.showNotification) notificationsManager.postNotification(roomId, eventId, roomName, senderName, text, icon) onNewMessage: if (!window.active && MSettings.showNotification) notificationsManager.postNotification(roomId, eventId, roomName, senderName, text, icon)
} }
Binding {
target: trayIcon
property: "notificationCount"
value: roomListModel.notificationCount
}
SortFilterProxyModel { SortFilterProxyModel {
id: sortedRoomListModel id: sortedRoomListModel

View File

@@ -2,8 +2,7 @@ import QtQuick 2.12
import QtQuick.Controls 2.12 import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12 import QtQuick.Layouts 1.12
import QtQuick.Controls.Material 2.12 import QtQuick.Controls.Material 2.12
import Qt.labs.settings 1.0 import Qt.labs.settings 1.1
import Qt.labs.platform 1.0 as Platform
import Spectral.Panel 2.0 import Spectral.Panel 2.0
import Spectral.Component 2.0 import Spectral.Component 2.0
@@ -35,20 +34,14 @@ ApplicationWindow {
color: MSettings.darkTheme ? "#303030" : "#FFFFFF" color: MSettings.darkTheme ? "#303030" : "#FFFFFF"
} }
Platform.SystemTrayIcon { TrayIcon {
visible: MSettings.showTray id: trayIcon
iconSource: "qrc:/assets/img/icon.png"
menu: Platform.Menu { visible: MSettings.showTray
Platform.MenuItem {
text: qsTr("Toggle Window") iconSource: ":/assets/img/icon.png"
onTriggered: window.visible ? hideWindow() : showWindow()
} onShowWindow: window.showWindow()
Platform.MenuItem {
text: qsTr("Quit")
onTriggered: Qt.quit()
}
}
} }
Controller { Controller {

View File

@@ -44,7 +44,8 @@ HEADERS += \
include/hoedown/escape.h \ include/hoedown/escape.h \
include/hoedown/html.h \ include/hoedown/html.h \
include/hoedown/stack.h \ include/hoedown/stack.h \
include/hoedown/version.h include/hoedown/version.h \
src/trayicon.h
SOURCES += \ SOURCES += \
include/hoedown/autolink.c \ include/hoedown/autolink.c \
@@ -55,7 +56,8 @@ SOURCES += \
include/hoedown/html_blocks.c \ include/hoedown/html_blocks.c \
include/hoedown/html_smartypants.c \ include/hoedown/html_smartypants.c \
include/hoedown/stack.c \ include/hoedown/stack.c \
include/hoedown/version.c include/hoedown/version.c \
src/trayicon.cpp
# The following define makes your compiler emit warnings if you use # The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked deprecated (the exact warnings # any feature of Qt which as been marked deprecated (the exact warnings

View File

@@ -307,7 +307,3 @@ int Controller::dpi() {
void Controller::setDpi(int dpi) { void Controller::setDpi(int dpi) {
SettingsGroup("Interface").setValue("dpi", dpi); SettingsGroup("Interface").setValue("dpi", dpi);
} }
QString Controller::removeReply(const QString& text) {
return utils::removeReply(text);
}

View File

@@ -74,14 +74,12 @@ class Controller : public QObject {
QByteArray loadAccessTokenFromKeyChain(const AccountSettings& account); QByteArray loadAccessTokenFromKeyChain(const AccountSettings& account);
bool saveAccessTokenToFile(const AccountSettings& account, bool saveAccessTokenToFile(const AccountSettings& account,
const QByteArray& accessToken); const QByteArray& accessToken);
bool saveAccessTokenToKeyChain(const AccountSettings& account, bool saveAccessTokenToKeyChain(const AccountSettings& account,
const QByteArray& accessToken); const QByteArray& accessToken);
void loadSettings(); void loadSettings();
void saveSettings() const; void saveSettings() const;
Q_INVOKABLE QString removeReply(const QString& text);
private slots: private slots:
void invokeLogin(); void invokeLogin();
@@ -94,6 +92,7 @@ class Controller : public QObject {
void initiated(); void initiated();
void notificationClicked(const QString roomId, const QString eventId); void notificationClicked(const QString roomId, const QString eventId);
void quitOnLastWindowClosedChanged(); void quitOnLastWindowClosedChanged();
void unreadCountChanged();
void connectionChanged(); void connectionChanged();
public slots: public slots:

View File

@@ -16,6 +16,7 @@
#include "roomlistmodel.h" #include "roomlistmodel.h"
#include "spectralroom.h" #include "spectralroom.h"
#include "spectraluser.h" #include "spectraluser.h"
#include "trayicon.h"
#include "userlistmodel.h" #include "userlistmodel.h"
#include "csapi/joining.h" #include "csapi/joining.h"
@@ -47,6 +48,7 @@ int main(int argc, char* argv[]) {
qmlRegisterType<EmojiModel>("Spectral", 0, 1, "EmojiModel"); qmlRegisterType<EmojiModel>("Spectral", 0, 1, "EmojiModel");
qmlRegisterType<NotificationsManager>("Spectral", 0, 1, qmlRegisterType<NotificationsManager>("Spectral", 0, 1,
"NotificationsManager"); "NotificationsManager");
qmlRegisterType<TrayIcon>("Spectral", 0, 1, "TrayIcon");
qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard"); qmlRegisterType<ImageClipboard>("Spectral", 0, 1, "ImageClipboard");
qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1, qmlRegisterUncreatableType<RoomMessageEvent>("Spectral", 0, 1,
"RoomMessageEvent", "ENUM"); "RoomMessageEvent", "ENUM");

View File

@@ -26,7 +26,7 @@ NotificationsManager::NotificationsManager(QObject *parent)
void NotificationsManager::postNotification( void NotificationsManager::postNotification(
const QString &roomid, const QString &eventid, const QString &roomname, const QString &roomid, const QString &eventid, const QString &roomname,
const QString &sender, const QString &text, const QImage &icon) { const QString &sender, const QString &text, const QImage &icon) {
uint id = showNotification(roomname, sender + ": " + text, icon); uint id = showNotification(sender + " (" + roomname + ")", text, icon);
notificationIds[id] = roomEventId{roomid, eventid}; notificationIds[id] = roomEventId{roomid, eventid};
} }
/** /**

View File

@@ -62,6 +62,7 @@ void RoomListModel::doResetModel() {
for (auto r : m_connection->roomMap()) for (auto r : m_connection->roomMap())
doAddRoom(r); doAddRoom(r);
endResetModel(); endResetModel();
refreshNotificationCount();
} }
SpectralRoom* RoomListModel::roomAt(int row) { SpectralRoom* RoomListModel::roomAt(int row) {
@@ -104,6 +105,17 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
sender->displayname(), room->eventToString(*lastEvent), sender->displayname(), room->eventToString(*lastEvent),
room->avatar(128)); room->avatar(128));
}); });
connect(room, &Room::notificationCountChanged, this,
&RoomListModel::refreshNotificationCount);
}
void RoomListModel::refreshNotificationCount() {
int count = 0;
for (auto room : m_rooms) {
count += room->notificationCount();
}
m_notificationCount = count;
emit notificationCountChanged();
} }
void RoomListModel::updateRoom(Room* room, Room* prev) { void RoomListModel::updateRoom(Room* room, Room* prev) {

View File

@@ -27,6 +27,8 @@ class RoomType : public QObject {
class RoomListModel : public QAbstractListModel { class RoomListModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
Q_PROPERTY(Connection* connection READ connection WRITE setConnection) Q_PROPERTY(Connection* connection READ connection WRITE setConnection)
Q_PROPERTY(int notificationCount READ notificationCount NOTIFY
notificationCountChanged)
public: public:
enum EventRoles { enum EventRoles {
@@ -59,19 +61,27 @@ class RoomListModel : public QAbstractListModel {
QHash<int, QByteArray> roleNames() const; QHash<int, QByteArray> roleNames() const;
int notificationCount() { return m_notificationCount; }
private slots: private slots:
void doAddRoom(Room* room); void doAddRoom(Room* room);
void updateRoom(Room* room, Room* prev); void updateRoom(Room* room, Room* prev);
void deleteRoom(Room* room); void deleteRoom(Room* room);
void refresh(SpectralRoom* room, const QVector<int>& roles = {}); void refresh(SpectralRoom* room, const QVector<int>& roles = {});
void refreshNotificationCount();
private: private:
Connection* m_connection = nullptr; Connection* m_connection = nullptr;
QList<SpectralRoom*> m_rooms; QList<SpectralRoom*> m_rooms;
int m_notificationCount = 0;
void connectRoomSignals(SpectralRoom* room); void connectRoomSignals(SpectralRoom* room);
signals: signals:
void connectionChanged(); void connectionChanged();
void notificationCountChanged();
void roomAdded(SpectralRoom* room); void roomAdded(SpectralRoom* room);
void newMessage(const QString& roomId, void newMessage(const QString& roomId,
const QString& eventId, const QString& eventId,

138
src/trayicon.cpp Normal file
View File

@@ -0,0 +1,138 @@
#include "trayicon.h"
// Modified from mujx/nheko's TrayIcon.
#include <QApplication>
#include <QList>
#include <QMenu>
#include <QTimer>
#include <QtDebug>
#if defined(Q_OS_MAC)
#include <QtMacExtras>
#endif
MsgCountComposedIcon::MsgCountComposedIcon(const QString& filename)
: QIconEngine() {
icon_ = QIcon(filename);
}
void MsgCountComposedIcon::paint(QPainter* painter,
const QRect& rect,
QIcon::Mode mode,
QIcon::State state) {
painter->setRenderHint(QPainter::TextAntialiasing);
painter->setRenderHint(QPainter::SmoothPixmapTransform);
painter->setRenderHint(QPainter::Antialiasing);
icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
if (msgCount <= 0)
return;
QColor backgroundColor("red");
QColor textColor("white");
QBrush brush;
brush.setStyle(Qt::SolidPattern);
brush.setColor(backgroundColor);
painter->setBrush(brush);
painter->setPen(Qt::NoPen);
painter->setFont(QFont("Open Sans", 8, QFont::Black));
QRectF bubble(rect.width() - BubbleDiameter, rect.height() - BubbleDiameter,
BubbleDiameter, BubbleDiameter);
painter->drawEllipse(bubble);
painter->setPen(QPen(textColor));
painter->setBrush(Qt::NoBrush);
if (msgCount < 100) {
painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
} else {
painter->drawText(bubble, Qt::AlignCenter, "99+");
}
}
QIconEngine* MsgCountComposedIcon::clone() const {
return new MsgCountComposedIcon(*this);
}
QList<QSize> MsgCountComposedIcon::availableSizes(QIcon::Mode mode,
QIcon::State state) const {
Q_UNUSED(mode)
Q_UNUSED(state)
QList<QSize> sizes;
sizes.append(QSize(24, 24));
sizes.append(QSize(32, 32));
sizes.append(QSize(48, 48));
sizes.append(QSize(64, 64));
sizes.append(QSize(128, 128));
sizes.append(QSize(256, 256));
return sizes;
}
QPixmap MsgCountComposedIcon::pixmap(const QSize& size,
QIcon::Mode mode,
QIcon::State state) {
QImage img(size, QImage::Format_ARGB32);
img.fill(qRgba(0, 0, 0, 0));
QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion);
{
QPainter painter(&result);
paint(&painter, QRect(QPoint(0, 0), size), mode, state);
}
return result;
}
TrayIcon::TrayIcon(QObject* parent) : QSystemTrayIcon(parent) {
QMenu* menu = new QMenu();
viewAction_ = new QAction(tr("Show"), parent);
quitAction_ = new QAction(tr("Quit"), parent);
connect(viewAction_, &QAction::triggered, this, &TrayIcon::showWindow);
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
menu->addAction(viewAction_);
menu->addAction(quitAction_);
setContextMenu(menu);
}
void TrayIcon::setNotificationCount(int count) {
m_notificationCount = count;
// Use the native badge counter in MacOS.
#if defined(Q_OS_MAC)
auto labelText = count == 0 ? "" : QString::number(count);
if (labelText == QtMac::badgeLabelText())
return;
QtMac::setBadgeLabelText(labelText);
#elif defined(Q_OS_WIN)
// FIXME: Find a way to use Windows apis for the badge counter (if any).
#else
if (count == icon_->msgCount)
return;
// Custom drawing on Linux.
MsgCountComposedIcon* tmp =
static_cast<MsgCountComposedIcon*>(icon_->clone());
tmp->msgCount = count;
setIcon(QIcon(tmp));
icon_ = tmp;
#endif
emit notificationCountChanged();
}
void TrayIcon::setIconSource(const QString& source) {
m_iconSource = source;
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
setIcon(QIcon(source));
#else
icon_ = new MsgCountComposedIcon(source);
setIcon(QIcon(icon_));
#endif
emit iconSourceChanged();
}

67
src/trayicon.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef TRAYICON_H
#define TRAYICON_H
// Modified from mujx/nheko's TrayIcon.
#include <QAction>
#include <QIcon>
#include <QIconEngine>
#include <QPainter>
#include <QRect>
#include <QSystemTrayIcon>
class MsgCountComposedIcon : public QIconEngine {
public:
MsgCountComposedIcon(const QString& filename);
virtual void paint(QPainter* p,
const QRect& rect,
QIcon::Mode mode,
QIcon::State state);
virtual QIconEngine* clone() const;
virtual QList<QSize> availableSizes(QIcon::Mode mode,
QIcon::State state) const;
virtual QPixmap pixmap(const QSize& size,
QIcon::Mode mode,
QIcon::State state);
int msgCount = 0;
private:
const int BubbleDiameter = 14;
QIcon icon_;
};
class TrayIcon : public QSystemTrayIcon {
Q_OBJECT
Q_PROPERTY(QString iconSource READ iconSource WRITE setIconSource NOTIFY
iconSourceChanged)
Q_PROPERTY(int notificationCount READ notificationCount WRITE
setNotificationCount NOTIFY notificationCountChanged)
public:
TrayIcon(QObject* parent = nullptr);
QString iconSource() { return m_iconSource; }
void setIconSource(const QString& source);
int notificationCount() { return m_notificationCount; }
void setNotificationCount(int count);
signals:
void notificationCountChanged();
void iconSourceChanged();
void showWindow();
private:
QString m_iconSource;
int m_notificationCount = 0;
QAction* viewAction_;
QAction* quitAction_;
MsgCountComposedIcon* icon_;
};
#endif // TRAYICON_H