textedit.c 9.91 KB
Newer Older
Lars Clausen's avatar
Lars Clausen committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
15 16
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Lars Clausen's avatar
Lars Clausen committed
17 18 19 20 21 22 23 24 25
 */

/** This file handles the display part of text edit stuff: Making text
 * edit start and stop at the right time and highlighting the edits.
 * lib/text.c and lib/focus.c handles internal things like who can have
 * focus and how to enter text.  app/disp_callbacks.c handles the actual
 * keystrokes.
 *
 * There's an invariant that all objects in the focus list must be selected.
26 27 28 29 30 31 32
 *
 * When starting to edit a particular text, we force entering text edit mode,
 * and when leaving text edit mode, we force stopping editing the text.
 * However, when changing between which texts are edited, we don't leave
 * textedit mode just to enter it again.  However, due to the text edit
 * tool, it is possible to be in text edit mode without editing any
 * particular text.
Lars Clausen's avatar
Lars Clausen committed
33 34 35 36 37 38 39 40 41 42 43 44
 */

#include <config.h>

#include "object.h"
#include "focus.h"
#include "display.h"
#include "highlight.h"
#include "textedit.h"
#include "object_ops.h"
#include "text.h"

45 46 47 48 49 50 51 52 53 54 55 56 57
typedef struct TextEditChange {
  Change obj_change;

  /** The text before editing began */
  gchar *orig_text;
  /** The text after editing finished */
  gchar *new_text;
  /** The Text item that this change happened to (in case there's more than
   *  one on an object). */
  Text *text;
} TextEditChange;

static void textedit_end_edit(DDisplay *ddisp, Focus *focus);
58 59 60 61 62

/** Returns TRUE if the given display is currently in text-edit mode. */
gboolean
textedit_mode(DDisplay *ddisp)
{
63 64 65 66 67 68 69 70 71 72
  return ddisplay_active_focus(ddisp) != NULL;
}

/** Perform the necessary changes to the display according to whether
 *  or not we are in text edit mode.  While normally called from
 *  textedit_enter and textedit_exit, this is exposed in order to allow
 *  for switching between displays.

 * @param ddisp The display to set according to mode.
 */
73
static void
74 75
textedit_display_change(DDisplay *ddisp)
{
76 77 78 79 80 81 82
}

/** Start editing text.  This brings Dia into text-edit mode, which
 * changes some menu items.  We may or may not already be in text-edit mode,
 * but at the return of this function we are known to be in text-edit mode.
 * @param ddisp The display that editing happens in.
 */
Lars Clausen's avatar
Lars Clausen committed
83
static void
84
textedit_enter(DDisplay *ddisp)
Lars Clausen's avatar
Lars Clausen committed
85
{
86
  if (textedit_mode(ddisp)) {
87
    return;
88 89 90
  }
  /* Set textedit menus */
  /* Set textedit key-event handler */
91
  textedit_display_change(ddisp);
92
}
93

94
/** Stop editing text.  Whether or not we already are in text-edit mode,
95 96 97
 * this function leaves us in object-edit mode.  This function will call
 * textedit_end_edit if necessary.
 *
98 99 100 101 102 103 104 105
 * @param ddisp The display that editing happens in.
 */
static void
textedit_exit(DDisplay *ddisp)
{
  if (!textedit_mode(ddisp)) {
    return;
  }
106 107 108
  if (ddisplay_active_focus(ddisp) != NULL) {
    textedit_end_edit(ddisp, ddisplay_active_focus(ddisp));
  }
109 110
  /* Set object-edit menus */
  /* Set object-edit key-event handler */
111
  textedit_display_change(ddisp);
Lars Clausen's avatar
Lars Clausen committed
112 113
}

114 115 116 117

/** Begin editing a particular text focus.  This function will call
 * textedit_enter if necessary.  By return from this function, we will
 * be in textedit mode.
118 119 120
 * @param ddisp The display in use
 * @param focus The text focus to edit 
 */
Lars Clausen's avatar
Lars Clausen committed
121 122 123
static void
textedit_begin_edit(DDisplay *ddisp, Focus *focus)
{
124
  g_assert(dia_object_is_selected(focus_get_object(focus)));
125 126 127 128
  if (!textedit_mode(ddisp)) {
    textedit_enter(ddisp);
  }
  ddisplay_set_active_focus(ddisp, focus);
129
  highlight_object(focus->obj, DIA_HIGHLIGHT_TEXT_EDIT, ddisp->diagram);
130
  object_add_updates(focus->obj, ddisp->diagram);
Lars Clausen's avatar
Lars Clausen committed
131 132
}

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
/** Stop editing a particular text focus.  This must only be called in
 * text-edit mode.  This handles the object-specific changes required to
 * edit it.
 * @param ddisp The display in use
 * @param focus The text focus to stop editing
 */
