/* dia-handle-tool.c * Copyright (C) 2004 Arjan Molenaar * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include #include "dia-handle-tool.h" #include "dia-canvas-i18n.h" #include "dia-canvas-groupable.h" #include "dia-handle-layer.h" static void dia_handle_tool_class_init (DiaToolClass *class); static void dia_handle_tool_init (DiaHandleTool *tool); static void dia_handle_tool_dispose (GObject *object); static gboolean dia_handle_tool_button_press (DiaTool *tool, DiaCanvasView *view, GdkEventButton *event); static gboolean dia_handle_tool_button_release (DiaTool *tool, DiaCanvasView *view, GdkEventButton *event); static gboolean dia_handle_tool_motion_notify (DiaTool *tool, DiaCanvasView *view, GdkEventMotion *event); static gboolean dia_handle_tool_key_press (DiaTool *tool, DiaCanvasView *view, GdkEventKey *event); static gboolean dia_handle_tool_key_release (DiaTool *tool, DiaCanvasView *view, GdkEventKey *event); static DiaToolClass *parent_class = NULL; GType dia_handle_tool_get_type (void) { static GtkType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (DiaHandleToolClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) dia_handle_tool_class_init, (GClassFinalizeFunc) NULL, (gconstpointer) NULL, /* class_data */ sizeof (DiaHandleTool), (guint16) 0, /* n_preallocs */ (GInstanceInitFunc) dia_handle_tool_init, }; object_type = g_type_register_static (DIA_TYPE_TOOL, "DiaHandleTool", &object_info, 0); } return object_type; } static void dia_handle_tool_class_init (DiaToolClass *klass) { GObjectClass *object_class = (GObjectClass*) klass; parent_class = g_type_class_peek_parent (klass); object_class->dispose = dia_handle_tool_dispose; klass->button_press_event = dia_handle_tool_button_press; klass->button_release_event = dia_handle_tool_button_release; klass->motion_notify_event = dia_handle_tool_motion_notify; klass->key_press_event = dia_handle_tool_key_press; klass->key_release_event = dia_handle_tool_key_release; } static void dia_handle_tool_init (DiaHandleTool *tool) { tool->grabbed_handle = NULL; tool->glue_distance = 10; tool->connect_to = NULL; tool->event_mask = 0; } static void dia_handle_tool_dispose (GObject *object) { DiaHandleTool *tool = (DiaHandleTool*) object; G_OBJECT_CLASS (parent_class)->dispose (object); } /** * recursive_find_closest_handle: * @item: * @x: * @y: * @bb: * @actual_handle: * * Find the closest handle near the cursor. The cursor is placed at (x, y). * The distance is calculated using 'dy + dy' instead of 'dx^2 * dy^2', for * speed of course. * * Return value: **/ static double recursive_find_closest_handle (DiaCanvasViewItem *vitem, gdouble x, gdouble y, DiaRectangle *bb, DiaHandle **actual_handle, DiaCanvasViewItem **actual_item) { gdouble wx = 0.0, wy = 0.0, dist, closest; GList *hlist; DiaHandle *handle; DiaCanvasViewItem *closest_item = NULL; DiaCanvasItem *item = vitem->item; closest = G_MAXDOUBLE; *actual_handle = NULL; *actual_item = NULL; if (DIA_IS_CANVAS_GROUPABLE (item)) { GList *childs = GNOME_CANVAS_GROUP (vitem)->item_list; DiaCanvasViewItem *child; for (; childs != NULL; childs = childs->next) { child = childs->data; /* check if the child is a candidate for being * grabbed on a handle: */ /*if (!(GTK_OBJECT_FLAGS (child->item) & DIA_VISIBLE) || (((GnomeCanvasItem*)child)->x1 > bb->right) || (((GnomeCanvasItem*)child)->x2 < bb->left) || (((GnomeCanvasItem*)child)->y1 > bb->bottom) || (((GnomeCanvasItem*)child)->y2 < bb->top)) continue; */ dist = recursive_find_closest_handle (child, x, y, bb, &handle, &closest_item); if (handle && (dist <= closest)) { closest = dist; *actual_handle = handle; *actual_item = closest_item; } } } for (hlist = item->handles; hlist != NULL; hlist = hlist->next) { handle = hlist->data; if (!(handle->movable)) continue; dia_handle_get_pos_w (handle, &wx, &wy); dist = fabs (wx - x) + fabs (wy - y); if (dist <= closest) { closest = dist; *actual_handle = handle; *actual_item = vitem; } } return closest; } /** * dia_handle_layer_point: * @view: * @x: * @y: * @actual_item: * @actual_handle: * * Determine the distance from a point to the object. * In this case: determine the distance from a point to a handle. If the mouse * pointer is close to a handle, 0.0 is returned so that the handle layer * will get the focus and is able to move the actual handle. * The closest handle is stored in DiaHandleLayer::closest_handle. * * Return value: Distance from a point to the closest handle. **/ static double dia_handle_layer_point (DiaCanvasView *view, double x, double y, DiaCanvasViewItem **actual_item, DiaHandle **actual_handle) { DiaRectangle bb; gdouble dist; gdouble r; DiaCanvasViewItem *root; DiaHandleLayer *layer; DiaHandle *closest_handle = NULL; DiaCanvasViewItem *closest_item = NULL; root = view->root_item; if (!root) return G_MAXDOUBLE; r = dia_handle_size() / (2 * dia_canvas_view_get_zoom (view)); bb.right = bb.bottom = r; bb.left = bb.top = -bb.right; //layer = DIA_HANDLE_LAYER (item); /* First try the focused item: */ if (view->focus_item) { /* Dist is the distance from (x, y) to closest_handle. */ dist = recursive_find_closest_handle (view->focus_item, x, y, &bb, &closest_handle, &closest_item); if (closest_handle) { gdouble wx, wy; dia_handle_get_pos_w (closest_handle, &wx, &wy); if ((x >= wx + bb.left) && (x <= wx + bb.right) && (y >= wy + bb.top) && (y <= wy + bb.bottom)) { //layer->closest_handle = closest_handle; //layer->closest_item = closest_item; *actual_item = closest_item; *actual_handle = closest_handle; return 0.0; } } } /* Try the other objects: */ dist = recursive_find_closest_handle (root, x, y, &bb, &closest_handle, &closest_item); if (closest_handle) { gdouble wx, wy; g_assert (DIA_IS_HANDLE (closest_handle)); dia_handle_get_pos_w (closest_handle, &wx, &wy); if ((x >= wx + bb.left) && (x <= wx + bb.right) && (y >= wy + bb.top) && (y <= wy + bb.bottom)) { //layer->closest_handle = closest_handle; *actual_item = closest_item; *actual_handle = closest_handle; return 0.0; } } return G_MAXDOUBLE; } /** * dia_handle_tool_button_press: * @tool: * @view: * @event: * * * * Return value: **/ static gboolean dia_handle_tool_button_press (DiaTool *tool, DiaCanvasView *view, GdkEventButton *event) { DiaHandleTool *htool = (DiaHandleTool*) tool; DiaCanvasViewItem *closest_item; DiaHandle *closest_handle; /* add dia_handle_layer_point */ if (event->button == 1) { gdouble dist; dist = dia_handle_layer_point (view, event->x, event->y, &closest_item, &closest_handle); if (dist > htool->glue_distance) return FALSE; if (!closest_handle) return FALSE; dia_undo_manager_begin_transaction (dia_canvas_get_undo_manager (view->canvas)); if (!(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) dia_canvas_view_unselect_all (view); htool->grabbed_handle = closest_handle; htool->event_mask = (GdkEventMask) event->state; dia_handle_preserve_state (htool->grabbed_handle); dia_handle_remove_all_constraints (htool->grabbed_handle); htool->connect_to = htool->grabbed_handle->connected_to; dia_canvas_item_request_update (closest_item->item); //gnome_canvas_item_grab (item, DIA_HANDLE_EVENTS, // NULL, GDK_CURRENT_TIME); dia_canvas_view_focus (view, closest_item); return TRUE; } return FALSE; } static gboolean dia_handle_tool_button_release (DiaTool *tool, DiaCanvasView *view, GdkEventButton *event) { DiaHandleTool *htool = DIA_HANDLE_TOOL (tool); DiaHandleLayer *handle_layer = (DiaHandleLayer*) view->handle_layer; if (htool->grabbed_handle && (event->button == 1)) { /* Disconnect only if the object to connect to is * not the object it is already connected to. */ if (htool->grabbed_handle->connected_to && htool->grabbed_handle->connected_to != htool->connect_to) { dia_canvas_item_disconnect (htool->grabbed_handle->connected_to, htool->grabbed_handle); } if (htool->connect_to) { dia_canvas_item_connect (htool->connect_to, htool->grabbed_handle); htool->connect_to = NULL; } /* Update the handle's owner always: */ dia_canvas_item_request_update (htool->grabbed_handle->owner); dia_handle_layer_request_redraw_handle (handle_layer, htool->grabbed_handle); //gnome_canvas_item_ungrab (item, event->time); //gnome_canvas_item_ungrab (item, GDK_CURRENT_TIME); htool->grabbed_handle = NULL; dia_undo_manager_commit_transaction (dia_canvas_get_undo_manager (view->canvas)); return TRUE; } return FALSE; } /* If a handle is moved several things should happen in a * specific order: * 1. Ask the owner object if the movement is allowed at all * 2. Find a connection point nearby the handle * 3. If no CP is found, snap to the grid (if enabled) * 4. Ask the owner again, you can't be too sure. * 5. Move the handle. */ static gboolean dia_handle_tool_motion_notify (DiaTool *tool, DiaCanvasView *view, GdkEventMotion *event) { DiaHandleTool *htool = DIA_HANDLE_TOOL (tool); DiaHandleLayer *handle_layer = (DiaHandleLayer*) view->handle_layer; if (htool->grabbed_handle && (event->state & GDK_BUTTON1_MASK)) { gdouble glue_x, glue_y, dist = G_MAXDOUBLE; gdouble motion_x, motion_y; motion_x = event->x; motion_y = event->y; /* 2. Find a connection point (cp) nearby the handle */ if (htool->grabbed_handle->connectable) { dist = dia_canvas_glue_handle (htool->grabbed_handle->owner->canvas, htool->grabbed_handle, motion_x, motion_y, &glue_x, &glue_y, (DiaCanvasItem**) &htool->connect_to); } /* 3. If no CP is found, snap to the grid (if enabled) */ if (!htool->connect_to || (dist > htool->glue_distance)) { htool->connect_to = NULL; glue_x = motion_x; glue_y = motion_y; dia_canvas_snap_to_grid (view->canvas, &glue_x, &glue_y); } /* 4. Ask the handle's owner if the move is allowed */ if (DIA_CANVAS_ITEM_GET_CLASS (htool->grabbed_handle->owner)->handle_motion) DIA_CANVAS_ITEM_GET_CLASS (htool->grabbed_handle->owner)->handle_motion (htool->grabbed_handle->owner, htool->grabbed_handle, &glue_x, &glue_y, htool->event_mask); /* 5. Moving handle and request regions to be redrawn */ dia_handle_layer_request_redraw_handle (handle_layer, htool->grabbed_handle); dia_handle_set_pos_w (htool->grabbed_handle, glue_x, glue_y); dia_handle_layer_request_redraw_handle (handle_layer, htool->grabbed_handle); return TRUE; } return FALSE; } static gboolean dia_handle_tool_key_press (DiaTool *tool, DiaCanvasView *view, GdkEventKey *event) { return FALSE; } static gboolean dia_handle_tool_key_release (DiaTool *tool, DiaCanvasView *view, GdkEventKey *event) { return FALSE; } DiaTool* dia_handle_tool_new (void) { return g_object_new (DIA_TYPE_HANDLE_TOOL, NULL); } void dia_handle_tool_set_grabbed_handle (DiaHandleTool *tool, DiaHandle *handle) { g_return_if_fail (DIA_IS_HANDLE_TOOL (tool)); g_return_if_fail (handle == NULL || DIA_IS_HANDLE (handle)); tool->grabbed_handle = handle; }