Code

Simple tracking of time to display dialogs and main window.
[inkscape.git] / src / dialogs / text-edit.cpp
1 /** @file
2  * @brief Text editing dialog
3  */
4 /* Authors:
5  *   Lauris Kaplinski <lauris@ximian.com>
6  *   bulia byak <buliabyak@users.sf.net>
7  *   Johan Engelen <goejendaagh@zonnet.nl>
8  *   Abhishek Sharma
9  *
10  * Copyright (C) 1999-2007 Authors
11  * Copyright (C) 2000-2001 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
20 #include <libnrtype/font-instance.h>
21 #include <gtk/gtk.h>
23 #ifdef WITH_GTKSPELL
24 extern "C" {
25 # include <gtkspell/gtkspell.h>
26 }
27 #endif
29 #include "macros.h"
30 #include <glibmm/i18n.h>
31 #include "helper/window.h"
32 #include "../widgets/font-selector.h"
33 #include "../inkscape.h"
34 #include "../document.h"
35 #include "../desktop-style.h"
36 #include "../desktop-handles.h"
37 #include "../selection.h"
38 #include "../style.h"
39 #include "../sp-text.h"
40 #include "../sp-flowtext.h"
41 #include "../text-editing.h"
42 #include "../ui/icon-names.h"
43 #include <libnrtype/font-style-to-pos.h>
45 #include "dialog-events.h"
46 #include "../preferences.h"
47 #include "../verbs.h"
48 #include "../interface.h"
49 #include "svg/css-ostringstream.h"
50 #include "widgets/icon.h"
51 #include <xml/repr.h>
52 #include "util/ege-appear-time-tracker.h"
54 using Inkscape::DocumentUndo;
55 using ege::AppearTimeTracker;
57 #define VB_MARGIN 4
58 #define MIN_ONSCREEN_DISTANCE 50
60 static void sp_text_edit_dialog_selection_modified (Inkscape::Application *inkscape, Inkscape::Selection *sel, guint flags, GtkWidget *dlg);
61 static void sp_text_edit_dialog_selection_changed (Inkscape::Application *inkscape, Inkscape::Selection *sel, GtkWidget *dlg);
62 static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg);
64 static void sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg);
65 static void sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg);
66 static void sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg);
68 static void sp_text_edit_dialog_read_selection (GtkWidget *dlg, gboolean style, gboolean content);
70 static void sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg);
71 static void sp_text_edit_dialog_font_changed (SPFontSelector *fontsel, font_instance *font, GtkWidget *dlg);
72 static void sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg);
73 static void sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg);
75 static SPItem *sp_ted_get_selected_text_item (void);
76 static unsigned sp_ted_get_selected_text_count (void);
79 static const gchar *spacings[] = {"50%", "80%", "90%", "100%", "110%", "120%", "130%", "140%", "150%", "200%", "300%", NULL};
81 static GtkWidget *dlg = NULL;
82 static win_data wd;
83 // impossible original values to make sure they are read from prefs
84 static gint x = -1000, y = -1000, w = 0, h = 0;
85 static Glib::ustring const prefs_path = "/dialogs/textandfont/";
90 static void
91 sp_text_edit_dialog_destroy( GtkObject */*object*/, gpointer /*data*/ )
92 {
93     sp_signal_disconnect_by_data (INKSCAPE, dlg);
94     wd.win = dlg = NULL;
95     wd.stop = 0;
96 }
100 static gboolean
101 sp_text_edit_dialog_delete( GtkObject */*object*/, GdkEvent */*event*/, gpointer /*data*/ )
103     gtk_window_get_position ((GtkWindow *) dlg, &x, &y);
104     gtk_window_get_size ((GtkWindow *) dlg, &w, &h);
106     if (x<0) x=0;
107     if (y<0) y=0;
109     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
110     prefs->setInt(prefs_path + "x", x);
111     prefs->setInt(prefs_path + "y", y);
112     prefs->setInt(prefs_path + "w", w);
113     prefs->setInt(prefs_path + "h", h);
115     return FALSE; // which means, go ahead and destroy it
119 /**
120     These callbacks set the eatkeys flag when the text editor is entered and cancel it when it's left.
121     This flag is used to prevent passing keys from the dialog to canvas, so that the text editor
122     can handle keys like Esc and Ctrl+Z itself.
123  */
124 gboolean
125 text_view_focus_in( GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data )
127     GObject *dlg = (GObject *) data;
128     g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (TRUE));
129     return FALSE;
132 gboolean
133 text_view_focus_out (GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data)
135     GObject *dlg = (GObject *) data;
136     g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (FALSE));
137     return FALSE;
141 void
142 sp_text_edit_dialog (void)
144     bool wantTiming = Inkscape::Preferences::get()->getBool("/dialogs/debug/trackAppear", false);
145     GTimer *timer = wantTiming ? g_timer_new() : 0;
147     if (!dlg) {
149         gchar title[500];
150         sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_TEXT), title);
151         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
153         dlg = sp_window_new (title, TRUE);
154         if (x == -1000 || y == -1000) {
155             x = prefs->getInt(prefs_path + "x", -1000);
156             y = prefs->getInt(prefs_path + "y", -1000);
157         }
158         if (w ==0 || h == 0) {
159             w = prefs->getInt(prefs_path + "w", 0);
160             h = prefs->getInt(prefs_path + "h", 0);
161         }
163 //        if (x<0) x=0;
164 //        if (y<0) y=0;
166         if (w && h)
167             gtk_window_resize ((GtkWindow *) dlg, w, h);
168         if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
169             gtk_window_move ((GtkWindow *) dlg, x, y);
170         } else {
171             gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
172         }
175         sp_transientize (dlg);
176         wd.win = dlg;
177         wd.stop = 0;
178         g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd );
180         gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg );
182         gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_text_edit_dialog_destroy), dlg );
183         gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
184         g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
186         g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg );
187         g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg );
189         gtk_window_set_policy (GTK_WINDOW (dlg), TRUE, TRUE, FALSE);
191         GtkTooltips *tt = gtk_tooltips_new();
193         // box containing the notebook and the bottom buttons
194         GtkWidget *mainvb = gtk_vbox_new (FALSE, 0);
195         gtk_container_add (GTK_CONTAINER (dlg), mainvb);
197         // notebook
198         GtkWidget *nb = gtk_notebook_new ();
199         gtk_box_pack_start (GTK_BOX (mainvb), nb, TRUE, TRUE, 0);
200         g_object_set_data (G_OBJECT (dlg), "notebook", nb);
204         // Font tab
205         {
206             GtkWidget *l = gtk_label_new (_("Font"));
207             GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
208             gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
209             gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
211             /* HBox containing font selection and layout */
212             GtkWidget *hb = gtk_hbox_new (FALSE, 0);
213             gtk_box_pack_start (GTK_BOX (vb), hb, TRUE, TRUE, 0);
215             // font and style selector
216             GtkWidget *fontsel = sp_font_selector_new ();
217             g_signal_connect ( G_OBJECT (fontsel), "font_set", G_CALLBACK (sp_text_edit_dialog_font_changed), dlg );
219             g_signal_connect_swapped ( G_OBJECT (g_object_get_data (G_OBJECT(fontsel), "family-treeview")),
220                                       "row-activated",
221                                       G_CALLBACK (gtk_window_activate_default),
222                                       dlg);
224             gtk_box_pack_start (GTK_BOX (hb), fontsel, TRUE, TRUE, 0);
225             g_object_set_data (G_OBJECT (dlg), "fontsel", fontsel);
227             // Layout
228             {
229                 GtkWidget *f = gtk_frame_new (_("Layout"));
230                 gtk_box_pack_start (GTK_BOX (hb), f, FALSE, FALSE, 4);
231                 GtkWidget *l_vb = gtk_vbox_new (FALSE, VB_MARGIN);
232                 gtk_container_add (GTK_CONTAINER (f), l_vb);
234                 {
235                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
236                     GtkWidget *group;
238                     // align left
239                     {
240                         // TODO - replace with Inkscape-specific call
241                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_LEFT, GTK_ICON_SIZE_LARGE_TOOLBAR );
242                         GtkWidget *b = group = gtk_radio_button_new (NULL);
243                         gtk_tooltips_set_tip (tt, b, _("Align lines left"), NULL);
244                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
245                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg);
246                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE );
247                         gtk_container_add (GTK_CONTAINER (b), px);
248                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
249                         g_object_set_data (G_OBJECT (dlg), "text_anchor_start", b);
250                     }
252                     // align center
253                     {
254                         // TODO - replace with Inkscape-specific call
255                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_CENTER, GTK_ICON_SIZE_LARGE_TOOLBAR );
256                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
257                         /* TRANSLATORS: `Center' here is a verb. */
258                         gtk_tooltips_set_tip (tt, b, _("Center lines"), NULL);
259                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
260                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
261                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
262                         gtk_container_add (GTK_CONTAINER (b), px);
263                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
264                         g_object_set_data (G_OBJECT (dlg), "text_anchor_middle", b);
265                     }
267                     // align right
268                     {
269                         // TODO - replace with Inkscape-specific call
270                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_RIGHT, GTK_ICON_SIZE_LARGE_TOOLBAR );
271                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
272                         gtk_tooltips_set_tip (tt, b, _("Align lines right"), NULL);
273                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
274                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
275                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
276                         gtk_container_add (GTK_CONTAINER (b), px);
277                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
278                         g_object_set_data (G_OBJECT (dlg), "text_anchor_end", b);
279                     }
281                     // align justify
282                     {
283                         // TODO - replace with Inkscape-specific call
284                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_FILL, GTK_ICON_SIZE_LARGE_TOOLBAR );
285                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
286                         gtk_tooltips_set_tip (tt, b, _("Justify lines"), NULL);
287                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
288                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
289                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
290                         gtk_container_add (GTK_CONTAINER (b), px);
291                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
292                         g_object_set_data (G_OBJECT (dlg), "text_anchor_justify", b);
293                     }
295                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
296                 }
299                 {
300                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
301                     GtkWidget *group;
303                     // horizontal
304                     {
305                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
306                                                       INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
307                         GtkWidget *b = group = gtk_radio_button_new (NULL);
308                         gtk_tooltips_set_tip (tt, b, _("Horizontal text"), NULL);
309                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
310                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
311                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
312                         gtk_container_add (GTK_CONTAINER (b), px);
313                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
314                         g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL, b);
315                     }
317                     // vertical
318                     {
319                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
320                                                       INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
321                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
322                         gtk_tooltips_set_tip (tt, b, _("Vertical text"), NULL);
323                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
324                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
325                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
326                         gtk_container_add (GTK_CONTAINER (b), px);
327                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
328                         g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL, b);
329                     }
331                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
332                 }
334                 {
335                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
337                     l = gtk_label_new (_("Line spacing:"));
338                     gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5);
339                     gtk_box_pack_start (GTK_BOX (row), l, FALSE, FALSE, VB_MARGIN);
341                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
342                 }
344                 {
345                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
347                     GtkWidget *c = gtk_combo_new ();
348                     gtk_combo_set_value_in_list ((GtkCombo *) c, FALSE, FALSE);
349                     gtk_combo_set_use_arrows ((GtkCombo *) c, TRUE);
350                     gtk_combo_set_use_arrows_always ((GtkCombo *) c, TRUE);
351                     gtk_widget_set_size_request (c, 90, -1);
353                     { /* Setup strings */
354                         GList *sl = NULL;
355                         for (int i = 0; spacings[i]; i++) {
356                             sl = g_list_prepend (sl, (void *) spacings[i]);
357                         }
358                         sl = g_list_reverse (sl);
359                         gtk_combo_set_popdown_strings ((GtkCombo *) c, sl);
360                         g_list_free (sl);
361                     }
363                     g_signal_connect ( (GObject *) ((GtkCombo *) c)->entry,
364                                        "changed",
365                                        (GCallback) sp_text_edit_dialog_line_spacing_changed,
366                                        dlg );
367                     gtk_box_pack_start (GTK_BOX (row), c, FALSE, FALSE, VB_MARGIN);
368                     g_object_set_data (G_OBJECT (dlg), "line_spacing", c);
370                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, VB_MARGIN);
371                 }
372             }
374             /* Font preview */
375             GtkWidget *preview = sp_font_preview_new ();
376             gtk_box_pack_start (GTK_BOX (vb), preview, TRUE, TRUE, 4);
377             g_object_set_data (G_OBJECT (dlg), "preview", preview);
378         }
381         // Text tab
382         {
383             GtkWidget *l = gtk_label_new (_("Text"));
384             GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
385             gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
386             gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
388             GtkWidget *scroller = gtk_scrolled_window_new ( NULL, NULL );
389             gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (scroller),
390                                              GTK_POLICY_AUTOMATIC,
391                                              GTK_POLICY_AUTOMATIC );
392             gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_IN );
393             gtk_widget_show (scroller);
395             GtkTextBuffer *tb = gtk_text_buffer_new (NULL);
396             GtkWidget *txt = gtk_text_view_new_with_buffer (tb);
397             gtk_text_view_set_wrap_mode ((GtkTextView *) txt, GTK_WRAP_WORD);
398 #ifdef WITH_GTKSPELL
399             GError *error = NULL;
400             char *errortext = NULL;
401             /* todo: Use computed xml:lang attribute of relevant element, if present, to specify the
402                language (either as 2nd arg of gtkspell_new_attach, or with explicit
403                gtkspell_set_language call in; see advanced.c example in gtkspell docs).
404                sp_text_edit_dialog_read_selection looks like a suitable place. */
405             if (gtkspell_new_attach(GTK_TEXT_VIEW(txt), NULL, &error) == NULL) {
406                 g_print("gtkspell error: %s\n", error->message);
407                 errortext = g_strdup_printf("GtkSpell was unable to initialize.\n"
408                                             "%s", error->message);
409                 g_error_free(error);
410             }
411 #endif
412             gtk_widget_set_size_request (txt, -1, 64);
413             gtk_text_view_set_editable (GTK_TEXT_VIEW (txt), TRUE);
414             gtk_container_add (GTK_CONTAINER (scroller), txt);
415             gtk_box_pack_start (GTK_BOX (vb), scroller, TRUE, TRUE, 0);
416             g_signal_connect ( G_OBJECT (tb), "changed",
417                                G_CALLBACK (sp_text_edit_dialog_text_changed), dlg );
418             g_signal_connect (G_OBJECT (txt), "focus-in-event", G_CALLBACK (text_view_focus_in), dlg);
419             g_signal_connect (G_OBJECT (txt), "focus-out-event", G_CALLBACK (text_view_focus_out), dlg);
420             g_object_set_data (G_OBJECT (dlg), "text", tb);
421             g_object_set_data (G_OBJECT (dlg), "textw", txt);
422         }
424         /* Buttons */
425         GtkWidget *hb = gtk_hbox_new (FALSE, VB_MARGIN);
426         gtk_container_set_border_width (GTK_CONTAINER (hb), 4);
427         gtk_box_pack_start (GTK_BOX (mainvb), hb, FALSE, FALSE, 0);
429         {
430             GtkWidget *b = gtk_button_new_with_label (_("Set as default"));
431             g_signal_connect ( G_OBJECT (b), "clicked",
432                                G_CALLBACK (sp_text_edit_dialog_set_default),
433                                dlg );
434             gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0);
435             g_object_set_data (G_OBJECT (dlg), "default", b);
436         }
438         {
439             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
440             g_signal_connect ( G_OBJECT (b), "clicked",
441                                G_CALLBACK (sp_text_edit_dialog_close), dlg );
442             gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0);
443         }
445         {
446             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_APPLY);
447             GTK_WIDGET_SET_FLAGS (b, GTK_CAN_DEFAULT | GTK_HAS_DEFAULT);
448             g_signal_connect ( G_OBJECT (b), "clicked",
449                                G_CALLBACK (sp_text_edit_dialog_apply), dlg );
450             gtk_box_pack_end ( GTK_BOX (hb), b, FALSE, FALSE, 0 );
451             g_object_set_data (G_OBJECT (dlg), "apply", b);
452         }
454         g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection",
455                            G_CALLBACK (sp_text_edit_dialog_selection_modified), dlg);
456         g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection",
457                            G_CALLBACK (sp_text_edit_dialog_selection_changed), dlg);
458         g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_text_edit_dialog_subselection_changed), dlg);
460         gtk_widget_show_all (dlg);
462         sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
463     }
465     if ( wantTiming ) {
466         // Time tracker takes ownership of the timer.
467         AppearTimeTracker *tracker = new AppearTimeTracker(timer, GTK_WIDGET(dlg), "DialogText");
468         tracker->setAutodelete(true);
469         timer = 0;
470     }
472     gtk_window_present ((GtkWindow *) dlg);
474 } // end of sp_text_edit_dialog()
478 static void
479 sp_text_edit_dialog_selection_modified( Inkscape::Application */*inkscape*/,
480                                         Inkscape::Selection */*sel*/,
481                                         guint flags,
482                                         GtkWidget *dlg )
484     gboolean style, content;
486     style =
487         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
488                     SP_OBJECT_STYLE_MODIFIED_FLAG  )) != 0 );
490     content =
491         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
492                     SP_TEXT_CONTENT_MODIFIED_FLAG  )) != 0 );
494     sp_text_edit_dialog_read_selection (dlg, style, content);
500 static void
501 sp_text_edit_dialog_selection_changed( Inkscape::Application */*inkscape*/,
502                                        Inkscape::Selection */*sel*/,
503                                        GtkWidget *dlg )
505     sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
508 static void sp_text_edit_dialog_subselection_changed( Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, GtkWidget *dlg )
510     sp_text_edit_dialog_read_selection (dlg, TRUE, FALSE);
513 static void
514 sp_text_edit_dialog_update_object_text ( SPItem *text )
516         GtkTextBuffer *tb;
517         GtkTextIter start, end;
518         gchar *str;
520         tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
522         /* write text */
523         if (gtk_text_buffer_get_modified (tb)) {
524             gtk_text_buffer_get_bounds (tb, &start, &end);
525             str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
526             sp_te_set_repr_text_multiline (text, str);
527             g_free (str);
528             gtk_text_buffer_set_modified (tb, FALSE);
529         }
532 SPCSSAttr *
533 sp_get_text_dialog_style ()
535         GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
537         SPCSSAttr *css = sp_repr_css_attr_new ();
539         /* font */
540         font_instance *font = sp_font_selector_get_font (SP_FONT_SELECTOR (fontsel));
542         if ( font ) {
543             Glib::ustring fontName = font_factory::Default()->ConstructFontSpecification(font);
544             sp_repr_css_set_property (css, "-inkscape-font-specification", fontName.c_str());
546             gchar c[256];
548             font->Family(c, 256);
549             sp_repr_css_set_property (css, "font-family", c);
551             font->Attribute( "weight", c, 256);
552             sp_repr_css_set_property (css, "font-weight", c);
554             font->Attribute("style", c, 256);
555             sp_repr_css_set_property (css, "font-style", c);
557             font->Attribute("stretch", c, 256);
558             sp_repr_css_set_property (css, "font-stretch", c);
560             font->Attribute("variant", c, 256);
561             sp_repr_css_set_property (css, "font-variant", c);
563             Inkscape::CSSOStringStream os;
564             os << sp_font_selector_get_size (SP_FONT_SELECTOR (fontsel)) << "px"; // must specify px, see inkscape bug 1221626 and 1610103
565             sp_repr_css_set_property (css, "font-size", os.str().c_str());
567             font->Unref();
568             font=NULL;
569         }
571         /* Layout */
572         GtkWidget *b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "text_anchor_start");
574         // Align Left
575         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
576             sp_repr_css_set_property (css, "text-anchor", "start");
577             sp_repr_css_set_property (css, "text-align", "start");
578         } else {
579             // Align Center
580             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
581                                                 "text_anchor_middle");
582             if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
583                 sp_repr_css_set_property (css, "text-anchor", "middle");
584                 sp_repr_css_set_property (css, "text-align", "center");
585             } else {
586                 // Align Right
587                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
588                                                     "text_anchor_end");
589                 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
590                     sp_repr_css_set_property (css, "text-anchor", "end");
591                     sp_repr_css_set_property (css, "text-align", "end");
592                 } else {
593                     // Align Justify
594                     sp_repr_css_set_property (css, "text-anchor", "start");
595                     sp_repr_css_set_property (css, "text-align", "justify");
596                 }
597             }
598         }
600         b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
602         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
603             sp_repr_css_set_property (css, "writing-mode", "lr");
604         } else {
605             sp_repr_css_set_property (css, "writing-mode", "tb");
606         }
608         // Note that CSS 1.1 does not support line-height; we set it for consistency, but also set
609         // sodipodi:linespacing for backwards compatibility; in 1.2 we use line-height for flowtext
610         GtkWidget *combo = (GtkWidget*)g_object_get_data ((GObject *) dlg, "line_spacing");
611         const char *sstr = gtk_entry_get_text ((GtkEntry *) ((GtkCombo *) (combo))->entry);
612         sp_repr_css_set_property (css, "line-height", sstr);
614         return css;
618 static void
619 sp_text_edit_dialog_set_default( GtkButton */*button*/, GtkWidget *dlg )
621     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
623     SPCSSAttr *css = sp_get_text_dialog_style ();
624     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
626     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
627     prefs->mergeStyle("/tools/text/style", css);
628     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
630     sp_repr_css_attr_unref (css);
632     gtk_widget_set_sensitive (def, FALSE);
637 static void
638 sp_text_edit_dialog_apply( GtkButton */*button*/, GtkWidget *dlg )
640     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
642     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
643     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
644     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
646     unsigned items = 0;
647     const GSList *item_list = sp_desktop_selection(desktop)->itemList();
648     SPCSSAttr *css = sp_get_text_dialog_style ();
649     sp_desktop_set_style(desktop, css, true);
651     for (; item_list != NULL; item_list = item_list->next) {
652         // apply style to the reprs of all text objects in the selection
653         if (SP_IS_TEXT (item_list->data)) {
655             // backwards compatibility:
656             reinterpret_cast<SPObject*>(item_list->data)->getRepr()->setAttribute("sodipodi:linespacing", sp_repr_css_property (css, "line-height", NULL));
658             ++items;
659         }
660         else if (SP_IS_FLOWTEXT (item_list->data))
661             // no need to set sodipodi:linespacing, because Inkscape never supported it on flowtext
662             ++items;
663     }
665     if (items == 0) {
666         // no text objects; apply style to prefs for new objects
667         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
668         prefs->mergeStyle("/tools/text/style", css);
669         gtk_widget_set_sensitive (def, FALSE);
670     } else if (items == 1) {
671         /* exactly one text object; now set its text, too */
672         SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
673         if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) {
674             sp_text_edit_dialog_update_object_text (item);
675         }
676     }
678     // complete the transaction
679     DocumentUndo::done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_CONTEXT_TEXT,
680                        _("Set text style"));
681     gtk_widget_set_sensitive (apply, FALSE);
682     sp_repr_css_attr_unref (css);
683     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
686 static void
687 sp_text_edit_dialog_close( GtkButton */*button*/, GtkWidget *dlg )
689     gtk_widget_destroy (GTK_WIDGET (dlg));
692 static void
693 sp_text_edit_dialog_read_selection ( GtkWidget *dlg,
694                                      gboolean dostyle,
695                                      gboolean docontent )
697     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
698         return;
700     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
702     GtkWidget *notebook = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "notebook");
703     GtkWidget *textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
704     GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
705     GtkWidget *preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
706     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
707     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
709     GtkTextBuffer *tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
711     SPItem *text = sp_ted_get_selected_text_item ();
713     Inkscape::XML::Node *repr;
714     if (text)
715     {
716         guint items = sp_ted_get_selected_text_count ();
717         if (items == 1) {
718             gtk_widget_set_sensitive (textw, TRUE);
719         } else {
720             gtk_widget_set_sensitive (textw, FALSE);
721         }
722         gtk_widget_set_sensitive (apply, FALSE);
723         gtk_widget_set_sensitive (def, TRUE);
725         if (docontent) {
726             gchar *str;
727             str = sp_te_get_string_multiline (text);
729             if (str) {
730                 int pos;
731                 pos = 0;
733                 if (items == 1) {
734                     gtk_text_buffer_set_text (tb, str, strlen (str));
735                     gtk_text_buffer_set_modified (tb, FALSE);
736                 }
737                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
738                 g_free (str);
740             } else {
741                 gtk_text_buffer_set_text (tb, "", 0);
742                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
743             }
744         } // end of if (docontent)
745         repr = text->getRepr();
747     } else {
748         gtk_widget_set_sensitive (textw, FALSE);
749         gtk_widget_set_sensitive (apply, FALSE);
750         gtk_widget_set_sensitive (def, FALSE);
751     }
753     if (dostyle) {
755         // create temporary style
756         SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
757         // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
758         //int result_fontspec = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONT_SPECIFICATION);
759         int result_family = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY);
760         int result_style = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE);
761         int result_numbers = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
763         // If querying returned nothing, read the style from the text tool prefs (default style for new texts)
764         // (Ok to not get a font specification - must just rely on the family and style in that case)
765         if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING
766                 || result_numbers == QUERY_STYLE_NOTHING) {
767             sp_style_read_from_prefs(query, "/tools/text");
768         }
770         // FIXME: process result_family/style == QUERY_STYLE_MULTIPLE_DIFFERENT by showing "Many" in the lists
772         // Get a font_instance using the font-specification attribute stored in SPStyle if available
773         font_instance *font = font_factory::Default()->FaceFromStyle(query);
776         if (font) {
777             // the font is oversized, so we need to pass the true size separately
778             sp_font_selector_set_font (SP_FONT_SELECTOR (fontsel), font, query->font_size.computed);
779             sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fontsel));
780             font->Unref();
781             font=NULL;
782         }
784         GtkWidget *b;
785         if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) {
786             if (query->text_align.computed == SP_CSS_TEXT_ALIGN_JUSTIFY) {
787                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_justify" );
788             } else {
789                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_start" );
790             }
791         } else if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) {
792             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_middle" );
793         } else {
794             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_end" );
795         }
796         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
798         if (query->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) {
799             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
800         } else {
801             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
802         }
803         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
805         GtkWidget *combo = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "line_spacing" );
806         double height;
807         if (query->line_height.normal) height = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
808         else if (query->line_height.unit == SP_CSS_UNIT_PERCENT)
809             height = query->line_height.value;
810         else height = query->line_height.computed;
811         gchar *sstr = g_strdup_printf ("%d%%", (int) floor(height * 100 + 0.5));
812         gtk_entry_set_text ((GtkEntry *) ((GtkCombo *) (combo))->entry, sstr);
813         g_free(sstr);
815         sp_style_unref(query);
816     }
818     g_object_set_data (G_OBJECT (dlg), "blocked", NULL);
822 static void
823 sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg)
825     GtkWidget *textw, *preview, *apply, *def;
826     GtkTextIter start, end;
827     gchar *str;
829     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
830         return;
832     SPItem *text = sp_ted_get_selected_text_item ();
834     textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
835     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
836     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
837     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
839     gtk_text_buffer_get_bounds (tb, &start, &end);
840     str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
842     if (str && *str) {
843         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
844     } else {
845         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
846     }
847     g_free (str);
849     if (text) {
850         gtk_widget_set_sensitive (apply, TRUE);
851     }
852     gtk_widget_set_sensitive (def, TRUE);
854 } // end of sp_text_edit_dialog_text_changed()
856 void
857 sp_text_edit_dialog_default_set_insensitive ()
859     if (!dlg) return;
860     gpointer data = g_object_get_data (G_OBJECT (dlg), "default");
861     if (!data) return;
862     gtk_widget_set_sensitive (GTK_WIDGET (data), FALSE);
865 static void
866 sp_text_edit_dialog_font_changed ( SPFontSelector *fsel,
867                                    font_instance *font,
868                                    GtkWidget *dlg )
870     GtkWidget *preview, *apply, *def;
872     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
873         return;
875     SPItem *text = sp_ted_get_selected_text_item ();
877     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
878     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
879     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
881     sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fsel));
883     if (text)
884     {
885         gtk_widget_set_sensitive (apply, TRUE);
886     }
887     gtk_widget_set_sensitive (def, TRUE);
889 } // end of sp_text_edit_dialog_font_changed()
893 static void
894 sp_text_edit_dialog_any_toggled( GtkToggleButton */*tb*/, GtkWidget *dlg )
896     GtkWidget *apply, *def;
898     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
899         return;
901     SPItem *text = sp_ted_get_selected_text_item ();
903     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
904     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
906     if (text) {
907         gtk_widget_set_sensitive (apply, TRUE);
908     }
909     gtk_widget_set_sensitive (def, TRUE);
914 static void
915 sp_text_edit_dialog_line_spacing_changed( GtkEditable */*editable*/, GtkWidget *dlg )
917     GtkWidget *apply, *def;
919     if (g_object_get_data ((GObject *) dlg, "blocked"))
920         return;
922     SPItem *text = sp_ted_get_selected_text_item ();
924     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
925     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
927     if (text) {
928         gtk_widget_set_sensitive (apply, TRUE);
929     }
930     gtk_widget_set_sensitive (def, TRUE);
935 static SPItem *
936 sp_ted_get_selected_text_item (void)
938     if (!SP_ACTIVE_DESKTOP)
939         return NULL;
941     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
942          item != NULL;
943          item = item->next)
944     {
945         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
946             return SP_ITEM (item->data);
947     }
949     return NULL;
954 static unsigned
955 sp_ted_get_selected_text_count (void)
957     if (!SP_ACTIVE_DESKTOP)
958         return 0;
960     unsigned int items = 0;
962     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
963          item != NULL;
964          item = item->next)
965     {
966         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
967             ++items;
968     }
970     return items;
973 /*
974   Local Variables:
975   mode:c++
976   c-file-style:"stroustrup"
977   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
978   indent-tabs-mode:nil
979   fill-column:99
980   End:
981 */
982 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :