diff --git a/src/agenda/agendaview.cpp b/src/agenda/agendaview.cpp index 2421e29..6222125 100644 --- a/src/agenda/agendaview.cpp +++ b/src/agenda/agendaview.cpp @@ -1,2360 +1,2366 @@ /* Copyright (c) 2001 Cornelius Schumacher Copyright (C) 2003-2004 Reinhold Kainhofer Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net Author: Kevin Krammer, krake@kdab.com Author: Sergio Martins, sergio.martins@kdab.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. As a special exception, permission is given to link this program with any edition of Qt, and distribute the resulting executable, without including the source code for Qt in the source distribution. */ #include "agendaview.h" #include "agenda.h" #include "agendaitem.h" #include "viewcalendar.h" #include "alternatelabel.h" #include "calendardecoration.h" #include "decorationlabel.h" #include "prefs.h" #include "timelabels.h" #include "timelabelszone.h" #include "calendarview_debug.h" #include #include #include #include #include #include #include #include #include // for SmallIcon() #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace EventViews; enum { SPACING = 2 }; enum { SHRINKDOWN = 2 // points less for the timezone font }; class Q_DECL_HIDDEN EventIndicator::Private { EventIndicator *const q; public: Private(EventIndicator *parent, EventIndicator::Location loc) : q(parent) , mColumns(1) , mLocation(loc) { mEnabled.resize(mColumns); QChar ch; // Dashed up and down arrow characters ch = QChar(mLocation == Top ? 0x21e1 : 0x21e3); QFont font = q->font(); font.setPointSize(KIconLoader::global()->currentSize(KIconLoader::Dialog)); QFontMetrics fm(font); QRect rect = fm.boundingRect(ch).adjusted(-2, -2, 2, 2); mPixmap = QPixmap(rect.size()); mPixmap.fill(Qt::transparent); QPainter p(&mPixmap); p.setOpacity(0.33); p.setFont(font); p.setPen(q->palette().text().color()); p.drawText(-rect.left(), -rect.top(), ch); } void adjustGeometry() { QRect rect; rect.setWidth(q->parentWidget()->width()); rect.setHeight(q->height()); rect.setLeft(0); - rect.setTop(mLocation == EventIndicator::Top - ? 0 - : q->parentWidget()->height() - rect.height()); + rect.setTop(mLocation == EventIndicator::Top ? + 0 : + q->parentWidget()->height() - rect.height()); q->setGeometry(rect); } public: int mColumns; Location mLocation; QPixmap mPixmap; QVector mEnabled; }; EventIndicator::EventIndicator(Location loc, QWidget *parent) : QFrame(parent) , d(new Private(this, loc)) { setAttribute(Qt::WA_TransparentForMouseEvents); setFixedHeight(d->mPixmap.height()); parent->installEventFilter(this); } EventIndicator::~EventIndicator() { delete d; } void EventIndicator::paintEvent(QPaintEvent *) { QPainter painter(this); const double cellWidth = static_cast(width()) / d->mColumns; const bool isRightToLeft = QApplication::isRightToLeft(); const uint pixmapOffset = isRightToLeft ? 0 : (cellWidth - d->mPixmap.width()); for (int i = 0; i < d->mColumns; ++i) { if (d->mEnabled[ i ]) { const int xOffset = (isRightToLeft ? (d->mColumns - 1 - i) : i) * cellWidth; painter.drawPixmap(xOffset + pixmapOffset, 0, d->mPixmap); } } } bool EventIndicator::eventFilter(QObject *, QEvent *event) { if (event->type() == QEvent::Resize) { d->adjustGeometry(); } return false; } void EventIndicator::changeColumns(int columns) { d->mColumns = columns; d->mEnabled.resize(d->mColumns); show(); raise(); update(); } void EventIndicator::enableColumn(int column, bool enable) { Q_ASSERT(column < d->mEnabled.count()); d->mEnabled[ column ] = enable; } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// class AgendaView::Private : public Akonadi::ETMCalendar::CalendarObserver { AgendaView *const q; public: explicit Private(AgendaView *parent, bool isInteractive, bool isSideBySide) : q(parent) , mTopDayLabels(nullptr) , mLayoutTopDayLabels(nullptr) , mTopDayLabelsFrame(nullptr) , mLayoutBottomDayLabels(nullptr) , mBottomDayLabels(nullptr) , mBottomDayLabelsFrame(nullptr) , mTimeBarHeaderFrame(nullptr) , mAllDayAgenda(nullptr) , mAgenda(nullptr) , mTimeLabelsZone(nullptr) , mAllowAgendaUpdate(true) , mUpdateItem(0) , mIsSideBySide(isSideBySide) , mDummyAllDayLeft(nullptr) , mUpdateAllDayAgenda(true) , mUpdateAgenda(true) , mIsInteractive(isInteractive) , mUpdateEventIndicatorsScheduled(false) , mViewCalendar(MultiViewCalendar::Ptr(new MultiViewCalendar())) { mViewCalendar->mAgendaView = q; mViewCalendar->setETMCalendar(q->calendar()); } public: // view widgets QGridLayout *mGridLayout = nullptr; QFrame *mTopDayLabels = nullptr; QBoxLayout *mLayoutTopDayLabels = nullptr; QFrame *mTopDayLabelsFrame = nullptr; QList mDateDayLabels; QBoxLayout *mLayoutBottomDayLabels = nullptr; QFrame *mBottomDayLabels = nullptr; QFrame *mBottomDayLabelsFrame = nullptr; QFrame *mAllDayFrame = nullptr; QWidget *mTimeBarHeaderFrame = nullptr; QSplitter *mSplitterAgenda = nullptr; QList mTimeBarHeaders; Agenda *mAllDayAgenda = nullptr; Agenda *mAgenda = nullptr; TimeLabelsZone *mTimeLabelsZone = nullptr; KCalCore::DateList mSelectedDates; // List of dates to be displayed KCalCore::DateList mSaveSelectedDates; // Save the list of dates between updateViews int mViewType; EventIndicator *mEventIndicatorTop = nullptr; EventIndicator *mEventIndicatorBottom = nullptr; QVector mMinY; QVector mMaxY; QVector mHolidayMask; QDateTime mTimeSpanBegin; QDateTime mTimeSpanEnd; bool mTimeSpanInAllDay; bool mAllowAgendaUpdate; Akonadi::Item mUpdateItem; const bool mIsSideBySide; QWidget *mDummyAllDayLeft = nullptr; bool mUpdateAllDayAgenda; bool mUpdateAgenda; bool mIsInteractive; bool mUpdateEventIndicatorsScheduled; // Contains days that have at least one all-day Event with TRANSP: OPAQUE (busy) // that has you as organizer or attendee so we can color background with a different // color QMap mBusyDays; EventViews::MultiViewCalendar::Ptr mViewCalendar; bool makesWholeDayBusy(const KCalCore::Incidence::Ptr &incidence) const; CalendarDecoration::Decoration *loadCalendarDecoration(const QString &name); void clearView(); void setChanges(EventView::Changes changes, const KCalCore::Incidence::Ptr &incidence = KCalCore::Incidence::Ptr()); /** Returns a list of consecutive dates, starting with @p start and ending with @p end. If either start or end are invalid, a list with QDate::currentDate() is returned */ static QList generateDateList(const QDate &start, const QDate &end); void changeColumns(int numColumns); AgendaItem::List agendaItems(const QString &uid) const; // insertAtDateTime is in the view's timezone void insertIncidence(const KCalCore::Incidence::Ptr &, const QDateTime &recurrenceId, const QDateTime &insertAtDateTime, bool createSelected); void reevaluateIncidence(const KCalCore::Incidence::Ptr &incidence); bool datesEqual(const KCalCore::Incidence::Ptr &one, const KCalCore::Incidence::Ptr &two) const; /** * Returns false if the incidence is for sure outside of the visible timespan. * Returns true if it might be, meaning that to be sure, timezones must be * taken into account. * This is a very fast way of discarding incidences that are outside of the * timespan and only performing expensive timezone operations on the ones * that might be viisble */ bool mightBeVisible(const KCalCore::Incidence::Ptr &incidence) const; protected: /* reimplemented from KCalCore::Calendar::CalendarObserver */ void calendarIncidenceAdded(const KCalCore::Incidence::Ptr &incidence) override; void calendarIncidenceChanged(const KCalCore::Incidence::Ptr &incidence) override; void calendarIncidenceDeleted(const KCalCore::Incidence::Ptr &incidence, const KCalCore::Calendar *calendar) override; private: //quiet --overloaded-virtual warning using KCalCore::Calendar::CalendarObserver::calendarIncidenceDeleted; }; bool AgendaView::Private::datesEqual(const KCalCore::Incidence::Ptr &one, const KCalCore::Incidence::Ptr &two) const { const auto start1 = one->dtStart(); const auto start2 = two->dtStart(); const auto end1 = one->dateTime(KCalCore::Incidence::RoleDisplayEnd); const auto end2 = two->dateTime(KCalCore::Incidence::RoleDisplayEnd); if (start1.isValid() ^ start2.isValid()) { return false; } if (end1.isValid() ^ end2.isValid()) { return false; } if (start1.isValid() && start1 != start2) { return false; } if (end1.isValid() && end1 != end2) { return false; } return true; } AgendaItem::List AgendaView::Private::agendaItems(const QString &uid) const { AgendaItem::List allDayAgendaItems = mAllDayAgenda->agendaItems(uid); return allDayAgendaItems.isEmpty() ? mAgenda->agendaItems(uid) : allDayAgendaItems; } bool AgendaView::Private::mightBeVisible(const KCalCore::Incidence::Ptr &incidence) const { KCalCore::Todo::Ptr todo = incidence.dynamicCast(); // KDateTime::toTimeSpec() is expensive, so lets first compare only the date, // to see if the incidence is visible. // If it's more than 48h of diff, then for sure it won't be visible, // independently of timezone. // The largest difference between two timezones is about 24 hours. if (todo && todo->isOverdue()) { // Don't optimize this case. Overdue to-dos have their own rules for displaying themselves return true; } if (!incidence->recurs()) { // If DTEND/DTDUE is before the 1st visible column - if (incidence->dateTime(KCalCore::Incidence::RoleEnd).date().daysTo(mSelectedDates.first()) - > 2) { + const QDate tdate = incidence->dateTime(KCalCore::Incidence::RoleEnd).date(); + if (tdate.daysTo(mSelectedDates.first()) > 2) { return false; } // if DTSTART is after the last visible column if (!todo && mSelectedDates.last().daysTo(incidence->dtStart().date()) > 2) { return false; } // if DTDUE is after the last visible column if (todo && mSelectedDates.last().daysTo(todo->dtDue().date()) > 2) { return false; } } return true; } void AgendaView::Private::changeColumns(int numColumns) { // mMinY, mMaxY and mEnabled must all have the same size. // Make sure you preserve this order because mEventIndicatorTop->changeColumns() // can trigger a lot of stuff, and code will be executed when mMinY wasn't resized yet. mMinY.resize(numColumns); mMaxY.resize(numColumns); mEventIndicatorTop->changeColumns(numColumns); mEventIndicatorBottom->changeColumns(numColumns); } /** static */ QList AgendaView::Private::generateDateList(const QDate &start, const QDate &end) { QList list; - if (start.isValid() && end.isValid() && end >= start - && start.daysTo(end) < AgendaView::MAX_DAY_COUNT) { + if (start.isValid() && + end.isValid() && + end >= start && + start.daysTo(end) < AgendaView::MAX_DAY_COUNT) { QDate date = start; list.reserve(start.daysTo(end) + 1); while (date <= end) { list.append(date); date = date.addDays(1); } } else { list.append(QDate::currentDate()); } return list; } void AgendaView::Private::reevaluateIncidence(const KCalCore::Incidence::Ptr &incidence) { if (!incidence || !mViewCalendar->isValid(incidence)) { qCWarning(CALENDARVIEW_LOG) << "invalid incidence or item not found." << incidence; return; } q->removeIncidence(incidence); q->displayIncidence(incidence, false); mAgenda->checkScrollBoundaries(); q->updateEventIndicators(); } void AgendaView::Private::calendarIncidenceAdded(const KCalCore::Incidence::Ptr &incidence) { if (!incidence || !mViewCalendar->isValid(incidence)) { qCCritical(CALENDARVIEW_LOG) << "AgendaView::Private::calendarIncidenceAdded() Invalid incidence or item:" << incidence; Q_ASSERT(false); return; } if (incidence->hasRecurrenceId() && mViewCalendar->isValid(incidence)) { // Reevaluate the main event instead, if it was inserted before this one KCalCore::Incidence::Ptr mainIncidence = q->calendar2(incidence)->incidence(incidence->uid()); if (mainIncidence) { reevaluateIncidence(mainIncidence); } } else if (q->displayIncidence(incidence, false)) { mAgenda->checkScrollBoundaries(); q->scheduleUpdateEventIndicators(); } } void AgendaView::Private::calendarIncidenceChanged(const KCalCore::Incidence::Ptr &incidence) { if (!incidence || incidence->uid().isEmpty()) { qCCritical(CALENDARVIEW_LOG) << "AgendaView::calendarIncidenceChanged() Invalid incidence or empty UID. " << incidence; Q_ASSERT(false); return; } AgendaItem::List agendaItems = this->agendaItems(incidence->uid()); if (agendaItems.isEmpty()) { qCWarning(CALENDARVIEW_LOG) << "AgendaView::calendarIncidenceChanged() Invalid agendaItem for incidence " << incidence->uid(); return; } // Optimization: If the dates didn't change, just repaint it. // This optimization for now because we need to process collisions between agenda items. if (false && !incidence->recurs() && agendaItems.count() == 1) { KCalCore::Incidence::Ptr originalIncidence = agendaItems.first()->incidence(); if (datesEqual(originalIncidence, incidence)) { for (const AgendaItem::QPtr &agendaItem : qAsConst(agendaItems)) { agendaItem->setIncidence(KCalCore::Incidence::Ptr(incidence->clone())); agendaItem->update(); } return; } } if (incidence->hasRecurrenceId() && mViewCalendar->isValid(incidence)) { // Reevaluate the main event instead, if it exists KCalCore::Incidence::Ptr mainIncidence = q->calendar2(incidence)->incidence(incidence->uid()); reevaluateIncidence(mainIncidence ? mainIncidence : incidence); } else { reevaluateIncidence(incidence); } // No need to call setChanges(), that triggers a fillAgenda() // setChanges(q->changes() | IncidencesEdited, incidence); } void AgendaView::Private::calendarIncidenceDeleted(const KCalCore::Incidence::Ptr &incidence, const KCalCore::Calendar *calendar) { Q_UNUSED(calendar); if (!incidence || incidence->uid().isEmpty()) { qCWarning(CALENDARVIEW_LOG) << "invalid incidence or empty uid: " << incidence; Q_ASSERT(false); return; } q->removeIncidence(incidence); if (incidence->hasRecurrenceId()) { // Reevaluate the main event, if it exists. The exception was removed so the main recurrent series // will no be bigger. if (mViewCalendar->isValid(incidence->uid())) { KCalCore::Incidence::Ptr mainIncidence = q->calendar2(incidence->uid())->incidence( incidence->uid()); if (mainIncidence) { reevaluateIncidence(mainIncidence); } } } else if (mightBeVisible(incidence)) { // No need to call setChanges(), that triggers a fillAgenda() // setChanges(q->changes() | IncidencesDeleted, CalendarSupport::incidence(incidence)); mAgenda->checkScrollBoundaries(); q->scheduleUpdateEventIndicators(); } } void EventViews::AgendaView::Private::setChanges(EventView::Changes changes, const KCalCore::Incidence::Ptr &incidence) { // We could just call EventView::setChanges(...) but we're going to do a little // optimization. If only an all day item was changed, only all day agenda // should be updated. // all bits = 1 const int ones = ~0; const int incidenceOperations = IncidencesAdded | IncidencesEdited | IncidencesDeleted; // If changes has a flag turned on, other than incidence operations, then update both agendas if ((ones ^ incidenceOperations) & changes) { mUpdateAllDayAgenda = true; mUpdateAgenda = true; } else if (incidence) { mUpdateAllDayAgenda = mUpdateAllDayAgenda | incidence->allDay(); mUpdateAgenda = mUpdateAgenda | !incidence->allDay(); } q->EventView::setChanges(changes); } void AgendaView::Private::clearView() { if (mUpdateAllDayAgenda) { mAllDayAgenda->clear(); } if (mUpdateAgenda) { mAgenda->clear(); } mBusyDays.clear(); } void AgendaView::Private::insertIncidence(const KCalCore::Incidence::Ptr &incidence, const QDateTime &recurrenceId, const QDateTime &insertAtDateTime, bool createSelected) { if (!q->filterByCollectionSelection(incidence)) { return; } KCalCore::Event::Ptr event = CalendarSupport::event(incidence); KCalCore::Todo::Ptr todo = CalendarSupport::todo(incidence); const QDate insertAtDate = insertAtDateTime.date(); // In case incidence->dtStart() isn't visible (crosses bounderies) const int curCol = qMax(mSelectedDates.first().daysTo(insertAtDate), qint64(0)); // The date for the event is not displayed, just ignore it if (curCol >= mSelectedDates.count()) { return; } if (mMinY.count() <= curCol) { mMinY.resize(mSelectedDates.count()); } if (mMaxY.count() <= curCol) { mMaxY.resize(mSelectedDates.count()); } // Default values, which can never be reached mMinY[curCol] = mAgenda->timeToY(QTime(23, 59)) + 1; mMaxY[curCol] = mAgenda->timeToY(QTime(0, 0)) - 1; int beginX; int endX; if (event) { const QDate firstVisibleDate = mSelectedDates.first(); // its crossing bounderies, lets calculate beginX and endX const int duration = event->dtStart().toLocalTime().daysTo(event->dtEnd()); if (insertAtDate < firstVisibleDate) { beginX = curCol + firstVisibleDate.daysTo(insertAtDate); endX = beginX + duration; } else { beginX = curCol; endX = beginX + duration; } } else if (todo) { if (!todo->hasDueDate()) { return; // todo shall not be displayed if it has no date } beginX = endX = curCol; } else { return; } const QDate today = QDate::currentDate(); if (todo && todo->isOverdue() && today >= insertAtDate) { mAllDayAgenda->insertAllDayItem(incidence, recurrenceId, curCol, curCol, createSelected); } else if (incidence->allDay()) { mAllDayAgenda->insertAllDayItem(incidence, recurrenceId, beginX, endX, createSelected); } else if (event && event->isMultiDay(QTimeZone::systemTimeZone())) { // TODO: We need a better isMultiDay(), one that receives the occurrence. // In the single-day handling code there's a neat comment on why // we're calculating the start time this way const QTime startTime = insertAtDateTime.time(); // In the single-day handling code there's a neat comment on why we use the // duration instead of fetching the end time directly const int durationOfFirstOccurrence = event->dtStart().secsTo(event->dtEnd()); QTime endTime = startTime.addSecs(durationOfFirstOccurrence); const int startY = mAgenda->timeToY(startTime); if (endTime == QTime(0, 0, 0)) { endTime = QTime(23, 59, 59); } const int endY = mAgenda->timeToY(endTime) - 1; if ((beginX <= 0 && curCol == 0) || beginX == curCol) { mAgenda->insertMultiItem(incidence, recurrenceId, beginX, endX, startY, endY, createSelected); } if (beginX == curCol) { mMaxY[curCol] = mAgenda->timeToY(QTime(23, 59)); if (startY < mMinY[curCol]) { mMinY[curCol] = startY; } } else if (endX == curCol) { mMinY[curCol] = mAgenda->timeToY(QTime(0, 0)); if (endY > mMaxY[curCol]) { mMaxY[curCol] = endY; } } else { mMinY[curCol] = mAgenda->timeToY(QTime(0, 0)); mMaxY[curCol] = mAgenda->timeToY(QTime(23, 59)); } } else { int startY = 0, endY = 0; if (event) { // Single day events fall here // Don't use event->dtStart().toTimeSpec(timeSpec).time(). // If it's a UTC recurring event it should have a different time when it crosses DST, // so we must use insertAtDate here, so we get the correct time. // // The nth occurrence doesn't always have the same time as the 1st occurrence. const QTime startTime = insertAtDateTime.time(); // We could just fetch the end time directly from dtEnd() instead of adding a duration to the // start time. This way is best because it preserves the duration of the event. There are some // corner cases where the duration would be messed up, for example a UTC event that when // converted to local has dtStart() in day light saving time, but dtEnd() outside DST. // It could create events with 0 duration. const int durationOfFirstOccurrence = event->dtStart().secsTo(event->dtEnd()); QTime endTime = startTime.addSecs(durationOfFirstOccurrence); startY = mAgenda->timeToY(startTime); if (endTime == QTime(0, 0, 0)) { endTime = QTime(23, 59, 59); } endY = mAgenda->timeToY(endTime) - 1; } if (todo) { QTime t; if (todo->recurs()) { // The time we get depends on the insertAtDate, because of daylight savings changes const QDateTime ocurrrenceDateTime = QDateTime(insertAtDate, todo->dtDue().time(), todo->dtDue().timeZone()); t = ocurrrenceDateTime.toLocalTime().time(); } else { t = todo->dtDue().toLocalTime().time(); } if (t == QTime(0, 0) && !todo->recurs()) { // To-dos due at 00h00 are drawn at the previous day and ending at // 23h59. For recurring to-dos, that's not being done because it wasn't // implemented yet in ::fillAgenda(). t = QTime(23, 59); } const int halfHour = 1800; if (t.addSecs(-halfHour) < t) { startY = mAgenda->timeToY(t.addSecs(-halfHour)); endY = mAgenda->timeToY(t) - 1; } else { startY = 0; endY = mAgenda->timeToY(t.addSecs(halfHour)) - 1; } } if (endY < startY) { endY = startY; } mAgenda->insertItem(incidence, recurrenceId, curCol, startY, endY, 1, 1, createSelected); if (startY < mMinY[curCol]) { mMinY[curCol] = startY; } if (endY > mMaxY[curCol]) { mMaxY[curCol] = endY; } } } //////////////////////////////////////////////////////////////////////////// AgendaView::AgendaView(const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide, QWidget *parent) : EventView(parent) , d(new Private(this, isInteractive, isSideBySide)) { init(start, end); } AgendaView::AgendaView(const PrefsPtr &prefs, const QDate &start, const QDate &end, bool isInteractive, bool isSideBySide, QWidget *parent) : EventView(parent) , d(new Private(this, isInteractive, isSideBySide)) { setPreferences(prefs); init(start, end); } void AgendaView::init(const QDate &start, const QDate &end) { d->mSelectedDates = Private::generateDateList(start, end); d->mGridLayout = new QGridLayout(this); d->mGridLayout->setMargin(0); /* Create agenda splitter */ d->mSplitterAgenda = new QSplitter(Qt::Vertical, this); d->mGridLayout->addWidget(d->mSplitterAgenda, 1, 0); /* Create day name labels for agenda columns */ d->mTopDayLabelsFrame = new QFrame(d->mSplitterAgenda); auto layout = new QHBoxLayout(d->mTopDayLabelsFrame); layout->setMargin(0); layout->setSpacing(SPACING); /* Create all-day agenda widget */ d->mAllDayFrame = new QFrame(d->mSplitterAgenda); auto allDayFrameLayout = new QHBoxLayout(d->mAllDayFrame); allDayFrameLayout->setMargin(0); allDayFrameLayout->setSpacing(SPACING); // Alignment and description widgets if (!d->mIsSideBySide) { d->mTimeBarHeaderFrame = new QFrame(d->mAllDayFrame); allDayFrameLayout->addWidget(d->mTimeBarHeaderFrame); auto timeBarHeaderFrameLayout = new QHBoxLayout(d->mTimeBarHeaderFrame); timeBarHeaderFrameLayout->setMargin(0); timeBarHeaderFrameLayout->setSpacing(0); d->mDummyAllDayLeft = new QWidget(d->mAllDayFrame); allDayFrameLayout->addWidget(d->mDummyAllDayLeft); } // The widget itself AgendaScrollArea *allDayScrollArea = new AgendaScrollArea(true, this, d->mIsInteractive, d->mAllDayFrame); allDayFrameLayout->addWidget(allDayScrollArea); d->mAllDayAgenda = allDayScrollArea->agenda(); /* Create the main agenda widget and the related widgets */ QWidget *agendaFrame = new QWidget(d->mSplitterAgenda); QHBoxLayout *agendaLayout = new QHBoxLayout(agendaFrame); agendaLayout->setMargin(0); agendaLayout->setSpacing(SPACING); // Create agenda AgendaScrollArea *scrollArea = new AgendaScrollArea(false, this, d->mIsInteractive, agendaFrame); d->mAgenda = scrollArea->agenda(); // Create event indicator bars d->mEventIndicatorTop = new EventIndicator(EventIndicator::Top, scrollArea->viewport()); d->mEventIndicatorBottom = new EventIndicator(EventIndicator::Bottom, scrollArea->viewport()); // Create time labels d->mTimeLabelsZone = new TimeLabelsZone(this, preferences(), d->mAgenda); // This timeLabelsZoneLayout is for adding some spacing // to align timelabels, to agenda's grid QVBoxLayout *timeLabelsZoneLayout = new QVBoxLayout(); agendaLayout->addLayout(timeLabelsZoneLayout); agendaLayout->addWidget(scrollArea); timeLabelsZoneLayout->addSpacing(scrollArea->frameWidth()); timeLabelsZoneLayout->addWidget(d->mTimeLabelsZone); timeLabelsZoneLayout->addSpacing(scrollArea->frameWidth()); // Scrolling connect(d->mAgenda, &Agenda::zoomView, this, &AgendaView::zoomView); // Event indicator updates connect(d->mAgenda, &Agenda::lowerYChanged, this, &AgendaView::updateEventIndicatorTop); connect(d->mAgenda, &Agenda::upperYChanged, this, &AgendaView::updateEventIndicatorBottom); if (d->mIsSideBySide) { d->mTimeLabelsZone->hide(); } /* Create a frame at the bottom which may be used by decorations */ d->mBottomDayLabelsFrame = new QFrame(d->mSplitterAgenda); layout = new QHBoxLayout(d->mBottomDayLabelsFrame); layout->setMargin(0); layout->setSpacing(SPACING); if (!d->mIsSideBySide) { /* Make the all-day and normal agendas line up with each other */ int margin = style()->pixelMetric(QStyle::PM_ScrollBarExtent); if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { // Needed for some styles. Oxygen needs it, Plastique does not. margin -= scrollArea->frameWidth(); } d->mAllDayFrame->layout()->addItem(new QSpacerItem(margin, 0)); } updateTimeBarWidth(); // Don't call it now, bottom agenda isn't fully up yet #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(this, &AgendaView::alignAgendas, Qt::QueuedConnection); #else QMetaObject::invokeMethod(this, "alignAgendas", Qt::QueuedConnection); #endif // Whoever changes this code, remember to leave createDayLabels() // inside the ctor, so it's always called before readSettings(), so // readSettings() works on the splitter that has the right amount of // widgets (createDayLabels() via placeDecorationFrame() removes widgets). createDayLabels(true); /* Connect the agendas */ connect(d->mAllDayAgenda, &Agenda::newTimeSpanSignal, this, &AgendaView::newTimeSpanSelectedAllDay); connect(d->mAgenda, &Agenda::newTimeSpanSignal, this, &AgendaView::newTimeSpanSelected); connectAgenda(d->mAgenda, d->mAllDayAgenda); connectAgenda(d->mAllDayAgenda, d->mAgenda); } AgendaView::~AgendaView() { foreach (const ViewCalendar::Ptr &cal, d->mViewCalendar->mSubCalendars) { if (cal->getCalendar()) { cal->getCalendar()->unregisterObserver(d); } } delete d; } KCalCore::Calendar::Ptr AgendaView::calendar2(const KCalCore::Incidence::Ptr &incidence) const { return d->mViewCalendar->findCalendar(incidence)->getCalendar(); } KCalCore::Calendar::Ptr AgendaView::calendar2(const QString &incidenceIdentifier) const { return d->mViewCalendar->findCalendar(incidenceIdentifier)->getCalendar(); } void AgendaView::setCalendar(const Akonadi::ETMCalendar::Ptr &cal) { if (calendar()) { calendar()->unregisterObserver(d); } Q_ASSERT(cal); EventView::setCalendar(cal); calendar()->registerObserver(d); d->mViewCalendar->setETMCalendar(cal); d->mAgenda->setCalendar(d->mViewCalendar); d->mAllDayAgenda->setCalendar(d->mViewCalendar); } void AgendaView::addCalendar(const ViewCalendar::Ptr &cal) { d->mViewCalendar->addCalendar(cal); cal->getCalendar()->registerObserver(d); } void AgendaView::connectAgenda(Agenda *agenda, Agenda *otherAgenda) { connect(agenda, &Agenda::showNewEventPopupSignal, this, &AgendaView::showNewEventPopupSignal); connect(agenda, &Agenda::showIncidencePopupSignal, this, &AgendaView::slotShowIncidencePopup); agenda->setCalendar(d->mViewCalendar); connect(agenda, SIGNAL(newEventSignal()), SIGNAL(newEventSignal())); connect(agenda, &Agenda::newStartSelectSignal, otherAgenda, &Agenda::clearSelection); connect(agenda, &Agenda::newStartSelectSignal, this, &AgendaView::timeSpanSelectionChanged); connect(agenda, &Agenda::editIncidenceSignal, this, &AgendaView::slotEditIncidence); connect(agenda, &Agenda::showIncidenceSignal, this, &AgendaView::slotShowIncidence); connect(agenda, &Agenda::deleteIncidenceSignal, this, &AgendaView::slotDeleteIncidence); // drag signals - connect(agenda, &Agenda::startDragSignal, this, [this](const KCalCore::Incidence::Ptr &ptr) { startDrag(ptr); }); + connect(agenda, &Agenda::startDragSignal, + this, [this](const KCalCore::Incidence::Ptr &ptr) { startDrag(ptr); }); // synchronize selections connect(agenda, &Agenda::incidenceSelected, otherAgenda, &Agenda::deselectItem); connect(agenda, &Agenda::incidenceSelected, this, &AgendaView::slotIncidenceSelected); // rescheduling of todos by d'n'd connect(agenda, SIGNAL(droppedIncidences(KCalCore::Incidence::List,QPoint,bool)), SLOT(slotIncidencesDropped(KCalCore::Incidence::List,QPoint,bool))); connect(agenda, SIGNAL(droppedIncidences(QList,QPoint,bool)), SLOT(slotIncidencesDropped(QList,QPoint,bool))); } void AgendaView::slotIncidenceSelected(const KCalCore::Incidence::Ptr &incidence, const QDate &date) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT incidenceSelected(item, date); } } void AgendaView::slotShowIncidencePopup(const KCalCore::Incidence::Ptr &incidence, const QDate &date) { Akonadi::Item item = d->mViewCalendar->item(incidence); //qDebug() << "wanna see the popup for " << incidence->uid() << item.id(); if (item.isValid()) { Q_EMIT showIncidencePopupSignal(item, date); } } void AgendaView::slotShowIncidence(const KCalCore::Incidence::Ptr &incidence) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT showIncidenceSignal(item); } } void AgendaView::slotEditIncidence(const KCalCore::Incidence::Ptr &incidence) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT editIncidenceSignal(item); } } void AgendaView::slotDeleteIncidence(const KCalCore::Incidence::Ptr &incidence) { Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { Q_EMIT deleteIncidenceSignal(item); } } void AgendaView::zoomInVertically() { if (!d->mIsSideBySide) { preferences()->setHourSize(preferences()->hourSize() + 1); } d->mAgenda->updateConfig(); d->mAgenda->checkScrollBoundaries(); d->mTimeLabelsZone->updateAll(); setChanges(changes() | ZoomChanged); updateView(); } void AgendaView::zoomOutVertically() { if (preferences()->hourSize() > 4 || d->mIsSideBySide) { if (!d->mIsSideBySide) { preferences()->setHourSize(preferences()->hourSize() - 1); } d->mAgenda->updateConfig(); d->mAgenda->checkScrollBoundaries(); d->mTimeLabelsZone->updateAll(); setChanges(changes() | ZoomChanged); updateView(); } } void AgendaView::zoomInHorizontally(const QDate &date) { QDate begin; QDate newBegin; QDate dateToZoom = date; int ndays, count; begin = d->mSelectedDates.first(); ndays = begin.daysTo(d->mSelectedDates.last()); // zoom with Action and are there a selected Incidence?, Yes, I zoom in to it. if (!dateToZoom.isValid()) { dateToZoom = d->mAgenda->selectedIncidenceDate(); } if (!dateToZoom.isValid()) { if (ndays > 1) { newBegin = begin.addDays(1); count = ndays - 1; Q_EMIT zoomViewHorizontally(newBegin, count); } } else { if (ndays <= 2) { newBegin = dateToZoom; count = 1; } else { newBegin = dateToZoom.addDays(-ndays / 2 + 1); count = ndays - 1; } Q_EMIT zoomViewHorizontally(newBegin, count); } } void AgendaView::zoomOutHorizontally(const QDate &date) { QDate begin; QDate newBegin; QDate dateToZoom = date; int ndays, count; begin = d->mSelectedDates.first(); ndays = begin.daysTo(d->mSelectedDates.last()); // zoom with Action and are there a selected Incidence?, Yes, I zoom out to it. if (!dateToZoom.isValid()) { dateToZoom = d->mAgenda->selectedIncidenceDate(); } if (!dateToZoom.isValid()) { newBegin = begin.addDays(-1); count = ndays + 3; } else { newBegin = dateToZoom.addDays(-ndays / 2 - 1); count = ndays + 3; } if (abs(count) >= 31) { qCDebug(CALENDARVIEW_LOG) << "change to the month view?"; } else { //We want to center the date Q_EMIT zoomViewHorizontally(newBegin, count); } } void AgendaView::zoomView(const int delta, const QPoint &pos, const Qt::Orientation orient) { // TODO find out why this is necessary. seems to be some kind of performance hack static QDate zoomDate; static QTimer *t = new QTimer(this); //Zoom to the selected incidence, on the other way // zoom to the date on screen after the first mousewheel move. if (orient == Qt::Horizontal) { const QDate date = d->mAgenda->selectedIncidenceDate(); if (date.isValid()) { zoomDate = date; } else { if (!t->isActive()) { zoomDate = d->mSelectedDates[ pos.x() ]; } t->setSingleShot(true); t->start(1000); } if (delta > 0) { zoomOutHorizontally(zoomDate); } else { zoomInHorizontally(zoomDate); } } else { // Vertical zoom const QPoint posConstentsOld = d->mAgenda->gridToContents(pos); if (delta > 0) { zoomOutVertically(); } else { zoomInVertically(); } const QPoint posConstentsNew = d->mAgenda->gridToContents(pos); d->mAgenda->verticalScrollBar()->scroll(0, posConstentsNew.y() - posConstentsOld.y()); } } #ifndef EVENTVIEWS_NODECOS bool AgendaView::loadDecorations(const QStringList &decorations, DecorationList &decoList) { for (const QString &decoName : decorations) { if (preferences()->selectedPlugins().contains(decoName)) { CalendarDecoration::Decoration *deco = d->loadCalendarDecoration(decoName); if (deco != nullptr) { decoList << deco; } } } return !decoList.isEmpty(); } void AgendaView::placeDecorationsFrame(QFrame *frame, bool decorationsFound, bool isTop) { if (decorationsFound) { if (isTop) { // inserts in the first position d->mSplitterAgenda->insertWidget(0, frame); } else { // inserts in the last position frame->setParent(d->mSplitterAgenda); } } else { frame->setParent(this); d->mGridLayout->addWidget(frame, 0, 0); } } void AgendaView::placeDecorations(DecorationList &decoList, const QDate &date, QWidget *labelBox, bool forWeek) { foreach (CalendarDecoration::Decoration *deco, decoList) { CalendarDecoration::Element::List elements; elements = forWeek ? deco->weekElements(date) : deco->dayElements(date); if (!elements.isEmpty()) { auto decoHBox = new QFrame(labelBox); labelBox->layout()->addWidget(decoHBox); auto layout = new QHBoxLayout(decoHBox); layout->setSpacing(0); layout->setMargin(0); decoHBox->setFrameShape(QFrame::StyledPanel); decoHBox->setMinimumWidth(1); foreach (CalendarDecoration::Element *it, elements) { DecorationLabel *label = new DecorationLabel(it); label->setAlignment(Qt::AlignBottom); label->setMinimumWidth(1); layout->addWidget(label); } } } } #endif void AgendaView::createDayLabels(bool force) { // Check if mSelectedDates has changed, if not just return // Removes some flickering and gains speed (since this is called by each updateView()) if (!force && d->mSaveSelectedDates == d->mSelectedDates) { return; } d->mSaveSelectedDates = d->mSelectedDates; delete d->mTopDayLabels; delete d->mBottomDayLabels; d->mDateDayLabels.clear(); QFontMetrics fm = fontMetrics(); d->mTopDayLabels = new QFrame(d->mTopDayLabelsFrame); d->mTopDayLabelsFrame->layout()->addWidget(d->mTopDayLabels); static_cast(d->mTopDayLabelsFrame->layout())->setStretchFactor(d->mTopDayLabels, 1); d->mLayoutTopDayLabels = new QHBoxLayout(d->mTopDayLabels); d->mLayoutTopDayLabels->setMargin(0); d->mLayoutTopDayLabels->setSpacing(1); // this spacer moves the day labels over to line up with the day columns QSpacerItem *spacer - = new QSpacerItem((!d->mIsSideBySide ? d->mTimeLabelsZone->width() : 0) - +SPACING - +d->mAllDayAgenda->scrollArea()->frameWidth(), + = new QSpacerItem((!d->mIsSideBySide ? d->mTimeLabelsZone->width() : 0) + + SPACING + + d->mAllDayAgenda->scrollArea()->frameWidth(), 1, QSizePolicy::Fixed); d->mLayoutTopDayLabels->addSpacerItem(spacer); auto topWeekLabelBox = new QFrame(d->mTopDayLabels); auto topWeekLabelBoxLayout = new QVBoxLayout(topWeekLabelBox); topWeekLabelBoxLayout->setMargin(0); topWeekLabelBoxLayout->setSpacing(0); d->mLayoutTopDayLabels->addWidget(topWeekLabelBox); if (d->mIsSideBySide) { topWeekLabelBox->hide(); } d->mBottomDayLabels = new QFrame(d->mBottomDayLabelsFrame); d->mBottomDayLabelsFrame->layout()->addWidget(d->mBottomDayLabels); static_cast(d->mBottomDayLabelsFrame->layout())->setStretchFactor( d->mBottomDayLabels, 1); d->mLayoutBottomDayLabels = new QHBoxLayout(d->mBottomDayLabels); d->mLayoutBottomDayLabels->setMargin(0); auto bottomWeekLabelBox = new QFrame(d->mBottomDayLabels); auto bottomWeekLabelBoxLayout = new QVBoxLayout(bottomWeekLabelBox); bottomWeekLabelBoxLayout->setMargin(0); bottomWeekLabelBoxLayout->setSpacing(0); d->mLayoutBottomDayLabels->addWidget(bottomWeekLabelBox); #ifndef EVENTVIEWS_NODECOS QList topDecos; QStringList topStrDecos = preferences()->decorationsAtAgendaViewTop(); placeDecorationsFrame(d->mTopDayLabelsFrame, loadDecorations(topStrDecos, topDecos), true); QList botDecos; QStringList botStrDecos = preferences()->decorationsAtAgendaViewBottom(); placeDecorationsFrame(d->mBottomDayLabelsFrame, loadDecorations(botStrDecos, botDecos), false); #endif Q_FOREACH (const QDate &date, d->mSelectedDates) { auto topDayLabelBox = new QFrame(d->mTopDayLabels); auto topDayLabelBoxLayout = new QVBoxLayout(topDayLabelBox); topDayLabelBoxLayout->setMargin(0); topDayLabelBoxLayout->setSpacing(0); d->mLayoutTopDayLabels->addWidget(topDayLabelBox); auto bottomDayLabelBox = new QFrame(d->mBottomDayLabels); auto bottomDayLabelBoxLayout = new QVBoxLayout(bottomDayLabelBox); bottomDayLabelBoxLayout->setMargin(0); bottomDayLabelBoxLayout->setSpacing(0); d->mLayoutBottomDayLabels->addWidget(bottomDayLabelBox); int dW = date.dayOfWeek(); QString veryLongStr = QLocale::system().toString(date, QLocale::LongFormat); QString longstr = i18nc("short_weekday short_monthname date (e.g. Mon Aug 13)", "%1 %2 %3", QLocale::system().dayName(dW, QLocale::ShortFormat), QDate::shortMonthName(date.month()), date.day()); QString shortstr = QString::number(date.day()); AlternateLabel *dayLabel = new AlternateLabel(shortstr, longstr, veryLongStr, topDayLabelBox); topDayLabelBoxLayout->addWidget(dayLabel); dayLabel->useShortText(); // will be recalculated in updateDayLabelSizes() anyway dayLabel->setAlignment(Qt::AlignHCenter); if (date == QDate::currentDate()) { QFont font = dayLabel->font(); font.setBold(true); dayLabel->setFont(font); } d->mDateDayLabels.append(dayLabel); // if a holiday region is selected, show the holiday name const QStringList texts = CalendarSupport::holiday(date); for (const QString &text : texts) { // Compute a small version of the holiday string for AlternateLabel const KWordWrap ww = KWordWrap::formatText(fm, topDayLabelBox->rect(), 0, text, -1); AlternateLabel *label = new AlternateLabel( ww.truncatedString(), text, text, topDayLabelBox); topDayLabelBoxLayout->addWidget(label); label->setAlignment(Qt::AlignCenter); } #ifndef EVENTVIEWS_NODECOS // Day decoration labels placeDecorations(topDecos, date, topDayLabelBox, false); placeDecorations(botDecos, date, bottomDayLabelBox, false); #endif } QSpacerItem *rightSpacer = new QSpacerItem(d->mAllDayAgenda->scrollArea()->frameWidth(), 1, QSizePolicy::Fixed); d->mLayoutTopDayLabels->addSpacerItem(rightSpacer); #ifndef EVENTVIEWS_NODECOS // Week decoration labels placeDecorations(topDecos, d->mSelectedDates.first(), topWeekLabelBox, true); placeDecorations(botDecos, d->mSelectedDates.first(), bottomWeekLabelBox, true); #endif if (!d->mIsSideBySide) { d->mLayoutTopDayLabels->addSpacing(d->mAgenda->verticalScrollBar()->width()); d->mLayoutBottomDayLabels->addSpacing(d->mAgenda->verticalScrollBar()->width()); } d->mTopDayLabels->show(); d->mBottomDayLabels->show(); // Update the labels now and after a single event loop run. Now to avoid flicker, and // delayed so that the delayed layouting size is taken into account. updateDayLabelSizes(); } void AgendaView::updateDayLabelSizes() { // First, calculate the maximum text type that fits for all labels AlternateLabel::TextType overallType = AlternateLabel::Extensive; for (AlternateLabel *label : qAsConst(d->mDateDayLabels)) { AlternateLabel::TextType type = label->largestFittingTextType(); if (type < overallType) { overallType = type; } } // Then, set that maximum text type to all the labels foreach (AlternateLabel *label, d->mDateDayLabels) { label->setFixedType(overallType); } } void AgendaView::resizeEvent(QResizeEvent *resizeEvent) { updateDayLabelSizes(); EventView::resizeEvent(resizeEvent); } void AgendaView::enableAgendaUpdate(bool enable) { d->mAllowAgendaUpdate = enable; } int AgendaView::currentDateCount() const { return d->mSelectedDates.count(); } Akonadi::Item::List AgendaView::selectedIncidences() const { Akonadi::Item::List selected; KCalCore::Incidence::Ptr agendaitem = d->mAgenda->selectedIncidence(); if (agendaitem) { selected.append(d->mViewCalendar->item(agendaitem)); } KCalCore::Incidence::Ptr dayitem = d->mAllDayAgenda->selectedIncidence(); if (dayitem) { selected.append(d->mViewCalendar->item(dayitem)); } return selected; } KCalCore::DateList AgendaView::selectedIncidenceDates() const { KCalCore::DateList selected; QDate qd; qd = d->mAgenda->selectedIncidenceDate(); if (qd.isValid()) { selected.append(qd); } qd = d->mAllDayAgenda->selectedIncidenceDate(); if (qd.isValid()) { selected.append(qd); } return selected; } bool AgendaView::eventDurationHint(QDateTime &startDt, QDateTime &endDt, bool &allDay) const { if (selectionStart().isValid()) { QDateTime start = selectionStart(); QDateTime end = selectionEnd(); if (start.secsTo(end) == 15 * 60) { // One cell in the agenda view selected, e.g. // because of a double-click, => Use the default duration QTime defaultDuration(CalendarSupport::KCalPrefs::instance()->defaultDuration().time()); int addSecs = (defaultDuration.hour() * 3600) + (defaultDuration.minute() * 60); end = start.addSecs(addSecs); } startDt = start; endDt = end; allDay = selectedIsAllDay(); return true; } return false; } /** returns if only a single cell is selected, or a range of cells */ bool AgendaView::selectedIsSingleCell() const { if (!selectionStart().isValid() || !selectionEnd().isValid()) { return false; } if (selectedIsAllDay()) { int days = selectionStart().daysTo(selectionEnd()); return days < 1; } else { int secs = selectionStart().secsTo(selectionEnd()); return secs <= 24 * 60 * 60 / d->mAgenda->rows(); } } void AgendaView::updateView() { fillAgenda(); } /* Update configuration settings for the agenda view. This method is not complete. */ void AgendaView::updateConfig() { // Agenda can be null if setPreferences() is called inside the ctor // We don't need to update anything in this case. if (d->mAgenda && d->mAllDayAgenda) { d->mAgenda->updateConfig(); d->mAllDayAgenda->updateConfig(); d->mTimeLabelsZone->setPreferences(preferences()); d->mTimeLabelsZone->updateAll(); updateTimeBarWidth(); setHolidayMasks(); createDayLabels(true); setChanges(changes() | ConfigChanged); updateView(); } } void AgendaView::createTimeBarHeaders() { qDeleteAll(d->mTimeBarHeaders); d->mTimeBarHeaders.clear(); const QFont oldFont(font()); QFont labelFont = d->mTimeLabelsZone->preferences()->agendaTimeLabelsFont(); labelFont.setPointSize(labelFont.pointSize() - SHRINKDOWN); foreach (QScrollArea *area, d->mTimeLabelsZone->timeLabels()) { TimeLabels *timeLabel = static_cast(area->widget()); QLabel *label = new QLabel(timeLabel->header().replace(QLatin1Char('/'), QStringLiteral("/ ")), d->mTimeBarHeaderFrame); d->mTimeBarHeaderFrame->layout()->addWidget(label); label->setFont(labelFont); label->setAlignment(Qt::AlignBottom | Qt::AlignRight); label->setMargin(0); label->setWordWrap(true); label->setToolTip(timeLabel->headerToolTip()); d->mTimeBarHeaders.append(label); } setFont(oldFont); } void AgendaView::updateTimeBarWidth() { if (d->mIsSideBySide) { return; } createTimeBarHeaders(); const QFont oldFont(font()); QFont labelFont = d->mTimeLabelsZone->preferences()->agendaTimeLabelsFont(); labelFont.setPointSize(labelFont.pointSize() - SHRINKDOWN); QFontMetrics fm(labelFont); int width = d->mTimeLabelsZone->preferedTimeLabelsWidth(); for (QLabel *l : qAsConst(d->mTimeBarHeaders)) { foreach (const QString &word, l->text().split(QLatin1Char(' '))) { width = qMax(width, fm.width(word)); } } setFont(oldFont); width = width + fm.width(QLatin1Char('/')); const int timeBarWidth = width * d->mTimeBarHeaders.count(); d->mTimeBarHeaderFrame->setFixedWidth(timeBarWidth - SPACING); d->mTimeLabelsZone->setFixedWidth(timeBarWidth); if (d->mDummyAllDayLeft) { d->mDummyAllDayLeft->setFixedWidth(0); } } void AgendaView::updateEventDates(AgendaItem *item, bool addIncidence, Akonadi::Collection::Id collectionId) { qCDebug(CALENDARVIEW_LOG) << item->text() << "; item->cellXLeft(): " << item->cellXLeft() << "; item->cellYTop(): " << item->cellYTop() << "; item->lastMultiItem(): " << item->lastMultiItem() << "; item->itemPos(): " << item->itemPos() << "; item->itemCount(): " << item->itemCount(); QDateTime startDt, endDt; // Start date of this incidence, calculate the offset from it // (so recurring and non-recurring items can be treated exactly the same, // we never need to check for recurs(), because we only move the start day // by the number of days the agenda item was really moved. Smart, isn't it?) QDate thisDate; if (item->cellXLeft() < 0) { thisDate = (d->mSelectedDates.first()).addDays(item->cellXLeft()); } else { thisDate = d->mSelectedDates[ item->cellXLeft() ]; } int daysOffset = 0; // daysOffset should only be calculated if item->cellXLeft() is positive which doesn't happen // if the event's start isn't visible. if (item->cellXLeft() >= 0) { daysOffset = item->occurrenceDate().daysTo(thisDate); } int daysLength = 0; // startDt.setDate(startDate); KCalCore::Incidence::Ptr incidence = item->incidence(); Akonadi::Item aitem = d->mViewCalendar->item(incidence); if ((!aitem.isValid() && !addIncidence) || !incidence || !changer()) { qCWarning(CALENDARVIEW_LOG) << "changer is " << changer() << " and incidence is " << incidence.data(); return; } QTime startTime(0, 0, 0), endTime(0, 0, 0); if (incidence->allDay()) { daysLength = item->cellWidth() - 1; } else { startTime = d->mAgenda->gyToTime(item->cellYTop()); if (item->lastMultiItem()) { endTime = d->mAgenda->gyToTime(item->lastMultiItem()->cellYBottom() + 1); daysLength = item->lastMultiItem()->cellXLeft() - item->cellXLeft(); } else if (item->itemPos() == item->itemCount() && item->itemCount() > 1) { /* multiitem handling in agenda assumes two things: - The start (first KOAgendaItem) is always visible. - The first KOAgendaItem of the incidence has a non-null item->lastMultiItem() pointing to the last KOagendaItem. But those aren't always met, for example when in day-view. kolab/issue4417 */ // Cornercase 1: - Resizing the end of the event but the start isn't visible endTime = d->mAgenda->gyToTime(item->cellYBottom() + 1); daysLength = item->itemCount() - 1; startTime = incidence->dtStart().time(); } else if (item->itemPos() == 1 && item->itemCount() > 1) { // Cornercase 2: - Resizing the start of the event but the end isn't visible endTime = incidence->dateTime(KCalCore::Incidence::RoleEnd).time(); daysLength = item->itemCount() - 1; } else { endTime = d->mAgenda->gyToTime(item->cellYBottom() + 1); } } // FIXME: use a visitor here if (const KCalCore::Event::Ptr ev = CalendarSupport::event(incidence)) { startDt = incidence->dtStart(); // convert to calendar timespec because we then manipulate it // with time coming from the calendar startDt = startDt.toLocalTime(); startDt = startDt.addDays(daysOffset); if (!incidence->allDay()) { startDt.setTime(startTime); } endDt = startDt.addDays(daysLength); if (!incidence->allDay()) { endDt.setTime(endTime); } if (incidence->dtStart().toLocalTime() == startDt && ev->dtEnd().toLocalTime() == endDt) { // No change QTimer::singleShot(0, this, &AgendaView::updateView); return; } } else if (const KCalCore::Todo::Ptr td = CalendarSupport::todo(incidence)) { startDt = td->hasStartDate() ? td->dtStart() : td->dtDue(); // convert to calendar timespec because we then manipulate it with time coming from // the calendar startDt = startDt.toLocalTime(); startDt.setDate(thisDate.addDays(td->dtDue().daysTo(startDt))); if (!td->allDay()) { startDt.setTime(startTime); } endDt = startDt; endDt.setDate(thisDate); if (!td->allDay()) { endDt.setTime(endTime); } if (td->dtDue().toLocalTime() == endDt) { // No change #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(this, &AgendaView::updateView, Qt::QueuedConnection); #else QMetaObject::invokeMethod(this, "updateView", Qt::QueuedConnection); #endif return; } } // A commented code block which had 150 lines to adjust recurrence was here. // I deleted it in rev 1180272 to make this function readable. if (const KCalCore::Event::Ptr ev = CalendarSupport::event(incidence)) { /* setDtEnd() must be called before setDtStart(), otherwise, when moving * events, CalendarLocal::incidenceUpdated() will not remove the old hash * and that causes the event to be shown in the old date also (bug #179157). * * TODO: We need a better hashing mechanism for CalendarLocal. */ ev->setDtEnd( endDt.toTimeSpec(incidence->dateTime(KCalCore::Incidence::RoleEnd).timeSpec())); incidence->setDtStart(startDt.toTimeSpec(incidence->dtStart().timeSpec())); } else if (const KCalCore::Todo::Ptr td = CalendarSupport::todo(incidence)) { if (td->hasStartDate()) { td->setDtStart(startDt.toTimeSpec(incidence->dtStart().timeSpec())); } td->setDtDue(endDt.toTimeSpec(td->dtDue().timeSpec())); } if (!incidence->hasRecurrenceId()) { item->setOccurrenceDateTime(startDt); } bool result; if (addIncidence) { Akonadi::Collection collection = calendar()->collection(collectionId); result = changer()->createIncidence(incidence, collection, this) != -1; } else { KCalCore::Incidence::Ptr oldIncidence(CalendarSupport::incidence(aitem)); aitem.setPayload(incidence); result = changer()->modifyIncidence(aitem, oldIncidence, this) != -1; } // Update the view correctly if an agenda item move was aborted by // cancelling one of the subsequent dialogs. if (!result) { setChanges(changes() | IncidencesEdited); #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(this, &AgendaView::updateView, Qt::QueuedConnection); #else QMetaObject::invokeMethod(this, "updateView", Qt::QueuedConnection); #endif return; } // don't update the agenda as the item already has the correct coordinates. // an update would delete the current item and recreate it, but we are still // using a pointer to that item! => CRASH enableAgendaUpdate(false); // We need to do this in a timer to make sure we are not deleting the item // we are currently working on, which would lead to crashes // Only the actually moved agenda item is already at the correct position and mustn't be // recreated. All others have to!!! if (incidence->recurs() || incidence->hasRecurrenceId()) { d->mUpdateItem = aitem; #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(this, &AgendaView::updateView, Qt::QueuedConnection); #else QMetaObject::invokeMethod(this, "updateView", Qt::QueuedConnection); #endif } enableAgendaUpdate(true); } QDate AgendaView::startDate() const { if (d->mSelectedDates.isEmpty()) { return QDate(); } return d->mSelectedDates.first(); } QDate AgendaView::endDate() const { if (d->mSelectedDates.isEmpty()) { return QDate(); } return d->mSelectedDates.last(); } void AgendaView::showDates(const QDate &start, const QDate &end, const QDate &preferredMonth) { Q_UNUSED(preferredMonth); - if (!d->mSelectedDates.isEmpty() - && d->mSelectedDates.first() == start - && d->mSelectedDates.last() == end) { + if (!d->mSelectedDates.isEmpty() && + d->mSelectedDates.first() == start && + d->mSelectedDates.last() == end) { return; } - if (!start.isValid() || !end.isValid() || start > end - || start.daysTo(end) > MAX_DAY_COUNT) { + if (!start.isValid() || + !end.isValid() || + start > end || + start.daysTo(end) > MAX_DAY_COUNT) { qCWarning(CALENDARVIEW_LOG) << "got bizare parameters: " << start << end << " - aborting here"; return; } d->mSelectedDates = d->generateDateList(start, end); // and update the view setChanges(changes() | DatesChanged); fillAgenda(); d->mTimeLabelsZone->update(); } void AgendaView::showIncidences(const Akonadi::Item::List &incidences, const QDate &date) { Q_UNUSED(date); if (!calendar()) { qCCritical(CALENDARVIEW_LOG) << "No Calendar set"; return; } // we must check if they are not filtered; if they are, remove the filter KCalCore::CalFilter *filter = calendar()->filter(); bool wehaveall = true; if (filter) { Q_FOREACH (const Akonadi::Item &aitem, incidences) { if (!(wehaveall = filter->filterIncidence(CalendarSupport::incidence(aitem)))) { break; } } } if (!wehaveall) { calendar()->setFilter(nullptr); } QDateTime start = CalendarSupport::incidence(incidences.first())->dtStart().toLocalTime(); QDateTime end = CalendarSupport::incidence(incidences.first())->dateTime( KCalCore::Incidence::RoleEnd).toLocalTime(); Akonadi::Item first = incidences.first(); Q_FOREACH (const Akonadi::Item &aitem, incidences) { if (CalendarSupport::incidence(aitem)->dtStart().toLocalTime() < start) { first = aitem; } start = qMin(start, CalendarSupport::incidence(aitem)->dtStart().toLocalTime()); end = qMax(start, CalendarSupport::incidence(aitem)->dateTime( KCalCore::Incidence::RoleEnd).toLocalTime()); } end.toTimeZone(start.timeZone()); // allow direct comparison of dates if (start.date().daysTo(end.date()) + 1 <= currentDateCount()) { showDates(start.date(), end.date()); } else { showDates(start.date(), start.date().addDays(currentDateCount() - 1)); } d->mAgenda->selectItem(first); } void AgendaView::fillAgenda() { if (changes() == NothingChanged) { return; } if (d->mViewCalendar->calendars() == 0) { qCWarning(CALENDARVIEW_LOG) << "No calendar is set"; return; } /* qCDebug(CALENDARVIEW_LOG) << "changes = " << changes() << "; mUpdateAgenda = " << d->mUpdateAgenda << "; mUpdateAllDayAgenda = " << d->mUpdateAllDayAgenda; */ /* Remember the item Ids of the selected items. In case one of the * items was deleted and re-added, we want to reselect it. */ const QString selectedAgendaId = d->mAgenda->lastSelectedItemUid(); const QString selectedAllDayAgendaId = d->mAllDayAgenda->lastSelectedItemUid(); enableAgendaUpdate(true); d->clearView(); if (changes().testFlag(DatesChanged)) { d->mAllDayAgenda->changeColumns(d->mSelectedDates.count()); d->mAgenda->changeColumns(d->mSelectedDates.count()); d->changeColumns(d->mSelectedDates.count()); createDayLabels(false); setHolidayMasks(); d->mAgenda->setDateList(d->mSelectedDates); } setChanges(NothingChanged); bool somethingReselected = false; const KCalCore::Incidence::List incidences = d->mViewCalendar->incidences(); foreach (const KCalCore::Incidence::Ptr &incidence, incidences) { Q_ASSERT(incidence); - const bool wasSelected = incidence->uid() == selectedAgendaId - || incidence->uid() == selectedAllDayAgendaId; + const bool wasSelected = (incidence->uid() == selectedAgendaId) || + (incidence->uid() == selectedAllDayAgendaId); - if ((incidence->allDay() && d->mUpdateAllDayAgenda) - || (!incidence->allDay() && d->mUpdateAgenda)) { + if ((incidence->allDay() && d->mUpdateAllDayAgenda) || + (!incidence->allDay() && d->mUpdateAgenda)) { displayIncidence(incidence, wasSelected); } if (wasSelected) { somethingReselected = true; } } d->mAgenda->checkScrollBoundaries(); updateEventIndicators(); // mAgenda->viewport()->update(); // mAllDayAgenda->viewport()->update(); // make invalid deleteSelectedDateTime(); d->mUpdateAgenda = false; d->mUpdateAllDayAgenda = false; if (!somethingReselected) { Q_EMIT incidenceSelected(Akonadi::Item(), QDate()); } } bool AgendaView::displayIncidence(const KCalCore::Incidence::Ptr &incidence, bool createSelected) { if (!incidence || incidence->hasRecurrenceId()) { return false; } KCalCore::Todo::Ptr todo = CalendarSupport::todo(incidence); if (todo && (!preferences()->showTodosAgendaView() || !todo->hasDueDate())) { return false; } KCalCore::Event::Ptr event = CalendarSupport::event(incidence); const QDate today = QDate::currentDate(); QDateTime firstVisibleDateTime(d->mSelectedDates.first(), QTime(0, 0, 0), Qt::LocalTime); QDateTime lastVisibleDateTime(d->mSelectedDates.last(), QTime(23, 59, 59, 999), Qt::LocalTime); // Optimization, very cheap operation that discards incidences that aren't in the timespan if (!d->mightBeVisible(incidence)) { return false; } lastVisibleDateTime.setTime(QTime(23, 59, 59, 59)); firstVisibleDateTime.setTime(QTime(0, 0)); KCalCore::SortableList dateTimeList; const QDateTime incDtStart = incidence->dtStart().toLocalTime(); const QDateTime incDtEnd = incidence->dateTime(KCalCore::Incidence::RoleEnd).toLocalTime(); bool alreadyAddedToday = false; if (incidence->recurs()) { // timed incidences occur in [dtStart(), dtEnd()[ // all-day incidences occur in [dtStart(), dtEnd()] // so we subtract 1 second in the timed case const int secsToAdd = incidence->allDay() ? 0 : -1; const int eventDuration = event ? incDtStart.daysTo(incDtEnd.addSecs(secsToAdd)) : 0; // if there's a multiday event that starts before firstVisibleDateTime but ends after // lets include it. timesInInterval() ignores incidences that aren't totaly inside // the range const QDateTime startDateTimeWithOffset = firstVisibleDateTime.addDays(-eventDuration); KCalCore::OccurrenceIterator rIt(*calendar(), incidence, startDateTimeWithOffset, lastVisibleDateTime); while (rIt.hasNext()) { rIt.next(); const auto occurrenceDate = rIt.occurrenceStartDate().toLocalTime(); const bool makesDayBusy = preferences()->colorAgendaBusyDays() && makesWholeDayBusy(rIt.incidence()); if (makesDayBusy) { KCalCore::Event::List &busyEvents = d->mBusyDays[occurrenceDate.date()]; busyEvents.append(event); } if (occurrenceDate.date() == today) { alreadyAddedToday = true; } d->insertIncidence(rIt.incidence(), rIt.recurrenceId(), occurrenceDate, createSelected); } } else { QDateTime dateToAdd; // date to add to our date list - QDateTime incidenceStart; QDateTime incidenceEnd; if (todo && todo->hasDueDate() && !todo->isOverdue()) { // If it's not overdue it will be shown at the original date (not today) dateToAdd = todo->dtDue().toLocalTime(); // To-dos are drawn with the bottom of the rectangle at dtDue // if dtDue is at 00:00, then it should be displayed in the previous day, at 23:59 if (dateToAdd.time() == QTime(0, 0)) { dateToAdd = dateToAdd.addSecs(-1); } incidenceEnd = dateToAdd; } else if (event) { dateToAdd = incDtStart; incidenceEnd = incDtEnd; } if (dateToAdd.isValid() && incidence->allDay()) { // so comparisons with < > actually work dateToAdd.setTime(QTime(0, 0)); incidenceEnd.setTime(QTime(23, 59, 59, 59)); } if (dateToAdd <= lastVisibleDateTime && incidenceEnd > firstVisibleDateTime) { dateTimeList += dateToAdd; } } // ToDo items shall be displayed today if they are overdue const QDateTime dateTimeToday = QDateTime(today, QTime(0, 0), Qt::LocalTime); - if (todo - && todo->isOverdue() - && dateTimeToday >= firstVisibleDateTime - && dateTimeToday <= lastVisibleDateTime) { + if (todo && + todo->isOverdue() && + dateTimeToday >= firstVisibleDateTime && + dateTimeToday <= lastVisibleDateTime) { /* If there's a recurring instance showing up today don't add "today" again * we don't want the event to appear duplicated */ if (!alreadyAddedToday) { dateTimeList += dateTimeToday; } } const bool makesDayBusy = preferences()->colorAgendaBusyDays() && makesWholeDayBusy(incidence); for (auto t = dateTimeList.begin(); t != dateTimeList.end(); ++t) { if (makesDayBusy) { KCalCore::Event::List &busyEvents = d->mBusyDays[(*t).date()]; busyEvents.append(event); } d->insertIncidence(incidence, t->toLocalTime(), t->toLocalTime(), createSelected); } // Can be multiday if (event && makesDayBusy && event->isMultiDay()) { const QDate lastVisibleDate = d->mSelectedDates.last(); for (QDate date = event->dtStart().date(); date <= event->dtEnd().date() && date <= lastVisibleDate; date = date.addDays(1)) { KCalCore::Event::List &busyEvents = d->mBusyDays[date]; busyEvents.append(event); } } return !dateTimeList.isEmpty(); } void AgendaView::updateEventIndicatorTop(int newY) { for (int i = 0; i < d->mMinY.size(); ++i) { d->mEventIndicatorTop->enableColumn(i, newY > d->mMinY[i]); } d->mEventIndicatorTop->update(); } void AgendaView::updateEventIndicatorBottom(int newY) { for (int i = 0; i < d->mMaxY.size(); ++i) { d->mEventIndicatorBottom->enableColumn(i, newY <= d->mMaxY[i]); } d->mEventIndicatorBottom->update(); } void AgendaView::slotIncidencesDropped(const QList &items, const QPoint &gpos, bool allDay) { Q_UNUSED(items); Q_UNUSED(gpos); Q_UNUSED(allDay); -#ifdef AKONADI_PORT_DISABLED // one item -> multiple items, Incidence* -> akonadi item url (we might have to fetch the items here first!) +#ifdef AKONADI_PORT_DISABLED // one item -> multiple items, Incidence* -> akonadi item url + // (we might have to fetch the items here first!) if (gpos.x() < 0 || gpos.y() < 0) { return; } const QDate day = d->mSelectedDates[gpos.x()]; const QTime time = d->mAgenda->gyToTime(gpos.y()); KDateTime newTime(day, time, preferences()->timeSpec()); newTime.setDateOnly(allDay); Todo::Ptr todo = CalendarSupport::todo(todoItem); if (todo && dynamic_cast(calendar())) { const Akonadi::Item existingTodoItem = calendar()->itemForIncidence(calendar()->todo(todo->uid())); if (Todo::Ptr existingTodo = CalendarSupport::todo(existingTodoItem)) { qCDebug(CALENDARVIEW_LOG) << "Drop existing Todo"; Todo::Ptr oldTodo(existingTodo->clone()); if (changer()) { existingTodo->setDtDue(newTime); existingTodo->setAllDay(allDay); changer()->modifyIncidence(existingTodoItem, oldTodo, this); } else { KMessageBox::sorry(this, i18n("Unable to modify this to-do, " "because it cannot be locked.")); } } else { qCDebug(CALENDARVIEW_LOG) << "Drop new Todo"; todo->setDtDue(newTime); todo->setAllDay(allDay); if (!changer()->addIncidence(todo, this)) { KMessageBox::sorry(this, i18n("Unable to save %1 \"%2\".", i18n(todo->type()), todo->summary())); } } } #else qCDebug(CALENDARVIEW_LOG) << "AKONADI PORT: Disabled code in " << Q_FUNC_INFO; #endif } void AgendaView::slotIncidencesDropped(const KCalCore::Incidence::List &incidences, const QPoint &gpos, bool allDay) { if (gpos.x() < 0 || gpos.y() < 0) { return; } const QDate day = d->mSelectedDates[gpos.x()]; QDateTime newTime(day, {}, Qt::LocalTime); Q_FOREACH (const KCalCore::Incidence::Ptr &incidence, incidences) { const Akonadi::Item existingItem = calendar()->item(incidence); - const bool existsInSameCollection = existingItem.isValid() - && (existingItem.storageCollectionId() - == collectionId() || collectionId() == -1); + const bool existsInSameCollection = + existingItem.isValid() && + (existingItem.storageCollectionId() == collectionId() || collectionId() == -1); if (existingItem.isValid() && existsInSameCollection) { - KCalCore::Incidence::Ptr newIncidence - = existingItem.payload(); + KCalCore::Incidence::Ptr newIncidence = + existingItem.payload(); KCalCore::Incidence::Ptr oldIncidence(newIncidence->clone()); if (newIncidence->dtStart() == newTime && newIncidence->allDay() == allDay) { // Nothing changed continue; } newIncidence->setAllDay(allDay); newIncidence->setDateTime(newTime, KCalCore::Incidence::RoleDnD); changer()->modifyIncidence(existingItem, oldIncidence, this); } else { // Create a new one // The drop came from another application create a new incidence incidence->setDateTime(newTime, KCalCore::Incidence::RoleDnD); incidence->setAllDay(allDay); incidence->setUid(KCalCore::CalFormat::createUniqueId()); Akonadi::Collection collection(collectionId()); const bool added = -1 != changer()->createIncidence(incidence, collection, this); if (added) { // TODO: make async if (existingItem.isValid()) { // Dragged from one agenda to another, delete origin changer()->deleteIncidence(existingItem); } } } } } void AgendaView::startDrag(const KCalCore::Incidence::Ptr &incidence) { if (!calendar()) { qCCritical(CALENDARVIEW_LOG) << "No Calendar set"; return; } const Akonadi::Item item = d->mViewCalendar->item(incidence); if (item.isValid()) { startDrag(item); } } void AgendaView::startDrag(const Akonadi::Item &incidence) { if (!calendar()) { qCCritical(CALENDARVIEW_LOG) << "No Calendar set"; return; } if (QDrag *drag = CalendarSupport::createDrag(incidence, this)) { drag->exec(); } } void AgendaView::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); readSettings(config.data()); } void AgendaView::readSettings(const KConfig *config) { const KConfigGroup group = config->group("Views"); const QList sizes = group.readEntry("Separator AgendaView", QList()); // the size depends on the number of plugins used // we don't want to read invalid/corrupted settings or else agenda becomes invisible if (sizes.count() >= 2 && !sizes.contains(0)) { d->mSplitterAgenda->setSizes(sizes); updateConfig(); } } void AgendaView::writeSettings(KConfig *config) { KConfigGroup group = config->group("Views"); QList list = d->mSplitterAgenda->sizes(); group.writeEntry("Separator AgendaView", list); } QVector AgendaView::busyDayMask() const { if (d->mSelectedDates.isEmpty() || !d->mSelectedDates[0].isValid()) { return QVector(); } QVector busyDayMask; busyDayMask.resize(d->mSelectedDates.count()); for (int i = 0; i < d->mSelectedDates.count(); ++i) { busyDayMask[i] = !d->mBusyDays[d->mSelectedDates[i]].isEmpty(); } return busyDayMask; } void AgendaView::setHolidayMasks() { if (d->mSelectedDates.isEmpty() || !d->mSelectedDates[0].isValid()) { return; } d->mHolidayMask.resize(d->mSelectedDates.count() + 1); const QList workDays = CalendarSupport::workDays(d->mSelectedDates.first().addDays(-1), d->mSelectedDates.last()); for (int i = 0; i < d->mSelectedDates.count(); ++i) { d->mHolidayMask[i] = !workDays.contains(d->mSelectedDates[ i ]); } // Store the information about the day before the visible area (needed for // overnight working hours) in the last bit of the mask: bool showDay = !workDays.contains(d->mSelectedDates[ 0 ].addDays(-1)); d->mHolidayMask[ d->mSelectedDates.count() ] = showDay; d->mAgenda->setHolidayMask(&d->mHolidayMask); d->mAllDayAgenda->setHolidayMask(&d->mHolidayMask); } void AgendaView::clearSelection() { d->mAgenda->deselectItem(); d->mAllDayAgenda->deselectItem(); } void AgendaView::newTimeSpanSelectedAllDay(const QPoint &start, const QPoint &end) { newTimeSpanSelected(start, end); d->mTimeSpanInAllDay = true; } void AgendaView::newTimeSpanSelected(const QPoint &start, const QPoint &end) { if (!d->mSelectedDates.count()) { return; } d->mTimeSpanInAllDay = false; const QDate dayStart = d->mSelectedDates[ qBound(0, start.x(), (int)d->mSelectedDates.size() - 1) ]; const QDate dayEnd = d->mSelectedDates[ qBound(0, end.x(), (int)d->mSelectedDates.size() - 1) ]; const QTime timeStart = d->mAgenda->gyToTime(start.y()); const QTime timeEnd = d->mAgenda->gyToTime(end.y() + 1); d->mTimeSpanBegin = QDateTime(dayStart, timeStart); d->mTimeSpanEnd = QDateTime(dayEnd, timeEnd); } QDateTime AgendaView::selectionStart() const { return d->mTimeSpanBegin; } QDateTime AgendaView::selectionEnd() const { return d->mTimeSpanEnd; } bool AgendaView::selectedIsAllDay() const { return d->mTimeSpanInAllDay; } void AgendaView::deleteSelectedDateTime() { d->mTimeSpanBegin.setDate(QDate()); d->mTimeSpanEnd.setDate(QDate()); d->mTimeSpanInAllDay = false; } void AgendaView::removeIncidence(const KCalCore::Incidence::Ptr &incidence) { // Don't wrap this in a if (incidence->isAllDay) because whe all day // property might have changed d->mAllDayAgenda->removeIncidence(incidence); d->mAgenda->removeIncidence(incidence); if (!incidence->hasRecurrenceId() && d->mViewCalendar->isValid(incidence->uid())) { // Deleted incidence is an main incidence // Delete all exceptions as well KCalCore::Incidence::List exceptions = calendar2(incidence->uid())->instances(incidence); foreach (const KCalCore::Incidence::Ptr &exception, exceptions) { if (exception->allDay()) { d->mAllDayAgenda->removeIncidence(exception); } else { d->mAgenda->removeIncidence(exception); } } } } void AgendaView::updateEventIndicators() { d->mUpdateEventIndicatorsScheduled = false; d->mMinY = d->mAgenda->minContentsY(); d->mMaxY = d->mAgenda->maxContentsY(); d->mAgenda->checkScrollBoundaries(); updateEventIndicatorTop(d->mAgenda->visibleContentsYMin()); updateEventIndicatorBottom(d->mAgenda->visibleContentsYMax()); } void AgendaView::setIncidenceChanger(Akonadi::IncidenceChanger *changer) { EventView::setIncidenceChanger(changer); d->mAgenda->setIncidenceChanger(changer); d->mAllDayAgenda->setIncidenceChanger(changer); } void AgendaView::clearTimeSpanSelection() { d->mAgenda->clearSelection(); d->mAllDayAgenda->clearSelection(); deleteSelectedDateTime(); } Agenda *AgendaView::agenda() const { return d->mAgenda; } Agenda *AgendaView::allDayAgenda() const { return d->mAllDayAgenda; } QSplitter *AgendaView::splitter() const { return d->mSplitterAgenda; } bool AgendaView::filterByCollectionSelection(const KCalCore::Incidence::Ptr &incidence) { const Akonadi::Item item = d->mViewCalendar->item(incidence); if (!item.isValid()) { return true; } if (customCollectionSelection()) { return customCollectionSelection()->contains(item.parentCollection().id()); } if (collectionId() < 0) { return true; } else { return collectionId() == item.storageCollectionId(); } } void AgendaView::alignAgendas() { // resize dummy widget so the allday agenda lines up with the hourly agenda. if (d->mDummyAllDayLeft) { - d->mDummyAllDayLeft->setFixedWidth(-SPACING + d->mTimeLabelsZone->width() - -d->mTimeBarHeaderFrame->width()); + d->mDummyAllDayLeft->setFixedWidth(d->mTimeLabelsZone->width() - + d->mTimeBarHeaderFrame->width() - + SPACING); } // Must be async, so they are centered createDayLabels(true); } CalendarDecoration::Decoration *AgendaView::Private::loadCalendarDecoration(const QString &name) { const QString type = CalendarSupport::Plugin::serviceType(); const int version = CalendarSupport::Plugin::interfaceVersion(); QString constraint; if (version >= 0) { constraint = QStringLiteral("[X-KDE-PluginInterfaceVersion] == %1").arg(QString::number(version)); } KService::List list = KServiceTypeTrader::self()->query(type, constraint); KService::List::ConstIterator it; for (it = list.constBegin(); it != list.constEnd(); ++it) { if ((*it)->desktopEntryName() == name) { KService::Ptr service = *it; KPluginLoader loader(*service); auto factory = loader.instance(); if (!factory) { qCDebug(CALENDARVIEW_LOG) << "Factory creation failed"; return nullptr; } auto pluginFactory = qobject_cast(factory); if (!pluginFactory) { qCDebug(CALENDARVIEW_LOG) << "Cast failed"; return nullptr; } return pluginFactory->createPluginFactory(); } } return nullptr; } void AgendaView::setChanges(EventView::Changes changes) { d->setChanges(changes); } void AgendaView::scheduleUpdateEventIndicators() { if (!d->mUpdateEventIndicatorsScheduled) { d->mUpdateEventIndicatorsScheduled = true; QTimer::singleShot(0, this, &AgendaView::updateEventIndicators); } }