/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* This file is part of the GtkHTML library Copyright (C) 2000 Helix Code, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "gtkhtmldebug.h" #include "htmlclue.h" #include "htmlcolor.h" #include "htmlcolorset.h" #include "htmlengine-edit.h" #include "htmlengine-edit-fontstyle.h" #include "htmlengine-edit-movement.h" #include "htmlengine-edit-selection-updater.h" #include "htmlinterval.h" #include "htmltext.h" #include "htmlselection.h" #include "htmlsettings.h" #include "htmlundo.h" /* #define PARANOID_DEBUG */ static HTMLObject * html_engine_text_style_object (HTMLEngine *e); static GtkHTMLFontStyle get_font_style_from_selection (HTMLEngine *engine) { GtkHTMLFontStyle style; GtkHTMLFontStyle conflicts; gboolean first; HTMLPoint p; g_return_val_if_fail (engine->clue != NULL, GTK_HTML_FONT_STYLE_DEFAULT); g_return_val_if_fail (html_engine_is_selection_active (engine), GTK_HTML_FONT_STYLE_DEFAULT); /* printf ("%s mark %p,%d cursor %p,%d\n", __FUNCTION__, engine->mark, engine->mark->position, engine->cursor, engine->cursor->position); */ style = GTK_HTML_FONT_STYLE_DEFAULT; conflicts = GTK_HTML_FONT_STYLE_DEFAULT; first = TRUE; p = engine->selection->from; while (1) { if (html_object_is_text (p.object) && p.offset != html_object_get_length (p.object)) { if (first) { style = HTML_TEXT (p.object)->font_style; first = FALSE; } else conflicts |= HTML_TEXT (p.object)->font_style ^ style; } if (html_point_cursor_object_eq (&p, &engine->selection->to)) break; html_point_next_cursor (&p); if (p.object == NULL) { g_warning ("Unable to find style for end of selection"); return style; } } return style & ~conflicts; } static HTMLColor * get_color_from_selection (HTMLEngine *engine) { HTMLColor *color = NULL; HTMLPoint p; g_return_val_if_fail (engine->clue != NULL, NULL); g_return_val_if_fail (html_engine_is_selection_active (engine), NULL); p = engine->selection->from; while (1) { if (html_object_is_text (p.object) && p.offset != html_object_get_length (p.object)) { color = HTML_TEXT (p.object)->color; break; } if (html_point_cursor_object_eq (&p, &engine->selection->to)) break; html_point_next_cursor (&p); if (p.object == NULL) { g_warning ("Unable to find color for end of selection"); return color; } } return color; } GtkHTMLFontStyle html_engine_get_document_font_style (HTMLEngine *engine) { g_return_val_if_fail (engine != NULL, GTK_HTML_FONT_STYLE_DEFAULT); g_return_val_if_fail (HTML_IS_ENGINE (engine), GTK_HTML_FONT_STYLE_DEFAULT); g_return_val_if_fail (engine->editable, GTK_HTML_FONT_STYLE_DEFAULT); if (html_engine_is_selection_active (engine)) return get_font_style_from_selection (engine); else { HTMLObject *curr = engine->cursor->object; if (curr == NULL) return GTK_HTML_FONT_STYLE_DEFAULT; else if (! html_object_is_text (curr)) return GTK_HTML_FONT_STYLE_DEFAULT; else { HTMLObject *obj; obj = html_engine_text_style_object (engine); return obj ? HTML_TEXT (obj)->font_style : GTK_HTML_FONT_STYLE_DEFAULT; } } } HTMLColor * html_engine_get_document_color (HTMLEngine *engine) { g_return_val_if_fail (engine != NULL, NULL); g_return_val_if_fail (HTML_IS_ENGINE (engine), NULL); g_return_val_if_fail (engine->editable, NULL); if (html_engine_is_selection_active (engine)) return get_color_from_selection (engine); else { HTMLObject *curr = engine->cursor->object; if (curr == NULL) return NULL; else if (! html_object_is_text (curr)) return NULL; else { HTMLObject *obj; obj = html_engine_text_style_object (engine); return obj ? HTML_TEXT (obj)->color : html_colorset_get_color (engine->settings->color_set, HTMLTextColor); } } } GtkHTMLFontStyle html_engine_get_font_style (HTMLEngine *engine) { return (engine->insertion_font_style == GTK_HTML_FONT_STYLE_DEFAULT) ? html_engine_get_document_font_style (engine) : engine->insertion_font_style; } HTMLColor * html_engine_get_color (HTMLEngine *engine) { return engine->insertion_color; } /** * html_engine_update_insertion_font_style: * @engine: An HTMLEngine * * Update @engine's current insertion font style according to the * current selection and cursor position. * * Return value: **/ gboolean html_engine_update_insertion_font_style (HTMLEngine *engine) { GtkHTMLFontStyle new_style; new_style = html_engine_get_document_font_style (engine); if (new_style != engine->insertion_font_style) { engine->insertion_font_style = new_style; return TRUE; } return FALSE; } /** * html_engine_update_insertion_style: * @engine: An HTMLEngine * * Update @engine's current insertion font style/color according to the * current selection and cursor position. * * Return value: **/ gboolean html_engine_update_insertion_color (HTMLEngine *engine) { HTMLColor *new_color; new_color = html_engine_get_document_color (engine); if (new_color && !html_color_equal (new_color, engine->insertion_color)) { html_color_unref (engine->insertion_color); engine->insertion_color = new_color; html_color_ref (engine->insertion_color); return TRUE; } return FALSE; } /** * html_engine_set_font_style: * @engine: An HTMLEngine * @style: An HTMLFontStyle * * Set the current font style for `engine'. This has the same semantics as the * bold, italics etc. buttons in a word processor, i.e.: * * - If there is a selection, the selection gets the specified style. * * - If there is no selection, the style gets "attached" to the cursor. So * inserting text after this will cause text to have this style. * * Instead of specifying an "absolute" style, we specify it as a "difference" * from the current one, through an AND mask and an OR mask. * **/ struct tmp_font { GtkHTMLFontStyle and_mask; GtkHTMLFontStyle or_mask; }; static void object_set_font_style (HTMLObject *o, HTMLEngine *e, gpointer data) { if (html_object_is_text (o)) { struct tmp_font *tf = (struct tmp_font *) data; HTMLObject *prev; HTML_TEXT (o)->font_style &= tf->and_mask; HTML_TEXT (o)->font_style |= tf->or_mask; if (o->parent) { prev = html_object_prev_not_slave (o); if (prev) { html_object_merge (prev, o, e, NULL, NULL, NULL); } } } } struct _HTMLEmptyParaSetStyle { HTMLUndoData data; GtkHTMLFontStyle and_mask; GtkHTMLFontStyle or_mask; }; typedef struct _HTMLEmptyParaSetStyle HTMLEmptyParaSetStyle; static void set_empty_flow_style (HTMLEngine *e, GtkHTMLFontStyle and_mask, GtkHTMLFontStyle or_mask, HTMLUndoDirection dir); static void set_empty_flow_style_undo_action (HTMLEngine *e, HTMLUndoData *undo_data, HTMLUndoDirection dir, guint position_after) { HTMLEmptyParaSetStyle *undo = (HTMLEmptyParaSetStyle *) undo_data; set_empty_flow_style (e, undo->and_mask, undo->or_mask, html_undo_direction_reverse (dir)); } static void set_empty_flow_style (HTMLEngine *e, GtkHTMLFontStyle and_mask, GtkHTMLFontStyle or_mask, HTMLUndoDirection dir) { HTMLEmptyParaSetStyle *undo; GtkHTMLFontStyle old_or_mask; g_return_if_fail (html_object_is_text (e->cursor->object)); old_or_mask = HTML_TEXT (e->cursor->object)->font_style & ~and_mask; HTML_TEXT (e->cursor->object)->font_style &= and_mask; HTML_TEXT (e->cursor->object)->font_style |= or_mask; undo = g_new (HTMLEmptyParaSetStyle, 1); html_undo_data_init (HTML_UNDO_DATA (undo)); undo->and_mask = and_mask; undo->or_mask = old_or_mask; undo->data.destroy = NULL; html_undo_add_action (e->undo, html_undo_action_new ("Set empty paragraph text style", set_empty_flow_style_undo_action, HTML_UNDO_DATA (undo), html_cursor_get_position (e->cursor), html_cursor_get_position (e->cursor)), dir); } gboolean html_engine_set_font_style (HTMLEngine *e, GtkHTMLFontStyle and_mask, GtkHTMLFontStyle or_mask) { gboolean rv; GtkHTMLFontStyle old = e->insertion_font_style; g_return_val_if_fail (e != NULL, FALSE); g_return_val_if_fail (HTML_IS_ENGINE (e), FALSE); g_return_val_if_fail (e->editable, FALSE); /* printf ("and %d or %d\n", and_mask, or_mask); */ e->insertion_font_style &= and_mask; e->insertion_font_style |= or_mask; if (html_engine_is_selection_active (e)) { struct tmp_font *tf = g_new (struct tmp_font, 1); tf->and_mask = and_mask; tf->or_mask = or_mask; html_engine_cut_and_paste (e, "Set font style", "Unset font style", object_set_font_style, tf); g_free (tf); rv = TRUE; } else { if (e->cursor->object->parent && html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent))) { set_empty_flow_style (e, and_mask, or_mask, HTML_UNDO_UNDO); } rv = (old == e->insertion_font_style) ? FALSE : TRUE; } return rv; } gboolean html_engine_toggle_font_style (HTMLEngine *engine, GtkHTMLFontStyle style) { GtkHTMLFontStyle cur_style; cur_style = html_engine_get_font_style (engine); if (cur_style & style) return html_engine_set_font_style (engine, GTK_HTML_FONT_STYLE_MAX & ~style, 0); else return html_engine_set_font_style (engine, GTK_HTML_FONT_STYLE_MAX, style); } static GtkHTMLFontStyle inc_dec_size (GtkHTMLFontStyle style, gboolean inc) { GtkHTMLFontStyle size; if (style == GTK_HTML_FONT_STYLE_DEFAULT) style = GTK_HTML_FONT_STYLE_SIZE_3; size = style & GTK_HTML_FONT_STYLE_SIZE_MASK; if (inc && size < GTK_HTML_FONT_STYLE_SIZE_7) size++; else if (!inc && size > GTK_HTML_FONT_STYLE_SIZE_1) size--; style &= ~GTK_HTML_FONT_STYLE_SIZE_MASK; style |= size; return style; } static void inc_dec_size_cb (HTMLObject *o, HTMLEngine *e, gpointer data) { if (html_object_is_text (o)) { html_text_set_font_style (HTML_TEXT (o), e, inc_dec_size (HTML_TEXT (o)->font_style, GPOINTER_TO_INT (data))); if (o->prev) html_object_merge (o->prev, o, e, NULL, NULL, NULL); } } void html_engine_font_size_inc_dec (HTMLEngine *e, gboolean inc) { if (html_engine_is_selection_active (e)) html_engine_cut_and_paste (e, inc ? "Increase font size" : "Decrease font size", inc ? "Decrease font size" : "Increase font size", inc_dec_size_cb, GINT_TO_POINTER (inc)); else e->insertion_font_style = inc_dec_size (e->insertion_font_style, inc); } static void set_color (HTMLObject *o, HTMLEngine *e, gpointer data) { if (html_object_is_text (o)) { HTMLObject *prev; html_text_set_color (HTML_TEXT (o), NULL, (HTMLColor *) data); if (o->parent) { prev = html_object_prev_not_slave (o); if (prev) { html_object_merge (prev, o, e, NULL, NULL, NULL); } } } } struct _HTMLEmptyParaSetColor { HTMLUndoData data; HTMLColor *color; }; typedef struct _HTMLEmptyParaSetColor HTMLEmptyParaSetColor; static void set_empty_flow_color (HTMLEngine *e, HTMLColor *c, HTMLUndoDirection dir); static void set_empty_flow_color_undo_action (HTMLEngine *e, HTMLUndoData *undo_data, HTMLUndoDirection dir, guint position_after) { HTMLEmptyParaSetColor *undo = (HTMLEmptyParaSetColor *) undo_data; set_empty_flow_color (e, undo->color, html_undo_direction_reverse (dir)); } static void set_empty_flow_color_destroy (HTMLUndoData *undo_data) { HTMLEmptyParaSetColor *undo = (HTMLEmptyParaSetColor *) undo_data; html_color_unref (undo->color); } static void set_empty_flow_color (HTMLEngine *e, HTMLColor *color, HTMLUndoDirection dir) { HTMLColor *old_color; HTMLEmptyParaSetColor *undo; g_return_if_fail (html_object_is_text (e->cursor->object)); old_color = HTML_TEXT (e->cursor->object)->color; html_color_ref (old_color); html_text_set_color (HTML_TEXT (e->cursor->object), e, color); undo = g_new (HTMLEmptyParaSetColor, 1); html_undo_data_init (HTML_UNDO_DATA (undo)); undo->color = old_color; undo->data.destroy = set_empty_flow_color_destroy; html_undo_add_action (e->undo, html_undo_action_new ("Set empty paragraph color", set_empty_flow_color_undo_action, HTML_UNDO_DATA (undo), html_cursor_get_position (e->cursor), html_cursor_get_position (e->cursor)), dir); } gboolean html_engine_set_color (HTMLEngine *e, HTMLColor *color) { gboolean rv = TRUE; if (!color) color = html_colorset_get_color (e->settings->color_set, HTMLTextColor); if (html_engine_is_selection_active (e)) html_engine_cut_and_paste (e, "Set color", "Unset color", set_color, color); else { if (e->cursor->object->parent && html_clueflow_is_empty (HTML_CLUEFLOW (e->cursor->object->parent))) { set_empty_flow_color (e, color, HTML_UNDO_UNDO); } if (gdk_color_equal (&e->insertion_color->color, &color->color)) rv = FALSE; } html_color_unref (e->insertion_color); e->insertion_color = color; html_color_ref (e->insertion_color); return rv; } /* URL/Target get actual url/target */ const gchar * html_engine_get_url (HTMLEngine *e) { return e->insertion_url; } const gchar * html_engine_get_target (HTMLEngine *e) { return e->insertion_target; } void html_engine_set_url (HTMLEngine *e, const gchar *url) { if (e->insertion_url) g_free (e->insertion_url); e->insertion_url = g_strdup (url); } void html_engine_set_target (HTMLEngine *e, const gchar *target) { if (e->insertion_target) g_free (e->insertion_target); e->insertion_target = g_strdup (target); } /* get url/target from document */ static const gchar * get_url_or_target_from_selection (HTMLEngine *e, gboolean get_url) { const gchar *str = NULL; HTMLPoint p; g_return_val_if_fail (e->clue != NULL, NULL); g_return_val_if_fail (html_engine_is_selection_active (e), NULL); p = e->selection->from; while (1) { str = get_url ? html_object_get_url (p.object) : html_object_get_target (p.object); if (str || html_point_cursor_object_eq (&p, &e->selection->to)) break; html_point_next_cursor (&p); if (p.object == NULL) { g_warning ("Unable to find url by end of selection"); return str; } } return str; } static HTMLObject * html_engine_text_style_object (HTMLEngine *e) { if (HTML_IS_TEXT (e->cursor->object) || (e->cursor->offset && e->cursor->offset != html_object_get_length (e->cursor->object))) return e->cursor->object; if (e->cursor->offset) { HTMLObject *next; next = html_object_next_not_slave (e->cursor->object); if (next && HTML_IS_TEXT (next)) return next; } else { HTMLObject *prev; prev = html_object_prev_not_slave (e->cursor->object); if (prev && HTML_IS_TEXT (prev)) return prev; } return NULL; } const gchar * html_engine_get_document_url (HTMLEngine *e) { if (html_engine_is_selection_active (e)) return get_url_or_target_from_selection (e, TRUE); else { HTMLObject *obj; obj = html_engine_text_style_object (e); return obj ? html_object_get_url (obj) : NULL; } } const gchar * html_engine_get_document_target (HTMLEngine *e) { if (html_engine_is_selection_active (e)) return get_url_or_target_from_selection (e, FALSE); else { HTMLObject *obj; obj = html_engine_text_style_object (e); return obj ? html_object_get_target (obj) : NULL; } } gboolean html_engine_update_insertion_url_and_target (HTMLEngine *engine) { const gchar *url, *target; gboolean retval = FALSE; url = html_engine_get_document_url (engine); target = html_engine_get_document_target (engine); if (url != engine->insertion_url) { html_engine_set_url (engine, url); retval = TRUE; } if (target != engine->insertion_target) { html_engine_set_target (engine, target); retval = TRUE; } return retval; }