mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
2310 lines
69 KiB
C++
2310 lines
69 KiB
C++
/*
|
|
Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
|
|
Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
|
|
Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
|
|
Author: Kevin Krammer, krake@kdab.com
|
|
Author: Sergio Martins, sergio@kdab.com
|
|
|
|
Marcus Bains line.
|
|
Copyright (c) 2001 Ali Rahimi <ali@mit.edu>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
As a special exception, permission is given to link this program
|
|
with any edition of Qt, and distribute the resulting executable,
|
|
without including the source code for Qt in the source distribution.
|
|
*/
|
|
#include "agenda.h"
|
|
#include "agendaitem.h"
|
|
#include "agendaview.h"
|
|
#include "helper.h"
|
|
#include "prefs.h"
|
|
|
|
#include <Akonadi/Calendar/ETMCalendar>
|
|
#include <Akonadi/Calendar/IncidenceChanger>
|
|
#include <calendarsupport/utils.h>
|
|
|
|
#include <KCalCore/Incidence>
|
|
#include <KCalCore/Todo>
|
|
|
|
#include <KCalUtils/RecurrenceActions>
|
|
|
|
#include <KGlobal>
|
|
#include <KMessageBox>
|
|
#include <KLocale>
|
|
|
|
#include <QApplication>
|
|
#include <QLabel>
|
|
#include <KLocalizedString>
|
|
#include <QMouseEvent>
|
|
#include <QPainter>
|
|
#include <QPointer>
|
|
#include <QResizeEvent>
|
|
#include <QScrollBar>
|
|
#include <QTimer>
|
|
#include <QHash>
|
|
#include <QWheelEvent>
|
|
#include <QMultiHash>
|
|
|
|
#include <cmath> // for fabs()
|
|
|
|
using namespace EventViews;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
class MarcusBains::Private
|
|
{
|
|
public:
|
|
Private( EventView *eventView, Agenda *agenda )
|
|
: mEventView( eventView ), mAgenda( agenda ),
|
|
mTimer( 0 ), mTimeBox( 0 ), mOldTodayCol( -1 )
|
|
{
|
|
}
|
|
|
|
int todayColumn() const;
|
|
|
|
public:
|
|
EventView *mEventView;
|
|
Agenda *mAgenda;
|
|
QTimer *mTimer;
|
|
QLabel *mTimeBox; // Label showing the current time
|
|
KDateTime mOldDateTime;
|
|
int mOldTodayCol;
|
|
};
|
|
|
|
int MarcusBains::Private::todayColumn() const
|
|
{
|
|
const QDate currentDate = QDate::currentDate();
|
|
|
|
int col = 0;
|
|
const KCalCore::DateList dateList = mAgenda->dateList();
|
|
foreach ( const QDate &date, dateList ) {
|
|
if ( date == currentDate ) {
|
|
return QApplication::isRightToLeft() ? mAgenda->columns() - 1 - col : col;
|
|
}
|
|
++col;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
MarcusBains::MarcusBains( EventView *eventView, Agenda *agenda )
|
|
: QFrame( agenda ), d( new Private( eventView, agenda ) )
|
|
{
|
|
d->mTimeBox = new QLabel( d->mAgenda );
|
|
d->mTimeBox->setAlignment( Qt::AlignRight | Qt::AlignBottom );
|
|
|
|
d->mTimer = new QTimer( this );
|
|
d->mTimer->setSingleShot( true );
|
|
connect( d->mTimer, SIGNAL(timeout()), this, SLOT(updateLocation()) );
|
|
d->mTimer->start( 0 );
|
|
}
|
|
|
|
MarcusBains::~MarcusBains()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
void MarcusBains::updateLocation()
|
|
{
|
|
updateLocationRecalc();
|
|
}
|
|
|
|
void MarcusBains::updateLocationRecalc( bool recalculate )
|
|
{
|
|
const bool showSeconds = d->mEventView->preferences()->marcusBainsShowSeconds();
|
|
const QColor color = d->mEventView->preferences()->agendaMarcusBainsLineLineColor();
|
|
|
|
const KDateTime now = KDateTime::currentLocalDateTime();
|
|
const QTime time = now.time();
|
|
|
|
if (now.date() != d->mOldDateTime.date()) {
|
|
recalculate = true; // New day
|
|
}
|
|
const int todayCol = recalculate ? d->todayColumn() : d->mOldTodayCol;
|
|
|
|
// Number of minutes since beginning of the day
|
|
const int minutes = time.hour() * 60 + time.minute();
|
|
const int minutesPerCell = 24 * 60 / d->mAgenda->rows();
|
|
|
|
d->mOldDateTime = now;
|
|
d->mOldTodayCol = todayCol;
|
|
|
|
int y = int( minutes * d->mAgenda->gridSpacingY() / minutesPerCell );
|
|
int x = int( d->mAgenda->gridSpacingX() * todayCol );
|
|
|
|
bool hideIt = !( d->mEventView->preferences()->marcusBainsEnabled() );
|
|
if ( !isHidden() && ( hideIt || ( todayCol < 0 ) ) ) {
|
|
hide();
|
|
d->mTimeBox->hide();
|
|
return;
|
|
}
|
|
|
|
if ( isHidden() && !hideIt ) {
|
|
show();
|
|
d->mTimeBox->show();
|
|
}
|
|
|
|
/* Line */
|
|
// It seems logical to adjust the line width with the label's font weight
|
|
const int fw = d->mEventView->preferences()->agendaMarcusBainsLineFont().weight();
|
|
setLineWidth( 1 + abs( fw - QFont::Normal ) / QFont::Light );
|
|
setFrameStyle( QFrame::HLine | QFrame::Plain );
|
|
QPalette pal = palette();
|
|
pal.setColor( QPalette::Window, color ); // for Oxygen
|
|
pal.setColor( QPalette::WindowText, color ); // for Plastique
|
|
setPalette( pal );
|
|
if ( recalculate ) {
|
|
setFixedSize( int( d->mAgenda->gridSpacingX() ), 1 );
|
|
}
|
|
move( x, y );
|
|
raise();
|
|
|
|
/* Label */
|
|
d->mTimeBox->setFont( d->mEventView->preferences()->agendaMarcusBainsLineFont() );
|
|
QPalette pal1 = d->mTimeBox->palette();
|
|
pal1.setColor( QPalette::WindowText, color );
|
|
d->mTimeBox->setPalette( pal1 );
|
|
d->mTimeBox->setText( KGlobal::locale()->formatTime( time, showSeconds ) );
|
|
d->mTimeBox->adjustSize();
|
|
if ( y - d->mTimeBox->height() >= 0 ) {
|
|
y -= d->mTimeBox->height();
|
|
} else {
|
|
y++;
|
|
}
|
|
if ( x - d->mTimeBox->width() + d->mAgenda->gridSpacingX() > 0 ) {
|
|
x += int( d->mAgenda->gridSpacingX() - d->mTimeBox->width() - 1 );
|
|
} else {
|
|
x++;
|
|
}
|
|
d->mTimeBox->move( x, y );
|
|
d->mTimeBox->raise();
|
|
|
|
if ( showSeconds || recalculate ) {
|
|
d->mTimer->start( 1000 );
|
|
} else {
|
|
d->mTimer->start( 1000 * ( 60 - time.second() ) );
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
class Agenda::Private
|
|
{
|
|
public:
|
|
Private( AgendaView *agendaView, QScrollArea *scrollArea,
|
|
int columns, int rows, int rowSize, bool isInteractive )
|
|
: mAgendaView( agendaView ), mScrollArea( scrollArea ), mAllDayMode( false ),
|
|
mColumns( columns ), mRows( rows ), mGridSpacingX( 0.0 ), mGridSpacingY( rowSize ),
|
|
mDesiredGridSpacingY( rowSize ), mCalendar( 0 ), mChanger( 0 ),
|
|
mResizeBorderWidth( 0 ), mScrollBorderWidth( 0 ), mScrollDelay( 0 ), mScrollOffset( 0 ),
|
|
mWorkingHoursEnable( false ), mHolidayMask( 0 ), mWorkingHoursYTop( 0 ),
|
|
mWorkingHoursYBottom( 0 ), mHasSelection( 0 ), mSelectedId( -1 ), mMarcusBains( 0 ),
|
|
mActionType( Agenda::NOP ), mItemMoved( false ), mOldLowerScrollValue( 0 ),
|
|
mOldUpperScrollValue( 0 ), mReturnPressed( false ), mIsInteractive( isInteractive )
|
|
{
|
|
if ( mGridSpacingY < 4 || mGridSpacingY > 30 ) {
|
|
mGridSpacingY = 10;
|
|
}
|
|
}
|
|
|
|
public:
|
|
PrefsPtr preferences() const
|
|
{
|
|
return mAgendaView->preferences();
|
|
}
|
|
|
|
bool isQueuedForDeletion( Akonadi::Item::Id id ) const
|
|
{
|
|
// if mAgendaItemsById contains it it means that a createAgendaItem() was called
|
|
// before the previous agenda items were deleted.
|
|
return mItemsQueuedForDeletion.contains( id ) && !mAgendaItemsById.contains( id );
|
|
}
|
|
|
|
QMultiHash<Akonadi::Item::Id, AgendaItem::QPtr> mAgendaItemsById; // It's a QMultiHash because recurring incidences might have many agenda items
|
|
QSet<Akonadi::Item::Id> mItemsQueuedForDeletion;
|
|
|
|
AgendaView *mAgendaView;
|
|
QScrollArea *mScrollArea;
|
|
|
|
bool mAllDayMode;
|
|
|
|
// Number of Columns/Rows of agenda grid
|
|
int mColumns;
|
|
int mRows;
|
|
|
|
// Width and height of agenda cells. mDesiredGridSpacingY is the height
|
|
// set in the config. The actual height might be larger since otherwise
|
|
// more than 24 hours might be displayed.
|
|
double mGridSpacingX;
|
|
double mGridSpacingY;
|
|
double mDesiredGridSpacingY;
|
|
|
|
// We need the calendar for drag'n'drop and for paint the ResourceColor
|
|
Akonadi::ETMCalendar::Ptr mCalendar;
|
|
Akonadi::IncidenceChanger *mChanger;
|
|
|
|
// size of border, where mouse action will resize the AgendaItem
|
|
int mResizeBorderWidth;
|
|
|
|
// size of border, where mouse mve will cause a scroll of the agenda
|
|
int mScrollBorderWidth;
|
|
int mScrollDelay;
|
|
int mScrollOffset;
|
|
|
|
QTimer mScrollUpTimer;
|
|
QTimer mScrollDownTimer;
|
|
|
|
// Cells to store Move and Resize coordiantes while performing the action
|
|
QPoint mStartCell;
|
|
QPoint mEndCell;
|
|
|
|
// Working Hour coordiantes
|
|
bool mWorkingHoursEnable;
|
|
QVector<bool> *mHolidayMask;
|
|
int mWorkingHoursYTop;
|
|
int mWorkingHoursYBottom;
|
|
|
|
// Selection
|
|
bool mHasSelection;
|
|
QPoint mSelectionStartPoint;
|
|
QPoint mSelectionStartCell;
|
|
QPoint mSelectionEndCell;
|
|
|
|
// List of dates to be displayed
|
|
KCalCore::DateList mSelectedDates;
|
|
|
|
// The AgendaItem, which has been right-clicked last
|
|
QPointer<AgendaItem> mClickedItem;
|
|
|
|
// The AgendaItem, which is being moved/resized
|
|
QPointer<AgendaItem> mActionItem;
|
|
|
|
// Currently selected item
|
|
QPointer<AgendaItem> mSelectedItem;
|
|
// Id of the last selected item. Used for reselecting in situations
|
|
// where the selected item points to a no longer valid incidence, for
|
|
// example during resource reload.
|
|
Akonadi::Item::Id mSelectedId;
|
|
|
|
// The Marcus Bains Line widget.
|
|
MarcusBains *mMarcusBains;
|
|
|
|
MouseActionType mActionType;
|
|
|
|
bool mItemMoved;
|
|
|
|
// List of all Items contained in agenda
|
|
QList<AgendaItem::QPtr> mItems;
|
|
QList<AgendaItem::QPtr> mItemsToDelete;
|
|
|
|
int mOldLowerScrollValue;
|
|
int mOldUpperScrollValue;
|
|
|
|
bool mReturnPressed;
|
|
bool mIsInteractive;
|
|
};
|
|
|
|
/*
|
|
Create an agenda widget with rows rows and columns columns.
|
|
*/
|
|
Agenda::Agenda( AgendaView *agendaView, QScrollArea *scrollArea,
|
|
int columns, int rows, int rowSize, bool isInteractive )
|
|
: QWidget( scrollArea ),
|
|
d( new Private( agendaView, scrollArea, columns, rows, rowSize, isInteractive ) )
|
|
{
|
|
setMouseTracking( true );
|
|
|
|
init();
|
|
}
|
|
|
|
/*
|
|
Create an agenda widget with columns columns and one row. This is used for
|
|
all-day events.
|
|
*/
|
|
Agenda::Agenda( AgendaView *agendaView, QScrollArea *scrollArea,
|
|
int columns, bool isInteractive )
|
|
: QWidget( scrollArea ), d( new Private( agendaView, scrollArea, columns, 1, 24,
|
|
isInteractive ) )
|
|
{
|
|
d->mAllDayMode = true;
|
|
|
|
init();
|
|
}
|
|
|
|
Agenda::~Agenda()
|
|
{
|
|
delete d->mMarcusBains;
|
|
delete d;
|
|
}
|
|
|
|
Akonadi::Item Agenda::selectedIncidence() const
|
|
{
|
|
return d->mSelectedItem ? d->mSelectedItem->incidence() : Akonadi::Item();
|
|
}
|
|
|
|
QDate Agenda::selectedIncidenceDate() const
|
|
{
|
|
return d->mSelectedItem ? d->mSelectedItem->occurrenceDate() : QDate();
|
|
}
|
|
|
|
Akonadi::Item::Id Agenda::lastSelectedItemId() const
|
|
{
|
|
return d->mSelectedId;
|
|
}
|
|
|
|
void Agenda::init()
|
|
{
|
|
setAttribute( Qt::WA_OpaquePaintEvent );
|
|
|
|
d->mGridSpacingX = static_cast<double>( d->mScrollArea->width() ) / d->mColumns;
|
|
d->mDesiredGridSpacingY = d->preferences()->hourSize();
|
|
if ( d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30 ) {
|
|
d->mDesiredGridSpacingY = 10;
|
|
}
|
|
|
|
// make sure that there are not more than 24 per day
|
|
d->mGridSpacingY = static_cast<double>( height() ) / d->mRows;
|
|
if ( d->mGridSpacingY < d->mDesiredGridSpacingY ) {
|
|
d->mGridSpacingY = d->mDesiredGridSpacingY;
|
|
}
|
|
|
|
d->mResizeBorderWidth = 12;
|
|
d->mScrollBorderWidth = 12;
|
|
d->mScrollDelay = 30;
|
|
d->mScrollOffset = 10;
|
|
|
|
// Grab key strokes for keyboard navigation of agenda. Seems to have no
|
|
// effect. Has to be fixed.
|
|
setFocusPolicy( Qt::WheelFocus );
|
|
|
|
connect( &d->mScrollUpTimer, SIGNAL(timeout()), SLOT(scrollUp()) );
|
|
connect( &d->mScrollDownTimer, SIGNAL(timeout()), SLOT(scrollDown()) );
|
|
|
|
d->mStartCell = QPoint( 0, 0 );
|
|
d->mEndCell = QPoint( 0, 0 );
|
|
|
|
d->mHasSelection = false;
|
|
d->mSelectionStartPoint = QPoint( 0, 0 );
|
|
d->mSelectionStartCell = QPoint( 0, 0 );
|
|
d->mSelectionEndCell = QPoint( 0, 0 );
|
|
|
|
d->mOldLowerScrollValue = -1;
|
|
d->mOldUpperScrollValue = -1;
|
|
|
|
d->mClickedItem = 0;
|
|
|
|
d->mActionItem = 0;
|
|
d->mActionType = NOP;
|
|
d->mItemMoved = false;
|
|
|
|
d->mSelectedItem = 0;
|
|
d->mSelectedId = -1;
|
|
|
|
setAcceptDrops( true );
|
|
installEventFilter( this );
|
|
|
|
/* resizeContents( int( mGridSpacingX * mColumns ), int( mGridSpacingY * mRows ) ); */
|
|
|
|
d->mScrollArea->viewport()->update();
|
|
// mScrollArea->viewport()->setAttribute( Qt::WA_NoSystemBackground, true );
|
|
d->mScrollArea->viewport()->setFocusPolicy( Qt::WheelFocus );
|
|
|
|
calculateWorkingHours();
|
|
|
|
connect( verticalScrollBar(), SIGNAL(valueChanged(int)),
|
|
SLOT(checkScrollBoundaries(int)) );
|
|
|
|
// Create the Marcus Bains line.
|
|
if( d->mAllDayMode ) {
|
|
d->mMarcusBains = 0;
|
|
} else {
|
|
d->mMarcusBains = new MarcusBains( d->mAgendaView, this );
|
|
}
|
|
}
|
|
|
|
void Agenda::clear()
|
|
{
|
|
qDeleteAll( d->mItems );
|
|
qDeleteAll( d->mItemsToDelete );
|
|
d->mItems.clear();
|
|
d->mItemsToDelete.clear();
|
|
d->mAgendaItemsById.clear();
|
|
d->mItemsQueuedForDeletion.clear();
|
|
|
|
d->mSelectedItem = 0;
|
|
|
|
clearSelection();
|
|
}
|
|
|
|
void Agenda::clearSelection()
|
|
{
|
|
d->mHasSelection = false;
|
|
d->mActionType = NOP;
|
|
update();
|
|
}
|
|
|
|
void Agenda::marcus_bains()
|
|
{
|
|
if ( d->mMarcusBains ) {
|
|
d->mMarcusBains->updateLocationRecalc( true );
|
|
}
|
|
}
|
|
|
|
void Agenda::changeColumns( int columns )
|
|
{
|
|
if ( columns == 0 ) {
|
|
kDebug() << "called with argument 0";
|
|
return;
|
|
}
|
|
|
|
clear();
|
|
d->mColumns = columns;
|
|
// setMinimumSize( mColumns * 10, mGridSpacingY + 1 );
|
|
// init();
|
|
// update();
|
|
|
|
QResizeEvent event( size(), size() );
|
|
|
|
QApplication::sendEvent( this, &event );
|
|
}
|
|
|
|
int Agenda::columns() const
|
|
{
|
|
return d->mColumns;
|
|
}
|
|
|
|
int Agenda::rows() const
|
|
{
|
|
return d->mRows;
|
|
}
|
|
|
|
double Agenda::gridSpacingX() const
|
|
{
|
|
return d->mGridSpacingX;
|
|
}
|
|
|
|
double Agenda::gridSpacingY() const
|
|
{
|
|
return d->mGridSpacingY;
|
|
}
|
|
|
|
/*
|
|
This is the eventFilter function, which gets all events from the AgendaItems
|
|
contained in the agenda. It has to handle moving and resizing for all items.
|
|
*/
|
|
bool Agenda::eventFilter ( QObject *object, QEvent *event )
|
|
{
|
|
switch( event->type() ) {
|
|
case QEvent::MouseButtonPress:
|
|
case QEvent::MouseButtonDblClick:
|
|
case QEvent::MouseButtonRelease:
|
|
case QEvent::MouseMove:
|
|
return eventFilter_mouse( object, static_cast<QMouseEvent *>( event ) );
|
|
#ifndef QT_NO_WHEELEVENT
|
|
case QEvent::Wheel:
|
|
return eventFilter_wheel( object, static_cast<QWheelEvent *>( event ) );
|
|
#endif
|
|
case QEvent::KeyPress:
|
|
case QEvent::KeyRelease:
|
|
return eventFilter_key( object, static_cast<QKeyEvent *>( event ) );
|
|
|
|
case ( QEvent::Leave ):
|
|
#ifndef QT_NO_CURSOR
|
|
if ( !d->mActionItem ) {
|
|
setCursor( Qt::ArrowCursor );
|
|
}
|
|
#endif
|
|
|
|
if ( object == this ) {
|
|
// so timelabels hide the mouse cursor
|
|
emit leaveAgenda();
|
|
}
|
|
return true;
|
|
|
|
case QEvent::Enter:
|
|
emit enterAgenda();
|
|
return QWidget::eventFilter( object, event );
|
|
|
|
#ifndef QT_NO_DRAGANDDROP
|
|
#ifndef KORG_NODND
|
|
case QEvent::DragEnter:
|
|
case QEvent::DragMove:
|
|
case QEvent::DragLeave:
|
|
case QEvent::Drop:
|
|
// case QEvent::DragResponse:
|
|
return eventFilter_drag( object, static_cast<QDropEvent*>( event ) );
|
|
#endif
|
|
#endif
|
|
|
|
default:
|
|
return QWidget::eventFilter( object, event );
|
|
}
|
|
}
|
|
|
|
bool Agenda::eventFilter_drag( QObject *obj, QDropEvent *de )
|
|
{
|
|
#ifndef QT_NO_DRAGANDDROP
|
|
const QMimeData *md = de->mimeData();
|
|
|
|
switch ( de->type() ) {
|
|
case QEvent::DragEnter:
|
|
case QEvent::DragMove:
|
|
if ( !CalendarSupport::canDecode( md ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( CalendarSupport::mimeDataHasIncidence( md ) ) {
|
|
de->accept();
|
|
} else {
|
|
de->ignore();
|
|
}
|
|
return true;
|
|
break;
|
|
case QEvent::DragLeave:
|
|
return false;
|
|
break;
|
|
case QEvent::Drop:
|
|
{
|
|
if ( !CalendarSupport::canDecode( md ) ) {
|
|
return false;
|
|
}
|
|
|
|
const QList<KUrl> incidenceUrls = CalendarSupport::incidenceItemUrls( md );
|
|
const KCalCore::Incidence::List incidences =
|
|
CalendarSupport::incidences( md, d->mCalendar->timeSpec() );
|
|
|
|
Q_ASSERT( !incidenceUrls.isEmpty() || !incidences.isEmpty() );
|
|
|
|
de->setDropAction( Qt::MoveAction );
|
|
|
|
QWidget *dropTarget = qobject_cast<QWidget*>( obj );
|
|
QPoint dropPosition = de->pos();
|
|
if ( dropTarget && dropTarget != this ) {
|
|
dropPosition = dropTarget->mapTo( this, dropPosition );
|
|
}
|
|
|
|
const QPoint gridPosition = contentsToGrid( dropPosition );
|
|
if ( !incidenceUrls.isEmpty() ) {
|
|
emit droppedIncidences( incidenceUrls, gridPosition, d->mAllDayMode );
|
|
} else {
|
|
emit droppedIncidences( incidences, gridPosition, d->mAllDayMode );
|
|
}
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case QEvent::DragResponse:
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#ifndef QT_NO_WHEELEVENT
|
|
bool Agenda::eventFilter_wheel ( QObject *object, QWheelEvent *e )
|
|
{
|
|
QPoint viewportPos;
|
|
bool accepted=false;
|
|
if ( ( e->modifiers() & Qt::ShiftModifier ) == Qt::ShiftModifier ) {
|
|
if ( object != this ) {
|
|
viewportPos = ( (QWidget *) object )->mapToParent( e->pos() );
|
|
} else {
|
|
viewportPos = e->pos();
|
|
}
|
|
//kDebug() << type:" << e->type() << "delta:" << e->delta();
|
|
emit zoomView( -e->delta(),
|
|
contentsToGrid( viewportPos ), Qt::Horizontal );
|
|
accepted = true;
|
|
}
|
|
|
|
if ( ( e->modifiers() & Qt::ControlModifier ) == Qt::ControlModifier ){
|
|
if ( object != this ) {
|
|
viewportPos = ( (QWidget *)object )->mapToParent( e->pos() );
|
|
} else {
|
|
viewportPos = e->pos();
|
|
}
|
|
emit zoomView( -e->delta(), contentsToGrid( viewportPos ), Qt::Vertical );
|
|
emit mousePosSignal( gridToContents( contentsToGrid( viewportPos ) ) );
|
|
accepted = true;
|
|
}
|
|
if ( accepted ) {
|
|
e->accept();
|
|
}
|
|
return accepted;
|
|
}
|
|
#endif
|
|
|
|
bool Agenda::eventFilter_key( QObject *, QKeyEvent *ke )
|
|
{
|
|
return d->mAgendaView->processKeyEvent( ke );
|
|
}
|
|
|
|
bool Agenda::eventFilter_mouse( QObject *object, QMouseEvent *me )
|
|
{
|
|
QPoint viewportPos;
|
|
if ( object != this ) {
|
|
viewportPos = static_cast<QWidget *>( object )->mapToParent( me->pos() );
|
|
} else {
|
|
viewportPos = me->pos();
|
|
}
|
|
|
|
switch ( me->type() ) {
|
|
case QEvent::MouseButtonPress:
|
|
if ( object != this ) {
|
|
if ( me->button() == Qt::RightButton ) {
|
|
d->mClickedItem = dynamic_cast<AgendaItem *>( object );
|
|
if ( d->mClickedItem ) {
|
|
selectItem( d->mClickedItem );
|
|
emit showIncidencePopupSignal( d->mClickedItem->incidence(),
|
|
d->mClickedItem->occurrenceDate() );
|
|
}
|
|
} else {
|
|
AgendaItem::QPtr item = dynamic_cast<AgendaItem *>(object);
|
|
if (item) {
|
|
const Akonadi::Item aitem = item->incidence();
|
|
KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( aitem );
|
|
if ( incidence->isReadOnly() ) {
|
|
d->mActionItem = 0;
|
|
} else {
|
|
d->mActionItem = item;
|
|
startItemAction( viewportPos );
|
|
}
|
|
// Warning: do selectItem() as late as possible, since all
|
|
// sorts of things happen during this call. Some can lead to
|
|
// this filter being run again and mActionItem being set to
|
|
// null.
|
|
selectItem( item );
|
|
}
|
|
}
|
|
} else {
|
|
if ( me->button() == Qt::RightButton ) {
|
|
// if mouse pointer is not in selection, select the cell below the cursor
|
|
QPoint gpos = contentsToGrid( viewportPos );
|
|
if ( !ptInSelection( gpos ) ) {
|
|
d->mSelectionStartCell = gpos;
|
|
d->mSelectionEndCell = gpos;
|
|
d->mHasSelection = true;
|
|
emit newStartSelectSignal();
|
|
emit newTimeSpanSignal( d->mSelectionStartCell, d->mSelectionEndCell );
|
|
// updateContents();
|
|
}
|
|
showNewEventPopupSignal();
|
|
} else {
|
|
selectItem( 0 );
|
|
d->mActionItem = 0;
|
|
#ifndef QT_NO_CURSOR
|
|
setCursor( Qt::ArrowCursor );
|
|
#endif
|
|
startSelectAction( viewportPos );
|
|
update();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QEvent::MouseButtonRelease:
|
|
if ( d->mActionItem ) {
|
|
endItemAction();
|
|
} else if ( d->mActionType == SELECT ) {
|
|
endSelectAction( viewportPos );
|
|
}
|
|
// This nasty gridToContents(contentsToGrid(..)) is needed to
|
|
// avoid an offset of a few pixels. Don't ask me why...
|
|
emit mousePosSignal( gridToContents( contentsToGrid( viewportPos ) ) );
|
|
break;
|
|
|
|
case QEvent::MouseMove:
|
|
{
|
|
if ( !d->mIsInteractive ) {
|
|
return true;
|
|
}
|
|
|
|
// This nasty gridToContents(contentsToGrid(..)) is needed todos
|
|
// avoid an offset of a few pixels. Don't ask me why...
|
|
QPoint indicatorPos = gridToContents( contentsToGrid( viewportPos ) );
|
|
if ( object != this ) {
|
|
AgendaItem::QPtr moveItem = dynamic_cast<AgendaItem *>( object );
|
|
const Akonadi::Item aitem = moveItem ? moveItem->incidence() : Akonadi::Item();
|
|
KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( aitem );
|
|
if ( incidence && !incidence->isReadOnly() ) {
|
|
if ( !d->mActionItem ) {
|
|
setNoActionCursor( moveItem, viewportPos );
|
|
} else {
|
|
performItemAction( viewportPos );
|
|
|
|
if ( d->mActionType == MOVE ) {
|
|
// show cursor at the current begin of the item
|
|
AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem();
|
|
if ( !firstItem ) {
|
|
firstItem = d->mActionItem;
|
|
}
|
|
indicatorPos = gridToContents(
|
|
QPoint( firstItem->cellXLeft(), firstItem->cellYTop() ) );
|
|
|
|
} else if ( d->mActionType == RESIZEBOTTOM ) {
|
|
// RESIZETOP is handled correctly, only resizebottom works differently
|
|
indicatorPos = gridToContents(
|
|
QPoint( d->mActionItem->cellXLeft(), d->mActionItem->cellYBottom() + 1 ) );
|
|
}
|
|
|
|
} // If we have an action item
|
|
} // If move item && !read only
|
|
} else {
|
|
if ( d->mActionType == SELECT ) {
|
|
performSelectAction( viewportPos );
|
|
|
|
// show cursor at end of timespan
|
|
if ( ( ( d->mStartCell.y() < d->mEndCell.y() ) &&
|
|
( d->mEndCell.x() >= d->mStartCell.x() ) ) ||
|
|
( d->mEndCell.x() > d->mStartCell.x() ) ) {
|
|
indicatorPos = gridToContents( QPoint( d->mEndCell.x(), d->mEndCell.y() + 1 ) );
|
|
} else {
|
|
indicatorPos = gridToContents( d->mEndCell );
|
|
}
|
|
}
|
|
}
|
|
emit mousePosSignal( indicatorPos );
|
|
break;
|
|
}
|
|
|
|
case QEvent::MouseButtonDblClick:
|
|
if ( object == this ) {
|
|
selectItem( 0 );
|
|
emit newEventSignal();
|
|
} else {
|
|
AgendaItem::QPtr doubleClickedItem = dynamic_cast<AgendaItem *>( object );
|
|
if ( doubleClickedItem ) {
|
|
selectItem( doubleClickedItem );
|
|
emit editIncidenceSignal( doubleClickedItem->incidence() );
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Agenda::ptInSelection( const QPoint &gpos ) const
|
|
{
|
|
if ( !d->mHasSelection ) {
|
|
return false;
|
|
} else if ( gpos.x() < d->mSelectionStartCell.x() || gpos.x() > d->mSelectionEndCell.x() ) {
|
|
return false;
|
|
} else if ( ( gpos.x() == d->mSelectionStartCell.x() ) &&
|
|
( gpos.y() < d->mSelectionStartCell.y() ) ) {
|
|
return false;
|
|
} else if ( ( gpos.x() == d->mSelectionEndCell.x() ) &&
|
|
( gpos.y() > d->mSelectionEndCell.y() ) ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Agenda::startSelectAction( const QPoint &viewportPos )
|
|
{
|
|
emit newStartSelectSignal();
|
|
|
|
d->mActionType = SELECT;
|
|
d->mSelectionStartPoint = viewportPos;
|
|
d->mHasSelection = true;
|
|
|
|
QPoint pos = viewportPos ;
|
|
QPoint gpos = contentsToGrid( pos );
|
|
|
|
// Store new selection
|
|
d->mStartCell = gpos;
|
|
d->mEndCell = gpos;
|
|
d->mSelectionStartCell = gpos;
|
|
d->mSelectionEndCell = gpos;
|
|
|
|
// updateContents();
|
|
}
|
|
|
|
void Agenda::performSelectAction( const QPoint &pos )
|
|
{
|
|
const QPoint gpos = contentsToGrid( pos );
|
|
|
|
// Scroll if cursor was moved to upper or lower end of agenda.
|
|
if ( pos.y() - contentsY() < d->mScrollBorderWidth && contentsY() > 0 ) {
|
|
d->mScrollUpTimer.start( d->mScrollDelay );
|
|
} else if ( contentsY() + d->mScrollArea->viewport()->height() -
|
|
d->mScrollBorderWidth < pos.y() ) {
|
|
d->mScrollDownTimer.start( d->mScrollDelay );
|
|
} else {
|
|
d->mScrollUpTimer.stop();
|
|
d->mScrollDownTimer.stop();
|
|
}
|
|
|
|
if ( gpos != d->mEndCell ) {
|
|
d->mEndCell = gpos;
|
|
if ( d->mStartCell.x() > d->mEndCell.x() ||
|
|
( d->mStartCell.x() == d->mEndCell.x() && d->mStartCell.y() > d->mEndCell.y() ) ) {
|
|
// backward selection
|
|
d->mSelectionStartCell = d->mEndCell;
|
|
d->mSelectionEndCell = d->mStartCell;
|
|
} else {
|
|
d->mSelectionStartCell = d->mStartCell;
|
|
d->mSelectionEndCell = d->mEndCell;
|
|
}
|
|
|
|
update();
|
|
}
|
|
}
|
|
|
|
void Agenda::endSelectAction( const QPoint ¤tPos )
|
|
{
|
|
d->mScrollUpTimer.stop();
|
|
d->mScrollDownTimer.stop();
|
|
|
|
d->mActionType = NOP;
|
|
|
|
emit newTimeSpanSignal( d->mSelectionStartCell, d->mSelectionEndCell );
|
|
|
|
if ( d->preferences()->selectionStartsEditor() ) {
|
|
if ( ( d->mSelectionStartPoint - currentPos ).manhattanLength() >
|
|
QApplication::startDragDistance() ) {
|
|
emit newEventSignal();
|
|
}
|
|
}
|
|
}
|
|
|
|
Agenda::MouseActionType Agenda::isInResizeArea( bool horizontal,
|
|
const QPoint &pos,
|
|
AgendaItem::QPtr item )
|
|
{
|
|
if ( !item ) {
|
|
return NOP;
|
|
}
|
|
QPoint gridpos = contentsToGrid( pos );
|
|
QPoint contpos = gridToContents(
|
|
gridpos + QPoint( ( QApplication::isRightToLeft() ) ? 1 : 0, 0 ) );
|
|
|
|
if ( horizontal ) {
|
|
int clXLeft = item->cellXLeft();
|
|
int clXRight = item->cellXRight();
|
|
if ( QApplication::isRightToLeft() ) {
|
|
int tmp = clXLeft;
|
|
clXLeft = clXRight;
|
|
clXRight = tmp;
|
|
}
|
|
int gridDistanceX = int( pos.x() - contpos.x() );
|
|
if ( gridDistanceX < d->mResizeBorderWidth && clXLeft == gridpos.x() ) {
|
|
if ( QApplication::isRightToLeft() ) {
|
|
return RESIZERIGHT;
|
|
} else {
|
|
return RESIZELEFT;
|
|
}
|
|
} else if ( ( d->mGridSpacingX - gridDistanceX ) < d->mResizeBorderWidth &&
|
|
clXRight == gridpos.x() ) {
|
|
if ( QApplication::isRightToLeft() ) {
|
|
return RESIZELEFT;
|
|
} else {
|
|
return RESIZERIGHT;
|
|
}
|
|
} else {
|
|
return MOVE;
|
|
}
|
|
} else {
|
|
int gridDistanceY = int( pos.y() - contpos.y() );
|
|
if ( gridDistanceY < d->mResizeBorderWidth &&
|
|
item->cellYTop() == gridpos.y() && !item->firstMultiItem() ) {
|
|
return RESIZETOP;
|
|
} else if ( ( d->mGridSpacingY - gridDistanceY ) < d->mResizeBorderWidth &&
|
|
item->cellYBottom() == gridpos.y() && !item->lastMultiItem() ) {
|
|
return RESIZEBOTTOM;
|
|
} else {
|
|
return MOVE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Agenda::startItemAction( const QPoint &pos )
|
|
{
|
|
Q_ASSERT( d->mActionItem );
|
|
|
|
d->mStartCell = contentsToGrid( pos );
|
|
d->mEndCell = d->mStartCell;
|
|
|
|
bool noResize = CalendarSupport::hasTodo( d->mActionItem->incidence() );
|
|
|
|
d->mActionType = MOVE;
|
|
if ( !noResize ) {
|
|
d->mActionType = isInResizeArea( d->mAllDayMode, pos, d->mActionItem );
|
|
}
|
|
|
|
d->mActionItem->startMove();
|
|
setActionCursor( d->mActionType, true );
|
|
}
|
|
|
|
void Agenda::performItemAction( const QPoint &pos )
|
|
{
|
|
QPoint gpos = contentsToGrid( pos );
|
|
|
|
// Cursor left active agenda area.
|
|
// This starts a drag.
|
|
if ( pos.y() < 0 || pos.y() >= contentsY() + d->mScrollArea->viewport()->height() ||
|
|
pos.x() < 0 || pos.x() >= width() ) {
|
|
if ( d->mActionType == MOVE ) {
|
|
d->mScrollUpTimer.stop();
|
|
d->mScrollDownTimer.stop();
|
|
d->mActionItem->resetMove();
|
|
placeSubCells( d->mActionItem );
|
|
emit startDragSignal( d->mActionItem->incidence() );
|
|
#ifndef QT_NO_CURSOR
|
|
setCursor( Qt::ArrowCursor );
|
|
#endif
|
|
if ( d->mChanger ) {
|
|
// d->mChanger->cancelChange( d->mActionItem->incidence() );
|
|
}
|
|
d->mActionItem = 0;
|
|
d->mActionType = NOP;
|
|
d->mItemMoved = false;
|
|
return;
|
|
}
|
|
} else {
|
|
setActionCursor( d->mActionType, true );
|
|
}
|
|
|
|
// Scroll if item was moved to upper or lower end of agenda.
|
|
const int distanceToTop = pos.y() - contentsY();
|
|
if ( distanceToTop < d->mScrollBorderWidth && distanceToTop > -d->mScrollBorderWidth ) {
|
|
d->mScrollUpTimer.start( d->mScrollDelay );
|
|
} else if ( contentsY() + d->mScrollArea->viewport()->height() -
|
|
d->mScrollBorderWidth < pos.y() ) {
|
|
d->mScrollDownTimer.start( d->mScrollDelay );
|
|
} else {
|
|
d->mScrollUpTimer.stop();
|
|
d->mScrollDownTimer.stop();
|
|
}
|
|
|
|
// Move or resize item if necessary
|
|
if ( d->mEndCell != gpos ) {
|
|
if ( !d->mItemMoved ) {
|
|
if ( !d->mChanger ) {
|
|
KMessageBox::information( this,
|
|
i18n( "Unable to lock item for modification. "
|
|
"You cannot make any changes." ),
|
|
i18n( "Locking Failed" ), QLatin1String("AgendaLockingFailed") );
|
|
d->mScrollUpTimer.stop();
|
|
d->mScrollDownTimer.stop();
|
|
d->mActionItem->resetMove();
|
|
placeSubCells( d->mActionItem );
|
|
#ifndef QT_NO_CURSOR
|
|
setCursor( Qt::ArrowCursor );
|
|
#endif
|
|
d->mActionItem = 0;
|
|
d->mActionType = NOP;
|
|
d->mItemMoved = false;
|
|
return;
|
|
}
|
|
d->mItemMoved = true;
|
|
}
|
|
d->mActionItem->raise();
|
|
if ( d->mActionType == MOVE ) {
|
|
// Move all items belonging to a multi item
|
|
AgendaItem::QPtr firstItem = d->mActionItem->firstMultiItem();
|
|
if ( !firstItem ) {
|
|
firstItem = d->mActionItem;
|
|
}
|
|
AgendaItem::QPtr lastItem = d->mActionItem->lastMultiItem();
|
|
if ( !lastItem ) {
|
|
lastItem = d->mActionItem;
|
|
}
|
|
QPoint deltapos = gpos - d->mEndCell;
|
|
AgendaItem::QPtr moveItem = firstItem;
|
|
while ( moveItem ) {
|
|
bool changed = false;
|
|
if ( deltapos.x() != 0 ) {
|
|
moveItem->moveRelative( deltapos.x(), 0 );
|
|
changed = true;
|
|
}
|
|
// in all day view don't try to move multi items, since there are none
|
|
if ( moveItem == firstItem && !d->mAllDayMode ) { // is the first item
|
|
int newY = deltapos.y() + moveItem->cellYTop();
|
|
// If event start moved earlier than 0:00, it starts the previous day
|
|
if ( newY < 0 && newY > d->mScrollBorderWidth ) {
|
|
moveItem->expandTop( -moveItem->cellYTop() );
|
|
// prepend a new item at ( x-1, rows()+newY to rows() )
|
|
AgendaItem::QPtr newFirst = firstItem->prevMoveItem();
|
|
// cell's y values are first and last cell of the bar,
|
|
// so if newY=-1, they need to be the same
|
|
if ( newFirst ) {
|
|
newFirst->setCellXY( moveItem->cellXLeft() - 1, rows() + newY, rows() - 1 );
|
|
d->mItems.append( newFirst );
|
|
moveItem->resize( int( d->mGridSpacingX * newFirst->cellWidth() ),
|
|
int( d->mGridSpacingY * newFirst->cellHeight() ) );
|
|
QPoint cpos = gridToContents(
|
|
QPoint( newFirst->cellXLeft(), newFirst->cellYTop() ) );
|
|
newFirst->setParent( this );
|
|
newFirst->move( cpos.x(), cpos.y() );
|
|
} else {
|
|
newFirst = insertItem( moveItem->incidence(), moveItem->occurrenceDateTime(),
|
|
moveItem->cellXLeft() - 1, rows() + newY, rows() - 1,
|
|
moveItem->itemPos(), moveItem->itemCount(), false ) ;
|
|
}
|
|
if ( newFirst ) {
|
|
newFirst->show();
|
|
}
|
|
moveItem->prependMoveItem( newFirst );
|
|
firstItem = newFirst;
|
|
} else if ( newY >= rows() ) {
|
|
// If event start is moved past 24:00, it starts the next day
|
|
// erase current item (i.e. remove it from the multiItem list)
|
|
firstItem = moveItem->nextMultiItem();
|
|
moveItem->hide();
|
|
d->mItems.removeAll( moveItem );
|
|
// removeChild( moveItem );
|
|
d->mActionItem->removeMoveItem( moveItem );
|
|
moveItem=firstItem;
|
|
// adjust next day's item
|
|
if ( moveItem ) {
|
|
moveItem->expandTop( rows() - newY );
|
|
}
|
|
} else {
|
|
moveItem->expandTop( deltapos.y(), true );
|
|
}
|
|
changed=true;
|
|
}
|
|
if ( moveItem && !moveItem->lastMultiItem() && !d->mAllDayMode ) { // is the last item
|
|
int newY = deltapos.y() + moveItem->cellYBottom();
|
|
if ( newY < 0 ) {
|
|
// erase current item
|
|
lastItem = moveItem->prevMultiItem();
|
|
moveItem->hide();
|
|
d->mItems.removeAll( moveItem );
|
|
// removeChild( moveItem );
|
|
moveItem->removeMoveItem( moveItem );
|
|
moveItem = lastItem;
|
|
moveItem->expandBottom( newY + 1 );
|
|
} else if ( newY >= rows() ) {
|
|
moveItem->expandBottom( rows()-moveItem->cellYBottom() - 1 );
|
|
// append item at ( x+1, 0 to newY-rows() )
|
|
AgendaItem::QPtr newLast = lastItem->nextMoveItem();
|
|
if ( newLast ) {
|
|
newLast->setCellXY( moveItem->cellXLeft() + 1, 0, newY-rows() - 1 );
|
|
d->mItems.append( newLast );
|
|
moveItem->resize( int( d->mGridSpacingX * newLast->cellWidth() ),
|
|
int( d->mGridSpacingY * newLast->cellHeight() ) );
|
|
QPoint cpos = gridToContents( QPoint( newLast->cellXLeft(), newLast->cellYTop() ) ) ;
|
|
newLast->setParent( this );
|
|
newLast->move( cpos.x(), cpos.y() );
|
|
} else {
|
|
newLast = insertItem( moveItem->incidence(), moveItem->occurrenceDateTime(),
|
|
moveItem->cellXLeft() + 1, 0, newY - rows() - 1,
|
|
moveItem->itemPos(), moveItem->itemCount(), false ) ;
|
|
}
|
|
moveItem->appendMoveItem( newLast );
|
|
newLast->show();
|
|
lastItem = newLast;
|
|
} else {
|
|
moveItem->expandBottom( deltapos.y() );
|
|
}
|
|
changed = true;
|
|
}
|
|
if ( changed ) {
|
|
adjustItemPosition( moveItem );
|
|
}
|
|
if ( moveItem ) {
|
|
moveItem = moveItem->nextMultiItem();
|
|
}
|
|
}
|
|
} else if ( d->mActionType == RESIZETOP ) {
|
|
if ( d->mEndCell.y() <= d->mActionItem->cellYBottom() ) {
|
|
d->mActionItem->expandTop( gpos.y() - d->mEndCell.y() );
|
|
adjustItemPosition( d->mActionItem );
|
|
}
|
|
} else if ( d->mActionType == RESIZEBOTTOM ) {
|
|
if ( d->mEndCell.y() >= d->mActionItem->cellYTop() ) {
|
|
d->mActionItem->expandBottom( gpos.y() - d->mEndCell.y() );
|
|
adjustItemPosition( d->mActionItem );
|
|
}
|
|
} else if ( d->mActionType == RESIZELEFT ) {
|
|
if ( d->mEndCell.x() <= d->mActionItem->cellXRight() ) {
|
|
d->mActionItem->expandLeft( gpos.x() - d->mEndCell.x() );
|
|
adjustItemPosition( d->mActionItem );
|
|
}
|
|
} else if ( d->mActionType == RESIZERIGHT ) {
|
|
if ( d->mEndCell.x() >= d->mActionItem->cellXLeft() ) {
|
|
d->mActionItem->expandRight( gpos.x() - d->mEndCell.x() );
|
|
adjustItemPosition( d->mActionItem );
|
|
}
|
|
}
|
|
d->mEndCell = gpos;
|
|
}
|
|
}
|
|
|
|
void Agenda::endItemAction()
|
|
{
|
|
//PENDING(AKONADI_PORT) review all this cloning and changer calls
|
|
d->mActionType = NOP;
|
|
d->mScrollUpTimer.stop();
|
|
d->mScrollDownTimer.stop();
|
|
#ifndef QT_NO_CURSOR
|
|
setCursor( Qt::ArrowCursor );
|
|
#endif
|
|
|
|
if ( !d->mChanger ) {
|
|
kError() << "No IncidenceChanger set";
|
|
return;
|
|
}
|
|
|
|
bool multiModify = false;
|
|
// FIXME: do the cloning here...
|
|
Akonadi::Item inc = d->mActionItem->incidence();
|
|
const KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence( inc );
|
|
d->mItemMoved = d->mItemMoved && !( d->mStartCell.x() == d->mEndCell.x() &&
|
|
d->mStartCell.y() == d->mEndCell.y() );
|
|
|
|
bool addIncidence = false;
|
|
if ( d->mItemMoved ) {
|
|
bool modify = false;
|
|
if ( incidence->recurs() ) {
|
|
const int res = d->mAgendaView->showMoveRecurDialog(
|
|
CalendarSupport::incidence( d->mActionItem->incidence() ), d->mActionItem->occurrenceDate() );
|
|
switch ( res ) {
|
|
case KCalUtils::RecurrenceActions::AllOccurrences: // All occurrences
|
|
// Moving the whole sequene of events is handled by the itemModified below.
|
|
modify = true;
|
|
break;
|
|
case KCalUtils::RecurrenceActions::SelectedOccurrence:
|
|
case KCalUtils::RecurrenceActions::FutureOccurrences:
|
|
{
|
|
const bool thisAndFuture = (res == KCalUtils::RecurrenceActions::FutureOccurrences);
|
|
modify = true;
|
|
multiModify = true;
|
|
d->mChanger->startAtomicOperation( i18n( "Dissociate event from recurrence" ) );
|
|
KCalCore::Incidence::Ptr newInc( KCalCore::Calendar::createException(
|
|
incidence, d->mActionItem->occurrenceDateTime(), thisAndFuture ) );
|
|
if ( newInc ) {
|
|
// don't recreate items, they already have the correct position
|
|
d->mAgendaView->enableAgendaUpdate( false );
|
|
|
|
Akonadi::Item item;
|
|
item.setPayload( newInc );
|
|
d->mActionItem->setIncidence( item );
|
|
d->mActionItem->dissociateFromMultiItem();
|
|
|
|
addIncidence = true;
|
|
|
|
d->mAgendaView->enableAgendaUpdate( true );
|
|
} else {
|
|
KMessageBox::sorry(
|
|
this,
|
|
i18n( "Unable to add the exception item to the calendar. "
|
|
"No change will be done." ),
|
|
i18n( "Error Occurred" ) );
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
modify = false;
|
|
d->mActionItem->resetMove();
|
|
placeSubCells( d->mActionItem ); //PENDING(AKONADI_PORT) should this be done after
|
|
//the new item was asynchronously added?
|
|
}
|
|
}
|
|
|
|
AgendaItem::QPtr placeItem = d->mActionItem->firstMultiItem();
|
|
if ( !placeItem ) {
|
|
placeItem = d->mActionItem;
|
|
}
|
|
|
|
if ( modify ) {
|
|
d->mActionItem->endMove();
|
|
|
|
AgendaItem::QPtr modif = placeItem;
|
|
|
|
QList<AgendaItem::QPtr> oldconflictItems = placeItem->conflictItems();
|
|
QList<AgendaItem::QPtr>::iterator it;
|
|
for ( it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it ) {
|
|
if ( *it ) {
|
|
placeSubCells( *it );
|
|
}
|
|
}
|
|
while ( placeItem ) {
|
|
placeSubCells( placeItem );
|
|
placeItem = placeItem->nextMultiItem();
|
|
}
|
|
|
|
// Notify about change
|
|
// The agenda view will apply the changes to the actual Incidence*!
|
|
// Bug #228696 don't call endChanged now it's async in Akonadi so it can
|
|
// be called before that modified item was done. And endChange is
|
|
// calling when we move item.
|
|
// Not perfect need to improve it!
|
|
//mChanger->endChange( inc );
|
|
d->mAgendaView->updateEventDates( modif, addIncidence, inc.parentCollection().id() );
|
|
|
|
if ( addIncidence ) {
|
|
// delete the one we dragged, there's a new one being added async, due to dissociation.
|
|
delete modif;
|
|
}
|
|
} else {
|
|
// the item was moved, but not further modified, since it's not recurring
|
|
// make sure the view updates anyhow, with the right item
|
|
d->mAgendaView->updateEventDates( placeItem, addIncidence, inc.parentCollection().id() );
|
|
}
|
|
}
|
|
|
|
d->mActionItem = 0;
|
|
d->mItemMoved = false;
|
|
|
|
if ( multiModify ) {
|
|
d->mChanger->endAtomicOperation();
|
|
}
|
|
}
|
|
|
|
void Agenda::setActionCursor( int actionType, bool acting )
|
|
{
|
|
#ifndef QT_NO_CURSOR
|
|
switch ( actionType ) {
|
|
case MOVE:
|
|
if ( acting ) {
|
|
setCursor( Qt::SizeAllCursor );
|
|
} else {
|
|
setCursor( Qt::ArrowCursor );
|
|
}
|
|
break;
|
|
case RESIZETOP:
|
|
case RESIZEBOTTOM:
|
|
setCursor( Qt::SizeVerCursor );
|
|
break;
|
|
case RESIZELEFT:
|
|
case RESIZERIGHT:
|
|
setCursor( Qt::SizeHorCursor );
|
|
break;
|
|
default:
|
|
setCursor( Qt::ArrowCursor );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Agenda::setNoActionCursor( AgendaItem::QPtr moveItem, const QPoint &pos )
|
|
{
|
|
const Akonadi::Item item = moveItem ? moveItem->incidence() : Akonadi::Item();
|
|
|
|
const bool noResize = CalendarSupport::hasTodo( item );
|
|
|
|
Agenda::MouseActionType resizeType = MOVE;
|
|
if ( !noResize ) {
|
|
resizeType = isInResizeArea( d->mAllDayMode, pos, moveItem );
|
|
}
|
|
setActionCursor( resizeType );
|
|
}
|
|
|
|
/** calculate the width of the column subcells of the given item
|
|
*/
|
|
double Agenda::calcSubCellWidth( AgendaItem::QPtr item )
|
|
{
|
|
QPoint pt, pt1;
|
|
pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
|
|
pt1 = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) + QPoint( 1, 1 ) );
|
|
pt1 -= pt;
|
|
int maxSubCells = item->subCells();
|
|
double newSubCellWidth;
|
|
if ( d->mAllDayMode ) {
|
|
newSubCellWidth = static_cast<double>( pt1.y() ) / maxSubCells;
|
|
} else {
|
|
newSubCellWidth = static_cast<double>( pt1.x() ) / maxSubCells;
|
|
}
|
|
return newSubCellWidth;
|
|
}
|
|
|
|
void Agenda::adjustItemPosition( AgendaItem::QPtr item )
|
|
{
|
|
if ( !item ) {
|
|
return;
|
|
}
|
|
item->resize( int( d->mGridSpacingX * item->cellWidth() ),
|
|
int( d->mGridSpacingY * item->cellHeight() ) );
|
|
int clXLeft = item->cellXLeft();
|
|
if ( QApplication::isRightToLeft() ) {
|
|
clXLeft = item->cellXRight() + 1;
|
|
}
|
|
QPoint cpos = gridToContents( QPoint( clXLeft, item->cellYTop() ) );
|
|
item->move( cpos.x(), cpos.y() );
|
|
}
|
|
|
|
void Agenda::placeAgendaItem( AgendaItem::QPtr item, double subCellWidth )
|
|
{
|
|
// "left" upper corner, no subcells yet, RTL layouts have right/left
|
|
// switched, widths are negative then
|
|
QPoint pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
|
|
// right lower corner
|
|
QPoint pt1 = gridToContents(
|
|
QPoint( item->cellXLeft() + item->cellWidth(), item->cellYBottom() + 1 ) );
|
|
|
|
double subCellPos = item->subCell() * subCellWidth;
|
|
|
|
// we need to add 0.01 to make sure we don't loose one pixed due to numerics
|
|
// (i.e. if it would be x.9998, we want the integer, not rounded down.
|
|
double delta = 0.01;
|
|
if ( subCellWidth < 0 ) {
|
|
delta = -delta;
|
|
}
|
|
int height, width, xpos, ypos;
|
|
if ( d->mAllDayMode ) {
|
|
width = pt1.x() - pt.x();
|
|
height = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
|
|
xpos = pt.x();
|
|
ypos = pt.y() + int( subCellPos );
|
|
} else {
|
|
width = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
|
|
height = pt1.y() - pt.y();
|
|
xpos = pt.x() + int( subCellPos );
|
|
ypos = pt.y();
|
|
}
|
|
if ( QApplication::isRightToLeft() ) { // RTL language/layout
|
|
xpos += width;
|
|
width = -width;
|
|
}
|
|
if ( height < 0 ) { // BTT (bottom-to-top) layout ?!?
|
|
ypos += height;
|
|
height = -height;
|
|
}
|
|
item->resize( width, height );
|
|
item->move( xpos, ypos );
|
|
}
|
|
|
|
/*
|
|
Place item in cell and take care that multiple items using the same cell do
|
|
not overlap. This method is not yet optimal. It doesn't use the maximum space
|
|
it can get in all cases.
|
|
At the moment the method has a bug: When an item is placed only the sub cell
|
|
widths of the items are changed, which are within the Y region the item to
|
|
place spans. When the sub cell width change of one of this items affects a
|
|
cell, where other items are, which do not overlap in Y with the item to
|
|
place, the display gets corrupted, although the corruption looks quite nice.
|
|
*/
|
|
void Agenda::placeSubCells( AgendaItem::QPtr placeItem )
|
|
{
|
|
#if 0
|
|
kDebug();
|
|
if ( placeItem ) {
|
|
KCalCore::Incidence::Ptr event = placeItem->incidence();
|
|
if ( !event ) {
|
|
kDebug() << " event is 0";
|
|
} else {
|
|
kDebug() << " event:" << event->summary();
|
|
}
|
|
} else {
|
|
kDebug() << " placeItem is 0";
|
|
}
|
|
kDebug() << "Agenda::placeSubCells()...";
|
|
#endif
|
|
|
|
QList<CalendarSupport::CellItem*> cells;
|
|
foreach ( CalendarSupport::CellItem *item, d->mItems ) {
|
|
if ( item ) {
|
|
cells.append( item );
|
|
}
|
|
}
|
|
|
|
QList<CalendarSupport::CellItem*> items = CalendarSupport::CellItem::placeItem( cells, placeItem );
|
|
|
|
placeItem->setConflictItems( QList<AgendaItem::QPtr>() );
|
|
double newSubCellWidth = calcSubCellWidth( placeItem );
|
|
QList<CalendarSupport::CellItem*>::iterator it;
|
|
for ( it = items.begin(); it != items.end(); ++it ) {
|
|
if ( *it ) {
|
|
AgendaItem::QPtr item = static_cast<AgendaItem *>( *it );
|
|
placeAgendaItem( item, newSubCellWidth );
|
|
item->addConflictItem( placeItem );
|
|
placeItem->addConflictItem( item );
|
|
}
|
|
}
|
|
if ( items.isEmpty() ) {
|
|
placeAgendaItem( placeItem, newSubCellWidth );
|
|
}
|
|
placeItem->update();
|
|
}
|
|
|
|
int Agenda::columnWidth( int column ) const
|
|
{
|
|
int start = gridToContents( QPoint( column, 0 ) ).x();
|
|
if ( QApplication::isRightToLeft() ) {
|
|
column--;
|
|
} else {
|
|
column++;
|
|
}
|
|
int end = gridToContents( QPoint( column, 0 ) ).x();
|
|
return end - start;
|
|
}
|
|
|
|
void Agenda::paintEvent( QPaintEvent * )
|
|
{
|
|
QPainter p( this );
|
|
drawContents( &p, 0, -y(), d->mGridSpacingX * d->mColumns, d->mGridSpacingY * d->mRows + y() );
|
|
}
|
|
|
|
/*
|
|
Draw grid in the background of the agenda.
|
|
*/
|
|
void Agenda::drawContents( QPainter *p, int cx, int cy, int cw, int ch )
|
|
{
|
|
QPixmap db( cw, ch );
|
|
db.fill(); // We don't want to see leftovers from previous paints
|
|
QPainter dbp( &db );
|
|
// TODO: CHECK THIS
|
|
// if ( ! d->preferences()->agendaGridBackgroundImage().isEmpty() ) {
|
|
// QPixmap bgImage( d->preferences()->agendaGridBackgroundImage() );
|
|
// dbp.drawPixmap( 0, 0, cw, ch, bgImage ); FIXME
|
|
// }
|
|
|
|
dbp.fillRect( 0, 0, cw, ch,
|
|
d->preferences()->agendaGridBackgroundColor() );
|
|
|
|
dbp.translate( -cx, -cy );
|
|
|
|
double lGridSpacingY = d->mGridSpacingY * 2;
|
|
|
|
// If work day, use work color
|
|
// If busy day, use busy color
|
|
// if work and busy day, mix both, and busy color has alpha
|
|
|
|
const QVector<bool> busyDayMask = d->mAgendaView->busyDayMask();
|
|
|
|
// Highlight working hours
|
|
if ( d->mWorkingHoursEnable && d->mHolidayMask ) {
|
|
const QColor workColor = d->preferences()->workingHoursColor();
|
|
|
|
QPoint pt1( cx, d->mWorkingHoursYTop );
|
|
QPoint pt2( cx + cw, d->mWorkingHoursYBottom );
|
|
if ( pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) {
|
|
int gxStart = contentsToGrid( pt1 ).x();
|
|
int gxEnd = contentsToGrid( pt2 ).x();
|
|
// correct start/end for rtl layouts
|
|
if ( gxStart > gxEnd ) {
|
|
int tmp = gxStart;
|
|
gxStart = gxEnd;
|
|
gxEnd = tmp;
|
|
}
|
|
int xoffset = ( QApplication::isRightToLeft() ? 1 : 0 );
|
|
while ( gxStart <= gxEnd ) {
|
|
int xStart = gridToContents( QPoint( gxStart + xoffset, 0 ) ).x();
|
|
int xWidth = columnWidth( gxStart ) + 1;
|
|
|
|
if ( pt2.y() < pt1.y() ) {
|
|
// overnight working hours
|
|
if ( ( ( gxStart == 0 ) && !d->mHolidayMask->at( d->mHolidayMask->count() - 1 ) ) ||
|
|
( ( gxStart > 0 ) && ( gxStart < int( d->mHolidayMask->count() ) ) &&
|
|
( !d->mHolidayMask->at( gxStart - 1 ) ) ) ) {
|
|
if ( pt2.y() > cy ) {
|
|
dbp.fillRect( xStart, cy, xWidth, pt2.y() - cy + 1, workColor );
|
|
}
|
|
}
|
|
if ( ( gxStart < int( d->mHolidayMask->count() - 1 ) ) &&
|
|
( !d->mHolidayMask->at( gxStart ) ) ) {
|
|
if ( pt1.y() < cy + ch - 1 ) {
|
|
dbp.fillRect( xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1, workColor );
|
|
}
|
|
}
|
|
} else {
|
|
// last entry in holiday mask denotes the previous day not visible
|
|
// (needed for overnight shifts)
|
|
if ( gxStart < int( d->mHolidayMask->count() - 1 ) && !d->mHolidayMask->at( gxStart ) ) {
|
|
dbp.fillRect( xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1, workColor );
|
|
}
|
|
}
|
|
++gxStart;
|
|
}
|
|
}
|
|
}
|
|
|
|
// busy days
|
|
if ( d->preferences()->colorAgendaBusyDays() && !d->mAllDayMode ) {
|
|
for ( int i = 0; i < busyDayMask.count(); ++i ) {
|
|
if ( busyDayMask[i] ) {
|
|
const QPoint pt1( cx + d->mGridSpacingX * i, 0 );
|
|
// const QPoint pt2( cx + mGridSpacingX * ( i+1 ), ch );
|
|
QColor busyColor = d->preferences()->viewBgBusyColor();
|
|
busyColor.setAlpha( EventViews::BUSY_BACKGROUND_ALPHA );
|
|
dbp.fillRect( pt1.x(), pt1.y(), d->mGridSpacingX, cy + ch, busyColor );
|
|
}
|
|
}
|
|
}
|
|
|
|
// draw selection
|
|
if ( d->mHasSelection && d->mAgendaView->dateRangeSelectionEnabled() ) {
|
|
QPoint pt, pt1;
|
|
|
|
if ( d->mSelectionEndCell.x() > d->mSelectionStartCell.x() ) { // multi day selection
|
|
// draw start day
|
|
pt = gridToContents( d->mSelectionStartCell );
|
|
pt1 = gridToContents( QPoint( d->mSelectionStartCell.x() + 1, d->mRows + 1 ) );
|
|
dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() );
|
|
// draw all other days between the start day and the day of the selection end
|
|
for ( int c = d->mSelectionStartCell.x() + 1; c < d->mSelectionEndCell.x(); ++c ) {
|
|
pt = gridToContents( QPoint( c, 0 ) );
|
|
pt1 = gridToContents( QPoint( c + 1, d->mRows + 1 ) );
|
|
dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() );
|
|
}
|
|
// draw end day
|
|
pt = gridToContents( QPoint( d->mSelectionEndCell.x(), 0 ) );
|
|
pt1 = gridToContents( d->mSelectionEndCell + QPoint( 1, 1 ) );
|
|
dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() );
|
|
} else { // single day selection
|
|
pt = gridToContents( d->mSelectionStartCell );
|
|
pt1 = gridToContents( d->mSelectionEndCell + QPoint( 1, 1 ) );
|
|
dbp.fillRect( QRect( pt, pt1 ), d->preferences()->agendaGridHighlightColor() );
|
|
}
|
|
}
|
|
|
|
QPen hourPen( d->preferences()->agendaGridBackgroundColor().dark( 150 ) );
|
|
QPen halfHourPen( d->preferences()->agendaGridBackgroundColor().dark( 125 ) );
|
|
dbp.setPen( hourPen );
|
|
|
|
// Draw vertical lines of grid, start with the last line not yet visible
|
|
double x = ( int( cx / d->mGridSpacingX ) ) * d->mGridSpacingX;
|
|
while ( x < cx + cw ) {
|
|
dbp.drawLine( int( x ), cy, int( x ), cy + ch );
|
|
x += d->mGridSpacingX;
|
|
}
|
|
|
|
// Draw horizontal lines of grid
|
|
double y = ( int( cy / ( 2 * lGridSpacingY ) ) ) * 2 * lGridSpacingY;
|
|
while ( y < cy + ch ) {
|
|
dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
|
|
y += 2 * lGridSpacingY;
|
|
}
|
|
y = ( 2 * int( cy / ( 2 * lGridSpacingY ) ) + 1 ) * lGridSpacingY;
|
|
dbp.setPen( halfHourPen );
|
|
while ( y < cy + ch ) {
|
|
dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
|
|
y += 2 * lGridSpacingY;
|
|
}
|
|
p->drawPixmap( cx, cy, db );
|
|
}
|
|
|
|
/*
|
|
Convert srcollview contents coordinates to agenda grid coordinates.
|
|
*/
|
|
QPoint Agenda::contentsToGrid ( const QPoint &pos ) const
|
|
{
|
|
int gx = int( QApplication::isRightToLeft() ?
|
|
d->mColumns - pos.x() / d->mGridSpacingX : pos.x() / d->mGridSpacingX );
|
|
int gy = int( pos.y() / d->mGridSpacingY );
|
|
return QPoint( gx, gy );
|
|
}
|
|
|
|
/*
|
|
Convert agenda grid coordinates to scrollview contents coordinates.
|
|
*/
|
|
QPoint Agenda::gridToContents( const QPoint &gpos ) const
|
|
{
|
|
int x = int( QApplication::isRightToLeft() ?
|
|
( d->mColumns - gpos.x() ) * d->mGridSpacingX : gpos.x() * d->mGridSpacingX );
|
|
int y = int( gpos.y() * d->mGridSpacingY );
|
|
return QPoint( x, y );
|
|
}
|
|
|
|
/*
|
|
Return Y coordinate corresponding to time. Coordinates are rounded to
|
|
fit into the grid.
|
|
*/
|
|
int Agenda::timeToY( const QTime &time ) const
|
|
{
|
|
|
|
int minutesPerCell = 24 * 60 / d->mRows;
|
|
int timeMinutes = time.hour() * 60 + time.minute();
|
|
int Y = ( timeMinutes + ( minutesPerCell / 2 ) ) / minutesPerCell;
|
|
|
|
return Y;
|
|
}
|
|
|
|
/*
|
|
Return time corresponding to cell y coordinate. Coordinates are rounded to
|
|
fit into the grid.
|
|
*/
|
|
QTime Agenda::gyToTime( int gy ) const
|
|
{
|
|
int secondsPerCell = 24 * 60 * 60 / d->mRows;
|
|
int timeSeconds = secondsPerCell * gy;
|
|
|
|
QTime time( 0, 0, 0 );
|
|
if ( timeSeconds < 24 * 60 * 60 ) {
|
|
time = time.addSecs(timeSeconds);
|
|
} else {
|
|
time.setHMS( 23, 59, 59 );
|
|
}
|
|
return time;
|
|
}
|
|
|
|
QVector<int> Agenda::minContentsY() const
|
|
{
|
|
QVector<int> minArray;
|
|
minArray.fill( timeToY( QTime( 23, 59 ) ), d->mSelectedDates.count() );
|
|
foreach ( AgendaItem::QPtr item, d->mItems ) {
|
|
if ( item ) {
|
|
int ymin = item->cellYTop();
|
|
int index = item->cellXLeft();
|
|
if ( index >= 0 && index < (int)( d->mSelectedDates.count() ) ) {
|
|
if ( ymin < minArray[index] && !d->mItemsToDelete.contains( item ) ) {
|
|
minArray[index] = ymin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return minArray;
|
|
}
|
|
|
|
QVector<int> Agenda::maxContentsY() const
|
|
{
|
|
QVector<int> maxArray;
|
|
maxArray.fill( timeToY( QTime( 0, 0 ) ), d->mSelectedDates.count() );
|
|
foreach ( AgendaItem::QPtr item, d->mItems ) {
|
|
if ( item ) {
|
|
int ymax = item->cellYBottom();
|
|
|
|
int index = item->cellXLeft();
|
|
if ( index >= 0 && index < (int)( d->mSelectedDates.count() ) ) {
|
|
if ( ymax > maxArray[index] && !d->mItemsToDelete.contains( item ) ) {
|
|
maxArray[index] = ymax;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return maxArray;
|
|
}
|
|
|
|
void Agenda::setStartTime( const QTime &startHour )
|
|
{
|
|
const double startPos =
|
|
( startHour.hour() / 24. + startHour.minute() / 1440. + startHour.second() / 86400. ) *
|
|
d->mRows * gridSpacingY();
|
|
|
|
verticalScrollBar()->setValue( startPos );
|
|
|
|
}
|
|
|
|
/*
|
|
Insert AgendaItem into agenda.
|
|
*/
|
|
AgendaItem::QPtr Agenda::insertItem( const Akonadi::Item &incidence, const KDateTime &qd,
|
|
int X, int YTop, int YBottom, int itemPos, int itemCount,
|
|
bool isSelected )
|
|
{
|
|
if ( d->mAllDayMode ) {
|
|
kDebug() << "using this in all-day mode is illegal.";
|
|
return 0;
|
|
}
|
|
|
|
d->mActionType = NOP;
|
|
|
|
AgendaItem::QPtr agendaItem = createAgendaItem( incidence, itemPos, itemCount, qd, isSelected );
|
|
if ( !agendaItem ) {
|
|
return AgendaItem::QPtr();
|
|
}
|
|
|
|
if ( YBottom <= YTop ) {
|
|
kDebug() << "Text:" << agendaItem->text() << " YSize<0";
|
|
YBottom = YTop;
|
|
}
|
|
|
|
agendaItem->resize( int( ( X + 1 ) * d->mGridSpacingX ) -
|
|
int( X * d->mGridSpacingX ),
|
|
int( YTop * d->mGridSpacingY ) -
|
|
int( ( YBottom + 1 ) * d->mGridSpacingY ) );
|
|
agendaItem->setCellXY( X, YTop, YBottom );
|
|
agendaItem->setCellXRight( X );
|
|
agendaItem->setResourceColor( EventViews::resourceColor( incidence,
|
|
d->preferences() ) );
|
|
agendaItem->installEventFilter( this );
|
|
|
|
agendaItem->move( int( X * d->mGridSpacingX ), int( YTop * d->mGridSpacingY ) );
|
|
|
|
d->mItems.append( agendaItem );
|
|
|
|
placeSubCells( agendaItem );
|
|
|
|
agendaItem->show();
|
|
|
|
marcus_bains();
|
|
|
|
return agendaItem;
|
|
}
|
|
|
|
/*
|
|
Insert all-day AgendaItem into agenda.
|
|
*/
|
|
AgendaItem::QPtr Agenda::insertAllDayItem( const Akonadi::Item &incidence, const KDateTime &occurrenceDateTime,
|
|
int XBegin, int XEnd, bool isSelected )
|
|
{
|
|
if ( !d->mAllDayMode ) {
|
|
kError() << "using this in non all-day mode is illegal.";
|
|
return 0;
|
|
}
|
|
|
|
d->mActionType = NOP;
|
|
|
|
AgendaItem::QPtr agendaItem = createAgendaItem( incidence, 1, 1, occurrenceDateTime, isSelected );
|
|
if ( !agendaItem ) {
|
|
return AgendaItem::QPtr();
|
|
}
|
|
|
|
agendaItem->setCellXY( XBegin, 0, 0 );
|
|
agendaItem->setCellXRight( XEnd );
|
|
|
|
const double startIt = d->mGridSpacingX * ( agendaItem->cellXLeft() );
|
|
const double endIt = d->mGridSpacingX * ( agendaItem->cellWidth() +
|
|
agendaItem->cellXLeft() );
|
|
|
|
agendaItem->resize( int( endIt ) - int( startIt ), int( d->mGridSpacingY ) );
|
|
|
|
agendaItem->installEventFilter( this );
|
|
agendaItem->setResourceColor( EventViews::resourceColor( incidence,
|
|
d->preferences() ) );
|
|
agendaItem->move( int( XBegin * d->mGridSpacingX ), 0 ) ;
|
|
d->mItems.append( agendaItem );
|
|
|
|
placeSubCells( agendaItem );
|
|
|
|
agendaItem->show();
|
|
|
|
return agendaItem;
|
|
}
|
|
|
|
AgendaItem::QPtr Agenda::createAgendaItem( const Akonadi::Item &item, int itemPos,
|
|
int itemCount, const KDateTime &qd, bool isSelected )
|
|
{
|
|
if ( !item.isValid() ) {
|
|
kWarning() << "Agenda::createAgendaItem() item is invalid.";
|
|
return AgendaItem::QPtr();
|
|
}
|
|
|
|
AgendaItem::QPtr agendaItem = new AgendaItem( d->mAgendaView, d->mCalendar, item,
|
|
itemPos, itemCount, qd, isSelected, this );
|
|
|
|
connect( agendaItem, SIGNAL(removeAgendaItem(AgendaItem::QPtr)), SLOT(removeAgendaItem(AgendaItem::QPtr)) );
|
|
connect( agendaItem, SIGNAL(showAgendaItem(AgendaItem::QPtr)), SLOT(showAgendaItem(AgendaItem::QPtr)) );
|
|
|
|
d->mAgendaItemsById.insert( item.id(), agendaItem );
|
|
|
|
return agendaItem;
|
|
}
|
|
|
|
void Agenda::insertMultiItem( const Akonadi::Item &event, const KDateTime &occurrenceDateTime, int XBegin,
|
|
int XEnd, int YTop, int YBottom, bool isSelected )
|
|
{
|
|
KCalCore::Event::Ptr ev = CalendarSupport::event( event );
|
|
Q_ASSERT( ev );
|
|
if ( d->mAllDayMode ) {
|
|
kDebug() << "using this in all-day mode is illegal.";
|
|
return;
|
|
}
|
|
|
|
d->mActionType = NOP;
|
|
int cellX, cellYTop, cellYBottom;
|
|
QString newtext;
|
|
int width = XEnd - XBegin + 1;
|
|
int count = 0;
|
|
AgendaItem::QPtr current = 0;
|
|
QList<AgendaItem::QPtr> multiItems;
|
|
int visibleCount = d->mSelectedDates.first().daysTo( d->mSelectedDates.last() );
|
|
for ( cellX = XBegin; cellX <= XEnd; ++cellX ) {
|
|
++count;
|
|
//Only add the items that are visible.
|
|
if( cellX >=0 && cellX <= visibleCount ) {
|
|
if ( cellX == XBegin ) {
|
|
cellYTop = YTop;
|
|
} else {
|
|
cellYTop = 0;
|
|
}
|
|
if ( cellX == XEnd ) {
|
|
cellYBottom = YBottom;
|
|
} else {
|
|
cellYBottom = rows() - 1;
|
|
}
|
|
newtext = QString::fromLatin1( "(%1/%2): " ).arg( count ).arg( width );
|
|
newtext.append( ev->summary() );
|
|
|
|
current = insertItem( event, occurrenceDateTime, cellX, cellYTop, cellYBottom, count, width, isSelected );
|
|
Q_ASSERT( current );
|
|
current->setText( newtext );
|
|
multiItems.append( current );
|
|
}
|
|
}
|
|
|
|
QList<AgendaItem::QPtr>::iterator it = multiItems.begin();
|
|
QList<AgendaItem::QPtr>::iterator e = multiItems.end();
|
|
|
|
if ( it != e ) { // .first asserts if the list is empty
|
|
AgendaItem::QPtr first = multiItems.first();
|
|
AgendaItem::QPtr last = multiItems.last();
|
|
AgendaItem::QPtr prev = 0, next = 0;
|
|
|
|
while ( it != e ) {
|
|
AgendaItem::QPtr item = *it;
|
|
++it;
|
|
next = ( it == e ) ? 0 : (*it);
|
|
if ( item ) {
|
|
item->setMultiItem( ( item == first ) ? 0 : first,
|
|
prev, next,
|
|
( item == last ) ? 0 : last );
|
|
}
|
|
prev = item;
|
|
}
|
|
}
|
|
|
|
marcus_bains();
|
|
}
|
|
|
|
void Agenda::removeIncidence( const KCalCore::Incidence::Ptr &incidence )
|
|
{
|
|
if ( !incidence ) {
|
|
kWarning() << "Agenda::removeIncidence() incidence is invalid";
|
|
return;
|
|
}
|
|
|
|
// we get the id from the property, because the item might have been deleted from the etm/mCalendar
|
|
bool ok = false;
|
|
Akonadi::Item::Id id = incidence->customProperty( "VOLATILE", "AKONADI-ID" ).toLongLong( &ok );
|
|
|
|
if ( id == -1 || !ok ) {
|
|
id = d->mCalendar->item( incidence->instanceIdentifier() ).id();
|
|
|
|
if ( id == -1 ) {
|
|
// Ok, we really don't know the ID, give up.
|
|
kWarning() << "Agenda::removeIncidence() Item to remove is invalid. uid = "
|
|
<< incidence->instanceIdentifier();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( d->isQueuedForDeletion( id ) ) {
|
|
return; // It's already queued for deletion
|
|
}
|
|
|
|
AgendaItem::List agendaItems = d->mAgendaItemsById.values( id );
|
|
if ( agendaItems.isEmpty() ) {
|
|
// We're not displaying such item
|
|
// kDebug() << "Ignoring";
|
|
return;
|
|
}
|
|
foreach ( const AgendaItem::QPtr &agendaItem, agendaItems ) {
|
|
if ( agendaItem && !removeAgendaItem( agendaItem ) ) {
|
|
kWarning() << "Agenda::removeIncidence() Failed to remove " << incidence->uid();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Agenda::showAgendaItem( AgendaItem::QPtr agendaItem )
|
|
{
|
|
if ( !agendaItem ) {
|
|
kError() << "Show what?";
|
|
return;
|
|
}
|
|
|
|
agendaItem->hide();
|
|
|
|
agendaItem->setParent( this );
|
|
|
|
if ( !d->mItems.contains( agendaItem ) ) {
|
|
d->mItems.append( agendaItem );
|
|
}
|
|
placeSubCells( agendaItem );
|
|
|
|
agendaItem->show();
|
|
}
|
|
|
|
bool Agenda::removeAgendaItem( AgendaItem::QPtr agendaItem )
|
|
{
|
|
Q_ASSERT( agendaItem );
|
|
// we found the item. Let's remove it and update the conflicts
|
|
bool taken = false;
|
|
QList<AgendaItem::QPtr> conflictItems = agendaItem->conflictItems();
|
|
// removeChild( thisItem );
|
|
|
|
taken = d->mItems.removeAll( agendaItem ) > 0;
|
|
d->mAgendaItemsById.remove( agendaItem->incidence().id(), agendaItem );
|
|
|
|
QList<AgendaItem::QPtr>::iterator it;
|
|
for ( it = conflictItems.begin(); it != conflictItems.end(); ++it ) {
|
|
if ( *it ) {
|
|
(*it)->setSubCells( ( *it )->subCells()-1 );
|
|
}
|
|
}
|
|
|
|
for ( it = conflictItems.begin(); it != conflictItems.end(); ++it ) {
|
|
// the item itself is also in its own conflictItems list!
|
|
if ( *it && *it != agendaItem ) {
|
|
placeSubCells( *it );
|
|
}
|
|
}
|
|
d->mItemsToDelete.append( agendaItem );
|
|
d->mItemsQueuedForDeletion.insert( agendaItem->incidence().id() );
|
|
agendaItem->setVisible( false );
|
|
QTimer::singleShot( 0, this, SLOT(deleteItemsToDelete()) );
|
|
return taken;
|
|
}
|
|
|
|
void Agenda::deleteItemsToDelete()
|
|
{
|
|
qDeleteAll( d->mItemsToDelete );
|
|
d->mItemsToDelete.clear();
|
|
d->mItemsQueuedForDeletion.clear();
|
|
}
|
|
|
|
/*QSizePolicy Agenda::sizePolicy() const
|
|
{
|
|
// Thought this would make the all-day event agenda minimum size and the
|
|
// normal agenda take the remaining space. But it doesn't work. The QSplitter
|
|
// don't seem to think that an Expanding widget needs more space than a
|
|
// Preferred one.
|
|
// But it doesn't hurt, so it stays.
|
|
if (mAllDayMode) {
|
|
return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
|
|
} else {
|
|
return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
|
|
}
|
|
}*/
|
|
|
|
/*
|
|
Overridden from QScrollView to provide proper resizing of AgendaItems.
|
|
*/
|
|
void Agenda::resizeEvent ( QResizeEvent *ev )
|
|
{
|
|
QSize newSize( ev->size() );
|
|
|
|
if ( d->mAllDayMode ) {
|
|
d->mGridSpacingX = static_cast<double>( newSize.width() ) / d->mColumns;
|
|
d->mGridSpacingY = newSize.height();
|
|
} else {
|
|
d->mGridSpacingX = static_cast<double>( newSize.width() ) / d->mColumns;
|
|
// make sure that there are not more than 24 per day
|
|
d->mGridSpacingY = static_cast<double>( newSize.height() ) / d->mRows;
|
|
if ( d->mGridSpacingY < d->mDesiredGridSpacingY ) {
|
|
d->mGridSpacingY = d->mDesiredGridSpacingY;
|
|
}
|
|
}
|
|
calculateWorkingHours();
|
|
|
|
QTimer::singleShot( 0, this, SLOT(resizeAllContents()) );
|
|
emit gridSpacingYChanged( d->mGridSpacingY * 4 );
|
|
|
|
QWidget::resizeEvent( ev );
|
|
updateGeometry();
|
|
}
|
|
|
|
void Agenda::resizeAllContents()
|
|
{
|
|
double subCellWidth;
|
|
if ( d->mAllDayMode ) {
|
|
foreach ( const AgendaItem::QPtr &item, d->mItems ) {
|
|
if ( item ) {
|
|
subCellWidth = calcSubCellWidth( item );
|
|
placeAgendaItem( item, subCellWidth );
|
|
}
|
|
}
|
|
} else {
|
|
foreach ( const AgendaItem::QPtr &item, d->mItems ) {
|
|
if ( item ) {
|
|
subCellWidth = calcSubCellWidth( item );
|
|
placeAgendaItem( item, subCellWidth );
|
|
}
|
|
}
|
|
}
|
|
checkScrollBoundaries();
|
|
marcus_bains();
|
|
update();
|
|
}
|
|
|
|
void Agenda::scrollUp()
|
|
{
|
|
int currentValue = verticalScrollBar()->value();
|
|
verticalScrollBar()->setValue( currentValue - d->mScrollOffset );
|
|
}
|
|
|
|
void Agenda::scrollDown()
|
|
{
|
|
int currentValue = verticalScrollBar()->value();
|
|
verticalScrollBar()->setValue( currentValue + d->mScrollOffset );
|
|
}
|
|
|
|
QSize Agenda::minimumSize() const
|
|
{
|
|
return sizeHint();
|
|
}
|
|
|
|
QSize Agenda::minimumSizeHint() const
|
|
{
|
|
return sizeHint();
|
|
}
|
|
|
|
int Agenda::minimumHeight() const
|
|
{
|
|
// all day agenda never has scrollbars and the scrollarea will
|
|
// resize it to fit exactly on the viewport.
|
|
|
|
if ( d->mAllDayMode ) {
|
|
return 0;
|
|
} else {
|
|
return d->mGridSpacingY * d->mRows;
|
|
}
|
|
}
|
|
|
|
void Agenda::updateConfig()
|
|
{
|
|
const double oldGridSpacingY = d->mGridSpacingY;
|
|
|
|
if ( !d->mAllDayMode ) {
|
|
d->mDesiredGridSpacingY = d->preferences()->hourSize();
|
|
if ( d->mDesiredGridSpacingY < 4 || d->mDesiredGridSpacingY > 30 ) {
|
|
d->mDesiredGridSpacingY = 10;
|
|
}
|
|
|
|
/*
|
|
// make sure that there are not more than 24 per day
|
|
d->mGridSpacingY = static_cast<double>( height() ) / d->mRows;
|
|
if ( d->mGridSpacingY < d->mDesiredGridSpacingY || true) {
|
|
d->mGridSpacingY = d->mDesiredGridSpacingY;
|
|
}
|
|
*/
|
|
|
|
//can be two doubles equal?, it's better to compare them with an epsilon
|
|
if ( fabs( oldGridSpacingY - d->mDesiredGridSpacingY ) > 0.1 ) {
|
|
d->mGridSpacingY = d->mDesiredGridSpacingY;
|
|
updateGeometry();
|
|
}
|
|
}
|
|
|
|
calculateWorkingHours();
|
|
|
|
marcus_bains();
|
|
}
|
|
|
|
void Agenda::checkScrollBoundaries()
|
|
{
|
|
// Invalidate old values to force update
|
|
d->mOldLowerScrollValue = -1;
|
|
d->mOldUpperScrollValue = -1;
|
|
|
|
checkScrollBoundaries( verticalScrollBar()->value() );
|
|
}
|
|
|
|
void Agenda::checkScrollBoundaries( int v )
|
|
{
|
|
int yMin = int( (v) / d->mGridSpacingY );
|
|
int yMax = int( ( v + d->mScrollArea->height() ) / d->mGridSpacingY );
|
|
|
|
if ( yMin != d->mOldLowerScrollValue ) {
|
|
d->mOldLowerScrollValue = yMin;
|
|
emit lowerYChanged( yMin );
|
|
}
|
|
if ( yMax != d->mOldUpperScrollValue ) {
|
|
d->mOldUpperScrollValue = yMax;
|
|
emit upperYChanged( yMax );
|
|
}
|
|
}
|
|
|
|
int Agenda::visibleContentsYMin() const
|
|
{
|
|
int v = verticalScrollBar()->value();
|
|
return int( v / d->mGridSpacingY );
|
|
}
|
|
|
|
int Agenda::visibleContentsYMax() const
|
|
{
|
|
int v = verticalScrollBar()->value();
|
|
return int( ( v + d->mScrollArea->height() ) / d->mGridSpacingY );
|
|
}
|
|
|
|
void Agenda::deselectItem()
|
|
{
|
|
if ( d->mSelectedItem.isNull() ) {
|
|
return;
|
|
}
|
|
|
|
const Akonadi::Item selectedItem = d->mSelectedItem->incidence();
|
|
|
|
foreach ( AgendaItem::QPtr item, d->mItems ) {
|
|
if ( item ) {
|
|
const Akonadi::Item itemInc = item->incidence();
|
|
if( itemInc.isValid() && selectedItem.isValid() && itemInc.id() == selectedItem.id() ) {
|
|
item->select( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
d->mSelectedItem = 0;
|
|
}
|
|
|
|
void Agenda::selectItem( AgendaItem::QPtr item )
|
|
{
|
|
if ( ( AgendaItem::QPtr )d->mSelectedItem == item ) {
|
|
return;
|
|
}
|
|
deselectItem();
|
|
if ( item == 0 ) {
|
|
emit incidenceSelected( Akonadi::Item(), QDate() );
|
|
return;
|
|
}
|
|
d->mSelectedItem = item;
|
|
d->mSelectedItem->select();
|
|
Q_ASSERT( CalendarSupport::hasIncidence( d->mSelectedItem->incidence() ) );
|
|
d->mSelectedId = d->mSelectedItem->incidence().id();
|
|
|
|
foreach ( AgendaItem::QPtr item, d->mItems ) {
|
|
if( item && item->incidence().id() == d->mSelectedId ) {
|
|
item->select();
|
|
}
|
|
}
|
|
emit incidenceSelected( d->mSelectedItem->incidence(), d->mSelectedItem->occurrenceDate() );
|
|
}
|
|
|
|
void Agenda::selectItemByItemId( const Akonadi::Item::Id &id )
|
|
{
|
|
foreach ( AgendaItem::QPtr item, d->mItems ) {
|
|
if ( item && item->incidence().id() == id ) {
|
|
selectItem( item );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Agenda::selectItem( const Akonadi::Item &item )
|
|
{
|
|
selectItemByItemId( item.id() );
|
|
}
|
|
|
|
// This function seems never be called.
|
|
void Agenda::keyPressEvent( QKeyEvent *kev )
|
|
{
|
|
switch( kev->key() ) {
|
|
case Qt::Key_PageDown:
|
|
verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepAdd );
|
|
break;
|
|
case Qt::Key_PageUp:
|
|
verticalScrollBar()->triggerAction( QAbstractSlider::SliderPageStepSub );
|
|
break;
|
|
case Qt::Key_Down:
|
|
verticalScrollBar()->triggerAction( QAbstractSlider::SliderSingleStepAdd );
|
|
break;
|
|
case Qt::Key_Up:
|
|
verticalScrollBar()->triggerAction( QAbstractSlider::SliderSingleStepSub );
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
void Agenda::calculateWorkingHours()
|
|
{
|
|
d->mWorkingHoursEnable = !d->mAllDayMode;
|
|
|
|
QTime tmp = d->preferences()->workingHoursStart().time();
|
|
d->mWorkingHoursYTop = int( 4 * d->mGridSpacingY *
|
|
( tmp.hour() + tmp.minute() / 60. +
|
|
tmp.second() / 3600. ) );
|
|
tmp = d->preferences()->workingHoursEnd().time();
|
|
d->mWorkingHoursYBottom = int( 4 * d->mGridSpacingY *
|
|
( tmp.hour() + tmp.minute() / 60. +
|
|
tmp.second() / 3600. ) - 1 );
|
|
}
|
|
|
|
void Agenda::setDateList( const KCalCore::DateList &selectedDates )
|
|
{
|
|
d->mSelectedDates = selectedDates;
|
|
marcus_bains();
|
|
}
|
|
|
|
KCalCore::DateList Agenda::dateList() const
|
|
{
|
|
return d->mSelectedDates;
|
|
}
|
|
|
|
void Agenda::setCalendar( const Akonadi::ETMCalendar::Ptr &cal )
|
|
{
|
|
d->mCalendar = cal;
|
|
}
|
|
|
|
void Agenda::setIncidenceChanger( Akonadi::IncidenceChanger *changer )
|
|
{
|
|
d->mChanger = changer;
|
|
}
|
|
|
|
void Agenda::setHolidayMask( QVector<bool> *mask )
|
|
{
|
|
d->mHolidayMask = mask;
|
|
}
|
|
|
|
void Agenda::contentsMousePressEvent ( QMouseEvent *event )
|
|
{
|
|
Q_UNUSED( event );
|
|
}
|
|
|
|
QSize Agenda::sizeHint() const
|
|
{
|
|
if ( d->mAllDayMode ) {
|
|
return QWidget::sizeHint();
|
|
} else {
|
|
return QSize( parentWidget()->width(), d->mGridSpacingY * d->mRows );
|
|
}
|
|
}
|
|
|
|
QScrollBar * Agenda::verticalScrollBar() const
|
|
{
|
|
return d->mScrollArea->verticalScrollBar();
|
|
}
|
|
|
|
QScrollArea *Agenda::scrollArea() const
|
|
{
|
|
return d->mScrollArea;
|
|
}
|
|
|
|
AgendaItem::List Agenda::agendaItems( Akonadi::Item::Id id ) const
|
|
{
|
|
return d->mAgendaItemsById.values( id );
|
|
}
|
|
|
|
AgendaScrollArea::AgendaScrollArea( bool isAllDay, AgendaView *agendaView,
|
|
bool isInteractive, QWidget *parent )
|
|
: QScrollArea( parent )
|
|
{
|
|
if ( isAllDay ) {
|
|
mAgenda = new Agenda( agendaView, this, 1, isInteractive );
|
|
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
|
} else {
|
|
mAgenda = new Agenda( agendaView, this, 1, 96,
|
|
agendaView->preferences()->hourSize(), isInteractive );
|
|
}
|
|
|
|
#ifdef KDEPIM_MOBILE_UI
|
|
setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
|
#endif
|
|
|
|
setWidgetResizable( true );
|
|
setWidget( mAgenda );
|
|
setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
|
|
|
mAgenda->setStartTime( agendaView->preferences()->dayBegins().time() );
|
|
}
|
|
|
|
AgendaScrollArea::~AgendaScrollArea()
|
|
{
|
|
}
|
|
|
|
Agenda *AgendaScrollArea::agenda() const
|
|
{
|
|
return mAgenda;
|
|
}
|
|
|
|
|
|
// kate: space-indent on; indent-width 2; replace-tabs on;
|