korganizer

koagenda.cpp

00001 /*
00002     This file is part of KOrganizer.
00003     Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00004     Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00005 
00006     Marcus Bains line.
00007     Copyright (c) 2001 Ali Rahimi
00008 
00009     This program is free software; you can redistribute it and/or modify
00010     it under the terms of the GNU General Public License as published by
00011     the Free Software Foundation; either version 2 of the License, or
00012     (at your option) any later version.
00013 
00014     This program is distributed in the hope that it will be useful,
00015     but WITHOUT ANY WARRANTY; without even the implied warranty of
00016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00017     GNU General Public License for more details.
00018 
00019     You should have received a copy of the GNU General Public License
00020     along with this program; if not, write to the Free Software
00021     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00022 
00023     As a special exception, permission is given to link this program
00024     with any edition of Qt, and distribute the resulting executable,
00025     without including the source code for Qt in the source distribution.
00026 */
00027 #include <assert.h>
00028 
00029 #include <qintdict.h>
00030 #include <qdatetime.h>
00031 #include <qapplication.h>
00032 #include <qpopupmenu.h>
00033 #include <qcursor.h>
00034 #include <qpainter.h>
00035 #include <qlabel.h>
00036 
00037 #include <kdebug.h>
00038 #include <klocale.h>
00039 #include <kiconloader.h>
00040 #include <kglobal.h>
00041 #include <kmessagebox.h>
00042 
00043 #include "koagendaitem.h"
00044 #include "koprefs.h"
00045 #include "koglobals.h"
00046 #include "komessagebox.h"
00047 #include "incidencechanger.h"
00048 #include "kohelper.h"
00049 
00050 #include "koagendaview.h"
00051 #include "koagenda.h"
00052 #include "koagenda.moc"
00053 #include <korganizer/baseview.h>
00054 
00055 #include <libkcal/event.h>
00056 #include <libkcal/todo.h>
00057 #include <libkcal/dndfactory.h>
00058 #include <libkcal/icaldrag.h>
00059 #include <libkcal/vcaldrag.h>
00060 #include <libkcal/calendar.h>
00061 #include <libkcal/calendarresources.h>
00062 #include <libkcal/calhelper.h>
00063 #include <math.h>
00064 
00065 static void freeItemList( const AgendaItemList &list )
00066 {
00067   AgendaItemList::ConstIterator it;
00068   for ( it = list.begin(); it != list.end(); ++it ) {
00069     delete static_cast<KOAgendaItem*>( *it );
00070   }
00071 }
00072 
00074 MarcusBains::MarcusBains(KOAgenda *_agenda,const char *name )
00075     : QFrame(_agenda->viewport(), name), agenda(_agenda)
00076 {
00077   setLineWidth(0);
00078   setMargin(0);
00079   setBackgroundColor(Qt::red);
00080   minutes = new QTimer(this);
00081   connect(minutes, SIGNAL(timeout()), this, SLOT(updateLocation()));
00082   minutes->start(0, true);
00083 
00084   mTimeBox = new QLabel(this);
00085   mTimeBox->setAlignment(Qt::AlignRight | Qt::AlignBottom);
00086   QPalette pal = mTimeBox->palette();
00087   pal.setColor(QColorGroup::Foreground, Qt::red);
00088   mTimeBox->setPalette(pal);
00089   mTimeBox->setAutoMask(true);
00090 
00091   agenda->addChild(mTimeBox);
00092 
00093   mOldTime = QTime( 0, 0 );
00094   mOldToday = -1;
00095 }
00096 
00097 MarcusBains::~MarcusBains()
00098 {
00099   delete minutes;
00100 }
00101 
00102 int MarcusBains::todayColumn()
00103 {
00104   QDate currentDate = QDate::currentDate();
00105 
00106   DateList dateList = agenda->dateList();
00107   DateList::ConstIterator it;
00108   int col = 0;
00109   for(it = dateList.begin(); it != dateList.end(); ++it) {
00110     if((*it) == currentDate)
00111       return KOGlobals::self()->reverseLayout() ?
00112              agenda->columns() - 1 - col : col;
00113     ++col;
00114   }
00115 
00116   return -1;
00117 }
00118 
00119 void MarcusBains::updateLocation()
00120 {
00121   updateLocationRecalc();
00122 }
00123 
00124 void MarcusBains::updateLocationRecalc( bool recalculate )
00125 {
00126   QTime tim = QTime::currentTime();
00127   if((tim.hour() == 0) && (mOldTime.hour()==23))
00128     recalculate = true;
00129 
00130   const int mins = tim.hour()*60 + tim.minute();
00131   const int minutesPerCell = 24 * 60 / agenda->rows();
00132   int y = int( mins * agenda->gridSpacingY() / minutesPerCell );
00133   const int today = recalculate ? todayColumn() : mOldToday;
00134   int x = int( agenda->gridSpacingX() * today );
00135 
00136   mOldTime = tim;
00137   mOldToday = today;
00138 
00139   const bool hideIt = !( KOPrefs::instance()->mMarcusBainsEnabled );
00140 
00141   if ( !isHidden() && ( hideIt || ( today < 0 ) ) ) {
00142     hide();
00143     mTimeBox->hide();
00144     return;
00145   }
00146 
00147   if ( isHidden() && !hideIt ) {
00148     show();
00149     mTimeBox->show();
00150   }
00151 
00152   if ( recalculate ) setFixedSize( int( agenda->gridSpacingX() ), 1 );
00153   agenda->moveChild( this, x, y );
00154   raise();
00155 
00156   if(recalculate)
00157     mTimeBox->setFont(KOPrefs::instance()->mMarcusBainsFont);
00158 
00159   QString timeStr = KGlobal::locale()->formatTime(tim, KOPrefs::instance()->mMarcusBainsShowSeconds);
00160   QFontMetrics fm = fontMetrics();
00161   mTimeBox->setText( timeStr );
00162   const QSize sz( fm.width( timeStr + ' ' ), fm.height() );
00163   mTimeBox->setFixedSize( sz );
00164 
00165   if (y-mTimeBox->height()>=0) y-=mTimeBox->height(); else y++;
00166   if (x-mTimeBox->width()+agenda->gridSpacingX() > 0)
00167     x += int( agenda->gridSpacingX() - mTimeBox->width() - 1 );
00168   else x++;
00169   agenda->moveChild(mTimeBox,x,y);
00170   mTimeBox->raise();
00171   mTimeBox->setAutoMask(true);
00172 
00173   minutes->start(1000,true);
00174 }
00175 
00176 
00178 
00179 
00180 /*
00181   Create an agenda widget with rows rows and columns columns.
00182 */
00183 KOAgenda::KOAgenda( int columns, int rows, int rowSize, CalendarView *calendarView,
00184                     KOAgendaView *agendaView, QWidget *parent, const char *name,
00185                     WFlags f )
00186   : QScrollView( parent, name, f ), mChanger( 0 ), mAgendaView( agendaView )
00187 {
00188   mColumns = columns;
00189   mRows = rows;
00190   mGridSpacingY = rowSize;
00191   if ( mGridSpacingY < 4 || mGridSpacingY > 30 ) {
00192     mGridSpacingY = 10;
00193   }
00194 
00195   mCalendarView = calendarView;
00196 
00197   mAllDayMode = false;
00198 
00199   init();
00200 
00201   viewport()->setMouseTracking(true);
00202 }
00203 
00204 /*
00205   Create an agenda widget with columns columns and one row. This is used for
00206   all-day events.
00207 */
00208 KOAgenda::KOAgenda( int columns, CalendarView *calendarView, KOAgendaView *agendaView,
00209                     QWidget *parent, const char *name,
00210                     WFlags f ) : QScrollView( parent, name, f ), mAgendaView( agendaView )
00211 {
00212   mColumns = columns;
00213   mRows = 1;
00214   mGridSpacingY = 24;
00215   mAllDayMode = true;
00216   mCalendarView = calendarView;
00217   setVScrollBarMode( AlwaysOff );
00218 
00219   init();
00220 }
00221 
00222 
00223 KOAgenda::~KOAgenda()
00224 {
00225   delete mMarcusBains;
00226 }
00227 
00228 
00229 Incidence *KOAgenda::selectedIncidence() const
00230 {
00231   return ( mSelectedItem ? mSelectedItem->incidence() : 0 );
00232 }
00233 
00234 
00235 QDate KOAgenda::selectedIncidenceDate() const
00236 {
00237   return ( mSelectedItem ? mSelectedItem->itemDate() : QDate() );
00238 }
00239 
00240 const QString KOAgenda::lastSelectedUid() const
00241 {
00242   return mSelectedUid;
00243 }
00244 
00245 
00246 void KOAgenda::init()
00247 {
00248   mGridSpacingX = 100;
00249   mDesiredGridSpacingY = KOPrefs::instance()->mHourSize;
00250   if ( mDesiredGridSpacingY < 4 || mDesiredGridSpacingY > 30 ) {
00251     mDesiredGridSpacingY = 10;
00252   }
00253 
00254  // make sure that there are not more than 24 per day
00255   mGridSpacingY = (double)height() / (double)mRows;
00256   if ( mGridSpacingY < mDesiredGridSpacingY ) {
00257     mGridSpacingY = mDesiredGridSpacingY;
00258   }
00259 
00260   mResizeBorderWidth = 8;
00261   mScrollBorderWidth = 8;
00262   mScrollDelay = 30;
00263   mScrollOffset = 10;
00264 
00265   enableClipper( true );
00266 
00267   // Grab key strokes for keyboard navigation of agenda. Seems to have no
00268   // effect. Has to be fixed.
00269   setFocusPolicy( WheelFocus );
00270 
00271   connect( &mScrollUpTimer, SIGNAL( timeout() ), SLOT( scrollUp() ) );
00272   connect( &mScrollDownTimer, SIGNAL( timeout() ), SLOT( scrollDown() ) );
00273 
00274   mStartCell = QPoint( 0, 0 );
00275   mEndCell = QPoint( 0, 0 );
00276 
00277   mHasSelection = false;
00278   mSelectionStartPoint = QPoint( 0, 0 );
00279   mSelectionStartCell = QPoint( 0, 0 );
00280   mSelectionEndCell = QPoint( 0, 0 );
00281 
00282   mOldLowerScrollValue = -1;
00283   mOldUpperScrollValue = -1;
00284 
00285   mClickedItem = 0;
00286 
00287   mActionItem = 0;
00288   mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00289   mActionType = NOP;
00290   mItemMoved = false;
00291 
00292   mSelectedItem = 0;
00293   mSelectedUid = QString::null;
00294 
00295   setAcceptDrops( true );
00296   installEventFilter( this );
00297 
00298   resizeContents( int( mGridSpacingX * mColumns ),
00299                   int( mGridSpacingY * mRows ) );
00300 
00301   viewport()->update();
00302   viewport()->setBackgroundMode( NoBackground );
00303   viewport()->setFocusPolicy( WheelFocus );
00304 
00305   setMinimumSize( 30, int( mGridSpacingY + 1 ) );
00306 //  setMaximumHeight(mGridSpacingY * mRows + 5);
00307 
00308   // Disable horizontal scrollbar. This is a hack. The geometry should be
00309   // controlled in a way that the contents horizontally always fits. Then it is
00310   // not necessary to turn off the scrollbar.
00311   setHScrollBarMode( AlwaysOff );
00312 
00313   setStartTime( KOPrefs::instance()->mDayBegins.time() );
00314 
00315   calculateWorkingHours();
00316 
00317   connect( verticalScrollBar(), SIGNAL( valueChanged( int ) ),
00318            SLOT( checkScrollBoundaries( int ) ) );
00319 
00320   // Create the Marcus Bains line.
00321   if( mAllDayMode ) {
00322     mMarcusBains = 0;
00323   } else {
00324     mMarcusBains = new MarcusBains( this );
00325     addChild( mMarcusBains );
00326   }
00327 
00328   mTypeAhead = false;
00329   mTypeAheadReceiver = 0;
00330 
00331   mReturnPressed = false;
00332 }
00333 
00334 
00335 void KOAgenda::clear()
00336 {
00337 //  kdDebug(5850) << "KOAgenda::clear()" << endl;
00338 
00339   AgendaItemList::Iterator it;
00340   for ( it = mItems.begin(); it != mItems.end(); ++it  ) {
00341     if ( *it ) {
00342       removeChild( *it );
00343     }
00344   }
00345   freeItemList( mItems );
00346   freeItemList( mItemsToDelete );
00347 
00348   mItems.clear();
00349   mItemsToDelete.clear();
00350 
00351   mSelectedItem = 0;
00352 
00353   clearSelection();
00354 }
00355 
00356 
00357 void KOAgenda::clearSelection()
00358 {
00359   mHasSelection = false;
00360   mActionType = NOP;
00361   updateContents();
00362 }
00363 
00364 void KOAgenda::marcus_bains()
00365 {
00366   if(mMarcusBains) mMarcusBains->updateLocationRecalc( true );
00367 }
00368 
00369 
00370 void KOAgenda::changeColumns(int columns)
00371 {
00372   if (columns == 0) {
00373     kdDebug(5850) << "KOAgenda::changeColumns() called with argument 0" << endl;
00374     return;
00375   }
00376 
00377   clear();
00378   mColumns = columns;
00379 //  setMinimumSize(mColumns * 10, mGridSpacingY + 1);
00380 //  init();
00381 //  update();
00382 
00383   QResizeEvent event( size(), size() );
00384 
00385   QApplication::sendEvent( this, &event );
00386 }
00387 
00388 /*
00389   This is the eventFilter function, which gets all events from the KOAgendaItems
00390   contained in the agenda. It has to handle moving and resizing for all items.
00391 */
00392 bool KOAgenda::eventFilter ( QObject *object, QEvent *event )
00393 {
00394 //  kdDebug(5850) << "KOAgenda::eventFilter() " << int( event->type() ) << endl;
00395 
00396   switch( event->type() ) {
00397     case QEvent::MouseButtonPress:
00398     case QEvent::MouseButtonDblClick:
00399     case QEvent::MouseButtonRelease:
00400     case QEvent::MouseMove:
00401       return eventFilter_mouse( object, static_cast<QMouseEvent *>( event ) );
00402 #ifndef QT_NO_WHEELEVENT
00403     case QEvent::Wheel:
00404       return eventFilter_wheel( object, static_cast<QWheelEvent *>( event ) );
00405 #endif
00406     case QEvent::KeyPress:
00407     case QEvent::KeyRelease:
00408       return eventFilter_key( object, static_cast<QKeyEvent *>( event ) );
00409 
00410     case ( QEvent::Leave ):
00411       if ( !mActionItem )
00412         setCursor( arrowCursor );
00413       if ( object == viewport() )
00414         emit leaveAgenda();
00415       return true;
00416 
00417     case QEvent::Enter:
00418       emit enterAgenda();
00419       return QScrollView::eventFilter( object, event );
00420 
00421 #ifndef KORG_NODND
00422     case QEvent::DragEnter:
00423     case QEvent::DragMove:
00424     case QEvent::DragLeave:
00425     case QEvent::Drop:
00426  //   case QEvent::DragResponse:
00427       return eventFilter_drag(object, static_cast<QDropEvent*>(event));
00428 #endif
00429 
00430     default:
00431       return QScrollView::eventFilter( object, event );
00432   }
00433 }
00434 
00435 bool KOAgenda::eventFilter_drag( QObject *object, QDropEvent *de )
00436 {
00437 #ifndef KORG_NODND
00438   QPoint viewportPos;
00439   if ( object != viewport() && object != this ) {
00440     viewportPos = static_cast<QWidget *>( object )->mapToParent( de->pos() );
00441   } else {
00442     viewportPos = de->pos();
00443   }
00444 
00445   switch ( de->type() ) {
00446     case QEvent::DragEnter:
00447     case QEvent::DragMove:
00448       if ( ICalDrag::canDecode( de ) || VCalDrag::canDecode( de ) ) {
00449 
00450         DndFactory factory( mCalendar );
00451         Todo *todo = factory.createDropTodo( de );
00452         if ( todo ) {
00453           de->accept();
00454           delete todo;
00455         } else {
00456           de->ignore();
00457         }
00458         return true;
00459       } else return false;
00460       break;
00461     case QEvent::DragLeave:
00462       return false;
00463       break;
00464     case QEvent::Drop:
00465       {
00466         if ( !ICalDrag::canDecode( de ) && !VCalDrag::canDecode( de ) ) {
00467           return false;
00468         }
00469 
00470         DndFactory factory( mCalendar );
00471         Todo *todo = factory.createDropTodo( de );
00472 
00473         if ( todo ) {
00474           de->acceptAction();
00475           QPoint pos;
00476           // FIXME: This is a bad hack, as the viewportToContents seems to be off by
00477           // 2000 (which is the left upper corner of the viewport). It works correctly
00478           // for agendaItems.
00479           if ( object == this  ) {
00480             pos = viewportPos + QPoint( contentsX(), contentsY() );
00481           } else {
00482             pos = viewportToContents( viewportPos );
00483           }
00484           QPoint gpos = contentsToGrid( pos );
00485           emit droppedToDo( todo, gpos, mAllDayMode );
00486           return true;
00487         }
00488       }
00489       break;
00490 
00491     case QEvent::DragResponse:
00492     default:
00493       break;
00494   }
00495 #endif
00496 
00497   return false;
00498 }
00499 
00500 bool KOAgenda::eventFilter_key( QObject *, QKeyEvent *ke )
00501 {
00502   // kdDebug(5850) << "KOAgenda::eventFilter_key() " << ke->type() << endl;
00503 
00504   // If Return is pressed bring up an editor for the current selected time span.
00505   if ( ke->key() == Key_Return ) {
00506     if ( ke->type() == QEvent::KeyPress ) mReturnPressed = true;
00507     else if ( ke->type() == QEvent::KeyRelease ) {
00508       if ( mReturnPressed ) {
00509         emitNewEventForSelection();
00510         mReturnPressed = false;
00511         return true;
00512       } else {
00513         mReturnPressed = false;
00514       }
00515     }
00516   }
00517 
00518   // Ignore all input that does not produce any output
00519   if ( ke->text().isEmpty() ) return false;
00520 
00521   if ( ke->type() == QEvent::KeyPress || ke->type() == QEvent::KeyRelease ) {
00522     switch ( ke->key() ) {
00523       case Key_Escape:
00524       case Key_Return:
00525       case Key_Enter:
00526       case Key_Tab:
00527       case Key_Backtab:
00528       case Key_Left:
00529       case Key_Right:
00530       case Key_Up:
00531       case Key_Down:
00532       case Key_Backspace:
00533       case Key_Delete:
00534       case Key_Prior:
00535       case Key_Next:
00536       case Key_Home:
00537       case Key_End:
00538       case Key_Control:
00539       case Key_Meta:
00540       case Key_Alt:
00541         break;
00542       default:
00543         mTypeAheadEvents.append( new QKeyEvent( ke->type(), ke->key(),
00544                                                 ke->ascii(), ke->state(),
00545                                                 ke->text(), ke->isAutoRepeat(),
00546                                                 ke->count() ) );
00547         if ( !mTypeAhead ) {
00548           mTypeAhead = true;
00549           emitNewEventForSelection();
00550         }
00551         return true;
00552     }
00553   }
00554   return false;
00555 }
00556 
00557 void KOAgenda::emitNewEventForSelection()
00558 {
00559   QPair<ResourceCalendar *, QString>p = mCalendarView->viewSubResourceCalendar();
00560   emit newEventSignal( p.first, p.second );
00561 }
00562 
00563 void KOAgenda::finishTypeAhead()
00564 {
00565 //  kdDebug(5850) << "KOAgenda::finishTypeAhead()" << endl;
00566   if ( typeAheadReceiver() ) {
00567     for( QEvent *e = mTypeAheadEvents.first(); e;
00568          e = mTypeAheadEvents.next() ) {
00569 //      kdDebug(5850) << "sendEvent() " << int( typeAheadReceiver() ) << endl;
00570       QApplication::sendEvent( typeAheadReceiver(), e );
00571     }
00572   }
00573   mTypeAheadEvents.clear();
00574   mTypeAhead = false;
00575 }
00576 #ifndef QT_NO_WHEELEVENT
00577 bool KOAgenda::eventFilter_wheel ( QObject *object, QWheelEvent *e )
00578 {
00579   QPoint viewportPos;
00580   bool accepted=false;
00581   if  ( ( e->state() & ShiftButton) == ShiftButton ) {
00582     if ( object != viewport() ) {
00583       viewportPos = ( (QWidget *) object )->mapToParent( e->pos() );
00584     } else {
00585       viewportPos = e->pos();
00586     }
00587     //kdDebug(5850)<< "KOAgenda::eventFilter_wheel: type:"<<
00588     //  e->type()<<" delta: "<< e->delta()<< endl;
00589     emit zoomView( -e->delta() ,
00590       contentsToGrid( viewportToContents( viewportPos ) ),
00591       Qt::Horizontal );
00592     accepted=true;
00593   }
00594 
00595   if  ( ( e->state() & ControlButton ) == ControlButton ){
00596     if ( object != viewport() ) {
00597       viewportPos = ( (QWidget *)object )->mapToParent( e->pos() );
00598     } else {
00599       viewportPos = e->pos();
00600     }
00601     emit zoomView( -e->delta() ,
00602       contentsToGrid( viewportToContents( viewportPos ) ),
00603       Qt::Vertical );
00604     emit mousePosSignal(gridToContents(contentsToGrid(viewportToContents( viewportPos ))));
00605     accepted=true;
00606   }
00607   if (accepted ) e->accept();
00608   return accepted;
00609 }
00610 #endif
00611 bool KOAgenda::eventFilter_mouse(QObject *object, QMouseEvent *me)
00612 {
00613   QPoint viewportPos;
00614   if (object != viewport()) {
00615     viewportPos = ((QWidget *)object)->mapToParent(me->pos());
00616   } else {
00617     viewportPos = me->pos();
00618   }
00619 
00620   switch (me->type())  {
00621     case QEvent::MouseButtonPress:
00622 //      kdDebug(5850) << "koagenda: filtered button press" << endl;
00623       if (object != viewport()) {
00624         if (me->button() == RightButton) {
00625           mClickedItem = dynamic_cast<KOAgendaItem *>(object);
00626           if (mClickedItem) {
00627             selectItem(mClickedItem);
00628             emit showIncidencePopupSignal( mCalendar,
00629                                            mClickedItem->incidence(),
00630                                            mClickedItem->itemDate() );
00631           } else {
00632             return QScrollView::eventFilter( object, me ); // pass through for use by multiagenda
00633           }
00634         } else {
00635           KOAgendaItem* item = dynamic_cast<KOAgendaItem *>(object);
00636           if (item) {
00637             Incidence *incidence = item->incidence();
00638             if ( incidence->isReadOnly() ) {
00639               mActionItem = 0;
00640               mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00641             } else {
00642               mActionItem = item;
00643               mResPair = CalHelper::incSubResourceCalendar( mCalendar, incidence );
00644               startItemAction(viewportPos);
00645             }
00646             // Warning: do selectItem() as late as possible, since all
00647             // sorts of things happen during this call. Some can lead to
00648             // this filter being run again and mActionItem being set to
00649             // null.
00650             selectItem( item );
00651           } else {
00652             return QScrollView::eventFilter( object, me ); // pass through for use by multiagenda
00653           }
00654         }
00655       } else {
00656         if ( me->button() == RightButton ) {
00657           // if mouse pointer is not in selection, select the cell below the cursor
00658           QPoint gpos = contentsToGrid( viewportToContents( viewportPos ) );
00659           if ( !ptInSelection( gpos ) ) {
00660             mSelectionStartCell = gpos;
00661             mSelectionEndCell = gpos;
00662             mHasSelection = true;
00663             emit newStartSelectSignal();
00664             emit newTimeSpanSignal( mSelectionStartCell, mSelectionEndCell );
00665             updateContents();
00666           }
00667           showNewEventPopupSignal();
00668         } else {
00669           // if mouse pointer is in selection, don't change selection
00670           QPoint gpos = contentsToGrid( viewportToContents( viewportPos ) );
00671           if ( !ptInSelection( gpos ) ) {
00672             selectItem(0);
00673             mActionItem = 0;
00674             mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00675             setCursor(arrowCursor);
00676             startSelectAction(viewportPos);
00677           }
00678         }
00679         return QScrollView::eventFilter( object, me ); // pass through for use by multiagenda
00680       }
00681       break;
00682 
00683     case QEvent::MouseButtonRelease:
00684       if (mActionItem) {
00685         endItemAction();
00686       } else if ( mActionType == SELECT ) {
00687         endSelectAction( viewportPos );
00688       }
00689       // This nasty gridToContents(contentsToGrid(..)) is needed to
00690       // avoid an offset of a few pixels. Don't ask me why...
00691       emit mousePosSignal( gridToContents(contentsToGrid(
00692                            viewportToContents( viewportPos ) ) ));
00693       break;
00694 
00695     case QEvent::MouseMove: {
00696       // This nasty gridToContents(contentsToGrid(..)) is needed to
00697       // avoid an offset of a few pixels. Don't ask me why...
00698       QPoint indicatorPos = gridToContents(contentsToGrid(
00699                                           viewportToContents( viewportPos )));
00700       if (object != viewport()) {
00701         KOAgendaItem *moveItem = dynamic_cast<KOAgendaItem *>(object);
00702         if (moveItem && !moveItem->incidence()->isReadOnly() ) {
00703           if (!mActionItem)
00704             setNoActionCursor(moveItem,viewportPos);
00705           else {
00706             performItemAction(viewportPos);
00707 
00708             if ( mActionType == MOVE ) {
00709               // show cursor at the current begin of the item
00710               KOAgendaItem *firstItem = mActionItem->firstMultiItem();
00711               if (!firstItem) firstItem = mActionItem;
00712               indicatorPos = gridToContents( QPoint( firstItem->cellXLeft(),
00713                                                      firstItem->cellYTop() ) );
00714 
00715             } else if ( mActionType == RESIZEBOTTOM ) {
00716               // RESIZETOP is handled correctly, only resizebottom works differently
00717               indicatorPos = gridToContents( QPoint( mActionItem->cellXLeft(),
00718                                                      mActionItem->cellYBottom()+1 ) );
00719             }
00720 
00721           } // If we have an action item
00722         } // If move item && !read only
00723       } else {
00724         if ( mActionType == SELECT ) {
00725           performSelectAction( viewportPos );
00726 
00727           // show cursor at end of timespan
00728           if ( ((mStartCell.y() < mEndCell.y()) && (mEndCell.x() >= mStartCell.x())) ||
00729                (mEndCell.x() > mStartCell.x()) )
00730             indicatorPos = gridToContents( QPoint(mEndCell.x(), mEndCell.y()+1) );
00731           else
00732             indicatorPos = gridToContents( mEndCell );
00733         }
00734       }
00735       emit mousePosSignal( indicatorPos );
00736       break; }
00737 
00738     case QEvent::MouseButtonDblClick:
00739       if (object == viewport()) {
00740         selectItem(0);
00741         QPair<ResourceCalendar *, QString>p = mCalendarView->viewSubResourceCalendar();
00742         emit newEventSignal( p.first, p.second );
00743       } else {
00744         KOAgendaItem *doubleClickedItem = dynamic_cast<KOAgendaItem *>( object );
00745         if ( doubleClickedItem ) {
00746           selectItem( doubleClickedItem );
00747           emit editIncidenceSignal( doubleClickedItem->incidence(), doubleClickedItem->itemDate() );
00748         }
00749       }
00750       break;
00751 
00752     default:
00753       break;
00754   }
00755 
00756   return true;
00757 }
00758 
00759 bool KOAgenda::ptInSelection( QPoint gpos ) const
00760 {
00761   if ( !mHasSelection ) {
00762     return false;
00763   } else if ( gpos.x()<mSelectionStartCell.x() || gpos.x()>mSelectionEndCell.x() ) {
00764     return false;
00765   } else if ( (gpos.x()==mSelectionStartCell.x()) && (gpos.y()<mSelectionStartCell.y()) ) {
00766     return false;
00767   } else if ( (gpos.x()==mSelectionEndCell.x()) && (gpos.y()>mSelectionEndCell.y()) ) {
00768     return false;
00769   }
00770   return true;
00771 }
00772 
00773 void KOAgenda::startSelectAction( const QPoint &viewportPos )
00774 {
00775   emit newStartSelectSignal();
00776 
00777   mActionType = SELECT;
00778   mSelectionStartPoint = viewportPos;
00779   mHasSelection = true;
00780 
00781   QPoint pos = viewportToContents( viewportPos );
00782   QPoint gpos = contentsToGrid( pos );
00783 
00784   // Store new selection
00785   mStartCell = gpos;
00786   mEndCell = gpos;
00787   mSelectionStartCell = gpos;
00788   mSelectionEndCell = gpos;
00789 
00790   updateContents();
00791 }
00792 
00793 void KOAgenda::performSelectAction(const QPoint& viewportPos)
00794 {
00795   QPoint pos = viewportToContents( viewportPos );
00796   QPoint gpos = contentsToGrid( pos );
00797 
00798   QPoint clipperPos = clipper()->
00799                       mapFromGlobal(viewport()->mapToGlobal(viewportPos));
00800 
00801   // Scroll if cursor was moved to upper or lower end of agenda.
00802   if (clipperPos.y() < mScrollBorderWidth) {
00803     mScrollUpTimer.start(mScrollDelay);
00804   } else if (visibleHeight() - clipperPos.y() <
00805              mScrollBorderWidth) {
00806     mScrollDownTimer.start(mScrollDelay);
00807   } else {
00808     mScrollUpTimer.stop();
00809     mScrollDownTimer.stop();
00810   }
00811 
00812   if ( gpos != mEndCell ) {
00813     mEndCell = gpos;
00814     if ( mStartCell.x()>mEndCell.x() ||
00815          ( mStartCell.x()==mEndCell.x() && mStartCell.y()>mEndCell.y() ) ) {
00816       // backward selection
00817       mSelectionStartCell = mEndCell;
00818       mSelectionEndCell = mStartCell;
00819     } else {
00820       mSelectionStartCell = mStartCell;
00821       mSelectionEndCell = mEndCell;
00822     }
00823 
00824     updateContents();
00825   }
00826 }
00827 
00828 void KOAgenda::endSelectAction( const QPoint &currentPos )
00829 {
00830   mScrollUpTimer.stop();
00831   mScrollDownTimer.stop();
00832 
00833   mActionType = NOP;
00834 
00835   emit newTimeSpanSignal( mSelectionStartCell, mSelectionEndCell );
00836 
00837   if ( KOPrefs::instance()->mSelectionStartsEditor ) {
00838     if ( ( mSelectionStartPoint - currentPos ).manhattanLength() >
00839          QApplication::startDragDistance() ) {
00840        emitNewEventForSelection();
00841     }
00842   }
00843 }
00844 
00845 KOAgenda::MouseActionType KOAgenda::isInResizeArea( bool horizontal,
00846                                                     const QPoint &pos,
00847                                                     KOAgendaItem::GPtr item )
00848 {
00849   if ( !item ) {
00850     return NOP;
00851   }
00852 
00853   QPoint gridpos = contentsToGrid( pos );
00854   QPoint contpos = gridToContents( gridpos +
00855       QPoint( (KOGlobals::self()->reverseLayout())?1:0, 0 ) );
00856 
00857 //kdDebug(5850)<<"contpos="<<contpos<<", pos="<<pos<<", gpos="<<gpos<<endl;
00858 //kdDebug(5850)<<"clXLeft="<<clXLeft<<", clXRight="<<clXRight<<endl;
00859 
00860   if ( horizontal ) {
00861     int clXLeft = item->cellXLeft();
00862     int clXRight = item->cellXRight();
00863     if ( KOGlobals::self()->reverseLayout() ) {
00864       int tmp = clXLeft;
00865       clXLeft = clXRight;
00866       clXRight = tmp;
00867     }
00868     int gridDistanceX = int( pos.x() - contpos.x() );
00869     if (gridDistanceX < mResizeBorderWidth && clXLeft == gridpos.x() ) {
00870       if ( KOGlobals::self()->reverseLayout() ) return RESIZERIGHT;
00871       else return RESIZELEFT;
00872     } else if ((mGridSpacingX - gridDistanceX) < mResizeBorderWidth &&
00873                clXRight == gridpos.x() ) {
00874       if ( KOGlobals::self()->reverseLayout() ) return RESIZELEFT;
00875       else return RESIZERIGHT;
00876     } else {
00877       return MOVE;
00878     }
00879   } else {
00880     int gridDistanceY = int( pos.y() - contpos.y() );
00881     if (gridDistanceY < mResizeBorderWidth &&
00882         item->cellYTop() == gridpos.y() &&
00883         !item->firstMultiItem() ) {
00884       return RESIZETOP;
00885     } else if ((mGridSpacingY - gridDistanceY) < mResizeBorderWidth &&
00886                item->cellYBottom() == gridpos.y() &&
00887                !item->lastMultiItem() )  {
00888       return RESIZEBOTTOM;
00889     } else {
00890       return MOVE;
00891     }
00892   }
00893 }
00894 
00895 void KOAgenda::startItemAction(const QPoint& viewportPos)
00896 {
00897   QPoint pos = viewportToContents( viewportPos );
00898   mStartCell = contentsToGrid( pos );
00899   mEndCell = mStartCell;
00900 
00901   bool noResize = ( mActionItem->incidence()->type() == "Todo");
00902 
00903   mActionType = MOVE;
00904   if ( !noResize ) {
00905     mActionType = isInResizeArea( mAllDayMode, pos, mActionItem );
00906   }
00907 
00908 
00909   mActionItem->startMove();
00910   setActionCursor( mActionType, true );
00911 }
00912 
00913 void KOAgenda::performItemAction(const QPoint& viewportPos)
00914 {
00915 //  kdDebug(5850) << "viewportPos: " << viewportPos.x() << "," << viewportPos.y() << endl;
00916 //  QPoint point = viewport()->mapToGlobal(viewportPos);
00917 //  kdDebug(5850) << "Global: " << point.x() << "," << point.y() << endl;
00918 //  point = clipper()->mapFromGlobal(point);
00919 //  kdDebug(5850) << "clipper: " << point.x() << "," << point.y() << endl;
00920 //  kdDebug(5850) << "visible height: " << visibleHeight() << endl;
00921   QPoint pos = viewportToContents( viewportPos );
00922 //  kdDebug(5850) << "contents: " << x << "," << y << "\n" << endl;
00923   QPoint gpos = contentsToGrid( pos );
00924   QPoint clipperPos = clipper()->
00925                       mapFromGlobal(viewport()->mapToGlobal(viewportPos));
00926 
00927   // Cursor left active agenda area.
00928   // This starts a drag.
00929   if ( clipperPos.y() < 0 || clipperPos.y() > visibleHeight() ||
00930        clipperPos.x() < 0 || clipperPos.x() > visibleWidth() ) {
00931     if ( mActionType == MOVE ) {
00932       mScrollUpTimer.stop();
00933       mScrollDownTimer.stop();
00934       mActionItem->resetMove();
00935       placeSubCells( mActionItem );
00936       emit startDragSignal( mActionItem->incidence() );
00937       setCursor( arrowCursor );
00938       mActionItem = 0;
00939       mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00940       mActionType = NOP;
00941       mItemMoved = false;
00942       return;
00943     }
00944   } else {
00945     setActionCursor( mActionType );
00946   }
00947 
00948   // Scroll if item was moved to upper or lower end of agenda.
00949   if (clipperPos.y() < mScrollBorderWidth) {
00950     mScrollUpTimer.start(mScrollDelay);
00951   } else if (visibleHeight() - clipperPos.y() <
00952              mScrollBorderWidth) {
00953     mScrollDownTimer.start(mScrollDelay);
00954   } else {
00955     mScrollUpTimer.stop();
00956     mScrollDownTimer.stop();
00957   }
00958 
00959   // Move or resize item if necessary
00960   if ( mEndCell != gpos ) {
00961     if ( !mItemMoved ) {
00962       if ( !mChanger ||
00963            !mChanger->beginChange( mActionItem->incidence(), mResPair.first, mResPair.second ) ) {
00964         KMessageBox::information( this, i18n("Unable to lock item for "
00965                              "modification. You cannot make any changes."),
00966                              i18n("Locking Failed"), "AgendaLockingFailed" );
00967         mScrollUpTimer.stop();
00968         mScrollDownTimer.stop();
00969         mActionItem->resetMove();
00970         placeSubCells( mActionItem );
00971         setCursor( arrowCursor );
00972         mActionItem = 0;
00973         mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00974         mActionType = NOP;
00975         mItemMoved = false;
00976         return;
00977       }
00978       mItemMoved = true;
00979     }
00980     mActionItem->raise();
00981     if (mActionType == MOVE) {
00982       // Move all items belonging to a multi item
00983       KOAgendaItem *firstItem = mActionItem->firstMultiItem();
00984       if (!firstItem) firstItem = mActionItem;
00985       KOAgendaItem *lastItem = mActionItem->lastMultiItem();
00986       if (!lastItem) lastItem = mActionItem;
00987       QPoint deltapos = gpos - mEndCell;
00988       KOAgendaItem *moveItem = firstItem;
00989       while (moveItem) {
00990         bool changed=false;
00991         if ( deltapos.x()!=0 ) {
00992           moveItem->moveRelative( deltapos.x(), 0 );
00993           changed=true;
00994         }
00995         // in agenda's all day view don't try to move multi items, since there are none
00996         if ( moveItem==firstItem && !mAllDayMode ) { // is the first item
00997           int newY = deltapos.y() + moveItem->cellYTop();
00998           // If event start moved earlier than 0:00, it starts the previous day
00999           if ( newY<0 ) {
01000             moveItem->expandTop( -moveItem->cellYTop() );
01001             // prepend a new item at ( x-1, rows()+newY to rows() )
01002             KOAgendaItem *newFirst = firstItem->prevMoveItem();
01003             // cell's y values are first and last cell of the bar, so if newY=-1, they need to be the same
01004             if (newFirst) {
01005               newFirst->setCellXY(moveItem->cellXLeft()-1, rows()+newY, rows()-1);
01006               mItems.append( newFirst );
01007               moveItem->resize( int( mGridSpacingX * newFirst->cellWidth() ),
01008                                 int( mGridSpacingY * newFirst->cellHeight() ));
01009               QPoint cpos = gridToContents( QPoint( newFirst->cellXLeft(), newFirst->cellYTop() ) );
01010               addChild( newFirst, cpos.x(), cpos.y() );
01011             } else {
01012               newFirst = insertItem( moveItem->incidence(), moveItem->itemDate(),
01013                 moveItem->cellXLeft()-1, rows()+newY, rows()-1, moveItem->itemPos(), moveItem->itemCount() ) ;
01014             }
01015             if (newFirst) newFirst->show();
01016             moveItem->prependMoveItem(newFirst);
01017             firstItem=newFirst;
01018           } else if ( newY>=rows() ) {
01019             // If event start is moved past 24:00, it starts the next day
01020             // erase current item (i.e. remove it from the multiItem list)
01021             firstItem = moveItem->nextMultiItem();
01022             moveItem->hide();
01023             mItems.remove( mItems.find( moveItem ) ); // Don't free memory, QPtrList::take() was used here.
01024             removeChild( moveItem );
01025             mActionItem->removeMoveItem(moveItem);
01026             moveItem=firstItem;
01027             // adjust next day's item
01028             if (moveItem) moveItem->expandTop( rows()-newY );
01029           } else {
01030             moveItem->expandTop(deltapos.y());
01031           }
01032           changed=true;
01033         }
01034         if ( !moveItem->lastMultiItem() && !mAllDayMode ) { // is the last item
01035           int newY = deltapos.y()+moveItem->cellYBottom();
01036           if (newY<0) {
01037             // erase current item
01038             lastItem = moveItem->prevMultiItem();
01039             moveItem->hide();
01040             mItems.remove( mItems.find(moveItem) ); // Don't free memory, QPtrList::take() was used here.
01041             removeChild( moveItem );
01042             moveItem->removeMoveItem( moveItem );
01043             moveItem = lastItem;
01044             moveItem->expandBottom(newY+1);
01045           } else if (newY>=rows()) {
01046             moveItem->expandBottom( rows()-moveItem->cellYBottom()-1 );
01047             // append item at ( x+1, 0 to newY-rows() )
01048             KOAgendaItem *newLast = lastItem->nextMoveItem();
01049             if (newLast) {
01050               newLast->setCellXY( moveItem->cellXLeft()+1, 0, newY-rows()-1 );
01051               mItems.append(newLast);
01052               moveItem->resize( int( mGridSpacingX * newLast->cellWidth() ),
01053                                 int( mGridSpacingY * newLast->cellHeight() ));
01054               QPoint cpos = gridToContents( QPoint( newLast->cellXLeft(), newLast->cellYTop() ) ) ;
01055               addChild( newLast, cpos.x(), cpos.y() );
01056             } else {
01057               newLast = insertItem( moveItem->incidence(), moveItem->itemDate(),
01058                 moveItem->cellXLeft()+1, 0, newY-rows()-1, moveItem->itemPos(), moveItem->itemCount() ) ;
01059             }
01060             moveItem->appendMoveItem( newLast );
01061             newLast->show();
01062             lastItem = newLast;
01063           } else {
01064             moveItem->expandBottom( deltapos.y() );
01065           }
01066           changed=true;
01067         }
01068         if (changed) {
01069           adjustItemPosition( moveItem );
01070         }
01071         moveItem = moveItem->nextMultiItem();
01072       }
01073     } else if (mActionType == RESIZETOP) {
01074       if (mEndCell.y() <= mActionItem->cellYBottom()) {
01075         mActionItem->expandTop(gpos.y() - mEndCell.y());
01076         adjustItemPosition( mActionItem );
01077       }
01078     } else if (mActionType == RESIZEBOTTOM) {
01079       if (mEndCell.y() >= mActionItem->cellYTop()) {
01080         mActionItem->expandBottom(gpos.y() - mEndCell.y());
01081         adjustItemPosition( mActionItem );
01082       }
01083     } else if (mActionType == RESIZELEFT) {
01084       if (mEndCell.x() <= mActionItem->cellXRight()) {
01085         mActionItem->expandLeft( gpos.x() - mEndCell.x() );
01086         adjustItemPosition( mActionItem );
01087       }
01088     } else if (mActionType == RESIZERIGHT) {
01089       if (mEndCell.x() >= mActionItem->cellXLeft()) {
01090         mActionItem->expandRight(gpos.x() - mEndCell.x());
01091         adjustItemPosition( mActionItem );
01092       }
01093     }
01094     mEndCell = gpos;
01095   }
01096 }
01097 
01098 void KOAgenda::endItemAction()
01099 {
01100 //  kdDebug(5850) << "KOAgenda::endItemAction() " << endl;
01101   mActionType = NOP;
01102   mScrollUpTimer.stop();
01103   mScrollDownTimer.stop();
01104   setCursor( arrowCursor );
01105   bool useLastGroupwareDialogAnswer = false;
01106   bool addIncidence = false;
01107   bool multiModify = false;
01108   // FIXME: do the cloning here...
01109   Incidence *inc = mActionItem->incidence();
01110 
01111   if ( mStartCell.x() == mEndCell.x() && mStartCell.y() == mEndCell.y() ) {
01112     // not really moved, so stop any change
01113     if ( mItemMoved ) {
01114       mItemMoved = false;
01115       mChanger->endChange( inc, mResPair.first, mResPair.second );
01116     }
01117   }
01118 
01119   if ( mItemMoved ) {
01120     Incidence *incToChange = inc;
01121     if ( mActionItem->incidence()->doesRecur() ) {
01122 
01123       kdDebug() << "mActionItem->incidence()->dtStart() is " << inc->dtStart() << endl;
01124 
01125       Incidence* oldIncSaved = inc->clone();
01126       KOGlobals::WhichOccurrences chosenOption;
01127       inc->startUpdates();
01128       incToChange = mCalendarView->singleOccurrenceOrAll( inc,
01129                                                           KOGlobals::EDIT,
01130                                                           chosenOption,
01131                                                           mActionItem->itemDate() );
01132 
01133       if ( chosenOption == KOGlobals::ONLY_THIS_ONE ||
01134            chosenOption == KOGlobals::ONLY_FUTURE ) {
01135         multiModify = true;
01136         mAgendaView->enableAgendaUpdate( false );
01137         useLastGroupwareDialogAnswer = true;
01138         addIncidence = true;
01139         mAgendaView->enableAgendaUpdate( true );
01140         const KOGlobals::WhatChanged whatChanged = chosenOption == KOGlobals::ONLY_THIS_ONE ?
01141                                                            KOGlobals::RECURRENCE_MODIFIED_ONE_ONLY :
01142                                                           KOGlobals::RECURRENCE_MODIFIED_ALL_FUTURE;
01143 
01144         const bool success = mChanger->changeIncidence( oldIncSaved, inc, whatChanged, this );
01145 
01146         if ( success ) {
01147           mActionItem->dissociateFromMultiItem();
01148           mActionItem->setIncidence( incToChange );
01149           inc->endUpdates();
01150         } else {
01151           inc->cancelUpdates();
01152           incToChange = 0;
01153         }
01154       } else {
01155         inc->endUpdates();
01156       }
01157     }
01158 
01159     if ( incToChange ) {
01160       mActionItem->endMove();
01161       KOAgendaItem *placeItem = mActionItem->firstMultiItem();
01162       if  ( !placeItem ) {
01163         placeItem = mActionItem;
01164       }
01165 
01166       KOAgendaItem *modif = placeItem;
01167       AgendaItemList oldconflictItems = placeItem->conflictItems();
01168 
01169       AgendaItemList::Iterator it;
01170       for ( it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it ) {
01171         if ( *it ) {
01172           placeSubCells( *it );
01173         }
01174       }
01175 
01176       while ( placeItem ) {
01177         placeSubCells( placeItem );
01178         placeItem = placeItem->nextMultiItem();
01179       }
01180 
01181       // Notify about change
01182       // the agenda view will apply the changes to the actual Incidence*!
01183       mChanger->endChange( inc, mResPair.first, mResPair.second );
01184       kdDebug() << "Modified." << endl;
01185       mAgendaView->updateEventDates( modif, useLastGroupwareDialogAnswer, mResPair.first, mResPair.second, addIncidence );
01186     } else {
01187       mActionItem->resetMove();
01188       placeSubCells( mActionItem );
01189 
01190       // the item was moved, but not further modified, since it's not recurring
01191       // make sure the view updates anyhow, with the right item
01192       mChanger->endChange( inc, mResPair.first, mResPair.second );
01193       kdDebug() << "Not modified." << endl;
01194       mAgendaView->updateEventDates( mActionItem,
01195                                      useLastGroupwareDialogAnswer,
01196                                      mResPair.first,
01197                                      mResPair.second,
01198                                      addIncidence );
01199     }
01200   }
01201 
01202   mActionItem = 0;
01203   mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
01204   mItemMoved = false;
01205 
01206   if ( multiModify ) {
01207     emit endMultiModify();
01208   }
01209 
01210   kdDebug(5850) << "KOAgenda::endItemAction() done" << endl;
01211 }
01212 
01213 void KOAgenda::setActionCursor( int actionType, bool acting )
01214 {
01215   switch ( actionType ) {
01216     case MOVE:
01217       if (acting) setCursor( sizeAllCursor );
01218       else setCursor( arrowCursor );
01219       break;
01220     case RESIZETOP:
01221     case RESIZEBOTTOM:
01222       setCursor( sizeVerCursor );
01223       break;
01224     case RESIZELEFT:
01225     case RESIZERIGHT:
01226       setCursor( sizeHorCursor );
01227       break;
01228     default:
01229       setCursor( arrowCursor );
01230   }
01231 }
01232 
01233 void KOAgenda::setNoActionCursor( KOAgendaItem::GPtr moveItem, const QPoint& viewportPos )
01234 {
01235 //  kdDebug(5850) << "viewportPos: " << viewportPos.x() << "," << viewportPos.y() << endl;
01236 //  QPoint point = viewport()->mapToGlobal(viewportPos);
01237 //  kdDebug(5850) << "Global: " << point.x() << "," << point.y() << endl;
01238 //  point = clipper()->mapFromGlobal(point);
01239 //  kdDebug(5850) << "clipper: " << point.x() << "," << point.y() << endl;
01240 
01241   if ( !moveItem ) {
01242     return;
01243   }
01244 
01245   QPoint pos = viewportToContents( viewportPos );
01246   bool noResize = (moveItem && moveItem->incidence() &&
01247       moveItem->incidence()->type() == "Todo");
01248 
01249   KOAgenda::MouseActionType resizeType = MOVE;
01250   if ( !noResize ) resizeType = isInResizeArea( mAllDayMode, pos , moveItem);
01251   setActionCursor( resizeType );
01252 }
01253 
01254 
01258 double KOAgenda::calcSubCellWidth( KOAgendaItem::GPtr item )
01259 {
01260   if ( !item ) {
01261     return 0;
01262   }
01263 
01264   QPoint pt, pt1;
01265   pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
01266   pt1 = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) +
01267                         QPoint( 1, 1 ) );
01268   pt1 -= pt;
01269   int maxSubCells = item->subCells();
01270   double newSubCellWidth;
01271   if ( mAllDayMode ) {
01272     newSubCellWidth = double( pt1.y() ) / maxSubCells;
01273   } else {
01274     newSubCellWidth = double( pt1.x() ) / maxSubCells;
01275   }
01276   return newSubCellWidth;
01277 }
01278 
01279 void KOAgenda::adjustItemPosition( KOAgendaItem::GPtr item )
01280 {
01281   if (!item) return;
01282   item->resize( int( mGridSpacingX * item->cellWidth() ),
01283                 int( mGridSpacingY * item->cellHeight() ) );
01284   int clXLeft = item->cellXLeft();
01285   if ( KOGlobals::self()->reverseLayout() )
01286     clXLeft = item->cellXRight() + 1;
01287   QPoint cpos = gridToContents( QPoint( clXLeft, item->cellYTop() ) );
01288   moveChild( item, cpos.x(), cpos.y() );
01289 }
01290 
01291 void KOAgenda::placeAgendaItem( KOAgendaItem::GPtr item, double subCellWidth )
01292 {
01293   // kdDebug(5850) << "KOAgenda::placeAgendaItem(): " << item->incidence()->summary()
01294   //               << " subCellWidth: " << subCellWidth << endl;
01295 
01296   if ( !item ) {
01297     return;
01298   }
01299 
01300   // "left" upper corner, no subcells yet, RTL layouts have right/left switched, widths are negative then
01301   const QPoint pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
01302 
01303   // right lower corner
01304   const QPoint pt1 = gridToContents( QPoint( item->cellXLeft() + item->cellWidth(),
01305                                              item->cellYBottom()+1 ) );
01306 
01307   const double subCellPos = item->subCell() * subCellWidth;
01308 
01309   // we need to add 0.01 to make sure we don't loose one pixed due to
01310   // numerics (i.e. if it would be x.9998, we want the integer, not rounded down.
01311   const double delta = subCellWidth < 0 ? -0.01 : 0.01;
01312 
01313   int height, width, xpos, ypos;
01314   if ( mAllDayMode ) {
01315     width = pt1.x()-pt.x();
01316     height = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
01317     xpos = pt.x();
01318     ypos = pt.y() + int( subCellPos );
01319   } else {
01320     width = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
01321     height = pt1.y()-pt.y();
01322     xpos = pt.x() + int( subCellPos );
01323     ypos = pt.y();
01324   }
01325   if ( KOGlobals::self()->reverseLayout() ) { // RTL language/layout
01326     xpos += width;
01327     width = -width;
01328   }
01329   if ( height<0 ) { // BTT (bottom-to-top) layout ?!?
01330     ypos += height;
01331     height = -height;
01332   }
01333   item->resize( width, height );
01334   moveChild( item, xpos, ypos );
01335 }
01336 
01337 /*
01338   Place item in cell and take care that multiple items using the same cell do
01339   not overlap. This method is not yet optimal. It doesn't use the maximum space
01340   it can get in all cases.
01341   At the moment the method has a bug: When an item is placed only the sub cell
01342   widths of the items are changed, which are within the Y region the item to
01343   place spans. When the sub cell width change of one of this items affects a
01344   cell, where other items are, which do not overlap in Y with the item to place,
01345   the display gets corrupted, although the corruption looks quite nice.
01346 
01347   18/10/2010 UPDATE: this is fixed with to the updateWidthsRecursivley()
01348   call. kolab/issue2762.
01349 */
01350 void KOAgenda::placeSubCells( KOAgendaItem::GPtr placeItem )
01351 {
01352 #if 0
01353   kdDebug(5850) << "KOAgenda::placeSubCells()" << endl;
01354   if ( placeItem ) {
01355     Incidence *event = placeItem->incidence();
01356     if ( !event ) {
01357       kdDebug(5850) << "  event is 0" << endl;
01358     } else {
01359       kdDebug(5850) << "  event: " << event->summary() << endl;
01360     }
01361   } else {
01362     kdDebug(5850) << "  placeItem is 0" << endl;
01363   }
01364   kdDebug(5850) << "KOAgenda::placeSubCells()..." << endl;
01365 #endif
01366 
01367   if ( !placeItem ) {
01368     return;
01369   }
01370 
01371   QPtrList<KOrg::CellItem> cells;
01372   AgendaItemList::Iterator it;
01373   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01374     if ( *it ) {
01375       cells.append( *it );
01376     }
01377   }
01378 
01379   QPtrList<KOrg::CellItem> items = KOrg::CellItem::placeItem( cells,
01380                                                               placeItem );
01381   QPtrList<KOAgendaItem> updatedCells;
01382 
01383   KOrg::CellItem *i;
01384   placeItem->setConflictItems( AgendaItemList() );
01385   const double newSubCellWidth = calcSubCellWidth( placeItem );
01386   for ( i = items.first(); i; i = items.next() ) {
01387     KOAgendaItem *item = static_cast<KOAgendaItem *>( i );
01388     placeAgendaItem( item, newSubCellWidth );
01389     item->addConflictItem( placeItem );
01390 
01391     if ( placeItem->incidence() && placeItem->incidence()->doesFloat() ) {
01392       // untested for non-allday incidences, hence the check.
01393       updateWidthsRecursivley( item, updatedCells, placeItem->subCells() );
01394     }
01395 
01396     placeItem->addConflictItem( item );
01397   }
01398   if ( items.isEmpty() ) {
01399     placeAgendaItem( placeItem, newSubCellWidth );
01400   }
01401   placeItem->update();
01402 }
01403 
01405 bool KOAgenda::updateWidthsRecursivley( KOAgendaItem *item,
01406                                         QPtrList<KOAgendaItem> &itemsAlreadyUpdated,
01407                                         int numSubCells )
01408 {
01409   bool result = false;
01410   if ( !itemsAlreadyUpdated.contains( item ) ) {
01411     item->setSubCells( numSubCells );
01412 
01413     // So we don't end up in infinit recursion due to cycles.
01414     itemsAlreadyUpdated.append( item );
01415 
01416     // Tell our neighbours too.
01417     AgendaItemList list = item->conflictItems();
01418 
01419     for ( int i = 0; i < list.count(); ++i ) {
01420       KOAgendaItem::GPtr it = list[i];
01421       if ( it && numSubCells > it->subCells() ) {
01422         updateWidthsRecursivley( it, itemsAlreadyUpdated, numSubCells );
01423         result = true;
01424       }
01425     }
01426   }
01427 
01428   return result;
01429 }
01430 
01431 int KOAgenda::columnWidth( int column )
01432 {
01433   const int start = gridToContents( QPoint( column, 0 ) ).x();
01434   if (KOGlobals::self()->reverseLayout() )
01435     column--;
01436   else
01437     column++;
01438   const int end = gridToContents( QPoint( column, 0 ) ).x();
01439   return end - start;
01440 }
01441 /*
01442   Draw grid in the background of the agenda.
01443 */
01444 void KOAgenda::drawContents(QPainter* p, int cx, int cy, int cw, int ch)
01445 {
01446   QPixmap db(cw, ch);
01447   db.fill(KOPrefs::instance()->mAgendaBgColor);
01448   QPainter dbp(&db);
01449   dbp.translate(-cx,-cy);
01450 
01451   double lGridSpacingY = mGridSpacingY*2;
01452 
01453   // If work day, use work color
01454   // If busy day, use busy color
01455   // if work and busy day, mix both, and busy color has alpha
01456 
01457   const QMemArray<bool> busyDayMask = mAgendaView->busyDayMask();
01458 
01459   if ( KOPrefs::instance()->mColorBusyDaysEnabled && !mAllDayMode ) {
01460     for ( int i = 0; i < busyDayMask.count(); ++i ) {
01461       if ( busyDayMask[i] ) {
01462         const QPoint pt1( cx + mGridSpacingX * i, 0 );
01463         // const QPoint pt2( cx + mGridSpacingX * ( i+1 ), ch );
01464         dbp.fillRect( pt1.x(), pt1.y(), mGridSpacingX, cy + ch, KOPrefs::instance()->mAgendaMonthBgBusyColor );
01465       }
01466     }
01467   }
01468   // Highlight working hours
01469   if ( mWorkingHoursEnable ) {
01470 
01471     QColor workAndBusyColor;
01472 
01473     if ( KOPrefs::instance()->mColorBusyDaysEnabled ) {
01474       workAndBusyColor = KOHelper::mixColors( KOPrefs::instance()->mAgendaMonthBgBusyColor, 0.60, KOPrefs::instance()->mWorkingHoursColor );
01475     } else {
01476       workAndBusyColor = KOPrefs::instance()->mWorkingHoursColor;
01477     }
01478 
01479     const QPoint pt1( cx, mWorkingHoursYTop );
01480     const QPoint pt2( cx+cw, mWorkingHoursYBottom );
01481     if ( pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) {
01482       int gxStart = contentsToGrid( pt1 ).x();
01483       int gxEnd = contentsToGrid( pt2 ).x();
01484       // correct start/end for rtl layouts
01485       if ( gxStart > gxEnd ) {
01486         const int tmp = gxStart;
01487         gxStart = gxEnd;
01488         gxEnd = tmp;
01489       }
01490       const int xoffset = ( KOGlobals::self()->reverseLayout() ? 1 : 0 );
01491       while( gxStart <= gxEnd ) {
01492         const int xStart = gridToContents( QPoint( gxStart+xoffset, 0 ) ).x();
01493         const int xWidth = columnWidth( gxStart ) + 1;
01494         QColor color;
01495         if ( gxStart < busyDayMask.count() && busyDayMask[gxStart] ) {
01496           color = workAndBusyColor;
01497         } else {
01498           color = KOPrefs::instance()->mWorkingHoursColor;
01499         }
01500         if ( pt2.y() < pt1.y() ) {
01501           // overnight working hours
01502           if ( ( (gxStart==0) && !mHolidayMask->at(mHolidayMask->count()-1) ) ||
01503                ( (gxStart>0) && (gxStart<int(mHolidayMask->count())) && (!mHolidayMask->at(gxStart-1) ) ) ) {
01504             if ( pt2.y() > cy ) {
01505               dbp.fillRect( xStart, cy, xWidth, pt2.y() - cy + 1, color );
01506             }
01507           }
01508           if ( (gxStart < int(mHolidayMask->count()-1)) && (!mHolidayMask->at(gxStart)) ) {
01509             if ( pt1.y() < cy + ch - 1 ) {
01510               dbp.fillRect( xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1,
01511                             color );
01512             }
01513           }
01514         } else {
01515           // last entry in holiday mask denotes the previous day not visible (needed for overnight shifts)
01516           if ( gxStart < int(mHolidayMask->count()-1) && !mHolidayMask->at(gxStart)) {
01517             dbp.fillRect( xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1,
01518                           color );
01519           }
01520         }
01521         ++gxStart;
01522       }
01523     }
01524   }
01525 
01526 
01527 
01528   // draw selection
01529   if ( mHasSelection ) {
01530     QPoint pt, pt1;
01531 
01532     if ( mSelectionEndCell.x() > mSelectionStartCell.x() ) { // multi day selection
01533       // draw start day
01534       pt = gridToContents( mSelectionStartCell );
01535       pt1 = gridToContents( QPoint( mSelectionStartCell.x() + 1, mRows + 1 ) );
01536       dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01537       // draw all other days between the start day and the day of the selection end
01538       for ( int c = mSelectionStartCell.x() + 1; c < mSelectionEndCell.x(); ++c ) {
01539         pt = gridToContents( QPoint( c, 0 ) );
01540         pt1 = gridToContents( QPoint( c + 1, mRows + 1 ) );
01541         dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01542       }
01543       // draw end day
01544       pt = gridToContents( QPoint( mSelectionEndCell.x(), 0 ) );
01545       pt1 = gridToContents( mSelectionEndCell + QPoint(1,1) );
01546       dbp.fillRect( QRect( pt, pt1), KOPrefs::instance()->mHighlightColor );
01547     }  else { // single day selection
01548       pt = gridToContents( mSelectionStartCell );
01549       pt1 = gridToContents( mSelectionEndCell + QPoint(1,1) );
01550       dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01551     }
01552   }
01553 
01554   QPen hourPen( KOPrefs::instance()->mAgendaBgColor.dark( 150 ) );
01555   QPen halfHourPen( KOPrefs::instance()->mAgendaBgColor.dark( 125 ) );
01556   dbp.setPen( hourPen );
01557 
01558   // Draw vertical lines of grid, start with the last line not yet visible
01559   //  kdDebug(5850) << "drawContents cx: " << cx << " cy: " << cy << " cw: " << cw << " ch: " << ch << endl;
01560   double x = ( int( cx / mGridSpacingX ) ) * mGridSpacingX;
01561   while (x < cx + cw) {
01562     dbp.drawLine( int( x ), cy, int( x ), cy + ch );
01563     x+=mGridSpacingX;
01564   }
01565 
01566   // Draw horizontal lines of grid
01567   double y = ( int( cy / (2*lGridSpacingY) ) ) * 2 * lGridSpacingY;
01568   while (y < cy + ch) {
01569 //    kdDebug(5850) << " y: " << y << endl;
01570     dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
01571     y += 2 * lGridSpacingY;
01572   }
01573   y = ( 2 * int( cy / (2*lGridSpacingY) ) + 1) * lGridSpacingY;
01574   dbp.setPen( halfHourPen );
01575   while (y < cy + ch) {
01576 //    kdDebug(5850) << " y: " << y << endl;
01577     dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
01578     y+=2*lGridSpacingY;
01579   }
01580   p->drawPixmap(cx,cy, db);
01581 }
01582 
01583 /*
01584   Convert srcollview contents coordinates to agenda grid coordinates.
01585 */
01586 QPoint KOAgenda::contentsToGrid ( const QPoint &pos ) const
01587 {
01588   int gx = int( KOGlobals::self()->reverseLayout() ?
01589         mColumns - pos.x()/mGridSpacingX : pos.x()/mGridSpacingX );
01590   int gy = int( pos.y()/mGridSpacingY );
01591   return QPoint( gx, gy );
01592 }
01593 
01594 /*
01595   Convert agenda grid coordinates to scrollview contents coordinates.
01596 */
01597 QPoint KOAgenda::gridToContents( const QPoint &gpos ) const
01598 {
01599   int x = int( KOGlobals::self()->reverseLayout() ?
01600              (mColumns - gpos.x())*mGridSpacingX : gpos.x()*mGridSpacingX );
01601   int y = int( gpos.y()*mGridSpacingY );
01602   return QPoint( x, y );
01603 }
01604 
01605 
01606 /*
01607   Return Y coordinate corresponding to time. Coordinates are rounded to fit into
01608   the grid.
01609 */
01610 int KOAgenda::timeToY(const QTime &time)
01611 {
01612 //  kdDebug(5850) << "Time: " << time.toString() << endl;
01613   int minutesPerCell = 24 * 60 / mRows;
01614 //  kdDebug(5850) << "minutesPerCell: " << minutesPerCell << endl;
01615   int timeMinutes = time.hour() * 60 + time.minute();
01616 //  kdDebug(5850) << "timeMinutes: " << timeMinutes << endl;
01617   int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell;
01618 //  kdDebug(5850) << "y: " << Y << endl;
01619 //  kdDebug(5850) << "\n" << endl;
01620   return Y;
01621 }
01622 
01623 
01624 /*
01625   Return time corresponding to cell y coordinate. Coordinates are rounded to
01626   fit into the grid.
01627 */
01628 QTime KOAgenda::gyToTime(int gy)
01629 {
01630 //  kdDebug(5850) << "gyToTime: " << gy << endl;
01631   int secondsPerCell = 24 * 60 * 60/ mRows;
01632 
01633   int timeSeconds = secondsPerCell * gy;
01634 
01635   QTime time( 0, 0, 0 );
01636   if ( timeSeconds < 24 * 60 * 60 ) {
01637     time = time.addSecs(timeSeconds);
01638   } else {
01639     time.setHMS( 23, 59, 59 );
01640   }
01641 //  kdDebug(5850) << "  gyToTime: " << time.toString() << endl;
01642 
01643   return time;
01644 }
01645 
01646 QMemArray<int> KOAgenda::minContentsY()
01647 {
01648   QMemArray<int> minArray;
01649   minArray.fill( timeToY( QTime(23, 59) ), mSelectedDates.count() );
01650 
01651   AgendaItemList::Iterator it;
01652   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01653     if ( *it ) {
01654       const int ymin = (*it)->cellYTop();
01655       const int index = (*it)->cellXLeft();
01656       if ( index >= 0 && index < static_cast<int>( mSelectedDates.count() ) ) {
01657         if ( ymin < minArray[index] && !mItemsToDelete.contains( *it ) )
01658           minArray[index] = ymin;
01659       }
01660     }
01661   }
01662 
01663   return minArray;
01664 }
01665 
01666 QMemArray<int> KOAgenda::maxContentsY()
01667 {
01668   QMemArray<int> maxArray;
01669   maxArray.fill( timeToY( QTime(0, 0) ), mSelectedDates.count() );
01670   AgendaItemList::Iterator it;
01671   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01672     if ( *it ) {
01673       const int ymax = (*it)->cellYBottom();
01674       const int index = (*it)->cellXLeft();
01675       if ( index >= 0 && index < static_cast<int>( mSelectedDates.count() ) ) {
01676         if ( ymax > maxArray[index] && !mItemsToDelete.contains( *it ) )
01677           maxArray[index] = ymax;
01678       }
01679     }
01680   }
01681 
01682   return maxArray;
01683 }
01684 
01685 void KOAgenda::setStartTime( const QTime &startHour )
01686 {
01687   const double startPos = ( startHour.hour()/24. + startHour.minute()/1440. +
01688                             startHour.second()/86400. ) * mRows * gridSpacingY();
01689   setContentsPos( 0, static_cast<int>( startPos ) );
01690 }
01691 
01692 
01693 /*
01694   Insert KOAgendaItem into agenda.
01695 */
01696 KOAgendaItem *KOAgenda::insertItem( Incidence *incidence, const QDate &qd, int X,
01697                                     int YTop, int YBottom, int itemPos, int itemCount )
01698 {
01699   if ( mAllDayMode ) {
01700     kdDebug(5850) << "KOAgenda: calling insertItem in all-day mode is illegal." << endl;
01701     return 0;
01702   }
01703 
01704   mActionType = NOP;
01705 
01706   KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, incidence, qd, viewport(), itemPos, itemCount );
01707   connect( agendaItem, SIGNAL( removeAgendaItem( KOAgendaItem::GPtr  ) ),
01708            SLOT( removeAgendaItem( KOAgendaItem::GPtr  ) ) );
01709   connect( agendaItem, SIGNAL( showAgendaItem( KOAgendaItem::GPtr  ) ),
01710            SLOT( showAgendaItem( KOAgendaItem::GPtr  ) ) );
01711 
01712   if ( YBottom <= YTop ) {
01713     kdDebug(5850) << "KOAgenda::insertItem(): Text: " << agendaItem->text() << " YSize<0" << endl;
01714     YBottom = YTop;
01715   }
01716 
01717   agendaItem->resize( int( ( X + 1 ) * mGridSpacingX ) -
01718                       int( X * mGridSpacingX ),
01719                       int( YTop * mGridSpacingY ) -
01720                       int( ( YBottom + 1 ) * mGridSpacingY ) );
01721   agendaItem->setCellXY( X, YTop, YBottom );
01722   agendaItem->setCellXRight( X );
01723   agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, incidence ) );
01724   agendaItem->installEventFilter( this );
01725 
01726   addChild( agendaItem, int( X * mGridSpacingX ), int( YTop * mGridSpacingY ) );
01727   mItems.append( agendaItem );
01728 
01729   placeSubCells( agendaItem );
01730 
01731   agendaItem->show();
01732 
01733   marcus_bains();
01734 
01735   return agendaItem;
01736 }
01737 
01738 /*
01739   Insert all-day KOAgendaItem into agenda.
01740 */
01741 KOAgendaItem *KOAgenda::insertAllDayItem( Incidence *event, const QDate &qd,
01742                                           int XBegin, int XEnd )
01743 {
01744   if ( !mAllDayMode ) {
01745     kdWarning(5850) << "KOAgenda: calling insertAllDayItem in non all-day mode is illegal." << endl;
01746     return 0;
01747   }
01748 
01749   mActionType = NOP;
01750 
01751   KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, event, qd, viewport(), 1, 1 );
01752   connect( agendaItem, SIGNAL( removeAgendaItem( KOAgendaItem::GPtr ) ),
01753            SLOT( removeAgendaItem( KOAgendaItem::GPtr ) ) );
01754   connect( agendaItem, SIGNAL( showAgendaItem( KOAgendaItem::GPtr ) ),
01755            SLOT( showAgendaItem( KOAgendaItem::GPtr ) ) );
01756 
01757   { // Start hack
01758 
01759     // Fixes https://issues.kolab.org/issue3933 -
01760     // issue3933: A large wholeday event is not shown at all days in dayview.(rt#5884)
01761     // ( Some 32k pixel limit Qt3 has. )
01762     if ( event->doesFloat() && XBegin * mGridSpacingX <= -32767/*32k*/ + mGridSpacingX ) {
01763 
01764       // Our event starts _many_ days before dt, so we lie to agenda and make
01765       // it think the event starts one day before.
01766       // That part of the event won't be visible, so the lie won't have any effect
01767       // for the user
01768       XBegin = -1;
01769     }
01770   } // End hack
01771 
01772   agendaItem->setCellXY( XBegin, 0, 0 );
01773   agendaItem->setCellXRight( XEnd );
01774 
01775   const double startIt = mGridSpacingX * ( agendaItem->cellXLeft() );
01776   const double endIt = mGridSpacingX * ( agendaItem->cellWidth() +
01777                                          agendaItem->cellXLeft() );
01778 
01779   agendaItem->resize( int( endIt ) - int( startIt ), int( mGridSpacingY ) );
01780 
01781   agendaItem->installEventFilter( this );
01782   agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, event ) );
01783   addChild( agendaItem, int( XBegin * mGridSpacingX ), 0 );
01784   mItems.append( agendaItem );
01785 
01786   placeSubCells( agendaItem );
01787 
01788   agendaItem->show();
01789 
01790   return agendaItem;
01791 }
01792 
01793 
01794 void KOAgenda::insertMultiItem( Event *event, const QDate &qd, int XBegin, int XEnd,
01795                                 int YTop, int YBottom )
01796 {
01797   if ( mAllDayMode ) {
01798     kdDebug(5850) << "KOAgenda: calling insertMultiItem in all-day mode is illegal." << endl;
01799     return;
01800   }
01801   mActionType = NOP;
01802 
01803   int cellX,cellYTop,cellYBottom;
01804   QString newtext;
01805   int width = XEnd - XBegin + 1;
01806   int count = 0;
01807   KOAgendaItem *current = 0;
01808   QPtrList<KOAgendaItem> multiItems;
01809   const int visibleCount = mSelectedDates.first().daysTo( mSelectedDates.last() );
01810   for ( cellX = XBegin; cellX <= XEnd; ++cellX ) {
01811     ++count;
01812     //Only add the items that are visible.
01813     if( cellX >= 0 && cellX <= visibleCount ) {
01814       if ( cellX == XBegin ) {
01815         cellYTop = YTop;
01816       } else {
01817         cellYTop = 0;
01818       }
01819 
01820       if ( cellX == XEnd ) {
01821         cellYBottom = YBottom;
01822       } else {
01823         cellYBottom = rows() - 1;
01824       }
01825 
01826       newtext = QString("(%1/%2): ").arg( count ).arg( width );
01827       newtext.append( event->summary() );
01828 
01829       current = insertItem( event, qd, cellX, cellYTop, cellYBottom, count, width );
01830       current->setText( newtext );
01831       multiItems.append( current );
01832     }
01833   }
01834   QPtrList<KOAgendaItem>::iterator it = multiItems.begin();
01835   QPtrList<KOAgendaItem>::iterator e = multiItems.end();
01836 
01837   if ( it != e ) { // .first asserts if the list is empty
01838     KOAgendaItem *first = multiItems.first();
01839     KOAgendaItem *last = multiItems.last();
01840     KOAgendaItem *prev = 0, *next = 0;
01841 
01842     while ( it != e ) {
01843       KOAgendaItem *item = *it;
01844       ++it;
01845       next = ( it == e ) ? 0 : (*it);
01846       if ( item ) {
01847         item->setMultiItem( ( item == first ) ? 0 : first,
01848                             prev, next,
01849                             ( item == last ) ? 0 : last );
01850       }
01851       prev = item;
01852     }
01853   }
01854 
01855   marcus_bains();
01856 }
01857 
01858 void KOAgenda::removeIncidence( Incidence *incidence, bool relayoutNeighbours )
01859 {
01860   // First find all items to be deleted and store them
01861   // in its own list. Otherwise removeAgendaItem will reset
01862   // the current position and mess this up.
01863   QValueList<KOAgendaItem::GPtr > itemsToRemove;
01864 
01865   AgendaItemList::Iterator it;
01866   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01867     if ( *it ) {
01868       if ( (*it)->incidence() == incidence ) {
01869         itemsToRemove.append( *it );
01870       }
01871     }
01872   }
01873 
01874   for ( it = itemsToRemove.begin(); it != itemsToRemove.end(); ++it ) {
01875     removeAgendaItem( *it, relayoutNeighbours );
01876   }
01877 }
01878 
01879 void KOAgenda::showAgendaItem( KOAgendaItem::GPtr agendaItem )
01880 {
01881   if ( !agendaItem ) {
01882     return;
01883   }
01884 
01885   agendaItem->hide();
01886   addChild( agendaItem );
01887   if ( !mItems.contains( agendaItem ) ) {
01888     mItems.append( agendaItem );
01889   }
01890   placeSubCells( agendaItem );
01891 
01892   agendaItem->show();
01893 }
01894 
01895 bool KOAgenda::removeAgendaItem( KOAgendaItem::GPtr item, bool relayoutNeighbours )
01896 {
01897   // we found the item. Let's remove it and update the conflicts
01898   bool taken = false;
01899   KOAgendaItem::GPtr thisItem = item;
01900   AgendaItemList conflictItems = thisItem->conflictItems();
01901   removeChild( thisItem );
01902 
01903   AgendaItemList::Iterator it = mItems.find( thisItem );
01904   if ( it != mItems.end() ) {
01905     mItems.remove( it ); // Don't free memory, QPtrList::take() was used here.
01906     taken = true;
01907   }
01908 
01909   if ( relayoutNeighbours ) {
01910     for ( it = conflictItems.begin(); it != conflictItems.end(); ++it ) {
01911       // the item itself is also in its own conflictItems list!
01912       if ( *it != thisItem ) {
01913         placeSubCells( *it );
01914       }
01915     }
01916   }
01917 
01918   mItemsToDelete.append( thisItem );
01919   QTimer::singleShot( 0, this, SLOT( deleteItemsToDelete() ) );
01920   return taken;
01921 }
01922 
01923 void KOAgenda::deleteItemsToDelete()
01924 {
01925   freeItemList( mItemsToDelete );
01926   mItemsToDelete.clear();
01927 }
01928 
01929 /*QSizePolicy KOAgenda::sizePolicy() const
01930 {
01931   // Thought this would make the all-day event agenda minimum size and the
01932   // normal agenda take the remaining space. But it doesnt work. The QSplitter
01933   // dont seem to think that an Expanding widget needs more space than a
01934   // Preferred one.
01935   // But it doesnt hurt, so it stays.
01936   if (mAllDayMode) {
01937     return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
01938   } else {
01939     return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
01940   }
01941 }
01942 */
01943 
01944 /*
01945   Overridden from QScrollView to provide proper resizing of KOAgendaItems.
01946 */
01947 void KOAgenda::resizeEvent ( QResizeEvent *ev )
01948 {
01949 //  kdDebug(5850) << "KOAgenda::resizeEvent" << endl;
01950 
01951   QSize newSize( ev->size() );
01952   if (mAllDayMode) {
01953     mGridSpacingX = double( newSize.width() - 2 * frameWidth() ) / (double)mColumns;
01954     mGridSpacingY = newSize.height() - 2 * frameWidth();
01955   } else {
01956     int scrollbarWidth = vScrollBarMode() != AlwaysOff ? verticalScrollBar()->width() : 0;
01957     mGridSpacingX = double( newSize.width() - scrollbarWidth - 2 * frameWidth()) / double(mColumns);
01958     // make sure that there are not more than 24 per day
01959     mGridSpacingY = double(newSize.height() - 2 * frameWidth()) / double(mRows);
01960     if ( mGridSpacingY < mDesiredGridSpacingY )
01961       mGridSpacingY = mDesiredGridSpacingY;
01962   }
01963   calculateWorkingHours();
01964   QTimer::singleShot( 0, this, SLOT( resizeAllContents() ) );
01965   emit gridSpacingYChanged( mGridSpacingY * 4 );
01966   QScrollView::resizeEvent(ev);
01967 }
01968 
01969 void KOAgenda::resizeAllContents()
01970 {
01971   // For allday this is actually an height.
01972   double subCellWidth;
01973 
01974   if ( mItems.count() > 0 ) {
01975     KOAgendaItem::GPtr item;
01976     if ( mAllDayMode ) {
01977       AgendaItemList::Iterator it;
01978       for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01979         if ( *it ) {
01980           subCellWidth = calcSubCellWidth( *it );
01981           placeAgendaItem( *it, subCellWidth );
01982         }
01983       }
01984     } else {
01985       AgendaItemList::Iterator it;
01986       for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01987         if ( *it ) {
01988           subCellWidth = calcSubCellWidth( *it );
01989           placeAgendaItem( *it, subCellWidth );
01990         }
01991       }
01992     }
01993   }
01994   checkScrollBoundaries();
01995   marcus_bains();
01996 }
01997 
01998 void KOAgenda::scrollUp()
01999 {
02000   scrollBy(0,-mScrollOffset);
02001 }
02002 
02003 
02004 void KOAgenda::scrollDown()
02005 {
02006   scrollBy(0,mScrollOffset);
02007 }
02008 
02009 
02010 /*
02011   Calculates the minimum width
02012 */
02013 int KOAgenda::minimumWidth() const
02014 {
02015   // FIXME:: develop a way to dynamically determine the minimum width
02016   int min = 100;
02017 
02018   return min;
02019 }
02020 
02021 void KOAgenda::updateConfig()
02022 {
02023   double oldGridSpacingY = mGridSpacingY;
02024 
02025   mDesiredGridSpacingY = KOPrefs::instance()->mHourSize;
02026   if ( mDesiredGridSpacingY < 4 || mDesiredGridSpacingY > 30 ) {
02027     mDesiredGridSpacingY = 10;
02028   }
02029 
02030   // make sure that there are not more than 24 per day
02031   mGridSpacingY = (double)height() / (double)mRows;
02032   if ( mGridSpacingY < mDesiredGridSpacingY ) {
02033     mGridSpacingY = mDesiredGridSpacingY;
02034   }
02035 
02036   //can be two doubles equal?, it's better to compare them with an epsilon
02037   if ( fabs( oldGridSpacingY - mGridSpacingY ) > 0.1 ) {
02038     resizeContents( int( mGridSpacingX * mColumns ),
02039                     int( mGridSpacingY * mRows ) );
02040   }
02041 
02042   calculateWorkingHours();
02043 
02044   marcus_bains();
02045 }
02046 
02047 void KOAgenda::checkScrollBoundaries()
02048 {
02049   // Invalidate old values to force update
02050   mOldLowerScrollValue = -1;
02051   mOldUpperScrollValue = -1;
02052 
02053   checkScrollBoundaries(verticalScrollBar()->value());
02054 }
02055 
02056 void KOAgenda::checkScrollBoundaries( int v )
02057 {
02058   int yMin = int( (v) / mGridSpacingY );
02059   int yMax = int( ( v + visibleHeight() ) / mGridSpacingY );
02060 
02061 //  kdDebug(5850) << "--- yMin: " << yMin << "  yMax: " << yMax << endl;
02062 
02063   if ( yMin != mOldLowerScrollValue ) {
02064     mOldLowerScrollValue = yMin;
02065     emit lowerYChanged(yMin);
02066   }
02067   if ( yMax != mOldUpperScrollValue ) {
02068     mOldUpperScrollValue = yMax;
02069     emit upperYChanged(yMax);
02070   }
02071 }
02072 
02073 int KOAgenda::visibleContentsYMin()
02074 {
02075   int v = verticalScrollBar()->value();
02076   return int( v / mGridSpacingY );
02077 }
02078 
02079 int KOAgenda::visibleContentsYMax()
02080 {
02081   int v = verticalScrollBar()->value();
02082   return int( ( v + visibleHeight() ) / mGridSpacingY );
02083 }
02084 
02085 void KOAgenda::deselectItem()
02086 {
02087   if ( mSelectedItem.isNull() ) {
02088     return;
02089   }
02090   mSelectedItem->select(false);
02091   mSelectedItem = 0;
02092 }
02093 
02094 void KOAgenda::selectItem( KOAgendaItem::GPtr item )
02095 {
02096   if ((KOAgendaItem *)mSelectedItem == item) return;
02097   deselectItem();
02098   if ( !item ) {
02099     emit incidenceSelected( 0, QDate() );
02100     return;
02101   }
02102   mSelectedItem = item;
02103   mSelectedItem->select();
02104   assert( mSelectedItem->incidence() );
02105   mSelectedUid = mSelectedItem->incidence()->uid();
02106   emit incidenceSelected( mSelectedItem->incidence(), mSelectedItem->itemDate() );
02107 }
02108 
02109 void KOAgenda::selectItemByUID( const QString& uid )
02110 {
02111   KOAgendaItem::GPtr item;
02112   AgendaItemList::Iterator it;
02113   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
02114     if ( *it && (*it)->incidence() && (*it)->incidence()->uid() == uid ) {
02115       selectItem( *it );
02116       break;
02117     }
02118   }
02119 }
02120 
02121 // This function seems never be called.
02122 void KOAgenda::keyPressEvent( QKeyEvent *kev )
02123 {
02124   switch(kev->key()) {
02125     case Key_PageDown:
02126       verticalScrollBar()->addPage();
02127       break;
02128     case Key_PageUp:
02129       verticalScrollBar()->subtractPage();
02130       break;
02131     case Key_Down:
02132       verticalScrollBar()->addLine();
02133       break;
02134     case Key_Up:
02135       verticalScrollBar()->subtractLine();
02136       break;
02137     default:
02138       ;
02139   }
02140 }
02141 
02142 void KOAgenda::calculateWorkingHours()
02143 {
02144   mWorkingHoursEnable = !mAllDayMode;
02145 
02146   QTime tmp = KOPrefs::instance()->mWorkingHoursStart.time();
02147   mWorkingHoursYTop = int( 4 * mGridSpacingY *
02148                            ( tmp.hour() + tmp.minute() / 60. +
02149                              tmp.second() / 3600. ) );
02150   tmp = KOPrefs::instance()->mWorkingHoursEnd.time();
02151   mWorkingHoursYBottom = int( 4 * mGridSpacingY *
02152                               ( tmp.hour() + tmp.minute() / 60. +
02153                                 tmp.second() / 3600. ) - 1 );
02154 }
02155 
02156 
02157 DateList KOAgenda::dateList() const
02158 {
02159     return mSelectedDates;
02160 }
02161 
02162 void KOAgenda::setDateList(const DateList &selectedDates)
02163 {
02164     mSelectedDates = selectedDates;
02165     marcus_bains();
02166 }
02167 
02168 void KOAgenda::contentsMousePressEvent ( QMouseEvent *event )
02169 {
02170   kdDebug(5850) << "KOagenda::contentsMousePressEvent(): type: " << event->type() << endl;
02171   QScrollView::contentsMousePressEvent(event);
02172 }
02173 
02174 void KOAgenda::setTypeAheadReceiver( QObject *o )
02175 {
02176   mTypeAheadReceiver = o;
02177 }
02178 
02179 QObject *KOAgenda::typeAheadReceiver() const
02180 {
02181   return mTypeAheadReceiver;
02182 }
02183 
02184 void KOAgenda::setHolidayMask(QMemArray<bool> *mask)
02185 {
02186   mHolidayMask = mask;
02187 }
KDE Home | KDE Accessibility Home | Description of Access Keys