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 
41 const int taskWidth = 40;
42 const int timeWidth = 6;
43 const int totalTimeWidth = 7;
44 const int reportWidth = taskWidth + timeWidth;
45 
46 const TQString cr = TQString::fromLatin1("\n");
47 
48 TQString 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.
107 void 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 
127 void 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 
182 TQString 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 
308 TQString 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 
339 Week::Week(TQDate from)
340 {
341  _start = from;
342 }
343 
344 TQDate Week::start() const
345 {
346  return _start;
347 }
348 
349 TQDate Week::end() const
350 {
351  return _start.addDays(6);
352 }
353 
354 TQString Week::name() const
355 {
356  return i18n("Week of %1").arg(TDEGlobal::locale()->formatDate(start()));
357 }
358 
359 TQValueList<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
Task * firstChild() const
return parent Task or null in case of TaskView.
Definition: task.h:60
TQString uid() const
Return unique iCalendar Todo ID for this task.
Definition: task.h:70
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