Code

61f56e3f7fec8ac12aa32c4d3e1434937700173a
[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>
53 using Inkscape::DocumentUndo;
55 #define VB_MARGIN 4
56 #define MIN_ONSCREEN_DISTANCE 50
58 static void sp_text_edit_dialog_selection_modified (Inkscape::Application *inkscape, Inkscape::Selection *sel, guint flags, GtkWidget *dlg);
59 static void sp_text_edit_dialog_selection_changed (Inkscape::Application *inkscape, Inkscape::Selection *sel, GtkWidget *dlg);
60 static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg);
62 static void sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg);
63 static void sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg);
64 static void sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg);
66 static void sp_text_edit_dialog_read_selection (GtkWidget *dlg, gboolean style, gboolean content);
68 static void sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg);
69 static void sp_text_edit_dialog_font_changed (SPFontSelector *fontsel, font_instance *font, GtkWidget *dlg);
70 static void sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg);
71 static void sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg);
73 static SPItem *sp_ted_get_selected_text_item (void);
74 static unsigned sp_ted_get_selected_text_count (void);
77 static const gchar *spacings[] = {"50%", "80%", "90%", "100%", "110%", "120%", "130%", "140%", "150%", "200%", "300%", NULL};
79 static GtkWidget *dlg = NULL;
80 static win_data wd;
81 // impossible original values to make sure they are read from prefs
82 static gint x = -1000, y = -1000, w = 0, h = 0;
83 static Glib::ustring const prefs_path = "/dialogs/textandfont/";
88 static void
89 sp_text_edit_dialog_destroy( GtkObject */*object*/, gpointer /*data*/ )
90 {
91     sp_signal_disconnect_by_data (INKSCAPE, dlg);
92     wd.win = dlg = NULL;
93     wd.stop = 0;
94 }
98 static gboolean
99 sp_text_edit_dialog_delete( GtkObject */*object*/, GdkEvent */*event*/, gpointer /*data*/ )
101     gtk_window_get_position ((GtkWindow *) dlg, &x, &y);
102     gtk_window_get_size ((GtkWindow *) dlg, &w, &h);
104     if (x<0) x=0;
105     if (y<0) y=0;
107     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
108     prefs->setInt(prefs_path + "x", x);
109     prefs->setInt(prefs_path + "y", y);
110     prefs->setInt(prefs_path + "w", w);
111     prefs->setInt(prefs_path + "h", h);
113     return FALSE; // which means, go ahead and destroy it
117 /**
118     These callbacks set the eatkeys flag when the text editor is entered and cancel it when it's left.
119     This flag is used to prevent passing keys from the dialog to canvas, so that the text editor
120     can handle keys like Esc and Ctrl+Z itself.
121  */
122 gboolean
123 text_view_focus_in( GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data )
125     GObject *dlg = (GObject *) data;
126     g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (TRUE));
127     return FALSE;
130 gboolean
131 text_view_focus_out (GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data)
133     GObject *dlg = (GObject *) data;
134     g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (FALSE));
135     return FALSE;
139 void
140 sp_text_edit_dialog (void)
142     if (!dlg) {
144         gchar title[500];
145         sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_TEXT), title);
146         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
148         dlg = sp_window_new (title, TRUE);
149         if (x == -1000 || y == -1000) {
150             x = prefs->getInt(prefs_path + "x", -1000);
151             y = prefs->getInt(prefs_path + "y", -1000);
152         }
153         if (w ==0 || h == 0) {
154             w = prefs->getInt(prefs_path + "w", 0);
155             h = prefs->getInt(prefs_path + "h", 0);
156         }
158 //        if (x<0) x=0;
159 //        if (y<0) y=0;
161         if (w && h)
162             gtk_window_resize ((GtkWindow *) dlg, w, h);
163         if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
164             gtk_window_move ((GtkWindow *) dlg, x, y);
165         } else {
166             gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
167         }
170         sp_transientize (dlg);
171         wd.win = dlg;
172         wd.stop = 0;
173         g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd );
175         gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg );
177         gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_text_edit_dialog_destroy), dlg );
178         gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
179         g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
181         g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg );
182         g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg );
184         gtk_window_set_policy (GTK_WINDOW (dlg), TRUE, TRUE, FALSE);
186         GtkTooltips *tt = gtk_tooltips_new();
188         // box containing the notebook and the bottom buttons
189         GtkWidget *mainvb = gtk_vbox_new (FALSE, 0);
190         gtk_container_add (GTK_CONTAINER (dlg), mainvb);
192         // notebook
193         GtkWidget *nb = gtk_notebook_new ();
194         gtk_box_pack_start (GTK_BOX (mainvb), nb, TRUE, TRUE, 0);
195         g_object_set_data (G_OBJECT (dlg), "notebook", nb);
199         // Font tab
200         {
201             GtkWidget *l = gtk_label_new (_("Font"));
202             GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
203             gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
204             gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
206             /* HBox containing font selection and layout */
207             GtkWidget *hb = gtk_hbox_new (FALSE, 0);
208             gtk_box_pack_start (GTK_BOX (vb), hb, TRUE, TRUE, 0);
210             // font and style selector
211             GtkWidget *fontsel = sp_font_selector_new ();
212             g_signal_connect ( G_OBJECT (fontsel), "font_set", G_CALLBACK (sp_text_edit_dialog_font_changed), dlg );
214             g_signal_connect_swapped ( G_OBJECT (g_object_get_data (G_OBJECT(fontsel), "family-treeview")),
215                                       "row-activated",
216                                       G_CALLBACK (gtk_window_activate_default),
217                                       dlg);
219             gtk_box_pack_start (GTK_BOX (hb), fontsel, TRUE, TRUE, 0);
220             g_object_set_data (G_OBJECT (dlg), "fontsel", fontsel);
222             // Layout
223             {
224                 GtkWidget *f = gtk_frame_new (_("Layout"));
225                 gtk_box_pack_start (GTK_BOX (hb), f, FALSE, FALSE, 4);
226                 GtkWidget *l_vb = gtk_vbox_new (FALSE, VB_MARGIN);
227                 gtk_container_add (GTK_CONTAINER (f), l_vb);
229                 {
230                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
231                     GtkWidget *group;
233                     // align left
234                     {
235                         // TODO - replace with Inkscape-specific call
236                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_LEFT, GTK_ICON_SIZE_LARGE_TOOLBAR );
237                         GtkWidget *b = group = gtk_radio_button_new (NULL);
238                         gtk_tooltips_set_tip (tt, b, _("Align lines left"), NULL);
239                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
240                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg);
241                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE );
242                         gtk_container_add (GTK_CONTAINER (b), px);
243                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
244                         g_object_set_data (G_OBJECT (dlg), "text_anchor_start", b);
245                     }
247                     // align center
248                     {
249                         // TODO - replace with Inkscape-specific call
250                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_CENTER, GTK_ICON_SIZE_LARGE_TOOLBAR );
251                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
252                         /* TRANSLATORS: `Center' here is a verb. */
253                         gtk_tooltips_set_tip (tt, b, _("Center lines"), NULL);
254                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
255                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
256                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
257                         gtk_container_add (GTK_CONTAINER (b), px);
258                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
259                         g_object_set_data (G_OBJECT (dlg), "text_anchor_middle", b);
260                     }
262                     // align right
263                     {
264                         // TODO - replace with Inkscape-specific call
265                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_RIGHT, GTK_ICON_SIZE_LARGE_TOOLBAR );
266                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
267                         gtk_tooltips_set_tip (tt, b, _("Align lines right"), NULL);
268                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
269                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
270                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
271                         gtk_container_add (GTK_CONTAINER (b), px);
272                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
273                         g_object_set_data (G_OBJECT (dlg), "text_anchor_end", b);
274                     }
276                     // align justify
277                     {
278                         // TODO - replace with Inkscape-specific call
279                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_FILL, GTK_ICON_SIZE_LARGE_TOOLBAR );
280                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
281                         gtk_tooltips_set_tip (tt, b, _("Justify lines"), NULL);
282                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
283                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
284                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
285                         gtk_container_add (GTK_CONTAINER (b), px);
286                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
287                         g_object_set_data (G_OBJECT (dlg), "text_anchor_justify", b);
288                     }
290                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
291                 }
294                 {
295                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
296                     GtkWidget *group;
298                     // horizontal
299                     {
300                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
301                                                       INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
302                         GtkWidget *b = group = gtk_radio_button_new (NULL);
303                         gtk_tooltips_set_tip (tt, b, _("Horizontal text"), NULL);
304                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
305                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
306                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
307                         gtk_container_add (GTK_CONTAINER (b), px);
308                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
309                         g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL, b);
310                     }
312                     // vertical
313                     {
314                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
315                                                       INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
316                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
317                         gtk_tooltips_set_tip (tt, b, _("Vertical text"), NULL);
318                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
319                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
320                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
321                         gtk_container_add (GTK_CONTAINER (b), px);
322                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
323                         g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL, b);
324                     }
326                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
327                 }
329                 {
330                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
332                     l = gtk_label_new (_("Line spacing:"));
333                     gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5);
334                     gtk_box_pack_start (GTK_BOX (row), l, FALSE, FALSE, VB_MARGIN);
336                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
337                 }
339                 {
340                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
342                     GtkWidget *c = gtk_combo_new ();
343                     gtk_combo_set_value_in_list ((GtkCombo *) c, FALSE, FALSE);
344                     gtk_combo_set_use_arrows ((GtkCombo *) c, TRUE);
345                     gtk_combo_set_use_arrows_always ((GtkCombo *) c, TRUE);
346                     gtk_widget_set_size_request (c, 90, -1);
348                     { /* Setup strings */
349                         GList *sl = NULL;
350                         for (int i = 0; spacings[i]; i++) {
351                             sl = g_list_prepend (sl, (void *) spacings[i]);
352                         }
353                         sl = g_list_reverse (sl);
354                         gtk_combo_set_popdown_strings ((GtkCombo *) c, sl);
355                         g_list_free (sl);
356                     }
358                     g_signal_connect ( (GObject *) ((GtkCombo *) c)->entry,
359                                        "changed",
360                                        (GCallback) sp_text_edit_dialog_line_spacing_changed,
361                                        dlg );
362                     gtk_box_pack_start (GTK_BOX (row), c, FALSE, FALSE, VB_MARGIN);
363                     g_object_set_data (G_OBJECT (dlg), "line_spacing", c);
365                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, VB_MARGIN);
366                 }
367             }
369             /* Font preview */
370             GtkWidget *preview = sp_font_preview_new ();
371             gtk_box_pack_start (GTK_BOX (vb), preview, TRUE, TRUE, 4);
372             g_object_set_data (G_OBJECT (dlg), "preview", preview);
373         }
376         // Text tab
377         {
378             GtkWidget *l = gtk_label_new (_("Text"));
379             GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
380             gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
381             gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
383             GtkWidget *scroller = gtk_scrolled_window_new ( NULL, NULL );
384             gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (scroller),
385                                              GTK_POLICY_AUTOMATIC,
386                                              GTK_POLICY_AUTOMATIC );
387             gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_IN );
388             gtk_widget_show (scroller);
390             GtkTextBuffer *tb = gtk_text_buffer_new (NULL);
391             GtkWidget *txt = gtk_text_view_new_with_buffer (tb);
392             gtk_text_view_set_wrap_mode ((GtkTextView *) txt, GTK_WRAP_WORD);
393 #ifdef WITH_GTKSPELL
394             GError *error = NULL;
395             char *errortext = NULL;
396             /* todo: Use computed xml:lang attribute of relevant element, if present, to specify the
397                language (either as 2nd arg of gtkspell_new_attach, or with explicit
398                gtkspell_set_language call in; see advanced.c example in gtkspell docs).
399                sp_text_edit_dialog_read_selection looks like a suitable place. */
400             if (gtkspell_new_attach(GTK_TEXT_VIEW(txt), NULL, &error) == NULL) {
401                 g_print("gtkspell error: %s\n", error->message);
402                 errortext = g_strdup_printf("GtkSpell was unable to initialize.\n"
403                                             "%s", error->message);
404                 g_error_free(error);
405             }
406 #endif
407             gtk_widget_set_size_request (txt, -1, 64);
408             gtk_text_view_set_editable (GTK_TEXT_VIEW (txt), TRUE);
409             gtk_container_add (GTK_CONTAINER (scroller), txt);
410             gtk_box_pack_start (GTK_BOX (vb), scroller, TRUE, TRUE, 0);
411             g_signal_connect ( G_OBJECT (tb), "changed",
412                                G_CALLBACK (sp_text_edit_dialog_text_changed), dlg );
413             g_signal_connect (G_OBJECT (txt), "focus-in-event", G_CALLBACK (text_view_focus_in), dlg);
414             g_signal_connect (G_OBJECT (txt), "focus-out-event", G_CALLBACK (text_view_focus_out), dlg);
415             g_object_set_data (G_OBJECT (dlg), "text", tb);
416             g_object_set_data (G_OBJECT (dlg), "textw", txt);
417         }
419         /* Buttons */
420         GtkWidget *hb = gtk_hbox_new (FALSE, VB_MARGIN);
421         gtk_container_set_border_width (GTK_CONTAINER (hb), 4);
422         gtk_box_pack_start (GTK_BOX (mainvb), hb, FALSE, FALSE, 0);
424         {
425             GtkWidget *b = gtk_button_new_with_label (_("Set as default"));
426             g_signal_connect ( G_OBJECT (b), "clicked",
427                                G_CALLBACK (sp_text_edit_dialog_set_default),
428                                dlg );
429             gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0);
430             g_object_set_data (G_OBJECT (dlg), "default", b);
431         }
433         {
434             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
435             g_signal_connect ( G_OBJECT (b), "clicked",
436                                G_CALLBACK (sp_text_edit_dialog_close), dlg );
437             gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0);
438         }
440         {
441             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_APPLY);
442             GTK_WIDGET_SET_FLAGS (b, GTK_CAN_DEFAULT | GTK_HAS_DEFAULT);
443             g_signal_connect ( G_OBJECT (b), "clicked",
444                                G_CALLBACK (sp_text_edit_dialog_apply), dlg );
445             gtk_box_pack_end ( GTK_BOX (hb), b, FALSE, FALSE, 0 );
446             g_object_set_data (G_OBJECT (dlg), "apply", b);
447         }
449         g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection",
450                            G_CALLBACK (sp_text_edit_dialog_selection_modified), dlg);
451         g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection",
452                            G_CALLBACK (sp_text_edit_dialog_selection_changed), dlg);
453         g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_text_edit_dialog_subselection_changed), dlg);
455         gtk_widget_show_all (dlg);
457         sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
458     }
460     gtk_window_present ((GtkWindow *) dlg);
462 } // end of sp_text_edit_dialog()
466 static void
467 sp_text_edit_dialog_selection_modified( Inkscape::Application */*inkscape*/,
468                                         Inkscape::Selection */*sel*/,
469                                         guint flags,
470                                         GtkWidget *dlg )
472     gboolean style, content;
474     style =
475         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
476                     SP_OBJECT_STYLE_MODIFIED_FLAG  )) != 0 );
478     content =
479         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
480                     SP_TEXT_CONTENT_MODIFIED_FLAG  )) != 0 );
482     sp_text_edit_dialog_read_selection (dlg, style, content);
488 static void
489 sp_text_edit_dialog_selection_changed( Inkscape::Application */*inkscape*/,
490                                        Inkscape::Selection */*sel*/,
491                                        GtkWidget *dlg )
493     sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
496 static void sp_text_edit_dialog_subselection_changed( Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, GtkWidget *dlg )
498     sp_text_edit_dialog_read_selection (dlg, TRUE, FALSE);
501 static void
502 sp_text_edit_dialog_update_object_text ( SPItem *text )
504         GtkTextBuffer *tb;
505         GtkTextIter start, end;
506         gchar *str;
508         tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
510         /* write text */
511         if (gtk_text_buffer_get_modified (tb)) {
512             gtk_text_buffer_get_bounds (tb, &start, &end);
513             str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
514             sp_te_set_repr_text_multiline (text, str);
515             g_free (str);
516             gtk_text_buffer_set_modified (tb, FALSE);
517         }
520 SPCSSAttr *
521 sp_get_text_dialog_style ()
523         GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
525         SPCSSAttr *css = sp_repr_css_attr_new ();
527         /* font */
528         font_instance *font = sp_font_selector_get_font (SP_FONT_SELECTOR (fontsel));
530         if ( font ) {
531             Glib::ustring fontName = font_factory::Default()->ConstructFontSpecification(font);
532             sp_repr_css_set_property (css, "-inkscape-font-specification", fontName.c_str());
534             gchar c[256];
536             font->Family(c, 256);
537             sp_repr_css_set_property (css, "font-family", c);
539             font->Attribute( "weight", c, 256);
540             sp_repr_css_set_property (css, "font-weight", c);
542             font->Attribute("style", c, 256);
543             sp_repr_css_set_property (css, "font-style", c);
545             font->Attribute("stretch", c, 256);
546             sp_repr_css_set_property (css, "font-stretch", c);
548             font->Attribute("variant", c, 256);
549             sp_repr_css_set_property (css, "font-variant", c);
551             Inkscape::CSSOStringStream os;
552             os << sp_font_selector_get_size (SP_FONT_SELECTOR (fontsel)) << "px"; // must specify px, see inkscape bug 1221626 and 1610103
553             sp_repr_css_set_property (css, "font-size", os.str().c_str());
555             font->Unref();
556             font=NULL;
557         }
559         /* Layout */
560         GtkWidget *b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "text_anchor_start");
562         // Align Left
563         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
564             sp_repr_css_set_property (css, "text-anchor", "start");
565             sp_repr_css_set_property (css, "text-align", "start");
566         } else {
567             // Align Center
568             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
569                                                 "text_anchor_middle");
570             if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
571                 sp_repr_css_set_property (css, "text-anchor", "middle");
572                 sp_repr_css_set_property (css, "text-align", "center");
573             } else {
574                 // Align Right
575                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
576                                                     "text_anchor_end");
577                 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
578                     sp_repr_css_set_property (css, "text-anchor", "end");
579                     sp_repr_css_set_property (css, "text-align", "end");
580                 } else {
581                     // Align Justify
582                     sp_repr_css_set_property (css, "text-anchor", "start");
583                     sp_repr_css_set_property (css, "text-align", "justify");
584                 }
585             }
586         }
588         b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
590         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
591             sp_repr_css_set_property (css, "writing-mode", "lr");
592         } else {
593             sp_repr_css_set_property (css, "writing-mode", "tb");
594         }
596         // Note that CSS 1.1 does not support line-height; we set it for consistency, but also set
597         // sodipodi:linespacing for backwards compatibility; in 1.2 we use line-height for flowtext
598         GtkWidget *combo = (GtkWidget*)g_object_get_data ((GObject *) dlg, "line_spacing");
599         const char *sstr = gtk_entry_get_text ((GtkEntry *) ((GtkCombo *) (combo))->entry);
600         sp_repr_css_set_property (css, "line-height", sstr);
602         return css;
606 static void
607 sp_text_edit_dialog_set_default( GtkButton */*button*/, GtkWidget *dlg )
609     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
611     SPCSSAttr *css = sp_get_text_dialog_style ();
612     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
614     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
615     prefs->mergeStyle("/tools/text/style", css);
616     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
618     sp_repr_css_attr_unref (css);
620     gtk_widget_set_sensitive (def, FALSE);
625 static void
626 sp_text_edit_dialog_apply( GtkButton */*button*/, GtkWidget *dlg )
628     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
630     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
631     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
632     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
634     unsigned items = 0;
635     const GSList *item_list = sp_desktop_selection(desktop)->itemList();
636     SPCSSAttr *css = sp_get_text_dialog_style ();
637     sp_desktop_set_style(desktop, css, true);
639     for (; item_list != NULL; item_list = item_list->next) {
640         // apply style to the reprs of all text objects in the selection
641         if (SP_IS_TEXT (item_list->data)) {
643             // backwards compatibility:
644             reinterpret_cast<SPObject*>(item_list->data)->getRepr()->setAttribute("sodipodi:linespacing", sp_repr_css_property (css, "line-height", NULL));
646             ++items;
647         }
648         else if (SP_IS_FLOWTEXT (item_list->data))
649             // no need to set sodipodi:linespacing, because Inkscape never supported it on flowtext
650             ++items;
651     }
653     if (items == 0) {
654         // no text objects; apply style to prefs for new objects
655         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
656         prefs->mergeStyle("/tools/text/style", css);
657         gtk_widget_set_sensitive (def, FALSE);
658     } else if (items == 1) {
659         /* exactly one text object; now set its text, too */
660         SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
661         if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) {
662             sp_text_edit_dialog_update_object_text (item);
663         }
664     }
666     // complete the transaction
667     DocumentUndo::done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_CONTEXT_TEXT,
668                        _("Set text style"));
669     gtk_widget_set_sensitive (apply, FALSE);
670     sp_repr_css_attr_unref (css);
671     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
674 static void
675 sp_text_edit_dialog_close( GtkButton */*button*/, GtkWidget *dlg )
677     gtk_widget_destroy (GTK_WIDGET (dlg));
680 static void
681 sp_text_edit_dialog_read_selection ( GtkWidget *dlg,
682                                      gboolean dostyle,
683                                      gboolean docontent )
685     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
686         return;
688     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
690     GtkWidget *notebook = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "notebook");
691     GtkWidget *textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
692     GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
693     GtkWidget *preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
694     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
695     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
697     GtkTextBuffer *tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
699     SPItem *text = sp_ted_get_selected_text_item ();
701     Inkscape::XML::Node *repr;
702     if (text)
703     {
704         guint items = sp_ted_get_selected_text_count ();
705         if (items == 1) {
706             gtk_widget_set_sensitive (textw, TRUE);
707         } else {
708             gtk_widget_set_sensitive (textw, FALSE);
709         }
710         gtk_widget_set_sensitive (apply, FALSE);
711         gtk_widget_set_sensitive (def, TRUE);
713         if (docontent) {
714             gchar *str;
715             str = sp_te_get_string_multiline (text);
717             if (str) {
718                 int pos;
719                 pos = 0;
721                 if (items == 1) {
722                     gtk_text_buffer_set_text (tb, str, strlen (str));
723                     gtk_text_buffer_set_modified (tb, FALSE);
724                 }
725                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
726                 g_free (str);
728             } else {
729                 gtk_text_buffer_set_text (tb, "", 0);
730                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
731             }
732         } // end of if (docontent)
733         repr = text->getRepr();
735     } else {
736         gtk_widget_set_sensitive (textw, FALSE);
737         gtk_widget_set_sensitive (apply, FALSE);
738         gtk_widget_set_sensitive (def, FALSE);
739     }
741     if (dostyle) {
743         // create temporary style
744         SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
745         // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
746         //int result_fontspec = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONT_SPECIFICATION);
747         int result_family = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY);
748         int result_style = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE);
749         int result_numbers = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
751         // If querying returned nothing, read the style from the text tool prefs (default style for new texts)
752         // (Ok to not get a font specification - must just rely on the family and style in that case)
753         if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING
754                 || result_numbers == QUERY_STYLE_NOTHING) {
755             sp_style_read_from_prefs(query, "/tools/text");
756         }
758         // FIXME: process result_family/style == QUERY_STYLE_MULTIPLE_DIFFERENT by showing "Many" in the lists
760         // Get a font_instance using the font-specification attribute stored in SPStyle if available
761         font_instance *font = font_factory::Default()->FaceFromStyle(query);
764         if (font) {
765             // the font is oversized, so we need to pass the true size separately
766             sp_font_selector_set_font (SP_FONT_SELECTOR (fontsel), font, query->font_size.computed);
767             sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fontsel));
768             font->Unref();
769             font=NULL;
770         }
772         GtkWidget *b;
773         if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) {
774             if (query->text_align.computed == SP_CSS_TEXT_ALIGN_JUSTIFY) {
775                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_justify" );
776             } else {
777                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_start" );
778             }
779         } else if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) {
780             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_middle" );
781         } else {
782             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_end" );
783         }
784         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
786         if (query->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) {
787             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
788         } else {
789             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
790         }
791         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
793         GtkWidget *combo = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "line_spacing" );
794         double height;
795         if (query->line_height.normal) height = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
796         else if (query->line_height.unit == SP_CSS_UNIT_PERCENT)
797             height = query->line_height.value;
798         else height = query->line_height.computed;
799         gchar *sstr = g_strdup_printf ("%d%%", (int) floor(height * 100 + 0.5));
800         gtk_entry_set_text ((GtkEntry *) ((GtkCombo *) (combo))->entry, sstr);
801         g_free(sstr);
803         sp_style_unref(query);
804     }
806     g_object_set_data (G_OBJECT (dlg), "blocked", NULL);
810 static void
811 sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg)
813     GtkWidget *textw, *preview, *apply, *def;
814     GtkTextIter start, end;
815     gchar *str;
817     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
818         return;
820     SPItem *text = sp_ted_get_selected_text_item ();
822     textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
823     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
824     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
825     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
827     gtk_text_buffer_get_bounds (tb, &start, &end);
828     str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
830     if (str && *str) {
831         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
832     } else {
833         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
834     }
835     g_free (str);
837     if (text) {
838         gtk_widget_set_sensitive (apply, TRUE);
839     }
840     gtk_widget_set_sensitive (def, TRUE);
842 } // end of sp_text_edit_dialog_text_changed()
844 void
845 sp_text_edit_dialog_default_set_insensitive ()
847     if (!dlg) return;
848     gpointer data = g_object_get_data (G_OBJECT (dlg), "default");
849     if (!data) return;
850     gtk_widget_set_sensitive (GTK_WIDGET (data), FALSE);
853 static void
854 sp_text_edit_dialog_font_changed ( SPFontSelector *fsel,
855                                    font_instance *font,
856                                    GtkWidget *dlg )
858     GtkWidget *preview, *apply, *def;
860     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
861         return;
863     SPItem *text = sp_ted_get_selected_text_item ();
865     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
866     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
867     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
869     sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fsel));
871     if (text)
872     {
873         gtk_widget_set_sensitive (apply, TRUE);
874     }
875     gtk_widget_set_sensitive (def, TRUE);
877 } // end of sp_text_edit_dialog_font_changed()
881 static void
882 sp_text_edit_dialog_any_toggled( GtkToggleButton */*tb*/, GtkWidget *dlg )
884     GtkWidget *apply, *def;
886     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
887         return;
889     SPItem *text = sp_ted_get_selected_text_item ();
891     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
892     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
894     if (text) {
895         gtk_widget_set_sensitive (apply, TRUE);
896     }
897     gtk_widget_set_sensitive (def, TRUE);
902 static void
903 sp_text_edit_dialog_line_spacing_changed( GtkEditable */*editable*/, GtkWidget *dlg )
905     GtkWidget *apply, *def;
907     if (g_object_get_data ((GObject *) dlg, "blocked"))
908         return;
910     SPItem *text = sp_ted_get_selected_text_item ();
912     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
913     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
915     if (text) {
916         gtk_widget_set_sensitive (apply, TRUE);
917     }
918     gtk_widget_set_sensitive (def, TRUE);
923 static SPItem *
924 sp_ted_get_selected_text_item (void)
926     if (!SP_ACTIVE_DESKTOP)
927         return NULL;
929     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
930          item != NULL;
931          item = item->next)
932     {
933         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
934             return SP_ITEM (item->data);
935     }
937     return NULL;
942 static unsigned
943 sp_ted_get_selected_text_count (void)
945     if (!SP_ACTIVE_DESKTOP)
946         return 0;
948     unsigned int items = 0;
950     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
951          item != NULL;
952          item = item->next)
953     {
954         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
955             ++items;
956     }
958     return items;
961 /*
962   Local Variables:
963   mode:c++
964   c-file-style:"stroustrup"
965   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
966   indent-tabs-mode:nil
967   fill-column:99
968   End:
969 */
970 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :