korganizer

koagendaview.cpp

00001 /*
00002     This file is part of KOrganizer.
00003     Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00004     Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00005 
00006     This program is free software; you can redistribute it and/or modify
00007     it under the terms of the GNU General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or
00009     (at your option) any later version.
00010 
00011     This program is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00014     GNU General Public License for more details.
00015 
00016     You should have received a copy of the GNU General Public License
00017     along with this program; if not, write to the Free Software
00018     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00019 
00020     As a special exception, permission is given to link this program
00021     with any edition of Qt, and distribute the resulting executable,
00022     without including the source code for Qt in the source distribution.
00023 */
00024 
00025 #include <qhbox.h>
00026 #include <qvbox.h>
00027 #include <qlabel.h>
00028 #include <qframe.h>
00029 #include <qlayout.h>
00030 #ifndef KORG_NOSPLITTER
00031 #include <qsplitter.h>
00032 #endif
00033 #include <qfont.h>
00034 #include <qfontmetrics.h>
00035 #include <qpopupmenu.h>
00036 #include <qtooltip.h>
00037 #include <qpainter.h>
00038 #include <qpushbutton.h>
00039 #include <qcursor.h>
00040 #include <qbitarray.h>
00041 
00042 #include <kapplication.h>
00043 #include <kdebug.h>
00044 #include <kstandarddirs.h>
00045 #include <kiconloader.h>
00046 #include <klocale.h>
00047 #include <kconfig.h>
00048 #include <kglobal.h>
00049 #include <kglobalsettings.h>
00050 #include <kholidays.h>
00051 
00052 #include <libkcal/calendar.h>
00053 #include <libkcal/icaldrag.h>
00054 #include <libkcal/dndfactory.h>
00055 #include <libkcal/calfilter.h>
00056 
00057 #include <kcalendarsystem.h>
00058 
00059 #include "koglobals.h"
00060 #ifndef KORG_NOPLUGINS
00061 #include "kocore.h"
00062 #endif
00063 #include "koprefs.h"
00064 #include "koagenda.h"
00065 #include "koagendaitem.h"
00066 #include "timelabels.h"
00067 
00068 #include "koincidencetooltip.h"
00069 #include "kogroupware.h"
00070 #include "kodialogmanager.h"
00071 #include "koeventpopupmenu.h"
00072 
00073 #include "koagendaview.h"
00074 #include "koagendaview.moc"
00075 
00076 using namespace KOrg;
00077 
00078 bool KOAgendaView::sProcessingDrop = false;
00079 
00080 
00081 EventIndicator::EventIndicator(Location loc,QWidget *parent,const char *name)
00082   : QFrame(parent,name)
00083 {
00084   mColumns = 1;
00085   mEnabled.resize( mColumns );
00086   mLocation = loc;
00087 
00088   if (mLocation == Top) mPixmap = KOGlobals::self()->smallIcon("upindicator");
00089   else mPixmap = KOGlobals::self()->smallIcon("downindicator");
00090 
00091   setMinimumHeight(mPixmap.height());
00092 }
00093 
00094 EventIndicator::~EventIndicator()
00095 {
00096 }
00097 
00098 void EventIndicator::drawContents(QPainter *p)
00099 {
00100 //  kdDebug(5850) << "======== top: " << contentsRect().top() << "  bottom "
00101 //         << contentsRect().bottom() << "  left " << contentsRect().left()
00102 //         << "  right " << contentsRect().right() << endl;
00103 
00104   int i;
00105   for(i=0;i<mColumns;++i) {
00106     if (mEnabled[i]) {
00107       int cellWidth = contentsRect().right()/mColumns;
00108       int xOffset = KOGlobals::self()->reverseLayout() ?
00109                (mColumns - 1 - i)*cellWidth + cellWidth/2 -mPixmap.width()/2 :
00110                i*cellWidth + cellWidth/2 -mPixmap.width()/2;
00111       p->drawPixmap(QPoint(xOffset,0),mPixmap);
00112     }
00113   }
00114 }
00115 
00116 void EventIndicator::changeColumns(int columns)
00117 {
00118   mColumns = columns;
00119   mEnabled.resize(mColumns);
00120 
00121   update();
00122 }
00123 
00124 void EventIndicator::enableColumn(int column, bool enable)
00125 {
00126   mEnabled[column] = enable;
00127 }
00128 
00129 
00130 #include <libkcal/incidence.h>
00131 
00135 
00136 
00137 KOAlternateLabel::KOAlternateLabel(const QString &shortlabel, const QString &longlabel,
00138     const QString &extensivelabel, QWidget *parent, const char *name )
00139   : QLabel(parent, name), mTextTypeFixed(false), mShortText(shortlabel),
00140     mLongText(longlabel), mExtensiveText(extensivelabel)
00141 {
00142   setSizePolicy(QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ));
00143   if (mExtensiveText.isEmpty()) mExtensiveText = mLongText;
00144   squeezeTextToLabel();
00145 }
00146 
00147 KOAlternateLabel::~KOAlternateLabel()
00148 {
00149 }
00150 
00151 void KOAlternateLabel::useShortText()
00152 {
00153   mTextTypeFixed = true;
00154   QLabel::setText( mShortText );
00155   QToolTip::remove( this );
00156   QToolTip::add( this, mExtensiveText );
00157   update(); // for kolab/issue4350
00158 }
00159 
00160 void KOAlternateLabel::useLongText()
00161 {
00162   mTextTypeFixed = true;
00163   QLabel::setText( mLongText );
00164   QToolTip::remove( this );
00165   QToolTip::add( this, mExtensiveText );
00166   update(); // for kolab/issue4350
00167 }
00168 
00169 void KOAlternateLabel::useExtensiveText()
00170 {
00171   mTextTypeFixed = true;
00172   QLabel::setText( mExtensiveText );
00173   QToolTip::remove( this );
00174   QToolTip::add( this, "" );
00175   update(); // for kolab/issue4350
00176 }
00177 
00178 void KOAlternateLabel::useDefaultText()
00179 {
00180   mTextTypeFixed = false;
00181   squeezeTextToLabel();
00182 }
00183 
00184 KOAlternateLabel::TextType KOAlternateLabel::largestFittingTextType() const
00185 {
00186   QFontMetrics fm( fontMetrics() );
00187   const int labelWidth = size().width();
00188   const int longTextWidth = fm.width( mLongText );
00189   const int extensiveTextWidth = fm.width( mExtensiveText );
00190   if ( extensiveTextWidth <= labelWidth )
00191     return Extensive;
00192   else if ( longTextWidth <= labelWidth )
00193     return Long;
00194   else
00195     return Short;
00196 }
00197 
00198 void KOAlternateLabel::setFixedType( TextType type )
00199 {
00200   switch ( type )
00201   {
00202     case Extensive: useExtensiveText(); break;
00203     case Long: useLongText(); break;
00204     case Short: useShortText(); break;
00205   }
00206 }
00207 
00208 void KOAlternateLabel::squeezeTextToLabel()
00209 {
00210   if ( mTextTypeFixed )
00211     return;
00212 
00213   const TextType type = largestFittingTextType();
00214   switch ( type )
00215   {
00216     case Extensive:
00217       QLabel::setText( mExtensiveText );
00218       QToolTip::remove( this );
00219       QToolTip::add( this, "" );
00220       break;
00221     case Long:
00222       QLabel::setText( mLongText );
00223       QToolTip::remove( this );
00224       QToolTip::add( this, mExtensiveText );
00225       break;
00226     case Short:
00227       QLabel::setText( mShortText );
00228       QToolTip::remove( this );
00229       QToolTip::add( this, mExtensiveText );
00230       break;
00231   }
00232   update(); // for kolab/issue4350
00233 }
00234 
00235 void KOAlternateLabel::resizeEvent( QResizeEvent * )
00236 {
00237   squeezeTextToLabel();
00238 }
00239 
00240 QSize KOAlternateLabel::minimumSizeHint() const
00241 {
00242   QSize sh = QLabel::minimumSizeHint();
00243   sh.setWidth(-1);
00244   return sh;
00245 }
00246 
00250 
00251 KOAgendaView::KOAgendaView( Calendar *cal,
00252                             CalendarView *calendarView,
00253                             QWidget *parent,
00254                             const char *name,
00255                             bool isSideBySide ) :
00256   KOrg::AgendaView (cal, parent,name), mExpandButton( 0 ),
00257   mAllowAgendaUpdate( true ),
00258   mUpdateItem( 0 ),
00259   mIsSideBySide( isSideBySide ),
00260   mPendingChanges( true ),
00261   mAreDatesInitialized( false )
00262 {
00263   mSelectedDates.append(QDate::currentDate());
00264 
00265   mLayoutDayLabels = 0;
00266   mDayLabelsFrame = 0;
00267   mDayLabels = 0;
00268 
00269   bool isRTL = KOGlobals::self()->reverseLayout();
00270 
00271   if ( KOPrefs::instance()->compactDialogs() ) {
00272     if ( KOPrefs::instance()->mVerticalScreen ) {
00273       mExpandedPixmap = KOGlobals::self()->smallIcon( "1downarrow" );
00274       mNotExpandedPixmap = KOGlobals::self()->smallIcon( "1uparrow" );
00275     } else {
00276       mExpandedPixmap = KOGlobals::self()->smallIcon( isRTL ? "1leftarrow" : "1rightarrow" );
00277       mNotExpandedPixmap = KOGlobals::self()->smallIcon( isRTL ? "1rightarrow" : "1leftarrow" );
00278     }
00279   }
00280 
00281   QBoxLayout *topLayout = new QVBoxLayout(this);
00282 
00283   // Create day name labels for agenda columns
00284   mDayLabelsFrame = new QHBox(this);
00285   topLayout->addWidget(mDayLabelsFrame);
00286 
00287   // Create agenda splitter
00288 #ifndef KORG_NOSPLITTER
00289   mSplitterAgenda = new QSplitter(Vertical,this);
00290   topLayout->addWidget(mSplitterAgenda);
00291 
00292 #if KDE_IS_VERSION( 3, 1, 93 )
00293   mSplitterAgenda->setOpaqueResize( KGlobalSettings::opaqueResize() );
00294 #else
00295   mSplitterAgenda->setOpaqueResize();
00296 #endif
00297 
00298   mAllDayFrame = new QHBox(mSplitterAgenda);
00299 
00300   QWidget *agendaFrame = new QWidget(mSplitterAgenda);
00301 #else
00302   QVBox *mainBox = new QVBox( this );
00303   topLayout->addWidget( mainBox );
00304 
00305   mAllDayFrame = new QHBox(mainBox);
00306 
00307   QWidget *agendaFrame = new QWidget(mainBox);
00308 #endif
00309 
00310   // Create all-day agenda widget
00311   mDummyAllDayLeft = new QVBox( mAllDayFrame );
00312   if ( isSideBySide )
00313     mDummyAllDayLeft->hide();
00314 
00315   if ( KOPrefs::instance()->compactDialogs() ) {
00316     mExpandButton = new QPushButton(mDummyAllDayLeft);
00317     mExpandButton->setPixmap( mNotExpandedPixmap );
00318     mExpandButton->setSizePolicy( QSizePolicy( QSizePolicy::Fixed,
00319                                   QSizePolicy::Fixed ) );
00320     connect( mExpandButton, SIGNAL( clicked() ), SIGNAL( toggleExpand() ) );
00321   } else {
00322     QLabel *label = new QLabel( i18n("All Day"), mDummyAllDayLeft );
00323     label->setAlignment( Qt::AlignRight | Qt::AlignVCenter | Qt::WordBreak );
00324   }
00325 
00326   mAllDayAgenda = new KOAgenda( 1, calendarView, this, mAllDayFrame );
00327   mAllDayAgenda->setCalendar( calendar() );
00328   QWidget *dummyAllDayRight = new QWidget(mAllDayFrame);
00329 
00330   // Create agenda frame
00331   QGridLayout *agendaLayout = new QGridLayout(agendaFrame,3,3);
00332 //  QHBox *agendaFrame = new QHBox(splitterAgenda);
00333 
00334   // create event indicator bars
00335   mEventIndicatorTop = new EventIndicator(EventIndicator::Top,agendaFrame);
00336   agendaLayout->addWidget(mEventIndicatorTop,0,1);
00337   mEventIndicatorBottom = new EventIndicator(EventIndicator::Bottom,
00338                                              agendaFrame);
00339   agendaLayout->addWidget(mEventIndicatorBottom,2,1);
00340   QWidget *dummyAgendaRight = new QWidget(agendaFrame);
00341   agendaLayout->addWidget(dummyAgendaRight,0,2);
00342 
00343   // Create time labels
00344   mTimeLabels = new TimeLabels(24,agendaFrame);
00345   agendaLayout->addWidget(mTimeLabels,1,0);
00346 
00347   // Create agenda
00348   mAgenda = new KOAgenda( 1, 96, KOPrefs::instance()->mHourSize, calendarView, this, agendaFrame );
00349   mAgenda->setCalendar( calendar() );
00350   agendaLayout->addMultiCellWidget(mAgenda,1,1,1,2);
00351   agendaLayout->setColStretch(1,1);
00352 
00353   // Create event context menu for agenda
00354   mAgendaPopup = eventPopup();
00355 
00356   // Create event context menu for all day agenda
00357   mAllDayAgendaPopup = eventPopup();
00358 
00359   // make connections between dependent widgets
00360   mTimeLabels->setAgenda(mAgenda);
00361   if ( isSideBySide )
00362     mTimeLabels->hide();
00363 
00364   // Update widgets to reflect user preferences
00365 //  updateConfig();
00366 
00367   createDayLabels( true );
00368 
00369   if ( !isSideBySide ) {
00370     // these blank widgets make the All Day Event box line up with the agenda
00371     dummyAllDayRight->setFixedWidth(mAgenda->verticalScrollBar()->width());
00372     dummyAgendaRight->setFixedWidth(mAgenda->verticalScrollBar()->width());
00373   }
00374 
00375   updateTimeBarWidth();
00376 
00377   // Scrolling
00378   connect(mAgenda->verticalScrollBar(),SIGNAL(valueChanged(int)),
00379           mTimeLabels, SLOT(positionChanged()));
00380 
00381   connect( mAgenda,
00382     SIGNAL( zoomView( const int, const QPoint & ,const Qt::Orientation ) ),
00383     SLOT( zoomView( const int, const QPoint &, const Qt::Orientation ) ) );
00384 
00385   connect(mTimeLabels->verticalScrollBar(),SIGNAL(valueChanged(int)),
00386           SLOT(setContentsPos(int)));
00387 
00388   // Create Events, depends on type of agenda
00389   connect( mAgenda, SIGNAL(newTimeSpanSignal(const QPoint &, const QPoint &)),
00390                     SLOT(newTimeSpanSelected(const QPoint &, const QPoint &)));
00391   connect( mAllDayAgenda, SIGNAL(newTimeSpanSignal(const QPoint &, const QPoint &)),
00392                           SLOT(newTimeSpanSelectedAllDay(const QPoint &, const QPoint &)));
00393 
00394   // event indicator update
00395   connect( mAgenda, SIGNAL(lowerYChanged(int)),
00396                     SLOT(updateEventIndicatorTop(int)));
00397   connect( mAgenda, SIGNAL(upperYChanged(int)),
00398                     SLOT(updateEventIndicatorBottom(int)));
00399 
00400   if ( !readOnly() ) {
00401     connectAgenda( mAgenda, mAgendaPopup, mAllDayAgenda );
00402     connectAgenda( mAllDayAgenda, mAllDayAgendaPopup, mAgenda);
00403   }
00404 
00405   if ( cal ) {
00406     cal->registerObserver( this );
00407   }
00408 }
00409 
00410 
00411 KOAgendaView::~KOAgendaView()
00412 {
00413   if ( calendar() )
00414     calendar()->unregisterObserver( this );
00415   delete mAgendaPopup;
00416   delete mAllDayAgendaPopup;
00417 }
00418 
00419 void KOAgendaView::connectAgenda( KOAgenda *agenda, QPopupMenu *popup,
00420                                   KOAgenda *otherAgenda )
00421 {
00422   connect( agenda, SIGNAL(showIncidencePopupSignal(Calendar *,Incidence *,const QDate &)),
00423            popup, SLOT(showIncidencePopup(Calendar *,Incidence *,const QDate &)) );
00424 
00425   connect( agenda, SIGNAL(showNewEventPopupSignal()),
00426            SLOT(showNewEventPopup()) );
00427 
00428 
00429   // Create/Show/Edit/Delete Event
00430   connect( agenda, SIGNAL(newEventSignal(ResourceCalendar *,const QString &)),
00431            SIGNAL(newEventSignal(ResourceCalendar *,const QString &)) );
00432 
00433   connect( agenda, SIGNAL(newStartSelectSignal()),
00434            otherAgenda, SLOT(clearSelection()) );
00435   connect( agenda, SIGNAL(newStartSelectSignal()),
00436            SIGNAL(timeSpanSelectionChanged()) );
00437 
00438   connect( agenda, SIGNAL(editIncidenceSignal(Incidence *,const QDate &)),
00439            SIGNAL(editIncidenceSignal(Incidence *,const QDate &)) );
00440   connect( agenda, SIGNAL(showIncidenceSignal(Incidence *,const QDate &)),
00441            SIGNAL(showIncidenceSignal(Incidence *,const QDate &)) );
00442   connect( agenda, SIGNAL(deleteIncidenceSignal(Incidence *)),
00443            SIGNAL(deleteIncidenceSignal(Incidence *)) );
00444 
00445   connect( agenda, SIGNAL(startMultiModify(const QString &)),
00446            SIGNAL(startMultiModify(const QString &)) );
00447   connect( agenda, SIGNAL(endMultiModify()),
00448            SIGNAL(endMultiModify()) );
00449 
00450   // drag signals
00451   connect( agenda, SIGNAL(startDragSignal(Incidence *)),
00452            SLOT(startDrag(Incidence *)) );
00453 
00454   // synchronize selections
00455   connect( agenda, SIGNAL(incidenceSelected(Incidence *,const QDate &)),
00456            otherAgenda, SLOT(deselectItem()) );
00457   connect( agenda, SIGNAL(incidenceSelected(Incidence *,const QDate &)),
00458            SIGNAL(incidenceSelected(Incidence *,const QDate &)) );
00459 
00460   // rescheduling of todos by d'n'd
00461   connect( agenda, SIGNAL(droppedIncidence(Incidence *,const QPoint &,bool)),
00462            SLOT(slotIncidenceDropped(Incidence *,const QPoint &,bool)) );
00463 
00464 }
00465 
00466 void KOAgendaView::zoomInVertically( )
00467 {
00468   if ( !mIsSideBySide )
00469     KOPrefs::instance()->mHourSize++;
00470   mAgenda->updateConfig();
00471   mAgenda->checkScrollBoundaries();
00472 
00473   mTimeLabels->updateConfig();
00474   mTimeLabels->positionChanged();
00475   mTimeLabels->repaint();
00476 
00477   updateView();
00478 }
00479 
00480 void KOAgendaView::zoomOutVertically( )
00481 {
00482 
00483   if ( KOPrefs::instance()->mHourSize > 4 || mIsSideBySide ) {
00484 
00485     if ( !mIsSideBySide )
00486       KOPrefs::instance()->mHourSize--;
00487     mAgenda->updateConfig();
00488     mAgenda->checkScrollBoundaries();
00489 
00490     mTimeLabels->updateConfig();
00491     mTimeLabels->positionChanged();
00492     mTimeLabels->repaint();
00493 
00494     updateView();
00495   }
00496 }
00497 
00498 void KOAgendaView::zoomInHorizontally( const QDate &date)
00499 {
00500   QDate begin;
00501   QDate newBegin;
00502   QDate dateToZoom = date;
00503   int ndays,count;
00504 
00505   begin = mSelectedDates.first();
00506   ndays = begin.daysTo( mSelectedDates.last() );
00507 
00508   // zoom with Action and are there a selected Incidence?, Yes, I zoom in to it.
00509   if ( ! dateToZoom.isValid () )
00510     dateToZoom=mAgenda->selectedIncidenceDate();
00511 
00512   if( !dateToZoom.isValid() ) {
00513     if ( ndays > 1 ) {
00514       newBegin=begin.addDays(1);
00515       count = ndays-1;
00516       emit zoomViewHorizontally ( newBegin , count );
00517     }
00518   } else {
00519     if ( ndays <= 2 ) {
00520       newBegin = dateToZoom;
00521       count = 1;
00522     } else  {
00523       newBegin = dateToZoom.addDays( -ndays/2 +1  );
00524       count = ndays -1 ;
00525     }
00526     emit zoomViewHorizontally ( newBegin , count );
00527   }
00528 }
00529 
00530 void KOAgendaView::zoomOutHorizontally( const QDate &date )
00531 {
00532   QDate begin;
00533   QDate newBegin;
00534   QDate dateToZoom = date;
00535   int ndays,count;
00536 
00537   begin = mSelectedDates.first();
00538   ndays = begin.daysTo( mSelectedDates.last() );
00539 
00540   // zoom with Action and are there a selected Incidence?, Yes, I zoom out to it.
00541   if ( ! dateToZoom.isValid () )
00542     dateToZoom=mAgenda->selectedIncidenceDate();
00543 
00544   if ( !dateToZoom.isValid() ) {
00545     newBegin = begin.addDays(-1);
00546     count = ndays+3 ;
00547   } else {
00548     newBegin = dateToZoom.addDays( -ndays/2-1 );
00549     count = ndays+3;
00550   }
00551 
00552   if ( abs( count ) >= 31 )
00553     kdDebug(5850) << "change to the mounth view?"<<endl;
00554   else
00555     //We want to center the date
00556     emit zoomViewHorizontally( newBegin, count );
00557 }
00558 
00559 void KOAgendaView::zoomView( const int delta, const QPoint &pos,
00560   const Qt::Orientation orient )
00561 {
00562   static QDate zoomDate;
00563   static QTimer *t = new QTimer( this );
00564 
00565 
00566   //Zoom to the selected incidence, on the other way
00567   // zoom to the date on screen after the first mousewheel move.
00568   if ( orient == Qt::Horizontal ) {
00569     QDate date=mAgenda->selectedIncidenceDate();
00570     if ( date.isValid() )
00571       zoomDate=date;
00572     else{
00573       if ( !t->isActive() ) {
00574         zoomDate= mSelectedDates[pos.x()];
00575       }
00576       t->start ( 1000,true );
00577     }
00578     if ( delta > 0 )
00579       zoomOutHorizontally( zoomDate );
00580     else
00581       zoomInHorizontally( zoomDate );
00582   } else {
00583     // Vertical zoom
00584     QPoint posConstentsOld = mAgenda->gridToContents(pos);
00585     if ( delta > 0 ) {
00586       zoomOutVertically();
00587     } else {
00588       zoomInVertically();
00589     }
00590     QPoint posConstentsNew = mAgenda->gridToContents(pos);
00591     mAgenda->scrollBy( 0, posConstentsNew.y() - posConstentsOld.y() );
00592   }
00593 }
00594 
00595 void KOAgendaView::createDayLabels( bool force )
00596 {
00597 //  kdDebug(5850) << "KOAgendaView::createDayLabels()" << endl;
00598 
00599   // Check if mSelectedDates has changed, if not just return
00600   // Removes some flickering and gains speed (since this is called by each updateView())
00601   if ( !force && mSaveSelectedDates == mSelectedDates ) {
00602     return;
00603   }
00604   mSaveSelectedDates = mSelectedDates;
00605 
00606   delete mDayLabels;
00607   mDateDayLabels.clear();
00608 
00609   mDayLabels = new QFrame (mDayLabelsFrame);
00610   mLayoutDayLabels = new QHBoxLayout(mDayLabels);
00611   if ( !mIsSideBySide )
00612     mLayoutDayLabels->addSpacing(mTimeLabels->width());
00613 
00614   const KCalendarSystem*calsys=KOGlobals::self()->calendarSystem();
00615 
00616   DateList::ConstIterator dit;
00617   for( dit = mSelectedDates.begin(); dit != mSelectedDates.end(); ++dit ) {
00618     QDate date = *dit;
00619     QBoxLayout *dayLayout = new QVBoxLayout(mLayoutDayLabels);
00620     mLayoutDayLabels->setStretchFactor(dayLayout, 1);
00621 //    dayLayout->setMinimumWidth(1);
00622 
00623     int dW = calsys->dayOfWeek(date);
00624     QString veryLongStr = KGlobal::locale()->formatDate( date );
00625     QString longstr = i18n( "short_weekday date (e.g. Mon 13)","%1 %2" )
00626         .arg( calsys->weekDayName( dW, true ) )
00627         .arg( calsys->day(date) );
00628     QString shortstr = QString::number(calsys->day(date));
00629 
00630     KOAlternateLabel *dayLabel = new KOAlternateLabel(shortstr,
00631                                                       longstr, veryLongStr, mDayLabels);
00632     dayLabel->useShortText(); // will be recalculated in updateDayLabelSizes() anyway
00633     dayLabel->setMinimumWidth(1);
00634     dayLabel->setAlignment(QLabel::AlignHCenter);
00635     if (date == QDate::currentDate()) {
00636       QFont font = dayLabel->font();
00637       font.setBold(true);
00638       dayLabel->setFont(font);
00639     }
00640     dayLayout->addWidget(dayLabel);
00641     mDateDayLabels.append( dayLabel );
00642 
00643     // if a holiday region is selected, show the holiday name
00644     QStringList texts = KOGlobals::self()->holiday( date );
00645     QStringList::ConstIterator textit = texts.begin();
00646     for ( ; textit != texts.end(); ++textit ) {
00647       // use a KOAlternateLabel so when the text doesn't fit any more a tooltip is used
00648       KOAlternateLabel*label = new KOAlternateLabel( (*textit), (*textit), QString::null, mDayLabels );
00649       label->setMinimumWidth(1);
00650       label->setAlignment(AlignCenter);
00651       dayLayout->addWidget(label);
00652     }
00653 
00654 #ifndef KORG_NOPLUGINS
00655     CalendarDecoration::List cds = KOCore::self()->calendarDecorations();
00656     CalendarDecoration *it;
00657     for(it = cds.first(); it; it = cds.next()) {
00658       QString text = it->shortText( date );
00659       if ( !text.isEmpty() ) {
00660         // use a KOAlternateLabel so when the text doesn't fit any more a tooltip is used
00661         KOAlternateLabel*label = new KOAlternateLabel( text, text, QString::null, mDayLabels );
00662         label->setMinimumWidth(1);
00663         label->setAlignment(AlignCenter);
00664         dayLayout->addWidget(label);
00665       }
00666     }
00667 
00668     for(it = cds.first(); it; it = cds.next()) {
00669       QWidget *wid = it->smallWidget(mDayLabels,date);
00670       if ( wid ) {
00671 //      wid->setHeight(20);
00672         dayLayout->addWidget(wid);
00673       }
00674     }
00675 #endif
00676   }
00677 
00678   if ( !mIsSideBySide )
00679     mLayoutDayLabels->addSpacing(mAgenda->verticalScrollBar()->width());
00680   mDayLabels->show();
00681   QTimer::singleShot( 0, this, SLOT( updateDayLabelSizes() ) );
00682 }
00683 
00684 void KOAgendaView::enableAgendaUpdate( bool enable )
00685 {
00686   mAllowAgendaUpdate = enable;
00687 }
00688 
00689 int KOAgendaView::maxDatesHint()
00690 {
00691   // Not sure about the max number of events, so return 0 for now.
00692   return 0;
00693 }
00694 
00695 int KOAgendaView::currentDateCount()
00696 {
00697   return mSelectedDates.count();
00698 }
00699 
00700 Incidence::List KOAgendaView::selectedIncidences()
00701 {
00702   Incidence::List selected;
00703   Incidence *incidence;
00704 
00705   incidence = mAgenda->selectedIncidence();
00706   if (incidence) selected.append(incidence);
00707 
00708   incidence = mAllDayAgenda->selectedIncidence();
00709   if (incidence) selected.append(incidence);
00710 
00711   return selected;
00712 }
00713 
00714 DateList KOAgendaView::selectedIncidenceDates()
00715 {
00716   DateList selected;
00717   QDate qd;
00718 
00719   qd = mAgenda->selectedIncidenceDate();
00720   if (qd.isValid()) selected.append(qd);
00721 
00722   qd = mAllDayAgenda->selectedIncidenceDate();
00723   if (qd.isValid()) selected.append(qd);
00724 
00725   return selected;
00726 }
00727 
00728 bool KOAgendaView::eventDurationHint( QDateTime &startDt, QDateTime &endDt,
00729                                       bool &allDay )
00730 {
00731   if ( selectionStart().isValid() ) {
00732     QDateTime start = selectionStart();
00733     QDateTime end = selectionEnd();
00734 
00735     if ( start.secsTo( end ) == 15*60 ) {
00736       // One cell in the agenda view selected, e.g.
00737       // because of a double-click, => Use the default duration
00738       QTime defaultDuration( KOPrefs::instance()->mDefaultDuration.time() );
00739       int addSecs = ( defaultDuration.hour()*3600 ) +
00740                     ( defaultDuration.minute()*60 );
00741       end = start.addSecs( addSecs );
00742     }
00743 
00744     startDt = start;
00745     endDt = end;
00746     allDay = selectedIsAllDay();
00747     return true;
00748   }
00749   return false;
00750 }
00751 
00753 bool KOAgendaView::selectedIsSingleCell()
00754 {
00755   if ( !selectionStart().isValid() || !selectionEnd().isValid() ) return false;
00756 
00757   if (selectedIsAllDay()) {
00758     int days = selectionStart().daysTo(selectionEnd());
00759     return ( days < 1 );
00760   } else {
00761     int secs = selectionStart().secsTo(selectionEnd());
00762     return ( secs <= 24*60*60/mAgenda->rows() );
00763   }
00764 }
00765 
00766 
00767 void KOAgendaView::updateView()
00768 {
00769 //  kdDebug(5850) << "KOAgendaView::updateView()" << endl;
00770   fillAgenda();
00771 }
00772 
00773 
00774 /*
00775   Update configuration settings for the agenda view. This method is not
00776   complete.
00777 */
00778 void KOAgendaView::updateConfig()
00779 {
00780 //  kdDebug(5850) << "KOAgendaView::updateConfig()" << endl;
00781 
00782   // update config for children
00783   mTimeLabels->updateConfig();
00784   mAgenda->updateConfig();
00785   mAllDayAgenda->updateConfig();
00786 
00787   // widget synchronization
00788   // FIXME: find a better way, maybe signal/slot
00789   mTimeLabels->positionChanged();
00790 
00791   // for some reason, this needs to be called explicitly
00792   mTimeLabels->repaint();
00793 
00794   updateTimeBarWidth();
00795 
00796   // ToolTips displaying summary of events
00797   KOAgendaItem::toolTipGroup()->setEnabled(KOPrefs::instance()
00798                                            ->mEnableToolTips);
00799 
00800   setHolidayMasks();
00801 
00802   createDayLabels( true );
00803 
00804   updateView();
00805 }
00806 
00807 void KOAgendaView::updateTimeBarWidth()
00808 {
00809   int width;
00810 
00811   width = mDummyAllDayLeft->fontMetrics().width( i18n("All Day") );
00812   width = QMAX( width, mTimeLabels->width() );
00813 
00814   mDummyAllDayLeft->setFixedWidth( width );
00815   mTimeLabels->setFixedWidth( width );
00816 }
00817 
00818 void KOAgendaView::updateDayLabelSizes()
00819 {
00820   // First, calculate the maximum text type that fits for all labels
00821   KOAlternateLabel::TextType overallType = KOAlternateLabel::Extensive;
00822   QPtrList<KOAlternateLabel>::const_iterator it = mDateDayLabels.constBegin();
00823   for( ; it != mDateDayLabels.constEnd(); it++ ) {
00824     KOAlternateLabel::TextType type = (*it)->largestFittingTextType();
00825     if ( type < overallType )
00826       overallType = type;
00827   }
00828 
00829   // Then, set that maximum text type to all the labels
00830   it = mDateDayLabels.constBegin();
00831   for( ; it != mDateDayLabels.constEnd(); it++ ) {
00832     (*it)->setFixedType( overallType );
00833   }
00834 }
00835 
00836 void KOAgendaView::resizeEvent( QResizeEvent *resizeEvent )
00837 {
00838   updateDayLabelSizes();
00839   KOrg::AgendaView::resizeEvent( resizeEvent );
00840 }
00841 
00842 void KOAgendaView::updateEventDates( KOAgendaItem *item,
00843                                      bool useLastGroupwareDialogAnswer,
00844                                      ResourceCalendar *res,
00845                                      const QString &subRes,
00846                                      bool addIncidence )
00847 {
00848   kdDebug(5850) << "KOAgendaView::updateEventDates(): " << item->text()
00849                 << "; item->cellXLeft(): " << item->cellXLeft()
00850                 << "; item->cellYTop(): " << item->cellYTop()
00851                 << "; item->lastMultiItem(): " << item->lastMultiItem()
00852                 << "; item->itemPos(): " << item->itemPos()
00853                 << "; item->itemCount(): " << item->itemCount()
00854                 << endl;
00855 
00856   QDateTime startDt, endDt;
00857 
00858   // Start date of this incidence, calculate the offset from it (so recurring and
00859   // non-recurring items can be treated exactly the same, we never need to check
00860   // for doesRecur(), because we only move the start day by the number of days the
00861   // agenda item was really moved. Smart, isn't it?)
00862   QDate thisDate;
00863   if ( item->cellXLeft() < 0 ) {
00864     thisDate = ( mSelectedDates.first() ).addDays( item->cellXLeft() );
00865   } else {
00866     thisDate = mSelectedDates[ item->cellXLeft() ];
00867   }
00868   const QDate oldThisDate( item->itemDate() );
00869   const int daysOffset = oldThisDate.daysTo( thisDate );
00870   int daysLength = 0;
00871 
00872   Incidence *incidence = item->incidence();
00873 
00874   if ( !incidence || !mChanger ||
00875        ( !addIncidence && !mChanger->beginChange( incidence,
00876                                                   resourceCalendar(),
00877                                                   subResourceCalendar() ) ) ) {
00878     kdDebug() << "Weird, application has a bug?" << endl;
00879     return;
00880   }
00881   incidence->startUpdates();
00882   Incidence *oldIncidence = incidence->clone();
00883 
00884   QTime startTime( 0, 0, 0 ), endTime( 0, 0, 0 );
00885   if ( incidence->doesFloat() ) {
00886     daysLength = item->cellWidth() - 1;
00887   } else {
00888     startTime = mAgenda->gyToTime( item->cellYTop() );
00889     if ( item->lastMultiItem() ) {
00890       endTime = mAgenda->gyToTime( item->lastMultiItem()->cellYBottom() + 1 );
00891       daysLength = item->lastMultiItem()->cellXLeft() - item->cellXLeft();
00892       kdDebug(5850) << "item->lastMultiItem()->cellXLeft(): " << item->lastMultiItem()->cellXLeft()
00893                     << endl;
00894     } else if ( item->itemPos() == item->itemCount() && item->itemCount() > 1 ) {
00895       /* multiitem handling in agenda assumes two things:
00896          - The start (first KOAgendaItem) is always visible.
00897          - The first KOAgendaItem of the incidence has a non-null item->lastMultiItem()
00898              pointing to the last KOagendaItem.
00899 
00900         But those aren't always met, for example when in day-view.
00901         kolab/issue4417
00902        */
00903 
00904       // Cornercase 1: - Resizing the end of the event but the start isn't visible
00905       endTime = mAgenda->gyToTime( item->cellYBottom() + 1 );
00906       daysLength = item->itemCount() - 1;
00907       startTime = incidence->dtStart().time();
00908     } else if ( item->itemPos() == 1 && item->itemCount() > 1 ) {
00909       // Cornercase 2: - Resizing the start of the event but the end isn't visible
00910       endTime = incidence->dtEnd().time();
00911       daysLength = item->itemCount() - 1;
00912     } else {
00913       endTime = mAgenda->gyToTime( item->cellYBottom() + 1 );
00914     }
00915   }
00916 
00917   kdDebug(5850) << "daysLength: " << daysLength << "; startTime: " << startTime
00918                 << "; endTime: " << endTime << "; thisDate: " << thisDate
00919                 << "; incidence->dtStart(): " << incidence->dtStart() << endl;
00920 
00921   if ( incidence->type() == "Event" ) {
00922     startDt = incidence->dtStart();
00923     startDt = startDt.addDays( daysOffset );
00924     startDt.setTime( startTime );
00925     endDt = startDt.addDays( daysLength );
00926     endDt.setTime( endTime );
00927     Event* ev = static_cast<Event*>( incidence );
00928     if ( incidence->dtStart() == startDt && ev->dtEnd() == endDt ) {
00929       // No change
00930       delete oldIncidence;
00931       incidence->cancelUpdates();
00932       return;
00933     }
00934     incidence->setDtStart( startDt );
00935     ev->setDtEnd( endDt );
00936   } else if ( incidence->type() == "Todo" ) {
00937     // To-do logic must be reviewed.
00938 
00939     Todo *td = static_cast<Todo*>( incidence );
00940     startDt = td->hasStartDate() ? td->dtStart() : td->dtDue();
00941     startDt = thisDate.addDays( td->dtDue().daysTo( startDt ) );
00942     startDt.setTime( startTime );
00943     endDt.setDate( thisDate );
00944     endDt.setTime( endTime );
00945 
00946     if ( td->dtDue() == endDt ) {
00947       // No change
00948       delete oldIncidence;
00949       incidence->cancelUpdates();
00950       return;
00951     }
00952 
00953     if ( td->hasStartDate() ) {
00954       td->setDtStart( startDt );
00955     }
00956     td->setDtDue( endDt );
00957   }
00958 
00959   item->setItemDate( startDt.date() );
00960 
00961   KOIncidenceToolTip::remove( item );
00962   KOIncidenceToolTip::add( item, calendar(), incidence, thisDate, KOAgendaItem::toolTipGroup() );
00963 
00964 
00965   bool result;
00966   kdDebug() << "New date is " << incidence->dtStart() << endl;
00967   if ( addIncidence ) {
00968     result = mChanger->addIncidence( incidence, res, subRes, this, useLastGroupwareDialogAnswer );
00969   } else {
00970     result = mChanger->changeIncidence( oldIncidence, incidence,
00971                                                    KOGlobals::DATE_MODIFIED, this, useLastGroupwareDialogAnswer );
00972     mChanger->endChange( incidence, resourceCalendar(), subResourceCalendar() );
00973   }
00974 
00975   delete oldIncidence;
00976 
00977   if ( !result ) {
00978     mPendingChanges = true;
00979     QTimer::singleShot( 0, this, SLOT(updateView()) );
00980     incidence->cancelUpdates();
00981     return;
00982   } else {
00983     incidence->endUpdates();
00984   }
00985 
00986   // don't update the agenda as the item already has the correct coordinates.
00987   // an update would delete the current item and recreate it, but we are still
00988   // using a pointer to that item! => CRASH
00989   enableAgendaUpdate( false );
00990 
00991   // We need to do this in a timer to make sure we are not deleting the item
00992   // we are currently working on, which would lead to crashes
00993   // Only the actually moved agenda item is already at the correct position and mustn't be
00994   // recreated. All others have to!!!
00995   if ( incidence->doesRecur() ) {
00996     mUpdateItem = incidence;
00997     QTimer::singleShot( 0, this, SLOT( doUpdateItem() ) );
00998   }
00999 
01000   enableAgendaUpdate( true );
01001 }
01002 
01003 void KOAgendaView::doUpdateItem()
01004 {
01005   if ( mUpdateItem ) {
01006     changeIncidenceDisplay( mUpdateItem, KOGlobals::INCIDENCEEDITED );
01007     mUpdateItem = 0;
01008   }
01009 }
01010 
01011 
01012 
01013 void KOAgendaView::showDates( const QDate &start, const QDate &end )
01014 {
01015 //  kdDebug(5850) << "KOAgendaView::selectDates" << endl;
01016   if ( !mSelectedDates.isEmpty() && mSelectedDates.first() == start
01017         && mSelectedDates.last() == end && !mPendingChanges )
01018     return;
01019 
01020   mSelectedDates.clear();
01021 
01022   QDate d = start;
01023   while ( d <= end ) {
01024     mSelectedDates.append( d );
01025     d = d.addDays( 1 );
01026   }
01027 
01028   mAreDatesInitialized = true;
01029 
01030   // and update the view
01031   fillAgenda();
01032 }
01033 
01034 
01035 void KOAgendaView::showIncidences( const Incidence::List &, const QDate & )
01036 {
01037   kdDebug(5850) << "KOAgendaView::showIncidences( const Incidence::List & ) is not yet implemented" << endl;
01038 }
01039 
01040 void KOAgendaView::insertIncidence( Incidence *incidence, const QDate &curDate )
01041 {
01042   if ( !filterByResource( incidence ) ) {
01043     return;
01044   }
01045 
01046   // FIXME: Use a visitor here, or some other method to get rid of the dynamic_cast's
01047   Event *event = dynamic_cast<Event *>( incidence );
01048   Todo  *todo  = dynamic_cast<Todo  *>( incidence );
01049 
01050   int curCol = mSelectedDates.first().daysTo( curDate );
01051 
01052   // In case incidence->dtStart() isn't visible (crosses bounderies)
01053   if ( curCol < 0 ) {
01054     curCol = 0;
01055   }
01056 
01057   // The date for the event is not displayed, just ignore it
01058   if ( curCol >= static_cast<int>( mSelectedDates.count() ) ) {
01059     return;
01060   }
01061 
01062   // Default values, which can never be reached
01063   mMinY[curCol] = mAgenda->timeToY( QTime( 23, 59 ) ) + 1;
01064   mMaxY[curCol] = mAgenda->timeToY( QTime( 0, 0 ) ) - 1;
01065 
01066   int beginX;
01067   int endX;
01068   QDate columnDate;
01069   if ( event ) {
01070     QDate firstVisibleDate = mSelectedDates.first();
01071     // its crossing bounderies, lets calculate beginX and endX
01072     if ( curDate < firstVisibleDate ) {
01073       beginX = curCol + firstVisibleDate.daysTo( curDate );
01074       endX   = beginX + event->dtStart().daysTo( event->dtEnd() );
01075       columnDate = firstVisibleDate;
01076     } else {
01077       beginX = curCol;
01078       endX   = beginX + event->dtStart().daysTo( event->dtEnd() );
01079       columnDate = curDate;
01080     }
01081   } else if ( todo ) {
01082     if ( !todo->hasDueDate() ) {
01083       return;  // todo shall not be displayed if it has no date
01084     }
01085     columnDate = curDate;
01086     beginX = endX = curCol;
01087 
01088   } else {
01089     return;
01090   }
01091   if ( todo && todo->isOverdue() ) {
01092     mAllDayAgenda->insertAllDayItem( incidence, columnDate, curCol, curCol );
01093   } else if ( incidence->doesFloat() ||
01094               ( todo &&
01095                   !todo->dtDue().isValid() ) ) {
01096       mAllDayAgenda->insertAllDayItem( incidence, columnDate, beginX, endX );
01097   } else if ( event && event->isMultiDay() ) {
01098     int startY = mAgenda->timeToY( event->dtStart().time() );
01099     QTime endtime = event->dtEnd().time();
01100     if ( endtime == QTime( 0, 0, 0 ) ) {
01101       endtime = QTime( 23, 59, 59 );
01102     }
01103     int endY = mAgenda->timeToY( endtime ) - 1;
01104     if ( ( beginX <= 0 && curCol == 0 ) || beginX == curCol ) {
01105       mAgenda->insertMultiItem( event, columnDate, beginX, endX, startY, endY );
01106 
01107     }
01108     if ( beginX == curCol ) {
01109       mMaxY[curCol] = mAgenda->timeToY( QTime( 23, 59 ) );
01110       if ( startY < mMinY[curCol] ) {
01111         mMinY[curCol] = startY;
01112       }
01113     } else if ( endX == curCol ) {
01114       mMinY[curCol] = mAgenda->timeToY( QTime( 0, 0 ) );
01115       if ( endY > mMaxY[curCol] ) {
01116         mMaxY[curCol] = endY;
01117       }
01118     } else {
01119       mMinY[curCol] = mAgenda->timeToY( QTime( 0, 0 ) );
01120       mMaxY[curCol] = mAgenda->timeToY( QTime( 23, 59 ) );
01121     }
01122   } else {
01123     int startY = 0, endY = 0;
01124     if ( event ) {
01125       startY = mAgenda->timeToY( incidence->dtStart().time() );
01126       QTime endtime = event->dtEnd().time();
01127       if ( endtime == QTime( 0, 0, 0 ) ) {
01128         endtime = QTime( 23, 59, 59 );
01129       }
01130       endY = mAgenda->timeToY( endtime ) - 1;
01131     }
01132     if ( todo ) {
01133       QTime t = todo->dtDue().time();
01134 
01135       if ( t == QTime( 0, 0 ) ) {
01136         t = QTime( 23, 59 );
01137       }
01138 
01139       int halfHour = 1800;
01140       if ( t.addSecs( -halfHour ) < t ) {
01141         startY = mAgenda->timeToY( t.addSecs( -halfHour ) );
01142         endY   = mAgenda->timeToY( t ) - 1;
01143       } else {
01144         startY = 0;
01145         endY   = mAgenda->timeToY( t.addSecs( halfHour ) ) - 1;
01146       }
01147     }
01148     if ( endY < startY ) {
01149       endY = startY;
01150     }
01151     mAgenda->insertItem( incidence, columnDate, curCol, startY, endY, 1, 1 );
01152     if ( startY < mMinY[curCol] ) {
01153       mMinY[curCol] = startY;
01154     }
01155     if ( endY > mMaxY[curCol] ) {
01156       mMaxY[curCol] = endY;
01157     }
01158   }
01159 }
01160 
01161 void KOAgendaView::changeIncidenceDisplayAdded( Incidence *incidence )
01162 {
01163   Todo *todo = dynamic_cast<Todo *>(incidence);
01164   CalFilter *filter = calendar()->filter();
01165   if ( ( filter && !filter->filterIncidence( incidence ) ) ||
01166        ( ( todo && !KOPrefs::instance()->showAllDayTodo() ) ) ) {
01167     return;
01168   }
01169 
01170   displayIncidence( incidence );
01171 }
01172 
01173 void KOAgendaView::changeIncidenceDisplay( Incidence *incidence, int mode )
01174 {
01175   switch ( mode ) {
01176     case KOGlobals::INCIDENCEADDED:
01177     {
01178       // Add an event. No need to recreate the whole view!
01179       // recreating everything even causes troubles: dropping to the
01180       // day matrix recreates the agenda items, but the evaluation is
01181       // still in an agendaItems' code, which was deleted in the mean time.
01182       // Thus KOrg crashes...
01183       changeIncidenceDisplayAdded( incidence );
01184       updateEventIndicators();
01185       break;
01186     }
01187     case KOGlobals::INCIDENCEEDITED:
01188     {
01189       if ( mAllowAgendaUpdate ) {
01196         removeIncidence( incidence, false  );
01197         changeIncidenceDisplayAdded( incidence );
01198       }
01199       updateEventIndicators();
01200       break;
01201     }
01202     case KOGlobals::INCIDENCEDELETED:
01203     {
01204       removeIncidence( incidence );
01205       updateEventIndicators();
01206       break;
01207     }
01208     default:
01209       return;
01210   }
01211 
01212   // HACK: Update the view if the all-day agenda has been modified.
01213   // Do this because there are some layout problems in the
01214   // all-day agenda that are not easily solved, but clearing
01215   // and redrawing works ok.
01216   if ( incidence->doesFloat() ) {
01217     if ( sProcessingDrop ) {
01218      QTimer::singleShot( 0, this, SLOT(updateView()) );
01219     } else {
01220      updateView();
01221     }
01222   }
01223 }
01224 
01225 void KOAgendaView::fillAgenda( const QDate & )
01226 {
01227   fillAgenda();
01228 }
01229 
01230 void KOAgendaView::fillAgenda()
01231 {
01232   if ( !mAreDatesInitialized ) {
01233     return;
01234   }
01235 
01236   mPendingChanges = false;
01237 
01238   /* Remember the uids of the selected items. In case one of the
01239    * items was deleted and re-added, we want to reselect it. */
01240   const QString &selectedAgendaUid = mAgenda->lastSelectedUid();
01241   const QString &selectedAllDayAgendaUid = mAllDayAgenda->lastSelectedUid();
01242 
01243   enableAgendaUpdate( true );
01244   clearView();
01245 
01246   mAllDayAgenda->changeColumns( mSelectedDates.count() );
01247   mAgenda->changeColumns( mSelectedDates.count() );
01248   mEventIndicatorTop->changeColumns( mSelectedDates.count() );
01249   mEventIndicatorBottom->changeColumns( mSelectedDates.count() );
01250 
01251   createDayLabels( false );
01252   setHolidayMasks();
01253 
01254   mMinY.resize( mSelectedDates.count() );
01255   mMaxY.resize( mSelectedDates.count() );
01256 
01257   mAgenda->setDateList( mSelectedDates );
01258 
01259   bool somethingReselected = false;
01260   Incidence::List incidences = calendar()->incidences();
01261 
01262   for ( Incidence::List::ConstIterator it = incidences.begin(); it!=incidences.constEnd(); ++it ) {
01263     Incidence *incidence = (*it);
01264     displayIncidence( incidence );
01265 
01266     if( incidence->uid() == selectedAgendaUid && !selectedAgendaUid.isNull() ) {
01267       mAgenda->selectItemByUID( incidence->uid() );
01268       somethingReselected = true;
01269     }
01270 
01271     if( incidence->uid() == selectedAllDayAgendaUid && !selectedAllDayAgendaUid.isNull() ) {
01272       mAllDayAgenda->selectItemByUID( incidence->uid() );
01273       somethingReselected = true;
01274     }
01275 
01276   }
01277 
01278   mAgenda->checkScrollBoundaries();
01279   updateEventIndicators();
01280 
01281   //  mAgenda->viewport()->update();
01282   //  mAllDayAgenda->viewport()->update();
01283 
01284   // make invalid
01285   deleteSelectedDateTime();
01286 
01287   if( !somethingReselected ) {
01288     emit incidenceSelected( 0, QDate() );
01289   }
01290 }
01291 
01292 void KOAgendaView::displayIncidence( Incidence *incidence )
01293 {
01294   QDate today = QDate::currentDate();
01295   DateTimeList::iterator t;
01296 
01297   // FIXME: use a visitor here
01298   Todo *todo = dynamic_cast<Todo *>( incidence );
01299   Event *event = dynamic_cast<Event *>( incidence );
01300 
01301   QDateTime firstVisibleDateTime = mSelectedDates.first();
01302   QDateTime lastVisibleDateTime = mSelectedDates.last();
01303 
01304   lastVisibleDateTime.setTime( QTime( 23, 59, 59, 59 ) );
01305   firstVisibleDateTime.setTime( QTime( 0, 0 ) );
01306   DateTimeList dateTimeList;
01307 
01308   QDateTime incDtStart = incidence->dtStart();
01309   QDateTime incDtEnd   = incidence->dtEnd();
01310 
01311   if ( todo &&
01312        ( !KOPrefs::instance()->showAllDayTodo() || !todo->hasDueDate() ) ) {
01313     return;
01314   }
01315 
01316   if ( incidence->doesRecur() ) {
01317     int eventDuration = event ? incDtStart.daysTo( incDtEnd ) : 0;
01318 
01319     // if there's a multiday event that starts before firstVisibleDateTime but ends after
01320     // lets include it. timesInInterval() ignores incidences that aren't totaly inside
01321     // the range
01322     QDateTime startDateTimeWithOffset = firstVisibleDateTime.addDays( -eventDuration );
01323     dateTimeList =
01324       incidence->recurrence()->timesInInterval( startDateTimeWithOffset,
01325                                                 lastVisibleDateTime );
01326   } else {
01327     QDateTime dateToAdd; // date to add to our date list
01328     QDateTime incidenceStart;
01329     QDateTime incidenceEnd;
01330 
01331     if ( todo && todo->hasDueDate() && !todo->isOverdue() ) {
01332       // If it's not overdue it will be shown at the original date (not today)
01333       dateToAdd = todo->dtDue();
01334 
01335       // To-dos are drawn with the bottom of the rectangle at dtDue
01336       // if dtDue is at 00:00, then it should be displayed in the previous day, at 23:59
01337       if ( !todo->doesFloat() && dateToAdd.time() == QTime( 0, 0 ) ) {
01338         dateToAdd = dateToAdd.addSecs( -1 );
01339       }
01340 
01341       incidenceEnd = dateToAdd;
01342     } else if ( event ) {
01343       dateToAdd = incDtStart;
01344       incidenceEnd = incDtEnd;
01345     }
01346 
01347     if ( incidence->doesFloat() ) {
01348       // so comparisons with < > actually work
01349       dateToAdd.setTime( QTime( 0, 0 ) );
01350       incidenceEnd.setTime( QTime( 23, 59, 59, 59 ) );
01351     }
01352 
01353     if ( dateToAdd <= lastVisibleDateTime && incidenceEnd > firstVisibleDateTime ) {
01354       dateTimeList += dateToAdd;
01355     }
01356   }
01357 
01358   // ToDo items shall be displayed today if they are already overdude
01359   QDateTime dateTimeToday = today;
01360   if ( todo &&
01361        todo->isOverdue() &&
01362        dateTimeToday >= firstVisibleDateTime &&
01363        dateTimeToday <= lastVisibleDateTime ) {
01364 
01365     bool doAdd = true;
01366 
01367     if ( todo->doesRecur() ) {
01368       /* If there's a recurring instance showing up today don't add "today" again
01369        * we don't want the event to appear duplicated */
01370       for ( t = dateTimeList.begin(); t != dateTimeList.end(); ++t ) {
01371         if ( (*t).date() == today ) {
01372           doAdd = false;
01373           break;
01374        }
01375       }
01376     }
01377 
01378     if ( doAdd ) {
01379       dateTimeList += dateTimeToday;
01380     }
01381   }
01382 
01383   const bool makesDayBusy = KOEventView::makesWholeDayBusy( incidence );
01384   for ( t = dateTimeList.begin(); t != dateTimeList.end(); ++t ) {
01385     if ( makesDayBusy ) {
01386       Event::List &busyEvents = mBusyDays[(*t).date()];
01387       busyEvents.append( event );
01388     }
01389     insertIncidence( incidence, (*t).date() );
01390   }
01391 
01392   // Can be multiday
01393   if ( event && makesDayBusy && event->isMultiDay() ) {
01394     const QDate lastVisibleDate = mSelectedDates.last();
01395     for ( QDate date = event->dtStart().date();
01396           date <= event->dtEnd().date() && date <= lastVisibleDate ;
01397           date = date.addDays( 1 ) ) {
01398       Event::List &busyEvents = mBusyDays[date];
01399       busyEvents.append( event );
01400     }
01401   }
01402 }
01403 
01404 void KOAgendaView::clearView()
01405 {
01406 //  kdDebug(5850) << "ClearView" << endl;
01407   mAllDayAgenda->clear();
01408   mAgenda->clear();
01409   mBusyDays.clear();
01410 }
01411 
01412 CalPrinterBase::PrintType KOAgendaView::printType()
01413 {
01414   if ( currentDateCount() == 1 ) return CalPrinterBase::Day;
01415   else return CalPrinterBase::Week;
01416 }
01417 
01418 void KOAgendaView::updateEventIndicatorTop( int newY )
01419 {
01420   uint i;
01421   for( i = 0; i < mMinY.size(); ++i ) {
01422     mEventIndicatorTop->enableColumn( i, newY > mMinY[i] );
01423   }
01424   mEventIndicatorTop->update();
01425 }
01426 
01427 void KOAgendaView::updateEventIndicatorBottom( int newY )
01428 {
01429   uint i;
01430   for( i = 0; i < mMaxY.size(); ++i ) {
01431     mEventIndicatorBottom->enableColumn( i, newY <= mMaxY[i] );
01432   }
01433   mEventIndicatorBottom->update();
01434 }
01435 
01436 void KOAgendaView::slotIncidenceDropped( Incidence *incidence, const QPoint &gpos, bool allDay )
01437 {
01438   struct BoolChanger {
01439     BoolChanger( bool &bb ) : b( bb )
01440     {
01441       b = true;
01442     }
01443 
01444     ~BoolChanger()
01445     {
01446       b = false;
01447     }
01448     bool &b;
01449   };
01450 
01451   BoolChanger boolChanger( sProcessingDrop );
01452 
01453   if ( gpos.x()<0 || gpos.y()<0 ) return;
01454   QDate day = mSelectedDates[gpos.x()];
01455   QTime time = mAgenda->gyToTime( gpos.y() );
01456   QDateTime newTime( day, time );
01457 
01458   Event *event = 0;
01459   Todo *todo = 0;
01460 
01461   if ( incidence->type() == "Event" )
01462     event = static_cast<Event*>( incidence );
01463 
01464   if ( incidence->type() == "Todo" )
01465     todo = static_cast<Todo*>( incidence );
01466 
01467   if ( !event && !todo )
01468     return;
01469 
01470   if ( todo ) {
01471     Todo *existingTodo = calendar()->todo( todo->uid() );
01472     if ( existingTodo ) {
01473       kdDebug(5850) << "Drop existing Todo" << endl;
01474       Todo *oldTodo = existingTodo->clone();
01475       if ( mChanger &&
01476            mChanger->beginChange( existingTodo, resourceCalendar(), subResourceCalendar() ) ) {
01477 
01478         existingTodo->setDtDue( newTime );
01479         existingTodo->setFloats( allDay );
01480         existingTodo->setHasDueDate( true );
01481         mChanger->changeIncidence( oldTodo, existingTodo,
01482                                    KOGlobals::DATE_MODIFIED, this );
01483         mChanger->endChange( existingTodo, resourceCalendar(), subResourceCalendar() );
01484       } else {
01485         KMessageBox::sorry( this, i18n("Unable to modify this to-do, "
01486                             "because it cannot be locked.") );
01487       }
01488       delete oldTodo;
01489     } else {
01490       kdDebug(5850) << "Drop new Todo" << endl;
01491       todo->setDtDue( newTime );
01492       todo->setFloats( allDay );
01493       todo->setHasDueDate( true );
01494       if ( !mChanger->addIncidence( todo, 0, QString(), this ) ) {
01495         KODialogManager::errorSaveIncidence( this, todo );
01496       }
01497     }
01498   } else if ( event ) {
01499     Event *existingEvent = calendar()->event( event->uid() );
01500     Event *existingEventInSameResource = 0;
01501 
01502     if ( existingEvent ) {
01503       // If it comes from another calendar, create a new.
01504       // Otherwise reuse the same one
01505 
01506       if ( !resourceCalendar() ) {
01507         // We are in merged agenda, we'll just use a changeIncidence(), no need to delete/create
01508         existingEventInSameResource = existingEvent;
01509       } else if ( resourceCalendar()->incidence( incidence->uid() ) ) {
01510         // Ok, it's in the same resource, but is it in the same subresource?
01511         if ( subResourceCalendar() == resourceCalendar()->subresourceIdentifier( incidence ) ) {
01512           existingEventInSameResource = existingEvent;
01513         }
01514       }
01515     }
01516 
01517     if ( existingEventInSameResource ) {
01518       kdDebug(5850) << "Drop existing Event" << endl;
01519       Event *oldEvent = existingEventInSameResource->clone();
01520       if ( mChanger &&
01521            mChanger->beginChange( existingEvent, resourceCalendar(), subResourceCalendar() ) ) {
01522         existingEventInSameResource->setDtStart( newTime );
01523 
01524         const int duration = ( existingEventInSameResource->doesFloat() && !allDay ) ?
01525                               3600 : oldEvent->dtStart().secsTo( oldEvent->dtEnd() );
01526 
01527         existingEventInSameResource->setFloats( allDay );
01528         existingEventInSameResource->setDtEnd( newTime.addSecs( duration ) );
01529         mChanger->changeIncidence( oldEvent, existingEventInSameResource,
01530                                    KOGlobals::DATE_MODIFIED, this );
01531         mChanger->endChange( existingEventInSameResource, resourceCalendar(), subResourceCalendar() );
01532       } else {
01533         KMessageBox::sorry( this, i18n("Unable to modify this event, "
01534                             "because it cannot be locked.") );
01535       }
01536       delete oldEvent;
01537     } else {
01538       kdDebug(5850) << "Drop new Event" << endl;
01539       const int duration = event->dtStart().secsTo( event->dtEnd() );
01540       event->setDtStart( newTime );
01541       event->setFloats( allDay );
01542       event->setUid( CalFormat::createUniqueId() );
01543       event->setDtEnd( newTime.addSecs( duration ) );
01544       if ( mChanger->addIncidence( event, resourceCalendar(), subResourceCalendar(), this ) ) {
01545         if ( existingEvent && !existingEventInSameResource ) {
01546           // It's not a drag from another application, it's a drag from another agenda.
01547           mChanger->deleteIncidence( existingEvent, this );
01548         }
01549       } else {
01550         KODialogManager::errorSaveIncidence( this, event );
01551       }
01552     }
01553   }
01554 }
01555 
01556 void KOAgendaView::startDrag( Incidence *incidence )
01557 {
01558 #ifndef KORG_NODND
01559   sProcessingDrop = true;
01560   DndFactory factory( calendar() );
01561   ICalDrag *vd = factory.createDrag( incidence, this );
01562   if ( vd->drag() ) {
01563     kdDebug(5850) << "KOAgendaView::startDrag(): Delete drag source" << endl;
01564   }
01565   sProcessingDrop = false;
01566 #endif
01567 }
01568 
01569 void KOAgendaView::readSettings()
01570 {
01571   readSettings(KOGlobals::self()->config());
01572 }
01573 
01574 void KOAgendaView::readSettings(KConfig *config)
01575 {
01576 //  kdDebug(5850) << "KOAgendaView::readSettings()" << endl;
01577 
01578   config->setGroup("Views");
01579 
01580 #ifndef KORG_NOSPLITTER
01581   QValueList<int> sizes = config->readIntListEntry("Separator AgendaView");
01582   if (sizes.count() == 2) {
01583     mSplitterAgenda->setSizes(sizes);
01584   }
01585 #endif
01586 
01587   updateConfig();
01588 }
01589 
01590 void KOAgendaView::writeSettings(KConfig *config)
01591 {
01592 //  kdDebug(5850) << "KOAgendaView::writeSettings()" << endl;
01593 
01594   config->setGroup("Views");
01595 
01596 #ifndef KORG_NOSPLITTER
01597   QValueList<int> list = mSplitterAgenda->sizes();
01598   config->writeEntry("Separator AgendaView",list);
01599 #endif
01600 }
01601 
01602 void KOAgendaView::setHolidayMasks()
01603 {
01604   if ( mSelectedDates.isEmpty() || !mSelectedDates[0].isValid() ) {
01605     return;
01606   }
01607 
01608   mHolidayMask.resize( mSelectedDates.count() + 1 );
01609 
01610   for( uint i = 0; i < mSelectedDates.count(); ++i ) {
01611     mHolidayMask[i] = !KOGlobals::self()->isWorkDay( mSelectedDates[ i ] );
01612   }
01613 
01614   // Store the information about the day before the visible area (needed for
01615   // overnight working hours) in the last bit of the mask:
01616   bool showDay = !KOGlobals::self()->isWorkDay( mSelectedDates[ 0 ].addDays( -1 ) );
01617   mHolidayMask[ mSelectedDates.count() ] = showDay;
01618 
01619   mAgenda->setHolidayMask( &mHolidayMask );
01620   mAllDayAgenda->setHolidayMask( &mHolidayMask );
01621 }
01622 
01623 QMemArray<bool> KOAgendaView::busyDayMask()
01624 {
01625   if ( mSelectedDates.isEmpty() || !mSelectedDates[0].isValid() ) {
01626     return QMemArray<bool>();
01627   }
01628 
01629   QMemArray<bool> busyDayMask;
01630   busyDayMask.resize( mSelectedDates.count() );
01631 
01632   for( uint i = 0; i < mSelectedDates.count(); ++i ) {
01633     busyDayMask[i] = !mBusyDays[mSelectedDates[i]].isEmpty();
01634   }
01635 
01636   return busyDayMask;
01637 }
01638 
01639 void KOAgendaView::setContentsPos( int y )
01640 {
01641   mAgenda->setContentsPos( 0, y );
01642 }
01643 
01644 void KOAgendaView::setExpandedButton( bool expanded )
01645 {
01646   if ( !mExpandButton ) return;
01647 
01648   if ( expanded ) {
01649     mExpandButton->setPixmap( mExpandedPixmap );
01650   } else {
01651     mExpandButton->setPixmap( mNotExpandedPixmap );
01652   }
01653 }
01654 
01655 void KOAgendaView::clearSelection()
01656 {
01657   mAgenda->deselectItem();
01658   mAllDayAgenda->deselectItem();
01659 }
01660 
01661 void KOAgendaView::newTimeSpanSelectedAllDay( const QPoint &start, const QPoint &end )
01662 {
01663   newTimeSpanSelected( start, end );
01664   mTimeSpanInAllDay = true;
01665 }
01666 
01667 void KOAgendaView::newTimeSpanSelected( const QPoint &start, const QPoint &end )
01668 {
01669   if (!mSelectedDates.count()) return;
01670 
01671   mTimeSpanInAllDay = false;
01672 
01673   QDate dayStart = mSelectedDates[ kClamp( start.x(), 0, (int)mSelectedDates.size() - 1 ) ];
01674   QDate dayEnd = mSelectedDates[ kClamp( end.x(), 0, (int)mSelectedDates.size() - 1 ) ];
01675 
01676   QTime timeStart = mAgenda->gyToTime(start.y());
01677   QTime timeEnd = mAgenda->gyToTime( end.y() + 1 );
01678 
01679   QDateTime dtStart(dayStart,timeStart);
01680   QDateTime dtEnd(dayEnd,timeEnd);
01681 
01682   mTimeSpanBegin = dtStart;
01683   mTimeSpanEnd = dtEnd;
01684 }
01685 
01686 void KOAgendaView::deleteSelectedDateTime()
01687 {
01688   mTimeSpanBegin.setDate(QDate());
01689   mTimeSpanEnd.setDate(QDate());
01690   mTimeSpanInAllDay = false;
01691 }
01692 
01693 void KOAgendaView::setTypeAheadReceiver( QObject *o )
01694 {
01695   mAgenda->setTypeAheadReceiver( o );
01696   mAllDayAgenda->setTypeAheadReceiver( o );
01697 }
01698 
01699 void KOAgendaView::finishTypeAhead()
01700 {
01701   mAgenda->finishTypeAhead();
01702   mAllDayAgenda->finishTypeAhead();
01703 }
01704 
01705 void KOAgendaView::removeIncidence( Incidence *incidence, bool relayoutNeighbours )
01706 {
01707   mAgenda->removeIncidence( incidence, relayoutNeighbours );
01708   mAllDayAgenda->removeIncidence( incidence, relayoutNeighbours );
01709 }
01710 
01711 void KOAgendaView::updateEventIndicators()
01712 {
01713   mMinY = mAgenda->minContentsY();
01714   mMaxY = mAgenda->maxContentsY();
01715 
01716   mAgenda->checkScrollBoundaries();
01717   updateEventIndicatorTop( mAgenda->visibleContentsYMin() );
01718   updateEventIndicatorBottom( mAgenda->visibleContentsYMax() );
01719 }
01720 
01721 void KOAgendaView::setIncidenceChanger( IncidenceChangerBase *changer )
01722 {
01723   mChanger = changer;
01724   mAgenda->setIncidenceChanger( changer );
01725   mAllDayAgenda->setIncidenceChanger( changer );
01726 }
01727 
01728 void KOAgendaView::clearTimeSpanSelection()
01729 {
01730   mAgenda->clearSelection();
01731   mAllDayAgenda->clearSelection();
01732   deleteSelectedDateTime();
01733 }
01734 
01735 bool KOAgendaView::filterByResource( Incidence *incidence )
01736 {
01737   // Special handling for groupware to-dos that are in Task folders.
01738   // Put them in the top-level "Calendar" folder for lack of a better
01739   // place since we never show Task type folders even in the
01740   // multiagenda view.
01741   if ( resourceCalendar() && incidence->type() == "Todo" ) {
01742     QString subRes = resourceCalendar()->subresourceIdentifier( incidence );
01743     if ( resourceCalendar()->subresourceType( subRes ) == "todo" ) {
01744       QString calmatch = "/.INBOX.directory/Calendar";
01745       QString i18nmatch = "/.INBOX.directory/" + i18n( "Calendar" );
01746       if ( subResourceCalendar().contains( calmatch ) ||
01747            subResourceCalendar().contains( i18nmatch ) ) {
01748         return true;
01749       }
01750     }
01751   }
01752 
01753   // Normal handling
01754   if ( !resourceCalendar() )
01755     return true;
01756   CalendarResources *calRes = dynamic_cast<CalendarResources*>( calendar() );
01757   if ( !calRes )
01758     return true;
01759   if ( calRes->resource( incidence ) != resourceCalendar() )
01760     return false;
01761   if ( !subResourceCalendar().isEmpty() ) {
01762     if ( resourceCalendar()->subresourceIdentifier( incidence ) != subResourceCalendar() )
01763       return false;
01764   }
01765   return true;
01766 }
01767 
01768 void KOAgendaView::resourcesChanged()
01769 {
01770   mPendingChanges = true;
01771 }
01772 
01773 void KOAgendaView::calendarIncidenceAdded(Incidence * incidence)
01774 {
01775   Q_UNUSED( incidence );
01776   mPendingChanges = true;
01777 }
01778 
01779 void KOAgendaView::calendarIncidenceChanged(Incidence * incidence)
01780 {
01781   Q_UNUSED( incidence );
01782   mPendingChanges = true;
01783 
01784   // We shouldn't delete the agenda item while we're still processing
01785   if ( !sProcessingDrop )
01786     fillAgenda();
01787 }
01788 
01789 void KOAgendaView::calendarIncidenceDeleted(Incidence * incidence)
01790 {
01791   Q_UNUSED( incidence );
01792   mPendingChanges = true;
01793 }
KDE Home | KDE Accessibility Home | Description of Access Keys