/*
* 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, cknudsen@cknudsen.com, http://www.cknudsen.com
*
* Home Page:
* http://www.cknudsen.com/gtimer/
*
* History:
* 09-Mar-2003 Added support for -noversioncheck option
* 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>
#ifdef HAVE_LIBINTL_H
#include <libintl.h>
#include <locale.h>
#else
#define gettext(a) a
#endif
#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"
#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;
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 GTK_VERSION > 10100
GtkAccelGroup* mainag;
#endif
static sockfd connection = -1;
static gint gdk_input_id = -1;
static int version_check_is_auto = 0;
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 */
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 ( GtkWidget *widget, gpointer data );
static void changelog_callback ( GtkWidget *widget, gpointer data );
static void save_callback ( GtkWidget *widget, gpointer data );
static void exit_callback ( GtkWidget *widget, gpointer data );
static void start_callback ( GtkWidget *widget, gpointer data );
static void switch_to_callback ( GtkWidget *widget, gpointer data );
static void stop_callback ( GtkWidget *widget, gpointer data );
static void stop_all_callback ( GtkWidget *widget, gpointer data );
static void task_add_callback ( GtkWidget *widget, gpointer data );
static void task_edit_callback ( GtkWidget *widget, gpointer data );
static void task_hide_callback ( GtkWidget *widget, gpointer data );
static void task_unhide_callback ( GtkWidget *widget, gpointer data );
static void task_delete_callback ( GtkWidget *widget, gpointer data );
static void increment_time_callback ( GtkWidget *widget, gpointer data );
static void decrement_time_callback ( GtkWidget *widget, gpointer data );
static void project_add_callback ( GtkWidget *widget, gpointer data );
static void project_edit_callback ( GtkWidget *widget, gpointer data );
static void report_callback ( GtkWidget *widget, gpointer data );
static void annotate_callback ( GtkWidget *widget, gpointer data );
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 ( GtkWidget *widget, gpointer data );
static void idle_toggle_callback ( GtkWidget *widget, gpointer data );
static void autosave_toggle_callback ( GtkWidget *widget, gpointer data );
static void animate_toggle_callback ( GtkWidget *widget, gpointer data );
static void check_version_callback ( GtkWidget *widget, gpointer data );
/*
** Structure for defining the the pulldown menus.
*/
typedef struct {
int label_index;
void (*callback)();
gpointer data;
gint acckey;
GdkModifierType accmod;
} TTPulldown;
#define LABEL_ABOUT 1
#define LABEL_CHANGELOG 2
#define LABEL_CHECK_VERSION 3
#define LABEL_SAVE 4
#define LABEL_EXIT 5
#define LABEL_TOOLBAR 6
#define LABEL_IDLE_DETECT 7
#define LABEL_AUTO_SAVE 8
#define LABEL_ANIMATE 9
#define LABEL_START_TIMING 10
#define LABEL_STOP_TIMING 11
#define LABEL_STOP_ALL_TIMING 12
#define LABEL_NEW 13
#define LABEL_EDIT 14
#define LABEL_ANNOTATE 15
#define LABEL_HIDE 16
#define LABEL_UNHIDE 17
#define LABEL_DELETE 18
#define LABEL_INCREMENT_5 19
#define LABEL_INCREMENT_30 20
#define LABEL_DECREMENT_5 21
#define LABEL_DECREMENT_30 22
#define LABEL_SET_TO_ZERO 23
#define LABEL_DAILY 24
#define LABEL_WEEKLY 25
#define LABEL_MONTHLY 26
#define LABEL_YEARLY 27
#define TOOLTIP_START 100
#define TOOLTIP_STOP 101
#define TOOLTIP_STOP_ALL 102
#define TOOLTIP_ANNOTATE 103
#define TOOLTIP_ADD 104
#define TOOLTIP_EDIT 105
/* File pulldown menu */
TTPulldown file_menu[] = {
{ LABEL_SAVE, save_callback, NULL, 'w', GDK_CONTROL_MASK },
{ 0, NULL, NULL, 0, 0 },
{ LABEL_EXIT, exit_callback, NULL, 'q', GDK_CONTROL_MASK },
{ -1, NULL, NULL, 0, 0 }
};
/* Options pulldown menu */
TTPulldown options_menu[] = {
{ LABEL_TOOLBAR, toolbar_toggle_callback, NULL, 0, 0 },
{ LABEL_ANIMATE, animate_toggle_callback, NULL, 0, 0 },
{ LABEL_IDLE_DETECT, idle_toggle_callback, NULL, 0, 0 },
{ LABEL_AUTO_SAVE, autosave_toggle_callback, NULL, 0, 0 },
{ -1, NULL, NULL, 0, 0 }
};
/* Task pulldown menu */
TTPulldown task_menu[] = {
{ LABEL_START_TIMING, start_callback, NULL, 's', GDK_CONTROL_MASK },
{ LABEL_STOP_TIMING, stop_callback, NULL, 0, 0 },
{ LABEL_STOP_ALL_TIMING, stop_all_callback, NULL, 'x', GDK_CONTROL_MASK },
{ 0, NULL, NULL, 0, 0 },
{ LABEL_NEW, task_add_callback, NULL, 'n', GDK_CONTROL_MASK },
{ LABEL_EDIT, task_edit_callback, NULL, 'e', GDK_CONTROL_MASK },
{ LABEL_ANNOTATE, annotate_callback, NULL, 'a', GDK_CONTROL_MASK },
{ LABEL_HIDE, task_hide_callback, NULL, 'h', GDK_CONTROL_MASK },
{ LABEL_UNHIDE, task_unhide_callback, NULL, 'u', GDK_CONTROL_MASK },
{ LABEL_DELETE, task_delete_callback, NULL, 0, 0 },
{ 0, NULL, NULL, 0, 0 },
{ LABEL_INCREMENT_5, increment_time_callback, (gpointer) (5*60), 'i',
GDK_CONTROL_MASK },
{ LABEL_INCREMENT_30, increment_time_callback, (gpointer) (30*60), 0, 0 },
{ LABEL_DECREMENT_5, decrement_time_callback, (gpointer) (5*60), 'd',
GDK_CONTROL_MASK },
{ LABEL_DECREMENT_30, decrement_time_callback, (gpointer) (30*60), 0, 0 },
{ LABEL_SET_TO_ZERO, increment_time_callback, (gpointer) 0, 0, 0 },
{ -1, NULL, NULL, 0, 0 }
};
/* Project pulldown menu */
TTPulldown project_menu[] = {
{ LABEL_NEW, project_add_callback, NULL, 'n', GDK_CONTROL_MASK },
{ LABEL_EDIT, project_edit_callback, NULL, 'e', GDK_CONTROL_MASK },
{ -1, NULL, NULL, 0, 0 }
};
/* Report pulldown menu */
TTPulldown report_menu[] = {
{ LABEL_DAILY, report_callback, (gpointer)REPORT_TYPE_DAILY, 0, 0 },
{ LABEL_WEEKLY, report_callback, (gpointer)REPORT_TYPE_WEEKLY, 0, 0 },
{ LABEL_MONTHLY, report_callback, (gpointer)REPORT_TYPE_MONTHLY, 0, 0 },
{ LABEL_YEARLY, report_callback, (gpointer)REPORT_TYPE_YEARLY, 0, 0 },
{ -1, NULL, NULL, 0, 0 }
};
/* Tools pulldown menu */
TTPulldown tools_menu[] = {
{ LABEL_CHECK_VERSION, check_version_callback, NULL, 0, 0 },
{ -1, NULL, NULL, 0, 0 }
};
/* Help pulldown menu */
TTPulldown help_menu[] = {
{ LABEL_ABOUT, about_callback, NULL, 0, 0 },
{ LABEL_CHANGELOG, changelog_callback, NULL, 0, 0 },
{ -1, NULL, NULL, 0, 0 }
};
/*
** Structure for defining the toolbar
*/
typedef struct {
char *label;
int 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[] = {
{ "Start", TOOLTIP_START, start_xpm,
start_callback, NULL, NULL },
{ "Stop", TOOLTIP_STOP, stop_xpm,
stop_callback, NULL, NULL },
{ "Stop All", TOOLTIP_STOP_ALL, stop_all_xpm,
stop_all_callback, NULL, NULL },
{ "Annotate", TOOLTIP_ANNOTATE, annotate_xpm,
annotate_callback, NULL, NULL },
{ "Add", TOOLTIP_ADD, new_xpm,
task_add_callback, NULL, NULL },
{ "Edit", TOOLTIP_EDIT, 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;
}
/*
** Get the label for a pulldown menu item.
** (Required to support I18N.)
*/
static char *get_label_text ( ind )
int ind;
{
switch ( ind ) {
case LABEL_ABOUT: return ( gettext ("About...") );
case LABEL_CHANGELOG: return ( gettext ("View Change Log...") );
case LABEL_SAVE: return ( gettext ("Save") );
case LABEL_CHECK_VERSION: return ( gettext ("Check for New Version") );
case LABEL_EXIT: return ( gettext ("Exit") );
case LABEL_IDLE_DETECT: return ( gettext("Idle Detect") );
case LABEL_TOOLBAR: return ( gettext("Toolbar") );
case LABEL_ANIMATE: return ( gettext("Animate") );
case LABEL_AUTO_SAVE: return ( gettext("Auto Save") );
case LABEL_START_TIMING: return ( gettext("Start Timing") );
case LABEL_STOP_TIMING: return ( gettext("Stop Timing") );
case LABEL_STOP_ALL_TIMING: return ( gettext("Stop All Timing") );
case LABEL_NEW: return ( gettext("New...") );
case LABEL_EDIT: return ( gettext("Edit...") );
case LABEL_ANNOTATE: return ( gettext("Annotate...") );
case LABEL_HIDE: return ( gettext("Hide") );
case LABEL_UNHIDE: return ( gettext("Unhide...") );
case LABEL_DELETE: return ( gettext("Delete") );
case LABEL_INCREMENT_5: return ( gettext("Increment 5 minutes") );
case LABEL_INCREMENT_30: return ( gettext("Increment 30 minutes") );
case LABEL_DECREMENT_5: return ( gettext("Decrement 5 minutes") );
case LABEL_DECREMENT_30: return ( gettext("Decrement 30 minutes") );
case LABEL_SET_TO_ZERO: return ( gettext("Set to Zero") );
case LABEL_DAILY: return ( gettext("Daily...") );
case LABEL_WEEKLY: return ( gettext("Weekly...") );
case LABEL_MONTHLY: return ( gettext("Monthly...") );
case LABEL_YEARLY: return ( gettext("Yearly...") );
case TOOLTIP_START:
return ( gettext("Start Timing the Selected Task") );
case TOOLTIP_STOP:
return ( gettext("Stop Timing the Selected Task") );
case TOOLTIP_STOP_ALL:
return ( gettext("Stop Timing All Tasks") );
case TOOLTIP_ANNOTATE:
return ( gettext("Add Annotation to Selected Task") );
case TOOLTIP_ADD:
return ( gettext("Add New Task") );
case TOOLTIP_EDIT:
return ( gettext("Edit Name of the Selected Task") );
default:
fprintf ( stderr, "Unknown label (%d)!\n", ind );
exit ( 1 );
}
return ( "XXX" );
}
static int sort_task_by_project ( 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 );
}
static int sort_task_by_name ( td1, td2 )
TaskData **td1;
TaskData **td2;
{
TaskData *tda = *td1;
TaskData *tdb = *td2;
int ret;
ret = ( strcmp ( tda->task->name, tdb->task->name ) );
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
gint w, h;
char temp[128];
int loop;
/* save task data */
save_all ();
/* save window size */
gdk_window_get_size ( GTK_WIDGET ( main_window )->window, &w, &h );
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
gtk_statusbar_push ( GTK_STATUSBAR ( status ), status_id,
gettext("All data saved") );
save_all ();
}
static void about_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
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\ncknudsen@cknudsen.com\n\n" );
create_confirm_window ( CONFIRM_ABOUT,
gettext("About"),
text,
gettext("Ok"), NULL, NULL, NULL, NULL, NULL, NULL );
}
static void changelog_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
display_changelog ();
}
static void task_add_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
create_task_edit_window ( NULL );
}
static void task_edit_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 );
gtk_statusbar_push ( GTK_STATUSBAR ( status ), status_id,
gettext("Task hidden") );
}
}
static void task_unhide_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
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--;
gtk_statusbar_push ( GTK_STATUSBAR ( status ), status_id,
gettext("Task removed") );
update_list ();
}
static void task_delete_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
int active = GTK_CHECK_MENU_ITEM ( widget )->active;
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
int active = GTK_CHECK_MENU_ITEM ( widget )->active;
if ( active )
configSetAttributeInt ( CONFIG_ANIMATE, 1 );
else
configSetAttributeInt ( CONFIG_ANIMATE, 0 );
config_animate_enabled = active;
}
static void idle_toggle_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
int active = GTK_CHECK_MENU_ITEM ( widget )->active;
if ( active )
configSetAttributeInt ( CONFIG_IDLE_ON, 1 );
else
configSetAttributeInt ( CONFIG_IDLE_ON, 0 );
config_idle_enabled = active;
}
static void autosave_toggle_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
int active = GTK_CHECK_MENU_ITEM ( widget )->active;
if ( active )
configSetAttributeInt ( CONFIG_AUTOSAVE, 1 );
else
configSetAttributeInt ( CONFIG_AUTOSAVE, 0 );
config_autosave_enabled = active;
}
static void project_add_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
create_project_edit_window ( NULL );
}
static void project_edit_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 report_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
report_type rt = (report_type) data;
update_tasks ();
create_report_window ( rt );
}
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 ) {
td->todays_entry->seconds = 0;
} else if ( offset < 0 ) {
if ( td->todays_entry->seconds < ( 0 - offset ) ) {
td->todays_entry->seconds = 0;
} else {
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;
idle_prompt_window = NULL;
#if OLD_IMPLEMENTATION
/* clear all tasks out of memory */
taskClearAll ();
for ( loop = 0; loop < num_tasks; loop++ ) {
free ( tasks[loop] );
tasks[loop] = NULL;
}
free ( tasks );
tasks = NULL;
num_tasks = 0;
for ( loop = 0; loop < num_visible_tasks; loop++ ) {
visible_tasks[loop] = NULL;
}
free ( visible_tasks );
visible_tasks = NULL;
num_visible_tasks = 0;
num_timing = 0;
/* now reload tasks from the data files */
taskLoadAll ( taskdir );
/* recreate the selection list */
build_list ();
update_list ();
/* 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 );
}
#endif
taskRestoreAll ();
for ( loop = 0; loop < num_tasks; loop++ ) {
if ( tasks[loop]->timer_on ) {
tasks[loop]->timer_on = 0;
}
}
update_list ();
/* reset icon */
gdk_window_set_icon ( GTK_WIDGET ( main_window )->window,
NULL, appicon2, appicon2_mask );
}
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.
*/
static void idle_resume_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
int loop;
idle_prompt_window = NULL;
taskRestoreAll ();
for ( loop = 0; loop < num_visible_tasks; loop++ ) {
if ( visible_tasks[loop]->timer_on )
time ( &visible_tasks[loop]->on_since );
}
update_list ();
}
static void increment_time_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
int offset = (int)data;
adjust_task_time ( offset );
}
static void decrement_time_callback ( widget, data )
GtkWidget *widget;
gpointer data;
{
int offset = (int)data;
adjust_task_time ( 0 - offset );
}
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 ( widget, data )
GtkWidget *widget;
gpointer data;
{
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 ) {
GtkWidget *menu;
GtkWidget *menu_item;
int loop;
menu = gtk_menu_new ();
#if GTK_VERSION > 10100
if ( is_main ) {
menu_item = gtk_tearoff_menu_item_new ();
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
#endif
for ( loop = 0; task_menu[loop].label_index >= 0; loop++ ) {
if ( task_menu[loop].label_index > 0 )
menu_item = gtk_menu_item_new_with_label (
get_label_text ( task_menu[loop].label_index ) );
else
menu_item = gtk_menu_item_new ();
if ( task_menu[loop].callback )
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
GTK_SIGNAL_FUNC ( task_menu[loop].callback ),
(gpointer) task_menu[loop].data );
if ( task_menu[loop].acckey && is_main ) {
#if GTK_VERSION > 10100
gtk_accel_group_add ( mainag,task_menu[loop].acckey,
task_menu[loop].accmod, GTK_ACCEL_VISIBLE||GTK_ACCEL_LOCKED,
GTK_OBJECT(menu_item), "activate" );
#endif
} else {
gtk_signal_connect (GTK_OBJECT (menu), "event",
GTK_SIGNAL_FUNC (pulldown_event), NULL);
}
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
return menu;
}
/*
** 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;
if ( col == last_sort )
sort_forward = ! sort_forward;
else
sort_forward = 1;
last_sort = col;
switch ( col ) {
case 0:
qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
sort_task_by_project );
break;
case 1:
qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
sort_task_by_name );
break;
case 2:
qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
sort_task_by_today );
break;
default:
case 3:
qsort ( visible_tasks, num_visible_tasks, sizeof ( TaskData * ),
sort_task_by_total );
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 );
}
/*
** Create the main window's menu bar.
*/
static GtkWidget *create_main_window_menu_bar ()
{
GtkWidget *menu;
GtkWidget *root_menu;
GtkWidget *menu_item;
GtkWidget *menu_bar;
int loop;
menu_bar = gtk_menu_bar_new ();
#if GTK_VERSION > 10100
mainag = gtk_accel_group_new ();
#endif
/* File menu */
root_menu = gtk_menu_item_new_with_label ( gettext("File") );
menu = gtk_menu_new ();
#if GTK_VERSION > 10100
menu_item = gtk_tearoff_menu_item_new ();
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
#endif
for ( loop = 0; file_menu[loop].label_index >= 0; loop++ ) {
if ( file_menu[loop].label_index > 0 )
menu_item = gtk_menu_item_new_with_label (
get_label_text ( file_menu[loop].label_index ) );
else
menu_item = gtk_menu_item_new ();
if ( file_menu[loop].callback )
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
GTK_SIGNAL_FUNC ( file_menu[loop].callback ),
(gpointer) file_menu[loop].data );
#if GTK_VERSION > 10100
if ( file_menu[loop].acckey )
gtk_accel_group_add ( mainag, file_menu[loop].acckey,
file_menu[loop].accmod, GTK_ACCEL_VISIBLE||GTK_ACCEL_LOCKED,
GTK_OBJECT(menu_item), "activate" );
#endif
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
gtk_widget_show (root_menu);
/* Options menu */
root_menu = gtk_menu_item_new_with_label ( gettext("Options") );
menu = gtk_menu_new ();
#if GTK_VERSION > 10100
menu_item = gtk_tearoff_menu_item_new ();
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
#endif
for ( loop = 0; options_menu[loop].label_index >= 0; loop++ ) {
if ( options_menu[loop].label_index > 0 ) {
option_menu_items[loop] = menu_item =
gtk_check_menu_item_new_with_label (
get_label_text ( options_menu[loop].label_index ) );
}
else
menu_item = gtk_menu_item_new ();
if ( options_menu[loop].callback )
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
GTK_SIGNAL_FUNC ( options_menu[loop].callback ),
(gpointer) options_menu[loop].data );
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
gtk_widget_show (root_menu);
/* Task menu */
root_menu = gtk_menu_item_new_with_label ( gettext("Task") );
menu = create_task_pulldown ( TRUE );
#if GTK_VERSION > 10100
gtk_menu_set_accel_group ( GTK_MENU(menu), mainag );
#endif
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
gtk_widget_show (root_menu);
/* Project menu */
root_menu = gtk_menu_item_new_with_label ( gettext("Project") );
menu = gtk_menu_new ();
#if GTK_VERSION > 10100
menu_item = gtk_tearoff_menu_item_new ();
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
#endif
for ( loop = 0; project_menu[loop].label_index >= 0; loop++ ) {
if ( project_menu[loop].label_index > 0 )
menu_item = gtk_menu_item_new_with_label (
get_label_text ( project_menu[loop].label_index ) );
else
menu_item = gtk_menu_item_new ();
if ( project_menu[loop].callback )
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
GTK_SIGNAL_FUNC ( project_menu[loop].callback ),
(gpointer) project_menu[loop].data );
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
gtk_widget_show (root_menu);
/* Report menu */
root_menu = gtk_menu_item_new_with_label ( gettext("Report") );
menu = gtk_menu_new ();
#if GTK_VERSION > 10100
menu_item = gtk_tearoff_menu_item_new ();
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
#endif
for ( loop = 0; report_menu[loop].label_index >= 0; loop++ ) {
if ( report_menu[loop].label_index > 0 )
menu_item = gtk_menu_item_new_with_label (
get_label_text ( report_menu[loop].label_index ) );
else
menu_item = gtk_menu_item_new ();
if ( report_menu[loop].callback )
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
GTK_SIGNAL_FUNC ( report_menu[loop].callback ),
(gpointer) report_menu[loop].data );
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
gtk_widget_show (root_menu);
/* Tools menu */
root_menu = gtk_menu_item_new_with_label ( gettext("Tools") );
menu = gtk_menu_new ();
#if GTK_VERSION > 10100
menu_item = gtk_tearoff_menu_item_new ();
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
#endif
for ( loop = 0; tools_menu[loop].label_index >= 0; loop++ ) {
if ( tools_menu[loop].label_index > 0 ) {
menu_item = gtk_menu_item_new_with_label (
get_label_text ( tools_menu[loop].label_index ) );
}
else
menu_item = gtk_menu_item_new ();
if ( tools_menu[loop].callback )
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
GTK_SIGNAL_FUNC ( tools_menu[loop].callback ),
(gpointer) tools_menu[loop].data );
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
gtk_widget_show (root_menu);
/* Help menu */
root_menu = gtk_menu_item_new_with_label ( gettext("Help") );
gtk_menu_item_right_justify ( GTK_MENU_ITEM ( root_menu ) );
menu = gtk_menu_new ();
#if GTK_VERSION > 10100
menu_item = gtk_tearoff_menu_item_new ();
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
#endif
for ( loop = 0; help_menu[loop].label_index >= 0; loop++ ) {
if ( help_menu[loop].label_index > 0 ) {
menu_item = gtk_menu_item_new_with_label (
get_label_text ( help_menu[loop].label_index ) );
}
else
menu_item = gtk_menu_item_new ();
if ( help_menu[loop].callback )
gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
GTK_SIGNAL_FUNC ( help_menu[loop].callback ),
(gpointer) help_menu[loop].data );
gtk_menu_append ( GTK_MENU ( menu ), menu_item );
gtk_widget_show ( menu_item );
}
gtk_menu_item_set_submenu (GTK_MENU_ITEM (root_menu), menu);
gtk_menu_bar_append (GTK_MENU_BAR (menu_bar), root_menu);
gtk_widget_show (root_menu);
return ( menu_bar );
}
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;
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, 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 ();
taskMarkAll ();
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 ();
}
/* 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, 400, 200 );
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 ();
gdk_font_unref ( style->font );
style->font = gdk_font_load (
"-adobe-helvetica-bold-r-normal-*-24-*-*-*-*-*-iso8859-1" );
if ( style->font == NULL )
style->font = gdk_font_load ( "fixed" );
gtk_widget_push_style ( style );
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 );
gtk_widget_pop_style ();
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;
char msg[50];
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 );
#if GTK_VERSION > 10100
gtk_accel_group_attach ( mainag, GTK_OBJECT(main_window) );
#endif
/* create toolbar */
toolbar = gtk_toolbar_new ( GTK_ORIENTATION_HORIZONTAL,
GTK_TOOLBAR_ICONS );
gtk_toolbar_set_space_size ( GTK_TOOLBAR ( toolbar ), 2 );
gtk_toolbar_append_space ( GTK_TOOLBAR ( toolbar ) );
#if GTK_VERSION > 10100
gtk_toolbar_set_button_relief ( GTK_TOOLBAR ( toolbar ), GTK_RELIEF_NONE );
#endif
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 ),
main_toolbar[loop].label,
get_label_text ( 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 GTK_VERSION < 10100
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 GTK_VERSION < 10100
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 );
sprintf ( msg, "%s GTimer %s", gettext("Welcome to"),
GTIMER_VERSION );
gtk_statusbar_push ( GTK_STATUSBAR ( status ), status_id, msg );
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" ) );
}
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 */
int noversioncheck = 0; /* don't check for new version */
GtkWidget *win;
int w = 0, h = 0;
#ifdef HAVE_LIBINTL_H
char *localedir;
#endif
char *matches[100];
int nmatches = 0, found;
TaskData *td;
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" );
#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, "%s: %s %s\n",
gettext("Error"), gettext("unable to create directory"), taskdir );
exit ( 1 );
}
}
}
#ifdef HAVE_LIBINTL_H
/* internationalization stuff */
setlocale ( LC_ALL, "" );
localedir = (char *) malloc ( strlen ( taskdir ) + 8 );
sprintf ( localedir, "%s/locale", taskdir );
bindtextdomain ( "gtimer", localedir );
free ( localedir );
textdomain ( "gtimer" );
#endif
/* Init GTK */
gtk_init ( &argc, &argv );
#if GTK_VERSION > 10100
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
localedir = (char *) malloc ( strlen ( taskdir ) + 8 );
sprintf ( localedir, "%s/locale", taskdir );
bindtextdomain ( "gtimer", localedir );
free ( localedir );
#endif
#if GTK_VERSION > 10100
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], "-noversioncheck" ) == 0 ) {
noversioncheck = 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], "-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 */
/* 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 ();
gtk_check_menu_item_set_state ( GTK_CHECK_MENU_ITEM ( option_menu_items[0] ),
config_toolbar_enabled );
gtk_check_menu_item_set_state ( GTK_CHECK_MENU_ITEM ( option_menu_items[1] ),
config_animate_enabled );
gtk_check_menu_item_set_state ( GTK_CHECK_MENU_ITEM ( option_menu_items[2] ),
config_idle_enabled );
gtk_check_menu_item_set_state ( GTK_CHECK_MENU_ITEM ( option_menu_items[3] ),
config_autosave_enabled );
/* 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 */
if ( ! noversioncheck )
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
/* 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 );
}