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