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       incToChange = mCalendarView->singleOccurrenceOrAll( inc,
01128                                                           KOGlobals::EDIT,
01129                                                           chosenOption,
01130                                                           mActionItem->itemDate() );
01131 
01132       if ( chosenOption == KOGlobals::ONLY_THIS_ONE ||
01133            chosenOption == KOGlobals::ONLY_FUTURE ) {
01134         multiModify = true;
01135         mAgendaView->enableAgendaUpdate( false );
01136         useLastGroupwareDialogAnswer = true;
01137         addIncidence = true;
01138         mAgendaView->enableAgendaUpdate( true );
01139         KOGlobals::WhatChanged wc = chosenOption == KOGlobals::ONLY_THIS_ONE ?
01140                                     KOGlobals::RECURRENCE_MODIFIED_ONE_ONLY :
01141                                     KOGlobals::RECURRENCE_MODIFIED_ALL_FUTURE;
01142 
01143         const bool success = mChanger->changeIncidence( oldIncSaved, inc, wc, this );
01144 
01145         if ( success ) {
01146           mActionItem->dissociateFromMultiItem();
01147           mActionItem->setIncidence( incToChange );
01148         } else {
01149           incToChange = 0;
01150         }
01151       }
01152     }
01153 
01154     if ( incToChange ) {
01155       mActionItem->endMove();
01156       KOAgendaItem *placeItem = mActionItem->firstMultiItem();
01157       if  ( !placeItem ) {
01158         placeItem = mActionItem;
01159       }
01160 
01161       KOAgendaItem *modif = placeItem;
01162       AgendaItemList oldconflictItems = placeItem->conflictItems();
01163 
01164       AgendaItemList::Iterator it;
01165       for ( it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it ) {
01166         if ( *it ) {
01167           placeSubCells( *it );
01168         }
01169       }
01170 
01171       while ( placeItem ) {
01172         placeSubCells( placeItem );
01173         placeItem = placeItem->nextMultiItem();
01174       }
01175 
01176       // Notify about change
01177       // the agenda view will apply the changes to the actual Incidence*!
01178       mChanger->endChange( inc, mResPair.first, mResPair.second );
01179       kdDebug() << "Modified." << endl;
01180       mAgendaView->updateEventDates( modif, useLastGroupwareDialogAnswer, mResPair.first, mResPair.second, addIncidence );
01181     } else {
01182       mActionItem->resetMove();
01183       placeSubCells( mActionItem );
01184 
01185       // the item was moved, but not further modified, since it's not recurring
01186       // make sure the view updates anyhow, with the right item
01187       mChanger->endChange( inc, mResPair.first, mResPair.second );
01188       kdDebug() << "Not modified." << endl;
01189       mAgendaView->updateEventDates( mActionItem,
01190                                      useLastGroupwareDialogAnswer,
01191                                      mResPair.first,
01192                                      mResPair.second,
01193                                      addIncidence );
01194     }
01195   }
01196 
01197   mActionItem = 0;
01198   mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
01199   mItemMoved = false;
01200 
01201   if ( multiModify ) {
01202     emit endMultiModify();
01203   }
01204 
01205   kdDebug(5850) << "KOAgenda::endItemAction() done" << endl;
01206 }
01207 
01208 void KOAgenda::setActionCursor( int actionType, bool acting )
01209 {
01210   switch ( actionType ) {
01211     case MOVE:
01212       if (acting) setCursor( sizeAllCursor );
01213       else setCursor( arrowCursor );
01214       break;
01215     case RESIZETOP:
01216     case RESIZEBOTTOM:
01217       setCursor( sizeVerCursor );
01218       break;
01219     case RESIZELEFT:
01220     case RESIZERIGHT:
01221       setCursor( sizeHorCursor );
01222       break;
01223     default:
01224       setCursor( arrowCursor );
01225   }
01226 }
01227 
01228 void KOAgenda::setNoActionCursor( KOAgendaItem::GPtr moveItem, const QPoint& viewportPos )
01229 {
01230 //  kdDebug(5850) << "viewportPos: " << viewportPos.x() << "," << viewportPos.y() << endl;
01231 //  QPoint point = viewport()->mapToGlobal(viewportPos);
01232 //  kdDebug(5850) << "Global: " << point.x() << "," << point.y() << endl;
01233 //  point = clipper()->mapFromGlobal(point);
01234 //  kdDebug(5850) << "clipper: " << point.x() << "," << point.y() << endl;
01235 
01236   if ( !moveItem ) {
01237     return;
01238   }
01239 
01240   QPoint pos = viewportToContents( viewportPos );
01241   bool noResize = (moveItem && moveItem->incidence() &&
01242       moveItem->incidence()->type() == "Todo");
01243 
01244   KOAgenda::MouseActionType resizeType = MOVE;
01245   if ( !noResize ) resizeType = isInResizeArea( mAllDayMode, pos , moveItem);
01246   setActionCursor( resizeType );
01247 }
01248 
01249 
01253 double KOAgenda::calcSubCellWidth( KOAgendaItem::GPtr item )
01254 {
01255   if ( !item ) {
01256     return 0;
01257   }
01258 
01259   QPoint pt, pt1;
01260   pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
01261   pt1 = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) +
01262                         QPoint( 1, 1 ) );
01263   pt1 -= pt;
01264   int maxSubCells = item->subCells();
01265   double newSubCellWidth;
01266   if ( mAllDayMode ) {
01267     newSubCellWidth = double( pt1.y() ) / maxSubCells;
01268   } else {
01269     newSubCellWidth = double( pt1.x() ) / maxSubCells;
01270   }
01271   return newSubCellWidth;
01272 }
01273 
01274 void KOAgenda::adjustItemPosition( KOAgendaItem::GPtr item )
01275 {
01276   if (!item) return;
01277   item->resize( int( mGridSpacingX * item->cellWidth() ),
01278                 int( mGridSpacingY * item->cellHeight() ) );
01279   int clXLeft = item->cellXLeft();
01280   if ( KOGlobals::self()->reverseLayout() )
01281     clXLeft = item->cellXRight() + 1;
01282   QPoint cpos = gridToContents( QPoint( clXLeft, item->cellYTop() ) );
01283   moveChild( item, cpos.x(), cpos.y() );
01284 }
01285 
01286 void KOAgenda::placeAgendaItem( KOAgendaItem::GPtr item, double subCellWidth )
01287 {
01288   // kdDebug(5850) << "KOAgenda::placeAgendaItem(): " << item->incidence()->summary()
01289   //               << " subCellWidth: " << subCellWidth << endl;
01290 
01291   if ( !item ) {
01292     return;
01293   }
01294 
01295   // "left" upper corner, no subcells yet, RTL layouts have right/left switched, widths are negative then
01296   const QPoint pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
01297 
01298   // right lower corner
01299   const QPoint pt1 = gridToContents( QPoint( item->cellXLeft() + item->cellWidth(),
01300                                              item->cellYBottom()+1 ) );
01301 
01302   const double subCellPos = item->subCell() * subCellWidth;
01303 
01304   // we need to add 0.01 to make sure we don't loose one pixed due to
01305   // numerics (i.e. if it would be x.9998, we want the integer, not rounded down.
01306   const double delta = subCellWidth < 0 ? -0.01 : 0.01;
01307 
01308   int height, width, xpos, ypos;
01309   if ( mAllDayMode ) {
01310     width = pt1.x()-pt.x();
01311     height = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
01312     xpos = pt.x();
01313     ypos = pt.y() + int( subCellPos );
01314   } else {
01315     width = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
01316     height = pt1.y()-pt.y();
01317     xpos = pt.x() + int( subCellPos );
01318     ypos = pt.y();
01319   }
01320   if ( KOGlobals::self()->reverseLayout() ) { // RTL language/layout
01321     xpos += width;
01322     width = -width;
01323   }
01324   if ( height<0 ) { // BTT (bottom-to-top) layout ?!?
01325     ypos += height;
01326     height = -height;
01327   }
01328   item->resize( width, height );
01329   moveChild( item, xpos, ypos );
01330 }
01331 
01332 /*
01333   Place item in cell and take care that multiple items using the same cell do
01334   not overlap. This method is not yet optimal. It doesn't use the maximum space
01335   it can get in all cases.
01336   At the moment the method has a bug: When an item is placed only the sub cell
01337   widths of the items are changed, which are within the Y region the item to
01338   place spans. When the sub cell width change of one of this items affects a
01339   cell, where other items are, which do not overlap in Y with the item to place,
01340   the display gets corrupted, although the corruption looks quite nice.
01341 
01342   18/10/2010 UPDATE: this is fixed with to the updateWidthsRecursivley()
01343   call. kolab/issue2762.
01344 */
01345 void KOAgenda::placeSubCells( KOAgendaItem::GPtr placeItem )
01346 {
01347 #if 0
01348   kdDebug(5850) << "KOAgenda::placeSubCells()" << endl;
01349   if ( placeItem ) {
01350     Incidence *event = placeItem->incidence();
01351     if ( !event ) {
01352       kdDebug(5850) << "  event is 0" << endl;
01353     } else {
01354       kdDebug(5850) << "  event: " << event->summary() << endl;
01355     }
01356   } else {
01357     kdDebug(5850) << "  placeItem is 0" << endl;
01358   }
01359   kdDebug(5850) << "KOAgenda::placeSubCells()..." << endl;
01360 #endif
01361 
01362   if ( !placeItem ) {
01363     return;
01364   }
01365 
01366   QPtrList<KOrg::CellItem> cells;
01367   AgendaItemList::Iterator it;
01368   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01369     if ( *it ) {
01370       cells.append( *it );
01371     }
01372   }
01373 
01374   QPtrList<KOrg::CellItem> items = KOrg::CellItem::placeItem( cells,
01375                                                               placeItem );
01376   QPtrList<KOAgendaItem> updatedCells;
01377 
01378   KOrg::CellItem *i;
01379   placeItem->setConflictItems( AgendaItemList() );
01380   const double newSubCellWidth = calcSubCellWidth( placeItem );
01381   for ( i = items.first(); i; i = items.next() ) {
01382     KOAgendaItem *item = static_cast<KOAgendaItem *>( i );
01383     placeAgendaItem( item, newSubCellWidth );
01384     item->addConflictItem( placeItem );
01385 
01386     if ( placeItem->incidence() && placeItem->incidence()->doesFloat() ) {
01387       // untested for non-allday incidences, hence the check.
01388       updateWidthsRecursivley( item, updatedCells, placeItem->subCells() );
01389     }
01390 
01391     placeItem->addConflictItem( item );
01392   }
01393   if ( items.isEmpty() ) {
01394     placeAgendaItem( placeItem, newSubCellWidth );
01395   }
01396   placeItem->update();
01397 }
01398 
01400 bool KOAgenda::updateWidthsRecursivley( KOAgendaItem *item,
01401                                         QPtrList<KOAgendaItem> &itemsAlreadyUpdated,
01402                                         int numSubCells )
01403 {
01404   bool result = false;
01405   if ( !itemsAlreadyUpdated.contains( item ) ) {
01406     item->setSubCells( numSubCells );
01407 
01408     // So we don't end up in infinit recursion due to cycles.
01409     itemsAlreadyUpdated.append( item );
01410 
01411     // Tell our neighbours too.
01412     AgendaItemList list = item->conflictItems();
01413 
01414     for ( int i = 0; i < list.count(); ++i ) {
01415       KOAgendaItem::GPtr it = list[i];
01416       if ( it && numSubCells > it->subCells() ) {
01417         updateWidthsRecursivley( it, itemsAlreadyUpdated, numSubCells );
01418         result = true;
01419       }
01420     }
01421   }
01422 
01423   return result;
01424 }
01425 
01426 int KOAgenda::columnWidth( int column )
01427 {
01428   const int start = gridToContents( QPoint( column, 0 ) ).x();
01429   if (KOGlobals::self()->reverseLayout() )
01430     column--;
01431   else
01432     column++;
01433   const int end = gridToContents( QPoint( column, 0 ) ).x();
01434   return end - start;
01435 }
01436 /*
01437   Draw grid in the background of the agenda.
01438 */
01439 void KOAgenda::drawContents(QPainter* p, int cx, int cy, int cw, int ch)
01440 {
01441   QPixmap db(cw, ch);
01442   db.fill(KOPrefs::instance()->mAgendaBgColor);
01443   QPainter dbp(&db);
01444   dbp.translate(-cx,-cy);
01445 
01446   double lGridSpacingY = mGridSpacingY*2;
01447 
01448   // If work day, use work color
01449   // If busy day, use busy color
01450   // if work and busy day, mix both, and busy color has alpha
01451 
01452   const QMemArray<bool> busyDayMask = mAgendaView->busyDayMask();
01453 
01454   if ( KOPrefs::instance()->mColorBusyDaysEnabled && !mAllDayMode ) {
01455     for ( int i = 0; i < busyDayMask.count(); ++i ) {
01456       if ( busyDayMask[i] ) {
01457         const QPoint pt1( cx + mGridSpacingX * i, 0 );
01458         // const QPoint pt2( cx + mGridSpacingX * ( i+1 ), ch );
01459         dbp.fillRect( pt1.x(), pt1.y(), mGridSpacingX, cy + ch, KOPrefs::instance()->mAgendaMonthBgBusyColor );
01460       }
01461     }
01462   }
01463   // Highlight working hours
01464   if ( mWorkingHoursEnable ) {
01465 
01466     QColor workAndBusyColor;
01467 
01468     if ( KOPrefs::instance()->mColorBusyDaysEnabled ) {
01469       workAndBusyColor = KOHelper::mixColors( KOPrefs::instance()->mAgendaMonthBgBusyColor, 0.60, KOPrefs::instance()->mWorkingHoursColor );
01470     } else {
01471       workAndBusyColor = KOPrefs::instance()->mWorkingHoursColor;
01472     }
01473 
01474     const QPoint pt1( cx, mWorkingHoursYTop );
01475     const QPoint pt2( cx+cw, mWorkingHoursYBottom );
01476     if ( pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) {
01477       int gxStart = contentsToGrid( pt1 ).x();
01478       int gxEnd = contentsToGrid( pt2 ).x();
01479       // correct start/end for rtl layouts
01480       if ( gxStart > gxEnd ) {
01481         const int tmp = gxStart;
01482         gxStart = gxEnd;
01483         gxEnd = tmp;
01484       }
01485       const int xoffset = ( KOGlobals::self()->reverseLayout() ? 1 : 0 );
01486       while( gxStart <= gxEnd ) {
01487         const int xStart = gridToContents( QPoint( gxStart+xoffset, 0 ) ).x();
01488         const int xWidth = columnWidth( gxStart ) + 1;
01489         QColor color;
01490         if ( gxStart < busyDayMask.count() && busyDayMask[gxStart] ) {
01491           color = workAndBusyColor;
01492         } else {
01493           color = KOPrefs::instance()->mWorkingHoursColor;
01494         }
01495         if ( pt2.y() < pt1.y() ) {
01496           // overnight working hours
01497           if ( ( (gxStart==0) && !mHolidayMask->at(mHolidayMask->count()-1) ) ||
01498                ( (gxStart>0) && (gxStart<int(mHolidayMask->count())) && (!mHolidayMask->at(gxStart-1) ) ) ) {
01499             if ( pt2.y() > cy ) {
01500               dbp.fillRect( xStart, cy, xWidth, pt2.y() - cy + 1, color );
01501             }
01502           }
01503           if ( (gxStart < int(mHolidayMask->count()-1)) && (!mHolidayMask->at(gxStart)) ) {
01504             if ( pt1.y() < cy + ch - 1 ) {
01505               dbp.fillRect( xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1,
01506                             color );
01507             }
01508           }
01509         } else {
01510           // last entry in holiday mask denotes the previous day not visible (needed for overnight shifts)
01511           if ( gxStart < int(mHolidayMask->count()-1) && !mHolidayMask->at(gxStart)) {
01512             dbp.fillRect( xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1,
01513                           color );
01514           }
01515         }
01516         ++gxStart;
01517       }
01518     }
01519   }
01520 
01521 
01522 
01523   // draw selection
01524   if ( mHasSelection ) {
01525     QPoint pt, pt1;
01526 
01527     if ( mSelectionEndCell.x() > mSelectionStartCell.x() ) { // multi day selection
01528       // draw start day
01529       pt = gridToContents( mSelectionStartCell );
01530       pt1 = gridToContents( QPoint( mSelectionStartCell.x() + 1, mRows + 1 ) );
01531       dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01532       // draw all other days between the start day and the day of the selection end
01533       for ( int c = mSelectionStartCell.x() + 1; c < mSelectionEndCell.x(); ++c ) {
01534         pt = gridToContents( QPoint( c, 0 ) );
01535         pt1 = gridToContents( QPoint( c + 1, mRows + 1 ) );
01536         dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01537       }
01538       // draw end day
01539       pt = gridToContents( QPoint( mSelectionEndCell.x(), 0 ) );
01540       pt1 = gridToContents( mSelectionEndCell + QPoint(1,1) );
01541       dbp.fillRect( QRect( pt, pt1), KOPrefs::instance()->mHighlightColor );
01542     }  else { // single day selection
01543       pt = gridToContents( mSelectionStartCell );
01544       pt1 = gridToContents( mSelectionEndCell + QPoint(1,1) );
01545       dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01546     }
01547   }
01548 
01549   QPen hourPen( KOPrefs::instance()->mAgendaBgColor.dark( 150 ) );
01550   QPen halfHourPen( KOPrefs::instance()->mAgendaBgColor.dark( 125 ) );
01551   dbp.setPen( hourPen );
01552 
01553   // Draw vertical lines of grid, start with the last line not yet visible
01554   //  kdDebug(5850) << "drawContents cx: " << cx << " cy: " << cy << " cw: " << cw << " ch: " << ch << endl;
01555   double x = ( int( cx / mGridSpacingX ) ) * mGridSpacingX;
01556   while (x < cx + cw) {
01557     dbp.drawLine( int( x ), cy, int( x ), cy + ch );
01558     x+=mGridSpacingX;
01559   }
01560 
01561   // Draw horizontal lines of grid
01562   double y = ( int( cy / (2*lGridSpacingY) ) ) * 2 * lGridSpacingY;
01563   while (y < cy + ch) {
01564 //    kdDebug(5850) << " y: " << y << endl;
01565     dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
01566     y += 2 * lGridSpacingY;
01567   }
01568   y = ( 2 * int( cy / (2*lGridSpacingY) ) + 1) * lGridSpacingY;
01569   dbp.setPen( halfHourPen );
01570   while (y < cy + ch) {
01571 //    kdDebug(5850) << " y: " << y << endl;
01572     dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
01573     y+=2*lGridSpacingY;
01574   }
01575   p->drawPixmap(cx,cy, db);
01576 }
01577 
01578 /*
01579   Convert srcollview contents coordinates to agenda grid coordinates.
01580 */
01581 QPoint KOAgenda::contentsToGrid ( const QPoint &pos ) const
01582 {
01583   int gx = int( KOGlobals::self()->reverseLayout() ?
01584         mColumns - pos.x()/mGridSpacingX : pos.x()/mGridSpacingX );
01585   int gy = int( pos.y()/mGridSpacingY );
01586   return QPoint( gx, gy );
01587 }
01588 
01589 /*
01590   Convert agenda grid coordinates to scrollview contents coordinates.
01591 */
01592 QPoint KOAgenda::gridToContents( const QPoint &gpos ) const
01593 {
01594   int x = int( KOGlobals::self()->reverseLayout() ?
01595              (mColumns - gpos.x())*mGridSpacingX : gpos.x()*mGridSpacingX );
01596   int y = int( gpos.y()*mGridSpacingY );
01597   return QPoint( x, y );
01598 }
01599 
01600 
01601 /*
01602   Return Y coordinate corresponding to time. Coordinates are rounded to fit into
01603   the grid.
01604 */
01605 int KOAgenda::timeToY(const QTime &time)
01606 {
01607 //  kdDebug(5850) << "Time: " << time.toString() << endl;
01608   int minutesPerCell = 24 * 60 / mRows;
01609 //  kdDebug(5850) << "minutesPerCell: " << minutesPerCell << endl;
01610   int timeMinutes = time.hour() * 60 + time.minute();
01611 //  kdDebug(5850) << "timeMinutes: " << timeMinutes << endl;
01612   int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell;
01613 //  kdDebug(5850) << "y: " << Y << endl;
01614 //  kdDebug(5850) << "\n" << endl;
01615   return Y;
01616 }
01617 
01618 
01619 /*
01620   Return time corresponding to cell y coordinate. Coordinates are rounded to
01621   fit into the grid.
01622 */
01623 QTime KOAgenda::gyToTime(int gy)
01624 {
01625 //  kdDebug(5850) << "gyToTime: " << gy << endl;
01626   int secondsPerCell = 24 * 60 * 60/ mRows;
01627 
01628   int timeSeconds = secondsPerCell * gy;
01629 
01630   QTime time( 0, 0, 0 );
01631   if ( timeSeconds < 24 * 60 * 60 ) {
01632     time = time.addSecs(timeSeconds);
01633   } else {
01634     time.setHMS( 23, 59, 59 );
01635   }
01636 //  kdDebug(5850) << "  gyToTime: " << time.toString() << endl;
01637 
01638   return time;
01639 }
01640 
01641 QMemArray<int> KOAgenda::minContentsY()
01642 {
01643   QMemArray<int> minArray;
01644   minArray.fill( timeToY( QTime(23, 59) ), mSelectedDates.count() );
01645 
01646   AgendaItemList::Iterator it;
01647   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01648     if ( *it ) {
01649       const int ymin = (*it)->cellYTop();
01650       const int index = (*it)->cellXLeft();
01651       if ( index >= 0 && index < static_cast<int>( mSelectedDates.count() ) ) {
01652         if ( ymin < minArray[index] && !mItemsToDelete.contains( *it ) )
01653           minArray[index] = ymin;
01654       }
01655     }
01656   }
01657 
01658   return minArray;
01659 }
01660 
01661 QMemArray<int> KOAgenda::maxContentsY()
01662 {
01663   QMemArray<int> maxArray;
01664   maxArray.fill( timeToY( QTime(0, 0) ), mSelectedDates.count() );
01665   AgendaItemList::Iterator it;
01666   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01667     if ( *it ) {
01668       const int ymax = (*it)->cellYBottom();
01669       const int index = (*it)->cellXLeft();
01670       if ( index >= 0 && index < static_cast<int>( mSelectedDates.count() ) ) {
01671         if ( ymax > maxArray[index] && !mItemsToDelete.contains( *it ) )
01672           maxArray[index] = ymax;
01673       }
01674     }
01675   }
01676 
01677   return maxArray;
01678 }
01679 
01680 void KOAgenda::setStartTime( const QTime &startHour )
01681 {
01682   const double startPos = ( startHour.hour()/24. + startHour.minute()/1440. +
01683                             startHour.second()/86400. ) * mRows * gridSpacingY();
01684   setContentsPos( 0, static_cast<int>( startPos ) );
01685 }
01686 
01687 
01688 /*
01689   Insert KOAgendaItem into agenda.
01690 */
01691 KOAgendaItem *KOAgenda::insertItem( Incidence *incidence, const QDate &qd, int X,
01692                                     int YTop, int YBottom, int itemPos, int itemCount )
01693 {
01694   if ( mAllDayMode ) {
01695     kdDebug(5850) << "KOAgenda: calling insertItem in all-day mode is illegal." << endl;
01696     return 0;
01697   }
01698 
01699   mActionType = NOP;
01700 
01701   KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, incidence, qd, viewport(), itemPos, itemCount );
01702   connect( agendaItem, SIGNAL( removeAgendaItem( KOAgendaItem::GPtr  ) ),
01703            SLOT( removeAgendaItem( KOAgendaItem::GPtr  ) ) );
01704   connect( agendaItem, SIGNAL( showAgendaItem( KOAgendaItem::GPtr  ) ),
01705            SLOT( showAgendaItem( KOAgendaItem::GPtr  ) ) );
01706 
01707   if ( YBottom <= YTop ) {
01708     kdDebug(5850) << "KOAgenda::insertItem(): Text: " << agendaItem->text() << " YSize<0" << endl;
01709     YBottom = YTop;
01710   }
01711 
01712   agendaItem->resize( int( ( X + 1 ) * mGridSpacingX ) -
01713                       int( X * mGridSpacingX ),
01714                       int( YTop * mGridSpacingY ) -
01715                       int( ( YBottom + 1 ) * mGridSpacingY ) );
01716   agendaItem->setCellXY( X, YTop, YBottom );
01717   agendaItem->setCellXRight( X );
01718   agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, incidence ) );
01719   agendaItem->installEventFilter( this );
01720 
01721   addChild( agendaItem, int( X * mGridSpacingX ), int( YTop * mGridSpacingY ) );
01722   mItems.append( agendaItem );
01723 
01724   placeSubCells( agendaItem );
01725 
01726   agendaItem->show();
01727 
01728   marcus_bains();
01729 
01730   return agendaItem;
01731 }
01732 
01733 /*
01734   Insert all-day KOAgendaItem into agenda.
01735 */
01736 KOAgendaItem *KOAgenda::insertAllDayItem( Incidence *event, const QDate &qd,
01737                                           int XBegin, int XEnd )
01738 {
01739   if ( !mAllDayMode ) {
01740     kdWarning(5850) << "KOAgenda: calling insertAllDayItem in non all-day mode is illegal." << endl;
01741     return 0;
01742   }
01743 
01744   mActionType = NOP;
01745 
01746   KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, event, qd, viewport(), 1, 1 );
01747   connect( agendaItem, SIGNAL( removeAgendaItem( KOAgendaItem::GPtr ) ),
01748            SLOT( removeAgendaItem( KOAgendaItem::GPtr ) ) );
01749   connect( agendaItem, SIGNAL( showAgendaItem( KOAgendaItem::GPtr ) ),
01750            SLOT( showAgendaItem( KOAgendaItem::GPtr ) ) );
01751 
01752   { // Start hack
01753 
01754     // Fixes https://issues.kolab.org/issue3933 -
01755     // issue3933: A large wholeday event is not shown at all days in dayview.(rt#5884)
01756     // ( Some 32k pixel limit Qt3 has. )
01757     if ( event->doesFloat() && XBegin * mGridSpacingX <= -32767/*32k*/ + mGridSpacingX ) {
01758 
01759       // Our event starts _many_ days before dt, so we lie to agenda and make
01760       // it think the event starts one day before.
01761       // That part of the event won't be visible, so the lie won't have any effect
01762       // for the user
01763       XBegin = -1;
01764     }
01765   } // End hack
01766 
01767   agendaItem->setCellXY( XBegin, 0, 0 );
01768   agendaItem->setCellXRight( XEnd );
01769 
01770   const double startIt = mGridSpacingX * ( agendaItem->cellXLeft() );
01771   const double endIt = mGridSpacingX * ( agendaItem->cellWidth() +
01772                                          agendaItem->cellXLeft() );
01773 
01774   agendaItem->resize( int( endIt ) - int( startIt ), int( mGridSpacingY ) );
01775 
01776   agendaItem->installEventFilter( this );
01777   agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, event ) );
01778   addChild( agendaItem, int( XBegin * mGridSpacingX ), 0 );
01779   mItems.append( agendaItem );
01780 
01781   placeSubCells( agendaItem );
01782 
01783   agendaItem->show();
01784 
01785   return agendaItem;
01786 }
01787 
01788 
01789 void KOAgenda::insertMultiItem( Event *event, const QDate &qd, int XBegin, int XEnd,
01790                                 int YTop, int YBottom )
01791 {
01792   if ( mAllDayMode ) {
01793     kdDebug(5850) << "KOAgenda: calling insertMultiItem in all-day mode is illegal." << endl;
01794     return;
01795   }
01796   mActionType = NOP;
01797 
01798   int cellX,cellYTop,cellYBottom;
01799   QString newtext;
01800   int width = XEnd - XBegin + 1;
01801   int count = 0;
01802   KOAgendaItem *current = 0;
01803   QPtrList<KOAgendaItem> multiItems;
01804   const int visibleCount = mSelectedDates.first().daysTo( mSelectedDates.last() );
01805   for ( cellX = XBegin; cellX <= XEnd; ++cellX ) {
01806     ++count;
01807     //Only add the items that are visible.
01808     if( cellX >= 0 && cellX <= visibleCount ) {
01809       if ( cellX == XBegin ) {
01810         cellYTop = YTop;
01811       } else {
01812         cellYTop = 0;
01813       }
01814 
01815       if ( cellX == XEnd ) {
01816         cellYBottom = YBottom;
01817       } else {
01818         cellYBottom = rows() - 1;
01819       }
01820 
01821       newtext = QString("(%1/%2): ").arg( count ).arg( width );
01822       newtext.append( event->summary() );
01823 
01824       current = insertItem( event, qd, cellX, cellYTop, cellYBottom, count, width );
01825       current->setText( newtext );
01826       multiItems.append( current );
01827     }
01828   }
01829   QPtrList<KOAgendaItem>::iterator it = multiItems.begin();
01830   QPtrList<KOAgendaItem>::iterator e = multiItems.end();
01831 
01832   if ( it != e ) { // .first asserts if the list is empty
01833     KOAgendaItem *first = multiItems.first();
01834     KOAgendaItem *last = multiItems.last();
01835     KOAgendaItem *prev = 0, *next = 0;
01836 
01837     while ( it != e ) {
01838       KOAgendaItem *item = *it;
01839       ++it;
01840       next = ( it == e ) ? 0 : (*it);
01841       if ( item ) {
01842         item->setMultiItem( ( item == first ) ? 0 : first,
01843                             prev, next,
01844                             ( item == last ) ? 0 : last );
01845       }
01846       prev = item;
01847     }
01848   }
01849 
01850   marcus_bains();
01851 }
01852 
01853 void KOAgenda::removeIncidence( Incidence *incidence, bool relayoutNeighbours )
01854 {
01855   // First find all items to be deleted and store them
01856   // in its own list. Otherwise removeAgendaItem will reset
01857   // the current position and mess this up.
01858   QValueList<KOAgendaItem::GPtr > itemsToRemove;
01859 
01860   AgendaItemList::Iterator it;
01861   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01862     if ( *it ) {
01863       if ( (*it)->incidence() == incidence ) {
01864         itemsToRemove.append( *it );
01865       }
01866     }
01867   }
01868 
01869   for ( it = itemsToRemove.begin(); it != itemsToRemove.end(); ++it ) {
01870     removeAgendaItem( *it, relayoutNeighbours );
01871   }
01872 }
01873 
01874 void KOAgenda::showAgendaItem( KOAgendaItem::GPtr agendaItem )
01875 {
01876   if ( !agendaItem ) {
01877     return;
01878   }
01879 
01880   agendaItem->hide();
01881   addChild( agendaItem );
01882   if ( !mItems.contains( agendaItem ) ) {
01883     mItems.append( agendaItem );
01884   }
01885   placeSubCells( agendaItem );
01886 
01887   agendaItem->show();
01888 }
01889 
01890 bool KOAgenda::removeAgendaItem( KOAgendaItem::GPtr item, bool relayoutNeighbours )
01891 {
01892   // we found the item. Let's remove it and update the conflicts
01893   bool taken = false;
01894   KOAgendaItem::GPtr thisItem = item;
01895   AgendaItemList conflictItems = thisItem->conflictItems();
01896   removeChild( thisItem );
01897 
01898   AgendaItemList::Iterator it = mItems.find( thisItem );
01899   if ( it != mItems.end() ) {
01900     mItems.remove( it ); // Don't free memory, QPtrList::take() was used here.
01901     taken = true;
01902   }
01903 
01904   if ( relayoutNeighbours ) {
01905     for ( it = conflictItems.begin(); it != conflictItems.end(); ++it ) {
01906       // the item itself is also in its own conflictItems list!
01907       if ( *it != thisItem ) {
01908         placeSubCells( *it );
01909       }
01910     }
01911   }
01912 
01913   mItemsToDelete.append( thisItem );
01914   QTimer::singleShot( 0, this, SLOT( deleteItemsToDelete() ) );
01915   return taken;
01916 }
01917 
01918 void KOAgenda::deleteItemsToDelete()
01919 {
01920   freeItemList( mItemsToDelete );
01921   mItemsToDelete.clear();
01922 }
01923 
01924 /*QSizePolicy KOAgenda::sizePolicy() const
01925 {
01926   // Thought this would make the all-day event agenda minimum size and the
01927   // normal agenda take the remaining space. But it doesnt work. The QSplitter
01928   // dont seem to think that an Expanding widget needs more space than a
01929   // Preferred one.
01930   // But it doesnt hurt, so it stays.
01931   if (mAllDayMode) {
01932     return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
01933   } else {
01934     return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
01935   }
01936 }
01937 */
01938 
01939 /*
01940   Overridden from QScrollView to provide proper resizing of KOAgendaItems.
01941 */
01942 void KOAgenda::resizeEvent ( QResizeEvent *ev )
01943 {
01944 //  kdDebug(5850) << "KOAgenda::resizeEvent" << endl;
01945 
01946   QSize newSize( ev->size() );
01947   if (mAllDayMode) {
01948     mGridSpacingX = double( newSize.width() - 2 * frameWidth() ) / (double)mColumns;
01949     mGridSpacingY = newSize.height() - 2 * frameWidth();
01950   } else {
01951     int scrollbarWidth = vScrollBarMode() != AlwaysOff ? verticalScrollBar()->width() : 0;
01952     mGridSpacingX = double( newSize.width() - scrollbarWidth - 2 * frameWidth()) / double(mColumns);
01953     // make sure that there are not more than 24 per day
01954     mGridSpacingY = double(newSize.height() - 2 * frameWidth()) / double(mRows);
01955     if ( mGridSpacingY < mDesiredGridSpacingY )
01956       mGridSpacingY = mDesiredGridSpacingY;
01957   }
01958   calculateWorkingHours();
01959   QTimer::singleShot( 0, this, SLOT( resizeAllContents() ) );
01960   emit gridSpacingYChanged( mGridSpacingY * 4 );
01961   QScrollView::resizeEvent(ev);
01962 }
01963 
01964 void KOAgenda::resizeAllContents()
01965 {
01966   // For allday this is actually an height.
01967   double subCellWidth;
01968 
01969   if ( mItems.count() > 0 ) {
01970     KOAgendaItem::GPtr item;
01971     if ( mAllDayMode ) {
01972       AgendaItemList::Iterator it;
01973       for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01974         if ( *it ) {
01975           subCellWidth = calcSubCellWidth( *it );
01976           placeAgendaItem( *it, subCellWidth );
01977         }
01978       }
01979     } else {
01980       AgendaItemList::Iterator it;
01981       for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01982         if ( *it ) {
01983           subCellWidth = calcSubCellWidth( *it );
01984           placeAgendaItem( *it, subCellWidth );
01985         }
01986       }
01987     }
01988   }
01989   checkScrollBoundaries();
01990   marcus_bains();
01991 }
01992 
01993 void KOAgenda::scrollUp()
01994 {
01995   scrollBy(0,-mScrollOffset);
01996 }
01997 
01998 
01999 void KOAgenda::scrollDown()
02000 {
02001   scrollBy(0,mScrollOffset);
02002 }
02003 
02004 
02005 /*
02006   Calculates the minimum width
02007 */
02008 int KOAgenda::minimumWidth() const
02009 {
02010   // FIXME:: develop a way to dynamically determine the minimum width
02011   int min = 100;
02012 
02013   return min;
02014 }
02015 
02016 void KOAgenda::updateConfig()
02017 {
02018   double oldGridSpacingY = mGridSpacingY;
02019 
02020   mDesiredGridSpacingY = KOPrefs::instance()->mHourSize;
02021   if ( mDesiredGridSpacingY < 4 || mDesiredGridSpacingY > 30 ) {
02022     mDesiredGridSpacingY = 10;
02023   }
02024 
02025   // make sure that there are not more than 24 per day
02026   mGridSpacingY = (double)height() / (double)mRows;
02027   if ( mGridSpacingY < mDesiredGridSpacingY ) {
02028     mGridSpacingY = mDesiredGridSpacingY;
02029   }
02030 
02031   //can be two doubles equal?, it's better to compare them with an epsilon
02032   if ( fabs( oldGridSpacingY - mGridSpacingY ) > 0.1 ) {
02033     resizeContents( int( mGridSpacingX * mColumns ),
02034                     int( mGridSpacingY * mRows ) );
02035   }
02036 
02037   calculateWorkingHours();
02038 
02039   marcus_bains();
02040 }
02041 
02042 void KOAgenda::checkScrollBoundaries()
02043 {
02044   // Invalidate old values to force update
02045   mOldLowerScrollValue = -1;
02046   mOldUpperScrollValue = -1;
02047 
02048   checkScrollBoundaries(verticalScrollBar()->value());
02049 }
02050 
02051 void KOAgenda::checkScrollBoundaries( int v )
02052 {
02053   int yMin = int( (v) / mGridSpacingY );
02054   int yMax = int( ( v + visibleHeight() ) / mGridSpacingY );
02055 
02056 //  kdDebug(5850) << "--- yMin: " << yMin << "  yMax: " << yMax << endl;
02057 
02058   if ( yMin != mOldLowerScrollValue ) {
02059     mOldLowerScrollValue = yMin;
02060     emit lowerYChanged(yMin);
02061   }
02062   if ( yMax != mOldUpperScrollValue ) {
02063     mOldUpperScrollValue = yMax;
02064     emit upperYChanged(yMax);
02065   }
02066 }
02067 
02068 int KOAgenda::visibleContentsYMin()
02069 {
02070   int v = verticalScrollBar()->value();
02071   return int( v / mGridSpacingY );
02072 }
02073 
02074 int KOAgenda::visibleContentsYMax()
02075 {
02076   int v = verticalScrollBar()->value();
02077   return int( ( v + visibleHeight() ) / mGridSpacingY );
02078 }
02079 
02080 void KOAgenda::deselectItem()
02081 {
02082   if ( mSelectedItem.isNull() ) {
02083     return;
02084   }
02085   mSelectedItem->select(false);
02086   mSelectedItem = 0;
02087 }
02088 
02089 void KOAgenda::selectItem( KOAgendaItem::GPtr item )
02090 {
02091   if ((KOAgendaItem *)mSelectedItem == item) return;
02092   deselectItem();
02093   if ( !item ) {
02094     emit incidenceSelected( 0, QDate() );
02095     return;
02096   }
02097   mSelectedItem = item;
02098   mSelectedItem->select();
02099   assert( mSelectedItem->incidence() );
02100   mSelectedUid = mSelectedItem->incidence()->uid();
02101   emit incidenceSelected( mSelectedItem->incidence(), mSelectedItem->itemDate() );
02102 }
02103 
02104 void KOAgenda::selectItemByUID( const QString& uid )
02105 {
02106   KOAgendaItem::GPtr item;
02107   AgendaItemList::Iterator it;
02108   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
02109     if ( *it && (*it)->incidence() && (*it)->incidence()->uid() == uid ) {
02110       selectItem( *it );
02111       break;
02112     }
02113   }
02114 }
02115 
02116 // This function seems never be called.
02117 void KOAgenda::keyPressEvent( QKeyEvent *kev )
02118 {
02119   switch(kev->key()) {
02120     case Key_PageDown:
02121       verticalScrollBar()->addPage();
02122       break;
02123     case Key_PageUp:
02124       verticalScrollBar()->subtractPage();
02125       break;
02126     case Key_Down:
02127       verticalScrollBar()->addLine();
02128       break;
02129     case Key_Up:
02130       verticalScrollBar()->subtractLine();
02131       break;
02132     default:
02133       ;
02134   }
02135 }
02136 
02137 void KOAgenda::calculateWorkingHours()
02138 {
02139   mWorkingHoursEnable = !mAllDayMode;
02140 
02141   QTime tmp = KOPrefs::instance()->mWorkingHoursStart.time();
02142   mWorkingHoursYTop = int( 4 * mGridSpacingY *
02143                            ( tmp.hour() + tmp.minute() / 60. +
02144                              tmp.second() / 3600. ) );
02145   tmp = KOPrefs::instance()->mWorkingHoursEnd.time();
02146   mWorkingHoursYBottom = int( 4 * mGridSpacingY *
02147                               ( tmp.hour() + tmp.minute() / 60. +
02148                                 tmp.second() / 3600. ) - 1 );
02149 }
02150 
02151 
02152 DateList KOAgenda::dateList() const
02153 {
02154     return mSelectedDates;
02155 }
02156 
02157 void KOAgenda::setDateList(const DateList &selectedDates)
02158 {
02159     mSelectedDates = selectedDates;
02160     marcus_bains();
02161 }
02162 
02163 void KOAgenda::contentsMousePressEvent ( QMouseEvent *event )
02164 {
02165   kdDebug(5850) << "KOagenda::contentsMousePressEvent(): type: " << event->type() << endl;
02166   QScrollView::contentsMousePressEvent(event);
02167 }
02168 
02169 void KOAgenda::setTypeAheadReceiver( QObject *o )
02170 {
02171   mTypeAheadReceiver = o;
02172 }
02173 
02174 QObject *KOAgenda::typeAheadReceiver() const
02175 {
02176   return mTypeAheadReceiver;
02177 }
02178 
02179 void KOAgenda::setHolidayMask(QMemArray<bool> *mask)
02180 {
02181   mHolidayMask = mask;
02182 }
KDE Home | KDE Accessibility Home | Description of Access Keys