Re: [Planner Dev] Fixes for gantt bugz # 148637, 149359 and 149651
- From: "lincoln phipps openmutual net" <lincoln phipps openmutual net>
- To: Planner Project Manager - Development List <planner-dev lists imendio com>
- Subject: Re: [Planner Dev] Fixes for gantt bugz # 148637, 149359 and 149651
- Date: Tue, 10 Aug 2004 01:33:53 +0100
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]