Refactor ld-canvas.c, extend LdCanvas operations.

Now objects can be selected and moved by dragging the mouse.
This commit is contained in:
Přemysl Eric Janouch 2011-02-05 16:36:04 +01:00
parent 2b0672a2cb
commit 3cec64ebe8
1 changed files with 374 additions and 106 deletions

View File

@ -60,10 +60,14 @@ typedef void (*OperationEnd) (LdCanvas *self);
enum enum
{ {
OPER_0, OPER_0,
OPER_ADD_OBJECT OPER_ADD_OBJECT,
OPER_SELECT,
OPER_MOVE_SELECTION
}; };
typedef struct _AddObjectData AddObjectData; typedef struct _AddObjectData AddObjectData;
typedef struct _SelectData SelectData;
typedef struct _MoveSelectionData MoveSelectionData;
struct _AddObjectData struct _AddObjectData
{ {
@ -71,6 +75,16 @@ struct _AddObjectData
gboolean visible; gboolean visible;
}; };
struct _SelectData
{
LdPoint drag_last_pos;
};
struct _MoveSelectionData
{
LdPoint move_origin;
};
enum enum
{ {
COLOR_BASE, COLOR_BASE,
@ -100,6 +114,10 @@ struct _LdCanvasColor
* @x: the X coordinate of the center of view. * @x: the X coordinate of the center of view.
* @y: the Y coordinate of the center of view. * @y: the Y coordinate of the center of view.
* @zoom: the current zoom of the canvas. * @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: the current operation.
* @operation_data: data related to the current operation. * @operation_data: data related to the current operation.
* @operation_end: a callback to end the operation. * @operation_end: a callback to end the operation.
@ -120,10 +138,15 @@ struct _LdCanvasPrivate
LdPoint terminal; LdPoint terminal;
gboolean terminal_highlighted; gboolean terminal_highlighted;
LdPoint drag_start_pos;
gint drag_operation;
gint operation; gint operation;
union union
{ {
AddObjectData add_object; AddObjectData add_object;
SelectData select;
MoveSelectionData move_selection;
} }
operation_data; operation_data;
OperationEnd operation_end; 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_base_unit_in_px (GtkWidget *self);
static gdouble ld_canvas_get_scale_in_px (LdCanvas *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, static void ld_canvas_color_set (LdCanvasColor *color,
gdouble r, gdouble g, gdouble b, gdouble a); gdouble r, gdouble g, gdouble b, gdouble a);
static void ld_canvas_color_apply (LdCanvasColor *color, cairo_t *cr); 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, static void move_object_to_coords (LdCanvas *self, LdDiagramObject *object,
gdouble x, gdouble y); gdouble x, gdouble y);
static LdDiagramObject *get_object_at_coords (LdCanvas *self, 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 queue_terminal_draw (LdCanvas *self, LdPoint *terminal);
static void ld_canvas_real_cancel_operation (LdCanvas *self); 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, static gboolean on_expose_event (GtkWidget *widget, GdkEventExpose *event,
gpointer user_data); gpointer user_data);
@ -581,27 +616,16 @@ static void
ld_canvas_real_move (LdCanvas *self, gdouble dx, gdouble dy) ld_canvas_real_move (LdCanvas *self, gdouble dx, gdouble dy)
{ {
LdDiagram *diagram; LdDiagram *diagram;
GList *selection, *iter;
diagram = self->priv->diagram;
if (!diagram)
return;
/* TODO: Check/move boundaries, also implement normal /* TODO: Check/move boundaries, also implement normal
* getters and setters for priv->x and priv->y. * getters and setters for priv->x and priv->y.
*/ */
diagram = self->priv->diagram; if (ld_diagram_get_selection (diagram))
selection = ld_diagram_get_selection (diagram); move_selection (self, dx, dy);
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);
}
else else
{ {
self->priv->x += dx; self->priv->x += dx;
@ -915,62 +939,7 @@ ld_canvas_zoom_out (LdCanvas *self)
} }
/* ===== Operations ======================================================== */ /* ===== Helper functions ================================================== */
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 ================================================= */
static void static void
ld_canvas_color_set (LdCanvasColor *color, 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); 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 static void
move_object_to_coords (LdCanvas *self, LdDiagramObject *object, move_object_to_coords (LdCanvas *self, LdDiagramObject *object,
gdouble x, gdouble y) gdouble x, gdouble y)
@ -1208,6 +1204,219 @@ queue_terminal_draw (LdCanvas *self, LdPoint *terminal)
queue_draw (self, &rect); 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 static void
simulate_motion (LdCanvas *self) simulate_motion (LdCanvas *self)
{ {
@ -1236,21 +1445,40 @@ static gboolean
on_motion_notify (GtkWidget *widget, GdkEventMotion *event, gpointer user_data) on_motion_notify (GtkWidget *widget, GdkEventMotion *event, gpointer user_data)
{ {
LdCanvas *self; LdCanvas *self;
AddObjectData *add_data;
self = LD_CANVAS (widget); self = LD_CANVAS (widget);
switch (self->priv->operation) switch (self->priv->operation)
{ {
AddObjectData *data;
case OPER_ADD_OBJECT: case OPER_ADD_OBJECT:
data = &OPER_DATA (self, add_object); add_data = &OPER_DATA (self, add_object);
data->visible = TRUE; add_data->visible = TRUE;
queue_object_draw (self, data->object); queue_object_draw (self, add_data->object);
move_object_to_coords (self, data->object, event->x, event->y); move_object_to_coords (self, add_data->object, event->x, event->y);
queue_object_draw (self, data->object); 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; break;
case OPER_0: 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); check_terminals (self, event->x, event->y);
break; break;
} }
@ -1281,39 +1509,50 @@ static gboolean
on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer user_data) on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{ {
LdCanvas *self; LdCanvas *self;
AddObjectData *data;
LdDiagramObject *object;
if (event->button != 1)
return FALSE;
if (!gtk_widget_has_focus (widget)) if (!gtk_widget_has_focus (widget))
gtk_widget_grab_focus (widget); gtk_widget_grab_focus (widget);
self = LD_CANVAS (widget); self = LD_CANVAS (widget);
if (!self->priv->diagram)
return FALSE;
self->priv->drag_operation = OPER_0;
switch (self->priv->operation) switch (self->priv->operation)
{ {
AddObjectData *data;
case OPER_ADD_OBJECT: case OPER_ADD_OBJECT:
data = &OPER_DATA (self, add_object); data = &OPER_DATA (self, add_object);
queue_object_draw (self, data->object); queue_object_draw (self, data->object);
move_object_to_coords (self, data->object, event->x, event->y); move_object_to_coords (self, data->object, event->x, event->y);
ld_diagram_insert_object (self->priv->diagram, data->object, -1);
if (self->priv->diagram)
ld_diagram_insert_object (self->priv->diagram, data->object, -1);
/* XXX: "cancel" causes confusion. */ /* XXX: "cancel" causes confusion. */
ld_canvas_real_cancel_operation (self); ld_canvas_real_cancel_operation (self);
break; break;
case OPER_0: case OPER_0:
if (self->priv->diagram) self->priv->drag_start_pos.x = event->x;
{ self->priv->drag_start_pos.y = event->y;
LdDiagramObject *object;
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) if (event->state != GDK_SHIFT_MASK)
ld_diagram_unselect_all (self->priv->diagram); ld_diagram_unselect_all (self->priv->diagram);
ld_diagram_select (self->priv->diagram, object);
object = get_object_at_coords (self, event->x, event->y); self->priv->drag_operation = OPER_MOVE_SELECTION;
if (object)
ld_diagram_select (self->priv->diagram, object);
} }
else
self->priv->drag_operation = OPER_MOVE_SELECTION;
break; break;
} }
return FALSE; return FALSE;
@ -1322,6 +1561,32 @@ on_button_press (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
static gboolean static gboolean
on_button_release (GtkWidget *widget, GdkEventButton *event, gpointer user_data) 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; return FALSE;
} }
@ -1383,6 +1648,9 @@ on_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
draw_diagram (widget, &data); draw_diagram (widget, &data);
draw_terminal (widget, &data); draw_terminal (widget, &data);
if (data.self->priv->operation == OPER_SELECT)
oper_select_draw (widget, &data);
cairo_destroy (data.cr); cairo_destroy (data.cr);
return FALSE; return FALSE;
} }