From 3cec64ebe8e04b42fadaa6cba03960606d852832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 5 Feb 2011 16:36:04 +0100 Subject: Refactor ld-canvas.c, extend LdCanvas operations. Now objects can be selected and moved by dragging the mouse. --- liblogdiag/ld-canvas.c | 480 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 374 insertions(+), 106 deletions(-) diff --git a/liblogdiag/ld-canvas.c b/liblogdiag/ld-canvas.c index 60da2e5..7f0b236 100644 --- a/liblogdiag/ld-canvas.c +++ b/liblogdiag/ld-canvas.c @@ -60,10 +60,14 @@ typedef void (*OperationEnd) (LdCanvas *self); enum { OPER_0, - OPER_ADD_OBJECT + OPER_ADD_OBJECT, + OPER_SELECT, + OPER_MOVE_SELECTION }; typedef struct _AddObjectData AddObjectData; +typedef struct _SelectData SelectData; +typedef struct _MoveSelectionData MoveSelectionData; struct _AddObjectData { @@ -71,6 +75,16 @@ struct _AddObjectData gboolean visible; }; +struct _SelectData +{ + LdPoint drag_last_pos; +}; + +struct _MoveSelectionData +{ + LdPoint move_origin; +}; + enum { COLOR_BASE, @@ -100,6 +114,10 @@ struct _LdCanvasColor * @x: the X coordinate of the center of view. * @y: the Y coordinate of the center of view. * @zoom: the current zoom of the canvas. + * @terminal: position of the highlighted terminal. + * @terminal_highlighted: whether a terminal is highlighted. + * @drag_start_pos: position of the mouse pointer when dragging started. + * @drag_operation: the operation to start when dragging starts. * @operation: the current operation. * @operation_data: data related to the current operation. * @operation_end: a callback to end the operation. @@ -120,10 +138,15 @@ struct _LdCanvasPrivate LdPoint terminal; gboolean terminal_highlighted; + LdPoint drag_start_pos; + gint drag_operation; + gint operation; union { AddObjectData add_object; + SelectData select; + MoveSelectionData move_selection; } operation_data; OperationEnd operation_end; @@ -180,22 +203,11 @@ static void diagram_disconnect_signals (LdCanvas *self); static gdouble ld_canvas_get_base_unit_in_px (GtkWidget *self); static gdouble ld_canvas_get_scale_in_px (LdCanvas *self); -static void simulate_motion (LdCanvas *self); -static gboolean on_motion_notify (GtkWidget *widget, GdkEventMotion *event, - gpointer user_data); -static gboolean on_leave_notify (GtkWidget *widget, GdkEventCrossing *event, - gpointer user_data); -static gboolean on_button_press (GtkWidget *widget, GdkEventButton *event, - gpointer user_data); -static gboolean on_button_release (GtkWidget *widget, GdkEventButton *event, - gpointer user_data); -static gboolean on_scroll (GtkWidget *widget, GdkEventScroll *event, - gpointer user_data); - static void ld_canvas_color_set (LdCanvasColor *color, gdouble r, gdouble g, gdouble b, gdouble a); static void ld_canvas_color_apply (LdCanvasColor *color, cairo_t *cr); +static void move_selection (LdCanvas *self, gdouble dx, gdouble dy); static void move_object_to_coords (LdCanvas *self, LdDiagramObject *object, gdouble x, gdouble y); static LdDiagramObject *get_object_at_coords (LdCanvas *self, @@ -218,7 +230,30 @@ static void queue_object_draw (LdCanvas *self, LdDiagramObject *object); static void queue_terminal_draw (LdCanvas *self, LdPoint *terminal); static void ld_canvas_real_cancel_operation (LdCanvas *self); -static void ld_canvas_add_object_end (LdCanvas *self); +static void oper_add_object_end (LdCanvas *self); + +static void oper_select_begin (LdCanvas *self, gdouble x, gdouble y); +static void oper_select_end (LdCanvas *self); +static void oper_select_get_rectangle (LdCanvas *self, LdRectangle *rect); +static void oper_select_queue_draw (LdCanvas *self); +static void oper_select_draw (GtkWidget *widget, DrawData *data); +static void oper_select_motion (LdCanvas *self, gdouble x, gdouble y); + +static void oper_move_selection_begin (LdCanvas *self, gdouble x, gdouble y); +static void oper_move_selection_end (LdCanvas *self); +static void oper_move_selection_motion (LdCanvas *self, gdouble x, gdouble y); + +static void simulate_motion (LdCanvas *self); +static gboolean on_motion_notify (GtkWidget *widget, GdkEventMotion *event, + gpointer user_data); +static gboolean on_leave_notify (GtkWidget *widget, GdkEventCrossing *event, + gpointer user_data); +static gboolean on_button_press (GtkWidget *widget, GdkEventButton *event, + gpointer user_data); +static gboolean on_button_release (GtkWidget *widget, GdkEventButton *event, + gpointer user_data); +static gboolean on_scroll (GtkWidget *widget, GdkEventScroll *event, + gpointer user_data); static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data); @@ -581,27 +616,16 @@ static void ld_canvas_real_move (LdCanvas *self, gdouble dx, gdouble dy) { LdDiagram *diagram; - GList *selection, *iter; + + diagram = self->priv->diagram; + if (!diagram) + return; /* TODO: Check/move boundaries, also implement normal * getters and setters for priv->x and priv->y. */ - diagram = self->priv->diagram; - selection = ld_diagram_get_selection (diagram); - if (selection) - { - ld_diagram_begin_user_action (diagram); - for (iter = selection; iter; iter = g_list_next (iter)) - { - gdouble x, y; - - g_object_get (iter->data, "x", &x, "y", &y, NULL); - x += dx; - y += dy; - g_object_set (iter->data, "x", x, "y", y, NULL); - } - ld_diagram_end_user_action (diagram); - } + if (ld_diagram_get_selection (diagram)) + move_selection (self, dx, dy); else { self->priv->x += dx; @@ -915,62 +939,7 @@ ld_canvas_zoom_out (LdCanvas *self) } -/* ===== Operations ======================================================== */ - -static void -ld_canvas_real_cancel_operation (LdCanvas *self) -{ - g_return_if_fail (LD_IS_CANVAS (self)); - - if (self->priv->operation) - { - if (self->priv->operation_end) - self->priv->operation_end (self); - self->priv->operation = OPER_0; - self->priv->operation_end = NULL; - } -} - -/** - * ld_canvas_add_object_begin: - * @self: an #LdCanvas object. - * @object: (transfer full): the object to be added to the diagram. - * - * Begin an operation for adding an object into the diagram. - */ -void -ld_canvas_add_object_begin (LdCanvas *self, LdDiagramObject *object) -{ - AddObjectData *data; - - g_return_if_fail (LD_IS_CANVAS (self)); - g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object)); - - ld_canvas_real_cancel_operation (self); - - self->priv->operation = OPER_ADD_OBJECT; - self->priv->operation_end = ld_canvas_add_object_end; - - data = &OPER_DATA (self, add_object); - data->object = object; -} - -static void -ld_canvas_add_object_end (LdCanvas *self) -{ - AddObjectData *data; - - data = &OPER_DATA (self, add_object); - if (data->object) - { - queue_object_draw (self, data->object); - g_object_unref (data->object); - data->object = NULL; - } -} - - -/* ===== Events, rendering ================================================= */ +/* ===== Helper functions ================================================== */ static void ld_canvas_color_set (LdCanvasColor *color, @@ -988,6 +957,33 @@ ld_canvas_color_apply (LdCanvasColor *color, cairo_t *cr) cairo_set_source_rgba (cr, color->r, color->g, color->b, color->a); } +static void +move_selection (LdCanvas *self, gdouble dx, gdouble dy) +{ + LdDiagram *diagram; + GList *selection, *iter; + + diagram = self->priv->diagram; + if (!diagram) + return; + + selection = ld_diagram_get_selection (diagram); + if (!selection) + return; + + ld_diagram_begin_user_action (diagram); + for (iter = selection; iter; iter = g_list_next (iter)) + { + gdouble x, y; + + g_object_get (iter->data, "x", &x, "y", &y, NULL); + x += dx; + y += dy; + g_object_set (iter->data, "x", x, "y", y, NULL); + } + ld_diagram_end_user_action (diagram); +} + static void move_object_to_coords (LdCanvas *self, LdDiagramObject *object, gdouble x, gdouble y) @@ -1208,6 +1204,219 @@ queue_terminal_draw (LdCanvas *self, LdPoint *terminal) queue_draw (self, &rect); } + +/* ===== Operations ======================================================== */ + +static void +ld_canvas_real_cancel_operation (LdCanvas *self) +{ + g_return_if_fail (LD_IS_CANVAS (self)); + + if (self->priv->operation) + { + if (self->priv->operation_end) + self->priv->operation_end (self); + self->priv->operation = OPER_0; + self->priv->operation_end = NULL; + } +} + +/** + * ld_canvas_add_object_begin: + * @self: an #LdCanvas object. + * @object: (transfer full): the object to be added to the diagram. + * + * Begin an operation for adding an object into the diagram. + */ +void +ld_canvas_add_object_begin (LdCanvas *self, LdDiagramObject *object) +{ + AddObjectData *data; + + g_return_if_fail (LD_IS_CANVAS (self)); + g_return_if_fail (LD_IS_DIAGRAM_OBJECT (object)); + + ld_canvas_real_cancel_operation (self); + + self->priv->operation = OPER_ADD_OBJECT; + self->priv->operation_end = oper_add_object_end; + + data = &OPER_DATA (self, add_object); + data->object = object; +} + +static void +oper_add_object_end (LdCanvas *self) +{ + AddObjectData *data; + + data = &OPER_DATA (self, add_object); + if (data->object) + { + queue_object_draw (self, data->object); + g_object_unref (data->object); + data->object = NULL; + } +} + +static void +oper_select_begin (LdCanvas *self, gdouble x, gdouble y) +{ + SelectData *data; + + ld_canvas_real_cancel_operation (self); + + self->priv->operation = OPER_SELECT; + self->priv->operation_end = oper_select_end; + + data = &OPER_DATA (self, select); + data->drag_last_pos.x = self->priv->drag_start_pos.x; + data->drag_last_pos.y = self->priv->drag_start_pos.y; + + oper_select_motion (self, x, y); +} + +static void +oper_select_end (LdCanvas *self) +{ + oper_select_queue_draw (self); +} + +static void +oper_select_get_rectangle (LdCanvas *self, LdRectangle *rect) +{ + SelectData *data; + + data = &OPER_DATA (self, select); + rect->x = MIN (self->priv->drag_start_pos.x, data->drag_last_pos.x); + rect->y = MIN (self->priv->drag_start_pos.y, data->drag_last_pos.y); + rect->width = ABS (self->priv->drag_start_pos.x - data->drag_last_pos.x); + rect->height = ABS (self->priv->drag_start_pos.y - data->drag_last_pos.y); +} + +static void +oper_select_queue_draw (LdCanvas *self) +{ + LdRectangle rect; + SelectData *data; + + data = &OPER_DATA (self, select); + oper_select_get_rectangle (self, &rect); + queue_draw (self, &rect); +} + +static void +oper_select_draw (GtkWidget *widget, DrawData *data) +{ + static const double dashes[] = {3, 5}; + SelectData *select_data; + + g_return_if_fail (data->self->priv->operation == OPER_SELECT); + + ld_canvas_color_apply (COLOR_GET (data->self, COLOR_GRID), data->cr); + cairo_set_line_width (data->cr, 1); + cairo_set_line_cap (data->cr, CAIRO_LINE_CAP_SQUARE); + cairo_set_dash (data->cr, dashes, G_N_ELEMENTS (dashes), 0); + + select_data = &OPER_DATA (data->self, select); + + cairo_rectangle (data->cr, + data->self->priv->drag_start_pos.x - 0.5, + data->self->priv->drag_start_pos.y - 0.5, + select_data->drag_last_pos.x - data->self->priv->drag_start_pos.x + 1, + select_data->drag_last_pos.y - data->self->priv->drag_start_pos.y + 1); + cairo_stroke (data->cr); +} + +static void +oper_select_motion (LdCanvas *self, gdouble x, gdouble y) +{ + SelectData *data; + GList *objects, *iter; + LdRectangle selection_rect, object_rect; + + data = &OPER_DATA (self, select); + + oper_select_queue_draw (self); + data->drag_last_pos.x = x; + data->drag_last_pos.y = y; + oper_select_queue_draw (self); + + oper_select_get_rectangle (self, &selection_rect); + objects = (GList *) ld_diagram_get_objects (self->priv->diagram); + + for (iter = objects; iter; iter = g_list_next (iter)) + { + LdDiagramObject *object; + + object = LD_DIAGRAM_OBJECT (iter->data); + if (!get_object_area (self, object, &object_rect)) + continue; + + ld_rectangle_extend (&object_rect, OBJECT_BORDER_TOLERANCE); + if (ld_rectangle_intersects (&object_rect, &selection_rect)) + ld_diagram_select (self->priv->diagram, object); + else + ld_diagram_unselect (self->priv->diagram, object); + } +} + +static void +oper_move_selection_begin (LdCanvas *self, gdouble x, gdouble y) +{ + MoveSelectionData *data; + + ld_canvas_real_cancel_operation (self); + + self->priv->operation = OPER_MOVE_SELECTION; + self->priv->operation_end = oper_move_selection_end; + + ld_diagram_begin_user_action (self->priv->diagram); + + data = &OPER_DATA (self, move_selection); + data->move_origin.x = self->priv->drag_start_pos.x; + data->move_origin.y = self->priv->drag_start_pos.y; + + oper_move_selection_motion (self, x, y); +} + +static void +oper_move_selection_end (LdCanvas *self) +{ + ld_diagram_end_user_action (self->priv->diagram); +} + +static void +oper_move_selection_motion (LdCanvas *self, gdouble x, gdouble y) +{ + MoveSelectionData *data; + gdouble scale, move_x, move_y; + gdouble move = FALSE; + + scale = ld_canvas_get_scale_in_px (self); + data = &OPER_DATA (self, move_selection); + + move_x = floor ((x - data->move_origin.x) / scale); + move_y = floor ((y - data->move_origin.y) / scale); + + if (ABS (move_x) >= 1) + { + data->move_origin.x += move_x * scale; + move = TRUE; + } + if (ABS (move_y) >= 1) + { + data->move_origin.y += move_y * scale; + move = TRUE; + } + + if (move) + move_selection (self, move_x, move_y); +} + + +/* ===== Events, rendering ================================================= */ + static void simulate_motion (LdCanvas *self) { @@ -1236,21 +1445,40 @@ static gboolean on_motion_notify (GtkWidget *widget, GdkEventMotion *event, gpointer user_data) { LdCanvas *self; + AddObjectData *add_data; self = LD_CANVAS (widget); switch (self->priv->operation) { - AddObjectData *data; - case OPER_ADD_OBJECT: - data = &OPER_DATA (self, add_object); - data->visible = TRUE; + add_data = &OPER_DATA (self, add_object); + add_data->visible = TRUE; - queue_object_draw (self, data->object); - move_object_to_coords (self, data->object, event->x, event->y); - queue_object_draw (self, data->object); + queue_object_draw (self, add_data->object); + move_object_to_coords (self, add_data->object, event->x, event->y); + queue_object_draw (self, add_data->object); + break; + case OPER_SELECT: + oper_select_motion (self, event->x, event->y); + break; + case OPER_MOVE_SELECTION: + oper_move_selection_motion (self, event->x, event->y); break; case OPER_0: + if (event->state & GDK_BUTTON1_MASK + && (event->x != self->priv->drag_start_pos.x + || event->y != self->priv->drag_start_pos.y)) + { + switch (self->priv->drag_operation) + { + case OPER_SELECT: + oper_select_begin (self, event->x, event->y); + break; + case OPER_MOVE_SELECTION: + oper_move_selection_begin (self, event->x, event->y); + break; + } + } check_terminals (self, event->x, event->y); break; } @@ -1281,39 +1509,50 @@ static gboolean on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { LdCanvas *self; + AddObjectData *data; + LdDiagramObject *object; + if (event->button != 1) + return FALSE; if (!gtk_widget_has_focus (widget)) gtk_widget_grab_focus (widget); self = LD_CANVAS (widget); + if (!self->priv->diagram) + return FALSE; + + self->priv->drag_operation = OPER_0; switch (self->priv->operation) { - AddObjectData *data; - case OPER_ADD_OBJECT: data = &OPER_DATA (self, add_object); queue_object_draw (self, data->object); move_object_to_coords (self, data->object, event->x, event->y); - - if (self->priv->diagram) - ld_diagram_insert_object (self->priv->diagram, data->object, -1); + ld_diagram_insert_object (self->priv->diagram, data->object, -1); /* XXX: "cancel" causes confusion. */ ld_canvas_real_cancel_operation (self); break; case OPER_0: - if (self->priv->diagram) - { - LdDiagramObject *object; + self->priv->drag_start_pos.x = event->x; + self->priv->drag_start_pos.y = event->y; + object = get_object_at_coords (self, event->x, event->y); + if (!object) + { + ld_diagram_unselect_all (self->priv->diagram); + self->priv->drag_operation = OPER_SELECT; + } + else if (!is_object_selected (self, object)) + { if (event->state != GDK_SHIFT_MASK) ld_diagram_unselect_all (self->priv->diagram); - - object = get_object_at_coords (self, event->x, event->y); - if (object) - ld_diagram_select (self->priv->diagram, object); + ld_diagram_select (self->priv->diagram, object); + self->priv->drag_operation = OPER_MOVE_SELECTION; } + else + self->priv->drag_operation = OPER_MOVE_SELECTION; break; } return FALSE; @@ -1322,6 +1561,32 @@ on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer user_data) static gboolean on_button_release (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { + LdCanvas *self; + LdDiagramObject *object; + + if (event->button != 1) + return FALSE; + + self = LD_CANVAS (widget); + if (!self->priv->diagram) + return FALSE; + + switch (self->priv->operation) + { + case OPER_SELECT: + case OPER_MOVE_SELECTION: + ld_canvas_real_cancel_operation (self); + break; + case OPER_0: + object = get_object_at_coords (self, event->x, event->y); + if (object && is_object_selected (self, object)) + { + if (!(event->state & GDK_SHIFT_MASK)) + ld_diagram_unselect_all (self->priv->diagram); + ld_diagram_select (self->priv->diagram, object); + } + break; + } return FALSE; } @@ -1383,6 +1648,9 @@ on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data) draw_diagram (widget, &data); draw_terminal (widget, &data); + if (data.self->priv->operation == OPER_SELECT) + oper_select_draw (widget, &data); + cairo_destroy (data.cr); return FALSE; } -- cgit v1.2.3