Fix reactivate quotelevel
[kdepim.git] / korgac / alarmdialog.cpp
blob42502c439af38364085b2b8786ecce1f930a58e2
1 /*
2 This file is part of the KDE reminder agent.
4 Copyright (c) 2000,2003 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (c) 2008-2009 Allen Winter <winter@kde.org>
6 Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 As a special exception, permission is given to link this program
23 with any edition of Qt, and distribute the resulting executable,
24 without including the source code for Qt in the source distribution.
27 #include "config-kdepim.h"
28 #include "alarmdialog.h"
29 #include "korganizer_interface.h"
30 #include "mailclient.h"
31 #include "koalarmclient_debug.h"
33 #include <CalendarSupport/IncidenceViewer>
34 #include <CalendarSupport/KCalPrefs>
35 #include <CalendarSupport/IdentityManager>
36 #include <CalendarSupport/Utils>
38 #include <IncidenceEditorsng/IncidenceDialog>
39 #include <IncidenceEditorsng/IncidenceDialogFactory>
41 #include <KCalCore/Event>
42 #include <KCalCore/Todo>
43 #include <KCalUtils/IncidenceFormatter>
45 #include <KIdentityManagement/Identity>
46 #include <KIdentityManagement/IdentityManager>
48 #include <AkonadiCore/Item>
50 #include <MailTransport/TransportManager>
51 #include <QUrl>
53 #include <KLocale>
54 #include <KComboBox>
55 #include <QHBoxLayout>
56 #include <KLocalizedString>
57 #include <KMessageBox>
58 #include <KNotification>
59 #include <KSharedConfig>
60 #include <KSystemTimeZone>
61 #include <KToolInvocation>
62 #include <KWindowSystem>
63 #include <KIconLoader>
64 #include <QIcon>
65 #include <phonon/mediaobject.h>
66 #include <QLabel>
67 #include <QKeyEvent>
68 #include <QSpinBox>
69 #include <QTreeWidget>
70 #include <QVBoxLayout>
71 #include <KConfigGroup>
72 #include <QDialogButtonBox>
73 #include <QPushButton>
75 using namespace KIdentityManagement;
76 using namespace KCalCore;
77 using namespace KCalUtils;
79 static int defSuspendVal = 5;
80 static int defSuspendUnit = 0; // 0=>minutes, 1=>hours, 2=>days, 3=>weeks
82 class ReminderTree : public QTreeWidget
84 public:
85 ReminderTree(QWidget *parent) : QTreeWidget(parent)
91 class ReminderTreeItem : public QTreeWidgetItem
93 public:
94 ReminderTreeItem(const Akonadi::Item &incidence, QTreeWidget *parent)
95 : QTreeWidgetItem(parent), mIncidence(incidence), mNotified(false)
98 bool operator<(const QTreeWidgetItem &other) const Q_DECL_OVERRIDE;
100 QString mDisplayText;
102 const Akonadi::Item mIncidence;
103 QDateTime mRemindAt;
104 KDateTime mTrigger;
105 KDateTime mHappening;
106 bool mNotified;
109 struct ConfItem {
110 QString uid;
111 QUrl akonadiUrl;
112 QDateTime remindAt;
115 bool ReminderTreeItem::operator<(const QTreeWidgetItem &other) const
117 switch (treeWidget()->sortColumn()) {
118 case 1: { // happening datetime
119 const ReminderTreeItem *item = static_cast<const ReminderTreeItem *>(&other);
120 return item->mHappening < mHappening;
122 case 2: { // trigger datetime
123 const ReminderTreeItem *item = static_cast<const ReminderTreeItem *>(&other);
124 return item->mTrigger < mTrigger;
126 default:
127 return QTreeWidgetItem::operator < (other);
131 AlarmDialog::AlarmDialog(const Akonadi::ETMCalendar::Ptr &calendar, QWidget *parent)
132 : QDialog(parent, Qt::WindowStaysOnTopHint),
133 mCalendar(calendar), mSuspendTimer(this)
135 // User1 => Edit...
136 // User2 => Dismiss All
137 // User3 => Dismiss Selected
138 // Ok => Suspend
140 connect(calendar.data(), &Akonadi::ETMCalendar::calendarChanged, this, &AlarmDialog::slotCalendarChanged);
142 KIconLoader::global()->addAppDir(QStringLiteral("korgac"));
144 KSharedConfig::Ptr config = KSharedConfig::openConfig();
145 KConfigGroup generalConfig(config, "General");
146 QPoint pos = generalConfig.readEntry("Position", QPoint(0, 0));
148 QWidget *topBox = new QWidget(this);
149 if (!pos.isNull()) {
150 mPos = pos;
151 topBox->move(mPos);
153 setWindowTitle(i18nc("@title:window", "Reminders"));
154 setWindowIcon(QIcon::fromTheme(QStringLiteral("korgac")));
155 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
156 QVBoxLayout *mainLayout = new QVBoxLayout;
157 setLayout(mainLayout);
158 mainLayout->addWidget(topBox);
159 mOkButton = buttonBox->button(QDialogButtonBox::Ok);
160 mOkButton->setShortcut(Qt::CTRL | Qt::Key_Return);
161 mUser1Button = new QPushButton;
162 buttonBox->addButton(mUser1Button, QDialogButtonBox::ActionRole);
163 mUser2Button = new QPushButton;
164 buttonBox->addButton(mUser2Button, QDialogButtonBox::ActionRole);
165 mUser3Button = new QPushButton;
166 buttonBox->addButton(mUser3Button, QDialogButtonBox::ActionRole);
167 connect(buttonBox, &QDialogButtonBox::accepted, this, &AlarmDialog::accept);
168 connect(buttonBox, &QDialogButtonBox::rejected, this, &AlarmDialog::reject);
169 mainLayout->addWidget(buttonBox);
171 mUser3Button->setText(i18nc("@action:button", "Dismiss Reminder"));
172 mUser3Button->setToolTip(i18nc("@info:tooltip",
173 "Dismiss the reminders for the selected incidences"));
174 mUser2Button->setText(i18nc("@action:button", "Dismiss All"));
175 mUser2Button->setToolTip(i18nc("@info:tooltip",
176 "Dismiss the reminders for all listed incidences"));
177 mUser1Button->setText(i18nc("@action:button", "Edit..."));
178 mUser1Button->setToolTip(i18nc("@info:tooltip",
179 "Edit the selected incidence"));
180 mOkButton->setText(i18nc("@action:button", "Suspend"));
181 mOkButton->setToolTip(i18nc("@info:tooltip",
182 "Suspend the reminders for the selected incidences "
183 "by the specified interval"));
185 // Try to keep the dialog small and non-obtrusive.
186 setMinimumWidth(575);
187 setMinimumHeight(300);
189 QVBoxLayout *mTopLayout = new QVBoxLayout(topBox);
191 QLabel *label = new QLabel(
192 i18nc("@label",
193 "Reminders: "
194 "Click on a title to toggle the details viewer for that item"),
195 topBox);
196 mTopLayout->addWidget(label);
198 mIncidenceTree = new ReminderTree(topBox);
199 mIncidenceTree->setColumnCount(3);
200 mIncidenceTree->setSortingEnabled(true);
201 const QStringList headerLabels =
202 (QStringList(i18nc("@title:column reminder title", "Title"))
203 << i18nc("@title:column happens at date/time", "Date Time")
204 << i18nc("@title:column trigger date/time", "Trigger Time"));
205 mIncidenceTree->setHeaderLabels(headerLabels);
206 mIncidenceTree->headerItem()->setToolTip(
208 i18nc("@info:tooltip", "The event or to-do title"));
209 mIncidenceTree->headerItem()->setToolTip(
211 i18nc("@info:tooltip", "The reminder is set for this date/time"));
212 mIncidenceTree->headerItem()->setToolTip(
214 i18nc("@info:tooltip", "The date/time the reminder was triggered"));
216 mIncidenceTree->setWordWrap(true);
217 mIncidenceTree->setAllColumnsShowFocus(true);
218 mIncidenceTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
219 mIncidenceTree->setRootIsDecorated(false);
221 mTopLayout->addWidget(mIncidenceTree);
223 connect(mIncidenceTree, &QTreeWidget::itemClicked, this, &AlarmDialog::update);
224 connect(mIncidenceTree, &QTreeWidget::itemDoubleClicked, this, &AlarmDialog::edit);
225 connect(mIncidenceTree, &ReminderTree::itemSelectionChanged, this, &AlarmDialog::updateButtons);
227 mDetailView = new CalendarSupport::IncidenceViewer(mCalendar.data(), topBox);
228 QString s;
229 s = xi18nc("@info default incidence details string",
230 "<emphasis>Select an event or to-do from the list above "
231 "to view its details here.</emphasis>");
232 mDetailView->setDefaultMessage(s);
233 mTopLayout->addWidget(mDetailView);
234 mDetailView->hide();
235 mLastItem = 0;
237 QWidget *suspendBox = new QWidget(topBox);
238 QHBoxLayout *suspendBoxHBoxLayout = new QHBoxLayout(suspendBox);
239 suspendBoxHBoxLayout->setMargin(0);
240 mTopLayout->addWidget(suspendBox);
242 QLabel *l = new QLabel(i18nc("@label:spinbox", "Suspend &duration:"), suspendBox);
243 suspendBoxHBoxLayout->addWidget(l);
245 mSuspendSpin = new QSpinBox(suspendBox);
246 suspendBoxHBoxLayout->addWidget(mSuspendSpin);
247 mSuspendSpin->setRange(1, 9999);
248 mSuspendSpin->setValue(defSuspendVal); // default suspend duration
249 mSuspendSpin->setToolTip(
250 i18nc("@info:tooltip",
251 "Suspend the reminders by this amount of time"));
252 mSuspendSpin->setWhatsThis(
253 i18nc("@info:whatsthis",
254 "Each reminder for the selected incidences will be suspended "
255 "by this number of time units. You can choose the time units "
256 "(typically minutes) in the adjacent selector."));
258 l->setBuddy(mSuspendSpin);
260 mSuspendUnit = new KComboBox(suspendBox);
261 suspendBoxHBoxLayout->addWidget(mSuspendUnit);
262 mSuspendUnit->addItem(i18nc("@item:inlistbox suspend in terms of minutes", "minute(s)"));
263 mSuspendUnit->addItem(i18nc("@item:inlistbox suspend in terms of hours", "hour(s)"));
264 mSuspendUnit->addItem(i18nc("@item:inlistbox suspend in terms of days", "day(s)"));
265 mSuspendUnit->addItem(i18nc("@item:inlistbox suspend in terms of weeks", "week(s)"));
266 mSuspendUnit->setToolTip(
267 i18nc("@info:tooltip",
268 "Suspend the reminders using this time unit"));
269 mSuspendUnit->setWhatsThis(
270 i18nc("@info:whatsthis",
271 "Each reminder for the selected incidences will be suspended "
272 "using this time unit. You can set the number of time units "
273 "in the adjacent number entry input."));
275 mSuspendUnit->setCurrentIndex(defSuspendUnit);
277 connect(&mSuspendTimer, &QTimer::timeout, this, &AlarmDialog::wakeUp);
279 connect(mOkButton, &QPushButton::clicked, this, &AlarmDialog::slotOk);
280 connect(mUser1Button, &QPushButton::clicked, this, &AlarmDialog::slotUser1);
281 connect(mUser2Button, &QPushButton::clicked, this, &AlarmDialog::slotUser2);
282 connect(mUser3Button, &QPushButton::clicked, this, &AlarmDialog::slotUser3);
284 mIdentityManager = new CalendarSupport::IdentityManager;
287 AlarmDialog::~AlarmDialog()
289 mIncidenceTree->clear();
290 delete mIdentityManager;
293 ReminderTreeItem *AlarmDialog::searchByItem(const Akonadi::Item &incidence)
295 ReminderTreeItem *found = 0;
296 QTreeWidgetItemIterator it(mIncidenceTree);
297 while (*it) {
298 ReminderTreeItem *item = static_cast<ReminderTreeItem *>(*it);
299 if (item->mIncidence == incidence) {
300 found = item;
301 break;
303 ++it;
305 return found;
308 static QString cleanSummary(const QString &summary)
310 static QString etc = i18nc("@label an elipsis", "...");
311 int maxLen = 30;
312 QString retStr = summary;
313 retStr.replace(QLatin1Char('\n'), QLatin1Char(' '));
314 if (retStr.length() > maxLen) {
315 maxLen -= etc.length();
316 retStr = retStr.left(maxLen);
317 retStr += etc;
319 return retStr;
322 void AlarmDialog::addIncidence(const Akonadi::Item &incidenceitem,
323 const QDateTime &reminderAt,
324 const QString &displayText)
326 Incidence::Ptr incidence = CalendarSupport::incidence(incidenceitem);
327 ReminderTreeItem *item = searchByItem(incidenceitem);
328 if (!item) {
329 item = new ReminderTreeItem(incidenceitem, mIncidenceTree);
331 item->mNotified = false;
332 item->mHappening = KDateTime();
333 item->mRemindAt = reminderAt;
334 item->mTrigger = KDateTime::currentLocalDateTime();
335 item->mDisplayText = displayText;
336 item->setText(0, cleanSummary(incidence->summary()));
338 QString displayStr;
339 const KDateTime dateTime = triggerDateForIncidence(incidence, reminderAt,
340 displayStr);
342 if (incidence->type() == Incidence::TypeEvent) {
343 item->setIcon(0, QIcon::fromTheme(QStringLiteral("view-calendar-day")));
344 } else if (incidence->type() == Incidence::TypeTodo) {
345 item->setIcon(0, QIcon::fromTheme(QStringLiteral("view-calendar-tasks")));
348 item->mHappening = dateTime;
349 item->setText(1, displayStr);
351 item->setText(2, IncidenceFormatter::dateTimeToString(
352 item->mTrigger, false, true, KDateTime::Spec::LocalZone()));
353 QString tip =
354 IncidenceFormatter::toolTipStr(
355 CalendarSupport::displayName(mCalendar.data(), incidenceitem.parentCollection()),
356 incidence,
357 item->mRemindAt.date(), true,
358 KDateTime::Spec::LocalZone());
359 if (!item->mDisplayText.isEmpty()) {
360 tip += QLatin1String("<br>") + item->mDisplayText;
362 item->setToolTip(0, tip);
363 item->setToolTip(1, tip);
364 item->setToolTip(2, tip);
365 item->setData(0, QTreeWidgetItem::UserType, false);
367 mIncidenceTree->setCurrentItem(item);
368 showDetails(item);
369 slotSave();
372 void AlarmDialog::slotOk()
374 suspend();
377 void AlarmDialog::slotUser1()
379 const ReminderList selection = selectedItems();
380 if (!selection.isEmpty()) {
381 ReminderTreeItem *item = selection.first();
382 if (mCalendar->hasRight(item->mIncidence, Akonadi::Collection::CanChangeItem)) {
383 edit();
388 void AlarmDialog::slotUser2()
390 dismissAll();
393 void AlarmDialog::slotUser3()
395 dismissCurrent();
398 void AlarmDialog::dismissCurrent()
400 dismiss(selectedItems());
402 if (activeCount() == 0) {
403 accept();
404 } else {
405 update();
407 Q_EMIT reminderCount(activeCount());
410 void AlarmDialog::dismissAll()
412 ReminderList selections;
414 QTreeWidgetItemIterator it(mIncidenceTree);
415 while (*it) {
416 if (!(*it)->isDisabled()) { //do not disable suspended reminders
417 selections.append(static_cast<ReminderTreeItem *>(*it));
419 ++it;
421 dismiss(selections);
423 setTimer();
424 accept();
425 Q_EMIT reminderCount(activeCount());
428 void AlarmDialog::dismiss(ReminderList selections)
430 QList<Akonadi::Item::Id> ids;
431 ids.reserve(selections.count());
432 for (ReminderList::Iterator it = selections.begin(); it != selections.end(); ++it) {
433 qCDebug(KOALARMCLIENT_LOG) << "removing " << CalendarSupport::incidence((*it)->mIncidence)->summary();
434 if (mIncidenceTree->itemBelow(*it)) {
435 mIncidenceTree->setCurrentItem(mIncidenceTree->itemBelow(*it));
436 } else if (mIncidenceTree->itemAbove(*it)) {
437 mIncidenceTree->setCurrentItem(mIncidenceTree->itemAbove(*it));
439 mIncidenceTree->removeItemWidget(*it, 0);
440 ids.append((*it)->mIncidence.id());
441 delete *it;
444 removeFromConfig(ids);
447 void AlarmDialog::edit()
449 ReminderList selection = selectedItems();
450 if (selection.count() != 1) {
451 return;
453 Incidence::Ptr incidence = CalendarSupport::incidence(selection.first()->mIncidence);
454 if (!mCalendar->hasRight(selection.first()->mIncidence, Akonadi::Collection::CanChangeItem)) {
455 KMessageBox::sorry(
456 this,
457 i18nc("@info",
458 "\"%1\" is a read-only item so modifications are not possible.",
459 cleanSummary(incidence->summary())));
460 return;
463 openIncidenceEditorNG(selection.first()->mIncidence);
466 void AlarmDialog::suspend()
468 if (!isVisible()) { //do nothing if the dialog is hidden
469 return;
472 int unit = 1;
473 switch (mSuspendUnit->currentIndex()) {
474 case 3: // weeks
475 unit *= 7;
476 case 2: // days
477 unit *= 24;
478 case 1: // hours
479 unit *= 60;
480 case 0: // minutes
481 unit *= 60;
482 default:
483 break;
486 ReminderTreeItem *selitem = 0;
487 QTreeWidgetItemIterator it(mIncidenceTree);
488 while (*it) {
489 if ((*it)->isSelected() && !(*it)->isDisabled()) { //suspend selected, non-suspended reminders
490 (*it)->setSelected(false);
491 (*it)->setDisabled(true);
492 ReminderTreeItem *item = static_cast<ReminderTreeItem *>(*it);
493 item->mRemindAt = QDateTime::currentDateTime().addSecs(unit * mSuspendSpin->value());
494 item->mHappening = KDateTime(item->mRemindAt, KDateTime::Spec::LocalZone());
495 item->mNotified = false;
496 (*it)->setText(1, KLocale::global()->formatDateTime(item->mHappening));
497 selitem = item;
499 ++it;
502 if (selitem) {
503 if (mIncidenceTree->itemBelow(selitem)) {
504 mIncidenceTree->setCurrentItem(mIncidenceTree->itemBelow(selitem));
505 } else if (mIncidenceTree->itemAbove(selitem)) {
506 mIncidenceTree->setCurrentItem(mIncidenceTree->itemAbove(selitem));
510 // save suspended alarms too so they can be restored on restart
511 // kolab/issue4108
512 slotSave();
514 setTimer();
515 if (activeCount() == 0) {
516 accept();
517 } else {
518 update();
520 Q_EMIT reminderCount(activeCount());
523 void AlarmDialog::setTimer()
525 int nextReminderAt = -1;
527 QTreeWidgetItemIterator it(mIncidenceTree);
528 while (*it) {
529 ReminderTreeItem *item = static_cast<ReminderTreeItem *>(*it);
530 if (item->mRemindAt > QDateTime::currentDateTime()) {
531 const int secs = QDateTime::currentDateTime().secsTo(item->mRemindAt);
532 nextReminderAt = nextReminderAt <= 0 ? secs : qMin(nextReminderAt, secs);
534 ++it;
537 if (nextReminderAt >= 0) {
538 mSuspendTimer.stop();
539 mSuspendTimer.start(1000 * (nextReminderAt + 1));
540 mSuspendTimer.setSingleShot(true);
544 void AlarmDialog::show()
546 mIncidenceTree->resizeColumnToContents(0);
547 mIncidenceTree->resizeColumnToContents(1);
548 mIncidenceTree->resizeColumnToContents(2);
549 mIncidenceTree->sortItems(1, Qt::AscendingOrder);
551 // select the first item that hasn't already been notified
552 QTreeWidgetItemIterator it(mIncidenceTree);
553 while (*it) {
554 ReminderTreeItem *item = static_cast<ReminderTreeItem *>(*it);
555 if (!item->mNotified) {
556 (*it)->setSelected(true);
557 break;
559 ++it;
562 // reset the default suspend time
563 // Allen: commented-out the following lines on 17 Sept 2013
564 // mSuspendSpin->setValue( defSuspendVal );
565 // mSuspendUnit->setCurrentIndex( defSuspendUnit );
567 QDialog::show();
568 if (!mPos.isNull()) {
569 QDialog::move(mPos);
571 KWindowSystem::unminimizeWindow(winId(), false);
572 KWindowSystem::setState(winId(), NET::KeepAbove | NET::DemandsAttention);
573 KWindowSystem::setOnAllDesktops(winId(), true);
574 KWindowSystem::activateWindow(winId());
576 // Audio, Procedure, and EMail alarms
577 eventNotification();
580 void AlarmDialog::suspendAll()
582 mIncidenceTree->clearSelection();
583 QTreeWidgetItemIterator it(mIncidenceTree);
585 // first, select all non-suspended reminders
586 while (*it) {
587 if (!(*it)->isDisabled()) { //do not suspend suspended reminders
588 (*it)->setSelected(true);
590 ++it;
593 //suspend all selected reminders
594 suspend();
597 void AlarmDialog::eventNotification()
599 bool beeped = false;
600 bool found = false;
602 ReminderList list;
604 QTreeWidgetItemIterator it(mIncidenceTree);
605 while (*it) {
606 ReminderTreeItem *item = static_cast<ReminderTreeItem *>(*it);
607 ++it;
608 if (item->isDisabled() || item->mNotified) {
609 //skip suspended reminders or reminders that have been notified
610 continue;
612 found = true;
613 item->mNotified = true;
614 Incidence::Ptr incidence = CalendarSupport::incidence(item->mIncidence);
615 Alarm::List alarms = incidence->alarms();
616 Alarm::List::ConstIterator ait;
617 for (ait = alarms.constBegin(); ait != alarms.constEnd(); ++ait) {
618 Alarm::Ptr alarm = *ait;
619 // FIXME: Check whether this should be done for all multiple alarms
620 if (alarm->type() == Alarm::Procedure) {
621 // FIXME: Add a message box asking whether the procedure should really be executed
622 qCDebug(KOALARMCLIENT_LOG) << "Starting program: '" << alarm->programFile() << "'";
624 QString program = alarm->programFile();
626 // if the program name contains spaces escape it
627 if (program.contains(QLatin1Char(' ')) &&
628 !(program.startsWith(QLatin1Char('\"')) &&
629 program.endsWith(QLatin1Char('\"')))) {
630 program = QLatin1Char('\"') + program + QLatin1Char('\"');
633 QProcess::startDetached(program + QLatin1Char(' ') + alarm->programArguments());
634 } else if (alarm->type() == Alarm::Audio) {
635 beeped = true;
636 Phonon::MediaObject *player =
637 Phonon::createPlayer(Phonon::NotificationCategory,
638 QUrl::fromLocalFile(alarm->audioFile()));
639 player->setParent(this);
640 connect(player, &Phonon::MediaObject::finished, player, &Phonon::MediaObject::deleteLater);
641 player->play();
642 } else if (alarm->type() == Alarm::Email) {
643 QString from = CalendarSupport::KCalPrefs::instance()->email();
644 Identity id = mIdentityManager->identityForAddress(from);
645 QString to;
646 if (alarm->mailAddresses().isEmpty()) {
647 to = from;
648 } else {
649 const Person::List addresses = alarm->mailAddresses();
650 QStringList add;
651 add.reserve(addresses.count());
652 for (Person::List::ConstIterator it = addresses.constBegin();
653 it != addresses.constEnd(); ++it) {
654 add << (*it)->fullName();
656 to = add.join(QStringLiteral(", "));
659 QString subject;
661 Akonadi::Item parentItem = mCalendar->item(alarm->parentUid());
662 Incidence::Ptr parent = CalendarSupport::incidence(parentItem);
664 if (alarm->mailSubject().isEmpty()) {
665 if (parent->summary().isEmpty()) {
666 subject = i18nc("@title", "Reminder");
667 } else {
668 subject = i18nc("@title", "Reminder: %1", cleanSummary(parent->summary()));
670 } else {
671 subject = i18nc("@title", "Reminder: %1", alarm->mailSubject());
674 QString body =
675 IncidenceFormatter::mailBodyStr(
676 parent.staticCast<IncidenceBase>(), KSystemTimeZones::local());
677 if (!alarm->mailText().isEmpty()) {
678 body += QLatin1Char('\n') + alarm->mailText();
680 //TODO: support attachments
681 KOrg::MailClient mailer;
682 mailer.send(id, from, to, QString(), subject, body, true, false, QString(),
683 MailTransport::TransportManager::self()->defaultTransportName());
688 if (!beeped && found) {
689 KNotification::beep();
693 void AlarmDialog::wakeUp()
695 bool activeReminders = false;
696 QTreeWidgetItemIterator it(mIncidenceTree);
697 QTreeWidgetItem *firstItem = 0;
698 while (*it) {
699 if (!firstItem) {
700 firstItem = *it;
702 ReminderTreeItem *item = static_cast<ReminderTreeItem *>(*it);
703 Incidence::Ptr incidence = CalendarSupport::incidence(item->mIncidence);
705 if (item->mRemindAt <= QDateTime::currentDateTime()) {
706 if (item->isDisabled()) { //do not wakeup non-suspended reminders
707 item->setDisabled(false);
708 item->setSelected(false);
710 activeReminders = true;
711 } else {
712 item->setDisabled(true);
715 ++it;
718 if (activeReminders) {
719 show();
721 setTimer();
722 showDetails(firstItem);
723 Q_EMIT reminderCount(activeCount());
726 void AlarmDialog::slotSave()
728 KSharedConfig::Ptr config = KSharedConfig::openConfig();
729 KConfigGroup generalConfig(config, "General");
730 int numReminders = 0;
732 QTreeWidgetItemIterator it(mIncidenceTree);
733 while (*it) {
734 ReminderTreeItem *item = static_cast<ReminderTreeItem *>(*it);
735 KConfigGroup incidenceConfig(config,
736 QStringLiteral("Incidence-%1").arg(numReminders + 1));
738 Incidence::Ptr incidence = CalendarSupport::incidence(item->mIncidence);
739 incidenceConfig.writeEntry("AkonadiUrl", item->mIncidence.url());
740 incidenceConfig.writeEntry("RemindAt", item->mRemindAt);
741 ++numReminders;
742 ++it;
745 generalConfig.writeEntry("Reminders", numReminders);
746 generalConfig.writeEntry("Position", pos());
747 config->sync();
750 AlarmDialog::ReminderList AlarmDialog::selectedItems() const
752 ReminderList list;
754 QTreeWidgetItemIterator it(mIncidenceTree);
755 while (*it) {
756 if ((*it)->isSelected()) {
757 list.append(static_cast<ReminderTreeItem *>(*it));
759 ++it;
761 return list;
764 int AlarmDialog::activeCount()
766 int count = 0;
767 QTreeWidgetItemIterator it(mIncidenceTree);
768 while (*it) {
769 if (!(*it)->isDisabled()) { //suspended reminders are non-active
770 ++count;
772 ++it;
774 qCDebug(KOALARMCLIENT_LOG) << "computed " << count << " active reminders";
775 return count;
778 void AlarmDialog::closeEvent(QCloseEvent *)
780 slotSave();
781 accept();
784 void AlarmDialog::updateButtons()
786 const ReminderList selection = selectedItems();
787 const int count = selection.count();
788 const bool enabled = (count > 0);
789 qCDebug(KOALARMCLIENT_LOG) << "selected items=" << count;
790 mUser3Button->setEnabled(enabled);
791 mOkButton->setEnabled(enabled);
792 if (count == 1) {
793 ReminderTreeItem *item = selection.first();
794 mUser1Button->setEnabled(mCalendar->hasRight(item->mIncidence, Akonadi::Collection::CanChangeItem));
795 } else {
796 mUser1Button->setEnabled(false);
800 void AlarmDialog::toggleDetails(QTreeWidgetItem *item)
802 if (!item) {
803 return;
806 if (!mDetailView->isHidden()) {
807 if (mLastItem == item) {
808 resize(size().width(), size().height() - mDetailView->height() - 50);
809 mDetailView->hide();
810 } else {
811 showDetails(item);
813 } else {
814 resize(size().width(), size().height() + mDetailView->height() + 50);
815 showDetails(item);
816 mDetailView->show();
818 mLastItem = item;
821 void AlarmDialog::showDetails(QTreeWidgetItem *item)
823 if (!item) {
824 return;
827 ReminderTreeItem *reminderItem = dynamic_cast<ReminderTreeItem *>(item);
829 if (!reminderItem) {
830 mDetailView->setIncidence(Akonadi::Item());
831 } else {
832 if (!reminderItem->mDisplayText.isEmpty()) {
833 QString txt = QLatin1String("<qt><p><b>") + reminderItem->mDisplayText + QLatin1String("</b></p></qt>");
834 mDetailView->setHeaderText(txt);
835 } else {
836 mDetailView->setHeaderText(QString());
838 Incidence::Ptr incidence = CalendarSupport::incidence(reminderItem->mIncidence);
839 mDetailView->setIncidence(reminderItem->mIncidence, reminderItem->mRemindAt.date());
843 void AlarmDialog::update()
845 updateButtons();
847 const ReminderList selection = selectedItems();
848 if (!selection.isEmpty()) {
849 ReminderTreeItem *item = selection.first();
850 mUser1Button->setEnabled((mCalendar->hasRight(item->mIncidence, Akonadi::Collection::CanChangeItem)) && (selection.count() == 1));
851 toggleDetails(item);
855 void AlarmDialog::accept()
857 if (activeCount() == 0) {
858 mPos = pos();
859 hide();
863 /** static */
864 KDateTime AlarmDialog::triggerDateForIncidence(const Incidence::Ptr &incidence,
865 const QDateTime &reminderAt,
866 QString &displayStr)
868 KDateTime result;
870 if (incidence->alarms().isEmpty()) {
871 return result;
874 Alarm::Ptr alarm = incidence->alarms().at(0);
876 if (incidence->recurs()) {
877 result = incidence->recurrence()->getNextDateTime(
878 KDateTime(reminderAt, KDateTime::Spec::LocalZone()));
880 displayStr = KLocale::global()->formatDateTime(result.toLocalZone());
883 if (!result.isValid()) {
884 result = incidence->dateTime(Incidence::RoleAlarm);
885 displayStr = IncidenceFormatter::dateTimeToString(result, false,
886 true,
887 KDateTime::Spec::LocalZone());
890 return result;
893 void AlarmDialog::slotCalendarChanged()
895 KCalCore::Incidence::List incidences = mCalendar->incidences();
896 Akonadi::Item::List items = mCalendar->itemList(incidences);
897 for (Akonadi::Item::List::ConstIterator it = items.constBegin();
898 it != items.constEnd(); ++it) {
899 ReminderTreeItem *item = searchByItem(*it);
901 if (item) {
902 Incidence::Ptr incidence = CalendarSupport::incidence(*it);
903 QString displayStr;
905 // Yes, alarms can be empty, if someone edited the incidence and removed all alarms
906 if (!incidence->alarms().isEmpty()) {
907 const KDateTime dateTime = triggerDateForIncidence(incidence,
908 item->mRemindAt,
909 displayStr);
911 const QString summary = cleanSummary(incidence->summary());
913 if (displayStr != item->text(1) || summary != item->text(0)) {
914 item->setText(1, displayStr);
915 item->setText(0, summary);
922 void AlarmDialog::keyPressEvent(QKeyEvent *e)
924 const int key = e->key() | e->modifiers();
926 if (key == Qt::Key_Enter || key == Qt::Key_Return) {
927 e->ignore();
928 return;
931 QDialog::keyPressEvent(e);
934 bool AlarmDialog::openIncidenceEditorThroughKOrganizer(const Incidence::Ptr &incidence)
936 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.korganizer"))) {
937 if (KToolInvocation::startServiceByDesktopName(QStringLiteral("korganizer"), QString())) {
938 KMessageBox::error(
939 this,
940 i18nc("@info",
941 "Could not start KOrganizer so editing is not possible."));
942 return false;
945 org::kde::korganizer::Korganizer korganizer(
946 QStringLiteral("org.kde.korganizer"), QStringLiteral("/Korganizer"), QDBusConnection::sessionBus());
948 qCDebug(KOALARMCLIENT_LOG) << "editing incidence " << incidence->summary();
949 if (!korganizer.editIncidence(incidence->uid())) {
950 KMessageBox::error(
951 this,
952 i18nc("@info",
953 "An internal KOrganizer error occurred attempting to modify \"%1\"",
954 cleanSummary(incidence->summary())));
957 // get desktop # where korganizer (or kontact) runs
958 QString object =
959 QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.kontact")) ?
960 QStringLiteral("kontact/MainWindow_1") : QStringLiteral("korganizer/MainWindow_1");
961 QDBusInterface korganizerObj(QStringLiteral("org.kde.korganizer"), QLatin1Char('/') + object);
962 #if KDEPIM_HAVE_X11
963 QDBusReply<int> reply = korganizerObj.call(QStringLiteral("winId"));
964 if (reply.isValid()) {
965 int window = reply;
966 auto winInfo = KWindowInfo(window, NET::WMDesktop);
967 int desktop = winInfo.desktop();
968 if (KWindowSystem::currentDesktop() == desktop) {
969 KWindowSystem::minimizeWindow(winId(), false);
970 } else {
971 KWindowSystem::setCurrentDesktop(desktop);
973 KWindowSystem::activateWindow(winInfo.transientFor());
975 #elif defined(Q_OS_WIN)
976 // WId is a typedef to a void* on windows
977 QDBusReply<qlonglong> reply = korganizerObj.call(QStringLiteral("winId"));
978 if (reply.isValid()) {
979 qlonglong window = reply;
980 KWindowSystem::minimizeWindow(winId(), false);
981 KWindowSystem::allowExternalProcessWindowActivation();
982 KWindowSystem::activateWindow(reinterpret_cast<WId>(window));
984 #else
985 // TODO (mac)
986 #endif
987 return true;
990 bool AlarmDialog::openIncidenceEditorNG(const Akonadi::Item &item)
992 Incidence::Ptr incidence = CalendarSupport::incidence(item);
993 IncidenceEditorNG::IncidenceDialog *dialog =
994 IncidenceEditorNG::IncidenceDialogFactory::create(
995 false, /*doesn't need initial saving*/
996 incidence->type(), 0, this);
997 dialog->load(item);
998 return true;
1001 void AlarmDialog::removeFromConfig(const QList<Akonadi::Item::Id> &ids)
1003 KSharedConfig::Ptr config = KSharedConfig::openConfig();
1004 KConfigGroup genGroup(config, "General");
1006 const int oldNumReminders = genGroup.readEntry("Reminders", 0);
1008 QVector<ConfItem> newReminders;
1009 // Delete everything
1010 for (int i = 1; i <= oldNumReminders; ++i) {
1011 const QString group(QStringLiteral("Incidence-%1").arg(i));
1012 KConfigGroup incGroup(config, group);
1013 const QString uid = incGroup.readEntry("UID");
1014 const QDateTime remindAtDate = incGroup.readEntry("RemindAt", QDateTime());
1015 const QUrl akonadiUrl = incGroup.readEntry("AkonadiUrl");
1016 const Akonadi::Item::Id id = Akonadi::Item::fromUrl(akonadiUrl).id();
1017 if (!ids.contains(id)) {
1018 ConfItem ci;
1019 ci.akonadiUrl = akonadiUrl;
1020 ci.remindAt = remindAtDate;
1021 ci.uid = uid;
1022 newReminders.append(ci);
1024 config->deleteGroup(group);
1027 const int newRemindersCount(newReminders.count());
1028 genGroup.writeEntry("Reminders", newRemindersCount);
1030 //Write everything except those which have an uid we don't want
1031 for (int i = 0; i < newRemindersCount; ++i) {
1032 const QString group(QStringLiteral("Incidence-%1").arg(i + 1));
1033 KConfigGroup incGroup(config, group);
1034 const ConfItem conf = newReminders.at(i);
1035 incGroup.writeEntry("UID", conf.uid);
1036 incGroup.writeEntry("RemindAt", conf.remindAt);
1037 incGroup.writeEntry("AkonadiUrl", conf.akonadiUrl);
1038 incGroup.sync();
1040 genGroup.sync();