1 /*
2 This file is part of Kontact.
3
4 SPDX-FileCopyrightText: 2003 Tobias Koenig <tokoe@kde.org>
5 SPDX-FileCopyrightText: 2004, 2009 Allen Winter <winter@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
8 */
9
10 #include "sdsummarywidget.h"
11 #include "korganizer_kontactplugins_specialdates_debug.h"
12 #include <KontactInterface/Core>
13 #include <KontactInterface/Plugin>
14
15 #include <Akonadi/Contact/ContactSearchJob>
16 #include <Akonadi/Contact/ContactViewerDialog>
17 #include <Akonadi/EntityDisplayAttribute>
18 #include <Akonadi/ItemFetchJob>
19 #include <Akonadi/ItemFetchScope>
20 #include <Akonadi/SearchQuery>
21 #include <CalendarSupport/CalendarSingleton>
22 #include <CalendarSupport/Utils>
23
24 #include <KCalendarCore/Calendar>
25
26 #include <KConfig>
27 #include <KConfigGroup>
28 #include <KHolidays/HolidayRegion>
29 #include <KLocalizedString>
30 #include <KUrlLabel>
31 #include <QDesktopServices>
32 #include <QMenu>
33
34 #include <QDate>
35 #include <QEvent>
36 #include <QGridLayout>
37 #include <QLabel>
38 #include <QStyle>
39
40 using namespace KHolidays;
41
42 class BirthdaySearchJob : public Akonadi::ItemSearchJob
43 {
44 Q_OBJECT
45 public:
46 explicit BirthdaySearchJob(QObject *parent, int daysInAdvance);
47 };
48
BirthdaySearchJob(QObject * parent,int daysInAdvance)49 BirthdaySearchJob::BirthdaySearchJob(QObject *parent, int daysInAdvance)
50 : ItemSearchJob(parent)
51 {
52 fetchScope().fetchFullPayload();
53 setMimeTypes({KContacts::Addressee::mimeType()});
54
55 Akonadi::SearchQuery query;
56 query.addTerm(QStringLiteral("birthday"), QDate::currentDate().toJulianDay(), Akonadi::SearchTerm::CondGreaterOrEqual);
57 query.addTerm(QStringLiteral("birthday"), QDate::currentDate().addDays(daysInAdvance).toJulianDay(), Akonadi::SearchTerm::CondLessOrEqual);
58
59 ItemSearchJob::setQuery(query);
60 }
61
62 enum SDIncidenceType { IncidenceTypeContact, IncidenceTypeEvent };
63
64 enum SDCategory { CategoryBirthday, CategoryAnniversary, CategoryHoliday, CategorySeasonal, CategoryOther };
65
66 class SDEntry
67 {
68 public:
69 SDIncidenceType type;
70 SDCategory category;
71 int yearsOld;
72 int daysTo;
73 QDate date;
74 QString summary;
75 QString desc;
76 int span; // #days in the special occasion.
77 KContacts::Addressee addressee;
78 Akonadi::Item item;
79
operator <(const SDEntry & entry) const80 bool operator<(const SDEntry &entry) const
81 {
82 return daysTo < entry.daysTo;
83 }
84 };
85
SDSummaryWidget(KontactInterface::Plugin * plugin,QWidget * parent)86 SDSummaryWidget::SDSummaryWidget(KontactInterface::Plugin *plugin, QWidget *parent)
87 : KontactInterface::Summary(parent)
88 , mPlugin(plugin)
89 {
90 mCalendar = CalendarSupport::calendarSingleton();
91 // Create the Summary Layout
92 auto mainLayout = new QVBoxLayout(this);
93 mainLayout->setSpacing(3);
94 mainLayout->setContentsMargins(3, 3, 3, 3);
95
96 QWidget *header = createHeader(this, QStringLiteral("view-calendar-special-occasion"), i18n("Upcoming Special Dates"));
97 mainLayout->addWidget(header);
98
99 mLayout = new QGridLayout();
100 mainLayout->addItem(mLayout);
101 mLayout->setSpacing(3);
102 mLayout->setRowStretch(6, 1);
103
104 // Default settings
105 mDaysAhead = 7;
106 mShowBirthdaysFromKAB = true;
107 mShowBirthdaysFromCal = true;
108 mShowAnniversariesFromKAB = true;
109 mShowAnniversariesFromCal = true;
110 mShowHolidays = true;
111 mJobRunning = false;
112 mShowSpecialsFromCal = true;
113
114 // Setup the Addressbook
115 connect(mPlugin->core(), &KontactInterface::Core::dayChanged, this, &SDSummaryWidget::updateView);
116
117 connect(mCalendar.data(), &Akonadi::ETMCalendar::calendarChanged, this, &SDSummaryWidget::updateView);
118
119 // Update Configuration
120 configUpdated();
121 }
122
~SDSummaryWidget()123 SDSummaryWidget::~SDSummaryWidget()
124 {
125 delete mHolidays;
126 }
127
configUpdated()128 void SDSummaryWidget::configUpdated()
129 {
130 KConfig config(QStringLiteral("kcmsdsummaryrc"));
131
132 KConfigGroup group = config.group("Days");
133 mDaysAhead = group.readEntry("DaysToShow", 7);
134
135 group = config.group("Show");
136 mShowBirthdaysFromKAB = group.readEntry("BirthdaysFromContacts", true);
137 mShowBirthdaysFromCal = group.readEntry("BirthdaysFromCalendar", true);
138
139 mShowAnniversariesFromKAB = group.readEntry("AnniversariesFromContacts", true);
140 mShowAnniversariesFromCal = group.readEntry("AnniversariesFromCalendar", true);
141
142 mShowHolidays = group.readEntry("HolidaysFromCalendar", true);
143
144 mShowSpecialsFromCal = group.readEntry("SpecialsFromCalendar", true);
145
146 group = config.group("Groupware");
147 mShowMineOnly = group.readEntry("ShowMineOnly", false);
148
149 updateView();
150 }
151
initHolidays()152 bool SDSummaryWidget::initHolidays()
153 {
154 KConfig _hconfig(QStringLiteral("korganizerrc"));
155 KConfigGroup hconfig(&_hconfig, "Time & Date");
156 QString location = hconfig.readEntry("Holidays");
157 if (!location.isEmpty()) {
158 delete mHolidays;
159 mHolidays = new HolidayRegion(location);
160 return true;
161 }
162 return false;
163 }
164
165 // number of days remaining in an Event
span(const KCalendarCore::Event::Ptr & event) const166 int SDSummaryWidget::span(const KCalendarCore::Event::Ptr &event) const
167 {
168 int span = 1;
169 if (event->isMultiDay() && event->allDay()) {
170 QDate d = event->dtStart().date();
171 if (d < QDate::currentDate()) {
172 d = QDate::currentDate();
173 }
174 while (d < event->dtEnd().date()) {
175 span++;
176 d = d.addDays(1);
177 }
178 }
179 return span;
180 }
181
182 // day of a multiday Event
dayof(const KCalendarCore::Event::Ptr & event,const QDate & date) const183 int SDSummaryWidget::dayof(const KCalendarCore::Event::Ptr &event, const QDate &date) const
184 {
185 int dayof = 1;
186 QDate d = event->dtStart().date();
187 if (d < QDate::currentDate()) {
188 d = QDate::currentDate();
189 }
190 while (d < event->dtEnd().date()) {
191 if (d < date) {
192 dayof++;
193 }
194 d = d.addDays(1);
195 }
196 return dayof;
197 }
198
slotBirthdayJobFinished(KJob * job)199 void SDSummaryWidget::slotBirthdayJobFinished(KJob *job)
200 {
201 // ;)
202 auto bJob = qobject_cast<BirthdaySearchJob *>(job);
203 if (bJob) {
204 const auto items = bJob->items();
205 for (const Akonadi::Item &item : items) {
206 if (item.hasPayload<KContacts::Addressee>()) {
207 const auto addressee = item.payload<KContacts::Addressee>();
208 const QDate birthday = addressee.birthday().date();
209 if (birthday.isValid()) {
210 SDEntry entry;
211 entry.type = IncidenceTypeContact;
212 entry.category = CategoryBirthday;
213 dateDiff(birthday, entry.daysTo, entry.yearsOld);
214 if (entry.daysTo < mDaysAhead) {
215 // We need to check the days ahead here because we don't
216 // filter out Contact Birthdays by mDaysAhead in createLabels().
217 entry.date = birthday;
218 entry.addressee = addressee;
219 entry.item = item;
220 entry.span = 1;
221 mDates.append(entry);
222 }
223 }
224 }
225 }
226 // Carry on.
227 createLabels();
228 }
229
230 mJobRunning = false;
231 }
232
createLabels()233 void SDSummaryWidget::createLabels()
234 {
235 // Remove all special date labels from the layout and delete them, as we
236 // will re-create all labels below.
237 setUpdatesEnabled(false);
238 for (QLabel *label : std::as_const(mLabels)) {
239 mLayout->removeWidget(label);
240 delete (label);
241 update();
242 }
243 mLabels.clear();
244
245 QDate dt;
246 for (dt = QDate::currentDate(); dt <= QDate::currentDate().addDays(mDaysAhead - 1); dt = dt.addDays(1)) {
247 const KCalendarCore::Event::List events =
248 mCalendar->events(dt, mCalendar->timeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending);
249 for (const KCalendarCore::Event::Ptr &ev : events) {
250 // Optionally, show only my Events
251 /* if ( mShowMineOnly &&
252 !KCalendarCore::CalHelper::isMyCalendarIncidence( mCalendarAdaptor, ev. ) ) {
253 // FIXME; does isMyCalendarIncidence work !? It's deprecated too.
254 continue;
255 }
256 // TODO: CalHelper is deprecated, remove this?
257 */
258
259 if (ev->customProperty("KABC", "BIRTHDAY") == QLatin1String("YES")) {
260 // Skipping, because these are got by the BirthdaySearchJob
261 // See comments in updateView()
262 continue;
263 }
264
265 if (!ev->categoriesStr().isEmpty()) {
266 QStringList::ConstIterator it2;
267 const QStringList c = ev->categories();
268 QStringList::ConstIterator end(c.constEnd());
269 for (it2 = c.constBegin(); it2 != end; ++it2) {
270 const QString itUpper((*it2).toUpper());
271 // Append Birthday Event?
272 if (mShowBirthdaysFromCal && (itUpper == QLatin1String("BIRTHDAY"))) {
273 SDEntry entry;
274 entry.type = IncidenceTypeEvent;
275 entry.category = CategoryBirthday;
276 entry.date = dt;
277 entry.summary = ev->summary();
278 entry.desc = ev->description();
279 dateDiff(ev->dtStart().date(), entry.daysTo, entry.yearsOld);
280 entry.span = 1;
281
282 /* The following check is to prevent duplicate entries,
283 * so in case of having a KCal incidence with category birthday
284 * with summary and date equal to some KABC Attendee we don't show it
285 * FIXME: port to akonadi, it's kresource based
286 * */
287 if (/*!check( bdayRes, dt, ev->summary() )*/ true) {
288 mDates.append(entry);
289 }
290 break;
291 }
292
293 // Append Anniversary Event?
294 if (mShowAnniversariesFromCal && (itUpper == QLatin1String("ANNIVERSARY"))) {
295 SDEntry entry;
296 entry.type = IncidenceTypeEvent;
297 entry.category = CategoryAnniversary;
298 entry.date = dt;
299 entry.summary = ev->summary();
300 entry.desc = ev->description();
301 dateDiff(ev->dtStart().date(), entry.daysTo, entry.yearsOld);
302 entry.span = 1;
303 if (/*!check( annvRes, dt, ev->summary() )*/ true) {
304 mDates.append(entry);
305 }
306 break;
307 }
308
309 // Append Holiday Event?
310 if (mShowHolidays && (itUpper == QLatin1String("HOLIDAY"))) {
311 SDEntry entry;
312 entry.type = IncidenceTypeEvent;
313 entry.category = CategoryHoliday;
314 entry.date = dt;
315 entry.summary = ev->summary();
316 entry.desc = ev->description();
317 dateDiff(dt, entry.daysTo, entry.yearsOld);
318 entry.yearsOld = -1; // ignore age of holidays
319 entry.span = span(ev);
320 if (entry.span > 1 && dayof(ev, dt) > 1) { // skip days 2,3,...
321 break;
322 }
323 mDates.append(entry);
324 break;
325 }
326
327 // Append Special Occasion Event?
328 if (mShowSpecialsFromCal && (itUpper == QLatin1String("SPECIAL OCCASION"))) {
329 SDEntry entry;
330 entry.type = IncidenceTypeEvent;
331 entry.category = CategoryOther;
332 entry.date = dt;
333 entry.summary = ev->summary();
334 entry.desc = ev->description();
335 dateDiff(dt, entry.daysTo, entry.yearsOld);
336 entry.yearsOld = -1; // ignore age of special occasions
337 entry.span = span(ev);
338 if (entry.span > 1 && dayof(ev, dt) > 1) { // skip days 2,3,...
339 break;
340 }
341 mDates.append(entry);
342 break;
343 }
344 }
345 }
346 }
347 }
348
349 // Search for Holidays
350 if (mShowHolidays) {
351 if (initHolidays()) {
352 for (dt = QDate::currentDate(); dt <= QDate::currentDate().addDays(mDaysAhead - 1); dt = dt.addDays(1)) {
353 QList<Holiday> holidays = mHolidays->holidays(dt);
354 QList<Holiday>::ConstIterator it = holidays.constBegin();
355 for (; it != holidays.constEnd(); ++it) {
356 SDEntry entry;
357 entry.type = IncidenceTypeEvent;
358 if ((*it).categoryList().contains(QLatin1String("seasonal"))) {
359 entry.category = CategorySeasonal;
360 } else if ((*it).categoryList().contains(QLatin1String("public"))) {
361 entry.category = CategoryHoliday;
362 } else {
363 entry.category = CategoryOther;
364 }
365 entry.date = dt;
366 entry.summary = (*it).name();
367 dateDiff(dt, entry.daysTo, entry.yearsOld);
368 entry.yearsOld = -1; // ignore age of holidays
369 entry.span = 1;
370
371 mDates.append(entry);
372 }
373 }
374 }
375 }
376
377 // Sort, then Print the Special Dates
378 std::sort(mDates.begin(), mDates.end());
379
380 if (!mDates.isEmpty()) {
381 int counter = 0;
382 QList<SDEntry>::Iterator addrIt;
383 QList<SDEntry>::Iterator addrEnd(mDates.end());
384 for (addrIt = mDates.begin(); addrIt != addrEnd; ++addrIt) {
385 const bool makeBold = (*addrIt).daysTo == 0; // i.e., today
386
387 // Pixmap
388 QImage icon_img;
389 QString icon_name;
390 KContacts::Picture pic;
391 switch ((*addrIt).category) {
392 case CategoryBirthday:
393 icon_name = QStringLiteral("view-calendar-birthday");
394 pic = (*addrIt).addressee.photo();
395 if (pic.isIntern() && !pic.data().isNull()) {
396 QImage img = pic.data();
397 if (img.width() > img.height()) {
398 icon_img = img.scaledToWidth(32);
399 } else {
400 icon_img = img.scaledToHeight(32);
401 }
402 }
403 break;
404 case CategoryAnniversary:
405 icon_name = QStringLiteral("view-calendar-wedding-anniversary");
406 pic = (*addrIt).addressee.photo();
407 if (pic.isIntern() && !pic.data().isNull()) {
408 QImage img = pic.data();
409 if (img.width() > img.height()) {
410 icon_img = img.scaledToWidth(32);
411 } else {
412 icon_img = img.scaledToHeight(32);
413 }
414 }
415 break;
416 case CategoryHoliday:
417 icon_name = QStringLiteral("view-calendar-holiday");
418 break;
419 case CategorySeasonal:
420 case CategoryOther:
421 icon_name = QStringLiteral("view-calendar-special-occasion");
422 break;
423 }
424 auto label = new QLabel(this);
425 if (icon_img.isNull()) {
426 label->setPixmap(QIcon::fromTheme(icon_name).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)));
427 } else {
428 label->setPixmap(QPixmap::fromImage(icon_img));
429 }
430 label->setMaximumWidth(label->minimumSizeHint().width());
431 label->setAlignment(Qt::AlignVCenter);
432 mLayout->addWidget(label, counter, 0);
433 mLabels.append(label);
434
435 // Event date
436 QString datestr;
437
438 // Muck with the year -- change to the year 'daysTo' days away
439 int year = QDate::currentDate().addDays((*addrIt).daysTo).year();
440 QDate sD = QDate(year, (*addrIt).date.month(), (*addrIt).date.day());
441
442 if ((*addrIt).daysTo == 0) {
443 datestr = i18nc("the special day is today", "Today");
444 } else if ((*addrIt).daysTo == 1) {
445 datestr = i18nc("the special day is tomorrow", "Tomorrow");
446 } else {
447 const auto locale = QLocale::system();
448 for (int i = 3; i < 8; ++i) {
449 if ((*addrIt).daysTo < i) {
450 datestr = locale.dayName(sD.dayOfWeek(), QLocale::LongFormat);
451 break;
452 }
453 }
454 if (datestr.isEmpty()) {
455 datestr = locale.toString(sD, QLocale::ShortFormat);
456 }
457 }
458 // Print the date span for multiday, floating events, for the
459 // first day of the event only.
460 if ((*addrIt).span > 1) {
461 QString endstr = QLocale::system().toString(sD.addDays((*addrIt).span - 1), QLocale::LongFormat);
462 datestr += QLatin1String(" -\n ") + endstr;
463 }
464
465 label = new QLabel(datestr, this);
466 label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
467 mLayout->addWidget(label, counter, 1);
468 mLabels.append(label);
469 if (makeBold) {
470 QFont font = label->font();
471 font.setBold(true);
472 label->setFont(font);
473 }
474
475 // Countdown
476 label = new QLabel(this);
477 if ((*addrIt).daysTo == 0) {
478 label->setText(i18n("now"));
479 } else {
480 label->setText(i18np("in 1 day", "in %1 days", (*addrIt).daysTo));
481 }
482
483 label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
484 mLayout->addWidget(label, counter, 2);
485 mLabels.append(label);
486
487 // What
488 QString what;
489 switch ((*addrIt).category) {
490 case CategoryBirthday:
491 what = i18n("Birthday");
492 break;
493 case CategoryAnniversary:
494 what = i18n("Anniversary");
495 break;
496 case CategoryHoliday:
497 what = i18n("Holiday");
498 break;
499 case CategorySeasonal:
500 what = i18n("Change of Seasons");
501 break;
502 case CategoryOther:
503 what = i18n("Special Occasion");
504 break;
505 }
506 label = new QLabel(this);
507 label->setText(what);
508 label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
509 mLayout->addWidget(label, counter, 3);
510 mLabels.append(label);
511
512 // Description
513 if ((*addrIt).type == IncidenceTypeContact) {
514 auto urlLabel = new KUrlLabel(this);
515 urlLabel->installEventFilter(this);
516 urlLabel->setUrl((*addrIt).item.url(Akonadi::Item::UrlWithMimeType).url());
517 urlLabel->setText((*addrIt).addressee.realName());
518 urlLabel->setTextFormat(Qt::RichText);
519 urlLabel->setWordWrap(true);
520 mLayout->addWidget(urlLabel, counter, 4);
521 mLabels.append(urlLabel);
522 connect(urlLabel, &KUrlLabel::leftClickedUrl, this, [this, urlLabel] {
523 mailContact(urlLabel->url());
524 });
525 connect(urlLabel, &KUrlLabel::rightClickedUrl, this, [this, urlLabel] {
526 popupMenu(urlLabel->url());
527 });
528 } else {
529 label = new QLabel(this);
530 label->setText((*addrIt).summary);
531 label->setTextFormat(Qt::RichText);
532 mLayout->addWidget(label, counter, 4);
533 mLabels.append(label);
534 if (!(*addrIt).desc.isEmpty()) {
535 label->setToolTip((*addrIt).desc);
536 }
537 }
538
539 // Age
540 if ((*addrIt).category == CategoryBirthday || (*addrIt).category == CategoryAnniversary) {
541 label = new QLabel(this);
542 if ((*addrIt).yearsOld <= 0) {
543 label->setText(QString());
544 } else {
545 label->setText(i18np("one year", "%1 years", (*addrIt).yearsOld));
546 }
547 label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
548 mLayout->addWidget(label, counter, 5);
549 mLabels.append(label);
550 }
551
552 counter++;
553 }
554 } else {
555 auto label = new QLabel(i18np("No special dates within the next 1 day", "No special dates pending within the next %1 days", mDaysAhead), this);
556 label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
557 mLayout->addWidget(label, 0, 0);
558 mLabels.append(label);
559 }
560
561 QList<QLabel *>::ConstIterator lit;
562 QList<QLabel *>::ConstIterator endLit(mLabels.constEnd());
563 for (lit = mLabels.constBegin(); lit != endLit; ++lit) {
564 (*lit)->show();
565 }
566 setUpdatesEnabled(true);
567 }
568
updateView()569 void SDSummaryWidget::updateView()
570 {
571 mDates.clear();
572
573 /* KABC Birthdays are got through a ItemSearchJob/SPARQL Query
574 * I then added an ETM/CalendarModel because we need to search
575 * for calendar entries that have birthday/anniversary categories too.
576 *
577 * Also, we can't get KABC Anniversaries through nepomuk because the
578 * current S.D.O doesn't support it, so i also them through the ETM.
579 *
580 * So basically we have:
581 * Calendar anniversaries - ETM
582 * Calendar birthdays - ETM
583 * KABC birthdays - BirthdaySearchJob
584 * KABC anniversaries - ETM ( needs Birthday Agent running )
585 *
586 * We could remove thomas' BirthdaySearchJob and use the ETM for that
587 * but it has the advantage that we don't need a Birthday agent running.
588 *
589 **/
590
591 // Search for Birthdays
592 if (mShowBirthdaysFromKAB && !mJobRunning) {
593 auto job = new BirthdaySearchJob(this, mDaysAhead);
594
595 connect(job, &BirthdaySearchJob::result, this, &SDSummaryWidget::slotBirthdayJobFinished);
596 job->start();
597 mJobRunning = true;
598
599 // The result slot will trigger the rest of the update
600 }
601 }
602
mailContact(const QString & url)603 void SDSummaryWidget::mailContact(const QString &url)
604 {
605 const Akonadi::Item item = Akonadi::Item::fromUrl(QUrl(url));
606 if (!item.isValid()) {
607 qCDebug(KORGANIZER_KONTACTPLUGINS_SPECIALDATES_LOG) << QStringLiteral("Invalid item found");
608 return;
609 }
610
611 auto job = new Akonadi::ItemFetchJob(item, this);
612 job->fetchScope().fetchFullPayload();
613 connect(job, &Akonadi::ItemFetchJob::result, this, &SDSummaryWidget::slotItemFetchJobDone);
614 }
615
slotItemFetchJobDone(KJob * job)616 void SDSummaryWidget::slotItemFetchJobDone(KJob *job)
617 {
618 if (job->error()) {
619 qCWarning(KORGANIZER_KONTACTPLUGINS_SPECIALDATES_LOG) << job->errorString();
620 return;
621 }
622 const Akonadi::Item::List lst = qobject_cast<Akonadi::ItemFetchJob *>(job)->items();
623 if (lst.isEmpty()) {
624 return;
625 }
626 const auto contact = lst.first().payload<KContacts::Addressee>();
627
628 QDesktopServices::openUrl(QUrl(contact.fullEmail()));
629 }
630
viewContact(const QString & url)631 void SDSummaryWidget::viewContact(const QString &url)
632 {
633 const Akonadi::Item item = Akonadi::Item::fromUrl(QUrl(url));
634 if (!item.isValid()) {
635 qCDebug(KORGANIZER_KONTACTPLUGINS_SPECIALDATES_LOG) << "Invalid item found";
636 return;
637 }
638
639 QPointer<Akonadi::ContactViewerDialog> dlg = new Akonadi::ContactViewerDialog(this);
640 dlg->setContact(item);
641 dlg->exec();
642 delete dlg;
643 }
644
popupMenu(const QString & url)645 void SDSummaryWidget::popupMenu(const QString &url)
646 {
647 QMenu popup(this);
648 const QAction *sendMailAction = popup.addAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("Send &Mail"));
649 const QAction *viewContactAction = popup.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("View &Contact"));
650
651 const QAction *ret = popup.exec(QCursor::pos());
652 if (ret == sendMailAction) {
653 mailContact(url);
654 } else if (ret == viewContactAction) {
655 viewContact(url);
656 }
657 }
658
eventFilter(QObject * obj,QEvent * e)659 bool SDSummaryWidget::eventFilter(QObject *obj, QEvent *e)
660 {
661 if (obj->inherits("KUrlLabel")) {
662 auto label = static_cast<KUrlLabel *>(obj);
663 if (e->type() == QEvent::Enter) {
664 Q_EMIT message(i18n("Mail to:\"%1\"", label->text()));
665 }
666 if (e->type() == QEvent::Leave) {
667 Q_EMIT message(QString());
668 }
669 }
670
671 return KontactInterface::Summary::eventFilter(obj, e);
672 }
673
dateDiff(const QDate & date,int & days,int & years) const674 void SDSummaryWidget::dateDiff(const QDate &date, int &days, int &years) const
675 {
676 QDate currentDate;
677 QDate eventDate;
678
679 if (QDate::isLeapYear(date.year()) && date.month() == 2 && date.day() == 29) {
680 currentDate = QDate(date.year(), QDate::currentDate().month(), QDate::currentDate().day());
681 if (!QDate::isLeapYear(QDate::currentDate().year())) {
682 eventDate = QDate(date.year(), date.month(), 28); // celebrate one day earlier ;)
683 } else {
684 eventDate = QDate(date.year(), date.month(), date.day());
685 }
686 } else {
687 currentDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().day());
688 eventDate = QDate(QDate::currentDate().year(), date.month(), date.day());
689 }
690
691 int offset = currentDate.daysTo(eventDate);
692 if (offset < 0) {
693 days = 365 + offset;
694 years = QDate::currentDate().year() + 1 - date.year();
695 } else {
696 days = offset;
697 years = QDate::currentDate().year() - date.year();
698 }
699 }
700
701 #include "sdsummarywidget.moc"
702