2 * traywindow.cpp - the KDE system tray applet
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"
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>
46 #include <KIconLoader>
53 #include "kalarm_debug.h"
58 using namespace KAlarmCal
;
67 /*=============================================================================
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)
80 setToolTipIconByName(QStringLiteral("kalarm"));
81 setToolTipTitle(KAboutData::applicationData().displayName());
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"));
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()));
161 // Update when tooltip preferences are modified
162 Preferences::connect(SIGNAL(tooltipPreferencesChanged()), mToolTipUpdateTimer
, SLOT(start()));
165 TrayWindow::~TrayWindow()
168 theApp()->removeWindow(this);
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
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
;
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
;
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'
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();
275 // Show the icon only if the next active alarm complies
276 active
= theApp()->alarmsEnabled();
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);
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
)
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();
313 if (enabled
&& Preferences::tooltipAlarmCount())
314 subTitle
= tooltipAlarmText();
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"));
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
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
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
)
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;
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
));
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
389 for (int itend
= items
.count(); it
< itend
; ++it
)
391 if (item
.dateTime
<= items
[it
].dateTime
)
394 items
.insert(it
, item
);
400 for (i
= 0, iend
= items
.count(); i
< iend
; ++i
)
402 qCDebug(KALARM_LOG
) << "--" << (count
+1) << ")" << items
[i
].text
;
404 text
+= QLatin1String("<br />");
405 text
+= items
[i
].text
;
406 if (++count
== maxCount
)
412 /******************************************************************************
413 * Called when the associated main window is closed.
415 void TrayWindow::removeWindow(MainWindow
* win
)
417 if (win
== mAssocMainWindow
)
418 mAssocMainWindow
= Q_NULLPTR
;