karm Library API Documentation

karmstorage.cpp

00001 /* 00002 * This file only: 00003 * Copyright (C) 2003, 2004 Mark Bucciarelli <mark@hubcapconsulting.com> 00004 * 00005 * This program is free software; you can redistribute it and/or modify 00006 * it under the terms of the GNU General Public License as published by 00007 * the Free Software Foundation; either version 2 of the License, or 00008 * (at your option) any later version. 00009 * 00010 * This program is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 * GNU General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU General Public License along 00016 * with this program; if not, write to the 00017 * Free Software Foundation, Inc. 00018 * 59 Temple Place - Suite 330 00019 * Boston, MA 02111-1307 USA. 00020 * 00021 */ 00022 00023 #include <sys/types.h> 00024 #include <sys/stat.h> 00025 #include <fcntl.h> 00026 #include <unistd.h> 00027 00028 #include <cassert> 00029 00030 #include <qfile.h> 00031 #include <qsize.h> 00032 #include <qdict.h> 00033 #include <qdatetime.h> 00034 #include <qstringlist.h> 00035 00036 #include "incidence.h" 00037 #include "kapplication.h" // kapp 00038 #include <kdebug.h> 00039 #include <kemailsettings.h> 00040 #include <klocale.h> // i18n 00041 #include <kmessagebox.h> 00042 #include <kprogress.h> 00043 #include <resourcecalendar.h> 00044 #include <resourcelocal.h> 00045 #include <kpimprefs.h> 00046 #include <taskview.h> 00047 #include <timekard.h> 00048 #include <karmutility.h> 00049 00050 //#include <calendarlocal.h> 00051 //#include <journal.h> 00052 //#include <event.h> 00053 //#include <todo.h> 00054 00055 #include "karmstorage.h" 00056 #include "preferences.h" 00057 #include "task.h" 00058 #include "reportcriteria.h" 00059 00060 00061 KarmStorage *KarmStorage::_instance = 0; 00062 00063 KarmStorage *KarmStorage::instance() 00064 { 00065 if (_instance == 0) _instance = new KarmStorage(); 00066 return _instance; 00067 } 00068 00069 KarmStorage::KarmStorage() 00070 { 00071 _calendar = 0; 00072 _lock = 0; 00073 } 00074 00075 QString KarmStorage::load (TaskView* view, const Preferences* preferences) 00076 { 00077 // When I tried raising an exception from this method, the compiler 00078 // complained that exceptions are not allowed. Not sure how apps 00079 // typically handle error conditions in KDE, but I'll return the error 00080 // as a string (empty is no error). -- Mark, Aug 8, 2003 00081 00082 // Use KDE_CXXFLAGS=$(USE_EXCEPTIONS) in Makefile.am if you want to use 00083 // exceptions (David Faure) 00084 00085 QString err; 00086 KEMailSettings settings; 00087 00088 // If same file, don't reload 00089 if ( preferences->iCalFile() == _icalfile ) return err; 00090 00091 00092 // If file doesn't exist, create a blank one to avoid ResourceLocal load 00093 // error. We make it user and group read/write, others read. This is 00094 // masked by the users umask. (See man creat) 00095 int handle; 00096 handle = open ( 00097 QFile::encodeName( preferences->iCalFile() ), 00098 O_CREAT|O_EXCL|O_WRONLY, 00099 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH 00100 ); 00101 if (handle != -1) close(handle); 00102 00103 if ( _calendar) closeStorage(view); 00104 else _calendar = new KCal::CalendarResources(); 00105 00106 // Create local file resource and add to resources 00107 _icalfile = preferences->iCalFile(); 00108 KCal::ResourceCalendar *l = new KCal::ResourceLocal( _icalfile ); 00109 l->setTimeZoneId( KPimPrefs::timezone() ); 00110 l->setResourceName( QString::fromLatin1("KArm") ); 00111 l->open(); 00112 l->load(); 00113 00114 KCal::CalendarResourceManager *m = _calendar->resourceManager(); 00115 m->add(l); 00116 m->setStandardResource(l); 00117 00118 // Claim ownership of iCalendar file if no one else has. 00119 QString email = _calendar->getEmail(); 00120 QString owner = _calendar->getOwner(); 00121 if ( email.isEmpty() && owner.isEmpty() ) 00122 { 00123 _calendar->setEmail( settings.getSetting( KEMailSettings::EmailAddress ) ); 00124 _calendar->setOwner( settings.getSetting( KEMailSettings::RealName ) ); 00125 } 00126 00127 // Get lock. If no lock, allow read-only access to data. 00128 // 00129 // Note: An improved implementation would be to behave like KOrganizer, and 00130 // allow updates, and only request lock when trying to save data. 00131 _lock = _calendar->requestSaveTicket(m->standardResource()); 00132 if ( !_lock ) 00133 { 00134 KMessageBox::information(0, 00135 i18n("Another program is currently using this file. " 00136 "Access will be read-only.")); 00137 } 00138 00139 // Build task view from iCal data 00140 if (!err) 00141 { 00142 KCal::Todo::List todoList; 00143 KCal::Todo::List::ConstIterator todo; 00144 QDict< Task > map; 00145 00146 // Build dictionary to look up Task object from Todo uid. Each task is a 00147 // QListViewItem, and is initially added with the view as the parent. 00148 todoList = _calendar->rawTodos(); 00149 kdDebug(5970) << "KarmStorage::load " 00150 << "rawTodo count (includes completed todos) =" 00151 << todoList.count() << endl; 00152 for( todo = todoList.begin(); todo != todoList.end(); ++todo ) 00153 { 00154 // Initially, if a task was complete, it was removed from the view. 00155 // However, this increased the complexity of reporting on task history. 00156 // 00157 // For example, if a task is complete yet has time logged to it during 00158 // the date range specified on the history report, we have to figure out 00159 // how that task fits into the task hierarchy. Currently, this 00160 // structure is held in memory by the structure in the list view. 00161 // 00162 // I considered creating a second tree that held the full structure of 00163 // all complete and incomplete tasks. But this seemed to much of a 00164 // change with an impending beta release and a full todo list. 00165 // 00166 // Hence this "solution". Include completed tasks, but mark them as 00167 // inactive in the view. 00168 // 00169 //if ((*todo)->isCompleted()) continue; 00170 00171 Task* task = new Task(*todo, view); 00172 map.insert( (*todo)->uid(), task ); 00173 view->setRootIsDecorated(true); 00174 if ((*todo)->isCompleted()) 00175 { 00176 task->setEnabled(false); 00177 task->setOpen(false); 00178 } 00179 else 00180 task->setOpen(true); 00181 } 00182 00183 // Load each task under it's parent task. 00184 for( todo = todoList.begin(); todo != todoList.end(); ++todo ) 00185 { 00186 Task* task = map.find( (*todo)->uid() ); 00187 00188 // No relatedTo incident just means this is a top-level task. 00189 if ( (*todo)->relatedTo() ) 00190 { 00191 Task* newParent = map.find( (*todo)->relatedToUid() ); 00192 00193 // Complete the loading but return a message 00194 if ( !newParent ) 00195 err = i18n("Error loading \"%1\": could not find parent (uid=%2)") 00196 .arg(task->name()) 00197 .arg((*todo)->relatedToUid()); 00198 00199 if (!err) task->move( newParent); 00200 } 00201 } 00202 00203 kdDebug(5970) << "KarmStorage::load - loaded " << view->count() 00204 << " tasks from " << _icalfile << endl; 00205 } 00206 00207 return err; 00208 } 00209 00210 void KarmStorage::closeStorage(TaskView* view) 00211 { 00212 if ( _calendar ) 00213 { 00214 if ( _lock ) _calendar->releaseSaveTicket( _lock ); 00215 00216 _calendar->close(); 00217 00218 KCal::CalendarResourceManager *m = _calendar->resourceManager(); 00219 m->remove( m->standardResource() ); 00220 00221 view->clear(); 00222 } 00223 } 00224 00225 void KarmStorage::save(TaskView* taskview) 00226 { 00227 if ( !_lock ) return; 00228 00229 QPtrStack< KCal::Todo > parents; 00230 00231 for (Task* task=taskview->first_child(); task; task = task->nextSibling()) 00232 { 00233 writeTaskAsTodo(task, 1, parents ); 00234 } 00235 00236 _calendar->save(_lock); 00237 _lock = _calendar->requestSaveTicket 00238 ( _calendar->resourceManager()->standardResource() ); 00239 00240 kdDebug(5970) 00241 << "KarmStorage::save : wrote " 00242 << taskview->count() << " tasks to " << _icalfile << endl; 00243 } 00244 00245 void KarmStorage::writeTaskAsTodo(Task* task, const int level, 00246 QPtrStack< KCal::Todo >& parents ) 00247 { 00248 KCal::Todo* todo; 00249 00250 todo = _calendar->todo(task->uid()); 00251 task->asTodo(todo); 00252 if ( !parents.isEmpty() ) todo->setRelatedTo( parents.top() ); 00253 parents.push( todo ); 00254 00255 for (Task* nextTask = task->firstChild(); nextTask; 00256 nextTask = nextTask->nextSibling() ) 00257 { 00258 writeTaskAsTodo(nextTask, level+1, parents ); 00259 } 00260 00261 parents.pop(); 00262 } 00263 00264 bool KarmStorage::isEmpty() 00265 { 00266 KCal::Todo::List todoList; 00267 00268 todoList = _calendar->rawTodos(); 00269 return todoList.empty(); 00270 } 00271 00272 bool KarmStorage::isNewStorage(const Preferences* preferences) const 00273 { 00274 if ( !_icalfile.isNull() ) return preferences->iCalFile() != _icalfile; 00275 else return false; 00276 } 00277 00278 //---------------------------------------------------------------------------- 00279 // Routines that handle legacy flat file format. 00280 // These only stored total and session times. 00281 // 00282 00283 QString KarmStorage::loadFromFlatFile(TaskView* taskview, 00284 const QString& filename) 00285 { 00286 QString err; 00287 00288 kdDebug(5970) 00289 << "KarmStorage::loadFromFlatFile: " << filename << endl; 00290 00291 QFile f(filename); 00292 if( !f.exists() ) 00293 err = i18n("File \"%1\" not found.").arg(filename); 00294 00295 if (!err) 00296 { 00297 if( !f.open( IO_ReadOnly ) ) 00298 err = i18n("Could not open \"%1\".").arg(filename); 00299 } 00300 00301 if (!err) 00302 { 00303 00304 QString line; 00305 00306 QPtrStack<Task> stack; 00307 Task *task; 00308 00309 QTextStream stream(&f); 00310 00311 while( !stream.atEnd() ) { 00312 // lukas: this breaks for non-latin1 chars!!! 00313 // if ( file.readLine( line, T_LINESIZE ) == 0 ) 00314 // break; 00315 00316 line = stream.readLine(); 00317 kdDebug(5970) << "DEBUG: line: " << line << "\n"; 00318 00319 if (line.isNull()) 00320 break; 00321 00322 long minutes; 00323 int level; 00324 QString name; 00325 DesktopList desktopList; 00326 if (!parseLine(line, &minutes, &name, &level, &desktopList)) 00327 continue; 00328 00329 unsigned int stackLevel = stack.count(); 00330 for (unsigned int i = level; i<=stackLevel ; i++) { 00331 stack.pop(); 00332 } 00333 00334 if (level == 1) { 00335 kdDebug(5970) << "KarmStorage::loadFromFlatFile - toplevel task: " 00336 << name << " min: " << minutes << "\n"; 00337 task = new Task(name, minutes, 0, desktopList, taskview); 00338 task->setUid(addTask(task, 0)); 00339 } 00340 else { 00341 Task *parent = stack.top(); 00342 kdDebug(5970) << "KarmStorage::loadFromFlatFile - task: " << name 00343 << " min: " << minutes << " parent" << parent->name() << "\n"; 00344 task = new Task(name, minutes, 0, desktopList, parent); 00345 00346 task->setUid(addTask(task, parent)); 00347 00348 // Legacy File Format (!): 00349 parent->changeTimes(0, -minutes); 00350 taskview->setRootIsDecorated(true); 00351 parent->setOpen(true); 00352 } 00353 if (!task->uid().isNull()) 00354 stack.push(task); 00355 else 00356 delete task; 00357 } 00358 00359 f.close(); 00360 00361 } 00362 00363 return err; 00364 } 00365 00366 QString KarmStorage::loadFromFlatFileCumulative(TaskView* taskview, 00367 const QString& filename) 00368 { 00369 QString err = loadFromFlatFile(taskview, filename); 00370 if (!err) 00371 { 00372 for (Task* task = taskview->first_child(); task; 00373 task = task->nextSibling()) 00374 { 00375 adjustFromLegacyFileFormat(task); 00376 } 00377 } 00378 return err; 00379 } 00380 00381 bool KarmStorage::parseLine(QString line, long *time, QString *name, 00382 int *level, DesktopList* desktopList) 00383 { 00384 if (line.find('#') == 0) { 00385 // A comment line 00386 return false; 00387 } 00388 00389 int index = line.find('\t'); 00390 if (index == -1) { 00391 // This doesn't seem like a valid record 00392 return false; 00393 } 00394 00395 QString levelStr = line.left(index); 00396 QString rest = line.remove(0,index+1); 00397 00398 index = rest.find('\t'); 00399 if (index == -1) { 00400 // This doesn't seem like a valid record 00401 return false; 00402 } 00403 00404 QString timeStr = rest.left(index); 00405 rest = rest.remove(0,index+1); 00406 00407 bool ok; 00408 00409 index = rest.find('\t'); // check for optional desktops string 00410 if (index >= 0) { 00411 *name = rest.left(index); 00412 QString deskLine = rest.remove(0,index+1); 00413 00414 // now transform the ds string (e.g. "3", or "1,4,5") into 00415 // an DesktopList 00416 QString ds; 00417 int d; 00418 int commaIdx = deskLine.find(','); 00419 while (commaIdx >= 0) { 00420 ds = deskLine.left(commaIdx); 00421 d = ds.toInt(&ok); 00422 if (!ok) 00423 return false; 00424 00425 desktopList->push_back(d); 00426 deskLine.remove(0,commaIdx+1); 00427 commaIdx = deskLine.find(','); 00428 } 00429 00430 d = deskLine.toInt(&ok); 00431 00432 if (!ok) 00433 return false; 00434 00435 desktopList->push_back(d); 00436 } 00437 else { 00438 *name = rest.remove(0,index+1); 00439 } 00440 00441 *time = timeStr.toLong(&ok); 00442 00443 if (!ok) { 00444 // the time field was not a number 00445 return false; 00446 } 00447 *level = levelStr.toInt(&ok); 00448 if (!ok) { 00449 // the time field was not a number 00450 return false; 00451 } 00452 00453 return true; 00454 } 00455 00456 void KarmStorage::adjustFromLegacyFileFormat(Task* task) 00457 { 00458 // unless the parent is the listView 00459 if ( task->parent() ) 00460 task->parent()->changeTimes(-task->sessionTime(), -task->time()); 00461 00462 // traverse depth first - 00463 // as soon as we're in a leaf, we'll substract it's time from the parent 00464 // then, while descending back we'll do the same for each node untill 00465 // we reach the root 00466 for ( Task* subtask = task->firstChild(); subtask; 00467 subtask = subtask->nextSibling() ) 00468 adjustFromLegacyFileFormat(subtask); 00469 } 00470 00471 //---------------------------------------------------------------------------- 00472 // Routines that handle Comma-Separated Values export file format. 00473 // 00474 QString KarmStorage::exportcsvFile( TaskView *taskview, 00475 const ReportCriteria &rc ) 00476 { 00477 QString delim = rc.delimiter; 00478 QString dquote = rc.quote; 00479 QString double_dquote = dquote + dquote; 00480 bool to_quote = true; 00481 00482 QString err; 00483 Task* task; 00484 int maxdepth=0; 00485 00486 kdDebug(5970) 00487 << "KarmStorage::exportcsvFile: " << rc.url << endl; 00488 00489 QFile f( rc.url ); 00490 if( !f.open( IO_WriteOnly ) ) { 00491 err = i18n("Could not open \"%1\".").arg( rc.url ); 00492 } 00493 00494 if (!err) 00495 { 00496 QString title = i18n("Export Progress"); 00497 KProgressDialog dialog( taskview, 0, title ); 00498 dialog.setAutoClose( true ); 00499 dialog.setAllowCancel( true ); 00500 dialog.progressBar()->setTotalSteps( 2 * taskview->count() ); 00501 00502 // The default dialog was not displaying all the text in the title bar. 00503 int width = taskview->fontMetrics().width(title) * 3; 00504 QSize dialogsize; 00505 dialogsize.setWidth(width); 00506 dialog.setInitialSize( dialogsize, true ); 00507 00508 if ( taskview->count() > 1 ) dialog.show(); 00509 00510 QTextStream stream(&f); 00511 00512 // Find max task depth 00513 int tasknr = 0; 00514 while ( tasknr < taskview->count() && !dialog.wasCancelled() ) 00515 { 00516 dialog.progressBar()->advance( 1 ); 00517 if ( tasknr % 15 == 0 ) kapp->processEvents(); // repainting is slow 00518 if ( taskview->item_at_index(tasknr)->depth() > maxdepth ) 00519 maxdepth = taskview->item_at_index(tasknr)->depth(); 00520 tasknr++; 00521 } 00522 00523 // Export to file 00524 tasknr = 0; 00525 while ( tasknr < taskview->count() && !dialog.wasCancelled() ) 00526 { 00527 task = taskview->item_at_index( tasknr ); 00528 00529 dialog.progressBar()->advance( 1 ); 00530 if ( tasknr % 15 == 0 ) kapp->processEvents(); 00531 00532 // indent the task in the csv-file: 00533 for ( int i=0; i < task->depth(); ++i ) stream << delim; 00534 00535 /* 00536 // CSV compliance 00537 // Surround the field with quotes if the field contains 00538 // a comma (delim) or a double quote 00539 if (task->name().contains(delim) || task->name().contains(dquote)) 00540 to_quote = TRUE; 00541 else 00542 to_quote = FALSE; 00543 */ 00544 to_quote = true; 00545 00546 if (to_quote) 00547 stream << dquote; 00548 00549 // Double quotes replaced by a pair of consecutive double quotes 00550 stream << task->name().replace( dquote, double_dquote ); 00551 00552 if (to_quote) 00553 stream << dquote; 00554 00555 // maybe other tasks are more indented, so to align the columns: 00556 for ( int i = 0; i < maxdepth - task->depth(); ++i ) stream << delim; 00557 00558 stream << delim << formatTime( task->sessionTime(), 00559 rc.decimalMinutes ) 00560 << delim << formatTime( task->time(), 00561 rc.decimalMinutes ) 00562 << delim << formatTime( task->totalSessionTime(), 00563 rc.decimalMinutes ) 00564 << delim << formatTime( task->totalTime(), 00565 rc.decimalMinutes ) 00566 << endl; 00567 tasknr++; 00568 } 00569 f.close(); 00570 00571 } 00572 return err; 00573 } 00574 00575 //---------------------------------------------------------------------------- 00576 // Routines that handle logging KArm history 00577 // 00578 00579 // 00580 // public routines: 00581 // 00582 00583 QString KarmStorage::addTask(const Task* task, const Task* parent) 00584 { 00585 KCal::Todo* todo; 00586 QString uid; 00587 00588 todo = new KCal::Todo(); 00589 if (_calendar->addTodo(todo)) 00590 { 00591 task->asTodo(todo); 00592 if (parent) 00593 todo->setRelatedTo(_calendar->todo(parent->uid())); 00594 uid = todo->uid(); 00595 } 00596 00597 return uid; 00598 } 00599 00600 bool KarmStorage::removeTask(Task* task) 00601 { 00602 if ( !_lock ) return false; 00603 00604 // delete history 00605 KCal::Event::List eventList = _calendar->rawEvents(); 00606 for(KCal::Event::List::iterator i = eventList.begin(); 00607 i != eventList.end(); 00608 ++i) 00609 { 00610 //kdDebug(5970) << "KarmStorage::removeTask: " 00611 // << (*i)->uid() << " - relatedToUid() " 00612 // << (*i)->relatedToUid() 00613 // << ", relatedTo() = " << (*i)->relatedTo() <<endl; 00614 if ( (*i)->relatedToUid() == task->uid() 00615 || ( (*i)->relatedTo() 00616 && (*i)->relatedTo()->uid() == task->uid())) 00617 { 00618 _calendar->deleteEvent(*i); 00619 } 00620 } 00621 00622 // delete todo 00623 KCal::Todo *todo = _calendar->todo(task->uid()); 00624 _calendar->deleteTodo(todo); 00625 00626 // Save entire file 00627 _calendar->save(_lock); 00628 _lock = _calendar->requestSaveTicket 00629 (_calendar->resourceManager()->standardResource()); 00630 00631 return true; 00632 } 00633 00634 void KarmStorage::addComment(const Task* task, const QString& comment) 00635 { 00636 if ( !_lock) return; 00637 00638 KCal::Todo* todo; 00639 00640 todo = _calendar->todo(task->uid()); 00641 00642 // Do this to avoid compiler warnings about comment not being used. once we 00643 // transition to using the addComment method, we need this second param. 00644 QString s = comment; 00645 00646 // TODO: Use libkcal comments 00647 // todo->addComment(comment); 00648 // temporary 00649 todo->setDescription(task->comment()); 00650 00651 _calendar->save(_lock); 00652 _lock = _calendar->requestSaveTicket 00653 ( _calendar->resourceManager()->standardResource() ); 00654 } 00655 00656 void KarmStorage::printTaskHistory ( 00657 const Task *task, 00658 const QMap<QString,long> &taskdaytotals, 00659 QMap<QString,long> &daytotals, 00660 const QDate &from, 00661 const QDate &to, 00662 const int level, 00663 QString &s, 00664 const ReportCriteria &rc) 00665 // to>=from is precondition 00666 { 00667 QString delim = rc.delimiter; 00668 QString dquote = rc.quote; 00669 QString double_dquote = dquote + dquote; 00670 bool to_quote = true; 00671 00672 const QString cr = QString::fromLatin1("\n"); 00673 QString buf; 00674 QString daytaskkey, daykey; 00675 QDate day; 00676 long sum; 00677 00678 if ( !task ) return; 00679 00680 day = from; 00681 sum = 0; 00682 while (day <= to) 00683 { 00684 // write the time in seconds for the given task for the given day to s 00685 daykey = day.toString(QString::fromLatin1("yyyyMMdd")); 00686 daytaskkey = QString::fromLatin1("%1_%2") 00687 .arg(daykey) 00688 .arg(task->uid()); 00689 00690 if (taskdaytotals.contains(daytaskkey)) 00691 { 00692 s += QString::fromLatin1("%1") 00693 .arg(formatTime(taskdaytotals[daytaskkey]/60, rc.decimalMinutes)); 00694 sum += taskdaytotals[daytaskkey]; // in seconds 00695 00696 if (daytotals.contains(daykey)) 00697 daytotals.replace(daykey, daytotals[daykey]+taskdaytotals[daytaskkey]); 00698 else 00699 daytotals.insert(daykey, taskdaytotals[daytaskkey]); 00700 } 00701 s += delim; 00702 00703 day = day.addDays(1); 00704 } 00705 00706 // Total for task this week 00707 s += QString::fromLatin1("%1").arg(formatTime(sum/60, rc.decimalMinutes)); 00708 00709 // Task name 00710 for ( int i = level + 1; i > 0; i-- ) s += delim; 00711 00712 /* 00713 // CSV compliance 00714 // Surround the field with quotes if the field contains 00715 // a comma (delim) or a double quote 00716 to_quote = task->name().contains(delim) || task->name().contains(dquote); 00717 */ 00718 to_quote = true; 00719 if ( to_quote) s += dquote; 00720 00721 00722 // Double quotes replaced by a pair of consecutive double quotes 00723 s += task->name().replace( dquote, double_dquote ); 00724 00725 if ( to_quote) s += dquote; 00726 00727 s += cr; 00728 00729 for (Task* subTask = task->firstChild(); 00730 subTask; 00731 subTask = subTask->nextSibling()) 00732 { 00733 printTaskHistory( subTask, taskdaytotals, daytotals, from, to , level+1, s, 00734 rc ); 00735 } 00736 } 00737 00738 QString KarmStorage::report( TaskView *taskview, const ReportCriteria &rc ) 00739 { 00740 QString err; 00741 if ( rc.reportType == ReportCriteria::CSVHistoryExport ) 00742 err = exportActivityReport( taskview, rc.from, rc.to, rc ); 00743 else if ( rc.reportType == ReportCriteria::CSVTotalsExport ) 00744 err = exportcsvFile( taskview, rc ); 00745 else 00746 // hmmmm ... assert(0)? 00747 ; 00748 return err; 00749 }; 00750 00751 // export history report as csv, all tasks X all dates in one block 00752 QString KarmStorage::exportActivityReport ( TaskView *taskview, 00753 const QDate &from, 00754 const QDate &to, 00755 const ReportCriteria &rc) 00756 { 00757 QString delim = rc.delimiter; 00758 const QString cr = QString::fromLatin1("\n"); 00759 QString err; 00760 00761 // below taken from timekard.cpp 00762 QString retval; 00763 QString taskhdr, totalhdr; 00764 QString line, buf; 00765 long sum; 00766 00767 QValueList<Week>::iterator week; 00768 QValueList<HistoryEvent> events; 00769 QValueList<HistoryEvent>::iterator event; 00770 QMap<QString, long> taskdaytotals; 00771 QMap<QString, long> daytotals; 00772 QString daytaskkey, daykey; 00773 QDate day; 00774 QDate dayheading; 00775 00776 // parameter-plausi 00777 if ( from > to ) 00778 { 00779 err = QString::fromLatin1 ( 00780 "'to' has to be a date later than or equal to 'from'."); 00781 } 00782 00783 // header 00784 retval += i18n("Task History\n"); 00785 retval += i18n("From %1 to %2") 00786 .arg(KGlobal::locale()->formatDate(from)) 00787 .arg(KGlobal::locale()->formatDate(to)); 00788 retval += cr; 00789 retval += i18n("Printed on: %1") 00790 .arg(KGlobal::locale()->formatDateTime(QDateTime::currentDateTime())); 00791 retval += cr; 00792 00793 // output one time card table for each week in the date range 00794 QValueList<Week> weeks = Week::weeksFromDateRange(from, to); 00795 day=from; 00796 00797 events = taskview->getHistory(from, to); 00798 taskdaytotals.clear(); 00799 daytotals.clear(); 00800 00801 // Build lookup dictionary used to output data in table cells. keys are 00802 // in this format: YYYYMMDD_NNNNNN, where Y = year, M = month, d = day and 00803 // NNNNN = the VTODO uid. The value is the total seconds logged against 00804 // that task on that day. Note the UID is the todo id, not the event id, 00805 // so times are accumulated for each task. 00806 for (event = events.begin(); event != events.end(); ++event) 00807 { 00808 daykey = (*event).start().date().toString(QString::fromLatin1("yyyyMMdd")); 00809 daytaskkey = QString(QString::fromLatin1("%1_%2")) 00810 .arg(daykey) 00811 .arg((*event).todoUid()); 00812 00813 if (taskdaytotals.contains(daytaskkey)) 00814 taskdaytotals.replace(daytaskkey, 00815 taskdaytotals[daytaskkey] + (*event).duration()); 00816 else 00817 taskdaytotals.insert(daytaskkey, (*event).duration()); 00818 } 00819 00820 // day headings 00821 dayheading = from; 00822 while ( dayheading <= to ) 00823 { 00824 // Use ISO 8601 format for date. 00825 retval += dayheading.toString(QString::fromLatin1("yyyy-MM-dd")); 00826 retval += delim; 00827 dayheading=dayheading.addDays(1); 00828 } 00829 retval += cr; 00830 retval += line; 00831 00832 // the tasks 00833 if (events.empty()) 00834 { 00835 retval += i18n(" No hours logged."); 00836 } 00837 else 00838 { 00839 if ( rc.allTasks ) 00840 { 00841 for ( Task* task= taskview->item_at_index(0); 00842 task; task= task->nextSibling() ) 00843 { 00844 printTaskHistory( task, taskdaytotals, daytotals, from, to, 0, 00845 retval, rc ); 00846 } 00847 } 00848 else 00849 { 00850 printTaskHistory( taskview->current_item(), taskdaytotals, daytotals, 00851 from, to, 0, retval, rc ); 00852 } 00853 retval += line; 00854 00855 // totals 00856 sum = 0; 00857 day = from; 00858 while (day<=to) 00859 { 00860 daykey = day.toString(QString::fromLatin1("yyyyMMdd")); 00861 00862 if (daytotals.contains(daykey)) 00863 { 00864 retval += QString::fromLatin1("%1") 00865 .arg(formatTime(daytotals[daykey]/60, rc.decimalMinutes)); 00866 sum += daytotals[daykey]; // in seconds 00867 } 00868 retval += delim; 00869 day = day.addDays(1); 00870 } 00871 00872 retval += QString::fromLatin1("%1%2%3") 00873 .arg( formatTime( sum/60, rc.decimalMinutes ) ) 00874 .arg( delim ) 00875 .arg( i18n( "Total" ) ); 00876 } 00877 00878 // above taken from timekard.cpp 00879 00880 QFile f( rc.url ); 00881 if( !f.open( IO_WriteOnly ) ) { 00882 err = i18n( "Could not open \"%1\"." ).arg( rc.url ); 00883 } 00884 00885 if (!err) 00886 { 00887 00888 QTextStream stream(&f); 00889 // Export to file 00890 stream << retval; 00891 f.close(); 00892 00893 } 00894 return err; 00895 } 00896 00897 void KarmStorage::stopTimer(const Task* task) 00898 { 00899 long delta = task->startTime().secsTo(QDateTime::currentDateTime()); 00900 changeTime(task, delta); 00901 } 00902 00903 void KarmStorage::changeTime(const Task* task, const long deltaSeconds) 00904 { 00905 KCal::Event* e; 00906 QDateTime end; 00907 00908 // Don't write events (with timer start/stop duration) if user has turned 00909 // this off in the settings dialog. 00910 if ( ! task->taskView()->preferences()->logging() ) return; 00911 00912 e = baseEvent(task); 00913 00914 // Don't use duration, as ICalFormatImpl::writeIncidence never writes a 00915 // duration, even though it looks like it's used in event.cpp. 00916 end = task->startTime(); 00917 if ( deltaSeconds > 0 ) end = task->startTime().addSecs(deltaSeconds); 00918 e->setDtEnd(end); 00919 00920 // Use a custom property to keep a record of negative durations 00921 e->setCustomProperty( kapp->instanceName(), 00922 QCString("duration"), 00923 QString::number(deltaSeconds)); 00924 00925 _calendar->addEvent(e); 00926 00927 // This saves the entire iCal file each time, which isn't efficient but 00928 // ensures no data loss. A faster implementation would be to append events 00929 // to a file, and then when KArm closes, append the data in this file to the 00930 // iCal file. 00931 // 00932 // Meanwhile, we simply use a timer to delay the full-saving until the GUI 00933 // has updated, for better user feedback. Feel free to get rid of this 00934 // if/when implementing the faster saving (DF). 00935 task->taskView()->scheduleSave(); 00936 } 00937 00938 00939 KCal::Event* KarmStorage::baseEvent(const Task * task) 00940 { 00941 KCal::Event* e; 00942 QStringList categories; 00943 00944 e = new KCal::Event; 00945 e->setSummary(task->name()); 00946 00947 // Can't use setRelatedToUid()--no error, but no RelatedTo written to disk 00948 e->setRelatedTo(_calendar->todo(task->uid())); 00949 00950 // Debugging: some events where not getting a related-to field written. 00951 assert(e->relatedTo()->uid() == task->uid()); 00952 00953 // Have to turn this off to get datetimes in date fields. 00954 e->setFloats(false); 00955 e->setDtStart(task->startTime()); 00956 00957 // So someone can filter this mess out of their calendar display 00958 categories.append(i18n("KArm")); 00959 e->setCategories(categories); 00960 00961 return e; 00962 } 00963 00964 HistoryEvent::HistoryEvent(QString uid, QString name, long duration, 00965 QDateTime start, QDateTime stop, QString todoUid) 00966 { 00967 _uid = uid; 00968 _name = name; 00969 _duration = duration; 00970 _start = start; 00971 _stop = stop; 00972 _todoUid = todoUid; 00973 } 00974 00975 00976 QValueList<HistoryEvent> KarmStorage::getHistory(const QDate& from, 00977 const QDate& to) 00978 { 00979 QValueList<HistoryEvent> retval; 00980 QStringList processed; 00981 KCal::Event::List events; 00982 KCal::Event::List::iterator event; 00983 QString duration; 00984 00985 for(QDate d = from; d <= to; d = d.addDays(1)) 00986 { 00987 events = _calendar->events(d); 00988 for (event = events.begin(); event != events.end(); ++event) 00989 { 00990 00991 // KArm events have the custom property X-KDE-Karm-duration 00992 if (! processed.contains( (*event)->uid())) 00993 { 00994 // If an event spans multiple days, CalendarLocal::rawEventsForDate 00995 // will return the same event on both days. To avoid double-counting 00996 // such events, we (arbitrarily) attribute the hours from both days on 00997 // the first day. This mis-reports the actual time spent, but it is 00998 // an easy fix for a (hopefully) rare situation. 00999 processed.append( (*event)->uid()); 01000 01001 duration = (*event)->customProperty(kapp->instanceName(), 01002 QCString("duration")); 01003 if ( ! duration.isNull() ) 01004 { 01005 if ( (*event)->relatedTo() 01006 && ! (*event)->relatedTo()->uid().isEmpty() ) 01007 { 01008 retval.append(HistoryEvent( 01009 (*event)->uid(), 01010 (*event)->summary(), 01011 duration.toLong(), 01012 (*event)->dtStart(), 01013 (*event)->dtEnd(), 01014 (*event)->relatedTo()->uid() 01015 )); 01016 } 01017 else 01018 // Something is screwy with the ics file, as this KArm history event 01019 // does not have a todo related to it. Could have been deleted 01020 // manually? We'll continue with report on with report ... 01021 kdDebug(5970) << "KarmStorage::getHistory(): " 01022 << "The event " << (*event)->uid() 01023 << " is not related to a todo. Dropped." << endl; 01024 } 01025 } 01026 } 01027 } 01028 01029 return retval; 01030 } 01031 01032 /* 01033 * Obsolete methods for writing to flat file format. 01034 * Aug 8, 2003, Mark 01035 * 01036 void KarmStorage::saveToFileFormat() 01037 { 01038 //QFile f(_preferences->saveFile()); 01039 QFile f(_preferences->flatFile()); 01040 01041 if ( !f.open( IO_WriteOnly | IO_Truncate ) ) { 01042 QString msg = i18n( "There was an error trying to save your data file.\n" 01043 "Time accumulated during this session will not be saved!\n"); 01044 KMessageBox::error(0, msg ); 01045 return; 01046 } 01047 const char * comment = "# TaskView save data\n"; 01048 01049 f.writeBlock(comment, strlen(comment)); //comment 01050 f.flush(); 01051 01052 QTextStream stream(&f); 01053 for (Task* child = firstChild(); 01054 child; 01055 child = child->nextSibling()) 01056 writeTaskToFile(&stream, child, 1); 01057 01058 f.close(); 01059 kdDebug(5970) << "Saved data to file " << f.name() << endl; 01060 } 01061 void KarmStorage::writeTaskToFile( QTextStream *strm, Task *task, 01062 int level) 01063 { 01064 //lukas: correct version for non-latin1 users 01065 QString _line = QString::fromLatin1("%1\t%2\t%3").arg(level). 01066 arg(task->time()).arg(task->name()); 01067 01068 DesktopList d = task->getDesktops(); 01069 int dsize = d.size(); 01070 if (dsize>0) { 01071 _line += '\t'; 01072 for (int i=0; i<dsize-1; i++) { 01073 _line += QString::number(d[i]); 01074 _line += ','; 01075 } 01076 _line += QString::number(d[dsize-1]); 01077 } 01078 *strm << _line << "\n"; 01079 01080 for ( Task* child= task->firstChild(); 01081 child; 01082 child=child->nextSibling()) { 01083 writeTaskToFile(strm, child, level+1); 01084 } 01085 } 01086 01087 */
KDE Logo
This file is part of the documentation for karm Library Version 3.3.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 21 19:46:33 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003