File indexing completed on 2025-01-19 04:51:28

0001 /*
0002   This file is part of Kontact.
0003 
0004   SPDX-FileCopyrightText: 2003 Tobias Koenig <tokoe@kde.org>
0005   SPDX-FileCopyrightText: 2004, 2009 Allen Winter <winter@kde.org>
0006 
0007   SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0
0008 */
0009 
0010 #include "sdsummarywidget.h"
0011 #include "korganizer_kontactplugins_specialdates_debug.h"
0012 #include <KontactInterface/Core>
0013 #include <KontactInterface/Plugin>
0014 
0015 #include <Akonadi/ContactSearchJob>
0016 #include <Akonadi/ContactViewerDialog>
0017 #include <Akonadi/EntityDisplayAttribute>
0018 #include <Akonadi/ItemFetchJob>
0019 #include <Akonadi/ItemFetchScope>
0020 #include <Akonadi/SearchQuery>
0021 #include <CalendarSupport/CalendarSingleton>
0022 #include <CalendarSupport/Utils>
0023 
0024 #include <KCalendarCore/Calendar>
0025 
0026 #include <KConfig>
0027 #include <KConfigGroup>
0028 #include <KHolidays/HolidayRegion>
0029 #include <KLocalizedString>
0030 #include <KUrlLabel>
0031 #include <QDesktopServices>
0032 #include <QMenu>
0033 #include <QPointer>
0034 
0035 #include <QDate>
0036 #include <QEvent>
0037 #include <QGridLayout>
0038 #include <QLabel>
0039 #include <QStyle>
0040 
0041 using namespace KHolidays;
0042 
0043 class BirthdaySearchJob : public Akonadi::ItemSearchJob
0044 {
0045     Q_OBJECT
0046 public:
0047     explicit BirthdaySearchJob(QObject *parent, int daysInAdvance);
0048 };
0049 
0050 BirthdaySearchJob::BirthdaySearchJob(QObject *parent, int daysInAdvance)
0051     : ItemSearchJob(parent)
0052 {
0053     fetchScope().fetchFullPayload();
0054     setMimeTypes({KContacts::Addressee::mimeType()});
0055 
0056     Akonadi::SearchQuery query;
0057     query.addTerm(QStringLiteral("birthday"), QDate::currentDate().toJulianDay(), Akonadi::SearchTerm::CondGreaterOrEqual);
0058     query.addTerm(QStringLiteral("birthday"), QDate::currentDate().addDays(daysInAdvance).toJulianDay(), Akonadi::SearchTerm::CondLessOrEqual);
0059 
0060     ItemSearchJob::setQuery(query);
0061 }
0062 
0063 enum SDIncidenceType { IncidenceTypeContact, IncidenceTypeEvent };
0064 
0065 enum SDCategory { CategoryBirthday, CategoryAnniversary, CategoryHoliday, CategorySeasonal, CategoryOther };
0066 
0067 class SDEntry
0068 {
0069 public:
0070     SDIncidenceType type;
0071     SDCategory category;
0072     int yearsOld;
0073     int daysTo;
0074     QDate date;
0075     QString summary;
0076     QString desc;
0077     int span; // #days in the special occasion.
0078     KContacts::Addressee addressee;
0079     Akonadi::Item item;
0080 
0081     bool operator<(const SDEntry &entry) const
0082     {
0083         return daysTo < entry.daysTo;
0084     }
0085 };
0086 
0087 SDSummaryWidget::SDSummaryWidget(KontactInterface::Plugin *plugin, QWidget *parent)
0088     : KontactInterface::Summary(parent)
0089     , mPlugin(plugin)
0090 {
0091     mCalendar = CalendarSupport::calendarSingleton();
0092     // Create the Summary Layout
0093     auto mainLayout = new QVBoxLayout(this);
0094     mainLayout->setSpacing(3);
0095     mainLayout->setContentsMargins(3, 3, 3, 3);
0096 
0097     QWidget *header = createHeader(this, QStringLiteral("view-calendar-special-occasion"), i18n("Upcoming Special Dates"));
0098     mainLayout->addWidget(header);
0099 
0100     mLayout = new QGridLayout();
0101     mainLayout->addItem(mLayout);
0102     mLayout->setSpacing(3);
0103     mLayout->setRowStretch(6, 1);
0104 
0105     // Default settings
0106     mDaysAhead = 7;
0107     mShowBirthdaysFromKAB = true;
0108     mShowBirthdaysFromCal = true;
0109     mShowAnniversariesFromKAB = true;
0110     mShowAnniversariesFromCal = true;
0111     mShowHolidays = true;
0112     mJobRunning = false;
0113     mShowSpecialsFromCal = true;
0114 
0115     // Setup the Addressbook
0116     connect(mPlugin->core(), &KontactInterface::Core::dayChanged, this, &SDSummaryWidget::updateView);
0117 
0118     connect(mCalendar.data(), &Akonadi::ETMCalendar::calendarChanged, this, &SDSummaryWidget::updateView);
0119 
0120     // Update Configuration
0121     configUpdated();
0122 }
0123 
0124 SDSummaryWidget::~SDSummaryWidget()
0125 {
0126     delete mHolidays;
0127 }
0128 
0129 void SDSummaryWidget::configUpdated()
0130 {
0131     KConfig config(QStringLiteral("kcmsdsummaryrc"));
0132 
0133     KConfigGroup group = config.group(QStringLiteral("Days"));
0134     mDaysAhead = group.readEntry("DaysToShow", 7);
0135 
0136     group = config.group(QStringLiteral("Show"));
0137     mShowBirthdaysFromKAB = group.readEntry("BirthdaysFromContacts", true);
0138     mShowBirthdaysFromCal = group.readEntry("BirthdaysFromCalendar", true);
0139 
0140     mShowAnniversariesFromKAB = group.readEntry("AnniversariesFromContacts", true);
0141     mShowAnniversariesFromCal = group.readEntry("AnniversariesFromCalendar", true);
0142 
0143     mShowHolidays = group.readEntry("HolidaysFromCalendar", true);
0144 
0145     mShowSpecialsFromCal = group.readEntry("SpecialsFromCalendar", true);
0146 
0147     group = config.group(QStringLiteral("Groupware"));
0148     mShowMineOnly = group.readEntry("ShowMineOnly", false);
0149 
0150     updateView();
0151 }
0152 
0153 bool SDSummaryWidget::initHolidays()
0154 {
0155     KConfig _hconfig(QStringLiteral("korganizerrc"));
0156     KConfigGroup hconfig(&_hconfig, QStringLiteral("Time & Date"));
0157     QString location = hconfig.readEntry("Holidays");
0158     if (!location.isEmpty()) {
0159         delete mHolidays;
0160         mHolidays = new HolidayRegion(location);
0161         return true;
0162     }
0163     return false;
0164 }
0165 
0166 // number of days remaining in an Event
0167 int SDSummaryWidget::span(const KCalendarCore::Event::Ptr &event) const
0168 {
0169     int span = 1;
0170     if (event->isMultiDay() && event->allDay()) {
0171         QDate d = event->dtStart().date();
0172         if (d < QDate::currentDate()) {
0173             d = QDate::currentDate();
0174         }
0175         while (d < event->dtEnd().date()) {
0176             span++;
0177             d = d.addDays(1);
0178         }
0179     }
0180     return span;
0181 }
0182 
0183 // day of a multiday Event
0184 int SDSummaryWidget::dayof(const KCalendarCore::Event::Ptr &event, const QDate &date) const
0185 {
0186     int dayof = 1;
0187     QDate d = event->dtStart().date();
0188     if (d < QDate::currentDate()) {
0189         d = QDate::currentDate();
0190     }
0191     while (d < event->dtEnd().date()) {
0192         if (d < date) {
0193             dayof++;
0194         }
0195         d = d.addDays(1);
0196     }
0197     return dayof;
0198 }
0199 
0200 void SDSummaryWidget::slotBirthdayJobFinished(KJob *job)
0201 {
0202     // ;)
0203     auto bJob = qobject_cast<BirthdaySearchJob *>(job);
0204     if (bJob) {
0205         const auto items = bJob->items();
0206         for (const Akonadi::Item &item : items) {
0207             if (item.hasPayload<KContacts::Addressee>()) {
0208                 const auto addressee = item.payload<KContacts::Addressee>();
0209                 const QDate birthday = addressee.birthday().date();
0210                 if (birthday.isValid()) {
0211                     SDEntry entry;
0212                     entry.type = IncidenceTypeContact;
0213                     entry.category = CategoryBirthday;
0214                     dateDiff(birthday, entry.daysTo, entry.yearsOld);
0215                     if (entry.daysTo < mDaysAhead) {
0216                         // We need to check the days ahead here because we don't
0217                         // filter out Contact Birthdays by mDaysAhead in createLabels().
0218                         entry.date = birthday;
0219                         entry.addressee = addressee;
0220                         entry.item = item;
0221                         entry.span = 1;
0222                         mDates.append(entry);
0223                     }
0224                 }
0225             }
0226         }
0227         // Carry on.
0228         createLabels();
0229     }
0230 
0231     mJobRunning = false;
0232 }
0233 
0234 void SDSummaryWidget::createLabels()
0235 {
0236     // Remove all special date labels from the layout and delete them, as we
0237     // will re-create all labels below.
0238     setUpdatesEnabled(false);
0239     for (QLabel *label : std::as_const(mLabels)) {
0240         mLayout->removeWidget(label);
0241         delete (label);
0242         update();
0243     }
0244     mLabels.clear();
0245 
0246     QDate dt;
0247     for (dt = QDate::currentDate(); dt <= QDate::currentDate().addDays(mDaysAhead - 1); dt = dt.addDays(1)) {
0248         const KCalendarCore::Event::List events =
0249             mCalendar->events(dt, mCalendar->timeZone(), KCalendarCore::EventSortStartDate, KCalendarCore::SortDirectionAscending);
0250         for (const KCalendarCore::Event::Ptr &ev : events) {
0251             // Optionally, show only my Events
0252             /* if ( mShowMineOnly &&
0253                     !KCalendarCore::CalHelper::isMyCalendarIncidence( mCalendarAdaptor, ev. ) ) {
0254               // FIXME; does isMyCalendarIncidence work !? It's deprecated too.
0255               continue;
0256               }
0257               // TODO: CalHelper is deprecated, remove this?
0258               */
0259 
0260             if (ev->customProperty("KABC", "BIRTHDAY") == QLatin1StringView("YES")) {
0261                 // Skipping, because these are got by the BirthdaySearchJob
0262                 // See comments in updateView()
0263                 continue;
0264             }
0265 
0266             if (!mShowAnniversariesFromKAB && ev->customProperty("KABC", "ANNIVERSARY") == QLatin1StringView("YES")) {
0267                 continue;
0268             }
0269 
0270             if (!ev->categoriesStr().isEmpty()) {
0271                 QStringList::ConstIterator it2;
0272                 const QStringList c = ev->categories();
0273                 QStringList::ConstIterator end(c.constEnd());
0274                 for (it2 = c.constBegin(); it2 != end; ++it2) {
0275                     const QString itUpper((*it2).toUpper());
0276                     // Append Birthday Event?
0277                     if (mShowBirthdaysFromCal && (itUpper == QLatin1StringView("BIRTHDAY"))) {
0278                         SDEntry entry;
0279                         entry.type = IncidenceTypeEvent;
0280                         entry.category = CategoryBirthday;
0281                         entry.date = dt;
0282                         entry.summary = ev->summary();
0283                         entry.desc = ev->description();
0284                         dateDiff(ev->dtStart().date(), entry.daysTo, entry.yearsOld);
0285                         entry.span = 1;
0286 
0287                         /* The following check is to prevent duplicate entries,
0288                          * so in case of having a KCal incidence with category birthday
0289                          * with summary and date equal to some KABC Attendee we don't show it
0290                          * FIXME: port to akonadi, it's kresource based
0291                          * */
0292                         if (/*!check( bdayRes, dt, ev->summary() )*/ true) {
0293                             mDates.append(entry);
0294                         }
0295                         break;
0296                     }
0297 
0298                     // Append Anniversary Event?
0299                     if (itUpper == QLatin1StringView("ANNIVERSARY")) {
0300                         // !mShowAnniversariesFromKAB was handled above.
0301                         const bool isKAB = ev->customProperty("KABC", "ANNIVERSARY") == QLatin1StringView("YES");
0302                         if (isKAB || mShowAnniversariesFromCal) {
0303                             SDEntry entry;
0304                             entry.type = IncidenceTypeEvent;
0305                             entry.category = CategoryAnniversary;
0306                             entry.date = dt;
0307                             entry.summary = ev->summary();
0308                             entry.desc = ev->description();
0309                             dateDiff(ev->dtStart().date(), entry.daysTo, entry.yearsOld);
0310                             entry.span = 1;
0311                             mDates.append(entry);
0312                         }
0313                         break;
0314                     }
0315 
0316                     // Append Holiday Event?
0317                     if (mShowHolidays && (itUpper == QLatin1StringView("HOLIDAY"))) {
0318                         SDEntry entry;
0319                         entry.type = IncidenceTypeEvent;
0320                         entry.category = CategoryHoliday;
0321                         entry.date = dt;
0322                         entry.summary = ev->summary();
0323                         entry.desc = ev->description();
0324                         dateDiff(dt, entry.daysTo, entry.yearsOld);
0325                         entry.yearsOld = -1; // ignore age of holidays
0326                         entry.span = span(ev);
0327                         if (entry.span > 1 && dayof(ev, dt) > 1) { // skip days 2,3,...
0328                             break;
0329                         }
0330                         mDates.append(entry);
0331                         break;
0332                     }
0333 
0334                     // Append Special Occasion Event?
0335                     if (mShowSpecialsFromCal && (itUpper == QLatin1StringView("SPECIAL OCCASION"))) {
0336                         SDEntry entry;
0337                         entry.type = IncidenceTypeEvent;
0338                         entry.category = CategoryOther;
0339                         entry.date = dt;
0340                         entry.summary = ev->summary();
0341                         entry.desc = ev->description();
0342                         dateDiff(dt, entry.daysTo, entry.yearsOld);
0343                         entry.yearsOld = -1; // ignore age of special occasions
0344                         entry.span = span(ev);
0345                         if (entry.span > 1 && dayof(ev, dt) > 1) { // skip days 2,3,...
0346                             break;
0347                         }
0348                         mDates.append(entry);
0349                         break;
0350                     }
0351                 }
0352             }
0353         }
0354     }
0355 
0356     // Search for Holidays
0357     if (mShowHolidays) {
0358         if (initHolidays()) {
0359             for (dt = QDate::currentDate(); dt <= QDate::currentDate().addDays(mDaysAhead - 1); dt = dt.addDays(1)) {
0360                 QList<Holiday> holidays = mHolidays->rawHolidaysWithAstroSeasons(dt);
0361                 QList<Holiday>::ConstIterator it = holidays.constBegin();
0362                 for (; it != holidays.constEnd(); ++it) {
0363                     SDEntry entry;
0364                     entry.type = IncidenceTypeEvent;
0365                     if ((*it).categoryList().contains(QLatin1StringView("seasonal"))) {
0366                         entry.category = CategorySeasonal;
0367                     } else if ((*it).categoryList().contains(QLatin1StringView("public"))) {
0368                         entry.category = CategoryHoliday;
0369                     } else {
0370                         entry.category = CategoryOther;
0371                     }
0372                     entry.date = dt;
0373                     entry.summary = (*it).name();
0374                     dateDiff(dt, entry.daysTo, entry.yearsOld);
0375                     entry.yearsOld = -1; // ignore age of holidays
0376                     entry.span = 1;
0377 
0378                     mDates.append(entry);
0379                 }
0380             }
0381         }
0382     }
0383 
0384     // Sort, then Print the Special Dates
0385     std::sort(mDates.begin(), mDates.end());
0386 
0387     if (!mDates.isEmpty()) {
0388         int counter = 0;
0389         QList<SDEntry>::Iterator addrIt;
0390         QList<SDEntry>::Iterator addrEnd(mDates.end());
0391         for (addrIt = mDates.begin(); addrIt != addrEnd; ++addrIt) {
0392             const bool makeBold = (*addrIt).daysTo == 0; // i.e., today
0393 
0394             // Pixmap
0395             QImage icon_img;
0396             QString icon_name;
0397             KContacts::Picture pic;
0398             switch ((*addrIt).category) {
0399             case CategoryBirthday:
0400                 icon_name = QStringLiteral("view-calendar-birthday");
0401                 pic = (*addrIt).addressee.photo();
0402                 if (pic.isIntern() && !pic.data().isNull()) {
0403                     QImage img = pic.data();
0404                     if (img.width() > img.height()) {
0405                         icon_img = img.scaledToWidth(32);
0406                     } else {
0407                         icon_img = img.scaledToHeight(32);
0408                     }
0409                 }
0410                 break;
0411             case CategoryAnniversary:
0412                 icon_name = QStringLiteral("view-calendar-wedding-anniversary");
0413                 pic = (*addrIt).addressee.photo();
0414                 if (pic.isIntern() && !pic.data().isNull()) {
0415                     QImage img = pic.data();
0416                     if (img.width() > img.height()) {
0417                         icon_img = img.scaledToWidth(32);
0418                     } else {
0419                         icon_img = img.scaledToHeight(32);
0420                     }
0421                 }
0422                 break;
0423             case CategoryHoliday:
0424                 icon_name = QStringLiteral("view-calendar-holiday");
0425                 break;
0426             case CategorySeasonal:
0427             case CategoryOther:
0428                 icon_name = QStringLiteral("view-calendar-special-occasion");
0429                 break;
0430             }
0431             auto label = new QLabel(this);
0432             if (icon_img.isNull()) {
0433                 label->setPixmap(QIcon::fromTheme(icon_name).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize)));
0434             } else {
0435                 label->setPixmap(QPixmap::fromImage(icon_img));
0436             }
0437             label->setMaximumWidth(label->minimumSizeHint().width());
0438             label->setAlignment(Qt::AlignVCenter);
0439             mLayout->addWidget(label, counter, 0);
0440             mLabels.append(label);
0441 
0442             // Event date
0443             QString datestr;
0444 
0445             // Muck with the year -- change to the year 'daysTo' days away
0446             int year = QDate::currentDate().addDays((*addrIt).daysTo).year();
0447             QDate sD = QDate(year, (*addrIt).date.month(), (*addrIt).date.day());
0448 
0449             if ((*addrIt).daysTo == 0) {
0450                 datestr = i18nc("the special day is today", "Today");
0451             } else if ((*addrIt).daysTo == 1) {
0452                 datestr = i18nc("the special day is tomorrow", "Tomorrow");
0453             } else {
0454                 const auto locale = QLocale::system();
0455                 for (int i = 3; i < 8; ++i) {
0456                     if ((*addrIt).daysTo < i) {
0457                         datestr = locale.dayName(sD.dayOfWeek(), QLocale::LongFormat);
0458                         break;
0459                     }
0460                 }
0461                 if (datestr.isEmpty()) {
0462                     datestr = locale.toString(sD, QLocale::ShortFormat);
0463                 }
0464             }
0465             // Print the date span for multiday, floating events, for the
0466             // first day of the event only.
0467             if ((*addrIt).span > 1) {
0468                 QString endstr = QLocale::system().toString(sD.addDays((*addrIt).span - 1), QLocale::LongFormat);
0469                 datestr += QLatin1StringView(" -\n ") + endstr;
0470             }
0471 
0472             label = new QLabel(datestr, this);
0473             label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0474             mLayout->addWidget(label, counter, 1);
0475             mLabels.append(label);
0476             if (makeBold) {
0477                 QFont font = label->font();
0478                 font.setBold(true);
0479                 label->setFont(font);
0480             }
0481 
0482             // Countdown
0483             label = new QLabel(this);
0484             if ((*addrIt).daysTo == 0) {
0485                 label->setText(i18n("now"));
0486             } else {
0487                 label->setText(i18np("in 1 day", "in %1 days", (*addrIt).daysTo));
0488             }
0489 
0490             label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0491             mLayout->addWidget(label, counter, 2);
0492             mLabels.append(label);
0493 
0494             // What
0495             QString what;
0496             switch ((*addrIt).category) {
0497             case CategoryBirthday:
0498                 what = i18n("Birthday");
0499                 break;
0500             case CategoryAnniversary:
0501                 what = i18n("Anniversary");
0502                 break;
0503             case CategoryHoliday:
0504                 what = i18n("Holiday");
0505                 break;
0506             case CategorySeasonal:
0507                 what = i18n("Change of Seasons");
0508                 break;
0509             case CategoryOther:
0510                 what = i18n("Special Occasion");
0511                 break;
0512             }
0513             label = new QLabel(this);
0514             label->setText(what);
0515             label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0516             mLayout->addWidget(label, counter, 3);
0517             mLabels.append(label);
0518 
0519             // Description
0520             if ((*addrIt).type == IncidenceTypeContact) {
0521                 auto urlLabel = new KUrlLabel(this);
0522                 urlLabel->installEventFilter(this);
0523                 urlLabel->setUrl((*addrIt).item.url(Akonadi::Item::UrlWithMimeType).url());
0524                 urlLabel->setText((*addrIt).addressee.realName());
0525                 urlLabel->setTextFormat(Qt::RichText);
0526                 urlLabel->setWordWrap(true);
0527                 mLayout->addWidget(urlLabel, counter, 4);
0528                 mLabels.append(urlLabel);
0529                 connect(urlLabel, &KUrlLabel::leftClickedUrl, this, [this, urlLabel] {
0530                     mailContact(urlLabel->url());
0531                 });
0532                 connect(urlLabel, &KUrlLabel::rightClickedUrl, this, [this, urlLabel] {
0533                     popupMenu(urlLabel->url());
0534                 });
0535             } else {
0536                 label = new QLabel(this);
0537                 label->setText((*addrIt).summary);
0538                 label->setTextFormat(Qt::RichText);
0539                 mLayout->addWidget(label, counter, 4);
0540                 mLabels.append(label);
0541                 if (!(*addrIt).desc.isEmpty()) {
0542                     label->setToolTip((*addrIt).desc);
0543                 }
0544             }
0545 
0546             // Age
0547             if ((*addrIt).category == CategoryBirthday || (*addrIt).category == CategoryAnniversary) {
0548                 label = new QLabel(this);
0549                 if ((*addrIt).yearsOld <= 0) {
0550                     label->setText(QString());
0551                 } else {
0552                     label->setText(i18np("one year", "%1 years", (*addrIt).yearsOld));
0553                 }
0554                 label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
0555                 mLayout->addWidget(label, counter, 5);
0556                 mLabels.append(label);
0557             }
0558 
0559             counter++;
0560         }
0561     } else {
0562         auto label = new QLabel(i18np("No special dates within the next 1 day", "No special dates pending within the next %1 days", mDaysAhead), this);
0563         label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0564         mLayout->addWidget(label, 0, 0);
0565         mLabels.append(label);
0566     }
0567 
0568     QList<QLabel *>::ConstIterator lit;
0569     QList<QLabel *>::ConstIterator endLit(mLabels.constEnd());
0570     for (lit = mLabels.constBegin(); lit != endLit; ++lit) {
0571         (*lit)->show();
0572     }
0573     setUpdatesEnabled(true);
0574 }
0575 
0576 void SDSummaryWidget::updateView()
0577 {
0578     mDates.clear();
0579 
0580     /* KABC Birthdays are got through a ItemSearchJob/SPARQL Query
0581      * I then added an ETM/CalendarModel because we need to search
0582      * for calendar entries that have birthday/anniversary categories too.
0583      *
0584      * Also, we can't get KABC Anniversaries through nepomuk because the
0585      * current S.D.O doesn't support it, so i also them through the ETM.
0586      *
0587      * So basically we have:
0588      * Calendar anniversaries - ETM
0589      * Calendar birthdays - ETM
0590      * KABC birthdays - BirthdaySearchJob
0591      * KABC anniversaries - ETM ( needs Birthday Agent running )
0592      *
0593      * We could remove thomas' BirthdaySearchJob and use the ETM for that
0594      * but it has the advantage that we don't need a Birthday agent running.
0595      *
0596      **/
0597 
0598     if (mShowBirthdaysFromKAB) {
0599         if (!mJobRunning) {
0600             auto job = new BirthdaySearchJob(this, mDaysAhead);
0601             connect(job, &BirthdaySearchJob::result, this, &SDSummaryWidget::slotBirthdayJobFinished);
0602             job->start();
0603             mJobRunning = true;
0604             // The result slot will trigger the rest of the update.
0605         }
0606     } else {
0607         createLabels();
0608     }
0609 }
0610 
0611 void SDSummaryWidget::mailContact(const QString &url)
0612 {
0613     const Akonadi::Item item = Akonadi::Item::fromUrl(QUrl(url));
0614     if (!item.isValid()) {
0615         qCDebug(KORGANIZER_KONTACTPLUGINS_SPECIALDATES_LOG) << QStringLiteral("Invalid item found");
0616         return;
0617     }
0618 
0619     auto job = new Akonadi::ItemFetchJob(item, this);
0620     job->fetchScope().fetchFullPayload();
0621     connect(job, &Akonadi::ItemFetchJob::result, this, &SDSummaryWidget::slotItemFetchJobDone);
0622 }
0623 
0624 void SDSummaryWidget::slotItemFetchJobDone(KJob *job)
0625 {
0626     if (job->error()) {
0627         qCWarning(KORGANIZER_KONTACTPLUGINS_SPECIALDATES_LOG) << job->errorString();
0628         return;
0629     }
0630     const Akonadi::Item::List lst = qobject_cast<Akonadi::ItemFetchJob *>(job)->items();
0631     if (lst.isEmpty()) {
0632         return;
0633     }
0634     const auto contact = lst.first().payload<KContacts::Addressee>();
0635 
0636     QDesktopServices::openUrl(QUrl(contact.fullEmail()));
0637 }
0638 
0639 void SDSummaryWidget::viewContact(const QString &url)
0640 {
0641     const Akonadi::Item item = Akonadi::Item::fromUrl(QUrl(url));
0642     if (!item.isValid()) {
0643         qCDebug(KORGANIZER_KONTACTPLUGINS_SPECIALDATES_LOG) << "Invalid item found";
0644         return;
0645     }
0646 
0647     QPointer<Akonadi::ContactViewerDialog> dlg = new Akonadi::ContactViewerDialog(this);
0648     dlg->setContact(item);
0649     dlg->exec();
0650     delete dlg;
0651 }
0652 
0653 void SDSummaryWidget::popupMenu(const QString &url)
0654 {
0655     QMenu popup(this);
0656     const QAction *sendMailAction = popup.addAction(QIcon::fromTheme(QStringLiteral("mail-message-new")), i18n("Send &Mail"));
0657     const QAction *viewContactAction = popup.addAction(QIcon::fromTheme(QStringLiteral("view-pim-contacts")), i18n("View &Contact"));
0658 
0659     const QAction *ret = popup.exec(QCursor::pos());
0660     if (ret == sendMailAction) {
0661         mailContact(url);
0662     } else if (ret == viewContactAction) {
0663         viewContact(url);
0664     }
0665 }
0666 
0667 bool SDSummaryWidget::eventFilter(QObject *obj, QEvent *e)
0668 {
0669     if (obj->inherits("KUrlLabel")) {
0670         auto label = static_cast<KUrlLabel *>(obj);
0671         if (e->type() == QEvent::Enter) {
0672             Q_EMIT message(i18n("Mail to:\"%1\"", label->text()));
0673         }
0674         if (e->type() == QEvent::Leave) {
0675             Q_EMIT message(QString());
0676         }
0677     }
0678 
0679     return KontactInterface::Summary::eventFilter(obj, e);
0680 }
0681 
0682 void SDSummaryWidget::dateDiff(const QDate &date, int &days, int &years) const
0683 {
0684     QDate currentDate;
0685     QDate eventDate;
0686 
0687     if (QDate::isLeapYear(date.year()) && date.month() == 2 && date.day() == 29) {
0688         currentDate = QDate(date.year(), QDate::currentDate().month(), QDate::currentDate().day());
0689         if (!QDate::isLeapYear(QDate::currentDate().year())) {
0690             eventDate = QDate(date.year(), date.month(), 28); // celebrate one day earlier ;)
0691         } else {
0692             eventDate = QDate(date.year(), date.month(), date.day());
0693         }
0694     } else {
0695         currentDate = QDate(QDate::currentDate().year(), QDate::currentDate().month(), QDate::currentDate().day());
0696         eventDate = QDate(QDate::currentDate().year(), date.month(), date.day());
0697     }
0698 
0699     int offset = currentDate.daysTo(eventDate);
0700     if (offset < 0) {
0701         days = 365 + offset;
0702         years = QDate::currentDate().year() + 1 - date.year();
0703     } else {
0704         days = offset;
0705         years = QDate::currentDate().year() - date.year();
0706     }
0707 }
0708 
0709 #include "sdsummarywidget.moc"
0710 
0711 #include "moc_sdsummarywidget.cpp"