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 );
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         mChanger->changeIncidence( oldtodo, todo );
00278         mChanger->endChange( todo );
00279       } else {
00280         KMessageBox::sorry( this, i18n("Unable to add attendees to the to-do, "
00281             "because the to-do cannot be locked.") );
00282       }
00283     }
00284     else {
00285       kdDebug(5850) << "KOTodoListView::contentsDropEvent(): Todo from drop not decodable" << endl;
00286       e->ignore();
00287     }
00288   }
00289 #endif
00290 }
00291 
00292 void KOTodoListView::contentsMousePressEvent(QMouseEvent* e)
00293 {
00294   QListView::contentsMousePressEvent(e);
00295   QPoint p(contentsToViewport(e->pos()));
00296   QListViewItem *i = itemAt(p);
00297   if (i) {
00298     // if the user clicked into the root decoration of the item, don't
00299     // try to start a drag!
00300     if (p.x() > header()->sectionPos(header()->mapToIndex(0)) +
00301         treeStepSize() * (i->depth() + (rootIsDecorated() ? 1 : 0)) +
00302         itemMargin() ||
00303         p.x() < header()->sectionPos(header()->mapToIndex(0))) {
00304       if (e->button()==Qt::LeftButton) {
00305         mPressPos = e->pos();
00306         mMousePressed = true;
00307       }
00308     }
00309   }
00310 }
00311 
00312 void KOTodoListView::contentsMouseMoveEvent(QMouseEvent* e)
00313 {
00314 #ifndef KORG_NODND
00315 //  kdDebug(5850) << "KOTodoListView::contentsMouseMoveEvent()" << endl;
00316   QListView::contentsMouseMoveEvent(e);
00317   if (mMousePressed && (mPressPos - e->pos()).manhattanLength() >
00318       QApplication::startDragDistance()) {
00319     mMousePressed = false;
00320     QListViewItem *item = itemAt(contentsToViewport(mPressPos));
00321     if ( item && mCalendar ) {
00322 //      kdDebug(5850) << "Start Drag for item " << item->text(0) << endl;
00323       DndFactory factory( mCalendar );
00324       ICalDrag *vd = factory.createDrag(
00325                           ((KOTodoViewItem *)item)->todo(),viewport());
00326       if (vd->drag()) {
00327         kdDebug(5850) << "KOTodoListView::contentsMouseMoveEvent(): Delete drag source" << endl;
00328       }
00329 /*
00330       QString source = fullPath(item);
00331       if ( QFile::exists(source) ) {
00332         KURL url;
00333         url.setPath(source);
00334         KURLDrag* ud = KURLDrag::newDrag(KURL::List(url), viewport());
00335         if ( ud->drag() )
00336           QMessageBox::information( this, "Drag source",
00337                                     QString("Delete ")+source, "Not implemented" );
00338 */
00339     }
00340   }
00341 #endif
00342 }
00343 
00344 void KOTodoListView::contentsMouseReleaseEvent(QMouseEvent *e)
00345 {
00346   QListView::contentsMouseReleaseEvent(e);
00347   mMousePressed = false;
00348 }
00349 
00350 void KOTodoListView::contentsMouseDoubleClickEvent(QMouseEvent *e)
00351 {
00352   if (!e) return;
00353 
00354   QPoint vp = contentsToViewport(e->pos());
00355 
00356   QListViewItem *item = itemAt(vp);
00357 
00358   if (!item) return;
00359 
00360   emit doubleClicked(item,vp,0);
00361 }
00362 
00364 
00365 KOTodoView::KOTodoView( Calendar *calendar, QWidget *parent, const char* name)
00366   : KOrg::BaseView( calendar, parent, name )
00367 {
00368   QBoxLayout *topLayout = new QVBoxLayout( this );
00369 
00370   QLabel *title = new QLabel( i18n("To-dos:"), this );
00371   title->setFrameStyle( QFrame::Panel | QFrame::Raised );
00372   topLayout->addWidget( title );
00373 
00374   mQuickAdd = new KPIM::ClickLineEdit( this, i18n( "Click to add a new to-do" ) );
00375   mQuickAdd->setAcceptDrops( false );
00376   topLayout->addWidget( mQuickAdd );
00377 
00378   if ( !KOPrefs::instance()->mEnableQuickTodo ) mQuickAdd->hide();
00379 
00380   mTodoListView = new KOTodoListView( this );
00381   topLayout->addWidget( mTodoListView );
00382 
00383   mTodoListView->setRootIsDecorated( true );
00384   mTodoListView->setAllColumnsShowFocus( true );
00385 
00386   mTodoListView->setShowSortIndicator( true );
00387 
00388   mTodoListView->addColumn( i18n("Summary") );
00389   mTodoListView->addColumn( i18n("Recurs") );
00390   mTodoListView->addColumn( i18n("Priority") );
00391   mTodoListView->setColumnAlignment( ePriorityColumn, AlignHCenter );
00392   mTodoListView->addColumn( i18n("Complete") );
00393   mTodoListView->setColumnAlignment( ePercentColumn, AlignRight );
00394   mTodoListView->addColumn( i18n("Due Date/Time") );
00395   mTodoListView->setColumnAlignment( eDueDateColumn, AlignLeft );
00396   mTodoListView->addColumn( i18n("Categories") );
00397 #if 0
00398   mTodoListView->addColumn( i18n("Sort Id") );
00399   mTodoListView->setColumnAlignment( 4, AlignHCenter );
00400 #endif
00401 
00402   mTodoListView->setMinimumHeight( 60 );
00403   mTodoListView->setItemsRenameable( true );
00404   mTodoListView->setRenameable( 0 );
00405 
00406   mTodoListView->setColumnWidthMode( eSummaryColumn, QListView::Manual );
00407   mTodoListView->setColumnWidthMode( eRecurColumn, QListView::Manual );
00408   mTodoListView->setColumnWidthMode( ePriorityColumn, QListView::Manual );
00409   mTodoListView->setColumnWidthMode( ePercentColumn, QListView::Manual );
00410   mTodoListView->setColumnWidthMode( eDueDateColumn, QListView::Manual );
00411   mTodoListView->setColumnWidthMode( eCategoriesColumn, QListView::Manual );
00412 #if 0
00413   mTodoListView->setColumnWidthMode( eDescriptionColumn, QListView::Manual );
00414 #endif
00415 
00416   mPriorityPopupMenu = new QPopupMenu( this );
00417   mPriority[ mPriorityPopupMenu->insertItem( i18n("Unspecified priority", "unspecified") ) ] = 0;
00418   mPriority[ mPriorityPopupMenu->insertItem( i18n( "1 (highest)") ) ] = 1;
00419   mPriority[ mPriorityPopupMenu->insertItem( i18n( "2" ) ) ] = 2;
00420   mPriority[ mPriorityPopupMenu->insertItem( i18n( "3" ) ) ] = 3;
00421   mPriority[ mPriorityPopupMenu->insertItem( i18n( "4" ) ) ] = 4;
00422   mPriority[ mPriorityPopupMenu->insertItem( i18n( "5 (medium)" ) ) ] = 5;
00423   mPriority[ mPriorityPopupMenu->insertItem( i18n( "6" ) ) ] = 6;
00424   mPriority[ mPriorityPopupMenu->insertItem( i18n( "7" ) ) ] = 7;
00425   mPriority[ mPriorityPopupMenu->insertItem( i18n( "8" ) ) ] = 8;
00426   mPriority[ mPriorityPopupMenu->insertItem( i18n( "9 (lowest)" ) ) ] = 9;
00427   connect( mPriorityPopupMenu, SIGNAL( activated( int ) ),
00428            SLOT( setNewPriority( int ) ));
00429 
00430   mPercentageCompletedPopupMenu = new QPopupMenu(this);
00431   for (int i = 0; i <= 100; i+=10) {
00432     QString label = QString ("%1 %").arg (i);
00433     mPercentage[mPercentageCompletedPopupMenu->insertItem (label)] = i;
00434   }
00435   connect( mPercentageCompletedPopupMenu, SIGNAL( activated( int ) ),
00436            SLOT( setNewPercentage( int ) ) );
00437 
00438   mMovePopupMenu = new KDatePickerPopup(
00439                              KDatePickerPopup::NoDate |
00440                              KDatePickerPopup::DatePicker |
00441                              KDatePickerPopup::Words );
00442   mCopyPopupMenu = new KDatePickerPopup(
00443                              KDatePickerPopup::NoDate |
00444                              KDatePickerPopup::DatePicker |
00445                              KDatePickerPopup::Words );
00446 
00447 
00448   connect( mMovePopupMenu, SIGNAL( dateChanged( QDate )),
00449            SLOT( setNewDate( QDate ) ) );
00450   connect( mCopyPopupMenu, SIGNAL( dateChanged( QDate )),
00451            SLOT( copyTodoToDate( QDate ) ) );
00452 
00453   mItemPopupMenu = new QPopupMenu(this);
00454   mItemPopupMenu->insertItem(i18n("&Show"), this,
00455                              SLOT (showTodo()));
00456   mItemPopupMenu->insertItem(i18n("&Edit..."), this,
00457                              SLOT (editTodo()), 0, ePopupEdit );
00458 #ifndef KORG_NOPRINTER
00459   mItemPopupMenu->insertItem(KOGlobals::self()->smallIcon("printer1"), i18n("&Print..."), this, SLOT( printTodo() ) );
00460 #endif
00461   mItemPopupMenu->insertItem(KOGlobals::self()->smallIconSet("editdelete"), i18n("&Delete"), this,
00462                              SLOT (deleteTodo()), 0, ePopupDelete );
00463   mItemPopupMenu->insertSeparator();
00464   mItemPopupMenu->insertItem(KOGlobals::self()->smallIconSet("todo"), i18n("New &To-do..."), this,
00465                              SLOT (newTodo()));
00466   mItemPopupMenu->insertItem(i18n("New Su&b-to-do..."), this,
00467                              SLOT (newSubTodo()));
00468   mItemPopupMenu->insertItem( i18n("&Make this To-do Independent"), this,
00469       SIGNAL( unSubTodoSignal() ), 0, ePopupUnSubTodo );
00470   mItemPopupMenu->insertItem( i18n("Make all Sub-to-dos &Independent"), this,
00471       SIGNAL( unAllSubTodoSignal() ), 0, ePopupUnAllSubTodo );
00472   mItemPopupMenu->insertSeparator();
00473   mItemPopupMenu->insertItem( i18n("&Copy To"), mCopyPopupMenu, ePopupCopyTo );
00474   mItemPopupMenu->insertItem(i18n("&Move To"), mMovePopupMenu, ePopupMoveTo );
00475   mItemPopupMenu->insertSeparator();
00476   mItemPopupMenu->insertItem(i18n("delete completed to-dos","Pur&ge Completed"),
00477                              this, SLOT( purgeCompleted() ) );
00478 
00479   connect( mMovePopupMenu, SIGNAL( dateChanged( QDate ) ),
00480            mItemPopupMenu, SLOT( hide() ) );
00481   connect( mCopyPopupMenu, SIGNAL( dateChanged( QDate ) ),
00482            mItemPopupMenu, SLOT( hide() ) );
00483 
00484   mPopupMenu = new QPopupMenu(this);
00485   mPopupMenu->insertItem(KOGlobals::self()->smallIconSet("todo"), i18n("&New To-do..."), this,
00486                          SLOT (newTodo()));
00487   mPopupMenu->insertItem(i18n("delete completed to-dos","&Purge Completed"),
00488                          this, SLOT(purgeCompleted()));
00489 
00490   mDocPrefs = new DocPrefs( name );
00491 
00492   // Double clicking conflicts with opening/closing the subtree
00493   connect( mTodoListView, SIGNAL( doubleClicked( QListViewItem *,
00494                                                  const QPoint &, int ) ),
00495            SLOT( editItem( QListViewItem *, const QPoint &, int ) ) );
00496   connect( mTodoListView, SIGNAL( returnPressed( QListViewItem * ) ),
00497            SLOT( editItem( QListViewItem * ) ) );
00498   connect( mTodoListView, SIGNAL( contextMenuRequested( QListViewItem *,
00499                                                         const QPoint &, int ) ),
00500            SLOT( popupMenu( QListViewItem *, const QPoint &, int ) ) );
00501   connect( mTodoListView, SIGNAL( expanded( QListViewItem * ) ),
00502            SLOT( itemStateChanged( QListViewItem * ) ) );
00503   connect( mTodoListView, SIGNAL( collapsed( QListViewItem * ) ),
00504            SLOT( itemStateChanged( QListViewItem * ) ) );
00505 
00506 #if 0
00507   connect(mTodoListView,SIGNAL(selectionChanged(QListViewItem *)),
00508           SLOT(selectionChanged(QListViewItem *)));
00509   connect(mTodoListView,SIGNAL(clicked(QListViewItem *)),
00510           SLOT(selectionChanged(QListViewItem *)));
00511   connect(mTodoListView,SIGNAL(pressed(QListViewItem *)),
00512           SLOT(selectionChanged(QListViewItem *)));
00513 #endif
00514   connect( mTodoListView, SIGNAL(selectionChanged() ),
00515            SLOT( processSelectionChange() ) );
00516   connect( mQuickAdd, SIGNAL( returnPressed () ),
00517            SLOT( addQuickTodo() ) );
00518 }
00519 
00520 KOTodoView::~KOTodoView()
00521 {
00522   delete mDocPrefs;
00523 }
00524 
00525 void KOTodoView::setCalendar( Calendar *cal )
00526 {
00527   BaseView::setCalendar( cal );
00528   mTodoListView->setCalendar( cal );
00529 }
00530 
00531 void KOTodoView::updateView()
00532 {
00533 //  kdDebug(5850) << "KOTodoView::updateView()" << endl;
00534   int oldPos = mTodoListView->contentsY();
00535   mItemsToDelete.clear();
00536   mTodoListView->clear();
00537 
00538   Todo::List todoList = calendar()->todos();
00539 
00540 /*
00541   kdDebug(5850) << "KOTodoView::updateView(): Todo List:" << endl;
00542   Event *t;
00543   for(t = todoList.first(); t; t = todoList.next()) {
00544     kdDebug(5850) << "  " << t->getSummary() << endl;
00545 
00546     if (t->getRelatedTo()) {
00547       kdDebug(5850) << "      (related to " << t->getRelatedTo()->getSummary() << ")" << endl;
00548     }
00549 
00550     QPtrList<Event> l = t->getRelations();
00551     Event *c;
00552     for(c=l.first();c;c=l.next()) {
00553       kdDebug(5850) << "    - relation: " << c->getSummary() << endl;
00554     }
00555   }
00556 */
00557 
00558   // Put for each Event a KOTodoViewItem in the list view. Don't rely on a
00559   // specific order of events. That means that we have to generate parent items
00560   // recursively for proper hierarchical display of Todos.
00561   mTodoMap.clear();
00562   Todo::List::ConstIterator it;
00563   for( it = todoList.begin(); it != todoList.end(); ++it ) {
00564     if ( !mTodoMap.contains( *it ) ) {
00565       insertTodoItem( *it );
00566     }
00567   }
00568 
00569   // Restore opened/closed state
00570   mTodoListView->blockSignals( true );
00571   if( mDocPrefs ) restoreItemState( mTodoListView->firstChild() );
00572   mTodoListView->blockSignals( false );
00573 
00574   mTodoListView->setContentsPos( 0, oldPos );
00575 
00576   processSelectionChange();
00577 }
00578 
00579 void KOTodoView::restoreItemState( QListViewItem *item )
00580 {
00581   while( item ) {
00582     KOTodoViewItem *todoItem = (KOTodoViewItem *)item;
00583     todoItem->setOpen( mDocPrefs->readBoolEntry( todoItem->todo()->uid() ) );
00584     if( item->childCount() > 0 ) restoreItemState( item->firstChild() );
00585     item = item->nextSibling();
00586   }
00587 }
00588 
00589 
00590 QMap<Todo *,KOTodoViewItem *>::ConstIterator
00591   KOTodoView::insertTodoItem(Todo *todo)
00592 {
00593 //  kdDebug(5850) << "KOTodoView::insertTodoItem(): " << todo->getSummary() << endl;
00594   Incidence *incidence = todo->relatedTo();
00595   if (incidence && incidence->type() == "Todo") {
00596     // Use dynamic_cast, because in the future the related item might also be an event
00597     Todo *relatedTodo = dynamic_cast<Todo *>(incidence);
00598 
00599     // just make sure we know we have this item already to avoid endless recursion (Bug 101696)
00600     mTodoMap.insert(todo,0);
00601 
00602 //    kdDebug(5850) << "  has Related" << endl;
00603     QMap<Todo *,KOTodoViewItem *>::ConstIterator itemIterator;
00604     itemIterator = mTodoMap.find(relatedTodo);
00605     if (itemIterator == mTodoMap.end()) {
00606 //      kdDebug(5850) << "    related not yet in list" << endl;
00607       itemIterator = insertTodoItem (relatedTodo);
00608     }
00609     // isn't this pretty stupid? We give one Todo  to the KOTodoViewItem
00610     // and one into the map. Sure finding is more easy but why? -zecke
00611     KOTodoViewItem *todoItem;
00612 
00613     // in case we found a related parent, which has no KOTodoViewItem yet, this must
00614     // be the case where 2 items refer to each other, therefore simply create item as root item
00615     if ( *itemIterator == 0 ) {
00616       todo->setRelatedTo(0);  // break the recursion, else we will have troubles later
00617       todoItem = new KOTodoViewItem(mTodoListView,todo,this);
00618     }
00619     else
00620       todoItem = new KOTodoViewItem(*itemIterator,todo,this);
00621 
00622     return mTodoMap.insert(todo,todoItem);
00623   } else {
00624 //    kdDebug(5850) << "  no Related" << endl;
00625       // see above -zecke
00626     KOTodoViewItem *todoItem = new KOTodoViewItem(mTodoListView,todo,this);
00627     return mTodoMap.insert(todo,todoItem);
00628   }
00629 }
00630 
00631 void KOTodoView::removeTodoItems()
00632 {
00633   KOTodoViewItem *item;
00634   for ( item = mItemsToDelete.first(); item; item = mItemsToDelete.next() ) {
00635     Todo *todo = item->todo();
00636     if ( todo && mTodoMap.contains( todo ) ) {
00637       mTodoMap.remove( todo );
00638     }
00639     delete item;
00640   }
00641   mItemsToDelete.clear();
00642 }
00643 
00644 
00645 bool KOTodoView::scheduleRemoveTodoItem( KOTodoViewItem *todoItem )
00646 {
00647   if ( todoItem ) {
00648     mItemsToDelete.append( todoItem );
00649     QTimer::singleShot( 0, this, SLOT( removeTodoItems() ) );
00650     return true;
00651   } else
00652     return false;
00653 }
00654 
00655 void KOTodoView::updateConfig()
00656 {
00657   mTodoListView->repaintContents();
00658 }
00659 
00660 Incidence::List KOTodoView::selectedIncidences()
00661 {
00662   Incidence::List selected;
00663 
00664   KOTodoViewItem *item = (KOTodoViewItem *)(mTodoListView->selectedItem());
00665 //  if (!item) item = mActiveItem;
00666   if (item) selected.append(item->todo());
00667 
00668   return selected;
00669 }
00670 
00671 Todo::List KOTodoView::selectedTodos()
00672 {
00673   Todo::List selected;
00674 
00675   KOTodoViewItem *item = (KOTodoViewItem *)(mTodoListView->selectedItem());
00676 //  if (!item) item = mActiveItem;
00677   if (item) selected.append(item->todo());
00678 
00679   return selected;
00680 }
00681 
00682 void KOTodoView::changeIncidenceDisplay(Incidence *incidence, int action)
00683 {
00684   // The todo view only displays todos, so exit on all other incidences
00685   if ( incidence->type() != "Todo" )
00686     return;
00687   CalFilter *filter = calendar()->filter();
00688   bool isFiltered = filter && !filter->filterIncidence( incidence );
00689   Todo *todo = static_cast<Todo *>(incidence);
00690   if ( todo ) {
00691     KOTodoViewItem *todoItem = 0;
00692     if ( mTodoMap.contains( todo ) ) {
00693       todoItem = mTodoMap[todo];
00694     }
00695     switch ( action ) {
00696       case KOGlobals::INCIDENCEADDED:
00697       case KOGlobals::INCIDENCEEDITED:
00698         // If it's already there, edit it, otherwise just add
00699         if ( todoItem ) {
00700           if ( isFiltered ) {
00701             scheduleRemoveTodoItem( todoItem );
00702           } else {
00703             // correctly update changes in relations
00704             Todo*parent = dynamic_cast<Todo*>( todo->relatedTo() );
00705             KOTodoViewItem*parentItem = 0;
00706             if ( parent && mTodoMap.contains(parent) ) {
00707               parentItem = mTodoMap[ parent ];
00708             }
00709             if ( todoItem->parent() != parentItem ) {
00710               // The relations changed
00711               if ( parentItem ) {
00712                 parentItem->insertItem( todoItem );
00713               } else {
00714                 mTodoListView->insertItem( todoItem );
00715               }
00716             }
00717             todoItem->construct();
00718           }
00719         } else {
00720           if ( !isFiltered ) {
00721             insertTodoItem( todo );
00722           }
00723         }
00724         mTodoListView->sort();
00725         break;
00726       case KOGlobals::INCIDENCEDELETED:
00727         if ( todoItem ) {
00728           scheduleRemoveTodoItem( todoItem );
00729         }
00730         break;
00731       default:
00732         QTimer::singleShot( 0, this, SLOT( updateView() ) );
00733     }
00734   } else {
00735     // use a QTimer here, because when marking todos finished using
00736     // the checkbox, this slot gets called, but we cannot update the views
00737     // because we're still inside KOTodoViewItem::stateChange
00738     QTimer::singleShot(0,this,SLOT(updateView()));
00739   }
00740 }
00741 
00742 void KOTodoView::showDates(const QDate &, const QDate &)
00743 {
00744 }
00745 
00746 void KOTodoView::showIncidences( const Incidence::List &, const QDate & )
00747 {
00748   kdDebug(5850) << "KOTodoView::showIncidences( const Incidence::List & ): not yet implemented" << endl;
00749 }
00750 
00751 CalPrinterBase::PrintType KOTodoView::printType()
00752 {
00753   if ( mTodoListView->selectedItem() ) {
00754     return CalPrinterBase::Incidence;
00755   } else {
00756     return CalPrinterBase::Todolist;
00757   }
00758 }
00759 
00760 void KOTodoView::editItem( QListViewItem *item )
00761 {
00762   if (item)
00763     emit editIncidenceSignal( static_cast<KOTodoViewItem *>( item )->todo() );
00764 }
00765 
00766 void KOTodoView::editItem( QListViewItem *item, const QPoint &, int )
00767 {
00768   editItem( item );
00769 }
00770 
00771 void KOTodoView::showItem( QListViewItem *item )
00772 {
00773   if (item)
00774     emit showIncidenceSignal( static_cast<KOTodoViewItem *>( item )->todo() );
00775 }
00776 
00777 void KOTodoView::showItem( QListViewItem *item, const QPoint &, int )
00778 {
00779   showItem( item );
00780 }
00781 
00782 void KOTodoView::popupMenu( QListViewItem *item, const QPoint &, int column )
00783 {
00784   mActiveItem = static_cast<KOTodoViewItem *>( item );
00785   if ( mActiveItem && mActiveItem->todo() &&
00786        !mActiveItem->todo()->isReadOnly() ) {
00787     bool editable = !mActiveItem->todo()->isReadOnly();
00788     mItemPopupMenu->setItemEnabled( ePopupEdit, editable );
00789     mItemPopupMenu->setItemEnabled( ePopupDelete, editable );
00790     mItemPopupMenu->setItemEnabled( ePopupMoveTo, editable );
00791     mItemPopupMenu->setItemEnabled( ePopupCopyTo, editable );
00792     mItemPopupMenu->setItemEnabled( ePopupUnSubTodo, editable );
00793     mItemPopupMenu->setItemEnabled( ePopupUnAllSubTodo, editable );
00794 
00795     if ( editable ) {
00796       QDate date = mActiveItem->todo()->dtDue().date();
00797       if ( mActiveItem->todo()->hasDueDate () ) {
00798         mMovePopupMenu->datePicker()->setDate( date );
00799       } else {
00800         mMovePopupMenu->datePicker()->setDate( QDate::currentDate() );
00801       }
00802       switch ( column ) {
00803         case ePriorityColumn:
00804           mPriorityPopupMenu->popup( QCursor::pos() );
00805           break;
00806         case ePercentColumn: {
00807           mPercentageCompletedPopupMenu->popup( QCursor::pos() );
00808           break;
00809         }
00810         case eDueDateColumn:
00811           mMovePopupMenu->popup( QCursor::pos() );
00812           break;
00813         case eCategoriesColumn:
00814           getCategoryPopupMenu( mActiveItem )->popup( QCursor::pos() );
00815           break;
00816         default:
00817           mCopyPopupMenu->datePicker()->setDate( date );
00818           mCopyPopupMenu->datePicker()->setDate( QDate::currentDate() );
00819           mItemPopupMenu->setItemEnabled( ePopupUnSubTodo,
00820                                           mActiveItem->todo()->relatedTo() );
00821           mItemPopupMenu->setItemEnabled( ePopupUnAllSubTodo,
00822                                           !mActiveItem->todo()->relations().isEmpty() );
00823           mItemPopupMenu->popup( QCursor::pos() );
00824       }
00825     } else {
00826       mItemPopupMenu->popup( QCursor::pos() );
00827     }
00828   } else mPopupMenu->popup( QCursor::pos() );
00829 }
00830 
00831 void KOTodoView::newTodo()
00832 {
00833   kdDebug() << k_funcinfo << endl;
00834   emit newTodoSignal( QDate::currentDate().addDays(7) );
00835 }
00836 
00837 void KOTodoView::newSubTodo()
00838 {
00839   if (mActiveItem) {
00840     emit newSubTodoSignal(mActiveItem->todo());
00841   }
00842 }
00843 
00844 void KOTodoView::editTodo()
00845 {
00846   editItem( mActiveItem );
00847 }
00848 
00849 void KOTodoView::showTodo()
00850 {
00851   showItem( mActiveItem );
00852 }
00853 
00854 void KOTodoView::printTodo()
00855 {
00856 #ifndef KORG_NOPRINTER
00857   KOCoreHelper helper;
00858   CalPrinter printer( this, BaseView::calendar(), &helper );
00859   connect( this, SIGNAL(configChanged()), &printer, SLOT(updateConfig()) );
00860 
00861   Incidence::List selectedIncidences;
00862   selectedIncidences.append( mActiveItem->todo() );
00863 
00864   printer.print( KOrg::CalPrinterBase::Incidence,
00865                  QDate(), QDate(), selectedIncidences );
00866 #endif
00867 }
00868 
00869 void KOTodoView::deleteTodo()
00870 {
00871   if (mActiveItem) {
00872     emit deleteIncidenceSignal( mActiveItem->todo() );
00873   }
00874 }
00875 
00876 void KOTodoView::setNewPriority(int index)
00877 {
00878   if ( !mActiveItem || !mChanger ) return;
00879   Todo *todo = mActiveItem->todo();
00880   if ( !todo->isReadOnly () &&
00881        mChanger->beginChange( todo ) ) {
00882     Todo *oldTodo = todo->clone();
00883     todo->setPriority(mPriority[index]);
00884     mActiveItem->construct();
00885 
00886     mChanger->changeIncidence( oldTodo, todo, KOGlobals::PRIORITY_MODIFIED );
00887     mChanger->endChange( todo );
00888     delete oldTodo;
00889   }
00890 }
00891 
00892 void KOTodoView::setNewPercentage( KOTodoViewItem *item, int percentage )
00893 {
00894   kdDebug(5850) << "KOTodoView::setNewPercentage( " << percentage << "), item = " << item << endl;
00895   if ( !item || !mChanger  ) return;
00896   Todo *todo = item->todo();
00897   if ( !todo ) return;
00898 
00899   if ( !todo->isReadOnly () && mChanger->beginChange( todo ) ) {
00900     Todo *oldTodo = todo->clone();
00901 
00902 /*  Old code to make sub-items's percentage related to this one's:
00903     QListViewItem *myChild = firstChild();
00904     KOTodoViewItem *item;
00905     while( myChild ) {
00906       item = static_cast<KOTodoViewItem*>(myChild);
00907       item->stateChange(state);
00908       myChild = myChild->nextSibling();
00909     }*/
00910     if ( percentage == 100 ) {
00911       todo->setCompleted( QDateTime::currentDateTime() );
00912       // If the todo does recur, it doesn't get set as completed. However, the
00913       // item is still checked. Uncheck it again.
00914       if ( !todo->isCompleted() ) item->setState( QCheckListItem::Off );
00915       else todo->setPercentComplete( percentage );
00916     } else {
00917       todo->setCompleted( false );
00918       todo->setPercentComplete( percentage );
00919     }
00920     item->construct();
00921     if ( todo->doesRecur() && percentage == 100 )
00922       mChanger->changeIncidence( oldTodo, todo, KOGlobals::COMPLETION_MODIFIED_WITH_RECURRENCE );
00923     else
00924       mChanger->changeIncidence( oldTodo, todo, KOGlobals::COMPLETION_MODIFIED );
00925     mChanger->endChange( todo );
00926     delete oldTodo;
00927   } else {
00928     item->construct();
00929     kdDebug(5850) << "No active item, active item is read-only, or locking failed" << endl;
00930   }
00931 }
00932 
00933 void KOTodoView::setNewPercentage( int index )
00934 {
00935   setNewPercentage( mActiveItem, mPercentage[index] );
00936 }
00937 
00938 void KOTodoView::setNewDate( QDate date )
00939 {
00940   if ( !mActiveItem || !mChanger ) return;
00941   Todo *todo = mActiveItem->todo();
00942   if ( !todo ) return;
00943 
00944   if ( !todo->isReadOnly() && mChanger->beginChange( todo ) ) {
00945     Todo *oldTodo = todo->clone();
00946 
00947     QDateTime dt;
00948     dt.setDate( date );
00949 
00950     if ( !todo->doesFloat() )
00951       dt.setTime( todo->dtDue().time() );
00952 
00953     if ( date.isNull() )
00954       todo->setHasDueDate( false );
00955     else if ( !todo->hasDueDate() )
00956       todo->setHasDueDate( true );
00957     todo->setDtDue( dt );
00958 
00959     mActiveItem->construct();
00960     mChanger->changeIncidence( oldTodo, todo, KOGlobals::COMPLETION_MODIFIED );
00961     mChanger->endChange( todo );
00962     delete oldTodo;
00963   } else {
00964     kdDebug(5850) << "No active item, active item is read-only, or locking failed" << endl;
00965   }
00966 }
00967 
00968 void KOTodoView::copyTodoToDate( QDate date )
00969 {
00970   QDateTime dt( date );
00971 
00972   if ( mActiveItem && mChanger ) {
00973     Todo *newTodo = mActiveItem->todo()->clone();
00974     newTodo->recreate();
00975 
00976    newTodo->setHasDueDate( !date.isNull() );
00977    newTodo->setDtDue( dt );
00978    newTodo->setPercentComplete( 0 );
00979 
00980    // avoid forking
00981    if ( newTodo->doesRecur() )
00982      newTodo->recurrence()->unsetRecurs();
00983 
00984    mChanger->addIncidence( newTodo, this );
00985  }
00986 }
00987 
00988 QPopupMenu *KOTodoView::getCategoryPopupMenu( KOTodoViewItem *todoItem )
00989 {
00990   QPopupMenu *tempMenu = new QPopupMenu( this );
00991   QStringList checkedCategories = todoItem->todo()->categories();
00992 
00993   tempMenu->setCheckable( true );
00994   QStringList::Iterator it;
00995   for ( it = KOPrefs::instance()->mCustomCategories.begin();
00996         it != KOPrefs::instance()->mCustomCategories.end();
00997         ++it ) {
00998     int index = tempMenu->insertItem( *it );
00999     mCategory[ index ] = *it;
01000     if ( checkedCategories.find( *it ) != checkedCategories.end() )
01001       tempMenu->setItemChecked( index, true );
01002   }
01003 
01004   connect ( tempMenu, SIGNAL( activated( int ) ),
01005             SLOT( changedCategories( int ) ) );
01006   return tempMenu;
01007 }
01008 
01009 void KOTodoView::changedCategories(int index)
01010 {
01011   if ( !mActiveItem || !mChanger ) return;
01012   Todo *todo = mActiveItem->todo();
01013   if ( !todo ) return;
01014 
01015   if ( !todo->isReadOnly() && mChanger->beginChange( todo ) ) {
01016     Todo *oldTodo = todo->clone();
01017 
01018     QStringList categories = todo->categories ();
01019     if ( categories.find( mCategory[index] ) != categories.end() )
01020       categories.remove( mCategory[index] );
01021     else
01022       categories.insert( categories.end(), mCategory[index] );
01023     categories.sort();
01024     todo->setCategories( categories );
01025     mActiveItem->construct();
01026     mChanger->changeIncidence( oldTodo, todo, KOGlobals::CATEGORY_MODIFIED );
01027     mChanger->endChange( todo );
01028     delete oldTodo;
01029   } else {
01030     kdDebug(5850) << "No active item, active item is read-only, or locking failed" << endl;
01031   }
01032 }
01033 
01034 void KOTodoView::setDocumentId( const QString &id )
01035 {
01036   kdDebug(5850) << "KOTodoView::setDocumentId()" << endl;
01037 
01038   mDocPrefs->setDoc( id );
01039 }
01040 
01041 void KOTodoView::itemStateChanged( QListViewItem *item )
01042 {
01043   if (!item) return;
01044 
01045   KOTodoViewItem *todoItem = (KOTodoViewItem *)item;
01046 
01047 //  kdDebug(5850) << "KOTodoView::itemStateChanged(): " << todoItem->todo()->summary() << endl;
01048 
01049   if( mDocPrefs ) mDocPrefs->writeEntry( todoItem->todo()->uid(), todoItem->isOpen() );
01050 }
01051 
01052 void KOTodoView::setNewPercentageDelayed( KOTodoViewItem *item, int percentage )
01053 {
01054   mPercentChangedMap.append( qMakePair( item, percentage ) );
01055 
01056   QTimer::singleShot( 0, this, SLOT( processDelayedNewPercentage() ) );
01057 }
01058 
01059 void KOTodoView::processDelayedNewPercentage()
01060 {
01061   QValueList< QPair< KOTodoViewItem *, int> >::Iterator it;
01062   for ( it = mPercentChangedMap.begin(); it != mPercentChangedMap.end(); ++it )
01063     setNewPercentage( (*it).first, (*it).second );
01064 
01065   mPercentChangedMap.clear();
01066 }
01067 
01068 void KOTodoView::saveLayout(KConfig *config, const QString &group) const
01069 {
01070   mTodoListView->saveLayout(config,group);
01071 }
01072 
01073 void KOTodoView::restoreLayout(KConfig *config, const QString &group)
01074 {
01075   mTodoListView->restoreLayout(config,group);
01076 }
01077 
01078 void KOTodoView::processSelectionChange()
01079 {
01080 //  kdDebug(5850) << "KOTodoView::processSelectionChange()" << endl;
01081 
01082   KOTodoViewItem *item =
01083     static_cast<KOTodoViewItem *>( mTodoListView->selectedItem() );
01084 
01085   if ( !item ) {
01086     emit incidenceSelected( 0, QDate() );
01087   } else {
01088     if ( selectedDates().isEmpty() ) {
01089       emit incidenceSelected( item->todo(), QDate() );
01090     } else {
01091       emit incidenceSelected( item->todo(), selectedDates().first() );
01092     }
01093   }
01094 }
01095 
01096 void KOTodoView::clearSelection()
01097 {
01098   mTodoListView->selectAll( false );
01099 }
01100 
01101 void KOTodoView::purgeCompleted()
01102 {
01103   emit purgeCompletedSignal();
01104 }
01105 
01106 void KOTodoView::addQuickTodo()
01107 {
01108   if ( ! mQuickAdd->text().stripWhiteSpace().isEmpty() ) {
01109     Todo *todo = new Todo();
01110     todo->setSummary( mQuickAdd->text() );
01111     todo->setOrganizer( Person( KOPrefs::instance()->fullName(),
01112                         KOPrefs::instance()->email() ) );
01113     if ( !mChanger->addIncidence( todo, this ) ) {
01114       KODialogManager::errorSaveIncidence( this, todo );
01115       delete todo;
01116       return;
01117     }
01118     mQuickAdd->setText( QString::null );
01119   }
01120 }
01121 
01122 void KOTodoView::setIncidenceChanger( IncidenceChangerBase *changer )
01123 {
01124   mChanger = changer;
01125   mTodoListView->setIncidenceChanger( changer );
01126 }
KDE Home | KDE Accessibility Home | Description of Access Keys