kde-playground/kdepim/calendarviews/month/monthscene.cpp

789 lines
22 KiB
C++
Raw Normal View History

/*
Copyright (c) 2008 Bruno Virlet <bruno.virlet@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
As a special exception, permission is given to link this program
with any edition of Qt, and distribute the resulting executable,
without including the source code for Qt in the source distribution.
*/
#include "monthscene.h"
#include "monthgraphicsitems.h"
#include "monthitem.h"
#include "monthview.h"
#include "prefs.h"
#include <calendarsupport/utils.h>
#include <KCalendarSystem>
#include <KIconLoader>
#include <QGraphicsSceneMouseEvent>
#include <QResizeEvent>
static const int AUTO_REPEAT_DELAY = 600;
using namespace EventViews;
MonthScene::MonthScene( MonthView *parent )
: QGraphicsScene( parent ),
mMonthView( parent ),
mInitialized( false ),
mClickedItem( 0 ),
mActionItem( 0 ),
mActionInitiated( false ),
mSelectedItem( 0 ),
mStartCell( 0 ),
mPreviousCell( 0 ),
mActionType( None ),
mStartHeight( 0 ),
mCurrentIndicator( 0 )
{
mBirthdayPixmap = SmallIcon( QLatin1String("view-calendar-birthday") );
mAnniversaryPixmap = SmallIcon( QLatin1String("view-calendar-wedding-anniversary") );
mAlarmPixmap = SmallIcon( QLatin1String("appointment-reminder" ));
mRecurPixmap = SmallIcon( QLatin1String("appointment-recurring") );
mReadonlyPixmap = SmallIcon( QLatin1String("object-locked") );
mReplyPixmap = SmallIcon( QLatin1String("mail-reply-sender") );
mHolidayPixmap = SmallIcon( QLatin1String("view-calendar-holiday") );
setSceneRect( 0, 0, parent->width(), parent->height() );
}
MonthScene::~MonthScene()
{
qDeleteAll( mMonthCellMap );
qDeleteAll( mManagerList );
}
MonthCell *MonthScene::selectedCell() const
{
return mMonthCellMap.value( mSelectedCellDate );
}
MonthCell *MonthScene::previousCell() const
{
return mPreviousCell;
}
int MonthScene::getRightSpan( const QDate &date ) const
{
MonthCell *cell = mMonthCellMap.value( date );
if ( !cell ) {
return 0;
}
return 7 - cell->x() - 1;
}
int MonthScene::getLeftSpan( const QDate &date ) const
{
MonthCell *cell = mMonthCellMap.value( date );
if ( !cell ) {
return 0;
}
return cell->x();
}
int MonthScene::maxRowCount()
{
return ( rowHeight() - MonthCell::topMargin() ) / itemHeightIncludingSpacing();
}
int MonthScene::itemHeightIncludingSpacing()
{
return MonthCell::topMargin() + 2;
}
int MonthScene::itemHeight()
{
return MonthCell::topMargin();
}
MonthCell *MonthScene::firstCellForMonthItem( MonthItem *manager )
{
for ( QDate d = manager->startDate(); d <= manager->endDate(); d = d.addDays( 1 ) ) {
MonthCell *monthCell = mMonthCellMap.value( d );
if ( monthCell ) {
return monthCell;
}
}
return 0;
}
void MonthScene::updateGeometry()
{
foreach ( MonthItem *manager, mManagerList ) {
manager->updateGeometry();
}
}
int MonthScene::availableWidth() const
{
return static_cast<int> ( sceneRect().width() );
}
int MonthScene::availableHeight() const
{
return static_cast<int> ( sceneRect().height() - headerHeight() );
}
int MonthScene::columnWidth() const
{
return static_cast<int> ( ( availableWidth() - 1 ) / 7. );
}
int MonthScene::rowHeight() const
{
return static_cast<int> ( ( availableHeight() - 1 ) / 6. );
}
int MonthScene::headerHeight() const
{
return 50;
}
int MonthScene::cellVerticalPos( const MonthCell *cell ) const
{
return headerHeight() + cell->y() * rowHeight();
}
int MonthScene::cellHorizontalPos( const MonthCell *cell ) const
{
return cell->x() * columnWidth();
}
int MonthScene::sceneYToMonthGridY( int yScene )
{
return yScene - headerHeight();
}
int MonthScene::sceneXToMonthGridX( int xScene )
{
return xScene;
}
void MonthGraphicsView::drawBackground( QPainter *p, const QRectF & rect )
{
Q_ASSERT( mScene );
PrefsPtr prefs = mScene->monthView()->preferences();
p->setFont( prefs->monthViewFont() );
p->fillRect( rect, palette().color( QPalette::Base ) );
/*
Headers
*/
QFont font = prefs->monthViewFont();
font.setBold( true );
font.setPointSize( 15 );
p->setFont( font );
const int dayLabelsHeight = 20;
const KCalendarSystem *calSys = KGlobal::locale()->calendar();
p->drawText( QRect( 0, 0, // top right
static_cast<int> ( mScene->sceneRect().width() ),
static_cast<int> ( mScene->headerHeight() - dayLabelsHeight ) ),
Qt::AlignCenter,
i18nc( "monthname year", "%1 %2",
calSys->monthName( mMonthView->averageDate() ),
calSys->formatDate( mMonthView->averageDate(),
KLocale::Year, KLocale::LongNumber ) ) );
font.setPixelSize( dayLabelsHeight - 10 );
p->setFont( font );
const QDate start = mMonthView->actualStartDateTime().date();
const QDate end = mMonthView->actualEndDateTime().date();
for ( QDate d = start;
d <= start.addDays( 6 ); d = d.addDays( 1 ) ) {
MonthCell *cell = mScene->mMonthCellMap.value( d );
if ( !cell ) {
// This means drawBackground() is being called before reloadIncidences(). Can happen with some
// themes. Bug #190191
return;
}
p->drawText( QRect( mScene->cellHorizontalPos( cell ),
mScene->cellVerticalPos( cell ) - 15,
mScene->columnWidth(),
15 ),
Qt::AlignCenter,
calSys->weekDayName( d, KCalendarSystem::LongDayName ) );
}
/*
Month grid
*/
int columnWidth = mScene->columnWidth();
int rowHeight = mScene->rowHeight();
const QList<QDate> workDays = CalendarSupport::workDays( mMonthView->actualStartDateTime().date(),
mMonthView->actualEndDateTime().date() );
for ( QDate d = start; d <= end; d = d.addDays( 1 ) ) {
if ( !mScene->mMonthCellMap.contains( d ) ) {
// This means drawBackground() is being called before reloadIncidences(). Can happen with some
// themes. Bug #190191
return;
}
MonthCell *cell = mScene->mMonthCellMap[ d ];
QColor color;
if ( workDays.contains( d ) ) {
color = mMonthView->preferences()->monthGridWorkHoursBackgroundColor();
} else {
color = mMonthView->preferences()->monthGridBackgroundColor();
}
if ( cell == mScene->selectedCell() ) {
color = color.dark( 115 );
}
if ( cell->date() == QDate::currentDate() ) {
color = color.dark( 140 );
}
// Draw cell
p->setPen( mMonthView->preferences()->monthGridBackgroundColor().dark( 150 ) );
p->setBrush( color );
p->drawRect( QRect( mScene->cellHorizontalPos( cell ), mScene->cellVerticalPos( cell ),
columnWidth, rowHeight ) );
if ( mMonthView->isBusyDay( d ) ) {
QColor busyColor = mMonthView->preferences()->viewBgBusyColor();
busyColor.setAlpha( EventViews::BUSY_BACKGROUND_ALPHA );
p->setBrush( busyColor );
p->drawRect( QRect( mScene->cellHorizontalPos( cell ), mScene->cellVerticalPos( cell ),
columnWidth, rowHeight ) );
}
// Draw cell header
int cellHeaderX = mScene->cellHorizontalPos( cell ) + 1;
int cellHeaderY = mScene->cellVerticalPos( cell ) + 1;
int cellHeaderWidth = columnWidth - 2;
int cellHeaderHeight = cell->topMargin() - 2;
QLinearGradient bgGradient( QPointF( cellHeaderX, cellHeaderY ),
QPointF( cellHeaderX + cellHeaderWidth,
cellHeaderY + cellHeaderHeight ) );
bgGradient.setColorAt( 0, color.dark( 105 ) );
bgGradient.setColorAt( 0.7, color.dark( 105 ) );
bgGradient.setColorAt( 1, color );
p->setBrush( bgGradient );
p->setPen( Qt::NoPen );
p->drawRect( QRect( cellHeaderX, cellHeaderY,
cellHeaderWidth, cellHeaderHeight ) );
}
font = mMonthView->preferences()->monthViewFont();
font.setPixelSize( MonthCell::topMargin() - 4 );
p->setFont( font );
QPen oldPen = mMonthView->preferences()->monthGridBackgroundColor().dark( 150 );
// Draw dates
for ( QDate d = mMonthView->actualStartDateTime().date();
d <= mMonthView->actualEndDateTime().date(); d = d.addDays( 1 ) ) {
MonthCell *cell = mScene->mMonthCellMap.value( d );
QFont font = p->font();
if ( cell->date() == QDate::currentDate() ) {
font.setBold( true );
} else {
font.setBold( false );
}
p->setFont( font );
if ( d.month() == mMonthView->currentMonth() ) {
p->setPen( QPalette::Text );
} else {
p->setPen( oldPen );
}
/*
Draw arrows if all items won't fit
*/
// Up arrow if first item is above cell top
if ( mScene->startHeight() != 0 && cell->hasEventBelow( mScene->startHeight() ) ) {
cell->upArrow()->setPos(
mScene->cellHorizontalPos( cell ) + columnWidth / 2,
mScene->cellVerticalPos( cell ) + cell->upArrow()->boundingRect().height() / 2 + 2 );
cell->upArrow()->show();
} else {
cell->upArrow()->hide();
}
// Down arrow if last item is below cell bottom
if ( !mScene->lastItemFit( cell ) ) {
cell->downArrow()->setPos(
mScene->cellHorizontalPos( cell ) + columnWidth / 2,
mScene->cellVerticalPos( cell ) + rowHeight -
cell->downArrow()->boundingRect().height() / 2 - 2 );
cell->downArrow()->show();
} else {
cell->downArrow()->hide();
}
const KCalendarSystem *calSys = KGlobal::locale()->calendar();
QString dayText;
// Prepend month name if d is the first or last day of month
if ( calSys->day( d ) == 1 || // d is the first day of month
calSys->day( d.addDays( 1 ) ) == 1 ) { // d is the last day of month
dayText = i18nc( "'Month day' for month view cells", "%1 %2",
calSys->monthName( d, KCalendarSystem::ShortName ),
calSys->day( d ) );
} else {
dayText = QString::number( calSys->day( d ) );
}
p->drawText( QRect( mScene->cellHorizontalPos( cell ), // top right
mScene->cellVerticalPos( cell ), // of the cell
mScene->columnWidth() - 2,
cell->topMargin() ),
Qt::AlignRight,
dayText );
}
// ...
}
void MonthScene::resetAll()
{
qDeleteAll( mMonthCellMap );
mMonthCellMap.clear();
qDeleteAll( mManagerList );
mManagerList.clear();
mSelectedItem = 0;
mActionItem = 0;
mClickedItem = 0;
}
Akonadi::IncidenceChanger *MonthScene::incidenceChanger() const
{
return mMonthView->changer();
}
QDate MonthScene::firstDateOnRow( int row ) const
{
return mMonthView->actualStartDateTime().date().addDays( 7 * row );
}
bool MonthScene::lastItemFit( MonthCell *cell )
{
if ( cell->firstFreeSpace() > maxRowCount() + startHeight() ) {
return false;
} else {
return true;
}
}
int MonthScene::totalHeight()
{
int max = 0;
for ( QDate d = mMonthView->actualStartDateTime().date();
d <= mMonthView->actualEndDateTime().date(); d = d.addDays( 1 ) ) {
int c = mMonthCellMap[ d ]->firstFreeSpace();
if ( c > max ) {
max = c;
}
}
return max;
}
void MonthScene::wheelEvent( QGraphicsSceneWheelEvent *event )
{
Q_UNUSED( event ); // until we figure out what to do in here
/* int numDegrees = -event->delta() / 8;
int numSteps = numDegrees / 15;
if ( startHeight() + numSteps < 0 ) {
numSteps = -startHeight();
}
int cellHeight = 0;
MonthCell *currentCell = getCellFromPos( event->scenePos() );
if ( currentCell ) {
cellHeight = currentCell->firstFreeSpace();
}
if ( cellHeight == 0 ) {
// no items in this cell, there's no point to scroll
return;
}
int newHeight;
int maxStartHeight = qMax( 0, cellHeight - maxRowCount() );
if ( numSteps > 0 && startHeight() + numSteps >= maxStartHeight ) {
newHeight = maxStartHeight;
} else {
newHeight = startHeight() + numSteps;
}
if ( newHeight == startHeight() ) {
return;
}
setStartHeight( newHeight );
foreach ( MonthItem *manager, mManagerList ) {
manager->updateGeometry();
}
invalidate( QRectF(), BackgroundLayer );
event->accept();
*/
}
void MonthScene::scrollCellsDown()
{
int newHeight = startHeight() + 1;
setStartHeight( newHeight );
foreach ( MonthItem *manager, mManagerList ) {
manager->updateGeometry();
}
invalidate( QRectF(), BackgroundLayer );
}
void MonthScene::scrollCellsUp()
{
int newHeight = startHeight() - 1;
setStartHeight( newHeight );
foreach ( MonthItem *manager, mManagerList ) {
manager->updateGeometry();
}
invalidate( QRectF(), BackgroundLayer );
}
void MonthScene::clickOnScrollIndicator( ScrollIndicator *scrollItem )
{
if ( scrollItem->direction() == ScrollIndicator::UpArrow ) {
scrollCellsUp();
} else if ( scrollItem->direction() == ScrollIndicator::DownArrow ) {
scrollCellsDown();
}
}
void MonthScene::mouseDoubleClickEvent ( QGraphicsSceneMouseEvent *mouseEvent )
{
QPointF pos = mouseEvent->scenePos();
repeatTimer.stop();
MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem*>( itemAt( pos ) );
if ( iItem ) {
if ( iItem->monthItem() ) {
IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>( iItem->monthItem() );
if ( tmp ) {
selectItem( iItem->monthItem() );
mMonthView->defaultAction( tmp->akonadiItem() );
mouseEvent->accept();
}
}
} else {
emit newEventSignal();
}
}
void MonthScene::mouseMoveEvent ( QGraphicsSceneMouseEvent *mouseEvent )
{
QPointF pos = mouseEvent->scenePos();
MonthGraphicsView *view = static_cast<MonthGraphicsView*>( views().first() );
// Change cursor depending on the part of the item it hovers to inform
// the user that he can resize the item.
if ( mActionType == None ) {
MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem*>( itemAt( pos ) );
if ( iItem ) {
if ( iItem->monthItem()->isResizable() &&
iItem->isBeginItem() && iItem->mapFromScene( pos ).x() <= 10 ) {
view->setActionCursor( Resize );
} else if ( iItem->monthItem()->isResizable() &&
iItem->isEndItem() &&
iItem->mapFromScene( pos ).x() >= iItem->boundingRect().width() - 10 ) {
view->setActionCursor( Resize );
} else {
view->setActionCursor( None );
}
} else {
view->setActionCursor( None );
}
mouseEvent->accept();
return;
}
// If an item was selected during the click, we maybe have an item to move !
if ( mActionItem ) {
MonthCell *currentCell = getCellFromPos( pos );
// Initiate action if not already done
if ( !mActionInitiated && mActionType != None ) {
if ( mActionType == Move ) {
mActionItem->beginMove();
} else if ( mActionType == Resize ) {
mActionItem->beginResize();
}
mActionInitiated = true;
}
view->setActionCursor( mActionType );
// Move or resize action
if ( currentCell && currentCell != mPreviousCell ) {
bool ok = true;
if ( mActionType == Move ) {
mActionItem->moveBy( mPreviousCell->date().daysTo( currentCell->date() ) );
} else if ( mActionType == Resize ) {
ok = mActionItem->resizeBy( mPreviousCell->date().daysTo( currentCell->date() ) );
}
if ( ok ) {
mPreviousCell = currentCell;
}
mActionItem->updateGeometry();
update();
}
mouseEvent->accept();
}
}
void MonthScene::mousePressEvent ( QGraphicsSceneMouseEvent *mouseEvent )
{
QPointF pos = mouseEvent->scenePos();
mClickedItem = 0;
mCurrentIndicator = 0;
MonthGraphicsItem *iItem = dynamic_cast<MonthGraphicsItem*>( itemAt( pos ) );
if ( iItem ) {
mClickedItem = iItem->monthItem();
selectItem( mClickedItem );
if ( mouseEvent->button() == Qt::RightButton ) {
IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem *>( mClickedItem );
if ( tmp ) {
emit showIncidencePopupSignal( tmp->akonadiItem(), tmp->realStartDate() );
}
}
if ( mouseEvent->button() == Qt::LeftButton ) {
// Basic initialization for resize and move
mActionItem = mClickedItem;
mStartCell = getCellFromPos( pos );
mPreviousCell = mStartCell;
mActionInitiated = false;
// Move or resize ?
if ( iItem->monthItem()->isResizable() &&
iItem->isBeginItem() && iItem->mapFromScene( pos ).x() <= 10 ) {
mActionType = Resize;
mResizeType = ResizeLeft;
} else if ( iItem->monthItem()->isResizable() &&
iItem->isEndItem() &&
iItem->mapFromScene( pos ).x() >= iItem->boundingRect().width() - 10 ) {
mActionType = Resize;
mResizeType = ResizeRight;
} else if ( iItem->monthItem()->isMoveable() ) {
mActionType = Move;
}
}
mouseEvent->accept();
} else if ( ScrollIndicator *scrollItem = dynamic_cast<ScrollIndicator*>( itemAt( pos ) ) ) {
clickOnScrollIndicator( scrollItem );
mCurrentIndicator = scrollItem;
repeatTimer.start( AUTO_REPEAT_DELAY, this );
} else {
// unselect items when clicking somewhere else
selectItem( 0 );
MonthCell *cell = getCellFromPos( pos );
if ( cell ) {
mSelectedCellDate = cell->date();
update();
if ( mouseEvent->button() == Qt::RightButton ) {
emit showNewEventPopupSignal();
}
mouseEvent->accept();
}
}
}
void MonthScene::timerEvent( QTimerEvent *e )
{
if ( e->timerId() == repeatTimer.timerId() ) {
if ( mCurrentIndicator->isVisible() ) {
clickOnScrollIndicator( mCurrentIndicator );
repeatTimer.start( AUTO_REPEAT_DELAY, this );
} else {
mCurrentIndicator = 0;
repeatTimer.stop();
}
}
}
void MonthScene::mouseReleaseEvent ( QGraphicsSceneMouseEvent *mouseEvent )
{
QPointF pos = mouseEvent->scenePos();
static_cast<MonthGraphicsView*>( views().first() )->setActionCursor( None );
repeatTimer.stop();
mCurrentIndicator = 0;
if ( mActionItem ) {
MonthCell *currentCell = getCellFromPos( pos );
const bool somethingChanged = currentCell && currentCell != mStartCell;
if ( somethingChanged ) { // We want to act if a move really happened
if ( mActionType == Resize ) {
mActionItem->endResize();
} else if ( mActionType == Move ) {
mActionItem->endMove();
}
}
mActionItem = 0;
mActionType = None;
mStartCell = 0;
mouseEvent->accept();
}
}
// returns true if the point is in the monthgrid (allows to avoid selecting a cell when
// a click is outside the month grid
bool MonthScene::isInMonthGrid( int x, int y ) const
{
return x >= 0 && y >= 0 && x <= availableWidth() && y <= availableHeight();
}
// The function converts the coordinates to the month grid coordinates to
// be able to locate the cell.
MonthCell *MonthScene::getCellFromPos( const QPointF &pos )
{
int y = sceneYToMonthGridY( static_cast<int> ( pos.y() ) );
int x = sceneXToMonthGridX( static_cast<int> ( pos.x() ) );
if ( !isInMonthGrid( x, y ) ) {
return 0;
}
int id = ( int )( y / rowHeight() ) * 7 + ( int )( x / columnWidth() );
return mMonthCellMap.value( mMonthView->actualStartDateTime().date().addDays( id ) );
}
void MonthScene::selectItem( MonthItem *item )
{
/*
if ( mSelectedItem == item ) {
return;
}
I commented the above code so it's possible to selected a selected item.
korg-mobile needs that, otherwise clicking on a selected item wont bring the editor up.
Another solution would be to have two signals: incidenceSelected() and incidenceClicked()
*/
IncidenceMonthItem *tmp = qobject_cast<IncidenceMonthItem*>( item );
if ( !tmp ) {
mSelectedItem = 0;
emit incidenceSelected( Akonadi::Item(), QDate() );
return;
}
mSelectedItem = item;
Q_ASSERT( CalendarSupport::hasIncidence( tmp->akonadiItem() ) );
if ( mMonthView->selectedIncidenceDates().isEmpty() ) {
emit incidenceSelected( tmp->akonadiItem(), QDate() );
} else {
emit incidenceSelected( tmp->akonadiItem(), mMonthView->selectedIncidenceDates().first() );
}
update();
}
void MonthScene::removeIncidence( const QString &uid )
{
foreach ( MonthItem *manager, mManagerList ) {
IncidenceMonthItem *imi = qobject_cast<IncidenceMonthItem*>( manager );
if ( !imi )
continue;
KCalCore::Incidence::Ptr incidence = imi->incidence();
if ( !incidence )
continue;
if ( incidence->uid() == uid ) {
foreach ( MonthGraphicsItem *gitem, imi->monthGraphicsItems() ) {
removeItem( gitem );
}
}
}
}
//----------------------------------------------------------
MonthGraphicsView::MonthGraphicsView( MonthView *parent )
: QGraphicsView( parent ), mMonthView( parent )
{
setMouseTracking( true );
}
void MonthGraphicsView::setActionCursor( MonthScene::ActionType actionType )
{
switch ( actionType ) {
case MonthScene::Move:
#ifndef QT_NO_CURSOR
setCursor( Qt::ArrowCursor );
#endif
break;
case MonthScene::Resize:
#ifndef QT_NO_CURSOR
setCursor( Qt::SizeHorCursor );
#endif
break;
#ifndef QT_NO_CURSOR
default:
setCursor( Qt::ArrowCursor );
#endif
}
}
void MonthGraphicsView::setScene( MonthScene *scene )
{
mScene = scene;
QGraphicsView::setScene( scene );
}
void MonthGraphicsView::resizeEvent( QResizeEvent *event )
{
mScene->setSceneRect( 0, 0, event->size().width(), event->size().height() );
mScene->updateGeometry();
}