Code

* Make sure we don't read on NULL pointer instead of gobject instance in text-edit...
[inkscape.git] / src / dialogs / text-edit.cpp
1 #define __SP_TEXT_EDIT_C__
3 /**
4  * \brief Text editing dialog
5  *
6  * Author:
7  *   Lauris Kaplinski <lauris@ximian.com>
8  *   bulia byak <buliabyak@users.sf.net>
9  *
10  * Copyright (C) 1999-2002 Lauris Kaplinski
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>
22 #include <gtk/gtk.h>
24 #ifdef WITH_GTKSPELL
25 extern "C" {
26 # include <gtkspell/gtkspell.h>
27 }
28 #endif
30 #include "macros.h"
31 #include <glibmm/i18n.h>
32 #include "helper/window.h"
33 #include "../widgets/font-selector.h"
34 #include "../inkscape.h"
35 #include "../document.h"
36 #include "../desktop-style.h"
37 #include "../desktop-handles.h"
38 #include "../selection.h"
39 #include "../style.h"
40 #include "../sp-text.h"
41 #include "../sp-flowtext.h"
42 #include "../text-editing.h"
43 #include "../inkscape-stock.h"
44 #include <libnrtype/font-style-to-pos.h>
46 #include "dialog-events.h"
47 #include "../prefs-utils.h"
48 #include "../verbs.h"
49 #include "../interface.h"
50 #include "svg/css-ostringstream.h"
51 #include "widgets/icon.h"
52 #include <xml/repr.h>
54 #define VB_MARGIN 4
56 static void sp_text_edit_dialog_selection_modified (Inkscape::Application *inkscape, Inkscape::Selection *sel, guint flags, GtkWidget *dlg);
57 static void sp_text_edit_dialog_selection_changed (Inkscape::Application *inkscape, Inkscape::Selection *sel, GtkWidget *dlg);
58 static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg);
60 static void sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg);
61 static void sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg);
62 static void sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg);
64 static void sp_text_edit_dialog_read_selection (GtkWidget *dlg, gboolean style, gboolean content);
66 static void sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg);
67 static void sp_text_edit_dialog_font_changed (SPFontSelector *fontsel, font_instance *font, GtkWidget *dlg);
68 static void sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg);
69 static void sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg);
71 static SPItem *sp_ted_get_selected_text_item (void);
72 static unsigned sp_ted_get_selected_text_count (void);
75 static const gchar *spacings[] = {"50%", "80%", "90%", "100%", "110%", "120%", "130%", "140%", "150%", "200%", "300%", NULL};
77 static GtkWidget *dlg = NULL;
78 static win_data wd;
79 // impossible original values to make sure they are read from prefs
80 static gint x = -1000, y = -1000, w = 0, h = 0;
81 static gchar const *prefs_path = "dialogs.textandfont";
86 static void
87 sp_text_edit_dialog_destroy (GtkObject *object, gpointer data)
88 {
89     sp_signal_disconnect_by_data (INKSCAPE, dlg);
90     wd.win = dlg = NULL;
91     wd.stop = 0;
92 }
96 static gboolean
97 sp_text_edit_dialog_delete (GtkObject *object, GdkEvent *event, gpointer data)
98 {
99     gtk_window_get_position ((GtkWindow *) dlg, &x, &y);
100     gtk_window_get_size ((GtkWindow *) dlg, &w, &h);
102     if (x<0) x=0;
103     if (y<0) y=0;
105     prefs_set_int_attribute (prefs_path, "x", x);
106     prefs_set_int_attribute (prefs_path, "y", y);
107     prefs_set_int_attribute (prefs_path, "w", w);
108     prefs_set_int_attribute (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);
144         dlg = sp_window_new (title, TRUE);
145         if (x == -1000 || y == -1000) {
146             x = prefs_get_int_attribute (prefs_path, "x", 0);
147             y = prefs_get_int_attribute (prefs_path, "y", 0);
148         }
150         if (w ==0 || h == 0) {
151             w = prefs_get_int_attribute (prefs_path, "w", 0);
152             h = prefs_get_int_attribute (prefs_path, "h", 0);
153         }
155         if (x<0) x=0;
156         if (y<0) y=0;
158         if (x != 0 || y != 0) {
159             gtk_window_move ((GtkWindow *) dlg, x, y);
160         } else {
161             gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
162         }
164         if (w && h)
165             gtk_window_resize ((GtkWindow *) dlg, w, h);
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                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
274                 }
277                 {
278                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
279                     GtkWidget *group;
281                     // horizontal
282                     {
283                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
284                                                       INKSCAPE_STOCK_WRITING_MODE_LR );
285                         GtkWidget *b = group = gtk_radio_button_new (NULL);
286                         gtk_tooltips_set_tip (tt, b, _("Horizontal text"), 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), INKSCAPE_STOCK_WRITING_MODE_LR, b);
293                     }
295                     // vertical
296                     {
297                         GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
298                                                       INKSCAPE_STOCK_WRITING_MODE_TB );
299                         GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
300                         gtk_tooltips_set_tip (tt, b, _("Vertical 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_STOCK_WRITING_MODE_TB, b);
307                     }
309                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
310                 }
312                 {
313                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
315                     l = gtk_label_new (_("Line spacing:"));
316                     gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5);
317                     gtk_box_pack_start (GTK_BOX (row), l, FALSE, FALSE, VB_MARGIN);
319                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
320                 }
322                 {
323                     GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
325                     GtkWidget *c = gtk_combo_new ();
326                     gtk_combo_set_value_in_list ((GtkCombo *) c, FALSE, FALSE);
327                     gtk_combo_set_use_arrows ((GtkCombo *) c, TRUE);
328                     gtk_combo_set_use_arrows_always ((GtkCombo *) c, TRUE);
329                     gtk_widget_set_size_request (c, 90, -1);
331                     { /* Setup strings */
332                         GList *sl = NULL;
333                         for (int i = 0; spacings[i]; i++) {
334                             sl = g_list_prepend (sl, (void *) spacings[i]);
335                         }
336                         sl = g_list_reverse (sl);
337                         gtk_combo_set_popdown_strings ((GtkCombo *) c, sl);
338                         g_list_free (sl);
339                     }
341                     g_signal_connect ( (GObject *) ((GtkCombo *) c)->entry,
342                                        "changed",
343                                        (GCallback) sp_text_edit_dialog_line_spacing_changed,
344                                        dlg );
345                     gtk_box_pack_start (GTK_BOX (row), c, FALSE, FALSE, VB_MARGIN);
346                     g_object_set_data (G_OBJECT (dlg), "line_spacing", c);
348                     gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, VB_MARGIN);
349                 }
350             }
352             /* Font preview */
353             GtkWidget *preview = sp_font_preview_new ();
354             gtk_box_pack_start (GTK_BOX (vb), preview, TRUE, TRUE, 4);
355             g_object_set_data (G_OBJECT (dlg), "preview", preview);
356         }
359         // Text tab
360         {
361             GtkWidget *l = gtk_label_new (_("Text"));
362             GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
363             gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
364             gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
366             GtkWidget *scroller = gtk_scrolled_window_new ( NULL, NULL );
367             gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (scroller),
368                                              GTK_POLICY_AUTOMATIC,
369                                              GTK_POLICY_AUTOMATIC );
370             gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_IN );
371             gtk_widget_show (scroller);
373             GtkTextBuffer *tb = gtk_text_buffer_new (NULL);
374             GtkWidget *txt = gtk_text_view_new_with_buffer (tb);
375             gtk_text_view_set_wrap_mode ((GtkTextView *) txt, GTK_WRAP_WORD);
376 #ifdef WITH_GTKSPELL
377             GError *error = NULL;
378             char *errortext = NULL;
379             /* todo: Use computed xml:lang attribute of relevant element, if present, to specify the
380                language (either as 2nd arg of gtkspell_new_attach, or with explicit
381                gtkspell_set_language call in; see advanced.c example in gtkspell docs).
382                sp_text_edit_dialog_read_selection looks like a suitable place. */
383             if (gtkspell_new_attach(GTK_TEXT_VIEW(txt), NULL, &error) == NULL) {
384                 g_print("gtkspell error: %s\n", error->message);
385                 errortext = g_strdup_printf("GtkSpell was unable to initialize.\n"
386                                             "%s", error->message);
387                 g_error_free(error);
388             }
389 #endif
390             gtk_widget_set_size_request (txt, -1, 64);
391             gtk_text_view_set_editable (GTK_TEXT_VIEW (txt), TRUE);
392             gtk_container_add (GTK_CONTAINER (scroller), txt);
393             gtk_box_pack_start (GTK_BOX (vb), scroller, TRUE, TRUE, 0);
394             g_signal_connect ( G_OBJECT (tb), "changed",
395                                G_CALLBACK (sp_text_edit_dialog_text_changed), dlg );
396             g_signal_connect (G_OBJECT (txt), "focus-in-event", G_CALLBACK (text_view_focus_in), dlg);
397             g_signal_connect (G_OBJECT (txt), "focus-out-event", G_CALLBACK (text_view_focus_out), dlg);
398             g_object_set_data (G_OBJECT (dlg), "text", tb);
399             g_object_set_data (G_OBJECT (dlg), "textw", txt);
400         }
402         /* Buttons */
403         GtkWidget *hb = gtk_hbox_new (FALSE, VB_MARGIN);
404         gtk_container_set_border_width (GTK_CONTAINER (hb), 4);
405         gtk_box_pack_start (GTK_BOX (mainvb), hb, FALSE, FALSE, 0);
407         {
408             GtkWidget *b = gtk_button_new_with_label (_("Set as default"));
409             g_signal_connect ( G_OBJECT (b), "clicked",
410                                G_CALLBACK (sp_text_edit_dialog_set_default),
411                                dlg );
412             gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0);
413             g_object_set_data (G_OBJECT (dlg), "default", b);
414         }
416         {
417             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
418             g_signal_connect ( G_OBJECT (b), "clicked", 
419                                G_CALLBACK (sp_text_edit_dialog_close), dlg );
420             gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0);
421         }
423         {
424             GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_APPLY);
425             GTK_WIDGET_SET_FLAGS (b, GTK_CAN_DEFAULT | GTK_HAS_DEFAULT);
426             g_signal_connect ( G_OBJECT (b), "clicked", 
427                                G_CALLBACK (sp_text_edit_dialog_apply), dlg );
428             gtk_box_pack_end ( GTK_BOX (hb), b, FALSE, FALSE, 0 );
429             g_object_set_data (G_OBJECT (dlg), "apply", b);
430         }
432         g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection", 
433                            G_CALLBACK (sp_text_edit_dialog_selection_modified), dlg);
434         g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection", 
435                            G_CALLBACK (sp_text_edit_dialog_selection_changed), dlg);
436         g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_text_edit_dialog_subselection_changed), dlg);
438         gtk_widget_show_all (dlg);
440         sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
441     }
443     gtk_window_present ((GtkWindow *) dlg);
445 } // end of sp_text_edit_dialog()
449 static void
450 sp_text_edit_dialog_selection_modified ( Inkscape::Application *inkscape, 
451                                        Inkscape::Selection *sel, 
452                                        guint flags, 
453                                        GtkWidget *dlg )
455     gboolean style, content;
457     style = 
458         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG | 
459                     SP_OBJECT_STYLE_MODIFIED_FLAG  )) != 0 );
460     
461     content = 
462         ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG | 
463                     SP_TEXT_CONTENT_MODIFIED_FLAG  )) != 0 );
464     
465     sp_text_edit_dialog_read_selection (dlg, style, content);
467
471 static void
472 sp_text_edit_dialog_selection_changed ( Inkscape::Application *inkscape, 
473                                        Inkscape::Selection *sel, 
474                                        GtkWidget *dlg )
476     sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
479 static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg )
481     sp_text_edit_dialog_read_selection (dlg, TRUE, FALSE);
484 static void
485 sp_text_edit_dialog_update_object_text ( SPItem *text )
487         GtkTextBuffer *tb;
488         GtkTextIter start, end;
489         gchar *str;
491         tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
493         /* write text */
494         if (gtk_text_buffer_get_modified (tb)) {
495             gtk_text_buffer_get_bounds (tb, &start, &end);
496             str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
497             sp_te_set_repr_text_multiline (text, str);
498             g_free (str);
499             gtk_text_buffer_set_modified (tb, FALSE);
500         }
503 SPCSSAttr *
504 sp_get_text_dialog_style ()
506         GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
508         SPCSSAttr *css = sp_repr_css_attr_new ();
510         /* font */
511         font_instance *font = sp_font_selector_get_font (SP_FONT_SELECTOR (fontsel));
513                                 if ( font ) {
514                                         gchar c[256];
515                                         font->Family(c, 256);
516                                         sp_repr_css_set_property (css, "font-family", c);
517                                         
518                                         font->Attribute( "weight", c, 256);
519                                         sp_repr_css_set_property (css, "font-weight", c);
520                                         
521                                         font->Attribute("style", c, 256);
522                                         sp_repr_css_set_property (css, "font-style", c);
523                                         
524                                         font->Attribute("stretch", c, 256);
525                                         sp_repr_css_set_property (css, "font-stretch", c);
526                                         
527                                         font->Attribute("variant", c, 256);
528                                         sp_repr_css_set_property (css, "font-variant", c);
529                                         
530                                         Inkscape::CSSOStringStream os;
531                                         os << sp_font_selector_get_size (SP_FONT_SELECTOR (fontsel));
532                                         sp_repr_css_set_property (css, "font-size", os.str().c_str());
533                                         
534                                         font->Unref();
535                                         font=NULL;
536                                 }
537                                 
538         /* Layout */
539         GtkWidget *b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "text_anchor_start");
540         
541         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
542             sp_repr_css_set_property (css, "text-anchor", "start");
543             sp_repr_css_set_property (css, "text-align", "start");
544         } else {
545             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), 
546                                                 "text_anchor_middle");
547             if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
548                 sp_repr_css_set_property (css, "text-anchor", "middle");
549                 sp_repr_css_set_property (css, "text-align", "center");
550             } else {
551                 sp_repr_css_set_property (css, "text-anchor", "end");
552                 sp_repr_css_set_property (css, "text-align", "end");
553             }
554         }
556         b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_LR );
558         if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
559             sp_repr_css_set_property (css, "writing-mode", "lr");
560         } else {
561             sp_repr_css_set_property (css, "writing-mode", "tb");
562         }
564         // Note that CSS 1.1 does not support line-height; we set it for consistency, but also set
565         // sodipodi:linespacing for backwards compatibility; in 1.2 we use line-height for flowtext
566         GtkWidget *combo = (GtkWidget*)g_object_get_data ((GObject *) dlg, "line_spacing");
567         const char *sstr = gtk_entry_get_text ((GtkEntry *) ((GtkCombo *) (combo))->entry);
568         sp_repr_css_set_property (css, "line-height", sstr);
570         return css;
574 static void
575 sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg)
577     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
579     SPCSSAttr *css = sp_get_text_dialog_style ();
581     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
582     sp_repr_css_change (inkscape_get_repr (INKSCAPE, "tools.text"), css, "style");
583     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
585     sp_repr_css_attr_unref (css);
587     gtk_widget_set_sensitive (def, FALSE);
592 static void
593 sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg)
595     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
597     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
598     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
599     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
601     unsigned items = 0;
602     const GSList *item_list = sp_desktop_selection(desktop)->itemList();
603     SPCSSAttr *css = sp_get_text_dialog_style ();
604     sp_desktop_set_style(desktop, css, true);
606     for (; item_list != NULL; item_list = item_list->next) {
607         // apply style to the reprs of all text objects in the selection
608         if (SP_IS_TEXT (item_list->data)) {
610             // backwards compatibility:
611             SP_OBJECT_REPR(item_list->data)->setAttribute("sodipodi:linespacing", sp_repr_css_property (css, "line-height", NULL));
613             ++items;
614         }
615         else if (SP_IS_FLOWTEXT (item_list->data))
616             // no need to set sodipodi:linespacing, because Inkscape never supported it on flowtext
617             ++items;
618     }
620     if (items == 0) {
621         // no text objects; apply style to prefs for new objects
622         sp_repr_css_change (inkscape_get_repr (INKSCAPE, "tools.text"), css, "style");
623         gtk_widget_set_sensitive (def, FALSE);
624     } else if (items == 1) {
625         /* exactly one text object; now set its text, too */
626         SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
627         if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) {
628             sp_text_edit_dialog_update_object_text (item);
629         }
630     }
632     // complete the transaction
633     sp_document_done (sp_desktop_document (SP_ACTIVE_DESKTOP));
634     gtk_widget_set_sensitive (apply, FALSE);
635     sp_repr_css_attr_unref (css);
636     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
639 static void
640 sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg)
642     gtk_widget_destroy (GTK_WIDGET (dlg));
645 static void
646 sp_text_edit_dialog_read_selection ( GtkWidget *dlg,
647                                      gboolean dostyle,
648                                      gboolean docontent )
650     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
651         return;
653     g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
655     GtkWidget *notebook = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "notebook");
656     GtkWidget *textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
657     GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
658     GtkWidget *preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
659     GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
660     GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
662     GtkTextBuffer *tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
664     SPItem *text = sp_ted_get_selected_text_item ();
666     Inkscape::XML::Node *repr;
667     if (text)
668     {
669         guint items = sp_ted_get_selected_text_count ();
670         if (items == 1) {
671             gtk_widget_set_sensitive (textw, TRUE);
672         } else {
673             gtk_widget_set_sensitive (textw, FALSE);
674         }
675         gtk_widget_set_sensitive (apply, FALSE);
676         gtk_widget_set_sensitive (def, TRUE);
678         if (docontent) {
679             gchar *str;
680             str = sp_te_get_string_multiline (text);
682             if (str) {
683                 int pos;
684                 pos = 0;
686                 if (items == 1) {
687                     gtk_text_buffer_set_text (tb, str, strlen (str));
688                     gtk_text_buffer_set_modified (tb, FALSE);
689                 }
690                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
691                 g_free (str);
693             } else {
694                 gtk_text_buffer_set_text (tb, "", 0);
695                 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
696             }
697         } // end of if (docontent)
698         repr = SP_OBJECT_REPR (text);
700     } else {
701         gtk_widget_set_sensitive (textw, FALSE);
702         gtk_widget_set_sensitive (apply, FALSE);
703         gtk_widget_set_sensitive (def, FALSE);
704     }
706     if (dostyle) {
708         // create temporary style
709         SPStyle *query = sp_style_new ();
710         // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
711         int result_family = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY); 
712         int result_style = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE); 
713         int result_numbers = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS); 
715         // If querying returned nothing, read the style from the text tool prefs (default style for new texts)
716         if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING || result_numbers == QUERY_STYLE_NOTHING) {
717             repr = inkscape_get_repr (INKSCAPE, "tools.text");
718             if (repr) {
719                 gtk_widget_set_sensitive (notebook, TRUE);
720                 sp_style_read_from_repr (query, repr);
721             } else {
722                 gtk_widget_set_sensitive (notebook, FALSE);
723             }
724         }
726         // FIXME: process result_family/style == QUERY_STYLE_MULTIPLE_DIFFERENT by showing "Many" in the lists
727         font_instance *font = (font_factory::Default())->Face ( query->text->font_family.value, font_style_to_pos(*query) );
728         if (font) {
729             // the font is oversized, so we need to pass the true size separately
730             sp_font_selector_set_font (SP_FONT_SELECTOR (fontsel), font, query->font_size.computed);
731             sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fontsel));
732                                                 font->Unref();
733                                                 font=NULL;
734         }
736         GtkWidget *b;
737         if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) {
738             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_start" );
739         } else if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) {
740             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_middle" );
741         } else {
742             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_end" );
743         }
744         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
745        
746         if (query->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) {
747             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_LR );
748         } else {
749             b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_TB );
750         }
751         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
753         GtkWidget *combo = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "line_spacing" );
754         double height;
755         if (query->line_height.normal) height = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
756         else if (query->line_height.unit == SP_CSS_UNIT_PERCENT)
757             height = query->line_height.value;
758         else height = query->line_height.computed;
759         gchar *sstr = g_strdup_printf ("%d%%", (int) floor(height * 100 + 0.5));
760         gtk_entry_set_text ((GtkEntry *) ((GtkCombo *) (combo))->entry, sstr);
761         g_free(sstr);
763         g_free (query);
764     }
766     g_object_set_data (G_OBJECT (dlg), "blocked", NULL);
770 static void
771 sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg)
773     GtkWidget *textw, *preview, *apply, *def;
774     GtkTextIter start, end;
775     gchar *str;
777     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
778         return;
780     SPItem *text = sp_ted_get_selected_text_item ();
782     textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
783     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
784     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
785     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
787     gtk_text_buffer_get_bounds (tb, &start, &end);
788     str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
790     if (str && *str) {
791         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
792     } else {
793         sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
794     }
795     g_free (str);
797     if (text) {
798         gtk_widget_set_sensitive (apply, TRUE);
799     }
800     gtk_widget_set_sensitive (def, TRUE);
802 } // end of sp_text_edit_dialog_text_changed()
804 void
805 sp_text_edit_dialog_default_set_insensitive ()
807     if (!dlg) return;
808     gpointer data = g_object_get_data (G_OBJECT (dlg), "default");
809     if (!data) return;
810     gtk_widget_set_sensitive (GTK_WIDGET (data), FALSE);
813 static void
814 sp_text_edit_dialog_font_changed ( SPFontSelector *fsel,
815                                    font_instance *font,
816                                    GtkWidget *dlg )
818     GtkWidget *preview, *apply, *def;
820     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
821         return;
823     SPItem *text = sp_ted_get_selected_text_item ();
825     preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
826     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
827     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
829     sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fsel));
831     if (text)
832     {
833         gtk_widget_set_sensitive (apply, TRUE);
834     }
835     gtk_widget_set_sensitive (def, TRUE);
837 } // end of sp_text_edit_dialog_font_changed()
841 static void
842 sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg)
844     GtkWidget *apply, *def;
846     if (g_object_get_data (G_OBJECT (dlg), "blocked"))
847         return;
849     SPItem *text = sp_ted_get_selected_text_item ();
851     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
852     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
854     if (text) {
855         gtk_widget_set_sensitive (apply, TRUE);
856     }
857     gtk_widget_set_sensitive (def, TRUE);
862 static void
863 sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg)
865     GtkWidget *apply, *def;
867     if (g_object_get_data ((GObject *) dlg, "blocked"))
868         return;
870     SPItem *text = sp_ted_get_selected_text_item ();
872     apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
873     def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
875     if (text) {
876         gtk_widget_set_sensitive (apply, TRUE);
877     }
878     gtk_widget_set_sensitive (def, TRUE);
883 static SPItem *
884 sp_ted_get_selected_text_item (void)
886     if (!SP_ACTIVE_DESKTOP)
887         return NULL;
889     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
890          item != NULL;
891          item = item->next)
892     {
893         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
894             return SP_ITEM (item->data);
895     }
897     return NULL;
902 static unsigned
903 sp_ted_get_selected_text_count (void)
905     if (!SP_ACTIVE_DESKTOP)
906         return 0;
908     unsigned int items = 0;
910     for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
911          item != NULL;
912          item = item->next)
913     {
914         if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
915             ++items;
916     }
918     return items;
921 /*
922   Local Variables:
923   mode:c++
924   c-file-style:"stroustrup"
925   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
926   indent-tabs-mode:nil
927   fill-column:99
928   End:
929 */
930 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :