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 "../inkscape-stock.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
111 }
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 )
121 {
122 GObject *dlg = (GObject *) data;
123 g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (TRUE));
124 return FALSE;
125 }
127 gboolean
128 text_view_focus_out (GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data)
129 {
130 GObject *dlg = (GObject *) data;
131 g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (FALSE));
132 return FALSE;
133 }
136 void
137 sp_text_edit_dialog (void)
138 {
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_STOCK_WRITING_MODE_LR );
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_STOCK_WRITING_MODE_LR, b);
307 }
309 // vertical
310 {
311 GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
312 INKSCAPE_STOCK_WRITING_MODE_TB );
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_STOCK_WRITING_MODE_TB, 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 )
468 {
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);
481 }
485 static void
486 sp_text_edit_dialog_selection_changed( Inkscape::Application */*inkscape*/,
487 Inkscape::Selection */*sel*/,
488 GtkWidget *dlg )
489 {
490 sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
491 }
493 static void sp_text_edit_dialog_subselection_changed( Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, GtkWidget *dlg )
494 {
495 sp_text_edit_dialog_read_selection (dlg, TRUE, FALSE);
496 }
498 static void
499 sp_text_edit_dialog_update_object_text ( SPItem *text )
500 {
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 }
515 }
517 SPCSSAttr *
518 sp_get_text_dialog_style ()
519 {
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_STOCK_WRITING_MODE_LR );
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;
600 }
603 static void
604 sp_text_edit_dialog_set_default( GtkButton */*button*/, GtkWidget *dlg )
605 {
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);
618 }
622 static void
623 sp_text_edit_dialog_apply( GtkButton */*button*/, GtkWidget *dlg )
624 {
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 sp_document_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));
669 }
671 static void
672 sp_text_edit_dialog_close( GtkButton */*button*/, GtkWidget *dlg )
673 {
674 gtk_widget_destroy (GTK_WIDGET (dlg));
675 }
677 static void
678 sp_text_edit_dialog_read_selection ( GtkWidget *dlg,
679 gboolean dostyle,
680 gboolean docontent )
681 {
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_STOCK_WRITING_MODE_LR );
785 } else {
786 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_STOCK_WRITING_MODE_TB );
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);
804 }
807 static void
808 sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg)
809 {
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 ()
843 {
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);
848 }
850 static void
851 sp_text_edit_dialog_font_changed ( SPFontSelector *fsel,
852 font_instance *font,
853 GtkWidget *dlg )
854 {
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 )
880 {
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);
895 }
899 static void
900 sp_text_edit_dialog_line_spacing_changed( GtkEditable */*editable*/, GtkWidget *dlg )
901 {
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);
916 }
920 static SPItem *
921 sp_ted_get_selected_text_item (void)
922 {
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;
935 }
939 static unsigned
940 sp_ted_get_selected_text_count (void)
941 {
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;
956 }
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 :