mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 02:42:51 +00:00
506 lines
15 KiB
C++
506 lines
15 KiB
C++
/*
|
|
Copyright (c) 2007 Till Adam <adam@kde.org>
|
|
Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
|
|
Copyright (c) 2010 Andras Mantia <andras@kdab.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 "timelineview.h"
|
|
#include "timelineview_p.h"
|
|
#include "timelineitem.h"
|
|
#include "helper.h"
|
|
|
|
#include <kdgantt2/kdganttgraphicsitem.h>
|
|
#include <kdgantt2/kdganttgraphicsview.h>
|
|
#include <kdgantt2/kdganttabstractrowcontroller.h>
|
|
#include <kdgantt2/kdganttdatetimegrid.h>
|
|
#include <kdgantt2/kdganttitemdelegate.h>
|
|
#include <kdgantt2/kdganttstyleoptionganttitem.h>
|
|
|
|
#include <Akonadi/Calendar/ETMCalendar>
|
|
#include <calendarsupport/collectionselection.h>
|
|
#include <calendarsupport/utils.h>
|
|
#include <calendarsupport/kcalprefs.h>
|
|
#include <Akonadi/Calendar/IncidenceChanger>
|
|
|
|
#include <QApplication>
|
|
#include <QPainter>
|
|
#include <QStandardItemModel>
|
|
#include <QSplitter>
|
|
#include <QTreeWidget>
|
|
#include <QHeaderView>
|
|
#include <QPointer>
|
|
#include <QVBoxLayout>
|
|
#include <QHelpEvent>
|
|
|
|
using namespace KCalCore;
|
|
using namespace EventViews;
|
|
|
|
namespace EventViews {
|
|
|
|
class RowController : public KDGantt::AbstractRowController
|
|
{
|
|
private:
|
|
static const int ROW_HEIGHT ;
|
|
QPointer<QAbstractItemModel> m_model;
|
|
|
|
public:
|
|
RowController()
|
|
{
|
|
mRowHeight = 20;
|
|
}
|
|
|
|
void setModel( QAbstractItemModel *model )
|
|
{
|
|
m_model = model;
|
|
}
|
|
|
|
/*reimp*/int headerHeight() const
|
|
{
|
|
return 2 * mRowHeight + 10;
|
|
}
|
|
|
|
/*reimp*/bool isRowVisible( const QModelIndex & ) const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/*reimp*/bool isRowExpanded( const QModelIndex & ) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*reimp*/KDGantt::Span rowGeometry( const QModelIndex &idx ) const
|
|
{
|
|
return KDGantt::Span( idx.row() * mRowHeight, mRowHeight );
|
|
}
|
|
|
|
/*reimp*/int maximumItemHeight() const
|
|
{
|
|
return mRowHeight / 2;
|
|
}
|
|
|
|
/*reimp*/int totalHeight() const
|
|
{
|
|
return m_model->rowCount() * mRowHeight;
|
|
}
|
|
|
|
/*reimp*/QModelIndex indexAt( int height ) const
|
|
{
|
|
return m_model->index( height / mRowHeight, 0 );
|
|
}
|
|
|
|
/*reimp*/QModelIndex indexBelow( const QModelIndex &idx ) const
|
|
{
|
|
if ( !idx.isValid() ) {
|
|
return QModelIndex();
|
|
}
|
|
return idx.model()->index( idx.row() + 1, idx.column(), idx.parent() );
|
|
}
|
|
|
|
/*reimp*/QModelIndex indexAbove( const QModelIndex &idx ) const
|
|
{
|
|
if ( !idx.isValid() ) {
|
|
return QModelIndex();
|
|
}
|
|
return idx.model()->index( idx.row() - 1, idx.column(), idx.parent() );
|
|
}
|
|
|
|
void setRowHeight( int height )
|
|
{
|
|
mRowHeight = height;
|
|
}
|
|
|
|
private:
|
|
int mRowHeight;
|
|
};
|
|
|
|
class GanttHeaderView : public QHeaderView
|
|
{
|
|
public:
|
|
explicit GanttHeaderView( QWidget *parent=0 ) : QHeaderView( Qt::Horizontal, parent )
|
|
{
|
|
}
|
|
|
|
QSize sizeHint() const
|
|
{
|
|
QSize s = QHeaderView::sizeHint();
|
|
s.rheight() *= 2;
|
|
return s;
|
|
}
|
|
};
|
|
|
|
class GanttItemDelegate : public KDGantt::ItemDelegate
|
|
{
|
|
void paintGanttItem( QPainter *painter,
|
|
const KDGantt::StyleOptionGanttItem &opt,
|
|
const QModelIndex &idx )
|
|
{
|
|
painter->setRenderHints( QPainter::Antialiasing );
|
|
if ( !idx.isValid() ) {
|
|
return;
|
|
}
|
|
|
|
KDGantt::ItemType type = static_cast<KDGantt::ItemType>(
|
|
idx.model()->data( idx, KDGantt::ItemTypeRole ).toInt() );
|
|
|
|
QString txt = idx.model()->data( idx, Qt::DisplayRole ).toString();
|
|
QRectF itemRect = opt.itemRect;
|
|
QRectF boundingRect = opt.boundingRect;
|
|
boundingRect.setY( itemRect.y() );
|
|
boundingRect.setHeight( itemRect.height() );
|
|
|
|
QBrush brush = defaultBrush( type );
|
|
if ( opt.state & QStyle::State_Selected ) {
|
|
QLinearGradient selectedGrad( 0., 0., 0.,
|
|
QApplication::fontMetrics().height() );
|
|
selectedGrad.setColorAt( 0., Qt::red );
|
|
selectedGrad.setColorAt( 1., Qt::darkRed );
|
|
|
|
brush = QBrush( selectedGrad );
|
|
painter->setBrush( brush );
|
|
} else {
|
|
painter->setBrush( idx.model()->data( idx, Qt::DecorationRole ).value<QColor>() );
|
|
}
|
|
|
|
painter->setPen( defaultPen( type ) );
|
|
painter->setBrushOrigin( itemRect.topLeft() );
|
|
|
|
switch( type ) {
|
|
case KDGantt::TypeTask:
|
|
if ( itemRect.isValid() ) {
|
|
QRectF r = itemRect;
|
|
painter->drawRect( r );
|
|
|
|
Qt::Alignment ta;
|
|
switch( opt.displayPosition ) {
|
|
case KDGantt::StyleOptionGanttItem::Left:
|
|
ta = Qt::AlignLeft;
|
|
break;
|
|
case KDGantt::StyleOptionGanttItem::Right:
|
|
ta = Qt::AlignRight;
|
|
break;
|
|
case KDGantt::StyleOptionGanttItem::Center:
|
|
ta = Qt::AlignCenter;
|
|
break;
|
|
}
|
|
painter->drawText( boundingRect, ta, txt );
|
|
}
|
|
break;
|
|
default:
|
|
KDGantt::ItemDelegate::paintGanttItem( painter, opt, idx );
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
TimelineView::TimelineView( QWidget *parent )
|
|
: EventView( parent ), d( new Private( this ) )
|
|
{
|
|
QVBoxLayout *vbox = new QVBoxLayout( this );
|
|
vbox->setMargin( 0 );
|
|
QSplitter *splitter = new QSplitter( Qt::Horizontal, this );
|
|
d->mLeftView = new QTreeWidget;
|
|
d->mLeftView->setHeader( new GanttHeaderView );
|
|
d->mLeftView->setHeaderLabel( i18n( "Calendar" ) );
|
|
d->mLeftView->setRootIsDecorated( false );
|
|
d->mLeftView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
|
|
|
|
d->mGantt = new KDGantt::GraphicsView();
|
|
splitter->addWidget( d->mLeftView );
|
|
splitter->addWidget( d->mGantt );
|
|
connect( splitter, SIGNAL(splitterMoved(int,int)),
|
|
d, SLOT(splitterMoved()) );
|
|
QStandardItemModel *model = new QStandardItemModel( this );
|
|
|
|
d->mRowController = new RowController;
|
|
d->mRowController->setRowHeight( fontMetrics().height() ); //TODO: detect
|
|
|
|
d->mRowController->setModel( model );
|
|
d->mGantt->setRowController( d->mRowController );
|
|
|
|
KDGantt::DateTimeGrid *grid = new KDGantt::DateTimeGrid;
|
|
grid->setScale( KDGantt::DateTimeGrid::ScaleHour );
|
|
grid->setDayWidth( 800 );
|
|
grid->setRowSeparators( true );
|
|
d->mGantt->setGrid( grid );
|
|
d->mGantt->setModel( model );
|
|
d->mGantt->viewport()->setFixedWidth( 8000 );
|
|
|
|
d->mGantt->viewport()->installEventFilter( this );
|
|
|
|
#if 0
|
|
d->mGantt->setCalendarMode( true );
|
|
d->mGantt->setShowLegendButton( false );
|
|
d->mGantt->setFixedHorizon( true );
|
|
d->mGantt->removeColumn( 0 );
|
|
d->mGantt->addColumn( i18n( "Calendar" ) );
|
|
d->mGantt->setHeaderVisible( true );
|
|
if ( KGlobal::locale()->use12Clock() ) {
|
|
d->mGantt->setHourFormat( KDGanttView::Hour_12 );
|
|
} else {
|
|
d->mGantt->setHourFormat( KDGanttView::Hour_24_FourDigit );
|
|
}
|
|
#else
|
|
kDebug() << "Disabled code, port to KDGantt2";
|
|
#endif
|
|
d->mGantt->setItemDelegate( new GanttItemDelegate );
|
|
|
|
vbox->addWidget( splitter );
|
|
|
|
#if 0
|
|
connect( d->mGantt, SIGNAL(rescaling(KDGanttView::Scale)),
|
|
SLOT(overscale(KDGanttView::Scale)) );
|
|
#else
|
|
kDebug() << "Disabled code, port to KDGantt2";
|
|
#endif
|
|
connect( model, SIGNAL(itemChanged(QStandardItem*)),
|
|
d, SLOT(itemChanged(QStandardItem*)) );
|
|
connect( d->mGantt, SIGNAL(doubleClicked(QModelIndex)),
|
|
d, SLOT(itemDoubleClicked(QModelIndex)) );
|
|
connect( d->mGantt, SIGNAL(activated(QModelIndex)),
|
|
d, SLOT(itemSelected(QModelIndex)) );
|
|
d->mGantt->setContextMenuPolicy( Qt::CustomContextMenu );
|
|
connect( d->mGantt, SIGNAL(customContextMenuRequested(QPoint)),
|
|
d, SLOT(contextMenuRequested(QPoint)) );
|
|
|
|
#if 0
|
|
connect( d->mGantt, SIGNAL(dateTimeDoubleClicked(QDateTime)),
|
|
d, SLOT(newEventWithHint(QDateTime)) );
|
|
#else
|
|
kDebug() << "Disabled code, port to KDGantt2";
|
|
#endif
|
|
}
|
|
|
|
TimelineView::~TimelineView()
|
|
{
|
|
delete d->mRowController;
|
|
delete d;
|
|
}
|
|
|
|
/*virtual*/
|
|
Akonadi::Item::List TimelineView::selectedIncidences() const
|
|
{
|
|
return d->mSelectedItemList;
|
|
}
|
|
|
|
/*virtual*/
|
|
KCalCore::DateList TimelineView::selectedIncidenceDates() const
|
|
{
|
|
return KCalCore::DateList();
|
|
}
|
|
|
|
/*virtual*/
|
|
int TimelineView::currentDateCount() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*virtual*/
|
|
void TimelineView::showDates( const QDate &start, const QDate &end, const QDate &preferredMonth )
|
|
{
|
|
Q_UNUSED( preferredMonth );
|
|
Q_ASSERT_X( calendar(), "showDates()", "set a Akonadi::ETMCalendar" );
|
|
Q_ASSERT_X( start.isValid(), "showDates()", "start date must be valid" );
|
|
Q_ASSERT_X( end.isValid(), "showDates()", "end date must be valid" );
|
|
|
|
kDebug() << "start=" << start << "end=" << end;
|
|
|
|
d->mStartDate = start;
|
|
d->mEndDate = end;
|
|
d->mHintDate = QDateTime();
|
|
KDGantt::DateTimeGrid *grid = static_cast<KDGantt::DateTimeGrid*>( d->mGantt->grid() );
|
|
grid->setStartDateTime( QDateTime( start ) );
|
|
#if 0
|
|
d->mGantt->setHorizonStart( QDateTime( start ) );
|
|
d->mGantt->setHorizonEnd( QDateTime( end.addDays( 1 ) ) );
|
|
d->mGantt->setMinorScaleCount( 1 );
|
|
d->mGantt->setScale( KDGanttView::Hour );
|
|
d->mGantt->setMinimumScale( KDGanttView::Hour );
|
|
d->mGantt->setMaximumScale( KDGanttView::Hour );
|
|
d->mGantt->zoomToFit();
|
|
|
|
d->mGantt->setUpdateEnabled( false );
|
|
d->mGantt->clear();
|
|
#else
|
|
kDebug() << "Disabled code, port to KDGantt2";
|
|
#endif
|
|
|
|
d->mLeftView->clear();
|
|
uint index = 0;
|
|
// item for every calendar
|
|
TimelineItem *item = 0;
|
|
Akonadi::ETMCalendar::Ptr calres = calendar();
|
|
if ( !calres ) {
|
|
item = new TimelineItem( calendar(),
|
|
index++,
|
|
static_cast<QStandardItemModel*>( d->mGantt->model() ),
|
|
d->mGantt );
|
|
d->mLeftView->addTopLevelItem( new QTreeWidgetItem( QStringList() << i18n( "Calendar" ) ) );
|
|
d->mCalendarItemMap.insert( -1, item );
|
|
|
|
} else {
|
|
const CalendarSupport::CollectionSelection *colSel = collectionSelection();
|
|
const Akonadi::Collection::List collections = colSel->selectedCollections();
|
|
|
|
Q_FOREACH ( const Akonadi::Collection &collection, collections ) {
|
|
if ( collection.contentMimeTypes().contains( Event::eventMimeType() ) ) {
|
|
item = new TimelineItem( calendar(),
|
|
index++,
|
|
static_cast<QStandardItemModel*>( d->mGantt->model() ),
|
|
d->mGantt );
|
|
d->mLeftView->addTopLevelItem(
|
|
new QTreeWidgetItem(
|
|
QStringList() << CalendarSupport::displayName( calendar().data(), collection ) ) );
|
|
const QColor resourceColor = EventViews::resourceColor( collection, preferences() );
|
|
if ( resourceColor.isValid() ) {
|
|
item->setColor( resourceColor );
|
|
}
|
|
kDebug() << "Created item " << item
|
|
<< " (" << CalendarSupport::displayName( calendar().data(), collection ) << ") "
|
|
<< "with index " << index - 1 << " from collection " << collection.id();
|
|
d->mCalendarItemMap.insert( collection.id(), item );
|
|
}
|
|
}
|
|
}
|
|
|
|
// add incidences
|
|
|
|
/**
|
|
* We remove the model from the view here while we fill it with items,
|
|
* because every call to insertIncidence will cause the view to do an expensive
|
|
* updateScene() call otherwise.
|
|
*/
|
|
QAbstractItemModel *ganttModel = d->mGantt->model();
|
|
d->mGantt->setModel( 0 );
|
|
|
|
KCalCore::Event::List events;
|
|
KDateTime::Spec timeSpec = CalendarSupport::KCalPrefs::instance()->timeSpec();
|
|
for ( QDate day = start; day <= end; day = day.addDays( 1 ) ) {
|
|
events = calendar()->events( day, timeSpec,
|
|
KCalCore::EventSortStartDate,
|
|
KCalCore::SortDirectionAscending );
|
|
Q_FOREACH ( const KCalCore::Event::Ptr &event, events ) {
|
|
if ( event->hasRecurrenceId() ) {
|
|
continue;
|
|
}
|
|
Akonadi::Item item = calendar()->item( event );
|
|
d->insertIncidence( item, day );
|
|
}
|
|
}
|
|
d->mGantt->setModel( ganttModel );
|
|
d->splitterMoved();
|
|
}
|
|
|
|
/*virtual*/
|
|
void TimelineView::showIncidences( const Akonadi::Item::List &incidenceList, const QDate &date )
|
|
{
|
|
Q_UNUSED( incidenceList );
|
|
Q_UNUSED( date );
|
|
}
|
|
|
|
/*virtual*/
|
|
void TimelineView::updateView()
|
|
{
|
|
if ( d->mStartDate.isValid() && d->mEndDate.isValid() ) {
|
|
showDates( d->mStartDate, d->mEndDate );
|
|
}
|
|
}
|
|
|
|
/*virtual*/
|
|
void TimelineView::changeIncidenceDisplay( const Akonadi::Item &incidence, int mode )
|
|
{
|
|
switch ( mode ) {
|
|
case Akonadi::IncidenceChanger::ChangeTypeCreate:
|
|
d->insertIncidence( incidence );
|
|
break;
|
|
case Akonadi::IncidenceChanger::ChangeTypeModify:
|
|
d->removeIncidence( incidence );
|
|
d->insertIncidence( incidence );
|
|
break;
|
|
case Akonadi::IncidenceChanger::ChangeTypeDelete:
|
|
d->removeIncidence( incidence );
|
|
break;
|
|
default:
|
|
updateView();
|
|
}
|
|
}
|
|
|
|
bool TimelineView::eventDurationHint( QDateTime &startDt, QDateTime &endDt,
|
|
bool &allDay ) const
|
|
{
|
|
startDt = QDateTime( d->mHintDate );
|
|
endDt = QDateTime( d->mHintDate.addSecs( 2 * 60 * 60 ) );
|
|
allDay = false;
|
|
return d->mHintDate.isValid();
|
|
}
|
|
|
|
// void TimelineView::overscale( KDGanttView::Scale scale )
|
|
// {
|
|
// Q_UNUSED( scale );
|
|
// /* Disabled, looks *really* bogus:
|
|
// this triggers and endless rescaling loop; we want to set
|
|
// a fixed scale, the Gantt view doesn't like it and rescales
|
|
// (emitting a rescaling signal that leads here) and so on...
|
|
// //set a relative zoom factor of 1 (?!)
|
|
// d->mGantt->setZoomFactor( 1, false );
|
|
// d->mGantt->setScale( KDGanttView::Hour );
|
|
// d->mGantt->setMinorScaleCount( 12 );
|
|
// */
|
|
// }
|
|
|
|
QDate TimelineView::startDate() const
|
|
{
|
|
return d->mStartDate;
|
|
}
|
|
|
|
QDate TimelineView::endDate() const
|
|
{
|
|
return d->mEndDate;
|
|
}
|
|
|
|
bool TimelineView::eventFilter( QObject *object, QEvent *event )
|
|
{
|
|
if ( event->type() == QEvent::ToolTip ) {
|
|
QHelpEvent *helpEvent = static_cast<QHelpEvent*>( event );
|
|
QGraphicsItem *item = d->mGantt->itemAt( helpEvent->pos() );
|
|
if ( item ) {
|
|
if ( item->type() == KDGantt::GraphicsItem::Type ) {
|
|
KDGantt::GraphicsItem *graphicsItem = static_cast<KDGantt::GraphicsItem*>( item );
|
|
const QModelIndex itemIndex = graphicsItem->index();
|
|
|
|
QStandardItemModel *itemModel =
|
|
qobject_cast<QStandardItemModel*>( d->mGantt->model() );
|
|
|
|
TimelineSubItem *timelineItem =
|
|
dynamic_cast<TimelineSubItem*>( itemModel->item( itemIndex.row(), itemIndex.column() ) );
|
|
|
|
if ( timelineItem ) {
|
|
timelineItem->updateToolTip();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return EventView::eventFilter( object, event );
|
|
}
|
|
|