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