static void
textedit_end_edit(DDisplay *ddisp, Focus *focus) 
{
  /* During destruction of the diagram the display may already be gone */
  if (!ddisp)
    return;

  g_assert(textedit_mode(ddisp));

  /* Leak of focus highlight color here, but should it be handled
     by highlight or by us?
  */
  highlight_object_off(focus->obj, ddisp->diagram);
  object_add_updates(focus->obj, ddisp->diagram);
153
  diagram_object_modified(ddisp->diagram, focus->obj);
154
  ddisplay_set_active_focus(ddisp, NULL);
155 156
}

Lars Clausen's avatar
Lars Clausen committed
157 158 159 160
/** Move the text edit focus either backwards or forwards. */
Focus *
textedit_move_focus(DDisplay *ddisp, Focus *focus, gboolean forwards)
{
161
  g_return_val_if_fail (ddisp != NULL, NULL);
Lars Clausen's avatar
Lars Clausen committed
162 163
  if (focus != NULL) {
    textedit_end_edit(ddisp, focus);
Lars Clausen's avatar
Lars Clausen committed
164
  }
Lars Clausen's avatar
Lars Clausen committed
165
  if (forwards) {
166
    Focus *new_focus = focus_next_on_diagram((DiagramData *) ddisp->diagram);
Lars Clausen's avatar
Lars Clausen committed
167 168
    if (new_focus != NULL) give_focus(new_focus);    
  } else {
169
    Focus *new_focus = focus_previous_on_diagram((DiagramData *) ddisp->diagram);
Lars Clausen's avatar
Lars Clausen committed
170 171
    if (new_focus != NULL) give_focus(new_focus);    
  }
172
  focus = get_active_focus((DiagramData *) ddisp->diagram);
Lars Clausen's avatar
Lars Clausen committed
173 174

  if (focus != NULL) {
Lars Clausen's avatar
Lars Clausen committed
175
    textedit_begin_edit(ddisp, focus);
Lars Clausen's avatar
Lars Clausen committed
176 177 178 179 180 181 182 183 184 185 186
  }
  diagram_flush(ddisp->diagram);
  return focus;
}

/** Call when something recieves an actual focus (not to be confused
 * with doing request_focus(), which merely puts one in the focus list).
 */
void
textedit_activate_focus(DDisplay *ddisp, Focus *focus, Point *clicked)
{
187 188
  Focus *old_focus = get_active_focus((DiagramData *) ddisp->diagram);
  if (old_focus != NULL) {
Lars Clausen's avatar
Lars Clausen committed
189
    textedit_end_edit(ddisp, old_focus);
Lars Clausen's avatar
Lars Clausen committed
190 191
  }
  if (clicked) {
192
      text_set_cursor(focus->text, clicked, ddisp->renderer);
Lars Clausen's avatar
Lars Clausen committed
193
  }
Lars Clausen's avatar
Lars Clausen committed
194
  textedit_begin_edit(ddisp, focus);
Lars Clausen's avatar
Lars Clausen committed
195 196 197 198 199
  give_focus(focus);
  diagram_flush(ddisp->diagram);
}

/** Call when an object is chosen for activation (e.g. due to creation).
200 201
 * Calling this function will put us into text-edit mode if there is
 * text to edit, otherwise it will take us out of text-edit mode.
202 203
 *
 * Returns true if there is something to text edit.
Lars Clausen's avatar
Lars Clausen committed
204
 */
205
gboolean
Lars Clausen's avatar
Lars Clausen committed
206 207
textedit_activate_object(DDisplay *ddisp, DiaObject *obj, Point *clicked)
{
208 209 210
  Focus *new_focus;

  new_focus = focus_get_first_on_object(obj);
Lars Clausen's avatar
Lars Clausen committed
211
  if (new_focus != NULL) {
212 213 214 215
    Focus *focus = get_active_focus((DiagramData *) ddisp->diagram);
    if (focus != NULL) {
      textedit_end_edit(ddisp, focus);
    }
Lars Clausen's avatar
Lars Clausen committed
216
    give_focus(new_focus); 
Lars Clausen's avatar
Lars Clausen committed
217
    if (clicked) {
218
      text_set_cursor(new_focus->text, clicked, ddisp->renderer);
Lars Clausen's avatar
Lars Clausen committed
219
    }
Lars Clausen's avatar
Lars Clausen committed
220
    textedit_begin_edit(ddisp, new_focus);
Lars Clausen's avatar
Lars Clausen committed
221
    diagram_flush(ddisp->diagram);
222
    return TRUE;
223 224
  } else {
    textedit_exit(ddisp);
225
    return FALSE;
Lars Clausen's avatar
Lars Clausen committed
226 227 228
  }
}

