karm

timekard.cpp
1/*
2 * This file only:
3 * Copyright (C) 2003 Mark Bucciarelli <mark@hubcapconsutling.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the
17 * Free Software Foundation, Inc.
18 * 51 Franklin Street, Fifth Floor
19 * Boston, MA 02110-1301 USA.
20 *
21 */
22
23// #include <iostream>
24
25#include <tqdatetime.h>
26#include <tqpaintdevicemetrics.h>
27#include <tqpainter.h>
28#include <tqmap.h>
29
30#include <tdeglobal.h>
31#include <kdebug.h>
32#include <tdelocale.h> // i18n
33#include <event.h>
34
35#include "karmutility.h" // formatTime()
36#include "timekard.h"
37#include "task.h"
38#include "taskview.h"
39#include <assert.h>
40
41const int taskWidth = 40;
42const int timeWidth = 6;
43const int totalTimeWidth = 7;
44const int reportWidth = taskWidth + timeWidth;
45
46const TQString cr = TQString::fromLatin1("\n");
47
48TQString TimeKard::totalsAsText(TaskView* taskview, bool justThisTask, WhichTime which)
49// Print the total Times as text. If justThisTask, use activeTask, else, all tasks
50{
51 kdDebug(5970) << "Entering TimeKard::totalsAsText" << endl;
52 TQString retval;
53 TQString line;
54 TQString buf;
55 long sum;
56
57 line.fill('-', reportWidth);
58 line += cr;
59
60 // header
61 retval += i18n("Task Totals") + cr;
62 retval += TDEGlobal::locale()->formatDateTime(TQDateTime::currentDateTime());
63 retval += cr + cr;
64 retval += TQString(TQString::fromLatin1("%1 %2"))
65 .arg(i18n("Time"), timeWidth)
66 .arg(i18n("Task"));
67 retval += cr;
68 retval += line;
69
70 // tasks
71 if (taskview->current_item())
72 {
73 if (justThisTask)
74 {
75 // a task's total time includes the sum of all subtask times
76 sum = which == TotalTime ? taskview->current_item()->totalTime() : taskview->current_item()->sessionTime();
77 printTask(taskview->current_item(), retval, 0, which);
78 }
79 else
80 {
81 sum = 0;
82 for (Task* task= taskview->item_at_index(0); task;
83 task= task->nextSibling())
84 {
85 kdDebug(5970) << "Copying task " << task->name() << endl;
86 int time = which == TotalTime ? task->totalTime() : task->totalSessionTime();
87 sum += time;
88 if ( time || task->firstChild() )
89 printTask(task, retval, 0, which);
90 }
91 }
92
93 // total
94 buf.fill('-', reportWidth);
95 retval += TQString(TQString::fromLatin1("%1")).arg(buf, timeWidth) + cr;
96 retval += TQString(TQString::fromLatin1("%1 %2"))
97 .arg(formatTime(sum),timeWidth)
98 .arg(i18n("Total"));
99 }
100 else
101 retval += i18n("No tasks.");
102
103 return retval;
104}
105
106// Print out "<indent for level> <task total> <task>", for task and subtasks. Used by totalsAsText.
107void TimeKard::printTask(Task *task, TQString &s, int level, WhichTime which)
108{
109 TQString buf;
110
111 s += buf.fill(' ', level);
112 s += TQString(TQString::fromLatin1("%1 %2"))
113 .arg(formatTime(which == TotalTime?task->totalTime():task->totalSessionTime()), timeWidth)
114 .arg(task->name());
115 s += cr;
116
117 for (Task* subTask = task->firstChild();
118 subTask;
119 subTask = subTask->nextSibling())
120 {
121 int time = which == TotalTime ? subTask->totalTime() : subTask->totalSessionTime();
122 if (time)
123 printTask(subTask, s, level+1, which);
124 }
125}
126
127void TimeKard::printTaskHistory(const Task *task,
128 const TQMap<TQString,long>& taskdaytotals,
129 TQMap<TQString,long>& daytotals,
130 const TQDate& from,
131 const TQDate& to,
132 const int level, TQString& s, bool totalsOnly)
133{
134 long sectionsum = 0;
135 for ( TQDate day = from; day <= to; day = day.addDays(1) )
136 {
137 TQString daykey = day.toString(TQString::fromLatin1("yyyyMMdd"));
138 TQString daytaskkey = TQString::fromLatin1("%1_%2")
139 .arg(daykey)
140 .arg(task->uid());
141
142 if (taskdaytotals.contains(daytaskkey))
143 {
144 if ( !totalsOnly )
145 {
146 s += TQString::fromLatin1("%1")
147 .arg(formatTime(taskdaytotals[daytaskkey]/60), timeWidth);
148 }
149 sectionsum += taskdaytotals[daytaskkey]; // in seconds
150
151 if (daytotals.contains(daykey))
152 daytotals.replace(daykey, daytotals[daykey] + taskdaytotals[daytaskkey]);
153 else
154 daytotals.insert(daykey, taskdaytotals[daytaskkey]);
155 }
156 else if ( !totalsOnly )
157 {
158 TQString buf;
159 buf.fill(' ', timeWidth);
160 s += buf;
161 }
162 }
163
164 // Total for task this section (e.g. week)
165 s += TQString::fromLatin1("%1").arg(formatTime(sectionsum/60), totalTimeWidth);
166
167 // Task name
168 TQString buf;
169 s += buf.fill(' ', level + 1);
170 s += TQString::fromLatin1("%1").arg(task->name());
171 s += cr;
172
173 for (Task* subTask = task->firstChild();
174 subTask;
175 subTask = subTask->nextSibling())
176 {
177 // recursive
178 printTaskHistory(subTask, taskdaytotals, daytotals, from, to, level+1, s, totalsOnly);
179 }
180}
181
182TQString TimeKard::sectionHistoryAsText(
183 TaskView* taskview,
184 const TQDate& sectionFrom, const TQDate& sectionTo,
185 const TQDate& from, const TQDate& to,
186 const TQString& name,
187 bool justThisTask, bool totalsOnly)
188{
189
190 const int sectionReportWidth = taskWidth + ( totalsOnly ? 0 : sectionFrom.daysTo(sectionTo) * timeWidth ) + totalTimeWidth;
191 assert( sectionReportWidth > 0 );
192 TQString line;
193 line.fill('-', sectionReportWidth);
194 line += cr;
195
196 TQValueList<HistoryEvent> events;
197 if ( sectionFrom < from && sectionTo > to)
198 {
199 events = taskview->getHistory(from, to);
200 }
201 else if ( sectionFrom < from )
202 {
203 events = taskview->getHistory(from, sectionTo);
204 }
205 else if ( sectionTo > to)
206 {
207 events = taskview->getHistory(sectionFrom, to);
208 }
209 else
210 {
211 events = taskview->getHistory(sectionFrom, sectionTo);
212 }
213
214 TQMap<TQString, long> taskdaytotals;
215 TQMap<TQString, long> daytotals;
216
217 // Build lookup dictionary used to output data in table cells. keys are
218 // in this format: YYYYMMDD_NNNNNN, where Y = year, M = month, d = day and
219 // NNNNN = the VTODO uid. The value is the total seconds logged against
220 // that task on that day. Note the UID is the todo id, not the event id,
221 // so times are accumulated for each task.
222 for (TQValueList<HistoryEvent>::iterator event = events.begin(); event != events.end(); ++event)
223 {
224 TQString daykey = (*event).start().date().toString(TQString::fromLatin1("yyyyMMdd"));
225 TQString daytaskkey = TQString::fromLatin1("%1_%2")
226 .arg(daykey)
227 .arg((*event).todoUid());
228
229 if (taskdaytotals.contains(daytaskkey))
230 taskdaytotals.replace(daytaskkey,
231 taskdaytotals[daytaskkey] + (*event).duration());
232 else
233 taskdaytotals.insert(daytaskkey, (*event).duration());
234 }
235
236 TQString retval;
237 // section name (e.g. week name)
238 retval += cr + cr;
239 TQString buf;
240 if ( name.length() < (unsigned int)sectionReportWidth )
241 buf.fill(' ', int((sectionReportWidth - name.length()) / 2));
242 retval += buf + name + cr;
243
244 if ( !totalsOnly )
245 {
246 // day headings
247 for (TQDate day = sectionFrom; day <= sectionTo; day = day.addDays(1))
248 {
249 retval += TQString::fromLatin1("%1").arg(day.day(), timeWidth);
250 }
251 retval += cr;
252 retval += line;
253 }
254
255 // the tasks
256 if (events.empty())
257 {
258 retval += " ";
259 retval += i18n("No hours logged.");
260 }
261 else
262 {
263 if (justThisTask)
264 {
265 printTaskHistory(taskview->current_item(), taskdaytotals, daytotals,
266 sectionFrom, sectionTo, 0, retval, totalsOnly);
267 }
268 else
269 {
270 for (Task* task= taskview->current_item(); task;
271 task= task->nextSibling())
272 {
273 printTaskHistory(task, taskdaytotals, daytotals,
274 sectionFrom, sectionTo, 0, retval, totalsOnly);
275 }
276 }
277 retval += line;
278
279 // per-day totals at the bottom of the section
280 long sum = 0;
281 for (TQDate day = sectionFrom; day <= sectionTo; day = day.addDays(1))
282 {
283 TQString daykey = day.toString(TQString::fromLatin1("yyyyMMdd"));
284
285 if (daytotals.contains(daykey))
286 {
287 if ( !totalsOnly )
288 {
289 retval += TQString::fromLatin1("%1")
290 .arg(formatTime(daytotals[daykey]/60), timeWidth);
291 }
292 sum += daytotals[daykey]; // in seconds
293 }
294 else if ( !totalsOnly )
295 {
296 buf.fill(' ', timeWidth);
297 retval += buf;
298 }
299 }
300
301 retval += TQString::fromLatin1("%1 %2")
302 .arg(formatTime(sum/60), totalTimeWidth)
303 .arg(i18n("Total"));
304 }
305 return retval;
306}
307
308TQString TimeKard::historyAsText(TaskView* taskview, const TQDate& from,
309 const TQDate& to, bool justThisTask, bool perWeek, bool totalsOnly)
310{
311 // header
312 TQString retval;
313 retval += totalsOnly ? i18n("Task Totals") : i18n("Task History");
314 retval += cr;
315 retval += i18n("From %1 to %2")
316 .arg(TDEGlobal::locale()->formatDate(from))
317 .arg(TDEGlobal::locale()->formatDate(to));
318 retval += cr;
319 retval += i18n("Printed on: %1")
320 .arg(TDEGlobal::locale()->formatDateTime(TQDateTime::currentDateTime()));
321
322 if ( perWeek )
323 {
324 // output one time card table for each week in the date range
325 TQValueList<Week> weeks = Week::weeksFromDateRange(from, to);
326 for (TQValueList<Week>::iterator week = weeks.begin(); week != weeks.end(); ++week)
327 {
328 retval += sectionHistoryAsText( taskview, (*week).start(), (*week).end(), from, to, (*week).name(), justThisTask, totalsOnly );
329 }
330 } else
331 {
332 retval += sectionHistoryAsText( taskview, from, to, from, to, "", justThisTask, totalsOnly );
333 }
334 return retval;
335}
336
338
339Week::Week(TQDate from)
340{
341 _start = from;
342}
343
344TQDate Week::start() const
345{
346 return _start;
347}
348
349TQDate Week::end() const
350{
351 return _start.addDays(6);
352}
353
354TQString Week::name() const
355{
356 return i18n("Week of %1").arg(TDEGlobal::locale()->formatDate(start()));
357}
358
359TQValueList<Week> Week::weeksFromDateRange(const TQDate& from, const TQDate& to)
360{
361 TQDate start;
362 TQValueList<Week> weeks;
363
364 // The TQDate weekNumber() method always puts monday as the first day of the
365 // week.
366 //
367 // Not that it matters here, but week 1 always includes the first Thursday
368 // of the year. For example, January 1, 2000 was a Saturday, so
369 // TQDate(2000,1,1).weekNumber() returns 52.
370
371 // Since report always shows a full week, we generate a full week of dates,
372 // even if from and to are the same date. The week starts on the day
373 // that is set in the locale settings.
374 start = from.addDays(
375 -((7 - TDEGlobal::locale()->weekStartDay() + from.dayOfWeek()) % 7));
376
377 for (TQDate d = start; d <= to; d = d.addDays(7))
378 weeks.append(Week(d));
379
380 return weeks;
381}
382
Container and interface for the tasks.
Definition: taskview.h:43
Task * current_item() const
Return the current item in the view, cast to a Task pointer.
Definition: taskview.cpp:177
Task * item_at_index(int i)
Return the i'th item (zero-based), cast to a Task pointer.
Definition: taskview.cpp:182
TQValueList< HistoryEvent > getHistory(const TQDate &from, const TQDate &to) const
Return list of start/stop events for given date range.
Definition: taskview.cpp:782
A class representing a task.
Definition: task.h:42
TQString name() const
returns the name of this task.
Definition: task.h:162
TQString uid() const
Return unique iCalendar Todo ID for this task.
Definition: task.h:70
Task * firstChild() const
return parent Task or null in case of TaskView.
Definition: task.h:60
TQString totalsAsText(TaskView *taskview, bool justThisTask, WhichTime which)
Generates ascii text of task totals, for current task on down.
Definition: timekard.cpp:48
TQString historyAsText(TaskView *taskview, const TQDate &from, const TQDate &to, bool justThisTask, bool perWeek, bool totalsOnly)
Generates ascii text of weekly task history, for current task on down.
Definition: timekard.cpp:308
TQString name() const
Return the name of the week.
Definition: timekard.cpp:354
Week()
Need an empty constructor to use in a TQValueList.
Definition: timekard.cpp:337
static TQValueList< Week > weeksFromDateRange(const TQDate &from, const TQDate &to)
Returns a list of weeks for the given date range.
Definition: timekard.cpp:359