/*
 * GTimer
 *
 * Copyright:
 *	(C) 1999 Craig Knudsen, cknudsen@cknudsen.com
 *	See accompanying file "COPYING".
 * 
 *	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
 *
 * Description:
 *	Helps you keep track of time spent on different tasks.
 *
 * Author:
 *	Craig Knudsen, craig@k5n.us, http://www.k5n.us
 *
 * Home Page:
 *	http://www.cknudsen.com/gtimer/
 *
 * History:
 *      07-Aug-2008	Ver 1.1.7 - Support UTF-8 using GTK+ ver. > 2.4.0
 *	15-Jul-2005	Add -weekstart to configure the first day of the
 *			week.  (Russ Allbery)
 *	31-May-2005	When exiting, only change the window size
 *			configuration if gdk_window_get_size succeeds.
 *			(Russ Allbery)
 *	18-Apr-2005	Fully stop timing and update the icons properly
 *			after a reset from the idle window.
 *			Subtract off the five minute initial period when
 *			reverting after an idle.  (Russ Allbery)
 *	17-Apr-2005	Added configurability of the browser. (Russ Allbery)
 *	02-Jan-2004	Add time increment and decrement by one minute.
 *			Add more key shortcuts for increment/decrement.
 *			64-bit fixes for time changes.  (Klaus Ethgen)
 *	02-Jan-2004	Add a callback to clear the paste buffer.  (Klaus
 *			Ethgen)
 *	13-Mar-2003	Added cut/copy/paste in new Edit menu.
 *	06-Mar-2003	Added support for -resume option which will start
 *			timing the same task that was being timed last
 *			time gtimer exited.
 *	01-Mar-2003	Added projects
 *	09-Mar-2000	Improved idle handling.  On revert, the tasks are
 *			no longer reloaded from the data files and the
 *			display will not be resorted.
 *			Added a "resume" option for idles that continues
 *			timing but throws out all the idle time.
 *	21-May-1999	Moved around menu (added tools and help)
 *	19-May-1999	Added ability to check for new version.
 *			This is a really COOL feature :-)
 *	13-May-1999	Allow users to view ChangeLog.
 *	13-May-1999	Added handle box to menu.
 *	04-May-1999	Oops.  Fixed label that said "inc 5 seconds" to
 *			say "inc 5 minutes".
 *	30-Apr-1999	Added support help options: -h, -help, --help
 *	30-Apr-1999	Added support version options: -v, -version, --version
 *	30-Apr-1999	Added support for -start option.
 *	25-Mar-1999	Made animated clock in clist optional.
 *	25-Mar-1999	Applied some WIN32 patches provided by
 *			Thomas Epperly <Thomas.Epperly@aspentech.com>
 *	25-Mar-1999	Fixed bug where -nosplash would cause the main window
 *			to not remember the correct window size.
 *	25-Mar-1999	Fixed bugs when using start/stop/etc. before any
 *			tasks have been created.
 *	24-Mar-1999	Made toolbar optional
 *	24-Mar-1999	Added tearoff menus
 *	18-Mar-1999	Internationalization
 *	18-Mar-1999	Added support for X11 screen saver extension
 *			for idle detect.  (This will detect keyboard
 *			usage rather than just mouse usage.)
 *	16-Mar-1999	Added back in support for GTK 1.0 via autoconf.
 *			(Stole some of the autoconf stuff from xhippo-0.7.)
 *			GTK 1.1/1.2 handles clist double-click events
 *			completely different than GTK 1.0.
 *	24-Feb-1999	Added unhide function (finally... have had the
 *			hide function for a while now.)
 *	11-Feb-1999	Modified accelerator key code.
 *			Fixed to have task pulldown menu with right mouse
 *			button (API changed from GTK+1.0)
 *	08-Feb-1999	Added changes for accelerator keys provided by
 *			Matt Martin <mmartin@Calvin.SFC.Lehigh.Edu>
 *	02-Feb-1999	Remember main window width & height
 *	21-Jan-1999	Added gtk-1.1 support (received patches from
 *			Stephen Webb & Jim Bray).
 *	11-Nov-1998	Added support for hiding tasks
 *	13-Jul-1998	Make double-click stop timing all other tasks and
 *			start timing the selected task.
 *	09-May-1998	Added autosave option
 *	07-May-1998	Finished idle detect option
 *	08-Apr-1998	Set the application icon within the application.
 *			Display a different icon depening on whether or not
 *			we are timing any tasks.
 *	06-Apr-1998	Began adding code for idle detect
 *	05-Apr-1998	Fixed vertical resize (status was resizing)
 *			code submitted by Zach Beane (xach@mint.net)
 *	05-Apr-1998	Added splash screen.
 *	01-Apr-1998	Added status bar and total hours for today at
 *			the bottom of the main window.
 *	18-Mar-1998	Reduce flicker in task list when changing sort
 *			code submitted by Zach Beane (xach@mint.net)
 *	18-Mar-1998	Release 0.95
 *	18-Mar-1998	Added calls to gtk_window_set_wmclass so the windows
 *			behave better for window managers.
 *			code submitted by ObiTuarY (Obituary@cybernet.be)
 *	17-Mar-1998	Fixed handing if $HOME is not defined.
 *	16-Mar-1998	Changed name to "GTimer"
 *			updated application icon
 *	15-Mar-1998	Added memory debugging calls (memdebug.h).
 *			(The memdebug library is something I wrote myself
 *			a few years ago for another project.  It keeps
 *			track of all malloc/realloc/free calls and can
 *			show you what's been allocated but not freed with
 *			the md_print_all () function.  Email me if you
 *			would like this library.)
 *	15-Mar-1998	Added annotate icon and ability to add annotations
 *			(dated comments) to tasks.
 *	13-Mar-1998	Click on column header to sort by that column.
 *	13-Mar-1998	Did some code redesign.  Pulldown menus setup
 *			from the TTPulldown structure.
 *	13-Mar-1998	Add pulldown menu for right mouse click on a task.
 *			Added functions to add time and remove time from
 *			a task.
 *	13-Mar-1998	Add pulldown menu for right mouse click on a task.
 *			code submitted by Zach Beane (xach@mint.net)
 *	13-Mar-1998	Double-click on a task brings up the edit window
 *			code submitted by Zach Beane (xach@mint.net)
 *	13-Mar-1998	Fixed handling of date change when time passes
 *			midnight.
 *	11-Mar-1998	Rearranged UI.  Added toolbar with icons.
 *			Added "Report" to menubar.
 *	25-Feb-1998	Created
 *
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <pwd.h>
#include <time.h>
#include <memory.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <glib.h>

#include <gtk/gtk.h>

#ifdef HAVE_SCREEN_SAVER_EXT
#include <gdk/gdkx.h>
#endif

#include "project.h"
#include "task.h"
#include "gtimer.h"
#include "config.h"
#include "tcpt.h"
#include "http.h"
// PV:
#include "custom-list.h"


#ifdef GTIMER_MEMDEBUG
#include "memdebug/memdebug.h"
#endif

/* check for a new version every 30 days */
#define VERSION_CHECK_INTERVAL		(3600 * 24 * 30)

/* splash icon */
#include "icons/splash.xpm"

/* app icons */
#include "icons/gtimer.xpm"
#include "icons/gtimer2.xpm"

/* timer icon */
#include "icons/clock1.xpm"
#include "icons/clock2.xpm"
#include "icons/clock3.xpm"
#include "icons/clock4.xpm"
#include "icons/clock5.xpm"
#include "icons/clock6.xpm"
#include "icons/clock7.xpm"
#include "icons/clock8.xpm"
#include "icons/blank.xpm"

/* toolbar icons */
#include "icons/start.xpm"
#include "icons/stop.xpm"
#include "icons/stop_all.xpm"
#include "icons/annotate.xpm"
#include "icons/new.xpm"
#include "icons/edit.xpm"

GtkWidget *main_window = NULL;
static GtkWidget *splash_window = NULL;
static int move_to_task = -1;
static GtkWidget *idle_prompt_window = NULL;
static GtkWidget *option_menu_items[4];
static time_t splash_until, last_save;
static int modified_since_save = 0;
static int splash_seconds = 2;
GtkWidget *toolbar = NULL;
GtkWidget *task_list = NULL;
GtkWidget *status = NULL;
guint status_id = 0;
static time_t lastMessageTime = 0;
GtkWidget *total_label = NULL;
static char total_str[20];
GdkPixmap *icons[8], *blankicon, *appicon, *appicon2;
GdkBitmap *icon_masks[8], *blankicon_mask, *appicon_mask, *appicon2_mask;
#if OLD_GTK
#else
GtkAccelGroup* mainag;
#endif
static sockfd connection = -1;
static gint gdk_input_id = -1;
static int version_check_is_auto = 0;
static int cutBuffer = 0; /* Seconds from cut/copy/set-to-zero/revert/resume*/
static int messageDisplayTime = 15; /* how long to leave messages up */

typedef struct {
  char *name;
  int width;
  int max_width;
  GtkJustification justify;
  gboolean resizeable;
  GtkWidget *widget;
} list_column_def;

static int sort_forward = 1;
static int last_sort = 0;
static int rebuilding_list = 0;

int today_year, today_mon, today_mday;
int config_midnight_offset = 0;
int config_max_idle = 0;
int config_idle_enabled = 0;
int config_autosave_enabled = 1;
int config_toolbar_enabled = 1;
int config_animate_enabled = 1;
int config_autosave_interval = (60*15); /* 15 minutes */
int config_start_of_week = 0;

char *taskdir = NULL;
char *config_file = NULL;
char *gtkrc = NULL;
int selected_task = -1;
int pulldown_selected_task = -1; /* task selected with right mouse button */

TaskData **tasks;
int num_tasks = 0;
int num_timing = 0;
TaskData **visible_tasks; /* not hidden */
int num_visible_tasks;

list_column_def task_list_columns[4] = {
  { "Project",	150, 0,	GTK_JUSTIFY_LEFT,	(gboolean)1,	NULL },
  { "Task",	150, 0,	GTK_JUSTIFY_LEFT,	(gboolean)1,	NULL },
  { "Today",	70, 70,	GTK_JUSTIFY_RIGHT,	(gboolean)0,	NULL },
  { "Total",	70, 70,	GTK_JUSTIFY_RIGHT,	(gboolean)0,	NULL }
};

/*
** Local functions
*/
void update_list ();
static void build_list ();
static void about_callback ( GtkAction *act );
static void website_callback ( GtkAction *act );
static void changelog_callback ( GtkAction *act );
static void save_callback ( GtkAction *act );
static void exit_callback ( GtkAction *act );
static void start_callback ( GtkAction *act );
static void switch_to_callback ( GtkWidget *widget, gpointer data );
static void stop_callback ( GtkAction *act );
static void stop_all_callback ( GtkAction *act );
static void task_add_callback ( GtkAction *act );
static void task_edit_callback ( GtkAction *act );
static void task_hide_callback ( GtkAction *act );
static void task_unhide_callback ( GtkAction *act );
static void task_delete_callback ( GtkAction *act );
static void task_cut_callback ( GtkAction *act );
static void task_copy_callback ( GtkAction *act );
static void task_paste_callback ( GtkAction *act );
static void task_clear_callback ( GtkAction *act );
static void increment_time_callback ( GtkWidget *widget, gpointer data );
static void decrement_time_callback ( GtkWidget *widget, gpointer data );
static void project_add_callback ( GtkAction *act );
static void project_edit_callback ( GtkAction *act );
//static void report_callback ( GtkWidget *widget, gpointer data );
static void annotate_callback ( GtkAction *act );
static void idle_reset_callback ( GtkWidget *widget, gpointer data );
static void idle_cancel_callback ( GtkWidget *widget, gpointer data );
static void idle_resume_callback ( GtkWidget *widget, gpointer data );
static void column_selected_callback ( GtkWidget *widget, int col );
static void toolbar_toggle_callback ( GtkToggleAction *act );
static void idle_toggle_callback ( GtkToggleAction *act );
static void autosave_toggle_callback ( GtkToggleAction *act );
static void animate_toggle_callback ( GtkToggleAction *act );
static void check_version_callback ( GtkAction *act );
static void set_browser_callback ( GtkAction *act );

// PV:
static void shift_time_callback ( GtkAction *act );
static void report2_callback ( GtkAction *act );


/*
** Structure for defining the the pulldown menus.
*/
typedef struct {
  int label_index;
  void (*callback)();
  gpointer data;
  gint acckey;
  GdkModifierType accmod;
} TTPulldown;


// PV: move main menu to GtkActionEntry Structures (see GTK+ Reference Manual,
// Example 55 - 59)

// globally accessible UI Manager object
GtkUIManager *uimanager;

// Menu Entries - because it is neccessary to translete each menu item as separate word, I decide
// to assign unique prefix to each menu column and merge these prefixes to menu items.
// Prefix will be hidden during translation (see function mainmenu_transalate).
// Last character in prefix is '|', I hope this character never appears as a part of displayed word.
// If I'll not be right in future, please change it to another character in menu and function
// mainmenu_translate.
// Reccomendation: It will be better _do_not_translate_ these prefixes.

