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