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         Incidence::List incidences = factory.createDropIncidences( de );
00452         if ( !incidences.isEmpty() ) {
00453           de->accept();
00454           incidences.setAutoDelete( true );
00455         } else {
00456           de->ignore();
00457           return false;
00458         }
00459         return true;
00460       } else return false;
00461       break;
00462     case QEvent::DragLeave:
00463       return false;
00464       break;
00465     case QEvent::Drop:
00466       {
00467         if ( !ICalDrag::canDecode( de ) && !VCalDrag::canDecode( de ) ) {
00468           return false;
00469         }
00470 
00471         DndFactory factory( mCalendar );
00472         Incidence::List incidences = factory.createDropIncidences( de );
00473 
00474         if ( !incidences.isEmpty() ) {
00475           Incidence *incidence = incidences.first();
00476           for( uint i=1; i<incidences.count(); ++i ) {
00477             delete incidences[i];
00478           }
00479           // Journals not supported yet.
00480           if ( incidence->type() == "Event" || incidence->type() == "Todo" ) {
00481             de->acceptAction();
00482             QPoint pos;
00483             // FIXME: This is a bad hack, as the viewportToContents seems to be off by
00484             // 2000 (which is the left upper corner of the viewport). It works correctly
00485             // for agendaItems.
00486             if ( object == this  ) {
00487               pos = viewportPos + QPoint( contentsX(), contentsY() );
00488             } else {
00489               pos = viewportToContents( viewportPos );
00490             }
00491             QPoint gpos = contentsToGrid( pos );
00492             emit droppedIncidence( incidence, gpos, mAllDayMode );
00493             return true;
00494           }
00495         }
00496       }
00497       break;
00498     case QEvent::DragResponse:
00499     default:
00500       break;
00501   }
00502 #endif
00503 
00504   return false;
00505 }
00506 
00507 bool KOAgenda::eventFilter_key( QObject *, QKeyEvent *ke )
00508 {
00509   // kdDebug(5850) << "KOAgenda::eventFilter_key() " << ke->type() << endl;
00510 
00511   // If Return is pressed bring up an editor for the current selected time span.
00512   if ( ke->key() == Key_Return ) {
00513     if ( ke->type() == QEvent::KeyPress ) mReturnPressed = true;
00514     else if ( ke->type() == QEvent::KeyRelease ) {
00515       if ( mReturnPressed ) {
00516         emitNewEventForSelection();
00517         mReturnPressed = false;
00518         return true;
00519       } else {
00520         mReturnPressed = false;
00521       }
00522     }
00523   }
00524 
00525   // Ignore all input that does not produce any output
00526   if ( ke->text().isEmpty() ) return false;
00527 
00528   if ( ke->type() == QEvent::KeyPress || ke->type() == QEvent::KeyRelease ) {
00529     switch ( ke->key() ) {
00530       case Key_Escape:
00531       case Key_Return:
00532       case Key_Enter:
00533       case Key_Tab:
00534       case Key_Backtab:
00535       case Key_Left:
00536       case Key_Right:
00537       case Key_Up:
00538       case Key_Down:
00539       case Key_Backspace:
00540       case Key_Delete:
00541       case Key_Prior:
00542       case Key_Next:
00543       case Key_Home:
00544       case Key_End:
00545       case Key_Control:
00546       case Key_Meta:
00547       case Key_Alt:
00548         break;
00549       default:
00550         mTypeAheadEvents.append( new QKeyEvent( ke->type(), ke->key(),
00551                                                 ke->ascii(), ke->state(),
00552                                                 ke->text(), ke->isAutoRepeat(),
00553                                                 ke->count() ) );
00554         if ( !mTypeAhead ) {
00555           mTypeAhead = true;
00556           emitNewEventForSelection();
00557         }
00558         return true;
00559     }
00560   }
00561   return false;
00562 }
00563 
00564 void KOAgenda::emitNewEventForSelection()
00565 {
00566   QPair<ResourceCalendar *, QString>p = mCalendarView->viewSubResourceCalendar();
00567   emit newEventSignal( p.first, p.second );
00568 }
00569 
00570 void KOAgenda::finishTypeAhead()
00571 {
00572 //  kdDebug(5850) << "KOAgenda::finishTypeAhead()" << endl;
00573   if ( typeAheadReceiver() ) {
00574     for( QEvent *e = mTypeAheadEvents.first(); e;
00575          e = mTypeAheadEvents.next() ) {
00576 //      kdDebug(5850) << "sendEvent() " << int( typeAheadReceiver() ) << endl;
00577       QApplication::sendEvent( typeAheadReceiver(), e );
00578     }
00579   }
00580   mTypeAheadEvents.clear();
00581   mTypeAhead = false;
00582 }
00583 #ifndef QT_NO_WHEELEVENT
00584 bool KOAgenda::eventFilter_wheel ( QObject *object, QWheelEvent *e )
00585 {
00586   QPoint viewportPos;
00587   bool accepted=false;
00588   if  ( ( e->state() & ShiftButton) == ShiftButton ) {
00589     if ( object != viewport() ) {
00590       viewportPos = ( (QWidget *) object )->mapToParent( e->pos() );
00591     } else {
00592       viewportPos = e->pos();
00593     }
00594     //kdDebug(5850)<< "KOAgenda::eventFilter_wheel: type:"<<
00595     //  e->type()<<" delta: "<< e->delta()<< endl;
00596     emit zoomView( -e->delta() ,
00597       contentsToGrid( viewportToContents( viewportPos ) ),
00598       Qt::Horizontal );
00599     accepted=true;
00600   }
00601 
00602   if  ( ( e->state() & ControlButton ) == ControlButton ){
00603     if ( object != viewport() ) {
00604       viewportPos = ( (QWidget *)object )->mapToParent( e->pos() );
00605     } else {
00606       viewportPos = e->pos();
00607     }
00608     emit zoomView( -e->delta() ,
00609       contentsToGrid( viewportToContents( viewportPos ) ),
00610       Qt::Vertical );
00611     emit mousePosSignal(gridToContents(contentsToGrid(viewportToContents( viewportPos ))));
00612     accepted=true;
00613   }
00614   if (accepted ) e->accept();
00615   return accepted;
00616 }
00617 #endif
00618 bool KOAgenda::eventFilter_mouse(QObject *object, QMouseEvent *me)
00619 {
00620   QPoint viewportPos;
00621   if (object != viewport()) {
00622     viewportPos = ((QWidget *)object)->mapToParent(me->pos());
00623   } else {
00624     viewportPos = me->pos();
00625   }
00626 
00627   switch (me->type())  {
00628     case QEvent::MouseButtonPress:
00629 //      kdDebug(5850) << "koagenda: filtered button press" << endl;
00630       if (object != viewport()) {
00631         if (me->button() == RightButton) {
00632           mClickedItem = dynamic_cast<KOAgendaItem *>(object);
00633           if (mClickedItem) {
00634             selectItem(mClickedItem);
00635             emit showIncidencePopupSignal( mCalendar,
00636                                            mClickedItem->incidence(),
00637                                            mClickedItem->itemDate() );
00638           } else {
00639             return QScrollView::eventFilter( object, me ); // pass through for use by multiagenda
00640           }
00641         } else {
00642           KOAgendaItem* item = dynamic_cast<KOAgendaItem *>(object);
00643           if (item) {
00644             Incidence *incidence = item->incidence();
00645             if ( incidence->isReadOnly() ) {
00646               mActionItem = 0;
00647               mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00648             } else {
00649               mActionItem = item;
00650               mResPair = CalHelper::incSubResourceCalendar( mCalendar, incidence );
00651               startItemAction(viewportPos);
00652             }
00653             // Warning: do selectItem() as late as possible, since all
00654             // sorts of things happen during this call. Some can lead to
00655             // this filter being run again and mActionItem being set to
00656             // null.
00657             selectItem( item );
00658           } else {
00659             return QScrollView::eventFilter( object, me ); // pass through for use by multiagenda
00660           }
00661         }
00662       } else {
00663         if ( me->button() == RightButton ) {
00664           // if mouse pointer is not in selection, select the cell below the cursor
00665           QPoint gpos = contentsToGrid( viewportToContents( viewportPos ) );
00666           if ( !ptInSelection( gpos ) ) {
00667             mSelectionStartCell = gpos;
00668             mSelectionEndCell = gpos;
00669             mHasSelection = true;
00670             emit newStartSelectSignal();
00671             emit newTimeSpanSignal( mSelectionStartCell, mSelectionEndCell );
00672             updateContents();
00673           }
00674           showNewEventPopupSignal();
00675         } else {
00676           // if mouse pointer is in selection, don't change selection
00677           QPoint gpos = contentsToGrid( viewportToContents( viewportPos ) );
00678           if ( !ptInSelection( gpos ) ) {
00679             selectItem(0);
00680             mActionItem = 0;
00681             mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00682             setCursor(arrowCursor);
00683             startSelectAction(viewportPos);
00684           }
00685         }
00686         return QScrollView::eventFilter( object, me ); // pass through for use by multiagenda
00687       }
00688       break;
00689 
00690     case QEvent::MouseButtonRelease:
00691       if (mActionItem) {
00692         endItemAction();
00693       } else if ( mActionType == SELECT ) {
00694         endSelectAction( viewportPos );
00695       }
00696       // This nasty gridToContents(contentsToGrid(..)) is needed to
00697       // avoid an offset of a few pixels. Don't ask me why...
00698       emit mousePosSignal( gridToContents(contentsToGrid(
00699                            viewportToContents( viewportPos ) ) ));
00700       break;
00701 
00702     case QEvent::MouseMove: {
00703       // This nasty gridToContents(contentsToGrid(..)) is needed to
00704       // avoid an offset of a few pixels. Don't ask me why...
00705       QPoint indicatorPos = gridToContents(contentsToGrid(
00706                                           viewportToContents( viewportPos )));
00707       if (object != viewport()) {
00708         KOAgendaItem *moveItem = dynamic_cast<KOAgendaItem *>(object);
00709         if (moveItem && !moveItem->incidence()->isReadOnly() ) {
00710           if (!mActionItem)
00711             setNoActionCursor(moveItem,viewportPos);
00712           else {
00713             performItemAction(viewportPos);
00714 
00715             if ( mActionType == MOVE ) {
00716               // show cursor at the current begin of the item
00717               KOAgendaItem *firstItem = mActionItem->firstMultiItem();
00718               if (!firstItem) firstItem = mActionItem;
00719               indicatorPos = gridToContents( QPoint( firstItem->cellXLeft(),
00720                                                      firstItem->cellYTop() ) );
00721 
00722             } else if ( mActionType == RESIZEBOTTOM ) {
00723               // RESIZETOP is handled correctly, only resizebottom works differently
00724               indicatorPos = gridToContents( QPoint( mActionItem->cellXLeft(),
00725                                                      mActionItem->cellYBottom()+1 ) );
00726             }
00727 
00728           } // If we have an action item
00729         } // If move item && !read only
00730       } else {
00731         if ( mActionType == SELECT ) {
00732           performSelectAction( viewportPos );
00733 
00734           // show cursor at end of timespan
00735           if ( ((mStartCell.y() < mEndCell.y()) && (mEndCell.x() >= mStartCell.x())) ||
00736                (mEndCell.x() > mStartCell.x()) )
00737             indicatorPos = gridToContents( QPoint(mEndCell.x(), mEndCell.y()+1) );
00738           else
00739             indicatorPos = gridToContents( mEndCell );
00740         }
00741       }
00742       emit mousePosSignal( indicatorPos );
00743       break; }
00744 
00745     case QEvent::MouseButtonDblClick:
00746       if (object == viewport()) {
00747         selectItem(0);
00748         QPair<ResourceCalendar *, QString>p = mCalendarView->viewSubResourceCalendar();
00749         emit newEventSignal( p.first, p.second );
00750       } else {
00751         KOAgendaItem *doubleClickedItem = dynamic_cast<KOAgendaItem *>( object );
00752         if ( doubleClickedItem ) {
00753           selectItem( doubleClickedItem );
00754           emit editIncidenceSignal( doubleClickedItem->incidence(), doubleClickedItem->itemDate() );
00755         }
00756       }
00757       break;
00758 
00759     default:
00760       break;
00761   }
00762 
00763   return true;
00764 }
00765 
00766 bool KOAgenda::ptInSelection( QPoint gpos ) const
00767 {
00768   if ( !mHasSelection ) {
00769     return false;
00770   } else if ( gpos.x()<mSelectionStartCell.x() || gpos.x()>mSelectionEndCell.x() ) {
00771     return false;
00772   } else if ( (gpos.x()==mSelectionStartCell.x()) && (gpos.y()<mSelectionStartCell.y()) ) {
00773     return false;
00774   } else if ( (gpos.x()==mSelectionEndCell.x()) && (gpos.y()>mSelectionEndCell.y()) ) {
00775     return false;
00776   }
00777   return true;
00778 }
00779 
00780 void KOAgenda::startSelectAction( const QPoint &viewportPos )
00781 {
00782   emit newStartSelectSignal();
00783 
00784   mActionType = SELECT;
00785   mSelectionStartPoint = viewportPos;
00786   mHasSelection = true;
00787 
00788   QPoint pos = viewportToContents( viewportPos );
00789   QPoint gpos = contentsToGrid( pos );
00790 
00791   // Store new selection
00792   mStartCell = gpos;
00793   mEndCell = gpos;
00794   mSelectionStartCell = gpos;
00795   mSelectionEndCell = gpos;
00796 
00797   updateContents();
00798 }
00799 
00800 void KOAgenda::performSelectAction(const QPoint& viewportPos)
00801 {
00802   QPoint pos = viewportToContents( viewportPos );
00803   QPoint gpos = contentsToGrid( pos );
00804 
00805   QPoint clipperPos = clipper()->
00806                       mapFromGlobal(viewport()->mapToGlobal(viewportPos));
00807 
00808   // Scroll if cursor was moved to upper or lower end of agenda.
00809   if (clipperPos.y() < mScrollBorderWidth) {
00810     mScrollUpTimer.start(mScrollDelay);
00811   } else if (visibleHeight() - clipperPos.y() <
00812              mScrollBorderWidth) {
00813     mScrollDownTimer.start(mScrollDelay);
00814   } else {
00815     mScrollUpTimer.stop();
00816     mScrollDownTimer.stop();
00817   }
00818 
00819   if ( gpos != mEndCell ) {
00820     mEndCell = gpos;
00821     if ( mStartCell.x()>mEndCell.x() ||
00822          ( mStartCell.x()==mEndCell.x() && mStartCell.y()>mEndCell.y() ) ) {
00823       // backward selection
00824       mSelectionStartCell = mEndCell;
00825       mSelectionEndCell = mStartCell;
00826     } else {
00827       mSelectionStartCell = mStartCell;
00828       mSelectionEndCell = mEndCell;
00829     }
00830 
00831     updateContents();
00832   }
00833 }
00834 
00835 void KOAgenda::endSelectAction( const QPoint &currentPos )
00836 {
00837   mScrollUpTimer.stop();
00838   mScrollDownTimer.stop();
00839 
00840   mActionType = NOP;
00841 
00842   emit newTimeSpanSignal( mSelectionStartCell, mSelectionEndCell );
00843 
00844   if ( KOPrefs::instance()->mSelectionStartsEditor ) {
00845     if ( ( mSelectionStartPoint - currentPos ).manhattanLength() >
00846          QApplication::startDragDistance() ) {
00847        emitNewEventForSelection();
00848     }
00849   }
00850 }
00851 
00852 KOAgenda::MouseActionType KOAgenda::isInResizeArea( bool horizontal,
00853                                                     const QPoint &pos,
00854                                                     KOAgendaItem::GPtr item )
00855 {
00856   if ( !item ) {
00857     return NOP;
00858   }
00859 
00860   QPoint gridpos = contentsToGrid( pos );
00861   QPoint contpos = gridToContents( gridpos +
00862       QPoint( (KOGlobals::self()->reverseLayout())?1:0, 0 ) );
00863 
00864 //kdDebug(5850)<<"contpos="<<contpos<<", pos="<<pos<<", gpos="<<gpos<<endl;
00865 //kdDebug(5850)<<"clXLeft="<<clXLeft<<", clXRight="<<clXRight<<endl;
00866 
00867   if ( horizontal ) {
00868     int clXLeft = item->cellXLeft();
00869     int clXRight = item->cellXRight();
00870     if ( KOGlobals::self()->reverseLayout() ) {
00871       int tmp = clXLeft;
00872       clXLeft = clXRight;
00873       clXRight = tmp;
00874     }
00875     int gridDistanceX = int( pos.x() - contpos.x() );
00876     if (gridDistanceX < mResizeBorderWidth && clXLeft == gridpos.x() ) {
00877       if ( KOGlobals::self()->reverseLayout() ) return RESIZERIGHT;
00878       else return RESIZELEFT;
00879     } else if ((mGridSpacingX - gridDistanceX) < mResizeBorderWidth &&
00880                clXRight == gridpos.x() ) {
00881       if ( KOGlobals::self()->reverseLayout() ) return RESIZELEFT;
00882       else return RESIZERIGHT;
00883     } else {
00884       return MOVE;
00885     }
00886   } else {
00887     int gridDistanceY = int( pos.y() - contpos.y() );
00888     if (gridDistanceY < mResizeBorderWidth &&
00889         item->cellYTop() == gridpos.y() &&
00890         !item->firstMultiItem() ) {
00891       return RESIZETOP;
00892     } else if ((mGridSpacingY - gridDistanceY) < mResizeBorderWidth &&
00893                item->cellYBottom() == gridpos.y() &&
00894                !item->lastMultiItem() )  {
00895       return RESIZEBOTTOM;
00896     } else {
00897       return MOVE;
00898     }
00899   }
00900 }
00901 
00902 void KOAgenda::startItemAction(const QPoint& viewportPos)
00903 {
00904   QPoint pos = viewportToContents( viewportPos );
00905   mStartCell = contentsToGrid( pos );
00906   mEndCell = mStartCell;
00907 
00908   bool noResize = ( mActionItem->incidence()->type() == "Todo");
00909 
00910   mActionType = MOVE;
00911   if ( !noResize ) {
00912     mActionType = isInResizeArea( mAllDayMode, pos, mActionItem );
00913   }
00914 
00915 
00916   mActionItem->startMove();
00917   setActionCursor( mActionType, true );
00918 }
00919 
00920 void KOAgenda::performItemAction(const QPoint& viewportPos)
00921 {
00922 //  kdDebug(5850) << "viewportPos: " << viewportPos.x() << "," << viewportPos.y() << endl;
00923 //  QPoint point = viewport()->mapToGlobal(viewportPos);
00924 //  kdDebug(5850) << "Global: " << point.x() << "," << point.y() << endl;
00925 //  point = clipper()->mapFromGlobal(point);
00926 //  kdDebug(5850) << "clipper: " << point.x() << "," << point.y() << endl;
00927 //  kdDebug(5850) << "visible height: " << visibleHeight() << endl;
00928   QPoint pos = viewportToContents( viewportPos );
00929 //  kdDebug(5850) << "contents: " << x << "," << y << "\n" << endl;
00930   QPoint gpos = contentsToGrid( pos );
00931   QPoint clipperPos = clipper()->
00932                       mapFromGlobal(viewport()->mapToGlobal(viewportPos));
00933 
00934   // Cursor left active agenda area.
00935   // This starts a drag.
00936   if ( clipperPos.y() < 0 || clipperPos.y() > visibleHeight() ||
00937        clipperPos.x() < 0 || clipperPos.x() > visibleWidth() ) {
00938     if ( mActionType == MOVE ) {
00939       mScrollUpTimer.stop();
00940       mScrollDownTimer.stop();
00941       mActionItem->resetMove();
00942       placeSubCells( mActionItem );
00943       emit startDragSignal( mActionItem->incidence() );
00944       setCursor( arrowCursor );
00945       mActionItem = 0;
00946       mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00947       mActionType = NOP;
00948       mItemMoved = false;
00949       return;
00950     }
00951   } else {
00952     setActionCursor( mActionType );
00953   }
00954 
00955   // Scroll if item was moved to upper or lower end of agenda.
00956   if (clipperPos.y() < mScrollBorderWidth) {
00957     mScrollUpTimer.start(mScrollDelay);
00958   } else if (visibleHeight() - clipperPos.y() <
00959              mScrollBorderWidth) {
00960     mScrollDownTimer.start(mScrollDelay);
00961   } else {
00962     mScrollUpTimer.stop();
00963     mScrollDownTimer.stop();
00964   }
00965 
00966   // Move or resize item if necessary
00967   if ( mEndCell != gpos ) {
00968     if ( !mItemMoved ) {
00969       if ( !mChanger ||
00970            !mChanger->beginChange( mActionItem->incidence(), mResPair.first, mResPair.second ) ) {
00971         KMessageBox::information( this, i18n("Unable to lock item for "
00972                              "modification. You cannot make any changes."),
00973                              i18n("Locking Failed"), "AgendaLockingFailed" );
00974         mScrollUpTimer.stop();
00975         mScrollDownTimer.stop();
00976         mActionItem->resetMove();
00977         placeSubCells( mActionItem );
00978         setCursor( arrowCursor );
00979         mActionItem = 0;
00980         mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
00981         mActionType = NOP;
00982         mItemMoved = false;
00983         return;
00984       }
00985       mItemMoved = true;
00986     }
00987     mActionItem->raise();
00988     if (mActionType == MOVE) {
00989       // Move all items belonging to a multi item
00990       KOAgendaItem *firstItem = mActionItem->firstMultiItem();
00991       if (!firstItem) firstItem = mActionItem;
00992       KOAgendaItem *lastItem = mActionItem->lastMultiItem();
00993       if (!lastItem) lastItem = mActionItem;
00994       QPoint deltapos = gpos - mEndCell;
00995       KOAgendaItem *moveItem = firstItem;
00996       while (moveItem) {
00997         bool changed=false;
00998         if ( deltapos.x()!=0 ) {
00999           moveItem->moveRelative( deltapos.x(), 0 );
01000           changed=true;
01001         }
01002         // in agenda's all day view don't try to move multi items, since there are none
01003         if ( moveItem==firstItem && !mAllDayMode ) { // is the first item
01004           int newY = deltapos.y() + moveItem->cellYTop();
01005           // If event start moved earlier than 0:00, it starts the previous day
01006           if ( newY<0 ) {
01007             moveItem->expandTop( -moveItem->cellYTop() );
01008             // prepend a new item at ( x-1, rows()+newY to rows() )
01009             KOAgendaItem *newFirst = firstItem->prevMoveItem();
01010             // cell's y values are first and last cell of the bar, so if newY=-1, they need to be the same
01011             if (newFirst) {
01012               newFirst->setCellXY(moveItem->cellXLeft()-1, rows()+newY, rows()-1);
01013               mItems.append( newFirst );
01014               moveItem->resize( int( mGridSpacingX * newFirst->cellWidth() ),
01015                                 int( mGridSpacingY * newFirst->cellHeight() ));
01016               QPoint cpos = gridToContents( QPoint( newFirst->cellXLeft(), newFirst->cellYTop() ) );
01017               addChild( newFirst, cpos.x(), cpos.y() );
01018             } else {
01019               newFirst = insertItem( moveItem->incidence(), moveItem->itemDate(),
01020                 moveItem->cellXLeft()-1, rows()+newY, rows()-1, moveItem->itemPos(), moveItem->itemCount() ) ;
01021             }
01022             if (newFirst) newFirst->show();
01023             moveItem->prependMoveItem(newFirst);
01024             firstItem=newFirst;
01025           } else if ( newY>=rows() ) {
01026             // If event start is moved past 24:00, it starts the next day
01027             // erase current item (i.e. remove it from the multiItem list)
01028             firstItem = moveItem->nextMultiItem();
01029             moveItem->hide();
01030             mItems.remove( mItems.find( moveItem ) ); // Don't free memory, QPtrList::take() was used here.
01031             removeChild( moveItem );
01032             mActionItem->removeMoveItem(moveItem);
01033             moveItem=firstItem;
01034             // adjust next day's item
01035             if (moveItem) moveItem->expandTop( rows()-newY );
01036           } else {
01037             moveItem->expandTop(deltapos.y());
01038           }
01039           changed=true;
01040         }
01041         if ( !moveItem->lastMultiItem() && !mAllDayMode ) { // is the last item
01042           int newY = deltapos.y()+moveItem->cellYBottom();
01043           if (newY<0) {
01044             // erase current item
01045             lastItem = moveItem->prevMultiItem();
01046             moveItem->hide();
01047             mItems.remove( mItems.find(moveItem) ); // Don't free memory, QPtrList::take() was used here.
01048             removeChild( moveItem );
01049             moveItem->removeMoveItem( moveItem );
01050             moveItem = lastItem;
01051             moveItem->expandBottom(newY+1);
01052           } else if (newY>=rows()) {
01053             moveItem->expandBottom( rows()-moveItem->cellYBottom()-1 );
01054             // append item at ( x+1, 0 to newY-rows() )
01055             KOAgendaItem *newLast = lastItem->nextMoveItem();
01056             if (newLast) {
01057               newLast->setCellXY( moveItem->cellXLeft()+1, 0, newY-rows()-1 );
01058               mItems.append(newLast);
01059               moveItem->resize( int( mGridSpacingX * newLast->cellWidth() ),
01060                                 int( mGridSpacingY * newLast->cellHeight() ));
01061               QPoint cpos = gridToContents( QPoint( newLast->cellXLeft(), newLast->cellYTop() ) ) ;
01062               addChild( newLast, cpos.x(), cpos.y() );
01063             } else {
01064               newLast = insertItem( moveItem->incidence(), moveItem->itemDate(),
01065                 moveItem->cellXLeft()+1, 0, newY-rows()-1, moveItem->itemPos(), moveItem->itemCount() ) ;
01066             }
01067             moveItem->appendMoveItem( newLast );
01068             newLast->show();
01069             lastItem = newLast;
01070           } else {
01071             moveItem->expandBottom( deltapos.y() );
01072           }
01073           changed=true;
01074         }
01075         if (changed) {
01076           adjustItemPosition( moveItem );
01077         }
01078         moveItem = moveItem->nextMultiItem();
01079       }
01080     } else if (mActionType == RESIZETOP) {
01081       if (mEndCell.y() <= mActionItem->cellYBottom()) {
01082         mActionItem->expandTop(gpos.y() - mEndCell.y());
01083         adjustItemPosition( mActionItem );
01084       }
01085     } else if (mActionType == RESIZEBOTTOM) {
01086       if (mEndCell.y() >= mActionItem->cellYTop()) {
01087         mActionItem->expandBottom(gpos.y() - mEndCell.y());
01088         adjustItemPosition( mActionItem );
01089       }
01090     } else if (mActionType == RESIZELEFT) {
01091       if (mEndCell.x() <= mActionItem->cellXRight()) {
01092         mActionItem->expandLeft( gpos.x() - mEndCell.x() );
01093         adjustItemPosition( mActionItem );
01094       }
01095     } else if (mActionType == RESIZERIGHT) {
01096       if (mEndCell.x() >= mActionItem->cellXLeft()) {
01097         mActionItem->expandRight(gpos.x() - mEndCell.x());
01098         adjustItemPosition( mActionItem );
01099       }
01100     }
01101     mEndCell = gpos;
01102   }
01103 }
01104 
01105 void KOAgenda::endItemAction()
01106 {
01107 //  kdDebug(5850) << "KOAgenda::endItemAction() " << endl;
01108   mActionType = NOP;
01109   mScrollUpTimer.stop();
01110   mScrollDownTimer.stop();
01111   setCursor( arrowCursor );
01112   bool useLastGroupwareDialogAnswer = false;
01113   bool addIncidence = false;
01114   bool multiModify = false;
01115   bool endUpdatesPending = false;
01116   // FIXME: do the cloning here...
01117   Incidence *inc = mActionItem->incidence();
01118 
01119   if ( mStartCell.x() == mEndCell.x() && mStartCell.y() == mEndCell.y() ) {
01120     // not really moved, so stop any change
01121     if ( mItemMoved ) {
01122       mItemMoved = false;
01123       mChanger->endChange( inc, mResPair.first, mResPair.second );
01124     }
01125   }
01126 
01127   if ( mItemMoved ) {
01128     Incidence *incToChange = inc;
01129     if ( mActionItem->incidence()->doesRecur() ) {
01130 
01131       kdDebug() << "mActionItem->incidence()->dtStart() is " << inc->dtStart() << endl;
01132 
01133       Incidence* oldIncSaved = inc->clone();
01134       KOGlobals::WhichOccurrences chosenOption;
01135       inc->startUpdates();
01136       incToChange = mCalendarView->singleOccurrenceOrAll( inc,
01137                                                           KOGlobals::EDIT,
01138                                                           chosenOption,
01139                                                           mActionItem->itemDate() );
01140 
01141       if ( chosenOption == KOGlobals::ONLY_THIS_ONE ||
01142            chosenOption == KOGlobals::ONLY_FUTURE ) {
01143         multiModify = true;
01144         mAgendaView->enableAgendaUpdate( false );
01145         useLastGroupwareDialogAnswer = true;
01146         addIncidence = true;
01147         mAgendaView->enableAgendaUpdate( true );
01148         const KOGlobals::WhatChanged whatChanged = chosenOption == KOGlobals::ONLY_THIS_ONE ?
01149                                                            KOGlobals::RECURRENCE_MODIFIED_ONE_ONLY :
01150                                                           KOGlobals::RECURRENCE_MODIFIED_ALL_FUTURE;
01151 
01152         const bool success = mChanger->changeIncidence( oldIncSaved, inc, whatChanged, this );
01153 
01154         if ( success ) {
01155           mActionItem->dissociateFromMultiItem();
01156           mActionItem->setIncidence( incToChange );
01157           //inc->endUpdates();
01158           endUpdatesPending = true;
01159         } else {
01160           inc->cancelUpdates();
01161           incToChange = 0;
01162         }
01163       } else {
01164         endUpdatesPending = true;
01165         //inc->endUpdates();
01166       }
01167     }
01168 
01169     if ( incToChange ) {
01170       mActionItem->endMove();
01171       KOAgendaItem *placeItem = mActionItem->firstMultiItem();
01172       if  ( !placeItem ) {
01173         placeItem = mActionItem;
01174       }
01175 
01176       KOAgendaItem *modif = placeItem;
01177       AgendaItemList oldconflictItems = placeItem->conflictItems();
01178 
01179       AgendaItemList::Iterator it;
01180       for ( it = oldconflictItems.begin(); it != oldconflictItems.end(); ++it ) {
01181         if ( *it ) {
01182           placeSubCells( *it );
01183         }
01184       }
01185 
01186       while ( placeItem ) {
01187         placeSubCells( placeItem );
01188         placeItem = placeItem->nextMultiItem();
01189       }
01190 
01191       // Notify about change
01192       // the agenda view will apply the changes to the actual Incidence*!
01193       mChanger->endChange( inc, mResPair.first, mResPair.second );
01194       kdDebug() << "Modified." << endl;
01195       mAgendaView->updateEventDates( modif, useLastGroupwareDialogAnswer, mResPair.first, mResPair.second, addIncidence );
01196     } else {
01197       mActionItem->resetMove();
01198       placeSubCells( mActionItem );
01199 
01200       // the item was moved, but not further modified, since it's not recurring
01201       // make sure the view updates anyhow, with the right item
01202       mChanger->endChange( inc, mResPair.first, mResPair.second );
01203       kdDebug() << "Not modified." << endl;
01204       mAgendaView->updateEventDates( mActionItem,
01205                                      useLastGroupwareDialogAnswer,
01206                                      mResPair.first,
01207                                      mResPair.second,
01208                                      addIncidence );
01209     }
01210   }
01211 
01212   if ( endUpdatesPending )
01213     inc->endUpdates();
01214 
01215   mActionItem = 0;
01216   mResPair = qMakePair( static_cast<ResourceCalendar *>( 0 ), QString() );
01217   mItemMoved = false;
01218 
01219   if ( multiModify ) {
01220     emit endMultiModify();
01221   }
01222 
01223   kdDebug(5850) << "KOAgenda::endItemAction() done" << endl;
01224 }
01225 
01226 void KOAgenda::setActionCursor( int actionType, bool acting )
01227 {
01228   switch ( actionType ) {
01229     case MOVE:
01230       if (acting) setCursor( sizeAllCursor );
01231       else setCursor( arrowCursor );
01232       break;
01233     case RESIZETOP:
01234     case RESIZEBOTTOM:
01235       setCursor( sizeVerCursor );
01236       break;
01237     case RESIZELEFT:
01238     case RESIZERIGHT:
01239       setCursor( sizeHorCursor );
01240       break;
01241     default:
01242       setCursor( arrowCursor );
01243   }
01244 }
01245 
01246 void KOAgenda::setNoActionCursor( KOAgendaItem::GPtr moveItem, const QPoint& viewportPos )
01247 {
01248 //  kdDebug(5850) << "viewportPos: " << viewportPos.x() << "," << viewportPos.y() << endl;
01249 //  QPoint point = viewport()->mapToGlobal(viewportPos);
01250 //  kdDebug(5850) << "Global: " << point.x() << "," << point.y() << endl;
01251 //  point = clipper()->mapFromGlobal(point);
01252 //  kdDebug(5850) << "clipper: " << point.x() << "," << point.y() << endl;
01253 
01254   if ( !moveItem ) {
01255     return;
01256   }
01257 
01258   QPoint pos = viewportToContents( viewportPos );
01259   bool noResize = (moveItem && moveItem->incidence() &&
01260       moveItem->incidence()->type() == "Todo");
01261 
01262   KOAgenda::MouseActionType resizeType = MOVE;
01263   if ( !noResize ) resizeType = isInResizeArea( mAllDayMode, pos , moveItem);
01264   setActionCursor( resizeType );
01265 }
01266 
01267 
01271 double KOAgenda::calcSubCellWidth( KOAgendaItem::GPtr item )
01272 {
01273   if ( !item ) {
01274     return 0;
01275   }
01276 
01277   QPoint pt, pt1;
01278   pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
01279   pt1 = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) +
01280                         QPoint( 1, 1 ) );
01281   pt1 -= pt;
01282   int maxSubCells = item->subCells();
01283   double newSubCellWidth;
01284   if ( mAllDayMode ) {
01285     newSubCellWidth = double( pt1.y() ) / maxSubCells;
01286   } else {
01287     newSubCellWidth = double( pt1.x() ) / maxSubCells;
01288   }
01289   return newSubCellWidth;
01290 }
01291 
01292 void KOAgenda::adjustItemPosition( KOAgendaItem::GPtr item )
01293 {
01294   if (!item) return;
01295   item->resize( int( mGridSpacingX * item->cellWidth() ),
01296                 int( mGridSpacingY * item->cellHeight() ) );
01297   int clXLeft = item->cellXLeft();
01298   if ( KOGlobals::self()->reverseLayout() )
01299     clXLeft = item->cellXRight() + 1;
01300   QPoint cpos = gridToContents( QPoint( clXLeft, item->cellYTop() ) );
01301   moveChild( item, cpos.x(), cpos.y() );
01302 }
01303 
01304 void KOAgenda::placeAgendaItem( KOAgendaItem::GPtr item, double subCellWidth )
01305 {
01306   // kdDebug(5850) << "KOAgenda::placeAgendaItem(): " << item->incidence()->summary()
01307   //               << " subCellWidth: " << subCellWidth << endl;
01308 
01309   if ( !item ) {
01310     return;
01311   }
01312 
01313   // "left" upper corner, no subcells yet, RTL layouts have right/left switched, widths are negative then
01314   const QPoint pt = gridToContents( QPoint( item->cellXLeft(), item->cellYTop() ) );
01315 
01316   // right lower corner
01317   const QPoint pt1 = gridToContents( QPoint( item->cellXLeft() + item->cellWidth(),
01318                                              item->cellYBottom()+1 ) );
01319 
01320   const double subCellPos = item->subCell() * subCellWidth;
01321 
01322   // we need to add 0.01 to make sure we don't loose one pixed due to
01323   // numerics (i.e. if it would be x.9998, we want the integer, not rounded down.
01324   const double delta = subCellWidth < 0 ? -0.01 : 0.01;
01325 
01326   int height, width, xpos, ypos;
01327   if ( mAllDayMode ) {
01328     width = pt1.x()-pt.x();
01329     height = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
01330     xpos = pt.x();
01331     ypos = pt.y() + int( subCellPos );
01332   } else {
01333     width = int( subCellPos + subCellWidth + delta ) - int( subCellPos );
01334     height = pt1.y()-pt.y();
01335     xpos = pt.x() + int( subCellPos );
01336     ypos = pt.y();
01337   }
01338   if ( KOGlobals::self()->reverseLayout() ) { // RTL language/layout
01339     xpos += width;
01340     width = -width;
01341   }
01342   if ( height<0 ) { // BTT (bottom-to-top) layout ?!?
01343     ypos += height;
01344     height = -height;
01345   }
01346   item->resize( width, height );
01347   moveChild( item, xpos, ypos );
01348 }
01349 
01350 /*
01351   Place item in cell and take care that multiple items using the same cell do
01352   not overlap. This method is not yet optimal. It doesn't use the maximum space
01353   it can get in all cases.
01354   At the moment the method has a bug: When an item is placed only the sub cell
01355   widths of the items are changed, which are within the Y region the item to
01356   place spans. When the sub cell width change of one of this items affects a
01357   cell, where other items are, which do not overlap in Y with the item to place,
01358   the display gets corrupted, although the corruption looks quite nice.
01359 
01360   18/10/2010 UPDATE: this is fixed with to the updateWidthsRecursivley()
01361   call. kolab/issue2762.
01362 */
01363 void KOAgenda::placeSubCells( KOAgendaItem::GPtr placeItem )
01364 {
01365 #if 0
01366   kdDebug(5850) << "KOAgenda::placeSubCells()" << endl;
01367   if ( placeItem ) {
01368     Incidence *event = placeItem->incidence();
01369     if ( !event ) {
01370       kdDebug(5850) << "  event is 0" << endl;
01371     } else {
01372       kdDebug(5850) << "  event: " << event->summary() << endl;
01373     }
01374   } else {
01375     kdDebug(5850) << "  placeItem is 0" << endl;
01376   }
01377   kdDebug(5850) << "KOAgenda::placeSubCells()..." << endl;
01378 #endif
01379 
01380   if ( !placeItem ) {
01381     return;
01382   }
01383 
01384   QPtrList<KOrg::CellItem> cells;
01385   AgendaItemList::Iterator it;
01386   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01387     if ( *it ) {
01388       cells.append( *it );
01389     }
01390   }
01391 
01392   QPtrList<KOrg::CellItem> items = KOrg::CellItem::placeItem( cells,
01393                                                               placeItem );
01394   QPtrList<KOAgendaItem> updatedCells;
01395 
01396   KOrg::CellItem *i;
01397   placeItem->setConflictItems( AgendaItemList() );
01398   const double newSubCellWidth = calcSubCellWidth( placeItem );
01399   for ( i = items.first(); i; i = items.next() ) {
01400     KOAgendaItem *item = static_cast<KOAgendaItem *>( i );
01401     placeAgendaItem( item, newSubCellWidth );
01402     item->addConflictItem( placeItem );
01403 
01404     if ( placeItem->incidence() && placeItem->incidence()->doesFloat() ) {
01405       // untested for non-allday incidences, hence the check.
01406       updateWidthsRecursivley( item, updatedCells, placeItem->subCells() );
01407     }
01408 
01409     placeItem->addConflictItem( item );
01410   }
01411   if ( items.isEmpty() ) {
01412     placeAgendaItem( placeItem, newSubCellWidth );
01413   }
01414   placeItem->update();
01415 }
01416 
01418 bool KOAgenda::updateWidthsRecursivley( KOAgendaItem *item,
01419                                         QPtrList<KOAgendaItem> &itemsAlreadyUpdated,
01420                                         int numSubCells )
01421 {
01422   bool result = false;
01423   if ( !itemsAlreadyUpdated.contains( item ) ) {
01424     item->setSubCells( numSubCells );
01425 
01426     // So we don't end up in infinit recursion due to cycles.
01427     itemsAlreadyUpdated.append( item );
01428 
01429     // Tell our neighbours too.
01430     AgendaItemList list = item->conflictItems();
01431 
01432     for ( int i = 0; i < list.count(); ++i ) {
01433       KOAgendaItem::GPtr it = list[i];
01434       if ( it && numSubCells > it->subCells() ) {
01435         updateWidthsRecursivley( it, itemsAlreadyUpdated, numSubCells );
01436         result = true;
01437       }
01438     }
01439   }
01440 
01441   return result;
01442 }
01443 
01444 int KOAgenda::columnWidth( int column )
01445 {
01446   const int start = gridToContents( QPoint( column, 0 ) ).x();
01447   if (KOGlobals::self()->reverseLayout() )
01448     column--;
01449   else
01450     column++;
01451   const int end = gridToContents( QPoint( column, 0 ) ).x();
01452   return end - start;
01453 }
01454 /*
01455   Draw grid in the background of the agenda.
01456 */
01457 void KOAgenda::drawContents(QPainter* p, int cx, int cy, int cw, int ch)
01458 {
01459   QPixmap db(cw, ch);
01460   db.fill(KOPrefs::instance()->mAgendaBgColor);
01461   QPainter dbp(&db);
01462   dbp.translate(-cx,-cy);
01463 
01464   double lGridSpacingY = mGridSpacingY*2;
01465 
01466   // If work day, use work color
01467   // If busy day, use busy color
01468   // if work and busy day, mix both, and busy color has alpha
01469 
01470   const QMemArray<bool> busyDayMask = mAgendaView->busyDayMask();
01471 
01472   if ( KOPrefs::instance()->mColorBusyDaysEnabled && !mAllDayMode ) {
01473     for ( int i = 0; i < busyDayMask.count(); ++i ) {
01474       if ( busyDayMask[i] ) {
01475         const QPoint pt1( cx + mGridSpacingX * i, 0 );
01476         // const QPoint pt2( cx + mGridSpacingX * ( i+1 ), ch );
01477         dbp.fillRect( pt1.x(), pt1.y(), mGridSpacingX, cy + ch, KOPrefs::instance()->mAgendaMonthBgBusyColor );
01478       }
01479     }
01480   }
01481   // Highlight working hours
01482   if ( mWorkingHoursEnable ) {
01483 
01484     QColor workAndBusyColor;
01485 
01486     if ( KOPrefs::instance()->mColorBusyDaysEnabled ) {
01487       workAndBusyColor = KOHelper::mixColors( KOPrefs::instance()->mAgendaMonthBgBusyColor, 0.60, KOPrefs::instance()->mWorkingHoursColor );
01488     } else {
01489       workAndBusyColor = KOPrefs::instance()->mWorkingHoursColor;
01490     }
01491 
01492     const QPoint pt1( cx, mWorkingHoursYTop );
01493     const QPoint pt2( cx+cw, mWorkingHoursYBottom );
01494     if ( pt2.x() >= pt1.x() /*&& pt2.y() >= pt1.y()*/) {
01495       int gxStart = contentsToGrid( pt1 ).x();
01496       int gxEnd = contentsToGrid( pt2 ).x();
01497       // correct start/end for rtl layouts
01498       if ( gxStart > gxEnd ) {
01499         const int tmp = gxStart;
01500         gxStart = gxEnd;
01501         gxEnd = tmp;
01502       }
01503       const int xoffset = ( KOGlobals::self()->reverseLayout() ? 1 : 0 );
01504       while( gxStart <= gxEnd ) {
01505         const int xStart = gridToContents( QPoint( gxStart+xoffset, 0 ) ).x();
01506         const int xWidth = columnWidth( gxStart ) + 1;
01507         QColor color;
01508         if ( gxStart < busyDayMask.count() && busyDayMask[gxStart] ) {
01509           color = workAndBusyColor;
01510         } else {
01511           color = KOPrefs::instance()->mWorkingHoursColor;
01512         }
01513         if ( pt2.y() < pt1.y() ) {
01514           // overnight working hours
01515           if ( ( (gxStart==0) && !mHolidayMask->at(mHolidayMask->count()-1) ) ||
01516                ( (gxStart>0) && (gxStart<int(mHolidayMask->count())) && (!mHolidayMask->at(gxStart-1) ) ) ) {
01517             if ( pt2.y() > cy ) {
01518               dbp.fillRect( xStart, cy, xWidth, pt2.y() - cy + 1, color );
01519             }
01520           }
01521           if ( (gxStart < int(mHolidayMask->count()-1)) && (!mHolidayMask->at(gxStart)) ) {
01522             if ( pt1.y() < cy + ch - 1 ) {
01523               dbp.fillRect( xStart, pt1.y(), xWidth, cy + ch - pt1.y() + 1,
01524                             color );
01525             }
01526           }
01527         } else {
01528           // last entry in holiday mask denotes the previous day not visible (needed for overnight shifts)
01529           if ( gxStart < int(mHolidayMask->count()-1) && !mHolidayMask->at(gxStart)) {
01530             dbp.fillRect( xStart, pt1.y(), xWidth, pt2.y() - pt1.y() + 1,
01531                           color );
01532           }
01533         }
01534         ++gxStart;
01535       }
01536     }
01537   }
01538 
01539 
01540 
01541   // draw selection
01542   if ( mHasSelection ) {
01543     QPoint pt, pt1;
01544 
01545     if ( mSelectionEndCell.x() > mSelectionStartCell.x() ) { // multi day selection
01546       // draw start day
01547       pt = gridToContents( mSelectionStartCell );
01548       pt1 = gridToContents( QPoint( mSelectionStartCell.x() + 1, mRows + 1 ) );
01549       dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01550       // draw all other days between the start day and the day of the selection end
01551       for ( int c = mSelectionStartCell.x() + 1; c < mSelectionEndCell.x(); ++c ) {
01552         pt = gridToContents( QPoint( c, 0 ) );
01553         pt1 = gridToContents( QPoint( c + 1, mRows + 1 ) );
01554         dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01555       }
01556       // draw end day
01557       pt = gridToContents( QPoint( mSelectionEndCell.x(), 0 ) );
01558       pt1 = gridToContents( mSelectionEndCell + QPoint(1,1) );
01559       dbp.fillRect( QRect( pt, pt1), KOPrefs::instance()->mHighlightColor );
01560     }  else { // single day selection
01561       pt = gridToContents( mSelectionStartCell );
01562       pt1 = gridToContents( mSelectionEndCell + QPoint(1,1) );
01563       dbp.fillRect( QRect( pt, pt1 ), KOPrefs::instance()->mHighlightColor );
01564     }
01565   }
01566 
01567   QPen hourPen( KOPrefs::instance()->mAgendaBgColor.dark( 150 ) );
01568   QPen halfHourPen( KOPrefs::instance()->mAgendaBgColor.dark( 125 ) );
01569   dbp.setPen( hourPen );
01570 
01571   // Draw vertical lines of grid, start with the last line not yet visible
01572   //  kdDebug(5850) << "drawContents cx: " << cx << " cy: " << cy << " cw: " << cw << " ch: " << ch << endl;
01573   double x = ( int( cx / mGridSpacingX ) ) * mGridSpacingX;
01574   while (x < cx + cw) {
01575     dbp.drawLine( int( x ), cy, int( x ), cy + ch );
01576     x+=mGridSpacingX;
01577   }
01578 
01579   // Draw horizontal lines of grid
01580   double y = ( int( cy / (2*lGridSpacingY) ) ) * 2 * lGridSpacingY;
01581   while (y < cy + ch) {
01582 //    kdDebug(5850) << " y: " << y << endl;
01583     dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
01584     y += 2 * lGridSpacingY;
01585   }
01586   y = ( 2 * int( cy / (2*lGridSpacingY) ) + 1) * lGridSpacingY;
01587   dbp.setPen( halfHourPen );
01588   while (y < cy + ch) {
01589 //    kdDebug(5850) << " y: " << y << endl;
01590     dbp.drawLine( cx, int( y ), cx + cw, int( y ) );
01591     y+=2*lGridSpacingY;
01592   }
01593   p->drawPixmap(cx,cy, db);
01594 }
01595 
01596 /*
01597   Convert srcollview contents coordinates to agenda grid coordinates.
01598 */
01599 QPoint KOAgenda::contentsToGrid ( const QPoint &pos ) const
01600 {
01601   int gx = int( KOGlobals::self()->reverseLayout() ?
01602         mColumns - pos.x()/mGridSpacingX : pos.x()/mGridSpacingX );
01603   int gy = int( pos.y()/mGridSpacingY );
01604   return QPoint( gx, gy );
01605 }
01606 
01607 /*
01608   Convert agenda grid coordinates to scrollview contents coordinates.
01609 */
01610 QPoint KOAgenda::gridToContents( const QPoint &gpos ) const
01611 {
01612   int x = int( KOGlobals::self()->reverseLayout() ?
01613              (mColumns - gpos.x())*mGridSpacingX : gpos.x()*mGridSpacingX );
01614   int y = int( gpos.y()*mGridSpacingY );
01615   return QPoint( x, y );
01616 }
01617 
01618 
01619 /*
01620   Return Y coordinate corresponding to time. Coordinates are rounded to fit into
01621   the grid.
01622 */
01623 int KOAgenda::timeToY(const QTime &time)
01624 {
01625 //  kdDebug(5850) << "Time: " << time.toString() << endl;
01626   int minutesPerCell = 24 * 60 / mRows;
01627 //  kdDebug(5850) << "minutesPerCell: " << minutesPerCell << endl;
01628   int timeMinutes = time.hour() * 60 + time.minute();
01629 //  kdDebug(5850) << "timeMinutes: " << timeMinutes << endl;
01630   int Y = (timeMinutes + (minutesPerCell / 2)) / minutesPerCell;
01631 //  kdDebug(5850) << "y: " << Y << endl;
01632 //  kdDebug(5850) << "\n" << endl;
01633   return Y;
01634 }
01635 
01636 
01637 /*
01638   Return time corresponding to cell y coordinate. Coordinates are rounded to
01639   fit into the grid.
01640 */
01641 QTime KOAgenda::gyToTime(int gy)
01642 {
01643 //  kdDebug(5850) << "gyToTime: " << gy << endl;
01644   int secondsPerCell = 24 * 60 * 60/ mRows;
01645 
01646   int timeSeconds = secondsPerCell * gy;
01647 
01648   QTime time( 0, 0, 0 );
01649   if ( timeSeconds < 24 * 60 * 60 ) {
01650     time = time.addSecs(timeSeconds);
01651   } else {
01652     time.setHMS( 23, 59, 59 );
01653   }
01654 //  kdDebug(5850) << "  gyToTime: " << time.toString() << endl;
01655 
01656   return time;
01657 }
01658 
01659 QMemArray<int> KOAgenda::minContentsY()
01660 {
01661   QMemArray<int> minArray;
01662   minArray.fill( timeToY( QTime(23, 59) ), mSelectedDates.count() );
01663 
01664   AgendaItemList::Iterator it;
01665   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01666     if ( *it ) {
01667       const int ymin = (*it)->cellYTop();
01668       const int index = (*it)->cellXLeft();
01669       if ( index >= 0 && index < static_cast<int>( mSelectedDates.count() ) ) {
01670         if ( ymin < minArray[index] && !mItemsToDelete.contains( *it ) )
01671           minArray[index] = ymin;
01672       }
01673     }
01674   }
01675 
01676   return minArray;
01677 }
01678 
01679 QMemArray<int> KOAgenda::maxContentsY()
01680 {
01681   QMemArray<int> maxArray;
01682   maxArray.fill( timeToY( QTime(0, 0) ), mSelectedDates.count() );
01683   AgendaItemList::Iterator it;
01684   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01685     if ( *it ) {
01686       const int ymax = (*it)->cellYBottom();
01687       const int index = (*it)->cellXLeft();
01688       if ( index >= 0 && index < static_cast<int>( mSelectedDates.count() ) ) {
01689         if ( ymax > maxArray[index] && !mItemsToDelete.contains( *it ) )
01690           maxArray[index] = ymax;
01691       }
01692     }
01693   }
01694 
01695   return maxArray;
01696 }
01697 
01698 void KOAgenda::setStartTime( const QTime &startHour )
01699 {
01700   const double startPos = ( startHour.hour()/24. + startHour.minute()/1440. +
01701                             startHour.second()/86400. ) * mRows * gridSpacingY();
01702   setContentsPos( 0, static_cast<int>( startPos ) );
01703 }
01704 
01705 
01706 /*
01707   Insert KOAgendaItem into agenda.
01708 */
01709 KOAgendaItem *KOAgenda::insertItem( Incidence *incidence, const QDate &qd, int X,
01710                                     int YTop, int YBottom, int itemPos, int itemCount )
01711 {
01712   if ( mAllDayMode ) {
01713     kdDebug(5850) << "KOAgenda: calling insertItem in all-day mode is illegal." << endl;
01714     return 0;
01715   }
01716 
01717   mActionType = NOP;
01718 
01719   KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, incidence, qd, viewport(), itemPos, itemCount );
01720   connect( agendaItem, SIGNAL( removeAgendaItem( KOAgendaItem::GPtr  ) ),
01721            SLOT( removeAgendaItem( KOAgendaItem::GPtr  ) ) );
01722   connect( agendaItem, SIGNAL( showAgendaItem( KOAgendaItem::GPtr  ) ),
01723            SLOT( showAgendaItem( KOAgendaItem::GPtr  ) ) );
01724 
01725   if ( YBottom <= YTop ) {
01726     kdDebug(5850) << "KOAgenda::insertItem(): Text: " << agendaItem->text() << " YSize<0" << endl;
01727     YBottom = YTop;
01728   }
01729 
01730   agendaItem->resize( int( ( X + 1 ) * mGridSpacingX ) -
01731                       int( X * mGridSpacingX ),
01732                       int( YTop * mGridSpacingY ) -
01733                       int( ( YBottom + 1 ) * mGridSpacingY ) );
01734   agendaItem->setCellXY( X, YTop, YBottom );
01735   agendaItem->setCellXRight( X );
01736   agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, incidence ) );
01737   agendaItem->installEventFilter( this );
01738 
01739   addChild( agendaItem, int( X * mGridSpacingX ), int( YTop * mGridSpacingY ) );
01740   mItems.append( agendaItem );
01741 
01742   placeSubCells( agendaItem );
01743 
01744   agendaItem->show();
01745 
01746   marcus_bains();
01747 
01748   return agendaItem;
01749 }
01750 
01751 /*
01752   Insert all-day KOAgendaItem into agenda.
01753 */
01754 KOAgendaItem *KOAgenda::insertAllDayItem( Incidence *event, const QDate &qd,
01755                                           int XBegin, int XEnd )
01756 {
01757   if ( !mAllDayMode ) {
01758     kdWarning(5850) << "KOAgenda: calling insertAllDayItem in non all-day mode is illegal." << endl;
01759     return 0;
01760   }
01761 
01762   mActionType = NOP;
01763 
01764   KOAgendaItem *agendaItem = new KOAgendaItem( mCalendar, event, qd, viewport(), 1, 1 );
01765   connect( agendaItem, SIGNAL( removeAgendaItem( KOAgendaItem::GPtr ) ),
01766            SLOT( removeAgendaItem( KOAgendaItem::GPtr ) ) );
01767   connect( agendaItem, SIGNAL( showAgendaItem( KOAgendaItem::GPtr ) ),
01768            SLOT( showAgendaItem( KOAgendaItem::GPtr ) ) );
01769 
01770   { // Start hack
01771 
01772     // Fixes https://issues.kolab.org/issue3933 -
01773     // issue3933: A large wholeday event is not shown at all days in dayview.(rt#5884)
01774     // ( Some 32k pixel limit Qt3 has. )
01775     if ( event->doesFloat() && XBegin * mGridSpacingX <= -32767/*32k*/ + mGridSpacingX ) {
01776 
01777       // Our event starts _many_ days before dt, so we lie to agenda and make
01778       // it think the event starts one day before.
01779       // That part of the event won't be visible, so the lie won't have any effect
01780       // for the user
01781       XBegin = -1;
01782     }
01783   } // End hack
01784 
01785   agendaItem->setCellXY( XBegin, 0, 0 );
01786   agendaItem->setCellXRight( XEnd );
01787 
01788   const double startIt = mGridSpacingX * ( agendaItem->cellXLeft() );
01789   const double endIt = mGridSpacingX * ( agendaItem->cellWidth() +
01790                                          agendaItem->cellXLeft() );
01791 
01792   agendaItem->resize( int( endIt ) - int( startIt ), int( mGridSpacingY ) );
01793 
01794   agendaItem->installEventFilter( this );
01795   agendaItem->setResourceColor( KOHelper::resourceColor( mCalendar, event ) );
01796   addChild( agendaItem, int( XBegin * mGridSpacingX ), 0 );
01797   mItems.append( agendaItem );
01798 
01799   placeSubCells( agendaItem );
01800 
01801   agendaItem->show();
01802 
01803   return agendaItem;
01804 }
01805 
01806 
01807 void KOAgenda::insertMultiItem( Event *event, const QDate &qd, int XBegin, int XEnd,
01808                                 int YTop, int YBottom )
01809 {
01810   if ( mAllDayMode ) {
01811     kdDebug(5850) << "KOAgenda: calling insertMultiItem in all-day mode is illegal." << endl;
01812     return;
01813   }
01814   mActionType = NOP;
01815 
01816   int cellX,cellYTop,cellYBottom;
01817   QString newtext;
01818   int width = XEnd - XBegin + 1;
01819   int count = 0;
01820   KOAgendaItem *current = 0;
01821   QPtrList<KOAgendaItem> multiItems;
01822   const int visibleCount = mSelectedDates.first().daysTo( mSelectedDates.last() );
01823   for ( cellX = XBegin; cellX <= XEnd; ++cellX ) {
01824     ++count;
01825     //Only add the items that are visible.
01826     if( cellX >= 0 && cellX <= visibleCount ) {
01827       if ( cellX == XBegin ) {
01828         cellYTop = YTop;
01829       } else {
01830         cellYTop = 0;
01831       }
01832 
01833       if ( cellX == XEnd ) {
01834         cellYBottom = YBottom;
01835       } else {
01836         cellYBottom = rows() - 1;
01837       }
01838 
01839       newtext = QString("(%1/%2): ").arg( count ).arg( width );
01840       newtext.append( event->summary() );
01841 
01842       current = insertItem( event, qd, cellX, cellYTop, cellYBottom, count, width );
01843       current->setText( newtext );
01844       multiItems.append( current );
01845     }
01846   }
01847   QPtrList<KOAgendaItem>::iterator it = multiItems.begin();
01848   QPtrList<KOAgendaItem>::iterator e = multiItems.end();
01849 
01850   if ( it != e ) { // .first asserts if the list is empty
01851     KOAgendaItem *first = multiItems.first();
01852     KOAgendaItem *last = multiItems.last();
01853     KOAgendaItem *prev = 0, *next = 0;
01854 
01855     while ( it != e ) {
01856       KOAgendaItem *item = *it;
01857       ++it;
01858       next = ( it == e ) ? 0 : (*it);
01859       if ( item ) {
01860         item->setMultiItem( ( item == first ) ? 0 : first,
01861                             prev, next,
01862                             ( item == last ) ? 0 : last );
01863       }
01864       prev = item;
01865     }
01866   }
01867 
01868   marcus_bains();
01869 }
01870 
01871 void KOAgenda::removeIncidence( Incidence *incidence, bool relayoutNeighbours )
01872 {
01873   // First find all items to be deleted and store them
01874   // in its own list. Otherwise removeAgendaItem will reset
01875   // the current position and mess this up.
01876   QValueList<KOAgendaItem::GPtr > itemsToRemove;
01877 
01878   AgendaItemList::Iterator it;
01879   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01880     if ( *it ) {
01881       if ( (*it)->incidence() == incidence ) {
01882         itemsToRemove.append( *it );
01883       }
01884     }
01885   }
01886 
01887   for ( it = itemsToRemove.begin(); it != itemsToRemove.end(); ++it ) {
01888     removeAgendaItem( *it, relayoutNeighbours );
01889   }
01890 }
01891 
01892 void KOAgenda::showAgendaItem( KOAgendaItem::GPtr agendaItem )
01893 {
01894   if ( !agendaItem ) {
01895     return;
01896   }
01897 
01898   agendaItem->hide();
01899   addChild( agendaItem );
01900   if ( !mItems.contains( agendaItem ) ) {
01901     mItems.append( agendaItem );
01902   }
01903   placeSubCells( agendaItem );
01904 
01905   agendaItem->show();
01906 }
01907 
01908 bool KOAgenda::removeAgendaItem( KOAgendaItem::GPtr item, bool relayoutNeighbours )
01909 {
01910   // we found the item. Let's remove it and update the conflicts
01911   bool taken = false;
01912   KOAgendaItem::GPtr thisItem = item;
01913   AgendaItemList conflictItems = thisItem->conflictItems();
01914   removeChild( thisItem );
01915 
01916   AgendaItemList::Iterator it = mItems.find( thisItem );
01917   if ( it != mItems.end() ) {
01918     mItems.remove( it ); // Don't free memory, QPtrList::take() was used here.
01919     taken = true;
01920   }
01921 
01922   if ( relayoutNeighbours ) {
01923     for ( it = conflictItems.begin(); it != conflictItems.end(); ++it ) {
01924       // the item itself is also in its own conflictItems list!
01925       if ( *it != thisItem ) {
01926         placeSubCells( *it );
01927       }
01928     }
01929   }
01930 
01931   mItemsToDelete.append( thisItem );
01932   QTimer::singleShot( 0, this, SLOT( deleteItemsToDelete() ) );
01933   return taken;
01934 }
01935 
01936 void KOAgenda::deleteItemsToDelete()
01937 {
01938   freeItemList( mItemsToDelete );
01939   mItemsToDelete.clear();
01940 }
01941 
01942 /*QSizePolicy KOAgenda::sizePolicy() const
01943 {
01944   // Thought this would make the all-day event agenda minimum size and the
01945   // normal agenda take the remaining space. But it doesnt work. The QSplitter
01946   // dont seem to think that an Expanding widget needs more space than a
01947   // Preferred one.
01948   // But it doesnt hurt, so it stays.
01949   if (mAllDayMode) {
01950     return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
01951   } else {
01952     return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
01953   }
01954 }
01955 */
01956 
01957 /*
01958   Overridden from QScrollView to provide proper resizing of KOAgendaItems.
01959 */
01960 void KOAgenda::resizeEvent ( QResizeEvent *ev )
01961 {
01962 //  kdDebug(5850) << "KOAgenda::resizeEvent" << endl;
01963 
01964   QSize newSize( ev->size() );
01965   if (mAllDayMode) {
01966     mGridSpacingX = double( newSize.width() - 2 * frameWidth() ) / (double)mColumns;
01967     mGridSpacingY = newSize.height() - 2 * frameWidth();
01968   } else {
01969     int scrollbarWidth = vScrollBarMode() != AlwaysOff ? verticalScrollBar()->width() : 0;
01970     mGridSpacingX = double( newSize.width() - scrollbarWidth - 2 * frameWidth()) / double(mColumns);
01971     // make sure that there are not more than 24 per day
01972     mGridSpacingY = double(newSize.height() - 2 * frameWidth()) / double(mRows);
01973     if ( mGridSpacingY < mDesiredGridSpacingY )
01974       mGridSpacingY = mDesiredGridSpacingY;
01975   }
01976   calculateWorkingHours();
01977   QTimer::singleShot( 0, this, SLOT( resizeAllContents() ) );
01978   emit gridSpacingYChanged( mGridSpacingY * 4 );
01979   QScrollView::resizeEvent(ev);
01980 }
01981 
01982 void KOAgenda::resizeAllContents()
01983 {
01984   // For allday this is actually an height.
01985   double subCellWidth;
01986 
01987   if ( mItems.count() > 0 ) {
01988     KOAgendaItem::GPtr item;
01989     if ( mAllDayMode ) {
01990       AgendaItemList::Iterator it;
01991       for ( it = mItems.begin(); it != mItems.end(); ++it ) {
01992         if ( *it ) {
01993           subCellWidth = calcSubCellWidth( *it );
01994           placeAgendaItem( *it, subCellWidth );
01995         }
01996       }
01997     } else {
01998       AgendaItemList::Iterator it;
01999       for ( it = mItems.begin(); it != mItems.end(); ++it ) {
02000         if ( *it ) {
02001           subCellWidth = calcSubCellWidth( *it );
02002           placeAgendaItem( *it, subCellWidth );
02003         }
02004       }
02005     }
02006   }
02007   checkScrollBoundaries();
02008   marcus_bains();
02009 }
02010 
02011 void KOAgenda::scrollUp()
02012 {
02013   scrollBy(0,-mScrollOffset);
02014 }
02015 
02016 
02017 void KOAgenda::scrollDown()
02018 {
02019   scrollBy(0,mScrollOffset);
02020 }
02021 
02022 
02023 /*
02024   Calculates the minimum width
02025 */
02026 int KOAgenda::minimumWidth() const
02027 {
02028   // FIXME:: develop a way to dynamically determine the minimum width
02029   int min = 100;
02030 
02031   return min;
02032 }
02033 
02034 void KOAgenda::updateConfig()
02035 {
02036   double oldGridSpacingY = mGridSpacingY;
02037 
02038   mDesiredGridSpacingY = KOPrefs::instance()->mHourSize;
02039   if ( mDesiredGridSpacingY < 4 || mDesiredGridSpacingY > 30 ) {
02040     mDesiredGridSpacingY = 10;
02041   }
02042 
02043   // make sure that there are not more than 24 per day
02044   mGridSpacingY = (double)height() / (double)mRows;
02045   if ( mGridSpacingY < mDesiredGridSpacingY ) {
02046     mGridSpacingY = mDesiredGridSpacingY;
02047   }
02048 
02049   //can be two doubles equal?, it's better to compare them with an epsilon
02050   if ( fabs( oldGridSpacingY - mGridSpacingY ) > 0.1 ) {
02051     resizeContents( int( mGridSpacingX * mColumns ),
02052                     int( mGridSpacingY * mRows ) );
02053   }
02054 
02055   calculateWorkingHours();
02056 
02057   marcus_bains();
02058 }
02059 
02060 void KOAgenda::checkScrollBoundaries()
02061 {
02062   // Invalidate old values to force update
02063   mOldLowerScrollValue = -1;
02064   mOldUpperScrollValue = -1;
02065 
02066   checkScrollBoundaries(verticalScrollBar()->value());
02067 }
02068 
02069 void KOAgenda::checkScrollBoundaries( int v )
02070 {
02071   int yMin = int( (v) / mGridSpacingY );
02072   int yMax = int( ( v + visibleHeight() ) / mGridSpacingY );
02073 
02074 //  kdDebug(5850) << "--- yMin: " << yMin << "  yMax: " << yMax << endl;
02075 
02076   if ( yMin != mOldLowerScrollValue ) {
02077     mOldLowerScrollValue = yMin;
02078     emit lowerYChanged(yMin);
02079   }
02080   if ( yMax != mOldUpperScrollValue ) {
02081     mOldUpperScrollValue = yMax;
02082     emit upperYChanged(yMax);
02083   }
02084 }
02085 
02086 int KOAgenda::visibleContentsYMin()
02087 {
02088   int v = verticalScrollBar()->value();
02089   return int( v / mGridSpacingY );
02090 }
02091 
02092 int KOAgenda::visibleContentsYMax()
02093 {
02094   int v = verticalScrollBar()->value();
02095   return int( ( v + visibleHeight() ) / mGridSpacingY );
02096 }
02097 
02098 void KOAgenda::deselectItem()
02099 {
02100   if ( mSelectedItem.isNull() ) {
02101     return;
02102   }
02103   mSelectedItem->select(false);
02104   mSelectedItem = 0;
02105 }
02106 
02107 void KOAgenda::selectItem( KOAgendaItem::GPtr item )
02108 {
02109   if ((KOAgendaItem *)mSelectedItem == item) return;
02110   deselectItem();
02111   if ( !item ) {
02112     emit incidenceSelected( 0, QDate() );
02113     return;
02114   }
02115   mSelectedItem = item;
02116   mSelectedItem->select();
02117   assert( mSelectedItem->incidence() );
02118   mSelectedUid = mSelectedItem->incidence()->uid();
02119   emit incidenceSelected( mSelectedItem->incidence(), mSelectedItem->itemDate() );
02120 }
02121 
02122 void KOAgenda::selectItemByUID( const QString& uid )
02123 {
02124   KOAgendaItem::GPtr item;
02125   AgendaItemList::Iterator it;
02126   for ( it = mItems.begin(); it != mItems.end(); ++it ) {
02127     if ( *it && (*it)->incidence() && (*it)->incidence()->uid() == uid ) {
02128       selectItem( *it );
02129       break;
02130     }
02131   }
02132 }
02133 
02134 // This function seems never be called.
02135 void KOAgenda::keyPressEvent( QKeyEvent *kev )
02136 {
02137   switch(kev->key()) {
02138     case Key_PageDown:
02139       verticalScrollBar()->addPage();
02140       break;
02141     case Key_PageUp:
02142       verticalScrollBar()->subtractPage();
02143       break;
02144     case Key_Down:
02145       verticalScrollBar()->addLine();
02146       break;
02147     case Key_Up:
02148       verticalScrollBar()->subtractLine();
02149       break;
02150     default:
02151       ;
02152   }
02153 }
02154 
02155 void KOAgenda::calculateWorkingHours()
02156 {
02157   mWorkingHoursEnable = !mAllDayMode;
02158 
02159   QTime tmp = KOPrefs::instance()->mWorkingHoursStart.time();
02160   mWorkingHoursYTop = int( 4 * mGridSpacingY *
02161                            ( tmp.hour() + tmp.minute() / 60. +
02162                              tmp.second() / 3600. ) );
02163   tmp = KOPrefs::instance()->mWorkingHoursEnd.time();
02164   mWorkingHoursYBottom = int( 4 * mGridSpacingY *
02165                               ( tmp.hour() + tmp.minute() / 60. +
02166                                 tmp.second() / 3600. ) - 1 );
02167 }
02168 
02169 
02170 DateList KOAgenda::dateList() const
02171 {
02172     return mSelectedDates;
02173 }
02174 
02175 void KOAgenda::setDateList(const DateList &selectedDates)
02176 {
02177     mSelectedDates = selectedDates;
02178     marcus_bains();
02179 }
02180 
02181 void KOAgenda::contentsMousePressEvent ( QMouseEvent *event )
02182 {
02183   kdDebug(5850) << "KOagenda::contentsMousePressEvent(): type: " << event->type() << endl;
02184   QScrollView::contentsMousePressEvent(event);
02185 }
02186 
02187 void KOAgenda::setTypeAheadReceiver( QObject *o )
02188 {
02189   mTypeAheadReceiver = o;
02190 }
02191 
02192 QObject *KOAgenda::typeAheadReceiver() const
02193 {
02194   return mTypeAheadReceiver;
02195 }
02196 
02197 void KOAgenda::setHolidayMask(QMemArray<bool> *mask)
02198 {
02199   mHolidayMask = mask;
02200 }
KDE Home | KDE Accessibility Home | Description of Access Keys