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