Clazy++
[kdepim.git] / eventviews / src / agenda / agenda.cpp
blobc2d30f19eeab231d6d514f86914e592ccb3f2657
1 /*
2 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
3 Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
4 Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
5 Author: Kevin Krammer, krake@kdab.com
6 Author: Sergio Martins, sergio@kdab.com
8 Marcus Bains line.
9 Copyright (c) 2001 Ali Rahimi <ali@mit.edu>
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License along
22 with this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 As a special exception, permission is given to link this program
26 with any edition of Qt, and distribute the resulting executable,
27 without including the source code for Qt in the source distribution.
29 #include "agenda.h"
30 #include "agendaitem.h"
31 #include "agendaview.h"
32 #include "viewcalendar.h"
33 #include "prefs.h"
35 #include <Akonadi/Calendar/ETMCalendar>
36 #include <Akonadi/Calendar/IncidenceChanger>
37 #include <CalendarSupport/Utils>
39 #include <KCalCore/Incidence>
40 #include <KCalCore/Todo>
42 #include <KCalUtils/RecurrenceActions>
44 #include <KMessageBox>
45 #include "calendarview_debug.h"
47 #include <QApplication>
48 #include <QLabel>
49 #include <KLocalizedString>
50 #include <QMouseEvent>
51 #include <QPainter>
52 #include <QPointer>
53 #include <QResizeEvent>
54 #include <QScrollBar>
55 #include <QTimer>
56 #include <QHash>
57 #include <QWheelEvent>
58 #include <QMultiHash>
60 #include <cmath> // for fabs()
62 using namespace EventViews;
64 ///////////////////////////////////////////////////////////////////////////////
65 class Q_DECL_HIDDEN MarcusBains::Private
67 public:
68 Private(EventView *eventView, Agenda *agenda)
69 : mEventView(eventView), mAgenda(agenda),
70 mTimer(Q_NULLPTR), mTimeBox(Q_NULLPTR), mOldTodayCol(-1)
74 int todayColumn() const;
76 public:
77 EventView *mEventView;
78 Agenda *mAgenda;
79 QTimer *mTimer;
80 QLabel *mTimeBox; // Label showing the current time
81 KDateTime mOldDateTime;
82 int mOldTodayCol;
85 int MarcusBains::Private::todayColumn() const
87 const QDate currentDate = QDate::currentDate();
89 int col = 0;
90 const KCalCore::DateList dateList = mAgenda->dateList();
91 foreach (const QDate &date, dateList) {
92 if (date == currentDate) {
93 return QApplication::isRightToLeft() ? mAgenda->columns() - 1 - col : col;
95 ++col;
98 return -1;
101 MarcusBains::MarcusBains(EventView *eventView, Agenda *agenda)
102 : QFrame(agenda), d(new Private(eventView, agenda))
104 d->mTimeBox = new QLabel(d->mAgenda);
105 d->mTimeBox->setAlignment(Qt::AlignRight | Qt::AlignBottom);
107 d->mTimer = new QTimer(this);
108 d->mTimer->setSingleShot(true);
109 connect(d->mTimer, &QTimer::timeout, this, &MarcusBains::updateLocation);
110 d->mTimer->start(0);
113 MarcusBains::~MarcusBains()
115 delete d;
118 void MarcusBains::updateLocation()
120 updateLocationRecalc();
123 void MarcusBains::updateLocationRecalc(bool recalculate)
125 const bool showSeconds = d->mEventView->preferences()->marcusBainsShowSeconds();
126 const QColor color = d->mEventView->preferences()->agendaMarcusBainsLineLineColor();
128 const KDateTime now = KDateTime::currentLocalDateTime();
129 const QTime time = now.time();
131 if (now.date() != d->mOldDateTime.date()) {
132 recalculate = true; // New day
134 const int todayCol = recalculate ? d->todayColumn() : d->mOldTodayCol;
136 // Number of minutes since beginning of the day
137 const int minutes = time.hour() * 60 + time.minute();
138 const int minutesPerCell = 24 * 60 / d->mAgenda->rows();
140 d->mOldDateTime = now;
141 d->mOldTodayCol = todayCol;
143 int y = int(minutes * d->mAgenda->gridSpacingY() / minutesPerCell);
144 int x = int(d->mAgenda->gridSpacingX() * todayCol);
146 bool hideIt = !(d->mEventView->preferences()->marcusBainsEnabled());
147 if (!isHidden() && (hideIt || (todayCol < 0))) {
148 hide();
149 d->mTimeBox->hide();
150 return;
153 if (isHidden() && !hideIt) {
154 show();
155 d->mTimeBox->show();
158 /* Line */
159 // It seems logical to adjust the line width with the label's font weight
160 const int fw = d->mEventView->preferences()->agendaMarcusBainsLineFont().weight();
161 setLineWidth(1 + abs(fw - QFont::Normal) / QFont::Light);
162 setFrameStyle(QFrame::HLine | QFrame::Plain);
163 QPalette pal = palette();
164 pal.setColor(QPalette::Window, color); // for Oxygen
165 pal.setColor(QPalette::WindowText, color); // for Plastique
166 setPalette(pal);
167 if (recalculate) {
168 setFixedSize(int(d->mAgenda->gridSpacingX()), 1);
170 move(x, y);
171 raise();
173 /* Label */
174 d->mTimeBox->setFont(d->mEventView->preferences()->agendaMarcusBainsLineFont());
175 QPalette pal1 = d->mTimeBox->palette();
176 pal1.setColor(QPalette::WindowText, color);
177 d->mTimeBox->setPalette(pal1);
178 d->mTimeBox->setText(QLocale::system().toString(time, showSeconds ? QLocale::LongFormat : QLocale::ShortFormat));
179 d->mTimeBox->adjustSize();
180 if (y - d->mTimeBox->height() >= 0) {
181 y -= d->mTimeBox->height();
182 } else {
183 y++;
185 if (x - d->mTimeBox->width() + d->mAgenda->gridSpacingX() > 0) {
186 x += int(d->mAgenda->gridSpacingX() - d->mTimeBox->width() - 1);
187 } else {
188 x++;
190 d->mTimeBox->move(x, y);
191 d->mTimeBox->raise();
193 if (showSeconds || recalculate) {
194 d->mTimer->start(1000);
195 } else {
196 d->mTimer->start(1000 * (60 - time.second()));
200 ////////////////////////////////////////////////////////////////////////////
202 class Q_DECL_HIDDEN Agenda::Private
204 public:
205 Private(AgendaView *agendaView, QScrollArea *scrollArea,
206 int columns, int rows, int rowSize, bool isInteractive)
207 : mAgendaView(agendaView), mScrollArea(scrollArea), mAllDayMode(false),
208 mColumns(columns), mRows(rows), mGridSpacingX(0.0), mGridSpacingY(rowSize),
209 mDesiredGridSpacingY(rowSize), mChanger(Q_NULLPTR),
210 mResizeBorderWidth(0), mScrollBorderWidth(0), mScrollDelay(0), mScrollOffset(0),
211 mWorkingHoursEnable(false), mHolidayMask(Q_NULLPTR), mWorkingHoursYTop(0),
212 mWorkingHoursYBottom(0), mHasSelection(0), mSelectedId(-1), mMarcusBains(Q_NULLPTR),
213 mActionType(Agenda::NOP), mItemMoved(false), mOldLowerScrollValue(0),
214 mOldUpperScrollValue(0), mReturnPressed(false), mIsInteractive(isInteractive)
216 if (mGridSpacingY < 4 || mGridSpacingY > 30) {
217 mGridSpacingY = 10;
221 public:
222 PrefsPtr preferences() const
224 return mAgendaView->preferences();
227 bool isQueuedForDeletion(const QString &uid) const
229 // if mAgendaItemsById contains it it means that a createAgendaItem() was called
230 // before the previous agenda items were deleted.
231 return mItemsQueuedForDeletion.contains(uid) && !mAgendaItemsById.contains(uid);
234 QMultiHash<QString, AgendaItem::QPtr> mAgendaItemsById; // It's a QMultiHash because recurring incidences might have many agenda items
235 QSet<QString> mItemsQueuedForDeletion;
237 AgendaView *mAgendaView;
238 QScrollArea *mScrollArea;
240 bool mAllDayMode;
242 // Number of Columns/Rows of agenda grid
243 int mColumns;
244 int mRows;
246 // Width and height of agenda cells. mDesiredGridSpacingY is the height
247 // set in the config. The actual height might be larger since otherwise
248 // more than 24 hours might be displayed.
249 double mGridSpacingX;
250 double mGridSpacingY;
251 double mDesiredGridSpacingY;
253 Akonadi::IncidenceChanger *mChanger;
255 // size of border, where mouse action will resize the AgendaItem
256 int mResizeBorderWidth;
258 // size of border, where mouse mve will cause a scroll of the agenda
259 int mScrollBorderWidth;
260 int mScrollDelay;
261 int mScrollOffset;
263 QTimer mScrollUpTimer;
264 QTimer mScrollDownTimer;
266 // Cells to store Move and Resize coordiantes while performing the action
267 QPoint mStartCell;
268 QPoint mEndCell;
270 // Working Hour coordiantes
271 bool mWorkingHoursEnable;
272 QVector<bool> *mHolidayMask;
273 int mWorkingHoursYTop;
274 int mWorkingHoursYBottom;
276 // Selection
277 bool mHasSelection;
278 QPoint mSelectionStartPoint;
279 QPoint mSelectionStartCell;
280 QPoint mSelectionEndCell;
282 // List of dates to be displayed
283 KCalCore::DateList mSelectedDates;
285 // The AgendaItem, which has been right-clicked last
286 QPointer<AgendaItem> mClickedItem;
288 // The AgendaItem, which is being moved/resized
289 QPointer<AgendaItem> mActionItem;
291 // Currently selected item
292 QPointer<AgendaItem> mSelectedItem;
293 // Uid of the last selected incidence. Used for reselecting in situations
294 // where the selected item points to a no longer valid incidence, for
295 // example during resource reload.
296 QString mSelectedId;
298 // The Marcus Bains Line widget.
299 MarcusBains *mMarcusBains;
301 MouseActionType mActionType;
303 bool mItemMoved;
305 // List of all Items contained in agenda
306 QList<AgendaItem::QPtr> mItems;
307 QList<AgendaItem::QPtr> mItemsToDelete;
309 int mOldLowerScrollValue;
310 int mOldUpperScrollValue;
312 bool mReturnPressed;
313 bool mIsInteractive;
315 MultiViewCalendar::Ptr mCalendar;
319 Create an agenda widget with rows rows and columns columns.
321 Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea,
322 int columns, int rows, int rowSize, bool isInteractive)
323 : QWidget(scrollArea),
324 d(new Private(agendaView, scrollArea, columns, rows, rowSize, isInteractive))
326 setMouseTracking(true);
328 init();
332 Create an agenda widget with columns columns and one row. This is used for
333 all-day events.
335 Agenda::Agenda(AgendaView *agendaView, QScrollArea *scrollArea,
336 int columns, bool isInteractive)
337 : QWidget(scrollArea), d(new Private(agendaView, scrollArea, columns, 1, 24,
338 isInteractive))
340 d->mAllDayMode = true;
342 init();
345 Agenda::~Agenda()
347 delete d->mMarcusBains;
348 delete d;
351 KCalCore::Incidence::Ptr Agenda::selectedIncidence() const
353 return d->mSelectedItem ? d->mSelectedItem->incidence() : KCalCore::Incidence::Ptr();
356 QDate Agenda::selectedIncidenceDate() const
358 return d->mSelectedItem ? d->mSelectedItem->occurrenceDate() : QDate();
361 QString Agenda::lastSelectedItemUid() const
363 return d->mSelectedId;
366 void Agenda::init()
368 setAttribute(Qt::WA_OpaquePaintEvent);
370 d->mGridSpacingX = static_cast<double>(d->mScrollArea->width()) / d->mColumns;
371 d->mDesiredGridSpacingY = d->preferences()->hourSize();
372 if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) {
373 d->mDesiredGridSpacingY = 10;
376 // make sure that there are not more than 24 per day
377 d->mGridSpacingY = static_cast<double>(height()) / d->mRows;
378 if (d->mGridSpacingY < d->mDesiredGridSpacingY) {
379 d->mGridSpacingY = d->mDesiredGridSpacingY;
382 d->mResizeBorderWidth = 12;
383 d->mScrollBorderWidth = 12;
384 d->mScrollDelay = 30;
385 d->mScrollOffset = 10;
387 // Grab key strokes for keyboard navigation of agenda. Seems to have no
388 // effect. Has to be fixed.
389 setFocusPolicy(Qt::WheelFocus);
391 connect(&d->mScrollUpTimer, &QTimer::timeout, this, &Agenda::scrollUp);
392 connect(&d->mScrollDownTimer, &QTimer::timeout, this, &Agenda::scrollDown);
394 d->mStartCell = QPoint(0, 0);
395 d->mEndCell = QPoint(0, 0);
397 d->mHasSelection = false;
398 d->mSelectionStartPoint = QPoint(0, 0);
399 d->mSelectionStartCell = QPoint(0, 0);
400 d->mSelectionEndCell = QPoint(0, 0);
402 d->mOldLowerScrollValue = -1;
403 d->mOldUpperScrollValue = -1;
405 d->mClickedItem = Q_NULLPTR;
407 d->mActionItem = Q_NULLPTR;
408 d->mActionType = NOP;
409 d->mItemMoved = false;
411 d->mSelectedItem = Q_NULLPTR;
412 d->mSelectedId = -1;
414 setAcceptDrops(true);
415 installEventFilter(this);
417 /* resizeContents(int(mGridSpacingX * mColumns), int(mGridSpacingY * mRows)); */
419 d->mScrollArea->viewport()->update();
420 // mScrollArea->viewport()->setAttribute(Qt::WA_NoSystemBackground, true);
421 d->mScrollArea->viewport()->setFocusPolicy(Qt::WheelFocus);
423 calculateWorkingHours();
425 connect(verticalScrollBar(), SIGNAL(valueChanged(int)),
426 SLOT(checkScrollBoundaries(int)));
428 // Create the Marcus Bains line.
429 if (d->mAllDayMode) {
430 d->mMarcusBains = Q_NULLPTR;
431 } else {
432 d->mMarcusBains = new MarcusBains(d->mAgendaView, this);
436 void Agenda::clear()
438 qDeleteAll(d->mItems);
439 qDeleteAll(d->mItemsToDelete);
440 d->mItems.clear();
441 d->mItemsToDelete.clear();
442 d->mAgendaItemsById.clear();
443 d->mItemsQueuedForDeletion.clear();
445 d->mSelectedItem = Q_NULLPTR;
447 clearSelection();
450 void Agenda::clearSelection()
452 d->mHasSelection = false;
453 d->mActionType = NOP;
454 update();
457 void Agenda::marcus_bains()
459 if (d->mMarcusBains) {
460 d->mMarcusBains->updateLocationRecalc(true);
464 void Agenda::changeColumns(int columns)
466 if (columns == 0) {
467 qCDebug(CALENDARVIEW_LOG) << "called with argument 0";
468 return;
471 clear();
472 d->mColumns = columns;
473 // setMinimumSize(mColumns * 10, mGridSpacingY + 1);
474 // init();
475 // update();
477 QResizeEvent event(size(), size());
479 QApplication::sendEvent(this, &event);
482 int Agenda::columns() const
484 return d->mColumns;
487 int Agenda::rows() const
489 return d->mRows;
492 double Agenda::gridSpacingX() const
494 return d->mGridSpacingX;
497 double Agenda::gridSpacingY() const
499 return d->mGridSpacingY;
503 This is the eventFilter function, which gets all events from the AgendaItems
504 contained in the agenda. It has to handle moving and resizing for all items.
506 bool Agenda::eventFilter(QObject *object, QEvent *event)
508 switch (event->type()) {
509 case QEvent::MouseButtonPress:
510 case QEvent::MouseButtonDblClick:
511 case QEvent::MouseButtonRelease:
512 case QEvent::MouseMove:
513 return eventFilter_mouse(object, static_cast<QMouseEvent *>(event));
514 #ifndef QT_NO_WHEELEVENT
515 case QEvent::Wheel:
516 return eventFilter_wheel(object, static_cast<QWheelEvent *>(event));
517 #endif
518 case QEvent::KeyPress:
519 case QEvent::KeyRelease:
520 return eventFilter_key(object, static_cast<QKeyEvent *>(event));
522 case (QEvent::Leave):
523 #ifndef QT_NO_CURSOR
524 if (!d->mActionItem) {
525 setCursor(Qt::ArrowCursor);
527 #endif
529 if (object == this) {
530 // so timelabels hide the mouse cursor
531 Q_EMIT leaveAgenda();
533 return true;
535 case QEvent::Enter:
536 Q_EMIT enterAgenda();
537 return QWidget::eventFilter(object, event);
539 #ifndef QT_NO_DRAGANDDROP
540 #ifndef KORG_NODND
541 case QEvent::DragEnter:
542 case QEvent::DragMove:
543 case QEvent::DragLeave:
544 case QEvent::Drop:
545 // case QEvent::DragResponse:
546 return eventFilter_drag(object, static_cast<QDropEvent *>(event));
547 #endif
548 #endif
550 default:
551 return QWidget::eventFilter(object, event);
555 bool Agenda::eventFilter_drag(QObject *obj, QDropEvent *de)
557 #ifndef QT_NO_DRAGANDDROP
558 const QMimeData *md = de->mimeData();
560 switch (de->type()) {
561 case QEvent::DragEnter:
562 case QEvent::DragMove:
563 if (!CalendarSupport::canDecode(md)) {
564 return false;
567 if (CalendarSupport::mimeDataHasIncidence(md)) {
568 de->accept();
569 } else {
570 de->ignore();
572 return true;
573 break;
574 case QEvent::DragLeave:
575 return false;
576 break;
577 case QEvent::Drop: {
578 if (!CalendarSupport::canDecode(md)) {
579 return false;
582 const QList<QUrl> incidenceUrls = CalendarSupport::incidenceItemUrls(md);
583 const KCalCore::Incidence::List incidences =
584 CalendarSupport::incidences(md, d->mCalendar->mETMCalendar->timeSpec());
586 Q_ASSERT(!incidenceUrls.isEmpty() || !incidences.isEmpty());
588 de->setDropAction(Qt::MoveAction);
590 QWidget *dropTarget = qobject_cast<QWidget *>(obj);
591 QPoint dropPosition = de->pos();
592 if (dropTarget && dropTarget != this) {
593 dropPosition = dropTarget->mapTo(this, dropPosition);
596 const QPoint gridPosition = contentsToGrid(dropPosition);
597 if (!incidenceUrls.isEmpty()) {
598 Q_EMIT droppedIncidences(incidenceUrls, gridPosition, d->mAllDayMode);
599 } else {
600 Q_EMIT droppedIncidences(incidences, gridPosition, d->mAllDayMode);
602 return true;
604 break;
606 case QEvent::DragResponse:
607 default:
608 break;
610 #endif
611 return false;
614 #ifndef QT_NO_WHEELEVENT
615 bool Agenda::eventFilter_wheel(QObject *object, QWheelEvent *e)
617 QPoint viewportPos;
618 bool accepted = false;
619 if ((e->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier) {
620 if (object != this) {
621 viewportPos = ((QWidget *) object)->mapToParent(e->pos());
622 } else {
623 viewportPos = e->pos();
625 //qCDebug(CALENDARVIEW_LOG) << type:" << e->type() << "delta:" << e->delta();
626 Q_EMIT zoomView(-e->delta(),
627 contentsToGrid(viewportPos), Qt::Horizontal);
628 accepted = true;
631 if ((e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) {
632 if (object != this) {
633 viewportPos = ((QWidget *)object)->mapToParent(e->pos());
634 } else {
635 viewportPos = e->pos();
637 Q_EMIT zoomView(-e->delta(), contentsToGrid(viewportPos), Qt::Vertical);
638 Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos)));
639 accepted = true;
641 if (accepted) {
642 e->accept();
644 return accepted;
646 #endif
648 bool Agenda::eventFilter_key(QObject *, QKeyEvent *ke)
650 return d->mAgendaView->processKeyEvent(ke);
653 bool Agenda::eventFilter_mouse(QObject *object, QMouseEvent *me)
655 QPoint viewportPos;
656 if (object != this) {
657 viewportPos = static_cast<QWidget *>(object)->mapToParent(me->pos());
658 } else {
659 viewportPos = me->pos();
662 switch (me->type()) {
663 case QEvent::MouseButtonPress:
664 if (object != this) {
665 if (me->button() == Qt::RightButton) {
666 d->mClickedItem = qobject_cast<AgendaItem *>(object);
667 if (d->mClickedItem) {
668 selectItem(d->mClickedItem);
669 Q_EMIT showIncidencePopupSignal(d->mClickedItem->incidence(),
670 d->mClickedItem->occurrenceDate());
672 } else {
673 AgendaItem::QPtr item = qobject_cast<AgendaItem *>(object);
674 if (item) {
675 KCalCore::Incidence::Ptr incidence = item->incidence();
676 if (incidence->isReadOnly()) {
677 d->mActionItem = Q_NULLPTR;
678 } else {
679 d->mActionItem = item;
680 startItemAction(viewportPos);
682 // Warning: do selectItem() as late as possible, since all
683 // sorts of things happen during this call. Some can lead to
684 // this filter being run again and mActionItem being set to
685 // null.
686 selectItem(item);
689 } else {
690 if (me->button() == Qt::RightButton) {
691 // if mouse pointer is not in selection, select the cell below the cursor
692 QPoint gpos = contentsToGrid(viewportPos);
693 if (!ptInSelection(gpos)) {
694 d->mSelectionStartCell = gpos;
695 d->mSelectionEndCell = gpos;
696 d->mHasSelection = true;
697 Q_EMIT newStartSelectSignal();
698 Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell);
699 // updateContents();
701 showNewEventPopupSignal();
702 } else {
703 selectItem(Q_NULLPTR);
704 d->mActionItem = Q_NULLPTR;
705 #ifndef QT_NO_CURSOR
706 setCursor(Qt::ArrowCursor);
707 #endif
708 startSelectAction(viewportPos);
709 update();
712 break;
714 case QEvent::MouseButtonRelease:
715 if (d->mActionItem) {
716 endItemAction();
717 } else if (d->mActionType == SELECT) {
718 endSelectAction(viewportPos);
720 // This nasty gridToContents(contentsToGrid(..)) is needed to
721 // avoid an offset of a few pixels. Don't ask me why...
722 Q_EMIT mousePosSignal(gridToContents(contentsToGrid(viewportPos)));
723 break;
725 case QEvent::MouseMove: {
726 if (!d->mIsInteractive) {
727 return true;
730 // This nasty gridToContents(contentsToGrid(..)) is needed todos
731 // avoid an offset of a few pixels. Don't ask me why...
732 QPoint indicatorPos = gridToContents(contentsToGrid(viewportPos));
733 if (object != this) {
734 AgendaItem::QPtr moveItem = qobject_cast<AgendaItem *>(object);
735 KCalCore::Incidence::Ptr incidence = moveItem ? moveItem->incidence() : KCalCore::Incidence::Ptr();
736 if (incidence && !incidence->isReadOnly()) {
737 if (!d->mActionItem) {
738 setNoActionCursor(moveItem, viewportPos);
739 } else {
740 performItemAction(viewportPos);
742 if (d->mActionType == MOVE) {
743 // show cursor at the current begin of the item
744 AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem();
745 if (!firstItem) {
746 firstItem = d->mActionItem;
748 indicatorPos = gridToContents(
749 QPoint(firstItem->cellXLeft(), firstItem->cellYTop()));
751 } else if (d->mActionType == RESIZEBOTTOM) {
752 // RESIZETOP is handled correctly, only resizebottom works differently
753 indicatorPos = gridToContents(
754 QPoint(d->mActionItem->cellXLeft(), d->mActionItem->cellYBottom() + 1));
757 } // If we have an action item
758 } // If move item && !read only
759 } else {
760 if (d->mActionType == SELECT) {
761 performSelectAction(viewportPos);
763 // show cursor at end of timespan
764 if (((d->mStartCell.y() < d->mEndCell.y()) &&
765 (d->mEndCell.x() >= d->mStartCell.x())) ||
766 (d->mEndCell.x() > d->mStartCell.x())) {
767 indicatorPos = gridToContents(QPoint(d->mEndCell.x(), d->mEndCell.y() + 1));
768 } else {
769 indicatorPos = gridToContents(d->mEndCell);
773 Q_EMIT mousePosSignal(indicatorPos);
774 break;
777 case QEvent::MouseButtonDblClick:
778 if (object == this) {
779 selectItem(Q_NULLPTR);
780 Q_EMIT newEventSignal();
781 } else {
782 AgendaItem::QPtr doubleClickedItem = qobject_cast<AgendaItem *>(object);
783 if (doubleClickedItem) {
784 selectItem(doubleClickedItem);
785 Q_EMIT editIncidenceSignal(doubleClickedItem->incidence());
788 break;
790 default:
791 break;
794 return true;
797 bool Agenda::ptInSelection(const QPoint &gpos) const
799 if (!d->mHasSelection) {
800 return false;
801 } else if (gpos.x() < d->mSelectionStartCell.x() || gpos.x() > d->mSelectionEndCell.x()) {
802 return false;
803 } else if ((gpos.x() == d->mSelectionStartCell.x()) &&
804 (gpos.y() < d->mSelectionStartCell.y())) {
805 return false;
806 } else if ((gpos.x() == d->mSelectionEndCell.x()) &&
807 (gpos.y() > d->mSelectionEndCell.y())) {
808 return false;
810 return true;
813 void Agenda::startSelectAction(const QPoint &viewportPos)
815 Q_EMIT newStartSelectSignal();
817 d->mActionType = SELECT;
818 d->mSelectionStartPoint = viewportPos;
819 d->mHasSelection = true;
821 QPoint pos = viewportPos;
822 QPoint gpos = contentsToGrid(pos);
824 // Store new selection
825 d->mStartCell = gpos;
826 d->mEndCell = gpos;
827 d->mSelectionStartCell = gpos;
828 d->mSelectionEndCell = gpos;
830 // updateContents();
833 void Agenda::performSelectAction(const QPoint &pos)
835 const QPoint gpos = contentsToGrid(pos);
837 // Scroll if cursor was moved to upper or lower end of agenda.
838 if (pos.y() - contentsY() < d->mScrollBorderWidth && contentsY() > 0) {
839 d->mScrollUpTimer.start(d->mScrollDelay);
840 } else if (contentsY() + d->mScrollArea->viewport()->height() -
841 d->mScrollBorderWidth < pos.y()) {
842 d->mScrollDownTimer.start(d->mScrollDelay);
843 } else {
844 d->mScrollUpTimer.stop();
845 d->mScrollDownTimer.stop();
848 if (gpos != d->mEndCell) {
849 d->mEndCell = gpos;
850 if (d->mStartCell.x() > d->mEndCell.x() ||
851 (d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() > d->mEndCell.y())) {
852 // backward selection
853 d->mSelectionStartCell = d->mEndCell;
854 d->mSelectionEndCell = d->mStartCell;
855 } else {
856 d->mSelectionStartCell = d->mStartCell;
857 d->mSelectionEndCell = d->mEndCell;
860 update();
864 void Agenda::endSelectAction(const QPoint &currentPos)
866 d->mScrollUpTimer.stop();
867 d->mScrollDownTimer.stop();
869 d->mActionType = NOP;
871 Q_EMIT newTimeSpanSignal(d->mSelectionStartCell, d->mSelectionEndCell);
873 if (d->preferences()->selectionStartsEditor()) {
874 if ((d->mSelectionStartPoint - currentPos).manhattanLength() >
875 QApplication::startDragDistance()) {
876 Q_EMIT newEventSignal();
881 Agenda::MouseActionType Agenda::isInResizeArea(bool horizontal,
882 const QPoint &pos,
883 const AgendaItem::QPtr &item)
885 if (!item) {
886 return NOP;
888 QPoint gridpos = contentsToGrid(pos);
889 QPoint contpos = gridToContents(
890 gridpos + QPoint((QApplication::isRightToLeft()) ? 1 : 0, 0));
892 if (horizontal) {
893 int clXLeft = item->cellXLeft();
894 int clXRight = item->cellXRight();
895 if (QApplication::isRightToLeft()) {
896 int tmp = clXLeft;
897 clXLeft = clXRight;
898 clXRight = tmp;
900 int gridDistanceX = int(pos.x() - contpos.x());
901 if (gridDistanceX < d->mResizeBorderWidth && clXLeft == gridpos.x()) {
902 if (QApplication::isRightToLeft()) {
903 return RESIZERIGHT;
904 } else {
905 return RESIZELEFT;
907 } else if ((d->mGridSpacingX - gridDistanceX) < d->mResizeBorderWidth &&
908 clXRight == gridpos.x()) {
909 if (QApplication::isRightToLeft()) {
910 return RESIZELEFT;
911 } else {
912 return RESIZERIGHT;
914 } else {
915 return MOVE;
917 } else {
918 int gridDistanceY = int(pos.y() - contpos.y());
919 if (gridDistanceY < d->mResizeBorderWidth &&
920 item->cellYTop() == gridpos.y() && !item->firstMultiItem()) {
921 return RESIZETOP;
922 } else if ((d->mGridSpacingY - gridDistanceY) < d->mResizeBorderWidth &&
923 item->cellYBottom() == gridpos.y() && !item->lastMultiItem()) {
924 return RESIZEBOTTOM;
925 } else {
926 return MOVE;
931 void Agenda::startItemAction(const QPoint &pos)
933 Q_ASSERT(d->mActionItem);
935 d->mStartCell = contentsToGrid(pos);
936 d->mEndCell = d->mStartCell;
938 bool noResize = CalendarSupport::hasTodo(d->mActionItem->incidence());
940 d->mActionType = MOVE;
941 if (!noResize) {
942 d->mActionType = isInResizeArea(d->mAllDayMode, pos, d->mActionItem);
945 d->mActionItem->startMove();
946 setActionCursor(d->mActionType, true);
949 void Agenda::performItemAction(const QPoint &pos)
951 QPoint gpos = contentsToGrid(pos);
953 // Cursor left active agenda area.
954 // This starts a drag.
955 if (pos.y() < 0 || pos.y() >= contentsY() + d->mScrollArea->viewport()->height() ||
956 pos.x() < 0 || pos.x() >= width()) {
957 if (d->mActionType == MOVE) {
958 d->mScrollUpTimer.stop();
959 d->mScrollDownTimer.stop();
960 d->mActionItem->resetMove();
961 placeSubCells(d->mActionItem);
962 Q_EMIT startDragSignal(d->mActionItem->incidence());
963 #ifndef QT_NO_CURSOR
964 setCursor(Qt::ArrowCursor);
965 #endif
966 if (d->mChanger) {
967 // d->mChanger->cancelChange(d->mActionItem->incidence());
969 d->mActionItem = Q_NULLPTR;
970 d->mActionType = NOP;
971 d->mItemMoved = false;
972 return;
974 } else {
975 setActionCursor(d->mActionType, true);
978 // Scroll if item was moved to upper or lower end of agenda.
979 const int distanceToTop = pos.y() - contentsY();
980 if (distanceToTop < d->mScrollBorderWidth && distanceToTop > -d->mScrollBorderWidth) {
981 d->mScrollUpTimer.start(d->mScrollDelay);
982 } else if (contentsY() + d->mScrollArea->viewport()->height() -
983 d->mScrollBorderWidth < pos.y()) {
984 d->mScrollDownTimer.start(d->mScrollDelay);
985 } else {
986 d->mScrollUpTimer.stop();
987 d->mScrollDownTimer.stop();
990 // Move or resize item if necessary
991 if (d->mEndCell != gpos) {
992 if (!d->mItemMoved) {
993 if (!d->mChanger) {
994 KMessageBox::information(this,
995 i18n("Unable to lock item for modification. "
996 "You cannot make any changes."),
997 i18n("Locking Failed"), QStringLiteral("AgendaLockingFailed"));
998 d->mScrollUpTimer.stop();
999 d->mScrollDownTimer.stop();
1000 d->mActionItem->resetMove();
1001 placeSubCells(d->mActionItem);
1002 #ifndef QT_NO_CURSOR
1003 setCursor(Qt::ArrowCursor);
1004 #endif
1005 d->mActionItem = Q_NULLPTR;
1006 d->mActionType = NOP;
1007 d->mItemMoved = false;
1008 return;
1010 d->mItemMoved = true;
1012 d->mActionItem->raise();
1013 if (d->mActionType == MOVE) {
1014 // Move all items belonging to a multi item
1015 AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem();
1016 if (!firstItem) {
1017 firstItem = d->mActionItem;
1019 AgendaItem::QPtr lastItem = d->mActionItem->lastMultiItem();
1020 if (!lastItem) {
1021 lastItem = d->mActionItem;
1023 QPoint deltapos = gpos - d->mEndCell;
1024 AgendaItem::QPtr moveItem = firstItem;
1025 while (moveItem) {
1026 bool changed = false;
1027 if (deltapos.x() != 0) {
1028 moveItem->moveRelative(deltapos.x(), 0);
1029 changed = true;
1031 // in all day view don't try to move multi items, since there are none
1032 if (moveItem == firstItem && !d->mAllDayMode) { // is the first item
1033 int newY = deltapos.y() + moveItem->cellYTop();
1034 // If event start moved earlier than 0:00, it starts the previous day
1035 if (newY < 0 && newY > d->mScrollBorderWidth) {
1036 moveItem->expandTop(-moveItem->cellYTop());
1037 // prepend a new item at (x-1, rows()+newY to rows())
1038 AgendaItem::QPtr newFirst = firstItem->prevMoveItem();
1039 // cell's y values are first and last cell of the bar,
1040 // so if newY=-1, they need to be the same
1041 if (newFirst) {
1042 newFirst->setCellXY(moveItem->cellXLeft() - 1, rows() + newY, rows() - 1);
1043 d->mItems.append(newFirst);
1044 moveItem->resize(int(d->mGridSpacingX * newFirst->cellWidth()),
1045 int(d->mGridSpacingY * newFirst->cellHeight()));
1046 QPoint cpos = gridToContents(
1047 QPoint(newFirst->cellXLeft(), newFirst->cellYTop()));
1048 newFirst->setParent(this);
1049 newFirst->move(cpos.x(), cpos.y());
1050 } else {
1051 newFirst = insertItem(moveItem->incidence(), moveItem->occurrenceDateTime(),
1052 moveItem->cellXLeft() - 1, rows() + newY, rows() - 1,
1053 moveItem->itemPos(), moveItem->itemCount(), false);
1055 if (newFirst) {
1056 newFirst->show();
1058 moveItem->prependMoveItem(newFirst);
1059 firstItem = newFirst;
1060 } else if (newY >= rows()) {
1061 // If event start is moved past 24:00, it starts the next day
1062 // erase current item (i.e. remove it from the multiItem list)
1063 firstItem = moveItem->nextMultiItem();
1064 moveItem->hide();
1065 d->mItems.removeAll(moveItem);
1066 // removeChild(moveItem);
1067 d->mActionItem->removeMoveItem(moveItem);
1068 moveItem = firstItem;
1069 // adjust next day's item
1070 if (moveItem) {
1071 moveItem->expandTop(rows() - newY);
1073 } else {
1074 moveItem->expandTop(deltapos.y(), true);
1076 changed = true;
1078 if (moveItem && !moveItem->lastMultiItem() && !d->mAllDayMode) { // is the last item
1079 int newY = deltapos.y() + moveItem->cellYBottom();
1080 if (newY < 0) {
1081 // erase current item
1082 lastItem = moveItem->prevMultiItem();
1083 moveItem->hide();
1084 d->mItems.removeAll(moveItem);
1085 // removeChild(moveItem);
1086 moveItem->removeMoveItem(moveItem);
1087 moveItem = lastItem;
1088 moveItem->expandBottom(newY + 1);
1089 } else if (newY >= rows()) {
1090 moveItem->expandBottom(rows() - moveItem->cellYBottom() - 1);
1091 // append item at (x+1, 0 to newY-rows())
1092 AgendaItem::QPtr newLast = lastItem->nextMoveItem();
1093 if (newLast) {
1094 newLast->setCellXY(moveItem->cellXLeft() + 1, 0, newY - rows() - 1);
1095 d->mItems.append(newLast);
1096 moveItem->resize(int(d->mGridSpacingX * newLast->cellWidth()),
1097 int(d->mGridSpacingY * newLast->cellHeight()));
1098 QPoint cpos = gridToContents(QPoint(newLast->cellXLeft(), newLast->cellYTop()));
1099 newLast->setParent(this);
1100 newLast->move(cpos.x(), cpos.y());
1101 } else {
1102 newLast = insertItem(moveItem->incidence(), moveItem->occurrenceDateTime(),
1103 moveItem->cellXLeft() + 1, 0, newY - rows() - 1,
1104 moveItem->itemPos(), moveItem->itemCount(), false);
1106 moveItem->appendMoveItem(newLast);
1107 newLast->show();
1108 lastItem = newLast;
1109 } else {
1110 moveItem->expandBottom(deltapos.y());
1112 changed = true;
1114 if (changed) {
1115 adjustItemPosition(moveItem);
1117 if (moveItem) {
1118 moveItem = moveItem->nextMultiItem();
1121 } else if (d->mActionType == RESIZETOP) {
1122 if (d->mEndCell.y() <= d->mActionItem->cellYBottom()) {
1123 d->mActionItem->expandTop(gpos.y() - d->mEndCell.y());
1124 adjustItemPosition(d->mActionItem);
1126 } else if (d->mActionType == RESIZEBOTTOM) {
1127 if (d->mEndCell.y() >= d->mActionItem->cellYTop()) {
1128 d->mActionItem->expandBottom(gpos.y() - d->mEndCell.y());
1129 adjustItemPosition(d->mActionItem);
1131 } else if (d->mActionType == RESIZELEFT) {
1132 if (d->mEndCell.x() <= d->mActionItem->cellXRight()) {
1133 d->mActionItem->expandLeft(gpos.x() - d->mEndCell.x());
1134 adjustItemPosition(d->mActionItem);
1136 } else if (d->mActionType == RESIZERIGHT) {
1137 if (d->mEndCell.x() >= d->mActionItem->cellXLeft()) {
1138 d->mActionItem->expandRight(gpos.x() - d->mEndCell.x());
1139 adjustItemPosition(d->mActionItem);
1142 d->mEndCell = gpos;
1146 void Agenda::endItemAction()
1148 //PENDING(AKONADI_PORT) review all this cloning and changer calls
1149 d->mActionType = NOP;
1150 d->mScrollUpTimer.stop();
1151 d->mScrollDownTimer.stop();
1152 #ifndef QT_NO_CURSOR
1153 setCursor(Qt::ArrowCursor);
1154 #endif
1156 if (!d->mChanger) {
1157 qCCritical(CALENDARVIEW_LOG) << "No IncidenceChanger set";
1158 return;
1161 bool multiModify = false;
1162 // FIXME: do the cloning here...
1163 KCalCore::Incidence::Ptr incidence = d->mActionItem->incidence();
1164 const KDateTime recurrenceId = d->mActionItem->occurrenceDateTime();
1166 d->mItemMoved = d->mItemMoved && !(d->mStartCell.x() == d->mEndCell.x() &&
1167 d->mStartCell.y() == d->mEndCell.y());
1169 bool addIncidence = false;
1170 if (d->mItemMoved) {
1171 bool modify = false;
1173 //get the main event and not the exception
1174 if (incidence->hasRecurrenceId() && !incidence->recurs()) {
1175 KCalCore::Incidence::Ptr mainIncidence;
1176 KCalCore::Calendar::Ptr cal = d->mCalendar->findCalendar(incidence)->getCalendar();
1177 if (CalendarSupport::hasEvent(incidence)) {
1178 mainIncidence = cal->event(incidence->uid());
1179 } else if (CalendarSupport::hasTodo(incidence)) {
1180 mainIncidence = cal->todo(incidence->uid());
1182 incidence = mainIncidence;
1185 Akonadi::Item item = d->mCalendar->item(incidence);
1187 if (incidence->recurs()) {
1188 const int res = d->mAgendaView->showMoveRecurDialog(incidence, recurrenceId.date());
1189 switch (res) {
1190 case KCalUtils::RecurrenceActions::AllOccurrences: // All occurrences
1191 // Moving the whole sequene of events is handled by the itemModified below.
1192 modify = true;
1193 break;
1194 case KCalUtils::RecurrenceActions::SelectedOccurrence:
1195 case KCalUtils::RecurrenceActions::FutureOccurrences: {
1196 const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences);
1197 modify = true;
1198 multiModify = true;
1199 d->mChanger->startAtomicOperation(i18n("Dissociate event from recurrence"));
1200 KCalCore::Incidence::Ptr newInc(KCalCore::Calendar::createException(
1201 incidence, recurrenceId, thisAndFuture));
1202 if (newInc) {
1203 newInc->removeCustomProperty("VOLATILE", "AKONADI-ID");
1204 Akonadi::Item newItem = d->mCalendar->item(newInc);
1206 if (newItem.isValid() && newItem != item) { //it is not a new exception
1207 item = newItem;
1208 newInc->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(newItem.id()));
1209 addIncidence = false;
1210 } else {
1211 addIncidence = true;
1213 // don't recreate items, they already have the correct position
1214 d->mAgendaView->enableAgendaUpdate(false);
1216 d->mActionItem->setIncidence(newInc);
1217 d->mActionItem->dissociateFromMultiItem();
1219 d->mAgendaView->enableAgendaUpdate(true);
1220 } else {
1221 KMessageBox::sorry(
1222 this,
1223 i18n("Unable to add the exception item to the calendar. "
1224 "No change will be done."),
1225 i18n("Error Occurred"));
1227 break;
1229 default:
1230 modify = false;
1231 d->mActionItem->resetMove();
1232 placeSubCells(d->mActionItem); //PENDING(AKONADI_PORT) should this be done after
1233 //the new item was asynchronously added?
1237 AgendaItem::QPtr placeItem = d->mActionItem->firstMultiItem();
1238 if (!placeItem) {
1239 placeItem = d->mActionItem;
1242 Akonadi::Collection::Id saveCollection = -1;
1244 if (item.isValid()) {
1245 saveCollection = item.parentCollection().id();
1247 // if parent collection is only a search collection for example
1248 if (!(item.parentCollection().rights() & Akonadi::Collection::CanCreateItem)) {
1249 saveCollection = item.storageCollectionId();
1253 if (modify) {
1254 d->mActionItem->endMove();
1256 AgendaItem::QPtr modif = placeItem;
1258 QList<AgendaItem::QPtr> oldconflictItems = placeItem->conflictItems();
1259 QList<AgendaItem::QPtr>::iterator it;
1260 for (it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it) {
1261 if (*it) {
1262 placeSubCells(*it);
1265 while (placeItem) {
1266 placeSubCells(placeItem);
1267 placeItem = placeItem->nextMultiItem();
1270 // Notify about change
1271 // The agenda view will apply the changes to the actual Incidence*!
1272 // Bug #228696 don't call endChanged now it's async in Akonadi so it can
1273 // be called before that modified item was done. And endChange is
1274 // calling when we move item.
1275 // Not perfect need to improve it!
1276 //mChanger->endChange(inc);
1277 if (item.isValid()) {
1278 d->mAgendaView->updateEventDates(modif, addIncidence, saveCollection);
1280 if (addIncidence) {
1281 // delete the one we dragged, there's a new one being added async, due to dissociation.
1282 delete modif;
1284 } else {
1285 // the item was moved, but not further modified, since it's not recurring
1286 // make sure the view updates anyhow, with the right item
1287 if (item.isValid()) {
1288 d->mAgendaView->updateEventDates(placeItem, addIncidence, saveCollection);
1293 d->mActionItem = Q_NULLPTR;
1294 d->mItemMoved = false;
1296 if (multiModify) {
1297 d->mChanger->endAtomicOperation();
1301 void Agenda::setActionCursor(int actionType, bool acting)
1303 #ifndef QT_NO_CURSOR
1304 switch (actionType) {
1305 case MOVE:
1306 if (acting) {
1307 setCursor(Qt::SizeAllCursor);
1308 } else {
1309 setCursor(Qt::ArrowCursor);
1311 break;
1312 case RESIZETOP:
1313 case RESIZEBOTTOM:
1314 setCursor(Qt::SizeVerCursor);
1315 break;
1316 case RESIZELEFT:
1317 case RESIZERIGHT:
1318 setCursor(Qt::SizeHorCursor);
1319 break;
1320 default:
1321 setCursor(Qt::ArrowCursor);
1323 #endif
1326 void Agenda::setNoActionCursor(const AgendaItem::QPtr &moveItem, const QPoint &pos)
1328 const KCalCore::Incidence::Ptr item = moveItem ? moveItem->incidence() : KCalCore::Incidence::Ptr();
1330 const bool noResize = CalendarSupport::hasTodo(item);
1332 Agenda::MouseActionType resizeType = MOVE;
1333 if (!noResize) {
1334 resizeType = isInResizeArea(d->mAllDayMode, pos, moveItem);
1336 setActionCursor(resizeType);
1339 /** calculate the width of the column subcells of the given item
1341 double Agenda::calcSubCellWidth(const AgendaItem::QPtr &item)
1343 QPoint pt, pt1;
1344 pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()));
1345 pt1 = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()) + QPoint(1, 1));
1346 pt1 -= pt;
1347 int maxSubCells = item->subCells();
1348 double newSubCellWidth;
1349 if (d->mAllDayMode) {
1350 newSubCellWidth = static_cast<double>(pt1.y()) / maxSubCells;
1351 } else {
1352 newSubCellWidth = static_cast<double>(pt1.x()) / maxSubCells;
1354 return newSubCellWidth;
1357 void Agenda::adjustItemPosition(const AgendaItem::QPtr &item)
1359 if (!item) {
1360 return;
1362 item->resize(int(d->mGridSpacingX * item->cellWidth()),
1363 int(d->mGridSpacingY * item->cellHeight()));
1364 int clXLeft = item->cellXLeft();
1365 if (QApplication::isRightToLeft()) {
1366 clXLeft = item->cellXRight() + 1;
1368 QPoint cpos = gridToContents(QPoint(clXLeft, item->cellYTop()));
1369 item->move(cpos.x(), cpos.y());
1372 void Agenda::placeAgendaItem(const AgendaItem::QPtr &item, double subCellWidth)
1374 // "left" upper corner, no subcells yet, RTL layouts have right/left
1375 // switched, widths are negative then
1376 QPoint pt = gridToContents(QPoint(item->cellXLeft(), item->cellYTop()));
1377 // right lower corner
1378 QPoint pt1 = gridToContents(
1379 QPoint(item->cellXLeft() + item->cellWidth(), item->cellYBottom() + 1));
1381 double subCellPos = item->subCell() * subCellWidth;
1383 // we need to add 0.01 to make sure we don't loose one pixed due to numerics
1384 // (i.e. if it would be x.9998, we want the integer, not rounded down.
1385 double delta = 0.01;
1386 if (subCellWidth < 0) {
1387 delta = -delta;
1389 int height, width, xpos, ypos;
1390 if (d->mAllDayMode) {
1391 width = pt1.x() - pt.x();
1392 height = int(subCellPos + subCellWidth + delta) - int(subCellPos);
1393 xpos = pt.x();
1394 ypos = pt.y() + int(subCellPos);
1395 } else {
1396 width = int(subCellPos + subCellWidth + delta) - int(subCellPos);
1397 height = pt1.y() - pt.y();
1398 xpos = pt.x() + int(subCellPos);
1399 ypos = pt.y();
1401 if (QApplication::isRightToLeft()) { // RTL language/layout
1402 xpos += width;
1403 width = -width;
1405 if (height < 0) { // BTT (bottom-to-top) layout ?!?
1406 ypos += height;
1407 height = -height;
1409 item->resize(width, height);
1410 item->move(xpos, ypos);
1414 Place item in cell and take care that multiple items using the same cell do
1415 not overlap. This method is not yet optimal. It doesn't use the maximum space
1416 it can get in all cases.
1417 At the moment the method has a bug: When an item is placed only the sub cell
1418 widths of the items are changed, which are within the Y region the item to
1419 place spans. When the sub cell width change of one of this items affects a
1420 cell, where other items are, which do not overlap in Y with the item to
1421 place, the display gets corrupted, although the corruption looks quite nice.
1423 void Agenda::placeSubCells(const AgendaItem::QPtr &placeItem)
1425 #if 0
1426 qCDebug(CALENDARVIEW_LOG);
1427 if (placeItem) {
1428 KCalCore::Incidence::Ptr event = placeItem->incidence();
1429 if (!event) {
1430 qCDebug(CALENDARVIEW_LOG) << " event is 0";
1431 } else {
1432 qCDebug(CALENDARVIEW_LOG) << " event:" << event->summary();
1434 } else {
1435 qCDebug(CALENDARVIEW_LOG) << " placeItem is 0";
1437 qCDebug(CALENDARVIEW_LOG) << "Agenda::placeSubCells()...";
1438 #endif
1440 QList<CalendarSupport::CellItem *> cells;
1441 foreach (CalendarSupport::CellItem *item, d->mItems) {
1442 if (item) {
1443 cells.append(item);
1447 QList<CalendarSupport::CellItem *> items = CalendarSupport::CellItem::placeItem(cells, placeItem);
1449 placeItem->setConflictItems(QList<AgendaItem::QPtr>());
1450 double newSubCellWidth = calcSubCellWidth(placeItem);
1451 QList<CalendarSupport::CellItem *>::iterator it;
1452 for (it = items.begin(); it != items.end(); ++it) {
1453 if (*it) {
1454 AgendaItem::QPtr item = static_cast<AgendaItem *>(*it);
1455 placeAgendaItem(item, newSubCellWidth);
1456 item->addConflictItem(placeItem);
1457 placeItem->addConflictItem(item);
1460 if (items.isEmpty()) {
1461 placeAgendaItem(placeItem, newSubCellWidth);
1463 placeItem->update();
1466 int Agenda::columnWidth(int column) const
1468 int start = gridToContents(QPoint(column, 0)).x();
1469 if (QApplication::isRightToLeft()) {
1470 column--;
1471 } else {
1472 column++;
1474 int end = gridToContents(QPoint(column, 0)).x();
1475 return end - start;
1478 void Agenda::paintEvent(QPaintEvent *)
1480 QPainter p(this);
1481 drawContents(&p, 0, -y(), d->mGridSpacingX * d->mColumns, d->mGridSpacingY * d->mRows + y());
1485 Draw grid in the background of the agenda.
1487 void Agenda::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
1489 QPixmap db(cw, ch);
1490 db.fill(); // We don't want to see leftovers from previous paints
1491 QPainter dbp(&db);
1492 // TODO: CHECK THIS
1493 // if (! d->preferences()->agendaGridBackgroundImage().isEmpty()) {
1494 // QPixmap bgImage(d->preferences()->agendaGridBackgroundImage());
1495 // dbp.drawPixmap(0, 0, cw, ch, bgImage); FIXME
1496 // }
1498 dbp.fillRect(0, 0, cw, ch,
1499 d->preferences()->agendaGridBackgroundColor());
1501 dbp.translate(-cx, -cy);
1503 double lGridSpacingY = d->mGridSpacingY * 2;
1505 // If work day, use work color
1506 // If busy day, use busy color
1507 // if work and busy day, mix both, and busy color has alpha
1509 const QVector<bool> busyDayMask = d->mAgendaView->busyDayMask();
1511 // Highlight working hours
1512 if (d->mWorkingHoursEnable && d->mHolidayMask) {
1513 const QColor workColor = d->preferences()->workingHoursColor();
1515 QPoint pt1(cx, d->mWorkingHoursYTop);
1516 QPoint pt2(cx + cw, d->mWorkingHoursYBottom);
1517 if (pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) {
1518 int gxStart = contentsToGrid(pt1).x();
1519 int gxEnd = contentsToGrid(pt2).x();
1520 // correct start/end for rtl layouts
1521 if (gxStart > gxEnd) {
1522 int tmp = gxStart;
1523 gxStart = gxEnd;
1524 gxEnd = tmp;
1526 int xoffset = (QApplication::isRightToLeft() ? 1 : 0);
1527 while (gxStart <= gxEnd) {
1528 int xStart = gridToContents(QPoint(gxStart + xoffset, 0)).x();
1529 int xWidth = columnWidth(gxStart) + 1;
1531 if (pt2.y() < pt1.y()) {
1532 // overnight working hours
1533 if (((gxStart == 0) && !d->mHolidayMask->at(d->mHolidayMask->count() - 1)) ||
1534 ((gxStart > 0) && (gxStart < int(d->mHolidayMask->count())) &&
1535 (!d->mHolidayMask->at(gxStart - 1)))) {
1536 if (pt2.y() > cy) {
1537 dbp.fillRect(xStart, cy, xWidth, pt2.y() - cy + 1, workColor);
1540 if ((gxStart < int(d->mHolidayMask->count() - 1)) &&
1541 (!d->mHolidayMask->at(gxStart))) {
1542 if (pt1.y() < cy + ch - 1) {
1543 dbp.fillRect(xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1, workColor);
1546 } else {
1547 // last entry in holiday mask denotes the previous day not visible
1548 // (needed for overnight shifts)
1549 if (gxStart < int(d->mHolidayMask->count() - 1) && !d->mHolidayMask->at(gxStart)) {
1550 dbp.fillRect(xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1, workColor);
1553 ++gxStart;
1558 // busy days
1559 if (d->preferences()->colorAgendaBusyDays() && !d->mAllDayMode) {
1560 for (int i = 0; i < busyDayMask.count(); ++i) {
1561 if (busyDayMask[i]) {
1562 const QPoint pt1(cx + d->mGridSpacingX * i, 0);
1563 // const QPoint pt2(cx + mGridSpacingX * (i+1), ch);
1564 QColor busyColor = d->preferences()->viewBgBusyColor();
1565 busyColor.setAlpha(EventViews::BUSY_BACKGROUND_ALPHA);
1566 dbp.fillRect(pt1.x(), pt1.y(), d->mGridSpacingX, cy + ch, busyColor);
1571 // draw selection
1572 if (d->mHasSelection && d->mAgendaView->dateRangeSelectionEnabled()) {
1573 QPoint pt, pt1;
1575 if (d->mSelectionEndCell.x() > d->mSelectionStartCell.x()) { // multi day selection
1576 // draw start day
1577 pt = gridToContents(d->mSelectionStartCell);
1578 pt1 = gridToContents(QPoint(d->mSelectionStartCell.x() + 1, d->mRows + 1));
1579 dbp.fillRect(QRect(pt, pt1), d->preferences()->agendaGridHighlightColor());
1580 // draw all other days between the start day and the day of the selection end
1581 for (int c = d->mSelectionStartCell.x() + 1; c < d->mSelectionEndCell.x(); ++c) {
1582 pt = gridToContents(QPoint(c, 0));
1583 pt1 = gridToContents(QPoint(c + 1, d->mRows + 1));
1584 dbp.fillRect(QRect(pt, pt1), d->preferences()->agendaGridHighlightColor());
1586 // draw end day
1587 pt = gridToContents(QPoint(d->mSelectionEndCell.x(), 0));
1588 pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1));
1589 dbp.fillRect(QRect(pt, pt1), d->preferences()->agendaGridHighlightColor());
1590 } else { // single day selection
1591 pt = gridToContents(d->mSelectionStartCell);
1592 pt1 = gridToContents(d->mSelectionEndCell + QPoint(1, 1));
1593 dbp.fillRect(QRect(pt, pt1), d->preferences()->agendaGridHighlightColor());
1597 QPen hourPen(d->preferences()->agendaGridBackgroundColor().dark(150));
1598 QPen halfHourPen(d->preferences()->agendaGridBackgroundColor().dark(125));
1599 dbp.setPen(hourPen);
1601 // Draw vertical lines of grid, start with the last line not yet visible
1602 double x = (int(cx / d->mGridSpacingX)) * d->mGridSpacingX;
1603 while (x < cx + cw) {
1604 dbp.drawLine(int(x), cy, int(x), cy + ch);
1605 x += d->mGridSpacingX;
1608 // Draw horizontal lines of grid
1609 double y = (int(cy / (2 * lGridSpacingY))) * 2 * lGridSpacingY;
1610 while (y < cy + ch) {
1611 dbp.drawLine(cx, int(y), cx + cw, int(y));
1612 y += 2 * lGridSpacingY;
1614 y = (2 * int(cy / (2 * lGridSpacingY)) + 1) * lGridSpacingY;
1615 dbp.setPen(halfHourPen);
1616 while (y < cy + ch) {
1617 dbp.drawLine(cx, int(y), cx + cw, int(y));
1618 y += 2 * lGridSpacingY;
1620 p->drawPixmap(cx, cy, db);
1624 Convert srcollview contents coordinates to agenda grid coordinates.
1626 QPoint Agenda::contentsToGrid(const QPoint &pos) const
1628 int gx = int(QApplication::isRightToLeft() ?
1629 d->mColumns - pos.x() / d->mGridSpacingX : pos.x() / d->mGridSpacingX);
1630 int gy = int(pos.y() / d->mGridSpacingY);
1631 return QPoint(gx, gy);
1635 Convert agenda grid coordinates to scrollview contents coordinates.
1637 QPoint Agenda::gridToContents(const QPoint &gpos) const
1639 int x = int(QApplication::isRightToLeft() ?
1640 (d->mColumns - gpos.x()) * d->mGridSpacingX : gpos.x() * d->mGridSpacingX);
1641 int y = int(gpos.y() * d->mGridSpacingY);
1642 return QPoint(x, y);
1646 Return Y coordinate corresponding to time. Coordinates are rounded to
1647 fit into the grid.
1649 int Agenda::timeToY(const QTime &time) const
1652 int minutesPerCell = 24 * 60 / d->mRows;
1653 int timeMinutes = time.hour() * 60 + time.minute();
1654 int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell;
1656 return Y;
1660 Return time corresponding to cell y coordinate. Coordinates are rounded to
1661 fit into the grid.
1663 QTime Agenda::gyToTime(int gy) const
1665 int secondsPerCell = 24 * 60 * 60 / d->mRows;
1666 int timeSeconds = secondsPerCell * gy;
1668 QTime time(0, 0, 0);
1669 if (timeSeconds < 24 * 60 * 60) {
1670 time = time.addSecs(timeSeconds);
1671 } else {
1672 time.setHMS(23, 59, 59);
1674 return time;
1677 QVector<int> Agenda::minContentsY() const
1679 QVector<int> minArray;
1680 minArray.fill(timeToY(QTime(23, 59)), d->mSelectedDates.count());
1681 foreach (AgendaItem::QPtr item, d->mItems) {
1682 if (item) {
1683 int ymin = item->cellYTop();
1684 int index = item->cellXLeft();
1685 if (index >= 0 && index < (int)(d->mSelectedDates.count())) {
1686 if (ymin < minArray[index] && !d->mItemsToDelete.contains(item)) {
1687 minArray[index] = ymin;
1693 return minArray;
1696 QVector<int> Agenda::maxContentsY() const
1698 QVector<int> maxArray;
1699 maxArray.fill(timeToY(QTime(0, 0)), d->mSelectedDates.count());
1700 foreach (AgendaItem::QPtr item, d->mItems) {
1701 if (item) {
1702 int ymax = item->cellYBottom();
1704 int index = item->cellXLeft();
1705 if (index >= 0 && index < (int)(d->mSelectedDates.count())) {
1706 if (ymax > maxArray[index] && !d->mItemsToDelete.contains(item)) {
1707 maxArray[index] = ymax;
1713 return maxArray;
1716 void Agenda::setStartTime(const QTime &startHour)
1718 const double startPos =
1719 (startHour.hour() / 24. + startHour.minute() / 1440. + startHour.second() / 86400.) *
1720 d->mRows * gridSpacingY();
1722 verticalScrollBar()->setValue(startPos);
1727 Insert AgendaItem into agenda.
1729 AgendaItem::QPtr Agenda::insertItem(const KCalCore::Incidence::Ptr &incidence, const KDateTime &recurrenceId,
1730 int X, int YTop, int YBottom, int itemPos, int itemCount,
1731 bool isSelected)
1733 if (d->mAllDayMode) {
1734 qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal.";
1735 return Q_NULLPTR;
1738 d->mActionType = NOP;
1740 AgendaItem::QPtr agendaItem = createAgendaItem(incidence, itemPos, itemCount, recurrenceId, isSelected);
1741 if (!agendaItem) {
1742 return AgendaItem::QPtr();
1745 if (YBottom <= YTop) {
1746 qCDebug(CALENDARVIEW_LOG) << "Text:" << agendaItem->text() << " YSize<0";
1747 YBottom = YTop;
1750 agendaItem->resize(int((X + 1) * d->mGridSpacingX) -
1751 int(X * d->mGridSpacingX),
1752 int(YTop * d->mGridSpacingY) -
1753 int((YBottom + 1) * d->mGridSpacingY));
1754 agendaItem->setCellXY(X, YTop, YBottom);
1755 agendaItem->setCellXRight(X);
1756 agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence));
1757 agendaItem->installEventFilter(this);
1759 agendaItem->move(int(X * d->mGridSpacingX), int(YTop * d->mGridSpacingY));
1761 d->mItems.append(agendaItem);
1763 placeSubCells(agendaItem);
1765 agendaItem->show();
1767 marcus_bains();
1769 return agendaItem;
1773 Insert all-day AgendaItem into agenda.
1775 AgendaItem::QPtr Agenda::insertAllDayItem(const KCalCore::Incidence::Ptr &incidence, const KDateTime &recurrenceId,
1776 int XBegin, int XEnd, bool isSelected)
1778 if (!d->mAllDayMode) {
1779 qCCritical(CALENDARVIEW_LOG) << "using this in non all-day mode is illegal.";
1780 return Q_NULLPTR;
1783 d->mActionType = NOP;
1785 AgendaItem::QPtr agendaItem = createAgendaItem(incidence, 1, 1, recurrenceId, isSelected);
1786 if (!agendaItem) {
1787 return AgendaItem::QPtr();
1790 agendaItem->setCellXY(XBegin, 0, 0);
1791 agendaItem->setCellXRight(XEnd);
1793 const double startIt = d->mGridSpacingX * (agendaItem->cellXLeft());
1794 const double endIt = d->mGridSpacingX * (agendaItem->cellWidth() +
1795 agendaItem->cellXLeft());
1797 agendaItem->resize(int(endIt) - int(startIt), int(d->mGridSpacingY));
1799 agendaItem->installEventFilter(this);
1800 agendaItem->setResourceColor(d->mCalendar->resourceColor(incidence));
1801 agendaItem->move(int(XBegin * d->mGridSpacingX), 0);
1802 d->mItems.append(agendaItem);
1804 placeSubCells(agendaItem);
1806 agendaItem->show();
1808 return agendaItem;
1811 AgendaItem::QPtr Agenda::createAgendaItem(const KCalCore::Incidence::Ptr &incidence, int itemPos,
1812 int itemCount, const KDateTime &recurrenceId, bool isSelected)
1814 if (!incidence) {
1815 qCWarning(CALENDARVIEW_LOG) << "Agenda::createAgendaItem() item is invalid.";
1816 return AgendaItem::QPtr();
1819 AgendaItem::QPtr agendaItem = new AgendaItem(d->mAgendaView, d->mCalendar, incidence,
1820 itemPos, itemCount, recurrenceId, isSelected, this);
1822 connect(agendaItem.data(), &AgendaItem::removeAgendaItem, this, &Agenda::removeAgendaItem);
1823 connect(agendaItem.data(), &AgendaItem::showAgendaItem, this, &Agenda::showAgendaItem);
1825 d->mAgendaItemsById.insert(incidence->uid(), agendaItem);
1827 return agendaItem;
1830 void Agenda::insertMultiItem(const KCalCore::Incidence::Ptr &event, const KDateTime &recurrenceId, int XBegin,
1831 int XEnd, int YTop, int YBottom, bool isSelected)
1833 KCalCore::Event::Ptr ev = CalendarSupport::event(event);
1834 Q_ASSERT(ev);
1835 if (d->mAllDayMode) {
1836 qCDebug(CALENDARVIEW_LOG) << "using this in all-day mode is illegal.";
1837 return;
1840 d->mActionType = NOP;
1841 int cellX, cellYTop, cellYBottom;
1842 QString newtext;
1843 int width = XEnd - XBegin + 1;
1844 int count = 0;
1845 AgendaItem::QPtr current = Q_NULLPTR;
1846 QList<AgendaItem::QPtr> multiItems;
1847 int visibleCount = d->mSelectedDates.first().daysTo(d->mSelectedDates.last());
1848 for (cellX = XBegin; cellX <= XEnd; ++cellX) {
1849 ++count;
1850 //Only add the items that are visible.
1851 if (cellX >= 0 && cellX <= visibleCount) {
1852 if (cellX == XBegin) {
1853 cellYTop = YTop;
1854 } else {
1855 cellYTop = 0;
1857 if (cellX == XEnd) {
1858 cellYBottom = YBottom;
1859 } else {
1860 cellYBottom = rows() - 1;
1862 newtext = QStringLiteral("(%1/%2): ").arg(count, width);
1863 newtext.append(ev->summary());
1865 current = insertItem(event, recurrenceId, cellX, cellYTop, cellYBottom, count, width, isSelected);
1866 Q_ASSERT(current);
1867 current->setText(newtext);
1868 multiItems.append(current);
1872 QList<AgendaItem::QPtr>::iterator it = multiItems.begin();
1873 QList<AgendaItem::QPtr>::iterator e = multiItems.end();
1875 if (it != e) { // .first asserts if the list is empty
1876 AgendaItem::QPtr first = multiItems.first();
1877 AgendaItem::QPtr last = multiItems.last();
1878 AgendaItem::QPtr prev = Q_NULLPTR, next = Q_NULLPTR;
1880 while (it != e) {
1881 AgendaItem::QPtr item = *it;
1882 ++it;
1883 next = (it == e) ? Q_NULLPTR : (*it);
1884 if (item) {
1885 item->setMultiItem((item == first) ? Q_NULLPTR : first,
1886 prev, next,
1887 (item == last) ? Q_NULLPTR : last);
1889 prev = item;
1893 marcus_bains();
1896 void Agenda::removeIncidence(const KCalCore::Incidence::Ptr &incidence)
1898 if (!incidence) {
1899 qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() incidence is invalid" << incidence->uid();
1900 return;
1903 if (d->isQueuedForDeletion(incidence->uid())) {
1904 return; // It's already queued for deletion
1907 AgendaItem::List agendaItems = d->mAgendaItemsById.values(incidence->uid());
1908 if (agendaItems.isEmpty()) {
1909 // We're not displaying such item
1910 // qCDebug(CALENDARVIEW_LOG) << "Ignoring";
1911 return;
1913 foreach (const AgendaItem::QPtr &agendaItem, agendaItems) {
1914 if (agendaItem) {
1915 if (incidence->instanceIdentifier() != agendaItem->incidence()->instanceIdentifier()) {
1916 continue;
1918 if (removeAgendaItem(agendaItem)) {
1919 qCWarning(CALENDARVIEW_LOG) << "Agenda::removeIncidence() Failed to remove " << incidence->uid();
1925 void Agenda::showAgendaItem(const AgendaItem::QPtr &agendaItem)
1927 if (!agendaItem) {
1928 qCCritical(CALENDARVIEW_LOG) << "Show what?";
1929 return;
1932 agendaItem->hide();
1934 agendaItem->setParent(this);
1936 if (!d->mItems.contains(agendaItem)) {
1937 d->mItems.append(agendaItem);
1939 placeSubCells(agendaItem);
1941 agendaItem->show();
1944 bool Agenda::removeAgendaItem(const AgendaItem::QPtr &agendaItem)
1946 Q_ASSERT(agendaItem);
1947 // we found the item. Let's remove it and update the conflicts
1948 bool taken = false;
1949 QList<AgendaItem::QPtr> conflictItems = agendaItem->conflictItems();
1950 // removeChild(thisItem);
1952 taken = d->mItems.removeAll(agendaItem) > 0;
1953 d->mAgendaItemsById.remove(agendaItem->incidence()->uid(), agendaItem);
1955 QList<AgendaItem::QPtr>::iterator it;
1956 for (it = conflictItems.begin(); it != conflictItems.end(); ++it) {
1957 if (*it) {
1958 (*it)->setSubCells((*it)->subCells() - 1);
1962 for (it = conflictItems.begin(); it != conflictItems.end(); ++it) {
1963 // the item itself is also in its own conflictItems list!
1964 if (*it && *it != agendaItem) {
1965 placeSubCells(*it);
1968 d->mItemsToDelete.append(agendaItem);
1969 d->mItemsQueuedForDeletion.insert(agendaItem->incidence()->uid());
1970 agendaItem->setVisible(false);
1971 QTimer::singleShot(0, this, &Agenda::deleteItemsToDelete);
1972 return taken;
1975 void Agenda::deleteItemsToDelete()
1977 qDeleteAll(d->mItemsToDelete);
1978 d->mItemsToDelete.clear();
1979 d->mItemsQueuedForDeletion.clear();
1982 /*QSizePolicy Agenda::sizePolicy() const
1984 // Thought this would make the all-day event agenda minimum size and the
1985 // normal agenda take the remaining space. But it doesn't work. The QSplitter
1986 // don't seem to think that an Expanding widget needs more space than a
1987 // Preferred one.
1988 // But it doesn't hurt, so it stays.
1989 if (mAllDayMode) {
1990 return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
1991 } else {
1992 return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
1997 Overridden from QScrollView to provide proper resizing of AgendaItems.
1999 void Agenda::resizeEvent(QResizeEvent *ev)
2001 QSize newSize(ev->size());
2003 if (d->mAllDayMode) {
2004 d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns;
2005 d->mGridSpacingY = newSize.height();
2006 } else {
2007 d->mGridSpacingX = static_cast<double>(newSize.width()) / d->mColumns;
2008 // make sure that there are not more than 24 per day
2009 d->mGridSpacingY = static_cast<double>(newSize.height()) / d->mRows;
2010 if (d->mGridSpacingY < d->mDesiredGridSpacingY) {
2011 d->mGridSpacingY = d->mDesiredGridSpacingY;
2014 calculateWorkingHours();
2016 QTimer::singleShot(0, this, &Agenda::resizeAllContents);
2017 Q_EMIT gridSpacingYChanged(d->mGridSpacingY * 4);
2019 QWidget::resizeEvent(ev);
2020 updateGeometry();
2023 void Agenda::resizeAllContents()
2025 double subCellWidth;
2026 if (d->mAllDayMode) {
2027 foreach (const AgendaItem::QPtr &item, d->mItems) {
2028 if (item) {
2029 subCellWidth = calcSubCellWidth(item);
2030 placeAgendaItem(item, subCellWidth);
2033 } else {
2034 foreach (const AgendaItem::QPtr &item, d->mItems) {
2035 if (item) {
2036 subCellWidth = calcSubCellWidth(item);
2037 placeAgendaItem(item, subCellWidth);
2041 checkScrollBoundaries();
2042 marcus_bains();
2043 update();
2046 void Agenda::scrollUp()
2048 int currentValue = verticalScrollBar()->value();
2049 verticalScrollBar()->setValue(currentValue - d->mScrollOffset);
2052 void Agenda::scrollDown()
2054 int currentValue = verticalScrollBar()->value();
2055 verticalScrollBar()->setValue(currentValue + d->mScrollOffset);
2058 QSize Agenda::minimumSize() const
2060 return sizeHint();
2063 QSize Agenda::minimumSizeHint() const
2065 return sizeHint();
2068 int Agenda::minimumHeight() const
2070 // all day agenda never has scrollbars and the scrollarea will
2071 // resize it to fit exactly on the viewport.
2073 if (d->mAllDayMode) {
2074 return 0;
2075 } else {
2076 return d->mGridSpacingY * d->mRows;
2080 void Agenda::updateConfig()
2082 const double oldGridSpacingY = d->mGridSpacingY;
2084 if (!d->mAllDayMode) {
2085 d->mDesiredGridSpacingY = d->preferences()->hourSize();
2086 if (d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30) {
2087 d->mDesiredGridSpacingY = 10;
2091 // make sure that there are not more than 24 per day
2092 d->mGridSpacingY = static_cast<double>(height()) / d->mRows;
2093 if (d->mGridSpacingY < d->mDesiredGridSpacingY || true) {
2094 d->mGridSpacingY = d->mDesiredGridSpacingY;
2098 //can be two doubles equal?, it's better to compare them with an epsilon
2099 if (fabs(oldGridSpacingY - d->mDesiredGridSpacingY) > 0.1) {
2100 d->mGridSpacingY = d->mDesiredGridSpacingY;
2101 updateGeometry();
2105 calculateWorkingHours();
2107 marcus_bains();
2110 void Agenda::checkScrollBoundaries()
2112 // Invalidate old values to force update
2113 d->mOldLowerScrollValue = -1;
2114 d->mOldUpperScrollValue = -1;
2116 checkScrollBoundaries(verticalScrollBar()->value());
2119 void Agenda::checkScrollBoundaries(int v)
2121 int yMin = int((v) / d->mGridSpacingY);
2122 int yMax = int((v + d->mScrollArea->height()) / d->mGridSpacingY);
2124 if (yMin != d->mOldLowerScrollValue) {
2125 d->mOldLowerScrollValue = yMin;
2126 Q_EMIT lowerYChanged(yMin);
2128 if (yMax != d->mOldUpperScrollValue) {
2129 d->mOldUpperScrollValue = yMax;
2130 Q_EMIT upperYChanged(yMax);
2134 int Agenda::visibleContentsYMin() const
2136 int v = verticalScrollBar()->value();
2137 return int(v / d->mGridSpacingY);
2140 int Agenda::visibleContentsYMax() const
2142 int v = verticalScrollBar()->value();
2143 return int((v + d->mScrollArea->height()) / d->mGridSpacingY);
2146 void Agenda::deselectItem()
2148 if (d->mSelectedItem.isNull()) {
2149 return;
2152 const KCalCore::Incidence::Ptr selectedItem = d->mSelectedItem->incidence();
2154 foreach (AgendaItem::QPtr item, d->mItems) {
2155 if (item) {
2156 const KCalCore::Incidence::Ptr itemInc = item->incidence();
2157 if (itemInc && selectedItem && itemInc->uid() == selectedItem->uid()) {
2158 item->select(false);
2163 d->mSelectedItem = Q_NULLPTR;
2166 void Agenda::selectItem(const AgendaItem::QPtr &item)
2168 if ((AgendaItem::QPtr)d->mSelectedItem == item) {
2169 return;
2171 deselectItem();
2172 if (item == Q_NULLPTR) {
2173 Q_EMIT incidenceSelected(KCalCore::Incidence::Ptr(), QDate());
2174 return;
2176 d->mSelectedItem = item;
2177 d->mSelectedItem->select();
2178 Q_ASSERT(d->mSelectedItem->incidence());
2179 d->mSelectedId = d->mSelectedItem->incidence()->uid();
2181 foreach (AgendaItem::QPtr item, d->mItems) {
2182 if (item && item->incidence()->uid() == d->mSelectedId) {
2183 item->select();
2186 Q_EMIT incidenceSelected(d->mSelectedItem->incidence(), d->mSelectedItem->occurrenceDate());
2189 void Agenda::selectIncidenceByUid(const QString &uid)
2191 foreach (AgendaItem::QPtr item, d->mItems) {
2192 if (item && item->incidence()->uid() == uid) {
2193 selectItem(item);
2194 break;
2199 void Agenda::selectItem(const Akonadi::Item &item)
2201 selectIncidenceByUid(CalendarSupport::incidence(item)->uid());
2204 // This function seems never be called.
2205 void Agenda::keyPressEvent(QKeyEvent *kev)
2207 switch (kev->key()) {
2208 case Qt::Key_PageDown:
2209 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
2210 break;
2211 case Qt::Key_PageUp:
2212 verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
2213 break;
2214 case Qt::Key_Down:
2215 verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd);
2216 break;
2217 case Qt::Key_Up:
2218 verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub);
2219 break;
2220 default:
2225 void Agenda::calculateWorkingHours()
2227 d->mWorkingHoursEnable = !d->mAllDayMode;
2229 QTime tmp = d->preferences()->workingHoursStart().time();
2230 d->mWorkingHoursYTop = int(4 * d->mGridSpacingY *
2231 (tmp.hour() + tmp.minute() / 60. +
2232 tmp.second() / 3600.));
2233 tmp = d->preferences()->workingHoursEnd().time();
2234 d->mWorkingHoursYBottom = int(4 * d->mGridSpacingY *
2235 (tmp.hour() + tmp.minute() / 60. +
2236 tmp.second() / 3600.) - 1);
2239 void Agenda::setDateList(const KCalCore::DateList &selectedDates)
2241 d->mSelectedDates = selectedDates;
2242 marcus_bains();
2245 KCalCore::DateList Agenda::dateList() const
2247 return d->mSelectedDates;
2250 void Agenda::setCalendar(const MultiViewCalendar::Ptr &cal)
2252 d->mCalendar = cal;
2255 void Agenda::setIncidenceChanger(Akonadi::IncidenceChanger *changer)
2257 d->mChanger = changer;
2260 void Agenda::setHolidayMask(QVector<bool> *mask)
2262 d->mHolidayMask = mask;
2265 void Agenda::contentsMousePressEvent(QMouseEvent *event)
2267 Q_UNUSED(event);
2270 QSize Agenda::sizeHint() const
2272 if (d->mAllDayMode) {
2273 return QWidget::sizeHint();
2274 } else {
2275 return QSize(parentWidget()->width(), d->mGridSpacingY * d->mRows);
2279 QScrollBar *Agenda::verticalScrollBar() const
2281 return d->mScrollArea->verticalScrollBar();
2284 QScrollArea *Agenda::scrollArea() const
2286 return d->mScrollArea;
2289 AgendaItem::List Agenda::agendaItems(const QString &uid) const
2291 return d->mAgendaItemsById.values(uid);
2294 AgendaScrollArea::AgendaScrollArea(bool isAllDay, AgendaView *agendaView,
2295 bool isInteractive, QWidget *parent)
2296 : QScrollArea(parent)
2298 if (isAllDay) {
2299 mAgenda = new Agenda(agendaView, this, 1, isInteractive);
2300 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2301 } else {
2302 mAgenda = new Agenda(agendaView, this, 1, 96,
2303 agendaView->preferences()->hourSize(), isInteractive);
2306 setWidgetResizable(true);
2307 setWidget(mAgenda);
2308 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
2310 mAgenda->setStartTime(agendaView->preferences()->dayBegins().time());
2313 AgendaScrollArea::~AgendaScrollArea()
2317 Agenda *AgendaScrollArea::agenda() const
2319 return mAgenda;