Re: [Planner Dev] Fixes for gantt bugz # 148637, 149359 and 149651





Richard,
	
	I've redone  this ...and in the best open source
tradition its completely refactored and has new features ;)

I've still got the 10,000 day task limit in as I  think this
is an important sanity check which is already in place in
the Task dialog.

For my new feature, you can now SHIFT+click drag a Fixed
duration task in gantt to update its duration (as opposed
to the work) (I always wanted to play with the modifier keys ;)

That funny safe time value was 1st Jan 2038 so I now use,
	mrp_time_compose( 2038,1,1,0,0,0);

as thats more obvious.

The reason for both setting the time values to the correct
limits outside the loop and then checking for verflow within
the loop is that,

	t = planner_scale_time_next (t, priv->major_unit);

can actually overflow within the loop.  My checks seemed
safer on my testing.

I've fixed my comments.

I've moved this property undo to planner-task-cmd.c/planner-task-cmd.h
and it now handles both work and duration updates.

Rgds,
Lincoln.
Index: libplanner/mrp-task-manager.c
===================================================================
RCS file: /cvs/gnome/planner/libplanner/mrp-task-manager.c,v
retrieving revision 1.10
diff -u -B -b -p -r1.10 mrp-task-manager.c
--- libplanner/mrp-task-manager.c	2 Aug 2004 22:47:19 -0000	1.10
+++ libplanner/mrp-task-manager.c	10 Aug 2004 00:20:44 -0000
@@ -2165,11 +2165,12 @@ mrp_task_manager_calculate_task_work (Mr
 				      mrptime         finish)
 {
 	MrpTaskManagerPriv *priv;
-	gint                work = 0;
+	gint                work = 0, this_work = 0;
 	MrpAssignment      *assignment;
 	MrpResource        *resource;
 	GList              *assignments, *a;
-	MrpCalendar        *calendar;
+	MrpCalendar        *calendar = NULL, *last_calendar = NULL;
+       gint                units = 0, last_units = 0;
 
 	priv = manager->priv;
 
@@ -2194,9 +2195,23 @@ mrp_task_manager_calculate_task_work (Mr
 	 */
 
 	assignments = mrp_task_get_assignments (task);
+
+	if (!assignments) {
+		calendar = mrp_project_get_calendar (priv->project);
+
+		work = task_manager_get_work_for_calendar (manager,
+							   calendar,
+							   start,
+							   finish);
+	} else {
 	for (a = assignments; a; a = a->next) {
 		assignment = a->data;
 
+                        last_units = units;
+                        last_calendar = calendar;
+                        
+                        units = mrp_assignment_get_units (assignment);
+                        
 		resource = mrp_assignment_get_resource (assignment);
 
 		calendar = mrp_resource_get_calendar (resource);
@@ -2204,20 +2219,24 @@ mrp_task_manager_calculate_task_work (Mr
 			calendar = mrp_project_get_calendar (priv->project);
 		}
 
-		work += task_manager_get_work_for_calendar (manager,
-							    calendar,
-							    start,
-							    finish) *
-			mrp_assignment_get_units (assignment) / 100;
-	}
-	
-	if (!assignments) {
-		calendar = mrp_project_get_calendar (priv->project);
+	/* A simple check to see if the last calendar and units is the same as this    */
+        /* calendar and units. If so then resuse last calculated work value. This MAY  */
+        /* help speed up gantt if we have multiple resources on this task.             */
+        /* TODO: Ideally we should cache the work calculations based on a key of ....  */
+        /* manager, calendar, start, finish, units */
+        
+		        if (last_calendar == calendar && last_units == units) {
+        	                work += this_work;                                          
+		        } else {
 
-		work = task_manager_get_work_for_calendar (manager,
+	/* Maths done this way to make sure we don't have an integer OVERFLOW on large workloads */
+			this_work = (task_manager_get_work_for_calendar (manager,
 							   calendar,
 							   start,
-							   finish);
+							   finish) / 100.0) * units;
+			work += this_work;
+		        }
+                }
 	}
 
 	return work;
Index: libplanner/mrp-time.h
===================================================================
RCS file: /cvs/gnome/planner/libplanner/mrp-time.h,v
retrieving revision 1.1.1.1
diff -u -B -b -p -r1.1.1.1 mrp-time.h
--- libplanner/mrp-time.h	1 Dec 2003 17:36:21 -0000	1.1.1.1
+++ libplanner/mrp-time.h	10 Aug 2004 00:20:44 -0000
@@ -33,6 +33,7 @@ typedef long mrptime;
 #define MRP_TIME_INVALID 0
 #define MRP_TIME_MIN 0
 #define MRP_TIME_MAX 2147483647
+#define MRP_WORK_MAX 288000000 
 
 mrptime      mrp_time_current_time       (void);
 
Index: src/planner-gantt-header.c
===================================================================
RCS file: /cvs/gnome/planner/src/planner-gantt-header.c,v
retrieving revision 1.2
diff -u -B -b -p -r1.2 planner-gantt-header.c
--- src/planner-gantt-header.c	11 Dec 2003 10:52:46 -0000	1.2
+++ src/planner-gantt-header.c	10 Aug 2004 00:20:48 -0000
@@ -498,7 +498,7 @@ gantt_header_expose_event (GtkWidget    
 	PlannerGanttHeader     *header;
 	PlannerGanttHeaderPriv *priv;
 	gint               width, height;
-	gdouble            hscale;
+	gdouble            hscale, tresult;
 	gint               x;
 	mrptime            t0;
 	mrptime            t1;
@@ -508,13 +508,27 @@ gantt_header_expose_event (GtkWidget    
 	gint               major_width;
 	GdkGC             *gc;
 	GdkRectangle       rect;
+        gboolean           overflow;
 	
 	header = PLANNER_GANTT_HEADER (widget);
 	priv = header->priv;
 	hscale = priv->hscale;
 
-	t0 = floor ((priv->x1 + event->area.x) / hscale + 0.5);
-	t1 = floor ((priv->x1 + event->area.x + event->area.width) / hscale + 0.5);
+        /* FIXME: This wraps at around year 2038 due to use of signed 32 bit fields */
+        
+        tresult = floor ((priv->x1 + event->area.x) / hscale + 0.5);
+        if (tresult >= MRP_TIME_MAX) {
+                t0 = MRP_TIME_MAX - event->area.width;
+        } else { 
+                t0 = tresult;
+        }
+        
+        tresult = floor ((priv->x1 + event->area.x + event->area.width) / hscale + 0.5);
+        if (tresult >= MRP_TIME_MAX) {
+                t1 = MRP_TIME_MAX;
+        } else {
+                t1 = tresult;
+        }
 
 	gdk_drawable_get_size (event->window, &width, &height);
 
@@ -562,8 +576,10 @@ gantt_header_expose_event (GtkWidget    
 	}
 	
 	t = planner_scale_time_prev (t0, priv->major_unit);
+       overflow = FALSE;
+	
+	while (t <= t1 && !overflow) {
 	
-	while (t <= t1) {
 		x = floor (t * hscale - priv->x1 + 0.5);
 		
 		gdk_draw_line (event->window,
@@ -590,6 +606,9 @@ gantt_header_expose_event (GtkWidget    
 				 priv->layout);
 		
 		t = planner_scale_time_next (t, priv->major_unit);
+                if ( t < t0 ) {  /* The overflow detector that scales for all ranges */
+                        overflow = TRUE;
+                }
 	}
 
  minor_ticks:
@@ -601,8 +620,10 @@ gantt_header_expose_event (GtkWidget    
 	}
 	
 	t = planner_scale_time_prev (t0, priv->minor_unit);
+        overflow = FALSE;
+
+	while (t <= t1 && !overflow) {
 
-	while (t <= t1) {
 		x = floor (t * hscale - priv->x1 + 0.5);
 
 		gdk_draw_line (event->window,
@@ -629,6 +650,9 @@ gantt_header_expose_event (GtkWidget    
 				 priv->layout);
 
 		t = planner_scale_time_next (t, priv->minor_unit);
+                if ( t < t0 ) {  /* The overflow detector thats fine for all ranges */
+                        overflow = TRUE;
+                }
 	}
 
  done:
Index: src/planner-gantt-row.c
===================================================================
RCS file: /cvs/gnome/planner/src/planner-gantt-row.c,v
retrieving revision 1.19
diff -u -B -b -p -r1.19 planner-gantt-row.c
--- src/planner-gantt-row.c	31 Jul 2004 21:32:54 -0000	1.19
+++ src/planner-gantt-row.c	10 Aug 2004 00:20:49 -0000
@@ -4,6 +4,7 @@
  * Copyright (C) 2001-2003 CodeFactory AB
  * Copyright (C) 2001-2003 Richard Hult <richard imendio com>
  * Copyright (C) 2001-2003 Mikael Hallendal <micke imendio com>
+ * Copyright (C) 2004 Lincoln Phipps <lincoln phipps openmutual net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -80,9 +81,10 @@ enum {
 typedef enum {
 	STATE_NONE          = 0,
 	STATE_DRAG_LINK     = 1 << 0,
-	STATE_DRAG_DURATION = 1 << 1,
+	STATE_DRAG_END_WORK     = 1 << 1,
+       STATE_DRAG_END_DURATION = 1 << 2,
 
-	STATE_DRAG_ANY = STATE_DRAG_LINK | STATE_DRAG_DURATION
+	STATE_DRAG_ANY = STATE_DRAG_LINK | STATE_DRAG_END_WORK | STATE_DRAG_END_DURATION
 } State;
 
 struct _PlannerGanttRowPriv {
@@ -1461,7 +1463,7 @@ gantt_row_scroll_timeout_cb (PlannerGant
 	return TRUE;
 }
 
-#define IN_DRAG_DURATION_SPOT(x,y,right,top,height) \
+#define IN_DRAG_END_SPOT(x,y,right,top,height) \
 	((abs(x - (right)) <= 3) && \
 	(y > top + 0.15 * height) && (y < top + 0.70 * height))
 
@@ -1546,14 +1548,25 @@ gantt_row_event (GnomeCanvasItem *item, 
 			}
 
 			if (type != MRP_TASK_TYPE_MILESTONE &&
-			    !summary && IN_DRAG_DURATION_SPOT (event->button.x,
+			    !summary && IN_DRAG_END_SPOT (event->button.x,
 							       event->button.y,
 							       priv->x + priv->width,
 							       priv->y,
 							       priv->height)) {
 				guint rgba;
 				
-				priv->state = STATE_DRAG_DURATION;
+                                /* User is requesting (with the shift) to change duration, though this is 
+                                 * ONLY directly possible on fixed duration tasks. 
+                                 */ 
+                                if (event->button.state & GDK_SHIFT_MASK) {
+                                        if (mrp_task_get_sched (priv->task) == MRP_TASK_SCHED_FIXED_DURATION) {
+				                priv->state = STATE_DRAG_END_DURATION;
+                                        } else { 
+                                                priv->state = STATE_DRAG_END_WORK;
+                                        }
+                                } else {
+                                        priv->state = STATE_DRAG_END_WORK;
+                                }
 
 				wx1 = priv->x;
 				wy1 = priv->y + 0.15 * priv->height;
@@ -1677,13 +1690,20 @@ gantt_row_event (GnomeCanvasItem *item, 
 		if (event->motion.is_hint) {
 			gint x, y;
 
+                        /* FIXME: Certain weird conditions can cause x value to drop by 1/2. This 
+                         *        happens on large work values e.g. 9000 days and when the end is 
+                         *        dragged to the border of your screen (i.e far right side) and   
+                         *        happens when the pointer exceeds the window size.               
+                         */
+                        
 			gdk_window_get_pointer (event->motion.window, &x, &y, NULL);
+                                                
 			gnome_canvas_c2w (item->canvas, x, y, &event->motion.x, &event->motion.y);
 		}
 
 		if (!(priv->state & STATE_DRAG_ANY)) {
 			if (type != MRP_TASK_TYPE_MILESTONE &&
-			    !summary && IN_DRAG_DURATION_SPOT (event->button.x,
+			    !summary && IN_DRAG_END_SPOT (event->button.x,
 							       event->button.y,
 							       priv->x + priv->width,
 							       priv->y,
@@ -1776,12 +1796,13 @@ gantt_row_event (GnomeCanvasItem *item, 
 			
 			old_target_item = target_item;
 		}
-		else if (priv->state == STATE_DRAG_DURATION) {
+		else if (priv->state == STATE_DRAG_END_WORK || priv->state == STATE_DRAG_END_DURATION) {
 			gint         duration;
 			gint         work;
 			MrpProject  *project;
 			MrpCalendar *calendar;
 			gint         hours_per_day;
+                        mrptime      start, end, safe_max;
 
 			project = mrp_object_get_project (MRP_OBJECT (priv->task));
 			calendar = mrp_project_get_calendar (project);
@@ -1810,16 +1831,43 @@ gantt_row_event (GnomeCanvasItem *item, 
 			/* Snap to quarters. */
 			duration = floor (duration / SNAP + 0.5) * SNAP;
 
+                        start = mrp_task_get_start (priv->task);
+                        
+                        /* FIXME: We can't go beyond late January 2038 on some systems. Need unsigned 32 or 
+			 * 64 bit time handling. 
+                         * The safe_max keeps the task end away from the end of the unix dates else it
+                         * hits up against the gantt header end time.
+                         */
+                        
+                        safe_max = mrp_time_compose (2038, 1, 1, 0, 0, 0);
+                                                
+                        if ((safe_max - duration) <= start) {
+                                end = safe_max;
+                        } else {
+                                end = start + duration;
+                        }
+                                                
 			work = mrp_project_calculate_task_work (
 				project,
 				priv->task,
 				-1,
-				mrp_task_get_start (priv->task) + duration);
+				end);
+
+                        if (work > MRP_WORK_MAX) {
+                                work = MRP_WORK_MAX;
+                        }
 
+                        if (priv->state == STATE_DRAG_END_WORK) {
 			message = g_strdup_printf (
 				_("Change work to %s"),
 				planner_format_duration (work, hours_per_day));
 			planner_gantt_chart_status_updated (chart, message);
+                        } else {
+                                message = g_strdup_printf (
+				        _("Change duration to %s"),
+				        planner_format_duration (work, hours_per_day));
+			                planner_gantt_chart_status_updated (chart, message);
+                        }
 			g_free (message);
 		}
 			
@@ -1837,10 +1885,14 @@ gantt_row_event (GnomeCanvasItem *item, 
 		if (priv->state == STATE_NONE) {
 			return TRUE;
 		}
-		else if (priv->state == STATE_DRAG_DURATION) {
+		else if (priv->state == STATE_DRAG_END_WORK || priv->state == STATE_DRAG_END_DURATION) {
 			MrpProject *project;
 			gint        duration;
 			gint        work;
+                        PlannerWindow     *main_window;
+                        PlannerCmd        *cmd;
+                        mrptime           start, end, safe_max;
+                        GValue            value = { 0 };
 
 			project = mrp_object_get_project (MRP_OBJECT (priv->task));
 			
@@ -1848,22 +1900,53 @@ gantt_row_event (GnomeCanvasItem *item, 
 			/* Snap to quarters. */
 			duration = floor (duration / SNAP + 0.5) * SNAP;
 
+                        start = mrp_task_get_start (priv->task);
+                                      
+                        safe_max = mrp_time_compose (2038, 1, 1, 0, 0, 0);
+                                 
+                        if ((safe_max - duration) <= start) {
+                                end = safe_max;
+                        } else {
+                                end = start + duration;
+                        }
+                                                
 			work = mrp_project_calculate_task_work (
 				project,
 				priv->task,
 				-1,
-				mrp_task_get_start (priv->task) + duration);
+				end);
 			
-			g_object_set (priv->task,
-				      "work", work,
-				      NULL);
-
-			gtk_object_destroy (GTK_OBJECT (drag_item));
-			drag_item = NULL;
+                        if (work > MRP_WORK_MAX) {
+                                work = MRP_WORK_MAX;
+                        }
 
 			chart = g_object_get_data (G_OBJECT (item->canvas),
 						   "chart");
 
+                        main_window = planner_task_tree_get_window ( planner_gantt_chart_get_view (chart));
+                        
+                        g_value_init (&value, G_TYPE_INT);
+	                g_value_set_int (&value, work);
+        
+                        /* If the task is a fixed duration and the SHIFT key was pressed then we update
+                         * the duration else we update the work.
+                         */
+                         
+                        if (priv->state == STATE_DRAG_END_WORK ) {
+                                cmd = planner_task_cmd_edit_property (main_window,
+			                                             priv->task,
+                                                                     "work",
+			                                             &value);
+                        } else { 
+                                cmd = planner_task_cmd_edit_property (main_window,
+			                                             priv->task,
+                                                                     "duration",
+			                                             &value);  
+                        }
+        
+			gtk_object_destroy (GTK_OBJECT (drag_item));
+			drag_item = NULL;
+                                                   
 			planner_gantt_chart_status_updated (chart, NULL);
 		}
 		else if (priv->state == STATE_DRAG_LINK) {
Index: src/planner-task-cmd.c
===================================================================
RCS file: /cvs/gnome/planner/src/planner-task-cmd.c,v
retrieving revision 1.4
diff -u -B -b -p -r1.4 planner-task-cmd.c
--- src/planner-task-cmd.c	5 Aug 2004 22:08:38 -0000	1.4
+++ src/planner-task-cmd.c	10 Aug 2004 00:20:50 -0000
@@ -1,6 +1,7 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
 /*
  * Copyright (C) 2004 Alvaro del Castillo <acs barrapunto com>
+ * Copyright (C) 2004 Lincoln Phipps <lincoln phipps openmutual net>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +35,15 @@ typedef struct {
 	GError           *error;
 } TaskCmdLink;
 
+typedef struct {
+	PlannerCmd base;
+
+	MrpTask *task;
+	gchar   *property;  
+	GValue  *value;
+	GValue  *old_value;
+} TaskCmdEditProperty;
+
 static gboolean
 task_cmd_link_do (PlannerCmd *cmd_base)
 {
@@ -287,3 +297,80 @@ planner_task_cmd_insert (PlannerWindow  
 
 	return cmd_base;
 }
+
+static gboolean
+task_cmd_edit_property_do (PlannerCmd *cmd_base)
+{
+	TaskCmdEditProperty *cmd;
+
+	cmd = (TaskCmdEditProperty*) cmd_base;
+
+	g_object_set_property (G_OBJECT (cmd->task),
+			       cmd->property,
+			       cmd->value);
+
+	return TRUE;
+}
+
+static void
+task_cmd_edit_property_undo (PlannerCmd *cmd_base)
+{
+	TaskCmdEditProperty *cmd;
+
+	cmd = (TaskCmdEditProperty*) cmd_base;
+
+	g_object_set_property (G_OBJECT (cmd->task),
+			       cmd->property,
+			       cmd->old_value);
+}
+
+static void
+task_cmd_edit_property_free (PlannerCmd *cmd_base)
+{
+	TaskCmdEditProperty *cmd;
+
+	cmd = (TaskCmdEditProperty*) cmd_base;
+
+	g_value_unset (cmd->value);
+	g_value_unset (cmd->old_value);
+
+	g_free (cmd->property);
+	g_object_unref (cmd->task);
+}
+
+PlannerCmd *
+planner_task_cmd_edit_property	(PlannerWindow *main_window,
+			        MrpTask       *task,
+			        const gchar   *property,
+			        const GValue  *value)
+{
+	PlannerCmd          *cmd_base;
+	TaskCmdEditProperty *cmd;
+
+	cmd_base = planner_cmd_new (TaskCmdEditProperty,
+				    _("Edit task property"),
+				    task_cmd_edit_property_do,
+				    task_cmd_edit_property_undo,
+				    task_cmd_edit_property_free);
+
+	cmd = (TaskCmdEditProperty *) cmd_base;
+
+	cmd->property = g_strdup (property);
+	cmd->task = g_object_ref (task);
+
+	cmd->value = g_new0 (GValue, 1);
+	g_value_init (cmd->value, G_VALUE_TYPE (value));
+	g_value_copy (value, cmd->value);
+
+	cmd->old_value = g_new0 (GValue, 1);
+	g_value_init (cmd->old_value, G_VALUE_TYPE (value));
+
+	g_object_get_property (G_OBJECT (task),
+			       cmd->property,
+			       cmd->old_value);
+
+	planner_cmd_manager_insert_and_do (planner_window_get_cmd_manager (main_window),
+					   cmd_base);
+
+	return cmd_base;
+}
Index: src/planner-task-cmd.h
===================================================================
RCS file: /cvs/gnome/planner/src/planner-task-cmd.h,v
retrieving revision 1.1
diff -u -B -b -p -r1.1 planner-task-cmd.h
--- src/planner-task-cmd.h	14 Jun 2004 15:28:50 -0000	1.1
+++ src/planner-task-cmd.h	10 Aug 2004 00:20:50 -0000
@@ -40,5 +40,9 @@ PlannerCmd * planner_task_cmd_insert (Pl
 				      gint            work,
 				      gint            duration,
 				      MrpTask        *new_task);
+PlannerCmd * planner_task_cmd_edit_property	(PlannerWindow *main_window,
+			        		MrpTask        *task,
+			        		const gchar    *property,
+			        		const GValue   *value);
 
 #endif /* __PLANNER_TASK_CMD_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]