You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdepim/korganizer/koagenda.cpp

2062 lines
64 KiB

/*
This file is part of KOrganizer.
Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
Marcus Bains line.
Copyright (c) 2001 Ali Rahimi
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 TQt, and distribute the resulting executable,
without including the source code for TQt in the source distribution.
*/
#include <assert.h>
#include <tqintdict.h>
#include <tqdatetime.h>
#include <tqapplication.h>
#include <tqpopupmenu.h>
#include <tqcursor.h>
#include <tqpainter.h>
#include <tqlabel.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kiconloader.h>
#include <tdeglobal.h>
#include <tdemessagebox.h>
#include "koagendaitem.h"
#include "koprefs.h"
#include "koglobals.h"
#include "komessagebox.h"
#include "incidencechanger.h"
#include "kohelper.h"
#include "koagenda.h"
#include "koagenda.moc"
#include <korganizer/baseview.h>
#include <libkcal/event.h>
#include <libkcal/todo.h>
#include <libkcal/dndfactory.h>
#include <libkcal/icaldrag.h>
#include <libkcal/vcaldrag.h>
#include <libkcal/calendar.h>
#include <libkcal/calendarresources.h>
#include <libkcal/calhelper.h>
#include <math.h>
////////////////////////////////////////////////////////////////////////////
MarcusBains::MarcusBains(KOAgenda *_agenda,const char *name )
: TQFrame(_agenda->viewport(), name), agenda(_agenda)
{
setLineWidth(0);
setMargin(0);
setBackgroundColor(TQt::red);
minutes = new TQTimer(this);
connect(minutes, TQT_SIGNAL(timeout()), this, TQT_SLOT(updateLocation()));
minutes->start(0, true);
mTimeBox = new TQLabel(this);
mTimeBox->setAlignment(TQt::AlignRight | TQt::AlignBottom);
TQPalette pal = mTimeBox->palette();
pal.setColor(TQColorGroup::Foreground, TQt::red);
mTimeBox->setPalette(pal);
mTimeBox->setAutoMask(true);
agenda->addChild(mTimeBox);
mOldTime = TQTime( 0, 0 );
mOldToday = -1;
}
MarcusBains::~MarcusBains()
{
delete minutes;
}
int MarcusBains::todayColumn()
{
TQDate currentDate = TQDate::currentDate();
DateList dateList = agenda->dateList();
DateList::ConstIterator it;
int col = 0;
for(it = dateList.begin(); it != dateList.end(); ++it) {
if((*it) == currentDate)
return KOGlobals::self()->reverseLayout() ?
agenda->columns() - 1 - col : col;
++col;
}
return -1;
}
void MarcusBains::updateLocation()
{
updateLocationRecalc();
}
void MarcusBains::updateLocationRecalc( bool recalculate )
{
TQTime tim = TQTime::currentTime();
if((tim.hour() == 0) && (mOldTime.hour()==23))
recalculate = true;
int mins = tim.hour()*60 + tim.minute();
int minutesPerCell = 24 * 60 / agenda->rows();
int y = int( mins * agenda->gridSpacingY() / minutesPerCell );
int today = recalculate ? todayColumn() : mOldToday;
int x = int( agenda->gridSpacingX() * today );
mOldTime = tim;
mOldToday = today;
bool hideIt = !( KOPrefs::instance()->mMarcusBainsEnabled );
if ( !isHidden() && ( hideIt || ( today < 0 ) ) ) {
hide();
mTimeBox->hide();
return;
}
if ( isHidden() && !hideIt ) {
show();
mTimeBox->show();
}
if ( recalculate ) setFixedSize( int( agenda->gridSpacingX() ), 1 );
agenda->moveChild( this, x, y );
raise();
if(recalculate)
mTimeBox->setFont(KOPrefs::instance()->mMarcusBainsFont);
TQString timeStr = TDEGlobal::locale()->formatTime(tim, KOPrefs::instance()->mMarcusBainsShowSeconds);
TQFontMetrics fm = fontMetrics();
mTimeBox->setText( timeStr );
TQSize sz( fm.width( timeStr + ' ' ), fm.height() );
mTimeBox->setFixedSize( sz );
if (y-mTimeBox->height()>=0) y-=mTimeBox->height(); else y++;
if (x-mTimeBox->width()+agenda->gridSpacingX() > 0)
x += int( agenda->gridSpacingX() - mTimeBox->width() - 1 );
else x++;
agenda->moveChild(mTimeBox,x,y);
mTimeBox->raise();
mTimeBox->setAutoMask(true);
minutes->start(1000,true);
}
////////////////////////////////////////////////////////////////////////////
/*
Create an agenda widget with rows rows and columns columns.
*/
KOAgenda::KOAgenda( int columns, int rows, int rowSize, CalendarView *calendarView,
TQWidget *parent, const char *name, WFlags f )
: TQScrollView( parent, name, f ), mChanger( 0 )
{
mColumns = columns;
mRows = rows;
mGridSpacingY = rowSize;
if ( mGridSpacingY < 4 || mGridSpacingY > 30 ) {
mGridSpacingY = 10;
}
mCalendarView = calendarView;
mAllDayMode = false;
init();
viewport()->setMouseTracking(true);
}
/*
Create an agenda widget with columns columns and one row. This is used for
all-day events.
*/
KOAgenda::KOAgenda( int columns, CalendarView *calendarView, TQWidget *parent,
const char *name, WFlags f ) : TQScrollView( parent, name, f )
{
mColumns = columns;
mRows = 1;
mGridSpacingY = 24;
mAllDayMode = true;
mCalendarView = calendarView;
setVScrollBarMode( AlwaysOff );
init();
}
KOAgenda::~KOAgenda()
{
delete mMarcusBains;
}
Incidence *KOAgenda::selectedIncidence() const
{
return ( mSelectedItem ? mSelectedItem->incidence() : 0 );
}
TQDate KOAgenda::selectedIncidenceDate() const
{
return ( mSelectedItem ? mSelectedItem->itemDate() : TQDate() );
}
const TQString KOAgenda::lastSelectedUid() const
{
return mSelectedUid;
}
void KOAgenda::init()
{
mGridSpacingX = 100;
mDesiredGridSpacingY = KOPrefs::instance()->mHourSize;
if ( mDesiredGridSpacingY < 4 || mDesiredGridSpacingY > 30 ) {
mDesiredGridSpacingY = 10;
}
// make sure that there are not more than 24 per day
mGridSpacingY = (double)height() / (double)mRows;
if ( mGridSpacingY < mDesiredGridSpacingY ) {
mGridSpacingY = mDesiredGridSpacingY;
}
mResizeBorderWidth = 8;
mScrollBorderWidth = 8;
mScrollDelay = 30;
mScrollOffset = 10;
enableClipper( true );
// Grab key strokes for keyboard navigation of agenda. Seems to have no
// effect. Has to be fixed.
setFocusPolicy( TQ_WheelFocus );
connect( &mScrollUpTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( scrollUp() ) );
connect( &mScrollDownTimer, TQT_SIGNAL( timeout() ), TQT_SLOT( scrollDown() ) );
mStartCell = TQPoint( 0, 0 );
mEndCell = TQPoint( 0, 0 );
mHasSelection = false;
mSelectionStartPoint = TQPoint( 0, 0 );
mSelectionStartCell = TQPoint( 0, 0 );
mSelectionEndCell = TQPoint( 0, 0 );
mOldLowerScrollValue = -1;
mOldUpperScrollValue = -1;
mClickedItem = 0;
mActionItem = 0;
mResPair = tqMakePair( static_cast<ResourceCalendar *>( 0 ), TQString() );
mActionType = NOP;
mItemMoved = false;
mSelectedItem = 0;
mSelectedUid = TQString();
setAcceptDrops( true );
installEventFilter( this );
mItems.setAutoDelete( true );
mItemsToDelete.setAutoDelete( true );
resizeContents( int( mGridSpacingX * mColumns ),
int( mGridSpacingY * mRows ) );
viewport()->update();
viewport()->setBackgroundMode( NoBackground );
viewport()->setFocusPolicy( TQ_WheelFocus );
setMinimumSize( 30, int( mGridSpacingY + 1 ) );
// setMaximumHeight(mGridSpacingY * mRows + 5);
// Disable horizontal scrollbar. This is a hack. The geometry should be
// controlled in a way that the contents horizontally always fits. Then it is
// not necessary to turn off the scrollbar.
setHScrollBarMode( AlwaysOff );
setStartTime( KOPrefs::instance()->mDayBegins.time() );
calculateWorkingHours();
connect( verticalScrollBar(), TQT_SIGNAL( valueChanged( int ) ),
TQT_SLOT( checkScrollBoundaries( int ) ) );
// Create the Marcus Bains line.
if( mAllDayMode ) {
mMarcusBains = 0;
} else {
mMarcusBains = new MarcusBains( this );
addChild( mMarcusBains );
}
mTypeAhead = false;
mTypeAheadReceiver = 0;
mReturnPressed = false;
}
void KOAgenda::clear()
{
// kdDebug(5850) << "KOAgenda::clear()" << endl;
KOAgendaItem *item;
for ( item = mItems.first(); item != 0; item = mItems.next() ) {
removeChild( item );
}
mItems.clear();
mItemsToDelete.clear();
mSelectedItem = 0;
clearSelection();
}
void KOAgenda::clearSelection()
{
mHasSelection = false;
mActionType = NOP;
updateContents();
}
void KOAgenda::marcus_bains()
{
if(mMarcusBains) mMarcusBains->updateLocationRecalc( true );
}
void KOAgenda::changeColumns(int columns)
{
if (columns == 0) {
kdDebug(5850) << "KOAgenda::changeColumns() called with argument 0" << endl;
return;
}
clear();
mColumns = columns;
// setMinimumSize(mColumns * 10, mGridSpacingY + 1);
// init();
// update();
TQResizeEvent event( size(), size() );
TQApplication::sendEvent( this, &event );
}
/*
This is the eventFilter function, which gets all events from the KOAgendaItems
contained in the agenda. It has to handle moving and resizing for all items.
*/
bool KOAgenda::eventFilter ( TQObject *object, TQEvent *event )
{
// kdDebug(5850) << "KOAgenda::eventFilter() " << int( event->type() ) << endl;
switch( event->type() ) {
case TQEvent::MouseButtonPress:
case TQEvent::MouseButtonDblClick:
case TQEvent::MouseButtonRelease:
case TQEvent::MouseMove:
return eventFilter_mouse( object, TQT_TQMOUSEEVENT( event ) );
#ifndef TQT_NO_WHEELEVENT
case TQEvent::Wheel:
return eventFilter_wheel( object, TQT_TQWHEELEVENT( event ) );
#endif
case TQEvent::KeyPress:
case TQEvent::KeyRelease:
return eventFilter_key( object, TQT_TQKEYEVENT( event ) );
case ( TQEvent::Leave ):
if ( !mActionItem )
setCursor( arrowCursor );
if ( object == viewport() )
emit leaveAgenda();
return true;
case TQEvent::Enter:
emit enterAgenda();
return TQScrollView::eventFilter( object, event );
#ifndef KORG_NODND
case TQEvent::DragEnter:
case TQEvent::DragMove:
case TQEvent::DragLeave:
case TQEvent::Drop:
// case TQEvent::DragResponse:
return eventFilter_drag(object, static_cast<TQDropEvent*>(event));
#endif
default:
return TQScrollView::eventFilter( object, event );
}
}
bool KOAgenda::eventFilter_drag( TQObject *object, TQDropEvent *de )
{
#ifndef KORG_NODND
TQPoint viewportPos;
if ( object != viewport() && object != this ) {
viewportPos = TQT_TQWIDGET( object )->mapToParent( de->pos() );
} else {
viewportPos = de->pos();
}
switch ( de->type() ) {
case TQEvent::DragEnter:
case TQEvent::DragMove:
if ( ICalDrag::canDecode( de ) || VCalDrag::canDecode( de ) ) {
DndFactory factory( mCalendar );
Todo *todo = factory.createDropTodo( de );
if ( todo ) {
de->accept();
delete todo;
} else {
de->ignore();
}
return true;
} else return false;
break;
case TQEvent::DragLeave:
return false;
break;
case TQEvent::Drop:
{
if ( !ICalDrag::canDecode( de ) && !VCalDrag::canDecode( de ) ) {
return false;
}
DndFactory factory( mCalendar );
Todo *todo = factory.createDropTodo( de );
if ( todo ) {
de->acceptAction();
TQPoint pos;
// FIXME: This is a bad hack, as the viewportToContents seems to be off by
// 2000 (which is the left upper corner of the viewport). It works correctly
// for agendaItems.
if ( object == this ) {
pos = viewportPos + TQPoint( contentsX(), contentsY() );
} else {
pos = viewportToContents( viewportPos );
}
TQPoint gpos = contentsToGrid( pos );
emit droppedToDo( todo, gpos, mAllDayMode );
return true;
}
}
break;
case TQEvent::DragResponse:
default:
break;
}
#endif
return false;
}
bool KOAgenda::eventFilter_key( TQObject *, TQKeyEvent *ke )
{
// kdDebug(5850) << "KOAgenda::eventFilter_key() " << ke->type() << endl;
// If Return is pressed bring up an editor for the current selected time span.
if ( ke->key() == Key_Return ) {
if ( ke->type() == TQEvent::KeyPress ) mReturnPressed = true;
else if ( ke->type() == TQEvent::KeyRelease ) {
if ( mReturnPressed ) {
emitNewEventForSelection();
mReturnPressed = false;
return true;
} else {
mReturnPressed = false;
}
}
}
// Ignore all input that does not produce any output
if ( ke->text().isEmpty() ) return false;
if ( ke->type() == TQEvent::KeyPress || ke->type() == TQEvent::KeyRelease ) {
switch ( ke->key() ) {
case Key_Escape:
case Key_Return:
case Key_Enter:
case Key_Tab:
case Key_Backtab:
case Key_Left:
case Key_Right:
case Key_Up:
case Key_Down:
case Key_Backspace:
case Key_Delete:
case Key_Prior:
case Key_Next:
case Key_Home:
case Key_End:
case Key_Control:
case Key_Meta:
case Key_Alt:
break;
default:
mTypeAheadEvents.append( TQT_TQEVENT( new TQKeyEvent( ke->type(), ke->key(),
ke->ascii(), ke->state(),
ke->text(), ke->isAutoRepeat(),
ke->count() ) ) );
if ( !mTypeAhead ) {
mTypeAhead = true;
emitNewEventForSelection();
}
return true;
}
}
return false;
}
void KOAgenda::emitNewEventForSelection()
{
TQPair<ResourceCalendar *, TQString>p = mCalendarView->viewSubResourceCalendar();
emit newEventSignal( p.first, p.second );
}
void KOAgenda::finishTypeAhead()
{
// kdDebug(5850) << "KOAgenda::finishTypeAhead()" << endl;
if ( typeAheadReceiver() ) {
for( TQEvent *e = mTypeAheadEvents.first(); e;
e = mTypeAheadEvents.next() ) {
// kdDebug(5850) << "sendEvent() " << int( typeAheadReceiver() ) << endl;
TQApplication::sendEvent( typeAheadReceiver(), e );
}
}
mTypeAheadEvents.clear();
mTypeAhead = false;
}
#ifndef TQT_NO_WHEELEVENT
bool KOAgenda::eventFilter_wheel ( TQObject *object, TQWheelEvent *e )
{
TQPoint viewportPos;
bool accepted=false;
if ( ( e->state() & ShiftButton) == ShiftButton ) {
if ( object != viewport() ) {
viewportPos = ( (TQWidget *) object )->mapToParent( e->pos() );
} else {
viewportPos = e->pos();
}
//kdDebug(5850)<< "KOAgenda::eventFilter_wheel: type:"<<
// e->type()<<" delta: "<< e->delta()<< endl;
emit zoomView( -e->delta() ,
contentsToGrid( viewportToContents( viewportPos ) ),
Qt::Horizontal );
accepted=true;
}
if ( ( e->state() & ControlButton ) == ControlButton ){
if ( object != viewport() ) {
viewportPos = ( (TQWidget *)object )->mapToParent( e->pos() );
} else {
viewportPos = e->pos();
}
emit zoomView( -e->delta() ,
contentsToGrid( viewportToContents( viewportPos ) ),
Qt::Vertical );
emit mousePosSignal(gridToContents(contentsToGrid(viewportToContents( viewportPos ))));
accepted=true;
}
if (accepted ) e->accept();
return accepted;
}
#endif
bool KOAgenda::eventFilter_mouse(TQObject *object, TQMouseEvent *me)
{
TQPoint viewportPos;
if (object != viewport()) {
viewportPos = ((TQWidget *)object)->mapToParent(me->pos());
} else {
viewportPos = me->pos();
}
switch (me->type()) {
case TQEvent::MouseButtonPress:
// kdDebug(5850) << "koagenda: filtered button press" << endl;
if (object != viewport()) {
if (me->button() == Qt::RightButton) {
mClickedItem = dynamic_cast<KOAgendaItem *>(object);
if (mClickedItem) {
selectItem(mClickedItem);
emit showIncidencePopupSignal( mCalendar,
mClickedItem->incidence(),
mClickedItem->itemDate() );
} else {
return TQScrollView::eventFilter( TQT_TQOBJECT(object), TQT_TQEVENT(me) ); // pass through for use by multiagenda
}
} else {
KOAgendaItem* item = dynamic_cast<KOAgendaItem *>(object);
if (item) {
Incidence *incidence = item->incidence();
if ( incidence->isReadOnly() ) {
mActionItem = 0;
mResPair = tqMakePair( static_cast<ResourceCalendar *>( 0 ), TQString() );
} else {
mActionItem = item;
mResPair = CalHelper::incSubResourceCalendar( mCalendar, incidence );
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 {
return TQScrollView::eventFilter( TQT_TQOBJECT(object), TQT_TQEVENT(me) ); // pass through for use by multiagenda
}
}
} else {
if ( me->button() == Qt::RightButton ) {
// if mouse pointer is not in selection, select the cell below the cursor
TQPoint gpos = contentsToGrid( viewportToContents( viewportPos ) );
if ( !ptInSelection( gpos ) ) {
mSelectionStartCell = gpos;
mSelectionEndCell = gpos;
mHasSelection = true;
emit newStartSelectSignal();
emit newTimeSpanSignal( mSelectionStartCell, mSelectionEndCell );
updateContents();
}
showNewEventPopupSignal();
} else {
// if mouse pointer is in selection, don't change selection
TQPoint gpos = contentsToGrid( viewportToContents( viewportPos ) );
if ( !ptInSelection( gpos ) ) {
selectItem(0);
mActionItem = 0;
mResPair = tqMakePair( static_cast<ResourceCalendar *>( 0 ), TQString() );
setCursor(arrowCursor);
startSelectAction(viewportPos);
}
}
return TQScrollView::eventFilter( TQT_TQOBJECT(object), TQT_TQEVENT(me) ); // pass through for use by multiagenda
}
break;
case TQEvent::MouseButtonRelease:
if (mActionItem) {
endItemAction();
} else if ( 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(
viewportToContents( viewportPos ) ) ));
break;
case TQEvent::MouseMove: {
// This nasty gridToContents(contentsToGrid(..)) is needed to
// avoid an offset of a few pixels. Don't ask me why...
TQPoint indicatorPos = gridToContents(contentsToGrid(
viewportToContents( viewportPos )));
if (object != viewport()) {
KOAgendaItem *moveItem = dynamic_cast<KOAgendaItem *>(object);
if (moveItem && !moveItem->incidence()->isReadOnly() ) {
if (!mActionItem)
setNoActionCursor(moveItem,viewportPos);
else {
performItemAction(viewportPos);
if ( mActionType == MOVE ) {
// show cursor at the current begin of the item
KOAgendaItem *firstItem = mActionItem->firstMultiItem();
if (!firstItem) firstItem = mActionItem;
indicatorPos = gridToContents( TQPoint( firstItem->cellXLeft(),
firstItem->cellYTop() ) );
} else if ( mActionType == RESIZEBOTTOM ) {
// RESIZETOP is handled correctly, only resizebottom works differently
indicatorPos = gridToContents( TQPoint( mActionItem->cellXLeft(),
mActionItem->cellYBottom()+1 ) );
}
} // If we have an action item
} // If move item && !read only
} else {
if ( mActionType == SELECT ) {
performSelectAction( viewportPos );
// show cursor at end of timespan
if ( ((mStartCell.y() < mEndCell.y()) && (mEndCell.x() >= mStartCell.x())) ||
(mEndCell.x() > mStartCell.x()) )
indicatorPos = gridToContents( TQPoint(mEndCell.x(), mEndCell.y()+1) );
else
indicatorPos = gridToContents( mEndCell );
}
}
emit mousePosSignal( indicatorPos );
break; }
case TQEvent::MouseButtonDblClick:
if (object == viewport()) {
selectItem(0);
TQPair<ResourceCalendar *, TQString>p = mCalendarView->viewSubResourceCalendar();
emit newEventSignal( p.first, p.second );
} else {
KOAgendaItem *doubleClickedItem = dynamic_cast<KOAgendaItem *>( object );
if ( doubleClickedItem ) {
selectItem( doubleClickedItem );
emit editIncidenceSignal( doubleClickedItem->incidence(), doubleClickedItem->itemDate() );
}
}
break;
default:
break;
}
return true;
}
bool KOAgenda::ptInSelection( TQPoint gpos ) const
{
if ( !mHasSelection ) {
return false;
} else if ( gpos.x()<mSelectionStartCell.x() || gpos.x()>mSelectionEndCell.x() ) {
return false;
} else if ( (gpos.x()==mSelectionStartCell.x()) && (gpos.y()<mSelectionStartCell.y()) ) {
return false;
} else if ( (gpos.x()==mSelectionEndCell.x()) && (gpos.y()>mSelectionEndCell.y()) ) {
return false;
}
return true;
}
void KOAgenda::startSelectAction( const TQPoint &viewportPos )
{
emit newStartSelectSignal();
mActionType = SELECT;
mSelectionStartPoint = viewportPos;
mHasSelection = true;
TQPoint pos = viewportToContents( viewportPos );
TQPoint gpos = contentsToGrid( pos );
// Store new selection
mStartCell = gpos;
mEndCell = gpos;
mSelectionStartCell = gpos;
mSelectionEndCell = gpos;
updateContents();
}
void KOAgenda::performSelectAction(const TQPoint& viewportPos)
{
TQPoint pos = viewportToContents( viewportPos );
TQPoint gpos = contentsToGrid( pos );
TQPoint clipperPos = clipper()->
mapFromGlobal(viewport()->mapToGlobal(viewportPos));
// Scroll if cursor was moved to upper or lower end of agenda.
if (clipperPos.y() < mScrollBorderWidth) {
mScrollUpTimer.start(mScrollDelay);
} else if (visibleHeight() - clipperPos.y() <
mScrollBorderWidth) {
mScrollDownTimer.start(mScrollDelay);
} else {
mScrollUpTimer.stop();
mScrollDownTimer.stop();
}
if ( gpos != mEndCell ) {
mEndCell = gpos;
if ( mStartCell.x()>mEndCell.x() ||
( mStartCell.x()==mEndCell.x() && mStartCell.y()>mEndCell.y() ) ) {
// backward selection
mSelectionStartCell = mEndCell;
mSelectionEndCell = mStartCell;
} else {
mSelectionStartCell = mStartCell;
mSelectionEndCell = mEndCell;
}
updateContents();
}
}
void KOAgenda::endSelectAction( const TQPoint &currentPos )
{
mScrollUpTimer.stop();
mScrollDownTimer.stop();
mActionType = NOP;
emit newTimeSpanSignal( mSelectionStartCell, mSelectionEndCell );
if ( KOPrefs::instance()->mSelectionStartsEditor ) {
if ( ( mSelectionStartPoint - currentPos ).manhattanLength() >
TQApplication::startDragDistance() ) {
emitNewEventForSelection();
}
}
}
KOAgenda::MouseActionType KOAgenda::isInResizeArea( bool horizontal,
const TQPoint &pos, KOAgendaItem*item )
{
if (!item) return NOP;
TQPoint gridpos = contentsToGrid( pos );
TQPoint contpos = gridToContents( gridpos +
TQPoint( (KOGlobals::self()->reverseLayout())?1:0, 0 ) );
//kdDebug(5850)<<"contpos="<<contpos<<", pos="<<pos<<", gpos="<<gpos<<endl;
//kdDebug(5850)<<"clXLeft="<<clXLeft<<", clXRight="<<clXRight<<endl;
if ( horizontal ) {
int clXLeft = item->cellXLeft();
int clXRight = item->cellXRight();
if ( KOGlobals::self()->reverseLayout() ) {
int tmp = clXLeft;
clXLeft = clXRight;
clXRight = tmp;
}
int gridDistanceX = int( pos.x() - contpos.x() );
if (gridDistanceX < mResizeBorderWidth && clXLeft == gridpos.x() ) {
if ( KOGlobals::self()->reverseLayout() ) return RESIZERIGHT;
else return RESIZELEFT;
} else if ((mGridSpacingX - gridDistanceX) < mResizeBorderWidth &&
clXRight == gridpos.x() ) {
if ( KOGlobals::self()->reverseLayout() ) return RESIZELEFT;
else return RESIZERIGHT;
} else {
return MOVE;
}
} else {
int gridDistanceY = int( pos.y() - contpos.y() );
if (gridDistanceY < mResizeBorderWidth &&
item->cellYTop() == gridpos.y() &&
!item->firstMultiItem() ) {
return RESIZETOP;
} else if ((mGridSpacingY - gridDistanceY) < mResizeBorderWidth &&
item->cellYBottom() == gridpos.y() &&
!item->lastMultiItem() ) {
return RESIZEBOTTOM;
} else {
return MOVE;
}
}
}
void KOAgenda::startItemAction(const TQPoint& viewportPos)
{
TQPoint pos = viewportToContents( viewportPos );
mStartCell = contentsToGrid( pos );
mEndCell = mStartCell;
bool noResize = ( mActionItem->incidence()->type() == "Todo");
mActionType = MOVE;
if ( !noResize ) {
mActionType = isInResizeArea( mAllDayMode, pos, mActionItem );
}
mActionItem->startMove();
setActionCursor( mActionType, true );
}
void KOAgenda::performItemAction(const TQPoint& viewportPos)
{
// kdDebug(5850) << "viewportPos: " << viewportPos.x() << "," << viewportPos.y() << endl;
// TQPoint point = viewport()->mapToGlobal(viewportPos);
// kdDebug(5850) << "Global: " << point.x() << "," << point.y() << endl;
// point = clipper()->mapFromGlobal(point);
// kdDebug(5850) << "clipper: " << point.x() << "," << point.y() << endl;
// kdDebug(5850) << "visible height: " << visibleHeight() << endl;
TQPoint pos = viewportToContents( viewportPos );
// kdDebug(5850) << "contents: " << x << "," << y << "\n" << endl;
TQPoint gpos = contentsToGrid( pos );
TQPoint clipperPos = clipper()->
mapFromGlobal(viewport()->mapToGlobal(viewportPos));
// Cursor left active agenda area.
// This starts a drag.
if ( clipperPos.y() < 0 || clipperPos.y() > visibleHeight() ||
clipperPos.x() < 0 || clipperPos.x() > visibleWidth() ) {
if ( mActionType == MOVE ) {
mScrollUpTimer.stop();
mScrollDownTimer.stop();
mActionItem->resetMove();
placeSubCells( mActionItem );
emit startDragSignal( mActionItem->incidence() );
setCursor( arrowCursor );
mActionItem = 0;
mResPair = tqMakePair( static_cast<ResourceCalendar *>( 0 ), TQString() );
mActionType = NOP;
mItemMoved = false;
return;
}
} else {
setActionCursor( mActionType );
}
// Scroll if item was moved to upper or lower end of agenda.
if (clipperPos.y() < mScrollBorderWidth) {
mScrollUpTimer.start(mScrollDelay);
} else if (visibleHeight() - clipperPos.y() <
mScrollBorderWidth) {
mScrollDownTimer.start(mScrollDelay);
} else {
mScrollUpTimer.stop();
mScrollDownTimer.stop();
}
// Move or resize item if necessary
if ( mEndCell != gpos ) {
if ( !mItemMoved ) {
if ( !mChanger ||
!mChanger->beginChange( mActionItem->incidence(), mResPair.first, mResPair.second ) ) {
KMessageBox::information( this, i18n("Unable to lock item for "
"modification. You cannot make any changes."),
i18n("Locking Failed"), "AgendaLockingFailed" );
mScrollUpTimer.stop();
mScrollDownTimer.stop();
mActionItem->resetMove();
placeSubCells( mActionItem );
setCursor( arrowCursor );
mActionItem = 0;
mResPair = tqMakePair( static_cast<ResourceCalendar *>( 0 ), TQString() );
mActionType = NOP;
mItemMoved = false;
return;
}
mItemMoved = true;
}
mActionItem->raise();
if (mActionType == MOVE) {
// Move all items belonging to a multi item
KOAgendaItem *firstItem = mActionItem->firstMultiItem();
if (!firstItem) firstItem = mActionItem;
KOAgendaItem *lastItem = mActionItem->lastMultiItem();
if (!lastItem) lastItem = mActionItem;
TQPoint deltapos = gpos - mEndCell;
KOAgendaItem *moveItem = firstItem;
while (moveItem) {
bool changed=false;
if ( deltapos.x()!=0 ) {
moveItem->moveRelative( deltapos.x(), 0 );
changed=true;
}
// in agenda's all day view don't try to move multi items, since there are none
if ( moveItem==firstItem && !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 ) {
moveItem->expandTop( -moveItem->cellYTop() );
// prepend a new item at ( x-1, rows()+newY to rows() )
KOAgendaItem *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);
mItems.append( newFirst );
moveItem->resize( int( mGridSpacingX * newFirst->cellWidth() ),
int( mGridSpacingY * newFirst->cellHeight() ));
TQPoint cpos = gridToContents( TQPoint( newFirst->cellXLeft(), newFirst->cellYTop() ) );
addChild( newFirst, cpos.x(), cpos.y() );
} else {
newFirst = insertItem( moveItem->incidence(), moveItem->itemDate(),
moveItem->cellXLeft()-1, rows()+newY, rows()-1, moveItem->itemPos(), moveItem->itemCount() ) ;
}
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();
mItems.take( mItems.find( moveItem ) );
removeChild( moveItem );
mActionItem->removeMoveItem(moveItem);
moveItem=firstItem;
// adjust next day's item
if (moveItem) moveItem->expandTop( rows()-newY );
} else {
moveItem->expandTop(deltapos.y());
}
changed=true;
}
if ( !moveItem->lastMultiItem() && !mAllDayMode ) { // is the last item
int newY = deltapos.y()+moveItem->cellYBottom();
if (newY<0) {
// erase current item
lastItem = moveItem->prevMultiItem();
moveItem->hide();
mItems.take( mItems.find(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() )
KOAgendaItem *newLast = lastItem->nextMoveItem();
if (newLast) {
newLast->setCellXY( moveItem->cellXLeft()+1, 0, newY-rows()-1 );
mItems.append(newLast);
moveItem->resize( int( mGridSpacingX * newLast->cellWidth() ),
int( mGridSpacingY * newLast->cellHeight() ));
TQPoint cpos = gridToContents( TQPoint( newLast->cellXLeft(), newLast->cellYTop() ) ) ;
addChild( newLast, cpos.x(), cpos.y() );
} else {
newLast = insertItem( moveItem->incidence(), moveItem->itemDate(),
moveItem->cellXLeft()+1, 0, newY-rows()-1, moveItem->itemPos(), moveItem->itemCount() ) ;
}
moveItem->appendMoveItem( newLast );
newLast->show();
lastItem = newLast;
} else {
moveItem->expandBottom( deltapos.y() );
}
changed=true;
}
if (changed) {
adjustItemPosition( moveItem );
}
moveItem = moveItem->nextMultiItem();
}
} else if (mActionType == RESIZETOP) {
if (mEndCell.y() <= mActionItem->cellYBottom()) {
mActionItem->expandTop(gpos.y() - mEndCell.y());
adjustItemPosition( mActionItem );
}
} else if (mActionType == RESIZEBOTTOM) {
if (mEndCell.y() >= mActionItem->cellYTop()) {
mActionItem->expandBottom(gpos.y() - mEndCell.y());
adjustItemPosition( mActionItem );
}
} else if (mActionType == RESIZELEFT) {
if (mEndCell.x() <= mActionItem->cellXRight()) {
mActionItem->expandLeft( gpos.x() - mEndCell.x() );
adjustItemPosition( mActionItem );
}
} else if (mActionType == RESIZERIGHT) {
if (mEndCell.x() >= mActionItem->cellXLeft()) {
mActionItem->expandRight(gpos.x() - mEndCell.x());
adjustItemPosition( mActionItem );
}
}
mEndCell = gpos;
}
}
void KOAgenda::endItemAction()
{
// kdDebug(5850) << "KOAgenda::endItemAction() " << endl;
mActionType = NOP;
mScrollUpTimer.stop();
mScrollDownTimer.stop();
setCursor( arrowCursor );
bool multiModify = false;
// FIXME: do the cloning here...
Incidence* inc = mActionItem->incidence();
if ( mStartCell.x() == mEndCell.x() && mStartCell.y() == mEndCell.y() ) {
// not really moved, so stop any change
if ( mItemMoved ) {
mItemMoved = false;
mChanger->endChange( inc, mResPair.first, mResPair.second );
}
}
if ( mItemMoved ) {
Incidence *incToChange = inc;
if ( mActionItem->incidence()->doesRecur() ) {
Incidence* oldIncSaved = inc->clone();
KOGlobals::WhichOccurrences chosenOption;
incToChange = mCalendarView->singleOccurrenceOrAll( inc,
KOGlobals::EDIT,
chosenOption,
mActionItem->itemDate() );
if ( chosenOption == KOGlobals::ONLY_THIS_ONE ||
chosenOption == KOGlobals::ONLY_FUTURE ) {
// FIXME Prompt for this...it is quite possible that the user does not want to broadcast the change
// That prompting dialog will require the ability to suppress/override the mChanger->endChange GroupWare communication though.
int autoAnswerGroupWare = 1; // Send all possible GroupWare messages without prompting
// Store modification information in case it is needed to recreate the changes with a new actionitem...
int mai_xl = mActionItem->cellXLeft();
int mai_xr = mActionItem->cellXRight();
int mai_yt = mActionItem->cellYTop();
int mai_yb = mActionItem->cellYBottom();
multiModify = true;
emit startMultiModify( i18n("Dissociate event from recurrence") );
enableAgendaUpdate( false );
mChanger->addIncidence( incToChange, mResPair.first, mResPair.second, this, autoAnswerGroupWare );
enableAgendaUpdate( true );
KOGlobals::WhatChanged wc = chosenOption == KOGlobals::ONLY_THIS_ONE ?
KOGlobals::RECURRENCE_MODIFIED_ONE_ONLY :
KOGlobals::RECURRENCE_MODIFIED_ALL_FUTURE;
mChanger->changeIncidence( oldIncSaved, inc, wc, this, autoAnswerGroupWare );
// mActionItem does not exist any more, seeing as we just got done deleting it
// (by deleting/replacing the original incidence it was created from through
// user modification of said incidence) above!
// Therefore we have to find the new KOAgendaItem that matches the new incidence
// Then we can apply the saved X/Y settings from the original move operation as shown.
KOAgendaItem *koai_insertedItem;
for ( koai_insertedItem = mItems.first(); koai_insertedItem; koai_insertedItem = mItems.next() ) {
if (koai_insertedItem->incidence() == incToChange) {
selectItem( koai_insertedItem );
mSelectedItem->startMove();
mSelectedItem->setCellY(mai_yt, mai_yb);
mSelectedItem->setCellX(mai_xl, mai_xr);
mActionItem = mSelectedItem;
//mSelectedItem->endMove();
break;
}
}
mActionItem->dissociateFromMultiItem();
mActionItem->setIncidence( incToChange );
}
}
if ( incToChange ) {
mActionItem->endMove();
KOAgendaItem *placeItem = mActionItem->firstMultiItem();
if ( !placeItem ) {
placeItem = mActionItem;
}
KOAgendaItem *modif = placeItem;
TQPtrList<KOAgendaItem> oldconflictItems = placeItem->conflictItems();
KOAgendaItem *item;
for ( item = oldconflictItems.first(); item != 0;
item = oldconflictItems.next() ) {
placeSubCells( item );
}
while ( placeItem ) {
placeSubCells( placeItem );
placeItem = placeItem->nextMultiItem();
}
// Notify about change
// the agenda view will apply the changes to the actual Incidence*!
mChanger->endChange( inc, mResPair.first, mResPair.second );
emit itemModified( modif );
} else {
mActionItem->resetMove();
placeSubCells( mActionItem );
// the item was moved, but not further modified, since it's not recurring
// make sure the view updates anyhow, with the right item
mChanger->endChange( inc, mResPair.first, mResPair.second );
emit itemModified( mActionItem );
}
}
mActionItem = 0;
mResPair = tqMakePair( static_cast<ResourceCalendar *>( 0 ), TQString() );
mItemMoved = false;
if ( multiModify ) {
emit endMultiModify();
}
kdDebug(5850) << "KOAgenda::endItemAction() done" << endl;
}
void KOAgenda::setActionCursor( int actionType, bool acting )
{
switch ( actionType ) {
case MOVE:
if (acting) setCursor( sizeAllCursor );
else setCursor( arrowCursor );
break;
case RESIZETOP:
case RESIZEBOTTOM:
setCursor( sizeVerCursor );
break;
case RESIZELEFT:
case RESIZERIGHT:
setCursor( sizeHorCursor );
break;
default:
setCursor( arrowCursor );
}
}
void KOAgenda::setNoActionCursor( KOAgendaItem *moveItem, const TQPoint& viewportPos )
{
// kdDebug(5850) << "viewportPos: " << viewportPos.x() << "," << viewportPos.y() << endl;
// TQPoint point = viewport()->mapToGlobal(viewportPos);
// kdDebug(5850) << "Global: " << point.x() << "," << point.y() << endl;
// point = clipper()->mapFromGlobal(point);
// kdDebug(5850) << "clipper: " << point.x() << "," << point.y() << endl;
TQPoint pos = viewportToContents( viewportPos );
bool noResize = (moveItem && moveItem->incidence() &&
moveItem->incidence()->type() == "Todo");
KOAgenda::MouseActionType resizeType = MOVE;
if ( !noResize ) resizeType = isInResizeArea( mAllDayMode, pos , moveItem);
setActionCursor( resizeType );
}
/** calculate the width of the column subcells of the given item
*/
double KOAgenda::calcSubCellWidth( KOAgendaItem *item )
{
TQPoint pt, pt1;
pt = gridToContents( TQPoint( item->cellXLeft(), item->cellYTop() ) );
pt1 = gridToContents( TQPoint( item->cellXLeft(), item->cellYTop() ) +
TQPoint( 1, 1 ) );
pt1 -= pt;
int maxSubCells = item->subCells();
double newSubCellWidth;
if ( mAllDayMode ) {
newSubCellWidth = double( pt1.y() ) / maxSubCells;
} else {
newSubCellWidth = double( pt1.x() ) / maxSubCells;
}
return newSubCellWidth;
}
void KOAgenda::adjustItemPosition( KOAgendaItem *item )
{
if (!item) return;
item->resize( int( mGridSpacingX * item->cellWidth() ),
int( mGridSpacingY * item->cellHeight() ) );
int clXLeft = item->cellXLeft();
if ( KOGlobals::self()->reverseLayout() )
clXLeft = item->cellXRight() + 1;
TQPoint cpos = gridToContents( TQPoint( clXLeft, item->cellYTop() ) );
moveChild( item, cpos.x(), cpos.y() );
}
void KOAgenda::placeAgendaItem( KOAgendaItem *item, double subCellWidth )
{
// kdDebug(5850) << "KOAgenda::placeAgendaItem(): " << item->incidence()->summary()
// << " subCellWidth: " << subCellWidth << endl;
// "left" upper corner, no subcells yet, RTL layouts have right/left switched, widths are negative then
TQPoint pt = gridToContents( TQPoint( item->cellXLeft(), item->cellYTop() ) );
// right lower corner
TQPoint pt1 = gridToContents( TQPoint( 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 (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 ( KOGlobals::self()->reverseLayout() ) { // RTL language/layout
xpos += width;
width = -width;
}
if ( height<0 ) { // BTT (bottom-to-top) layout ?!?
ypos += height;
height = -height;
}
item->resize( width, height );
moveChild( item, 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 KOAgenda::placeSubCells( KOAgendaItem *placeItem )
{
#if 0
kdDebug(5850) << "KOAgenda::placeSubCells()" << endl;
if ( placeItem ) {
Incidence *event = placeItem->incidence();
if ( !event ) {
kdDebug(5850) << " event is 0" << endl;
} else {
kdDebug(5850) << " event: " << event->summary() << endl;
}
} else {
kdDebug(5850) << " placeItem is 0" << endl;
}
kdDebug(5850) << "KOAgenda::placeSubCells()..." << endl;
#endif
TQPtrList<KOrg::CellItem> cells;
KOAgendaItem *item;
for ( item = mItems.first(); item != 0; item = mItems.next() ) {
cells.append( item );
}
TQPtrList<KOrg::CellItem> items = KOrg::CellItem::placeItem( cells,
placeItem );
placeItem->setConflictItems( TQPtrList<KOAgendaItem>() );
double newSubCellWidth = calcSubCellWidth( placeItem );
KOrg::CellItem *i;
for ( i = items.first(); i; i = items.next() ) {
item = static_cast<KOAgendaItem *>( i );
placeAgendaItem( item, newSubCellWidth );
item->addConflictItem( placeItem );
placeItem->addConflictItem( item );
}
if ( items.isEmpty() ) {
placeAgendaItem( placeItem, newSubCellWidth );
}
placeItem->update();
}
int KOAgenda::columnWidth( int column )
{
int start = gridToContents( TQPoint( column, 0 ) ).x();
if (KOGlobals::self()->reverseLayout() )
column--;
else
column++;
int end = gridToContents( TQPoint( column, 0 ) ).x();
return end - start;
}
/*
Draw grid in the background of the agenda.
*/
void KOAgenda::drawContents(TQPainter* p, int cx, int cy, int cw, int ch)
{
TQPixmap db(cw, ch);
db.fill(KOPrefs::instance()->mAgendaBgColor);
TQPainter dbp(&db);
dbp.translate(-cx,-cy);
// kdDebug(5850) << "KOAgenda::drawContents()" << endl;
double lGridSpacingY = mGridSpacingY*2;
// Highlight working hours
if (mWorkingHoursEnable) {
TQPoint pt1( cx, mWorkingHoursYTop );
TQPoint pt2( cx+cw, 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 = ( KOGlobals::self()->reverseLayout()?1:0 );
while( gxStart <= gxEnd ) {
int xStart = gridToContents( TQPoint( gxStart+xoffset, 0 ) ).x();
int xWidth = columnWidth( gxStart ) + 1;
if ( pt2.y() < pt1.y() ) {
// overnight working hours
if ( ( (gxStart==0) && !mHolidayMask->at(mHolidayMask->count()-1) ) ||
( (gxStart>0) && (gxStart<int(mHolidayMask->count())) && (!mHolidayMask->at(gxStart-1) ) ) ) {
if ( pt2.y() > cy ) {
dbp.fillRect( xStart, cy, xWidth, pt2.y() - cy + 1,
KOPrefs::instance()->mWorkingHoursColor);
}
}
if ( (gxStart < int(mHolidayMask->count()-1)) && (!mHolidayMask->at(gxStart)) ) {
if ( pt1.y() < cy + ch - 1 ) {
dbp.fillRect( xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1,
KOPrefs::instance()->mWorkingHoursColor);
}
}
} else {
// last entry in holiday mask denotes the previous day not visible (needed for overnight shifts)
if ( gxStart < int(mHolidayMask->count()-1) && !mHolidayMask->at(gxStart)) {
dbp.fillRect( xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1,
KOPrefs::instance()->mWorkingHoursColor );
}
}
++gxStart;
}
}
}
// draw selection
if ( mHasSelection ) {
TQPoint pt, pt1;
if ( mSelectionEndCell.x() > mSelectionStartCell.x() ) { // multi day selection
// draw start day
pt = gridToContents( mSelectionStartCell );
pt1 = gridToContents( TQPoint( mSelectionStartCell.x() + 1, mRows + 1 ) );
dbp.fillRect( TQRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
// draw all other days between the start day and the day of the selection end
for ( int c = mSelectionStartCell.x() + 1; c < mSelectionEndCell.x(); ++c ) {
pt = gridToContents( TQPoint( c, 0 ) );
pt1 = gridToContents( TQPoint( c + 1, mRows + 1 ) );
dbp.fillRect( TQRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
}
// draw end day
pt = gridToContents( TQPoint( mSelectionEndCell.x(), 0 ) );
pt1 = gridToContents( mSelectionEndCell + TQPoint(1,1) );
dbp.fillRect( TQRect( pt, pt1), KOPrefs::instance()->mHighlightColor );
} else { // single day selection
pt = gridToContents( mSelectionStartCell );
pt1 = gridToContents( mSelectionEndCell + TQPoint(1,1) );
dbp.fillRect( TQRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
}
}
TQPen hourPen( KOPrefs::instance()->mAgendaBgColor.dark( 150 ) );
TQPen halfHourPen( KOPrefs::instance()->mAgendaBgColor.dark( 125 ) );
dbp.setPen( hourPen );
// Draw vertical lines of grid, start with the last line not yet visible
// kdDebug(5850) << "drawContents cx: " << cx << " cy: " << cy << " cw: " << cw << " ch: " << ch << endl;
double x = ( int( cx / mGridSpacingX ) ) * mGridSpacingX;
while (x < cx + cw) {
dbp.drawLine( int( x ), cy, int( x ), cy + ch );
x+=mGridSpacingX;
}
// Draw horizontal lines of grid
double y = ( int( cy / (2*lGridSpacingY) ) ) * 2 * lGridSpacingY;
while (y < cy + ch) {
// kdDebug(5850) << " y: " << y << endl;
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) {
// kdDebug(5850) << " y: " << y << endl;
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.
*/
TQPoint KOAgenda::contentsToGrid ( const TQPoint &pos ) const
{
int gx = int( KOGlobals::self()->reverseLayout() ?
mColumns - pos.x()/mGridSpacingX : pos.x()/mGridSpacingX );
int gy = int( pos.y()/mGridSpacingY );
return TQPoint( gx, gy );
}
/*
Convert agenda grid coordinates to scrollview contents coordinates.
*/
TQPoint KOAgenda::gridToContents( const TQPoint &gpos ) const
{
int x = int( KOGlobals::self()->reverseLayout() ?
(mColumns - gpos.x())*mGridSpacingX : gpos.x()*mGridSpacingX );
int y = int( gpos.y()*mGridSpacingY );
return TQPoint( x, y );
}
/*
Return Y coordinate corresponding to time. Coordinates are rounded to fit into
the grid.
*/
int KOAgenda::timeToY(const TQTime &time)
{
// kdDebug(5850) << "Time: " << time.toString() << endl;
int minutesPerCell = 24 * 60 / mRows;
// kdDebug(5850) << "minutesPerCell: " << minutesPerCell << endl;
int timeMinutes = time.hour() * 60 + time.minute();
// kdDebug(5850) << "timeMinutes: " << timeMinutes << endl;
int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell;
// kdDebug(5850) << "y: " << Y << endl;
// kdDebug(5850) << "\n" << endl;
return Y;
}
/*
Return time corresponding to cell y coordinate. Coordinates are rounded to
fit into the grid.
*/
TQTime KOAgenda::gyToTime(int gy)
{
// kdDebug(5850) << "gyToTime: " << gy << endl;
int secondsPerCell = 24 * 60 * 60/ mRows;
int timeSeconds = secondsPerCell * gy;
TQTime time( 0, 0, 0 );
if ( timeSeconds < 24 * 60 * 60 ) {
time = time.addSecs(timeSeconds);
} else {
time.setHMS( 23, 59, 59 );
}
// kdDebug(5850) << " gyToTime: " << time.toString() << endl;
return time;
}
TQMemArray<int> KOAgenda::minContentsY()
{
TQMemArray<int> minArray;
minArray.fill( timeToY( TQTime(23, 59) ), mSelectedDates.count() );
for ( KOAgendaItem *item = mItems.first();
item != 0; item = mItems.next() ) {
int ymin = item->cellYTop();
int index = item->cellXLeft();
if ( index>=0 && index<(int)(mSelectedDates.count()) ) {
if ( ymin < minArray[index] && mItemsToDelete.findRef( item ) == -1 )
minArray[index] = ymin;
}
}
return minArray;
}
TQMemArray<int> KOAgenda::maxContentsY()
{
TQMemArray<int> maxArray;
maxArray.fill( timeToY( TQTime(0, 0) ), mSelectedDates.count() );
for ( KOAgendaItem *item = mItems.first();
item != 0; item = mItems.next() ) {
int ymax = item->cellYBottom();
int index = item->cellXLeft();
if ( index>=0 && index<(int)(mSelectedDates.count()) ) {
if ( ymax > maxArray[index] && mItemsToDelete.findRef( item ) == -1 )
maxArray[index] = ymax;
}
}
return maxArray;
}
void KOAgenda::setStartTime( const TQTime &startHour )
{
double startPos = ( startHour.hour()/24. + startHour.minute()/1440. +
startHour.second()/86400. ) * mRows * gridSpacingY();
setContentsPos( 0, int( startPos ) );
}
/*
Insert KOAgendaItem into agenda.
*/
KOAgendaItem *KOAgenda::insertItem( Incidence *incidence, const TQDate &qd, int X,
int YTop, int YBottom, int itemPos, int itemCount )
{
if ( mAllDayMode ) {
kdDebug(5850) << "KOAgenda: calling insertItem in all-day mode is illegal." << endl;
return 0;
}
mActionType = NOP;
KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, incidence, qd, viewport(), itemPos, itemCount );
connect( agendaItem, TQT_SIGNAL( removeAgendaItem( KOAgendaItem * ) ),
TQT_SLOT( removeAgendaItem( KOAgendaItem * ) ) );
connect( agendaItem, TQT_SIGNAL( showAgendaItem( KOAgendaItem * ) ),
TQT_SLOT( showAgendaItem( KOAgendaItem * ) ) );
if ( YBottom <= YTop ) {
kdDebug(5850) << "KOAgenda::insertItem(): Text: " << agendaItem->text() << " YSize<0" << endl;
YBottom = YTop;
}
agendaItem->resize( int( ( X + 1 ) * mGridSpacingX ) -
int( X * mGridSpacingX ),
int( YTop * mGridSpacingY ) -
int( ( YBottom + 1 ) * mGridSpacingY ) );
agendaItem->setCellXY( X, YTop, YBottom );
agendaItem->setCellXRight( X );
agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, incidence ) );
agendaItem->installEventFilter( this );
addChild( agendaItem, int( X * mGridSpacingX ), int( YTop * mGridSpacingY ) );
mItems.append( agendaItem );
placeSubCells( agendaItem );
agendaItem->show();
marcus_bains();
return agendaItem;
}
/*
Insert all-day KOAgendaItem into agenda.
*/
KOAgendaItem *KOAgenda::insertAllDayItem( Incidence *event, const TQDate &qd,
int XBegin, int XEnd )
{
if ( !mAllDayMode ) {
kdDebug(5850) << "KOAgenda: calling insertAllDayItem in non all-day mode is illegal." << endl;
return 0;
}
mActionType = NOP;
KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, event, qd, viewport(), 1, 1 );
connect( agendaItem, TQT_SIGNAL( removeAgendaItem( KOAgendaItem* ) ),
TQT_SLOT( removeAgendaItem( KOAgendaItem* ) ) );
connect( agendaItem, TQT_SIGNAL( showAgendaItem( KOAgendaItem* ) ),
TQT_SLOT( showAgendaItem( KOAgendaItem* ) ) );
agendaItem->setCellXY( XBegin, 0, 0 );
agendaItem->setCellXRight( XEnd );
double startIt = mGridSpacingX * ( agendaItem->cellXLeft() );
double endIt = mGridSpacingX * ( agendaItem->cellWidth() +
agendaItem->cellXLeft() );
agendaItem->resize( int( endIt ) - int( startIt ), int( mGridSpacingY ) );
agendaItem->installEventFilter( this );
agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, event ) );
addChild( agendaItem, int( XBegin * mGridSpacingX ), 0 );
mItems.append( agendaItem );
placeSubCells( agendaItem );
agendaItem->show();
return agendaItem;
}
void KOAgenda::insertMultiItem( Event *event, const TQDate &qd, int XBegin, int XEnd,
int YTop, int YBottom )
{
if ( mAllDayMode ) {
kdDebug(5850) << "KOAgenda: calling insertMultiItem in all-day mode is illegal." << endl;
return;
}
mActionType = NOP;
int cellX,cellYTop,cellYBottom;
TQString newtext;
int width = XEnd - XBegin + 1;
int count = 0;
KOAgendaItem *current = 0;
TQPtrList<KOAgendaItem> multiItems;
const int visibleCount = mSelectedDates.first().daysTo( 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 = TQString("(%1/%2): ").arg( count ).arg( width );
newtext.append( event->summary() );
current = insertItem( event, qd, cellX, cellYTop, cellYBottom, count, width );
current->setText( newtext );
multiItems.append( current );
}
}
TQPtrList<KOAgendaItem>::iterator it = multiItems.begin();
TQPtrList<KOAgendaItem>::iterator e = multiItems.end();
if ( it != e ) { // .first asserts if the list is empty
KOAgendaItem *first = multiItems.first();
KOAgendaItem *last = multiItems.last();
KOAgendaItem *prev = 0, *next = 0;
while ( it != e ) {
KOAgendaItem *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 KOAgenda::removeIncidence( Incidence *incidence )
{
// First find all items to be deleted and store them
// in its own list. Otherwise removeAgendaItem will reset
// the current position and mess this up.
TQPtrList<KOAgendaItem> itemsToRemove;
KOAgendaItem *item = mItems.first();
while ( item ) {
if ( item->incidence() == incidence ) {
itemsToRemove.append( item );
}
item = mItems.next();
}
item = itemsToRemove.first();
while ( item ) {
removeAgendaItem( item );
item = itemsToRemove.next();
}
}
void KOAgenda::showAgendaItem( KOAgendaItem *agendaItem )
{
if ( !agendaItem ) {
return;
}
agendaItem->hide();
addChild( agendaItem );
if ( !mItems.containsRef( agendaItem ) ) {
mItems.append( agendaItem );
}
placeSubCells( agendaItem );
agendaItem->show();
}
bool KOAgenda::removeAgendaItem( KOAgendaItem *item )
{
// we found the item. Let's remove it and update the conflicts
bool taken = false;
KOAgendaItem *thisItem = item;
TQPtrList<KOAgendaItem> conflictItems = thisItem->conflictItems();
removeChild( thisItem );
int pos = mItems.find( thisItem );
if ( pos >= 0 ) {
mItems.take( pos );
taken = true;
}
KOAgendaItem *confitem;
for ( confitem = conflictItems.first(); confitem != 0;
confitem = conflictItems.next() ) {
// the item itself is also in its own conflictItems list!
if ( confitem != thisItem ) placeSubCells(confitem);
}
mItemsToDelete.append( thisItem );
TQTimer::singleShot( 0, this, TQT_SLOT( deleteItemsToDelete() ) );
return taken;
}
void KOAgenda::deleteItemsToDelete()
{
mItemsToDelete.clear();
}
/*TQSizePolicy KOAgenda::sizePolicy() const
{
// Thought this would make the all-day event agenda minimum size and the
// normal agenda take the remaining space. But it doesnt work. The TQSplitter
// dont seem to think that an Expanding widget needs more space than a
// Preferred one.
// But it doesnt hurt, so it stays.
if (mAllDayMode) {
return TQSizePolicy(TQSizePolicy::Expanding,TQSizePolicy::Preferred);
} else {
return TQSizePolicy(TQSizePolicy::Expanding,TQSizePolicy::Expanding);
}
}
*/
/*
Overridden from TQScrollView to provide proper resizing of KOAgendaItems.
*/
void KOAgenda::resizeEvent ( TQResizeEvent *ev )
{
// kdDebug(5850) << "KOAgenda::resizeEvent" << endl;
TQSize newSize( ev->size() );
if (mAllDayMode) {
mGridSpacingX = double( newSize.width() - 2 * frameWidth() ) / (double)mColumns;
mGridSpacingY = newSize.height() - 2 * frameWidth();
} else {
int scrollbarWidth = vScrollBarMode() != AlwaysOff ? verticalScrollBar()->width() : 0;
mGridSpacingX = double( newSize.width() - scrollbarWidth - 2 * frameWidth()) / double(mColumns);
// make sure that there are not more than 24 per day
mGridSpacingY = double(newSize.height() - 2 * frameWidth()) / double(mRows);
if ( mGridSpacingY < mDesiredGridSpacingY )
mGridSpacingY = mDesiredGridSpacingY;
}
calculateWorkingHours();
TQTimer::singleShot( 0, this, TQT_SLOT( resizeAllContents() ) );
emit gridSpacingYChanged( mGridSpacingY * 4 );
TQScrollView::resizeEvent(ev);
}
void KOAgenda::resizeAllContents()
{
double subCellWidth;
if ( mItems.count() > 0 ) {
KOAgendaItem *item;
if (mAllDayMode) {
for ( item=mItems.first(); item != 0; item=mItems.next() ) {
subCellWidth = calcSubCellWidth( item );
placeAgendaItem( item, subCellWidth );
}
} else {
for ( item=mItems.first(); item != 0; item=mItems.next() ) {
subCellWidth = calcSubCellWidth( item );
placeAgendaItem( item, subCellWidth );
}
}
}
checkScrollBoundaries();
marcus_bains();
}
void KOAgenda::scrollUp()
{
scrollBy(0,-mScrollOffset);
}
void KOAgenda::scrollDown()
{
scrollBy(0,mScrollOffset);
}
/*
Calculates the minimum width
*/
int KOAgenda::minimumWidth() const
{
// FIXME:: develop a way to dynamically determine the minimum width
int min = 100;
return min;
}
void KOAgenda::updateConfig()
{
double oldGridSpacingY = mGridSpacingY;
mDesiredGridSpacingY = KOPrefs::instance()->mHourSize;
if ( mDesiredGridSpacingY < 4 || mDesiredGridSpacingY > 30 ) {
mDesiredGridSpacingY = 10;
}
// make sure that there are not more than 24 per day
mGridSpacingY = (double)height() / (double)mRows;
if ( mGridSpacingY < mDesiredGridSpacingY ) {
mGridSpacingY = mDesiredGridSpacingY;
}
//can be two doubles equal?, it's better to compare them with an epsilon
if ( fabs( oldGridSpacingY - mGridSpacingY ) > 0.1 ) {
resizeContents( int( mGridSpacingX * mColumns ),
int( mGridSpacingY * mRows ) );
}
calculateWorkingHours();
marcus_bains();
}
void KOAgenda::checkScrollBoundaries()
{
// Invalidate old values to force update
mOldLowerScrollValue = -1;
mOldUpperScrollValue = -1;
checkScrollBoundaries(verticalScrollBar()->value());
}
void KOAgenda::checkScrollBoundaries( int v )
{
int yMin = int( (v) / mGridSpacingY );
int yMax = int( ( v + visibleHeight() ) / mGridSpacingY );
// kdDebug(5850) << "--- yMin: " << yMin << " yMax: " << yMax << endl;
if ( yMin != mOldLowerScrollValue ) {
mOldLowerScrollValue = yMin;
emit lowerYChanged(yMin);
}
if ( yMax != mOldUpperScrollValue ) {
mOldUpperScrollValue = yMax;
emit upperYChanged(yMax);
}
}
int KOAgenda::visibleContentsYMin()
{
int v = verticalScrollBar()->value();
return int( v / mGridSpacingY );
}
int KOAgenda::visibleContentsYMax()
{
int v = verticalScrollBar()->value();
return int( ( v + visibleHeight() ) / mGridSpacingY );
}
void KOAgenda::deselectItem()
{
if ( mSelectedItem.isNull() ) {
return;
}
mSelectedItem->select(false);
mSelectedItem = 0;
}
void KOAgenda::selectItem(KOAgendaItem *item)
{
if ((KOAgendaItem *)mSelectedItem == item) return;
deselectItem();
if (item == 0) {
emit incidenceSelected( 0, TQDate() );
return;
}
mSelectedItem = item;
mSelectedItem->select();
assert( mSelectedItem->incidence() );
mSelectedUid = mSelectedItem->incidence()->uid();
emit incidenceSelected( mSelectedItem->incidence(), mSelectedItem->itemDate() );
}
void KOAgenda::selectItemByUID( const TQString& uid )
{
KOAgendaItem *item;
for ( item = mItems.first(); item != 0; item = mItems.next() ) {
if( item->incidence() && item->incidence()->uid() == uid ) {
selectItem( item );
break;
}
}
}
// This function seems never be called.
void KOAgenda::keyPressEvent( TQKeyEvent *kev )
{
switch(kev->key()) {
case Key_PageDown:
verticalScrollBar()->addPage();
break;
case Key_PageUp:
verticalScrollBar()->subtractPage();
break;
case Key_Down:
verticalScrollBar()->addLine();
break;
case Key_Up:
verticalScrollBar()->subtractLine();
break;
default:
;
}
}
void KOAgenda::calculateWorkingHours()
{
mWorkingHoursEnable = !mAllDayMode;
TQTime tmp = KOPrefs::instance()->mWorkingHoursStart.time();
mWorkingHoursYTop = int( 4 * mGridSpacingY *
( tmp.hour() + tmp.minute() / 60. +
tmp.second() / 3600. ) );
tmp = KOPrefs::instance()->mWorkingHoursEnd.time();
mWorkingHoursYBottom = int( 4 * mGridSpacingY *
( tmp.hour() + tmp.minute() / 60. +
tmp.second() / 3600. ) - 1 );
}
DateList KOAgenda::dateList() const
{
return mSelectedDates;
}
void KOAgenda::setDateList(const DateList &selectedDates)
{
mSelectedDates = selectedDates;
marcus_bains();
}
void KOAgenda::setHolidayMask(TQMemArray<bool> *mask)
{
mHolidayMask = mask;
}
void KOAgenda::contentsMousePressEvent ( TQMouseEvent *event )
{
kdDebug(5850) << "KOagenda::contentsMousePressEvent(): type: " << event->type() << endl;
TQScrollView::contentsMousePressEvent(event);
}
void KOAgenda::setTypeAheadReceiver( TQObject *o )
{
mTypeAheadReceiver = o;
}
TQObject *KOAgenda::typeAheadReceiver() const
{
return mTypeAheadReceiver;
}