File indexing completed on 2024-05-12 05:21:23
0001 /* 0002 This file is part of KOrganizer. 0003 0004 SPDX-FileCopyrightText: 2001 Eitzenberger Thomas <thomas.eitzenberger@siemens.at> 0005 Parts of the source code have been copied from kdpdatebutton.cpp 0006 0007 SPDX-FileCopyrightText: 2003 Cornelius Schumacher <schumacher@kde.org> 0008 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0009 0010 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0011 */ 0012 0013 #include "kodaymatrix.h" 0014 #include "koglobals.h" 0015 #include "prefs/koprefs.h" 0016 0017 #include <CalendarSupport/Utils> 0018 0019 #include <Akonadi/ItemFetchJob> 0020 #include <Akonadi/ItemFetchScope> 0021 0022 #include <QIcon> 0023 #include <QMenu> 0024 #include <QUrl> 0025 0026 #include <KLocalizedString> 0027 #include <QMimeData> 0028 #include <QMouseEvent> 0029 #include <QPainter> 0030 #include <QToolTip> 0031 0032 // ============================================================================ 0033 // K O D A Y M A T R I X 0034 // ============================================================================ 0035 0036 const int KODayMatrix::NOSELECTION = -1000; 0037 const int KODayMatrix::NUMDAYS = 42; 0038 0039 KODayMatrix::KODayMatrix(QWidget *parent) 0040 : QFrame(parent) 0041 , mStartDate() 0042 { 0043 // initialize dynamic arrays 0044 mDays = new QDate[NUMDAYS]; 0045 mDayLabels = new QString[NUMDAYS]; 0046 0047 mTodayMarginWidth = 2; 0048 mSelEnd = mSelStart = NOSELECTION; 0049 0050 recalculateToday(); 0051 0052 mHighlightEvents = true; 0053 mHighlightTodos = false; 0054 mHighlightJournals = false; 0055 } 0056 0057 void KODayMatrix::addCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0058 { 0059 calendar->registerObserver(this); 0060 mCalendars.push_back(calendar); 0061 0062 setAcceptDrops(true); 0063 updateIncidences(); 0064 } 0065 0066 void KODayMatrix::removeCalendar(const Akonadi::CollectionCalendar::Ptr &calendar) 0067 { 0068 calendar->unregisterObserver(this); 0069 mCalendars.removeOne(calendar); 0070 0071 setAcceptDrops(!mCalendars.empty()); 0072 updateIncidences(); 0073 } 0074 0075 QColor KODayMatrix::getShadedColor(const QColor &color) const 0076 { 0077 QColor shaded; 0078 int h = 0; 0079 int s = 0; 0080 int v = 0; 0081 color.getHsv(&h, &s, &v); 0082 s = s / 4; 0083 v = 192 + v / 4; 0084 shaded.setHsv(h, s, v); 0085 0086 return shaded; 0087 } 0088 0089 KODayMatrix::~KODayMatrix() 0090 { 0091 for (const auto &calendar : mCalendars) { 0092 calendar->unregisterObserver(this); 0093 } 0094 0095 delete[] mDays; 0096 delete[] mDayLabels; 0097 } 0098 0099 void KODayMatrix::addSelectedDaysTo(KCalendarCore::DateList &selDays) 0100 { 0101 if (mSelStart == NOSELECTION) { 0102 return; 0103 } 0104 0105 // cope with selection being out of matrix limits at top (< 0) 0106 int i0 = mSelStart; 0107 if (i0 < 0) { 0108 for (int i = i0; i < 0; ++i) { 0109 selDays.append(mDays[0].addDays(i)); 0110 } 0111 i0 = 0; 0112 } 0113 0114 // cope with selection being out of matrix limits at bottom (> NUMDAYS-1) 0115 if (mSelEnd > NUMDAYS - 1) { 0116 for (int i = i0; i <= NUMDAYS - 1; ++i) { 0117 selDays.append(mDays[i]); 0118 } 0119 for (int i = NUMDAYS; i < mSelEnd; ++i) { 0120 selDays.append(mDays[0].addDays(i)); 0121 } 0122 } else { 0123 // apply normal routine to selection being entirely within matrix limits 0124 for (int i = i0; i <= mSelEnd; ++i) { 0125 selDays.append(mDays[i]); 0126 } 0127 } 0128 } 0129 0130 void KODayMatrix::setSelectedDaysFrom(QDate start, QDate end) 0131 { 0132 if (mStartDate.isValid()) { 0133 mSelStart = mStartDate.daysTo(start); 0134 mSelEnd = mStartDate.daysTo(end); 0135 } 0136 } 0137 0138 void KODayMatrix::clearSelection() 0139 { 0140 mSelEnd = mSelStart = NOSELECTION; 0141 } 0142 0143 void KODayMatrix::recalculateToday() 0144 { 0145 if (!mStartDate.isValid()) { 0146 return; 0147 } 0148 0149 mToday = -1; 0150 for (int i = 0; i < NUMDAYS; ++i) { 0151 mDays[i] = mStartDate.addDays(i); 0152 mDayLabels[i] = QString::number(mDays[i].day()); 0153 0154 // if today is in the currently displayed month, highlight today 0155 if (mDays[i].year() == QDate::currentDate().year() && mDays[i].month() == QDate::currentDate().month() 0156 && mDays[i].day() == QDate::currentDate().day()) { 0157 mToday = i; 0158 } 0159 } 0160 } 0161 0162 void KODayMatrix::updateView() 0163 { 0164 updateView(mStartDate); 0165 } 0166 0167 void KODayMatrix::setUpdateNeeded() 0168 { 0169 mPendingChanges = true; 0170 } 0171 0172 void KODayMatrix::updateView(QDate actdate) 0173 { 0174 if (!actdate.isValid() || NUMDAYS < 1) { 0175 return; 0176 } 0177 // flag to indicate if the starting day of the matrix has changed by this call 0178 bool daychanged = false; 0179 0180 // if a new startdate is to be set then apply Cornelius's calculation 0181 // of the first day to be shown 0182 if (actdate != mStartDate) { 0183 // reset index of selection according to shift of starting date from 0184 // startdate to actdate. 0185 if (mSelStart != NOSELECTION) { 0186 int tmp = actdate.daysTo(mStartDate); 0187 // shift selection if new one would be visible at least partly ! 0188 if (mSelStart + tmp < NUMDAYS && mSelEnd + tmp >= 0) { 0189 // nested if required for next X display pushed from a different month 0190 // correction required. otherwise, for month forward and backward, 0191 // it must be avoided. 0192 if (mSelStart > NUMDAYS || mSelStart < 0) { 0193 mSelStart = mSelStart + tmp; 0194 } 0195 if (mSelEnd > NUMDAYS || mSelEnd < 0) { 0196 mSelEnd = mSelEnd + tmp; 0197 } 0198 } 0199 } 0200 0201 mStartDate = actdate; 0202 daychanged = true; 0203 } 0204 0205 if (daychanged) { 0206 recalculateToday(); 0207 } 0208 0209 // The calendar has not changed in the meantime and the selected range 0210 // is still the same so we can save the expensive updateIncidences() call 0211 if (!daychanged && !mPendingChanges) { 0212 return; 0213 } 0214 0215 // TODO_Recurrence: If we just change the selection, but not the data, 0216 // there's no need to update the whole list of incidences... This is just a 0217 // waste of computational power 0218 updateIncidences(); 0219 QMap<QDate, QStringList> holidaysByDate = KOGlobals::self()->holiday(mDays[0], mDays[NUMDAYS - 1]); 0220 for (int i = 0; i < NUMDAYS; ++i) { 0221 // if it is a holy day then draw it red. Sundays are consider holidays, too 0222 QStringList holidays = holidaysByDate[mDays[i]]; 0223 QString holiStr; 0224 0225 if (!holidays.isEmpty()) { 0226 holiStr = holidays.join(i18nc("delimiter for joining holiday names", ",")); 0227 if (holiStr.isEmpty()) { 0228 holiStr = QLatin1StringView(""); 0229 } 0230 } 0231 mHolidays[i] = holiStr; 0232 } 0233 } 0234 0235 void KODayMatrix::updateIncidences() 0236 { 0237 mEvents.clear(); 0238 0239 if (mHighlightEvents) { 0240 updateEvents(); 0241 } 0242 0243 if (mHighlightTodos) { 0244 updateTodos(); 0245 } 0246 0247 if (mHighlightJournals) { 0248 updateJournals(); 0249 } 0250 0251 mPendingChanges = false; 0252 } 0253 0254 void KODayMatrix::updateJournals() 0255 { 0256 for (const auto &calendar : mCalendars) { 0257 for (const KCalendarCore::Incidence::Ptr &inc : calendar->incidences()) { 0258 Q_ASSERT(inc); 0259 QDate d = inc->dtStart().toLocalTime().date(); 0260 if (inc->type() == KCalendarCore::Incidence::TypeJournal && d >= mDays[0] && d <= mDays[NUMDAYS - 1] && !mEvents.contains(d)) { 0261 mEvents.append(d); 0262 } 0263 if (mEvents.count() == NUMDAYS) { 0264 // No point in wasting cpu, all days are bold already 0265 break; 0266 } 0267 } 0268 } 0269 } 0270 0271 /** 0272 * Although updateTodos() is simpler it has some similarities with updateEvent() 0273 * but don't bother refactoring them so they share code, there's a bigger fish: 0274 * Try to refactor updateTodos(), updateEvent(), updateJournals(), monthview, 0275 * agenda view, timeline view, event list view and todo list view 0276 * all these 9 places have incidence listing code in common, maybe it could go 0277 * to kcal. Ah, and then there's kontact's summary view which still uses 0278 * the old CPU consuming code. 0279 */ 0280 void KODayMatrix::updateTodos() 0281 { 0282 QDate d; 0283 for (const auto &calendar : mCalendars) { 0284 for (const KCalendarCore::Todo::Ptr &t : calendar->todos()) { 0285 if (mEvents.count() == NUMDAYS) { 0286 // No point in wasting cpu, all days are bold already 0287 break; 0288 } 0289 Q_ASSERT(t); 0290 if (t->hasDueDate()) { 0291 ushort recurType = t->recurrenceType(); 0292 0293 if (t->recurs() && !(recurType == KCalendarCore::Recurrence::rDaily && !KOPrefs::instance()->mDailyRecur) 0294 && !(recurType == KCalendarCore::Recurrence::rWeekly && !KOPrefs::instance()->mWeeklyRecur)) { 0295 // It's a recurring todo, find out in which days it occurs 0296 const auto timeDateList = 0297 t->recurrence()->timesInInterval(QDateTime(mDays[0], {}, Qt::LocalTime), QDateTime(mDays[NUMDAYS - 1], {}, Qt::LocalTime)); 0298 0299 for (const QDateTime &dt : timeDateList) { 0300 d = dt.toLocalTime().date(); 0301 if (!mEvents.contains(d)) { 0302 mEvents.append(d); 0303 } 0304 } 0305 } else { 0306 d = t->dtDue().toLocalTime().date(); 0307 if (d >= mDays[0] && d <= mDays[NUMDAYS - 1] && !mEvents.contains(d)) { 0308 mEvents.append(d); 0309 } 0310 } 0311 } 0312 } 0313 } 0314 } 0315 0316 void KODayMatrix::updateEvents() 0317 { 0318 if (mEvents.count() == NUMDAYS) { 0319 mPendingChanges = false; 0320 // No point in wasting cpu, all days are bold already 0321 return; 0322 } 0323 0324 for (const auto &calendar : mCalendars) { 0325 const auto eventlist = calendar->events(mDays[0], mDays[NUMDAYS - 1], calendar->timeZone()); 0326 for (const KCalendarCore::Event::Ptr &event : eventlist) { 0327 if (mEvents.count() == NUMDAYS) { 0328 // No point in wasting cpu, all days are bold already 0329 break; 0330 } 0331 0332 Q_ASSERT(event); 0333 const ushort recurType = event->recurrenceType(); 0334 const QDateTime dtStart = event->dtStart().toLocalTime(); 0335 0336 // timed incidences occur in 0337 // [dtStart(), dtEnd()[. All-day incidences occur in [dtStart(), dtEnd()] 0338 // so we subtract 1 second in the timed case 0339 const int secsToAdd = event->allDay() ? 0 : -1; 0340 const QDateTime dtEnd = event->dtEnd().toLocalTime().addSecs(secsToAdd); 0341 0342 if (!(recurType == KCalendarCore::Recurrence::rDaily && !KOPrefs::instance()->mDailyRecur) 0343 && !(recurType == KCalendarCore::Recurrence::rWeekly && !KOPrefs::instance()->mWeeklyRecur)) { 0344 KCalendarCore::DateTimeList timeDateList; 0345 const bool isRecurrent = event->recurs(); 0346 const int eventDuration = dtStart.daysTo(dtEnd); 0347 0348 if (isRecurrent) { 0349 // Its a recurring event, find out in which days it occurs 0350 timeDateList = 0351 event->recurrence()->timesInInterval(QDateTime(mDays[0], {}, Qt::LocalTime), QDateTime(mDays[NUMDAYS - 1], {}, Qt::LocalTime)); 0352 } else { 0353 if (dtStart.date() >= mDays[0]) { 0354 timeDateList.append(dtStart); 0355 } else { 0356 // The event starts in another month (not visible)) 0357 timeDateList.append(QDateTime(mDays[0], {}, Qt::LocalTime)); 0358 } 0359 } 0360 0361 for (auto t = timeDateList.begin(); t != timeDateList.end(); ++t) { 0362 // This could be a multiday event, so iterate from dtStart() to dtEnd() 0363 QDate d = t->toLocalTime().date(); 0364 int j = 0; 0365 0366 QDate occurrenceEnd; 0367 if (isRecurrent) { 0368 occurrenceEnd = d.addDays(eventDuration); 0369 } else { 0370 occurrenceEnd = dtEnd.date(); 0371 } 0372 0373 do { 0374 mEvents.append(d); 0375 ++j; 0376 d = d.addDays(1); 0377 } while (d <= occurrenceEnd && j < NUMDAYS); 0378 } 0379 } 0380 } 0381 mPendingChanges = false; 0382 } 0383 } 0384 0385 const QDate &KODayMatrix::getDate(int offset) const 0386 { 0387 if (offset < 0 || offset > NUMDAYS - 1) { 0388 return mDays[0]; 0389 } 0390 return mDays[offset]; 0391 } 0392 0393 QString KODayMatrix::getHolidayLabel(int offset) const 0394 { 0395 if (offset < 0 || offset > NUMDAYS - 1) { 0396 return {}; 0397 } 0398 return mHolidays[offset]; 0399 } 0400 0401 int KODayMatrix::getDayIndexFrom(int x, int y) const 0402 { 0403 return 7 * (y / mDaySize.height()) + (KOGlobals::self()->reverseLayout() ? 6 - x / mDaySize.width() : x / mDaySize.width()); 0404 } 0405 0406 void KODayMatrix::calendarIncidenceAdded(const KCalendarCore::Incidence::Ptr &incidence) 0407 { 0408 Q_UNUSED(incidence) 0409 mPendingChanges = true; 0410 } 0411 0412 void KODayMatrix::calendarIncidenceChanged(const KCalendarCore::Incidence::Ptr &incidence) 0413 { 0414 Q_UNUSED(incidence) 0415 mPendingChanges = true; 0416 } 0417 0418 void KODayMatrix::calendarIncidenceDeleted(const KCalendarCore::Incidence::Ptr &incidence, const KCalendarCore::Calendar *calendar) 0419 { 0420 Q_UNUSED(incidence) 0421 Q_UNUSED(calendar) 0422 mPendingChanges = true; 0423 } 0424 0425 void KODayMatrix::setHighlightMode(bool highlightEvents, bool highlightTodos, bool highlightJournals) 0426 { 0427 // don't set mPendingChanges to true if nothing changed 0428 if (highlightTodos != mHighlightTodos || highlightEvents != mHighlightEvents || highlightJournals != mHighlightJournals) { 0429 mHighlightEvents = highlightEvents; 0430 mHighlightTodos = highlightTodos; 0431 mHighlightJournals = highlightJournals; 0432 mPendingChanges = true; 0433 } 0434 } 0435 0436 void KODayMatrix::resourcesChanged() 0437 { 0438 mPendingChanges = true; 0439 } 0440 0441 // ---------------------------------------------------------------------------- 0442 // M O U S E E V E N T H A N D L I N G 0443 // ---------------------------------------------------------------------------- 0444 0445 bool KODayMatrix::event(QEvent *event) 0446 { 0447 if (KOPrefs::instance()->mEnableToolTips && event->type() == QEvent::ToolTip) { 0448 auto helpEvent = static_cast<QHelpEvent *>(event); 0449 0450 // calculate which cell of the matrix the mouse is in 0451 QRect sz = frameRect(); 0452 int dheight = sz.height() * 7 / 42; 0453 int dwidth = sz.width() / 7; 0454 int row = helpEvent->pos().y() / dheight; 0455 int col = helpEvent->pos().x() / dwidth; 0456 0457 // show holiday names only 0458 QString tipText = getHolidayLabel(col + row * 7); 0459 if (!tipText.isEmpty()) { 0460 QToolTip::showText(helpEvent->globalPos(), tipText); 0461 } else { 0462 QToolTip::hideText(); 0463 } 0464 } 0465 return QWidget::event(event); 0466 } 0467 0468 void KODayMatrix::mousePressEvent(QMouseEvent *e) 0469 { 0470 mSelStart = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y()); 0471 if (e->button() == Qt::RightButton) { 0472 popupMenu(mDays[mSelStart], mDays[mSelEnd]); 0473 } else if (e->button() == Qt::LeftButton) { 0474 if (mSelStart > NUMDAYS - 1) { 0475 mSelStart = NUMDAYS - 1; 0476 } 0477 mSelInit = mSelStart; 0478 } 0479 } 0480 void KODayMatrix::popupMenu(const QDate &date, const QDate &date2) 0481 { 0482 QMenu popup(this); 0483 popup.setTitle(date.toString()); 0484 QAction *newEventAction = popup.addAction(QIcon::fromTheme(QStringLiteral("appointment-new")), i18n("New E&vent...")); 0485 QAction *newTodoAction = popup.addAction(QIcon::fromTheme(QStringLiteral("task-new")), i18n("New &To-do...")); 0486 QAction *newJournalAction = popup.addAction(QIcon::fromTheme(QStringLiteral("journal-new")), i18n("New &Journal...")); 0487 QAction *ret = popup.exec(QCursor::pos()); 0488 if (ret == newEventAction) { 0489 Q_EMIT newEventSignal(date, date2); 0490 } else if (ret == newTodoAction) { 0491 Q_EMIT newTodoSignal(date); 0492 } else if (ret == newJournalAction) { 0493 Q_EMIT newJournalSignal(date); 0494 } 0495 } 0496 0497 void KODayMatrix::mouseReleaseEvent(QMouseEvent *e) 0498 { 0499 if (e->button() != Qt::LeftButton) { 0500 return; 0501 } 0502 int tmp = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y()); 0503 if (tmp > NUMDAYS - 1) { 0504 tmp = NUMDAYS - 1; 0505 } 0506 0507 if (mSelInit > tmp) { 0508 mSelEnd = mSelInit; 0509 if (tmp != mSelStart) { 0510 mSelStart = tmp; 0511 update(); 0512 } 0513 } else { 0514 mSelStart = mSelInit; 0515 0516 // repaint only if selection has changed 0517 if (tmp != mSelEnd) { 0518 mSelEnd = tmp; 0519 update(); 0520 } 0521 } 0522 0523 KCalendarCore::DateList daylist; 0524 if (mSelStart < 0) { 0525 mSelStart = 0; 0526 } 0527 daylist.reserve(mSelEnd - mSelStart + 1); 0528 for (int i = mSelStart; i <= mSelEnd; ++i) { 0529 daylist.append(mDays[i]); 0530 } 0531 Q_EMIT selected(static_cast<const KCalendarCore::DateList>(daylist)); 0532 } 0533 0534 void KODayMatrix::mouseMoveEvent(QMouseEvent *e) 0535 { 0536 int tmp = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y()); 0537 if (tmp > NUMDAYS - 1) { 0538 tmp = NUMDAYS - 1; 0539 } 0540 0541 if (mSelInit > tmp) { 0542 mSelEnd = mSelInit; 0543 if (tmp != mSelStart) { 0544 mSelStart = tmp; 0545 update(); 0546 } 0547 } else { 0548 mSelStart = mSelInit; 0549 0550 // repaint only if selection has changed 0551 if (tmp != mSelEnd) { 0552 mSelEnd = tmp; 0553 update(); 0554 } 0555 } 0556 } 0557 0558 // ---------------------------------------------------------------------------- 0559 // D R A G ' N D R O P H A N D L I N G 0560 // ---------------------------------------------------------------------------- 0561 0562 //----------------------------------------------------------------------------- 0563 // Drag and Drop handling -- based on the Troll Tech dirview example 0564 0565 enum { DRAG_COPY = 0, DRAG_MOVE = 1, DRAG_CANCEL = 2 }; 0566 0567 void KODayMatrix::dragEnterEvent(QDragEnterEvent *e) 0568 { 0569 e->acceptProposedAction(); 0570 const QMimeData *md = e->mimeData(); 0571 if (!CalendarSupport::canDecode(md)) { 0572 e->ignore(); 0573 return; 0574 } 0575 0576 // some visual feedback 0577 // oldPalette = palette(); 0578 // setPalette(my_HilitePalette); 0579 // update(); 0580 } 0581 0582 void KODayMatrix::dragMoveEvent(QDragMoveEvent *e) 0583 { 0584 const QMimeData *md = e->mimeData(); 0585 if (!CalendarSupport::canDecode(md)) { 0586 e->ignore(); 0587 return; 0588 } 0589 e->accept(); 0590 } 0591 0592 void KODayMatrix::dragLeaveEvent(QDragLeaveEvent *dl) 0593 { 0594 Q_UNUSED(dl) 0595 // setPalette(oldPalette); 0596 // update(); 0597 } 0598 0599 void KODayMatrix::dropEvent(QDropEvent *e) 0600 { 0601 QList<QUrl> urls = (e->mimeData()->urls()); 0602 // qCDebug(KORGANIZER_LOG)<<" urls :"<<urls; 0603 if (urls.isEmpty()) { 0604 e->ignore(); 0605 return; 0606 } 0607 // For the moment support 1 url 0608 if (urls.count() >= 1) { 0609 QUrl res = urls.at(0); 0610 0611 auto job = new Akonadi::ItemFetchJob(Akonadi::Item::fromUrl(res)); 0612 job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); 0613 job->fetchScope().fetchFullPayload(); 0614 Akonadi::Item::List items; 0615 if (job->exec()) { 0616 items = job->items(); 0617 } 0618 bool exist = items.at(0).isValid(); 0619 int action = DRAG_CANCEL; 0620 Qt::KeyboardModifiers keyboardModifiers = e->modifiers(); 0621 if (keyboardModifiers & Qt::ControlModifier) { 0622 action = DRAG_COPY; 0623 } else if (keyboardModifiers & Qt::ShiftModifier) { 0624 action = DRAG_MOVE; 0625 } else { 0626 QAction *copy = nullptr; 0627 QAction *move = nullptr; 0628 auto menu = new QMenu(this); 0629 if (exist) { 0630 move = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("&Move")); 0631 if (/*existingEvent*/ true) { 0632 copy = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy")); 0633 } 0634 } else { 0635 move = menu->addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("&Add")); 0636 } 0637 menu->addSeparator(); 0638 /*QAction *cancel =*/ 0639 menu->addAction(QIcon::fromTheme(QStringLiteral("process-stop")), i18n("&Cancel")); 0640 QAction *a = menu->exec(QCursor::pos()); 0641 if (a == copy) { 0642 action = DRAG_COPY; 0643 } else if (a == move) { 0644 action = DRAG_MOVE; 0645 } 0646 delete menu; 0647 } 0648 0649 if (action == DRAG_COPY || action == DRAG_MOVE) { 0650 e->accept(); 0651 int idx = getDayIndexFrom(e->position().toPoint().x(), e->position().toPoint().y()); 0652 0653 if (action == DRAG_COPY) { 0654 Q_EMIT incidenceDropped(items.at(0), mDays[idx]); 0655 } else if (action == DRAG_MOVE) { 0656 Q_EMIT incidenceDroppedMove(items.at(0), mDays[idx]); 0657 } 0658 } 0659 } 0660 } 0661 0662 // ---------------------------------------------------------------------------- 0663 // P A I N T E V E N T H A N D L I N G 0664 // ---------------------------------------------------------------------------- 0665 0666 void KODayMatrix::paintEvent(QPaintEvent *) 0667 { 0668 QPainter p; 0669 const QRect rect = frameRect(); 0670 const int dayHeight = mDaySize.height(); 0671 const int dayWidth = mDaySize.width(); 0672 int row; 0673 int column; 0674 const bool isRTL = KOGlobals::self()->reverseLayout(); 0675 0676 QPalette pal = palette(); 0677 0678 p.begin(this); 0679 0680 // draw background 0681 p.fillRect(0, 0, rect.width(), rect.height(), QBrush(pal.color(QPalette::Base))); 0682 0683 // draw topleft frame 0684 p.setPen(pal.color(QPalette::Mid)); 0685 p.drawRect(0, 0, rect.width() - 1, rect.height() - 1); 0686 // don't paint over borders 0687 p.translate(1, 1); 0688 0689 // draw selected days with highlighted background color 0690 if (mSelStart != NOSELECTION) { 0691 row = mSelStart / 7; 0692 // fix larger selections starting in the previous month 0693 if (row < 0 && mSelEnd > 0) { 0694 row = 0; 0695 } 0696 column = mSelStart - row * 7; 0697 const QColor selectionColor = KOPrefs::instance()->agendaGridHighlightColor(); 0698 0699 if (row < 6 && row >= 0) { 0700 if (row == mSelEnd / 7) { 0701 // Single row selection 0702 p.fillRect(isRTL ? (7 - (mSelEnd - mSelStart + 1) - column) * dayWidth : column * dayWidth, 0703 row * dayHeight, 0704 (mSelEnd - mSelStart + 1) * dayWidth, 0705 dayHeight, 0706 selectionColor); 0707 } else { 0708 // draw first row to the right 0709 p.fillRect(isRTL ? 0 : column * dayWidth, row * dayHeight, (7 - column) * dayWidth, dayHeight, selectionColor); 0710 // draw full block till last line 0711 int selectionHeight = mSelEnd / 7 - row; 0712 if (selectionHeight + row >= 6) { 0713 selectionHeight = 6 - row; 0714 } 0715 if (selectionHeight > 1) { 0716 p.fillRect(0, (row + 1) * dayHeight, 7 * dayWidth, (selectionHeight - 1) * dayHeight, selectionColor); 0717 } 0718 // draw last block from left to mSelEnd 0719 if (mSelEnd / 7 < 6) { 0720 int selectionWidth = mSelEnd - 7 * (mSelEnd / 7) + 1; 0721 p.fillRect(isRTL ? (7 - selectionWidth) * dayWidth : 0, 0722 (row + selectionHeight) * dayHeight, 0723 selectionWidth * dayWidth, 0724 dayHeight, 0725 selectionColor); 0726 } 0727 } 0728 } 0729 } 0730 0731 // iterate over all days in the matrix and draw the day label in appropriate colors 0732 const QColor textColor = pal.color(QPalette::Text); 0733 const QColor textColorShaded = getShadedColor(textColor); 0734 QColor actcol = textColorShaded; 0735 p.setPen(actcol); 0736 QPen tmppen; 0737 0738 const QList<QDate> workDays = CalendarSupport::workDays(mDays[0], mDays[NUMDAYS - 1]); 0739 for (int i = 0; i < NUMDAYS; ++i) { 0740 row = i / 7; 0741 column = isRTL ? 6 - (i - row * 7) : i - row * 7; 0742 0743 // if it is the first day of a month switch color from normal to shaded and vice versa 0744 if (mDays[i].day() == 1) { 0745 if (actcol == textColorShaded) { 0746 actcol = textColor; 0747 } else { 0748 actcol = textColorShaded; 0749 } 0750 p.setPen(actcol); 0751 } 0752 0753 // Reset pen color after selected days block 0754 if (i == mSelEnd + 1) { 0755 p.setPen(actcol); 0756 } 0757 0758 const bool holiday = !workDays.contains(mDays[i]); 0759 0760 const QColor holidayColorShaded = getShadedColor(KOPrefs::instance()->agendaHolidaysBackgroundColor()); 0761 0762 // if today then draw rectangle around day 0763 if (mToday == i) { 0764 tmppen = p.pen(); 0765 QPen todayPen(p.pen()); 0766 0767 todayPen.setWidth(mTodayMarginWidth); 0768 // draw red rectangle for holidays 0769 if (holiday) { 0770 if (actcol == textColor) { 0771 todayPen.setColor(KOPrefs::instance()->agendaHolidaysBackgroundColor()); 0772 } else { 0773 todayPen.setColor(holidayColorShaded); 0774 } 0775 } 0776 // draw gray rectangle for today if in selection 0777 if (i >= mSelStart && i <= mSelEnd) { 0778 const QColor grey(QStringLiteral("grey")); 0779 todayPen.setColor(grey); 0780 } 0781 p.setPen(todayPen); 0782 p.drawRect(column * dayWidth, row * dayHeight, dayWidth, dayHeight); 0783 p.setPen(tmppen); 0784 } 0785 0786 // if any events are on that day then draw it using a bold font 0787 if (mEvents.contains(mDays[i])) { 0788 QFont myFont = font(); 0789 myFont.setBold(true); 0790 p.setFont(myFont); 0791 } 0792 0793 // if it is a holiday then use the default holiday color 0794 if (holiday) { 0795 if (actcol == textColor) { 0796 p.setPen(KOPrefs::instance()->agendaHolidaysBackgroundColor()); 0797 } else { 0798 p.setPen(holidayColorShaded); 0799 } 0800 } 0801 0802 // draw selected days with special color 0803 if (i >= mSelStart && i <= mSelEnd && !holiday) { 0804 p.setPen(Qt::white); 0805 } 0806 0807 p.drawText(column * dayWidth, row * dayHeight, dayWidth, dayHeight, Qt::AlignHCenter | Qt::AlignVCenter, mDayLabels[i]); 0808 0809 // reset color to actual color 0810 if (holiday) { 0811 p.setPen(actcol); 0812 } 0813 // reset bold font to plain font 0814 if (mEvents.contains(mDays[i]) > 0) { 0815 QFont myFont = font(); 0816 myFont.setBold(false); 0817 p.setFont(myFont); 0818 } 0819 } 0820 p.end(); 0821 } 0822 0823 // ---------------------------------------------------------------------------- 0824 // R E SI Z E E V E N T H A N D L I N G 0825 // ---------------------------------------------------------------------------- 0826 0827 void KODayMatrix::resizeEvent(QResizeEvent *) 0828 { 0829 QRect sz = frameRect(); 0830 mDaySize.setHeight(sz.height() * 7 / NUMDAYS); 0831 mDaySize.setWidth(sz.width() / 7); 0832 } 0833 0834 /* static */ 0835 QPair<QDate, QDate> KODayMatrix::matrixLimits(QDate month) 0836 { 0837 QDate d(month.year(), month.month(), 1); 0838 0839 const int dayOfWeek = d.dayOfWeek(); 0840 const int weekstart = KOGlobals::self()->firstDayOfWeek(); 0841 0842 d = d.addDays(-(7 + dayOfWeek - weekstart) % 7); 0843 0844 if (dayOfWeek == weekstart) { 0845 d = d.addDays(-7); // Start on the second line 0846 } 0847 0848 return qMakePair(d, d.addDays(NUMDAYS - 1)); 0849 } 0850 0851 #include "moc_kodaymatrix.cpp"