diff --git a/CMakeLists.txt b/CMakeLists.txt index 49bce33e1..3843bec1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,6 +161,7 @@ set(quaternion_SRCS client/chatedit.cpp client/chatroomwidget.cpp client/systemtrayicon.cpp + client/popupnotifier.cpp client/models/messageeventmodel.cpp client/models/userlistmodel.cpp client/models/roomlistmodel.cpp diff --git a/README.md b/README.md index 22ad0dfaa..2f3ef3e82 100644 --- a/README.md +++ b/README.md @@ -126,14 +126,6 @@ Quaternion stores its configuration in a way standard for Qt applications. It wi Some settings exposed in UI (Settings and View menus): -- `UI/notifications` - a general setting whether Quaternion should distract - the user with notifications and how. - - `none` suppresses notifications entirely (rooms and messages are still - hightlighted but the tray icon is muted); - - `non-intrusive` allows the tray icon show notification popups; - - `intrusive` (default) adds to that activation of Quaternion window - (i.e. the application blinking in the task bar, or getting raised, - or otherwise demands attention in an environment-specific way). - `UI/timeline_layout` - this allows to choose the timeline layout. If this is set to "xchat", Quaternion will show the author to the left of each message, in a xchat/hexchat style (this was also the only available layout on @@ -165,6 +157,26 @@ Some settings exposed in UI (Settings and View menus): Settings not exposed in UI: +- `Notification/popup_mode` - a general setting whether Quaternion should distract + the user with **popup notifications** and how. As Quaternion doesn't support + push notifications, a compromise has to be made; if you make use of server side + push rules then only the affected room name will be displayed instead of the actual + message. If you don't, then your global notification rules stored on the server + won't be used by Quaternion. (Note: color markers on the tray icon are always based + on server side push rules.) + - `server` enables tray icon pop up when a notification sent by the server. + - `client` (default) enables tray icon pop up when Quaternion received a new + message (low priority rooms are excluded by default). + - `none` suppresses popup notifications entirely (rooms and messages are still + hightlighted but the tray icon is muted). +- `Notification/popup_tweaks` - fine tuning popup notifications with the following + flags (many can be specified separated by commas). + - `highlight+` enables popup notifications only for highlights and private chats. + - `lowprio` enables popup notifications for low priority rooms too + (when `popup_mode=client`). + - `intrusive` activates Quaternion window on highlights + (i.e. the application blinking in the task bar, or getting raised, + or otherwise demands attention in an environment-specific way). - `UI/condense_chat` - set this to 1 to make the timeline rendered tighter, eliminating vertical gaps between messages as much as possible. - `UI/show_author_avatars` - set this to 1 (or true) to show author avatars in diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 5bfe2367d..fb3bd70fe 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -25,7 +25,6 @@ #include "logindialog.h" #include "networkconfigdialog.h" #include "roomdialogs.h" -#include "systemtrayicon.h" #include #include @@ -98,8 +97,6 @@ MainWindow::MainWindow() chatRoomWidget, &ChatRoomWidget::insertMention); createMenu(); - systemTrayIcon = new SystemTrayIcon(this); - systemTrayIcon->show(); busyIndicator = new QMovie(QStringLiteral(":/busy.gif")); busyLabel = new QLabel(this); @@ -315,46 +312,6 @@ void MainWindow::createMenu() helpMenu->addAction(QIcon::fromTheme("help-about"), tr("&About"), [=]{ showAboutWindow(); }); - { - auto notifGroup = new QActionGroup(this); - connect(notifGroup, &QActionGroup::triggered, this, - [] (QAction* notifAction) - { - notifAction->setChecked(true); - Settings().setValue("UI/notifications", - notifAction->data().toString()); - }); - - auto noNotif = notifGroup->addAction(tr("&Highlight only")); - noNotif->setData(QStringLiteral("none")); - noNotif->setStatusTip(tr("Notifications are entirely suppressed")); - auto gentleNotif = notifGroup->addAction(tr("&Non-intrusive")); - gentleNotif->setData(QStringLiteral("non-intrusive")); - gentleNotif->setStatusTip( - tr("Show notifications but do not activate the window")); - auto fullNotif = notifGroup->addAction(tr("&Full")); - fullNotif->setData(QStringLiteral("intrusive")); - fullNotif->setStatusTip( - tr("Show notifications and activate the window")); - - auto notifMenu = settingsMenu->addMenu( - QIcon::fromTheme("preferences-desktop-notification"), - tr("Notifications")); - for (auto a: {noNotif, gentleNotif, fullNotif}) - { - a->setCheckable(true); - notifMenu->addAction(a); - } - - const auto curSetting = Settings().value("UI/notifications", - fullNotif->data().toString()); - if (curSetting == noNotif->data().toString()) - noNotif->setChecked(true); - else if (curSetting == gentleNotif->data().toString()) - gentleNotif->setChecked(true); - else - fullNotif->setChecked(true); - } { auto layoutGroup = new QActionGroup(this); connect(layoutGroup, &QActionGroup::triggered, this, @@ -712,7 +669,6 @@ void MainWindow::addConnection(Connection* c, const QString& deviceName) }); connect( c, &Connection::loginError, this, [=](const QString& msg){ loginError(c, msg); } ); - connect( c, &Connection::newRoom, systemTrayIcon, &SystemTrayIcon::newRoom ); connect( c, &Connection::createdRoom, this, &MainWindow::selectRoom); connect( c, &Connection::joinedRoom, this, [this] (Room* r, Room* prev) { diff --git a/client/mainwindow.h b/client/mainwindow.h index 0547d4ef1..c67bdc786 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -30,14 +30,12 @@ namespace Quotient { class RoomListDock; class UserListDock; class ChatRoomWidget; -class SystemTrayIcon; class QuaternionRoom; class LoginDialog; class QAction; class QMenu; class QMenuBar; -class QSystemTrayIcon; class QMovie; class QLabel; class QLineEdit; @@ -132,8 +130,6 @@ class MainWindow: public QMainWindow QAction* dcAction = nullptr; QAction* joinAction = nullptr; - SystemTrayIcon* systemTrayIcon = nullptr; - // FIXME: This will be a problem when we get ability to show // several rooms at once. QuaternionRoom* currentRoom = nullptr; diff --git a/client/models/roomlistmodel.cpp b/client/models/roomlistmodel.cpp index 6e8cff309..457209c77 100644 --- a/client/models/roomlistmodel.cpp +++ b/client/models/roomlistmodel.cpp @@ -483,6 +483,8 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const } case HasUnreadRole: return room->hasUnreadMessages(); + case NotificationCountRole: + return room->notificationCount(); case HighlightCountRole: return room->highlightCount(); case JoinStateRole: diff --git a/client/models/roomlistmodel.h b/client/models/roomlistmodel.h index 9af9ffb78..73429f2fe 100644 --- a/client/models/roomlistmodel.h +++ b/client/models/roomlistmodel.h @@ -35,7 +35,7 @@ class RoomListModel: public QAbstractItemModel public: enum Roles { HasUnreadRole = Qt::UserRole + 1, - HighlightCountRole, JoinStateRole, ObjectRole + NotificationCountRole, HighlightCountRole, JoinStateRole, ObjectRole }; using Room = Quotient::Room; diff --git a/client/popupnotifier.cpp b/client/popupnotifier.cpp new file mode 100644 index 000000000..0cd10368a --- /dev/null +++ b/client/popupnotifier.cpp @@ -0,0 +1,179 @@ +/************************************************************************** + * * + * Copyright (C) 2019 Roland Pallai * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 3 * + * of the License, or (at your option) any later version. * + * * + * 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 General Public License * + * along with this program. If not, see . * + * * + **************************************************************************/ + +#include "popupnotifier.h" + +#include "mainwindow.h" +#include "systemtrayicon.h" +#include "models/roomlistmodel.h" +#include +#include + +#include + +using namespace Quotient; + +PopupNotifier::PopupNotifier(MainWindow* parent, RoomListModel* roomlistmodel, SystemTrayIcon* systemTrayIcon) + : QObject(parent) + , m_parent(parent) + , m_roomlistmodel(roomlistmodel) + , m_systemtrayicon(systemTrayIcon) +{ + connect(roomlistmodel, &RoomListModel::rowsInserted, this, + [this](QModelIndex parent, int first, int last) { + qDebug() << "PopupNotifier: RoomListModel: rowsInserted"; + for (int i = first; i <= last; i++) { + auto idx = m_roomlistmodel->index(i, 0, parent); + auto room = qvariant_cast(m_roomlistmodel->data(idx, RoomListModel::ObjectRole)); + if (room) + connectRoomSignals(room); + } + } + ); +} + +void PopupNotifier::connectRoomSignals(QuaternionRoom* room) +{ + qDebug() << "PopupNotifier::connectRoomSignals:" << room; + + connect(room, &Room::highlightCountChanged, + this, [this,room] { highlightCountChanged(room); }); + connect(room, &Room::notificationCountChanged, + this, [this,room] { notificationCountChanged(room); }); + connect(room, &Room::addedMessages, + this, [this,room](int from, int to) { addedMessages(room, from, to); }); +} + +void PopupNotifier::showMessage(Room* room, const QString &title, const QString &message) +{ + // we could use libnotify here on Linux + if (m_systemtrayicon && m_systemtrayicon->supportsMessages()) { + m_systemtrayicon->showMessage(title, message); + connectSingleShot(m_systemtrayicon, &SystemTrayIcon::messageClicked, m_parent, + [this,room] { m_parent->selectRoom(room); }); + } +} + +void PopupNotifier::highlightCountChanged(QuaternionRoom* room) +{ + if (popupMode() == "server" && room->highlightCount() > 0) { + showMessage(room, + tr("Highlight in %1").arg(room->displayName()), + tr("%n highlight(s) in total", "", room->highlightCount())); + + if (hasPopupTweak(QStringList{"intrusive"})) + m_parent->activateWindow(); + } +} + +void PopupNotifier::notificationCountChanged(QuaternionRoom* room) +{ + if (popupMode() == "server" && room->notificationCount() > 0 && + !hasPopupTweak(QStringList{"highlight+"})) + { + showMessage(room, + tr("Notification in %1").arg(room->displayName()), + tr("%n notification(s) in total", "", room->notificationCount())); + } +} + +void PopupNotifier::addedMessages(QuaternionRoom* room, int lowest, int biggest) +{ + if (popupMode() != "client") + return; + + qDebug() << "PopupNotifier: addedMessages:" << room; + qDebug() << "timelineSize:" << room->timelineSize() + << "minTimelineIndex:" << room->minTimelineIndex() + << "maxTimelineIndex:" << room->maxTimelineIndex() + << "lowest:" << lowest + << "biggest:" << biggest; + + bool backfill = lowest < 0 || biggest < 0; + if (backfill) + return; + + Q_ASSERT(lowest >= room->minTimelineIndex()); + Q_ASSERT(biggest <= room->maxTimelineIndex()); + + auto it = room->messageEvents().end()-1 - room->maxTimelineIndex() + lowest; + auto itEnd = room->messageEvents().end()-1 - room->maxTimelineIndex() + biggest; + do { + const RoomEvent& evt = **it; + qDebug() << "timestamp:" << evt.timestamp(); + + if (const RoomMessageEvent* e = eventCast(&evt)) { + auto senderName = room->user(evt.senderId())->displayname(room); + auto roomName = room->displayName(); + bool isHighlight = room->isEventHighlighted(&evt) || room->isDirectChat(); + if ( + (!room->isLowPriority() || hasPopupTweak(QStringList{"lowprio"})) && + (isHighlight || !hasPopupTweak(QStringList{"highlights+"})) && + !m_parent->isActiveWindow() + ) { + if (e->msgtype() == MessageEventType::Image) { + qDebug() << "image"; + } else if (e->hasFileContent()) { + qDebug() << "file"; + } else { + QString msg, title; + + title = room->isDirectChat() ? + tr("Private message from %1").arg(senderName) : + tr("Message in %1").arg(roomName); + + if (e->msgtype() == MessageEventType::Notice) + msg = e->plainBody() + " (notice)"; + else if (e->msgtype() == MessageEventType::Emote) + msg = "/me " + e->plainBody(); + else + msg = e->plainBody(); + + if (!room->isDirectChat()) + msg.prepend(senderName + "> "); + + showMessage(room, title, msg); + } + + if (hasPopupTweak(QStringList{"intrusive"}) && isHighlight) + m_parent->activateWindow(); + } + } + if (evt.isStateEvent()) + qDebug() << "stateEvent"; + } while (it++ != itEnd); +} + +const QString PopupNotifier::popupMode() +{ + return qvariant_cast(Quotient::SettingsGroup("Notification").value("popup_mode", "client")); +} + +bool PopupNotifier::hasPopupTweak(const QStringList &flags) +{ + auto popup_tweaks = SettingsGroup("Notification").get("popup_tweaks"); + qDebug() << "popup_tweaks:" << popup_tweaks; + + for (const auto flag : flags) { + qDebug() << "checking for" << flag; + if (popup_tweaks.contains(flag)) + return true; + } + return false; +} diff --git a/client/popupnotifier.h b/client/popupnotifier.h new file mode 100644 index 000000000..ee3f57cea --- /dev/null +++ b/client/popupnotifier.h @@ -0,0 +1,48 @@ +/************************************************************************** + * * + * Copyright (C) 2019 Roland Pallai * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 3 * + * of the License, or (at your option) any later version. * + * * + * 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 General Public License * + * along with this program. If not, see . * + * * + **************************************************************************/ + +#ifndef POPUPNOTIFIER_H +#define POPUPNOTIFIER_H + +#include "systemtrayicon.h" +#include "quaternionroom.h" + +class PopupNotifier : public QObject { + Q_OBJECT + +public: + PopupNotifier(MainWindow* parent, RoomListModel* roomlistmodel, SystemTrayIcon* systemTrayIcon); + +private slots: + void highlightCountChanged(QuaternionRoom* room); + void notificationCountChanged(QuaternionRoom* room); + void addedMessages(QuaternionRoom* room, int fromIndex, int toIndex); + +private: + MainWindow* m_parent; + RoomListModel* m_roomlistmodel; + SystemTrayIcon* m_systemtrayicon; + + void connectRoomSignals(QuaternionRoom* room); + void showMessage(Quotient::Room* room, const QString &title, const QString &message); + const QString popupMode(); + bool hasPopupTweak(const QStringList &flags); +}; + +#endif /* POPUPNOTIFIER_H */ diff --git a/client/roomlistdock.cpp b/client/roomlistdock.cpp index 703fcd340..d5913c04a 100644 --- a/client/roomlistdock.cpp +++ b/client/roomlistdock.cpp @@ -30,6 +30,8 @@ #include "models/orderbytag.h" #include "quaternionroom.h" #include "roomdialogs.h" +#include "systemtrayicon.h" +#include "popupnotifier.h" #include #include @@ -212,6 +214,10 @@ RoomListDock::RoomListDock(MainWindow* parent) setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &QWidget::customContextMenuRequested, this, &RoomListDock::showContextMenu); + + systemTrayIcon = new SystemTrayIcon(parent, model); + popupNotifier = new PopupNotifier(parent, model, systemTrayIcon); + } void RoomListDock::addConnection(Quotient::Connection* connection) diff --git a/client/roomlistdock.h b/client/roomlistdock.h index 215c0b37f..0cea2666d 100644 --- a/client/roomlistdock.h +++ b/client/roomlistdock.h @@ -27,6 +27,8 @@ class MainWindow; class RoomListModel; class QuaternionRoom; +class SystemTrayIcon; +class PopupNotifier; namespace Quotient { class Connection; @@ -54,6 +56,8 @@ class RoomListDock : public QDockWidget void refreshTitle(); private: + SystemTrayIcon* systemTrayIcon = nullptr; + PopupNotifier* popupNotifier = nullptr; QTreeView* view; RoomListModel* model; // QSortFilterProxyModel* proxyModel; diff --git a/client/systemtrayicon.cpp b/client/systemtrayicon.cpp index 537b8d2b7..36834e283 100644 --- a/client/systemtrayicon.cpp +++ b/client/systemtrayicon.cpp @@ -20,40 +20,158 @@ #include "systemtrayicon.h" #include "mainwindow.h" +#include "models/roomlistmodel.h" #include "quaternionroom.h" #include #include +#include +#include -SystemTrayIcon::SystemTrayIcon(MainWindow* parent) +// Based on Spectral's src/trayicon.cpp +ComposedTrayIcon::ComposedTrayIcon(const QString& filename) : QIconEngine() +{ + icon_ = QIcon(filename); +} + +void ComposedTrayIcon::paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) +{ + painter->setRenderHint(QPainter::Antialiasing); + icon_.paint(painter, rect, Qt::AlignCenter, mode, state); + + if (hasInvite || hasHighlight || hasNotification) { + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(QColor(hasInvite ? "#0acb00" : hasHighlight ? "red" : "#df52df")); + painter->setBrush(brush); + QRectF bubble(rect.width() - BubbleDiameter, 0, BubbleDiameter, BubbleDiameter); + painter->drawEllipse(bubble); + } +} + +QIconEngine* ComposedTrayIcon::clone() const +{ + return new ComposedTrayIcon(*this); +} + +QList ComposedTrayIcon::availableSizes(QIcon::Mode mode, QIcon::State state) const +{ + Q_UNUSED(mode) + Q_UNUSED(state) + QList 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 ComposedTrayIcon::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; +} + + +SystemTrayIcon::SystemTrayIcon(MainWindow* parent, RoomListModel* roomlistmodel) : QSystemTrayIcon(parent) , m_parent(parent) + , m_roomlistmodel(roomlistmodel) { - setIcon(QIcon(":/icon.png")); + m_icon = new ComposedTrayIcon(":/icon.png"); + setIcon(QIcon(m_icon)); setToolTip("Quaternion"); connect( this, &SystemTrayIcon::activated, this, &SystemTrayIcon::systemTrayIconAction); + + connect(roomlistmodel, &RoomListModel::dataChanged, this, + [this](QModelIndex idx1, QModelIndex idx2, const QVector& roles) { + //qDebug() << "dataChanged@" << idx1 << idx2 << roles; + evaluate(); + } + ); + connect(roomlistmodel, &RoomListModel::rowsInserted, this, + [this](QModelIndex parent, int first, int last) { + //qDebug() << "rowsInserted"; + evaluate(); + } + ); + connect(roomlistmodel, &RoomListModel::rowsRemoved, this, + [this](QModelIndex parent, int first, int last) { + //qDebug() << "rowsRemoved"; + evaluate(); + } + ); + + show(); } -void SystemTrayIcon::newRoom(Quotient::Room* room) +template +bool roomIndicesForeachCallback(std::function callback, const QModelIndex *message) +{ + callback(message); + return false; +} +template<> +bool roomIndicesForeachCallback(std::function callback, const QModelIndex *index) { - connect(room, &Quotient::Room::highlightCountChanged, - this, [this,room] { highlightCountChanged(room); }); + return callback(index); } -void SystemTrayIcon::highlightCountChanged(Quotient::Room* room) +template +void SystemTrayIcon::roomIndicesForeach(std::function callback) const { - using namespace Quotient; - auto mode = SettingsGroup("UI").value("notifications", "intrusive"); - if (mode == "none") - return; - if( room->highlightCount() > 0 ) - { - showMessage(tr("Highlight in %1").arg(room->displayName()), - tr("%n highlight(s)", "", room->highlightCount())); - if (mode != "non-intrusive") - m_parent->activateWindow(); - connectSingleShot(this, &SystemTrayIcon::messageClicked, m_parent, - [this,qRoom=static_cast(room)] - { m_parent->selectRoom(qRoom); }); + Q_ASSERT(m_roomlistmodel->columnCount(QModelIndex()) == 1); + + for (int i = 0; i < m_roomlistmodel->rowCount(QModelIndex()); i++) { + auto groupindex = m_roomlistmodel->index(i, 0); + for (int j = 0; j < m_roomlistmodel->rowCount(groupindex); j++) { + auto index = m_roomlistmodel->index(j, 0, groupindex); + if (roomIndicesForeachCallback(callback, &index)) + return; + } + } +} + +void SystemTrayIcon::evaluate() +{ + bool hasInvite = false; + bool hasHighlight = false; + bool hasNotification = false; + bool hasUnread = false; + roomIndicesForeach([&hasInvite,&hasHighlight,&hasNotification,&hasUnread](const QModelIndex *index) { + hasInvite |= index->data(RoomListModel::JoinStateRole).toString() == "invite"; + hasHighlight |= index->data(RoomListModel::HighlightCountRole).toInt() > 0; + hasNotification |= index->data(RoomListModel::NotificationCountRole).toInt() > 0; + hasUnread |= index->data(RoomListModel::HasUnreadRole).toBool(); + }); + qDebug() << "evaluate; hasInvite:" << hasInvite << "hasHighlight:" << hasHighlight << "hasNotification" << hasNotification; + + ComposedTrayIcon* icon = static_cast(m_icon->clone()); + icon->hasInvite = hasInvite; + icon->hasHighlight = hasHighlight; + icon->hasNotification = hasNotification; + icon->hasUnread = hasUnread; + m_icon = icon; + setIcon(QIcon(m_icon)); + + if (hasInvite || hasHighlight || hasNotification) { + auto hasTypes = QStringList(); + if (hasInvite) + hasTypes << tr("undecided invitation"); + if (hasHighlight) + hasTypes << tr("unseen highlights"); + if (hasNotification) + hasTypes << tr("unseen notifications"); + setToolTip(tr("Quaternion: you have ") + hasTypes.join(QStringLiteral(", "))); + } else { + setToolTip(tr("Quaternion")); } } @@ -70,17 +188,58 @@ void SystemTrayIcon::systemTrayIconAction(QSystemTrayIcon::ActivationReason reas } } +void SystemTrayIcon::selectRoomByIndex(const QModelIndex *index) +{ + auto room = qvariant_cast(index->data(RoomListModel::ObjectRole)); + m_parent->selectRoom(room); +} + +void SystemTrayIcon::selectNextRoom() +{ + bool accepted = false; + + if (!accepted) + roomIndicesForeach([this,&accepted](const QModelIndex *index) { + if (index->data(RoomListModel::JoinStateRole).toString() == "invite") { + selectRoomByIndex(index); + return accepted = true; + } + return false; + }); + + if (!accepted) + roomIndicesForeach([this,&accepted](const QModelIndex *index) { + if (index->data(RoomListModel::HighlightCountRole).toInt() > 0) { + selectRoomByIndex(index); + return accepted = true; + } + return false; + }); + + if (!accepted) + roomIndicesForeach([this,&accepted](const QModelIndex *index) { + if (index->data(RoomListModel::NotificationCountRole).toInt() > 0) { + selectRoomByIndex(index); + return accepted = true; + } + return false; + }); +} + void SystemTrayIcon::showHide() { - if( m_parent->isVisible() ) + if (m_parent->isVisible() && !(m_parent->windowState() & Qt::WindowMinimized) && + m_parent->isActiveWindow() && + !m_icon->hasInvite && !m_icon->hasHighlight && !m_icon->hasNotification) { m_parent->hide(); } else { + m_parent->setWindowState(m_parent->windowState() & ~Qt::WindowMinimized); m_parent->show(); m_parent->activateWindow(); m_parent->raise(); - m_parent->setFocus(); + selectNextRoom(); } } diff --git a/client/systemtrayicon.h b/client/systemtrayicon.h index 5ff94dceb..8ba788654 100644 --- a/client/systemtrayicon.h +++ b/client/systemtrayicon.h @@ -20,6 +20,8 @@ #pragma once #include +#include +#include namespace Quotient { @@ -27,21 +29,46 @@ namespace Quotient } class MainWindow; +class RoomListModel; + +class ComposedTrayIcon : public QIconEngine { +public: + ComposedTrayIcon(const QString& filename); + + virtual void paint(QPainter* p, const QRect& rect, QIcon::Mode mode, QIcon::State state); + virtual QIconEngine* clone() const; + virtual QList availableSizes(QIcon::Mode mode, QIcon::State state) const; + virtual QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state); + + bool hasInvite; + bool hasNotification; + bool hasHighlight; + bool hasUnread; + +private: + const int BubbleDiameter = 8; + + QIcon icon_; +}; class SystemTrayIcon: public QSystemTrayIcon { Q_OBJECT public: - explicit SystemTrayIcon(MainWindow* parent = nullptr); + explicit SystemTrayIcon(MainWindow* parent, RoomListModel* roomlistmodel); public slots: - void newRoom(Quotient::Room* room); + void evaluate(); private slots: - void highlightCountChanged(Quotient::Room* room); void systemTrayIconAction(QSystemTrayIcon::ActivationReason reason); + void selectNextRoom(); private: MainWindow* m_parent; + RoomListModel* m_roomlistmodel; + ComposedTrayIcon* m_icon; void showHide(); + template void roomIndicesForeach(std::function callback) const; + void selectRoomByIndex(const QModelIndex *index); };