/* Dia -- an diagram creation/manipulation program * Copyright (C) 1998 Alexander Larsson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "modify_tool.h" #include "handle_ops.h" #include "object_ops.h" #include "connectionpoint_ops.h" #include "message.h" #include "properties.h" #include "render_gdk.h" #include "select.h" #include "preferences.h" static Object *click_select_object(DDisplay *ddisp, Point *clickedpoint, GdkEventButton *event); static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool, Point *clickedpoint, GdkEventButton *event); static void modify_button_press(ModifyTool *tool, GdkEventButton *event, DDisplay *ddisp); static void modify_button_release(ModifyTool *tool, GdkEventButton *event, DDisplay *ddisp); static void modify_motion(ModifyTool *tool, GdkEventMotion *event, DDisplay *ddisp); static void modify_double_click(ModifyTool *tool, GdkEventButton *event, DDisplay *ddisp); Tool * create_modify_tool(void) { ModifyTool *tool; tool = g_new(ModifyTool, 1); tool->tool.type = MODIFY_TOOL; tool->tool.button_press_func = (ButtonPressFunc) &modify_button_press; tool->tool.button_release_func = (ButtonReleaseFunc) &modify_button_release; tool->tool.motion_func = (MotionFunc) &modify_motion; tool->tool.double_click_func = (DoubleClickFunc) &modify_double_click; tool->gc = NULL; tool->state = STATE_NONE; tool->break_connections = 0; tool->orig_pos = NULL; return (Tool *)tool; } void free_modify_tool(Tool *tool) { ModifyTool *mtool = (ModifyTool *)tool; if (mtool->gc) gdk_gc_unref(mtool->gc); g_free(mtool); } /* This function is buggy. Fix it later! static void transitive_select(DDisplay *ddisp, Point *clickedpoint, Object *obj) { guint i; GList *j; Object *obj1; for(i = 0; i < obj->num_connections; i++) { printf("%d\n", i); j = obj->connections[i]->connected; while(j != NULL && (obj1 = (Object *)j->data) != NULL) { diagram_select(ddisp->diagram, obj1); obj1->ops->select(obj1, clickedpoint, (Renderer *)ddisp->renderer); transitive_select(ddisp, clickedpoint, obj1); j = g_list_next(j); } } } */ static Object * click_select_object(DDisplay *ddisp, Point *clickedpoint, GdkEventButton *event) { Diagram *diagram; real click_distance; Object *obj; diagram = ddisp->diagram; /* Find the closest object to select it: */ click_distance = ddisplay_untransform_length(ddisp, 3.0); obj = diagram_find_clicked_object(diagram, clickedpoint, click_distance); if (obj!=NULL) { /* Selected an object. */ GList *already; /*printf("Selected object!\n");*/ already = g_list_find(diagram->data->selected, obj); if (already == NULL) { /* Not already selected */ /*printf("Not already selected\n");*/ if (!(event->state & GDK_SHIFT_MASK)) { /* Not Multi-select => remove current selection */ diagram_remove_all_selected(diagram, TRUE); } diagram_select(diagram, obj); obj->ops->select(obj, clickedpoint, (Renderer *)ddisp->renderer); /* This stuff is buggy, fix it later. if (event->state & GDK_CONTROL_MASK) { transitive_select(ddisp, clickedpoint, obj); } */ diagram_update_menu_sensitivity(diagram); object_add_updates_list(diagram->data->selected, diagram); diagram_flush(diagram); return obj; } else { /* Clicked on already selected. */ /*printf("Already selected\n");*/ obj->ops->select(obj, clickedpoint, (Renderer *)ddisp->renderer); object_add_updates_list(diagram->data->selected, diagram); diagram_flush(diagram); if (event->state & GDK_SHIFT_MASK) { /* Multi-select */ /* Remove the selected selected */ diagram_update_menu_sensitivity(diagram); diagram_unselect_object(ddisp->diagram, (Object *)already->data); diagram_flush(ddisp->diagram); } else { return obj; } } } /* Else part moved to allow union/intersection select */ return NULL; } static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool, Point *clickedpoint, GdkEventButton *event) { Object *obj; Handle *handle; real dist; handle = NULL; dist = diagram_find_closest_handle(ddisp->diagram, &handle, &obj, clickedpoint); if (handle_is_clicked(ddisp, handle, clickedpoint)) { tool->state = STATE_MOVE_HANDLE; tool->break_connections = TRUE; tool->last_to = handle->pos; tool->handle = handle; tool->object = obj; gdk_pointer_grab (ddisp->canvas->window, FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL, event->time); tool->start_at = *clickedpoint; return TRUE; } return FALSE; } static void modify_button_press(ModifyTool *tool, GdkEventButton *event, DDisplay *ddisp) { Point clickedpoint; Object *clicked_obj; ddisplay_untransform_coords(ddisp, (int)event->x, (int)event->y, &clickedpoint.x, &clickedpoint.y); if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event)) return; clicked_obj = click_select_object(ddisp, &clickedpoint, event); if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event)) return; if ( clicked_obj != NULL ) { tool->state = STATE_MOVE_OBJECT; tool->object = clicked_obj; tool->move_compensate = clicked_obj->position; point_sub(&tool->move_compensate, &clickedpoint); tool->break_connections = TRUE; gdk_pointer_grab (ddisp->canvas->window, FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL, event->time); tool->start_at = clickedpoint; } else { tool->state = STATE_BOX_SELECT; tool->start_box = clickedpoint; tool->end_box = clickedpoint; tool->x1 = tool->x2 = (int) event->x; tool->y1 = tool->y2 = (int) event->y; if (tool->gc == NULL) { tool->gc = gdk_gc_new(ddisp->canvas->window); gdk_gc_set_line_attributes(tool->gc, 1, GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER); gdk_gc_set_foreground(tool->gc, &color_gdk_white); gdk_gc_set_function(tool->gc, GDK_XOR); } gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE, tool->x1, tool->y1, tool->x2 - tool->x1, tool->y2 - tool->y1); gdk_pointer_grab (ddisp->canvas->window, FALSE, GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, NULL, event->time); } } static void modify_double_click(ModifyTool *tool, GdkEventButton *event, DDisplay *ddisp) { Point clickedpoint; Object *clicked_obj; ddisplay_untransform_coords(ddisp, (int)event->x, (int)event->y, &clickedpoint.x, &clickedpoint.y); clicked_obj = click_select_object(ddisp, &clickedpoint, event); if ( clicked_obj != NULL ) { properties_show(ddisp->diagram, clicked_obj); } else { /* No object selected */ /*printf("didn't select object\n");*/ if (!(event->state & GDK_SHIFT_MASK)) { /* Not Multi-select => Remove all selected */ diagram_update_menu_sensitivity(ddisp->diagram); diagram_remove_all_selected(ddisp->diagram, TRUE); diagram_flush(ddisp->diagram); } } } static void modify_motion(ModifyTool *tool, GdkEventMotion *event, DDisplay *ddisp) { Point to; Point now, delta, full_delta; gboolean auto_scroll, vertical = FALSE; ConnectionPoint *connectionpoint; if (tool->state==STATE_NONE) return; /* Fast path... */ auto_scroll = ddisplay_autoscroll(ddisp, event->x, event->y); ddisplay_untransform_coords(ddisp, event->x, event->y, &to.x, &to.y); switch (tool->state) { case STATE_MOVE_OBJECT: if (tool->orig_pos == NULL) { GList *list; int i; Object *obj; list = ddisp->diagram->data->selected; tool->orig_pos = g_new(Point, g_list_length(list)); i=0; while (list != NULL) { obj = (Object *) list->data; tool->orig_pos[i] = obj->position; list = g_list_next(list); i++; } } if (tool->break_connections) diagram_unconnect_selected(ddisp->diagram); /* Pushes UNDO info */ if (event->state & GDK_CONTROL_MASK) { full_delta = to; point_sub(&full_delta, &tool->start_at); vertical = (fabs(full_delta.x) < fabs(full_delta.y)); } point_add(&to, &tool->move_compensate); snap_to_grid(ddisp, &to.x, &to.y); now = tool->object->position; delta = to; point_sub(&delta, &now); if (event->state & GDK_CONTROL_MASK) { /* Up-down or left-right */ if (vertical) { delta.x = tool->start_at.x + tool->move_compensate.x - now.x; } else { delta.y = tool->start_at.y + tool->move_compensate.y - now.y; } } object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram); object_list_move_delta(ddisp->diagram->data->selected, &delta); object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram); diagram_update_connections_selection(ddisp->diagram); diagram_flush(ddisp->diagram); break; case STATE_MOVE_HANDLE: /* Move to ConnectionPoint if near: */ connectionpoint = object_find_connectpoint_display(ddisp, &to); if (event->state & GDK_CONTROL_MASK) { full_delta = to; point_sub(&full_delta, &tool->start_at); vertical = (fabs(full_delta.x) < fabs(full_delta.y)); } if ( (tool->handle->connect_type != HANDLE_NONCONNECTABLE) && (connectionpoint != NULL) ) { to = connectionpoint->pos; } else { /* No connectionopoint near, then snap to grid (if enabled) */ snap_to_grid(ddisp, &to.x, &to.y); } if (tool->break_connections) { /* break connections to the handle currently selected. */ if (tool->handle->connected_to!=NULL) { Change *change = undo_unconnect(ddisp->diagram, tool->object, tool->handle); (change->apply)(change, ddisp->diagram); } } if (event->state & GDK_CONTROL_MASK) { /* Up-down or left-right */ if (vertical) { to.x = tool->start_at.x; } else { to.y = tool->start_at.y; } } if (tool->orig_pos == NULL) { tool->orig_pos = g_new(Point, 1); *tool->orig_pos = tool->handle->pos; } object_add_updates(tool->object, ddisp->diagram); tool->object->ops->move_handle(tool->object, tool->handle, &to, HANDLE_MOVE_USER,0); object_add_updates(tool->object, ddisp->diagram); diagram_update_connections_selection(ddisp->diagram); diagram_flush(ddisp->diagram); break; case STATE_BOX_SELECT: if (! auto_scroll) { gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE, tool->x1, tool->y1, tool->x2 - tool->x1, tool->y2 - tool->y1); } tool->end_box = to; ddisplay_transform_coords(ddisp, MIN(tool->start_box.x, tool->end_box.x), MIN(tool->start_box.y, tool->end_box.y), &tool->x1, &tool->y1); ddisplay_transform_coords(ddisp, MAX(tool->start_box.x, tool->end_box.x), MAX(tool->start_box.y, tool->end_box.y), &tool->x2, &tool->y2); gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE, tool->x1, tool->y1, tool->x2 - tool->x1, tool->y2 - tool->y1); break; case STATE_NONE: break; default: message_error("Internal error: Strange state in modify_tool\n"); } tool->last_to = to; } static void modify_button_release(ModifyTool *tool, GdkEventButton *event, DDisplay *ddisp) { Point *dest_pos; GList *list; int i; Object *obj; switch (tool->state) { case STATE_MOVE_OBJECT: gdk_pointer_ungrab (event->time); diagram_update_connections_selection(ddisp->diagram); if (tool->orig_pos != NULL) { list = ddisp->diagram->data->selected; dest_pos = g_new(Point, g_list_length(list)); i=0; while (list != NULL) { obj = (Object *) list->data; dest_pos[i] = obj->position; list = g_list_next(list); i++; } undo_move_objects(ddisp->diagram, tool->orig_pos, dest_pos, g_list_copy(ddisp->diagram->data->selected)); } ddisplay_connect_selected(ddisp); /* pushes UNDO info */ diagram_update_extents(ddisp->diagram); diagram_modified(ddisp->diagram); diagram_flush(ddisp->diagram); undo_set_transactionpoint(ddisp->diagram->undo); tool->orig_pos = NULL; tool->state = STATE_NONE; break; case STATE_MOVE_HANDLE: gdk_pointer_ungrab (event->time); if (tool->orig_pos != NULL) { undo_move_handle(ddisp->diagram, tool->handle, tool->object, *tool->orig_pos, tool->last_to); } /* Final move: */ object_add_updates(tool->object, ddisp->diagram); tool->object->ops->move_handle(tool->object, tool->handle, &tool->last_to, HANDLE_MOVE_USER_FINAL,0); object_add_updates(tool->object, ddisp->diagram); /* Connect if possible: */ if (tool->handle->connect_type != HANDLE_NONCONNECTABLE) { object_connect_display(ddisp, tool->object, tool->handle); /* pushes UNDO info */ diagram_update_connections_selection(ddisp->diagram); } diagram_flush(ddisp->diagram); diagram_modified(ddisp->diagram); diagram_update_extents(ddisp->diagram); undo_set_transactionpoint(ddisp->diagram->undo); if (tool->orig_pos != NULL) { g_free(tool->orig_pos); tool->orig_pos = NULL; } tool->state = STATE_NONE; break; case STATE_BOX_SELECT: gdk_pointer_ungrab (event->time); /* Remove last box: */ gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE, tool->x1, tool->y1, tool->x2 - tool->x1, tool->y2 - tool->y1); { Rectangle r; GList *list, *list_to_free; Object *obj; r.left = MIN(tool->start_box.x, tool->end_box.x); r.right = MAX(tool->start_box.x, tool->end_box.x); r.top = MIN(tool->start_box.y, tool->end_box.y); r.bottom = MAX(tool->start_box.y, tool->end_box.y); if (prefs.reverse_rubberbanding_intersects) { if (tool->start_box.x > tool->end_box.x) { list = list_to_free = layer_find_objects_intersecting_rectangle(ddisp->diagram->data->active_layer, &r); } else { list = list_to_free = layer_find_objects_in_rectangle(ddisp->diagram->data->active_layer, &r); } } else { list = list_to_free = layer_find_objects_in_rectangle(ddisp->diagram->data->active_layer, &r); } if (selection_style == SELECT_REPLACE && !(event->state & GDK_SHIFT_MASK)) { /* Not Multi-select => Remove all selected */ diagram_remove_all_selected(ddisp->diagram, TRUE); } if (selection_style == SELECT_INTERSECTION) { GList *intersection = NULL; while (list != NULL) { obj = (Object *)list->data; if (diagram_is_selected(ddisp->diagram, obj)) { intersection = g_list_append(intersection, obj); } list = g_list_next(list); } list = intersection; diagram_remove_all_selected(ddisp->diagram, TRUE); while (list != NULL) { obj = (Object *)list->data; diagram_select(ddisp->diagram, obj); list = g_list_next(list); } g_list_free(intersection); } else { while (list != NULL) { obj = (Object *)list->data; if (selection_style == SELECT_REMOVE) { if (diagram_is_selected(ddisp->diagram, obj)) diagram_unselect_object(ddisp->diagram, obj); } else if (selection_style == SELECT_INVERT) { if (diagram_is_selected(ddisp->diagram, obj)) diagram_unselect_object(ddisp->diagram, obj); else diagram_select(ddisp->diagram, obj); } else { if (!diagram_is_selected(ddisp->diagram, obj)) diagram_select(ddisp->diagram, obj); } list = g_list_next(list); } } g_list_free(list_to_free); } diagram_update_menu_sensitivity(ddisp->diagram); ddisplay_flush(ddisp); tool->state = STATE_NONE; break; case STATE_NONE: break; default: message_error("Internal error: Strange state in modify_tool\n"); } tool->break_connections = FALSE; }