Warning, file /pim/kalarm/src/traywindow.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * traywindow.cpp - the KDE system tray applet 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2002-2023 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "traywindow.h" 0010 0011 #include "functions.h" 0012 #include "kalarmapp.h" 0013 #include "mainwindow.h" 0014 #include "messagedisplay.h" 0015 #include "newalarmaction.h" 0016 #include "prefdlg.h" 0017 #include "preferences.h" 0018 #include "resourcescalendar.h" 0019 #include "resources/datamodel.h" 0020 #include "resources/eventmodel.h" 0021 #include "lib/synchtimer.h" 0022 #include "kalarmcalendar/alarmtext.h" 0023 #include "kalarm_debug.h" 0024 0025 #include <KLocalizedString> 0026 #include <KStandardAction> 0027 #include <KAboutData> 0028 0029 #include <QList> 0030 #include <QTimer> 0031 #include <QLocale> 0032 #include <QMenu> 0033 0034 #include <stdlib.h> 0035 #include <limits.h> 0036 0037 using namespace KAlarmCal; 0038 0039 struct TipItem 0040 { 0041 QDateTime dateTime; 0042 QString text; 0043 }; 0044 0045 0046 /*============================================================================= 0047 = Class: TrayWindow 0048 = The KDE system tray window. 0049 =============================================================================*/ 0050 0051 TrayWindow::TrayWindow(MainWindow* parent) 0052 : KStatusNotifierItem(parent) 0053 , mAssocMainWindow(parent) 0054 , mStatusUpdateTimer(new QTimer(this)) 0055 { 0056 qCDebug(KALARM_LOG) << "TrayWindow:"; 0057 setToolTipIconByName(QStringLiteral("kalarm")); 0058 setToolTipTitle(KAboutData::applicationData().displayName()); 0059 setIconByName(QStringLiteral("kalarm")); 0060 setStatus(KStatusNotifierItem::Active); 0061 // Set up the context menu 0062 mActionEnabled = KAlarm::createAlarmEnableAction(this); 0063 addAction(QStringLiteral("tAlarmsEnable"), mActionEnabled); 0064 contextMenu()->addAction(mActionEnabled); 0065 connect(theApp(), &KAlarmApp::alarmEnabledToggled, this, &TrayWindow::setEnabledStatus); 0066 contextMenu()->addSeparator(); 0067 0068 mActionNew = new NewAlarmAction(false, i18nc("@action", "New Alarm"), this); 0069 addAction(QStringLiteral("tNew"), mActionNew); 0070 contextMenu()->addAction(mActionNew); 0071 connect(mActionNew, &NewAlarmAction::selected, this, &TrayWindow::slotNewAlarm); 0072 connect(mActionNew, &NewAlarmAction::selectedTemplate, this, &TrayWindow::slotNewFromTemplate); 0073 contextMenu()->addSeparator(); 0074 0075 QAction* a = KAlarm::createStopPlayAction(this); 0076 addAction(QStringLiteral("tStopPlay"), a); 0077 contextMenu()->addAction(a); 0078 QObject::connect(theApp(), &KAlarmApp::audioPlaying, a, &QAction::setVisible); 0079 QObject::connect(theApp(), &KAlarmApp::audioPlaying, this, &TrayWindow::updateStatus); 0080 0081 a = KAlarm::createSpreadWindowsAction(this); 0082 addAction(QStringLiteral("tSpread"), a); 0083 contextMenu()->addAction(a); 0084 contextMenu()->addSeparator(); 0085 contextMenu()->addAction(KStandardAction::preferences(this, &TrayWindow::slotPreferences, this)); 0086 0087 // Disable standard quit behaviour. We have to intercept the quit event 0088 // (which triggers KStatusNotifierItem to quit unconditionally). 0089 QAction* act = action(QStringLiteral("quit")); 0090 if (act) 0091 { 0092 disconnect(act, &QAction::triggered, this, nullptr); 0093 connect(act, &QAction::triggered, this, &TrayWindow::slotQuit); 0094 } 0095 0096 // Set icon to correspond with the alarms enabled menu status 0097 setEnabledStatus(theApp()->alarmsEnabled()); 0098 0099 connect(ResourcesCalendar::instance(), &ResourcesCalendar::haveDisabledAlarmsChanged, this, &TrayWindow::slotHaveDisabledAlarms); 0100 connect(this, &TrayWindow::activateRequested, this, &TrayWindow::slotActivateRequested); 0101 connect(this, &TrayWindow::secondaryActivateRequested, this, &TrayWindow::slotSecondaryActivateRequested); 0102 slotHaveDisabledAlarms(ResourcesCalendar::haveDisabledAlarms()); 0103 0104 // Hack: KSNI does not let us know when it is about to show the tooltip, 0105 // so we need to update it whenever something change in it. 0106 0107 // This timer ensures that updateToolTip() is not called several times in a row 0108 mToolTipUpdateTimer = new QTimer(this); 0109 mToolTipUpdateTimer->setInterval(0); 0110 mToolTipUpdateTimer->setSingleShot(true); 0111 connect(mToolTipUpdateTimer, &QTimer::timeout, this, &TrayWindow::updateToolTip); 0112 0113 // Update every minute to show accurate deadlines 0114 MinuteTimer::connect(mToolTipUpdateTimer, SLOT(start())); 0115 0116 // Update when alarms are modified 0117 AlarmListModel* all = DataModel::allAlarmListModel(); 0118 connect(all, &QAbstractItemModel::dataChanged, mToolTipUpdateTimer, qOverload<>(&QTimer::start)); 0119 connect(all, &QAbstractItemModel::rowsInserted, mToolTipUpdateTimer, qOverload<>(&QTimer::start)); 0120 connect(all, &QAbstractItemModel::rowsMoved, mToolTipUpdateTimer, qOverload<>(&QTimer::start)); 0121 connect(all, &QAbstractItemModel::rowsRemoved, mToolTipUpdateTimer, qOverload<>(&QTimer::start)); 0122 connect(all, &QAbstractItemModel::modelReset, mToolTipUpdateTimer, qOverload<>(&QTimer::start)); 0123 0124 // Set auto-hide status when next alarm or preferences change 0125 mStatusUpdateTimer->setSingleShot(true); 0126 connect(mStatusUpdateTimer, &QTimer::timeout, this, &TrayWindow::updateStatus); 0127 connect(ResourcesCalendar::instance(), &ResourcesCalendar::earliestAlarmChanged, this, &TrayWindow::updateStatus); 0128 Preferences::connect(&Preferences::autoHideSystemTrayChanged, this, &TrayWindow::updateStatus); 0129 updateStatus(); 0130 0131 // Update when tooltip preferences are modified 0132 Preferences::connect(&Preferences::tooltipPreferencesChanged, mToolTipUpdateTimer, qOverload<>(&QTimer::start)); 0133 } 0134 0135 TrayWindow::~TrayWindow() 0136 { 0137 qCDebug(KALARM_LOG) << "~TrayWindow"; 0138 theApp()->removeWindow(this); 0139 Q_EMIT deleted(); 0140 } 0141 0142 /****************************************************************************** 0143 * Called when the "New Alarm" menu item is selected to edit a new alarm. 0144 */ 0145 void TrayWindow::slotNewAlarm(EditAlarmDlg::Type type) 0146 { 0147 KAlarm::editNewAlarm(type); 0148 } 0149 0150 /****************************************************************************** 0151 * Called when the "New Alarm" menu item is selected to edit a new alarm from a 0152 * template. 0153 */ 0154 void TrayWindow::slotNewFromTemplate(const KAEvent& event) 0155 { 0156 KAlarm::editNewAlarm(event); 0157 } 0158 0159 /****************************************************************************** 0160 * Called when the "Configure KAlarm" menu item is selected. 0161 */ 0162 void TrayWindow::slotPreferences() 0163 { 0164 KAlarmPrefDlg::display(); 0165 } 0166 0167 /****************************************************************************** 0168 * Called when the Quit context menu item is selected. 0169 * Note that KAlarmApp::doQuit() must be called by the event loop, not directly 0170 * from the menu item, since otherwise the tray icon will be deleted while still 0171 * processing the menu, resulting in a crash. 0172 * Ideally, the connect() call setting up this slot in the constructor would use 0173 * Qt::QueuedConnection, but the slot is never called in that case. 0174 */ 0175 void TrayWindow::slotQuit() 0176 { 0177 // Note: QTimer::singleShot(0, ...) never calls the slot. 0178 QTimer::singleShot(1, this, &TrayWindow::slotQuitAfter); //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) 0179 } 0180 void TrayWindow::slotQuitAfter() 0181 { 0182 theApp()->doQuit(static_cast<QWidget*>(parent())); 0183 } 0184 0185 /****************************************************************************** 0186 * Called when the Alarms Enabled action status has changed. 0187 * Updates the alarms enabled menu item check state, and the icon pixmap. 0188 */ 0189 void TrayWindow::setEnabledStatus(bool status) 0190 { 0191 qCDebug(KALARM_LOG) << "TrayWindow::setEnabledStatus:" << status; 0192 updateIcon(); 0193 updateStatus(); 0194 updateToolTip(); 0195 } 0196 0197 /****************************************************************************** 0198 * Called when individual alarms are enabled or disabled. 0199 * Set the enabled icon to show or hide a disabled indication. 0200 */ 0201 void TrayWindow::slotHaveDisabledAlarms(bool haveDisabled) 0202 { 0203 qCDebug(KALARM_LOG) << "TrayWindow::slotHaveDisabledAlarms:" << haveDisabled; 0204 mHaveDisabledAlarms = haveDisabled; 0205 updateIcon(); 0206 updateToolTip(); 0207 } 0208 0209 /****************************************************************************** 0210 * Show the associated main window. 0211 */ 0212 void TrayWindow::showAssocMainWindow() 0213 { 0214 if (mAssocMainWindow) 0215 { 0216 mAssocMainWindow->show(); 0217 mAssocMainWindow->raise(); 0218 mAssocMainWindow->activateWindow(); 0219 } 0220 } 0221 0222 /****************************************************************************** 0223 * A left click displays the KAlarm main window. 0224 */ 0225 void TrayWindow::slotActivateRequested() 0226 { 0227 // Left click: display/hide the first main window 0228 if (mAssocMainWindow && mAssocMainWindow->isVisible()) 0229 { 0230 mAssocMainWindow->raise(); 0231 mAssocMainWindow->activateWindow(); 0232 } 0233 } 0234 0235 /****************************************************************************** 0236 * A middle button click displays the New Alarm window. 0237 */ 0238 void TrayWindow::slotSecondaryActivateRequested() 0239 { 0240 if (mActionNew->isEnabled()) 0241 mActionNew->trigger(); // display a New Alarm dialog 0242 } 0243 0244 /****************************************************************************** 0245 * Adjust icon auto-hide status according to when the next alarm is due. 0246 * The icon is always shown if audio is playing, to give access to the 'stop' 0247 * menu option. 0248 */ 0249 void TrayWindow::updateStatus() 0250 { 0251 mStatusUpdateTimer->stop(); 0252 int period = Preferences::autoHideSystemTray(); 0253 // If the icon is always to be shown (AutoHideSystemTray = 0), 0254 // or audio is playing, show the icon. 0255 bool active = !period || MessageDisplay::isAudioPlaying(); 0256 if (!active) 0257 { 0258 // Show the icon only if the next active alarm complies 0259 active = theApp()->alarmsEnabled(); 0260 if (active) 0261 { 0262 KADateTime dt; 0263 const KAEvent& event = ResourcesCalendar::earliestAlarm(dt); 0264 active = event.isValid(); 0265 if (active && period > 0) 0266 { 0267 qint64 delay = KADateTime::currentLocalDateTime().secsTo(dt); 0268 delay -= static_cast<qint64>(period) * 60; // delay until icon to be shown 0269 active = (delay <= 0); 0270 if (!active) 0271 { 0272 // First alarm trigger is too far in future, so tray icon is to 0273 // be auto-hidden. Set timer for when it should be shown again. 0274 delay *= 1000; // convert to msec 0275 int delay_int = static_cast<int>(delay); 0276 if (delay_int != delay) 0277 delay_int = INT_MAX; 0278 mStatusUpdateTimer->setInterval(delay_int); 0279 mStatusUpdateTimer->start(); 0280 } 0281 } 0282 } 0283 } 0284 setStatus(active ? Active : Passive); 0285 } 0286 0287 /****************************************************************************** 0288 * Adjust tooltip according to the app state. 0289 * The tooltip text shows alarms due in the next 24 hours. The limit of 24 0290 * hours is because only times, not dates, are displayed. 0291 */ 0292 void TrayWindow::updateToolTip() 0293 { 0294 bool enabled = theApp()->alarmsEnabled(); 0295 QString subTitle; 0296 if (enabled && Preferences::tooltipAlarmCount()) 0297 subTitle = tooltipAlarmText(); 0298 0299 if (!enabled) 0300 subTitle = i18n("Disabled"); 0301 else if (mHaveDisabledAlarms) 0302 { 0303 if (!subTitle.isEmpty()) 0304 subTitle += QLatin1String("<br/>"); 0305 subTitle += i18nc("@info:tooltip Brief: some alarms are disabled", "(Some alarms disabled)"); 0306 } 0307 setToolTipSubTitle(subTitle); 0308 } 0309 0310 /****************************************************************************** 0311 * Adjust icon according to the app state. 0312 */ 0313 void TrayWindow::updateIcon() 0314 { 0315 setIconByName(!theApp()->alarmsEnabled() ? QStringLiteral("kalarm-disabled") 0316 : mHaveDisabledAlarms ? QStringLiteral("kalarm-partdisabled") 0317 : QStringLiteral("kalarm")); 0318 } 0319 0320 /****************************************************************************** 0321 * Return the tooltip text showing alarms due in the next 24 hours. 0322 * The limit of 24 hours is because only times, not dates, are displayed. 0323 */ 0324 QString TrayWindow::tooltipAlarmText() const 0325 { 0326 const QString& prefix = Preferences::tooltipTimeToPrefix(); 0327 int maxCount = Preferences::tooltipAlarmCount(); 0328 const KADateTime now = KADateTime::currentLocalDateTime(); 0329 const KADateTime tomorrow = now.addDays(1); 0330 0331 // Get today's and tomorrow's alarms, sorted in time order 0332 int i, iend; 0333 QList<TipItem> items; 0334 QList<KAEvent> events = KAlarm::getSortedActiveEvents(const_cast<TrayWindow*>(this), &mAlarmsModel); 0335 for (i = 0, iend = events.count(); i < iend; ++i) 0336 { 0337 KAEvent* event = &events[i]; 0338 if (event->actionSubType() == KAEvent::SubAction::Message) 0339 { 0340 TipItem item; 0341 QDateTime dateTime = event->nextTrigger(KAEvent::Trigger::Display).effectiveKDateTime().toLocalZone().qDateTime(); 0342 if (dateTime > tomorrow.qDateTime()) 0343 break; // ignore alarms after tomorrow at the current clock time 0344 item.dateTime = dateTime; 0345 0346 // The alarm is due today, or early tomorrow 0347 if (Preferences::showTooltipAlarmTime()) 0348 { 0349 item.text += QLocale().toString(item.dateTime.time(), QLocale::ShortFormat); 0350 item.text += QLatin1Char(' '); 0351 } 0352 if (Preferences::showTooltipTimeToAlarm()) 0353 { 0354 int mins = (now.qDateTime().secsTo(item.dateTime) + 59) / 60; 0355 if (mins < 0) 0356 mins = 0; 0357 char minutes[3] = "00"; 0358 minutes[0] = static_cast<char>((mins%60) / 10 + '0'); 0359 minutes[1] = static_cast<char>((mins%60) % 10 + '0'); 0360 if (Preferences::showTooltipAlarmTime()) 0361 item.text += i18nc("@info prefix + hours:minutes", "(%1%2:%3)", prefix, mins/60, QLatin1String(minutes)); 0362 else 0363 item.text += i18nc("@info prefix + hours:minutes", "%1%2:%3", prefix, mins/60, QLatin1String(minutes)); 0364 item.text += QLatin1Char(' '); 0365 } 0366 item.text += AlarmText::summary(*event); 0367 0368 // Insert the item into the list in time-sorted order 0369 int it = 0; 0370 for (int itend = items.count(); it < itend; ++it) 0371 { 0372 if (item.dateTime <= items.at(it).dateTime) 0373 break; 0374 } 0375 items.insert(it, item); 0376 } 0377 } 0378 qCDebug(KALARM_LOG) << "TrayWindow::tooltipAlarmText"; 0379 QString text; 0380 int count = 0; 0381 for (i = 0, iend = items.count(); i < iend; ++i) 0382 { 0383 qCDebug(KALARM_LOG) << "TrayWindow::tooltipAlarmText: --" << (count+1) << ")" << items.at(i).text; 0384 if (i > 0) 0385 text += QLatin1String("<br />"); 0386 text += items.at(i).text; 0387 if (++count == maxCount) 0388 break; 0389 } 0390 return text; 0391 } 0392 0393 /****************************************************************************** 0394 * Called when the associated main window is closed. 0395 */ 0396 void TrayWindow::removeWindow(MainWindow* win) 0397 { 0398 if (win == mAssocMainWindow) 0399 mAssocMainWindow = nullptr; 0400 } 0401 0402 #include "moc_traywindow.cpp" 0403 0404 // vim: et sw=4: