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