static const GtkActionEntry MM_NormalEntries[] = {
  { "FileMenu", NULL, gettext_noop("MM|_File") },
  { "EditMenu", NULL, gettext_noop("MM|_Edit") },
  { "OptionsMenu", NULL, gettext_noop("MM|_Options") },
  { "TaskMenu", NULL, gettext_noop( "MM|_Task") },
  { "ProjectMenu", NULL, gettext_noop("MM|_Project") },
  { "ReportMenu", NULL, gettext_noop("MM|_Report") },
  { "ToolsMenu", NULL, gettext_noop("MM|_Tools") },
  { "HelpMenu", NULL, gettext_noop("MM|_Help") },
  { "FM_Save", GTK_STOCK_SAVE, gettext_noop("FM|_Save"), "<control>S",
	gettext_noop("Save actual state"), G_CALLBACK(save_callback) },
  { "FM_Exit", GTK_STOCK_QUIT, gettext_noop("FM|_Exit"), "<control>Q",
	gettext_noop("Exit program"), G_CALLBACK(exit_callback) },
  { "EM_Cut", GTK_STOCK_CUT, gettext_noop("EM|C_ut Time"), "<control>X",
	gettext_noop("Cut actual time from line to buffer"), G_CALLBACK(task_cut_callback) },
  { "EM_Copy", GTK_STOCK_COPY, gettext_noop("EM|_Copy Time"), "<control>C",
	gettext_noop("Copy actual time from line to buffer"), G_CALLBACK(task_copy_callback) },
  { "EM_Paste", GTK_STOCK_PASTE, gettext_noop("EM|_Paste Time"), "<control>V",
	gettext_noop("Paste actual time from buffer to actual line"), G_CALLBACK(task_paste_callback) },
  { "EM_Clear", GTK_STOCK_CLEAR, gettext_noop("EM|Clear _Buffer"), NULL,
	gettext_noop("Clear buffer"), G_CALLBACK(task_clear_callback) },
  { "OM_Browser", NULL, gettext_noop("OM|_Browser ..."), NULL,
	gettext_noop("Set your preferred web browser"), G_CALLBACK(set_browser_callback) },
  { "TM_Start", NULL, gettext_noop("TM|_Start Timing"), "<alt>S",
	gettext_noop("Start timing of selected task"), G_CALLBACK(start_callback) },
  { "TM_Stop", NULL, gettext_noop("TM|S_top Timing"), "<alt>X",
	gettext_noop("Stop timing of selected task"), G_CALLBACK(stop_callback) },
  { "TM_Stopall", NULL, gettext_noop("TM|Stop A_ll Timing"), "<alt>T",
	gettext_noop("Stop timing of all running tasks"), G_CALLBACK(stop_all_callback) },
  { "TM_New", NULL, gettext_noop("TM|_New..."), "<control>N",
	gettext_noop("Create new task"), G_CALLBACK(task_add_callback) },
  { "TM_Edit", NULL, gettext_noop("TM|_Edit... "), "<control>E",
	gettext_noop("Edit selected task"), G_CALLBACK(task_edit_callback) },
  { "TM_Annotate", NULL, gettext_noop("TM|_Annotate..."), "<control>A",
	gettext_noop("Add annotation"), G_CALLBACK(annotate_callback) },
  { "TM_Hide", NULL, gettext_noop("TM|_Hide"), "<control>H",
	gettext_noop("Hide task"), G_CALLBACK(task_hide_callback) },
  { "TM_Unhide", NULL, gettext_noop("TM|_Unhide... "), "<control>U",
	gettext_noop("Unhide tasks"), G_CALLBACK(task_unhide_callback) },
  { "TM_Delete", NULL, gettext_noop("TM|_Delete"), "<control>A",
	gettext_noop("Add annotation"), G_CALLBACK(task_delete_callback) },
  { "TM_Inc1", NULL, gettext_noop("TM|_Increment 1 minute"), "<shift><control>I",
	gettext_noop("Increment time"), G_CALLBACK(shift_time_callback) },
  { "TM_Inc5", NULL, gettext_noop("TM|I_ncrement 5 minutes"), "<control>I",
	gettext_noop("Increment time"), G_CALLBACK(shift_time_callback) },
  { "TM_Inc30", NULL, gettext_noop("TM|In_crement 30 minute"), "<control><alt>I",
	gettext_noop("Increment time"), G_CALLBACK(shift_time_callback) },
  { "TM_Dec1", NULL, gettext_noop("TM|Decrement _1 minute"), "<shift><control>D",
	gettext_noop("Decrement time"), G_CALLBACK(shift_time_callback) },
  { "TM_Dec5", NULL, gettext_noop("TM|Decrement _5 minutes"), "<control>D",
	gettext_noop("Decrement time"), G_CALLBACK(shift_time_callback) },
  { "TM_Dec30", NULL, gettext_noop("TM|Decrement _30 minutes"), "<control><alt>D",
	gettext_noop("Decrement time"), G_CALLBACK(shift_time_callback) },
  { "TM_Zero", NULL, gettext_noop("TM|Set to _Zero"), "<control><alt>0",
	gettext_noop("Clear time of selected task"), G_CALLBACK(shift_time_callback) },
  { "PM_New", NULL, gettext_noop("PM|_New..."), NULL,
	gettext_noop("New Project"), G_CALLBACK(project_add_callback) },
  { "PM_Edit", NULL, gettext_noop("PM|_Edit..."), NULL,
	gettext_noop("Edit Project"), G_CALLBACK(project_edit_callback) },
  { "RM_Daily", NULL, gettext_noop("RM|_Daily..."), NULL,
	gettext_noop("Daily Report"), G_CALLBACK(report2_callback) },
  { "RM_Weekly", NULL, gettext_noop("RM|_Weekly..."), NULL,
	gettext_noop("Weekly Report"), G_CALLBACK(report2_callback) },
  { "RM_Monthly", NULL, gettext_noop("RM|_Monthly..."), NULL,
	gettext_noop("Monthly Report"), G_CALLBACK(report2_callback) },
  { "RM_Yearly", NULL, gettext_noop("RM|_Yearly..."), NULL,
	gettext_noop("Yearly Report"), G_CALLBACK(report2_callback) },
  { "TL_Check", NULL, gettext_noop("TL|_Check for New Version..."), NULL,
	gettext_noop("Keep your program updated"), G_CALLBACK(check_version_callback) },
  { "HM_About", NULL, gettext_noop("HM|_About..."), NULL,
	gettext_noop("About"), G_CALLBACK(about_callback) },
  { "HM_ViewChL", NULL, gettext_noop("HM|View _Change log..."), NULL,
	gettext_noop("Changelog"), G_CALLBACK(changelog_callback) },
  { "HM_VisitWeb", NULL, gettext_noop("HM|Visit _Website..."), NULL,
	gettext_noop("Open web browser"), G_CALLBACK(website_callback) }
};

/* PV: Add Toggle Entries
 *   This structure is initialized with FALSE initial values. Before activating it will
 *   be modified according to configuration settings. This is why this structure is not 
 *   declared as 'const'
 */ 

//static const GtkToggleActionEntry MM_ToggleEntries[] = {
static GtkToggleActionEntry MM_ToggleEntries[] = {
  { "OM_Toolbar", NULL, gettext_noop("OM|_Toolbar"), NULL,
	NULL, G_CALLBACK(toolbar_toggle_callback), FALSE },
  { "OM_Animate", NULL, gettext_noop("OM|_Animate"), NULL,
	NULL, G_CALLBACK(animate_toggle_callback), FALSE },
  { "OM_Idle", NULL, gettext_noop("OM|_Idle Detect"), NULL,
	NULL, G_CALLBACK(idle_toggle_callback), FALSE },
  { "OM_Autosave", NULL, gettext_noop("OM|Auto _Save"), NULL,
	NULL, G_CALLBACK(autosave_toggle_callback), FALSE },
};

// according to MM_ToggleEntries set following constants:
#define	MM_TOGGLE_TOOLBAR	0
#define	MM_TOGGLE_ANIMATE	1
#define	MM_TOGGLE_IDLE		2
#define	MM_TOGGLE_AUTOSAVE	3




//PV: XML description:

static const char * menustring =
"<ui>"
"  <menubar name='MainMenu'>"
"    <menu action='FileMenu'>"
"      <menuitem action='FM_Save'/>"
"      <separator/>"
"      <menuitem action='FM_Exit'/>"
"    </menu>"
"    <menu action='EditMenu'>"
"      <menuitem action='EM_Cut'/>"
"      <menuitem action='EM_Copy'/>"
"      <menuitem action='EM_Paste'/>"
"      <menuitem action='EM_Clear'/>"
"    </menu>"
"    <menu action='OptionsMenu'>"
"      <menuitem action='OM_Toolbar'/>"
"      <menuitem action='OM_Animate'/>"
"      <menuitem action='OM_Idle'/>"
"      <menuitem action='OM_Autosave'/>"
"      <menuitem action='OM_Browser'/>"
"    </menu>"
"    <menu action='TaskMenu'>"
"      <menuitem action='TM_Start'/>"
"      <menuitem action='TM_Stop'/>"
"      <menuitem action='TM_Stopall'/>"
"      <separator/>"
"      <menuitem action='TM_New'/>"
"      <menuitem action='TM_Edit'/>"
"      <menuitem action='TM_Annotate'/>"
"      <menuitem action='TM_Hide'/>"
"      <menuitem action='TM_Unhide'/>"
"      <menuitem action='TM_Delete'/>"
"      <separator/>"
"      <menuitem action='TM_Inc1'/>"
"      <menuitem action='TM_Inc5'/>"
"      <menuitem action='TM_Inc30'/>"
"      <menuitem action='TM_Dec1'/>"
"      <menuitem action='TM_Dec5'/>"
"      <menuitem action='TM_Dec30'/>"
"      <menuitem action='TM_Zero'/>"
"    </menu>"
"    <menu action='ProjectMenu'>"
"      <menuitem action='PM_New'/>"
"      <menuitem action='PM_Edit'/>"
"    </menu>"
"    <menu action='ReportMenu'>"
"      <menuitem action='RM_Daily'/>"
"      <menuitem action='RM_Weekly'/>"
"      <menuitem action='RM_Monthly'/>"
"      <menuitem action='RM_Yearly'/>"
"    </menu>"
"    <menu action='ToolsMenu'>"
"      <menuitem action='TL_Check'/>"
"    </menu>"
"    <separator/>"
"    <menu action='HelpMenu'>"
"      <menuitem action='HM_About'/>"
"      <menuitem action='HM_ViewChL'/>"
"      <menuitem action='HM_VisitWeb'/>"
"    </menu>"
"  </menubar>"
"  <popup name='PopupMenu'>"
"      <menuitem action='TM_Start'/>"
"      <menuitem action='TM_Stop'/>"
"      <menuitem action='TM_Stopall'/>"
"      <separator/>"
"      <menuitem action='TM_New'/>"
"      <menuitem action='TM_Edit'/>"
"      <menuitem action='TM_Annotate'/>"
"      <menuitem action='TM_Hide'/>"
"      <menuitem action='TM_Unhide'/>"
"      <menuitem action='TM_Delete'/>"
"      <separator/>"
"      <menuitem action='TM_Inc1'/>"
"      <menuitem action='TM_Inc5'/>"
"      <menuitem action='TM_Inc30'/>"
"      <menuitem action='TM_Dec1'/>"
"      <menuitem action='TM_Dec5'/>"
"      <menuitem action='TM_Dec30'/>"
"      <menuitem action='TM_Zero'/>"
"  </popup>"
"</ui>";



/*
** Structure for defining the toolbar
*/
// PV: Move it to UI manager too? Later ...
typedef struct {
  char *label;
  char *tooltip_index;
  gchar **icon_data;
  void (*callback)();
  GtkWidget *widget;
  gpointer data;
} TTToolButton;
#define TOOLBAR_START_BUTTON		0
#define TOOLBAR_STOP_BUTTON		1
#define TOOLBAR_STOP_ALL_BUTTON		2
TTToolButton main_toolbar[] = {
  { gettext_noop("Start"),
    gettext_noop("Start Timing the Selected Task"),
    start_xpm,
    start_callback, NULL, NULL },
  { gettext_noop("Stop"),
    gettext_noop("Stop Timing the Selected Task"),
    stop_xpm,
    stop_callback, NULL, NULL },
  { gettext_noop("Stop All"),
    gettext_noop("Stop Timing All Tasks"),
    stop_all_xpm,
    stop_all_callback, NULL, NULL },
  { gettext_noop("Annotate"),
    gettext_noop("Add Annotation to Selected Task"),
    annotate_xpm,
    annotate_callback, NULL, NULL },
  { gettext_noop("Add"),
    gettext_noop("Add New Task"),
    new_xpm,
    task_add_callback, NULL, NULL },
  { gettext_noop("Edit"),
    gettext_noop("Edit Name of the Selected Task") ,
    edit_xpm,
    task_edit_callback, NULL, NULL },
  { NULL, 0, NULL, NULL, NULL, NULL }
};



#ifdef WIN32
static void convert_backslash ( filename )
char *filename;
{
  while ( *filename ){
    if ( *filename == '\\' )
      *filename = '/';
    filename++;
  }
}
#endif


/*
** Get the currently selected task.  If the user has used the right mouse
** button to create a pulldown menu, then pulldown_selected_task will
** be set.  This routine should called only once per callback since
** we reset the value of pulldown_selected_task.
*/
static int get_selected_task ()
{
  int ret;

  if ( pulldown_selected_task >= 0 ) {
    ret = pulldown_selected_task;
    pulldown_selected_task = -1;
  } else
    ret = selected_task;

  return ret;
}


