1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1998 Alexander Larsson
3  *
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.
8  *
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.
13  *
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.
17  */
18 
19 /** This file handles the display part of text edit stuff: Making text
20  * edit start and stop at the right time and highlighting the edits.
21  * lib/text.c and lib/focus.c handles internal things like who can have
22  * focus and how to enter text.  app/disp_callbacks.c handles the actual
23  * keystrokes.
24  *
25  * There's an invariant that all objects in the focus list must be selected.
26  *
27  * When starting to edit a particular text, we force entering text edit mode,
28  * and when leaving text edit mode, we force stopping editing the text.
29  * However, when changing between which texts are edited, we don't leave
30  * textedit mode just to enter it again.  However, due to the text edit
31  * tool, it is possible to be in text edit mode without editing any
32  * particular text.
33  */
34 
35 #include <config.h>
36 
37 #include "object.h"
38 #include "focus.h"
39 #include "display.h"
40 #include "highlight.h"
41 #include "textedit.h"
42 #include "object_ops.h"
43 #include "text.h"
44 
45 typedef struct TextEditChange {
46   Change obj_change;
47 
48   /** The text before editing began */
49   gchar *orig_text;
50   /** The text after editing finished */
51   gchar *new_text;
52   /** The Text item that this change happened to (in case there's more than
53    *  one on an object). */
54   Text *text;
55 } TextEditChange;
56 
57 static Change *text_edit_create_change(Text *text);
58 static void text_edit_update(TextEditChange *change);
59 static void text_edit_apply(Change *change, Diagram *dia);
60 static void textedit_end_edit(DDisplay *ddisp, Focus *focus);
61 
62 /** Returns TRUE if the given display is currently in text-edit mode. */
63 gboolean
textedit_mode(DDisplay * ddisp)64 textedit_mode(DDisplay *ddisp)
65 {
66   return ddisplay_active_focus(ddisp) != NULL;
67 }
68 
69 /** Perform the necessary changes to the display according to whether
70  *  or not we are in text edit mode.  While normally called from
71  *  textedit_enter and textedit_exit, this is exposed in order to allow
72  *  for switching between displays.
73 
74  * @param ddisp The display to set according to mode.
75  */
76 static void
textedit_display_change(DDisplay * ddisp)77 textedit_display_change(DDisplay *ddisp)
78 {
79 }
80 
81 /** Start editing text.  This brings Dia into text-edit mode, which
82  * changes some menu items.  We may or may not already be in text-edit mode,
83  * but at the return of this function we are known to be in text-edit mode.
84  * @param ddisp The display that editing happens in.
85  */
86 static void
textedit_enter(DDisplay * ddisp)87 textedit_enter(DDisplay *ddisp)
88 {
89   if (textedit_mode(ddisp)) {
90     return;
91   }
92   /* Set textedit menus */
93   /* Set textedit key-event handler */
94   textedit_display_change(ddisp);
95 }
96 
97 /** Stop editing text.  Whether or not we already are in text-edit mode,
98  * this function leaves us in object-edit mode.  This function will call
99  * textedit_end_edit if necessary.
100  *
101  * @param ddisp The display that editing happens in.
102  */
103 static void
textedit_exit(DDisplay * ddisp)104 textedit_exit(DDisplay *ddisp)
105 {
106   if (!textedit_mode(ddisp)) {
107     return;
108   }
109   if (ddisplay_active_focus(ddisp) != NULL) {
110     textedit_end_edit(ddisp, ddisplay_active_focus(ddisp));
111   }
112   /* Set object-edit menus */
113   /* Set object-edit key-event handler */
114   textedit_display_change(ddisp);
115 }
116 
117 
118 /** Begin editing a particular text focus.  This function will call
119  * textedit_enter if necessary.  By return from this function, we will
120  * be in textedit mode.
121  * @param ddisp The display in use
122  * @param focus The text focus to edit
123  */
124 static void
textedit_begin_edit(DDisplay * ddisp,Focus * focus)125 textedit_begin_edit(DDisplay *ddisp, Focus *focus)
126 {
127   Color *focus_col = color_new_rgb(1.0, 1.0, 0.0);
128 
129   g_assert(dia_object_is_selected(focus_get_object(focus)));
130   if (!textedit_mode(ddisp)) {
131     textedit_enter(ddisp);
132   }
133   ddisplay_set_active_focus(ddisp, focus);
134   highlight_object(focus->obj, focus_col, ddisp->diagram);
135   object_add_updates(focus->obj, ddisp->diagram);
136 /* Undo not quite ready yet.
137   undo_push_change(ddisp->diagram->undo, text_edit_create_change(focus->text));
138 */
139 }
140 
141 /** Stop editing a particular text focus.  This must only be called in
142  * text-edit mode.  This handles the object-specific changes required to
143  * edit it.
144  * @param ddisp The display in use
145  * @param focus The text focus to stop editing
146  */
147 static void
textedit_end_edit(DDisplay * ddisp,Focus * focus)148 textedit_end_edit(DDisplay *ddisp, Focus *focus)
149 {
150   TextEditChange *change;
151   /* During destruction of the diagram the display may already be gone */
152   if (!ddisp)
153     return;
154 
155   g_assert(textedit_mode(ddisp));
156 
157   /* Leak of focus highlight color here, but should it be handled
158      by highlight or by us?
159   */
160   highlight_object_off(focus->obj, ddisp->diagram);
161   object_add_updates(focus->obj, ddisp->diagram);
162 /* Undo not quite ready yet
163   change = (TextEditChange *) undo_remove_to(ddisp->diagram->undo,
164 					     text_edit_apply);
165   if (change != NULL) {
166     text_edit_update(change);
167   }
168 */
169   ddisplay_set_active_focus(ddisp, NULL);
170 }
171 
172 /** Move the text edit focus either backwards or forwards. */
173 Focus *
textedit_move_focus(DDisplay * ddisp,Focus * focus,gboolean forwards)174 textedit_move_focus(DDisplay *ddisp, Focus *focus, gboolean forwards)
175 {
176   if (focus != NULL) {
177     textedit_end_edit(ddisp, focus);
178   }
179   if (forwards) {
180     Focus *new_focus = focus_next_on_diagram((DiagramData *) ddisp->diagram);
181     if (new_focus != NULL) give_focus(new_focus);
182   } else {
183     Focus *new_focus = focus_previous_on_diagram((DiagramData *) ddisp->diagram);
184     if (new_focus != NULL) give_focus(new_focus);
185   }
186   focus = get_active_focus((DiagramData *) ddisp->diagram);
187 
188   if (focus != NULL) {
189     textedit_begin_edit(ddisp, focus);
190   }
191   diagram_flush(ddisp->diagram);
192   return focus;
193 }
194 
195 /** Call when something recieves an actual focus (not to be confused
196  * with doing request_focus(), which merely puts one in the focus list).
197  */
198 void
textedit_activate_focus(DDisplay * ddisp,Focus * focus,Point * clicked)199 textedit_activate_focus(DDisplay *ddisp, Focus *focus, Point *clicked)
200 {
201   Focus *old_focus = get_active_focus((DiagramData *) ddisp->diagram);
202   if (old_focus != NULL) {
203     textedit_end_edit(ddisp, old_focus);
204   }
205   if (clicked) {
206       text_set_cursor((Text*)focus->user_data, clicked, ddisp->renderer);
207   }
208   textedit_begin_edit(ddisp, focus);
209   give_focus(focus);
210   diagram_flush(ddisp->diagram);
211 }
212 
213 /** Call when an object is chosen for activation (e.g. due to creation).
214  * Calling this function will put us into text-edit mode if there is
215  * text to edit, otherwise it will take us out of text-edit mode.
216  *
217  * Returns true if there is something to text edit.
218  */
219 gboolean
textedit_activate_object(DDisplay * ddisp,DiaObject * obj,Point * clicked)220 textedit_activate_object(DDisplay *ddisp, DiaObject *obj, Point *clicked)
221 {
222   Focus *new_focus;
223 
224   new_focus = focus_get_first_on_object(obj);
225   if (new_focus != NULL) {
226     Focus *focus = get_active_focus((DiagramData *) ddisp->diagram);
227     if (focus != NULL) {
228       textedit_end_edit(ddisp, focus);
229     }
230     give_focus(new_focus);
231     if (clicked) {
232       text_set_cursor((Text*)new_focus->user_data, clicked, ddisp->renderer);
233     }
234     textedit_begin_edit(ddisp, new_focus);
235     diagram_flush(ddisp->diagram);
236     return TRUE;
237   } else {
238     textedit_exit(ddisp);
239     return FALSE;
240   }
241 }
242 
243 /** Call to activate the first editable selected object.
244  * Deactivates the old edit.
245  * Calling this function will put us into text-edit mode if there is
246  * text to edit, otherwise it will take us out of text-edit mode.
247  */
248 void
textedit_activate_first(DDisplay * ddisp)249 textedit_activate_first(DDisplay *ddisp)
250 {
251   Focus *new_focus = NULL;
252   GList *tmp, *selected = diagram_get_sorted_selected(ddisp->diagram);
253   Focus *focus = get_active_focus((DiagramData *) ddisp->diagram);
254 
255   if (focus != NULL) {
256     textedit_end_edit(ddisp, focus);
257   }
258   tmp = selected;
259   while (new_focus == NULL && tmp != NULL) {
260     DiaObject *obj = (DiaObject*) selected->data;
261     new_focus = focus_get_first_on_object(obj);
262     tmp = g_list_next(tmp);
263   }
264   g_list_free (selected);
265   if (new_focus != NULL) {
266     give_focus(new_focus);
267     textedit_begin_edit(ddisp, new_focus);
268     diagram_flush(ddisp->diagram);
269   } else {
270     textedit_exit(ddisp);
271   }
272 }
273 
274 /** Call when something causes the text focus to disappear.
275  * Does not remove objects from the focus list, but removes the
276  * focus highlight and stuff.
277  * Calling remove_focus on the active object or remove_focus_all
278  * implies deactivating the focus.
279  * Calling this takes us out of textedit mode.
280  */
281 void
textedit_deactivate_focus(void)282 textedit_deactivate_focus(void)
283 {
284   if (ddisplay_active() != NULL) {
285     DiagramData *dia = (DiagramData *) ddisplay_active()->diagram;
286     Focus *focus = get_active_focus(dia);
287     if (focus != NULL) {
288       remove_focus_on_diagram(dia);
289     }
290     /* This also ends the edit */
291     textedit_exit(ddisplay_active());
292   }
293 }
294 
295 /** Call when something should be removed from the focus list.
296  * Calling this takes us out of textedit mode.
297  */
298 void
textedit_remove_focus(DiaObject * obj,Diagram * diagram)299 textedit_remove_focus(DiaObject *obj, Diagram *diagram)
300 {
301   Focus *old_focus = get_active_focus((DiagramData *) diagram);
302   if (remove_focus_object(obj)) {
303     /* TODO: make sure the focus is deactivated */
304   }
305   /* This also ends the edit */
306   if (ddisplay_active() != NULL) {
307     textedit_exit(ddisplay_active());
308   }
309 }
310 
311 /** Call when the entire list of focusable texts gets reset.
312  * Calling this takes us out of textedit mode.
313  */
314 void
textedit_remove_focus_all(Diagram * diagram)315 textedit_remove_focus_all(Diagram *diagram)
316 {
317   Focus *focus = get_active_focus((DiagramData *) diagram);
318   if (focus != NULL) {
319     /* TODO: make sure the focus is deactivated */
320   }
321   reset_foci_on_diagram((DiagramData *) diagram);
322   /* This also ends the edit */
323   if (ddisplay_active() != NULL) {
324     textedit_exit(ddisplay_active());
325   }
326 }
327 
328 /* *************** Textedit-mode related Undo ******************* */
329 
330 /* Each edit of a text part of an object counts as one undo after it's
331  * done.  While editing, full undo is available, but afterwards the
332  * changes get merged into one.  This is done by sticking a TextEditChange
333  * on the undo stack at the beginning with the original text, and then
334  * at the end removing every change after that one.  This is why non-text-edit
335  * changes are not allowed in text edit mode:  It would break the undo.
336  */
337 
338 static void
text_edit_apply(Change * change,Diagram * dia)339 text_edit_apply(Change *change, Diagram *dia)
340 {
341   TextEditChange *te_change = (TextEditChange *) change;
342   text_set_string(te_change->text, te_change->new_text);
343 }
344 
345 static void
text_edit_revert(TextEditChange * change,Diagram * dia)346 text_edit_revert(TextEditChange *change, Diagram *dia)
347 {
348   if (textedit_mode(ddisplay_active())) {
349     DDisplay *ddisp = ddisplay_active();
350     textedit_exit(ddisp);
351   }
352   text_set_string(change->text, change->orig_text);
353 }
354 
355 static void
text_edit_free(TextEditChange * change)356 text_edit_free(TextEditChange *change)
357 {
358   g_free(change->orig_text);
359   if (change->new_text) {
360     g_free(change->new_text);
361   }
362 }
363 
364 /** Note that the new text isn't known when this is made.  That gets
365  * added later.
366  */
367 static Change *
text_edit_create_change(Text * text)368 text_edit_create_change(Text *text)
369 {
370   TextEditChange *change;
371 
372   change = g_new0(TextEditChange, 1);
373 
374   change->obj_change.apply = (UndoApplyFunc) text_edit_apply;
375   change->obj_change.revert = (UndoRevertFunc) text_edit_revert;
376   change->obj_change.free = (UndoFreeFunc) text_edit_free;
377 
378   change->text = text;
379   if (text_is_empty(text)) {
380     change->orig_text = g_strdup("");
381   } else {
382     change->orig_text = text_get_string_copy(text);
383   }
384   /* new_text not ready yet */
385 
386   return (Change *)change;
387 }
388 
389 /** This should be called when an edit is finished, to store the final
390  *  text. */
391 static void
text_edit_update(TextEditChange * change)392 text_edit_update(TextEditChange *change)
393 {
394   change->new_text = text_get_string_copy(change->text);
395 }
396 
397