/* * This file only: * Copyright (C) 2003, 2004 Mark Bucciarelli * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include "incidence.h" #include "kapplication.h" // kapp #include #include #include // i18n #include #include #include #include #include #include #include #include #include #include #include #include #include //#include //#include //#include //#include #include "karmstorage.h" #include "preferences.h" #include "task.h" #include "reportcriteria.h" using namespace std; KarmStorage *KarmStorage::_instance = 0; static long linenr; // how many lines written by printTaskHistory so far KarmStorage *KarmStorage::instance() { if (_instance == 0) _instance = new KarmStorage(); return _instance; } KarmStorage::KarmStorage() { _calendar = 0; } TQString KarmStorage::load (TaskView* view, const Preferences* preferences, TQString fileName ) // loads data from filename into view. If no filename is given, filename from preferences is used. // filename might be of use if this program is run as embedded konqueror plugin. { // When I tried raising an exception from this method, the compiler // complained that exceptions are not allowed. Not sure how apps // typically handle error conditions in KDE, but I'll return the error // as a string (empty is no error). -- Mark, Aug 8, 2003 // Use KDE_CXXFLAGS=$(USE_EXCEPTIONS) in Makefile.am if you want to use // exceptions (David Faure) TQString err; KEMailSettings settings; if ( fileName.isEmpty() ) fileName = preferences->iCalFile(); // If same file, don't reload if ( fileName == _icalfile ) return err; // If file doesn't exist, create a blank one to avoid ResourceLocal load // error. We make it user and group read/write, others read. This is // masked by the users umask. (See man creat) if ( ! remoteResource( _icalfile ) ) { int handle; handle = open ( TQFile::encodeName( fileName ), O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH ); if (handle != -1) close(handle); } if ( _calendar) closeStorage(view); // Create local file resource and add to resources _icalfile = fileName; KCal::ResourceCached *resource; if ( remoteResource( _icalfile ) ) { KURL url( _icalfile ); resource = new KCal::ResourceRemote( url, url ); // same url for upload and download } else { resource = new KCal::ResourceLocal( _icalfile ); } _calendar = resource; TQObject::connect (_calendar, TQT_SIGNAL(resourceChanged(ResourceCalendar *)), view, TQT_SLOT(iCalFileModified(ResourceCalendar *))); _calendar->setTimeZoneId( KPimPrefs::timezone() ); _calendar->setResourceName( TQString::tqfromLatin1("KArm") ); _calendar->open(); _calendar->load(); // Claim ownership of iCalendar file if no one else has. KCal::Person owner = resource->getOwner(); if ( owner.isEmpty() ) { resource->setOwner( KCal::Person( settings.getSetting( KEMailSettings::RealName ), settings.getSetting( KEMailSettings::EmailAddress ) ) ); } // Build task view from iCal data if (!err) { KCal::Todo::List todoList; KCal::Todo::List::ConstIterator todo; TQDict< Task > map; // Build dictionary to look up Task object from Todo uid. Each task is a // TQListViewItem, and is initially added with the view as the parent. todoList = _calendar->rawTodos(); kdDebug(5970) << "KarmStorage::load " << "rawTodo count (includes completed todos) =" << todoList.count() << endl; for( todo = todoList.begin(); todo != todoList.end(); ++todo ) { // Initially, if a task was complete, it was removed from the view. // However, this increased the complexity of reporting on task history. // // For example, if a task is complete yet has time logged to it during // the date range specified on the history report, we have to figure out // how that task fits into the task hierarchy. Currently, this // structure is held in memory by the structure in the list view. // // I considered creating a second tree that held the full structure of // all complete and incomplete tasks. But this seemed to much of a // change with an impending beta release and a full todo list. // // Hence this "solution". Include completed tasks, but mark them as // inactive in the view. // //if ((*todo)->isCompleted()) continue; Task* task = new Task(*todo, view); map.insert( (*todo)->uid(), task ); view->setRootIsDecorated(true); task->setPixmapProgress(); } // Load each task under it's parent task. for( todo = todoList.begin(); todo != todoList.end(); ++todo ) { Task* task = map.find( (*todo)->uid() ); // No relatedTo incident just means this is a top-level task. if ( (*todo)->relatedTo() ) { Task* newParent = map.find( (*todo)->relatedToUid() ); // Complete the loading but return a message if ( !newParent ) err = i18n("Error loading \"%1\": could not find parent (uid=%2)") .tqarg(task->name()) .tqarg((*todo)->relatedToUid()); if (!err) task->move( newParent); } } kdDebug(5970) << "KarmStorage::load - loaded " << view->count() << " tasks from " << _icalfile << endl; } return err; } TQString KarmStorage::icalfile() { kdDebug(5970) << "Entering KarmStorage::icalfile" << endl; return _icalfile; } TQString KarmStorage::buildTaskView(KCal::ResourceCalendar *rc, TaskView *view) // makes *view contain the tasks out of *rc. { TQString err; KCal::Todo::List todoList; KCal::Todo::List::ConstIterator todo; TQDict< Task > map; vector runningTasks; vector startTimes; // remember tasks that are running and their start times for ( int i=0; icount(); i++) { if ( view->item_at_index(i)->isRunning() ) { runningTasks.push_back( view->item_at_index(i)->uid() ); startTimes.push_back( view->item_at_index(i)->lastStart() ); } } //view->stopAllTimers(); // delete old tasks while (view->item_at_index(0)) view->item_at_index(0)->cut(); // 1. insert tasks form rc into taskview // 1.1. Build dictionary to look up Task object from Todo uid. Each task is a // TQListViewItem, and is initially added with the view as the parent. todoList = rc->rawTodos(); for( todo = todoList.begin(); todo != todoList.end(); ++todo ) { Task* task = new Task(*todo, view); map.insert( (*todo)->uid(), task ); view->setRootIsDecorated(true); task->setPixmapProgress(); } // 1.1. Load each task under it's parent task. for( todo = todoList.begin(); todo != todoList.end(); ++todo ) { Task* task = map.find( (*todo)->uid() ); // No relatedTo incident just means this is a top-level task. if ( (*todo)->relatedTo() ) { Task* newParent = map.find( (*todo)->relatedToUid() ); // Complete the loading but return a message if ( !newParent ) err = i18n("Error loading \"%1\": could not find parent (uid=%2)") .tqarg(task->name()) .tqarg((*todo)->relatedToUid()); if (!err) task->move( newParent); } } view->clearActiveTasks(); // restart tasks that have been running with their start times for ( int i=0; icount(); i++) { for ( unsigned int n=0; nitem_at_index(i)->uid() ) { view->startTimerFor( view->item_at_index(i), startTimes[n] ); } } } view->refresh(); return err; } void KarmStorage::closeStorage(TaskView* view) { if ( _calendar ) { _calendar->close(); delete _calendar; _calendar = 0; view->clear(); } } TQString KarmStorage::save(TaskView* taskview) { kdDebug(5970) << "entering KarmStorage::save" << endl; TQString err=TQString(); TQPtrStack< KCal::Todo > parents; for (Task* task=taskview->first_child(); task; task = task->nextSibling()) { err=writeTaskAsTodo(task, 1, parents ); } if ( !saveCalendar() ) { err="Could not save"; } if ( err.isEmpty() ) { kdDebug(5970) << "KarmStorage::save : wrote " << taskview->count() << " tasks to " << _icalfile << endl; } else { kdWarning(5970) << "KarmStorage::save : " << err << endl; } return err; } TQString KarmStorage::writeTaskAsTodo(Task* task, const int level, TQPtrStack< KCal::Todo >& parents ) { TQString err; KCal::Todo* todo; todo = _calendar->todo(task->uid()); if ( !todo ) { kdDebug(5970) << "Could not get todo from calendar" << endl; return "Could not get todo from calendar"; } task->asTodo(todo); if ( !parents.isEmpty() ) todo->setRelatedTo( parents.top() ); parents.push( todo ); for ( Task* nextTask = task->firstChild(); nextTask; nextTask = nextTask->nextSibling() ) { err = writeTaskAsTodo(nextTask, level+1, parents ); } parents.pop(); return err; } bool KarmStorage::isEmpty() { KCal::Todo::List todoList; todoList = _calendar->rawTodos(); return todoList.empty(); } bool KarmStorage::isNewStorage(const Preferences* preferences) const { if ( !_icalfile.isNull() ) return preferences->iCalFile() != _icalfile; else return false; } //---------------------------------------------------------------------------- // Routines that handle legacy flat file format. // These only stored total and session times. // TQString KarmStorage::loadFromFlatFile(TaskView* taskview, const TQString& filename) { TQString err; kdDebug(5970) << "KarmStorage::loadFromFlatFile: " << filename << endl; TQFile f(filename); if( !f.exists() ) err = i18n("File \"%1\" not found.").tqarg(filename); if (!err) { if( !f.open( IO_ReadOnly ) ) err = i18n("Could not open \"%1\".").tqarg(filename); } if (!err) { TQString line; TQPtrStack stack; Task *task; TQTextStream stream(&f); while( !stream.atEnd() ) { // lukas: this breaks for non-latin1 chars!!! // if ( file.readLine( line, T_LINESIZE ) == 0 ) // break; line = stream.readLine(); kdDebug(5970) << "DEBUG: line: " << line << "\n"; if (line.isNull()) break; long minutes; int level; TQString name; DesktopList desktopList; if (!parseLine(line, &minutes, &name, &level, &desktopList)) continue; unsigned int stackLevel = stack.count(); for (unsigned int i = level; i<=stackLevel ; i++) { stack.pop(); } if (level == 1) { kdDebug(5970) << "KarmStorage::loadFromFlatFile - toplevel task: " << name << " min: " << minutes << "\n"; task = new Task(name, minutes, 0, desktopList, taskview); task->setUid(addTask(task, 0)); } else { Task *parent = stack.top(); kdDebug(5970) << "KarmStorage::loadFromFlatFile - task: " << name << " min: " << minutes << " parent" << parent->name() << "\n"; task = new Task(name, minutes, 0, desktopList, parent); task->setUid(addTask(task, parent)); // Legacy File Format (!): parent->changeTimes(0, -minutes); taskview->setRootIsDecorated(true); parent->setOpen(true); } if (!task->uid().isNull()) stack.push(task); else delete task; } f.close(); } return err; } TQString KarmStorage::loadFromFlatFileCumulative(TaskView* taskview, const TQString& filename) { TQString err = loadFromFlatFile(taskview, filename); if (!err) { for (Task* task = taskview->first_child(); task; task = task->nextSibling()) { adjustFromLegacyFileFormat(task); } } return err; } bool KarmStorage::parseLine(TQString line, long *time, TQString *name, int *level, DesktopList* desktopList) { if (line.find('#') == 0) { // A comment line return false; } int index = line.find('\t'); if (index == -1) { // This doesn't seem like a valid record return false; } TQString levelStr = line.left(index); TQString rest = line.remove(0,index+1); index = rest.find('\t'); if (index == -1) { // This doesn't seem like a valid record return false; } TQString timeStr = rest.left(index); rest = rest.remove(0,index+1); bool ok; index = rest.find('\t'); // check for optional desktops string if (index >= 0) { *name = rest.left(index); TQString deskLine = rest.remove(0,index+1); // now transform the ds string (e.g. "3", or "1,4,5") into // an DesktopList TQString ds; int d; int commaIdx = deskLine.find(','); while (commaIdx >= 0) { ds = deskLine.left(commaIdx); d = ds.toInt(&ok); if (!ok) return false; desktopList->push_back(d); deskLine.remove(0,commaIdx+1); commaIdx = deskLine.find(','); } d = deskLine.toInt(&ok); if (!ok) return false; desktopList->push_back(d); } else { *name = rest.remove(0,index+1); } *time = timeStr.toLong(&ok); if (!ok) { // the time field was not a number return false; } *level = levelStr.toInt(&ok); if (!ok) { // the time field was not a number return false; } return true; } void KarmStorage::adjustFromLegacyFileFormat(Task* task) { // unless the parent is the listView if ( task->parent() ) task->parent()->changeTimes(-task->sessionTime(), -task->time()); // traverse depth first - // as soon as we're in a leaf, we'll substract it's time from the parent // then, while descending back we'll do the same for each node untill // we reach the root for ( Task* subtask = task->firstChild(); subtask; subtask = subtask->nextSibling() ) adjustFromLegacyFileFormat(subtask); } //---------------------------------------------------------------------------- // Routines that handle Comma-Separated Values export file format. // TQString KarmStorage::exportcsvFile( TaskView *taskview, const ReportCriteria &rc ) { TQString delim = rc.delimiter; TQString dquote = rc.quote; TQString double_dquote = dquote + dquote; bool to_quote = true; TQString err; Task* task; int maxdepth=0; kdDebug(5970) << "KarmStorage::exportcsvFile: " << rc.url << endl; TQString title = i18n("Export Progress"); KProgressDialog dialog( taskview, 0, title ); dialog.setAutoClose( true ); dialog.setAllowCancel( true ); dialog.progressBar()->setTotalSteps( 2 * taskview->count() ); // The default dialog was not displaying all the text in the title bar. int width = taskview->fontMetrics().width(title) * 3; TQSize dialogsize; dialogsize.setWidth(width); dialog.setInitialSize( dialogsize, true ); if ( taskview->count() > 1 ) dialog.show(); TQString retval; // Find max task depth int tasknr = 0; while ( tasknr < taskview->count() && !dialog.wasCancelled() ) { dialog.progressBar()->advance( 1 ); if ( tasknr % 15 == 0 ) kapp->processEvents(); // repainting is slow if ( taskview->item_at_index(tasknr)->depth() > maxdepth ) maxdepth = taskview->item_at_index(tasknr)->depth(); tasknr++; } // Export to file tasknr = 0; while ( tasknr < taskview->count() && !dialog.wasCancelled() ) { task = taskview->item_at_index( tasknr ); dialog.progressBar()->advance( 1 ); if ( tasknr % 15 == 0 ) kapp->processEvents(); // indent the task in the csv-file: for ( int i=0; i < task->depth(); ++i ) retval += delim; /* // CSV compliance // Surround the field with quotes if the field contains // a comma (delim) or a double quote if (task->name().contains(delim) || task->name().contains(dquote)) to_quote = true; else to_quote = false; */ to_quote = true; if (to_quote) retval += dquote; // Double quotes replaced by a pair of consecutive double quotes retval += task->name().replace( dquote, double_dquote ); if (to_quote) retval += dquote; // maybe other tasks are more indented, so to align the columns: for ( int i = 0; i < maxdepth - task->depth(); ++i ) retval += delim; retval += delim + formatTime( task->sessionTime(), rc.decimalMinutes ) + delim + formatTime( task->time(), rc.decimalMinutes ) + delim + formatTime( task->totalSessionTime(), rc.decimalMinutes ) + delim + formatTime( task->totalTime(), rc.decimalMinutes ) + "\n"; tasknr++; } // save, either locally or remote if ((rc.url.isLocalFile()) || (!rc.url.url().contains("/"))) { TQString filename=rc.url.path(); if (filename.isEmpty()) filename=rc.url.url(); TQFile f( filename ); if( !f.open( IO_WriteOnly ) ) { err = i18n( "Could not open \"%1\"." ).tqarg( filename ); } if (!err) { TQTextStream stream(&f); // Export to file stream << retval; f.close(); } } else // use remote file { KTempFile tmpFile; if ( tmpFile.status() != 0 ) err = TQString::tqfromLatin1( "Unable to get temporary file" ); else { TQTextStream *stream=tmpFile.textStream(); *stream << retval; tmpFile.close(); if (!KIO::NetAccess::upload( tmpFile.name(), rc.url, 0 )) err=TQString::tqfromLatin1("Could not upload"); } } return err; } //---------------------------------------------------------------------------- // Routines that handle logging KArm history // // // public routines: // TQString KarmStorage::addTask(const Task* task, const Task* parent) { KCal::Todo* todo; TQString uid; todo = new KCal::Todo(); if ( _calendar->addTodo( todo ) ) { task->asTodo( todo ); if (parent) todo->setRelatedTo(_calendar->todo(parent->uid())); uid = todo->uid(); } else { // Most likely a lock could not be pulled, although there are other // possiblities (like a really confused resource manager). uid = ""; } return uid; } bool KarmStorage::removeTask(Task* task) { // delete history KCal::Event::List eventList = _calendar->rawEvents(); for(KCal::Event::List::iterator i = eventList.begin(); i != eventList.end(); ++i) { //kdDebug(5970) << "KarmStorage::removeTask: " // << (*i)->uid() << " - relatedToUid() " // << (*i)->relatedToUid() // << ", relatedTo() = " << (*i)->relatedTo() <relatedToUid() == task->uid() || ( (*i)->relatedTo() && (*i)->relatedTo()->uid() == task->uid())) { _calendar->deleteEvent(*i); } } // delete todo KCal::Todo *todo = _calendar->todo(task->uid()); _calendar->deleteTodo(todo); // Save entire file saveCalendar(); return true; } void KarmStorage::addComment(const Task* task, const TQString& comment) { KCal::Todo* todo; todo = _calendar->todo(task->uid()); // Do this to avoid compiler warnings about comment not being used. once we // transition to using the addComment method, we need this second param. TQString s = comment; // TODO: Use libkcal comments // todo->addComment(comment); // temporary todo->setDescription(task->comment()); saveCalendar(); } long KarmStorage::printTaskHistory ( const Task *task, const TQMap &taskdaytotals, TQMap &daytotals, const TQDate &from, const TQDate &to, const int level, vector &matrix, const ReportCriteria &rc) // to>=from is precondition { long ownline=linenr++; // the how many-th instance of this function is this long colrectot=0; // colum where to write the task's total recursive time vector cell; // each line of the matrix is stored in an array of cells, one containing the recursive total long add; // total recursive time of all subtasks TQString delim = rc.delimiter; TQString dquote = rc.quote; TQString double_dquote = dquote + dquote; bool to_quote = true; const TQString cr = TQString::tqfromLatin1("\n"); TQString buf; TQString daytaskkey, daykey; TQDate day; long sum; if ( !task ) return 0; day = from; sum = 0; while (day <= to) { // write the time in seconds for the given task for the given day to s daykey = day.toString(TQString::tqfromLatin1("yyyyMMdd")); daytaskkey = TQString::tqfromLatin1("%1_%2") .tqarg(daykey) .tqarg(task->uid()); if (taskdaytotals.contains(daytaskkey)) { cell.push_back(TQString::tqfromLatin1("%1") .tqarg(formatTime(taskdaytotals[daytaskkey]/60, rc.decimalMinutes))); sum += taskdaytotals[daytaskkey]; // in seconds if (daytotals.contains(daykey)) daytotals.replace(daykey, daytotals[daykey]+taskdaytotals[daytaskkey]); else daytotals.insert(daykey, taskdaytotals[daytaskkey]); } cell.push_back(delim); day = day.addDays(1); } // Total for task cell.push_back(TQString::tqfromLatin1("%1").tqarg(formatTime(sum/60, rc.decimalMinutes))); // room for the recursive total time (that cannot be calculated now) cell.push_back(delim); colrectot = cell.size(); cell.push_back("???"); cell.push_back(delim); // Task name for ( int i = level + 1; i > 0; i-- ) cell.push_back(delim); /* // CSV compliance // Surround the field with quotes if the field contains // a comma (delim) or a double quote to_quote = task->name().contains(delim) || task->name().contains(dquote); */ to_quote = true; if ( to_quote) cell.push_back(dquote); // Double quotes replaced by a pair of consecutive double quotes cell.push_back(task->name().replace( dquote, double_dquote )); if ( to_quote) cell.push_back(dquote); cell.push_back(cr); add=0; for (Task* subTask = task->firstChild(); subTask; subTask = subTask->nextSibling()) { add += printTaskHistory( subTask, taskdaytotals, daytotals, from, to , level+1, matrix, rc ); } cell[colrectot]=(TQString::tqfromLatin1("%1").tqarg(formatTime((add+sum)/60, rc.decimalMinutes ))); for (unsigned int i=0; i < cell.size(); i++) matrix[ownline]+=cell[i]; return add+sum; } TQString KarmStorage::report( TaskView *taskview, const ReportCriteria &rc ) { TQString err; if ( rc.reportType == ReportCriteria::CSVHistoryExport ) err = exportcsvHistory( taskview, rc.from, rc.to, rc ); else if ( rc.reportType == ReportCriteria::CSVTotalsExport ) err = exportcsvFile( taskview, rc ); else { // hmmmm ... assert(0)? } return err; } // export history report as csv, all tasks X all dates in one block TQString KarmStorage::exportcsvHistory ( TaskView *taskview, const TQDate &from, const TQDate &to, const ReportCriteria &rc) { TQString delim = rc.delimiter; const TQString cr = TQString::tqfromLatin1("\n"); TQString err; // below taken from timekard.cpp TQString retval; TQString taskhdr, totalhdr; TQString line, buf; long sum; TQValueList events; TQValueList::iterator event; TQMap taskdaytotals; TQMap daytotals; TQString daytaskkey, daykey; TQDate day; TQDate dayheading; // parameter-plausi if ( from > to ) { err = TQString::tqfromLatin1 ( "'to' has to be a date later than or equal to 'from'."); } // header retval += i18n("Task History\n"); retval += i18n("From %1 to %2") .tqarg(KGlobal::locale()->formatDate(from)) .tqarg(KGlobal::locale()->formatDate(to)); retval += cr; retval += i18n("Printed on: %1") .tqarg(KGlobal::locale()->formatDateTime(TQDateTime::tqcurrentDateTime())); retval += cr; day=from; events = taskview->getHistory(from, to); taskdaytotals.clear(); daytotals.clear(); // Build lookup dictionary used to output data in table cells. keys are // in this format: YYYYMMDD_NNNNNN, where Y = year, M = month, d = day and // NNNNN = the VTODO uid. The value is the total seconds logged against // that task on that day. Note the UID is the todo id, not the event id, // so times are accumulated for each task. for (event = events.begin(); event != events.end(); ++event) { daykey = (*event).start().date().toString(TQString::tqfromLatin1("yyyyMMdd")); daytaskkey = TQString(TQString::tqfromLatin1("%1_%2")) .tqarg(daykey) .tqarg((*event).todoUid()); if (taskdaytotals.contains(daytaskkey)) taskdaytotals.replace(daytaskkey, taskdaytotals[daytaskkey] + (*event).duration()); else taskdaytotals.insert(daytaskkey, (*event).duration()); } // day headings dayheading = from; while ( dayheading <= to ) { // Use ISO 8601 format for date. retval += dayheading.toString(TQString::tqfromLatin1("yyyy-MM-dd")); retval += delim; dayheading=dayheading.addDays(1); } retval += i18n("Sum") + delim + i18n("Total Sum") + delim + i18n("Task Hierarchy"); retval += cr; retval += line; // the tasks vector matrix; linenr=0; for (int i=0; i<=taskview->count()+1; i++) matrix.push_back(""); if (events.empty()) { retval += i18n(" No hours logged."); } else { if ( rc.allTasks ) { for ( Task* task= taskview->item_at_index(0); task; task= task->nextSibling() ) { printTaskHistory( task, taskdaytotals, daytotals, from, to, 0, matrix, rc ); } } else { printTaskHistory( taskview->current_item(), taskdaytotals, daytotals, from, to, 0, matrix, rc ); } for (unsigned int i=0; istartTime().secsTo(when); changeTime(task, delta); } bool KarmStorage::bookTime(const Task* task, const TQDateTime& startDateTime, const long durationInSeconds) { // Ignores preferences setting re: logging history. KCal::Event* e; TQDateTime end; e = baseEvent( task ); e->setDtStart( startDateTime ); e->setDtEnd( startDateTime.addSecs( durationInSeconds ) ); // Use a custom property to keep a record of negative durations e->setCustomProperty( kapp->instanceName(), TQCString("duration"), TQString::number(durationInSeconds)); return _calendar->addEvent(e); } void KarmStorage::changeTime(const Task* task, const long deltaSeconds) { kdDebug(5970) << "Entering KarmStorage::changeTime ( " << task->name() << "," << deltaSeconds << " )" << endl; KCal::Event* e; TQDateTime end; // Don't write events (with timer start/stop duration) if user has turned // this off in the settings dialog. if ( ! task->taskView()->preferences()->logging() ) return; e = baseEvent(task); // Don't use duration, as ICalFormatImpl::writeIncidence never writes a // duration, even though it looks like it's used in event.cpp. end = task->startTime(); if ( deltaSeconds > 0 ) end = task->startTime().addSecs(deltaSeconds); e->setDtEnd(end); // Use a custom property to keep a record of negative durations e->setCustomProperty( kapp->instanceName(), TQCString("duration"), TQString::number(deltaSeconds)); _calendar->addEvent(e); // This saves the entire iCal file each time, which isn't efficient but // ensures no data loss. A faster implementation would be to append events // to a file, and then when KArm closes, append the data in this file to the // iCal file. // // Meanwhile, we simply use a timer to delay the full-saving until the GUI // has updated, for better user feedback. Feel free to get rid of this // if/when implementing the faster saving (DF). task->taskView()->scheduleSave(); } KCal::Event* KarmStorage::baseEvent(const Task * task) { KCal::Event* e; TQStringList categories; e = new KCal::Event; e->setSummary(task->name()); // Can't use setRelatedToUid()--no error, but no RelatedTo written to disk e->setRelatedTo(_calendar->todo(task->uid())); // Debugging: some events where not getting a related-to field written. assert(e->relatedTo()->uid() == task->uid()); // Have to turn this off to get datetimes in date fields. e->setFloats(false); e->setDtStart(task->startTime()); // So someone can filter this mess out of their calendar display categories.append(i18n("KArm")); e->setCategories(categories); return e; } HistoryEvent::HistoryEvent(TQString uid, TQString name, long duration, TQDateTime start, TQDateTime stop, TQString todoUid) { _uid = uid; _name = name; _duration = duration; _start = start; _stop = stop; _todoUid = todoUid; } TQValueList KarmStorage::getHistory(const TQDate& from, const TQDate& to) { TQValueList retval; TQStringList processed; KCal::Event::List events; KCal::Event::List::iterator event; TQString duration; for(TQDate d = from; d <= to; d = d.addDays(1)) { events = _calendar->rawEventsForDate( d ); for (event = events.begin(); event != events.end(); ++event) { // KArm events have the custom property X-KDE-Karm-duration if (! processed.contains( (*event)->uid())) { // If an event spans multiple days, CalendarLocal::rawEventsForDate // will return the same event on both days. To avoid double-counting // such events, we (arbitrarily) attribute the hours from both days on // the first day. This mis-reports the actual time spent, but it is // an easy fix for a (hopefully) rare situation. processed.append( (*event)->uid()); duration = (*event)->customProperty(kapp->instanceName(), TQCString("duration")); if ( ! duration.isNull() ) { if ( (*event)->relatedTo() && ! (*event)->relatedTo()->uid().isEmpty() ) { retval.append(HistoryEvent( (*event)->uid(), (*event)->summary(), duration.toLong(), (*event)->dtStart(), (*event)->dtEnd(), (*event)->relatedTo()->uid() )); } else // Something is screwy with the ics file, as this KArm history event // does not have a todo related to it. Could have been deleted // manually? We'll continue with report on with report ... kdDebug(5970) << "KarmStorage::getHistory(): " << "The event " << (*event)->uid() << " is not related to a todo. Dropped." << endl; } } } } return retval; } bool KarmStorage::remoteResource( const TQString& file ) const { TQString f = file.lower(); bool rval = f.startsWith( "http://" ) || f.startsWith( "ftp://" ); kdDebug(5970) << "KarmStorage::remoteResource( " << file << " ) returns " << rval << endl; return rval; } bool KarmStorage::saveCalendar() { kdDebug(5970) << "KarmStorage::saveCalendar" << endl; #if 0 Event::List evl=_calendar->rawEvents(); kdDebug(5970) << "summary - dtStart - dtEnd" << endl; for (unsigned int i=0; isummary() << evl[i]->dtStart() << evl[i]->dtEnd() << endl; } #endif KABC::Lock *lock = _calendar->lock(); if ( !lock || !lock->lock() ) return false; if ( _calendar && _calendar->save() ) { lock->unlock(); return true; } lock->unlock(); return false; }