static int my_strcasecmp ( char *str1, char *str2 )
{
  char *ptr1, *ptr2, *ptr;
  int ret;

  ptr1 = strdup ( str1 );
  ptr2 = strdup ( str2 );

  for ( ptr = ptr1; *ptr != '\0'; ptr++ ) {
    *ptr = toupper ( *ptr );
  }
  for ( ptr = ptr2; *ptr != '\0'; ptr++ ) {
    *ptr = toupper ( *ptr );
  }

  ret = strcmp ( ptr1, ptr2 );
  free ( ptr1 );
  free ( ptr2 );
  return ( ret );
}


/*
 * Sort by project id, which will put oldest projects first, newest
 * projects last.
 */
static int sort_task_by_project_id ( td1, td2 )
TaskData **td1;
TaskData **td2;
{
  TaskData *tda = *td1;
  TaskData *tdb = *td2;
  int ret;

  /* put tasks with no projects (-1) at the end of the list, most
     recent projects at the top */
  if ( tda->task->project_id > tdb->task->project_id )
    ret = -1;
  else if ( tda->task->project_id < tdb->task->project_id )
    ret = 1;
  else
    ret = 0;
  
  if ( sort_forward )
    return ( ret );
  else
    return ( - ret );
}


/*
 * Sort by project name.
 */
static int sort_task_by_project_name ( td1, td2 )
TaskData **td1;
TaskData **td2;
{
  TaskData *tda = *td1;
  TaskData *tdb = *td2;
  int ret;

  /* put tasks with no projects (-1) at the end of the list, most
     recent projects at the top */
  if ( tda->task->project_id < 0 )
    ret = -1;
  else if ( tdb->task->project_id < 0 )
    ret = 1;
  else
    ret = my_strcasecmp ( tda->project_name, tdb->project_name );
  
  if ( sort_forward )
    return ( ret );
  else
    return ( - ret );
}

static int sort_task_by_name ( td1, td2 )
TaskData **td1;
TaskData **td2;
{
  TaskData *tda = *td1;
  TaskData *tdb = *td2;
  int ret;

  ret = ( my_strcasecmp ( tda->task->name, tdb->task->name ) );
  if ( sort_forward )
    return ( ret );
  else
    return ( - ret );
}

static int sort_task_by_id ( td1, td2 )
TaskData **td1;
TaskData **td2;
{
  TaskData *tda = *td1;
  TaskData *tdb = *td2;
  int ret;

  /* put most recent tasks at the top */
  if ( tda->task->number > tdb->task->number )
    ret = -1;
  else if ( tda->task->number < tdb->task->number )
    ret = 1;
  else
    ret = 0;

  if ( sort_forward )
    return ( ret );
  else
    return ( - ret );
}

static int sort_task_by_today ( td1, td2 )
TaskData **td1;
TaskData **td2;
{
  TaskData *tda = *td1;
  TaskData *tdb = *td2;
  int ret = 0;

  if ( tda->last_today_int > tdb->last_today_int )
    ret = 1;
  else if ( tda->last_today_int < tdb->last_today_int )
    ret = -1;
  else if ( tda->last_today_int == tdb->last_today_int )
    ret = ( (void *)tda < (void *)td2 );

  if ( sort_forward )
    return ( - ret );
  else
    return ( ret );
}


static int sort_task_by_total ( td1, td2 )
TaskData **td1;
TaskData **td2;
{
  TaskData *tda = *td1;
  TaskData *tdb = *td2;
  int ret = 0;

  if ( tda->last_total_int > tdb->last_total_int )
    ret = 1;
  else if ( tda->last_total_int < tdb->last_total_int )
    ret = -1;
  else if ( tda->last_total_int == tdb->last_total_int )
    ret = ( (void *)tda < (void *)td2 );

  if ( sort_forward )
    return ( - ret );
  else
    return ( ret );
}




/*
** Transfer all the time for tasks currently being timed into the
** Task data structure so the reports will have access to it easily.
*/
static void update_tasks ()
{
  int i;
  time_t now, diff;

  time ( &now );
  for ( i = 0; i < num_visible_tasks; i++ ) {
    if ( visible_tasks[i]->timer_on ) {
      diff = now - visible_tasks[i]->on_since;
      visible_tasks[i]->todays_entry->seconds += diff;
      visible_tasks[i]->on_since = now;
    }
  }
}




/*
** Save all the tasks to their files.
*/
void save_all ()
{
  update_tasks ();
  taskSaveAll ( taskdir );
  projectSaveAll ( taskdir );
  time ( &last_save );
  modified_since_save = 0;
}



/* Delete window handler */
gint delete_event ( widget, event, data )
GtkWidget *widget;
GdkEvent *event;
gpointer data;
{
  save_all ();
  configSaveAttributes ( config_file );
#ifdef GTIMER_MEMDEBUG
  configClear ();
#endif
  return ( TRUE );
}

static void exit_callback ( GtkAction *act )
{
  gint w, h;
  char temp[128];
  int loop;

  /* save task data */
  save_all ();

  /* save window size */
  w = 0;
  h = 0;
  gdk_window_get_size ( GTK_WIDGET ( main_window )->window, &w, &h );
  if ( w != 0 && h != 0 ) {
    configSetAttributeInt ( CONFIG_MAIN_WINDOW_WIDTH, w );
    configSetAttributeInt ( CONFIG_MAIN_WINDOW_HEIGHT, h );
  }

  /* get columns widths in main window */
  w = GTK_CLIST ( task_list )->column[0].width;
  configSetAttributeInt ( CONFIG_MAIN_WINDOW_PROJECT_WIDTH, w );
  w = GTK_CLIST ( task_list )->column[1].width;
  configSetAttributeInt ( CONFIG_MAIN_WINDOW_TASK_WIDTH, w );
  w = GTK_CLIST ( task_list )->column[2].width;
  configSetAttributeInt ( CONFIG_MAIN_WINDOW_TODAY_WIDTH, w );
  w = GTK_CLIST ( task_list )->column[3].width;
  configSetAttributeInt ( CONFIG_MAIN_WINDOW_TOTAL_WIDTH, w );

  /* keep track of which tasks were being timed in case the user starts up
     with -resume next time */
  temp[0] = '\0';
  for ( loop = 0; loop < num_visible_tasks; loop++ ) {
    if ( visible_tasks[loop]->timer_on ) {
      if ( strlen ( temp ) )
        strcat ( temp, "," );
      sprintf ( temp + strlen ( temp ), "%d",
        visible_tasks[loop]->task->number );
    }
  }
  configSetAttribute ( CONFIG_LAST_TIMED_TASKS, temp );

  /* save config settings */
  configSaveAttributes ( config_file );

#ifdef GTIMER_MEMDEBUG
  free ( config_file );
  configClear ();
#endif
  gtk_main_quit ();
}

/*
** Set toolbar buttons to sensitive/insensitive based
** on our current status.
*/
static void update_toolbar_buttons () {
  if ( num_timing ) {
    gtk_widget_set_sensitive (
      GTK_WIDGET ( main_toolbar[TOOLBAR_STOP_BUTTON].widget ), 1 );
    gtk_widget_set_sensitive (
      GTK_WIDGET ( main_toolbar[TOOLBAR_STOP_ALL_BUTTON].widget ), 1 );
  } else {
    gtk_widget_set_sensitive (
      GTK_WIDGET ( main_toolbar[TOOLBAR_STOP_BUTTON].widget ), 0 );
    gtk_widget_set_sensitive (
      GTK_WIDGET ( main_toolbar[TOOLBAR_STOP_ALL_BUTTON].widget ), 0 );
  }
  if ( num_visible_tasks ) {
    gtk_widget_set_sensitive (
      GTK_WIDGET ( main_toolbar[TOOLBAR_START_BUTTON].widget ), 1 );
  } else {
    gtk_widget_set_sensitive (
      GTK_WIDGET ( main_toolbar[TOOLBAR_START_BUTTON].widget ), 0 );
  }
}

static void save_callback ( GtkAction *act )
{
  showMessage ( gettext("All data saved") );
  save_all ();
}

static void about_callback ( GtkAction *act )
{
  char text[1024];

  sprintf ( text,
    "GTimer\n%s\n%s: %s (%s)\n%s\nGTK %s: %d.%d.%d\n\n",
    GTIMER_COPYRIGHT, gettext("Version"), GTIMER_VERSION, GTIMER_VERSION_DATE,
    GTIMER_URL, gettext("Version"),
    gtk_major_version, gtk_minor_version, gtk_micro_version );
  strcat ( text, gettext("Author") );
  sprintf ( text + strlen ( text ),
     ":\nCraig Knudsen\ncraig@k5n.us\n\n" );
  create_confirm_window ( CONFIRM_ABOUT,
    gettext("About"),
    text,
    gettext("Ok"), NULL, NULL, NULL, NULL, NULL, NULL );
}

static void changelog_callback ( GtkAction *act )
{
  display_changelog ();
}


static void website_callback ( GtkAction *act )
{
  char *path, *command;

  if ( configGetAttribute ( CONFIG_BROWSER, &path ) < 0 )
    path = "mozilla";

  command = (char *) malloc ( strlen ( path ) + 128 );
  if ( strstr ( path, "%s" ) )
    sprintf ( command, path, GTIMER_URL );
  else
    sprintf ( command, "%s %s", path, GTIMER_URL );
      
  if ( system ( command ) != 0 ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"), gettext("Error communicating with browser."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL, NULL );
  }

  free ( command );


}



static void task_add_callback ( GtkAction *act )
{
  create_task_edit_window ( NULL );
}

static void task_edit_callback ( GtkAction *act )
{
  int st = get_selected_task ();

  if ( st < 0 || ! num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task to edit."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    create_task_edit_window ( visible_tasks[st] );
  }
}



static void task_hide_callback ( GtkAction *act )
{
  TaskData *td;
  int i, st;

  st = get_selected_task ();
  if ( st < 0 || ! num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task to hide."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    td = visible_tasks[st];
    taskSetOption ( td->task, GTIMER_TASK_OPTION_HIDDEN );
    td->timer_on = 0;
    for ( i = st; i < num_visible_tasks; i++ ) {
      if ( i + 1 < num_visible_tasks )
        visible_tasks[i] = visible_tasks[i + 1];
    }
    num_visible_tasks--;
    update_list ();
    gtk_clist_remove ( GTK_CLIST ( task_list ), st );
    showMessage ( gettext("Task hidden") );
  }
}



static void task_unhide_callback ( GtkAction *act )
{
  int st;

  st = get_selected_task ();
  if ( num_tasks == num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("There are no hidden tasks."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    update_list ();
    create_unhide_window ();
  }
}



static void delete_confirm_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
  TaskData *td = (TaskData *)data;
  int ret, loop, tasknumber;
  char msg[500];

  if ( ( ret = taskDelete ( td->task, taskdir ) ) ) {
    sprintf ( msg, "%s:\n%s",
      gettext("Error deleting task"), taskErrorString ( ret ) );
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      msg,
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL, NULL );
  }

  /* delete from visible_tasks[] and the list window */
  tasknumber = -1;
  for ( loop = 0; loop < num_visible_tasks && tasknumber < 0; loop++ ) {
    if ( visible_tasks[loop] == td )
      tasknumber = loop;
  }
  if ( tasknumber >= 0 ) {
    gtk_clist_remove ( GTK_CLIST ( task_list ), tasknumber );
    for ( loop = tasknumber; loop < num_visible_tasks; loop++ ) {
      if ( loop + 1 < num_visible_tasks )
        visible_tasks[loop] = visible_tasks[loop + 1];
    }
  }

  /* delete from tasks[] */
  tasknumber = -1;
  for ( loop = 0; loop < num_tasks && tasknumber < 0; loop++ ) {
    if ( tasks[loop] == td )
      tasknumber = loop;
  }
  if ( tasknumber >= 0 ) {
    for ( loop = tasknumber; loop < num_tasks; loop++ ) {
      if ( loop + 1 < num_tasks )
        tasks[loop] = tasks[loop + 1];
    }
  }

  free ( td );
  num_tasks--;
  num_visible_tasks--;
  showMessage ( gettext("Task removed") );

  update_list ();
}


static void task_delete_callback ( GtkAction *act )
{
  int st = get_selected_task ();

  if ( st < 0 || ! num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task to delete."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    create_confirm_window ( CONFIRM_CONFIRM,
      gettext("Delete Task?"),
      gettext("Are you sure you want\nto delete this task?"),
      gettext("Ok"), gettext("Cancel"), NULL,
      delete_confirm_callback, NULL, NULL,
      (char *)visible_tasks[st] );
  }
}

static void start_callback ( GtkAction *act )
{
  TaskData *td;
  int st = get_selected_task ();

  if ( st < 0 || ! num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task to start timing."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    td = visible_tasks[st];
    if ( td->timer_on ) {
      create_confirm_window ( CONFIRM_ERROR,
        gettext("Error"),
        gettext("Task is already being timed."),
        gettext("Ok"), NULL, NULL,
        NULL, NULL, NULL,
        NULL );
    } else {
      td->timer_on = 1;
      time ( &td->on_since );
      if ( td->todays_entry == NULL )
        td->todays_entry = taskNewTimeEntry ( td->task, today_year,
          today_mon, today_mday );
      update_list ();
      num_timing++;
      if ( num_timing == 1 )
        gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
          NULL, appicon, appicon_mask );
    }
  }
  update_toolbar_buttons ();
}

