Fix of bug #350246: Broken snap-to-grid inside objects.
[dia.git] / app / modify_tool.c
blobfd576051d448c31cd9869ce9b5dffbd99cf98ec8
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 "select.h"
30 #include "preferences.h"
31 #include "cursor.h"
32 #include "highlight.h"
33 #include "textedit.h"
35 #include "parent.h"
36 #include "diacanvas.h"
37 #include "prop_text.h"
38 #include "gtk/gtk.h"
41 static DiaObject *click_select_object(DDisplay *ddisp, Point *clickedpoint,
42 GdkEventButton *event);
43 static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool,
44 Point *clickedpoint,
45 GdkEventButton *event);
46 static void modify_button_press(ModifyTool *tool, GdkEventButton *event,
47 DDisplay *ddisp);
48 static void modify_button_release(ModifyTool *tool, GdkEventButton *event,
49 DDisplay *ddisp);
50 static void modify_motion(ModifyTool *tool, GdkEventMotion *event,
51 DDisplay *ddisp);
52 static void modify_double_click(ModifyTool *tool, GdkEventButton *event,
53 DDisplay *ddisp);
54 static void modify_make_text_edit(DDisplay *ddisp, DiaObject *obj,
55 Point *clickedpoint);
56 static void modify_start_text_edit(DDisplay *ddisp, Text *text, DiaObject *obj,
57 Point *clickedpoint);
60 Tool *
61 create_modify_tool(void)
63 ModifyTool *tool;
65 tool = g_new0(ModifyTool, 1);
66 tool->tool.type = MODIFY_TOOL;
67 tool->tool.button_press_func = (ButtonPressFunc) &modify_button_press;
68 tool->tool.button_release_func = (ButtonReleaseFunc) &modify_button_release;
69 tool->tool.motion_func = (MotionFunc) &modify_motion;
70 tool->tool.double_click_func = (DoubleClickFunc) &modify_double_click;
71 tool->gc = NULL;
72 tool->state = STATE_NONE;
73 tool->break_connections = 0;
74 tool->auto_scrolled = FALSE;
76 tool->orig_pos = NULL;
78 return (Tool *)tool;
81 static ModifierKeys
82 gdk_event_to_dia_ModifierKeys(guint event_state)
84 ModifierKeys mod = MODIFIER_NONE;
85 if (event_state & GDK_SHIFT_MASK)
86 mod |= MODIFIER_SHIFT;
87 /* Used intenally do not propagate
88 * if (event_state & GDK_CONTROL_MASK)
89 mod | MODIFIER_CONTROL;
91 return mod;
95 void
96 free_modify_tool(Tool *tool)
98 ModifyTool *mtool = (ModifyTool *)tool;
99 if (mtool->gc)
100 gdk_gc_unref(mtool->gc);
101 g_free(mtool);
105 This function is buggy. Fix it later!
106 static void
107 transitive_select(DDisplay *ddisp, Point *clickedpoint, DiaObject *obj)
109 guint i;
110 GList *j;
111 DiaObject *obj1;
113 for(i = 0; i < obj->num_connections; i++) {
114 printf("%d\n", i);
115 j = obj->connections[i]->connected;
116 while(j != NULL && (obj1 = (DiaObject *)j->data) != NULL) {
117 diagram_select(ddisp->diagram, obj1);
118 obj1->ops->select(obj1, clickedpoint,
119 (Renderer *)ddisp->renderer);
120 transitive_select(ddisp, clickedpoint, obj1);
121 j = g_list_next(j);
127 static DiaObject *
128 click_select_object(DDisplay *ddisp, Point *clickedpoint,
129 GdkEventButton *event)
131 Diagram *diagram;
132 real click_distance;
133 DiaObject *obj;
135 diagram = ddisp->diagram;
137 /* Find the closest object to select it: */
139 click_distance = ddisplay_untransform_length(ddisp, 3.0);
141 obj = diagram_find_clicked_object(diagram, clickedpoint,
142 click_distance);
144 if (obj!=NULL) {
145 /* Selected an object. */
146 GList *already;
147 /*printf("Selected object!\n");*/
149 already = g_list_find(diagram->data->selected, obj);
150 if (already == NULL) { /* Not already selected */
151 /*printf("Not already selected\n");*/
153 if (!(event->state & GDK_SHIFT_MASK)) {
154 /* Not Multi-select => remove current selection */
155 diagram_remove_all_selected(diagram, TRUE);
158 diagram_select(diagram, obj);
159 textedit_activate_object(ddisp, obj, clickedpoint);
162 This stuff is buggy, fix it later.
163 if (event->state & GDK_CONTROL_MASK) {
164 transitive_select(ddisp, clickedpoint, obj);
168 ddisplay_do_update_menu_sensitivity(ddisp);
169 object_add_updates_list(diagram->data->selected, diagram);
170 diagram_flush(diagram);
172 return obj;
173 } else { /* Clicked on already selected. */
174 /*printf("Already selected\n");*/
175 textedit_activate_object(ddisp, obj, clickedpoint);
176 object_add_updates_list(diagram->data->selected, diagram);
177 diagram_flush(diagram);
179 if (event->state & GDK_SHIFT_MASK) { /* Multi-select */
180 /* Remove the selected selected */
181 ddisplay_do_update_menu_sensitivity(ddisp);
182 diagram_unselect_object(diagram, (DiaObject *)already->data);
183 diagram_flush(ddisp->diagram);
184 } else {
185 /* Maybe start editing text */
186 #ifdef NEW_TEXT_EDIT
187 modify_make_text_edit(ddisp, obj, clickedpoint);
188 #endif
189 return obj;
192 } /* Else part moved to allow union/intersection select */
194 return NULL;
197 static glong
198 time_micro()
200 GTimeVal tv;
202 g_get_current_time(&tv);
203 return tv.tv_sec*G_USEC_PER_SEC+tv.tv_usec;
206 static int do_if_clicked_handle(DDisplay *ddisp, ModifyTool *tool,
207 Point *clickedpoint, GdkEventButton *event)
209 DiaObject *obj;
210 Handle *handle;
211 real dist;
213 handle = NULL;
214 dist = diagram_find_closest_handle(ddisp->diagram, &handle,
215 &obj, clickedpoint);
216 if (handle_is_clicked(ddisp, handle, clickedpoint)) {
217 tool->state = STATE_MOVE_HANDLE;
218 tool->break_connections = TRUE;
219 tool->last_to = handle->pos;
220 tool->handle = handle;
221 tool->object = obj;
222 gdk_pointer_grab (ddisp->canvas->window, FALSE,
223 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
224 NULL, NULL, event->time);
225 tool->start_at = handle->pos;
226 tool->start_time = time_micro();
227 ddisplay_set_all_cursor(get_cursor(CURSOR_SCROLL));
228 return TRUE;
230 return FALSE;
233 static void
234 modify_button_press(ModifyTool *tool, GdkEventButton *event,
235 DDisplay *ddisp)
237 Point clickedpoint;
238 DiaObject *clicked_obj;
240 ddisplay_untransform_coords(ddisp,
241 (int)event->x, (int)event->y,
242 &clickedpoint.x, &clickedpoint.y);
245 if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event))
246 return;
248 clicked_obj = click_select_object(ddisp, &clickedpoint, event);
249 if (do_if_clicked_handle(ddisp, tool, &clickedpoint, event))
250 return;
252 if ( clicked_obj != NULL ) {
253 tool->state = STATE_MOVE_OBJECT;
254 tool->object = clicked_obj;
255 tool->move_compensate = clicked_obj->position;
256 point_sub(&tool->move_compensate, &clickedpoint);
257 tool->break_connections = TRUE;
258 gdk_pointer_grab (ddisp->canvas->window, FALSE,
259 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
260 NULL, NULL, event->time);
261 tool->start_at = clickedpoint;
262 tool->start_time = time_micro();
263 ddisplay_set_all_cursor(get_cursor(CURSOR_SCROLL));
264 } else {
265 tool->state = STATE_BOX_SELECT;
266 tool->start_box = clickedpoint;
267 tool->end_box = clickedpoint;
268 tool->x1 = tool->x2 = (int) event->x;
269 tool->y1 = tool->y2 = (int) event->y;
271 if (tool->gc == NULL) {
272 tool->gc = gdk_gc_new(ddisp->canvas->window);
273 gdk_gc_set_line_attributes(tool->gc, 1, GDK_LINE_ON_OFF_DASH,
274 GDK_CAP_BUTT, GDK_JOIN_MITER);
275 gdk_gc_set_foreground(tool->gc, &color_gdk_white);
276 gdk_gc_set_function(tool->gc, GDK_XOR);
279 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
280 tool->x1, tool->y1,
281 tool->x2 - tool->x1, tool->y2 - tool->y1);
283 gdk_pointer_grab (ddisp->canvas->window, FALSE,
284 GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_RELEASE_MASK,
285 NULL, NULL, event->time);
290 static void
291 modify_double_click(ModifyTool *tool, GdkEventButton *event,
292 DDisplay *ddisp)
294 Point clickedpoint;
295 DiaObject *clicked_obj;
297 ddisplay_untransform_coords(ddisp,
298 (int)event->x, (int)event->y,
299 &clickedpoint.x, &clickedpoint.y);
301 clicked_obj = click_select_object(ddisp, &clickedpoint, event);
303 if ( clicked_obj != NULL ) {
304 properties_show(ddisp->diagram, clicked_obj);
305 } else { /* No object selected */
306 /*printf("didn't select object\n");*/
307 if (!(event->state & GDK_SHIFT_MASK)) {
308 /* Not Multi-select => Remove all selected */
309 ddisplay_do_update_menu_sensitivity(ddisp);
310 diagram_remove_all_selected(ddisp->diagram, TRUE);
311 diagram_flush(ddisp->diagram);
316 #define MIN_PIXELS 10
318 /** Makes sure that objects aren't accidentally moved when double-clicking
319 * for properties. Objects do not move unless double click time has passed
320 * or the move is 'significant'. Allowing the 'significant' move makes a
321 * regular grab-and-move less jerky.
323 * There's a slight chance that the user could move outside the
324 * minimum movement limit and back in within the double click time
325 * (normally .25 seconds), but that's very, very unlikely. If
326 * it happens, the cursor wouldn't reset just right.
329 static gboolean
330 modify_move_already(ModifyTool *tool, DDisplay *ddisp, Point *to)
332 static gboolean settings_taken = FALSE;
333 static int double_click_time = 250;
334 real dist;
336 if (!settings_taken) {
337 /* One could argue that if the settings were updated while running,
338 we should re-read them. But I don't see any way to get notified,
339 and I don't want to do this whole thing for each bit of the
340 move --Lars */
341 GtkSettings *settings = gtk_settings_get_default();
342 if (settings == NULL) {
343 g_message(_("Couldn't get GTK settings"));
344 } else {
345 g_object_get(G_OBJECT(settings),
346 "gtk-double-click-time", &double_click_time, NULL);
348 settings_taken = TRUE;
350 if (tool->start_time < time_micro()-double_click_time*1000) {
351 return TRUE;
353 dist = distance_point_point_manhattan(&tool->start_at, to);
354 if (ddisp->grid.snap) {
355 real grid_x = ddisp->diagram->grid.width_x;
356 real grid_y = ddisp->diagram->grid.width_y;
357 if (dist > grid_x || dist > grid_y) {
358 return TRUE;
361 if (ddisplay_transform_length(ddisp, dist) > MIN_PIXELS) {
362 return (ddisplay_transform_length(ddisp, dist) > MIN_PIXELS);
363 } else {
364 return FALSE;
368 /** Used for highlighting mainpoint connections. */
369 static Color mainpoint_color = { 1.0, 0.8, 0.0 };
370 /** Used for highlighting normal connections. */
371 static Color cp_color = { 1.0, 0.0, 0.0 };
373 static void
374 modify_motion(ModifyTool *tool, GdkEventMotion *event,
375 DDisplay *ddisp)
377 Point to;
378 Point now, delta, full_delta;
379 gboolean auto_scroll, vertical = FALSE;
380 ConnectionPoint *connectionpoint = NULL;
381 ObjectChange *objchange;
382 gchar *postext;
383 GtkStatusbar *statusbar;
384 guint context_id;
386 if (tool->state==STATE_NONE)
387 return; /* Fast path... */
389 auto_scroll = ddisplay_autoscroll(ddisp, event->x, event->y);
391 ddisplay_untransform_coords(ddisp, event->x, event->y, &to.x, &to.y);
393 if (!modify_move_already(tool, ddisp, &to)) return;
395 switch (tool->state) {
396 case STATE_MOVE_OBJECT:
398 if (tool->orig_pos == NULL) {
399 GList *list;
400 int i;
401 DiaObject *obj;
403 /* consider non-selected children affected */
404 list = parent_list_affected(ddisp->diagram->data->selected);
405 tool->orig_pos = g_new(Point, g_list_length(list));
406 i=0;
407 while (list != NULL) {
408 obj = (DiaObject *) list->data;
409 tool->orig_pos[i] = obj->position;
410 list = g_list_next(list); i++;
414 if (tool->break_connections)
415 diagram_unconnect_selected(ddisp->diagram); /* Pushes UNDO info */
417 if (event->state & GDK_CONTROL_MASK) {
418 full_delta = to;
419 point_sub(&full_delta, &tool->start_at);
420 vertical = (fabs(full_delta.x) < fabs(full_delta.y));
423 point_add(&to, &tool->move_compensate);
424 snap_to_grid(ddisp, &to.x, &to.y);
426 now = tool->object->position;
428 delta = to;
429 point_sub(&delta, &now);
431 if (event->state & GDK_CONTROL_MASK) {
432 /* Up-down or left-right */
433 if (vertical) {
434 delta.x = tool->start_at.x + tool->move_compensate.x - now.x;
435 } else {
436 delta.y = tool->start_at.y + tool->move_compensate.y - now.y;
440 object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram);
441 objchange = object_list_move_delta(ddisp->diagram->data->selected, &delta);
442 if (objchange != NULL) {
443 undo_object_change(ddisp->diagram, tool->object, objchange);
445 object_add_updates_list(ddisp->diagram->data->selected, ddisp->diagram);
447 object_add_updates(tool->object, ddisp->diagram);
449 /* Put current mouse position in status bar */
450 statusbar = GTK_STATUSBAR (ddisp->modified_status);
451 context_id = gtk_statusbar_get_context_id (statusbar, "ObjectPos");
453 postext = g_strdup_printf("%.3f, %.3f - %.3f, %.3f",
454 tool->object->bounding_box.left,
455 tool->object->bounding_box.top,
456 tool->object->bounding_box.right,
457 tool->object->bounding_box.bottom);
459 gtk_statusbar_pop (statusbar, context_id);
460 gtk_statusbar_push (statusbar, context_id, postext);
462 g_free(postext);
464 diagram_update_connections_selection(ddisp->diagram);
465 diagram_flush(ddisp->diagram);
466 break;
467 case STATE_MOVE_HANDLE:
468 full_delta = to;
469 point_sub(&full_delta, &tool->start_at);
471 /* make sure resizing is restricted to its parent */
474 /* if resize was blocked by parent, that means the resizing was
475 outward, thus it won't bother the children so we don't have to
476 check the children */
477 if (!parent_handle_move_out_check(tool->object, &to))
478 parent_handle_move_in_check(tool->object, &to, &tool->start_at);
480 if (event->state & GDK_CONTROL_MASK)
481 vertical = (fabs(full_delta.x) < fabs(full_delta.y));
483 highlight_reset_all(ddisp->diagram);
484 if ((tool->handle->connect_type != HANDLE_NONCONNECTABLE)) {
485 /* Move to ConnectionPoint if near: */
486 connectionpoint =
487 object_find_connectpoint_display(ddisp, &to, tool->object, TRUE);
488 if (connectionpoint != NULL) {
489 Color *hi_color;
490 to = connectionpoint->pos;
491 if (connectionpoint->flags & CP_FLAGS_MAIN) {
492 hi_color = &mainpoint_color;
493 } else {
494 hi_color = &cp_color;
496 highlight_object(connectionpoint->object, hi_color, ddisp->diagram);
497 ddisplay_set_all_cursor(get_cursor(CURSOR_CONNECT));
500 if (connectionpoint == NULL) {
501 /* No connectionopoint near, then snap to grid (if enabled) */
502 snap_to_grid(ddisp, &to.x, &to.y);
503 ddisplay_set_all_cursor(get_cursor(CURSOR_SCROLL));
506 if (tool->break_connections) {
507 /* break connections to the handle currently selected. */
508 if (tool->handle->connected_to!=NULL) {
509 Change *change = undo_unconnect(ddisp->diagram, tool->object,
510 tool->handle);
512 (change->apply)(change, ddisp->diagram);
516 if (event->state & GDK_CONTROL_MASK) {
517 /* Up-down or left-right */
518 if (vertical) {
519 to.x = tool->start_at.x;
520 } else {
521 to.y = tool->start_at.y;
525 if (tool->orig_pos == NULL) {
526 tool->orig_pos = g_new(Point, 1);
527 *tool->orig_pos = tool->handle->pos;
530 /* Put current mouse position in status bar */
531 statusbar = GTK_STATUSBAR (ddisp->modified_status);
532 context_id = gtk_statusbar_get_context_id (statusbar, "CursorPos");
534 postext = g_strdup_printf("%.3f, %.3f", to.x, to.y);
536 gtk_statusbar_pop (statusbar, context_id);
537 gtk_statusbar_push (statusbar, context_id, postext);
539 g_free(postext);
541 object_add_updates(tool->object, ddisp->diagram);
543 /* Handle undo */
544 objchange = tool->object->ops->move_handle(tool->object, tool->handle,
545 &to, connectionpoint,
546 HANDLE_MOVE_USER, gdk_event_to_dia_ModifierKeys(event->state));
547 if (objchange != NULL) {
548 undo_object_change(ddisp->diagram, tool->object, objchange);
550 object_add_updates(tool->object, ddisp->diagram);
552 diagram_update_connections_selection(ddisp->diagram);
553 diagram_flush(ddisp->diagram);
554 break;
555 case STATE_BOX_SELECT:
557 if (!auto_scroll && !tool->auto_scrolled)
559 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
560 tool->x1, tool->y1,
561 tool->x2 - tool->x1, tool->y2 - tool->y1);
564 tool->end_box = to;
566 ddisplay_transform_coords(ddisp,
567 MIN(tool->start_box.x, tool->end_box.x),
568 MIN(tool->start_box.y, tool->end_box.y),
569 &tool->x1, &tool->y1);
570 ddisplay_transform_coords(ddisp,
571 MAX(tool->start_box.x, tool->end_box.x),
572 MAX(tool->start_box.y, tool->end_box.y),
573 &tool->x2, &tool->y2);
575 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
576 tool->x1, tool->y1,
577 tool->x2 - tool->x1, tool->y2 - tool->y1);
578 break;
579 case STATE_NONE:
581 break;
582 default:
583 message_error("Internal error: Strange state in modify_tool\n");
586 tool->last_to = to;
587 tool->auto_scrolled = auto_scroll;
590 /** Find the list of objects selected by current rubberbanding.
591 * The list should be freed after use. */
592 static GList *
593 find_selected_objects(DDisplay *ddisp, ModifyTool *tool)
595 Rectangle r;
596 r.left = MIN(tool->start_box.x, tool->end_box.x);
597 r.right = MAX(tool->start_box.x, tool->end_box.x);
598 r.top = MIN(tool->start_box.y, tool->end_box.y);
599 r.bottom = MAX(tool->start_box.y, tool->end_box.y);
601 if (prefs.reverse_rubberbanding_intersects &&
602 tool->start_box.x > tool->end_box.x) {
603 return
604 layer_find_objects_intersecting_rectangle(ddisp->diagram->data->active_layer, &r);
605 } else {
606 return
607 layer_find_objects_in_rectangle(ddisp->diagram->data->active_layer, &r);
611 static void
612 modify_button_release(ModifyTool *tool, GdkEventButton *event,
613 DDisplay *ddisp)
615 Point *dest_pos, to;
616 GList *list;
617 int i;
618 DiaObject *active_obj = NULL;
619 ObjectChange *objchange;
621 tool->break_connections = FALSE;
622 ddisplay_set_all_cursor(default_cursor);
624 switch (tool->state) {
625 case STATE_MOVE_OBJECT:
626 /* Return to normal state */
627 gdk_pointer_ungrab (event->time);
629 ddisplay_untransform_coords(ddisp, event->x, event->y, &to.x, &to.y);
630 if (!modify_move_already(tool, ddisp, &to)) {
631 tool->orig_pos = NULL;
632 tool->state = STATE_NONE;
633 return;
636 diagram_update_connections_selection(ddisp->diagram);
638 if (tool->orig_pos != NULL) {
639 /* consider the non-selected children affected */
640 list = parent_list_affected(ddisp->diagram->data->selected);
641 dest_pos = g_new(Point, g_list_length(list));
642 i=0;
643 while (list != NULL) {
644 DiaObject *obj = (DiaObject *) list->data;
645 dest_pos[i] = obj->position;
646 list = g_list_next(list); i++;
649 undo_move_objects(ddisp->diagram, tool->orig_pos, dest_pos,
650 parent_list_affected(ddisp->diagram->data->selected));
653 ddisplay_connect_selected(ddisp); /* pushes UNDO info */
654 diagram_update_extents(ddisp->diagram);
655 diagram_modified(ddisp->diagram);
656 diagram_flush(ddisp->diagram);
658 undo_set_transactionpoint(ddisp->diagram->undo);
660 tool->orig_pos = NULL;
661 tool->state = STATE_NONE;
662 break;
663 case STATE_MOVE_HANDLE:
664 gdk_pointer_ungrab (event->time);
665 tool->state = STATE_NONE;
667 if (tool->orig_pos != NULL) {
668 undo_move_handle(ddisp->diagram, tool->handle, tool->object,
669 *tool->orig_pos, tool->last_to);
672 /* Final move: */
673 object_add_updates(tool->object, ddisp->diagram);
674 objchange = tool->object->ops->move_handle(tool->object, tool->handle,
675 &tool->last_to, NULL,
676 HANDLE_MOVE_USER_FINAL,gdk_event_to_dia_ModifierKeys(event->state));
677 if (objchange != NULL) {
678 undo_object_change(ddisp->diagram, tool->object, objchange);
681 object_add_updates(tool->object, ddisp->diagram);
683 /* Connect if possible: */
684 if (tool->handle->connect_type != HANDLE_NONCONNECTABLE) {
685 object_connect_display(ddisp, tool->object, tool->handle, TRUE); /* pushes UNDO info */
686 diagram_update_connections_selection(ddisp->diagram);
689 highlight_reset_all(ddisp->diagram);
690 diagram_flush(ddisp->diagram);
692 diagram_modified(ddisp->diagram);
693 diagram_update_extents(ddisp->diagram);
695 undo_set_transactionpoint(ddisp->diagram->undo);
697 if (tool->orig_pos != NULL) {
698 g_free(tool->orig_pos);
699 tool->orig_pos = NULL;
702 break;
703 case STATE_BOX_SELECT:
704 gdk_pointer_ungrab (event->time);
705 /* Remember the currently active object for reactivating. */
706 active_obj = active_focus()!=NULL?focus_get_object(active_focus()):NULL;
707 /* Remove last box: */
708 if (!tool->auto_scrolled) {
709 gdk_draw_rectangle (ddisp->canvas->window, tool->gc, FALSE,
710 tool->x1, tool->y1,
711 tool->x2 - tool->x1, tool->y2 - tool->y1);
715 GList *list, *list_to_free;
717 list = list_to_free = find_selected_objects(ddisp, tool);
719 if (selection_style == SELECT_REPLACE &&
720 !(event->state & GDK_SHIFT_MASK)) {
721 /* Not Multi-select => Remove all selected */
722 diagram_remove_all_selected(ddisp->diagram, TRUE);
725 if (selection_style == SELECT_INTERSECTION) {
726 GList *intersection = NULL;
728 while (list != NULL) {
729 DiaObject *obj = (DiaObject *)list->data;
731 if (diagram_is_selected(ddisp->diagram, obj)) {
732 intersection = g_list_append(intersection, obj);
735 list = g_list_next(list);
737 list = intersection;
738 diagram_remove_all_selected(ddisp->diagram, TRUE);
739 while (list != NULL) {
740 DiaObject *obj = (DiaObject *)list->data;
742 diagram_select(ddisp->diagram, obj);
744 list = g_list_next(list);
746 g_list_free(intersection);
747 } else {
748 while (list != NULL) {
749 DiaObject *obj = (DiaObject *)list->data;
751 if (selection_style == SELECT_REMOVE) {
752 if (diagram_is_selected(ddisp->diagram, obj))
753 diagram_unselect_object(ddisp->diagram, obj);
754 } else if (selection_style == SELECT_INVERT) {
755 if (diagram_is_selected(ddisp->diagram, obj))
756 diagram_unselect_object(ddisp->diagram, obj);
757 else
758 diagram_select(ddisp->diagram, obj);
759 } else {
760 if (!diagram_is_selected(ddisp->diagram, obj))
761 diagram_select(ddisp->diagram, obj);
764 list = g_list_next(list);
768 g_list_free(list_to_free);
772 if (active_obj != NULL &&
773 diagram_is_selected(ddisp->diagram, active_obj)) {
774 textedit_activate_object(ddisp, active_obj, NULL);
775 } else {
776 textedit_activate_first(ddisp);
778 ddisplay_do_update_menu_sensitivity(ddisp);
779 ddisplay_flush(ddisp);
781 tool->state = STATE_NONE;
782 break;
783 case STATE_NONE:
784 break;
785 default:
786 message_error("Internal error: Strange state in modify_tool\n");
791 #define EDIT_BORDER_WIDTH 5
793 static gboolean
794 modify_edit_end(GtkWidget *widget, GdkEventFocus *event, gpointer data)
796 GtkTextView *view = GTK_TEXT_VIEW(widget);
797 DiaObject *obj = (DiaObject*)data;
798 GQuark quark = g_quark_from_string(PROP_TYPE_TEXT);
799 const PropDescription *props = obj->ops->describe_props(obj);
800 int i;
802 printf("Ending focus\n");
804 for (i = 0; props[i].name != NULL; i++) {
805 printf("Testing to remove: %s\n", props[i].name);
806 if (props[i].type_quark == quark) {
807 GPtrArray *textprops = g_ptr_array_sized_new(1);
808 TextProperty *textprop;
809 Property *prop = props[i].ops->new_prop(&props[i], pdtpp_true);
810 GtkTextBuffer *buf;
811 GtkTextIter start, end;
813 printf("Going to stop %d\n", i);
814 buf = gtk_text_view_get_buffer(view);
815 g_ptr_array_add(textprops, prop);
816 obj->ops->get_props(obj, textprops);
817 textprop = (TextProperty*)prop;
818 if (textprop->text_data != NULL) g_free(textprop->text_data);
819 gtk_text_buffer_get_bounds(buf, &start, &end);
820 textprop->text_data = gtk_text_buffer_get_text(buf, &start, &end, TRUE);
821 printf("Setting text %s\n", textprop->text_data);
822 obj->ops->set_props(obj, textprops);
823 gtk_widget_destroy(widget);
826 return FALSE;
829 /** Start editing the first text among selected objects, if any */
830 static void
831 modify_edit_first_text(DDisplay *ddisp)
833 GList *edits = ddisp->diagram->data->text_edits;
835 if (edits != NULL) {
836 Text *text = (Text*)edits->data;
837 modify_start_text_edit(ddisp, text, text->parent_object, NULL);
841 void
842 modify_start_text_edit(DDisplay *ddisp, Text *text, DiaObject *obj, Point *clickedpoint)
844 GtkWidget *view = gtk_text_view_new();
845 int x, y, i;
846 GtkTextBuffer *buf;
847 GtkTextTag *fonttag;
848 GtkTextIter start, end;
849 real ascent;
850 int ascent_pixels;
852 printf("modify_start_text_edit\n");
853 ddisplay_transform_coords(ddisp,
854 text->position.x,
855 text->position.y,
856 &x, &y);
857 ascent = dia_font_scaled_ascent(text->line[0],
858 text->font,
859 text->height,
860 ddisp->zoom_factor);
861 printf("Text prop string %s pos %d, %d ascent %f\n",
862 text->line[0], x, y, ascent);
863 ascent_pixels = ddisplay_transform_length(ddisp, ascent);
864 y -= ascent_pixels;
865 dia_canvas_put(DIA_CANVAS(ddisp->canvas), view,
866 x-EDIT_BORDER_WIDTH, y-EDIT_BORDER_WIDTH);
867 buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
868 for (i = 0; i < text->numlines; i++) {
869 gtk_text_buffer_insert_at_cursor(buf, text->line[i], -1);
871 fonttag =
872 gtk_text_buffer_create_tag(buf,
873 NULL,
874 "font-desc",
875 dia_font_get_description(text->font),
876 NULL);
877 gtk_text_buffer_get_bounds(buf, &start, &end);
878 gtk_text_buffer_apply_tag(buf, fonttag, &start, &end);
880 printf("Above lines %d below %d\n",
881 gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(view)),
882 gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(view)));
884 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
885 GTK_TEXT_WINDOW_LEFT,
886 EDIT_BORDER_WIDTH);
887 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
888 GTK_TEXT_WINDOW_RIGHT,
889 EDIT_BORDER_WIDTH);
890 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
891 GTK_TEXT_WINDOW_TOP,
892 EDIT_BORDER_WIDTH);
893 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
894 GTK_TEXT_WINDOW_BOTTOM,
895 EDIT_BORDER_WIDTH);
896 /* Using deprecated function because the fucking gobject documentation
897 * fucking sucks. */
898 #ifdef NEW_TEXT_EDIT
899 gtk_signal_connect(GTK_OBJECT(view), "focus-out-event",
900 modify_edit_end, obj);
901 #endif
902 gtk_widget_grab_focus(view);
903 gtk_widget_show(view);
906 void
907 modify_make_text_edit(DDisplay *ddisp, DiaObject *obj, Point *clickedpoint)
909 const PropDescription *props = obj->ops->describe_props(obj);
910 int i;
911 for (i = 0; props[i].name != NULL; i++) {
912 GQuark type = g_quark_from_string(PROP_TYPE_TEXT);
913 printf("Testing %s\n", props[i].type);
914 if (props[i].type_quark == type) {
915 GtkWidget *view = gtk_text_view_new();
916 GPtrArray *textprops = g_ptr_array_sized_new(1);
917 TextProperty *textprop;
918 Property *prop = props[i].ops->new_prop(&props[i], pdtpp_true);
919 int x, y;
920 GtkTextBuffer *buf;
921 GtkTextTag *fonttag;
922 GtkTextIter start, end;
923 real ascent;
924 int ascent_pixels;
926 g_ptr_array_add(textprops, prop);
928 printf("Found text prop %d\n", i);
929 obj->ops->get_props(obj, textprops);
930 textprop = (TextProperty*)prop;
931 ddisplay_transform_coords(ddisp,
932 textprop->attr.position.x,
933 textprop->attr.position.y,
934 &x, &y);
935 ascent = dia_font_scaled_ascent(textprop->text_data,
936 textprop->attr.font,
937 textprop->attr.height,
938 ddisp->zoom_factor);
939 printf("Text prop string %s pos %d, %d ascent %f\n",
940 textprop->text_data, x, y, ascent);
941 ascent_pixels = ddisplay_transform_length(ddisp, ascent);
942 y -= ascent_pixels;
943 dia_canvas_put(DIA_CANVAS(ddisp->canvas), view,
944 x-EDIT_BORDER_WIDTH, y-EDIT_BORDER_WIDTH);
945 buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
946 gtk_text_buffer_insert_at_cursor(buf, textprop->text_data, -1);
947 fonttag =
948 gtk_text_buffer_create_tag(buf,
949 NULL,
950 "font-desc",
951 dia_font_get_description(textprop->attr.font),
952 NULL);
953 gtk_text_buffer_get_bounds(buf, &start, &end);
954 gtk_text_buffer_apply_tag(buf, fonttag, &start, &end);
956 printf("Above lines %d below %d\n",
957 gtk_text_view_get_pixels_above_lines(GTK_TEXT_VIEW(view)),
958 gtk_text_view_get_pixels_below_lines(GTK_TEXT_VIEW(view)));
960 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
961 GTK_TEXT_WINDOW_LEFT,
962 EDIT_BORDER_WIDTH);
963 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
964 GTK_TEXT_WINDOW_RIGHT,
965 EDIT_BORDER_WIDTH);
966 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
967 GTK_TEXT_WINDOW_TOP,
968 EDIT_BORDER_WIDTH);
969 gtk_text_view_set_border_window_size(GTK_TEXT_VIEW(view),
970 GTK_TEXT_WINDOW_BOTTOM,
971 EDIT_BORDER_WIDTH);
972 /* Using deprecated function because the fucking gobject documentation
973 * fucking sucks. */
974 #ifdef NEW_TEXT_EDIT
975 gtk_signal_connect(GTK_OBJECT(view), "focus-out-event",
976 modify_edit_end, obj);
977 #endif
978 gtk_widget_grab_focus(view);
979 gtk_widget_show(view);