|
|
|
/*
|
|
|
|
* This file only:
|
|
|
|
* Copyright (C) 2003 Mark Bucciarelli <mark@hubcapconsutling.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the
|
|
|
|
* Free Software Foundation, Inc.
|
|
|
|
* 51 Franklin Street, Fifth Floor
|
|
|
|
* Boston, MA 02110-1301 USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
// #include <iostream>
|
|
|
|
|
|
|
|
#include <tqdatetime.h>
|
|
|
|
#include <tqpaintdevicemetrics.h>
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqmap.h>
|
|
|
|
|
|
|
|
#include <kglobal.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <klocale.h> // i18n
|
|
|
|
#include <event.h>
|
|
|
|
|
|
|
|
#include "karmutility.h" // formatTime()
|
|
|
|
#include "timekard.h"
|
|
|
|
#include "task.h"
|
|
|
|
#include "taskview.h"
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
const int taskWidth = 40;
|
|
|
|
const int timeWidth = 6;
|
|
|
|
const int totalTimeWidth = 7;
|
|
|
|
const int reportWidth = taskWidth + timeWidth;
|
|
|
|
|
|
|
|
const TQString cr = TQString::tqfromLatin1("\n");
|
|
|
|
|
|
|
|
TQString TimeKard::totalsAsText(TaskView* taskview, bool justThisTask, WhichTime which)
|
|
|
|
// Print the total Times as text. If justThisTask, use activeTask, else, all tasks
|
|
|
|
{
|
|
|
|
kdDebug(5970) << "Entering TimeKard::totalsAsText" << endl;
|
|
|
|
TQString retval;
|
|
|
|
TQString line;
|
|
|
|
TQString buf;
|
|
|
|
long sum;
|
|
|
|
|
|
|
|
line.fill('-', reportWidth);
|
|
|
|
line += cr;
|
|
|
|
|
|
|
|
// header
|
|
|
|
retval += i18n("Task Totals") + cr;
|
|
|
|
retval += KGlobal::locale()->formatDateTime(TQDateTime::tqcurrentDateTime());
|
|
|
|
retval += cr + cr;
|
|
|
|
retval += TQString(TQString::tqfromLatin1("%1 %2"))
|
|
|
|
.tqarg(i18n("Time"), timeWidth)
|
|
|
|
.tqarg(i18n("Task"));
|
|
|
|
retval += cr;
|
|
|
|
retval += line;
|
|
|
|
|
|
|
|
// tasks
|
|
|
|
if (taskview->current_item())
|
|
|
|
{
|
|
|
|
if (justThisTask)
|
|
|
|
{
|
|
|
|
// a task's total time includes the sum of all subtask times
|
|
|
|
sum = which == TotalTime ? taskview->current_item()->totalTime() : taskview->current_item()->sessionTime();
|
|
|
|
printTask(taskview->current_item(), retval, 0, which);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sum = 0;
|
|
|
|
for (Task* task= taskview->item_at_index(0); task;
|
|
|
|
task= task->nextSibling())
|
|
|
|
{
|
|
|
|
kdDebug(5970) << "Copying task " << task->name() << endl;
|
|
|
|
int time = which == TotalTime ? task->totalTime() : task->totalSessionTime();
|
|
|
|
sum += time;
|
|
|
|
if ( time || task->firstChild() )
|
|
|
|
printTask(task, retval, 0, which);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// total
|
|
|
|
buf.fill('-', reportWidth);
|
|
|
|
retval += TQString(TQString::tqfromLatin1("%1")).tqarg(buf, timeWidth) + cr;
|
|
|
|
retval += TQString(TQString::tqfromLatin1("%1 %2"))
|
|
|
|
.tqarg(formatTime(sum),timeWidth)
|
|
|
|
.tqarg(i18n("Total"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
retval += i18n("No tasks.");
|
|
|
|
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print out "<indent for level> <task total> <task>", for task and subtasks. Used by totalsAsText.
|
|
|
|
void TimeKard::printTask(Task *task, TQString &s, int level, WhichTime which)
|
|
|
|
{
|
|
|
|
TQString buf;
|
|
|
|
|
|
|
|
s += buf.fill(' ', level);
|
|
|
|
s += TQString(TQString::tqfromLatin1("%1 %2"))
|
|
|
|
.tqarg(formatTime(which == TotalTime?task->totalTime():task->totalSessionTime()), timeWidth)
|
|
|
|
.tqarg(task->name());
|
|
|
|
s += cr;
|
|
|
|
|
|
|
|
for (Task* subTask = task->firstChild();
|
|
|
|
subTask;
|
|
|
|
subTask = subTask->nextSibling())
|
|
|
|
{
|
|
|
|
int time = which == TotalTime ? subTask->totalTime() : subTask->totalSessionTime();
|
|
|
|
if (time)
|
|
|
|
printTask(subTask, s, level+1, which);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void TimeKard::printTaskHistory(const Task *task,
|
|
|
|
const TQMap<TQString,long>& taskdaytotals,
|
|
|
|
TQMap<TQString,long>& daytotals,
|
|
|
|
const TQDate& from,
|
|
|
|
const TQDate& to,
|
|
|
|
const int level, TQString& s, bool totalsOnly)
|
|
|
|
{
|
|
|
|
long sectionsum = 0;
|
|
|
|
for ( TQDate day = from; day <= to; day = day.addDays(1) )
|
|
|
|
{
|
|
|
|
TQString daykey = day.toString(TQString::tqfromLatin1("yyyyMMdd"));
|
|
|
|
TQString daytaskkey = TQString::tqfromLatin1("%1_%2")
|
|
|
|
.tqarg(daykey)
|
|
|
|
.tqarg(task->uid());
|
|
|
|
|
|
|
|
if (taskdaytotals.contains(daytaskkey))
|
|
|
|
{
|
|
|
|
if ( !totalsOnly )
|
|
|
|
{
|
|
|
|
s += TQString::tqfromLatin1("%1")
|
|
|
|
.tqarg(formatTime(taskdaytotals[daytaskkey]/60), timeWidth);
|
|
|
|
}
|
|
|
|
sectionsum += taskdaytotals[daytaskkey]; // in seconds
|
|
|
|
|
|
|
|
if (daytotals.contains(daykey))
|
|
|
|
daytotals.replace(daykey, daytotals[daykey] + taskdaytotals[daytaskkey]);
|
|
|
|
else
|
|
|
|
daytotals.insert(daykey, taskdaytotals[daytaskkey]);
|
|
|
|
}
|
|
|
|
else if ( !totalsOnly )
|
|
|
|
{
|
|
|
|
TQString buf;
|
|
|
|
buf.fill(' ', timeWidth);
|
|
|
|
s += buf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Total for task this section (e.g. week)
|
|
|
|
s += TQString::tqfromLatin1("%1").tqarg(formatTime(sectionsum/60), totalTimeWidth);
|
|
|
|
|
|
|
|
// Task name
|
|
|
|
TQString buf;
|
|
|
|
s += buf.fill(' ', level + 1);
|
|
|
|
s += TQString::tqfromLatin1("%1").tqarg(task->name());
|
|
|
|
s += cr;
|
|
|
|
|
|
|
|
for (Task* subTask = task->firstChild();
|
|
|
|
subTask;
|
|
|
|
subTask = subTask->nextSibling())
|
|
|
|
{
|
|
|
|
// recursive
|
|
|
|
printTaskHistory(subTask, taskdaytotals, daytotals, from, to, level+1, s, totalsOnly);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString TimeKard::sectionHistoryAsText(
|
|
|
|
TaskView* taskview,
|
|
|
|
const TQDate& sectionFrom, const TQDate& sectionTo,
|
|
|
|
const TQDate& from, const TQDate& to,
|
|
|
|
const TQString& name,
|
|
|
|
bool justThisTask, bool totalsOnly)
|
|
|
|
{
|
|
|
|
|
|
|
|
const int sectionReportWidth = taskWidth + ( totalsOnly ? 0 : sectionFrom.daysTo(sectionTo) * timeWidth ) + totalTimeWidth;
|
|
|
|
assert( sectionReportWidth > 0 );
|
|
|
|
TQString line;
|
|
|
|
line.fill('-', sectionReportWidth);
|
|
|
|
line += cr;
|
|
|
|
|
|
|
|
TQValueList<HistoryEvent> events;
|
|
|
|
if ( sectionFrom < from && sectionTo > to)
|
|
|
|
{
|
|
|
|
events = taskview->getHistory(from, to);
|
|
|
|
}
|
|
|
|
else if ( sectionFrom < from )
|
|
|
|
{
|
|
|
|
events = taskview->getHistory(from, sectionTo);
|
|
|
|
}
|
|
|
|
else if ( sectionTo > to)
|
|
|
|
{
|
|
|
|
events = taskview->getHistory(sectionFrom, to);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
events = taskview->getHistory(sectionFrom, sectionTo);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQMap<TQString, long> taskdaytotals;
|
|
|
|
TQMap<TQString, long> daytotals;
|
|
|
|
|
|
|
|
// Build lookup dictionary used to output data in table cells. keys are
|
|
|
|
// in this format: YYYYMMDD_NNNNNN, where Y = year, M = month, d = day and
|
|
|
|
// NNNNN = the VTODO uid. The value is the total seconds logged against
|
|
|
|
// that task on that day. Note the UID is the todo id, not the event id,
|
|
|
|
// so times are accumulated for each task.
|
|
|
|
for (TQValueList<HistoryEvent>::iterator event = events.begin(); event != events.end(); ++event)
|
|
|
|
{
|
|
|
|
TQString daykey = (*event).start().date().toString(TQString::tqfromLatin1("yyyyMMdd"));
|
|
|
|
TQString daytaskkey = TQString::tqfromLatin1("%1_%2")
|
|
|
|
.tqarg(daykey)
|
|
|
|
.tqarg((*event).todoUid());
|
|
|
|
|
|
|
|
if (taskdaytotals.contains(daytaskkey))
|
|
|
|
taskdaytotals.replace(daytaskkey,
|
|
|
|
taskdaytotals[daytaskkey] + (*event).duration());
|
|
|
|
else
|
|
|
|
taskdaytotals.insert(daytaskkey, (*event).duration());
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString retval;
|
|
|
|
// section name (e.g. week name)
|
|
|
|
retval += cr + cr;
|
|
|
|
TQString buf;
|
|
|
|
if ( name.length() < (unsigned int)sectionReportWidth )
|
|
|
|
buf.fill(' ', int((sectionReportWidth - name.length()) / 2));
|
|
|
|
retval += buf + name + cr;
|
|
|
|
|
|
|
|
if ( !totalsOnly )
|
|
|
|
{
|
|
|
|
// day headings
|
|
|
|
for (TQDate day = sectionFrom; day <= sectionTo; day = day.addDays(1))
|
|
|
|
{
|
|
|
|
retval += TQString::tqfromLatin1("%1").tqarg(day.day(), timeWidth);
|
|
|
|
}
|
|
|
|
retval += cr;
|
|
|
|
retval += line;
|
|
|
|
}
|
|
|
|
|
|
|
|
// the tasks
|
|
|
|
if (events.empty())
|
|
|
|
{
|
|
|
|
retval += " ";
|
|
|
|
retval += i18n("No hours logged.");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (justThisTask)
|
|
|
|
{
|
|
|
|
printTaskHistory(taskview->current_item(), taskdaytotals, daytotals,
|
|
|
|
sectionFrom, sectionTo, 0, retval, totalsOnly);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (Task* task= taskview->current_item(); task;
|
|
|
|
task= task->nextSibling())
|
|
|
|
{
|
|
|
|
printTaskHistory(task, taskdaytotals, daytotals,
|
|
|
|
sectionFrom, sectionTo, 0, retval, totalsOnly);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
retval += line;
|
|
|
|
|
|
|
|
// per-day totals at the bottom of the section
|
|
|
|
long sum = 0;
|
|
|
|
for (TQDate day = sectionFrom; day <= sectionTo; day = day.addDays(1))
|
|
|
|
{
|
|
|
|
TQString daykey = day.toString(TQString::tqfromLatin1("yyyyMMdd"));
|
|
|
|
|
|
|
|
if (daytotals.contains(daykey))
|
|
|
|
{
|
|
|
|
if ( !totalsOnly )
|
|
|
|
{
|
|
|
|
retval += TQString::tqfromLatin1("%1")
|
|
|
|
.tqarg(formatTime(daytotals[daykey]/60), timeWidth);
|
|
|
|
}
|
|
|
|
sum += daytotals[daykey]; // in seconds
|
|
|
|
}
|
|
|
|
else if ( !totalsOnly )
|
|
|
|
{
|
|
|
|
buf.fill(' ', timeWidth);
|
|
|
|
retval += buf;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
retval += TQString::tqfromLatin1("%1 %2")
|
|
|
|
.tqarg(formatTime(sum/60), totalTimeWidth)
|
|
|
|
.tqarg(i18n("Total"));
|
|
|
|
}
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString TimeKard::historyAsText(TaskView* taskview, const TQDate& from,
|
|
|
|
const TQDate& to, bool justThisTask, bool perWeek, bool totalsOnly)
|
|
|
|
{
|
|
|
|
// header
|
|
|
|
TQString retval;
|
|
|
|
retval += totalsOnly ? i18n("Task Totals") : i18n("Task History");
|
|
|
|
retval += cr;
|
|
|
|
retval += i18n("From %1 to %2")
|
|
|
|
.tqarg(KGlobal::locale()->formatDate(from))
|
|
|
|
.tqarg(KGlobal::locale()->formatDate(to));
|
|
|
|
retval += cr;
|
|
|
|
retval += i18n("Printed on: %1")
|
|
|
|
.tqarg(KGlobal::locale()->formatDateTime(TQDateTime::tqcurrentDateTime()));
|
|
|
|
|
|
|
|
if ( perWeek )
|
|
|
|
{
|
|
|
|
// output one time card table for each week in the date range
|
|
|
|
TQValueList<Week> weeks = Week::weeksFromDateRange(from, to);
|
|
|
|
for (TQValueList<Week>::iterator week = weeks.begin(); week != weeks.end(); ++week)
|
|
|
|
{
|
|
|
|
retval += sectionHistoryAsText( taskview, (*week).start(), (*week).end(), from, to, (*week).name(), justThisTask, totalsOnly );
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
retval += sectionHistoryAsText( taskview, from, to, from, to, "", justThisTask, totalsOnly );
|
|
|
|
}
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
Week::Week() {}
|
|
|
|
|
|
|
|
Week::Week(TQDate from)
|
|
|
|
{
|
|
|
|
_start = from;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDate Week::start() const
|
|
|
|
{
|
|
|
|
return _start;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQDate Week::end() const
|
|
|
|
{
|
|
|
|
return _start.addDays(6);
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString Week::name() const
|
|
|
|
{
|
|
|
|
return i18n("Week of %1").tqarg(KGlobal::locale()->formatDate(start()));
|
|
|
|
}
|
|
|
|
|
|
|
|
TQValueList<Week> Week::weeksFromDateRange(const TQDate& from, const TQDate& to)
|
|
|
|
{
|
|
|
|
TQDate start;
|
|
|
|
TQValueList<Week> weeks;
|
|
|
|
|
|
|
|
// The TQDate weekNumber() method always puts monday as the first day of the
|
|
|
|
// week.
|
|
|
|
//
|
|
|
|
// Not that it matters here, but week 1 always includes the first Thursday
|
|
|
|
// of the year. For example, January 1, 2000 was a Saturday, so
|
|
|
|
// TQDate(2000,1,1).weekNumber() returns 52.
|
|
|
|
|
|
|
|
// Since report always shows a full week, we generate a full week of dates,
|
|
|
|
// even if from and to are the same date. The week starts on the day
|
|
|
|
// that is set in the locale settings.
|
|
|
|
start = from.addDays(
|
|
|
|
-((7 - KGlobal::locale()->weekStartDay() + from.dayOfWeek()) % 7));
|
|
|
|
|
|
|
|
for (TQDate d = start; d <= to; d = d.addDays(7))
|
|
|
|
weeks.append(Week(d));
|
|
|
|
|
|
|
|
return weeks;
|
|
|
|
}
|
|
|
|
|