File indexing completed on 2025-03-09 04:45:05
0001 /* 0002 SPDX-FileCopyrightText: 2001 Cornelius Schumacher <schumacher@kde.org> 0003 SPDX-FileCopyrightText: 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com> 0004 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 0005 SPDX-FileContributor: Kevin Krammer <krake@kdab.com> 0006 SPDX-FileContributor: Sergio Martins <sergio@kdab.com> 0007 0008 Marcus Bains line. 0009 SPDX-FileCopyrightText: 2001 Ali Rahimi <ali@mit.edu> 0010 0011 SPDX-License-Identifier: GPL-2.0-or-later WITH Qt-Commercial-exception-1.0 0012 */ 0013 #include "agenda.h" 0014 #include "agendaview.h" 0015 #include "prefs.h" 0016 0017 #include <Akonadi/CalendarUtils> 0018 #include <Akonadi/IncidenceChanger> 0019 #include <CalendarSupport/Utils> 0020 0021 #include <KCalendarCore/Incidence> 0022 0023 #include <KCalUtils/RecurrenceActions> 0024 0025 #include "calendarview_debug.h" 0026 #include <KMessageBox> 0027 0028 #include <KLocalizedString> 0029 #include <QApplication> 0030 #include <QHash> 0031 #include <QLabel> 0032 #include <QMouseEvent> 0033 #include <QMultiHash> 0034 #include <QPainter> 0035 #include <QPointer> 0036 #include <QResizeEvent> 0037 #include <QScrollBar> 0038 #include <QTimer> 0039 #include <QWheelEvent> 0040 0041 #include <chrono> 0042 #include <cmath> 0043 0044 using namespace std::chrono_literals; // for fabs() 0045 0046 using namespace EventViews; 0047 0048 /////////////////////////////////////////////////////////////////////////////// 0049 class EventViews::MarcusBainsPrivate 0050 { 0051 public: 0052 MarcusBainsPrivate(EventView *eventView, Agenda *agenda) 0053 : mEventView(eventView) 0054 , mAgenda(agenda) 0055 { 0056 } 0057 0058 [[nodiscard]] int todayColumn() const; 0059 0060 public: 0061 EventView *const mEventView; 0062 Agenda *const mAgenda; 0063 QTimer *mTimer = nullptr; 0064 QLabel *mTimeBox = nullptr; // Label showing the current time 0065 QDateTime mOldDateTime; 0066 int mOldTodayCol = -1; 0067 }; 0068 0069 int MarcusBainsPrivate::todayColumn() const 0070 { 0071 const QDate currentDate = QDate::currentDate(); 0072 0073 int col = 0; 0074 const KCalendarCore::DateList dateList = mAgenda->dateList(); 0075 for (const QDate &date : dateList) { 0076 if (date == currentDate) { 0077 return QApplication::isRightToLeft() ? mAgenda->columns() - 1 - col : col; 0078 } 0079 ++col; 0080 } 0081 0082 return -1; 0083 } 0084 0085 MarcusBains::MarcusBains(EventView *eventView, Agenda *agenda) 0086 : QFrame(agenda) 0087 , d(new MarcusBainsPrivate(eventView, agenda)) 0088 { 0089 d->mTimeBox = new QLabel(d->mAgenda); 0090 d->mTimeBox->setAlignment(Qt::AlignRight | Qt::AlignBottom); 0091 0092 d->mTimer = new QTimer(this); 0093 d->mTimer->setSingleShot(true); 0094 connect(d->mTimer, &QTimer::timeout, this, &MarcusBains::updateLocation); 0095 d->mTimer->start(0); 0096 } 0097 0098 MarcusBains::~MarcusBains() = default; 0099 0100 void MarcusBains::updateLocation() 0101 { 0102 updateLocationRecalc(); 0103 } 0104 0105 void MarcusBains::updateLocationRecalc(bool recalculate) 0106 { 0107 const bool showSeconds = d->mEventView->preferences()->marcusBainsShowSeconds(); 0108 const QColor color = d->mEventView->preferences()->agendaMarcusBainsLineLineColor(); 0109 0110 const QDateTime now = QDateTime::currentDateTime(); 0111 const QTime time = now.time(); 0112 0113 if (now.date() != d->mOldDateTime.date()) { 0114 recalculate = true; // New day 0115 } 0116 const int todayCol = recalculate ? d->todayColumn() : d->mOldTodayCol; 0117 0118 // Number of minutes since beginning of the day 0119 const int minutes = time.hour() * 60 + time.minute(); 0120 const int minutesPerCell = 24 * 60 / d->mAgenda->rows(); 0121 0122 d->mOldDateTime = now; 0123 d->mOldTodayCol = todayCol; 0124 0125 int y = int(minutes * d->mAgenda->gridSpacingY() / minutesPerCell); 0126 int x = int(d->mAgenda->gridSpacingX() * todayCol); 0127 0128 bool hideIt = !(d->mEventView->preferences()->marcusBainsEnabled()); 0129 if (!isHidden() && (hideIt || (todayCol < 0))) { 0130 hide(); 0131 d->mTimeBox->hide(); 0132 return; 0133 } 0134 0135 if (isHidden() && !hideIt) { 0136 show(); 0137 d->mTimeBox->show(); 0138 } 0139 0140 /* Line */ 0141 // It seems logical to adjust the line width with the label's font weight 0142 const int fw = d->mEventView->preferences()->agendaMarcusBainsLineFont().weight(); 0143 setLineWidth(1 + abs(fw - QFont::Normal) / QFont::Light); 0144 setFrameStyle(QFrame::HLine | QFrame::Plain); 0145 QPalette pal = palette(); 0146 pal.setColor(QPalette::Window, color); // for Oxygen 0147 pal.setColor(QPalette::WindowText, color); // for Plastique 0148 setPalette(pal); 0149 if (recalculate) { 0150 setFixedSize(int(d->mAgenda->gridSpacingX()), 1); 0151 } 0152 move(x, y); 0153 raise(); 0154 0155 /* Label */ 0156 d->mTimeBox->setFont(d->mEventView->preferences()->agendaMarcusBainsLineFont()); 0157 QPalette pal1 = d->mTimeBox->palette(); 0158 pal1.setColor(QPalette::WindowText, color); 0159 d->mTimeBox->setPalette(pal1); 0160 d->mTimeBox->setText(QLocale::system().toString(time, showSeconds ? QLocale::LongFormat : QLocale::ShortFormat)); 0161 d->mTimeBox->adjustSize(); 0162 if (y - d->mTimeBox->height() >= 0) { 0163 y -= d->mTimeBox->height(); 0164 } else { 0165 y++; 0166 } 0167 if (x - d->mTimeBox->width() + d->mAgenda->gridSpacingX() > 0) { 0168 x += int(d->mAgenda->gridSpacingX() - d->mTimeBox->width() - 1); 0169 } else { 0170 x++; 0171 } 0172 d->mTimeBox->move(x, y); 0173 d->mTimeBox->raise(); 0174 0175 if (showSeconds || recalculate) { 0176 d->mTimer->start(1s); 0177 } else { 0178 d->mTimer->start(1000 * (60 - time.second())); 0179 } 0180 } 0181 0182 //////////////////////////////////////////////////////////////////////////// 0183 0184 class EventViews::AgendaPrivate 0185 { 0186 public: 0187 AgendaPrivate(AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive) 0188 : mAgendaView(agendaView) 0189 , mScrollArea(scrollArea) 0190 , mAllDayMode(false) 0191 , mColumns(columns) 0192 , mRows(rows) 0193 , mGridSpacingX(0.0) 0194 , mGridSpacingY(rowSize) 0195 , mDesiredGridSpacingY(rowSize) 0196 , mChanger(nullptr) 0197 , mResizeBorderWidth(0) 0198 , mScrollBorderWidth(0) 0199 , mScrollDelay(0) 0200 , mScrollOffset(0) 0201 , mWorkingHoursEnable(false) 0202 , mHolidayMask(nullptr) 0203 , mWorkingHoursYTop(0) 0204 , mWorkingHoursYBottom(0) 0205 , mHasSelection(false) 0206 , mMarcusBains(nullptr) 0207 , mActionType(Agenda::NOP) 0208 , mItemMoved(false) 0209 , mOldLowerScrollValue(0) 0210 , mOldUpperScrollValue(0) 0211 , mReturnPressed(false) 0212 , mIsInteractive(isInteractive) 0213 { 0214 if (mGridSpacingY < 4 || mGridSpacingY > 30) { 0215 mGridSpacingY = 10; 0216 } 0217 } 0218 0219 public: 0220 PrefsPtr preferences() const 0221 { 0222 return mAgendaView->preferences(); 0223 } 0224 0225 bool isQueuedForDeletion(const QString &uid) const 0226 { 0227 // if mAgendaItemsById contains it it means that a createAgendaItem() was called 0228 // before the previous agenda items were deleted. 0229 return mItemsQueuedForDeletion.contains(uid) && !mAgendaItemsById.contains(uid); 0230 } 0231 0232 QMultiHash<QString, AgendaItem::QPtr> mAgendaItemsById; // A QMultiHash because recurring incs 0233 // might have many agenda items 0234 QSet<QString> mItemsQueuedForDeletion; 0235 0236 AgendaView *mAgendaView = nullptr; 0237 QScrollArea *mScrollArea = nullptr; 0238 0239 bool mAllDayMode; 0240 0241 // Number of Columns/Rows of agenda grid 0242 int mColumns; 0243 int mRows; 0244 0245 // Width and height of agenda cells. mDesiredGridSpacingY is the height 0246 // set in the config. The actual height might be larger since otherwise 0247 // more than 24 hours might be displayed. 0248 double mGridSpacingX; 0249 double mGridSpacingY; 0250 double mDesiredGridSpacingY; 0251 0252 Akonadi::IncidenceChanger *mChanger = nullptr; 0253 0254 // size of border, where mouse action will resize the AgendaItem 0255 int mResizeBorderWidth; 0256 0257 // size of border, where mouse mve will cause a scroll of the agenda 0258 int mScrollBorderWidth; 0259 int mScrollDelay; 0260 int mScrollOffset; 0261 0262 QTimer mScrollUpTimer; 0263 QTimer mScrollDownTimer; 0264 0265 // Cells to store Move and Resize coordinates while performing the action 0266 QPoint mStartCell; 0267 QPoint mEndCell; 0268 0269 // Working Hour coordinates 0270 bool mWorkingHoursEnable; 0271 QList<bool> *mHolidayMask = nullptr; 0272 int mWorkingHoursYTop; 0273 int mWorkingHoursYBottom; 0274 0275 // Selection 0276 bool mHasSelection; 0277 QPoint mSelectionStartPoint; 0278 QPoint mSelectionStartCell; 0279 QPoint mSelectionEndCell; 0280 0281 // List of dates to be displayed 0282 KCalendarCore::DateList mSelectedDates; 0283 0284 // The AgendaItem, which has been right-clicked last 0285 QPointer<AgendaItem> mClickedItem; 0286 0287 // The AgendaItem, which is being moved/resized 0288 QPointer<AgendaItem> mActionItem; 0289 0290 // Currently selected item 0291 QPointer<AgendaItem> mSelectedItem; 0292 // Uid of the last selected incidence. Used for reselecting in situations 0293 // where the selected item points to a no longer valid incidence, for 0294 // example during resource reload. 0295 QString mSelectedId; 0296 0297 // The Marcus Bains Line widget. 0298 MarcusBains *mMarcusBains = nullptr; 0299 0300 Agenda::MouseActionType mActionType; 0301 0302 bool mItemMoved; 0303 0304 // List of all Items contained in agenda 0305 QList<AgendaItem::QPtr> mItems; 0306 QList<AgendaItem::QPtr> mItemsToDelete; 0307 0308 int mOldLowerScrollValue; 0309 int mOldUpperScrollValue; 0310 0311 bool mReturnPressed; 0312 bool mIsInteractive; 0313 0314 MultiViewCalendar::Ptr mCalendar; 0315 }; 0316 0317 /* 0318 Create an agenda widget with rows rows and columns columns. 0319 */ 0320 Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, int rows, int rowSize, bool isInteractive) 0321 : QWidget(scrollArea) 0322 , d(new AgendaPrivate(agendaView, scrollArea, columns, rows, rowSize, isInteractive)) 0323 { 0324 setMouseTracking(true); 0325 0326 init(); 0327 } 0328 0329 /* 0330 Create an agenda widget with columns columns and one row. This is used for 0331 all-day events. 0332 */ 0333 Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea, int columns, bool isInteractive) 0334 : QWidget(scrollArea) 0335 , d(new AgendaPrivate(agendaView, scrollArea, columns, 1, 24, isInteractive)) 0336 { 0337 d->mAllDayMode = true; 0338 0339 init(); 0340 } 0341 0342 Agenda::~Agenda() 0343 { 0344 delete d->mMarcusBains; 0345 } 0346 0347 KCalendarCore::Incidence::Ptr Agenda::selectedIncidence() const 0348 { 0349 return d->mSelectedItem ? d->mSelectedItem->incidence() : KCalendarCore::Incidence::Ptr(); 0350 } 0351 0352 QDate Agenda::selectedIncidenceDate() const 0353 { 0354 return d->mSelectedItem ? d->mSelectedItem->occurrenceDate() : QDate(); 0355 } 0356 0357 QString Agenda::lastSelectedItemUid() const 0358 { 0359 return d->mSelectedId; 0360 } 0361 0362 void Agenda::init() 0363 { 0364 setAttribute(Qt::WA_OpaquePaintEvent); 0365 0366 d->mGridSpacingX = static_cast<double>(d->mScrollArea->width()) / d->mColumns; 0367 d->mDesiredGridSpacingY = d->preferences()->hourSize(); 0368 if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) { 0369 d->mDesiredGridSpacingY = 10; 0370 } 0371 0372 // make sure that there are not more than 24 per day 0373 d->mGridSpacingY = static_cast<double>(height()) / d->mRows; 0374 if (d->mGridSpacingY < d->mDesiredGridSpacingY) { 0375 d->mGridSpacingY = d->mDesiredGridSpacingY; 0376 } 0377 0378 d->mResizeBorderWidth = 12; 0379 d->mScrollBorderWidth = 12; 0380 d->mScrollDelay = 30; 0381 d->mScrollOffset = 10; 0382 0383 // Grab key strokes for keyboard navigation of agenda. Seems to have no 0384 // effect. Has to be fixed. 0385 setFocusPolicy(Qt::WheelFocus); 0386 0387 connect(&d->mScrollUpTimer, &QTimer::timeout, this, &Agenda::scrollUp); 0388 connect(&d->mScrollDownTimer, &QTimer::timeout, this, &Agenda::scrollDown); 0389 0390 d->mStartCell = QPoint(0, 0); 0391 d->mEndCell = QPoint(0, 0); 0392 0393 d->mHasSelection = false; 0394 d->mSelectionStartPoint = QPoint(0, 0); 0395 d->mSelectionStartCell = QPoint(0, 0); 0396 d->mSelectionEndCell = QPoint(0, 0); 0397 0398 d->mOldLowerScrollValue = -1; 0399 d->mOldUpperScrollValue = -1; 0400 0401 d->mClickedItem = nullptr; 0402 0403 d->mActionItem = nullptr; 0404 d->mActionType = NOP; 0405 d->mItemMoved = false; 0406 0407 d->mSelectedItem = nullptr; 0408 0409 setAcceptDrops(true); 0410 installEventFilter(this); 0411 0412 /* resizeContents(int(mGridSpacingX * mColumns), int(mGridSpacingY * mRows)); */ 0413 0414 d->mScrollArea->viewport()->update(); 0415 // mScrollArea->viewport()->setAttribute(Qt::WA_NoSystemBackground, true); 0416 d->mScrollArea->viewport()->setFocusPolicy(Qt::WheelFocus); 0417 0418 calculateWorkingHours(); 0419 0420 connect(verticalScrollBar(), &QScrollBar::valueChanged, this, qOverload<int>(&Agenda::checkScrollBoundaries)); 0421 0422 // Create the Marcus Bains line. 0423 if (d->mAllDayMode) { 0424 d->mMarcusBains = nullptr; 0425 } else { 0426 d->mMarcusBains = new MarcusBains(d->mAgendaView, this); 0427 } 0428 } 0429 0430 void Agenda::clear() 0431 { 0432 qDeleteAll(d->mItems); 0433 qDeleteAll(d->mItemsToDelete); 0434 d->mItems.clear(); 0435 d->mItemsToDelete.clear(); 0436 d->mAgendaItemsById.clear(); 0437 d->mItemsQueuedForDeletion.clear(); 0438 0439 d->mSelectedItem = nullptr; 0440 0441 clearSelection(); 0442 } 0443 0444 void Agenda::clearSelection() 0445 { 0446 d->mHasSelection = false; 0447 d->mActionType = NOP; 0448 update(); 0449 } 0450 0451 void Agenda::marcus_bains() 0452 { 0453 if (d->mMarcusBains) { 0454 d->mMarcusBains->updateLocationRecalc(true); 0455 } 0456 } 0457 0458 void Agenda::changeColumns(int columns) 0459 { 0460 if (columns == 0) { 0461 qCDebug(CALENDARVIEW_LOG) << "called with argument 0"; 0462 return; 0463 } 0464 0465 clear(); 0466 d->mColumns = columns; 0467 // setMinimumSize(mColumns * 10, mGridSpacingY + 1); 0468 // init(); 0469 // update(); 0470 0471 QResizeEvent event(size(), size()); 0472 0473 QApplication::sendEvent(this, &event); 0474 } 0475 0476 int Agenda::columns() const 0477 { 0478 return d->mColumns; 0479 } 0480 0481 int Agenda::rows() const 0482 { 0483 return d->mRows; 0484 } 0485 0486 double Agenda::gridSpacingX() const 0487 { 0488 return d->mGridSpacingX; 0489 } 0490 0491 double Agenda::gridSpacingY() const 0492 { 0493 return d->mGridSpacingY; 0494 } 0495 0496 /* 0497 This is the eventFilter function, which gets all events from the AgendaItems 0498 contained in the agenda. It has to handle moving and resizing for all items. 0499 */ 0500 bool Agenda::eventFilter(QObject *object, QEvent *event) 0501 { 0502 switch (event->type()) { 0503 case QEvent::MouseButtonPress: 0504 case QEvent::MouseButtonDblClick: 0505 case QEvent::MouseButtonRelease: 0506 case QEvent::MouseMove: 0507 return eventFilter_mouse(object, static_cast<QMouseEvent *>(event)); 0508 #ifndef QT_NO_WHEELEVENT 0509 case QEvent::Wheel: 0510 return eventFilter_wheel(object, static_cast<QWheelEvent *>(event)); 0511 #endif 0512 case QEvent::KeyPress: 0513 case QEvent::KeyRelease: 0514 return eventFilter_key(object, static_cast<QKeyEvent *>(event)); 0515 0516 case QEvent::Leave: 0517 #ifndef QT_NO_CURSOR 0518 if (!d->mActionItem) { 0519 setCursor(Qt::ArrowCursor); 0520 } 0521 #endif 0522 0523 if (object == this) { 0524 // so timelabels hide the mouse cursor 0525 Q_EMIT leaveAgenda(); 0526 } 0527 return true; 0528 0529 case QEvent::Enter: 0530 Q_EMIT enterAgenda(); 0531 return QWidget::eventFilter(object, event); 0532 0533 #ifndef QT_NO_DRAGANDDROP 0534 case QEvent::DragEnter: 0535 case QEvent::DragMove: 0536 case QEvent::DragLeave: 0537 case QEvent::Drop: 0538 // case QEvent::DragResponse: 0539 return eventFilter_drag(object, static_cast<QDropEvent *>(event)); 0540 #endif 0541 0542 default: 0543 return QWidget::eventFilter(object, event); 0544 } 0545 } 0546 0547 bool Agenda::eventFilter_drag(QObject *obj, QDropEvent *de) 0548 { 0549 #ifndef QT_NO_DRAGANDDROP 0550 const QMimeData *md = de->mimeData(); 0551 0552 switch (de->type()) { 0553 case QEvent::DragEnter: 0554 case QEvent::DragMove: 0555 if (!CalendarSupport::canDecode(md)) { 0556 return false; 0557 } 0558 0559 if (CalendarSupport::mimeDataHasIncidence(md)) { 0560 de->accept(); 0561 } else { 0562 de->ignore(); 0563 } 0564 return true; 0565 break; 0566 case QEvent::DragLeave: 0567 return false; 0568 break; 0569 case QEvent::Drop: { 0570 if (!CalendarSupport::canDecode(md)) { 0571 return false; 0572 } 0573 0574 const QList<QUrl> incidenceUrls = CalendarSupport::incidenceItemUrls(md); 0575 const KCalendarCore::Incidence::List incidences = CalendarSupport::incidences(md); 0576 0577 Q_ASSERT(!incidenceUrls.isEmpty() || !incidences.isEmpty()); 0578 0579 de->setDropAction(Qt::MoveAction); 0580 0581 QWidget *dropTarget = qobject_cast<QWidget *>(obj); 0582 QPoint dropPosition = de->position().toPoint(); 0583 if (dropTarget && dropTarget != this) { 0584 dropPosition = dropTarget->mapTo(this, dropPosition); 0585 } 0586 0587 const QPoint gridPosition = contentsToGrid(dropPosition); 0588 if (!incidenceUrls.isEmpty()) { 0589 Q_EMIT droppedIncidences(incidenceUrls, gridPosition, d->mAllDayMode); 0590 } else { 0591 Q_EMIT droppedIncidences(incidences, gridPosition, d->mAllDayMode); 0592 } 0593 return true; 0594 } 0595 0596 case QEvent::DragResponse: 0597 default: 0598 break; 0599 } 0600 #endif 0601 return false; 0602 } 0603 0604 #ifndef QT_NO_WHEELEVENT 0605 bool Agenda::eventFilter_wheel(QObject *object, QWheelEvent *e) 0606 { 0607 QPoint viewportPos; 0608 bool accepted = false; 0609 const QPoint pos = e->position().toPoint(); 0610 if ((e->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier) { 0611 if (object != this) { 0612 viewportPos = ((QWidget *)object)->mapToParent(pos); 0613 } else { 0614 viewportPos = pos; 0615 } 0616 // qCDebug(CALENDARVIEW_LOG) << type:" << e->type() << "angleDelta:" << e->angleDelta(); 0617 Q_EMIT zoomView(-e->angleDelta().y(), contentsToGrid(viewportPos), Qt::Horizontal); 0618 accepted = true; 0619 } 0620 0621 if ((e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) { 0622 if (object != this) { 0623 viewportPos = ((QWidget *)object)->mapToParent(pos); 0624 } else { 0625 viewportPos = pos; 0626 } 0627 Q_EMIT zoomView(-e->angleDelta().y(), contentsToGrid(viewportPos), Qt::Vertical); 0628 Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos))); 0629 accepted = true; 0630 } 0631 if (accepted) { 0632 e->accept(); 0633 } 0634 return accepted; 0635 } 0636 0637 #endif 0638 0639 bool Agenda::eventFilter_key(QObject *, QKeyEvent *ke) 0640 { 0641 return d->mAgendaView->processKeyEvent(ke); 0642 } 0643 0644 bool Agenda::eventFilter_mouse(QObject *object, QMouseEvent *me) 0645 { 0646 QPoint viewportPos; 0647 if (object != this) { 0648 viewportPos = static_cast<QWidget *>(object)->mapToParent(me->pos()); 0649 } else { 0650 viewportPos = me->pos(); 0651 } 0652 0653 switch (me->type()) { 0654 case QEvent::MouseButtonPress: 0655 if (object != this) { 0656 if (me->button() == Qt::RightButton) { 0657 d->mClickedItem = qobject_cast<AgendaItem *>(object); 0658 if (d->mClickedItem) { 0659 selectItem(d->mClickedItem); 0660 Q_EMIT showIncidencePopupSignal(d->mClickedItem->incidence(), d->mClickedItem->occurrenceDate()); 0661 } 0662 } else { 0663 AgendaItem::QPtr item = qobject_cast<AgendaItem *>(object); 0664 if (item) { 0665 KCalendarCore::Incidence::Ptr incidence = item->incidence(); 0666 if (incidence->isReadOnly()) { 0667 d->mActionItem = nullptr; 0668 } else { 0669 d->mActionItem = item; 0670 startItemAction(viewportPos); 0671 } 0672 // Warning: do selectItem() as late as possible, since all 0673 // sorts of things happen during this call. Some can lead to 0674 // this filter being run again and mActionItem being set to 0675 // null. 0676 selectItem(item); 0677 } 0678 } 0679 } else { 0680 if (me->button() == Qt::RightButton) { 0681 // if mouse pointer is not in selection, select the cell below the cursor 0682 QPoint gpos = contentsToGrid(viewportPos); 0683 if (!ptInSelection(gpos)) { 0684 d->mSelectionStartCell = gpos; 0685 d->mSelectionEndCell = gpos; 0686 d->mHasSelection = true; 0687 Q_EMIT newStartSelectSignal(); 0688 Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell); 0689 // updateContents(); 0690 } 0691 Q_EMIT showNewEventPopupSignal(); 0692 } else { 0693 selectItem(nullptr); 0694 d->mActionItem = nullptr; 0695 #ifndef QT_NO_CURSOR 0696 setCursor(Qt::ArrowCursor); 0697 #endif 0698 startSelectAction(viewportPos); 0699 update(); 0700 } 0701 } 0702 break; 0703 0704 case QEvent::MouseButtonRelease: 0705 if (d->mActionItem) { 0706 endItemAction(); 0707 } else if (d->mActionType == SELECT) { 0708 endSelectAction(viewportPos); 0709 } 0710 // This nasty gridToContents(contentsToGrid(..)) is needed to 0711 // avoid an offset of a few pixels. Don't ask me why... 0712 Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos))); 0713 break; 0714 0715 case QEvent::MouseMove: { 0716 if (!d->mIsInteractive) { 0717 return true; 0718 } 0719 0720 // This nasty gridToContents(contentsToGrid(..)) is needed todos 0721 // avoid an offset of a few pixels. Don't ask me why... 0722 QPoint indicatorPos = gridToContents(contentsToGrid(viewportPos)); 0723 if (object != this) { 0724 AgendaItem::QPtr moveItem = qobject_cast<AgendaItem *>(object); 0725 KCalendarCore::Incidence::Ptr incidence = moveItem ? moveItem->incidence() : KCalendarCore::Incidence::Ptr(); 0726 if (incidence && !incidence->isReadOnly()) { 0727 if (!d->mActionItem) { 0728 setNoActionCursor(moveItem, viewportPos); 0729 } else { 0730 performItemAction(viewportPos); 0731 0732 if (d->mActionType == MOVE) { 0733 // show cursor at the current begin of the item 0734 AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem(); 0735 if (!firstItem) { 0736 firstItem = d->mActionItem; 0737 } 0738 indicatorPos = gridToContents(QPoint(firstItem->cellXLeft(), firstItem->cellYTop())); 0739 } else if (d->mActionType == RESIZEBOTTOM) { 0740 // RESIZETOP is handled correctly, only resizebottom works differently 0741 indicatorPos = gridToContents(QPoint(d->mActionItem->cellXLeft(), d->mActionItem->cellYBottom() + 1)); 0742 } 0743 } // If we have an action item 0744 } // If move item && !read only 0745 } else { 0746 if (d->mActionType == SELECT) { 0747 performSelectAction(viewportPos); 0748 0749 // show cursor at end of timespan 0750 if (((d->mStartCell.y() < d->mEndCell.y()) && (d->mEndCell.x() >= d->mStartCell.x())) || (d->mEndCell.x() > d->mStartCell.x())) { 0751 indicatorPos = gridToContents(QPoint(d->mEndCell.x(), d->mEndCell.y() + 1)); 0752 } else { 0753 indicatorPos = gridToContents(d->mEndCell); 0754 } 0755 } 0756 } 0757 Q_EMIT mousePosSignal(indicatorPos); 0758 break; 0759 } 0760 0761 case QEvent::MouseButtonDblClick: 0762 if (object == this) { 0763 selectItem(nullptr); 0764 Q_EMIT newEventSignal(); 0765 } else { 0766 AgendaItem::QPtr doubleClickedItem = qobject_cast<AgendaItem *>(object); 0767 if (doubleClickedItem) { 0768 selectItem(doubleClickedItem); 0769 Q_EMIT editIncidenceSignal(doubleClickedItem->incidence()); 0770 } 0771 } 0772 break; 0773 0774 default: 0775 break; 0776 } 0777 0778 return true; 0779 } 0780 0781 bool Agenda::ptInSelection(QPoint gpos) const 0782 { 0783 if (!d->mHasSelection) { 0784 return false; 0785 } else if (gpos.x() < d->mSelectionStartCell.x() || gpos.x() > d->mSelectionEndCell.x()) { 0786 return false; 0787 } else if ((gpos.x() == d->mSelectionStartCell.x()) && (gpos.y() < d->mSelectionStartCell.y())) { 0788 return false; 0789 } else if ((gpos.x() == d->mSelectionEndCell.x()) && (gpos.y() > d->mSelectionEndCell.y())) { 0790 return false; 0791 } 0792 return true; 0793 } 0794 0795 void Agenda::startSelectAction(QPoint viewportPos) 0796 { 0797 Q_EMIT newStartSelectSignal(); 0798 0799 d->mActionType = SELECT; 0800 d->mSelectionStartPoint = viewportPos; 0801 d->mHasSelection = true; 0802 0803 QPoint pos = viewportPos; 0804 QPoint gpos = contentsToGrid(pos); 0805 0806 // Store new selection 0807 d->mStartCell = gpos; 0808 d->mEndCell = gpos; 0809 d->mSelectionStartCell = gpos; 0810 d->mSelectionEndCell = gpos; 0811 0812 // updateContents(); 0813 } 0814 0815 void Agenda::performSelectAction(QPoint pos) 0816 { 0817 const QPoint gpos = contentsToGrid(pos); 0818 0819 // Scroll if cursor was moved to upper or lower end of agenda. 0820 if (pos.y() - contentsY() < d->mScrollBorderWidth && contentsY() > 0) { 0821 d->mScrollUpTimer.start(d->mScrollDelay); 0822 } else if (contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y()) { 0823 d->mScrollDownTimer.start(d->mScrollDelay); 0824 } else { 0825 d->mScrollUpTimer.stop(); 0826 d->mScrollDownTimer.stop(); 0827 } 0828 0829 if (gpos != d->mEndCell) { 0830 d->mEndCell = gpos; 0831 if (d->mStartCell.x() > d->mEndCell.x() || (d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() > d->mEndCell.y())) { 0832 // backward selection 0833 d->mSelectionStartCell = d->mEndCell; 0834 d->mSelectionEndCell = d->mStartCell; 0835 } else { 0836 d->mSelectionStartCell = d->mStartCell; 0837 d->mSelectionEndCell = d->mEndCell; 0838 } 0839 0840 update(); 0841 } 0842 } 0843 0844 void Agenda::endSelectAction(const QPoint ¤tPos) 0845 { 0846 d->mScrollUpTimer.stop(); 0847 d->mScrollDownTimer.stop(); 0848 0849 d->mActionType = NOP; 0850 0851 Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell); 0852 0853 if (d->preferences()->selectionStartsEditor()) { 0854 if ((d->mSelectionStartPoint - currentPos).manhattanLength() > QApplication::startDragDistance()) { 0855 Q_EMIT newEventSignal(); 0856 } 0857 } 0858 } 0859 0860 Agenda::MouseActionType Agenda::isInResizeArea(bool horizontal, QPoint pos, const AgendaItem::QPtr &item) 0861 { 0862 if (!item) { 0863 return NOP; 0864 } 0865 QPoint gridpos = contentsToGrid(pos); 0866 QPoint contpos = gridToContents(gridpos + QPoint((QApplication::isRightToLeft()) ? 1 : 0, 0)); 0867 0868 if (horizontal) { 0869 int clXLeft = item->cellXLeft(); 0870 int clXRight = item->cellXRight(); 0871 if (QApplication::isRightToLeft()) { 0872 int tmp = clXLeft; 0873 clXLeft = clXRight; 0874 clXRight = tmp; 0875 } 0876 int gridDistanceX = int(pos.x() - contpos.x()); 0877 if (gridDistanceX < d->mResizeBorderWidth && clXLeft == gridpos.x()) { 0878 if (QApplication::isRightToLeft()) { 0879 return RESIZERIGHT; 0880 } else { 0881 return RESIZELEFT; 0882 } 0883 } else if ((d->mGridSpacingX - gridDistanceX) < d->mResizeBorderWidth && clXRight == gridpos.x()) { 0884 if (QApplication::isRightToLeft()) { 0885 return RESIZELEFT; 0886 } else { 0887 return RESIZERIGHT; 0888 } 0889 } else { 0890 return MOVE; 0891 } 0892 } else { 0893 int gridDistanceY = int(pos.y() - contpos.y()); 0894 if (gridDistanceY < d->mResizeBorderWidth && item->cellYTop() == gridpos.y() && !item->firstMultiItem()) { 0895 return RESIZETOP; 0896 } else if ((d->mGridSpacingY - gridDistanceY) < d->mResizeBorderWidth && item->cellYBottom() == gridpos.y() && !item->lastMultiItem()) { 0897 return RESIZEBOTTOM; 0898 } else { 0899 return MOVE; 0900 } 0901 } 0902 } 0903 0904 void Agenda::startItemAction(const QPoint &pos) 0905 { 0906 Q_ASSERT(d->mActionItem); 0907 0908 d->mStartCell = contentsToGrid(pos); 0909 d->mEndCell = d->mStartCell; 0910 0911 bool noResize = CalendarSupport::hasTodo(d->mActionItem->incidence()); 0912 0913 d->mActionType = MOVE; 0914 if (!noResize) { 0915 d->mActionType = isInResizeArea(d->mAllDayMode, pos, d->mActionItem); 0916 } 0917 0918 d->mActionItem->startMove(); 0919 setActionCursor(d->mActionType, true); 0920 } 0921 0922 void Agenda::performItemAction(QPoint pos) 0923 { 0924 QPoint gpos = contentsToGrid(pos); 0925 0926 // Cursor left active agenda area. 0927 // This starts a drag. 0928 if (pos.y() < 0 || pos.y() >= contentsY() + d->mScrollArea->viewport()->height() || pos.x() < 0 || pos.x() >= width()) { 0929 if (d->mActionType == MOVE) { 0930 d->mScrollUpTimer.stop(); 0931 d->mScrollDownTimer.stop(); 0932 d->mActionItem->resetMove(); 0933 placeSubCells(d->mActionItem); 0934 Q_EMIT startDragSignal(d->mActionItem->incidence()); 0935 #ifndef QT_NO_CURSOR 0936 setCursor(Qt::ArrowCursor); 0937 #endif 0938 if (d->mChanger) { 0939 // d->mChanger->cancelChange(d->mActionItem->incidence()); 0940 } 0941 d->mActionItem = nullptr; 0942 d->mActionType = NOP; 0943 d->mItemMoved = false; 0944 return; 0945 } 0946 } else { 0947 setActionCursor(d->mActionType, true); 0948 } 0949 0950 // Scroll if item was moved to upper or lower end of agenda. 0951 const int distanceToTop = pos.y() - contentsY(); 0952 if (distanceToTop < d->mScrollBorderWidth && distanceToTop > -d->mScrollBorderWidth) { 0953 d->mScrollUpTimer.start(d->mScrollDelay); 0954 } else if (contentsY() + d->mScrollArea->viewport()->height() - d->mScrollBorderWidth < pos.y()) { 0955 d->mScrollDownTimer.start(d->mScrollDelay); 0956 } else { 0957 d->mScrollUpTimer.stop(); 0958 d->mScrollDownTimer.stop(); 0959 } 0960 0961 // Move or resize item if necessary 0962 if (d->mEndCell != gpos) { 0963 if (!d->mItemMoved) { 0964 if (!d->mChanger) { 0965 KMessageBox::information(this, 0966 i18n("Unable to lock item for modification. " 0967 "You cannot make any changes."), 0968 i18nc("@title:window", "Locking Failed"), 0969 QStringLiteral("AgendaLockingFailed")); 0970 d->mScrollUpTimer.stop(); 0971 d->mScrollDownTimer.stop(); 0972 d->mActionItem->resetMove(); 0973 placeSubCells(d->mActionItem); 0974 #ifndef QT_NO_CURSOR 0975 setCursor(Qt::ArrowCursor); 0976 #endif 0977 d->mActionItem = nullptr; 0978 d->mActionType = NOP; 0979 d->mItemMoved = false; 0980 return; 0981 } 0982 d->mItemMoved = true; 0983 } 0984 d->mActionItem->raise(); 0985 if (d->mActionType == MOVE) { 0986 // Move all items belonging to a multi item 0987 AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem(); 0988 if (!firstItem) { 0989 firstItem = d->mActionItem; 0990 } 0991 AgendaItem::QPtr lastItem = d->mActionItem->lastMultiItem(); 0992 if (!lastItem) { 0993 lastItem = d->mActionItem; 0994 } 0995 QPoint deltapos = gpos - d->mEndCell; 0996 AgendaItem::QPtr moveItem = firstItem; 0997 while (moveItem) { 0998 bool changed = false; 0999 if (deltapos.x() != 0) { 1000 moveItem->moveRelative(deltapos.x(), 0); 1001 changed = true; 1002 } 1003 // in all day view don't try to move multi items, since there are none 1004 if (moveItem == firstItem && !d->mAllDayMode) { // is the first item 1005 int newY = deltapos.y() + moveItem->cellYTop(); 1006 // If event start moved earlier than 0:00, it starts the previous day 1007 if (newY < 0 && newY > d->mScrollBorderWidth) { 1008 moveItem->expandTop(-moveItem->cellYTop()); 1009 // prepend a new item at (x-1, rows()+newY to rows()) 1010 AgendaItem::QPtr newFirst = firstItem->prevMoveItem(); 1011 // cell's y values are first and last cell of the bar, 1012 // so if newY=-1, they need to be the same 1013 if (newFirst) { 1014 newFirst->setCellXY(moveItem->cellXLeft() - 1, rows() + newY, rows() - 1); 1015 d->mItems.append(newFirst); 1016 moveItem->resize(int(d->mGridSpacingX * newFirst->cellWidth()), int(d->mGridSpacingY * newFirst->cellHeight())); 1017 QPoint cpos = gridToContents(QPoint(newFirst->cellXLeft(), newFirst->cellYTop())); 1018 newFirst->setParent(this); 1019 newFirst->move(cpos.x(), cpos.y()); 1020 } else { 1021 newFirst = insertItem(moveItem->incidence(), 1022 moveItem->occurrenceDateTime(), 1023 moveItem->cellXLeft() - 1, 1024 rows() + newY, 1025 rows() - 1, 1026 moveItem->itemPos(), 1027 moveItem->itemCount(), 1028 false); 1029 } 1030 if (newFirst) { 1031 newFirst->show(); 1032 } 1033 moveItem->prependMoveItem(newFirst); 1034 firstItem = newFirst; 1035 } else if (newY >= rows()) { 1036 // If event start is moved past 24:00, it starts the next day 1037 // erase current item (i.e. remove it from the multiItem list) 1038 firstItem = moveItem->nextMultiItem(); 1039 moveItem->hide(); 1040 d->mItems.removeAll(moveItem); 1041 // removeChild(moveItem); 1042 d->mActionItem->removeMoveItem(moveItem); 1043 moveItem = firstItem; 1044 // adjust next day's item 1045 if (moveItem) { 1046 moveItem->expandTop(rows() - newY); 1047 } 1048 } else { 1049 moveItem->expandTop(deltapos.y(), true); 1050 } 1051 changed = true; 1052 } 1053 if (moveItem && !moveItem->lastMultiItem() && !d->mAllDayMode) { // is the last item 1054 int newY = deltapos.y() + moveItem->cellYBottom(); 1055 if (newY < 0) { 1056 // erase current item 1057 lastItem = moveItem->prevMultiItem(); 1058 moveItem->hide(); 1059 d->mItems.removeAll(moveItem); 1060 // removeChild(moveItem); 1061 moveItem->removeMoveItem(moveItem); 1062 moveItem = lastItem; 1063 moveItem->expandBottom(newY + 1); 1064 } else if (newY >= rows()) { 1065 moveItem->expandBottom(rows() - moveItem->cellYBottom() - 1); 1066 // append item at (x+1, 0 to newY-rows()) 1067 AgendaItem::QPtr newLast = lastItem->nextMoveItem(); 1068 if (newLast) { 1069 newLast->setCellXY(moveItem->cellXLeft() + 1, 0, newY - rows() - 1); 1070 d->mItems.append(newLast); 1071 moveItem->resize(int(d->mGridSpacingX * newLast->cellWidth()), int(d->mGridSpacingY * newLast->cellHeight())); 1072 QPoint cpos = gridToContents(QPoint(newLast->cellXLeft(), newLast->cellYTop())); 1073 newLast->setParent(this); 1074 newLast->move(cpos.x(), cpos.y()); 1075 } else { 1076 newLast = insertItem(moveItem->incidence(), 1077 moveItem->occurrenceDateTime(), 1078 moveItem->cellXLeft() + 1, 1079 0, 1080 newY - rows() - 1, 1081 moveItem->itemPos(), 1082 moveItem->itemCount(), 1083 false); 1084 } 1085 moveItem->appendMoveItem(newLast); 1086 newLast->show(); 1087 lastItem = newLast; 1088 } else { 1089 moveItem->expandBottom(deltapos.y()); 1090 } 1091 changed = true; 1092 } 1093 if (changed) { 1094 adjustItemPosition(moveItem); 1095 } 1096 if (moveItem) { 1097 moveItem = moveItem->nextMultiItem(); 1098 } 1099 } 1100 } else if (d->mActionType == RESIZETOP) { 1101 if (d->mEndCell.y() <= d->mActionItem->cellYBottom()) { 1102 d->mActionItem->expandTop(gpos.y() - d->mEndCell.y()); 1103 adjustItemPosition(d->mActionItem); 1104 } 1105 } else if (d->mActionType == RESIZEBOTTOM) { 1106 if (d->mEndCell.y() >= d->mActionItem->cellYTop()) { 1107 d->mActionItem->expandBottom(gpos.y() - d->mEndCell.y()); 1108 adjustItemPosition(d->mActionItem); 1109 } 1110 } else if (d->mActionType == RESIZELEFT) { 1111 if (d->mEndCell.x() <= d->mActionItem->cellXRight()) { 1112 d->mActionItem->expandLeft(gpos.x() - d->mEndCell.x()); 1113 adjustItemPosition(d->mActionItem); 1114 } 1115 } else if (d->mActionType == RESIZERIGHT) { 1116 if (d->mEndCell.x() >= d->mActionItem->cellXLeft()) { 1117 d->mActionItem->expandRight(gpos.x() - d->mEndCell.x()); 1118 adjustItemPosition(d->mActionItem); 1119 } 1120 } 1121 d->mEndCell = gpos; 1122 } 1123 } 1124 1125 void Agenda::endItemAction() 1126 { 1127 // PENDING(AKONADI_PORT) review all this cloning and changer calls 1128 d->mActionType = NOP; 1129 d->mScrollUpTimer.stop(); 1130 d->mScrollDownTimer.stop(); 1131 #ifndef QT_NO_CURSOR 1132 setCursor(Qt::ArrowCursor); 1133 #endif 1134 1135 if (!d->mChanger) { 1136 qCCritical(CALENDARVIEW_LOG) << "No IncidenceChanger set"; 1137 return; 1138 } 1139 1140 bool multiModify = false; 1141 // FIXME: do the cloning here... 1142 KCalendarCore::Incidence::Ptr incidence = d->mActionItem->incidence(); 1143 const auto recurrenceId = d->mActionItem->occurrenceDateTime(); 1144 1145 d->mItemMoved = d->mItemMoved && !(d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() == d->mEndCell.y()); 1146 1147 if (d->mItemMoved) { 1148 bool addIncidence = false; 1149 bool modify = false; 1150 1151 // get the main event and not the exception 1152 if (incidence->hasRecurrenceId() && !incidence->recurs()) { 1153 KCalendarCore::Incidence::Ptr mainIncidence; 1154 KCalendarCore::Calendar::Ptr cal = d->mCalendar->findCalendar(incidence)->getCalendar(); 1155 if (CalendarSupport::hasEvent(incidence)) { 1156 mainIncidence = cal->event(incidence->uid()); 1157 } else if (CalendarSupport::hasTodo(incidence)) { 1158 mainIncidence = cal->todo(incidence->uid()); 1159 } 1160 incidence = mainIncidence; 1161 } 1162 1163 Akonadi::Item item = d->mCalendar->item(incidence); 1164 if (incidence && incidence->recurs()) { 1165 const int res = d->mAgendaView->showMoveRecurDialog(incidence, recurrenceId.date()); 1166 1167 if (!d->mActionItem) { 1168 qCWarning(CALENDARVIEW_LOG) << "mActionItem was reset while the 'move' dialog was active"; 1169 d->mItemMoved = false; 1170 return; 1171 } 1172 1173 switch (res) { 1174 case KCalUtils::RecurrenceActions::AllOccurrences: // All occurrences 1175 // Moving the whole sequence of events is handled by the itemModified below. 1176 modify = true; 1177 break; 1178 case KCalUtils::RecurrenceActions::SelectedOccurrence: 1179 case KCalUtils::RecurrenceActions::FutureOccurrences: { 1180 const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences); 1181 modify = true; 1182 multiModify = true; 1183 d->mChanger->startAtomicOperation(i18n("Dissociate event from recurrence")); 1184 KCalendarCore::Incidence::Ptr newInc(KCalendarCore::Calendar::createException(incidence, recurrenceId, thisAndFuture)); 1185 if (newInc) { 1186 newInc->removeCustomProperty("VOLATILE", "AKONADI-ID"); 1187 Akonadi::Item newItem = d->mCalendar->item(newInc); 1188 1189 if (newItem.isValid() && newItem != item) { // it is not a new exception 1190 item = newItem; 1191 newInc->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(newItem.id())); 1192 addIncidence = false; 1193 } else { 1194 addIncidence = true; 1195 } 1196 // don't recreate items, they already have the correct position 1197 d->mAgendaView->enableAgendaUpdate(false); 1198 1199 d->mActionItem->setIncidence(newInc); 1200 d->mActionItem->dissociateFromMultiItem(); 1201 1202 d->mAgendaView->enableAgendaUpdate(true); 1203 } else { 1204 KMessageBox::error(this, 1205 i18n("Unable to add the exception item to the calendar. " 1206 "No change will be done."), 1207 i18nc("@title:window", "Error Occurred")); 1208 } 1209 break; 1210 } 1211 default: 1212 modify = false; 1213 d->mActionItem->resetMove(); 1214 placeSubCells(d->mActionItem); // PENDING(AKONADI_PORT) should this be done after 1215 // the new item was asynchronously added? 1216 } 1217 } 1218 1219 AgendaItem::QPtr placeItem = d->mActionItem->firstMultiItem(); 1220 if (!placeItem) { 1221 placeItem = d->mActionItem; 1222 } 1223 1224 Akonadi::Collection::Id saveCollection = -1; 1225 1226 if (item.isValid()) { 1227 saveCollection = item.parentCollection().id(); 1228 1229 // if parent collection is only a search collection for example 1230 if (!(item.parentCollection().rights() & Akonadi::Collection::CanCreateItem)) { 1231 saveCollection = item.storageCollectionId(); 1232 } 1233 } 1234 1235 if (modify) { 1236 d->mActionItem->endMove(); 1237 1238 AgendaItem::QPtr modif = placeItem; 1239 1240 QList<AgendaItem::QPtr> oldconflictItems = placeItem->conflictItems(); 1241 QList<AgendaItem::QPtr>::iterator it; 1242 for (it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it) { 1243 if (*it) { 1244 placeSubCells(*it); 1245 } 1246 } 1247 while (placeItem) { 1248 placeSubCells(placeItem); 1249 placeItem = placeItem->nextMultiItem(); 1250 } 1251 1252 // Notify about change 1253 // The agenda view will apply the changes to the actual Incidence*! 1254 // Bug #228696 don't call endChanged now it's async in Akonadi so it can 1255 // be called before that modified item was done. And endChange is 1256 // calling when we move item. 1257 // Not perfect need to improve it! 1258 // mChanger->endChange(inc); 1259 if (item.isValid()) { 1260 d->mAgendaView->updateEventDates(modif, addIncidence, saveCollection); 1261 } 1262 if (addIncidence) { 1263 // delete the one we dragged, there's a new one being added async, due to dissociation. 1264 delete modif; 1265 } 1266 } else { 1267 // the item was moved, but not further modified, since it's not recurring 1268 // make sure the view updates anyhow, with the right item 1269 if (item.isValid()) { 1270 d->mAgendaView->updateEventDates(placeItem, addIncidence, saveCollection); 1271 } 1272 } 1273 } 1274 1275 d->mActionItem = nullptr; 1276 d->mItemMoved = false; 1277 1278 if (multiModify) { 1279 d->mChanger->endAtomicOperation(); 1280 } 1281 } 1282 1283 void Agenda::setActionCursor(int actionType, bool acting) 1284 { 1285 #ifndef QT_NO_CURSOR 1286 switch (actionType) { 1287 case MOVE: 1288 if (acting) { 1289 setCursor(Qt::SizeAllCursor); 1290 } else { 1291 setCursor(Qt::ArrowCursor); 1292 } 1293 break; 1294 case RESIZETOP: 1295 case RESIZEBOTTOM: 1296 setCursor(Qt::SizeVerCursor); 1297 break; 1298 case RESIZELEFT: 1299 case RESIZERIGHT: 1300 setCursor(Qt::SizeHorCursor); 1301 break; 1302 default: 1303 setCursor(Qt::ArrowCursor); 1304 } 1305 #endif 1306 } 1307 1308 void Agenda::setNoActionCursor(const AgendaItem::QPtr &moveItem, QPoint pos) 1309 { 1310 const KCalendarCore::Incidence::Ptr item = moveItem ? moveItem->incidence() : KCalendarCore::Incidence::Ptr(); 1311 1312 const bool noResize = CalendarSupport::hasTodo(item); 1313 1314 Agenda::MouseActionType resizeType = MOVE; 1315 if (!noResize) { 1316 resizeType = isInResizeArea(d->mAllDayMode, pos, moveItem); 1317 } 1318 setActionCursor(resizeType); 1319 } 1320 1321 /** calculate the width of the column subcells of the given item 1322 */ 1323 double Agenda::calcSubCellWidth(const AgendaItem::QPtr &item) 1324 { 1325 QPoint pt; 1326 QPoint pt1; 1327 pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop())); 1328 pt1 = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()) + QPoint(1, 1)); 1329 pt1 -= pt; 1330 int maxSubCells = item->subCells(); 1331 double newSubCellWidth; 1332 if (d->mAllDayMode) { 1333 newSubCellWidth = static_cast<double>(pt1.y()) / maxSubCells; 1334 } else { 1335 newSubCellWidth = static_cast<double>(pt1.x()) / maxSubCells; 1336 } 1337 return newSubCellWidth; 1338 } 1339 1340 void Agenda::adjustItemPosition(const AgendaItem::QPtr &item) 1341 { 1342 if (!item) { 1343 return; 1344 } 1345 item->resize(int(d->mGridSpacingX * item->cellWidth()), int(d->mGridSpacingY * item->cellHeight())); 1346 int clXLeft = item->cellXLeft(); 1347 if (QApplication::isRightToLeft()) { 1348 clXLeft = item->cellXRight() + 1; 1349 } 1350 QPoint cpos = gridToContents(QPoint(clXLeft, item->cellYTop())); 1351 item->move(cpos.x(), cpos.y()); 1352 } 1353 1354 void Agenda::placeAgendaItem(const AgendaItem::QPtr &item, double subCellWidth) 1355 { 1356 // "left" upper corner, no subcells yet, RTL layouts have right/left 1357 // switched, widths are negative then 1358 QPoint pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop())); 1359 // right lower corner 1360 QPoint pt1 = gridToContents(QPoint(item->cellXLeft() + item->cellWidth(), item->cellYBottom() + 1)); 1361 1362 double subCellPos = item->subCell() * subCellWidth; 1363 1364 // we need to add 0.01 to make sure we don't loose one pixed due to numerics 1365 // (i.e. if it would be x.9998, we want the integer, not rounded down. 1366 double delta = 0.01; 1367 if (subCellWidth < 0) { 1368 delta = -delta; 1369 } 1370 int height; 1371 int width; 1372 int xpos; 1373 int ypos; 1374 if (d->mAllDayMode) { 1375 width = pt1.x() - pt.x(); 1376 height = int(subCellPos + subCellWidth + delta) - int(subCellPos); 1377 xpos = pt.x(); 1378 ypos = pt.y() + int(subCellPos); 1379 } else { 1380 width = int(subCellPos + subCellWidth + delta) - int(subCellPos); 1381 height = pt1.y() - pt.y(); 1382 xpos = pt.x() + int(subCellPos); 1383 ypos = pt.y(); 1384 } 1385 if (QApplication::isRightToLeft()) { // RTL language/layout 1386 xpos += width; 1387 width = -width; 1388 } 1389 if (height < 0) { // BTT (bottom-to-top) layout ?!? 1390 ypos += height; 1391 height = -height; 1392 } 1393 item->resize(width, height); 1394 item->move(xpos, ypos); 1395 } 1396 1397 /* 1398 Place item in cell and take care that multiple items using the same cell do 1399 not overlap. This method is not yet optimal. It doesn't use the maximum space 1400 it can get in all cases. 1401 At the moment the method has a bug: When an item is placed only the sub cell 1402 widths of the items are changed, which are within the Y region the item to 1403 place spans. When the sub cell width change of one of this items affects a 1404 cell, where other items are, which do not overlap in Y with the item to 1405 place, the display gets corrupted, although the corruption looks quite nice. 1406 */ 1407 void Agenda::placeSubCells(const AgendaItem::QPtr &placeItem) 1408 { 1409 #if 0 1410 qCDebug(CALENDARVIEW_LOG); 1411 if (placeItem) { 1412 KCalendarCore::Incidence::Ptr event = placeItem->incidence(); 1413 if (!event) { 1414 qCDebug(CALENDARVIEW_LOG) << " event is 0"; 1415 } else { 1416 qCDebug(CALENDARVIEW_LOG) << " event:" << event->summary(); 1417 } 1418 } else { 1419 qCDebug(CALENDARVIEW_LOG) << " placeItem is 0"; 1420 } 1421 qCDebug(CALENDARVIEW_LOG) << "Agenda::placeSubCells()..."; 1422 #endif 1423 1424 QList<CalendarSupport::CellItem *> cells; 1425 for (CalendarSupport::CellItem *item : std::as_const(d->mItems)) { 1426 if (item) { 1427 cells.append(item); 1428 } 1429 } 1430 1431 QList<CalendarSupport::CellItem *> items = CalendarSupport::CellItem::placeItem(cells, placeItem); 1432 1433 placeItem->setConflictItems(QList<AgendaItem::QPtr>()); 1434 double newSubCellWidth = calcSubCellWidth(placeItem); 1435 QList<CalendarSupport::CellItem *>::iterator it; 1436 for (it = items.begin(); it != items.end(); ++it) { 1437 if (*it) { 1438 AgendaItem::QPtr item = static_cast<AgendaItem *>(*it); 1439 placeAgendaItem(item, newSubCellWidth); 1440 item->addConflictItem(placeItem); 1441 placeItem->addConflictItem(item); 1442 } 1443 } 1444 if (items.isEmpty()) { 1445 placeAgendaItem(placeItem, newSubCellWidth); 1446 } 1447 placeItem->update(); 1448 } 1449 1450 int Agenda::columnWidth(int column) const 1451 { 1452 int start = gridToContents(QPoint(column, 0)).x(); 1453 if (QApplication::isRightToLeft()) { 1454 column--; 1455 } else { 1456 column++; 1457 } 1458 int end = gridToContents(QPoint(column, 0)).x(); 1459 return end - start; 1460 } 1461 1462 void Agenda::paintEvent(QPaintEvent *) 1463 { 1464 QPainter p(this); 1465 drawContents(&p, 0, -y(), d->mGridSpacingX * d->mColumns, d->mGridSpacingY * d->mRows + y()); 1466 } 1467 1468 /* 1469 Draw grid in the background of the agenda. 1470 */ 1471 void Agenda::drawContents(QPainter *p, int cx, int cy, int cw, int ch) 1472 { 1473 QPixmap db(cw, ch); 1474 db.fill(); // We don't want to see leftovers from previous paints 1475 QPainter dbp(&db); 1476 // TODO: CHECK THIS 1477 // if (! d->preferences()->agendaGridBackgroundImage().isEmpty()) { 1478 // QPixmap bgImage(d->preferences()->agendaGridBackgroundImage()); 1479 // dbp.drawPixmap(0, 0, cw, ch, bgImage); FIXME 1480 // } 1481 if (!d->preferences()->useSystemColor()) { 1482 dbp.fillRect(0, 0, cw, ch, d->preferences()->agendaGridBackgroundColor()); 1483 } else { 1484 dbp.fillRect(0, 0, cw, ch, palette().color(QPalette::Window)); 1485 } 1486 1487 dbp.translate(-cx, -cy); 1488 1489 double lGridSpacingY = d->mGridSpacingY * 2; 1490 1491 // If work day, use work color 1492 // If busy day, use busy color 1493 // if work and busy day, mix both, and busy color has alpha 1494 1495 const QList<bool> busyDayMask = d->mAgendaView->busyDayMask(); 1496 1497 // Highlight working hours 1498 if (d->mWorkingHoursEnable && d->mHolidayMask) { 1499 QColor workColor; 1500 if (!d->preferences()->useSystemColor()) { 1501 workColor = d->preferences()->workingHoursColor(); 1502 } else { 1503 workColor = palette().color(QPalette::Base); 1504 } 1505 1506 QPoint pt1(cx, d->mWorkingHoursYTop); 1507 QPoint pt2(cx + cw, d->mWorkingHoursYBottom); 1508 if (pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) { 1509 int gxStart = contentsToGrid(pt1).x(); 1510 int gxEnd = contentsToGrid(pt2).x(); 1511 // correct start/end for rtl layouts 1512 if (gxStart > gxEnd) { 1513 int tmp = gxStart; 1514 gxStart = gxEnd; 1515 gxEnd = tmp; 1516 } 1517 int xoffset = (QApplication::isRightToLeft() ? 1 : 0); 1518 while (gxStart <= gxEnd) { 1519 int xStart = gridToContents(QPoint(gxStart + xoffset, 0)).x(); 1520 int xWidth = columnWidth(gxStart) + 1; 1521 1522 if (pt2.y() < pt1.y()) { 1523 // overnight working hours 1524 if (((gxStart == 0) && !d->mHolidayMask->at(d->mHolidayMask->count() - 1)) 1525 || ((gxStart > 0) && (gxStart < int(d->mHolidayMask->count())) && (!d->mHolidayMask->at(gxStart - 1)))) { 1526 if (pt2.y() > cy) { 1527 dbp.fillRect(xStart, cy, xWidth, pt2.y() - cy + 1, workColor); 1528 } 1529 } 1530 if ((gxStart < int(d->mHolidayMask->count() - 1)) && (!d->mHolidayMask->at(gxStart))) { 1531 if (pt1.y() < cy + ch - 1) { 1532 dbp.fillRect(xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1, workColor); 1533 } 1534 } 1535 } else { 1536 // last entry in holiday mask denotes the previous day not visible 1537 // (needed for overnight shifts) 1538 if (gxStart < int(d->mHolidayMask->count() - 1) && !d->mHolidayMask->at(gxStart)) { 1539 dbp.fillRect(xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1, workColor); 1540 } 1541 } 1542 ++gxStart; 1543 } 1544 } 1545 } 1546 1547 // busy days 1548 if (d->preferences()->colorAgendaBusyDays() && !d->mAllDayMode) { 1549 for (int i = 0; i < busyDayMask.count(); ++i) { 1550 if (busyDayMask[i]) { 1551 const QPoint pt1(cx + d->mGridSpacingX * i, 0); 1552 // const QPoint pt2(cx + mGridSpacingX * (i+1), ch); 1553 QColor busyColor; 1554 if (!d->preferences()->useSystemColor()) { 1555 busyColor = d->preferences()->viewBgBusyColor(); 1556 } else { 1557 busyColor = palette().color(QPalette::Window); 1558 if ((busyColor.blue() + busyColor.red() + busyColor.green()) > (256 / 2 * 3)) { 1559 // dark 1560 busyColor = busyColor.lighter(140); 1561 } else { 1562 // light 1563 busyColor = busyColor.darker(140); 1564 } 1565 } 1566 busyColor.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA); 1567 dbp.fillRect(pt1.x(), pt1.y(), d->mGridSpacingX, cy + ch, busyColor); 1568 } 1569 } 1570 } 1571 1572 // draw selection 1573 if (d->mHasSelection && d->mAgendaView->dateRangeSelectionEnabled()) { 1574 QPoint pt; 1575 QPoint pt1; 1576 QColor highlightColor; 1577 if (!d->preferences()->useSystemColor()) { 1578 highlightColor = d->preferences()->agendaGridHighlightColor(); 1579 } else { 1580 highlightColor = palette().color(QPalette::Highlight); 1581 } 1582 1583 if (d->mSelectionEndCell.x() > d->mSelectionStartCell.x()) { // multi day selection 1584 // draw start day 1585 pt = gridToContents(d->mSelectionStartCell); 1586 pt1 = gridToContents(QPoint(d->mSelectionStartCell.x() + 1, d->mRows + 1)); 1587 dbp.fillRect(QRect(pt, pt1), highlightColor); 1588 // draw all other days between the start day and the day of the selection end 1589 for (int c = d->mSelectionStartCell.x() + 1; c < d->mSelectionEndCell.x(); ++c) { 1590 pt = gridToContents(QPoint(c, 0)); 1591 pt1 = gridToContents(QPoint(c + 1, d->mRows + 1)); 1592 dbp.fillRect(QRect(pt, pt1), highlightColor); 1593 } 1594 // draw end day 1595 pt = gridToContents(QPoint(d->mSelectionEndCell.x(), 0)); 1596 pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1)); 1597 dbp.fillRect(QRect(pt, pt1), highlightColor); 1598 } else { // single day selection 1599 pt = gridToContents(d->mSelectionStartCell); 1600 pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1)); 1601 dbp.fillRect(QRect(pt, pt1), highlightColor); 1602 } 1603 } 1604 1605 // Compute the grid line color for both the hour and half-hour 1606 // The grid colors are always computed as a function of the palette's windowText color. 1607 QPen hourPen; 1608 QPen halfHourPen; 1609 1610 const QColor windowTextColor = palette().color(QPalette::WindowText); 1611 if (windowTextColor.red() + windowTextColor.green() + windowTextColor.blue() < (256 / 2 * 3)) { 1612 // dark grey line 1613 hourPen = windowTextColor.lighter(200); 1614 halfHourPen = windowTextColor.lighter(500); 1615 } else { 1616 // light grey line 1617 hourPen = windowTextColor.darker(150); 1618 halfHourPen = windowTextColor.darker(200); 1619 } 1620 1621 dbp.setPen(hourPen); 1622 1623 // Draw vertical lines of grid, start with the last line not yet visible 1624 double x = (int(cx / d->mGridSpacingX)) * d->mGridSpacingX; 1625 while (x < cx + cw) { 1626 dbp.drawLine(int(x), cy, int(x), cy + ch); 1627 x += d->mGridSpacingX; 1628 } 1629 1630 // Draw horizontal lines of grid 1631 double y = (int(cy / (2 * lGridSpacingY))) * 2 * lGridSpacingY; 1632 while (y < cy + ch) { 1633 dbp.drawLine(cx, int(y), cx + cw, int(y)); 1634 y += 2 * lGridSpacingY; 1635 } 1636 y = (2 * int(cy / (2 * lGridSpacingY)) + 1) * lGridSpacingY; 1637 dbp.setPen(halfHourPen); 1638 while (y < cy + ch) { 1639 dbp.drawLine(cx, int(y), cx + cw, int(y)); 1640 y += 2 * lGridSpacingY; 1641 } 1642 p->drawPixmap(cx, cy, db); 1643 } 1644 1645 /* 1646 Convert srcollview contents coordinates to agenda grid coordinates. 1647 */ 1648 QPoint Agenda::contentsToGrid(QPoint pos) const 1649 { 1650 int gx = int(QApplication::isRightToLeft() ? d->mColumns - pos.x() / d->mGridSpacingX : pos.x() / d->mGridSpacingX); 1651 int gy = int(pos.y() / d->mGridSpacingY); 1652 return {gx, gy}; 1653 } 1654 1655 /* 1656 Convert agenda grid coordinates to scrollview contents coordinates. 1657 */ 1658 QPoint Agenda::gridToContents(QPoint gpos) const 1659 { 1660 int x = int(QApplication::isRightToLeft() ? (d->mColumns - gpos.x()) * d->mGridSpacingX : gpos.x() * d->mGridSpacingX); 1661 int y = int(gpos.y() * d->mGridSpacingY); 1662 return {x, y}; 1663 } 1664 1665 /* 1666 Return Y coordinate corresponding to time. Coordinates are rounded to 1667 fit into the grid. 1668 */ 1669 int Agenda::timeToY(QTime time) const 1670 { 1671 int minutesPerCell = 24 * 60 / d->mRows; 1672 int timeMinutes = time.hour() * 60 + time.minute(); 1673 int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell; 1674 1675 return Y; 1676 } 1677 1678 /* 1679 Return time corresponding to cell y coordinate. Coordinates are rounded to 1680 fit into the grid. 1681 */ 1682 QTime Agenda::gyToTime(int gy) const 1683 { 1684 int secondsPerCell = 24 * 60 * 60 / d->mRows; 1685 int timeSeconds = secondsPerCell * gy; 1686 1687 QTime time(0, 0, 0); 1688 if (timeSeconds < 24 * 60 * 60) { 1689 time = time.addSecs(timeSeconds); 1690 } else { 1691 time.setHMS(23, 59, 59); 1692 } 1693 return time; 1694 } 1695 1696 QList<int> Agenda::minContentsY() const 1697 { 1698 QList<int> minArray; 1699 minArray.fill(timeToY(QTime(23, 59)), d->mSelectedDates.count()); 1700 for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) { 1701 if (item) { 1702 int ymin = item->cellYTop(); 1703 int index = item->cellXLeft(); 1704 if (index >= 0 && index < (int)(d->mSelectedDates.count())) { 1705 if (ymin < minArray[index] && !d->mItemsToDelete.contains(item)) { 1706 minArray[index] = ymin; 1707 } 1708 } 1709 } 1710 } 1711 1712 return minArray; 1713 } 1714 1715 QList<int> Agenda::maxContentsY() const 1716 { 1717 QList<int> maxArray; 1718 maxArray.fill(timeToY(QTime(0, 0)), d->mSelectedDates.count()); 1719 for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) { 1720 if (item) { 1721 int ymax = item->cellYBottom(); 1722 1723 int index = item->cellXLeft(); 1724 if (index >= 0 && index < (int)(d->mSelectedDates.count())) { 1725 if (ymax > maxArray[index] && !d->mItemsToDelete.contains(item)) { 1726 maxArray[index] = ymax; 1727 } 1728 } 1729 } 1730 } 1731 1732 return maxArray; 1733 } 1734 1735 void Agenda::setStartTime(QTime startHour) 1736 { 1737 const double startPos = (startHour.hour() / 24. + startHour.minute() / 1440. + startHour.second() / 86400.) * d->mRows * gridSpacingY(); 1738 1739 verticalScrollBar()->setValue(startPos); 1740 } 1741 1742 /* 1743 Insert AgendaItem into agenda. 1744 */ 1745 AgendaItem::QPtr Agenda::insertItem(const KCalendarCore::Incidence::Ptr &incidence, 1746 const QDateTime &recurrenceId, 1747 int X, 1748 int YTop, 1749 int YBottom, 1750 int itemPos, 1751 int itemCount, 1752 bool isSelected) 1753 { 1754 if (d->mAllDayMode) { 1755 qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal."; 1756 return nullptr; 1757 } 1758 1759 d->mActionType = NOP; 1760 1761 AgendaItem::QPtr agendaItem = createAgendaItem(incidence, itemPos, itemCount, recurrenceId, isSelected); 1762 if (!agendaItem) { 1763 return {}; 1764 } 1765 1766 if (YTop >= d->mRows) { 1767 YBottom -= YTop - (d->mRows - 1); // Slide the item up into view. 1768 YTop = d->mRows - 1; 1769 } 1770 if (YBottom <= YTop) { 1771 qCDebug(CALENDARVIEW_LOG) << "Text:" << agendaItem->text() << " YSize<0"; 1772 YBottom = YTop; 1773 } 1774 1775 agendaItem->resize(int((X + 1) * d->mGridSpacingX) - int(X * d->mGridSpacingX), int(YTop * d->mGridSpacingY) - int((YBottom + 1) * d->mGridSpacingY)); 1776 agendaItem->setCellXY(X, YTop, YBottom); 1777 agendaItem->setCellXRight(X); 1778 agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence)); 1779 agendaItem->installEventFilter(this); 1780 1781 agendaItem->move(int(X * d->mGridSpacingX), int(YTop * d->mGridSpacingY)); 1782 1783 d->mItems.append(agendaItem); 1784 1785 placeSubCells(agendaItem); 1786 1787 agendaItem->show(); 1788 1789 marcus_bains(); 1790 1791 return agendaItem; 1792 } 1793 1794 /* 1795 Insert all-day AgendaItem into agenda. 1796 */ 1797 AgendaItem::QPtr Agenda::insertAllDayItem(const KCalendarCore::Incidence::Ptr &incidence, const QDateTime &recurrenceId, int XBegin, int XEnd, bool isSelected) 1798 { 1799 if (!d->mAllDayMode) { 1800 qCCritical(CALENDARVIEW_LOG) << "using this in non all-day mode is illegal."; 1801 return nullptr; 1802 } 1803 1804 d->mActionType = NOP; 1805 1806 AgendaItem::QPtr agendaItem = createAgendaItem(incidence, 1, 1, recurrenceId, isSelected); 1807 if (!agendaItem) { 1808 return {}; 1809 } 1810 1811 agendaItem->setCellXY(XBegin, 0, 0); 1812 agendaItem->setCellXRight(XEnd); 1813 1814 const double startIt = d->mGridSpacingX * (agendaItem->cellXLeft()); 1815 const double endIt = d->mGridSpacingX * (agendaItem->cellWidth() + agendaItem->cellXLeft()); 1816 1817 agendaItem->resize(int(endIt) - int(startIt), int(d->mGridSpacingY)); 1818 1819 agendaItem->installEventFilter(this); 1820 agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence)); 1821 agendaItem->move(int(XBegin * d->mGridSpacingX), 0); 1822 d->mItems.append(agendaItem); 1823 1824 placeSubCells(agendaItem); 1825 1826 agendaItem->show(); 1827 1828 return agendaItem; 1829 } 1830 1831 AgendaItem::QPtr 1832 Agenda::createAgendaItem(const KCalendarCore::Incidence::Ptr &incidence, int itemPos, int itemCount, const QDateTime &recurrenceId, bool isSelected) 1833 { 1834 if (!incidence) { 1835 qCWarning(CALENDARVIEW_LOG) << "Agenda::createAgendaItem() item is invalid."; 1836 return {}; 1837 } 1838 1839 AgendaItem::QPtr agendaItem = new AgendaItem(d->mAgendaView, d->mCalendar, incidence, itemPos, itemCount, recurrenceId, isSelected, this); 1840 1841 connect(agendaItem.data(), &AgendaItem::removeAgendaItem, this, &Agenda::removeAgendaItem); 1842 connect(agendaItem.data(), &AgendaItem::showAgendaItem, this, &Agenda::showAgendaItem); 1843 1844 d->mAgendaItemsById.insert(incidence->uid(), agendaItem); 1845 1846 return agendaItem; 1847 } 1848 1849 void Agenda::insertMultiItem(const KCalendarCore::Incidence::Ptr &event, 1850 const QDateTime &recurrenceId, 1851 int XBegin, 1852 int XEnd, 1853 int YTop, 1854 int YBottom, 1855 bool isSelected) 1856 { 1857 KCalendarCore::Event::Ptr ev = CalendarSupport::event(event); 1858 Q_ASSERT(ev); 1859 if (d->mAllDayMode) { 1860 qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal."; 1861 return; 1862 } 1863 1864 d->mActionType = NOP; 1865 int cellX; 1866 int cellYTop; 1867 int cellYBottom; 1868 QString newtext; 1869 int width = XEnd - XBegin + 1; 1870 int count = 0; 1871 AgendaItem::QPtr current = nullptr; 1872 QList<AgendaItem::QPtr> multiItems; 1873 int visibleCount = d->mSelectedDates.first().daysTo(d->mSelectedDates.last()); 1874 for (cellX = XBegin; cellX <= XEnd; ++cellX) { 1875 ++count; 1876 // Only add the items that are visible. 1877 if (cellX >= 0 && cellX <= visibleCount) { 1878 if (cellX == XBegin) { 1879 cellYTop = YTop; 1880 } else { 1881 cellYTop = 0; 1882 } 1883 if (cellX == XEnd) { 1884 cellYBottom = YBottom; 1885 } else { 1886 cellYBottom = rows() - 1; 1887 } 1888 newtext = QStringLiteral("(%1/%2): ").arg(count).arg(width); 1889 newtext.append(ev->summary()); 1890 1891 current = insertItem(event, recurrenceId, cellX, cellYTop, cellYBottom, count, width, isSelected); 1892 Q_ASSERT(current); 1893 current->setText(newtext); 1894 multiItems.append(current); 1895 } 1896 } 1897 1898 QList<AgendaItem::QPtr>::iterator it = multiItems.begin(); 1899 QList<AgendaItem::QPtr>::iterator e = multiItems.end(); 1900 1901 if (it != e) { // .first asserts if the list is empty 1902 AgendaItem::QPtr first = multiItems.first(); 1903 AgendaItem::QPtr last = multiItems.last(); 1904 AgendaItem::QPtr prev = nullptr; 1905 AgendaItem::QPtr next = nullptr; 1906 1907 while (it != e) { 1908 AgendaItem::QPtr item = *it; 1909 ++it; 1910 next = (it == e) ? nullptr : (*it); 1911 if (item) { 1912 item->setMultiItem((item == first) ? nullptr : first, prev, next, (item == last) ? nullptr : last); 1913 } 1914 prev = item; 1915 } 1916 } 1917 1918 marcus_bains(); 1919 } 1920 1921 void Agenda::removeIncidence(const KCalendarCore::Incidence::Ptr &incidence) 1922 { 1923 if (!incidence) { 1924 qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() incidence is invalid" << incidence->uid(); 1925 return; 1926 } 1927 1928 if (d->isQueuedForDeletion(incidence->uid())) { 1929 return; // It's already queued for deletion 1930 } 1931 1932 const AgendaItem::List agendaItems = d->mAgendaItemsById.values(incidence->uid()); 1933 if (agendaItems.isEmpty()) { 1934 // We're not displaying such item 1935 // qCDebug(CALENDARVIEW_LOG) << "Ignoring"; 1936 return; 1937 } 1938 for (const AgendaItem::QPtr &agendaItem : agendaItems) { 1939 if (agendaItem) { 1940 if (incidence->instanceIdentifier() != agendaItem->incidence()->instanceIdentifier()) { 1941 continue; 1942 } 1943 if (!removeAgendaItem(agendaItem)) { 1944 qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() Failed to remove " << incidence->uid(); 1945 } 1946 } 1947 } 1948 } 1949 1950 void Agenda::showAgendaItem(const AgendaItem::QPtr &agendaItem) 1951 { 1952 if (!agendaItem) { 1953 qCCritical(CALENDARVIEW_LOG) << "Show what?"; 1954 return; 1955 } 1956 1957 agendaItem->hide(); 1958 1959 agendaItem->setParent(this); 1960 1961 if (!d->mItems.contains(agendaItem)) { 1962 d->mItems.append(agendaItem); 1963 } 1964 placeSubCells(agendaItem); 1965 1966 agendaItem->show(); 1967 } 1968 1969 bool Agenda::removeAgendaItem(const AgendaItem::QPtr &agendaItem) 1970 { 1971 Q_ASSERT(agendaItem); 1972 // we found the item. Let's remove it and update the conflicts 1973 QList<AgendaItem::QPtr> conflictItems = agendaItem->conflictItems(); 1974 // removeChild(thisItem); 1975 1976 bool taken = d->mItems.removeAll(agendaItem) > 0; 1977 d->mAgendaItemsById.remove(agendaItem->incidence()->uid(), agendaItem); 1978 1979 QList<AgendaItem::QPtr>::iterator it; 1980 for (it = conflictItems.begin(); it != conflictItems.end(); ++it) { 1981 if (*it) { 1982 (*it)->setSubCells((*it)->subCells() - 1); 1983 } 1984 } 1985 1986 for (it = conflictItems.begin(); it != conflictItems.end(); ++it) { 1987 // the item itself is also in its own conflictItems list! 1988 if (*it && *it != agendaItem) { 1989 placeSubCells(*it); 1990 } 1991 } 1992 d->mItemsToDelete.append(agendaItem); 1993 d->mItemsQueuedForDeletion.insert(agendaItem->incidence()->uid()); 1994 agendaItem->setVisible(false); 1995 QTimer::singleShot(0, this, &Agenda::deleteItemsToDelete); 1996 return taken; 1997 } 1998 1999 void Agenda::deleteItemsToDelete() 2000 { 2001 qDeleteAll(d->mItemsToDelete); 2002 d->mItemsToDelete.clear(); 2003 d->mItemsQueuedForDeletion.clear(); 2004 } 2005 2006 /*QSizePolicy Agenda::sizePolicy() const 2007 { 2008 // Thought this would make the all-day event agenda minimum size and the 2009 // normal agenda take the remaining space. But it doesn't work. The QSplitter 2010 // don't seem to think that an Expanding widget needs more space than a 2011 // Preferred one. 2012 // But it doesn't hurt, so it stays. 2013 if (mAllDayMode) { 2014 return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); 2015 } else { 2016 return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); 2017 } 2018 }*/ 2019 2020 /* 2021 Overridden from QScrollView to provide proper resizing of AgendaItems. 2022 */ 2023 void Agenda::resizeEvent(QResizeEvent *ev) 2024 { 2025 QSize newSize(ev->size()); 2026 2027 if (d->mAllDayMode) { 2028 d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns; 2029 d->mGridSpacingY = newSize.height(); 2030 } else { 2031 d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns; 2032 // make sure that there are not more than 24 per day 2033 d->mGridSpacingY = static_cast<double>(newSize.height()) / d->mRows; 2034 if (d->mGridSpacingY < d->mDesiredGridSpacingY) { 2035 d->mGridSpacingY = d->mDesiredGridSpacingY; 2036 } 2037 } 2038 calculateWorkingHours(); 2039 2040 QTimer::singleShot(0, this, &Agenda::resizeAllContents); 2041 Q_EMIT gridSpacingYChanged(d->mGridSpacingY * 4); 2042 2043 QWidget::resizeEvent(ev); 2044 updateGeometry(); 2045 } 2046 2047 void Agenda::resizeAllContents() 2048 { 2049 double subCellWidth; 2050 for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) { 2051 if (item) { 2052 subCellWidth = calcSubCellWidth(item); 2053 placeAgendaItem(item, subCellWidth); 2054 } 2055 } 2056 /* 2057 if (d->mAllDayMode) { 2058 foreach (const AgendaItem::QPtr &item, d->mItems) { 2059 if (item) { 2060 subCellWidth = calcSubCellWidth(item); 2061 placeAgendaItem(item, subCellWidth); 2062 } 2063 } 2064 } else { 2065 foreach (const AgendaItem::QPtr &item, d->mItems) { 2066 if (item) { 2067 subCellWidth = calcSubCellWidth(item); 2068 placeAgendaItem(item, subCellWidth); 2069 } 2070 } 2071 } 2072 */ 2073 checkScrollBoundaries(); 2074 marcus_bains(); 2075 update(); 2076 } 2077 2078 void Agenda::scrollUp() 2079 { 2080 int currentValue = verticalScrollBar()->value(); 2081 verticalScrollBar()->setValue(currentValue - d->mScrollOffset); 2082 } 2083 2084 void Agenda::scrollDown() 2085 { 2086 int currentValue = verticalScrollBar()->value(); 2087 verticalScrollBar()->setValue(currentValue + d->mScrollOffset); 2088 } 2089 2090 QSize Agenda::minimumSize() const 2091 { 2092 return sizeHint(); 2093 } 2094 2095 QSize Agenda::minimumSizeHint() const 2096 { 2097 return sizeHint(); 2098 } 2099 2100 int Agenda::minimumHeight() const 2101 { 2102 // all day agenda never has scrollbars and the scrollarea will 2103 // resize it to fit exactly on the viewport. 2104 2105 if (d->mAllDayMode) { 2106 return 0; 2107 } else { 2108 return d->mGridSpacingY * d->mRows; 2109 } 2110 } 2111 2112 void Agenda::updateConfig() 2113 { 2114 const double oldGridSpacingY = d->mGridSpacingY; 2115 2116 if (!d->mAllDayMode) { 2117 d->mDesiredGridSpacingY = d->preferences()->hourSize(); 2118 if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) { 2119 d->mDesiredGridSpacingY = 10; 2120 } 2121 2122 /* 2123 // make sure that there are not more than 24 per day 2124 d->mGridSpacingY = static_cast<double>(height()) / d->mRows; 2125 if (d->mGridSpacingY < d->mDesiredGridSpacingY || true) { 2126 d->mGridSpacingY = d->mDesiredGridSpacingY; 2127 } 2128 */ 2129 2130 // can be two doubles equal?, it's better to compare them with an epsilon 2131 if (fabs(oldGridSpacingY - d->mDesiredGridSpacingY) > 0.1) { 2132 d->mGridSpacingY = d->mDesiredGridSpacingY; 2133 updateGeometry(); 2134 } 2135 } 2136 2137 calculateWorkingHours(); 2138 2139 marcus_bains(); 2140 } 2141 2142 void Agenda::checkScrollBoundaries() 2143 { 2144 // Invalidate old values to force update 2145 d->mOldLowerScrollValue = -1; 2146 d->mOldUpperScrollValue = -1; 2147 2148 checkScrollBoundaries(verticalScrollBar()->value()); 2149 } 2150 2151 void Agenda::checkScrollBoundaries(int v) 2152 { 2153 int yMin = int((v) / d->mGridSpacingY); 2154 int yMax = int((v + d->mScrollArea->height()) / d->mGridSpacingY); 2155 2156 if (yMin != d->mOldLowerScrollValue) { 2157 d->mOldLowerScrollValue = yMin; 2158 Q_EMIT lowerYChanged(yMin); 2159 } 2160 if (yMax != d->mOldUpperScrollValue) { 2161 d->mOldUpperScrollValue = yMax; 2162 Q_EMIT upperYChanged(yMax); 2163 } 2164 } 2165 2166 int Agenda::visibleContentsYMin() const 2167 { 2168 int v = verticalScrollBar()->value(); 2169 return int(v / d->mGridSpacingY); 2170 } 2171 2172 int Agenda::visibleContentsYMax() const 2173 { 2174 int v = verticalScrollBar()->value(); 2175 return int((v + d->mScrollArea->height()) / d->mGridSpacingY); 2176 } 2177 2178 void Agenda::deselectItem() 2179 { 2180 if (d->mSelectedItem.isNull()) { 2181 return; 2182 } 2183 2184 const KCalendarCore::Incidence::Ptr selectedItem = d->mSelectedItem->incidence(); 2185 2186 for (AgendaItem::QPtr item : std::as_const(d->mItems)) { 2187 if (item) { 2188 const KCalendarCore::Incidence::Ptr itemInc = item->incidence(); 2189 if (itemInc && selectedItem && itemInc->uid() == selectedItem->uid()) { 2190 item->select(false); 2191 } 2192 } 2193 } 2194 2195 d->mSelectedItem = nullptr; 2196 } 2197 2198 void Agenda::selectItem(const AgendaItem::QPtr &item) 2199 { 2200 if ((AgendaItem::QPtr)d->mSelectedItem == item) { 2201 return; 2202 } 2203 deselectItem(); 2204 if (item == nullptr) { 2205 Q_EMIT incidenceSelected(KCalendarCore::Incidence::Ptr(), QDate()); 2206 return; 2207 } 2208 d->mSelectedItem = item; 2209 d->mSelectedItem->select(); 2210 Q_ASSERT(d->mSelectedItem->incidence()); 2211 d->mSelectedId = d->mSelectedItem->incidence()->uid(); 2212 2213 for (AgendaItem::QPtr item : std::as_const(d->mItems)) { 2214 if (item && item->incidence()->uid() == d->mSelectedId) { 2215 item->select(); 2216 } 2217 } 2218 Q_EMIT incidenceSelected(d->mSelectedItem->incidence(), d->mSelectedItem->occurrenceDate()); 2219 } 2220 2221 void Agenda::selectIncidenceByUid(const QString &uid) 2222 { 2223 for (const AgendaItem::QPtr &item : std::as_const(d->mItems)) { 2224 if (item && item->incidence()->uid() == uid) { 2225 selectItem(item); 2226 break; 2227 } 2228 } 2229 } 2230 2231 void Agenda::selectItem(const Akonadi::Item &item) 2232 { 2233 selectIncidenceByUid(Akonadi::CalendarUtils::incidence(item)->uid()); 2234 } 2235 2236 // This function seems never be called. 2237 void Agenda::keyPressEvent(QKeyEvent *kev) 2238 { 2239 switch (kev->key()) { 2240 case Qt::Key_PageDown: 2241 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd); 2242 break; 2243 case Qt::Key_PageUp: 2244 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub); 2245 break; 2246 case Qt::Key_Down: 2247 verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd); 2248 break; 2249 case Qt::Key_Up: 2250 verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub); 2251 break; 2252 default:; 2253 } 2254 } 2255 2256 void Agenda::calculateWorkingHours() 2257 { 2258 d->mWorkingHoursEnable = !d->mAllDayMode; 2259 2260 QTime tmp = d->preferences()->workingHoursStart().time(); 2261 d->mWorkingHoursYTop = int(4 * d->mGridSpacingY * (tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600.)); 2262 tmp = d->preferences()->workingHoursEnd().time(); 2263 d->mWorkingHoursYBottom = int(4 * d->mGridSpacingY * (tmp.hour() + tmp.minute() / 60. + tmp.second() / 3600.) - 1); 2264 } 2265 2266 void Agenda::setDateList(const KCalendarCore::DateList &selectedDates) 2267 { 2268 d->mSelectedDates = selectedDates; 2269 marcus_bains(); 2270 } 2271 2272 KCalendarCore::DateList Agenda::dateList() const 2273 { 2274 return d->mSelectedDates; 2275 } 2276 2277 void Agenda::setCalendar(const MultiViewCalendar::Ptr &cal) 2278 { 2279 d->mCalendar = cal; 2280 } 2281 2282 void Agenda::setIncidenceChanger(Akonadi::IncidenceChanger *changer) 2283 { 2284 d->mChanger = changer; 2285 } 2286 2287 void Agenda::setHolidayMask(QList<bool> *mask) 2288 { 2289 d->mHolidayMask = mask; 2290 } 2291 2292 void Agenda::contentsMousePressEvent(QMouseEvent *event) 2293 { 2294 Q_UNUSED(event) 2295 } 2296 2297 QSize Agenda::sizeHint() const 2298 { 2299 if (d->mAllDayMode) { 2300 return QWidget::sizeHint(); 2301 } else { 2302 return {parentWidget()->width(), static_cast<int>(d->mGridSpacingY * d->mRows)}; 2303 } 2304 } 2305 2306 QScrollBar *Agenda::verticalScrollBar() const 2307 { 2308 return d->mScrollArea->verticalScrollBar(); 2309 } 2310 2311 QScrollArea *Agenda::scrollArea() const 2312 { 2313 return d->mScrollArea; 2314 } 2315 2316 AgendaItem::List Agenda::agendaItems(const QString &uid) const 2317 { 2318 return d->mAgendaItemsById.values(uid); 2319 } 2320 2321 AgendaScrollArea::AgendaScrollArea(bool isAllDay, AgendaView *agendaView, bool isInteractive, QWidget *parent) 2322 : QScrollArea(parent) 2323 { 2324 if (isAllDay) { 2325 mAgenda = new Agenda(agendaView, this, 1, isInteractive); 2326 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 2327 } else { 2328 mAgenda = new Agenda(agendaView, this, 1, 96, agendaView->preferences()->hourSize(), isInteractive); 2329 } 2330 2331 setWidgetResizable(true); 2332 setWidget(mAgenda); 2333 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 2334 2335 mAgenda->setStartTime(agendaView->preferences()->dayBegins().time()); 2336 } 2337 2338 AgendaScrollArea::~AgendaScrollArea() = default; 2339 2340 Agenda *AgendaScrollArea::agenda() const 2341 { 2342 return mAgenda; 2343 } 2344 2345 #include "moc_agenda.cpp"