229 230
/** Call to activate the first editable selected object.
 * Deactivates the old edit.
231 232
 * Calling this function will put us into text-edit mode if there is
 * text to edit, otherwise it will take us out of text-edit mode.
233
 */
234
gboolean
235 236 237
textedit_activate_first(DDisplay *ddisp)
{
  Focus *new_focus = NULL;
238
  GList *tmp, *selected = diagram_get_sorted_selected(ddisp->diagram);
239
  Focus *focus = get_active_focus((DiagramData *) ddisp->diagram);
240

241
  if (focus != NULL) {
242 243
    textedit_end_edit(ddisp, focus);
  }
244 245
  tmp = selected;
  while (new_focus == NULL && tmp != NULL) {
246 247
    DiaObject *obj = (DiaObject*) selected->data;
    new_focus = focus_get_first_on_object(obj);
248
    tmp = g_list_next(tmp);
249
  }
250
  g_list_free (selected);
251 252 253 254
  if (new_focus != NULL) {
    give_focus(new_focus); 
    textedit_begin_edit(ddisp, new_focus);
    diagram_flush(ddisp->diagram);
255
    return TRUE;
256 257
  } else {
    textedit_exit(ddisp);
258
    return FALSE;
259
  }
260 261
}

Lars Clausen's avatar
Lars Clausen committed
262 263 264 265
/** Call when something causes the text focus to disappear.
 * Does not remove objects from the focus list, but removes the
 * focus highlight and stuff.
 * Calling remove_focus on the active object or remove_focus_all
266 267 268
 * implies deactivating the focus.
 * Calling this takes us out of textedit mode.
 */
Lars Clausen's avatar
Lars Clausen committed
269
void
270
textedit_deactivate_focus(void)
Lars Clausen's avatar
Lars Clausen committed
271
{
272 273 274 275 276 277 278 279
  if (ddisplay_active() != NULL) {
    DiagramData *dia = (DiagramData *) ddisplay_active()->diagram;
    Focus *focus = get_active_focus(dia);
    if (focus != NULL) {
      remove_focus_on_diagram(dia);
    }
    /* This also ends the edit */
    textedit_exit(ddisplay_active());
Lars Clausen's avatar
Lars Clausen committed
280 281 282
  }
}

283 284 285
/** Call when something should be removed from the focus list.
 * Calling this takes us out of textedit mode.
 */
Lars Clausen's avatar
Lars Clausen committed
286 287 288
void
textedit_remove_focus(DiaObject *obj, Diagram *diagram)
{
289
  Focus *old_focus = get_active_focus((DiagramData *) diagram);
Lars Clausen's avatar
Lars Clausen committed
290
  if (remove_focus_object(obj)) {
Lars Clausen's avatar
Lars Clausen committed
291
    /* TODO: make sure the focus is deactivated */
Lars Clausen's avatar
Lars Clausen committed
292
  }
293 294 295 296
  /* This also ends the edit */
  if (ddisplay_active() != NULL) {
    textedit_exit(ddisplay_active());
  }
Lars Clausen's avatar
Lars Clausen committed
297 298
}

299 300 301
/** Call when the entire list of focusable texts gets reset.
 * Calling this takes us out of textedit mode.
 */
Lars Clausen's avatar
Lars Clausen committed
302 303 304
void
textedit_remove_focus_all(Diagram *diagram)
{
305
  Focus *focus = get_active_focus((DiagramData *) diagram);
Lars Clausen's avatar
Lars Clausen committed
306
  if (focus != NULL) {
Lars Clausen's avatar
Lars Clausen committed
307
    /* TODO: make sure the focus is deactivated */
Lars Clausen's avatar
Lars Clausen committed
308
  }
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
  reset_foci_on_diagram((DiagramData *) diagram);
  /* This also ends the edit */
  if (ddisplay_active() != NULL) {
    textedit_exit(ddisplay_active());
  }
}

/* *************** Textedit-mode related Undo ******************* */

/* Each edit of a text part of an object counts as one undo after it's
 * done.  While editing, full undo is available, but afterwards the
 * changes get merged into one.  This is done by sticking a TextEditChange
 * on the undo stack at the beginning with the original text, and then
 * at the end removing every change after that one.  This is why non-text-edit
 * changes are not allowed in text edit mode:  It would break the undo.
 */