static void stop_callback ( GtkAction *act )
{
  TaskData *td;
  time_t now, diff;
  int st = get_selected_task ();

  if ( st < 0 || ! num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task to stop timing."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    td = visible_tasks[st];
    if ( ! td->timer_on ) {
      create_confirm_window ( CONFIRM_ERROR,
        gettext("Error"),
        gettext("Task is not being timed."),
        gettext("Ok"), NULL, NULL,
        NULL, NULL, NULL,
        NULL );
    } else {
      td->timer_on = 0;
      time ( &now );
      diff = now - td->on_since;
      td->todays_entry->seconds += diff;
      td->on_since = 0;
      update_list ();
      num_timing--;
      if ( num_timing == 0 )
        gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
          NULL, appicon2, appicon2_mask );
    }
  }
  update_toolbar_buttons ();
}




static void annotate_callback ( GtkAction *act )
{
  int st = get_selected_task ();

  if ( st < 0 || ! num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task to annotate."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    create_annotate_window ( visible_tasks[st] );
  }
}


static void stop_all_callback ( GtkAction *act )
{
  TaskData *td;
  time_t now, diff;
  int loop;

  get_selected_task (); /* reset pulldown task selection */

  for ( loop = 0; loop < num_visible_tasks; loop++ ) {
    td = visible_tasks[loop];
    if ( td->timer_on ) {
      td->timer_on = 0;
      time ( &now );
      diff = now - td->on_since;
      td->todays_entry->seconds += diff;
      td->on_since = 0;
    }
  }
  if ( num_timing )
    gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
       NULL, appicon2, appicon2_mask );
  num_timing = 0;
  update_list ();
  update_toolbar_buttons ();
}



static void switch_to_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
  TaskData *td;
  time_t now, diff;
  int loop;
  int new_icon = 1;
  int st = get_selected_task ();

  for ( loop = 0; loop < num_visible_tasks; loop++ ) {
    td = visible_tasks[loop];
    if ( td->timer_on ) {
      td->timer_on = 0;
      time ( &now );
      diff = now - td->on_since;
      td->todays_entry->seconds += diff;
      td->on_since = 0;
      new_icon = 0;
      num_timing--;
    }
  }
  td = visible_tasks[st];
  td->timer_on = 1;
  time ( &td->on_since );
  if ( td->todays_entry == NULL )
    td->todays_entry = taskNewTimeEntry ( td->task, today_year,
      today_mon, today_mday );
  update_list ();
  num_timing = 1;
  if ( new_icon )
    gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
       NULL, appicon, appicon_mask );
  update_list ();
  update_toolbar_buttons ();
}


static void toolbar_toggle_callback ( GtkToggleAction *act )
{
  int active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION( act ));
  if ( active ) {
    configSetAttributeInt ( CONFIG_TOOLBAR_STATUS, 1 );
    gtk_widget_show ( toolbar );
  } else {
    configSetAttributeInt ( CONFIG_TOOLBAR_STATUS, 0 );
    gtk_widget_hide ( toolbar );
  }
  config_toolbar_enabled = active;
}


static void animate_toggle_callback ( GtkToggleAction *act )
{
  int active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION( act ));
  if ( active )
    configSetAttributeInt ( CONFIG_ANIMATE, 1 );
  else
    configSetAttributeInt ( CONFIG_ANIMATE, 0 );
  config_animate_enabled = active;
}


static void idle_toggle_callback ( GtkToggleAction *act )
{
  int active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION( act ));
  if ( active )
    configSetAttributeInt ( CONFIG_IDLE_ON, 1 );
  else
    configSetAttributeInt ( CONFIG_IDLE_ON, 0 );
  config_idle_enabled = active;
}


static void autosave_toggle_callback ( GtkToggleAction *act )
{
  int active = gtk_toggle_action_get_active(GTK_TOGGLE_ACTION( act ));
  if ( active )
    configSetAttributeInt ( CONFIG_AUTOSAVE, 1 );
  else
    configSetAttributeInt ( CONFIG_AUTOSAVE, 0 );
  config_autosave_enabled = active;
}


static void set_browser_callback ( GtkAction *act )
{
  create_browser_edit_window ();
}


static void project_add_callback ( GtkAction *act )
{
  create_project_edit_window ( NULL );
}

static void project_edit_callback ( GtkAction *act )
{
  int st = get_selected_task ();
  Project *p;

  if ( st < 0 || ! num_visible_tasks ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    if ( visible_tasks[st]->task->project_id < 0 ) {
      create_confirm_window ( CONFIRM_ERROR,
        gettext("Error"),
        gettext("The selected task does not\nhave a project."),
        gettext("Ok"), NULL, NULL,
        NULL, NULL, NULL,
        NULL );
    } else {
      p = projectGet ( visible_tasks[st]->task->project_id );
      create_project_edit_window ( p );
    }
  }
}

static void report2_callback ( GtkAction *act )
{
//  report_type rt = (report_type) data;

  typedef struct {
	gchar *name;
	report_type rt;
  } TTarray;

  const TTarray acts[] = {
	{"RM_Daily", REPORT_TYPE_DAILY},
	{"RM_Weekly", REPORT_TYPE_WEEKLY},
	{"RM_MOnthly", REPORT_TYPE_MONTHLY},
	{"RM_Yearly", REPORT_TYPE_YEARLY}
   };
  const gchar *aname = gtk_action_get_name(GTK_ACTION(act));
  report_type rt = REPORT_TYPE_NONE ;
  int i;

  for ( i = 0 ; i <= 3 ; i++ ) {
    if ( !strcmp ( acts[i].name, aname ) ) {
	rt = acts[i].rt;
	break;
    }
  }
  if ( rt == REPORT_TYPE_NONE ) {
	fprintf( stderr, "Gtimer: Unknown report (%s)!\n", aname); return; 
  }

#if PV_DEBUG
  g_message("Report 2 cback start");
#endif
  update_tasks ();
  create_report_window ( rt );

#if PV_DEBUG
  g_message("Report 2 cback end");
#endif

}

/*
 * Display the time in the cutBuffer in the status area.
 */
static void showCutBuffer ()
{
  char temp[48];
  int cb, h, m, s;

  cb = cutBuffer;
  h = cb / 3600;
  cb %= 3600;
  m = cb / 60;
  cb %= 60;
  s = cb;

  sprintf ( temp, "%s %02d:%02d:%02d",
    gettext ("Cut/Paste buffer is" ), h, m, s );
  showMessage ( temp );
}

static void adjust_task_time ( offset )
int offset;
{
  TaskData *td;
  int st = get_selected_task ();

  if ( st < 0 ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected\na task to adjust the time for."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    update_tasks ();
    td = visible_tasks[st];
    if ( td->todays_entry == NULL )
      td->todays_entry = taskNewTimeEntry ( td->task,
        today_year, today_mon, today_mday );
    if ( offset == 0 ) {
      /* set to zero */
      cutBuffer = td->todays_entry->seconds;
      td->todays_entry->seconds = 0;
    } else if ( offset < 0 ) {
      /* decrement */
      if ( td->todays_entry->seconds < ( 0 - offset ) ) {
        cutBuffer += td->todays_entry->seconds;
        td->todays_entry->seconds = 0;
      } else {
        cutBuffer += - offset;
        td->todays_entry->seconds += offset;
      }
    } else {
      td->todays_entry->seconds += offset;
    }
    modified_since_save = 1;
    update_list ();
  }
}



/*
** Revert to where we were when we first noticed that the user was idle.
*/
static void idle_reset_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
  int loop;
  int timeBeforeReset, timeAfterReset;
  time_t now;

  idle_prompt_window = NULL;

  time ( &now );

  /* add up total time for today */
  for ( timeBeforeReset = loop = 0; loop < num_tasks; loop++ )  {
    if ( tasks[loop]->todays_entry ) {
      timeBeforeReset += tasks[loop]->todays_entry->seconds;
      if ( tasks[loop]->timer_on ) {
        timeBeforeReset += ( now - tasks[loop]->on_since );
      }
    }
  }

  taskRestoreAll ();

  for ( timeAfterReset = loop = 0; loop < num_tasks; loop++ )  {
    if ( tasks[loop]->timer_on ) {
      tasks[loop]->timer_on = 0;
      tasks[loop]->on_since = 0;
      num_timing--;
    }
    if ( tasks[loop]->todays_entry ) {
      timeAfterReset += tasks[loop]->todays_entry->seconds;
    }
  }
  update_list ();
  update_toolbar_buttons ();

  /* reset icon */
  gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
    NULL, appicon2, appicon2_mask );

  /*
   * Store however much time we just removed in the cut buffer, so the
   * user can Edit->Paste to apply the time to a different task
   */
  cutBuffer = timeBeforeReset - timeAfterReset;
  if ( cutBuffer < 0 )
    cutBuffer = 0;
  showCutBuffer ();
}

static void task_clear_callback ( GtkAction *act )
{
  /* set to zero */
  cutBuffer = 0;
  showCutBuffer ();
}

static void idle_cancel_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
  idle_prompt_window = NULL;
}

/*
** Revert to the data that was last saved to file (which we did when
** we first noticed the user was idle.) and continue timing.
** Save the idle time in the cut/paste buffer.
*/
static void idle_resume_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
  int loop;
  int timeBeforeReset, timeAfterReset;
  time_t now;

  time ( &now );

  idle_prompt_window = NULL;

  /* add up total time for today */
  for ( timeBeforeReset = loop = 0; loop < num_tasks; loop++ )  {
    if ( tasks[loop]->todays_entry ) {
      timeBeforeReset += tasks[loop]->todays_entry->seconds;
      if ( tasks[loop]->timer_on ) {
        timeBeforeReset += ( now - tasks[loop]->on_since );
      }
    }
  }

  taskRestoreAll ();

  for ( timeAfterReset = loop = 0; loop < num_visible_tasks; loop++ ) {
    if ( visible_tasks[loop]->timer_on )
      time ( &visible_tasks[loop]->on_since );
    if ( tasks[loop]->todays_entry )
      timeAfterReset += tasks[loop]->todays_entry->seconds;
  }
  update_list ();

  /*
   * Store however much time we just removed in the cut buffer, so the
   * user can Edit->Paste to apply the time to a different task
   */
  cutBuffer = timeBeforeReset - timeAfterReset;
  if ( cutBuffer < 0 )
    cutBuffer = 0;
  showCutBuffer ();
}


static void task_cut_callback ( GtkAction *act )
{
  adjust_task_time ( 0 );
  showCutBuffer ();
}

static void task_copy_callback ( GtkAction *act )
{
  TaskData *td;
  int st = get_selected_task ();

  cutBuffer = 0;

  if ( st < 0 ) {
    create_confirm_window ( CONFIRM_ERROR,
      gettext("Error"),
      gettext("You have not selected a task."),
      gettext("Ok"), NULL, NULL,
      NULL, NULL, NULL,
      NULL );
  } else {
    update_tasks ();
    td = visible_tasks[st];
    if ( td->todays_entry != NULL ) {
      cutBuffer = td->todays_entry->seconds;
    }
    showCutBuffer ();
  }
}

static void task_paste_callback ( GtkAction *act )
{
  if ( cutBuffer > 0 ) {
    adjust_task_time ( cutBuffer );
  }
  /* note that we don't reset the cut buffer back to zero here.
     this allows them to paste more than once. */
  showCutBuffer ();
}


static void increment_time_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
  int offset = GPOINTER_TO_INT(data);
  adjust_task_time ( offset );
}


static void decrement_time_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
  int offset = GPOINTER_TO_INT(data);
  adjust_task_time ( 0 - offset );
  showCutBuffer ();
}


static void shift_time_callback ( GtkAction *act )
{
  typedef struct {
	gchar *name;
	int shift;
  } TTarray;

  int time_shift;  //Shift in seconds
  int i;

  const TTarray acts[] = {
	{"TM_Inc1",    60 },
	{"TM_Inc5",   300 },
	{"TM_Inc30", 1800 },
	{"TM_Dec1",   -60 },
	{"TM_Dec5",  -300 },
	{"TM_Dec30",-1800 },
	{"TM_Zero",     0 }
  };

  const gchar *aname = gtk_action_get_name(GTK_ACTION(act));

  time_shift = -1;

  for ( i = 0 ; i <= 6 ; i++ ) {
//  g_message( "%s x %s", acts[i].name, aname);
    if ( !strcmp ( acts[i].name, aname ) ) {
	time_shift = acts[i].shift;
	break;
    }
  }
  if ( time_shift == -1 ) {
	fprintf( stderr, "Gtimer: Unknown action (%s)!\n", aname); return; 
  }
#if PV_DEBUG
  g_message("Shift_time: %d s", time_shift);
#endif
  adjust_task_time(time_shift);
  if (time_shift < 0 ) showCutBuffer();
}


static void read_http_socket ( data, source, condition )
gpointer data;
gint source;
GdkInputCondition condition;
{
  httpProcessRead ( connection );
}


