Code

A simple layout document as to what, why and how is cppification.
[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  *
9  * Copyright (C) 1999-2007 Authors
10  * Copyright (C) 2000-2001 Ximian, Inc.
11  *
12  * Released under GNU GPL, read the file 'COPYING' for more information
13  */
15 #ifdef HAVE_CONFIG_H
16 # include "config.h"
17 #endif
19 #include <libnrtype/font-instance.h>
20 #include <gtk/gtk.h>
22 #ifdef WITH_GTKSPELL
23 extern "C" {
24 # include <gtkspell/gtkspell.h>
25 }
26 #endif
28 #include "macros.h"
29 #include <glibmm/i18n.h>
30 #include "helper/window.h"
31 #include "../widgets/font-selector.h"
32 #include "../inkscape.h"
33 #include "../document.h"
34 #include "../desktop-style.h"
35 #include "../desktop-handles.h"
36 #include "../selection.h"
37 #include "../style.h"
38 #include "../sp-text.h"
39 #include "../sp-flowtext.h"
40 #include "../text-editing.h"
41 #include "../ui/icon-names.h"
42 #include <libnrtype/font-style-to-pos.h>
44 #include "dialog-events.h"
45 #include "../preferences.h"
46 #include "../verbs.h"
47 #include "../interface.h"
48 #include "svg/css-ostringstream.h"
49 #include "widgets/icon.h"
50 #include <xml/repr.h>
52 #define VB_MARGIN 4
53 #define MIN_ONSCREEN_DISTANCE 50
55 static void sp_text_edit_dialog_selection_modified (Inkscape::Application *inkscape, Inkscape::Selection *sel, guint flags, GtkWidget *dlg);
56 static void sp_text_edit_dialog_selection_changed (Inkscape::Application *inkscape, Inkscape::Selection *sel, GtkWidget *dlg);
57 static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg);
59 static void sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg);
60 static void sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg);
61 static void sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg);
63 static void sp_text_edit_dialog_read_selection (GtkWidget *dlg, gboolean style, gboolean content);
65 static void sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg);
66 static void sp_text_edit_dialog_font_changed (SPFontSelector *fontsel, font_instance *font, GtkWidget *dlg);
67 static void sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg);
68 static void sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg);
70 static SPItem *sp_ted_get_selected_text_item (void);
71 static unsigned sp_ted_get_selected_text_count (void);
74 static const gchar *spacings[] = {"50%", "80%", "90%", "100%", "110%", "120%", "130%", "140%", "150%", "200%", "300%", NULL};
76 static GtkWidget *dlg = NULL;
77 static win_data wd;
78 // impossible original values to make sure they are read from prefs
79 static gint x = -1000, y = -1000, w = 0, h = 0;
80 static Glib::ustring const prefs_path = "/dialogs/textandfont/";
85 static void
86 sp_text_edit_dialog_destroy( GtkObject */*object*/, gpointer /*data*/ )
87 {
88     sp_signal_disconnect_by_data (INKSCAPE, dlg);
89     wd.win = dlg = NULL;
90     wd.stop = 0;
91 }
95 static gboolean
96 sp_text_edit_dialog_delete( GtkObject */*object*/, GdkEvent */*event*/, gpointer /*data*/ )
97 {
98     gtk_window_get_position ((GtkWindow *) dlg, &x, &y);
99     gtk_window_get_size ((GtkWindow *) dlg, &w, &h);
101     if (x<0) x=0;
102     if (y<0) y=0;
104     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
105     prefs->setInt(prefs_path + "x", x);
106     prefs->setInt(prefs_path + "y", y);
107     prefs->setInt(prefs_path + "w", w);
108     prefs->setInt(prefs_path + "h", h);
110     return FALSE; // which means, go ahead and destroy it
114 /**
115     These callbacks set the eatkeys flag when the text editor is entered and cancel it when it's left.
116     This flag is used to prevent passing keys from the dialog to canvas, so that the text editor
117     can handle keys like Esc and Ctrl+Z itself.
118  */
119 gboolean
120 text_view_focus_in( GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data )
122     GObject *dlg = (GObject *) data;
123     g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (TRUE));
124     return FALSE;
127 gboolean
128 text_view_focus_out (GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data)
130     GObject *dlg = (GObject *) data;
131     g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (FALSE));
132     return FALSE;
136 void
137 sp_text_edit_dialog (void)
139     if (!dlg) {
141         gchar title[500];
142         sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_TEXT), title);
143         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
145         dlg = sp_window_new (title, TRUE);
146         if (x == -1000 || y == -1000) {
147             x = prefs->getInt(prefs_path + "x", -1000);
148             y = prefs->getInt(prefs_path + "y", -1000);
149         }
150         if (w ==0 || h == 0) {
151             w = prefs->getInt(prefs_path + "w", 0);
152             h = prefs->getInt(prefs_path + "h", 0);
153         }
155 //        if (x<0) x=0;
156 //        if (y<0) y=0;
158         if (w && h)
159             gtk_window_resize ((GtkWindow *) dlg, w, h);
160         if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
161             gtk_window_move ((GtkWindow *) dlg, x, y);
162         } else {
163             gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
164         }
167         sp_transientize (dlg);
168         wd.win = dlg;
169         wd.stop = 0;
170         g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd );
172         gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg );
174         gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_text_edit_dialog_destroy), dlg );
175         gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
176         g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
178         g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg );
179         g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg );
181         gtk_window_set_policy (GTK_WINDOW (dlg), TRUE, TRUE, FALSE);
183         GtkTooltips *tt = gtk_tooltips_new();
185         // box containing the notebook and the bottom buttons
186         GtkWidget *mainvb = gtk_vbox_new (FALSE, 0);
187         gtk_container_add (GTK_CONTAINER (dlg), mainvb);
189         // notebook
190         GtkWidget *nb = gtk_notebook_new ();
191         gtk_box_pack_start (GTK_BOX (mainvb), nb, TRUE, TRUE, 0);
192         g_object_set_data (G_OBJECT (dlg), "notebook", nb);
196         // Font tab
197         {
198             GtkWidget *l = gtk_label_new (_("Font"));
199             GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
200             gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
201             gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
203             /* HBox containing font selection and layout */
204             GtkWidget *hb = gtk_hbox_new (FALSE, 0);
205             gtk_box_pack_start (GTK_BOX (vb), hb, TRUE, TRUE, 0);
207             // font and style selector
208             GtkWidget *fontsel = sp_font_selector_new ();
209             g_signal_connect ( G_OBJECT (fontsel), "font_set", G_CALLBACK (sp_text_edit_dialog_font_changed), dlg );
211             g_signal_connect_swapped ( G_OBJECT (g_object_get_data (G_OBJECT(fontsel), "family-treeview")),
212                                       "row-activated",
213                                       G_CALLBACK (gtk_window_activate_default),
214                                       dlg);
216             gtk_box_pack_start (GTK_BOX (hb), fontsel, TRUE, TRUE, 0);
217             g_object_set_data (G_OBJECT (dlg), "fontsel", fontsel);
219             // Layout
220             {
221                 GtkWidget *f = gtk_frame_new (_("Layout"));
222                 gtk_box_pack_start (GTK_BOX (hb), f, FALSE, FALSE, 4);
223                 GtkWidget *l_vb = gtk_vbox_new (FALSE, VB_MARGIN);
224                 gtk_container_add (GTK_CONTAINER (f), l_vb);
226                 {
227                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
228                     GtkWidget *group;
230                     // align left
231                     {
232                         // TODO - replace with Inkscape-specific call
233                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_LEFT, GTK_ICON_SIZE_LARGE_TOOLBAR );
234                         GtkWidget *b = group = gtk_radio_button_new (NULL);
235                         gtk_tooltips_set_tip (tt, b, _("Align lines left"), NULL);
236                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
237                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg);
238                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE );
239                         gtk_container_add (GTK_CONTAINER (b), px);
240                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
241                         g_object_set_data (G_OBJECT (dlg), "text_anchor_start", b);
242                     }
244                     // align center
245                     {
246                         // TODO - replace with Inkscape-specific call
247                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_CENTER, GTK_ICON_SIZE_LARGE_TOOLBAR );
248                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
249                         /* TRANSLATORS: `Center' here is a verb. */
250                         gtk_tooltips_set_tip (tt, b, _("Center lines"), NULL);
251                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
252                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
253                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
254                         gtk_container_add (GTK_CONTAINER (b), px);
255                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
256                         g_object_set_data (G_OBJECT (dlg), "text_anchor_middle", b);
257                     }
259                     // align right
260                     {
261                         // TODO - replace with Inkscape-specific call
262                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_RIGHT, GTK_ICON_SIZE_LARGE_TOOLBAR );
263                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
264                         gtk_tooltips_set_tip (tt, b, _("Align lines right"), NULL);
265                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
266                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
267                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
268                         gtk_container_add (GTK_CONTAINER (b), px);
269                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
270                         g_object_set_data (G_OBJECT (dlg), "text_anchor_end", b);
271                     }
273                     // align justify
274                     {
275                         // TODO - replace with Inkscape-specific call
276                         GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_FILL, GTK_ICON_SIZE_LARGE_TOOLBAR );
277                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
278                         gtk_tooltips_set_tip (tt, b, _("Justify lines"), NULL);
279                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
280                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
281                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
282                         gtk_container_add (GTK_CONTAINER (b), px);
283                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
284                         g_object_set_data (G_OBJECT (dlg), "text_anchor_justify", b);
285                     }
287                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
288                 }
291                 {
292                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
293                     GtkWidget *group;
295                     // horizontal
296                     {
297                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
298                                                       INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
299                         GtkWidget *b = group = gtk_radio_button_new (NULL);
300                         gtk_tooltips_set_tip (tt, b, _("Horizontal text"), NULL);
301                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
302                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
303                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
304                         gtk_container_add (GTK_CONTAINER (b), px);
305                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
306                         g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL, b);
307                     }
309                     // vertical
310                     {
311                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
312                                                       INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
313                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
314                         gtk_tooltips_set_tip (tt, b, _("Vertical text"), NULL);
315                         gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
316                         g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
317                         gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
318                         gtk_container_add (GTK_CONTAINER (b), px);
319                         gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
320                         g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL, b);
321                     }
323                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
324                 }
326                 {
327                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
329                     l = gtk_label_new (_("Line spacing:"));
330                     gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5);
331                     gtk_box_pack_start (GTK_BOX (row), l, FALSE, FALSE, VB_MARGIN);
333                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
334                 }
336                 {
337                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
339                     GtkWidget *c = gtk_combo_new ();
340                     gtk_combo_set_value_in_list ((GtkCombo *) c, FALSE, FALSE);
341                     gtk_combo_set_use_arrows ((GtkCombo *) c, TRUE);
342                     gtk_combo_set_use_arrows_always ((GtkCombo *) c, TRUE);
343                     gtk_widget_set_size_request (c, 90, -1);
345                     { /* Setup strings */
346                         GList *sl = NULL;
347                         for (int i = 0; spacings[i]; i++) {
348                             sl = g_list_prepend (sl, (void *) spacings[i]);
349                         }
350                         sl = g_list_reverse (sl);
351                         gtk_combo_set_popdown_strings ((GtkCombo *) c, sl);
352                         g_list_free (sl);
353                     }
355                     g_signal_connect ( (GObject *) ((GtkCombo *) c)->entry,
356                                        "changed",
357                                        (GCallback) sp_text_edit_dialog_line_spacing_changed,
358                                        dlg );
359                     gtk_box_pack_start (GTK_BOX (row), c, FALSE, FALSE, VB_MARGIN);
360                     g_object_set_data (G_OBJECT (dlg), "line_spacing", c);
362                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, VB_MARGIN);
363                 }
364             }
366             /* Font preview */
367             GtkWidget *preview = sp_font_preview_new ();
368             gtk_box_pack_start (GTK_BOX (vb), preview, TRUE, TRUE, 4);
369             g_object_set_data (G_OBJECT (dlg), "preview", preview);
370         }
373         // Text tab
374         {
375             GtkWidget *l = gtk_label_new (_("Text"));
376             GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
377             gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
378             gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
380             GtkWidget *scroller = gtk_scrolled_window_new ( NULL, NULL );
381             gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (scroller),
382                                              GTK_POLICY_AUTOMATIC,
383                                              GTK_POLICY_AUTOMATIC );
384             gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_IN );
385             gtk_widget_show (scroller);
387             GtkTextBuffer *tb = gtk_text_buffer_new (NULL);
388             GtkWidget *txt = gtk_text_view_new_with_buffer (tb);
389             gtk_text_view_set_wrap_mode ((GtkTextView *) txt, GTK_WRAP_WORD);
390 #ifdef WITH_GTKSPELL
391             GError *error = NULL;
392             char *errortext = NULL;
393             /* todo: Use computed xml:lang attribute of relevant element, if present, to specify the
394                language (either as 2nd arg of gtkspell_new_attach, or with explicit
395                gtkspell_set_language call in; see advanced.c example in gtkspell docs).
396                sp_text_edit_dialog_read_selection looks like a suitable place. */
397             if (gtkspell_new_attach(GTK_TEXT_VIEW(txt), NULL, &error) == NULL) {
398                 g_print("gtkspell error: %s\n", error->message);
399                 errortext = g_strdup_printf("GtkSpell was unable to initialize.\n"
400                                             "%s", error->message);
401                 g_error_free(error);
402             }
403 #endif
404             gtk_widget_set_size_request (txt, -1, 64);
405             gtk_text_view_set_editable (GTK_TEXT_VIEW (txt), TRUE);
406             gtk_container_add (GTK_CONTAINER (scroller), txt);
407             gtk_box_pack_start (GTK_BOX (vb), scroller, TRUE, TRUE, 0);
408             g_signal_connect ( G_OBJECT (tb), "changed",
409                                G_CALLBACK (sp_text_edit_dialog_text_changed), dlg );
410             g_signal_connect (G_OBJECT (txt), "focus-in-event", G_CALLBACK (text_view_focus_in), dlg);
411             g_signal_connect (G_OBJECT (txt), "focus-out-event", G_CALLBACK (text_view_focus_out), dlg);
412             g_object_set_data (G_OBJECT (dlg), "text", tb);
413             g_object_set_data (G_OBJECT (dlg), "textw", txt);
414         }
416         /* Buttons */
417         GtkWidget *hb = gtk_hbox_new (FALSE, VB_MARGIN);
418         gtk_container_set_border_width (GTK_CONTAINER (hb), 4);
419         gtk_box_pack_start (GTK_BOX (mainvb), hb, FALSE, FALSE, 0);
421         {
422             GtkWidget *b = gtk_button_new_with_label (_("Set as default"));
423             g_signal_connect ( G_OBJECT (b), "clicked",
424                                G_CALLBACK (sp_text_edit_dialog_set_default),
425                                dlg );
426             gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0);
427             g_object_set_data (G_OBJECT (dlg), "default", b);
428         }
430         {
431             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
432             g_signal_connect ( G_OBJECT (b), "clicked",
433                                G_CALLBACK (sp_text_edit_dialog_close), dlg );
434             gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0);
435         }
437         {
438             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_APPLY);
439             GTK_WIDGET_SET_FLAGS (b, GTK_CAN_DEFAULT | GTK_HAS_DEFAULT);
440             g_signal_connect ( G_OBJECT (b), "clicked",
441                                G_CALLBACK (sp_text_edit_dialog_apply), dlg );
442             gtk_box_pack_end ( GTK_BOX (hb), b, FALSE, FALSE, 0 );
443             g_object_set_data (G_OBJECT (dlg), "apply", b);
444         }
446         g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection",
447                            G_CALLBACK (sp_text_edit_dialog_selection_modified), dlg);
448         g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection",
449                            G_CALLBACK (sp_text_edit_dialog_selection_changed), dlg);
450         g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_text_edit_dialog_subselection_changed), dlg);
452         gtk_widget_show_all (dlg);
454         sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
455     }
457     gtk_window_present ((GtkWindow *) dlg);
459 } // end of sp_text_edit_dialog()
463 static void
464 sp_text_edit_dialog_selection_modified( Inkscape::Application */*inkscape*/,
465                                         Inkscape::Selection */*sel*/,
466                                         guint flags,
467                                         GtkWidget *dlg )
469     gboolean style, content;
471     style =
472         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
473                     SP_OBJECT_STYLE_MODIFIED_FLAG  )) != 0 );
475     content =
476         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
477                     SP_TEXT_CONTENT_MODIFIED_FLAG  )) != 0 );
479     sp_text_edit_dialog_read_selection (dlg, style, content);
485 static void
486 sp_text_edit_dialog_selection_changed( Inkscape::Application */*inkscape*/,
487                                        Inkscape::Selection */*sel*/,
488                                        GtkWidget *dlg )
490     sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
493 static void sp_text_edit_dialog_subselection_changed( Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, GtkWidget *dlg )
495     sp_text_edit_dialog_read_selection (dlg, TRUE, FALSE);
498 static void
499 sp_text_edit_dialog_update_object_text ( SPItem *text )
501         GtkTextBuffer *tb;
502         GtkTextIter start, end;
503         gchar *str;
505         tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
507         /* write text */
508         if (gtk_text_buffer_get_modified (tb)) {
509             gtk_text_buffer_get_bounds (tb, &start, &end);
510             str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
511             sp_te_set_repr_text_multiline (text, str);
512             g_free (str);
513             gtk_text_buffer_set_modified (tb, FALSE);
514         }
517 SPCSSAttr *
518 sp_get_text_dialog_style ()
520         GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
522         SPCSSAttr *css = sp_repr_css_attr_new ();
524         /* font */
525         font_instance *font = sp_font_selector_get_font (SP_FONT_SELECTOR (fontsel));
527         if ( font ) {
528             Glib::ustring fontName = font_factory::Default()->ConstructFontSpecification(font);
529             sp_repr_css_set_property (css, "-inkscape-font-specification", fontName.c_str());
531             gchar c[256];
533             font->Family(c, 256);
534             sp_repr_css_set_property (css, "font-family", c);
536             font->Attribute( "weight", c, 256);
537             sp_repr_css_set_property (css, "font-weight", c);
539             font->Attribute("style", c, 256);
540             sp_repr_css_set_property (css, "font-style", c);
542             font->Attribute("stretch", c, 256);
543             sp_repr_css_set_property (css, "font-stretch", c);
545             font->Attribute("variant", c, 256);
546             sp_repr_css_set_property (css, "font-variant", c);
548             Inkscape::CSSOStringStream os;
549             os << sp_font_selector_get_size (SP_FONT_SELECTOR (fontsel)) << "px"; // must specify px, see inkscape bug 1221626 and 1610103
550             sp_repr_css_set_property (css, "font-size", os.str().c_str());
552             font->Unref();
553             font=NULL;
554         }
556         /* Layout */
557         GtkWidget *b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "text_anchor_start");
559         // Align Left
560         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
561             sp_repr_css_set_property (css, "text-anchor", "start");
562             sp_repr_css_set_property (css, "text-align", "start");
563         } else {
564             // Align Center
565             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
566                                                 "text_anchor_middle");
567             if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
568                 sp_repr_css_set_property (css, "text-anchor", "middle");
569                 sp_repr_css_set_property (css, "text-align", "center");
570             } else {
571                 // Align Right
572                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
573                                                     "text_anchor_end");
574                 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
575                     sp_repr_css_set_property (css, "text-anchor", "end");
576                     sp_repr_css_set_property (css, "text-align", "end");
577                 } else {
578                     // Align Justify
579                     sp_repr_css_set_property (css, "text-anchor", "start");
580                     sp_repr_css_set_property (css, "text-align", "justify");
581                 }
582             }
583         }
585         b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
587         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
588             sp_repr_css_set_property (css, "writing-mode", "lr");
589         } else {
590             sp_repr_css_set_property (css, "writing-mode", "tb");
591         }
593         // Note that CSS 1.1 does not support line-height; we set it for consistency, but also set
594         // sodipodi:linespacing for backwards compatibility; in 1.2 we use line-height for flowtext
595         GtkWidget *combo = (GtkWidget*)g_object_get_data ((GObject *) dlg, "line_spacing");
596         const char *sstr = gtk_entry_get_text ((GtkEntry *) ((GtkCombo *) (combo))->entry);
597         sp_repr_css_set_property (css, "line-height", sstr);
599         return css;
603 static void
604 sp_text_edit_dialog_set_default( GtkButton */*button*/, GtkWidget *dlg )
606     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
608     SPCSSAttr *css = sp_get_text_dialog_style ();
609     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
611     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
612     prefs->mergeStyle("/tools/text/style", css);
613     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
615     sp_repr_css_attr_unref (css);
617     gtk_widget_set_sensitive (def, FALSE);
622 static void
623 sp_text_edit_dialog_apply( GtkButton */*button*/, GtkWidget *dlg )
625     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
627     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
628     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
629     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
631     unsigned items = 0;
632     const GSList *item_list = sp_desktop_selection(desktop)->itemList();
633     SPCSSAttr *css = sp_get_text_dialog_style ();
634     sp_desktop_set_style(desktop, css, true);
636     for (; item_list != NULL; item_list = item_list->next) {
637         // apply style to the reprs of all text objects in the selection
638         if (SP_IS_TEXT (item_list->data)) {
640             // backwards compatibility:
641             SP_OBJECT_REPR(item_list->data)->setAttribute("sodipodi:linespacing", sp_repr_css_property (css, "line-height", NULL));
643             ++items;
644         }
645         else if (SP_IS_FLOWTEXT (item_list->data))
646             // no need to set sodipodi:linespacing, because Inkscape never supported it on flowtext
647             ++items;
648     }
650     if (items == 0) {
651         // no text objects; apply style to prefs for new objects
652         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
653         prefs->mergeStyle("/tools/text/style", css);
654         gtk_widget_set_sensitive (def, FALSE);
655     } else if (items == 1) {
656         /* exactly one text object; now set its text, too */
657         SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
658         if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) {
659             sp_text_edit_dialog_update_object_text (item);
660         }
661     }
663     // complete the transaction
664     SPDocumentUndo::done (sp_desktop_document (SP_ACTIVE_DESKTOP), SP_VERB_CONTEXT_TEXT,
665                       _("Set text style"));
666     gtk_widget_set_sensitive (apply, FALSE);
667     sp_repr_css_attr_unref (css);
668     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
671 static void
672 sp_text_edit_dialog_close( GtkButton */*button*/, GtkWidget *dlg )
674     gtk_widget_destroy (GTK_WIDGET (dlg));
677 static void
678 sp_text_edit_dialog_read_selection ( GtkWidget *dlg,
679                                      gboolean dostyle,
680                                      gboolean docontent )
682     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
683         return;
685     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
687     GtkWidget *notebook = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "notebook");
688     GtkWidget *textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
689     GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
690     GtkWidget *preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
691     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
692     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
694     GtkTextBuffer *tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
696     SPItem *text = sp_ted_get_selected_text_item ();
698     Inkscape::XML::Node *repr;
699     if (text)
700     {
701         guint items = sp_ted_get_selected_text_count ();
702         if (items == 1) {
703             gtk_widget_set_sensitive (textw, TRUE);
704         } else {
705             gtk_widget_set_sensitive (textw, FALSE);
706         }
707         gtk_widget_set_sensitive (apply, FALSE);
708         gtk_widget_set_sensitive (def, TRUE);
710         if (docontent) {
711             gchar *str;
712             str = sp_te_get_string_multiline (text);
714             if (str) {
715                 int pos;
716                 pos = 0;
718                 if (items == 1) {
719                     gtk_text_buffer_set_text (tb, str, strlen (str));
720                     gtk_text_buffer_set_modified (tb, FALSE);
721                 }
722                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
723                 g_free (str);
725             } else {
726                 gtk_text_buffer_set_text (tb, "", 0);
727                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
728             }
729         } // end of if (docontent)
730         repr = SP_OBJECT_REPR (text);
732     } else {
733         gtk_widget_set_sensitive (textw, FALSE);
734         gtk_widget_set_sensitive (apply, FALSE);
735         gtk_widget_set_sensitive (def, FALSE);
736     }
738     if (dostyle) {
740         // create temporary style
741         SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
742         // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
743         //int result_fontspec = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONT_SPECIFICATION);
744         int result_family = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY);
745         int result_style = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE);
746         int result_numbers = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
748         // If querying returned nothing, read the style from the text tool prefs (default style for new texts)
749         // (Ok to not get a font specification - must just rely on the family and style in that case)
750         if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING
751                 || result_numbers == QUERY_STYLE_NOTHING) {
752             sp_style_read_from_prefs(query, "/tools/text");
753         }
755         // FIXME: process result_family/style == QUERY_STYLE_MULTIPLE_DIFFERENT by showing "Many" in the lists
757         // Get a font_instance using the font-specification attribute stored in SPStyle if available
758         font_instance *font = font_factory::Default()->FaceFromStyle(query);
761         if (font) {
762             // the font is oversized, so we need to pass the true size separately
763             sp_font_selector_set_font (SP_FONT_SELECTOR (fontsel), font, query->font_size.computed);
764             sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fontsel));
765             font->Unref();
766             font=NULL;
767         }
769         GtkWidget *b;
770         if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) {
771             if (query->text_align.computed == SP_CSS_TEXT_ALIGN_JUSTIFY) {
772                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_justify" );
773             } else {
774                 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_start" );
775             }
776         } else if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) {
777             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_middle" );
778         } else {
779             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_end" );
780         }
781         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
783         if (query->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) {
784             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
785         } else {
786             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
787         }
788         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
790         GtkWidget *combo = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "line_spacing" );
791         double height;
792         if (query->line_height.normal) height = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
793         else if (query->line_height.unit == SP_CSS_UNIT_PERCENT)
794             height = query->line_height.value;
795         else height = query->line_height.computed;
796         gchar *sstr = g_strdup_printf ("%d%%", (int) floor(height * 100 + 0.5));
797         gtk_entry_set_text ((GtkEntry *) ((GtkCombo *) (combo))->entry, sstr);
798         g_free(sstr);
800         sp_style_unref(query);
801     }
803     g_object_set_data (G_OBJECT (dlg), "blocked", NULL);
807 static void
808 sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg)
810     GtkWidget *textw, *preview, *apply, *def;
811     GtkTextIter start, end;
812     gchar *str;
814     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
815         return;
817     SPItem *text = sp_ted_get_selected_text_item ();
819     textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
820     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
821     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
822     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
824     gtk_text_buffer_get_bounds (tb, &start, &end);
825     str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
827     if (str && *str) {
828         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
829     } else {
830         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
831     }
832     g_free (str);
834     if (text) {
835         gtk_widget_set_sensitive (apply, TRUE);
836     }
837     gtk_widget_set_sensitive (def, TRUE);
839 } // end of sp_text_edit_dialog_text_changed()
841 void
842 sp_text_edit_dialog_default_set_insensitive ()
844     if (!dlg) return;
845     gpointer data = g_object_get_data (G_OBJECT (dlg), "default");
846     if (!data) return;
847     gtk_widget_set_sensitive (GTK_WIDGET (data), FALSE);
850 static void
851 sp_text_edit_dialog_font_changed ( SPFontSelector *fsel,
852                                    font_instance *font,
853                                    GtkWidget *dlg )
855     GtkWidget *preview, *apply, *def;
857     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
858         return;
860     SPItem *text = sp_ted_get_selected_text_item ();
862     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
863     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
864     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
866     sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fsel));
868     if (text)
869     {
870         gtk_widget_set_sensitive (apply, TRUE);
871     }
872     gtk_widget_set_sensitive (def, TRUE);
874 } // end of sp_text_edit_dialog_font_changed()
878 static void
879 sp_text_edit_dialog_any_toggled( GtkToggleButton */*tb*/, GtkWidget *dlg )
881     GtkWidget *apply, *def;
883     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
884         return;
886     SPItem *text = sp_ted_get_selected_text_item ();
888     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
889     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
891     if (text) {
892         gtk_widget_set_sensitive (apply, TRUE);
893     }
894     gtk_widget_set_sensitive (def, TRUE);
899 static void
900 sp_text_edit_dialog_line_spacing_changed( GtkEditable */*editable*/, GtkWidget *dlg )
902     GtkWidget *apply, *def;
904     if (g_object_get_data ((GObject *) dlg, "blocked"))
905         return;
907     SPItem *text = sp_ted_get_selected_text_item ();
909     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
910     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
912     if (text) {
913         gtk_widget_set_sensitive (apply, TRUE);
914     }
915     gtk_widget_set_sensitive (def, TRUE);
920 static SPItem *
921 sp_ted_get_selected_text_item (void)
923     if (!SP_ACTIVE_DESKTOP)
924         return NULL;
926     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
927          item != NULL;
928          item = item->next)
929     {
930         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
931             return SP_ITEM (item->data);
932     }
934     return NULL;
939 static unsigned
940 sp_ted_get_selected_text_count (void)
942     if (!SP_ACTIVE_DESKTOP)
943         return 0;
945     unsigned int items = 0;
947     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
948          item != NULL;
949          item = item->next)
950     {
951         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
952             ++items;
953     }
955     return items;
958 /*
959   Local Variables:
960   mode:c++
961   c-file-style:"stroustrup"
962   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
963   indent-tabs-mode:nil
964   fill-column:99
965   End:
966 */
967 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :