For those who prefer to read code instead of prose, the patch is attached =) On Tue, Jul 03, 2007 at 09:55:17PM -0400, Kurt Maute wrote: > On Sun, 2007-07-01 at 01:34 +0200, Maurice van der Pot wrote: > > This patch allows dragging of the completion for a task. > > The only suggestion I have is that there's currently no way to see what > value you're dragging completion to - so you still have to go into > 'edit' for the task to see it. Some sort of popup like a tooltip would > be really cool... or maybe it should be one of the default columns in > the task tree. > > What do you think? Agreed. To keep it consistent with dragging the duration, I used to status bar to display the current completion. > > This version of the patch also fixes dragging of the duration of a > > task. It will now scroll the gantt if you drag over the edge of > > the window (just like it does for dragging relations). > > Interesting. It seems the gantt doesn't scroll infinitely, but only up > to the final duration of the project, so you can extend the task up to > as far as your mouse can reach outside of the planner window, which is a > bit odd. Can you make it scroll out further than the current end of the > project? I had not expected this to be such a can of worms, but I had to change quite a bit to get it to work properly. I'll first give an overview of what the code did before my modifications. - from time to time gantt_chart_reflow_idle is called (by various functions) to determine the union of the bounds of canvas elements. The resulting size is then used to both set the scroll region of the canvas as well as set the size of the gantt header. - on GDK_BUTTON_PRESS the callback gantt_row_scroll_timeout_cb is installed to run N times per second and a drag_item (a bar indicating the future size of the task) is created. - on GDK_MOTION_NOTIFY the drag_item is redrawn to extend to the cursor position - gantt_row_scroll_timeout_cb gets the mouse position and uses it to determine how far the canvas should scroll. Then it calls gantt_row_canvas_scroll. My new code does the following: - I fixed eel_canvas_rect_bounds() to return the same type of values as other canvas items. The difference became apparent when I started using gantt_chart_reflow_idle to extend the scroll region. - I modified gantt_chart_reflow_idle to set the scroll region to the union of the area occupied by canvas items and the area currently in the viewport. This prevents the viewport from jumping to a different position in the gantt chart when you shorten the project by decreasing the duration of the last task. - I changed the static function gantt_chart_reflow_now to a non-static planner_gantt_chart_reflow_now so i can call it from planner-gantt-row.c - I added a new function gantt_row_drag_item_to_pointer that merges the code from the GDK_MOTION_NOTIFY handler (redrawing the drag_item) and the code from gantt_row_scroll_timeout_cb (scrolling). This new function also calls planner_gantt_chart_reflow_now to update the scroll region and the gantt header. Both the GDK_MOTION_NOTIFY handler and gantt_row_scroll_timeout_cb call this new function. - I moved dragging related static vars out of gantt_row_event to give them file scope, so I can use them from gantt_row_drag_item_to_pointer. from both gantt_row_event and from gantt_row_scroll_timeout_cb. I also added 4 variables indicating the min and max x and y of the drag_item (the limits within which you should be able to drag something). There are still a few things I'm not completely satisfied with though: - When scrolling to the left is causing the scroll region to become smaller, the "change scroll position" and "set scroll region" parts are not atomic, so you can see flicker in both the canvas and the scrollbars. I think this is an issue with a scrolledwindow or something. Any ideas how to get rid of this? - When dragging the duration really really far (a couple of years?) the outline of the bar disappears. It seems to be completely deterministic and there's some strange behaviour around the point where it disappears: it seems to extend normally up to one point, then lag a bit and extend tens of pixels at a time and finally not extend at all anymore. Ideas? Comments on this patch are very welcome. Regards, Maurice. -- Maurice van der Pot Gentoo Linux Developer griffon26 gentoo org http://www.gentoo.org Creator of BiteMe! griffon26 kfk4ever com http://www.kfk4ever.com
Index: src/planner-gantt-chart.c =================================================================== --- src/planner-gantt-chart.c (revision 846) +++ src/planner-gantt-chart.c (working copy) @@ -181,7 +181,6 @@ static void gantt_chart_build_tree (PlannerGanttChart *chart); static void gantt_chart_reflow (PlannerGanttChart *chart, gboolean height_changed); -static void gantt_chart_reflow_now (PlannerGanttChart *chart); static TreeNode * gantt_chart_insert_task (PlannerGanttChart *chart, GtkTreePath *path, MrpTask *task); @@ -190,6 +189,11 @@ TreeNode *task, TreeNode *predecessor, MrpRelationType type); +static void gantt_chart_get_visible_region (PlannerGanttChart *chart, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2); static void gantt_chart_set_scroll_region (PlannerGanttChart *chart, gdouble x1, gdouble y1, @@ -572,7 +576,7 @@ } chart->priv->height_changed = TRUE; - gantt_chart_reflow_now (chart); + planner_gantt_chart_reflow_now (chart); } static void @@ -592,7 +596,7 @@ * jumping around. */ if (GTK_WIDGET_MAPPED (chart)) { - gantt_chart_reflow_now (chart); + planner_gantt_chart_reflow_now (chart); } } @@ -963,6 +967,7 @@ PlannerGanttChartPriv *priv; mrptime t1, t2; gdouble x1, y1, x2, y2; + gdouble vx1, vy1, vx2, vy2; gdouble width, height; gdouble bx1, bx2; GtkAllocation allocation; @@ -1001,11 +1006,16 @@ /* Put some padding after the right-most coordinate. */ bx2 += PADDING; - + width = MAX (width, bx2 - bx1); x2 = x1 + width; - + + gantt_chart_get_visible_region(chart, &vx1, &vy1, &vx2, &vy2); + + x2 = MAX (x2, vx2); + y2 = MAX (y2, vy2); + gantt_chart_set_scroll_region (chart, x1, y1, @@ -1025,8 +1035,8 @@ return FALSE; } -static void -gantt_chart_reflow_now (PlannerGanttChart *chart) +void +planner_gantt_chart_reflow_now (PlannerGanttChart *chart) { if (!GTK_WIDGET_MAPPED (chart)) { return; @@ -1199,7 +1209,7 @@ "project-start", t, NULL); - gantt_chart_reflow_now (chart); + planner_gantt_chart_reflow_now (chart); } static void @@ -1521,7 +1531,7 @@ * start-up . */ priv->height_changed = TRUE; - gantt_chart_reflow_now (chart); + planner_gantt_chart_reflow_now (chart); } g_object_notify (G_OBJECT (chart), "model"); @@ -1661,6 +1671,25 @@ } static void +gantt_chart_get_visible_region (PlannerGanttChart *chart, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2) +{ + GnomeCanvas *canvas; + gint cx, cy; + + canvas = chart->priv->canvas; + + gnome_canvas_get_scroll_offsets(canvas, &cx, &cy); + gnome_canvas_c2w(canvas, cx, cy, x1, y1); + + *x2 = *x1 + GTK_WIDGET(canvas)->allocation.width; + *y2 = *y1 + GTK_WIDGET(canvas)->allocation.height; +} + +static void gantt_chart_set_scroll_region (PlannerGanttChart *chart, gdouble x1, gdouble y1, @@ -1750,7 +1779,7 @@ "zoom", priv->zoom, NULL); - gantt_chart_reflow_now (chart); + planner_gantt_chart_reflow_now (chart); } static mrptime Index: src/planner-gantt-chart.h =================================================================== --- src/planner-gantt-chart.h (revision 846) +++ src/planner-gantt-chart.h (working copy) @@ -82,6 +82,7 @@ const gchar *message); void planner_gantt_chart_resource_clicked (PlannerGanttChart *chart, MrpResource *resource); +void planner_gantt_chart_reflow_now (PlannerGanttChart *chart); void planner_gantt_chart_set_highlight_critical_tasks (PlannerGanttChart *chart, Index: src/planner-gantt-row.c =================================================================== --- src/planner-gantt-row.c (revision 846) +++ src/planner-gantt-row.c (working copy) @@ -280,6 +280,17 @@ static GdkBitmap *break_stipple = NULL; static gchar break_stipple_pattern[] = { 0x03 }; +/* Data related to dragging items */ +static GnomeCanvasItem *drag_item; +static gdouble drag_item_wx_min; +static gdouble drag_item_wx_max; +static gdouble drag_item_wy_min; +static gdouble drag_item_wy_max; +static gdouble drag_wx1, drag_wy1; +static GnomeCanvasPoints *drag_points = NULL; +static GnomeCanvasItem *target_item; +static GnomeCanvasItem *old_target_item; + GType planner_gantt_row_get_type (void) { @@ -2278,44 +2289,194 @@ g_list_free (resources); } -static gboolean -gantt_row_scroll_timeout_cb (PlannerGanttRow *row) +static gboolean gantt_row_drag_item_to_pointer (PlannerGanttRow *row, gboolean scroll) { - GtkWidget *widget; + PlannerGanttChart *chart; + GnomeCanvas *canvas; gint width, height; - gint x, y, dx = 0, dy = 0; + gint x, y, item_cx, item_cy; + gint cx, cy; + gdouble wx2, wy2; + gint dx, dy; - widget = GTK_WIDGET (GNOME_CANVAS_ITEM (row)->canvas); + gint duration; + gint work; + MrpProject *project; + gchar *message; + + gint percent_complete; + + canvas = GNOME_CANVAS_ITEM (row)->canvas; + chart = g_object_get_data (G_OBJECT (GNOME_CANVAS_ITEM (row)->canvas), "chart"); + /* Get the current mouse position so that we can decide if the pointer * is inside the viewport. */ - gdk_window_get_pointer (widget->window, &x, &y, NULL); + gdk_window_get_pointer (GTK_WIDGET(canvas)->window, &x, &y, NULL); - width = widget->allocation.width; - height = widget->allocation.height; + gnome_canvas_get_scroll_offsets (canvas, &cx, &cy); + width = GTK_WIDGET(canvas)->allocation.width; + height = GTK_WIDGET(canvas)->allocation.height; + + if(!scroll) + { + x = CLAMP(x, 0, width - 1); + y = CLAMP(y, 0, height - 1); + } + + gnome_canvas_c2w (canvas, cx + x, cy + y, &wx2, &wy2); + + if(drag_item_wx_min != -1.0) wx2 = MAX(wx2, drag_item_wx_min); + if(drag_item_wx_max != -1.0) wx2 = MIN(wx2, drag_item_wx_max); + if(drag_item_wy_min != -1.0) wy2 = MAX(wy2, drag_item_wy_min); + if(drag_item_wy_max != -1.0) wy2 = MIN(wy2, drag_item_wy_max); + + switch(row->priv->state) + { + case STATE_DRAG_DURATION: + + project = mrp_object_get_project (MRP_OBJECT (row->priv->task)); + + duration = MAX (0, (wx2 - row->priv->x_start) / row->priv->scale); + + /* Snap to quarters. */ + duration = floor (duration / SNAP + 0.5) * SNAP; + + work = mrp_project_calculate_task_work ( + project, + row->priv->task, + -1, + mrp_task_get_start (row->priv->task) + duration); + + message = g_strdup_printf (_("Change work to %s"), + planner_format_duration (project, work)); + planner_gantt_chart_status_updated (chart, message); + g_free (message); + + gnome_canvas_item_set (drag_item, + "x2", wx2, + NULL); + break; + + case STATE_DRAG_COMPLETE: + percent_complete = floor ((CLAMP(wx2 - row->priv->x, 0, row->priv->width) * 100.0) / row->priv->width + 0.5); + message = g_strdup_printf (_("Change completion to %u%%"), percent_complete); + planner_gantt_chart_status_updated (chart, message); + g_free (message); + + gnome_canvas_item_set (drag_item, + "x2", wx2, + NULL); + break; + case STATE_DRAG_LINK: + target_item = gnome_canvas_get_item_at (canvas, wx2, wy2); + + drag_points->coords[0] = drag_wx1; + drag_points->coords[1] = drag_wy1; + drag_points->coords[2] = wx2; + drag_points->coords[3] = wy2; + + gnome_canvas_item_set (drag_item, + "points", drag_points, + NULL); + + if (old_target_item && old_target_item != target_item) { + g_object_set (old_target_item, + "highlight", + FALSE, + NULL); + } + + if (target_item && target_item != GNOME_CANVAS_ITEM(row)) { + const gchar *task_name, *target_name; + + g_object_set (target_item, + "highlight", + TRUE, + NULL); + + target_name = mrp_task_get_name (PLANNER_GANTT_ROW (target_item)->priv->task); + task_name = mrp_task_get_name (row->priv->task); + + if (target_name == NULL || target_name[0] == 0) { + target_name = _("No name"); + } + if (task_name == NULL || task_name[0] == 0) { + task_name = _("No name"); + } + + message = g_strdup_printf (_("Make task '%s' a predecessor of '%s'"), + task_name, + target_name); + + planner_gantt_chart_status_updated (chart, message); + + g_free (message); + } + + if (target_item == NULL) { + planner_gantt_chart_status_updated (chart, NULL); + } + + old_target_item = target_item; + break; + default: + g_assert (FALSE); + break; + } + + if(!scroll) return TRUE; + + gnome_canvas_w2c (canvas, wx2, wy2, &item_cx, &item_cy); + if (x < 0) { - dx = x; - } else if (x >= widget->allocation.width) { - dx = x - widget->allocation.width + 1; + dx = MAX(x, (item_cx - cx) - width / 2); + dx = MIN(dx, 0); + } else if (x >= width) { + dx = MIN(x - width + 1, (item_cx - cx) - width/2); + dx = MAX(dx, 0); } else { dx = 0; } if (y < 0) { dy = y; - } else if (y >= widget->allocation.height) { - dy = y - widget->allocation.height + 1; + } else if (y >= height) { + dy = y - height + 1; } else { dy = 0; } - gantt_row_canvas_scroll (widget, dx, dy); - + if(dx > 0) + { + planner_gantt_chart_reflow_now(chart); + gantt_row_canvas_scroll (GTK_WIDGET(canvas), dx, dy); + } + else if(dx < 0) + { + gantt_row_canvas_scroll (GTK_WIDGET(canvas), dx, dy); + planner_gantt_chart_reflow_now(chart); + } + else + { + if(dy != 0) + { + gantt_row_canvas_scroll (GTK_WIDGET(canvas), dx, dy); + } + planner_gantt_chart_reflow_now(chart); + } + return TRUE; } +static gboolean +gantt_row_scroll_timeout_cb (PlannerGanttRow *row) +{ + return gantt_row_drag_item_to_pointer(row, TRUE); +} + static DragSpot get_drag_spot(gdouble x, gdouble y, PlannerGanttRowPriv *priv) { gdouble x2 = priv->x + priv->width; @@ -2366,19 +2527,13 @@ PlannerGanttRowPriv *priv; PlannerGanttChart *chart; GtkWidget *canvas_widget; - static gdouble x1, y1; gdouble wx1, wy1; gdouble wx2, wy2; - static GnomeCanvasItem *target_item; - static GnomeCanvasItem *old_target_item; - static GnomeCanvasItem *drag_item = NULL; - static GnomeCanvasPoints *drag_points = NULL; MrpTask *task; MrpTask *target_task; GdkCursor *cursor; gboolean summary; MrpTaskType type; - gchar *message; DragSpot drag_spot; row = PLANNER_GANTT_ROW (item); @@ -2473,6 +2628,12 @@ "width_pixels", 1, NULL); gnome_canvas_item_hide (drag_item); + + /* set limits */ + drag_item_wx_min = wx1; + drag_item_wx_max = -1.0; + drag_item_wy_min = wy1; + drag_item_wy_max = wy2; } /* Start the autoscroll timeout. */ @@ -2509,6 +2670,12 @@ "width_pixels", 1, NULL); gnome_canvas_item_hide (drag_item); + + /* set limits */ + drag_item_wx_min = wx1; + drag_item_wx_max = priv->x + priv->width; + drag_item_wy_min = wy1; + drag_item_wy_max = wy2; } /* Start the autoscroll timeout. */ @@ -2535,6 +2702,12 @@ NULL); gnome_canvas_item_hide (drag_item); + /* set limits */ + gnome_canvas_get_scroll_region(item->canvas, &drag_item_wx_min, + &drag_item_wy_min, + &drag_item_wx_max, + &drag_item_wy_max); + old_target_item = NULL; /* Start the autoscroll timeout. */ @@ -2577,8 +2750,8 @@ NULL, event->button.time); - x1 = event->button.x; - y1 = event->button.y; + drag_wx1 = event->button.x; + drag_wy1 = event->button.y; return TRUE; @@ -2608,7 +2781,7 @@ gint x, y; gdk_window_get_pointer (event->motion.window, &x, &y, NULL); - gnome_canvas_c2w (item->canvas, x, y, &event->motion.x, &event->motion.y); + gnome_canvas_window_to_world(item->canvas, x, y, &event->motion.x, &event->motion.y); } if (!(priv->state & STATE_DRAG_ANY)) { @@ -2652,123 +2825,13 @@ } return TRUE; } - else if (priv->state == STATE_DRAG_LINK) { - target_item = gnome_canvas_get_item_at (item->canvas, - event->motion.x, - event->motion.y); - + else { gnome_canvas_item_raise_to_top (drag_item); gnome_canvas_item_show (drag_item); - - drag_points->coords[0] = x1; - drag_points->coords[1] = y1; - drag_points->coords[2] = event->motion.x; - drag_points->coords[3] = event->motion.y; - - gnome_canvas_item_set (drag_item, - "points", drag_points, - NULL); - - chart = g_object_get_data (G_OBJECT (item->canvas), - "chart"); - - if (old_target_item && old_target_item != target_item) { - g_object_set (old_target_item, - "highlight", - FALSE, - NULL); - } - - if (target_item && target_item != item) { - const gchar *task_name, *target_name; - - g_object_set (target_item, - "highlight", - TRUE, - NULL); - target_name = mrp_task_get_name (PLANNER_GANTT_ROW (target_item)->priv->task); - task_name = mrp_task_get_name (priv->task); - - if (target_name == NULL || target_name[0] == 0) { - target_name = _("No name"); - } - if (task_name == NULL || task_name[0] == 0) { - task_name = _("No name"); - } - - message = g_strdup_printf (_("Make task '%s' a predecessor of '%s'"), - task_name, - target_name); - - planner_gantt_chart_status_updated (chart, message); - - g_free (message); - } - - if (target_item == NULL) { - planner_gantt_chart_status_updated (chart, NULL); - } - - old_target_item = target_item; + gantt_row_drag_item_to_pointer (row, FALSE); } - else if (priv->state == STATE_DRAG_COMPLETE) { - chart = g_object_get_data (G_OBJECT (item->canvas), "chart"); - - wx2 = MIN(event->motion.x, priv->x + priv->width); - wy2 = priv->y + priv->bar_bot - 4; - gnome_canvas_item_i2w (item, &wx2, &wy2); - - gnome_canvas_item_set (drag_item, - "x2", wx2, - "y2", wy2, - NULL); - - gnome_canvas_item_raise_to_top (drag_item); - gnome_canvas_item_show (drag_item); - - planner_gantt_chart_status_updated (chart, NULL); - } - else if (priv->state == STATE_DRAG_DURATION) { - gint duration; - gint work; - MrpProject *project; - - project = mrp_object_get_project (MRP_OBJECT (priv->task)); - - wx2 = event->motion.x; - wy2 = priv->y + priv->bar_bot; - - gnome_canvas_item_i2w (item, &wx2, &wy2); - - gnome_canvas_item_set (drag_item, - "x2", wx2, - "y2", wy2, - NULL); - - gnome_canvas_item_raise_to_top (drag_item); - gnome_canvas_item_show (drag_item); - - chart = g_object_get_data (G_OBJECT (item->canvas), "chart"); - - duration = MAX (0, (event->motion.x - priv->x_start) / priv->scale); - - /* Snap to quarters. */ - duration = floor (duration / SNAP + 0.5) * SNAP; - - work = mrp_project_calculate_task_work ( - project, - priv->task, - -1, - mrp_task_get_start (priv->task) + duration); - - message = g_strdup_printf (_("Change work to %s"), - planner_format_duration (project, work)); - planner_gantt_chart_status_updated (chart, message); - g_free (message); - } - break; case GDK_BUTTON_RELEASE: { Index: src/eel-canvas-rect.c =================================================================== --- src/eel-canvas-rect.c (revision 846) +++ src/eel-canvas-rect.c (working copy) @@ -636,17 +636,18 @@ { EelCanvasRect *rect; EelCanvasRectDetails *details; - double hwidth; + gint cx, cy; rect = EEL_CANVAS_RECT (item); details = rect->details; - hwidth = (details->width_pixels / item->canvas->pixels_per_unit) / 2.0; - - *x1 = details->x1 - hwidth; - *y1 = details->y1 - hwidth; - *x2 = details->x2 + hwidth; - *y2 = details->y2 + hwidth; + gnome_canvas_w2c (item->canvas, details->x1, details->y1, &cx, &cy); + *x1 = cx; + *y1 = cy; + + gnome_canvas_w2c (item->canvas, details->x2, details->y2, &cx, &cy); + *x2 = cx; + *y2 = cy; } static void
Attachment:
pgp2cJ40tVAp2.pgp
Description: PGP signature