* make the shape export work again
[dia.git] / app / modify_tool.c
blob2349b58ff8c5fa2d6cefec1fc1dfdf3a9a2e450e
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 <stdio.h>
19 #include <math.h>
21 #include "modify_tool.h"
22 #include "handle_ops.h"
23 #include "object_ops.h"
24 #include "connectionpoint_ops.h"
25 #include "message.h"
26 #include "properties.h"
27 #include "render_gdk.h"
28 #include "select.h"
29 #include "preferences.h"
31 static Object *click_select_object(DDisplay *ddisp, Point *clickedpoint,
32 GdkEventButton *event);
33 static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool,
34 Point *clickedpoint,
35 GdkEventButton *event);
36 static void modify_button_press(ModifyTool *tool, GdkEventButton *event,
37 DDisplay *ddisp);
38 static void modify_button_release(ModifyTool *tool, GdkEventButton *event,
39 DDisplay *ddisp);
40 static void modify_motion(ModifyTool *tool, GdkEventMotion *event,
41 DDisplay *ddisp);
42 static void modify_double_click(ModifyTool *tool, GdkEventButton *event,
43 DDisplay *ddisp);
45 Tool *
46 create_modify_tool(void)
48 ModifyTool *tool;
50 tool = g_new(ModifyTool, 1);
51 tool->tool.type = MODIFY_TOOL;
52 tool->tool.button_press_func = (ButtonPressFunc) &modify_button_press;
53 tool->tool.button_release_func = (ButtonReleaseFunc) &modify_button_release;
54 tool->tool.motion_func = (MotionFunc) &modify_motion;
55 tool->tool.double_click_func = (DoubleClickFunc) &modify_double_click;
56 tool->gc = NULL;
57 tool->state = STATE_NONE;
58 tool->break_connections = 0;
60 tool->orig_pos = NULL;
62 return (Tool *)tool;
66 void
67 free_modify_tool(Tool *tool)
69 ModifyTool *mtool = (ModifyTool *)tool;
70 if (mtool->gc)
71 gdk_gc_unref(mtool->gc);
72 g_free(mtool);
76 This function is buggy. Fix it later!
77 static void
78 transitive_select(DDisplay *ddisp, Point *clickedpoint, Object *obj)
80 guint i;
81 GList *j;
82 Object *obj1;
84 for(i = 0; i < obj->num_connections; i++) {
85 printf("%d\n", i);
86 j = obj->connections[i]->connected;
87 while(j != NULL && (obj1 = (Object *)j->data) != NULL) {
88 diagram_select(ddisp->diagram, obj1);
89 obj1->ops->select(obj1, clickedpoint,
90 (Renderer *)ddisp->renderer);
91 transitive_select(ddisp, clickedpoint, obj1);
92 j = g_list_next(j);
98 static Object *
99 click_select_object(DDisplay *ddisp, Point *clickedpoint,
100 GdkEventButton *event)
102 Diagram *diagram;
103 real click_distance;
104 Object *obj;
106 diagram = ddisp->diagram;
108 /* Find the closest object to select it: */
110 click_distance = ddisplay_untransform_length(ddisp, 3.0);
112 obj = diagram_find_clicked_object(diagram, clickedpoint,
113 click_distance);
115 if (obj!=NULL) {
116 /* Selected an object. */
117 GList *already;
118 /*printf("Selected object!\n");*/
120 already = g_list_find(diagram->data->selected, obj);
121 if (already == NULL) { /* Not already selected */
122 /*printf("Not already selected\n");*/
124 if (!(event->state & GDK_SHIFT_MASK)) {
125 /* Not Multi-select => remove current selection */
126 diagram_remove_all_selected(diagram, TRUE);
129 diagram_select(diagram, obj);
130 obj->ops->selectf(obj, clickedpoint,
131 (Renderer *)ddisp->renderer);
134 This stuff is buggy, fix it later.
135 if (event->state & GDK_CONTROL_MASK) {
136 transitive_select(ddisp, clickedpoint, obj);
140 diagram_update_menu_sensitivity(diagram);
141 object_add_updates_list(diagram->data->selected, diagram);
142 diagram_flush(diagram);
144 return obj;
145 } else { /* Clicked on already selected. */
146 /*printf("Already selected\n");*/
147 obj->ops->selectf(obj, clickedpoint,
148 (Renderer *)ddisp->renderer);
149 object_add_updates_list(diagram->data->selected, diagram);
150 diagram_flush(diagram);
152 if (event->state & GDK_SHIFT_MASK) { /* Multi-select */
153 /* Remove the selected selected */
154 diagram_update_menu_sensitivity(diagram);
155 diagram_unselect_object(ddisp->diagram, (Object *)already->data);
156 diagram_flush(ddisp->diagram);
157 } else {
158 return obj;
161 } /* Else part moved to allow union/intersection select */
163 return NULL;
166 static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool,
167 Point *clickedpoint, GdkEventButton *event)
169 Object *obj;
170 Handle *handle;
171 real dist;
173 handle = NULL;
174 dist = diagram_find_closest_handle(ddisp->diagram, &handle,
175 &obj, clickedpoint);
176 if (handle_is_clicked(ddisp, handle, clickedpoint)) {
177 tool->state = STATE_MOVE_HANDLE;
178 tool->break_connections = TRUE;
179 tool->last_to = handle->pos;
180 tool->handle = handle;
181 tool->object = obj;
182 gdk_pointer_grab (ddisp->canvas->window, FALSE,
183 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
184 NULL, NULL, event->time);
185 tool->start_at = *clickedpoint;
186 return TRUE;
188 return FALSE;
191 static void
192 modify_button_press(ModifyTool *tool, GdkEventButton *event,
193 DDisplay *ddisp)
195 Point clickedpoint;
196 Object *clicked_obj;
198 ddisplay_untransform_coords(ddisp,
199 (int)event->x, (int)event->y,
200 &clickedpoint.x, &clickedpoint.y);
202 if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event))
203 return;
205 clicked_obj = click_select_object(ddisp, &clickedpoint, event);
207 if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event))
208 return;
210 if ( clicked_obj != NULL ) {
211 tool->state = STATE_MOVE_OBJECT;
212 tool->object = clicked_obj;
213 tool->move_compensate = clicked_obj->position;
214 point_sub(&tool->move_compensate, &clickedpoint);
215 tool->break_connections = TRUE;
216 gdk_pointer_grab (ddisp->canvas->window, FALSE,
217 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
218 NULL, NULL, event->time);
219 tool->start_at = clickedpoint;
220 } else {
221 tool->state = STATE_BOX_SELECT;
222 tool->start_box = clickedpoint;
223 tool->end_box = clickedpoint;
224 tool->x1 = tool->x2 = (int) event->x;
225 tool->y1 = tool->y2 = (int) event->y;
227 if (tool->gc == NULL) {
228 tool->gc = gdk_gc_new(ddisp->canvas->window);
229 gdk_gc_set_line_attributes(tool->gc, 1, GDK_LINE_ON_OFF_DASH,
230 GDK_CAP_BUTT, GDK_JOIN_MITER);
231 gdk_gc_set_foreground(tool->gc, &color_gdk_white);
232 gdk_gc_set_function(tool->gc, GDK_XOR);
235 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
236 tool->x1, tool->y1,
237 tool->x2 - tool->x1, tool->y2 - tool->y1);
239 gdk_pointer_grab (ddisp->canvas->window, FALSE,
240 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
241 NULL, NULL, event->time);
246 static void
247 modify_double_click(ModifyTool *tool, GdkEventButton *event,
248 DDisplay *ddisp)
250 Point clickedpoint;
251 Object *clicked_obj;
253 ddisplay_untransform_coords(ddisp,
254 (int)event->x, (int)event->y,
255 &clickedpoint.x, &clickedpoint.y);
257 clicked_obj = click_select_object(ddisp, &clickedpoint, event);
259 if ( clicked_obj != NULL ) {
260 properties_show(ddisp->diagram, clicked_obj);
261 } else { /* No object selected */
262 /*printf("didn't select object\n");*/
263 if (!(event->state & GDK_SHIFT_MASK)) {
264 /* Not Multi-select => Remove all selected */
265 diagram_update_menu_sensitivity(ddisp->diagram);
266 diagram_remove_all_selected(ddisp->diagram, TRUE);
267 diagram_flush(ddisp->diagram);
273 static void
274 modify_motion(ModifyTool *tool, GdkEventMotion *event,
275 DDisplay *ddisp)
277 Point to;
278 Point now, delta, full_delta;
279 gboolean auto_scroll, vertical = FALSE;
280 ConnectionPoint *connectionpoint;
282 if (tool->state==STATE_NONE)
283 return; /* Fast path... */
285 auto_scroll = ddisplay_autoscroll(ddisp, event->x, event->y);
287 ddisplay_untransform_coords(ddisp, event->x, event->y, &to.x, &to.y);
289 switch (tool->state) {
290 case STATE_MOVE_OBJECT:
292 if (tool->orig_pos == NULL) {
293 GList *list;
294 int i;
295 Object *obj;
297 list = ddisp->diagram->data->selected;
298 tool->orig_pos = g_new(Point, g_list_length(list));
299 i=0;
300 while (list != NULL) {
301 obj = (Object *) list->data;
302 tool->orig_pos[i] = obj->position;
303 list = g_list_next(list); i++;
307 if (tool->break_connections)
308 diagram_unconnect_selected(ddisp->diagram); /* Pushes UNDO info */
310 if (event->state & GDK_CONTROL_MASK) {
311 full_delta = to;
312 point_sub(&full_delta, &tool->start_at);
313 vertical = (fabs(full_delta.x) < fabs(full_delta.y));
316 point_add(&to, &tool->move_compensate);
317 snap_to_grid(ddisp, &to.x, &to.y);
319 now = tool->object->position;
321 delta = to;
322 point_sub(&delta, &now);
324 if (event->state & GDK_CONTROL_MASK) {
325 /* Up-down or left-right */
326 if (vertical) {
327 delta.x = tool->start_at.x + tool->move_compensate.x - now.x;
328 } else {
329 delta.y = tool->start_at.y + tool->move_compensate.y - now.y;
333 object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram);
334 object_list_move_delta(ddisp->diagram->data->selected, &delta);
335 object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram);
337 diagram_update_connections_selection(ddisp->diagram);
338 diagram_flush(ddisp->diagram);
339 break;
340 case STATE_MOVE_HANDLE:
341 /* Move to ConnectionPoint if near: */
342 connectionpoint =
343 object_find_connectpoint_display(ddisp, &to);
345 if (event->state & GDK_CONTROL_MASK) {
346 full_delta = to;
347 point_sub(&full_delta, &tool->start_at);
348 vertical = (fabs(full_delta.x) < fabs(full_delta.y));
351 if ( (tool->handle->connect_type != HANDLE_NONCONNECTABLE) &&
352 (connectionpoint != NULL) ) {
353 to = connectionpoint->pos;
354 } else {
355 /* No connectionopoint near, then snap to grid (if enabled) */
356 snap_to_grid(ddisp, &to.x, &to.y);
359 if (tool->break_connections) {
360 /* break connections to the handle currently selected. */
361 if (tool->handle->connected_to!=NULL) {
362 Change *change = undo_unconnect(ddisp->diagram, tool->object,
363 tool->handle);
365 (change->apply)(change, ddisp->diagram);
369 if (event->state & GDK_CONTROL_MASK) {
370 /* Up-down or left-right */
371 if (vertical) {
372 to.x = tool->start_at.x;
373 } else {
374 to.y = tool->start_at.y;
378 if (tool->orig_pos == NULL) {
379 tool->orig_pos = g_new(Point, 1);
380 *tool->orig_pos = tool->handle->pos;
383 object_add_updates(tool->object, ddisp->diagram);
384 tool->object->ops->move_handle(tool->object, tool->handle, &to,
385 HANDLE_MOVE_USER,0);
386 object_add_updates(tool->object, ddisp->diagram);
388 diagram_update_connections_selection(ddisp->diagram);
389 diagram_flush(ddisp->diagram);
390 break;
391 case STATE_BOX_SELECT:
393 if (! auto_scroll)
395 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
396 tool->x1, tool->y1,
397 tool->x2 - tool->x1, tool->y2 - tool->y1);
400 tool->end_box = to;
402 ddisplay_transform_coords(ddisp,
403 MIN(tool->start_box.x, tool->end_box.x),
404 MIN(tool->start_box.y, tool->end_box.y),
405 &tool->x1, &tool->y1);
406 ddisplay_transform_coords(ddisp,
407 MAX(tool->start_box.x, tool->end_box.x),
408 MAX(tool->start_box.y, tool->end_box.y),
409 &tool->x2, &tool->y2);
411 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
412 tool->x1, tool->y1,
413 tool->x2 - tool->x1, tool->y2 - tool->y1);
414 break;
415 case STATE_NONE:
417 break;
418 default:
419 message_error("Internal error: Strange state in modify_tool\n");
422 tool->last_to = to;
426 static void
427 modify_button_release(ModifyTool *tool, GdkEventButton *event,
428 DDisplay *ddisp)
430 Point *dest_pos;
431 GList *list;
432 int i;
433 Object *obj;
435 switch (tool->state) {
436 case STATE_MOVE_OBJECT:
437 gdk_pointer_ungrab (event->time);
438 diagram_update_connections_selection(ddisp->diagram);
440 if (tool->orig_pos != NULL) {
441 list = ddisp->diagram->data->selected;
442 dest_pos = g_new(Point, g_list_length(list));
443 i=0;
444 while (list != NULL) {
445 obj = (Object *) list->data;
446 dest_pos[i] = obj->position;
447 list = g_list_next(list); i++;
450 undo_move_objects(ddisp->diagram, tool->orig_pos, dest_pos,
451 g_list_copy(ddisp->diagram->data->selected));
454 ddisplay_connect_selected(ddisp); /* pushes UNDO info */
455 diagram_update_extents(ddisp->diagram);
456 diagram_modified(ddisp->diagram);
457 diagram_flush(ddisp->diagram);
459 undo_set_transactionpoint(ddisp->diagram->undo);
461 tool->orig_pos = NULL;
462 tool->state = STATE_NONE;
463 break;
464 case STATE_MOVE_HANDLE:
465 gdk_pointer_ungrab (event->time);
467 if (tool->orig_pos != NULL) {
468 undo_move_handle(ddisp->diagram, tool->handle, tool->object,
469 *tool->orig_pos, tool->last_to);
472 /* Final move: */
473 object_add_updates(tool->object, ddisp->diagram);
474 tool->object->ops->move_handle(tool->object, tool->handle,
475 &tool->last_to,
476 HANDLE_MOVE_USER_FINAL,0);
477 object_add_updates(tool->object, ddisp->diagram);
479 /* Connect if possible: */
480 if (tool->handle->connect_type != HANDLE_NONCONNECTABLE) {
481 object_connect_display(ddisp, tool->object, tool->handle); /* pushes UNDO info */
482 diagram_update_connections_selection(ddisp->diagram);
485 diagram_flush(ddisp->diagram);
487 diagram_modified(ddisp->diagram);
488 diagram_update_extents(ddisp->diagram);
490 undo_set_transactionpoint(ddisp->diagram->undo);
492 if (tool->orig_pos != NULL) {
493 g_free(tool->orig_pos);
494 tool->orig_pos = NULL;
499 tool->state = STATE_NONE;
500 break;
501 case STATE_BOX_SELECT:
502 gdk_pointer_ungrab (event->time);
503 /* Remove last box: */
504 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
505 tool->x1, tool->y1,
506 tool->x2 - tool->x1, tool->y2 - tool->y1);
509 Rectangle r;
510 GList *list, *list_to_free;
511 Object *obj;
513 r.left = MIN(tool->start_box.x, tool->end_box.x);
514 r.right = MAX(tool->start_box.x, tool->end_box.x);
515 r.top = MIN(tool->start_box.y, tool->end_box.y);
516 r.bottom = MAX(tool->start_box.y, tool->end_box.y);
518 if (prefs.reverse_rubberbanding_intersects) {
519 if (tool->start_box.x > tool->end_box.x) {
520 list = list_to_free =
521 layer_find_objects_intersecting_rectangle(ddisp->diagram->data->active_layer, &r);
522 } else {
523 list = list_to_free =
524 layer_find_objects_in_rectangle(ddisp->diagram->data->active_layer, &r);
526 } else {
527 list = list_to_free =
528 layer_find_objects_in_rectangle(ddisp->diagram->data->active_layer, &r);
531 if (selection_style == SELECT_REPLACE &&
532 !(event->state & GDK_SHIFT_MASK)) {
533 /* Not Multi-select => Remove all selected */
534 diagram_remove_all_selected(ddisp->diagram, TRUE);
537 if (selection_style == SELECT_INTERSECTION) {
538 GList *intersection = NULL;
540 while (list != NULL) {
541 obj = (Object *)list->data;
543 if (diagram_is_selected(ddisp->diagram, obj)) {
544 intersection = g_list_append(intersection, obj);
547 list = g_list_next(list);
549 list = intersection;
550 diagram_remove_all_selected(ddisp->diagram, TRUE);
551 while (list != NULL) {
552 obj = (Object *)list->data;
554 diagram_select(ddisp->diagram, obj);
556 list = g_list_next(list);
558 g_list_free(intersection);
559 } else {
560 while (list != NULL) {
561 obj = (Object *)list->data;
563 if (selection_style == SELECT_REMOVE) {
564 if (diagram_is_selected(ddisp->diagram, obj))
565 diagram_unselect_object(ddisp->diagram, obj);
566 } else if (selection_style == SELECT_INVERT) {
567 if (diagram_is_selected(ddisp->diagram, obj))
568 diagram_unselect_object(ddisp->diagram, obj);
569 else
570 diagram_select(ddisp->diagram, obj);
571 } else {
572 if (!diagram_is_selected(ddisp->diagram, obj))
573 diagram_select(ddisp->diagram, obj);
576 list = g_list_next(list);
580 g_list_free(list_to_free);
584 diagram_update_menu_sensitivity(ddisp->diagram);
585 ddisplay_flush(ddisp);
587 tool->state = STATE_NONE;
588 break;
589 case STATE_NONE:
590 break;
591 default:
592 message_error("Internal error: Strange state in modify_tool\n");
595 tool->break_connections = FALSE;