rssguard/src/librssguard/gui/notifications/toastnotificationsmanager.cpp

310 lines
9.6 KiB
C++

// For license of this file, see <project-root-folder>/LICENSE.md.
#include "gui/notifications/toastnotificationsmanager.h"
#include "3rd-party/boolinq/boolinq.h"
#include "gui/notifications/articlelistnotification.h"
#include "gui/notifications/basetoastnotification.h"
#include "gui/notifications/toastnotification.h"
#include "miscellaneous/settings.h"
#include <QRect>
#include <QScreen>
#include <QWindow>
QString ToastNotificationsManager::textForPosition(ToastNotificationsManager::NotificationPosition pos) {
switch (pos) {
case TopLeft:
return QObject::tr("top-left");
case TopRight:
return QObject::tr("top-right");
case BottomLeft:
return QObject::tr("bottom-left");
case BottomRight:
default:
return QObject::tr("bottom-right");
}
}
ToastNotificationsManager::ToastNotificationsManager(QObject* parent)
: QObject(parent), m_position(ToastNotificationsManager::NotificationPosition::TopRight), m_screen(0), m_margins(0),
m_width(0), m_opacity(0.0), m_articleListNotification(nullptr) {
resetNotifications(false);
}
ToastNotificationsManager::~ToastNotificationsManager() {
clear(true);
}
QList<BaseToastNotification*> ToastNotificationsManager::activeNotifications() const {
return m_activeNotifications;
}
int ToastNotificationsManager::screen() const {
return m_screen;
}
void ToastNotificationsManager::setScreen(int screen) {
m_screen = screen;
}
ToastNotificationsManager::NotificationPosition ToastNotificationsManager::position() const {
return m_position;
}
void ToastNotificationsManager::setPosition(NotificationPosition position) {
m_position = position;
}
void ToastNotificationsManager::resetNotifications(bool reload_existing_notifications) {
m_position = qApp->settings()
->value(GROUP(GUI), SETTING(GUI::ToastNotificationsPosition))
.value<ToastNotificationsManager::NotificationPosition>();
m_screen = qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsScreen)).toInt();
m_margins = qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsMargin)).toInt();
m_opacity = qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsOpacity)).toDouble();
m_width = qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsWidth)).toInt();
if (reload_existing_notifications) {
auto notif = m_activeNotifications;
clear(false);
while (!notif.isEmpty()) {
BaseToastNotification* one_notif = notif.takeLast();
processNotification(one_notif);
}
}
}
void ToastNotificationsManager::clear(bool delete_from_memory) {
for (BaseToastNotification* notif : m_activeNotifications) {
closeNotification(notif, delete_from_memory);
}
m_activeNotifications.clear();
}
void ToastNotificationsManager::processNotification(BaseToastNotification* notif) {
notif->setWindowOpacity(m_opacity);
notif->setFixedWidth(m_width);
notif->show();
auto* screen = moveToProperScreen(notif);
auto notif_new_pos = cornerForNewNotification(screen->availableGeometry());
// Make sure notification is finally resized.
notif->adjustSize();
QCoreApplication::processEvents();
// Move notification, at this point we already need to know its precise size.
moveNotificationToCorner(notif, notif_new_pos);
// Remove out-of-bounds old notifications and shift existing
// ones to make space for new notifications.
removeOutOfBoundsNotifications(notif->height());
makeSpaceForNotification(notif->height());
m_activeNotifications.prepend(notif);
}
void ToastNotificationsManager::showNotification(Notification::Event event,
const GuiMessage& msg,
const GuiAction& action) {
BaseToastNotification* notif;
if (!msg.m_feedFetchResults.updatedFeeds().isEmpty()) {
if (m_articleListNotification == nullptr) {
initializeArticleListNotification();
}
else if (m_activeNotifications.contains(m_articleListNotification)) {
// Article notification is somewhere in list, clear first to move it to first positon.
closeNotification(m_articleListNotification, false);
}
m_articleListNotification->loadResults(msg.m_feedFetchResults.updatedFeeds());
notif = m_articleListNotification;
}
else {
notif = new ToastNotification(event, msg, action, qApp->mainFormWidget());
hookNotification(notif);
}
processNotification(notif);
}
void ToastNotificationsManager::closeNotification(BaseToastNotification* notif, bool delete_from_memory) {
auto notif_idx = m_activeNotifications.indexOf(notif);
if (delete_from_memory) {
notif->deleteLater();
}
else {
notif->hide();
}
m_activeNotifications.removeAll(notif);
// Shift all notifications.
if (notif_idx < 0) {
return;
}
makeSpaceForNotification(notif->height(), true, notif_idx);
}
QScreen* ToastNotificationsManager::activeScreen() const {
if (m_screen >= 0) {
auto all_screens = QGuiApplication::screens();
if (m_screen < all_screens.size()) {
return all_screens.at(m_screen);
}
}
return QGuiApplication::primaryScreen();
}
QPoint ToastNotificationsManager::cornerForNewNotification(QRect screen_rect) {
switch (m_position) {
case ToastNotificationsManager::TopLeft:
return screen_rect.topLeft() + QPoint(m_margins, m_margins);
case ToastNotificationsManager::TopRight:
return screen_rect.topRight() - QPoint(m_margins, -m_margins);
case ToastNotificationsManager::BottomLeft:
return screen_rect.bottomLeft() - QPoint(-m_margins, m_margins);
case ToastNotificationsManager::BottomRight:
default:
return screen_rect.bottomRight() - QPoint(m_margins, m_margins);
}
}
void ToastNotificationsManager::initializeArticleListNotification() {
m_articleListNotification = new ArticleListNotification();
hookNotification(m_articleListNotification);
connect(m_articleListNotification,
&ArticleListNotification::openingArticleInArticleListRequested,
this,
&ToastNotificationsManager::openingArticleInArticleListRequested);
connect(m_articleListNotification,
&ArticleListNotification::reloadMessageListRequested,
this,
&ToastNotificationsManager::reloadMessageListRequested);
}
void ToastNotificationsManager::hookNotification(BaseToastNotification* notif) {
connect(notif, &BaseToastNotification::closeRequested, this, [this](BaseToastNotification* notif) {
closeNotification(notif, notif != m_articleListNotification);
});
}
void ToastNotificationsManager::moveNotificationToCorner(BaseToastNotification* notif, QPoint corner) {
switch (m_position) {
case ToastNotificationsManager::TopLeft:
notif->move(corner);
break;
case ToastNotificationsManager::TopRight:
notif->move(corner.x() - notif->frameGeometry().width(), corner.y());
break;
case ToastNotificationsManager::BottomLeft:
notif->move(corner.x(), corner.y() - notif->frameGeometry().height());
break;
case ToastNotificationsManager::BottomRight:
notif->move(corner.x() - notif->frameGeometry().width(), corner.y() - notif->frameGeometry().height());
break;
}
}
void ToastNotificationsManager::makeSpaceForNotification(int height_to_make_space, bool reverse, int stard_idx) {
for (int i = stard_idx; i < m_activeNotifications.size(); i++) {
BaseToastNotification* notif = m_activeNotifications.at(i);
switch (m_position) {
case ToastNotificationsManager::TopLeft:
case ToastNotificationsManager::TopRight: {
std::function<int(int, int)> shift_down;
if (reverse) {
shift_down = [](int x, int y) {
return x - y;
};
}
else {
shift_down = [](int x, int y) {
return x + y;
};
}
// Move it all down.
notif->move(notif->pos().x(), shift_down(notif->pos().y(), (height_to_make_space + m_margins)));
break;
}
case ToastNotificationsManager::BottomLeft:
case ToastNotificationsManager::BottomRight: {
std::function<int(int, int)> shift_up;
if (reverse) {
shift_up = [](int x, int y) {
return x + y;
};
}
else {
shift_up = [](int x, int y) {
return x - y;
};
}
// Move it all up.
notif->move(notif->pos().x(), shift_up(notif->pos().y(), height_to_make_space + m_margins));
break;
}
}
}
}
void ToastNotificationsManager::removeOutOfBoundsNotifications(int height_to_reserve) {
auto* screen = activeScreen();
int available_height = screen->availableSize().height();
while (boolinq::from(m_activeNotifications).sum([this](BaseToastNotification* notif) {
return notif->height() + m_margins;
}) + height_to_reserve >
available_height) {
if (!m_activeNotifications.isEmpty()) {
m_activeNotifications.takeLast()->deleteLater();
}
else {
break;
}
}
}
QScreen* ToastNotificationsManager::moveToProperScreen(BaseToastNotification* notif) {
if (m_screen >= 0) {
auto all_screens = QGuiApplication::screens();
if (m_screen < all_screens.size()) {
notif->windowHandle()->setScreen(all_screens.at(m_screen));
return all_screens.at(m_screen);
}
}
return QGuiApplication::primaryScreen();
}