SVN_SILENT made messages (.desktop file) - always resolve ours
[kdepim.git] / kalarm / traywindow.cpp
blobbddb11d5302aaada06b9c842dfb4ac171a410df5
1 /*
2 * traywindow.cpp - the KDE system tray applet
3 * Program: kalarm
4 * Copyright © 2002-2012 by David Jarvie <djarvie@kde.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "kalarm.h" //krazy:exclude=includes (kalarm.h must be first)
22 #include "traywindow.h"
24 #include "alarmcalendar.h"
25 #include "alarmlistview.h"
26 #include "functions.h"
27 #include "kalarmapp.h"
28 #include "mainwindow.h"
29 #include "messagewin.h"
30 #include "newalarmaction.h"
31 #include "prefdlg.h"
32 #include "preferences.h"
33 #include "synchtimer.h"
34 #include "templatemenuaction.h"
36 #include <kalarmcal/alarmtext.h>
38 #include <kactioncollection.h>
39 #include <ktoggleaction.h>
40 #include <KLocalizedString>
41 #include <kmessagebox.h>
42 #include <kstandardaction.h>
43 #include <kstandardguiitem.h>
44 #include <kiconeffect.h>
45 #include <kconfig.h>
46 #include <KIconLoader>
47 #include <KLocale>
48 #include <K4AboutData>
50 #include <QMenu>
51 #include <QList>
52 #include <QTimer>
53 #include "kalarm_debug.h"
55 #include <stdlib.h>
56 #include <limits.h>
58 using namespace KAlarmCal;
60 struct TipItem
62 QDateTime dateTime;
63 QString text;
67 /*=============================================================================
68 = Class: TrayWindow
69 = The KDE system tray window.
70 =============================================================================*/
72 TrayWindow::TrayWindow(MainWindow* parent)
73 : KStatusNotifierItem(parent),
74 mAssocMainWindow(parent),
75 mAlarmsModel(Q_NULLPTR),
76 mStatusUpdateTimer(new QTimer(this)),
77 mHaveDisabledAlarms(false)
79 qCDebug(KALARM_LOG);
80 setToolTipIconByName(QStringLiteral("kalarm"));
81 setToolTipTitle(KAboutData::applicationData().componentName());
82 setIconByName(QStringLiteral("kalarm"));
83 // Load the disabled icon for use by setIconByPixmap()
84 // - setIconByName() doesn't work for this one!
85 mIconDisabled.addPixmap(KIconLoader::global()->loadIcon(QStringLiteral("kalarm-disabled"), KIconLoader::Panel));
86 setStatus(KStatusNotifierItem::Active);
87 // Set up the context menu
88 mActionEnabled = KAlarm::createAlarmEnableAction(this);
89 addAction(QStringLiteral("tAlarmsEnable"), mActionEnabled);
90 contextMenu()->addAction(mActionEnabled);
91 connect(theApp(), &KAlarmApp::alarmEnabledToggled, this, &TrayWindow::setEnabledStatus);
92 contextMenu()->addSeparator();
94 mActionNew = new NewAlarmAction(false, i18nc("@action", "&New Alarm"), this);
95 addAction(QStringLiteral("tNew"), mActionNew);
96 contextMenu()->addAction(mActionNew);
97 connect(mActionNew, &NewAlarmAction::selected, this, &TrayWindow::slotNewAlarm);
98 connect(mActionNew->fromTemplateAlarmAction(), &TemplateMenuAction::selected, this, &TrayWindow::slotNewFromTemplate);
99 contextMenu()->addSeparator();
101 QAction* a = KAlarm::createStopPlayAction(this);
102 addAction(QStringLiteral("tStopPlay"), a);
103 contextMenu()->addAction(a);
104 QObject::connect(theApp(), &KAlarmApp::audioPlaying, a, &QAction::setVisible);
105 QObject::connect(theApp(), &KAlarmApp::audioPlaying, this, &TrayWindow::updateStatus);
107 a = KAlarm::createSpreadWindowsAction(this);
108 addAction(QStringLiteral("tSpread"), a);
109 contextMenu()->addAction(a);
110 contextMenu()->addSeparator();
111 contextMenu()->addAction(KStandardAction::preferences(this, SLOT(slotPreferences()), this));
113 // Disable standard quit behaviour. We have to intercept the quit even,
114 // if the main window is hidden.
115 QAction* act = action(QStringLiteral("quit"));
116 if (act)
118 act->disconnect(SIGNAL(triggered(bool)), this, SLOT(maybeQuit()));
119 connect(act, &QAction::triggered, this, &TrayWindow::slotQuit);
122 // Set icon to correspond with the alarms enabled menu status
123 setEnabledStatus(theApp()->alarmsEnabled());
125 connect(AlarmCalendar::resources(), &AlarmCalendar::haveDisabledAlarmsChanged, this, &TrayWindow::slotHaveDisabledAlarms);
126 connect(this, &TrayWindow::activateRequested, this, &TrayWindow::slotActivateRequested);
127 connect(this, &TrayWindow::secondaryActivateRequested, this, &TrayWindow::slotSecondaryActivateRequested);
128 slotHaveDisabledAlarms(AlarmCalendar::resources()->haveDisabledAlarms());
130 // Hack: KSNI does not let us know when it is about to show the tooltip,
131 // so we need to update it whenever something change in it.
133 // This timer ensures that updateToolTip() is not called several times in a row
134 mToolTipUpdateTimer = new QTimer(this);
135 mToolTipUpdateTimer->setInterval(0);
136 mToolTipUpdateTimer->setSingleShot(true);
137 connect(mToolTipUpdateTimer, &QTimer::timeout, this, &TrayWindow::updateToolTip);
139 // Update every minute to show accurate deadlines
140 MinuteTimer::connect(mToolTipUpdateTimer, SLOT(start()));
142 // Update when alarms are modified
143 connect(AlarmListModel::all(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
144 mToolTipUpdateTimer, SLOT(start()));
145 connect(AlarmListModel::all(), SIGNAL(rowsInserted(QModelIndex,int,int)),
146 mToolTipUpdateTimer, SLOT(start()));
147 connect(AlarmListModel::all(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)),
148 mToolTipUpdateTimer, SLOT(start()));
149 connect(AlarmListModel::all(), SIGNAL(rowsRemoved(QModelIndex,int,int)),
150 mToolTipUpdateTimer, SLOT(start()));
151 connect(AlarmListModel::all(), SIGNAL(modelReset()),
152 mToolTipUpdateTimer, SLOT(start()));
154 // Set auto-hide status when next alarm or preferences change
155 mStatusUpdateTimer->setSingleShot(true);
156 connect(mStatusUpdateTimer, &QTimer::timeout, this, &TrayWindow::updateStatus);
157 connect(AlarmCalendar::resources(), &AlarmCalendar::earliestAlarmChanged, this, &TrayWindow::updateStatus);
158 Preferences::connect(SIGNAL(autoHideSystemTrayChanged(int)), this, SLOT(updateStatus()));
159 updateStatus();
161 // Update when tooltip preferences are modified
162 Preferences::connect(SIGNAL(tooltipPreferencesChanged()), mToolTipUpdateTimer, SLOT(start()));
165 TrayWindow::~TrayWindow()
167 qCDebug(KALARM_LOG);
168 theApp()->removeWindow(this);
169 Q_EMIT deleted();
172 /******************************************************************************
173 * Called when the "New Alarm" menu item is selected to edit a new alarm.
175 void TrayWindow::slotNewAlarm(EditAlarmDlg::Type type)
177 KAlarm::editNewAlarm(type);
180 /******************************************************************************
181 * Called when the "New Alarm" menu item is selected to edit a new alarm from a
182 * template.
184 void TrayWindow::slotNewFromTemplate(const KAEvent* event)
186 KAlarm::editNewAlarm(event);
189 /******************************************************************************
190 * Called when the "Configure KAlarm" menu item is selected.
192 void TrayWindow::slotPreferences()
194 KAlarmPrefDlg::display();
197 /******************************************************************************
198 * Called when the Quit context menu item is selected.
199 * Note that KAlarmApp::doQuit() must be called by the event loop, not directly
200 * from the menu item, since otherwise the tray icon will be deleted while still
201 * processing the menu, resulting in a crash.
202 * Ideally, the connect() call setting up this slot in the constructor would use
203 * Qt::QueuedConnection, but the slot is never called in that case.
205 void TrayWindow::slotQuit()
207 // Note: QTimer::singleShot(0, ...) never calls the slot.
208 QTimer::singleShot(1, this, &TrayWindow::slotQuitAfter);
210 void TrayWindow::slotQuitAfter()
212 theApp()->doQuit(static_cast<QWidget*>(parent()));
215 /******************************************************************************
216 * Called when the Alarms Enabled action status has changed.
217 * Updates the alarms enabled menu item check state, and the icon pixmap.
219 void TrayWindow::setEnabledStatus(bool status)
221 qCDebug(KALARM_LOG) << (int)status;
222 updateIcon();
223 updateStatus();
224 updateToolTip();
227 /******************************************************************************
228 * Called when individual alarms are enabled or disabled.
229 * Set the enabled icon to show or hide a disabled indication.
231 void TrayWindow::slotHaveDisabledAlarms(bool haveDisabled)
233 qCDebug(KALARM_LOG) << haveDisabled;
234 mHaveDisabledAlarms = haveDisabled;
235 updateIcon();
236 updateToolTip();
239 /******************************************************************************
240 * A left click displays the KAlarm main window.
242 void TrayWindow::slotActivateRequested()
244 // Left click: display/hide the first main window
245 if (mAssocMainWindow && mAssocMainWindow->isVisible())
247 mAssocMainWindow->raise();
248 mAssocMainWindow->activateWindow();
252 /******************************************************************************
253 * A middle button click displays the New Alarm window.
255 void TrayWindow::slotSecondaryActivateRequested()
257 if (mActionNew->isEnabled())
258 mActionNew->trigger(); // display a New Alarm dialog
261 /******************************************************************************
262 * Adjust icon auto-hide status according to when the next alarm is due.
263 * The icon is always shown if audio is playing, to give access to the 'stop'
264 * menu option.
266 void TrayWindow::updateStatus()
268 mStatusUpdateTimer->stop();
269 int period = Preferences::autoHideSystemTray();
270 // If the icon is always to be shown (AutoHideSystemTray = 0),
271 // or audio is playing, show the icon.
272 bool active = !period || MessageWin::isAudioPlaying();
273 if (!active)
275 // Show the icon only if the next active alarm complies
276 active = theApp()->alarmsEnabled();
277 if (active)
279 KAEvent* event = AlarmCalendar::resources()->earliestAlarm();
280 active = static_cast<bool>(event);
281 if (event && period > 0)
283 KDateTime dt = event->nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
284 qint64 delay = KDateTime::currentLocalDateTime().secsTo_long(dt);
285 delay -= static_cast<qint64>(period) * 60; // delay until icon to be shown
286 active = (delay <= 0);
287 if (!active)
289 // First alarm trigger is too far in future, so tray icon is to
290 // be auto-hidden. Set timer for when it should be shown again.
291 delay *= 1000; // convert to msec
292 int delay_int = static_cast<int>(delay);
293 if (delay_int != delay)
294 delay_int = INT_MAX;
295 mStatusUpdateTimer->setInterval(delay_int);
296 mStatusUpdateTimer->start();
301 setStatus(active ? Active : Passive);
304 /******************************************************************************
305 * Adjust tooltip according to the app state.
306 * The tooltip text shows alarms due in the next 24 hours. The limit of 24
307 * hours is because only times, not dates, are displayed.
309 void TrayWindow::updateToolTip()
311 bool enabled = theApp()->alarmsEnabled();
312 QString subTitle;
313 if (enabled && Preferences::tooltipAlarmCount())
314 subTitle = tooltipAlarmText();
316 if (!enabled)
317 subTitle = i18n("Disabled");
318 else if (mHaveDisabledAlarms)
320 if (!subTitle.isEmpty())
321 subTitle += QLatin1String("<br/>");
322 subTitle += i18nc("@info:tooltip Brief: some alarms are disabled", "(Some alarms disabled)");
324 setToolTipSubTitle(subTitle);
327 /******************************************************************************
328 * Adjust icon according to the app state.
330 void TrayWindow::updateIcon()
332 if (theApp()->alarmsEnabled())
333 setIconByName(mHaveDisabledAlarms ? QStringLiteral("kalarm-partdisabled") : QStringLiteral("kalarm"));
334 else
335 setIconByPixmap(mIconDisabled);
338 /******************************************************************************
339 * Return the tooltip text showing alarms due in the next 24 hours.
340 * The limit of 24 hours is because only times, not dates, are displayed.
342 QString TrayWindow::tooltipAlarmText() const
344 KAEvent event;
345 const QString& prefix = Preferences::tooltipTimeToPrefix();
346 int maxCount = Preferences::tooltipAlarmCount();
347 KDateTime now = KDateTime::currentLocalDateTime();
348 KDateTime tomorrow = now.addDays(1);
350 // Get today's and tomorrow's alarms, sorted in time order
351 int i, iend;
352 QList<TipItem> items;
353 QVector<KAEvent> events = KAlarm::getSortedActiveEvents(const_cast<TrayWindow*>(this), &mAlarmsModel);
354 for (i = 0, iend = events.count(); i < iend; ++i)
356 KAEvent* event = &events[i];
357 if (event->actionSubType() == KAEvent::MESSAGE)
359 TipItem item;
360 QDateTime dateTime = event->nextTrigger(KAEvent::DISPLAY_TRIGGER).effectiveKDateTime().toLocalZone().dateTime();
361 if (dateTime > tomorrow.dateTime())
362 break; // ignore alarms after tomorrow at the current clock time
363 item.dateTime = dateTime;
365 // The alarm is due today, or early tomorrow
366 if (Preferences::showTooltipAlarmTime())
368 item.text += KLocale::global()->formatTime(item.dateTime.time());
369 item.text += QLatin1Char(' ');
371 if (Preferences::showTooltipTimeToAlarm())
373 int mins = (now.dateTime().secsTo(item.dateTime) + 59) / 60;
374 if (mins < 0)
375 mins = 0;
376 char minutes[3] = "00";
377 minutes[0] = static_cast<char>((mins%60) / 10 + '0');
378 minutes[1] = static_cast<char>((mins%60) % 10 + '0');
379 if (Preferences::showTooltipAlarmTime())
380 item.text += i18nc("@info prefix + hours:minutes", "(%1%2:%3)", prefix, mins/60, QLatin1String(minutes));
381 else
382 item.text += i18nc("@info prefix + hours:minutes", "%1%2:%3", prefix, mins/60, QLatin1String(minutes));
383 item.text += QLatin1Char(' ');
385 item.text += AlarmText::summary(*event);
387 // Insert the item into the list in time-sorted order
388 int it = 0;
389 for (int itend = items.count(); it < itend; ++it)
391 if (item.dateTime <= items[it].dateTime)
392 break;
394 items.insert(it, item);
397 qCDebug(KALARM_LOG);
398 QString text;
399 int count = 0;
400 for (i = 0, iend = items.count(); i < iend; ++i)
402 qCDebug(KALARM_LOG) << "--" << (count+1) << ")" << items[i].text;
403 if (i > 0)
404 text += QLatin1String("<br />");
405 text += items[i].text;
406 if (++count == maxCount)
407 break;
409 return text;
412 /******************************************************************************
413 * Called when the associated main window is closed.
415 void TrayWindow::removeWindow(MainWindow* win)
417 if (win == mAssocMainWindow)
418 mAssocMainWindow = Q_NULLPTR;
421 // vim: et sw=4: