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 <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 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 += KGlobal::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(KGlobal::locale()->formatDate(from)) 00317 .arg(KGlobal::locale()->formatDate(to)); 00318 retval += cr; 00319 retval += i18n("Printed on: %1") 00320 .arg(KGlobal::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(KGlobal::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 - KGlobal::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