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