/*
** This function will be called when the HTTP data is ready.
** It could actually be called multiple times each time a read()
** completes, but since the version info is so short, this will most
** likely never happen.
*/
static void read_version ( data, len )
char *data;
int len;	/* NOT NULL-terminated! */
{
  char *ptr, *data2, *msg, version[30];
  time_t now;
  char timestr[20];

  if ( len == 0 ) {
    gdk_input_remove ( gdk_input_id );
    httpKillConnection ( connection );
    connection = -1;
  } else {
    data2 = (char *) malloc ( len + 1 );
    strncpy ( data2, data, len );
    data2[len] = '\0';
    if ( strstr ( data2, "Not Found" ) != NULL ||
      strstr ( data2, "Not found" ) != NULL ||
      strstr ( data2, "not found" ) != NULL ) {
      if ( ! version_check_is_auto )
        create_confirm_window ( CONFIRM_ERROR,
          gettext("Error"),
          gettext("This service is no longer available."),
          gettext("Ok"), NULL, NULL, NULL, NULL, NULL, NULL );
    } else {
      ptr = strstr ( data2, "GTimer Version" );
      if ( ptr == NULL ) {
        if ( ! version_check_is_auto )
          create_confirm_window ( CONFIRM_ERROR,
            gettext("Error"),
            gettext("Unable to determine available GTimer version."),
            gettext("Ok"), NULL, NULL, NULL, NULL, NULL, NULL );
      } else {
        ptr += 15; /* skip over "GTimer Version" */
        sprintf ( version, "%s (%s)", GTIMER_VERSION, GTIMER_VERSION_DATE );
        if ( strncmp ( version, ptr, strlen ( version ) ) == 0 ) {
          if ( ! version_check_is_auto )
            create_confirm_window ( CONFIRM_ABOUT,
              gettext("Version"),
              gettext("You have the most recent version of GTimer."),
              gettext("Ok"), NULL, NULL, NULL, NULL, NULL, NULL );
        } else if ( strncmp ( version, ptr, strlen ( version ) ) > 0 ) {
          create_confirm_window ( CONFIRM_ABOUT,
            gettext("Version"),
            gettext("Strange....  You seem to have a more recent version\nof GTimer than is available on the server."),
            gettext("Ok"), NULL, NULL, NULL, NULL, NULL, NULL );
        } else {
          msg = (char *) malloc ( 200 + strlen ( ptr ) );
          sprintf ( msg, "%s:\n\n%s\n%s:\n\n%s",
            gettext("There is a new version of GTimer available"),
            ptr,
            gettext("You can download it at"),
            GTIMER_URL );
          create_confirm_window ( CONFIRM_ABOUT,
            gettext("Version"),
            msg, gettext("Ok"), NULL, NULL, NULL, NULL, NULL, NULL );
          free ( msg );
        }
      }
    }
    gdk_input_remove ( gdk_input_id );
    httpKillConnection ( connection );
    connection = -1;
  }

  /* calcuate the next time we will need to do this check */
  time ( &now );
  now += VERSION_CHECK_INTERVAL;
  sprintf ( timestr, "%ul", (unsigned int)now );
  configSetAttribute ( CONFIG_NEXT_VERSION_CHECK, timestr );
  version_check_is_auto = FALSE;
}

static void check_version_callback ( GtkAction *act )
{
  httpError ret;
  char msg[400];

  if ( connection >= 0 )
    httpKillConnection ( connection );

  ret = httpOpenConnection ( GTIMER_VERSION_CHECK_SERVER,
    GTIMER_VERSION_CHECK_PORT, &connection );
  if ( ret ) {
    /* only report errors if the user asked for a version check */
    if ( ! version_check_is_auto ) {
      strcpy ( msg,
        gettext("An error occurred while\nchecking for a new version.") );
      strcat ( msg, "\n\n" );
      sprintf ( msg + strlen ( msg ), "%s:\n\n%s", gettext("HTTP Error"),
        httpErrorString ( ret ) );
      create_confirm_window ( CONFIRM_ERROR,
        gettext("Error"), msg, gettext("Ok"), NULL, NULL,
        NULL, NULL, NULL, NULL );
    }
    version_check_is_auto = FALSE;
  } else {
    gdk_input_id = gdk_input_add ( (gint) connection,
      GDK_INPUT_READ, read_http_socket, NULL );
    ret = httpGet ( connection, GTIMER_VERSION_CHECK_SERVER,
      GTIMER_VERSION_CHECK_PATH, NULL, NULL, 0, read_version );
    if ( ret ) {
      if ( ! version_check_is_auto ) {
        sprintf ( msg, "%s:\n\n%s", gettext("HTTP Error"),
          httpErrorString ( ret ) );
        create_confirm_window ( CONFIRM_ERROR,
          gettext("Error"), msg, gettext("Ok"), NULL, NULL,
          NULL, NULL, NULL, NULL );
      }
      httpKillConnection ( connection );
      gdk_input_remove ( gdk_input_id );
      gdk_input_id = -1;
      version_check_is_auto = FALSE;
    }
  }
}




/*
** A generic event handler for the task pulldown menu (when created from
** a right mouse button in the task list).  If the user doesn't select
** anything from the pulldown, we need to forget about which task they
** selected for the pulldown.
*/
static gint pulldown_event ( widget, event )
GtkWidget *widget;
GdkEvent *event;
{
  if ( event->type == GDK_UNMAP )
    get_selected_task ();
  return ( FALSE );
}


/*
** Create a pulldown menu (from the user selecting a task with the
** right mouse button);
*/
static GtkWidget *create_task_pulldown ( int is_main ) {
  return  gtk_ui_manager_get_widget( uimanager, "/PopupMenu" );
}



/*
** Callback for selecting the column header.
** Sort the list by the selected column.
*/
static void column_selected_callback ( widget, col )
GtkWidget *widget;
int col;
{
  int i;
  static int same_col = 0;

  /*
   * Keep track of how many times they click on the same column header.
   * When they do this with the project column, it will sort as follows:
   *   1) project id
   *   2) project id (reverse)
   *   3) project name
   *   4) project name (reverse)
   *   5) back to 1...
   */
  if ( col == last_sort ) {
    same_col++;
  } else {
    same_col = 0;
  }

  if ( col == last_sort )
    sort_forward = ! sort_forward;
  else
    sort_forward = 1;
  last_sort = col;
  switch ( col ) {
    case 0:
      if ( same_col % 4 < 2 ) {
        qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
          sort_task_by_project_id );
        showMessage ( gettext("Sorted by project creation date") );
      } else {
        qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
          sort_task_by_project_name );
        showMessage ( gettext("Sorted by project name") );
      }
      break;
    case 1:
      if ( same_col % 4 < 2 ) {
        qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
          sort_task_by_name );
        showMessage ( gettext("Sorted by task name") );
      } else {
        qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
          sort_task_by_id );
        showMessage ( gettext("Sorted by task creation date") );
      }
      break;
    case 2:
      qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
        sort_task_by_today );
      showMessage ( gettext("Sorted by time for today") );
      break;
    default:
    case 3:
      qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
        sort_task_by_total );
      showMessage ( gettext("Sorted by total time") );
      break;
  }
  rebuilding_list = 1;
  gtk_clist_freeze( GTK_CLIST (task_list) );
  build_list ();
  update_list ();
  rebuilding_list = 0;
  for ( i = 0; i < num_visible_tasks; i++ ) {
    if ( visible_tasks[i]->selected )
      gtk_clist_select_row ( GTK_CLIST (task_list), i, 0 );
  }
  gtk_clist_thaw( GTK_CLIST (task_list) );
  configSetAttributeInt ( CONFIG_SORT, col );
  configSetAttributeInt ( CONFIG_SORT_FORWARD, sort_forward );
}






/*
** General event handler for the task list clist widget.  Catch
** right mouse button events and create the pulldown menu.
*/
static gint task_list_event ( widget, event )
GtkWidget *widget;
GdkEvent *event;
{
  GdkEventButton *eb;
  GtkWidget *menu;
  int row, col;

  if ( event->type == GDK_BUTTON_PRESS ) {
    eb = (GdkEventButton *)event;
    if ( eb->button == 3 ) {
      gtk_clist_get_selection_info ( GTK_CLIST ( task_list ),
        eb->x, eb->y, &row, &col );
      pulldown_selected_task = row;
      menu = create_task_pulldown ( FALSE );
      gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL,
         NULL, 3, eb->time);
    }
  }
  return ( FALSE );
}


/*
** Callback for user selecting a task (single-click, double-click,
** right-mouse, etc.)
*/
static gint task_selected_callback ( widget, col, row, bevent, user_data )
GtkWidget *widget;
int col;
int row;
GdkEventButton *bevent;
gpointer user_data;
{
  int i;

  if ( rebuilding_list )
    return ( TRUE );

  for ( i = 0; i < num_visible_tasks; i++ )
    visible_tasks[i]->selected = 0;

  selected_task = col;
  if ( selected_task >= 0 )
    visible_tasks[selected_task]->selected = 1;

  if ( bevent != NULL ) {
    /* double-click ? */
    if ( bevent->type == GDK_2BUTTON_PRESS )
      switch_to_callback ( widget, user_data );
  }

  return ( TRUE );
}

/*
** PV: Main Menu translations - because of duplicated items (e.g. "New" in menu Task and Project)
**     we have to know which one item is translated. Each string in menu can contain prefix in form
**     <prefix><delimiter><text>. Delimiter is now hardcoded as '|' (or any character not contained
**     in menu description).
*/


gchar *mainmenu_translate(const gchar *str, gpointer data)
{
	gchar *retval;
	gchar **split;

	retval = gettext(str);

        if ( strchr(str, '|') ) {

          split = g_strsplit(retval,"|",2);
	
        
	  //debug: 
#if PV_DEBUG
          printf("\nPrefix: %s", split[0]);
          printf("\nText  : %s", split[1]);
#endif
          retval = g_strdup(split[1]);
          g_strfreev(split);
#if PV_DEBUG
          printf("\n*** Retval *** %s", retval);
#endif
	}

	return retval;
}

/*
** Create the main window's menu bar.
** PV: For GTK 2.x --- Actions menus
**
*/
static GtkWidget *create_main_window_menu_bar()
{
  GtkWidget *menu;
  GtkWidget *root_menu;
  GtkWidget *menu_item;
  GtkWidget *menu_bar;
//  int loop;
//  PV: GTK2
//  GtkUIManager *uimanager;
  GtkActionGroup *actgroup;
  GtkAction *item;
  GError *err;

  actgroup = gtk_action_group_new("ActionMain");
  // set translation domain and function
  gtk_action_group_set_translation_domain(GTK_ACTION_GROUP(actgroup), DEFAULT_TEXT_DOMAIN);
  gtk_action_group_set_translate_func(GTK_ACTION_GROUP(actgroup), mainmenu_translate, NULL, NULL);
  // set entries
  gtk_action_group_add_actions  (GTK_ACTION_GROUP(actgroup), MM_NormalEntries,
				G_N_ELEMENTS(MM_NormalEntries), main_window);
  gtk_action_group_add_toggle_actions (GTK_ACTION_GROUP(actgroup), MM_ToggleEntries,
				G_N_ELEMENTS(MM_ToggleEntries), main_window);


  uimanager = gtk_ui_manager_new();
  gtk_ui_manager_set_add_tearoffs(uimanager, TRUE);
  gtk_ui_manager_insert_action_group(GTK_UI_MANAGER(uimanager),
                                     GTK_ACTION_GROUP(actgroup),
				     0);
  mainag = gtk_ui_manager_get_accel_group (uimanager);
  gtk_window_add_accel_group (GTK_WINDOW(main_window), GTK_ACCEL_GROUP(mainag));

  gtk_ui_manager_add_ui_from_string (uimanager, menustring, -1, &err);

    if (!(err == NULL))
    {
	g_message(gettext("Building menu Failed: %s \n"), err->message);
	g_error_free(err);
    }

  return ( gtk_ui_manager_get_widget (uimanager, "/MainMenu") );
}


static GtkWidget *create_list_column_def (num, cols)
int num;
list_column_def *cols;
{
  GtkWidget *clist;
  GtkWidget *alignment;
  GtkWidget *label;
  int i;

  clist = gtk_clist_new (num);
#ifdef GTK_CLIST_SET_FLAGS /* GTK 1.0.1 */
  GTK_CLIST_SET_FLAGS (clist, CLIST_SHOW_TITLES);
#else
  GTK_CLIST_SET_FLAG (clist, CLIST_SHOW_TITLES);
#endif

  for (i = 0; i < num; i++) {
    gtk_clist_set_column_width (GTK_CLIST (clist), i, cols[i].width);
/*
    gtk_clist_set_column_resizeable (GTK_CLIST (clist), i, cols[i].resizeable);
    gtk_clist_set_column_auto_resize (GTK_CLIST (clist), i, (gboolean)1);
    if ( cols[i].max_width )
      gtk_clist_set_column_max_width ( GTK_CLIST (clist), i,
        cols[i].max_width );
*/
    if (cols[i].justify != GTK_JUSTIFY_LEFT) {
      gtk_clist_set_column_justification (GTK_CLIST (clist), i,
        cols[i].justify);
    }

    alignment = gtk_alignment_new (0.0, 0.5, 0.0, 0.0);

    /*label = gtk_label_new (cols[i].name);*/
    label = gtk_label_new ( gettext(cols[i].name) );
    gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
    gtk_container_add (GTK_CONTAINER (alignment), label);
    gtk_widget_show (label);

    cols[i].widget = label;

    gtk_clist_set_column_widget (GTK_CLIST (clist), i, alignment);
    /*gtk_clist_column_title_passive ( GTK_CLIST(clist), i );*/
    gtk_widget_show (alignment);
  }

  return clist;
}



