/* Generate gtt-parsed html output for GnoTime - a time tracker
* Copyright (C) 2001,2002,2003,2004 Linas Vepstas
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include "app.h"
#include "ctree.h"
#include "cur-proj.h"
#include "gtt.h"
#include "ghtml.h"
#include "ghtml-deprecated.h"
#include "proj.h"
#include "query.h"
#include "util.h"
/* Design problems:
* The way this is currently defined, there is no type safety, and
* we could easily be slipping the wrong addresses to the wrong places.
* We really should add a type identifier to the the project,
* task and interval objects. This type should be in the cdr
* part of the object, so that it will stop recursion and list-walking.
* (The utility routines in gtt.scm walk lists and trees; thus,
* putting a non-pointer in the cdr is an effective way to stop the
* recursion.) The 'daily-totals' object already follows this
* convention.
*
* Another major design problem is that formatting for date/time
* strings is totally not user-settable. Most of the formatting
* needs to be moved over to scheme code, and we just need to let
* the functions below return plain-old time_t seconds.
*/
/* ============================================================== */
/* Seems to me like guile screwed the pooch; we need a global! */
GttGhtml *ghtml_guile_global_hack = NULL;
static SCM
do_ret_did_query (GttGhtml *ghtml)
{
return SCM_BOOL (ghtml->did_query);
}
static SCM
ret_did_query (void)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_ret_did_query (ghtml);
}
/* ============================================================== */
/* This routine will reverse the order of a scheme list */
static SCM
reverse_list (SCM node_list)
{
SCM rc, node;
rc = SCM_EOL;
while (FALSE == SCM_NULLP(node_list))
{
node = SCM_CAR (node_list);
rc = scm_cons (node, rc);
node_list = SCM_CDR (node_list);
}
return rc;
}
/* ============================================================== */
/* A routine to recursively apply a scheme form to a list of
* KVP key names. It returns the result of the apply. */
typedef enum
{
GTT_NONE=0,
GTT_PRJ,
GTT_TASK,
GTT_IVL
} PtrType;
static SCM
do_apply_based_on_type (GttGhtml *ghtml, SCM node,
PtrType cur_type,
SCM (*str_func)(GttGhtml *, const char *),
SCM (*prj_func)(GttGhtml *, GttProject *),
SCM (*tsk_func)(GttGhtml *, GttTask *),
SCM (*ivl_func)(GttGhtml *, GttInterval *))
{
/* Either a 'symbol or a "quoted string" */
if (SCM_SYMBOLP(node) || SCM_STRINGP (node))
{
SCM rc = SCM_EOL;
char *str = SCM_STRING_CHARS (node);
int len = SCM_STRING_LENGTH (node);
if ((0kvp) return SCM_EOL;
val = kvp_frame_get_slot (ghtml->kvp, key);
if (!val) return SCM_EOL;
str = kvp_value_get_string (val);
if (!str) return SCM_EOL;
return scm_mem2string (str, strlen (str));
}
static SCM
ret_kvp_str (SCM key)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_string (ghtml, key, kvp_cb);
}
/* ============================================================== */
/* This routine accepts an SCM node, and 'prints' it out.
* (or tries to). It knows how to print numbers strings and lists.
*/
static SCM
do_show_scm (GttGhtml *ghtml, SCM node)
{
size_t len;
char * str = NULL;
if (NULL == ghtml->write_stream) return SCM_EOL;
/* Need to test for numbers first, since later tests
* may core-dump guile-1.3.4 if the node was in fact a number. */
if (SCM_NUMBERP(node))
{
char buf[132];
double x;
long ix;
x = scm_num2dbl (node, "GnoTime::do_show_scm");
ix = (long) x;
/* If the number is representable in 32 bits,
* and if the fractional part is so small its
* not representable as a double, then print it
* as an integer. */
if ((INT_MAX > x) && (-INT_MAX < x) &&
((x-(double)ix) < 4.0*x*DBL_EPSILON) )
{
sprintf (buf, "%ld", ix);
}
else
{
sprintf (buf, "%26.18g", x);
}
(ghtml->write_stream) (ghtml, buf, strlen(buf), ghtml->user_data);
}
else
/* either a 'symbol or a "quoted string" */
if (SCM_SYMBOLP(node) || SCM_STRINGP (node))
{
str = SCM_STRING_CHARS (node);
len = SCM_STRING_LENGTH (node);
if (0write_stream) (ghtml, str, len, ghtml->user_data);
}
else
if (SCM_CONSP(node))
{
SCM node_list = node;
do
{
node = SCM_CAR (node_list);
do_show_scm (ghtml, node);
node_list = SCM_CDR (node_list);
}
while (SCM_CONSP(node_list));
do_show_scm (ghtml, node_list);
}
else
if (SCM_BOOLP(node))
{
const char *str;
if (SCM_FALSEP(node)) str = _("False");
else str = _("True");
(ghtml->write_stream) (ghtml, str, strlen(str), ghtml->user_data);
}
else
if (SCM_NULLP(node))
{
/* No op; maybe this should be a warning? */
}
else
{
g_warning ("Don't know how to gtt-show this type\n");
}
/* We could return the printed string, but I'm not sure why.. */
return SCM_EOL;
}
static SCM
show_scm (SCM node_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_show_scm (ghtml, node_list);
}
/* ============================================================== */
/* Cheesy hack, this returns a pointer to the currently
* selected project as a ulong. Its baaaad, but acheives its
* purpose for now. Its baad because the C pointer is not
* currently checked before use. This could lead to core dumps
* if the scheme code was bad. It sure would be nice to be
* able to check that the pointer is a valid pointer to a gtt
* project. For example, maybe projects and tasks should be
* GObjects, and then would could check the cast. Later.
* --linas
*/
static SCM
do_ret_project (GttGhtml *ghtml, GttProject *prj)
{
SCM node,rc;
rc = scm_ulong2num ((unsigned long) prj);
/* Label the pointer with a type identifier */
node = scm_mem2string ("gtt-project-ptr", 15);
rc = scm_cons (rc, node);
return rc;
}
/* The 'selected project' is the project highlighted by the
* focus row in the main window.
*/
static SCM
do_ret_selected_project (GttGhtml *ghtml)
{
GttProject *prj = ctree_get_focus_project (global_ptw);
return do_ret_project (ghtml, prj);
}
static SCM
ret_selected_project (void)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_ret_selected_project (ghtml);
}
static SCM
do_ret_linked_project (GttGhtml *ghtml)
{
return do_ret_project (ghtml, ghtml->prj);
}
static SCM
ret_linked_project (void)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_ret_linked_project (ghtml);
}
/* ============================================================== */
/* Control printing of internal links */
static SCM
do_set_links_on (GttGhtml *ghtml)
{
if (FALSE == ghtml->really_hide_links)
{
ghtml->show_links = TRUE;
}
return SCM_EOL;
}
static SCM
set_links_on (void)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_set_links_on (ghtml);
}
static SCM
do_set_links_off (GttGhtml *ghtml)
{
ghtml->show_links = FALSE;
return SCM_EOL;
}
static SCM
set_links_off (void)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_set_links_off (ghtml);
}
/* ============================================================== */
static SCM
do_include_file_scm (GttGhtml *ghtml, SCM node)
{
/* either a 'symbol or a "quoted string" */
if (SCM_SYMBOLP(node) || SCM_STRINGP (node))
{
const char * filepath = SCM_STRING_CHARS (node);
filepath = gtt_ghtml_resolve_path(filepath, ghtml->ref_path);
gtt_ghtml_display (ghtml, filepath, NULL);
}
else
if (SCM_CONSP(node))
{
SCM node_list = node;
do
{
node = SCM_CAR (node_list);
do_include_file_scm (ghtml, node);
node_list = SCM_CDR (node_list);
}
while (SCM_CONSP(node_list));
do_include_file_scm (ghtml, node_list);
}
else
if (SCM_NULLP(node))
{
/* No op; maybe this should be a warning? */
}
else
{
g_warning ("Don't know how to gtt-include this type\n");
}
/* We could return the printed string, but I'm not sure why.. */
return SCM_EOL;
}
static SCM
include_file_scm (SCM node_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_include_file_scm (ghtml, node_list);
}
/* ============================================================== */
/** Converts a g_list of pointers into a typed SCM list. This is
* a generic utility. It returns rc, where (car rc) is a list of
* pointers, and (cdr rc) is the type-string that identifies the
* type of the pointers. */
static SCM
g_list_to_scm (GList * gplist, const char * type)
{
SCM rc, node;
GList *n;
/* Get a pointer to null */
rc = SCM_EOL;
if (gplist)
{
/* Get tail of g_list */
for (n= gplist; n->next; n=n->next) {}
gplist = n;
/* Walk backwards, creating a scheme list */
for (n= gplist; n; n=n->prev)
{
node = scm_ulong2num ((unsigned long) n->data);
rc = scm_cons (node, rc);
}
}
/* Prepend type label */
node = scm_mem2string (type, strlen (type));
rc = scm_cons (rc, node);
return rc;
}
/* ============================================================== */
/* Return a list of all of the projects */
static SCM
do_ret_project_list (GttGhtml *ghtml, GList *proj_list)
{
SCM rc;
GList *n;
/* Get a pointer to null */
rc = SCM_EOL;
if (!proj_list) return rc;
/* XXX should use g_list_to_scm() here */
/* XXX should use type identifier gtt-project-list */
/* Find the tail */
for (n= proj_list; n->next; n=n->next) {}
proj_list = n;
/* Walk backwards, creating a scheme list */
for (n= proj_list; n; n=n->prev)
{
GttProject *prj = n->data;
SCM node;
#if 0
GList *subprjs;
/* Handle sub-projects, if any, before the project itself */
subprjs = gtt_project_get_children (prj);
if (subprjs)
{
node = do_ret_project_list (ghtml, subprjs);
rc = scm_cons (node, rc);
}
#endif
node = scm_ulong2num ((unsigned long) prj);
rc = scm_cons (node, rc);
}
return rc;
}
static SCM
ret_projects (void)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
/* Get list of all top-level projects */
GList *proj_list = gtt_project_list_get_list(master_list);
return do_ret_project_list (ghtml, proj_list);
}
static SCM
ret_query_projects (void)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_ret_project_list (ghtml, ghtml->query_result);
}
/* ============================================================== */
/* Return a list of all subprojects of a project */
static SCM
do_ret_subprjs (GttGhtml *ghtml, GttProject *prj)
{
GList *proj_list;
/* Get list of subprojects. */
proj_list = gtt_project_get_children (prj);
if (!proj_list) return SCM_EOL;
return do_ret_project_list (ghtml, proj_list);
}
static SCM
ret_project_subprjs(SCM proj_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_project (ghtml, proj_list, do_ret_subprjs);
}
/* ============================================================== */
/* Return the parent of a project */
static SCM
get_proj_parent_scm (GttGhtml *ghtml, GttProject *prj)
{
GttProject *parent = gtt_project_get_parent (prj);
return do_ret_project (ghtml, parent);
}
static SCM
ret_project_parent (SCM proj_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_project (ghtml, proj_list, get_proj_parent_scm);
}
/* ============================================================== */
/* Return a list of all of the tasks of a project */
static SCM
do_ret_tasks (GttGhtml *ghtml, GttProject *prj)
{
SCM rc;
GList *n, *task_list;
/* Get a pointer to null */
rc = SCM_EOL;
if (!prj) return rc;
/* Get list of tasks, then get tail */
task_list = gtt_project_get_tasks (prj);
if (!task_list) return rc;
/* XXX should use g_list_to_scm() here */
for (n= task_list; n->next; n=n->next) {}
task_list = n;
/* Walk backwards, creating a scheme list */
for (n= task_list; n; n=n->prev)
{
GttTask *tsk = n->data;
SCM node;
node = scm_ulong2num ((unsigned long) tsk);
rc = scm_cons (node, rc);
}
return rc;
}
static SCM
ret_tasks (SCM proj_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_project (ghtml, proj_list, do_ret_tasks);
}
/* ============================================================== */
/* Return a list of all of the intervals of a task */
static SCM
do_ret_intervals (GttGhtml *ghtml, GttTask *tsk)
{
SCM rc;
GList *n, *ivl_list;
/* Oddball hack to make interval datestamp printing work nicely */
ghtml->last_ivl_time = 0;
/* Get a pointer to null */
rc = SCM_EOL;
if (!tsk) return rc;
/* XXX should use g_list_to_scm() here */
/* Get list of intervals, then get tail */
ivl_list = gtt_task_get_intervals (tsk);
if (!ivl_list) return rc;
for (n= ivl_list; n->next; n=n->next) {}
ivl_list = n;
/* Walk backwards, creating a scheme list */
for (n= ivl_list; n; n=n->prev)
{
GttInterval *ivl = n->data;
SCM node;
node = scm_ulong2num ((unsigned long) ivl);
rc = scm_cons (node, rc);
}
return rc;
}
static SCM
ret_intervals (SCM task_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_task (ghtml, task_list, do_ret_intervals);
}
/* ============================================================== */
/* Return a list of date handles for accessing daily totals */
static SCM
do_ret_daily_totals (GttGhtml *ghtml, GttProject *prj)
{
SCM rc, rpt;
int i;
GArray *arr;
time_t earliest;
struct tm tday;
/* Get a pointer to null */
rc = SCM_EOL;
if (!prj) return rc;
/* Get the project data */
arr = gtt_project_get_daily_buckets (prj, TRUE);
if (!arr) return rc;
earliest = gtt_project_get_earliest_start (prj, TRUE);
/* Format the start date */
localtime_r (&earliest, &tday);
tday.tm_mday --;
for (i=0; i< arr->len; i++)
{
GttBucket *bu;
char buff[100];
SCM node;
time_t rptdate, secs;
tday.tm_mday ++;
bu = & g_array_index (arr, GttBucket, i);
secs = bu->total;
/* Skip days for which no time has been spent */
if (0 == secs) continue;
rpt = SCM_EOL;
/* Append the list of tasks and intervals for this day */
node = g_list_to_scm (bu->intervals, "gtt-interval-list");
rpt = scm_cons (node, rpt);
node = g_list_to_scm (bu->tasks, "gtt-task-list");
rpt = scm_cons (node, rpt);
/* XXX should use time_t, and srfi-19 to print, and have a type label */
/* Print time spent on project this day */
qof_print_hours_elapsed_buff (buff, 100, secs, TRUE);
node = scm_mem2string (buff, strlen (buff));
rpt = scm_cons (node, rpt);
/* XXX report date should be time_t in the middle of the interval */
/* Print date */
rptdate = mktime (&tday);
qof_print_date_buff (buff, 100, rptdate);
node = scm_mem2string (buff, strlen (buff));
rpt = scm_cons (node, rpt);
/* Put a data type in the cdr slot */
node = scm_mem2string ("gtt-daily", 9);
rpt = scm_cons (rpt, node);
rc = scm_cons (rpt, rc);
}
g_array_free (arr, TRUE);
return rc;
}
static SCM
ret_daily_totals (SCM proj_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_project (ghtml, proj_list, do_ret_daily_totals);
}
/* ============================================================== */
/* Define a set of subroutines that accept a scheme list of projects,
* applies the gtt_project function on each, and then returns a
* scheme list containing the results.
*
* For example, ret_project_title() takes a scheme list of gtt
* projects, gets the project title for each, and then returns
* a scheme list of the project titles.
*/
#define RET_PROJECT_SIMPLE(RET_FUNC,DO_SIMPLE) \
static SCM \
RET_FUNC (SCM proj_list) \
{ \
GttGhtml *ghtml = ghtml_guile_global_hack; \
return do_apply_on_project (ghtml, proj_list, DO_SIMPLE); \
}
#define RET_PROJECT_STR(RET_FUNC,GTT_GETTER) \
static SCM \
GTT_GETTER##_scm (GttGhtml *ghtml, GttProject *prj) \
{ \
const char * str = GTT_GETTER (prj); \
if (NULL == str) return SCM_EOL; \
return scm_mem2string (str, strlen (str)); \
} \
RET_PROJECT_SIMPLE(RET_FUNC,GTT_GETTER##_scm)
#define RET_PROJECT_LONG(RET_FUNC,GTT_GETTER) \
static SCM \
GTT_GETTER##_scm (GttGhtml *ghtml, GttProject *prj) \
{ \
long i = GTT_GETTER (prj); \
return scm_long2num (i); \
} \
RET_PROJECT_SIMPLE(RET_FUNC,GTT_GETTER##_scm)
\
#define RET_PROJECT_ULONG(RET_FUNC,GTT_GETTER) \
static SCM \
GTT_GETTER##_scm (GttGhtml *ghtml, GttProject *prj) \
{ \
unsigned long i = GTT_GETTER (prj); \
return scm_ulong2num (i); \
} \
RET_PROJECT_SIMPLE(RET_FUNC,GTT_GETTER##_scm)
RET_PROJECT_STR (ret_project_title, gtt_project_get_title)
RET_PROJECT_STR (ret_project_desc, gtt_project_get_desc)
RET_PROJECT_STR (ret_project_notes, gtt_project_get_notes)
RET_PROJECT_ULONG (ret_project_est_start, gtt_project_get_estimated_start)
RET_PROJECT_ULONG (ret_project_est_end, gtt_project_get_estimated_end)
RET_PROJECT_ULONG (ret_project_due_date, gtt_project_get_due_date)
RET_PROJECT_LONG (ret_project_sizing, gtt_project_get_sizing)
RET_PROJECT_LONG (ret_project_percent, gtt_project_get_percent_complete)
/* ============================================================== */
/* Handle ret_project_title_link in the almost-standard way,
* i.e. as
* RET_PROJECT_STR (ret_project_title, gtt_project_get_title)
* except that we need to handle url links as well.
*/
static SCM
get_project_title_link_scm (GttGhtml *ghtml, GttProject *prj)
{
if (ghtml->show_links)
{
GString *str;
str = g_string_new (NULL);
g_string_append_printf (str, "", (long) prj);
g_string_append (str, gtt_project_get_title (prj));
g_string_append (str, "");
return scm_mem2string (str->str, str->len);
}
else
{
const char * str = gtt_project_get_title (prj);
return scm_mem2string (str, strlen (str));
}
}
RET_PROJECT_SIMPLE (ret_project_title_link, get_project_title_link_scm)
/* ============================================================== */
static const char *
get_urgency (GttProject *prj)
{
GttRank rank = gtt_project_get_urgency (prj);
switch (rank)
{
case GTT_LOW: return _("Low");
case GTT_MEDIUM: return _("Normal");
case GTT_HIGH: return _("Urgent");
case GTT_UNDEFINED: return _("Undefined");
}
return _("Undefined");
}
static const char *
get_importance (GttProject *prj)
{
GttRank rank = gtt_project_get_importance (prj);
switch (rank)
{
case GTT_LOW: return _("Low");
case GTT_MEDIUM: return _("Medium");
case GTT_HIGH: return _("Important");
case GTT_UNDEFINED: return _("Undefined");
}
return _("Undefined");
}
static const char *
get_status (GttProject *prj)
{
GttProjectStatus status = gtt_project_get_status (prj);
switch (status)
{
case GTT_NO_STATUS: return _("No Status");
case GTT_NOT_STARTED: return _("Not Started");
case GTT_IN_PROGRESS: return _("In Progress");
case GTT_ON_HOLD: return _("On Hold");
case GTT_CANCELLED: return _("Cancelled");
case GTT_COMPLETED: return _("Completed");
}
return _("Undefined");
}
RET_PROJECT_STR (ret_project_urgency, get_urgency)
RET_PROJECT_STR (ret_project_importance, get_importance)
RET_PROJECT_STR (ret_project_status, get_status)
/* ============================================================== */
/* Define a set of subroutines that accept a scheme list of tasks,
* applies the gtt_task function on each, and then returns a
* scheme list containing the results.
*
* For example, ret_task_memo() takes a scheme list of gtt
* tasks, gets the task memo for each, and then returns
* a scheme list of the task memos.
*/
#define RET_TASK_SIMPLE(RET_FUNC,GTT_GETTER) \
static SCM \
RET_FUNC (SCM task_list) \
{ \
GttGhtml *ghtml = ghtml_guile_global_hack; \
return do_apply_on_task (ghtml, task_list, GTT_GETTER##_scm); \
}
#define RET_TASK_STR(RET_FUNC,GTT_GETTER) \
static SCM \
GTT_GETTER##_scm (GttGhtml *ghtml, GttTask *tsk) \
{ \
const char * str = GTT_GETTER (tsk); \
return scm_mem2string (str, strlen (str)); \
} \
\
static SCM \
RET_FUNC (SCM task_list) \
{ \
GttGhtml *ghtml = ghtml_guile_global_hack; \
return do_apply_on_task (ghtml, task_list, GTT_GETTER##_scm); \
}
RET_TASK_STR (ret_task_notes, gtt_task_get_notes)
/* ============================================================== */
/* Handle ret_task_memo in the almost-standard way,
* i.e. as
* RET_TASK_STR (ret_task_memo, gtt_task_get_memo)
* except that we need to handle url links as well.
*/
static SCM
get_task_memo_scm (GttGhtml *ghtml, GttTask *tsk)
{
if (ghtml && ghtml->show_links)
{
GString *str;
str = g_string_new (NULL);
g_string_append_printf (str, "", (long)tsk);
g_string_append (str, gtt_task_get_memo (tsk));
g_string_append (str, "");
return scm_mem2string (str->str, str->len);
}
else
{
const char * str = gtt_task_get_memo (tsk);
return scm_mem2string (str, strlen (str));
}
}
static SCM
ret_task_memo (SCM task_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_task (ghtml, task_list, get_task_memo_scm);
}
/* ============================================================== */
/* Handle ret_task_parent in the almost-standard way,
* except that we return a pointer object.
*/
static SCM
get_task_parent_scm (GttGhtml *ghtml, GttTask *tsk)
{
GttProject *prj = gtt_task_get_parent (tsk);
return do_ret_project (ghtml, prj);
}
static SCM
ret_task_parent (SCM task_list)
{
GttGhtml *ghtml = ghtml_guile_global_hack;
return do_apply_on_task (ghtml, task_list, get_task_parent_scm);
}
/* ============================================================== */
static const char *
task_get_billstatus (GttTask *tsk)
{
switch (gtt_task_get_billstatus (tsk))
{
case GTT_HOLD: return _("Hold");
case GTT_BILL: return _("Bill");
case GTT_PAID: return _("Paid");
default: return "";
}
return "";
};
static const char *
task_get_billable (GttTask *tsk)
{
switch (gtt_task_get_billable (tsk))
{
case GTT_BILLABLE: return _("Billable");
case GTT_NOT_BILLABLE: return _("Not Billable");
case GTT_NO_CHARGE: return _("No Charge");
default: return "";
}
return "";
};
static const char *
task_get_billrate (GttTask *tsk)
{
switch (gtt_task_get_billrate (tsk))
{
case GTT_REGULAR: return _("Regular");
case GTT_OVERTIME: return _("Overtime");
case GTT_OVEROVER: return _("Double Overtime");
case GTT_FLAT_FEE: return _("Flat Fee");
default: return "";
}
return "";
};
static SCM
task_get_time_str_scm (GttGhtml *ghtml, GttTask *tsk)
{
time_t task_secs;
char buff[100];
task_secs = gtt_task_get_secs_ever(tsk);
qof_print_hours_elapsed_buff (buff, 100, task_secs, TRUE);
return scm_mem2string (buff, strlen (buff));
}
static SCM
task_get_earliest_str_scm (GttGhtml *ghtml, GttTask *tsk)
{
char buff[100];
time_t task_date = gtt_task_get_secs_earliest(tsk);
size_t len = qof_print_date_time_buff (buff, 100, task_date);
return scm_mem2string (buff, len);
}
static SCM
task_get_latest_str_scm (GttGhtml *ghtml, GttTask *tsk)
{
char buff[100];
time_t task_date = gtt_task_get_secs_latest(tsk);
size_t len = qof_print_date_time_buff (buff, 100, task_date);
return scm_mem2string (buff, len);
}
static SCM
task_get_value_str_scm (GttGhtml *ghtml, GttTask *tsk)
{
time_t task_secs;
char buff[100];
double value;
GttProject *prj;
task_secs = gtt_task_get_secs_ever(tsk);
value = ((double) task_secs) / 3600.0;
prj = gtt_task_get_parent (tsk);
switch (gtt_task_get_billrate (tsk))
{
case GTT_REGULAR: value *= gtt_project_get_billrate (prj); break;
case GTT_OVERTIME: value *= gtt_project_get_overtime_rate (prj); break;
case GTT_OVEROVER: value *= gtt_project_get_overover_rate (prj); break;
case GTT_FLAT_FEE: value = gtt_project_get_flat_fee (prj); break;
default: value = 0.0;
}
/* hack alert should use i18n currency/monetary printing */
snprintf (buff, 100, "$%.2f", value+0.0049);
return scm_mem2string (buff, strlen (buff));
}
RET_TASK_STR (ret_task_billstatus, task_get_billstatus)
RET_TASK_STR (ret_task_billable, task_get_billable)
RET_TASK_STR (ret_task_billrate, task_get_billrate)
RET_TASK_SIMPLE (ret_task_time_str, task_get_time_str)
RET_TASK_SIMPLE (ret_task_earliest_str, task_get_earliest_str)
RET_TASK_SIMPLE (ret_task_latest_str, task_get_latest_str)
RET_TASK_SIMPLE (ret_task_value_str, task_get_value_str)
/* ============================================================== */
#define RET_IVL_SIMPLE(RET_FUNC,GTT_GETTER) \
static SCM \
RET_FUNC (SCM ivl_list) \
{ \
GttGhtml *ghtml = ghtml_guile_global_hack; \
return do_apply_on_interval (ghtml, ivl_list, GTT_GETTER##_scm); \
}
#define RET_IVL_STR(RET_FUNC,GTT_GETTER) \
static SCM \
GTT_GETTER##_scm (GttGhtml *ghtml, GttInterval *ivl) \
{ \
const char * str = GTT_GETTER (ivl); \
return scm_mem2string (str, strlen (str)); \
} \
RET_IVL_SIMPLE(RET_FUNC,GTT_GETTER)
#define RET_IVL_ULONG(RET_FUNC,GTT_GETTER) \
static SCM \
GTT_GETTER##_scm (GttGhtml *ghtml, GttInterval *ivl) \
{ \
unsigned long i = GTT_GETTER (ivl); \
return scm_ulong2num (i); \
} \
RET_IVL_SIMPLE(RET_FUNC,GTT_GETTER)
RET_IVL_ULONG (ret_ivl_start, gtt_interval_get_start)
RET_IVL_ULONG (ret_ivl_stop, gtt_interval_get_stop)
RET_IVL_ULONG (ret_ivl_fuzz, gtt_interval_get_fuzz)
static SCM
get_ivl_elapsed_str_scm (GttGhtml *ghtml, GttInterval *ivl)
{
char buff[100];
time_t elapsed;
elapsed = gtt_interval_get_stop (ivl);
elapsed -= gtt_interval_get_start (ivl);
qof_print_hours_elapsed_buff (buff, 100, elapsed, TRUE);
return scm_mem2string (buff, strlen (buff));
}
RET_IVL_SIMPLE (ret_ivl_elapsed_str, get_ivl_elapsed_str);
static SCM
get_ivl_start_stop_common_str_scm (GttGhtml *ghtml, GttInterval *ivl,
time_t starp, gboolean prt_date)
{
char buff[100];
if (prt_date) {
qof_print_date_buff (buff, 100, starp);
} else {
qof_print_time_buff (buff, 100, starp);
}
GString *str;
str = g_string_new (NULL);
if (ghtml->show_links)
{
g_string_append_printf (str, "", (long) ivl);
}
g_string_append (str, buff);
if (ghtml->show_links)
{
g_string_append (str, "");
}
return scm_mem2string (str->str, str->len);
}
static SCM
get_ivl_same_day_start_scm (GttGhtml *ghtml, GttInterval *ivl)
{
gboolean prt_date = TRUE;
time_t start, prev_stop;
/* Caution! use of the last_ivl_time thing makes this
* stateful in a way that may be surprising !
*/
start = gtt_interval_get_start (ivl);
prev_stop = ghtml->last_ivl_time;
ghtml->last_ivl_time = start;
if (0 != prev_stop)
{
prt_date = qof_is_same_day(start, prev_stop);
}
return SCM_BOOL (prt_date);
}
RET_IVL_SIMPLE (ret_ivl_same_day_start, get_ivl_same_day_start);
static SCM
get_ivl_same_day_stop_scm (GttGhtml *ghtml, GttInterval *ivl)
{
gboolean prt_date = TRUE;
time_t stop, prev_start;
/* Caution! use of the last_ivl_time thing makes this
* stateful in a way that may be surprising !
*/
stop = gtt_interval_get_stop (ivl);
prev_start = ghtml->last_ivl_time;
ghtml->last_ivl_time = stop;
if (0 != prev_start)
{
prt_date = qof_is_same_day(prev_start, stop);
}
return SCM_BOOL (prt_date);
}
RET_IVL_SIMPLE (ret_ivl_same_day_stop, get_ivl_same_day_stop);
static SCM
get_ivl_start_date_str_scm (GttGhtml *ghtml, GttInterval *ivl)
{
time_t start = gtt_interval_get_start (ivl);
return get_ivl_start_stop_common_str_scm (ghtml, ivl, start, 1);
}
RET_IVL_SIMPLE (ret_ivl_start_date_str, get_ivl_start_date_str);
static SCM
get_ivl_start_time_str_scm (GttGhtml *ghtml, GttInterval *ivl)
{
time_t start = gtt_interval_get_start (ivl);
return get_ivl_start_stop_common_str_scm (ghtml, ivl, start, 0);
}
RET_IVL_SIMPLE (ret_ivl_start_time_str, get_ivl_start_time_str);
static SCM
get_ivl_stop_date_str_scm (GttGhtml *ghtml, GttInterval *ivl)
{
time_t stop = gtt_interval_get_stop (ivl);
return get_ivl_start_stop_common_str_scm (ghtml, ivl, stop, 1);
}
RET_IVL_SIMPLE (ret_ivl_stop_date_str, get_ivl_stop_date_str);
static SCM
get_ivl_stop_time_str_scm (GttGhtml *ghtml, GttInterval *ivl)
{
time_t stop = gtt_interval_get_stop (ivl);
return get_ivl_start_stop_common_str_scm (ghtml, ivl, stop, 0);
}
RET_IVL_SIMPLE (ret_ivl_stop_time_str, get_ivl_stop_time_str);
static SCM
get_ivl_fuzz_str_scm (GttGhtml *ghtml, GttInterval *ivl)
{
char buff[100];
qof_print_hours_elapsed_buff (buff, 100, gtt_interval_get_fuzz (ivl), TRUE);
return scm_mem2string (buff, strlen (buff));
}
RET_IVL_SIMPLE (ret_ivl_fuzz_str, get_ivl_fuzz_str);
/* ============================================================== */
static SCM
my_catch_handler (void *data, SCM tag, SCM throw_args)
{
printf ("Error: GnoTime caught error during scheme parse\n");
if (SCM_SYMBOLP(tag))
{
char * str = SCM_SYMBOL_CHARS (tag);
printf ("\tScheme error was: %s\n", str);
}
scm_backtrace();
SCM fmt = scm_makfrom0str ("~S");
SCM s_str = scm_simple_format (SCM_BOOL_F, fmt, SCM_LIST1(throw_args));
printf ("\tthrow_args=%s\n", SCM_STRING_CHARS (s_str));
return SCM_EOL;
}
/* ============================================================== */
/* Parse style-sheet type links: links that look like
*
*/
static void
process_link (GttGhtml *ghtml, const gchar *str)
{
/* no-op for now, just copy it into the window */
if (ghtml->write_stream)
{
(ghtml->write_stream) (ghtml, "user_data);
size_t nr = strlen (str);
(ghtml->write_stream) (ghtml, str, nr, ghtml->user_data);
(ghtml->write_stream) (ghtml, ">", 1, ghtml->user_data);
}
}
/* ============================================================== */
void
gtt_ghtml_display (GttGhtml *ghtml, const char *filepath,
GttProject *prj)
{
GString *template;
char *start, *end, *scmstart, *comstart, *linkstart;
size_t nr;
if (!ghtml) return;
if (prj) ghtml->prj = prj;
if (!filepath && (0==ghtml->open_count))
{
if (ghtml->error)
{
(ghtml->error) (ghtml, 404, NULL, ghtml->user_data);
}
return;
}
/* Try to get the ghtml file ... */
GnomeVFSResult result;
GnomeVFSHandle *handle;
result = gnome_vfs_open (&handle, filepath, GNOME_VFS_OPEN_READ);
if ((GNOME_VFS_OK != result) && (0==ghtml->open_count))
{
if (ghtml->error)
{
(ghtml->error) (ghtml, 404, filepath, ghtml->user_data);
}
return;
}
ghtml->ref_path = filepath;
/* Read in the whole file. Hopefully its not huge */
template = g_string_new (NULL);
while (GNOME_VFS_OK == result)
{
#define BUFF_SIZE 4000
char buff[BUFF_SIZE+1];
GnomeVFSFileSize bytes_read;
result = gnome_vfs_read (handle, buff, BUFF_SIZE, &bytes_read);
if (0 >= bytes_read) break; /* EOF I presume */
buff[bytes_read] = 0x0;
g_string_append (template, buff);
}
gnome_vfs_close (handle);
/* ugh. gag. choke. puke. */
ghtml_guile_global_hack = ghtml;
#ifdef DEBUG
/* Load predefined scheme forms. We do this here only when debugging,
* since they may have changed since just a few minutes ago. */
scm_c_primitive_load (gtt_ghtml_resolve_path("gtt.scm", NULL));
#endif
/* Now open the output stream for writing */
if (ghtml->open_stream && (0==ghtml->open_count))
{
(ghtml->open_stream) (ghtml, ghtml->user_data);
}
ghtml->open_count ++;
/* Loop over input text, looking for scheme markup and
* sgml comments. */
start = template->str;
while (start)
{
/* Look for scheme markup */
scmstart = strstr (start, ", and try to handle stylesheets. */
linkstart = strstr (start, "");
if (end)
{
end +=3;
}
/* write everything that we got before the markup */
if (ghtml->write_stream)
{
nr = comstart - start;
(ghtml->write_stream) (ghtml, start, nr, ghtml->user_data);
}
start = end;
continue;
}
/* Look for , and try to handle stylesheets. */
if (linkstart && linkstart == end)
{
end = strstr (linkstart, ">");
if (end)
{
*end = 0;
end += 1;
}
/* write everything that we got before the markup */
if (ghtml->write_stream)
{
nr = linkstart - start;
(ghtml->write_stream) (ghtml, start, nr, ghtml->user_data);
}
/* dispatch and handle */
process_link (ghtml, linkstart+5);
start = end;
continue;
}
/* Look for termination of scm markup */
if (scmstart && scmstart == end)
{
end = strstr (scmstart, "?>");
if (end)
{
*end = 0;
end += 2;
}
/* write everything that we got before the markup */
if (ghtml->write_stream)
{
nr = scmstart - start;
(ghtml->write_stream) (ghtml, start, nr, ghtml->user_data);
}
/* dispatch and handle */
scmstart +=5;
// scm_c_eval_string (scmstart);
gh_eval_str_with_catch (scmstart, my_catch_handler);
start = end;
continue;
}
/* If we got to here, we didn't find any tags. Just output */
if (ghtml->write_stream)
{
nr = strlen (start);
(ghtml->write_stream) (ghtml, start, nr, ghtml->user_data);
}
break;
}
ghtml->open_count --;
if (ghtml->close_stream && (0==ghtml->open_count))
{
(ghtml->close_stream) (ghtml, ghtml->user_data);
}
}
/* ============================================================== */
/* Register callback handlers for various internally defined
* scheme forms.
*/
static int is_inited = 0;
static void
register_procs (void)
{
scm_c_define_gsubr("gtt-show", 1, 0, 0, show_scm);
scm_c_define_gsubr("gtt-include", 1, 0, 0, include_file_scm);
scm_c_define_gsubr("gtt-kvp-str", 1, 0, 0, ret_kvp_str);
scm_c_define_gsubr("gtt-linked-project", 0, 0, 0, ret_linked_project);
scm_c_define_gsubr("gtt-selected-project", 0, 0, 0, ret_selected_project);
scm_c_define_gsubr("gtt-projects", 0, 0, 0, ret_projects);
scm_c_define_gsubr("gtt-query-results", 0, 0, 0, ret_query_projects);
scm_c_define_gsubr("gtt-did-query", 0, 0, 0, ret_did_query);
scm_c_define_gsubr("gtt-tasks", 1, 0, 0, ret_tasks);
scm_c_define_gsubr("gtt-intervals", 1, 0, 0, ret_intervals);
scm_c_define_gsubr("gtt-daily-totals", 1, 0, 0, ret_daily_totals);
scm_c_define_gsubr("gtt-links-on", 0, 0, 0, set_links_on);
scm_c_define_gsubr("gtt-links-off", 0, 0, 0, set_links_off);
scm_c_define_gsubr("gtt-project-subprojects", 1, 0, 0, ret_project_subprjs);
scm_c_define_gsubr("gtt-project-parent", 1, 0, 0, ret_project_parent);
scm_c_define_gsubr("gtt-project-title", 1, 0, 0, ret_project_title);
scm_c_define_gsubr("gtt-project-title-link", 1, 0, 0, ret_project_title_link);
scm_c_define_gsubr("gtt-project-desc", 1, 0, 0, ret_project_desc);
scm_c_define_gsubr("gtt-project-notes", 1, 0, 0, ret_project_notes);
scm_c_define_gsubr("gtt-project-urgency", 1, 0, 0, ret_project_urgency);
scm_c_define_gsubr("gtt-project-importance", 1, 0, 0, ret_project_importance);
scm_c_define_gsubr("gtt-project-status", 1, 0, 0, ret_project_status);
scm_c_define_gsubr("gtt-project-estimated-start", 1, 0, 0, ret_project_est_start);
scm_c_define_gsubr("gtt-project-estimated-end", 1, 0, 0, ret_project_est_end);
scm_c_define_gsubr("gtt-project-due-date", 1, 0, 0, ret_project_due_date);
scm_c_define_gsubr("gtt-project-sizing", 1, 0, 0, ret_project_sizing);
scm_c_define_gsubr("gtt-project-percent-complete", 1, 0, 0, ret_project_percent);
scm_c_define_gsubr("gtt-task-memo", 1, 0, 0, ret_task_memo);
scm_c_define_gsubr("gtt-task-notes", 1, 0, 0, ret_task_notes);
scm_c_define_gsubr("gtt-task-billstatus", 1, 0, 0, ret_task_billstatus);
scm_c_define_gsubr("gtt-task-billable", 1, 0, 0, ret_task_billable);
scm_c_define_gsubr("gtt-task-billrate", 1, 0, 0, ret_task_billrate);
scm_c_define_gsubr("gtt-task-time-str", 1, 0, 0, ret_task_time_str);
scm_c_define_gsubr("gtt-task-earliest-str", 1, 0, 0, ret_task_earliest_str);
scm_c_define_gsubr("gtt-task-latest-str", 1, 0, 0, ret_task_latest_str);
scm_c_define_gsubr("gtt-task-value-str", 1, 0, 0, ret_task_value_str);
scm_c_define_gsubr("gtt-task-parent", 1, 0, 0, ret_task_parent);
scm_c_define_gsubr("gtt-interval-start", 1, 0, 0, ret_ivl_start);
scm_c_define_gsubr("gtt-interval-stop", 1, 0, 0, ret_ivl_stop);
scm_c_define_gsubr("gtt-interval-fuzz", 1, 0, 0, ret_ivl_fuzz);
scm_c_define_gsubr("gtt-interval-elapsed-str", 1, 0, 0, ret_ivl_elapsed_str);
scm_c_define_gsubr("gtt-interval-start-date-str", 1, 0, 0, ret_ivl_start_date_str);
scm_c_define_gsubr("gtt-interval-start-time-str", 1, 0, 0, ret_ivl_start_time_str);
scm_c_define_gsubr("gtt-interval-stop-date-str", 1, 0, 0, ret_ivl_stop_date_str);
scm_c_define_gsubr("gtt-interval-stop-time-str", 1, 0, 0, ret_ivl_stop_time_str);
scm_c_define_gsubr("gtt-interval-same-day-start", 1, 0, 0, ret_ivl_same_day_start);
scm_c_define_gsubr("gtt-interval-same-day-stop", 1, 0, 0, ret_ivl_same_day_stop);
scm_c_define_gsubr("gtt-interval-fuzz-str", 1, 0, 0, ret_ivl_fuzz_str);
}
/* ============================================================== */
GttGhtml *
gtt_ghtml_new (void)
{
GttGhtml *p;
if (!is_inited)
{
is_inited = 1;
register_procs();
/* I think I neeed to do this, not sure */
scm_init_debug();
scm_init_backtrace();
/* Load predefined scheme forms */
scm_c_primitive_load (gtt_ghtml_resolve_path("gtt.scm", NULL));
}
p = g_new0 (GttGhtml, 1);
p->open_count = 0;
p->kvp = NULL;
p->prj = NULL;
p->query_result = NULL;
p->did_query = FALSE;
p->show_links = TRUE;
p->really_hide_links = FALSE;
p->last_ivl_time = 0;
gtt_ghtml_deprecated_init (p);
return p;
}
void
gtt_ghtml_destroy (GttGhtml *p)
{
if (!p) return;
if (p->query_result) g_list_free (p->query_result);
g_free (p);
}
void
gtt_ghtml_set_stream (GttGhtml *p, gpointer ud,
GttGhtmlOpenStream op,
GttGhtmlWriteStream wr,
GttGhtmlCloseStream cl,
GttGhtmlError er)
{
if (!p) return;
p->user_data = ud;
p->open_stream = op;
p->write_stream = wr;
p->close_stream = cl;
p->error = er;
}
/* This sets the over-ride flag, so that no internal links are shown,
* really really for real, when printing out to file.
*/
void
gtt_ghtml_show_links (GttGhtml *p, gboolean sl)
{
if (!p) return;
p->really_hide_links = (FALSE == sl);
p->show_links = sl;
}
/* ===================== END OF FILE ============================== */