1 // For license of this file, see <project-root-folder>/LICENSE.md.
2 
3 #include "gui/systemtrayicon.h"
4 
5 #include "definitions/definitions.h"
6 #include "gui/dialogs/formmain.h"
7 #include "gui/dialogs/formsettings.h"
8 #include "miscellaneous/application.h"
9 #include "miscellaneous/settings.h"
10 
11 #include <QPainter>
12 #include <QTimer>
13 
14 #if defined(Q_OS_WIN)
TrayIconMenu(const QString & title,QWidget * parent)15 TrayIconMenu::TrayIconMenu(const QString& title, QWidget* parent) : QMenu(title, parent) {}
16 
event(QEvent * event)17 bool TrayIconMenu::event(QEvent* event) {
18   if (event->type() == QEvent::Type::Show && Application::activeModalWidget() != nullptr) {
19     QTimer::singleShot(0, this, &TrayIconMenu::hide);
20     qApp->showGuiMessage(Notification::Event::GeneralEvent,
21                          QSL(APP_LONG_NAME),
22                          tr("Close opened modal dialogs first."),
23                          QSystemTrayIcon::Warning,
24                          true);
25   }
26 
27   return QMenu::event(event);
28 }
29 
30 #endif
31 
SystemTrayIcon(const QString & normal_icon,const QString & plain_icon,FormMain * parent)32 SystemTrayIcon::SystemTrayIcon(const QString& normal_icon, const QString& plain_icon, FormMain* parent)
33   : QSystemTrayIcon(parent),
34   m_normalIcon(normal_icon),
35   m_plainPixmap(plain_icon) {
36   qDebugNN << LOGSEC_GUI << "Creating SystemTrayIcon instance.";
37   m_font.setBold(true);
38 
39   // Initialize icon.
40   setNumber();
41   setContextMenu(parent->trayMenu());
42 
43   // Create necessary connections.
44   connect(this, &SystemTrayIcon::activated, this, &SystemTrayIcon::onActivated);
45 }
46 
~SystemTrayIcon()47 SystemTrayIcon::~SystemTrayIcon() {
48   qDebugNN << LOGSEC_GUI << "Destroying SystemTrayIcon instance.";
49   hide();
50 }
51 
onActivated(QSystemTrayIcon::ActivationReason reason)52 void SystemTrayIcon::onActivated(QSystemTrayIcon::ActivationReason reason) {
53   switch (reason) {
54     case SystemTrayIcon::Trigger:
55     case SystemTrayIcon::DoubleClick:
56     case SystemTrayIcon::MiddleClick:
57       static_cast<FormMain*>(parent())->switchVisibility();
58       break;
59 
60     default:
61       break;
62   }
63 }
64 
isSystemTrayAreaAvailable()65 bool SystemTrayIcon::isSystemTrayAreaAvailable() {
66   return QSystemTrayIcon::isSystemTrayAvailable() && QSystemTrayIcon::supportsMessages();
67 }
68 
isSystemTrayDesired()69 bool SystemTrayIcon::isSystemTrayDesired() {
70   return qApp->settings()->value(GROUP(GUI), SETTING(GUI::UseTrayIcon)).toBool();
71 }
72 
areNotificationsEnabled()73 bool SystemTrayIcon::areNotificationsEnabled() {
74   return qApp->settings()->value(GROUP(GUI), SETTING(GUI::EnableNotifications)).toBool();
75 }
76 
showPrivate()77 void SystemTrayIcon::showPrivate() {
78   // Make sure that application does not exit some window (for example
79   // the settings window) gets closed. Behavior for main window
80   // is handled explicitly by FormMain::closeEvent() method.
81   qApp->setQuitOnLastWindowClosed(false);
82 
83   // Display the tray icon.
84   QSystemTrayIcon::show();
85   emit shown();
86 
87   qDebugNN << LOGSEC_GUI << "Tray icon displayed.";
88 }
89 
show()90 void SystemTrayIcon::show() {
91 #if defined(Q_OS_WIN)
92   // Show immediately.
93   qDebugNN << LOGSEC_GUI << "Showing tray icon immediately.";
94   showPrivate();
95 #else
96   // Delay avoids race conditions and tray icon is properly displayed.
97   qDebugNN << LOGSEC_GUI << "Showing tray icon with 3000 ms delay.";
98   QTimer::singleShot(3000, this, &SystemTrayIcon::showPrivate);
99 #endif
100 }
101 
setNumber(int number,bool any_new_message)102 void SystemTrayIcon::setNumber(int number, bool any_new_message) {
103   Q_UNUSED(any_new_message)
104 
105   if (number <= 0 || !qApp->settings()->value(GROUP(GUI), SETTING(GUI::UnreadNumbersInTrayIcon)).toBool()) {
106     // Either no unread messages or numbers in tray icon are disabled.
107     setToolTip(QSL(APP_LONG_NAME));
108     QSystemTrayIcon::setIcon(QIcon(m_normalIcon));
109   }
110   else {
111     setToolTip(tr("%1\nUnread news: %2").arg(QSL(APP_LONG_NAME), QString::number(number)));
112     QPixmap background(m_plainPixmap);
113     QPainter tray_painter;
114 
115     tray_painter.begin(&background);
116 
117     if (qApp->settings()->value(GROUP(GUI), SETTING(GUI::MonochromeTrayIcon)).toBool()) {
118       tray_painter.setPen(Qt::GlobalColor::white);
119     }
120     else {
121       tray_painter.setPen(Qt::GlobalColor::black);
122     }
123 
124     tray_painter.setRenderHint(QPainter::RenderHint::SmoothPixmapTransform, true);
125     tray_painter.setRenderHint(QPainter::RenderHint::TextAntialiasing, true);
126 
127     // Numbers with more than 2 digits won't be readable, display
128     // infinity symbol in that case.
129     if (number > 999) {
130       m_font.setPixelSize(background.width() * 0.78);
131       tray_painter.setFont(m_font);
132       tray_painter.drawText(background.rect(), Qt::AlignmentFlag::AlignCenter, QChar(8734));
133     }
134     else {
135       // Smaller number if it has 3 digits.
136       if (number > 99) {
137         m_font.setPixelSize(background.width() * 0.43);
138       }
139       else if (number > 9) {
140         m_font.setPixelSize(background.width() * 0.56);
141       }
142 
143       // Bigger number if it has just one digit.
144       else {
145         m_font.setPixelSize(background.width() * 0.78);
146       }
147 
148       tray_painter.setFont(m_font);
149       tray_painter.drawText(background.rect(),
150                             Qt::AlignmentFlag::AlignCenter,
151                             QString::number(number));
152     }
153 
154     tray_painter.end();
155     QSystemTrayIcon::setIcon(QIcon(background));
156   }
157 }
158 
showMessage(const QString & title,const QString & message,QSystemTrayIcon::MessageIcon icon,int milliseconds_timeout_hint,const std::function<void ()> & functor)159 void SystemTrayIcon::showMessage(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon,
160                                  int milliseconds_timeout_hint, const std::function<void()>& functor) {
161   if (m_connection != nullptr) {
162     // Disconnect previous bubble click signalling.
163     disconnect(m_connection);
164   }
165 
166   if (functor) {
167     // Establish new connection for bubble click.
168     m_connection = connect(this, &SystemTrayIcon::messageClicked, functor);
169   }
170 
171   // NOTE: If connections do not work, then use QMetaObject::invokeMethod(...).
172   QSystemTrayIcon::showMessage(title, message, icon, milliseconds_timeout_hint);
173 }
174