/*
** Update the time values shown in the list.
*/
void update_list () {
  TaskData *taskdata;
  int i;
  int h, m, s;
  char text[100];
  time_t now, diff, total, today;
  GdkPixmap *icon;
  GdkBitmap *mask;
  char *row[4];
  int total_today = 0;
  char today_test[20];
  char *project_name;
  Project *p;
  static char *noproject = "";

  time ( &now );
  if ( config_animate_enabled ) {
    icon = icons[now%8];
    mask = icon_masks[now%8];
  } else {
    icon = icons[0];
    mask = icon_masks[0];
  }

  /*gtk_clist_freeze ( GTK_CLIST(task_list) );*/
  for ( i = 0; i < num_visible_tasks; i++ ) {
    taskdata = visible_tasks[i];
    /* new task ? */
    if ( taskdata->new_task ) {
      modified_since_save = 1;
      taskdata->new_task = 0;
      if ( taskdata->task->project_id > 0 ) {
        p = projectGet ( taskdata->task->project_id );
        project_name = p->name;
      } else {
        project_name = noproject;
      }
      row[0] = project_name;
      row[1] = taskdata->task->name;
      row[2] = "00:00:00";
      row[3] = "00:00:00";
      gtk_clist_append ( GTK_CLIST(task_list), row );
      gtk_clist_set_pixtext (GTK_CLIST (task_list), i, 0, 
        taskdata->project_name, 2, blankicon, blankicon_mask);
      continue;
    }
    /* update the name ? */
    if ( taskdata->name_updated || taskdata->moved ) {
      modified_since_save = 1;
      taskdata->name_updated = 0;
      if ( taskdata->timer_on ) {
        gtk_clist_set_pixtext (GTK_CLIST (task_list), i, 0,
          taskdata->project_name, 2, icon, mask);
        taskdata->last_on = 1;
      } else {
        gtk_clist_set_pixtext (GTK_CLIST (task_list), i, 0, 
          taskdata->project_name, 2, blankicon, blankicon_mask);
        taskdata->last_on = 0;
      }
    }
    gtk_clist_set_text ( GTK_CLIST(task_list), i, 1, taskdata->task->name );
    /* calc total */
    total = taskdata->total;
    if ( taskdata->todays_entry )
      total += taskdata->todays_entry->seconds;
    if ( taskdata->timer_on ) {
      time ( &now );
      diff = now - taskdata->on_since;
      total += diff;
    }
    h = total / 3600;
    m = ( total - h * 3600 ) / 60;
    s = total % 60;
    sprintf ( text, "%d:%02d:%02d", h, m, s );
    if ( strcmp ( text, taskdata->last_total ) || taskdata->moved ) {
      gtk_clist_set_text ( GTK_CLIST(task_list), i, 3, text );
      strcpy ( taskdata->last_total, text );
    }
    taskdata->last_total_int = total;
    today = 0;
    if ( taskdata->todays_entry )
      today = taskdata->todays_entry->seconds;
    if ( taskdata->timer_on ) {
      time ( &now );
      diff = now - taskdata->on_since;
      today += diff;
    } 
    h = today / 3600;
    m = ( today - h * 3600 ) / 60;
    s = today % 60;
    sprintf ( text, "%d:%02d:%02d", h, m, s );
    if ( strcmp ( text, taskdata->last_today ) || taskdata->moved ) {
      gtk_clist_set_text ( GTK_CLIST(task_list), i, 2, text );
      strcpy ( taskdata->last_today, text );
    }
    taskdata->last_today_int = today;
    /* draw the icon ? */
    if ( taskdata->timer_on ) {
      modified_since_save = 1;
      gtk_clist_set_pixtext (GTK_CLIST (task_list), i, 0,
        taskdata->project_name, 2, icon, mask);
      taskdata->last_on = 1;
    } else if ( ! taskdata->timer_on && taskdata->last_on ) {
      gtk_clist_set_pixtext (GTK_CLIST (task_list), i, 0, 
        taskdata->project_name, 2, blankicon, blankicon_mask);
      taskdata->last_on = 0;
    }
    taskdata->moved = 0;
    total_today += today;
  }
  /*gtk_clist_thaw ( GTK_CLIST(task_list) );*/

  h = total_today / 3600;
  m = ( total_today - h * 3600 ) / 60;
  s = total_today % 60;
  sprintf ( today_test, "%s: %d:%02d:%02d", gettext("Today"), h, m, s );
  if ( strcmp ( today_test, total_str ) ) {
    strcpy ( total_str, today_test );
    gtk_label_set ( GTK_LABEL ( total_label ), total_str );
  }
}


/*
** Create the task list.
*/
static void build_list () {
  Task *task;
  TaskData *taskdata;
  Project *p;
  char *project_name;
  char today_str[100], total_str[100];
  char *row[4];
  int i, j;
  GdkPixmap *icon;
  GdkBitmap *mask;
  time_t now;
  static int first = 1;
  GtkWidget *win;
  static char *noproject = "";

  if ( splash_window )
    win = splash_window;
  else
    win = main_window;

  /* gtk_clist_freeze ( GTK_CLIST(task_list) ); */
  gtk_clist_clear ( GTK_CLIST(task_list) );

  if ( first ) {
    icons[0] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[0],
      &win->style->white, clock1_xpm);
    icons[1] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[1],
      &win->style->white, clock2_xpm);
    icons[2] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[2],
      &win->style->white, clock3_xpm);
    icons[3] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[3],
      &win->style->white, clock4_xpm);
    icons[4] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[4],
      &win->style->white, clock5_xpm);
    icons[5] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[5],
      &win->style->white, clock6_xpm);
    icons[6] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[6],
      &win->style->white, clock7_xpm);
    icons[7] = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &icon_masks[7],
      &win->style->white, clock8_xpm);
    blankicon = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( win )->window, &blankicon_mask,
      &win->style->white, blank_xpm);
  }

  if ( tasks == NULL ) {
    tasks = (TaskData **) malloc ( taskCount() * sizeof ( TaskData * ) );
    visible_tasks = (TaskData **) malloc ( taskCount() *
      sizeof ( TaskData * ) );
    for ( i = 0, task = taskGetFirst(); task != NULL;
      i++, task = taskGetNext () ) {
      taskdata = (TaskData *) malloc ( sizeof ( TaskData ) );
      memset ( taskdata, '\0', sizeof ( TaskData ) );
      taskdata->task = task;
      taskdata->todays_entry = taskGetTimeEntry ( taskdata->task, today_year,
        today_mon, today_mday );
      for ( j = 0; j < taskdata->task->num_entries; j++ ) {
        if ( taskdata->task->entries[j] != taskdata->todays_entry )
          taskdata->total += taskdata->task->entries[j]->seconds;
      }
      strcpy ( taskdata->last_today, "" );
      strcpy ( taskdata->last_total, "" );
      taskdata->project_name = "";
      if ( taskdata->task->project_id >= 0 ) {
        p = projectGet ( task->project_id );
        if ( p != NULL )
          taskdata->project_name = p->name;
      }
      tasks[num_tasks++] = taskdata;
      if ( ! taskOptionEnabled ( taskdata->task, GTIMER_TASK_OPTION_HIDDEN ) )
        visible_tasks[num_visible_tasks++] = taskdata;
    }
    /* sort the list of tasks */
    qsort ( tasks, num_tasks, sizeof ( TaskData * ), sort_task_by_name );
    qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
      sort_task_by_name );
  }

  time ( &now );
  icon = icons[now%8];
  mask = icon_masks[now%8];
  for ( i = 0; i < num_visible_tasks; i++ ) {
    visible_tasks[i]->moved = 1;
    task = visible_tasks[i]->task;
    row[0] = noproject;
    if ( task->project_id >= 0 ) {
      p = projectGet ( task->project_id );
      row[0] = p->name;
    }
    row[1] = task->name;
    sprintf ( today_str, "00:00:00" );
    row[2] = today_str;
    sprintf ( total_str, "00:00:00" );
    row[3] = total_str;
    gtk_clist_append ( GTK_CLIST(task_list), row );
    if ( tasks[i]->timer_on )
      gtk_clist_set_pixtext (GTK_CLIST (task_list), i, 0, 
        visible_tasks[i]->project_name, 2, icon, mask);
    else
      gtk_clist_set_pixtext (GTK_CLIST (task_list), i, 0, 
        visible_tasks[i]->project_name, 2, blankicon, blankicon_mask);
  }
  /* gtk_clist_thaw ( GTK_CLIST(task_list) ); */

  first = 0;
}

/*
** Check for a new version of GTimer.
** Notice that, Unlike Xt, you don't have to add the timeout again :-)
*/
static gint version_timeout_handler ( gpointer data ) {
  time_t now;
  char *next_check, now_str[20];
  int do_check = FALSE;

  /**
   * 2003/05/09 Chad Walstrom <chewie@debian.org>
   *   Since this auto version checking is not configurable -- it can't be shut
   *   off -- we'll just shortcut the whole process and return TRUE like a
   *   happy camper.
   */
  return ( TRUE );

  time ( &now );
  sprintf ( now_str, "%ul", (unsigned int)now );
  if ( configGetAttribute ( CONFIG_NEXT_VERSION_CHECK, &next_check ) == 0 ) {
    if ( strcmp ( now_str, next_check ) > 0 ) {
      /* time for another check! */
      do_check = TRUE;
    }
  } else {
    /* have never checked! */
    do_check = TRUE;
  }

  if ( do_check ) {
    version_check_is_auto = TRUE;
    check_version_callback ( NULL );
  }

  /* return TRUE to so this timeout happens again */
  return ( TRUE );
}


/*
** Handle the update.  This gets called every 1 second.
** Notice that, Unlike Xt, you don't have to add the timeout again :-)
*/
static gint timeout_handler ( gpointer data ) {
  time_t now;
  struct tm *tm;
  int loop;
  gint w, h, x, y;
  static gint last_x, last_y;
  static time_t last_move = 0;
  GdkModifierType mask;
  char *ptr;
  int idle;

  time ( &now );

  /* remove splash window ? */
  if ( splash_window && now > splash_until ) {
    if ( GTK_IS_WIDGET ( splash_window ) )
      gtk_widget_destroy ( splash_window );
    splash_window = NULL;
    gtk_widget_show ( main_window );
    if ( configGetAttributeInt ( CONFIG_MAIN_WINDOW_WIDTH, &w ) == 0 &&
      configGetAttributeInt ( CONFIG_MAIN_WINDOW_HEIGHT, &h ) == 0 ) {
      gdk_window_resize ( GTK_WIDGET ( main_window )->window, w, h );
    }
    gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
      NULL, appicon2, appicon2_mask );
    if ( move_to_task >= 0 ) {
      gtk_clist_moveto ( GTK_CLIST ( task_list ), move_to_task, -1, 0.5, 0 );
      move_to_task = -1;
    }
  } else {
    gdk_window_get_pointer ( GTK_WIDGET ( main_window )->window,
      &x, &y, &mask );
    if ( x != last_x || y != last_y ) {
      last_x = x;
      last_y = y;
      last_move = now;
    }
  }

  /* Check to see if the date has changed. */
  now -= config_midnight_offset;
  tm = localtime ( &now );
  if ( today_mday != tm->tm_mday ) {
    update_tasks ();
    today_year = tm->tm_year + 1900;
    today_mon = tm->tm_mon + 1;
    today_mday = tm->tm_mday;
    for ( loop = 0; loop < num_tasks; loop++ ) {
      if ( tasks[loop]->todays_entry )
        tasks[loop]->total += tasks[loop]->todays_entry->seconds;
      tasks[loop]->todays_entry = taskGetTimeEntry ( tasks[loop]->task,
        today_year, today_mon, today_mday );
      if ( tasks[loop]->timer_on ) {
        if ( ! tasks[loop]->todays_entry )
          tasks[loop]->todays_entry = taskNewTimeEntry ( tasks[loop]->task,
            today_year, today_mon, today_mday );
        time ( &tasks[loop]->on_since );
      }
    }
  }

  /* Update the list */
  update_list ();

  /* have we been idle for too long? */
  if ( num_timing && configGetAttribute ( CONFIG_IDLE, &ptr ) == 0 &&
    ! idle_prompt_window && config_idle_enabled ) {
#ifdef HAVE_SCREEN_SAVER_EXT
    idle = (int) get_x_idle_time ( GDK_DISPLAY() );
#else
    time ( &now );
    idle = (int) ( now - last_move );
#endif
    if ( idle > config_max_idle) {
      /* we've been idle too long. mark time, save to file, then popup window */
      update_tasks ();
      save_all ();
      for ( loop = 0; loop < num_tasks; loop++ ) {
        if ( tasks[loop]->timer_on ) {
          taskMark ( tasks[loop]->task, idle );
        } else {
          taskMark ( tasks[loop]->task, 0 );
        }
      }
      time ( &now );
      now -= idle;
      tm = localtime ( &now );
      ptr = (char *) malloc ( 500 );
      sprintf ( ptr, "%s\n%d %s (%s %d:%02d)\n\n",
        gettext("You have been idle for"),
        config_max_idle / 60, gettext("minutes"), gettext("since"),
        tm->tm_hour, tm->tm_min );
      strcat ( ptr, gettext("You may now:") );
      strcat ( ptr, "\n\n" );
      strcat ( ptr, gettext("Revert to back to before the idle") );
      strcat ( ptr, "\n\n" );
      strcat ( ptr, gettext("Continue timing, ignoring the idle") );
      strcat ( ptr, "\n\n" );
      strcat ( ptr, gettext("Resume timing from when the idle started") );
      idle_prompt_window = create_confirm_toplevel ( CONFIRM_WARNING,
        gettext("Idle Detect"), ptr,
        gettext("Revert"), gettext("Continue"), gettext("Resume"),
         idle_reset_callback, idle_cancel_callback, idle_resume_callback,
         NULL );
      free ( ptr );
    }
  }

  /* autosave ? (do not autosave if idle) */
  time ( &now );
  if ( modified_since_save &&
    ( now > ( last_save + config_autosave_interval ) ) &&
    config_autosave_enabled && ( idle_prompt_window == NULL ) ) {
    save_all ();
  }

  /* clear message area? */
  if ( lastMessageTime && ( now - lastMessageTime > messageDisplayTime ) ) {
    showMessage ( NULL ); /* clear msg area */
  }

  /* return TRUE to so this timeout happens again in 1 second */
  return ( TRUE );
}




