/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * PikaTextStyleEditor * Copyright (C) 2010 Michael Natterer * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #include "core/pika.h" #include "core/pikacontainer.h" #include "core/pikacontext.h" #include "text/pikatext.h" #include "text/pikafont.h" #include "pikacolorpanel.h" #include "pikacontainerentry.h" #include "pikacontainerview.h" #include "pikatextbuffer.h" #include "pikatextstyleeditor.h" #include "pikatexttag.h" #include "pikawidgets-utils.h" #include "pika-intl.h" enum { PROP_0, PROP_PIKA, PROP_TEXT, PROP_BUFFER, PROP_FONTS, PROP_RESOLUTION_X, PROP_RESOLUTION_Y }; static void pika_text_style_editor_constructed (GObject *object); static void pika_text_style_editor_dispose (GObject *object); static void pika_text_style_editor_finalize (GObject *object); static void pika_text_style_editor_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_text_style_editor_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static GtkWidget * pika_text_style_editor_create_toggle (PikaTextStyleEditor *editor, GtkTextTag *tag, const gchar *icon_name, const gchar *tooltip); static void pika_text_style_editor_clear_tags (GtkButton *button, PikaTextStyleEditor *editor); static void pika_text_style_editor_font_changed (PikaContext *context, PikaFont *font, PikaTextStyleEditor *editor); static void pika_text_style_editor_set_font (PikaTextStyleEditor *editor, GtkTextTag *font_tag); static void pika_text_style_editor_set_default_font (PikaTextStyleEditor *editor); static void pika_text_style_editor_color_changed (PikaColorButton *button, PikaTextStyleEditor *editor); static void pika_text_style_editor_set_color (PikaTextStyleEditor *editor, GtkTextTag *color_tag); static void pika_text_style_editor_set_default_color (PikaTextStyleEditor *editor); static void pika_text_style_editor_tag_toggled (GtkToggleButton *toggle, PikaTextStyleEditor *editor); static void pika_text_style_editor_set_toggle (PikaTextStyleEditor *editor, GtkToggleButton *toggle, gboolean active); static void pika_text_style_editor_size_changed (PikaSizeEntry *entry, PikaTextStyleEditor *editor); static void pika_text_style_editor_set_size (PikaTextStyleEditor *editor, GtkTextTag *size_tag); static void pika_text_style_editor_set_default_size (PikaTextStyleEditor *editor); static void pika_text_style_editor_baseline_changed (GtkAdjustment *adjustment, PikaTextStyleEditor *editor); static void pika_text_style_editor_set_baseline (PikaTextStyleEditor *editor, GtkTextTag *baseline_tag); static void pika_text_style_editor_kerning_changed (GtkAdjustment *adjustment, PikaTextStyleEditor *editor); static void pika_text_style_editor_set_kerning (PikaTextStyleEditor *editor, GtkTextTag *kerning_tag); static void pika_text_style_editor_update (PikaTextStyleEditor *editor); static gboolean pika_text_style_editor_update_idle (PikaTextStyleEditor *editor); G_DEFINE_TYPE (PikaTextStyleEditor, pika_text_style_editor, GTK_TYPE_BOX) #define parent_class pika_text_style_editor_parent_class static void pika_text_style_editor_class_init (PikaTextStyleEditorClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->constructed = pika_text_style_editor_constructed; object_class->dispose = pika_text_style_editor_dispose; object_class->finalize = pika_text_style_editor_finalize; object_class->set_property = pika_text_style_editor_set_property; object_class->get_property = pika_text_style_editor_get_property; g_object_class_install_property (object_class, PROP_PIKA, g_param_spec_object ("pika", NULL, NULL, PIKA_TYPE_PIKA, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_TEXT, g_param_spec_object ("text", NULL, NULL, PIKA_TYPE_TEXT, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_BUFFER, g_param_spec_object ("buffer", NULL, NULL, PIKA_TYPE_TEXT_BUFFER, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_FONTS, g_param_spec_object ("fonts", NULL, NULL, PIKA_TYPE_CONTAINER, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_RESOLUTION_X, g_param_spec_double ("resolution-x", NULL, NULL, PIKA_MIN_RESOLUTION, PIKA_MAX_RESOLUTION, 1.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_RESOLUTION_Y, g_param_spec_double ("resolution-y", NULL, NULL, PIKA_MIN_RESOLUTION, PIKA_MAX_RESOLUTION, 1.0, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); gtk_widget_class_set_css_name (widget_class, "PikaTextStyleEditor"); } static void pika_text_style_editor_init (PikaTextStyleEditor *editor) { GtkWidget *image; PikaRGB color; gtk_orientable_set_orientation (GTK_ORIENTABLE (editor), GTK_ORIENTATION_VERTICAL); gtk_box_set_spacing (GTK_BOX (editor), 2); /* upper row */ editor->upper_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); gtk_box_pack_start (GTK_BOX (editor), editor->upper_hbox, FALSE, FALSE, 0); gtk_widget_show (editor->upper_hbox); editor->font_entry = pika_container_entry_new (NULL, NULL, PIKA_VIEW_SIZE_SMALL, 1); gtk_box_pack_start (GTK_BOX (editor->upper_hbox), editor->font_entry, FALSE, FALSE, 0); gtk_widget_show (editor->font_entry); pika_help_set_help_data (editor->font_entry, _("Change font of selected text"), NULL); editor->size_entry = pika_size_entry_new (1, 0, "%a", TRUE, FALSE, FALSE, 10, PIKA_SIZE_ENTRY_UPDATE_SIZE); gtk_box_pack_start (GTK_BOX (editor->upper_hbox), editor->size_entry, FALSE, FALSE, 0); gtk_widget_show (editor->size_entry); pika_help_set_help_data (editor->size_entry, _("Change size of selected text"), NULL); g_signal_connect (editor->size_entry, "value-changed", G_CALLBACK (pika_text_style_editor_size_changed), editor); /* lower row */ editor->lower_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); gtk_box_pack_start (GTK_BOX (editor), editor->lower_hbox, FALSE, FALSE, 0); gtk_widget_show (editor->lower_hbox); editor->clear_button = gtk_button_new (); gtk_widget_set_can_focus (editor->clear_button, FALSE); gtk_box_pack_start (GTK_BOX (editor->lower_hbox), editor->clear_button, FALSE, FALSE, 0); gtk_widget_show (editor->clear_button); pika_help_set_help_data (editor->clear_button, _("Clear style of selected text"), NULL); g_signal_connect (editor->clear_button, "clicked", G_CALLBACK (pika_text_style_editor_clear_tags), editor); image = gtk_image_new_from_icon_name ("edit-clear", GTK_ICON_SIZE_MENU); gtk_container_add (GTK_CONTAINER (editor->clear_button), image); gtk_widget_show (image); pika_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); editor->color_button = pika_color_panel_new (_("Change color of selected text"), &color, PIKA_COLOR_AREA_FLAT, 20, 20); pika_widget_set_fully_opaque (editor->color_button, TRUE); gtk_box_pack_end (GTK_BOX (editor->lower_hbox), editor->color_button, FALSE, FALSE, 0); gtk_widget_show (editor->color_button); pika_help_set_help_data (editor->color_button, _("Change color of selected text"), NULL); g_signal_connect (editor->color_button, "color-changed", G_CALLBACK (pika_text_style_editor_color_changed), editor); editor->kerning_adjustment = gtk_adjustment_new (0.0, -1000.0, 1000.0, 1.0, 10.0, 0.0); editor->kerning_spinbutton = pika_spin_button_new (editor->kerning_adjustment, 1.0, 1); gtk_entry_set_width_chars (GTK_ENTRY (editor->kerning_spinbutton), 5); gtk_box_pack_end (GTK_BOX (editor->lower_hbox), editor->kerning_spinbutton, FALSE, FALSE, 0); gtk_widget_show (editor->kerning_spinbutton); pika_help_set_help_data (editor->kerning_spinbutton, _("Change kerning of selected text"), NULL); g_signal_connect (editor->kerning_adjustment, "value-changed", G_CALLBACK (pika_text_style_editor_kerning_changed), editor); editor->baseline_adjustment = gtk_adjustment_new (0.0, -1000.0, 1000.0, 1.0, 10.0, 0.0); editor->baseline_spinbutton = pika_spin_button_new (editor->baseline_adjustment, 1.0, 1); gtk_entry_set_width_chars (GTK_ENTRY (editor->baseline_spinbutton), 5); gtk_box_pack_end (GTK_BOX (editor->lower_hbox), editor->baseline_spinbutton, FALSE, FALSE, 0); gtk_widget_show (editor->baseline_spinbutton); pika_help_set_help_data (editor->baseline_spinbutton, _("Change baseline of selected text"), NULL); g_signal_connect (editor->baseline_adjustment, "value-changed", G_CALLBACK (pika_text_style_editor_baseline_changed), editor); } static void pika_text_style_editor_constructed (GObject *object) { PikaTextStyleEditor *editor = PIKA_TEXT_STYLE_EDITOR (object); G_OBJECT_CLASS (parent_class)->constructed (object); pika_assert (PIKA_IS_PIKA (editor->pika)); pika_assert (PIKA_IS_CONTAINER (editor->fonts)); pika_assert (PIKA_IS_TEXT (editor->text)); pika_assert (PIKA_IS_TEXT_BUFFER (editor->buffer)); editor->context = pika_context_new (editor->pika, "text style editor", NULL); g_signal_connect (editor->context, "font-changed", G_CALLBACK (pika_text_style_editor_font_changed), editor); pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (editor->size_entry), 0, editor->resolution_y, TRUE); /* use the global user context so we get the global FG/BG colors */ pika_color_panel_set_context (PIKA_COLOR_PANEL (editor->color_button), pika_get_user_context (editor->pika)); pika_container_view_set_container (PIKA_CONTAINER_VIEW (editor->font_entry), editor->fonts); pika_container_view_set_context (PIKA_CONTAINER_VIEW (editor->font_entry), editor->context); pika_text_style_editor_create_toggle (editor, editor->buffer->bold_tag, PIKA_ICON_FORMAT_TEXT_BOLD, _("Bold")); pika_text_style_editor_create_toggle (editor, editor->buffer->italic_tag, PIKA_ICON_FORMAT_TEXT_ITALIC, _("Italic")); pika_text_style_editor_create_toggle (editor, editor->buffer->underline_tag, PIKA_ICON_FORMAT_TEXT_UNDERLINE, _("Underline")); pika_text_style_editor_create_toggle (editor, editor->buffer->strikethrough_tag, PIKA_ICON_FORMAT_TEXT_STRIKETHROUGH, _("Strikethrough")); g_signal_connect_swapped (editor->text, "notify::font", G_CALLBACK (pika_text_style_editor_update), editor); g_signal_connect_swapped (editor->text, "notify::font-size", G_CALLBACK (pika_text_style_editor_update), editor); g_signal_connect_swapped (editor->text, "notify::font-size-unit", G_CALLBACK (pika_text_style_editor_update), editor); g_signal_connect_swapped (editor->text, "notify::color", G_CALLBACK (pika_text_style_editor_update), editor); g_signal_connect_data (editor->buffer, "changed", G_CALLBACK (pika_text_style_editor_update), editor, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_data (editor->buffer, "apply-tag", G_CALLBACK (pika_text_style_editor_update), editor, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_data (editor->buffer, "remove-tag", G_CALLBACK (pika_text_style_editor_update), editor, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED); g_signal_connect_data (editor->buffer, "mark-set", G_CALLBACK (pika_text_style_editor_update), editor, 0, G_CONNECT_AFTER | G_CONNECT_SWAPPED); pika_text_style_editor_update (editor); } static void pika_text_style_editor_dispose (GObject *object) { PikaTextStyleEditor *editor = PIKA_TEXT_STYLE_EDITOR (object); if (editor->text) { g_signal_handlers_disconnect_by_func (editor->text, pika_text_style_editor_update, editor); } if (editor->buffer) { g_signal_handlers_disconnect_by_func (editor->buffer, pika_text_style_editor_update, editor); } if (editor->context) { g_signal_handlers_disconnect_by_func (editor->context, pika_text_style_editor_font_changed, editor); } if (editor->update_idle_id) { g_source_remove (editor->update_idle_id); editor->update_idle_id = 0; } G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_text_style_editor_finalize (GObject *object) { PikaTextStyleEditor *editor = PIKA_TEXT_STYLE_EDITOR (object); g_clear_object (&editor->context); g_clear_object (&editor->text); g_clear_object (&editor->buffer); g_clear_object (&editor->fonts); if (editor->toggles) { g_list_free (editor->toggles); editor->toggles = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_text_style_editor_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaTextStyleEditor *editor = PIKA_TEXT_STYLE_EDITOR (object); switch (property_id) { case PROP_PIKA: editor->pika = g_value_get_object (value); /* don't ref */ break; case PROP_TEXT: editor->text = g_value_dup_object (value); break; case PROP_BUFFER: editor->buffer = g_value_dup_object (value); break; case PROP_FONTS: editor->fonts = g_value_dup_object (value); break; case PROP_RESOLUTION_X: editor->resolution_x = g_value_get_double (value); break; case PROP_RESOLUTION_Y: editor->resolution_y = g_value_get_double (value); if (editor->size_entry) pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (editor->size_entry), 0, editor->resolution_y, TRUE); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_text_style_editor_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaTextStyleEditor *editor = PIKA_TEXT_STYLE_EDITOR (object); switch (property_id) { case PROP_PIKA: g_value_set_object (value, editor->pika); break; case PROP_TEXT: g_value_set_object (value, editor->text); break; case PROP_BUFFER: g_value_set_object (value, editor->buffer); break; case PROP_FONTS: g_value_set_object (value, editor->fonts); break; case PROP_RESOLUTION_X: g_value_set_double (value, editor->resolution_x); break; case PROP_RESOLUTION_Y: g_value_set_double (value, editor->resolution_y); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /* public functions */ GtkWidget * pika_text_style_editor_new (Pika *pika, PikaText *text, PikaTextBuffer *buffer, PikaContainer *fonts, gdouble resolution_x, gdouble resolution_y) { g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); g_return_val_if_fail (PIKA_IS_TEXT (text), NULL); g_return_val_if_fail (PIKA_IS_TEXT_BUFFER (buffer), NULL); g_return_val_if_fail (resolution_x > 0.0, NULL); g_return_val_if_fail (resolution_y > 0.0, NULL); return g_object_new (PIKA_TYPE_TEXT_STYLE_EDITOR, "pika", pika, "text", text, "buffer", buffer, "fonts", fonts, "resolution-x", resolution_x, "resolution-y", resolution_y, NULL); } GList * pika_text_style_editor_list_tags (PikaTextStyleEditor *editor, GList **remove_tags) { GList *toggles; GList *tags = NULL; g_return_val_if_fail (PIKA_IS_TEXT_STYLE_EDITOR (editor), NULL); g_return_val_if_fail (remove_tags != NULL, NULL); *remove_tags = NULL; for (toggles = editor->toggles; toggles; toggles = g_list_next (toggles)) { GtkTextTag *tag = g_object_get_data (toggles->data, "tag"); if (gtk_toggle_button_get_active (toggles->data)) { tags = g_list_prepend (tags, tag); } else { *remove_tags = g_list_prepend (*remove_tags, tag); } } { GList *list; gdouble pixels; for (list = editor->buffer->size_tags; list; list = g_list_next (list)) *remove_tags = g_list_prepend (*remove_tags, list->data); pixels = pika_size_entry_get_refval (PIKA_SIZE_ENTRY (editor->size_entry), 0); if (pixels != 0.0) { GtkTextTag *tag; gdouble points; points = pika_units_to_points (pixels, PIKA_UNIT_PIXEL, editor->resolution_y); tag = pika_text_buffer_get_size_tag (editor->buffer, PANGO_SCALE * points); tags = g_list_prepend (tags, tag); } } { GList *list; const gchar *font_name; for (list = editor->buffer->font_tags; list; list = g_list_next (list)) *remove_tags = g_list_prepend (*remove_tags, list->data); font_name = pika_context_get_font_name (editor->context); if (font_name) { GtkTextTag *tag; tag = pika_text_buffer_get_font_tag (editor->buffer, font_name); tags = g_list_prepend (tags, tag); } } { GList *list; PikaRGB color; for (list = editor->buffer->color_tags; list; list = g_list_next (list)) *remove_tags = g_list_prepend (*remove_tags, list->data); pika_color_button_get_color (PIKA_COLOR_BUTTON (editor->color_button), &color); if (TRUE) /* FIXME should have "inconsistent" state as for font and size */ { GtkTextTag *tag; tag = pika_text_buffer_get_color_tag (editor->buffer, &color); tags = g_list_prepend (tags, tag); } } *remove_tags = g_list_reverse (*remove_tags); return g_list_reverse (tags); } /* private functions */ static GtkWidget * pika_text_style_editor_create_toggle (PikaTextStyleEditor *editor, GtkTextTag *tag, const gchar *icon_name, const gchar *tooltip) { GtkWidget *toggle; GtkWidget *image; toggle = gtk_toggle_button_new (); gtk_widget_set_can_focus (toggle, FALSE); gtk_box_pack_start (GTK_BOX (editor->lower_hbox), toggle, FALSE, FALSE, 0); gtk_widget_show (toggle); pika_help_set_help_data (toggle, tooltip, NULL); editor->toggles = g_list_append (editor->toggles, toggle); g_object_set_data (G_OBJECT (toggle), "tag", tag); g_signal_connect (toggle, "toggled", G_CALLBACK (pika_text_style_editor_tag_toggled), editor); image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU); gtk_container_add (GTK_CONTAINER (toggle), image); gtk_widget_show (image); return toggle; } static void pika_text_style_editor_clear_tags (GtkButton *button, PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); if (gtk_text_buffer_get_has_selection (buffer)) { GtkTextIter start, end; gtk_text_buffer_get_selection_bounds (buffer, &start, &end); gtk_text_buffer_begin_user_action (buffer); gtk_text_buffer_remove_all_tags (buffer, &start, &end); gtk_text_buffer_end_user_action (buffer); } } static void pika_text_style_editor_font_changed (PikaContext *context, PikaFont *font, PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); GList *insert_tags; GList *remove_tags; if (gtk_text_buffer_get_has_selection (buffer)) { GtkTextIter start, end; gtk_text_buffer_get_selection_bounds (buffer, &start, &end); pika_text_buffer_set_font (editor->buffer, &start, &end, pika_context_get_font_name (context)); } insert_tags = pika_text_style_editor_list_tags (editor, &remove_tags); pika_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); } static void pika_text_style_editor_set_font (PikaTextStyleEditor *editor, GtkTextTag *font_tag) { gchar *font = NULL; if (font_tag) font = pika_text_tag_get_font (font_tag); g_signal_handlers_block_by_func (editor->context, pika_text_style_editor_font_changed, editor); pika_context_set_font_name (editor->context, font); g_signal_handlers_unblock_by_func (editor->context, pika_text_style_editor_font_changed, editor); g_free (font); } static void pika_text_style_editor_set_default_font (PikaTextStyleEditor *editor) { g_signal_handlers_block_by_func (editor->context, pika_text_style_editor_font_changed, editor); pika_context_set_font (editor->context, editor->text->font); g_signal_handlers_unblock_by_func (editor->context, pika_text_style_editor_font_changed, editor); } static void pika_text_style_editor_color_changed (PikaColorButton *button, PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); GList *insert_tags; GList *remove_tags; if (gtk_text_buffer_get_has_selection (buffer)) { GtkTextIter start, end; PikaRGB color; gtk_text_buffer_get_selection_bounds (buffer, &start, &end); pika_color_button_get_color (button, &color); pika_text_buffer_set_color (editor->buffer, &start, &end, &color); } insert_tags = pika_text_style_editor_list_tags (editor, &remove_tags); pika_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); } static void pika_text_style_editor_set_color (PikaTextStyleEditor *editor, GtkTextTag *color_tag) { PikaRGB color; pika_rgba_set (&color, 0.0, 0.0, 0.0, 1.0); if (color_tag) pika_text_tag_get_fg_color (color_tag, &color); g_signal_handlers_block_by_func (editor->color_button, pika_text_style_editor_color_changed, editor); pika_color_button_set_color (PIKA_COLOR_BUTTON (editor->color_button), &color); /* FIXME should have "inconsistent" state as for font and size */ g_signal_handlers_unblock_by_func (editor->color_button, pika_text_style_editor_color_changed, editor); } static void pika_text_style_editor_set_default_color (PikaTextStyleEditor *editor) { g_signal_handlers_block_by_func (editor->color_button, pika_text_style_editor_color_changed, editor); pika_color_button_set_color (PIKA_COLOR_BUTTON (editor->color_button), &editor->text->color); g_signal_handlers_unblock_by_func (editor->color_button, pika_text_style_editor_color_changed, editor); } static void pika_text_style_editor_tag_toggled (GtkToggleButton *toggle, PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); GtkTextTag *tag = g_object_get_data (G_OBJECT (toggle), "tag"); GList *insert_tags; GList *remove_tags; if (gtk_text_buffer_get_has_selection (buffer)) { GtkTextIter start, end; gtk_text_buffer_get_selection_bounds (buffer, &start, &end); gtk_text_buffer_begin_user_action (buffer); if (gtk_toggle_button_get_active (toggle)) { gtk_text_buffer_apply_tag (buffer, tag, &start, &end); } else { gtk_text_buffer_remove_tag (buffer, tag, &start, &end); } gtk_text_buffer_end_user_action (buffer); } insert_tags = pika_text_style_editor_list_tags (editor, &remove_tags); pika_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); } static void pika_text_style_editor_set_toggle (PikaTextStyleEditor *editor, GtkToggleButton *toggle, gboolean active) { g_signal_handlers_block_by_func (toggle, pika_text_style_editor_tag_toggled, editor); gtk_toggle_button_set_active (toggle, active); g_signal_handlers_unblock_by_func (toggle, pika_text_style_editor_tag_toggled, editor); } static void pika_text_style_editor_size_changed (PikaSizeEntry *entry, PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); GList *insert_tags; GList *remove_tags; GtkTextIter start, end; gdouble points; points = pika_units_to_points (pika_size_entry_get_refval (entry, 0), PIKA_UNIT_PIXEL, editor->resolution_y); if (gtk_text_buffer_get_has_selection (buffer)) { gtk_text_buffer_get_selection_bounds (buffer, &start, &end); } else { gtk_text_buffer_get_iter_at_offset (buffer, &start, 0); gtk_text_buffer_get_iter_at_offset (buffer, &end, -1); } pika_text_buffer_set_size (editor->buffer, &start, &end, PANGO_SCALE * points); insert_tags = pika_text_style_editor_list_tags (editor, &remove_tags); pika_text_buffer_set_insert_tags (editor->buffer, insert_tags, remove_tags); } static void pika_text_style_editor_set_size (PikaTextStyleEditor *editor, GtkTextTag *size_tag) { gint size = 0; gdouble pixels; if (size_tag) size = pika_text_tag_get_size (size_tag); g_signal_handlers_block_by_func (editor->size_entry, pika_text_style_editor_size_changed, editor); pixels = pika_units_to_pixels ((gdouble) size / PANGO_SCALE, PIKA_UNIT_POINT, editor->resolution_y); pika_size_entry_set_refval (PIKA_SIZE_ENTRY (editor->size_entry), 0, pixels); if (size == 0) { GtkWidget *spinbutton; spinbutton = pika_size_entry_get_help_widget (PIKA_SIZE_ENTRY (editor->size_entry), 0); gtk_entry_set_text (GTK_ENTRY (spinbutton), ""); } g_signal_handlers_unblock_by_func (editor->size_entry, pika_text_style_editor_size_changed, editor); } static void pika_text_style_editor_set_default_size (PikaTextStyleEditor *editor) { gdouble pixels = pika_units_to_pixels (editor->text->font_size, editor->text->unit, editor->resolution_y); g_signal_handlers_block_by_func (editor->size_entry, pika_text_style_editor_size_changed, editor); pika_size_entry_set_refval (PIKA_SIZE_ENTRY (editor->size_entry), 0, pixels); g_signal_handlers_unblock_by_func (editor->size_entry, pika_text_style_editor_size_changed, editor); } static void pika_text_style_editor_baseline_changed (GtkAdjustment *adjustment, PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); GtkTextIter start, end; if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) { gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer)); gtk_text_buffer_get_end_iter (buffer, &end); } pika_text_buffer_set_baseline (editor->buffer, &start, &end, gtk_adjustment_get_value (adjustment) * PANGO_SCALE); } static void pika_text_style_editor_set_baseline (PikaTextStyleEditor *editor, GtkTextTag *baseline_tag) { gint baseline = 0; if (baseline_tag) baseline = pika_text_tag_get_baseline (baseline_tag); g_signal_handlers_block_by_func (editor->baseline_adjustment, pika_text_style_editor_baseline_changed, editor); if (gtk_adjustment_get_value (editor->baseline_adjustment) != (gdouble) baseline / PANGO_SCALE) { gtk_adjustment_set_value (editor->baseline_adjustment, (gdouble) baseline / PANGO_SCALE); } else { /* make sure the "" really gets replaced */ g_signal_emit_by_name (editor->baseline_adjustment, "value-changed"); } g_signal_handlers_unblock_by_func (editor->baseline_adjustment, pika_text_style_editor_baseline_changed, editor); } static void pika_text_style_editor_kerning_changed (GtkAdjustment *adjustment, PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); GtkTextIter start, end; if (! gtk_text_buffer_get_selection_bounds (buffer, &start, &end)) { gtk_text_buffer_get_iter_at_mark (buffer, &start, gtk_text_buffer_get_insert (buffer)); end = start; gtk_text_iter_forward_char (&end); } pika_text_buffer_set_kerning (editor->buffer, &start, &end, gtk_adjustment_get_value (adjustment) * PANGO_SCALE); } static void pika_text_style_editor_set_kerning (PikaTextStyleEditor *editor, GtkTextTag *kerning_tag) { gint kerning = 0; if (kerning_tag) kerning = pika_text_tag_get_kerning (kerning_tag); g_signal_handlers_block_by_func (editor->kerning_adjustment, pika_text_style_editor_kerning_changed, editor); if (gtk_adjustment_get_value (editor->baseline_adjustment) != (gdouble) kerning / PANGO_SCALE) { gtk_adjustment_set_value (editor->kerning_adjustment, (gdouble) kerning / PANGO_SCALE); } else { /* make sure the "" really gets replaced */ g_signal_emit_by_name (editor->kerning_adjustment, "value-changed"); } g_signal_handlers_unblock_by_func (editor->kerning_adjustment, pika_text_style_editor_kerning_changed, editor); } static void pika_text_style_editor_update (PikaTextStyleEditor *editor) { if (editor->update_idle_id) g_source_remove (editor->update_idle_id); editor->update_idle_id = gdk_threads_add_idle ((GSourceFunc) pika_text_style_editor_update_idle, editor); } static gboolean pika_text_style_editor_update_idle (PikaTextStyleEditor *editor) { GtkTextBuffer *buffer = GTK_TEXT_BUFFER (editor->buffer); if (editor->update_idle_id) { g_source_remove (editor->update_idle_id); editor->update_idle_id = 0; } if (gtk_text_buffer_get_has_selection (buffer)) { GtkTextIter start, end; GtkTextIter iter; GList *list; gboolean any_toggle_active = TRUE; gboolean font_differs = FALSE; gboolean color_differs = FALSE; gboolean size_differs = FALSE; gboolean baseline_differs = FALSE; gboolean kerning_differs = FALSE; GtkTextTag *font_tag = NULL; GtkTextTag *color_tag = NULL; GtkTextTag *size_tag = NULL; GtkTextTag *baseline_tag = NULL; GtkTextTag *kerning_tag = NULL; gtk_text_buffer_get_selection_bounds (buffer, &start, &end); gtk_text_iter_order (&start, &end); /* first, switch all toggles on */ for (list = editor->toggles; list; list = g_list_next (list)) { GtkToggleButton *toggle = list->data; pika_text_style_editor_set_toggle (editor, toggle, TRUE); } /* and get some initial values */ font_tag = pika_text_buffer_get_iter_font (editor->buffer, &start, NULL); color_tag = pika_text_buffer_get_iter_color (editor->buffer, &start, NULL); size_tag = pika_text_buffer_get_iter_size (editor->buffer, &start, NULL); baseline_tag = pika_text_buffer_get_iter_baseline (editor->buffer, &start, NULL); kerning_tag = pika_text_buffer_get_iter_kerning (editor->buffer, &start, NULL); for (iter = start; gtk_text_iter_in_range (&iter, &start, &end); gtk_text_iter_forward_cursor_position (&iter)) { if (any_toggle_active) { any_toggle_active = FALSE; for (list = editor->toggles; list; list = g_list_next (list)) { GtkToggleButton *toggle = list->data; GtkTextTag *tag = g_object_get_data (G_OBJECT (toggle), "tag"); if (! gtk_text_iter_has_tag (&iter, tag)) { pika_text_style_editor_set_toggle (editor, toggle, FALSE); } else { any_toggle_active = TRUE; } } } if (! font_differs) { GtkTextTag *tag; tag = pika_text_buffer_get_iter_font (editor->buffer, &iter, NULL); if (tag != font_tag) font_differs = TRUE; } if (! color_differs) { GtkTextTag *tag; tag = pika_text_buffer_get_iter_color (editor->buffer, &iter, NULL); if (tag != color_tag) color_differs = TRUE; } if (! size_differs) { GtkTextTag *tag; tag = pika_text_buffer_get_iter_size (editor->buffer, &iter, NULL); if (tag != size_tag) size_differs = TRUE; } if (! baseline_differs) { GtkTextTag *tag; tag = pika_text_buffer_get_iter_baseline (editor->buffer, &iter, NULL); if (tag != baseline_tag) baseline_differs = TRUE; } if (! kerning_differs) { GtkTextTag *tag; tag = pika_text_buffer_get_iter_kerning (editor->buffer, &iter, NULL); if (tag != kerning_tag) kerning_differs = TRUE; } if (! any_toggle_active && color_differs && font_differs && size_differs && baseline_differs && kerning_differs) break; } if (font_differs) pika_text_style_editor_set_font (editor, NULL); else if (font_tag) pika_text_style_editor_set_font (editor, font_tag); else pika_text_style_editor_set_default_font (editor); if (color_differs) pika_text_style_editor_set_color (editor, NULL); else if (color_tag) pika_text_style_editor_set_color (editor, color_tag); else pika_text_style_editor_set_default_color (editor); if (size_differs) pika_text_style_editor_set_size (editor, NULL); else if (size_tag) pika_text_style_editor_set_size (editor, size_tag); else pika_text_style_editor_set_default_size (editor); if (baseline_differs) gtk_entry_set_text (GTK_ENTRY (editor->baseline_spinbutton), ""); else pika_text_style_editor_set_baseline (editor, baseline_tag); if (kerning_differs) gtk_entry_set_text (GTK_ENTRY (editor->kerning_spinbutton), ""); else pika_text_style_editor_set_kerning (editor, kerning_tag); } else /* no selection */ { GtkTextIter cursor; GSList *tags; GSList *tags_on; GSList *tags_off; GList *list; gtk_text_buffer_get_iter_at_mark (buffer, &cursor, gtk_text_buffer_get_insert (buffer)); tags = gtk_text_iter_get_tags (&cursor); tags_on = gtk_text_iter_get_toggled_tags (&cursor, TRUE); tags_off = gtk_text_iter_get_toggled_tags (&cursor, FALSE); for (list = editor->buffer->font_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if ((g_slist_find (tags, tag) && ! g_slist_find (tags_on, tag)) || g_slist_find (tags_off, tag)) { pika_text_style_editor_set_font (editor, tag); break; } } if (! list) pika_text_style_editor_set_default_font (editor); for (list = editor->buffer->color_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if ((g_slist_find (tags, tag) && ! g_slist_find (tags_on, tag)) || g_slist_find (tags_off, tag)) { pika_text_style_editor_set_color (editor, tag); break; } } if (! list) pika_text_style_editor_set_default_color (editor); for (list = editor->buffer->size_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if ((g_slist_find (tags, tag) && ! g_slist_find (tags_on, tag)) || g_slist_find (tags_off, tag)) { pika_text_style_editor_set_size (editor, tag); break; } } if (! list) pika_text_style_editor_set_default_size (editor); for (list = editor->buffer->baseline_tags; list; list = g_list_next (list)) { GtkTextTag *tag = list->data; if ((g_slist_find (tags, tag) && ! g_slist_find (tags_on, tag)) || g_slist_find (tags_off, tag)) { pika_text_style_editor_set_baseline (editor, tag); break; } } if (! list) pika_text_style_editor_set_baseline (editor, NULL); for (list = editor->toggles; list; list = g_list_next (list)) { GtkToggleButton *toggle = list->data; GtkTextTag *tag = g_object_get_data (G_OBJECT (toggle), "tag"); pika_text_style_editor_set_toggle (editor, toggle, (g_slist_find (tags, tag) && ! g_slist_find (tags_on, tag)) || g_slist_find (tags_off, tag)); } { GtkTextTag *tag; tag = pika_text_buffer_get_iter_kerning (editor->buffer, &cursor, NULL); pika_text_style_editor_set_kerning (editor, tag); } g_slist_free (tags); g_slist_free (tags_on); g_slist_free (tags_off); } if (editor->context->font_name && g_strcmp0 (editor->context->font_name, pika_font_get_lookup_name (pika_context_get_font (editor->context)))) { /* A font is set, but is unavailable; change the help text. */ gchar *help_text; help_text = g_strdup_printf (_("Font \"%s\" unavailable on this system"), editor->context->font_name); pika_help_set_help_data (editor->font_entry, help_text, NULL); g_free (help_text); } else { pika_help_set_help_data (editor->font_entry, _("Change font of selected text"), NULL); } return FALSE; }