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.
23 #include "modify_tool.h"
24 #include "handle_ops.h"
25 #include "object_ops.h"
26 #include "connectionpoint_ops.h"
28 #include "properties.h"
29 #include "render_gdk.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
,
37 GdkEventButton
*event
);
38 static void modify_button_press(ModifyTool
*tool
, GdkEventButton
*event
,
40 static void modify_button_release(ModifyTool
*tool
, GdkEventButton
*event
,
42 static void modify_motion(ModifyTool
*tool
, GdkEventMotion
*event
,
44 static void modify_double_click(ModifyTool
*tool
, GdkEventButton
*event
,
48 create_modify_tool(void)
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
;
59 tool
->state
= STATE_NONE
;
60 tool
->break_connections
= 0;
62 tool
->orig_pos
= NULL
;
69 free_modify_tool(Tool
*tool
)
71 ModifyTool
*mtool
= (ModifyTool
*)tool
;
73 gdk_gc_unref(mtool
->gc
);
78 This function is buggy. Fix it later!
80 transitive_select(DDisplay *ddisp, Point *clickedpoint, Object *obj)
86 for(i = 0; i < obj->num_connections; 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);
101 click_select_object(DDisplay
*ddisp
, Point
*clickedpoint
,
102 GdkEventButton
*event
)
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
,
118 /* Selected an object. */
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
);
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
);
163 } /* Else part moved to allow union/intersection select */
168 static int do_if_clicked_handle(DDisplay
*ddisp
, ModifyTool
*tool
,
169 Point
*clickedpoint
, GdkEventButton
*event
)
176 dist
= diagram_find_closest_handle(ddisp
->diagram
, &handle
,
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
;
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
;
194 modify_button_press(ModifyTool
*tool
, GdkEventButton
*event
,
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
))
207 clicked_obj
= click_select_object(ddisp
, &clickedpoint
, event
);
209 if (do_if_clicked_handle(ddisp
, tool
, &clickedpoint
, event
))
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
;
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
,
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
);
249 modify_double_click(ModifyTool
*tool
, GdkEventButton
*event
,
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
);
276 modify_motion(ModifyTool
*tool
, GdkEventMotion
*event
,
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
) {
299 list
= ddisp
->diagram
->data
->selected
;
300 tool
->orig_pos
= g_new(Point
, g_list_length(list
));
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
) {
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
;
324 point_sub(&delta
, &now
);
326 if (event
->state
& GDK_CONTROL_MASK
) {
327 /* Up-down or left-right */
329 delta
.x
= tool
->start_at
.x
+ tool
->move_compensate
.x
- now
.x
;
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
);
342 case STATE_MOVE_HANDLE
:
343 /* Move to ConnectionPoint if near: */
345 object_find_connectpoint_display(ddisp
, &to
);
347 if (event
->state
& GDK_CONTROL_MASK
) {
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
;
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
,
367 (change
->apply
)(change
, ddisp
->diagram
);
371 if (event
->state
& GDK_CONTROL_MASK
) {
372 /* Up-down or left-right */
374 to
.x
= tool
->start_at
.x
;
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
,
388 object_add_updates(tool
->object
, ddisp
->diagram
);
390 diagram_update_connections_selection(ddisp
->diagram
);
391 diagram_flush(ddisp
->diagram
);
393 case STATE_BOX_SELECT
:
395 if (!auto_scroll
&& !tool
->auto_scrolled
)
397 gdk_draw_rectangle (ddisp
->canvas
->window
, tool
->gc
, FALSE
,
399 tool
->x2
- tool
->x1
, tool
->y2
- tool
->y1
);
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
,
415 tool
->x2
- tool
->x1
, tool
->y2
- tool
->y1
);
421 message_error("Internal error: Strange state in modify_tool\n");
425 tool
->auto_scrolled
= auto_scroll
;
430 modify_button_release(ModifyTool
*tool
, GdkEventButton
*event
,
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
));
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
;
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
);
476 object_add_updates(tool
->object
, ddisp
->diagram
);
477 tool
->object
->ops
->move_handle(tool
->object
, tool
->handle
,
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
;
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
,
510 tool
->x2
- tool
->x1
, tool
->y2
- tool
->y1
);
515 GList
*list
, *list_to_free
;
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
);
528 list
= list_to_free
=
529 layer_find_objects_in_rectangle(ddisp
->diagram
->data
->active_layer
, &r
);
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
);
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
);
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
);
575 diagram_select(ddisp
->diagram
, obj
);
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
;
597 message_error("Internal error: Strange state in modify_tool\n");
600 tool
->break_connections
= FALSE
;