void create_splash_window () {
  GtkWidget *table, *pixmap, *label;
  GdkPixmap *icon;
  GdkBitmap *mask;
  char msg[500];
  GtkStyle *style;

  splash_window = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
  gtk_window_set_wmclass ( GTK_WINDOW ( splash_window ), "GTimer", "gtimer" );
  gtk_window_set_title ( GTK_WINDOW ( splash_window ), "GTimer" );
  gtk_widget_set_usize ( splash_window, 450, 180 );
  gtk_window_position ( GTK_WINDOW ( splash_window ), GTK_WIN_POS_CENTER );
  gtk_widget_realize ( splash_window );
  gdk_window_set_decorations ( GTK_WIDGET ( splash_window )->window,
    GDK_DECOR_BORDER );

  table = gtk_table_new ( 2, 2, FALSE );
  gtk_table_set_row_spacings (GTK_TABLE (table), 4);
  gtk_table_set_col_spacings (GTK_TABLE (table), 8);
  gtk_container_border_width (GTK_CONTAINER (table), 6);
  gtk_container_add ( GTK_CONTAINER ( splash_window ), table );

  icon = gdk_pixmap_create_from_xpm_d (
    GTK_WIDGET ( splash_window )->window, &mask,
    &splash_window->style->white, splash_xpm );
  pixmap = gtk_pixmap_new ( icon, mask );
  gtk_misc_set_alignment (GTK_MISC (pixmap), 0.0, 0.5);
  gtk_table_attach (GTK_TABLE (table), pixmap, 0, 1, 0, 2,
    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show ( pixmap );

  style = gtk_style_new ();

  sprintf ( msg, "GTimer v%s", GTIMER_VERSION );
  label = gtk_label_new ( msg );
  gtk_table_attach (GTK_TABLE (table), label, 1, 2, 0, 1,
    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show ( label );

  sprintf ( msg, "%s\nGTK Version: %d.%d.%d\n",
    GTIMER_COPYRIGHT,
    gtk_major_version, gtk_minor_version, gtk_micro_version );
  label = gtk_label_new ( msg );
  gtk_table_attach (GTK_TABLE (table), label, 1, 2, 1, 2,
    GTK_FILL, GTK_FILL, 0, 0);
  gtk_widget_show ( label );

  gtk_widget_show ( table );
  gtk_widget_show ( splash_window );

  time ( &splash_until );
  splash_until += splash_seconds;
}


void create_main_window () {
  GtkWidget *vbox;
  GtkWidget *menu_bar, *toolbutton, *iconw, *table, *scroll, *handlebox;
  GdkPixmap *icon;
  GdkBitmap *mask;
  int loop;
  gint w;

  main_window = gtk_window_new ( GTK_WINDOW_TOPLEVEL );
  gtk_window_set_wmclass( GTK_WINDOW ( main_window ), "GTimer", "gtimer" );
  gtk_signal_connect ( GTK_OBJECT ( main_window ), "delete_event",
    GTK_SIGNAL_FUNC ( exit_callback ), NULL );
  gtk_signal_connect ( GTK_OBJECT ( main_window ), "destroy",
    GTK_SIGNAL_FUNC ( exit_callback ), NULL );
  gtk_window_set_title (GTK_WINDOW (main_window), "GTimer" );
  gtk_widget_realize ( main_window );

  vbox = gtk_vbox_new ( FALSE, 0 );
  gtk_container_add ( GTK_CONTAINER ( main_window ), vbox );

  menu_bar = create_main_window_menu_bar();
  handlebox = gtk_handle_box_new ();
  gtk_container_add ( GTK_CONTAINER ( handlebox ), menu_bar );
  gtk_widget_show ( handlebox );

  gtk_box_pack_start ( GTK_BOX ( vbox ), handlebox, FALSE, FALSE, 0 );
  gtk_widget_show ( menu_bar );

  /* create toolbar */
  toolbar = gtk_toolbar_new ();
  gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) );

  gtk_box_pack_start ( GTK_BOX ( vbox ), toolbar, FALSE, FALSE, 0 );
/*
  toolbar_handlebox = gtk_handle_box_new ();
  gtk_container_add ( GTK_CONTAINER ( toolbar_handlebox ), toolbar );
  gtk_widget_show ( toolbar_handlebox );

  gtk_box_pack_start ( GTK_BOX ( vbox ), toolbar_handlebox, FALSE, FALSE, 0 );
*/

  for ( loop = 0; main_toolbar[loop].label != NULL; loop++ ) {
    icon = gdk_pixmap_create_from_xpm_d (
      GTK_WIDGET ( main_window )->window, &mask,
      &main_window->style->white, main_toolbar[loop].icon_data );
    iconw = gtk_pixmap_new ( icon, mask );
    toolbutton = gtk_toolbar_append_item ( GTK_TOOLBAR ( toolbar ),
      gettext ( main_toolbar[loop].label ),
      gettext ( main_toolbar[loop].tooltip_index ), "Private",
      iconw, GTK_SIGNAL_FUNC ( main_toolbar[loop].callback ), NULL );
    main_toolbar[loop].widget = toolbutton;
    gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) );
    gtk_widget_show ( toolbutton );
  }

  if ( config_toolbar_enabled )
    gtk_widget_show ( toolbar );

  /* add in list here */
  if ( configGetAttributeInt ( CONFIG_MAIN_WINDOW_PROJECT_WIDTH, &w ) == 0 )
    task_list_columns[0].width = w;
  if ( configGetAttributeInt ( CONFIG_MAIN_WINDOW_TASK_WIDTH, &w ) == 0 )
    task_list_columns[1].width = w;
  if ( configGetAttributeInt ( CONFIG_MAIN_WINDOW_TODAY_WIDTH, &w ) == 0 )
    task_list_columns[2].width = w;
  if ( configGetAttributeInt ( CONFIG_MAIN_WINDOW_TOTAL_WIDTH, &w ) == 0 )
    task_list_columns[3].width = w;
  task_list = create_list_column_def ( 4, task_list_columns );
  gtk_clist_set_selection_mode (GTK_CLIST (task_list), GTK_SELECTION_BROWSE);
  gtk_widget_set_usize (GTK_WIDGET (task_list), 350, 150);
#if OLD_GTK
  gtk_clist_set_policy (GTK_CLIST (task_list),
    GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
#endif
  gtk_signal_connect (GTK_OBJECT (task_list), "click_column",
    GTK_SIGNAL_FUNC (column_selected_callback), NULL);
  gtk_signal_connect (GTK_OBJECT (task_list), "event",
    GTK_SIGNAL_FUNC (task_list_event), NULL);
  gtk_signal_connect_after (GTK_OBJECT (task_list), "select_row",
    GTK_SIGNAL_FUNC (task_selected_callback), NULL);

#if OLD_GTK
  gtk_box_pack_start ( GTK_BOX ( vbox ), task_list, TRUE, TRUE, 0 );
#else
  scroll = gtk_scrolled_window_new ( NULL, NULL );
  gtk_widget_set_usize (GTK_WIDGET (scroll), 400, 150);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
    GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
  gtk_container_add (GTK_CONTAINER ( scroll ), task_list );
  gtk_box_pack_start ( GTK_BOX ( vbox ), scroll, TRUE, TRUE, 0 );
  gtk_widget_show ( scroll );
#endif
  gtk_widget_show ( task_list );

  /* add a status area and a place for the total time for today */
  table = gtk_table_new ( 10, 1, FALSE );
  gtk_box_pack_start ( GTK_BOX ( vbox ), table, FALSE, TRUE, 2 );

  status = gtk_statusbar_new ();
  gtk_table_attach_defaults ( GTK_TABLE (table), status, 0, 9, 0, 1 );
  gtk_widget_show (status);
  status_id = gtk_statusbar_get_context_id ( GTK_STATUSBAR ( status ),
    GTIMER_STATUS_ID );

  total_label = gtk_label_new ( "Total: 0:00:00" );
  gtk_table_attach_defaults ( GTK_TABLE (table), total_label, 9, 10, 0, 1 );
  gtk_label_set_justify ( GTK_LABEL ( total_label ), GTK_JUSTIFY_RIGHT );
  gtk_widget_show (total_label);

  gtk_widget_show (table);

  gtk_widget_show (vbox);
}



static void print_version () {
  /*int gtkmajor, gtkminor, gtkmicro;*/
  printf ( "GTimer v%s (%s)\n", GTIMER_VERSION, GTIMER_VERSION_DATE );
  /*
  gtkmajor = GTK_VERSION / 10000;
  gtkminor = ( GTK_VERSION / 100 ) % 100;
  gtkmicro = GTK_VERSION % 100;
  printf ( "%s %d.%d.%d\n", gettext ( "Compiled with GTK+" ),
    gtkmajor, gtkminor, gtkmicro );
  */
  printf ( "%s: %d.%d.%d\n",
    gettext ( "GTK+ runtime" ),
    gtk_major_version, gtk_minor_version, gtk_micro_version );
  printf ( "%s: %s\n", gettext ( "Home page" ), GTIMER_URL );
  printf ( "%s\n", GTIMER_COPYRIGHT );
}


static void print_help () {
  printf ( "GTimer %s:\n", gettext ( "options" ) );
  printf ( "%-20s %s\n", "-nosplash",
    gettext ( "don't display the splash screen" ) );
  printf ( "%-20s %s\n", "-help",
    gettext ( "display this help info" ) );
  printf ( "%-20s %s\n", "-version",
    gettext ( "display the version" ) );
  printf ( "%-20s %s\n", "-midnight N",
    gettext ( "specify the midnight offset" ) );
  printf ( "%-20s %s\n", "-start taskname",
    gettext ( "start timing the specified task" ) );
  printf ( "%-20s %s\n", "-weekstart N",
    gettext ( "use N as the first day of weeks" ) );
}



