korganizer Library API Documentation

koagenda.cpp

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