svg text alignment
[dia.git] / app / modify_tool.c
blobadd61beddea5670cc3f0f7f0d0a77fbcc15f3d6d
1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 #include <config.h>
20 #include <stdio.h>
21 #include <math.h>
23 #include "modify_tool.h"
24 #include "handle_ops.h"
25 #include "object_ops.h"
26 #include "connectionpoint_ops.h"
27 #include "message.h"
28 #include "properties.h"
29 #include "render_gdk.h"
30 #include "select.h"
31 #include "preferences.h"
33 static Object *click_select_object(DDisplay *ddisp, Point *clickedpoint,
34 GdkEventButton *event);
35 static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool,
36 Point *clickedpoint,
37 GdkEventButton *event);
38 static void modify_button_press(ModifyTool *tool, GdkEventButton *event,
39 DDisplay *ddisp);
40 static void modify_button_release(ModifyTool *tool, GdkEventButton *event,
41 DDisplay *ddisp);
42 static void modify_motion(ModifyTool *tool, GdkEventMotion *event,
43 DDisplay *ddisp);
44 static void modify_double_click(ModifyTool *tool, GdkEventButton *event,
45 DDisplay *ddisp);
47 Tool *
48 create_modify_tool(void)
50 ModifyTool *tool;
52 tool = g_new(ModifyTool, 1);
53 tool->tool.type = MODIFY_TOOL;
54 tool->tool.button_press_func = (ButtonPressFunc) &modify_button_press;
55 tool->tool.button_release_func = (ButtonReleaseFunc) &modify_button_release;
56 tool->tool.motion_func = (MotionFunc) &modify_motion;
57 tool->tool.double_click_func = (DoubleClickFunc) &modify_double_click;
58 tool->gc = NULL;
59 tool->state = STATE_NONE;
60 tool->break_connections = 0;
62 tool->orig_pos = NULL;
64 return (Tool *)tool;
68 void
69 free_modify_tool(Tool *tool)
71 ModifyTool *mtool = (ModifyTool *)tool;
72 if (mtool->gc)
73 gdk_gc_unref(mtool->gc);
74 g_free(mtool);
78 This function is buggy. Fix it later!
79 static void
80 transitive_select(DDisplay *ddisp, Point *clickedpoint, Object *obj)
82 guint i;
83 GList *j;
84 Object *obj1;
86 for(i = 0; i < obj->num_connections; i++) {
87 printf("%d\n", i);
88 j = obj->connections[i]->connected;
89 while(j != NULL && (obj1 = (Object *)j->data) != NULL) {
90 diagram_select(ddisp->diagram, obj1);
91 obj1->ops->select(obj1, clickedpoint,
92 (Renderer *)ddisp->renderer);
93 transitive_select(ddisp, clickedpoint, obj1);
94 j = g_list_next(j);
100 static Object *
101 click_select_object(DDisplay *ddisp, Point *clickedpoint,
102 GdkEventButton *event)
104 Diagram *diagram;
105 real click_distance;
106 Object *obj;
108 diagram = ddisp->diagram;
110 /* Find the closest object to select it: */
112 click_distance = ddisplay_untransform_length(ddisp, 3.0);
114 obj = diagram_find_clicked_object(diagram, clickedpoint,
115 click_distance);
117 if (obj!=NULL) {
118 /* Selected an object. */
119 GList *already;
120 /*printf("Selected object!\n");*/
122 already = g_list_find(diagram->data->selected, obj);
123 if (already == NULL) { /* Not already selected */
124 /*printf("Not already selected\n");*/
126 if (!(event->state & GDK_SHIFT_MASK)) {
127 /* Not Multi-select => remove current selection */
128 diagram_remove_all_selected(diagram, TRUE);
131 diagram_select(diagram, obj);
132 obj->ops->selectf(obj, clickedpoint,
133 (Renderer *)ddisp->renderer);
136 This stuff is buggy, fix it later.
137 if (event->state & GDK_CONTROL_MASK) {
138 transitive_select(ddisp, clickedpoint, obj);
142 ddisplay_do_update_menu_sensitivity(ddisp);
143 object_add_updates_list(diagram->data->selected, diagram);
144 diagram_flush(diagram);
146 return obj;
147 } else { /* Clicked on already selected. */
148 /*printf("Already selected\n");*/
149 obj->ops->selectf(obj, clickedpoint,
150 (Renderer *)ddisp->renderer);
151 object_add_updates_list(diagram->data->selected, diagram);
152 diagram_flush(diagram);
154 if (event->state & GDK_SHIFT_MASK) { /* Multi-select */
155 /* Remove the selected selected */
156 ddisplay_do_update_menu_sensitivity(ddisp);
157 diagram_unselect_object(ddisp->diagram, (Object *)already->data);
158 diagram_flush(ddisp->diagram);
159 } else {
160 return obj;
163 } /* Else part moved to allow union/intersection select */
165 return NULL;
168 static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool,
169 Point *clickedpoint, GdkEventButton *event)
171 Object *obj;
172 Handle *handle;
173 real dist;
175 handle = NULL;
176 dist = diagram_find_closest_handle(ddisp->diagram, &handle,
177 &obj, clickedpoint);
178 if (handle_is_clicked(ddisp, handle, clickedpoint)) {
179 tool->state = STATE_MOVE_HANDLE;
180 tool->break_connections = TRUE;
181 tool->last_to = handle->pos;
182 tool->handle = handle;
183 tool->object = obj;
184 gdk_pointer_grab (ddisp->canvas->window, FALSE,
185 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
186 NULL, NULL, event->time);
187 tool->start_at = *clickedpoint;
188 return TRUE;
190 return FALSE;
193 static void
194 modify_button_press(ModifyTool *tool, GdkEventButton *event,
195 DDisplay *ddisp)
197 Point clickedpoint;
198 Object *clicked_obj;
200 ddisplay_untransform_coords(ddisp,
201 (int)event->x, (int)event->y,
202 &clickedpoint.x, &clickedpoint.y);
204 if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event))
205 return;
207 clicked_obj = click_select_object(ddisp, &clickedpoint, event);
209 if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event))
210 return;
212 if ( clicked_obj != NULL ) {
213 tool->state = STATE_MOVE_OBJECT;
214 tool->object = clicked_obj;
215 tool->move_compensate = clicked_obj->position;
216 point_sub(&tool->move_compensate, &clickedpoint);
217 tool->break_connections = TRUE;
218 gdk_pointer_grab (ddisp->canvas->window, FALSE,
219 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
220 NULL, NULL, event->time);
221 tool->start_at = clickedpoint;
222 } else {
223 tool->state = STATE_BOX_SELECT;
224 tool->start_box = clickedpoint;
225 tool->end_box = clickedpoint;
226 tool->x1 = tool->x2 = (int) event->x;
227 tool->y1 = tool->y2 = (int) event->y;
229 if (tool->gc == NULL) {
230 tool->gc = gdk_gc_new(ddisp->canvas->window);
231 gdk_gc_set_line_attributes(tool->gc, 1, GDK_LINE_ON_OFF_DASH,
232 GDK_CAP_BUTT, GDK_JOIN_MITER);
233 gdk_gc_set_foreground(tool->gc, &color_gdk_white);
234 gdk_gc_set_function(tool->gc, GDK_XOR);
237 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
238 tool->x1, tool->y1,
239 tool->x2 - tool->x1, tool->y2 - tool->y1);
241 gdk_pointer_grab (ddisp->canvas->window, FALSE,
242 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
243 NULL, NULL, event->time);
248 static void
249 modify_double_click(ModifyTool *tool, GdkEventButton *event,
250 DDisplay *ddisp)
252 Point clickedpoint;
253 Object *clicked_obj;
255 ddisplay_untransform_coords(ddisp,
256 (int)event->x, (int)event->y,
257 &clickedpoint.x, &clickedpoint.y);
259 clicked_obj = click_select_object(ddisp, &clickedpoint, event);
261 if ( clicked_obj != NULL ) {
262 properties_show(ddisp->diagram, clicked_obj);
263 } else { /* No object selected */
264 /*printf("didn't select object\n");*/
265 if (!(event->state & GDK_SHIFT_MASK)) {
266 /* Not Multi-select => Remove all selected */
267 ddisplay_do_update_menu_sensitivity(ddisp);
268 diagram_remove_all_selected(ddisp->diagram, TRUE);
269 diagram_flush(ddisp->diagram);
275 static void
276 modify_motion(ModifyTool *tool, GdkEventMotion *event,
277 DDisplay *ddisp)
279 Point to;
280 Point now, delta, full_delta;
281 gboolean auto_scroll, vertical = FALSE;
282 ConnectionPoint *connectionpoint;
284 if (tool->state==STATE_NONE)
285 return; /* Fast path... */
287 auto_scroll = ddisplay_autoscroll(ddisp, event->x, event->y);
289 ddisplay_untransform_coords(ddisp, event->x, event->y, &to.x, &to.y);
291 switch (tool->state) {
292 case STATE_MOVE_OBJECT:
294 if (tool->orig_pos == NULL) {
295 GList *list;
296 int i;
297 Object *obj;
299 list = ddisp->diagram->data->selected;
300 tool->orig_pos = g_new(Point, g_list_length(list));
301 i=0;
302 while (list != NULL) {
303 obj = (Object *) list->data;
304 tool->orig_pos[i] = obj->position;
305 list = g_list_next(list); i++;
309 if (tool->break_connections)
310 diagram_unconnect_selected(ddisp->diagram); /* Pushes UNDO info */
312 if (event->state & GDK_CONTROL_MASK) {
313 full_delta = to;
314 point_sub(&full_delta, &tool->start_at);
315 vertical = (fabs(full_delta.x) < fabs(full_delta.y));
318 point_add(&to, &tool->move_compensate);
319 snap_to_grid(ddisp, &to.x, &to.y);
321 now = tool->object->position;
323 delta = to;
324 point_sub(&delta, &now);
326 if (event->state & GDK_CONTROL_MASK) {
327 /* Up-down or left-right */
328 if (vertical) {
329 delta.x = tool->start_at.x + tool->move_compensate.x - now.x;
330 } else {
331 delta.y = tool->start_at.y + tool->move_compensate.y - now.y;
335 object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram);
336 object_list_move_delta(ddisp->diagram->data->selected, &delta);
337 object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram);
339 diagram_update_connections_selection(ddisp->diagram);
340 diagram_flush(ddisp->diagram);
341 break;
342 case STATE_MOVE_HANDLE:
343 /* Move to ConnectionPoint if near: */
344 connectionpoint =
345 object_find_connectpoint_display(ddisp, &to);
347 if (event->state & GDK_CONTROL_MASK) {
348 full_delta = to;
349 point_sub(&full_delta, &tool->start_at);
350 vertical = (fabs(full_delta.x) < fabs(full_delta.y));
353 if ( (tool->handle->connect_type != HANDLE_NONCONNECTABLE) &&
354 (connectionpoint != NULL) ) {
355 to = connectionpoint->pos;
356 } else {
357 /* No connectionopoint near, then snap to grid (if enabled) */
358 snap_to_grid(ddisp, &to.x, &to.y);
361 if (tool->break_connections) {
362 /* break connections to the handle currently selected. */
363 if (tool->handle->connected_to!=NULL) {
364 Change *change = undo_unconnect(ddisp->diagram, tool->object,
365 tool->handle);
367 (change->apply)(change, ddisp->diagram);
371 if (event->state & GDK_CONTROL_MASK) {
372 /* Up-down or left-right */
373 if (vertical) {
374 to.x = tool->start_at.x;
375 } else {
376 to.y = tool->start_at.y;
380 if (tool->orig_pos == NULL) {
381 tool->orig_pos = g_new(Point, 1);
382 *tool->orig_pos = tool->handle->pos;
385 object_add_updates(tool->object, ddisp->diagram);
386 tool->object->ops->move_handle(tool->object, tool->handle, &to,
387 HANDLE_MOVE_USER,0);
388 object_add_updates(tool->object, ddisp->diagram);
390 diagram_update_connections_selection(ddisp->diagram);
391 diagram_flush(ddisp->diagram);
392 break;
393 case STATE_BOX_SELECT:
395 if (!auto_scroll && !tool->auto_scrolled)
397 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
398 tool->x1, tool->y1,
399 tool->x2 - tool->x1, tool->y2 - tool->y1);
402 tool->end_box = to;
404 ddisplay_transform_coords(ddisp,
405 MIN(tool->start_box.x, tool->end_box.x),
406 MIN(tool->start_box.y, tool->end_box.y),
407 &tool->x1, &tool->y1);
408 ddisplay_transform_coords(ddisp,
409 MAX(tool->start_box.x, tool->end_box.x),
410 MAX(tool->start_box.y, tool->end_box.y),
411 &tool->x2, &tool->y2);
413 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
414 tool->x1, tool->y1,
415 tool->x2 - tool->x1, tool->y2 - tool->y1);
416 break;
417 case STATE_NONE:
419 break;
420 default:
421 message_error("Internal error: Strange state in modify_tool\n");
424 tool->last_to = to;
425 tool->auto_scrolled = auto_scroll;
429 static void
430 modify_button_release(ModifyTool *tool, GdkEventButton *event,
431 DDisplay *ddisp)
433 Point *dest_pos;
434 GList *list;
435 int i;
436 Object *obj;
438 switch (tool->state) {
439 case STATE_MOVE_OBJECT:
440 gdk_pointer_ungrab (event->time);
441 diagram_update_connections_selection(ddisp->diagram);
443 if (tool->orig_pos != NULL) {
444 list = ddisp->diagram->data->selected;
445 dest_pos = g_new(Point, g_list_length(list));
446 i=0;
447 while (list != NULL) {
448 obj = (Object *) list->data;
449 dest_pos[i] = obj->position;
450 list = g_list_next(list); i++;
453 undo_move_objects(ddisp->diagram, tool->orig_pos, dest_pos,
454 g_list_copy(ddisp->diagram->data->selected));
457 ddisplay_connect_selected(ddisp); /* pushes UNDO info */
458 diagram_update_extents(ddisp->diagram);
459 diagram_modified(ddisp->diagram);
460 diagram_flush(ddisp->diagram);
462 undo_set_transactionpoint(ddisp->diagram->undo);
464 tool->orig_pos = NULL;
465 tool->state = STATE_NONE;
466 break;
467 case STATE_MOVE_HANDLE:
468 gdk_pointer_ungrab (event->time);
470 if (tool->orig_pos != NULL) {
471 undo_move_handle(ddisp->diagram, tool->handle, tool->object,
472 *tool->orig_pos, tool->last_to);
475 /* Final move: */
476 object_add_updates(tool->object, ddisp->diagram);
477 tool->object->ops->move_handle(tool->object, tool->handle,
478 &tool->last_to,
479 HANDLE_MOVE_USER_FINAL,0);
480 object_add_updates(tool->object, ddisp->diagram);
482 /* Connect if possible: */
483 if (tool->handle->connect_type != HANDLE_NONCONNECTABLE) {
484 object_connect_display(ddisp, tool->object, tool->handle); /* pushes UNDO info */
485 diagram_update_connections_selection(ddisp->diagram);
488 diagram_flush(ddisp->diagram);
490 diagram_modified(ddisp->diagram);
491 diagram_update_extents(ddisp->diagram);
493 undo_set_transactionpoint(ddisp->diagram->undo);
495 if (tool->orig_pos != NULL) {
496 g_free(tool->orig_pos);
497 tool->orig_pos = NULL;
502 tool->state = STATE_NONE;
503 break;
504 case STATE_BOX_SELECT:
505 gdk_pointer_ungrab (event->time);
506 /* Remove last box: */
507 if (!tool->auto_scrolled) {
508 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
509 tool->x1, tool->y1,
510 tool->x2 - tool->x1, tool->y2 - tool->y1);
514 Rectangle r;
515 GList *list, *list_to_free;
516 Object *obj;
518 r.left = MIN(tool->start_box.x, tool->end_box.x);
519 r.right = MAX(tool->start_box.x, tool->end_box.x);
520 r.top = MIN(tool->start_box.y, tool->end_box.y);
521 r.bottom = MAX(tool->start_box.y, tool->end_box.y);
523 if (prefs.reverse_rubberbanding_intersects) {
524 if (tool->start_box.x > tool->end_box.x) {
525 list = list_to_free =
526 layer_find_objects_intersecting_rectangle(ddisp->diagram->data->active_layer, &r);
527 } else {
528 list = list_to_free =
529 layer_find_objects_in_rectangle(ddisp->diagram->data->active_layer, &r);
531 } else {
532 list = list_to_free =
533 layer_find_objects_in_rectangle(ddisp->diagram->data->active_layer, &r);
536 if (selection_style == SELECT_REPLACE &&
537 !(event->state & GDK_SHIFT_MASK)) {
538 /* Not Multi-select => Remove all selected */
539 diagram_remove_all_selected(ddisp->diagram, TRUE);
542 if (selection_style == SELECT_INTERSECTION) {
543 GList *intersection = NULL;
545 while (list != NULL) {
546 obj = (Object *)list->data;
548 if (diagram_is_selected(ddisp->diagram, obj)) {
549 intersection = g_list_append(intersection, obj);
552 list = g_list_next(list);
554 list = intersection;
555 diagram_remove_all_selected(ddisp->diagram, TRUE);
556 while (list != NULL) {
557 obj = (Object *)list->data;
559 diagram_select(ddisp->diagram, obj);
561 list = g_list_next(list);
563 g_list_free(intersection);
564 } else {
565 while (list != NULL) {
566 obj = (Object *)list->data;
568 if (selection_style == SELECT_REMOVE) {
569 if (diagram_is_selected(ddisp->diagram, obj))
570 diagram_unselect_object(ddisp->diagram, obj);
571 } else if (selection_style == SELECT_INVERT) {
572 if (diagram_is_selected(ddisp->diagram, obj))
573 diagram_unselect_object(ddisp->diagram, obj);
574 else
575 diagram_select(ddisp->diagram, obj);
576 } else {
577 if (!diagram_is_selected(ddisp->diagram, obj))
578 diagram_select(ddisp->diagram, obj);
581 list = g_list_next(list);
585 g_list_free(list_to_free);
589 ddisplay_do_update_menu_sensitivity(ddisp);
590 ddisplay_flush(ddisp);
592 tool->state = STATE_NONE;
593 break;
594 case STATE_NONE:
595 break;
596 default:
597 message_error("Internal error: Strange state in modify_tool\n");
600 tool->break_connections = FALSE;