int main ( int argc, char *argv[] ) {
  char *home = "";
#ifndef WIN32
  uid_t uid;
  struct passwd *passwd;
#endif
  time_t now;
  struct tm *tm;
  int loop, loop2, offset, lastTaskNumber;
  char *ptr, *ptr2;
  struct stat buf;
  int display_splash = 1;
  int resume = 0; /* start time tasks from last exit */
  GtkWidget *win;
  int w = 0, h = 0;
#ifdef HAVE_LIBINTL_H
#ifdef WIN32
  char *localedir;
#endif
#endif
  char *matches[100];
  int nmatches = 0, found;
  TaskData *td;
  char msg[128];

#if PV_DEBUG
  g_message ( "PV: DEBUG version");
#endif

  if ( getenv ( "HOME" ) ) {
    home = getenv ( "HOME" );
  }
#ifndef WIN32
  else {
    uid = getuid ();
    passwd = getpwuid ( uid );
    if ( passwd )
      home = passwd->pw_dir;
  }
#endif

  taskdir = (char *) malloc ( strlen ( home ) +
    strlen ( TASK_DIRECTORY ) + 10 );
  sprintf ( taskdir, "%s/%s", home, TASK_DIRECTORY );
#ifdef WIN32
  convert_backslash ( taskdir );
#endif

  if ( stat ( taskdir, &buf ) != 0 ) {
    /* check for ".tasktimer" directory for backwards compatiblity */
    sprintf ( taskdir, "%s/%s", home, ".tasktimer" );
    /* Note: in 2.0 it seems to be good idea to create new command-line option '--config' */
#ifdef WIN32
    convert_backslash ( taskdir );
#endif
    if ( stat ( taskdir, &buf ) != 0 ) {
      sprintf ( taskdir, "%s/%s", home, TASK_DIRECTORY );
#ifdef WIN32
      convert_backslash ( taskdir );
      if ( _mkdir ( taskdir ) ) {
#else
      if ( mkdir ( taskdir, 0777 ) ) {
#endif
        fprintf ( stderr, "Error: unable to create directory %s\n", taskdir );
        exit ( 1 );
      }
    }
  }

#ifdef HAVE_LIBINTL_H
  /* internationalization stuff */
  setlocale ( LC_MESSAGES, "" );
#ifdef WIN32
  localedir = (char *) malloc ( strlen ( taskdir ) + 8 );
  sprintf ( localedir, "%s/locale", taskdir );
  convert_backslash(localedir);
  bindtextdomain ( DEFAULT_TEXT_DOMAIN, localedir );
  free ( localedir );
#else
  bindtextdomain(DEFAULT_TEXT_DOMAIN, LOCALEDIR);
#endif
//  textdomain ( "gtimer" );
#endif
  bind_textdomain_codeset(DEFAULT_TEXT_DOMAIN, "UTF-8");

  /* Init GTK */
  gtk_init ( &argc, &argv );
#if OLD_GTK
#else
  gtkrc = (char *) malloc ( strlen ( taskdir ) +
    strlen ( "gtkrc" ) + 2 );
  sprintf ( gtkrc, "%s/%s", taskdir, "gtkrc" );
  gtk_rc_parse ( gtkrc );
#endif

  /* Examine command line args */
  for ( loop = 1; loop < argc; loop++ ) {
    if ( strcmp ( argv[loop], "-dir" ) == 0 ) {
      if ( ! argv[loop+1] ) {
        fprintf ( stderr, "%s: -dir %s.\n",
          gettext("Error"), gettext("requires an argument") );
        exit ( 1 );
      }
      taskdir = argv[++loop];
#ifdef HAVE_LIBINTL_H
#ifdef WIN32
      localedir = (char *) malloc ( strlen ( taskdir ) + 8 );
      sprintf ( localedir, "%s/locale", taskdir );
      convertbackslash(localedir);
      bindtextdomain ( "gtimer", localedir );
      free ( localedir );
#endif
#endif
#if OLD_GTK
#else
      if ( gtkrc )
        free ( gtkrc );
      gtkrc = (char *) malloc ( strlen ( taskdir ) +
        strlen ( "gtkrc" ) + 2 );
      sprintf ( gtkrc, "%s/%s", taskdir, "gtkrc" );
      gtk_rc_parse ( gtkrc );
#endif
    } else if ( strcmp ( argv[loop], "-nosplash" ) == 0 ) {
      display_splash = 0;
    } else if ( strcmp ( argv[loop], "-resume" ) == 0 ) {
      resume = 1;
    } else if ( strcmp ( argv[loop], "-midnight" ) == 0 ) {
      if ( ! argv[loop+1] ) {
        fprintf ( stderr, "%s: -midnight %s.\n",
          gettext("Error"), gettext("requires an argument") );
        exit ( 1 );
      }
      for ( ptr = argv[++loop]; *ptr != '\0'; ptr++ ) {
        if ( ! isdigit ( *ptr ) && *ptr != '-' ) {
          fprintf ( stderr, "%s: -midnight %s (%s %s)\n",
            gettext("Error"), gettext("requires a number"),
            gettext("not"), argv[loop] );
          exit ( 1 );
        }
      }
      ptr = argv[loop];
      if ( *ptr == '-' )
        offset = atoi ( ptr + 1 );
      else
        offset = atoi ( ptr );
      if ( offset > 2359 ) {
        fprintf ( stderr, "%s -midnight: %s\n",
          gettext("Invalid offset for"), argv[loop] );
        fprintf ( stderr, "%s HHMM (<2359)\n",
          gettext("Format should be") );
        exit ( 1 );
      }
      config_midnight_offset = ( offset / 100 * 3600 ) + ( offset % 100 * 60 );
      if ( *ptr == '-' )
        config_midnight_offset *= -1;
    } else if ( strcmp ( argv[loop], "-weekstart" ) == 0 ) {
      if ( ! argv[loop+1] ) {
        fprintf ( stderr, "%s: -weekstart %s.\n",
          gettext("Error"), gettext("requires an argument") );
        exit ( 1 );
      }
      for ( ptr = argv[++loop]; *ptr != '\0'; ptr++ ) {
        if ( ! isdigit ( *ptr ) ) {
          fprintf ( stderr, "%s: -weekstart %s (%s %s)\n",
            gettext("Error"), gettext("requires a number"),
            gettext("not"), argv[loop] );
          exit ( 1 );
        }
      }
      config_start_of_week = atoi ( argv[loop] );
      if ( config_start_of_week > 6 ) {
        fprintf ( stderr, "%s -weekstart: %s\n",
          gettext("Invalid day of week number for"), argv[loop] );
        exit ( 1 );
      }
    } else if ( strcmp ( argv[loop], "-start" ) == 0 ) {
      if ( nmatches < 99 )
        matches[nmatches++] = argv[++loop];
    } else if ( strcmp ( argv[loop], "-v" ) == 0 ||
      strcmp ( argv[loop], "-version" ) == 0 ||
      strcmp ( argv[loop], "--version" ) == 0 ) {
      print_version ();
      exit ( 0 );
    } else if ( strcmp ( argv[loop], "-h" ) == 0 ||
      strcmp ( argv[loop], "-help" ) == 0 ||
      strcmp ( argv[loop], "--help" ) == 0 ) {
      print_version ();
      print_help ();
      exit ( 0 );
    } else {
      fprintf ( stderr, "%s: %s\n",
        gettext("Ingoring unknown option"), argv[loop] );
    }
  }

  /* read config values */
  config_file = (char *) malloc ( strlen ( taskdir ) + 
    strlen ( CONFIG_DEFAULT_FILE ) + 2 );
  sprintf ( config_file, "%s/%s", taskdir, CONFIG_DEFAULT_FILE );
  configReadAttributes ( config_file );

  /* Get the toolbar setting */
  configGetAttributeInt ( CONFIG_TOOLBAR_STATUS, &config_toolbar_enabled );

  /* Get the animate setting */
  configGetAttributeInt ( CONFIG_ANIMATE, &config_animate_enabled );

  /* Get the autosave setting */
  configGetAttributeInt ( CONFIG_AUTOSAVE, &config_autosave_enabled );

  /* Get the idle delay */
  if ( configGetAttributeInt ( CONFIG_IDLE_ON, &config_idle_enabled ) < 0 )
    config_idle_enabled = 1;
  if ( configGetAttributeInt ( CONFIG_IDLE, &config_max_idle ) < 0 )
    config_max_idle = 15 * 60; /* default */

  /* PV: Set menu check boxes according to config */
  if ( config_toolbar_enabled )
    MM_ToggleEntries[MM_TOGGLE_TOOLBAR].is_active = TRUE;
    
  if ( config_animate_enabled )
    MM_ToggleEntries[MM_TOGGLE_ANIMATE].is_active = TRUE;

   if ( config_autosave_enabled )
    MM_ToggleEntries[MM_TOGGLE_AUTOSAVE].is_active = TRUE;

   if ( config_idle_enabled )
    MM_ToggleEntries[MM_TOGGLE_IDLE].is_active = TRUE;
 
  /* in the future check version number and pop up license and/or
  ** release notes if a new version
  */
  configSetAttribute ( CONFIG_VERSION, GTIMER_VERSION );

  /* load all projects */
  projectLoadAll ( taskdir );

  /* load all tasks */
  taskLoadAll ( taskdir );

  /* Create splash window */
  if ( display_splash )
    create_splash_window ();

  /* Create window */
  create_main_window ();

  /* move main window */
  if ( configGetAttributeInt ( CONFIG_MAIN_WINDOW_WIDTH, &w ) == 0 &&
    configGetAttributeInt ( CONFIG_MAIN_WINDOW_HEIGHT, &h ) == 0 ) {
    /* wait until after gtk_widget_show() to resize */
  }

  if ( ! splash_window ) {
    gtk_widget_show ( main_window );
    if ( w )
      gdk_window_resize ( GTK_WIDGET ( main_window )->window, w, h );
  }

  /* set application icon */
  win = splash_window ? splash_window : main_window;
  appicon = gdk_pixmap_create_from_xpm_d (
    GTK_WIDGET ( win )->window, &appicon_mask,
    &win->style->white, gtimer_xpm);
  appicon2 = gdk_pixmap_create_from_xpm_d (
    GTK_WIDGET ( win )->window, &appicon2_mask,
    &win->style->white, gtimer2_xpm);
  if ( ! splash_window )
    gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
      NULL, num_timing ? appicon : appicon2,
      num_timing ? appicon_mask : appicon2_mask );

  /* set the current date */
  time ( &now );
  now -= config_midnight_offset;
  tm = localtime ( &now );
  today_year = tm->tm_year + 1900;
  today_mon = tm->tm_mon + 1;
  today_mday = tm->tm_mday;

  /* build and update the task list */
  build_list ();
  update_list ();
  update_toolbar_buttons ();

  /* sort list like it was last time */
  if ( configGetAttribute ( CONFIG_SORT, &ptr ) == 0 ) {
    last_sort = atoi ( ptr );
    if ( configGetAttribute ( CONFIG_SORT_FORWARD, &ptr ) == 0 )
      sort_forward = ! atoi ( ptr );
    else
      sort_forward = 0;
    column_selected_callback ( NULL, last_sort );
  } else {
    sort_forward = 0;
    column_selected_callback ( NULL, 0 );
  }

  /* handle tasks specified with -start */
  for ( loop = 0; ! resume && loop < nmatches; loop++ ) {
    found = 0;
    for ( loop2 = 0; loop2 < num_visible_tasks && ! found; loop2++ ) {
      td =  visible_tasks[loop2];
      if ( strcmp ( td->task->name, matches[loop] ) == 0 ) {
        found = 1;
        num_timing++;
        td->timer_on = 1;
        time ( &td->on_since );
        if ( td->todays_entry == NULL )
          td->todays_entry = taskNewTimeEntry ( td->task, today_year,
            today_mon, today_mday );
        /* select the task */
        gtk_clist_select_row ( GTK_CLIST ( task_list ), loop2, 0 );
        /* make task visible */
        move_to_task = loop2;
        if ( ! splash_window )
          gtk_clist_moveto ( GTK_CLIST ( task_list ), loop2, -1, 0.5, 0 );
      }
    }
    if ( ! found ) {
      fprintf ( stderr, "%s \"%s\" %s.\n",
        gettext ( "Task" ), matches[loop], gettext ( "not found" ) );
    }
  }

  if ( resume &&
    ( configGetAttribute ( CONFIG_LAST_TIMED_TASKS, &ptr ) == 0 ) ) {
    ptr = strdup ( ptr );
    for ( ptr2 = strtok ( ptr, "," ); ptr2 != NULL;
      ptr2 = strtok ( NULL, "," ) ) {
      lastTaskNumber = atoi ( ptr2 );
      for ( loop = 0; loop < num_visible_tasks; loop++ ) {
        td =  visible_tasks[loop];
        if ( td->task->number == lastTaskNumber ) {
          num_timing++;
          td->timer_on = 1;
          time ( &td->on_since );
          if ( td->todays_entry == NULL )
            td->todays_entry = taskNewTimeEntry ( td->task, today_year,
              today_mon, today_mday );
          /* select the task */
          gtk_clist_select_row ( GTK_CLIST ( task_list ), loop, 0 );
          /* make task visible */
          move_to_task = loop2;
          if ( ! splash_window )
            gtk_clist_moveto ( GTK_CLIST ( task_list ), loop, -1, 0.5, 0 );
          break;
        }
      }
    }
    free ( ptr );
  }

  /* Add a timeout to update the display once a second */
  gtk_timeout_add ( 1000, timeout_handler, NULL );

  /* Add a timeout to check for a new version in 30 seconds
     (We will just check every 30 seconds to see if we should check */
  gtk_timeout_add ( 30 * 1000, version_timeout_handler, NULL );

  /* record time for use with autosave */
  time ( &last_save );
  modified_since_save = 0;

  /* set x error handler... */
#ifdef WIN32
  set_x_error_handler ();
#endif

  sprintf ( msg, "%s GTimer %s", gettext("Welcome to"),
     GTIMER_VERSION );
  showMessage ( msg );

  /* Loop endlessly */
  gtk_main ();

#ifdef GTIMER_MEMDEBUG
  /* memory debugging... make sure md_print_all gets linked in so we can
  ** call it from gdb.
  */
  for ( loop = 0; loop < num_tasks; loop++ ) {
    taskFree ( tasks[loop]->task );
    free ( tasks[loop] );
  }
  free ( tasks );
  free ( visible_tasks );
  md_print_all ();
#endif

  return ( 0 );
}


/*
 * Update the message area at the bottom of the screen.
 * If msg == NULL, the area will be cleared.
 * The message will be cleared automatically after a while.
 */
void showMessage ( msg )
char *msg;
{
  gtk_statusbar_push ( GTK_STATUSBAR ( status ), status_id, msg ? msg : "" );
  if ( msg == NULL )
    lastMessageTime = 0;
  else
    time ( &lastMessageTime );
}



/*
** Determine the path to an executable using the $PATH environment
** variable.
** Return value should be freed (unless it's NULL!)
*/
char *get_client_path ( char *file )
{
  char *path;
  static char *path2 = "/usr/bin/X11:/usr/local/bin:/usr/bin:/opt/local/bin";
  char *env;
  struct stat buf;
  char *ret, *ptr;

  env = getenv ( "PATH" );
  if ( env ) {
    path = (char *) malloc ( strlen ( env ) + strlen ( path2 ) + 2 );
    sprintf ( path, "%s:%s", env, path2 );
  } else {
    path = (char *) malloc ( strlen ( path2 ) + 1 );
    strcpy ( path, path2 );
  }

  ptr = strtok ( path, ":" );
  while ( ptr ) {
    ret = (char *) malloc ( strlen ( ptr ) + strlen ( file ) + 2 );
    sprintf ( ret, "%s/%s", ptr, file );
    if ( stat ( ret, &buf ) == 0 ) {
      /* found it */
      free ( path );
      return ( ret );
    }
    ptr = strtok ( NULL, ":" );
  }

  /* not found */
  free ( path );
  return ( NULL );
}
