diff --git a/autotests/conflictresolvertest.cpp b/autotests/conflictresolvertest.cpp index 0a699af..2f5746e 100644 --- a/autotests/conflictresolvertest.cpp +++ b/autotests/conflictresolvertest.cpp @@ -1,337 +1,336 @@ /* Copyright (C) 2010 Casey Link Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "conflictresolvertest.h" #include "conflictresolver.h" #include #include #include -#include #include #include using namespace IncidenceEditorNG; void ConflictResolverTest::insertAttendees() { foreach (const CalendarSupport::FreeBusyItem::Ptr &item, qAsConst(attendees)) { resolver->insertAttendee(item); } } void ConflictResolverTest::addAttendee(const QString &email, const KCalCore::FreeBusy::Ptr &fb, KCalCore::Attendee::Role role) { QString name = QStringLiteral("attendee %1").arg(attendees.count()); CalendarSupport::FreeBusyItem::Ptr item(new CalendarSupport::FreeBusyItem(KCalCore::Attendee::Ptr( new KCalCore::Attendee(name, email, false, KCalCore::Attendee::Accepted, role)), nullptr)); item->setFreeBusy(KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(*fb.data()))); attendees << item; } void ConflictResolverTest::initTestCase() { parent = new QWidget; init(); } void ConflictResolverTest::init() { - base = KDateTime::currentLocalDateTime().addDays(1); + base = QDateTime::currentDateTime().addDays(1); end = base.addSecs(10 * 60 * 60); resolver = new ConflictResolver(parent, parent); } void ConflictResolverTest::cleanup() { delete resolver; resolver = nullptr; attendees.clear(); } void ConflictResolverTest::simpleTest() { - KCalCore::Period meeting(KCalCore::k2q(end).addSecs(-3 * 60 * 60), KCalCore::Duration(2 * 60 * 60)); + KCalCore::Period meeting(end.addSecs(-3 * 60 * 60), KCalCore::Duration(2 * 60 * 60)); addAttendee(QStringLiteral("albert@einstein.net"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << meeting))); insertAttendees(); static const int resolution = 15 * 60; resolver->setResolution(resolution); resolver->setEarliestDateTime(base); resolver->setLatestDateTime(end); resolver->findAllFreeSlots(); QVERIFY(resolver->availableSlots().size() == 2); KCalCore::Period first = resolver->availableSlots().at(0); - QCOMPARE(first.start(), KCalCore::k2q(base)); + QCOMPARE(first.start(), base); QCOMPARE(first.end(), meeting.start()); KCalCore::Period second = resolver->availableSlots().at(1); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Continue); QCOMPARE(second.start(), meeting.end().addSecs(resolution)); //add 15 minutes because the //free block doesn't start until //the next timeslot - QCOMPARE(second.end(), KCalCore::k2q(end)); + QCOMPARE(second.end(), end); } void ConflictResolverTest::stillPrettySimpleTest() { - KCalCore::Period meeting1(KCalCore::k2q(base), KCalCore::Duration(2 * 60 * 60)); - KCalCore::Period meeting2(KCalCore::k2q(base).addSecs(60 * 60), KCalCore::Duration(2 * 60 * 60)); - KCalCore::Period meeting3(KCalCore::k2q(end).addSecs(-3 * 60 * 60), KCalCore::Duration(2 * 60 * 60)); + KCalCore::Period meeting1(base, KCalCore::Duration(2 * 60 * 60)); + KCalCore::Period meeting2(base.addSecs(60 * 60), KCalCore::Duration(2 * 60 * 60)); + KCalCore::Period meeting3(end.addSecs(-3 * 60 * 60), KCalCore::Duration(2 * 60 * 60)); addAttendee(QStringLiteral("john.f@kennedy.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << meeting1 << meeting3))); addAttendee(QStringLiteral("elvis@rock.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << meeting2 << meeting3))); addAttendee(QStringLiteral("albert@einstein.net"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << meeting3))); insertAttendees(); static const int resolution = 15 * 60; resolver->setResolution(resolution); resolver->setEarliestDateTime(base); resolver->setLatestDateTime(end); resolver->findAllFreeSlots(); QVERIFY(resolver->availableSlots().size() == 2); KCalCore::Period first = resolver->availableSlots().at(0); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Continue); QCOMPARE(first.start(), meeting2.end().addSecs(resolution)); QCOMPARE(first.end(), meeting3.start()); KCalCore::Period second = resolver->availableSlots().at(1); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Continue); QCOMPARE(second.start(), meeting3.end().addSecs(resolution)); //add 15 minutes because the //free block doesn't start until //the next timeslot - QCOMPARE(second.end(), KCalCore::k2q(end)); + QCOMPARE(second.end(), end); } #define _time( h, m ) QDateTime( base.date(), QTime( h, m ) ) void ConflictResolverTest::akademy2010() { // based off akademy 2010 schedule // first event was at 9:30, so lets align our start time there base.setTime(QTime(9, 30)); end = base.addSecs(8 * 60 * 60); KCalCore::Period opening(_time(9, 30), _time(9, 45)); KCalCore::Period keynote(_time(9, 45), _time(10, 30)); KCalCore::Period sevenPrinciples(_time(10, 30), _time(11, 15)); KCalCore::Period commAsService(_time(10, 30), _time(11, 15)); KCalCore::Period kdeForums(_time(11, 15), _time(11, 45)); KCalCore::Period oviStore(_time(11, 15), _time(11, 45)); // 10 min break KCalCore::Period highlights(_time(12, 0), _time(12, 45)); KCalCore::Period styles(_time(12, 0), _time(12, 45)); KCalCore::Period wikimedia(_time(12, 45), _time(13, 15)); KCalCore::Period avalanche(_time(12, 45), _time(13, 15)); KCalCore::Period pimp(_time(13, 15), _time(13, 45)); KCalCore::Period direction(_time(13, 15), _time(13, 45)); // lunch 1 hr 25 min lunch KCalCore::Period blurr(_time(15, 15), _time(16, 00)); KCalCore::Period plasma(_time(15, 15), _time(16, 00)); // for ( int i = 1; i < 80; ++i ) { // adds 80 people (adds the same 8 peopl 10 times) addAttendee(QStringLiteral("akademyattendee1@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << opening << keynote << oviStore << wikimedia << direction))); addAttendee(QStringLiteral("akademyattendee2@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << opening << keynote << commAsService << highlights << pimp))); addAttendee(QStringLiteral("akademyattendee3@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << opening << kdeForums << styles << pimp << plasma))); addAttendee(QStringLiteral("akademyattendee4@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << opening << keynote << oviStore << pimp << blurr))); addAttendee(QStringLiteral("akademyattendee5@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << keynote << oviStore << highlights << avalanche))); addAttendee(QStringLiteral("akademyattendee6@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << opening << keynote << commAsService << highlights))); addAttendee(QStringLiteral("akademyattendee7@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << opening << kdeForums << styles << avalanche << pimp << plasma))); addAttendee(QStringLiteral("akademyattendee8@email.com"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << opening << keynote << oviStore << wikimedia << blurr))); // } insertAttendees(); const int resolution = 5 * 60; resolver->setResolution(resolution); resolver->setEarliestDateTime(base); resolver->setLatestDateTime(end); // QBENCHMARK { resolver->findAllFreeSlots(); // } QVERIFY(resolver->availableSlots().size() == 3); QEXPECT_FAIL("", "Got broken in revision f17b9a8c975588ad7cf4ce8b94ab8e32ac193ed8", Abort); QCOMPARE(resolver->availableSlots().at(0).duration(), KCalCore::Duration(10 * 60)); QCOMPARE(resolver->availableSlots().at(1).duration(), KCalCore::Duration(1 * 60 * 60 + 25 * 60)); QVERIFY(resolver->availableSlots().at(2).start() > plasma.end()); } void ConflictResolverTest::testPeriodIsLargerThenTimeframe() { base.setDate(QDate(2010, 7, 29)); base.setTime(QTime(7, 30)); end.setDate(QDate(2010, 7, 29)); end.setTime(QTime(8, 30)); KCalCore::Period testEvent(_time(5, 45), _time(8, 45)); addAttendee(QStringLiteral("kdabtest1@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << testEvent))); addAttendee(QStringLiteral("kdabtest2@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List()))); insertAttendees(); resolver->setEarliestDateTime(base); resolver->setLatestDateTime(end); resolver->findAllFreeSlots(); QCOMPARE(resolver->availableSlots().size(), 0); } void ConflictResolverTest::testPeriodBeginsBeforeTimeframeBegins() { base.setDate(QDate(2010, 7, 29)); base.setTime(QTime(7, 30)); end.setDate(QDate(2010, 7, 29)); end.setTime(QTime(9, 30)); KCalCore::Period testEvent(_time(5, 45), _time(8, 45)); addAttendee(QStringLiteral("kdabtest1@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << testEvent))); addAttendee(QStringLiteral("kdabtest2@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List()))); insertAttendees(); resolver->setEarliestDateTime(base); resolver->setLatestDateTime(end); resolver->findAllFreeSlots(); QCOMPARE(resolver->availableSlots().size(), 1); KCalCore::Period freeslot = resolver->availableSlots().at(0); QCOMPARE(freeslot.start(), _time(8, 45)); - QCOMPARE(freeslot.end(), KCalCore::k2q(end)); + QCOMPARE(freeslot.end(), end); } void ConflictResolverTest::testPeriodEndsAfterTimeframeEnds() { base.setDate(QDate(2010, 7, 29)); base.setTime(QTime(7, 30)); end.setDate(QDate(2010, 7, 29)); end.setTime(QTime(9, 30)); KCalCore::Period testEvent(_time(8, 00), _time(9, 45)); addAttendee(QStringLiteral("kdabtest1@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << testEvent))); addAttendee(QStringLiteral("kdabtest2@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List()))); insertAttendees(); resolver->setEarliestDateTime(base); resolver->setLatestDateTime(end); resolver->findAllFreeSlots(); QCOMPARE(resolver->availableSlots().size(), 1); KCalCore::Period freeslot = resolver->availableSlots().at(0); QCOMPARE(freeslot.duration(), KCalCore::Duration(30 * 60)); - QCOMPARE(freeslot.start(), KCalCore::k2q(base)); + QCOMPARE(freeslot.start(), base); QCOMPARE(freeslot.end(), _time(8, 00)); } void ConflictResolverTest::testPeriodEndsAtSametimeAsTimeframe() { base.setDate(QDate(2010, 7, 29)); base.setTime(QTime(7, 45)); end.setDate(QDate(2010, 7, 29)); end.setTime(QTime(8, 45)); KCalCore::Period testEvent(_time(5, 45), _time(8, 45)); addAttendee(QStringLiteral("kdabtest1@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List() << testEvent))); addAttendee(QStringLiteral("kdabtest2@demo.kolab.org"), KCalCore::FreeBusy::Ptr(new KCalCore::FreeBusy(KCalCore::Period::List()))); insertAttendees(); resolver->setEarliestDateTime(base); resolver->setLatestDateTime(end); resolver->findAllFreeSlots(); QCOMPARE(resolver->availableSlots().size(), 0); } QTEST_MAIN(ConflictResolverTest) diff --git a/autotests/conflictresolvertest.h b/autotests/conflictresolvertest.h index dd2cfb2..d5609a3 100644 --- a/autotests/conflictresolvertest.h +++ b/autotests/conflictresolvertest.h @@ -1,61 +1,61 @@ /* Copyright (C) 2010 Casey Link Copyright (C) 2009-2010 Klaralvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CONFLICTRESOLVERTEST_H #define CONFLICTRESOLVERTEST_H #include "CalendarSupport/FreeBusyItem" #include #include #include namespace IncidenceEditorNG { class ConflictResolver; } class ConflictResolverTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void simpleTest(); void stillPrettySimpleTest(); void akademy2010(); void testPeriodBeginsBeforeTimeframeBegins(); void testPeriodEndsAfterTimeframeEnds(); void testPeriodIsLargerThenTimeframe(); void testPeriodEndsAtSametimeAsTimeframe(); private: void insertAttendees(); void addAttendee(const QString &email, const KCalCore::FreeBusy::Ptr &fb, KCalCore::Attendee::Role role = KCalCore::Attendee::ReqParticipant); QList attendees; QWidget *parent; IncidenceEditorNG::ConflictResolver *resolver; - KDateTime base, end; + QDateTime base, end; }; #endif diff --git a/src/conflictresolver.cpp b/src/conflictresolver.cpp index bea84cd..b307cbe 100644 --- a/src/conflictresolver.cpp +++ b/src/conflictresolver.cpp @@ -1,508 +1,506 @@ /* Copyright (c) 2000,2001,2004 Cornelius Schumacher Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia Copyright (C) 2010 Casey Link This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "conflictresolver.h" #include "CalendarSupport/FreeBusyItemModel" #include "incidenceeditor_debug.h" -#include - #include static const int DEFAULT_RESOLUTION_SECONDS = 15 * 60; // 15 minutes, 1 slot = 15 minutes using namespace IncidenceEditorNG; ConflictResolver::ConflictResolver(QWidget *parentWidget, QObject *parent) : QObject(parent) , mFBModel(new CalendarSupport::FreeBusyItemModel(this)) , mParentWidget(parentWidget) , mWeekdays(7) , mSlotResolutionSeconds(DEFAULT_RESOLUTION_SECONDS) { const QDateTime currentLocalDateTime = QDateTime::currentDateTime(); mTimeframeConstraint = KCalCore::Period(currentLocalDateTime, currentLocalDateTime); // trigger a reload in case any attendees were inserted before // the connection was made // triggerReload(); // set default values, all the days mWeekdays.setBit(0); // Monday mWeekdays.setBit(1); mWeekdays.setBit(2); mWeekdays.setBit(3); mWeekdays.setBit(4); mWeekdays.setBit(5); mWeekdays.setBit(6); // Sunday mMandatoryRoles.reserve(4); mMandatoryRoles << KCalCore::Attendee::ReqParticipant << KCalCore::Attendee::OptParticipant << KCalCore::Attendee::NonParticipant << KCalCore::Attendee::Chair; connect(mFBModel, &CalendarSupport::FreeBusyItemModel::dataChanged, this, &ConflictResolver::freebusyDataChanged); connect(&mCalculateTimer, &QTimer::timeout, this, &ConflictResolver::findAllFreeSlots); mCalculateTimer.setSingleShot(true); } void ConflictResolver::insertAttendee(const KCalCore::Attendee::Ptr &attendee) { if (!mFBModel->containsAttendee(attendee)) { mFBModel->addItem(CalendarSupport::FreeBusyItem::Ptr(new CalendarSupport::FreeBusyItem( attendee, mParentWidget))); } } void ConflictResolver::insertAttendee(const CalendarSupport::FreeBusyItem::Ptr &freebusy) { if (!mFBModel->containsAttendee(freebusy->attendee())) { mFBModel->addItem(freebusy); } } void ConflictResolver::removeAttendee(const KCalCore::Attendee::Ptr &attendee) { mFBModel->removeAttendee(attendee); calculateConflicts(); } void ConflictResolver::clearAttendees() { mFBModel->clear(); } bool ConflictResolver::containsAttendee(const KCalCore::Attendee::Ptr &attendee) { return mFBModel->containsAttendee(attendee); } void ConflictResolver::setEarliestDate(const QDate &newDate) { QDateTime newStart = mTimeframeConstraint.start(); newStart.setDate(newDate); mTimeframeConstraint = KCalCore::Period(newStart, mTimeframeConstraint.end()); calculateConflicts(); } void ConflictResolver::setEarliestTime(const QTime &newTime) { QDateTime newStart = mTimeframeConstraint.start(); newStart.setTime(newTime); mTimeframeConstraint = KCalCore::Period(newStart, mTimeframeConstraint.end()); calculateConflicts(); } void ConflictResolver::setLatestDate(const QDate &newDate) { QDateTime newEnd = mTimeframeConstraint.end(); newEnd.setDate(newDate); mTimeframeConstraint = KCalCore::Period(mTimeframeConstraint.start(), newEnd); calculateConflicts(); } void ConflictResolver::setLatestTime(const QTime &newTime) { QDateTime newEnd = mTimeframeConstraint.end(); newEnd.setTime(newTime); mTimeframeConstraint = KCalCore::Period(mTimeframeConstraint.start(), newEnd); calculateConflicts(); } -void ConflictResolver::setEarliestDateTime(const KDateTime &newDateTime) +void ConflictResolver::setEarliestDateTime(const QDateTime &newDateTime) { - mTimeframeConstraint = KCalCore::Period(KCalCore::k2q(newDateTime), mTimeframeConstraint.end()); + mTimeframeConstraint = KCalCore::Period(newDateTime, mTimeframeConstraint.end()); calculateConflicts(); } -void ConflictResolver::setLatestDateTime(const KDateTime &newDateTime) +void ConflictResolver::setLatestDateTime(const QDateTime &newDateTime) { - mTimeframeConstraint = KCalCore::Period(mTimeframeConstraint.start(), KCalCore::k2q(newDateTime)); + mTimeframeConstraint = KCalCore::Period(mTimeframeConstraint.start(), newDateTime); calculateConflicts(); } void ConflictResolver::freebusyDataChanged() { calculateConflicts(); } -int ConflictResolver::tryDate(KDateTime &tryFrom, KDateTime &tryTo) +int ConflictResolver::tryDate(QDateTime &tryFrom, QDateTime &tryTo) { int conflicts_count = 0; for (int i = 0; i < mFBModel->rowCount(); ++i) { QModelIndex index = mFBModel->index(i); KCalCore::Attendee::Ptr attendee = mFBModel->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value(); if (!matchesRoleConstraint(attendee)) { continue; } KCalCore::FreeBusy::Ptr freebusy = mFBModel->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value(); if (!tryDate(freebusy, tryFrom, tryTo)) { ++conflicts_count; } } return conflicts_count; } -bool ConflictResolver::tryDate(const KCalCore::FreeBusy::Ptr &fb, KDateTime &tryFrom, - KDateTime &tryTo) +bool ConflictResolver::tryDate(const KCalCore::FreeBusy::Ptr &fb, QDateTime &tryFrom, + QDateTime &tryTo) { // If we don't have any free/busy information, assume the // participant is free. Otherwise a participant without available // information would block the whole allocation. if (!fb) { return true; } KCalCore::Period::List busyPeriods = fb->busyPeriods(); for (auto it = busyPeriods.begin(); it != busyPeriods.end(); ++it) { - if ((*it).end() <= KCalCore::k2q(tryFrom) // busy period ends before try period - || (*it).start() >= KCalCore::k2q(tryTo)) { // busy period starts after try period + if ((*it).end() <= tryFrom // busy period ends before try period + || (*it).start() >= tryTo) { // busy period starts after try period continue; } else { // the current busy period blocks the try period, try // after the end of the current busy period const int secsDuration = tryFrom.secsTo(tryTo); - tryFrom = KCalCore::q2k((*it).end()); + tryFrom = (*it).end(); tryTo = tryFrom.addSecs(secsDuration); // try again with the new try period tryDate(fb, tryFrom, tryTo); // we had to change the date at least once return false; } } return true; } bool ConflictResolver::findFreeSlot(const KCalCore::Period &dateTimeRange) { - KDateTime dtFrom = KCalCore::q2k(dateTimeRange.start()); - KDateTime dtTo = KCalCore::q2k(dateTimeRange.end()); + QDateTime dtFrom = dateTimeRange.start(); + QDateTime dtTo = dateTimeRange.end(); if (tryDate(dtFrom, dtTo)) { // Current time is acceptable return true; } - KDateTime tryFrom = dtFrom; - KDateTime tryTo = dtTo; + QDateTime tryFrom = dtFrom; + QDateTime tryTo = dtTo; // Make sure that we never suggest a date in the past, even if the // user originally scheduled the meeting to be in the past. - KDateTime now = KDateTime::currentUtcDateTime(); + QDateTime now = QDateTime::currentDateTimeUtc(); if (tryFrom < now) { // The slot to look for is at least partially in the past. const int secs = tryFrom.secsTo(tryTo); tryFrom = now; tryTo = tryFrom.addSecs(secs); } bool found = false; while (!found) { found = tryDate(tryFrom, tryTo); // PENDING(kalle) Make the interval configurable if (!found && dtFrom.daysTo(tryFrom) > 365) { break; // don't look more than one year in the future } } dtFrom = tryFrom; dtTo = tryTo; return found; } void ConflictResolver::findAllFreeSlots() { // Uses an O(p*n) (n number of attendees, p timeframe range / timeslot resolution ) algorithm to // locate all free blocks in a given timeframe that match the search constraints. // Does so by: // 1. convert each attendees schedule for the timeframe into a bitarray according to // the time resolution, where each time slot has a value of 1 = busy, 0 = free. // 2. align the arrays vertically, and sum the columns // 3. the resulting summation indcates # of conflicts at each timeslot // 4. locate contiguous timeslots with a values of 0. these are the free time blocks. // define these locally for readability const QDateTime begin = mTimeframeConstraint.start(); const QDateTime end = mTimeframeConstraint.end(); // calculate the time resolution // each timeslot in the arrays represents a unit of time // specified here. if (mSlotResolutionSeconds < 1) { // fallback to default, if the user's value is invalid mSlotResolutionSeconds = DEFAULT_RESOLUTION_SECONDS; } // calculate the length of the timeframe in terms of the amount of timeslots. // Example: 1 week timeframe, with resolution of 15 minutes // 1 week = 10080 minutes / 15 = 672 15 min timeslots // So, the array would have a length of 672 const int range = begin.secsTo(end) / mSlotResolutionSeconds; if (range <= 0) { qCWarning(INCIDENCEEDITOR_LOG) << "free slot calculation: invalid range. range( " << begin.secsTo(end) << ") / mSlotResolutionSeconds(" << mSlotResolutionSeconds << ") = " << range; return; } qCDebug(INCIDENCEEDITOR_LOG) << "from " << begin << " to " << end << "; mSlotResolutionSeconds = " << mSlotResolutionSeconds << "; range = " << range; // filter out attendees for which we don't have FB data // and which don't match the mandatory role contrstaint QList filteredFBItems; for (int i = 0; i < mFBModel->rowCount(); ++i) { QModelIndex index = mFBModel->index(i); KCalCore::Attendee::Ptr attendee = mFBModel->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value(); if (!matchesRoleConstraint(attendee)) { continue; } KCalCore::FreeBusy::Ptr freebusy = mFBModel->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value(); if (freebusy) { filteredFBItems << freebusy; } } // now we know the number of attendees we are calculating for const int number_attendees = filteredFBItems.size(); if (number_attendees <= 0) { qCDebug(INCIDENCEEDITOR_LOG) << "no attendees match search criteria"; return; } qCDebug(INCIDENCEEDITOR_LOG) << "num attendees: " << number_attendees; // this is a 2 dimensional array where the rows are attendees // and the columns are 0 or 1 denoting freee or busy respectively. QVector< QVector > fbTable; // Explanation of the following loop: // iterate: through each attendee // allocate: an array of length and fill it with 0s // iterate: through each attendee's busy period // if: the period lies inside our timeframe // then: // calculate the array index within the timeframe range of the beginning of the busy period // fill from that index until the period ends with a 1, representing busy // fi // etareti // append the allocated array to // etareti foreach (const KCalCore::FreeBusy::Ptr ¤tFB, filteredFBItems) { Q_ASSERT(currentFB); // sanity check const KCalCore::Period::List busyPeriods = currentFB->busyPeriods(); QVector fbArray(range); fbArray.fill(0); // initialize to zero for (const auto &period : busyPeriods) { if (period.end() >= begin && period.start() <= end) { int start_index = -1; // Initialize it to an invalid value. int duration = -1; // Initialize it to an invalid value. // case1: the period is completely in our timeframe if (period.end() <= end && period.start() >= begin) { start_index = begin.secsTo(period.start()) / mSlotResolutionSeconds; duration = period.start().secsTo(period.end()) / mSlotResolutionSeconds; duration -= 1; // vector starts at 0 // case2: the period begins before our timeframe begins } else if (period.start() <= begin && period.end() <= end) { start_index = 0; duration = (begin.secsTo(period.end()) / mSlotResolutionSeconds) - 1; // case3: the period ends after our timeframe ends } else if (period.end() >= end && period.start() >= begin) { start_index = begin.secsTo(period.start()) / mSlotResolutionSeconds; duration = range - start_index - 1; // case4: case2+case3: our timeframe is inside the period } else if (period.start() <= begin && period.end() >= end) { start_index = 0; duration = range - 1; } else { //QT5 //qCCritical(INCIDENCEEDITOR_LOG) << "impossible condition reached" << period.start() << period.end(); } // qCDebug(INCIDENCEEDITOR_LOG) << start_index << "+" << duration << "=" // << start_index + duration << "<=" << range; Q_ASSERT((start_index + duration) < range); // sanity check for (int i = start_index; i <= start_index + duration; ++i) { fbArray[i] = 1; } } } Q_ASSERT(fbArray.size() == range); // sanity check fbTable.append(fbArray); } Q_ASSERT(fbTable.size() == number_attendees); // Now, create another array to represent the allowed weekdays constraints // All days which are not allowed, will be marked as busy QVector fbArray(range); fbArray.fill(0); // initialize to zero for (int slot = 0; slot < fbArray.size(); ++slot) { const QDateTime dateTime = begin.addSecs(slot * mSlotResolutionSeconds); const int dayOfWeek = dateTime.date().dayOfWeek() - 1; // bitarray is 0 indexed if (!mWeekdays[dayOfWeek]) { fbArray[slot] = 1; } } fbTable.append(fbArray); // Create the composite array that will hold the sums for // each 15 minute timeslot QVector summed(range); summed.fill(0); // initialize to zero // Sum the columns of the table for (int i = 0; i < fbTable.size(); ++i) { for (int j = 0; j < range; ++j) { summed[j] += fbTable[i][j]; } } // Finally, iterate through the composite array locating contiguous free timeslots int free_count = 0; bool free_found = false; mAvailableSlots.clear(); for (int i = 0; i < range; ++i) { // free timeslot encountered, increment counter if (summed[i] == 0) { ++free_count; } if (summed[i] != 0 || (i == (range - 1) && summed[i] == 0)) { // current slot is not free, so push the previous free blocks // OR we are in the last slot and it is free if (free_count > 0) { int free_start_i;// start index of the free block int free_end_i; // end index of the free block if (summed[i] == 0) { // special case: we are on the last slot and it is free // so we want to include this slot in the free block free_start_i = i - free_count + 1; // add one, to set us back inside the array because // free_count was incremented already this iteration free_end_i = i + 1; // add one to compensate for the fact that the array is 0 indexed } else { free_start_i = i - free_count; free_end_i = i - 1 + 1; // add one to compensate for the fact that the array is 0 indexed // compiler will optmize out the -1+1, but I leave it here to make the reasoning apparent. } // convert from our timeslot interval back into to normal seconds // then calculate the date times of the free block based on // our initial timeframe const QDateTime freeBegin = begin.addSecs(free_start_i * mSlotResolutionSeconds); const QDateTime freeEnd = freeBegin.addSecs((free_end_i - free_start_i) * mSlotResolutionSeconds); // push the free block onto the list mAvailableSlots << KCalCore::Period(freeBegin, freeEnd); free_count = 0; if (!free_found) { free_found = true; } } } } if (free_found) { Q_EMIT freeSlotsAvailable(mAvailableSlots); } #if 0 //DEBUG, dump the arrays. very helpful for debugging QTextStream dump(stdout); dump << " "; dump.setFieldWidth(3); for (int i = 0; i < range; ++i) { // header dump << i; } dump.setFieldWidth(1); dump << "\n\n"; for (int i = 0; i < number_attendees; ++i) { dump.setFieldWidth(1); dump << i << ": "; dump.setFieldWidth(3); for (int j = 0; j < range; ++j) { dump << fbTable[i][j]; } dump << "\n\n"; } dump.setFieldWidth(1); dump << " "; dump.setFieldWidth(3); for (int i = 0; i < range; ++i) { dump << summed[i]; } dump << "\n"; #endif } void ConflictResolver::calculateConflicts() { - KDateTime start = KCalCore::q2k(mTimeframeConstraint.start()); - KDateTime end = KCalCore::q2k(mTimeframeConstraint.end()); + QDateTime start = mTimeframeConstraint.start(); + QDateTime end = mTimeframeConstraint.end(); const int count = tryDate(start, end); Q_EMIT conflictsDetected(count); if (!mCalculateTimer.isActive()) { mCalculateTimer.start(0); } } void ConflictResolver::setAllowedWeekdays(const QBitArray &weekdays) { mWeekdays = weekdays; calculateConflicts(); } void ConflictResolver::setMandatoryRoles(const QSet< KCalCore::Attendee::Role > &roles) { mMandatoryRoles = roles; calculateConflicts(); } bool ConflictResolver::matchesRoleConstraint(const KCalCore::Attendee::Ptr &attendee) { return mMandatoryRoles.contains(attendee->role()); } KCalCore::Period::List ConflictResolver::availableSlots() const { return mAvailableSlots; } void ConflictResolver::setResolution(int seconds) { mSlotResolutionSeconds = seconds; } CalendarSupport::FreeBusyItemModel *ConflictResolver::model() const { return mFBModel; } diff --git a/src/conflictresolver.h b/src/conflictresolver.h index 0b11969..b3f75d5 100644 --- a/src/conflictresolver.h +++ b/src/conflictresolver.h @@ -1,203 +1,203 @@ /* Copyright (c) 2000,2001,2004 Cornelius Schumacher Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com Copyright (c) 2010 Andras Mantia Copyright (C) 2010 Casey Link This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCIDENCEEDITOR_CONFLICTRESOLVER_H #define INCIDENCEEDITOR_CONFLICTRESOLVER_H #include "incidenceeditor_export.h" #include "CalendarSupport/FreeBusyItem" #include #include #include namespace CalendarSupport { class FreeBusyItemModel; } namespace IncidenceEditorNG { /** * Takes a list of attendees and event info (e.g., min time start, max time end) * fetches their freebusy information, then identifies conflicts and periods of non-conflict. * * It exposes these periods so another class can display them to the user and allow * them to choose a correct time. * @author Casey Link */ class INCIDENCEEDITOR_EXPORT ConflictResolver : public QObject { Q_OBJECT public: /** * @param parentWidget is passed to Akonadi when fetching free/busy data. */ explicit ConflictResolver(QWidget *parentWidget, QObject *parent = nullptr); /** * Add an attendee * The attendees free busy info will be fetched * and integrated into the resolver. */ void insertAttendee(const KCalCore::Attendee::Ptr &attendee); void insertAttendee(const CalendarSupport::FreeBusyItem::Ptr &freebusy); /** * Removes an attendee * The attendee will no longer be considered when * resolving conflicts */ void removeAttendee(const KCalCore::Attendee::Ptr &attendee); /** * Clear all attendees */ void clearAttendees(); /** * Returns whether the resolver contains the attendee */ bool containsAttendee(const KCalCore::Attendee::Ptr &attendee); /** * Constrain the free time slot search to the weekdays * identified by their KCalendarSystem integer representation * Default is Monday - Friday * @param weekdays a 7 bit array indicating the allowed days (bit 0=Monday, value 1=allowed). * @see KCalendarSystem */ void setAllowedWeekdays(const QBitArray &weekdays); /** * Constrain the free time slot search to the set participant roles. * Mandatory roles are considered the minimum required to attend * the meeting, so only those attendees with the mandatory roles will * be considered in the search. * Default is all roles are mandatory. * @param roles the set of mandatory participant roles */ void setMandatoryRoles(const QSet &roles); /** * Returns a list of date time ranges that conform to the * search constraints. * @see setMandatoryRoles * @see setAllowedWeekdays */ KCalCore::Period::List availableSlots() const; /** Finds a free slot in the future which has at least the same size as the initial slot. */ bool findFreeSlot(const KCalCore::Period &dateTimeRange); QList freeBusyItems() const; CalendarSupport::FreeBusyItemModel *model() const; Q_SIGNALS: /** * Emitted when the user changes the start and end dateTimes * for the incidence. */ - void dateTimesChanged(const KDateTime &newStart, const KDateTime &newEnd); + void dateTimesChanged(const QDateTime &newStart, const QDateTime &newEnd); /** * Emitted when there are conflicts * @param number the number of conflicts */ void conflictsDetected(int number); /** * Emitted when the resolver locates new free slots. */ void freeSlotsAvailable(const KCalCore::Period::List &); public Q_SLOTS: /** * Set the timeframe constraints * * These control the timeframe for which conflicts are to be resolved. */ void setEarliestDate(const QDate &newDate); void setEarliestTime(const QTime &newTime); void setLatestDate(const QDate &newDate); void setLatestTime(const QTime &newTime); - void setEarliestDateTime(const KDateTime &newDateTime); - void setLatestDateTime(const KDateTime &newDateTime); + void setEarliestDateTime(const QDateTime &newDateTime); + void setLatestDateTime(const QDateTime &newDateTime); void freebusyDataChanged(); void findAllFreeSlots(); void setResolution(int seconds); private: /** Checks whether the slot specified by (tryFrom, tryTo) matches the search constraints. If yes, return true. The return value is the number of conflicts that were detected, and (tryFrom, tryTo) contain the next free slot for that participant. In other words, the returned slot does not have to be free for everybody else. */ - int tryDate(KDateTime &tryFrom, KDateTime &tryTo); + int tryDate(QDateTime &tryFrom, QDateTime &tryTo); /** Checks whether the slot specified by (tryFrom, tryTo) is available for the participant with specified fb. If yes, return true. If not, return false and change (tryFrom, tryTo) to contain the next possible slot for this participant (not necessarily a slot that is available for all participants). */ - bool tryDate(const KCalCore::FreeBusy::Ptr &fb, KDateTime &tryFrom, KDateTime &tryTo); + bool tryDate(const KCalCore::FreeBusy::Ptr &fb, QDateTime &tryFrom, QDateTime &tryTo); /** * Checks whether the supplied attendee passes the * current mandatory role constraint. * @return true if the attendee is of one of the mandatory roles, false if not */ bool matchesRoleConstraint(const KCalCore::Attendee::Ptr &attendee); void calculateConflicts(); KCalCore::Period mTimeframeConstraint; //!< the datetime range for outside of which //free slots won't be searched. KCalCore::Period::List mAvailableSlots; QTimer mCalculateTimer; //!< A timer is used control the calculation of conflicts // to prevent the process from being repeated many times // after a series of quick parameter changes. CalendarSupport::FreeBusyItemModel *mFBModel = nullptr; QWidget *mParentWidget = nullptr; QSet mMandatoryRoles; QBitArray mWeekdays; //!< a 7 bit array indicating the allowed days //(bit 0 = Monday, value 1 = allowed). int mSlotResolutionSeconds; }; } #endif diff --git a/src/incidenceattendee.cpp b/src/incidenceattendee.cpp index 5bb9f77..0982483 100644 --- a/src/incidenceattendee.cpp +++ b/src/incidenceattendee.cpp @@ -1,1069 +1,1068 @@ /* Copyright (C) 2010 Casey Link Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company Based on old attendeeeditor.cpp: Copyright (c) 2000,2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "incidenceattendee.h" #include "attendeetablemodel.h" #include "attendeeeditor.h" #include "attendeecomboboxdelegate.h" #include "attendeelineeditdelegate.h" #include "conflictresolver.h" #include "editorconfig.h" #include "incidencedatetime.h" #include "schedulingdialog.h" #include "CalendarSupport/FreeBusyItemModel" #include "ui_dialogdesktop.h" #include #include #include #include #include -#include #include "incidenceeditor_debug.h" #include #include #include using namespace IncidenceEditorNG; IncidenceAttendee::IncidenceAttendee(QWidget *parent, IncidenceDateTime *dateTime, Ui::EventOrTodoDesktop *ui) : mUi(ui) , mParentWidget(parent) , mConflictResolver(nullptr) , mDateTime(dateTime) , mStateDelegate(new AttendeeComboBoxDelegate(this)) , mRoleDelegate(new AttendeeComboBoxDelegate(this)) , mResponseDelegate(new AttendeeComboBoxDelegate(this)) { KCalCore::Attendee::List attendees; KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(QLatin1String(""), QLatin1String(""))); attendees.append(attendee); mDataModel = new AttendeeTableModel(attendees, this); mDataModel->setKeepEmpty(true); mDataModel->setRemoveEmptyLines(true); mOldIconName = QIcon::themeName(); QIcon::setThemeName(QStringLiteral("oxygen")); mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant")), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::ReqParticipant)); mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-optional")), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::OptParticipant)); mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-observer")), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::NonParticipant)); mRoleDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-chair")), KCalUtils::Stringify::attendeeRole(KCalCore::Attendee::Chair)); mResponseDelegate->addItem(QIcon::fromTheme(QStringLiteral( "meeting-participant-request-response")), i18nc("@item:inlistbox", "Request Response")); mResponseDelegate->addItem(QIcon::fromTheme(QStringLiteral("meeting-participant-no-response")), i18nc("@item:inlistbox", "Request No Response")); mStateDelegate->setWhatsThis(i18nc("@info:whatsthis", "Edits the current attendance status of the attendee.")); mRoleDelegate->setWhatsThis(i18nc("@info:whatsthis", "Edits the role of the attendee.")); mResponseDelegate->setToolTip(i18nc("@info:tooltip", "Request a response from the attendee")); mResponseDelegate->setWhatsThis(i18nc("@info:whatsthis", "Edits whether to send an email to the " "attendee to request a response concerning " "attendance.")); setObjectName(QStringLiteral("IncidenceAttendee")); AttendeeFilterProxyModel *filterProxyModel = new AttendeeFilterProxyModel(this); filterProxyModel->setDynamicSortFilter(true); filterProxyModel->setSourceModel(mDataModel); connect(mUi->mGroupSubstitution, &QPushButton::clicked, this, &IncidenceAttendee::slotGroupSubstitutionPressed); mUi->mAttendeeTable->setModel(filterProxyModel); mAttendeeDelegate = new AttendeeLineEditDelegate(this); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Role, roleDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::FullName, attendeeDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Status, stateDelegate()); mUi->mAttendeeTable->setItemDelegateForColumn(AttendeeTableModel::Response, responseDelegate()); mUi->mOrganizerStack->setCurrentIndex(0); fillOrganizerCombo(); mUi->mSolveButton->setEnabled(false); mUi->mOrganizerLabel->setVisible(false); mConflictResolver = new ConflictResolver(parent, parent); mConflictResolver->setEarliestDate(mDateTime->startDate()); mConflictResolver->setEarliestTime(mDateTime->startTime()); mConflictResolver->setLatestDate(mDateTime->endDate()); mConflictResolver->setLatestTime(mDateTime->endTime()); connect(mUi->mSelectButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSelectAddresses); connect(mUi->mSolveButton, &QPushButton::clicked, this, &IncidenceAttendee::slotSolveConflictPressed); /* Added as part of kolab/issue2297, which is currently under review connect(mUi->mOrganizerCombo, QOverload::of(&KComboBox::activated), this, &IncidenceAttendee::slotOrganizerChanged); */ connect(mUi->mOrganizerCombo, QOverload::of(&KComboBox::currentIndexChanged), this, &IncidenceAttendee::checkDirtyStatus); connect(mDateTime, &IncidenceDateTime::startDateChanged, this, &IncidenceAttendee::slotEventDurationChanged); connect(mDateTime, &IncidenceDateTime::endDateChanged, this, &IncidenceAttendee::slotEventDurationChanged); connect(mDateTime, &IncidenceDateTime::startTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged); connect(mDateTime, &IncidenceDateTime::endTimeChanged, this, &IncidenceAttendee::slotEventDurationChanged); connect(mConflictResolver, &ConflictResolver::conflictsDetected, this, &IncidenceAttendee::slotUpdateConflictLabel); connect(mConflictResolver->model(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotFreeBusyAdded(QModelIndex,int,int))); connect(mConflictResolver->model(), SIGNAL(layoutChanged()), SLOT(updateFBStatus())); connect(mConflictResolver->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(slotFreeBusyChanged(QModelIndex,QModelIndex))); slotUpdateConflictLabel(0); //initialize label // confict resolver (should show also resources) connect(mDataModel, &AttendeeTableModel::layoutChanged, this, &IncidenceAttendee::slotConflictResolverLayoutChanged); connect(mDataModel, &AttendeeTableModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotConflictResolverAttendeeRemoved); connect(mDataModel, &AttendeeTableModel::rowsInserted, this, &IncidenceAttendee::slotConflictResolverAttendeeAdded); connect(mDataModel, &AttendeeTableModel::dataChanged, this, &IncidenceAttendee::slotConflictResolverAttendeeChanged); //Group substitution connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::slotGroupSubstitutionLayoutChanged); connect(filterProxyModel, &AttendeeFilterProxyModel::rowsAboutToBeRemoved, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved); connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeAdded); connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::slotGroupSubstitutionAttendeeChanged); connect(filterProxyModel, &AttendeeFilterProxyModel::rowsInserted, this, &IncidenceAttendee::updateCount); connect(filterProxyModel, &AttendeeFilterProxyModel::rowsRemoved, this, &IncidenceAttendee::updateCount); // only update when FullName is changed connect(filterProxyModel, &AttendeeFilterProxyModel::dataChanged, this, &IncidenceAttendee::updateCount); connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::updateCount); connect(filterProxyModel, &AttendeeFilterProxyModel::layoutChanged, this, &IncidenceAttendee::filterLayoutChanged); } IncidenceAttendee::~IncidenceAttendee() { QIcon::setThemeName(mOldIconName); } void IncidenceAttendee::load(const KCalCore::Incidence::Ptr &incidence) { mLoadedIncidence = incidence; if (iAmOrganizer() || incidence->organizer()->isEmpty()) { mUi->mOrganizerStack->setCurrentIndex(0); int found = -1; const QString fullOrganizer = incidence->organizer()->fullName(); const QString organizerEmail = incidence->organizer()->email(); for (int i = 0; i < mUi->mOrganizerCombo->count(); ++i) { KCalCore::Person::Ptr organizerCandidate = KCalCore::Person::fromFullName(mUi->mOrganizerCombo->itemText(i)); if (organizerCandidate->email() == organizerEmail) { found = i; mUi->mOrganizerCombo->setCurrentIndex(i); break; } } if (found < 0 && !fullOrganizer.isEmpty()) { mUi->mOrganizerCombo->insertItem(0, fullOrganizer); mUi->mOrganizerCombo->setCurrentIndex(0); } mUi->mOrganizerLabel->setVisible(false); } else { // someone else is the organizer mUi->mOrganizerStack->setCurrentIndex(1); mUi->mOrganizerLabel->setText(incidence->organizer()->fullName()); mUi->mOrganizerLabel->setVisible(true); } KCalCore::Attendee::List attendees; const KCalCore::Attendee::List incidenceAttendees = incidence->attendees(); attendees.reserve(incidenceAttendees.count()); for (const KCalCore::Attendee::Ptr &a : incidenceAttendees) { attendees << KCalCore::Attendee::Ptr(new KCalCore::Attendee(*a)); } mDataModel->setAttendees(attendees); slotUpdateConflictLabel(0); setActions(incidence->type()); mWasDirty = false; } void IncidenceAttendee::save(const KCalCore::Incidence::Ptr &incidence) { incidence->clearAttendees(); const KCalCore::Attendee::List attendees = mDataModel->attendees(); for (const KCalCore::Attendee::Ptr &attendee : attendees) { Q_ASSERT(attendee); bool skip = false; if (attendee->fullName().isEmpty()) { continue; } if (KEmailAddress::isValidAddress(attendee->email())) { if (KMessageBox::warningYesNo( nullptr, i18nc("@info", "%1 does not look like a valid email address. " "Are you sure you want to invite this participant?", attendee->email()), i18nc("@title:window", "Invalid Email Address")) != KMessageBox::Yes) { skip = true; } } if (!skip) { incidence->addAttendee(attendee); } } // Must not have an organizer for items without attendees if (!incidence->attendeeCount()) { return; } if (mUi->mOrganizerStack->currentIndex() == 0) { incidence->setOrganizer(mUi->mOrganizerCombo->currentText()); } else { incidence->setOrganizer(mUi->mOrganizerLabel->text()); } } bool IncidenceAttendee::isDirty() const { if (iAmOrganizer()) { KCalCore::Event tmp; tmp.setOrganizer(mUi->mOrganizerCombo->currentText()); if (mLoadedIncidence->organizer()->email() != tmp.organizer()->email()) { qCDebug(INCIDENCEEDITOR_LOG) << "Organizer changed. Old was " << mLoadedIncidence->organizer()->name() << mLoadedIncidence->organizer()->email() << "; new is " << tmp.organizer()->name() << tmp.organizer()->email(); return true; } } const KCalCore::Attendee::List originalList = mLoadedIncidence->attendees(); KCalCore::Attendee::List newList; foreach (KCalCore::Attendee::Ptr attendee, mDataModel->attendees()) { if (!attendee->fullName().isEmpty()) { newList.append(attendee); } } // The lists sizes *must* be the same. When the organizer is attending the // event as well, he should be in the attendees list as well. if (originalList.size() != newList.size()) { return true; } // Okay, again not the most efficient algorithm, but I'm assuming that in the // bulk of the use cases, the number of attendees is not much higher than 10 or so. foreach (const KCalCore::Attendee::Ptr &attendee, originalList) { bool found = false; for (int i = 0; i < newList.count(); ++i) { if (*(newList[i]) == *attendee) { newList.remove(i); found = true; break; } } if (!found) { // One of the attendees in the original list was not found in the new list. return true; } } return false; } void IncidenceAttendee::changeStatusForMe(KCalCore::Attendee::PartStat stat) { const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance(); Q_ASSERT(config); for (int i = 0; i < mDataModel->rowCount(); ++i) { QModelIndex index = mDataModel->index(i, AttendeeTableModel::Email); if (config->thatIsMe(mDataModel->data(index, Qt::DisplayRole).toString())) { index = mDataModel->index(i, AttendeeTableModel::Status); mDataModel->setData(index, stat); break; } } checkDirtyStatus(); } void IncidenceAttendee::acceptForMe() { changeStatusForMe(KCalCore::Attendee::Accepted); } void IncidenceAttendee::declineForMe() { changeStatusForMe(KCalCore::Attendee::Declined); } void IncidenceAttendee::fillOrganizerCombo() { mUi->mOrganizerCombo->clear(); const QStringList lst = IncidenceEditorNG::EditorConfig::instance()->fullEmails(); QStringList uniqueList; for (QStringList::ConstIterator it = lst.begin(), end = lst.end(); it != end; ++it) { if (!uniqueList.contains(*it)) { uniqueList << *it; } } mUi->mOrganizerCombo->addItems(uniqueList); } void IncidenceAttendee::checkIfExpansionIsNeeded(const KCalCore::Attendee::Ptr &attendee) { QString fullname = attendee->fullName(); // stop old job KJob *oldJob = mMightBeGroupJobs.key(attendee); if (oldJob != nullptr) { disconnect(oldJob); oldJob->deleteLater(); mMightBeGroupJobs.remove(oldJob); } mGroupList.remove(attendee); if (!fullname.isEmpty()) { Akonadi::ContactGroupSearchJob *job = new Akonadi::ContactGroupSearchJob(); job->setQuery(Akonadi::ContactGroupSearchJob::Name, fullname); connect(job, &Akonadi::ContactGroupSearchJob::result, this, &IncidenceAttendee::groupSearchResult); mMightBeGroupJobs.insert(job, attendee); } } void IncidenceAttendee::groupSearchResult(KJob *job) { Akonadi::ContactGroupSearchJob *searchJob = qobject_cast(job); Q_ASSERT(searchJob); Q_ASSERT(mMightBeGroupJobs.contains(job)); KCalCore::Attendee::Ptr attendee = mMightBeGroupJobs.take(job); const KContacts::ContactGroup::List contactGroups = searchJob->contactGroups(); if (contactGroups.isEmpty()) { updateGroupExpand(); return; // Nothing todo, probably a normal email address was entered } // TODO: Give the user the possibility to choose a group when there is more than one?! KContacts::ContactGroup group = contactGroups.first(); int row = dataModel()->attendees().indexOf(attendee); QModelIndex index = dataModel()->index(row, AttendeeTableModel::CuType); dataModel()->setData(index, KCalCore::Attendee::Group); mGroupList.insert(attendee, group); updateGroupExpand(); } void IncidenceAttendee::updateGroupExpand() { mUi->mGroupSubstitution->setEnabled(!mGroupList.isEmpty()); } void IncidenceAttendee::slotGroupSubstitutionPressed() { for (auto it = mGroupList.cbegin(), end = mGroupList.cend(); it != end; ++it) { const KCalCore::Attendee::Ptr attendee = it.key(); Akonadi::ContactGroupExpandJob *expandJob = new Akonadi::ContactGroupExpandJob(mGroupList.value( attendee), this); connect(expandJob, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult); mExpandGroupJobs.insert(expandJob, attendee); expandJob->start(); } } void IncidenceAttendee::expandResult(KJob *job) { Akonadi::ContactGroupExpandJob *expandJob = qobject_cast(job); Q_ASSERT(expandJob); Q_ASSERT(mExpandGroupJobs.contains(job)); KCalCore::Attendee::Ptr attendee = mExpandGroupJobs.take(job); int row = dataModel()->attendees().indexOf(attendee); const QString currentEmail = attendee->email(); const KContacts::Addressee::List groupMembers = expandJob->contacts(); bool wasACorrectEmail = false; for (const KContacts::Addressee &member : groupMembers) { if (member.preferredEmail() == currentEmail) { wasACorrectEmail = true; break; } } if (!wasACorrectEmail) { dataModel()->removeRow(row); for (const KContacts::Addressee &member : groupMembers) { KCalCore::Attendee::Ptr newAt(new KCalCore::Attendee( member.realName(), member.preferredEmail(), attendee->RSVP(), attendee->status(), attendee->role(), member.uid())); dataModel()->insertAttendee(row, newAt); } } } void IncidenceAttendee::slotSelectAddresses() { QSharedPointer d(new Akonadi::EmailAddressSelectionDialog( mParentWidget)); QWeakPointer dialog(d); dialog.data()->view()->view()->setSelectionMode(QAbstractItemView::ExtendedSelection); dialog.data()->setWindowTitle(i18n("Select Attendees")); if (dialog.data()->exec() == QDialog::Accepted) { Akonadi::EmailAddressSelectionDialog *dialogPtr = dialog.data(); if (dialogPtr) { const Akonadi::EmailAddressSelection::List list = dialogPtr->selectedAddresses(); for (const Akonadi::EmailAddressSelection &selection : list) { if (selection.item().hasPayload()) { Akonadi::ContactGroupExpandJob *job = new Akonadi::ContactGroupExpandJob( selection.item().payload(), this); connect(job, &Akonadi::ContactGroupExpandJob::result, this, &IncidenceAttendee::expandResult); KCalCore::Attendee::PartStat partStat = KCalCore::Attendee::NeedsAction; bool rsvp = true; int pos = 0; KCalCore::Attendee::Ptr newAt(new KCalCore::Attendee( selection.name(), selection.email(), rsvp, partStat, KCalCore::Attendee::ReqParticipant )); dataModel()->insertAttendee(pos, newAt); mExpandGroupJobs.insert(job, newAt); job->start(); } else { KContacts::Addressee contact; contact.setName(selection.name()); contact.insertEmail(selection.email()); if (selection.item().hasPayload()) { contact.setUid(selection.item().payload().uid()); } insertAttendeeFromAddressee(contact); } } } else { qCDebug(INCIDENCEEDITOR_LOG) << "dialog was already deleted"; } } if (dialog.data()) { dialog.data()->deleteLater(); } } void IncidenceEditorNG::IncidenceAttendee::slotSolveConflictPressed() { const int duration = mDateTime->startTime().secsTo(mDateTime->endTime()); QScopedPointer dialog(new SchedulingDialog(mDateTime->startDate(), mDateTime->startTime(), duration, mConflictResolver, mParentWidget)); dialog->slotUpdateIncidenceStartEnd(mDateTime->currentStartDateTime(), mDateTime->currentEndDateTime()); if (dialog->exec() == QDialog::Accepted) { qCDebug(INCIDENCEEDITOR_LOG) << dialog->selectedStartDate() << dialog->selectedStartTime(); if (dialog->selectedStartDate().isValid() && dialog->selectedStartTime().isValid()) { mDateTime->setStartDate(dialog->selectedStartDate()); mDateTime->setStartTime(dialog->selectedStartTime()); } } } void IncidenceAttendee::slotConflictResolverAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) { for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole). value(); if (mConflictResolver->containsAttendee(attendee)) { mConflictResolver->removeAttendee(attendee); } if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->insertAttendee(attendee); } } } checkDirtyStatus(); } void IncidenceAttendee::slotConflictResolverAttendeeAdded(const QModelIndex &index, int first, int last) { for (int i = first; i <= last; ++i) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index); if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->insertAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value< KCalCore::Attendee::Ptr>()); } } checkDirtyStatus(); } void IncidenceAttendee::slotConflictResolverAttendeeRemoved(const QModelIndex &index, int first, int last) { for (int i = first; i <= last; ++i) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email, index); if (!dataModel()->data(email).toString().isEmpty()) { mConflictResolver->removeAttendee(dataModel()->data(email, AttendeeTableModel::AttendeeRole).value< KCalCore::Attendee::Ptr>()); } } checkDirtyStatus(); } void IncidenceAttendee::slotConflictResolverLayoutChanged() { const KCalCore::Attendee::List attendees = mDataModel->attendees(); mConflictResolver->clearAttendees(); for (const KCalCore::Attendee::Ptr &attendee : attendees) { if (!attendee->email().isEmpty()) { mConflictResolver->insertAttendee(attendee); } } checkDirtyStatus(); } void IncidenceAttendee::slotFreeBusyAdded(const QModelIndex &parent, int first, int last) { // We are only interested in toplevel changes if (parent.isValid()) { return; } QAbstractItemModel *model = mConflictResolver->model(); for (int i = first; i <= last; ++i) { QModelIndex index = model->index(i, 0, parent); const KCalCore::Attendee::Ptr &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::slotFreeBusyChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { // We are only interested in toplevel changes if (topLeft.parent().isValid()) { return; } QAbstractItemModel *model = mConflictResolver->model(); for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { QModelIndex index = model->index(i, 0); const KCalCore::Attendee::Ptr &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::updateFBStatus() { QAbstractItemModel *model = mConflictResolver->model(); for (int i = 0; i < model->rowCount(); ++i) { QModelIndex index = model->index(i, 0); const KCalCore::Attendee::Ptr &attendee = model->data(index, CalendarSupport::FreeBusyItemModel::AttendeeRole).value(); const KCalCore::FreeBusy::Ptr &fb = model->data(index, CalendarSupport::FreeBusyItemModel::FreeBusyRole).value(); if (attendee) { updateFBStatus(attendee, fb); } } } void IncidenceAttendee::updateFBStatus(const KCalCore::Attendee::Ptr &attendee, const KCalCore::FreeBusy::Ptr &fb) { KCalCore::Attendee::List attendees = mDataModel->attendees(); QDateTime startTime = mDateTime->currentStartDateTime(); QDateTime endTime = mDateTime->currentEndDateTime(); if (attendees.contains(attendee)) { int row = dataModel()->attendees().indexOf(attendee); QModelIndex attendeeIndex = dataModel()->index(row, AttendeeTableModel::Available); if (fb) { KCalCore::Period::List busyPeriods = fb->busyPeriods(); for (auto it = busyPeriods.begin(); it != busyPeriods.end(); ++it) { // periods started before and laping into the incidence (s < startTime && e >= startTime) // periods starting in the time of incidende (s >= startTime && s <= endTime) if (((*it).start() < startTime && (*it).end() > startTime) || ((*it).start() >= startTime && (*it).start() <= endTime)) { switch (attendee->status()) { case KCalCore::Attendee::Accepted: dataModel()->setData(attendeeIndex, AttendeeTableModel::Accepted); return; default: dataModel()->setData(attendeeIndex, AttendeeTableModel::Busy); return; } } } dataModel()->setData(attendeeIndex, AttendeeTableModel::Free); } else { dataModel()->setData(attendeeIndex, AttendeeTableModel::Unknown); } } } void IncidenceAttendee::slotUpdateConflictLabel(int count) { if (attendeeCount() > 0) { mUi->mSolveButton->setEnabled(true); if (count > 0) { QString label = i18ncp("@label Shows the number of scheduling conflicts", "%1 conflict", "%1 conflicts", count); mUi->mConflictsLabel->setText(label); mUi->mConflictsLabel->setVisible(true); } else { mUi->mConflictsLabel->setVisible(false); } } else { mUi->mSolveButton->setEnabled(false); mUi->mConflictsLabel->setVisible(false); } } void IncidenceAttendee::slotGroupSubstitutionAttendeeChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { if (AttendeeTableModel::FullName <= bottomRight.column() && AttendeeTableModel::FullName >= topLeft.column()) { for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole). value(); checkIfExpansionIsNeeded(attendee); } } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionAttendeeAdded(const QModelIndex &index, int first, int last) { Q_UNUSED(index); for (int i = first; i <= last; ++i) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); checkIfExpansionIsNeeded(attendee); } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionAttendeeRemoved(const QModelIndex &index, int first, int last) { Q_UNUSED(index); for (int i = first; i <= last; ++i) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole).value(); KJob *job = mMightBeGroupJobs.key(attendee); if (job) { disconnect(job); job->deleteLater(); mMightBeGroupJobs.remove(job); } job = mExpandGroupJobs.key(attendee); if (job) { disconnect(job); job->deleteLater(); mExpandGroupJobs.remove(job); } mGroupList.remove(attendee); } updateGroupExpand(); } void IncidenceAttendee::slotGroupSubstitutionLayoutChanged() { for (auto it = mMightBeGroupJobs.cbegin(), end = mMightBeGroupJobs.cend(); it != end; ++it) { KJob *job = it.key(); disconnect(job); job->deleteLater(); } for (auto it = mExpandGroupJobs.cbegin(), end = mExpandGroupJobs.cend(); it != end; ++it) { KJob *job = it.key(); disconnect(job); job->deleteLater(); } mMightBeGroupJobs.clear(); mExpandGroupJobs.clear(); mGroupList.clear(); QAbstractItemModel *model = mUi->mAttendeeTable->model(); if (!model) { return; } for (int i = 0; i < model->rowCount(QModelIndex()); ++i) { QModelIndex index = model->index(i, AttendeeTableModel::FullName); if (!model->data(index).toString().isEmpty()) { QModelIndex email = dataModel()->index(i, AttendeeTableModel::Email); KCalCore::Attendee::Ptr attendee = dataModel()->data(email, AttendeeTableModel::AttendeeRole). value(); checkIfExpansionIsNeeded(attendee); } } updateGroupExpand(); } bool IncidenceAttendee::iAmOrganizer() const { if (mLoadedIncidence) { const IncidenceEditorNG::EditorConfig *config = IncidenceEditorNG::EditorConfig::instance(); return config->thatIsMe(mLoadedIncidence->organizer()->email()); } return true; } void IncidenceAttendee::insertAttendeeFromAddressee(const KContacts::Addressee &a, int pos /*=-1*/) { const bool sameAsOrganizer = mUi->mOrganizerCombo && KEmailAddress::compareEmail(a.preferredEmail(), mUi->mOrganizerCombo->currentText(), false); KCalCore::Attendee::PartStat partStat = KCalCore::Attendee::NeedsAction; bool rsvp = true; if (iAmOrganizer() && sameAsOrganizer) { partStat = KCalCore::Attendee::Accepted; rsvp = false; } KCalCore::Attendee::Ptr newAt(new KCalCore::Attendee(a.realName(), a.preferredEmail(), rsvp, partStat, KCalCore::Attendee::ReqParticipant, a.uid())); if (pos < 0) { pos = dataModel()->rowCount() - 1; } dataModel()->insertAttendee(pos, newAt); } void IncidenceAttendee::slotEventDurationChanged() { const QDateTime start = mDateTime->currentStartDateTime(); const QDateTime end = mDateTime->currentEndDateTime(); if (start >= end) { // This can happen, especially for todos. return; } - mConflictResolver->setEarliestDateTime(KCalCore::q2k(start)); - mConflictResolver->setLatestDateTime(KCalCore::q2k(end)); + mConflictResolver->setEarliestDateTime(start); + mConflictResolver->setLatestDateTime(end); updateFBStatus(); } void IncidenceAttendee::slotOrganizerChanged(const QString &newOrganizer) { if (KEmailAddress::compareEmail(newOrganizer, mOrganizer, false)) { return; } QString name; QString email; bool success = KEmailAddress::extractEmailAddressAndName(newOrganizer, email, name); if (!success) { qCWarning(INCIDENCEEDITOR_LOG) << "Could not extract email address and name"; return; } int currentOrganizerAttendee = -1; int newOrganizerAttendee = -1; for (int i = 0; i < mDataModel->rowCount(); ++i) { QModelIndex index = mDataModel->index(i, AttendeeTableModel::FullName); QString fullName = mDataModel->data(index, Qt::DisplayRole).toString(); if (fullName == mOrganizer) { currentOrganizerAttendee = i; } if (fullName == newOrganizer) { newOrganizerAttendee = i; } } int answer = KMessageBox::No; if (currentOrganizerAttendee > -1) { answer = KMessageBox::questionYesNo( mParentWidget, i18nc("@option", "You are changing the organizer of this event. " "Since the organizer is also attending this event, would you " "like to change the corresponding attendee as well?")); } else { answer = KMessageBox::Yes; } if (answer == KMessageBox::Yes) { if (currentOrganizerAttendee > -1) { mDataModel->removeRows(currentOrganizerAttendee, 1); } if (newOrganizerAttendee == -1) { bool rsvp = !iAmOrganizer(); // if it is the user, don't make him rsvp. KCalCore::Attendee::PartStat status = iAmOrganizer() ? KCalCore::Attendee::Accepted : KCalCore::Attendee::NeedsAction; KCalCore::Attendee::Ptr newAt( new KCalCore::Attendee(name, email, rsvp, status, KCalCore::Attendee::ReqParticipant)); mDataModel->insertAttendee(mDataModel->rowCount(), newAt); } } mOrganizer = newOrganizer; } AttendeeTableModel *IncidenceAttendee::dataModel() const { return mDataModel; } AttendeeComboBoxDelegate *IncidenceAttendee::responseDelegate() const { return mResponseDelegate; } AttendeeComboBoxDelegate *IncidenceAttendee::roleDelegate() const { return mRoleDelegate; } AttendeeComboBoxDelegate *IncidenceAttendee::stateDelegate() const { return mStateDelegate; } AttendeeLineEditDelegate *IncidenceAttendee::attendeeDelegate() const { return mAttendeeDelegate; } void IncidenceAttendee::filterLayoutChanged() { QHeaderView *headerView = mUi->mAttendeeTable->horizontalHeader(); headerView->setSectionResizeMode(AttendeeTableModel::Role, QHeaderView::ResizeToContents); headerView->setSectionResizeMode(AttendeeTableModel::FullName, QHeaderView::Stretch); headerView->setSectionResizeMode(AttendeeTableModel::Status, QHeaderView::ResizeToContents); headerView->setSectionResizeMode(AttendeeTableModel::Response, QHeaderView::ResizeToContents); headerView->setSectionHidden(AttendeeTableModel::CuType, true); headerView->setSectionHidden(AttendeeTableModel::Name, true); headerView->setSectionHidden(AttendeeTableModel::Email, true); headerView->setSectionHidden(AttendeeTableModel::Available, true); } void IncidenceAttendee::updateCount() { Q_EMIT attendeeCountChanged(attendeeCount()); checkDirtyStatus(); } int IncidenceAttendee::attendeeCount() const { int c = 0; QModelIndex index; QAbstractItemModel *model = mUi->mAttendeeTable->model(); if (!model) { return 0; } for (int i = 0; i < model->rowCount(QModelIndex()); ++i) { index = model->index(i, AttendeeTableModel::FullName); if (!model->data(index).toString().isEmpty()) { ++c; } } return c; } void IncidenceAttendee::setActions(KCalCore::Incidence::IncidenceType actions) { mStateDelegate->clear(); if (actions == KCalCore::Incidence::TypeEvent) { mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-attention")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-accepted")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-reject")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-attempt")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-delegate")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated)); } else { mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-attention")), KCalUtils::Stringify::attendeeStatus(AttendeeData::NeedsAction)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-accepted")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Accepted)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-reject")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Declined)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-attempt")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Tentative)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-delegate")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Delegated)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-complete")), KCalUtils::Stringify::attendeeStatus(AttendeeData::Completed)); mStateDelegate->addItem(QIcon::fromTheme(QStringLiteral("task-ongoing")), KCalUtils::Stringify::attendeeStatus(AttendeeData::InProcess)); } } void IncidenceAttendee::printDebugInfo() const { qCDebug(INCIDENCEEDITOR_LOG) << "I'm organizer : " << iAmOrganizer(); qCDebug(INCIDENCEEDITOR_LOG) << "Loaded organizer: " << mLoadedIncidence->organizer()->email(); if (iAmOrganizer()) { KCalCore::Event tmp; tmp.setOrganizer(mUi->mOrganizerCombo->currentText()); qCDebug(INCIDENCEEDITOR_LOG) << "Organizer combo: " << tmp.organizer()->email(); } const KCalCore::Attendee::List originalList = mLoadedIncidence->attendees(); KCalCore::Attendee::List newList; qCDebug(INCIDENCEEDITOR_LOG) << "List sizes: " << originalList.count() << newList.count(); foreach (KCalCore::Attendee::Ptr attendee, mDataModel->attendees()) { if (!attendee->fullName().isEmpty()) { newList.append(attendee); } } // Okay, again not the most efficient algorithm, but I'm assuming that in the // bulk of the use cases, the number of attendees is not much higher than 10 or so. foreach (const KCalCore::Attendee::Ptr &attendee, originalList) { bool found = false; for (int i = 0; i < newList.count(); ++i) { if (newList[i] == attendee) { newList.remove(i); found = true; break; } } if (!found) { qCDebug(INCIDENCEEDITOR_LOG) << "Attendee not found: " << attendee->email() << attendee->name() << attendee->status() << attendee->RSVP() << attendee->role() << attendee->uid() << attendee->cuType() << attendee->delegate() << attendee->delegator() << "; we have:"; for (int i = 0; i < newList.count(); ++i) { KCalCore::Attendee::Ptr attendee = newList[i]; qCDebug(INCIDENCEEDITOR_LOG) << "Attendee: " << attendee->email() << attendee->name() << attendee->status() << attendee->RSVP() << attendee->role() << attendee->uid() << attendee->cuType() << attendee->delegate() << attendee->delegator(); } return; } } }