Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Remodel desktop notifications #626

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
44 changes: 0 additions & 44 deletions client/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
#include "logindialog.h"
#include "networkconfigdialog.h"
#include "roomdialogs.h"
#include "systemtrayicon.h"

#include <csapi/joining.h>
#include <connection.h>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down
4 changes: 0 additions & 4 deletions client/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions client/models/roomlistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion client/models/roomlistmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
179 changes: 179 additions & 0 deletions client/popupnotifier.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**************************************************************************
* *
* Copyright (C) 2019 Roland Pallai <[email protected]> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
**************************************************************************/

#include "popupnotifier.h"

#include "mainwindow.h"
#include "systemtrayicon.h"
#include "models/roomlistmodel.h"
#include <user.h>
#include <settings.h>

#include <QDebug>

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<QuaternionRoom*>(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<const RoomMessageEvent>(&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<QString>(Quotient::SettingsGroup("Notification").value("popup_mode", "client"));
}

bool PopupNotifier::hasPopupTweak(const QStringList &flags)
{
auto popup_tweaks = SettingsGroup("Notification").get<QStringList>("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;
}
48 changes: 48 additions & 0 deletions client/popupnotifier.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**************************************************************************
* *
* Copyright (C) 2019 Roland Pallai <[email protected]> *
* *
* 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 <http://www.gnu.org/licenses/>. *
* *
**************************************************************************/

#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 */
Loading