1 /** @file
2 * @brief Text editing dialog
3 */
4 /* Authors:
5 * Lauris Kaplinski <lauris@ximian.com>
6 * bulia byak <buliabyak@users.sf.net>
7 * Johan Engelen <goejendaagh@zonnet.nl>
8 * Abhishek Sharma
9 *
10 * Copyright (C) 1999-2007 Authors
11 * Copyright (C) 2000-2001 Ximian, Inc.
12 *
13 * Released under GNU GPL, read the file 'COPYING' for more information
14 */
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
20 #include <libnrtype/font-instance.h>
21 #include <gtk/gtk.h>
23 #ifdef WITH_GTKSPELL
24 extern "C" {
25 # include <gtkspell/gtkspell.h>
26 }
27 #endif
29 #include "macros.h"
30 #include <glibmm/i18n.h>
31 #include "helper/window.h"
32 #include "../widgets/font-selector.h"
33 #include "../inkscape.h"
34 #include "../document.h"
35 #include "../desktop-style.h"
36 #include "../desktop-handles.h"
37 #include "../selection.h"
38 #include "../style.h"
39 #include "../sp-text.h"
40 #include "../sp-flowtext.h"
41 #include "../text-editing.h"
42 #include "../ui/icon-names.h"
43 #include <libnrtype/font-style-to-pos.h>
45 #include "dialog-events.h"
46 #include "../preferences.h"
47 #include "../verbs.h"
48 #include "../interface.h"
49 #include "svg/css-ostringstream.h"
50 #include "widgets/icon.h"
51 #include <xml/repr.h>
52 #include "util/ege-appear-time-tracker.h"
54 using Inkscape::DocumentUndo;
55 using ege::AppearTimeTracker;
57 #define VB_MARGIN 4
58 #define MIN_ONSCREEN_DISTANCE 50
60 static void sp_text_edit_dialog_selection_modified (Inkscape::Application *inkscape, Inkscape::Selection *sel, guint flags, GtkWidget *dlg);
61 static void sp_text_edit_dialog_selection_changed (Inkscape::Application *inkscape, Inkscape::Selection *sel, GtkWidget *dlg);
62 static void sp_text_edit_dialog_subselection_changed ( Inkscape::Application *inkscape, SPDesktop *desktop, GtkWidget *dlg);
64 static void sp_text_edit_dialog_set_default (GtkButton *button, GtkWidget *dlg);
65 static void sp_text_edit_dialog_apply (GtkButton *button, GtkWidget *dlg);
66 static void sp_text_edit_dialog_close (GtkButton *button, GtkWidget *dlg);
68 static void sp_text_edit_dialog_read_selection (GtkWidget *dlg, gboolean style, gboolean content);
70 static void sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg);
71 static void sp_text_edit_dialog_font_changed (SPFontSelector *fontsel, font_instance *font, GtkWidget *dlg);
72 static void sp_text_edit_dialog_any_toggled (GtkToggleButton *tb, GtkWidget *dlg);
73 static void sp_text_edit_dialog_line_spacing_changed (GtkEditable *editable, GtkWidget *dlg);
75 static SPItem *sp_ted_get_selected_text_item (void);
76 static unsigned sp_ted_get_selected_text_count (void);
79 static const gchar *spacings[] = {"50%", "80%", "90%", "100%", "110%", "120%", "130%", "140%", "150%", "200%", "300%", NULL};
81 static GtkWidget *dlg = NULL;
82 static win_data wd;
83 // impossible original values to make sure they are read from prefs
84 static gint x = -1000, y = -1000, w = 0, h = 0;
85 static Glib::ustring const prefs_path = "/dialogs/textandfont/";
90 static void
91 sp_text_edit_dialog_destroy( GtkObject */*object*/, gpointer /*data*/ )
92 {
93 sp_signal_disconnect_by_data (INKSCAPE, dlg);
94 wd.win = dlg = NULL;
95 wd.stop = 0;
96 }
100 static gboolean
101 sp_text_edit_dialog_delete( GtkObject */*object*/, GdkEvent */*event*/, gpointer /*data*/ )
102 {
103 gtk_window_get_position ((GtkWindow *) dlg, &x, &y);
104 gtk_window_get_size ((GtkWindow *) dlg, &w, &h);
106 if (x<0) x=0;
107 if (y<0) y=0;
109 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
110 prefs->setInt(prefs_path + "x", x);
111 prefs->setInt(prefs_path + "y", y);
112 prefs->setInt(prefs_path + "w", w);
113 prefs->setInt(prefs_path + "h", h);
115 return FALSE; // which means, go ahead and destroy it
116 }
119 /**
120 These callbacks set the eatkeys flag when the text editor is entered and cancel it when it's left.
121 This flag is used to prevent passing keys from the dialog to canvas, so that the text editor
122 can handle keys like Esc and Ctrl+Z itself.
123 */
124 gboolean
125 text_view_focus_in( GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data )
126 {
127 GObject *dlg = (GObject *) data;
128 g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (TRUE));
129 return FALSE;
130 }
132 gboolean
133 text_view_focus_out (GtkWidget */*w*/, GdkEventKey */*event*/, gpointer data)
134 {
135 GObject *dlg = (GObject *) data;
136 g_object_set_data (dlg, "eatkeys", GINT_TO_POINTER (FALSE));
137 return FALSE;
138 }
141 void
142 sp_text_edit_dialog (void)
143 {
144 bool wantTiming = Inkscape::Preferences::get()->getBool("/dialogs/debug/trackAppear", false);
145 GTimer *timer = wantTiming ? g_timer_new() : 0;
147 if (!dlg) {
149 gchar title[500];
150 sp_ui_dialog_title_string (Inkscape::Verb::get(SP_VERB_DIALOG_TEXT), title);
151 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
153 dlg = sp_window_new (title, TRUE);
154 if (x == -1000 || y == -1000) {
155 x = prefs->getInt(prefs_path + "x", -1000);
156 y = prefs->getInt(prefs_path + "y", -1000);
157 }
158 if (w ==0 || h == 0) {
159 w = prefs->getInt(prefs_path + "w", 0);
160 h = prefs->getInt(prefs_path + "h", 0);
161 }
163 // if (x<0) x=0;
164 // if (y<0) y=0;
166 if (w && h)
167 gtk_window_resize ((GtkWindow *) dlg, w, h);
168 if (x >= 0 && y >= 0 && (x < (gdk_screen_width()-MIN_ONSCREEN_DISTANCE)) && (y < (gdk_screen_height()-MIN_ONSCREEN_DISTANCE))) {
169 gtk_window_move ((GtkWindow *) dlg, x, y);
170 } else {
171 gtk_window_set_position(GTK_WINDOW(dlg), GTK_WIN_POS_CENTER);
172 }
175 sp_transientize (dlg);
176 wd.win = dlg;
177 wd.stop = 0;
178 g_signal_connect ( G_OBJECT (INKSCAPE), "activate_desktop", G_CALLBACK (sp_transientize_callback), &wd );
180 gtk_signal_connect ( GTK_OBJECT (dlg), "event", GTK_SIGNAL_FUNC (sp_dialog_event_handler), dlg );
182 gtk_signal_connect ( GTK_OBJECT (dlg), "destroy", G_CALLBACK (sp_text_edit_dialog_destroy), dlg );
183 gtk_signal_connect ( GTK_OBJECT (dlg), "delete_event", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
184 g_signal_connect ( G_OBJECT (INKSCAPE), "shut_down", G_CALLBACK (sp_text_edit_dialog_delete), dlg );
186 g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_hide", G_CALLBACK (sp_dialog_hide), dlg );
187 g_signal_connect ( G_OBJECT (INKSCAPE), "dialogs_unhide", G_CALLBACK (sp_dialog_unhide), dlg );
189 gtk_window_set_policy (GTK_WINDOW (dlg), TRUE, TRUE, FALSE);
191 GtkTooltips *tt = gtk_tooltips_new();
193 // box containing the notebook and the bottom buttons
194 GtkWidget *mainvb = gtk_vbox_new (FALSE, 0);
195 gtk_container_add (GTK_CONTAINER (dlg), mainvb);
197 // notebook
198 GtkWidget *nb = gtk_notebook_new ();
199 gtk_box_pack_start (GTK_BOX (mainvb), nb, TRUE, TRUE, 0);
200 g_object_set_data (G_OBJECT (dlg), "notebook", nb);
204 // Font tab
205 {
206 GtkWidget *l = gtk_label_new (_("Font"));
207 GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
208 gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
209 gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
211 /* HBox containing font selection and layout */
212 GtkWidget *hb = gtk_hbox_new (FALSE, 0);
213 gtk_box_pack_start (GTK_BOX (vb), hb, TRUE, TRUE, 0);
215 // font and style selector
216 GtkWidget *fontsel = sp_font_selector_new ();
217 g_signal_connect ( G_OBJECT (fontsel), "font_set", G_CALLBACK (sp_text_edit_dialog_font_changed), dlg );
219 g_signal_connect_swapped ( G_OBJECT (g_object_get_data (G_OBJECT(fontsel), "family-treeview")),
220 "row-activated",
221 G_CALLBACK (gtk_window_activate_default),
222 dlg);
224 gtk_box_pack_start (GTK_BOX (hb), fontsel, TRUE, TRUE, 0);
225 g_object_set_data (G_OBJECT (dlg), "fontsel", fontsel);
227 // Layout
228 {
229 GtkWidget *f = gtk_frame_new (_("Layout"));
230 gtk_box_pack_start (GTK_BOX (hb), f, FALSE, FALSE, 4);
231 GtkWidget *l_vb = gtk_vbox_new (FALSE, VB_MARGIN);
232 gtk_container_add (GTK_CONTAINER (f), l_vb);
234 {
235 GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
236 GtkWidget *group;
238 // align left
239 {
240 // TODO - replace with Inkscape-specific call
241 GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_LEFT, GTK_ICON_SIZE_LARGE_TOOLBAR );
242 GtkWidget *b = group = gtk_radio_button_new (NULL);
243 gtk_tooltips_set_tip (tt, b, _("Align lines left"), NULL);
244 gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
245 g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg);
246 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE );
247 gtk_container_add (GTK_CONTAINER (b), px);
248 gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
249 g_object_set_data (G_OBJECT (dlg), "text_anchor_start", b);
250 }
252 // align center
253 {
254 // TODO - replace with Inkscape-specific call
255 GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_CENTER, GTK_ICON_SIZE_LARGE_TOOLBAR );
256 GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
257 /* TRANSLATORS: `Center' here is a verb. */
258 gtk_tooltips_set_tip (tt, b, _("Center lines"), NULL);
259 gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
260 g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
261 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
262 gtk_container_add (GTK_CONTAINER (b), px);
263 gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
264 g_object_set_data (G_OBJECT (dlg), "text_anchor_middle", b);
265 }
267 // align right
268 {
269 // TODO - replace with Inkscape-specific call
270 GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_RIGHT, GTK_ICON_SIZE_LARGE_TOOLBAR );
271 GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
272 gtk_tooltips_set_tip (tt, b, _("Align lines right"), NULL);
273 gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
274 g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
275 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
276 gtk_container_add (GTK_CONTAINER (b), px);
277 gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
278 g_object_set_data (G_OBJECT (dlg), "text_anchor_end", b);
279 }
281 // align justify
282 {
283 // TODO - replace with Inkscape-specific call
284 GtkWidget *px = gtk_image_new_from_stock ( GTK_STOCK_JUSTIFY_FILL, GTK_ICON_SIZE_LARGE_TOOLBAR );
285 GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
286 gtk_tooltips_set_tip (tt, b, _("Justify lines"), 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), "text_anchor_justify", b);
293 }
295 gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
296 }
299 {
300 GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
301 GtkWidget *group;
303 // horizontal
304 {
305 GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
306 INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
307 GtkWidget *b = group = gtk_radio_button_new (NULL);
308 gtk_tooltips_set_tip (tt, b, _("Horizontal text"), NULL);
309 gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
310 g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
311 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
312 gtk_container_add (GTK_CONTAINER (b), px);
313 gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
314 g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL, b);
315 }
317 // vertical
318 {
319 GtkWidget *px = sp_icon_new( Inkscape::ICON_SIZE_LARGE_TOOLBAR,
320 INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
321 GtkWidget *b = gtk_radio_button_new (gtk_radio_button_group (GTK_RADIO_BUTTON (group)));
322 gtk_tooltips_set_tip (tt, b, _("Vertical text"), NULL);
323 gtk_button_set_relief (GTK_BUTTON (b), GTK_RELIEF_NONE);
324 g_signal_connect ( G_OBJECT (b), "toggled", G_CALLBACK (sp_text_edit_dialog_any_toggled), dlg );
325 gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (b), FALSE);
326 gtk_container_add (GTK_CONTAINER (b), px);
327 gtk_box_pack_start (GTK_BOX (row), b, FALSE, FALSE, 0);
328 g_object_set_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL, b);
329 }
331 gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
332 }
334 {
335 GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
337 l = gtk_label_new (_("Line spacing:"));
338 gtk_misc_set_alignment (GTK_MISC (l), 1.0, 0.5);
339 gtk_box_pack_start (GTK_BOX (row), l, FALSE, FALSE, VB_MARGIN);
341 gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, 0);
342 }
344 {
345 GtkWidget *row = gtk_hbox_new (FALSE, VB_MARGIN);
347 GtkWidget *c = gtk_combo_new ();
348 gtk_combo_set_value_in_list ((GtkCombo *) c, FALSE, FALSE);
349 gtk_combo_set_use_arrows ((GtkCombo *) c, TRUE);
350 gtk_combo_set_use_arrows_always ((GtkCombo *) c, TRUE);
351 gtk_widget_set_size_request (c, 90, -1);
353 { /* Setup strings */
354 GList *sl = NULL;
355 for (int i = 0; spacings[i]; i++) {
356 sl = g_list_prepend (sl, (void *) spacings[i]);
357 }
358 sl = g_list_reverse (sl);
359 gtk_combo_set_popdown_strings ((GtkCombo *) c, sl);
360 g_list_free (sl);
361 }
363 g_signal_connect ( (GObject *) ((GtkCombo *) c)->entry,
364 "changed",
365 (GCallback) sp_text_edit_dialog_line_spacing_changed,
366 dlg );
367 gtk_box_pack_start (GTK_BOX (row), c, FALSE, FALSE, VB_MARGIN);
368 g_object_set_data (G_OBJECT (dlg), "line_spacing", c);
370 gtk_box_pack_start (GTK_BOX (l_vb), row, FALSE, FALSE, VB_MARGIN);
371 }
372 }
374 /* Font preview */
375 GtkWidget *preview = sp_font_preview_new ();
376 gtk_box_pack_start (GTK_BOX (vb), preview, TRUE, TRUE, 4);
377 g_object_set_data (G_OBJECT (dlg), "preview", preview);
378 }
381 // Text tab
382 {
383 GtkWidget *l = gtk_label_new (_("Text"));
384 GtkWidget *vb = gtk_vbox_new (FALSE, VB_MARGIN);
385 gtk_container_set_border_width (GTK_CONTAINER (vb), VB_MARGIN);
386 gtk_notebook_append_page (GTK_NOTEBOOK (nb), vb, l);
388 GtkWidget *scroller = gtk_scrolled_window_new ( NULL, NULL );
389 gtk_scrolled_window_set_policy ( GTK_SCROLLED_WINDOW (scroller),
390 GTK_POLICY_AUTOMATIC,
391 GTK_POLICY_AUTOMATIC );
392 gtk_scrolled_window_set_shadow_type ( GTK_SCROLLED_WINDOW(scroller), GTK_SHADOW_IN );
393 gtk_widget_show (scroller);
395 GtkTextBuffer *tb = gtk_text_buffer_new (NULL);
396 GtkWidget *txt = gtk_text_view_new_with_buffer (tb);
397 gtk_text_view_set_wrap_mode ((GtkTextView *) txt, GTK_WRAP_WORD);
398 #ifdef WITH_GTKSPELL
399 GError *error = NULL;
400 char *errortext = NULL;
401 /* todo: Use computed xml:lang attribute of relevant element, if present, to specify the
402 language (either as 2nd arg of gtkspell_new_attach, or with explicit
403 gtkspell_set_language call in; see advanced.c example in gtkspell docs).
404 sp_text_edit_dialog_read_selection looks like a suitable place. */
405 if (gtkspell_new_attach(GTK_TEXT_VIEW(txt), NULL, &error) == NULL) {
406 g_print("gtkspell error: %s\n", error->message);
407 errortext = g_strdup_printf("GtkSpell was unable to initialize.\n"
408 "%s", error->message);
409 g_error_free(error);
410 }
411 #endif
412 gtk_widget_set_size_request (txt, -1, 64);
413 gtk_text_view_set_editable (GTK_TEXT_VIEW (txt), TRUE);
414 gtk_container_add (GTK_CONTAINER (scroller), txt);
415 gtk_box_pack_start (GTK_BOX (vb), scroller, TRUE, TRUE, 0);
416 g_signal_connect ( G_OBJECT (tb), "changed",
417 G_CALLBACK (sp_text_edit_dialog_text_changed), dlg );
418 g_signal_connect (G_OBJECT (txt), "focus-in-event", G_CALLBACK (text_view_focus_in), dlg);
419 g_signal_connect (G_OBJECT (txt), "focus-out-event", G_CALLBACK (text_view_focus_out), dlg);
420 g_object_set_data (G_OBJECT (dlg), "text", tb);
421 g_object_set_data (G_OBJECT (dlg), "textw", txt);
422 }
424 /* Buttons */
425 GtkWidget *hb = gtk_hbox_new (FALSE, VB_MARGIN);
426 gtk_container_set_border_width (GTK_CONTAINER (hb), 4);
427 gtk_box_pack_start (GTK_BOX (mainvb), hb, FALSE, FALSE, 0);
429 {
430 GtkWidget *b = gtk_button_new_with_label (_("Set as default"));
431 g_signal_connect ( G_OBJECT (b), "clicked",
432 G_CALLBACK (sp_text_edit_dialog_set_default),
433 dlg );
434 gtk_box_pack_start (GTK_BOX (hb), b, FALSE, FALSE, 0);
435 g_object_set_data (G_OBJECT (dlg), "default", b);
436 }
438 {
439 GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
440 g_signal_connect ( G_OBJECT (b), "clicked",
441 G_CALLBACK (sp_text_edit_dialog_close), dlg );
442 gtk_box_pack_end (GTK_BOX (hb), b, FALSE, FALSE, 0);
443 }
445 {
446 GtkWidget *b = gtk_button_new_from_stock (GTK_STOCK_APPLY);
447 GTK_WIDGET_SET_FLAGS (b, GTK_CAN_DEFAULT | GTK_HAS_DEFAULT);
448 g_signal_connect ( G_OBJECT (b), "clicked",
449 G_CALLBACK (sp_text_edit_dialog_apply), dlg );
450 gtk_box_pack_end ( GTK_BOX (hb), b, FALSE, FALSE, 0 );
451 g_object_set_data (G_OBJECT (dlg), "apply", b);
452 }
454 g_signal_connect ( G_OBJECT (INKSCAPE), "modify_selection",
455 G_CALLBACK (sp_text_edit_dialog_selection_modified), dlg);
456 g_signal_connect ( G_OBJECT (INKSCAPE), "change_selection",
457 G_CALLBACK (sp_text_edit_dialog_selection_changed), dlg);
458 g_signal_connect (INKSCAPE, "change_subselection", G_CALLBACK (sp_text_edit_dialog_subselection_changed), dlg);
460 gtk_widget_show_all (dlg);
462 sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
463 }
465 if ( wantTiming ) {
466 // Time tracker takes ownership of the timer.
467 AppearTimeTracker *tracker = new AppearTimeTracker(timer, GTK_WIDGET(dlg), "DialogText");
468 tracker->setAutodelete(true);
469 timer = 0;
470 }
472 gtk_window_present ((GtkWindow *) dlg);
474 } // end of sp_text_edit_dialog()
478 static void
479 sp_text_edit_dialog_selection_modified( Inkscape::Application */*inkscape*/,
480 Inkscape::Selection */*sel*/,
481 guint flags,
482 GtkWidget *dlg )
483 {
484 gboolean style, content;
486 style =
487 ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
488 SP_OBJECT_STYLE_MODIFIED_FLAG )) != 0 );
490 content =
491 ((flags & ( SP_OBJECT_CHILD_MODIFIED_FLAG |
492 SP_TEXT_CONTENT_MODIFIED_FLAG )) != 0 );
494 sp_text_edit_dialog_read_selection (dlg, style, content);
496 }
500 static void
501 sp_text_edit_dialog_selection_changed( Inkscape::Application */*inkscape*/,
502 Inkscape::Selection */*sel*/,
503 GtkWidget *dlg )
504 {
505 sp_text_edit_dialog_read_selection (dlg, TRUE, TRUE);
506 }
508 static void sp_text_edit_dialog_subselection_changed( Inkscape::Application */*inkscape*/, SPDesktop */*desktop*/, GtkWidget *dlg )
509 {
510 sp_text_edit_dialog_read_selection (dlg, TRUE, FALSE);
511 }
513 static void
514 sp_text_edit_dialog_update_object_text ( SPItem *text )
515 {
516 GtkTextBuffer *tb;
517 GtkTextIter start, end;
518 gchar *str;
520 tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
522 /* write text */
523 if (gtk_text_buffer_get_modified (tb)) {
524 gtk_text_buffer_get_bounds (tb, &start, &end);
525 str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
526 sp_te_set_repr_text_multiline (text, str);
527 g_free (str);
528 gtk_text_buffer_set_modified (tb, FALSE);
529 }
530 }
532 SPCSSAttr *
533 sp_get_text_dialog_style ()
534 {
535 GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
537 SPCSSAttr *css = sp_repr_css_attr_new ();
539 /* font */
540 font_instance *font = sp_font_selector_get_font (SP_FONT_SELECTOR (fontsel));
542 if ( font ) {
543 Glib::ustring fontName = font_factory::Default()->ConstructFontSpecification(font);
544 sp_repr_css_set_property (css, "-inkscape-font-specification", fontName.c_str());
546 gchar c[256];
548 font->Family(c, 256);
549 sp_repr_css_set_property (css, "font-family", c);
551 font->Attribute( "weight", c, 256);
552 sp_repr_css_set_property (css, "font-weight", c);
554 font->Attribute("style", c, 256);
555 sp_repr_css_set_property (css, "font-style", c);
557 font->Attribute("stretch", c, 256);
558 sp_repr_css_set_property (css, "font-stretch", c);
560 font->Attribute("variant", c, 256);
561 sp_repr_css_set_property (css, "font-variant", c);
563 Inkscape::CSSOStringStream os;
564 os << sp_font_selector_get_size (SP_FONT_SELECTOR (fontsel)) << "px"; // must specify px, see inkscape bug 1221626 and 1610103
565 sp_repr_css_set_property (css, "font-size", os.str().c_str());
567 font->Unref();
568 font=NULL;
569 }
571 /* Layout */
572 GtkWidget *b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "text_anchor_start");
574 // Align Left
575 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
576 sp_repr_css_set_property (css, "text-anchor", "start");
577 sp_repr_css_set_property (css, "text-align", "start");
578 } else {
579 // Align Center
580 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
581 "text_anchor_middle");
582 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
583 sp_repr_css_set_property (css, "text-anchor", "middle");
584 sp_repr_css_set_property (css, "text-align", "center");
585 } else {
586 // Align Right
587 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg),
588 "text_anchor_end");
589 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
590 sp_repr_css_set_property (css, "text-anchor", "end");
591 sp_repr_css_set_property (css, "text-align", "end");
592 } else {
593 // Align Justify
594 sp_repr_css_set_property (css, "text-anchor", "start");
595 sp_repr_css_set_property (css, "text-align", "justify");
596 }
597 }
598 }
600 b = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
602 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (b))) {
603 sp_repr_css_set_property (css, "writing-mode", "lr");
604 } else {
605 sp_repr_css_set_property (css, "writing-mode", "tb");
606 }
608 // Note that CSS 1.1 does not support line-height; we set it for consistency, but also set
609 // sodipodi:linespacing for backwards compatibility; in 1.2 we use line-height for flowtext
610 GtkWidget *combo = (GtkWidget*)g_object_get_data ((GObject *) dlg, "line_spacing");
611 const char *sstr = gtk_entry_get_text ((GtkEntry *) ((GtkCombo *) (combo))->entry);
612 sp_repr_css_set_property (css, "line-height", sstr);
614 return css;
615 }
618 static void
619 sp_text_edit_dialog_set_default( GtkButton */*button*/, GtkWidget *dlg )
620 {
621 GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
623 SPCSSAttr *css = sp_get_text_dialog_style ();
624 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
626 g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
627 prefs->mergeStyle("/tools/text/style", css);
628 g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
630 sp_repr_css_attr_unref (css);
632 gtk_widget_set_sensitive (def, FALSE);
633 }
637 static void
638 sp_text_edit_dialog_apply( GtkButton */*button*/, GtkWidget *dlg )
639 {
640 g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
642 GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
643 GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
644 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
646 unsigned items = 0;
647 const GSList *item_list = sp_desktop_selection(desktop)->itemList();
648 SPCSSAttr *css = sp_get_text_dialog_style ();
649 sp_desktop_set_style(desktop, css, true);
651 for (; item_list != NULL; item_list = item_list->next) {
652 // apply style to the reprs of all text objects in the selection
653 if (SP_IS_TEXT (item_list->data)) {
655 // backwards compatibility:
656 reinterpret_cast<SPObject*>(item_list->data)->getRepr()->setAttribute("sodipodi:linespacing", sp_repr_css_property (css, "line-height", NULL));
658 ++items;
659 }
660 else if (SP_IS_FLOWTEXT (item_list->data))
661 // no need to set sodipodi:linespacing, because Inkscape never supported it on flowtext
662 ++items;
663 }
665 if (items == 0) {
666 // no text objects; apply style to prefs for new objects
667 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
668 prefs->mergeStyle("/tools/text/style", css);
669 gtk_widget_set_sensitive (def, FALSE);
670 } else if (items == 1) {
671 /* exactly one text object; now set its text, too */
672 SPItem *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->singleItem();
673 if (SP_IS_TEXT (item) || SP_IS_FLOWTEXT(item)) {
674 sp_text_edit_dialog_update_object_text (item);
675 }
676 }
678 // complete the transaction
679 DocumentUndo::done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_CONTEXT_TEXT,
680 _("Set text style"));
681 gtk_widget_set_sensitive (apply, FALSE);
682 sp_repr_css_attr_unref (css);
683 g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (FALSE));
684 }
686 static void
687 sp_text_edit_dialog_close( GtkButton */*button*/, GtkWidget *dlg )
688 {
689 gtk_widget_destroy (GTK_WIDGET (dlg));
690 }
692 static void
693 sp_text_edit_dialog_read_selection ( GtkWidget *dlg,
694 gboolean dostyle,
695 gboolean docontent )
696 {
697 if (g_object_get_data (G_OBJECT (dlg), "blocked"))
698 return;
700 g_object_set_data (G_OBJECT (dlg), "blocked", GINT_TO_POINTER (TRUE));
702 GtkWidget *notebook = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "notebook");
703 GtkWidget *textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
704 GtkWidget *fontsel = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "fontsel");
705 GtkWidget *preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
706 GtkWidget *apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
707 GtkWidget *def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
709 GtkTextBuffer *tb = (GtkTextBuffer*)g_object_get_data (G_OBJECT (dlg), "text");
711 SPItem *text = sp_ted_get_selected_text_item ();
713 Inkscape::XML::Node *repr;
714 if (text)
715 {
716 guint items = sp_ted_get_selected_text_count ();
717 if (items == 1) {
718 gtk_widget_set_sensitive (textw, TRUE);
719 } else {
720 gtk_widget_set_sensitive (textw, FALSE);
721 }
722 gtk_widget_set_sensitive (apply, FALSE);
723 gtk_widget_set_sensitive (def, TRUE);
725 if (docontent) {
726 gchar *str;
727 str = sp_te_get_string_multiline (text);
729 if (str) {
730 int pos;
731 pos = 0;
733 if (items == 1) {
734 gtk_text_buffer_set_text (tb, str, strlen (str));
735 gtk_text_buffer_set_modified (tb, FALSE);
736 }
737 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
738 g_free (str);
740 } else {
741 gtk_text_buffer_set_text (tb, "", 0);
742 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
743 }
744 } // end of if (docontent)
745 repr = text->getRepr();
747 } else {
748 gtk_widget_set_sensitive (textw, FALSE);
749 gtk_widget_set_sensitive (apply, FALSE);
750 gtk_widget_set_sensitive (def, FALSE);
751 }
753 if (dostyle) {
755 // create temporary style
756 SPStyle *query = sp_style_new (SP_ACTIVE_DOCUMENT);
757 // query style from desktop into it. This returns a result flag and fills query with the style of subselection, if any, or selection
758 //int result_fontspec = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONT_SPECIFICATION);
759 int result_family = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTFAMILY);
760 int result_style = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTSTYLE);
761 int result_numbers = sp_desktop_query_style (SP_ACTIVE_DESKTOP, query, QUERY_STYLE_PROPERTY_FONTNUMBERS);
763 // If querying returned nothing, read the style from the text tool prefs (default style for new texts)
764 // (Ok to not get a font specification - must just rely on the family and style in that case)
765 if (result_family == QUERY_STYLE_NOTHING || result_style == QUERY_STYLE_NOTHING
766 || result_numbers == QUERY_STYLE_NOTHING) {
767 sp_style_read_from_prefs(query, "/tools/text");
768 }
770 // FIXME: process result_family/style == QUERY_STYLE_MULTIPLE_DIFFERENT by showing "Many" in the lists
772 // Get a font_instance using the font-specification attribute stored in SPStyle if available
773 font_instance *font = font_factory::Default()->FaceFromStyle(query);
776 if (font) {
777 // the font is oversized, so we need to pass the true size separately
778 sp_font_selector_set_font (SP_FONT_SELECTOR (fontsel), font, query->font_size.computed);
779 sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fontsel));
780 font->Unref();
781 font=NULL;
782 }
784 GtkWidget *b;
785 if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START) {
786 if (query->text_align.computed == SP_CSS_TEXT_ALIGN_JUSTIFY) {
787 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_justify" );
788 } else {
789 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_start" );
790 }
791 } else if (query->text_anchor.computed == SP_CSS_TEXT_ANCHOR_MIDDLE) {
792 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_middle" );
793 } else {
794 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "text_anchor_end" );
795 }
796 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
798 if (query->writing_mode.computed == SP_CSS_WRITING_MODE_LR_TB) {
799 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_HORIZONTAL );
800 } else {
801 b = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), INKSCAPE_ICON_FORMAT_TEXT_DIRECTION_VERTICAL );
802 }
803 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (b), TRUE);
805 GtkWidget *combo = (GtkWidget*)g_object_get_data ( G_OBJECT (dlg), "line_spacing" );
806 double height;
807 if (query->line_height.normal) height = Inkscape::Text::Layout::LINE_HEIGHT_NORMAL;
808 else if (query->line_height.unit == SP_CSS_UNIT_PERCENT)
809 height = query->line_height.value;
810 else height = query->line_height.computed;
811 gchar *sstr = g_strdup_printf ("%d%%", (int) floor(height * 100 + 0.5));
812 gtk_entry_set_text ((GtkEntry *) ((GtkCombo *) (combo))->entry, sstr);
813 g_free(sstr);
815 sp_style_unref(query);
816 }
818 g_object_set_data (G_OBJECT (dlg), "blocked", NULL);
819 }
822 static void
823 sp_text_edit_dialog_text_changed (GtkTextBuffer *tb, GtkWidget *dlg)
824 {
825 GtkWidget *textw, *preview, *apply, *def;
826 GtkTextIter start, end;
827 gchar *str;
829 if (g_object_get_data (G_OBJECT (dlg), "blocked"))
830 return;
832 SPItem *text = sp_ted_get_selected_text_item ();
834 textw = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "textw");
835 preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
836 apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
837 def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
839 gtk_text_buffer_get_bounds (tb, &start, &end);
840 str = gtk_text_buffer_get_text (tb, &start, &end, TRUE);
842 if (str && *str) {
843 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), str);
844 } else {
845 sp_font_preview_set_phrase (SP_FONT_PREVIEW (preview), NULL);
846 }
847 g_free (str);
849 if (text) {
850 gtk_widget_set_sensitive (apply, TRUE);
851 }
852 gtk_widget_set_sensitive (def, TRUE);
854 } // end of sp_text_edit_dialog_text_changed()
856 void
857 sp_text_edit_dialog_default_set_insensitive ()
858 {
859 if (!dlg) return;
860 gpointer data = g_object_get_data (G_OBJECT (dlg), "default");
861 if (!data) return;
862 gtk_widget_set_sensitive (GTK_WIDGET (data), FALSE);
863 }
865 static void
866 sp_text_edit_dialog_font_changed ( SPFontSelector *fsel,
867 font_instance *font,
868 GtkWidget *dlg )
869 {
870 GtkWidget *preview, *apply, *def;
872 if (g_object_get_data (G_OBJECT (dlg), "blocked"))
873 return;
875 SPItem *text = sp_ted_get_selected_text_item ();
877 preview = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "preview");
878 apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
879 def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
881 sp_font_preview_set_font (SP_FONT_PREVIEW (preview), font, SP_FONT_SELECTOR(fsel));
883 if (text)
884 {
885 gtk_widget_set_sensitive (apply, TRUE);
886 }
887 gtk_widget_set_sensitive (def, TRUE);
889 } // end of sp_text_edit_dialog_font_changed()
893 static void
894 sp_text_edit_dialog_any_toggled( GtkToggleButton */*tb*/, GtkWidget *dlg )
895 {
896 GtkWidget *apply, *def;
898 if (g_object_get_data (G_OBJECT (dlg), "blocked"))
899 return;
901 SPItem *text = sp_ted_get_selected_text_item ();
903 apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
904 def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
906 if (text) {
907 gtk_widget_set_sensitive (apply, TRUE);
908 }
909 gtk_widget_set_sensitive (def, TRUE);
910 }
914 static void
915 sp_text_edit_dialog_line_spacing_changed( GtkEditable */*editable*/, GtkWidget *dlg )
916 {
917 GtkWidget *apply, *def;
919 if (g_object_get_data ((GObject *) dlg, "blocked"))
920 return;
922 SPItem *text = sp_ted_get_selected_text_item ();
924 apply = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "apply");
925 def = (GtkWidget*)g_object_get_data (G_OBJECT (dlg), "default");
927 if (text) {
928 gtk_widget_set_sensitive (apply, TRUE);
929 }
930 gtk_widget_set_sensitive (def, TRUE);
931 }
935 static SPItem *
936 sp_ted_get_selected_text_item (void)
937 {
938 if (!SP_ACTIVE_DESKTOP)
939 return NULL;
941 for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
942 item != NULL;
943 item = item->next)
944 {
945 if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
946 return SP_ITEM (item->data);
947 }
949 return NULL;
950 }
954 static unsigned
955 sp_ted_get_selected_text_count (void)
956 {
957 if (!SP_ACTIVE_DESKTOP)
958 return 0;
960 unsigned int items = 0;
962 for (const GSList *item = sp_desktop_selection(SP_ACTIVE_DESKTOP)->itemList();
963 item != NULL;
964 item = item->next)
965 {
966 if (SP_IS_TEXT(item->data) || SP_IS_FLOWTEXT(item->data))
967 ++items;
968 }
970 return items;
971 }
973 /*
974 Local Variables:
975 mode:c++
976 c-file-style:"stroustrup"
977 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
978 indent-tabs-mode:nil
979 fill-column:99
980 End:
981 */
982 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :