korganizer

kotodoview.cpp

00001 /*
00002     This file is part of KOrganizer.
00003 
00004     Copyright (c) 2000,2001,2003 Cornelius Schumacher <schumacher@kde.org>
00005     Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
00006 
00007     This program is free software; you can redistribute it and/or modify
00008     it under the terms of the GNU General Public License as published by
00009     the Free Software Foundation; either version 2 of the License, or
00010     (at your option) any later version.
00011 
00012     This program is distributed in the hope that it will be useful,
00013     but WITHOUT ANY WARRANTY; without even the implied warranty of
00014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00015     GNU General Public License for more details.
00016 
00017     You should have received a copy of the GNU General Public License
00018     along with this program; if not, write to the Free Software
00019     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020 
00021     As a special exception, permission is given to link this program
00022     with any edition of Qt, and distribute the resulting executable,
00023     without including the source code for Qt in the source distribution.
00024 */
00025 
00026 #include <qlayout.h>
00027 #include <qheader.h>
00028 #include <qcursor.h>
00029 #include <qlabel.h>
00030 #include <qtimer.h>
00031 
00032 #include <kdebug.h>
00033 #include <klocale.h>
00034 #include <kglobal.h>
00035 #include <kiconloader.h>
00036 #include <kmessagebox.h>
00037 
00038 #include <libkcal/icaldrag.h>
00039 #include <libkcal/vcaldrag.h>
00040 #include <libkcal/dndfactory.h>
00041 #include <libkcal/calendarresources.h>
00042 #include <libkcal/resourcecalendar.h>
00043 #include <libkcal/calfilter.h>
00044 #include <libkcal/incidenceformatter.h>
00045 
00046 #include <libkdepim/clicklineedit.h>
00047 #include <libkdepim/kdatepickerpopup.h>
00048 
00049 #include <libemailfunctions/email.h>
00050 
00051 #include "docprefs.h"
00052 
00053 #include "koincidencetooltip.h"
00054 #include "kodialogmanager.h"
00055 #include "kotodoview.h"
00056 #include "koprefs.h"
00057 #include "koglobals.h"
00058 using namespace KOrg;
00059 #include "kotodoviewitem.h"
00060 #include "kotodoview.moc"
00061 #ifndef KORG_NOPRINTER
00062 #include "kocorehelper.h"
00063 #include "calprinter.h"
00064 #endif
00065 
00066 KOTodoListViewToolTip::KOTodoListViewToolTip (QWidget *parent,
00067                                               Calendar *calendar,
00068                                               KOTodoListView *lv )
00069   :QToolTip(parent), mCalendar( calendar )
00070 {
00071   todolist=lv;
00072 }
00073 
00074 void KOTodoListViewToolTip::maybeTip( const QPoint & pos)
00075 {
00076   QRect r;
00077   int headerPos;
00078   int col=todolist->header()->sectionAt(todolist->contentsX() + pos.x());
00079   KOTodoViewItem *i=(KOTodoViewItem *)todolist->itemAt(pos);
00080 
00081   /* Check wether a tooltip is necessary. */
00082   if( i && KOPrefs::instance()->mEnableToolTips )
00083   {
00084 
00085     /* Calculate the rectangle. */
00086     r=todolist->itemRect(i);
00087     headerPos = todolist->header()->sectionPos(col)-todolist->contentsX();
00088     r.setLeft( (headerPos < 0 ? 0 : headerPos) );
00089     r.setRight(headerPos + todolist->header()->sectionSize(col));
00090 
00091     /* Show the tip */
00092     QString tipText( IncidenceFormatter::toolTipStr( mCalendar, i->todo(), QDate(), true ) );;
00093     if ( !tipText.isEmpty() ) {
00094       tip(r, tipText);
00095     }
00096   }
00097 
00098 }
00099 
00100 
00101 
00102 KOTodoListView::KOTodoListView( QWidget *parent, const char *name )
00103   : KListView( parent, name ), mCalendar( 0 ), mChanger( 0 )
00104 {
00105   mOldCurrent = 0;
00106   mMousePressed = false;
00107 
00108   /* Create a Tooltip */
00109   tooltip = new KOTodoListViewToolTip( viewport(), mCalendar, this );
00110 }
00111 
00112 KOTodoListView::~KOTodoListView()
00113 {
00114   delete tooltip;
00115 }
00116 
00117 void KOTodoListView::setCalendar( Calendar *cal )
00118 {
00119   mCalendar = cal;
00120   setAcceptDrops( mCalendar );
00121   viewport()->setAcceptDrops( mCalendar );
00122 }
00123 
00124 bool KOTodoListView::event(QEvent *e)
00125 {
00126   int tmp=0;
00127   KOTodoViewItem *i;
00128 
00129   /* Checks for an ApplicationPaletteChange event and updates
00130    * the small Progress bars to make therm have the right colors. */
00131   if(e->type()==QEvent::ApplicationPaletteChange)
00132   {
00133 
00134     KListView::event(e);
00135     i=(KOTodoViewItem *)itemAtIndex(tmp);
00136 
00137     while(i!=0)
00138     {
00139       i->construct();
00140       tmp++;
00141       i=(KOTodoViewItem *)itemAtIndex(tmp);
00142     }
00143 
00144   }
00145 
00146   return (KListView::event(e) || e->type()==QEvent::ApplicationPaletteChange);
00147 }
00148 
00149 void KOTodoListView::contentsDragEnterEvent(QDragEnterEvent *e)
00150 {
00151 #ifndef KORG_NODND
00152 //  kdDebug(5850) << "KOTodoListView::contentsDragEnterEvent" << endl;
00153   if ( !ICalDrag::canDecode( e ) && !VCalDrag::canDecode( e ) &&
00154        !QTextDrag::canDecode( e ) ) {
00155     e->ignore();
00156     return;
00157   }
00158 
00159   mOldCurrent = currentItem();
00160 #endif
00161 }
00162 
00163 void KOTodoListView::contentsDragMoveEvent(QDragMoveEvent *e)
00164 {
00165 #ifndef KORG_NODND
00166 //  kdDebug(5850) << "KOTodoListView::contentsDragMoveEvent" << endl;
00167 
00168   if ( !ICalDrag::canDecode( e ) && !VCalDrag::canDecode( e ) &&
00169        !QTextDrag::canDecode( e ) ) {
00170     e->ignore();
00171     return;
00172   }
00173 
00174   e->accept();
00175 #endif
00176 }
00177 
00178 void KOTodoListView::contentsDragLeaveEvent( QDragLeaveEvent * )
00179 {
00180 #ifndef KORG_NODND
00181 //  kdDebug(5850) << "KOTodoListView::contentsDragLeaveEvent" << endl;
00182 
00183   setCurrentItem(mOldCurrent);
00184   setSelected(mOldCurrent,true);
00185 #endif
00186 }
00187 
00188 void KOTodoListView::contentsDropEvent( QDropEvent *e )
00189 {
00190 #ifndef KORG_NODND
00191   kdDebug(5850) << "KOTodoListView::contentsDropEvent" << endl;
00192 
00193   if ( !mCalendar || !mChanger ||
00194        ( !ICalDrag::canDecode( e ) && !VCalDrag::canDecode( e ) &&
00195          !QTextDrag::canDecode( e ) ) ) {
00196     e->ignore();
00197     return;
00198   }
00199 
00200   DndFactory factory( mCalendar );
00201   Todo *todo = factory.createDropTodo(e);
00202 
00203   if ( todo ) {
00204     e->acceptAction();
00205 
00206     KOTodoViewItem *destination =
00207         (KOTodoViewItem *)itemAt(contentsToViewport(e->pos()));
00208     Todo *destinationEvent = 0;
00209     if (destination) destinationEvent = destination->todo();
00210 
00211     Todo *existingTodo = mCalendar->todo(todo->uid());
00212 
00213     if( existingTodo ) {
00214        kdDebug(5850) << "Drop existing Todo " << existingTodo << " onto " << destinationEvent << endl;
00215       Incidence *to = destinationEvent;
00216       while(to) {
00217         if (to->uid() == todo->uid()) {
00218           KMessageBox::information(this,
00219               i18n("Cannot move to-do to itself or a child of itself."),
00220               i18n("Drop To-do"), "NoDropTodoOntoItself" );
00221           delete todo;
00222           return;
00223         }
00224         to = to->relatedTo();
00225       }
00226       Todo*oldTodo = existingTodo->clone();
00227       if ( mChanger->beginChange( existingTodo ) ) {
00228         existingTodo->setRelatedTo( destinationEvent );
00229         mChanger->changeIncidence( oldTodo, existingTodo, KOGlobals::RELATION_MODIFIED, this );
00230         mChanger->endChange( existingTodo );
00231       } else {
00232         KMessageBox::sorry( this, i18n("Unable to change to-do's parent, "
00233                             "because the to-do cannot be locked.") );
00234       }
00235       delete oldTodo;
00236       delete todo;
00237     } else {
00238 //      kdDebug(5850) << "Drop new Todo" << endl;
00239       todo->setRelatedTo(destinationEvent);
00240       if ( !mChanger->addIncidence( todo, this ) ) {
00241         KODialogManager::errorSaveIncidence( this, todo );
00242         delete todo;
00243         return;
00244       }
00245     }
00246   }
00247   else {
00248     QString text;
00249     KOTodoViewItem *todoi = dynamic_cast<KOTodoViewItem *>(itemAt( contentsToViewport(e->pos()) ));
00250     if ( ! todoi ) {
00251       // Not dropped on a todo item:
00252       e->ignore();
00253       kdDebug( 5850 ) << "KOTodoListView::contentsDropEvent(): Not dropped on a todo item" << endl;
00254       kdDebug( 5850 ) << "TODO: Create a new todo with the given data" << endl;
00255       // FIXME: Create a new todo with the given text/contact/whatever
00256     } else if ( QTextDrag::decode(e, text) ) {
00257       //QListViewItem *qlvi = itemAt( contentsToViewport(e->pos()) );
00258       kdDebug(5850) << "Dropped : " << text << endl;
00259       Todo*todo = todoi->todo();
00260       if( mChanger->beginChange( todo ) ) {
00261         Todo*oldtodo = todo->clone();
00262 
00263         if( text.startsWith( "file:" ) ) {
00264           todo->addAttachment( new Attachment( text ) );
00265         } else {
00266           QStringList emails = KPIM::splitEmailAddrList( text );
00267           for(QStringList::ConstIterator it = emails.begin();it!=emails.end();++it) {
00268             kdDebug(5850) << " Email: " << (*it) << endl;
00269             int pos = (*it).find("<");
00270             QString name = (*it).left(pos);
00271             QString email = (*it).mid(pos);
00272             if (!email.isEmpty() && todoi) {
00273               todo->addAttendee( new Attendee( name, email ) );
00274             }
00275           }
00276         }
00277         //FIXME: attendees or attachment added, so there is something modified
00278         mChanger->changeIncidence( oldtodo, todo, KOGlobals::NOTHING_MODIFIED, this );
00279         mChanger->endChange( todo );
00280       } else {
00281         KMessageBox::sorry( this, i18n("Unable to add attendees to the to-do, "
00282             "because the to-do cannot be locked.") );
00283       }
00284     }
00285     else {
00286       kdDebug(5850) << "KOTodoListView::contentsDropEvent(): Todo from drop not decodable" << endl;
00287       e->ignore();
00288     }
00289   }
00290 #endif
00291 }
00292 
00293 void KOTodoListView::contentsMousePressEvent(QMouseEvent* e)
00294 {
00295   QListView::contentsMousePressEvent(e);
00296   QPoint p(contentsToViewport(e->pos()));
00297   QListViewItem *i = itemAt(p);
00298   if (i) {
00299     // if the user clicked into the root decoration of the item, don't
00300     // try to start a drag!
00301     if (p.x() > header()->sectionPos(header()->mapToIndex(0)) +
00302         treeStepSize() * (i->depth() + (rootIsDecorated() ? 1 : 0)) +
00303         itemMargin() ||
00304         p.x() < header()->sectionPos(header()->mapToIndex(0))) {
00305       if (e->button()==Qt::LeftButton) {
00306         mPressPos = e->pos();
00307         mMousePressed = true;
00308       }
00309     }
00310   }
00311 }
00312 
00313 void KOTodoListView::contentsMouseMoveEvent(QMouseEvent* e)
00314 {
00315 #ifndef KORG_NODND
00316 //  kdDebug(5850) << "KOTodoListView::contentsMouseMoveEvent()" << endl;
00317   QListView::contentsMouseMoveEvent(e);
00318   if (mMousePressed && (mPressPos - e->pos()).manhattanLength() >
00319       QApplication::startDragDistance()) {
00320     mMousePressed = false;
00321     QListViewItem *item = itemAt(contentsToViewport(mPressPos));
00322     if ( item && mCalendar ) {
00323 //      kdDebug(5850) << "Start Drag for item " << item->text(0) << endl;
00324       DndFactory factory( mCalendar );
00325       ICalDrag *vd = factory.createDrag(
00326                           ((KOTodoViewItem *)item)->todo(),viewport());
00327       if (vd->drag()) {
00328         kdDebug(5850) << "KOTodoListView::contentsMouseMoveEvent(): Delete drag source" << endl;
00329       }
00330 /*
00331       QString source = fullPath(item);
00332       if ( QFile::exists(source) ) {
00333         KURL url;
00334         url.setPath(source);
00335         KURLDrag* ud = KURLDrag::newDrag(KURL::List(url), viewport());
00336         if ( ud->drag() )
00337           QMessageBox::information( this, "Drag source",
00338                                     QString("Delete ")+source, "Not implemented" );
00339 */
00340     }
00341   }
00342 #endif
00343 }
00344 
00345 void KOTodoListView::contentsMouseReleaseEvent(QMouseEvent *e)
00346 {
00347   QListView::contentsMouseReleaseEvent(e);
00348   mMousePressed = false;
00349 }
00350 
00351 void KOTodoListView::contentsMouseDoubleClickEvent(QMouseEvent *e)
00352 {
00353   if (!e) return;
00354 
00355   QPoint vp = contentsToViewport(e->pos());
00356 
00357   QListViewItem *item = itemAt(vp);
00358 
00359   if (!item) return;
00360 
00361   emit doubleClicked(item,vp,0);
00362 }
00363 
00365 
00366 KOTodoView::KOTodoView( Calendar *calendar, QWidget *parent, const char* name)
00367   : KOrg::BaseView( calendar, parent, name )
00368 {
00369   QBoxLayout *topLayout = new QVBoxLayout( this );
00370 
00371   QLabel *title = new QLabel( i18n("To-dos:"), this );
00372   title->setFrameStyle( QFrame::Panel | QFrame::Raised );
00373   topLayout->addWidget( title );
00374 
00375   mQuickAdd = new KPIM::ClickLineEdit( this, i18n( "Click to add a new to-do" ) );
00376   mQuickAdd->setAcceptDrops( false );
00377   topLayout->addWidget( mQuickAdd );
00378 
00379   if ( !KOPrefs::instance()->mEnableQuickTodo ) mQuickAdd->hide();
00380 
00381   mTodoListView = new KOTodoListView( this );
00382   topLayout->addWidget( mTodoListView );
00383 
00384   mTodoListView->setRootIsDecorated( true );
00385   mTodoListView->setAllColumnsShowFocus( true );
00386 
00387   mTodoListView->setShowSortIndicator( true );
00388 
00389   mTodoListView->addColumn( i18n("Summary") );
00390   mTodoListView->addColumn( i18n("Recurs") );
00391   mTodoListView->addColumn( i18n("Priority") );
00392   mTodoListView->setColumnAlignment( ePriorityColumn, AlignHCenter );
00393   mTodoListView->addColumn( i18n("Complete") );
00394   mTodoListView->setColumnAlignment( ePercentColumn, AlignRight );
00395   mTodoListView->addColumn( i18n("Due Date/Time") );
00396   mTodoListView->setColumnAlignment( eDueDateColumn, AlignLeft );
00397   mTodoListView->addColumn( i18n("Categories") );
00398 #if 0
00399   mTodoListView->addColumn( i18n("Sort Id") );
00400   mTodoListView->setColumnAlignment( 4, AlignHCenter );
00401 #endif
00402 
00403   mTodoListView->setMinimumHeight( 60 );
00404   mTodoListView->setItemsRenameable( true );
00405   mTodoListView->setRenameable( 0 );
00406 
00407   mTodoListView->setColumnWidthMode( eSummaryColumn, QListView::Manual );
00408   mTodoListView->setColumnWidthMode( eRecurColumn, QListView::Manual );
00409   mTodoListView->setColumnWidthMode( ePriorityColumn, QListView::Manual );
00410   mTodoListView->setColumnWidthMode( ePercentColumn, QListView::Manual );
00411   mTodoListView->setColumnWidthMode( eDueDateColumn, QListView::Manual );
00412   mTodoListView->setColumnWidthMode( eCategoriesColumn, QListView::Manual );
00413 #if 0
00414   mTodoListView->setColumnWidthMode( eDescriptionColumn, QListView::Manual );
00415 #endif
00416 
00417   mPriorityPopupMenu = new QPopupMenu( this );
00418   mPriority[ mPriorityPopupMenu->insertItem( i18n("Unspecified priority", "unspecified") ) ] = 0;
00419   mPriority[ mPriorityPopupMenu->insertItem( i18n( "1 (highest)") ) ] = 1;
00420   mPriority[ mPriorityPopupMenu->insertItem( i18n( "2" ) ) ] = 2;
00421   mPriority[ mPriorityPopupMenu->insertItem( i18n( "3" ) ) ] = 3;
00422   mPriority[ mPriorityPopupMenu->insertItem( i18n( "4" ) ) ] = 4;
00423   mPriority[ mPriorityPopupMenu->insertItem( i18n( "5 (medium)" ) ) ] = 5;
00424   mPriority[ mPriorityPopupMenu->insertItem( i18n( "6" ) ) ] = 6;
00425   mPriority[ mPriorityPopupMenu->insertItem( i18n( "7" ) ) ] = 7;
00426   mPriority[ mPriorityPopupMenu->insertItem( i18n( "8" ) ) ] = 8;
00427   mPriority[ mPriorityPopupMenu->insertItem( i18n( "9 (lowest)" ) ) ] = 9;
00428   connect( mPriorityPopupMenu, SIGNAL( activated( int ) ),
00429            SLOT( setNewPriority( int ) ));
00430 
00431   mPercentageCompletedPopupMenu = new QPopupMenu(this);
00432   for (int i = 0; i <= 100; i+=10) {
00433     QString label = QString ("%1 %").arg (i);
00434     mPercentage[mPercentageCompletedPopupMenu->insertItem (label)] = i;
00435   }
00436   connect( mPercentageCompletedPopupMenu, SIGNAL( activated( int ) ),
00437            SLOT( setNewPercentage( int ) ) );
00438 
00439   mMovePopupMenu = new KDatePickerPopup(
00440                              KDatePickerPopup::NoDate |
00441                              KDatePickerPopup::DatePicker |
00442                              KDatePickerPopup::Words );
00443   mCopyPopupMenu = new KDatePickerPopup(
00444                              KDatePickerPopup::NoDate |
00445                              KDatePickerPopup::DatePicker |
00446                              KDatePickerPopup::Words );
00447 
00448 
00449   connect( mMovePopupMenu, SIGNAL( dateChanged( QDate )),
00450            SLOT( setNewDate( QDate ) ) );
00451   connect( mCopyPopupMenu, SIGNAL( dateChanged( QDate )),
00452            SLOT( copyTodoToDate( QDate ) ) );
00453 
00454   mItemPopupMenu = new QPopupMenu(this);
00455   mItemPopupMenu->insertItem(i18n("&Show"), this,
00456                              SLOT (showTodo()));
00457   mItemPopupMenu->insertItem(i18n("&Edit..."), this,
00458                              SLOT (editTodo()), 0, ePopupEdit );
00459 #ifndef KORG_NOPRINTER
00460   mItemPopupMenu->insertItem(KOGlobals::self()->smallIcon("printer1"), i18n("&Print..."), this, SLOT( printTodo() ) );
00461 #endif
00462   mItemPopupMenu->insertItem(KOGlobals::self()->smallIconSet("editdelete"), i18n("&Delete"), this,
00463                              SLOT (deleteTodo()), 0, ePopupDelete );
00464   mItemPopupMenu->insertSeparator();
00465   mItemPopupMenu->insertItem(KOGlobals::self()->smallIconSet("todo"), i18n("New &To-do..."), this,
00466                              SLOT (newTodo()));
00467   mItemPopupMenu->insertItem(i18n("New Su&b-to-do..."), this,
00468                              SLOT (newSubTodo()));
00469   mItemPopupMenu->insertItem( i18n("&Make this To-do Independent"), this,
00470       SIGNAL( unSubTodoSignal() ), 0, ePopupUnSubTodo );
00471   mItemPopupMenu->insertItem( i18n("Make all Sub-to-dos &Independent"), this,
00472       SIGNAL( unAllSubTodoSignal() ), 0, ePopupUnAllSubTodo );
00473   mItemPopupMenu->insertSeparator();
00474   mItemPopupMenu->insertItem( i18n("&Copy To"), mCopyPopupMenu, ePopupCopyTo );
00475   mItemPopupMenu->insertItem(i18n("&Move To"), mMovePopupMenu, ePopupMoveTo );
00476   mItemPopupMenu->insertSeparator();
00477   mItemPopupMenu->insertItem(i18n("delete completed to-dos","Pur&ge Completed"),
00478                              this, SLOT( purgeCompleted() ) );
00479 
00480   connect( mMovePopupMenu, SIGNAL( dateChanged( QDate ) ),
00481            mItemPopupMenu, SLOT( hide() ) );
00482   connect( mCopyPopupMenu, SIGNAL( dateChanged( QDate ) ),
00483            mItemPopupMenu, SLOT( hide() ) );
00484 
00485   mPopupMenu = new QPopupMenu(this);
00486   mPopupMenu->insertItem(KOGlobals::self()->smallIconSet("todo"), i18n("&New To-do..."), this,
00487                          SLOT (newTodo()));
00488   mPopupMenu->insertItem(i18n("delete completed to-dos","&Purge Completed"),
00489                          this, SLOT(purgeCompleted()));
00490 
00491   mDocPrefs = new DocPrefs( name );
00492 
00493   // Double clicking conflicts with opening/closing the subtree
00494   connect( mTodoListView, SIGNAL( doubleClicked( QListViewItem *,
00495                                                  const QPoint &, int ) ),
00496            SLOT( editItem( QListViewItem *, const QPoint &, int ) ) );
00497   connect( mTodoListView, SIGNAL( returnPressed( QListViewItem * ) ),
00498            SLOT( editItem( QListViewItem * ) ) );
00499   connect( mTodoListView, SIGNAL( contextMenuRequested( QListViewItem *,
00500                                                         const QPoint &, int ) ),
00501            SLOT( popupMenu( QListViewItem *, const QPoint &, int ) ) );
00502   connect( mTodoListView, SIGNAL( expanded( QListViewItem * ) ),
00503            SLOT( itemStateChanged( QListViewItem * ) ) );
00504   connect( mTodoListView, SIGNAL( collapsed( QListViewItem * ) ),
00505            SLOT( itemStateChanged( QListViewItem * ) ) );
00506 
00507 #if 0
00508   connect(mTodoListView,SIGNAL(selectionChanged(QListViewItem *)),
00509           SLOT(selectionChanged(QListViewItem *)));
00510   connect(mTodoListView,SIGNAL(clicked(QListViewItem *)),
00511           SLOT(selectionChanged(QListViewItem *)));
00512   connect(mTodoListView,SIGNAL(pressed(QListViewItem *)),
00513           SLOT(selectionChanged(QListViewItem *)));
00514 #endif
00515   connect( mTodoListView, SIGNAL(selectionChanged() ),
00516            SLOT( processSelectionChange() ) );
00517   connect( mQuickAdd, SIGNAL( returnPressed () ),
00518            SLOT( addQuickTodo() ) );
00519 }
00520 
00521 KOTodoView::~KOTodoView()
00522 {
00523   delete mDocPrefs;
00524 }
00525 
00526 void KOTodoView::setCalendar( Calendar *cal )
00527 {
00528   BaseView::setCalendar( cal );
00529   mTodoListView->setCalendar( cal );
00530 }
00531 
00532 void KOTodoView::updateView()
00533 {
00534 //  kdDebug(5850) << "KOTodoView::updateView()" << endl;
00535   int oldPos = mTodoListView->contentsY();
00536   mItemsToDelete.clear();
00537   mTodoListView->clear();
00538 
00539   Todo::List todoList = calendar()->todos();
00540 
00541 /*
00542   kdDebug(5850) << "KOTodoView::updateView(): Todo List:" << endl;
00543   Event *t;
00544   for(t = todoList.first(); t; t = todoList.next()) {
00545     kdDebug(5850) << "  " << t->getSummary() << endl;
00546 
00547     if (t->getRelatedTo()) {
00548       kdDebug(5850) << "      (related to " << t->getRelatedTo()->getSummary() << ")" << endl;
00549     }
00550 
00551     QPtrList<Event> l = t->getRelations();
00552     Event *c;
00553     for(c=l.first();c;c=l.next()) {
00554       kdDebug(5850) << "    - relation: " << c->getSummary() << endl;
00555     }
00556   }
00557 */
00558 
00559   // Put for each Event a KOTodoViewItem in the list view. Don't rely on a
00560   // specific order of events. That means that we have to generate parent items
00561   // recursively for proper hierarchical display of Todos.
00562   mTodoMap.clear();
00563   Todo::List::ConstIterator it;
00564   for( it = todoList.begin(); it != todoList.end(); ++it ) {
00565     if ( !mTodoMap.contains( *it ) ) {
00566       insertTodoItem( *it );
00567     }
00568   }
00569 
00570   // Restore opened/closed state
00571   mTodoListView->blockSignals( true );
00572   if( mDocPrefs ) restoreItemState( mTodoListView->firstChild() );
00573   mTodoListView->blockSignals( false );
00574 
00575   mTodoListView->setContentsPos( 0, oldPos );
00576 
00577   processSelectionChange();
00578 }
00579 
00580 void KOTodoView::restoreItemState( QListViewItem *item )
00581 {
00582   while( item ) {
00583     KOTodoViewItem *todoItem = (KOTodoViewItem *)item;
00584     todoItem->setOpen( mDocPrefs->readBoolEntry( todoItem->todo()->uid() ) );
00585     if( item->childCount() > 0 ) restoreItemState( item->firstChild() );
00586     item = item->nextSibling();
00587   }
00588 }
00589 
00590 
00591 QMap<Todo *,KOTodoViewItem *>::ConstIterator
00592   KOTodoView::insertTodoItem(Todo *todo)
00593 {
00594 //  kdDebug(5850) << "KOTodoView::insertTodoItem(): " << todo->getSummary() << endl;
00595   Incidence *incidence = todo->relatedTo();
00596   if (incidence && incidence->type() == "Todo") {
00597     // Use dynamic_cast, because in the future the related item might also be an event
00598     Todo *relatedTodo = dynamic_cast<Todo *>(incidence);
00599 
00600     // just make sure we know we have this item already to avoid endless recursion (Bug 101696)
00601     mTodoMap.insert(todo,0);
00602 
00603 //    kdDebug(5850) << "  has Related" << endl;
00604     QMap<Todo *,KOTodoViewItem *>::ConstIterator itemIterator;
00605     itemIterator = mTodoMap.find(relatedTodo);
00606     if (itemIterator == mTodoMap.end()) {
00607 //      kdDebug(5850) << "    related not yet in list" << endl;
00608       itemIterator = insertTodoItem (relatedTodo);
00609     }
00610     // isn't this pretty stupid? We give one Todo  to the KOTodoViewItem
00611     // and one into the map. Sure finding is more easy but why? -zecke
00612     KOTodoViewItem *todoItem;
00613 
00614     // in case we found a related parent, which has no KOTodoViewItem yet, this must
00615     // be the case where 2 items refer to each other, therefore simply create item as root item
00616     if ( *itemIterator == 0 ) {
00617       todo->setRelatedTo(0);  // break the recursion, else we will have troubles later
00618       todoItem = new KOTodoViewItem(mTodoListView,todo,this);
00619     }
00620     else
00621       todoItem = new KOTodoViewItem(*itemIterator,todo,this);
00622 
00623     return mTodoMap.insert(todo,todoItem);
00624   } else {
00625 //    kdDebug(5850) << "  no Related" << endl;
00626       // see above -zecke
00627     KOTodoViewItem *todoItem = new KOTodoViewItem(mTodoListView,todo,this);
00628     return mTodoMap.insert(todo,todoItem);
00629   }
00630 }
00631 
00632 void KOTodoView::removeTodoItems()
00633 {
00634   KOTodoViewItem *item;
00635   for ( item = mItemsToDelete.first(); item; item = mItemsToDelete.next() ) {
00636     Todo *todo = item->todo();
00637     if ( todo && mTodoMap.contains( todo ) ) {
00638       mTodoMap.remove( todo );
00639     }
00640     delete item;
00641   }
00642   mItemsToDelete.clear();
00643 }
00644 
00645 
00646 bool KOTodoView::scheduleRemoveTodoItem( KOTodoViewItem *todoItem )
00647 {
00648   if ( todoItem ) {
00649     mItemsToDelete.append( todoItem );
00650     QTimer::singleShot( 0, this, SLOT( removeTodoItems() ) );
00651     return true;
00652   } else
00653     return false;
00654 }
00655 
00656 void KOTodoView::updateConfig()
00657 {
00658   mTodoListView->repaintContents();
00659 }
00660 
00661 Incidence::List KOTodoView::selectedIncidences()
00662 {
00663   Incidence::List selected;
00664 
00665   KOTodoViewItem *item = (KOTodoViewItem *)(mTodoListView->selectedItem());
00666 //  if (!item) item = mActiveItem;
00667   if (item) selected.append(item->todo());
00668 
00669   return selected;
00670 }
00671 
00672 Todo::List KOTodoView::selectedTodos()
00673 {
00674   Todo::List selected;
00675 
00676   KOTodoViewItem *item = (KOTodoViewItem *)(mTodoListView->selectedItem());
00677 //  if (!item) item = mActiveItem;
00678   if (item) selected.append(item->todo());
00679 
00680   return selected;
00681 }
00682 
00683 void KOTodoView::changeIncidenceDisplay(Incidence *incidence, int action)
00684 {
00685   // The todo view only displays todos, so exit on all other incidences
00686   if ( incidence->type() != "Todo" )
00687     return;
00688   CalFilter *filter = calendar()->filter();
00689   bool isFiltered = filter && !filter->filterIncidence( incidence );
00690   Todo *todo = static_cast<Todo *>(incidence);
00691   if ( todo ) {
00692     KOTodoViewItem *todoItem = 0;
00693     if ( mTodoMap.contains( todo ) ) {
00694       todoItem = mTodoMap[todo];
00695     }
00696     switch ( action ) {
00697       case KOGlobals::INCIDENCEADDED:
00698       case KOGlobals::INCIDENCEEDITED:
00699         // If it's already there, edit it, otherwise just add
00700         if ( todoItem ) {
00701           if ( isFiltered ) {
00702             scheduleRemoveTodoItem( todoItem );
00703           } else {
00704             // correctly update changes in relations
00705             Todo*parent = dynamic_cast<Todo*>( todo->relatedTo() );
00706             KOTodoViewItem*parentItem = 0;
00707             if ( parent && mTodoMap.contains(parent) ) {
00708               parentItem = mTodoMap[ parent ];
00709             }
00710             if ( todoItem->parent() != parentItem ) {
00711               // The relations changed
00712               if ( parentItem ) {
00713                 parentItem->insertItem( todoItem );
00714               } else {
00715                 mTodoListView->insertItem( todoItem );
00716               }
00717             }
00718             todoItem->construct();
00719           }
00720         } else {
00721           if ( !isFiltered ) {
00722             insertTodoItem( todo );
00723           }
00724         }
00725         mTodoListView->sort();
00726         break;
00727       case KOGlobals::INCIDENCEDELETED:
00728         if ( todoItem ) {
00729           scheduleRemoveTodoItem( todoItem );
00730         }
00731         break;
00732       default:
00733         QTimer::singleShot( 0, this, SLOT( updateView() ) );
00734     }
00735   } else {
00736     // use a QTimer here, because when marking todos finished using
00737     // the checkbox, this slot gets called, but we cannot update the views
00738     // because we're still inside KOTodoViewItem::stateChange
00739     QTimer::singleShot(0,this,SLOT(updateView()));
00740   }
00741 }
00742 
00743 void KOTodoView::showDates(const QDate &, const QDate &)
00744 {
00745 }
00746 
00747 void KOTodoView::showIncidences( const Incidence::List &, const QDate & )
00748 {
00749   kdDebug(5850) << "KOTodoView::showIncidences( const Incidence::List & ): not yet implemented" << endl;
00750 }
00751 
00752 CalPrinterBase::PrintType KOTodoView::printType()
00753 {
00754   if ( mTodoListView->selectedItem() ) {
00755     return CalPrinterBase::Incidence;
00756   } else {
00757     return CalPrinterBase::Todolist;
00758   }
00759 }
00760 
00761 void KOTodoView::editItem( QListViewItem *item )
00762 {
00763   if (item)
00764     emit editIncidenceSignal( static_cast<KOTodoViewItem *>( item )->todo() );
00765 }
00766 
00767 void KOTodoView::editItem( QListViewItem *item, const QPoint &, int )
00768 {
00769   editItem( item );
00770 }
00771 
00772 void KOTodoView::showItem( QListViewItem *item )
00773 {
00774   if (item)
00775     emit showIncidenceSignal( static_cast<KOTodoViewItem *>( item )->todo() );
00776 }
00777 
00778 void KOTodoView::showItem( QListViewItem *item, const QPoint &, int )
00779 {
00780   showItem( item );
00781 }
00782 
00783 void KOTodoView::popupMenu( QListViewItem *item, const QPoint &, int column )
00784 {
00785   mActiveItem = static_cast<KOTodoViewItem *>( item );
00786   if ( mActiveItem && mActiveItem->todo() &&
00787        !mActiveItem->todo()->isReadOnly() ) {
00788     bool editable = !mActiveItem->todo()->isReadOnly();
00789     mItemPopupMenu->setItemEnabled( ePopupEdit, editable );
00790     mItemPopupMenu->setItemEnabled( ePopupDelete, editable );
00791     mItemPopupMenu->setItemEnabled( ePopupMoveTo, editable );
00792     mItemPopupMenu->setItemEnabled( ePopupCopyTo, editable );
00793     mItemPopupMenu->setItemEnabled( ePopupUnSubTodo, editable );
00794     mItemPopupMenu->setItemEnabled( ePopupUnAllSubTodo, editable );
00795 
00796     if ( editable ) {
00797       QDate date = mActiveItem->todo()->dtDue().date();
00798       if ( mActiveItem->todo()->hasDueDate () ) {
00799         mMovePopupMenu->datePicker()->setDate( date );
00800       } else {
00801         mMovePopupMenu->datePicker()->setDate( QDate::currentDate() );
00802       }
00803       switch ( column ) {
00804         case ePriorityColumn:
00805           mPriorityPopupMenu->popup( QCursor::pos() );
00806           break;
00807         case ePercentColumn: {
00808           mPercentageCompletedPopupMenu->popup( QCursor::pos() );
00809           break;
00810         }
00811         case eDueDateColumn:
00812           mMovePopupMenu->popup( QCursor::pos() );
00813           break;
00814         case eCategoriesColumn:
00815           getCategoryPopupMenu( mActiveItem )->popup( QCursor::pos() );
00816           break;
00817         default:
00818           mCopyPopupMenu->datePicker()->setDate( date );
00819           mCopyPopupMenu->datePicker()->setDate( QDate::currentDate() );
00820           mItemPopupMenu->setItemEnabled( ePopupUnSubTodo,
00821                                           mActiveItem->todo()->relatedTo() );
00822           mItemPopupMenu->setItemEnabled( ePopupUnAllSubTodo,
00823                                           !mActiveItem->todo()->relations().isEmpty() );
00824           mItemPopupMenu->popup( QCursor::pos() );
00825       }
00826     } else {
00827       mItemPopupMenu->popup( QCursor::pos() );
00828     }
00829   } else mPopupMenu->popup( QCursor::pos() );
00830 }
00831 
00832 void KOTodoView::newTodo()
00833 {
00834   kdDebug() << k_funcinfo << endl;
00835   emit newTodoSignal( QDate::currentDate().addDays(7) );
00836 }
00837 
00838 void KOTodoView::newSubTodo()
00839 {
00840   if (mActiveItem) {
00841     emit newSubTodoSignal(mActiveItem->todo());
00842   }
00843 }
00844 
00845 void KOTodoView::editTodo()
00846 {
00847   editItem( mActiveItem );
00848 }
00849 
00850 void KOTodoView::showTodo()
00851 {
00852   showItem( mActiveItem );
00853 }
00854 
00855 void KOTodoView::printTodo()
00856 {
00857 #ifndef KORG_NOPRINTER
00858   KOCoreHelper helper;
00859   CalPrinter printer( this, BaseView::calendar(), &helper );
00860   connect( this, SIGNAL(configChanged()), &printer, SLOT(updateConfig()) );
00861 
00862   Incidence::List selectedIncidences;
00863   selectedIncidences.append( mActiveItem->todo() );
00864 
00865   printer.print( KOrg::CalPrinterBase::Incidence,
00866                  QDate(), QDate(), selectedIncidences );
00867 #endif
00868 }
00869 
00870 void KOTodoView::deleteTodo()
00871 {
00872   if (mActiveItem) {
00873     emit deleteIncidenceSignal( mActiveItem->todo() );
00874   }
00875 }
00876 
00877 void KOTodoView::setNewPriority(int index)
00878 {
00879   if ( !mActiveItem || !mChanger ) return;
00880   Todo *todo = mActiveItem->todo();
00881   if ( !todo->isReadOnly () &&
00882        mChanger->beginChange( todo ) ) {
00883     Todo *oldTodo = todo->clone();
00884     todo->setPriority(mPriority[index]);
00885     mActiveItem->construct();
00886 
00887     mChanger->changeIncidence( oldTodo, todo, KOGlobals::PRIORITY_MODIFIED, this );
00888     mChanger->endChange( todo );
00889     delete oldTodo;
00890   }
00891 }
00892 
00893 void KOTodoView::setNewPercentage( KOTodoViewItem *item, int percentage )
00894 {
00895   kdDebug(5850) << "KOTodoView::setNewPercentage( " << percentage << "), item = " << item << endl;
00896   if ( !item || !mChanger  ) return;
00897   Todo *todo = item->todo();
00898   if ( !todo ) return;
00899 
00900   if ( !todo->isReadOnly () && mChanger->beginChange( todo ) ) {
00901     Todo *oldTodo = todo->clone();
00902 
00903 /*  Old code to make sub-items's percentage related to this one's:
00904     QListViewItem *myChild = firstChild();
00905     KOTodoViewItem *item;
00906     while( myChild ) {
00907       item = static_cast<KOTodoViewItem*>(myChild);
00908       item->stateChange(state);
00909       myChild = myChild->nextSibling();
00910     }*/
00911     if ( percentage == 100 ) {
00912       todo->setCompleted( QDateTime::currentDateTime() );
00913       // If the todo does recur, it doesn't get set as completed. However, the
00914       // item is still checked. Uncheck it again.
00915       if ( !todo->isCompleted() ) item->setState( QCheckListItem::Off );
00916       else todo->setPercentComplete( percentage );
00917     } else {
00918       todo->setCompleted( false );
00919       todo->setPercentComplete( percentage );
00920     }
00921     item->construct();
00922     if ( todo->doesRecur() && percentage == 100 )
00923       mChanger->changeIncidence( oldTodo, todo,
00924                                  KOGlobals::COMPLETION_MODIFIED_WITH_RECURRENCE, this );
00925     else
00926       mChanger->changeIncidence( oldTodo, todo,
00927                                  KOGlobals::COMPLETION_MODIFIED, this );
00928     mChanger->endChange( todo );
00929     delete oldTodo;
00930   } else {
00931     item->construct();
00932     kdDebug(5850) << "No active item, active item is read-only, or locking failed" << endl;
00933   }
00934 }
00935 
00936 void KOTodoView::setNewPercentage( int index )
00937 {
00938   setNewPercentage( mActiveItem, mPercentage[index] );
00939 }
00940 
00941 void KOTodoView::setNewDate( QDate date )
00942 {
00943   if ( !mActiveItem || !mChanger ) return;
00944   Todo *todo = mActiveItem->todo();
00945   if ( !todo ) return;
00946 
00947   if ( !todo->isReadOnly() && mChanger->beginChange( todo ) ) {
00948     Todo *oldTodo = todo->clone();
00949 
00950     QDateTime dt;
00951     dt.setDate( date );
00952 
00953     if ( !todo->doesFloat() )
00954       dt.setTime( todo->dtDue().time() );
00955 
00956     if ( date.isNull() )
00957       todo->setHasDueDate( false );
00958     else if ( !todo->hasDueDate() )
00959       todo->setHasDueDate( true );
00960     todo->setDtDue( dt );
00961 
00962     mActiveItem->construct();
00963     mChanger->changeIncidence( oldTodo, todo, KOGlobals::COMPLETION_MODIFIED, this );
00964     mChanger->endChange( todo );
00965     delete oldTodo;
00966   } else {
00967     kdDebug(5850) << "No active item, active item is read-only, or locking failed" << endl;
00968   }
00969 }
00970 
00971 void KOTodoView::copyTodoToDate( QDate date )
00972 {
00973   QDateTime dt( date );
00974 
00975   if ( mActiveItem && mChanger ) {
00976     Todo *newTodo = mActiveItem->todo()->clone();
00977     newTodo->recreate();
00978 
00979    newTodo->setHasDueDate( !date.isNull() );
00980    newTodo->setDtDue( dt );
00981    newTodo->setPercentComplete( 0 );
00982 
00983    // avoid forking
00984    if ( newTodo->doesRecur() )
00985      newTodo->recurrence()->unsetRecurs();
00986 
00987    mChanger->addIncidence( newTodo, this );
00988  }
00989 }
00990 
00991 QPopupMenu *KOTodoView::getCategoryPopupMenu( KOTodoViewItem *todoItem )
00992 {
00993   QPopupMenu *tempMenu = new QPopupMenu( this );
00994   QStringList checkedCategories = todoItem->todo()->categories();
00995 
00996   tempMenu->setCheckable( true );
00997   QStringList::Iterator it;
00998   for ( it = KOPrefs::instance()->mCustomCategories.begin();
00999         it != KOPrefs::instance()->mCustomCategories.end();
01000         ++it ) {
01001     int index = tempMenu->insertItem( *it );
01002     mCategory[ index ] = *it;
01003     if ( checkedCategories.find( *it ) != checkedCategories.end() )
01004       tempMenu->setItemChecked( index, true );
01005   }
01006 
01007   connect ( tempMenu, SIGNAL( activated( int ) ),
01008             SLOT( changedCategories( int ) ) );
01009   return tempMenu;
01010 }
01011 
01012 void KOTodoView::changedCategories(int index)
01013 {
01014   if ( !mActiveItem || !mChanger ) return;
01015   Todo *todo = mActiveItem->todo();
01016   if ( !todo ) return;
01017 
01018   if ( !todo->isReadOnly() && mChanger->beginChange( todo ) ) {
01019     Todo *oldTodo = todo->clone();
01020 
01021     QStringList categories = todo->categories ();
01022     if ( categories.find( mCategory[index] ) != categories.end() )
01023       categories.remove( mCategory[index] );
01024     else
01025       categories.insert( categories.end(), mCategory[index] );
01026     categories.sort();
01027     todo->setCategories( categories );
01028     mActiveItem->construct();
01029     mChanger->changeIncidence( oldTodo, todo, KOGlobals::CATEGORY_MODIFIED, this );
01030     mChanger->endChange( todo );
01031     delete oldTodo;
01032   } else {
01033     kdDebug(5850) << "No active item, active item is read-only, or locking failed" << endl;
01034   }
01035 }
01036 
01037 void KOTodoView::setDocumentId( const QString &id )
01038 {
01039   kdDebug(5850) << "KOTodoView::setDocumentId()" << endl;
01040 
01041   mDocPrefs->setDoc( id );
01042 }
01043 
01044 void KOTodoView::itemStateChanged( QListViewItem *item )
01045 {
01046   if (!item) return;
01047 
01048   KOTodoViewItem *todoItem = (KOTodoViewItem *)item;
01049 
01050 //  kdDebug(5850) << "KOTodoView::itemStateChanged(): " << todoItem->todo()->summary() << endl;
01051 
01052   if( mDocPrefs ) mDocPrefs->writeEntry( todoItem->todo()->uid(), todoItem->isOpen() );
01053 }
01054 
01055 void KOTodoView::setNewPercentageDelayed( KOTodoViewItem *item, int percentage )
01056 {
01057   mPercentChangedMap.append( qMakePair( item, percentage ) );
01058 
01059   QTimer::singleShot( 0, this, SLOT( processDelayedNewPercentage() ) );
01060 }
01061 
01062 void KOTodoView::processDelayedNewPercentage()
01063 {
01064   QValueList< QPair< KOTodoViewItem *, int> >::Iterator it;
01065   for ( it = mPercentChangedMap.begin(); it != mPercentChangedMap.end(); ++it )
01066     setNewPercentage( (*it).first, (*it).second );
01067 
01068   mPercentChangedMap.clear();
01069 }
01070 
01071 void KOTodoView::saveLayout(KConfig *config, const QString &group) const
01072 {
01073   mTodoListView->saveLayout(config,group);
01074 }
01075 
01076 void KOTodoView::restoreLayout(KConfig *config, const QString &group)
01077 {
01078   mTodoListView->restoreLayout(config,group);
01079 }
01080 
01081 void KOTodoView::processSelectionChange()
01082 {
01083 //  kdDebug(5850) << "KOTodoView::processSelectionChange()" << endl;
01084 
01085   KOTodoViewItem *item =
01086     static_cast<KOTodoViewItem *>( mTodoListView->selectedItem() );
01087 
01088   if ( !item ) {
01089     emit incidenceSelected( 0, QDate() );
01090   } else {
01091     if ( selectedDates().isEmpty() ) {
01092       emit incidenceSelected( item->todo(), QDate() );
01093     } else {
01094       emit incidenceSelected( item->todo(), selectedDates().first() );
01095     }
01096   }
01097 }
01098 
01099 void KOTodoView::clearSelection()
01100 {
01101   mTodoListView->selectAll( false );
01102 }
01103 
01104 void KOTodoView::purgeCompleted()
01105 {
01106   emit purgeCompletedSignal();
01107 }
01108 
01109 void KOTodoView::addQuickTodo()
01110 {
01111   if ( ! mQuickAdd->text().stripWhiteSpace().isEmpty() ) {
01112     Todo *todo = new Todo();
01113     todo->setSummary( mQuickAdd->text() );
01114     todo->setOrganizer( Person( KOPrefs::instance()->fullName(),
01115                         KOPrefs::instance()->email() ) );
01116     if ( !mChanger->addIncidence( todo, this ) ) {
01117       KODialogManager::errorSaveIncidence( this, todo );
01118       delete todo;
01119       return;
01120     }
01121     mQuickAdd->setText( QString::null );
01122   }
01123 }
01124 
01125 void KOTodoView::setIncidenceChanger( IncidenceChangerBase *changer )
01126 {
01127   mChanger = changer;
01128   mTodoListView->setIncidenceChanger( changer );
01129 }