karm

timekard.cpp

00001 /*
00002  *   This file only:
00003  *     Copyright (C) 2003  Mark Bucciarelli <mark@hubcapconsutling.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  *      51 Franklin Street, Fifth Floor
00019  *      Boston, MA  02110-1301  USA.
00020  *
00021  */
00022 
00023 // #include <iostream>
00024 
00025 #include <tqdatetime.h>
00026 #include <tqpaintdevicemetrics.h>
00027 #include <tqpainter.h>
00028 #include <tqmap.h>
00029 
00030 #include <tdeglobal.h>
00031 #include <kdebug.h>
00032 #include <tdelocale.h>            // i18n
00033 #include <event.h>
00034 
00035 #include "karmutility.h"        // formatTime()
00036 #include "timekard.h"
00037 #include "task.h"
00038 #include "taskview.h"
00039 #include <assert.h>
00040 
00041 const int taskWidth = 40;
00042 const int timeWidth = 6;
00043 const int totalTimeWidth = 7;
00044 const int reportWidth = taskWidth + timeWidth;
00045 
00046 const TQString cr = TQString::fromLatin1("\n");
00047 
00048 TQString TimeKard::totalsAsText(TaskView* taskview, bool justThisTask, WhichTime which)
00049 // Print the total Times as text. If justThisTask, use activeTask, else, all tasks
00050 {
00051   kdDebug(5970) << "Entering TimeKard::totalsAsText" << endl;
00052   TQString retval;
00053   TQString line;
00054   TQString buf;
00055   long sum;
00056 
00057   line.fill('-', reportWidth);
00058   line += cr;
00059 
00060   // header
00061   retval += i18n("Task Totals") + cr;
00062   retval += TDEGlobal::locale()->formatDateTime(TQDateTime::currentDateTime());
00063   retval += cr + cr;
00064   retval += TQString(TQString::fromLatin1("%1    %2"))
00065     .arg(i18n("Time"), timeWidth)
00066     .arg(i18n("Task"));
00067   retval += cr;
00068   retval += line;
00069 
00070   // tasks
00071   if (taskview->current_item())
00072   {
00073     if (justThisTask)
00074     {
00075       // a task's total time includes the sum of all subtask times
00076       sum = which == TotalTime ? taskview->current_item()->totalTime() : taskview->current_item()->sessionTime();
00077       printTask(taskview->current_item(), retval, 0, which);
00078     }
00079     else
00080     {
00081       sum = 0;
00082       for (Task* task= taskview->item_at_index(0); task;
00083           task= task->nextSibling())
00084       {
00085         kdDebug(5970) << "Copying task " << task->name() << endl;
00086         int time = which == TotalTime ? task->totalTime() : task->totalSessionTime();
00087         sum += time;
00088         if ( time || task->firstChild() )
00089                 printTask(task, retval, 0, which);
00090       }
00091     }
00092 
00093     // total
00094     buf.fill('-', reportWidth);
00095     retval += TQString(TQString::fromLatin1("%1")).arg(buf, timeWidth) + cr;
00096     retval += TQString(TQString::fromLatin1("%1 %2"))
00097       .arg(formatTime(sum),timeWidth)
00098       .arg(i18n("Total"));
00099   }
00100   else
00101     retval += i18n("No tasks.");
00102 
00103   return retval;
00104 }
00105 
00106 // Print out "<indent for level> <task total> <task>", for task and subtasks. Used by totalsAsText.
00107 void TimeKard::printTask(Task *task, TQString &s, int level, WhichTime which)
00108 {
00109   TQString buf;
00110 
00111   s += buf.fill(' ', level);
00112   s += TQString(TQString::fromLatin1("%1    %2"))
00113     .arg(formatTime(which == TotalTime?task->totalTime():task->totalSessionTime()), timeWidth)
00114     .arg(task->name());
00115   s += cr;
00116 
00117   for (Task* subTask = task->firstChild();
00118       subTask;
00119       subTask = subTask->nextSibling())
00120   {
00121     int time = which == TotalTime ? subTask->totalTime() : subTask->totalSessionTime();
00122     if (time)
00123       printTask(subTask, s, level+1, which);
00124   }
00125 }
00126 
00127 void TimeKard::printTaskHistory(const Task *task,
00128     const TQMap<TQString,long>& taskdaytotals,
00129     TQMap<TQString,long>& daytotals,
00130     const TQDate& from,
00131     const TQDate& to,
00132     const int level, TQString& s, bool totalsOnly)
00133 {
00134   long sectionsum = 0;
00135   for ( TQDate day = from; day <= to; day = day.addDays(1) )
00136   {
00137     TQString daykey = day.toString(TQString::fromLatin1("yyyyMMdd"));
00138     TQString daytaskkey = TQString::fromLatin1("%1_%2")
00139                          .arg(daykey)
00140                          .arg(task->uid());
00141 
00142     if (taskdaytotals.contains(daytaskkey))
00143     {
00144       if ( !totalsOnly )
00145       {
00146         s += TQString::fromLatin1("%1")
00147              .arg(formatTime(taskdaytotals[daytaskkey]/60), timeWidth);
00148       }
00149       sectionsum += taskdaytotals[daytaskkey];  // in seconds
00150 
00151       if (daytotals.contains(daykey))
00152         daytotals.replace(daykey, daytotals[daykey] + taskdaytotals[daytaskkey]);
00153       else
00154         daytotals.insert(daykey, taskdaytotals[daytaskkey]);
00155     }
00156     else if ( !totalsOnly )
00157     {
00158       TQString buf;
00159       buf.fill(' ', timeWidth);
00160       s += buf;
00161     }
00162   }
00163 
00164   // Total for task this section (e.g. week)
00165   s += TQString::fromLatin1("%1").arg(formatTime(sectionsum/60), totalTimeWidth);
00166 
00167   // Task name
00168   TQString buf;
00169   s += buf.fill(' ', level + 1);
00170   s += TQString::fromLatin1("%1").arg(task->name());
00171   s += cr;
00172 
00173   for (Task* subTask = task->firstChild();
00174       subTask;
00175       subTask = subTask->nextSibling())
00176   {
00177     // recursive
00178     printTaskHistory(subTask, taskdaytotals, daytotals, from, to, level+1, s, totalsOnly);
00179   }
00180 }
00181 
00182 TQString TimeKard::sectionHistoryAsText(
00183   TaskView* taskview,
00184   const TQDate& sectionFrom, const TQDate& sectionTo,
00185   const TQDate& from, const TQDate& to,
00186   const TQString& name,
00187   bool justThisTask, bool totalsOnly)
00188 {
00189 
00190   const int sectionReportWidth = taskWidth + ( totalsOnly ? 0 : sectionFrom.daysTo(sectionTo) * timeWidth ) + totalTimeWidth;
00191   assert( sectionReportWidth > 0 );
00192   TQString line;
00193   line.fill('-', sectionReportWidth);
00194   line += cr;
00195 
00196   TQValueList<HistoryEvent> events;
00197   if ( sectionFrom < from && sectionTo > to)
00198   {
00199     events = taskview->getHistory(from, to);
00200   }
00201   else if ( sectionFrom < from )
00202   {
00203     events = taskview->getHistory(from, sectionTo);
00204   }
00205   else if ( sectionTo > to)
00206   {
00207     events = taskview->getHistory(sectionFrom, to);
00208   }
00209   else
00210   {
00211     events = taskview->getHistory(sectionFrom, sectionTo);
00212   }
00213 
00214   TQMap<TQString, long> taskdaytotals;
00215   TQMap<TQString, long> daytotals;
00216 
00217   // Build lookup dictionary used to output data in table cells.  keys are
00218   // in this format: YYYYMMDD_NNNNNN, where Y = year, M = month, d = day and
00219   // NNNNN = the VTODO uid.  The value is the total seconds logged against
00220   // that task on that day.  Note the UID is the todo id, not the event id,
00221   // so times are accumulated for each task.
00222   for (TQValueList<HistoryEvent>::iterator event = events.begin(); event != events.end(); ++event)
00223   {
00224     TQString daykey = (*event).start().date().toString(TQString::fromLatin1("yyyyMMdd"));
00225     TQString daytaskkey = TQString::fromLatin1("%1_%2")
00226                          .arg(daykey)
00227                          .arg((*event).todoUid());
00228 
00229     if (taskdaytotals.contains(daytaskkey))
00230       taskdaytotals.replace(daytaskkey,
00231                             taskdaytotals[daytaskkey] + (*event).duration());
00232     else
00233       taskdaytotals.insert(daytaskkey, (*event).duration());
00234   }
00235 
00236   TQString retval;
00237   // section name (e.g. week name)
00238   retval += cr + cr;
00239   TQString buf;
00240   if ( name.length() < (unsigned int)sectionReportWidth )
00241     buf.fill(' ', int((sectionReportWidth - name.length()) / 2));
00242   retval += buf + name + cr;
00243 
00244   if ( !totalsOnly )
00245   {
00246     // day headings
00247     for (TQDate day = sectionFrom; day <= sectionTo; day = day.addDays(1))
00248     {
00249       retval += TQString::fromLatin1("%1").arg(day.day(), timeWidth);
00250     }
00251     retval += cr;
00252     retval += line;
00253   }
00254 
00255   // the tasks
00256   if (events.empty())
00257   {
00258     retval += "  ";
00259     retval += i18n("No hours logged.");
00260   }
00261   else
00262   {
00263     if (justThisTask)
00264     {
00265       printTaskHistory(taskview->current_item(), taskdaytotals, daytotals,
00266                        sectionFrom, sectionTo, 0, retval, totalsOnly);
00267     }
00268     else
00269     {
00270       for (Task* task= taskview->current_item(); task;
00271            task= task->nextSibling())
00272       {
00273         printTaskHistory(task, taskdaytotals, daytotals,
00274                          sectionFrom, sectionTo, 0, retval, totalsOnly);
00275       }
00276     }
00277     retval += line;
00278 
00279     // per-day totals at the bottom of the section
00280     long sum = 0;
00281     for (TQDate day = sectionFrom; day <= sectionTo; day = day.addDays(1))
00282     {
00283       TQString daykey = day.toString(TQString::fromLatin1("yyyyMMdd"));
00284 
00285       if (daytotals.contains(daykey))
00286       {
00287         if ( !totalsOnly )
00288         {
00289           retval += TQString::fromLatin1("%1")
00290                     .arg(formatTime(daytotals[daykey]/60), timeWidth);
00291         }
00292         sum += daytotals[daykey];  // in seconds
00293       }
00294       else if ( !totalsOnly )
00295       {
00296         buf.fill(' ', timeWidth);
00297         retval += buf;
00298       }
00299     }
00300 
00301     retval += TQString::fromLatin1("%1 %2")
00302               .arg(formatTime(sum/60), totalTimeWidth)
00303               .arg(i18n("Total"));
00304   }
00305   return retval;
00306 }
00307 
00308 TQString TimeKard::historyAsText(TaskView* taskview, const TQDate& from,
00309     const TQDate& to, bool justThisTask, bool perWeek, bool totalsOnly)
00310 {
00311   // header
00312   TQString retval;
00313   retval += totalsOnly ? i18n("Task Totals") : i18n("Task History");
00314   retval += cr;
00315   retval += i18n("From %1 to %2")
00316     .arg(TDEGlobal::locale()->formatDate(from))
00317     .arg(TDEGlobal::locale()->formatDate(to));
00318   retval += cr;
00319   retval += i18n("Printed on: %1")
00320     .arg(TDEGlobal::locale()->formatDateTime(TQDateTime::currentDateTime()));
00321 
00322   if ( perWeek )
00323   {
00324     // output one time card table for each week in the date range
00325     TQValueList<Week> weeks = Week::weeksFromDateRange(from, to);
00326     for (TQValueList<Week>::iterator week = weeks.begin(); week != weeks.end(); ++week)
00327     {
00328       retval += sectionHistoryAsText( taskview, (*week).start(), (*week).end(), from, to, (*week).name(), justThisTask, totalsOnly );
00329     }
00330   } else
00331   {
00332     retval += sectionHistoryAsText( taskview, from, to, from, to, "", justThisTask, totalsOnly );
00333   }
00334   return retval;
00335 }
00336 
00337 Week::Week() {}
00338 
00339 Week::Week(TQDate from)
00340 {
00341   _start = from;
00342 }
00343 
00344 TQDate Week::start() const
00345 {
00346   return _start;
00347 }
00348 
00349 TQDate Week::end() const
00350 {
00351   return _start.addDays(6);
00352 }
00353 
00354 TQString Week::name() const
00355 {
00356   return i18n("Week of %1").arg(TDEGlobal::locale()->formatDate(start()));
00357 }
00358 
00359 TQValueList<Week> Week::weeksFromDateRange(const TQDate& from, const TQDate& to)
00360 {
00361   TQDate start;
00362   TQValueList<Week> weeks;
00363 
00364   // The TQDate weekNumber() method always puts monday as the first day of the
00365   // week.
00366   //
00367   // Not that it matters here, but week 1 always includes the first Thursday
00368   // of the year.  For example, January 1, 2000 was a Saturday, so
00369   // TQDate(2000,1,1).weekNumber() returns 52.
00370 
00371   // Since report always shows a full week, we generate a full week of dates,
00372   // even if from and to are the same date.  The week starts on the day
00373   // that is set in the locale settings.
00374   start = from.addDays(
00375       -((7 - TDEGlobal::locale()->weekStartDay() + from.dayOfWeek()) % 7));
00376 
00377   for (TQDate d = start; d <= to; d = d.addDays(7))
00378     weeks.append(Week(d));
00379 
00380   return weeks;
00381 }
00382