/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* This file is part of the GtkHTML library. Copyright (C) 1999, 2000 Helix Code, Inc. Copyright (C) 2001 Ximian, 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. Authors: Ettore Perazzoli Radek Doulik */ #include #include #include #include #include "gtkhtml.h" #include "gtkhtml-properties.h" #include "htmlclueflow.h" #include "htmlcolorset.h" #include "htmlcursor.h" #include "htmllinktext.h" #include "htmlobject.h" #include "htmltable.h" #include "htmltext.h" #include "htmltextslave.h" #include "htmlimage.h" #include "htmlinterval.h" #include "htmlselection.h" #include "htmlsettings.h" #include "htmlundo.h" #include "htmlengine-edit.h" #include "htmlengine-edit-cut-and-paste.h" #include "htmlengine-edit-cursor.h" #include "htmlengine-edit-movement.h" #include "htmlengine-edit-selection-updater.h" #include "htmlengine-edit-table.h" #include "htmlengine-edit-tablecell.h" void html_engine_undo (HTMLEngine *e) { HTMLUndo *undo; g_return_if_fail (e != NULL); g_return_if_fail (HTML_IS_ENGINE (e)); g_return_if_fail (e->undo != NULL); g_return_if_fail (e->editable); html_engine_unselect_all (e); undo = e->undo; html_undo_do_undo (undo, e); } void html_engine_redo (HTMLEngine *e) { HTMLUndo *undo; g_return_if_fail (e != NULL); g_return_if_fail (HTML_IS_ENGINE (e)); g_return_if_fail (e->undo != NULL); html_engine_unselect_all (e); undo = e->undo; html_undo_do_redo (undo, e); } void html_engine_set_mark (HTMLEngine *e) { g_return_if_fail (e != NULL); g_return_if_fail (HTML_IS_ENGINE (e)); g_return_if_fail (e->editable); if (e->mark != NULL) html_engine_unselect_all (e); e->mark = html_cursor_dup (e->cursor); html_engine_edit_selection_updater_reset (e->selection_updater); html_engine_edit_selection_updater_schedule (e->selection_updater); } void html_engine_clipboard_push (HTMLEngine *e) { e->clipboard_stack = g_list_prepend (e->clipboard_stack, GUINT_TO_POINTER (e->clipboard_len)); e->clipboard_stack = g_list_prepend (e->clipboard_stack, e->clipboard); e->clipboard = NULL; } void html_engine_clipboard_pop (HTMLEngine *e) { g_assert (e->clipboard_stack); e->clipboard = HTML_OBJECT (e->clipboard_stack->data); e->clipboard_stack = g_list_remove (e->clipboard_stack, e->clipboard_stack->data); e->clipboard_len = GPOINTER_TO_UINT (e->clipboard_stack->data); e->clipboard_stack = g_list_remove (e->clipboard_stack, e->clipboard_stack->data); } void html_engine_selection_push (HTMLEngine *e) { if (html_engine_is_selection_active (e)) { e->selection_stack = g_list_prepend (e->selection_stack, GINT_TO_POINTER (html_cursor_get_position (e->mark))); e->selection_stack = g_list_prepend (e->selection_stack, GINT_TO_POINTER (html_cursor_get_position (e->cursor))); e->selection_stack = g_list_prepend (e->selection_stack, GINT_TO_POINTER (TRUE)); } else { e->selection_stack = g_list_prepend (e->selection_stack, GINT_TO_POINTER (FALSE)); } } void html_engine_selection_pop (HTMLEngine *e) { gboolean selection; g_assert (e->selection_stack); selection = GPOINTER_TO_INT (e->selection_stack->data); e->selection_stack = g_list_remove (e->selection_stack, e->selection_stack->data); html_engine_disable_selection (e); if (selection) { gint cursor, mark; cursor = GPOINTER_TO_INT (e->selection_stack->data); e->selection_stack = g_list_remove (e->selection_stack, e->selection_stack->data); mark = GPOINTER_TO_INT (e->selection_stack->data); e->selection_stack = g_list_remove (e->selection_stack, e->selection_stack->data); html_cursor_jump_to_position (e->cursor, e, mark); html_engine_set_mark (e); html_cursor_jump_to_position (e->cursor, e, cursor); } html_engine_edit_selection_updater_update_now (e->selection_updater); } static void spell_check_object (HTMLObject *o, HTMLEngine *e, gpointer data) { if (HTML_OBJECT_TYPE (o) == HTML_TYPE_CLUEFLOW) html_clueflow_spell_check (HTML_CLUEFLOW (o), e, (HTMLInterval *) data); } void html_engine_spell_check_range (HTMLEngine *e, HTMLCursor *begin, HTMLCursor *end) { HTMLInterval *i; gboolean cited; e->need_spell_check = FALSE; if (!e->widget->editor_api || !GTK_HTML_CLASS (GTK_OBJECT (e->widget)->klass)->properties->live_spell_check || !begin->object->parent) return; begin = html_cursor_dup (begin); end = html_cursor_dup (end); cited = FALSE; while (html_selection_spell_word (html_cursor_get_prev_char (begin), &cited) || cited) { if (html_cursor_backward (begin, e)) ; cited = FALSE; } cited = FALSE; while (html_selection_spell_word (html_cursor_get_current_char (end), &cited) || cited) { if (html_cursor_forward (end, e)) ; cited = FALSE; } i = html_interval_new_from_cursor (begin, end); if (begin->object->parent != end->object->parent) html_interval_forall (i, e, spell_check_object, i); else if (HTML_IS_CLUEFLOW (begin->object->parent)) html_clueflow_spell_check (HTML_CLUEFLOW (begin->object->parent), e, i); html_interval_destroy (i); html_cursor_destroy (begin); html_cursor_destroy (end); } gboolean html_is_in_word (gunichar uc) { /* printf ("test %d %c => %d\n", uc, uc, g_unichar_isalnum (uc) || uc == '\''); */ return g_unichar_isalpha (uc) || uc == '\''; } void html_engine_select_word_editable (HTMLEngine *e) { while (html_selection_word (html_cursor_get_prev_char (e->cursor))) html_cursor_backward (e->cursor, e); html_engine_set_mark (e); while (html_selection_word (html_cursor_get_current_char (e->cursor))) html_cursor_forward (e->cursor, e); } void html_engine_select_spell_word_editable (HTMLEngine *e) { gboolean cited, cited2; cited = cited2 = FALSE; while (html_selection_spell_word (html_cursor_get_prev_char (e->cursor), &cited)) html_cursor_backward (e->cursor, e); html_engine_set_mark (e); while (html_selection_spell_word (html_cursor_get_current_char (e->cursor), &cited2) || (!cited && cited2)) { html_cursor_forward (e->cursor, e); cited2 = FALSE; } } void html_engine_select_line_editable (HTMLEngine *e) { html_engine_beginning_of_line (e); html_engine_set_mark (e); html_engine_end_of_line (e); } void html_engine_select_paragraph_editable (HTMLEngine *e) { html_engine_beginning_of_paragraph (e); html_engine_set_mark (e); html_engine_end_of_paragraph (e); } void html_engine_select_paragraph_extended (HTMLEngine *e) { gboolean fw; html_engine_hide_cursor (e); html_engine_beginning_of_paragraph (e); fw = html_cursor_backward (e->cursor, e); html_engine_set_mark (e); if (fw) html_cursor_forward (e->cursor, e); html_engine_end_of_paragraph (e); html_cursor_forward (e->cursor, e); html_engine_show_cursor (e); html_engine_update_selection_if_necessary (e); } void html_engine_select_all_editable (HTMLEngine *e) { html_engine_beginning_of_document (e); html_engine_set_mark (e); html_engine_end_of_document (e); } struct SetData { HTMLType object_type; const gchar *key; const gchar *value; }; static void set_data (HTMLObject *o, HTMLEngine *e, gpointer p) { struct SetData *data = (struct SetData *) p; if (HTML_OBJECT_TYPE (o) == data->object_type) { /* printf ("set data %s --> %p\n", data->key, data->value); */ html_object_set_data (o, data->key, data->value); } } void html_engine_set_data_by_type (HTMLEngine *e, HTMLType object_type, const gchar *key, const gchar * value) { struct SetData *data = g_new (struct SetData, 1); /* printf ("html_engine_set_data_by_type %s\n", key); */ data->object_type = object_type; data->key = key; data->value = value; html_object_forall (e->clue, NULL, set_data, data); g_free (data); } void html_engine_clipboard_clear (HTMLEngine *e) { if (e->clipboard) { html_object_destroy (e->clipboard); e->clipboard = NULL; } } HTMLObject * html_engine_new_text (HTMLEngine *e, const gchar *text, gint len) { if (e->insertion_url && *e->insertion_url) { return html_link_text_new_with_len (text, len, e->insertion_font_style, e->insertion_color, e->insertion_url, e->insertion_target); } else return html_text_new_with_len (text, len, e->insertion_font_style, e->insertion_color); } HTMLObject * html_engine_new_link (HTMLEngine *e, const gchar *text, gint len, gchar *url) { HTMLObject *link; gchar *real_url, *real_target; real_target = strchr (text, '#'); if (real_target) { real_url = g_strndup (url, real_target - url); real_target ++; } else real_url = url; link = html_link_text_new_with_len (text, len, e->insertion_font_style, html_colorset_get_color (e->settings->color_set, HTMLLinkColor), real_url, real_target); if (real_target) g_free (real_url); return link; } HTMLObject * html_engine_new_text_empty (HTMLEngine *e) { return html_engine_new_text (e, "", 0); } gboolean html_engine_cursor_on_bop (HTMLEngine *e) { g_assert (e); g_assert (e->cursor); g_assert (e->cursor->object); return e->cursor->offset == 0 && html_object_prev_not_slave (e->cursor->object) == NULL; } guint html_engine_get_indent (HTMLEngine *e) { g_assert (e); g_assert (e->cursor); g_assert (e->cursor->object); return e->cursor->object && e->cursor->object->parent && HTML_OBJECT_TYPE (e->cursor->object->parent) == HTML_TYPE_CLUEFLOW ? html_clueflow_get_indentation (HTML_CLUEFLOW (e->cursor->object->parent)) : 0; } #define LINE_LEN 71 static inline guint inc_line_offset (guint line_offset, gunichar uc) { return uc == '\t' ? line_offset + 8 - (line_offset % 8) : line_offset + 1; } static guint try_break_this_line (HTMLEngine *e, guint line_offset, guint last_space) { HTMLObject *flow; gunichar uc; flow = e->cursor->object->parent; while (html_cursor_forward (e->cursor, e) && e->cursor->object->parent == flow) { uc = html_cursor_get_current_char (e->cursor); line_offset = inc_line_offset (line_offset, uc); if (uc == ' ' || uc == '\t') last_space = line_offset; if (uc && line_offset >= LINE_LEN) { if (last_space) { html_cursor_backward_n (e->cursor, e, line_offset - last_space); uc = ' '; } else { /* go to end of word */ while (html_cursor_forward (e->cursor, e)) { line_offset = inc_line_offset (line_offset, uc); uc = html_cursor_get_current_char (e->cursor); if (uc == ' ' || uc == '\t' || !uc) break; } } if (uc == ' ' || uc == '\t') { html_engine_insert_empty_paragraph (e); html_engine_delete_n (e, 1, TRUE); flow = e->cursor->object->parent; last_space = 0; line_offset = 0; } } if (!uc) return line_offset; } return line_offset; } static void go_to_begin_of_para (HTMLEngine *e) { HTMLObject *prev; do { gint offset; html_cursor_beginning_of_paragraph (e->cursor, e); offset = 0; prev = html_object_prev_cursor (e->cursor->object, &offset); if (prev && !html_object_is_container (prev) && html_object_get_length (prev) && html_clueflow_style_equals (HTML_CLUEFLOW (e->cursor->object->parent), HTML_CLUEFLOW (prev->parent))) html_cursor_backward (e->cursor, e); else break; } while (1); } void html_engine_indent_paragraph (HTMLEngine *e) { guint position; guint line_offset; guint last_space; g_assert (e->cursor->object); if (!HTML_IS_CLUEFLOW (e->cursor->object->parent)) return; html_engine_disable_selection (e); position = e->cursor->position; html_undo_level_begin (e->undo, "Indent paragraph", "Reverse paragraph indentation"); html_engine_freeze (e); go_to_begin_of_para (e); line_offset = 0; last_space = 0; do { HTMLObject *flow; line_offset = try_break_this_line (e, line_offset, last_space); flow = e->cursor->object->parent; if (html_cursor_forward (e->cursor, e) && e->cursor->offset == 0 && html_object_get_length (e->cursor->object) && !html_object_is_container (e->cursor->object) && html_clueflow_style_equals (HTML_CLUEFLOW (e->cursor->object->parent), HTML_CLUEFLOW (flow)) && html_object_prev_not_slave (e->cursor->object) == NULL) { if (line_offset < LINE_LEN - 1) { gunichar prev; html_engine_delete_n (e, 1, FALSE); prev = html_cursor_get_prev_char (e->cursor); if (prev != ' ' && prev != '\t') { html_engine_insert_text (e, " ", 1); line_offset ++; } else if (position > e->cursor->position) position --; last_space = line_offset - 1; } else { line_offset = 0; last_space = 0; } } else break; } while (1); html_cursor_jump_to_position (e->cursor, e, position); html_engine_thaw (e); html_undo_level_end (e->undo); } void html_engine_indent_pre_line (HTMLEngine *e) { guint position; guint line_offset; guint last_space; HTMLObject *flow; gunichar uc; g_assert (e->cursor->object); if (HTML_OBJECT_TYPE (e->cursor->object->parent) != HTML_TYPE_CLUEFLOW || html_clueflow_get_style (HTML_CLUEFLOW (e->cursor->object->parent)) != HTML_CLUEFLOW_STYLE_PRE) return; html_engine_disable_selection (e); position = e->cursor->position; html_undo_level_begin (e->undo, "Indent PRE paragraph", "Reverse paragraph indentation"); html_engine_freeze (e); last_space = 0; line_offset = 0; html_cursor_beginning_of_paragraph (e->cursor, e); flow = e->cursor->object->parent; while (html_cursor_forward (e->cursor, e) && e->cursor->object->parent == flow) { uc = html_cursor_get_current_char (e->cursor); line_offset = inc_line_offset (line_offset, uc); if (uc == ' ' || uc == '\t') { last_space = line_offset; } if (line_offset >= LINE_LEN) { if (last_space) { html_cursor_backward_n (e->cursor, e, line_offset - last_space); html_cursor_forward (e->cursor, e); if ((uc = html_cursor_get_current_char (e->cursor))) { html_engine_insert_empty_paragraph (e); if (position >= e->cursor->position) position++; } } } if (!uc) break; } html_cursor_jump_to_position (e->cursor, e, position); html_engine_thaw (e); html_undo_level_end (e->undo); } void html_engine_fill_pre_line (HTMLEngine *e) { guint position; guint line_offset; guint last_space; HTMLObject *flow; gunichar uc; g_assert (e->cursor->object); position = e->cursor->position; if (HTML_OBJECT_TYPE (e->cursor->object->parent) != HTML_TYPE_CLUEFLOW || html_clueflow_get_style (HTML_CLUEFLOW (e->cursor->object->parent)) != HTML_CLUEFLOW_STYLE_PRE) { return; } last_space = 0; line_offset = 0; html_cursor_beginning_of_paragraph (e->cursor, e); flow = e->cursor->object->parent; while (html_cursor_forward (e->cursor, e) && (e->cursor->position < position - 1)) { uc = html_cursor_get_current_char (e->cursor); line_offset = inc_line_offset (line_offset, uc); if (uc == ' ' || uc == '\t') { last_space = line_offset; } if (line_offset >= LINE_LEN) { if (last_space) { html_cursor_backward_n (e->cursor, e, line_offset - last_space); html_cursor_forward (e->cursor, e); if ((uc = html_cursor_get_current_char (e->cursor))) { html_engine_insert_empty_paragraph (e); if (position >= e->cursor->position) position++; line_offset = 0; last_space = 0; } } } if (!uc) break; } html_cursor_jump_to_position (e->cursor, e, position); } void html_engine_space_and_fill_line (HTMLEngine *e) { g_assert (e->cursor->object); html_undo_level_begin (e->undo, "insert and fill", "reverse insert and fill"); html_engine_disable_selection (e); html_engine_freeze (e); html_engine_insert_text (e, " ", 1); html_engine_fill_pre_line (e); html_engine_thaw (e); html_undo_level_end (e->undo); } void html_engine_break_and_fill_line (HTMLEngine *e) { html_undo_level_begin (e->undo, "break and fill", "reverse break and fill"); html_engine_disable_selection (e); html_engine_freeze (e); html_engine_fill_pre_line (e); html_engine_insert_empty_paragraph (e); html_engine_thaw (e); html_undo_level_end (e->undo); } gboolean html_engine_next_cell (HTMLEngine *e, gboolean create) { HTMLTableCell *cell, *current_cell; cell = html_engine_get_table_cell (e); if (cell) { html_engine_hide_cursor (e); do { html_cursor_end_of_line (e->cursor, e); html_cursor_forward (e->cursor, e); current_cell = html_engine_get_table_cell (e); } while (current_cell == cell); if (create && HTML_IS_TABLE (e->cursor->object)) { html_cursor_backward (e->cursor, e); html_engine_insert_table_row (e, TRUE); } html_engine_show_cursor (e); return TRUE; } return FALSE; } gboolean html_engine_prev_cell (HTMLEngine *e) { HTMLTableCell *cell, *current_cell; cell = html_engine_get_table_cell (e); if (cell) { html_engine_hide_cursor (e); do { html_cursor_beginning_of_line (e->cursor, e); html_cursor_backward (e->cursor, e); current_cell = html_engine_get_table_cell (e); } while (current_cell == cell); html_engine_show_cursor (e); return TRUE; } return FALSE; } void html_engine_set_title (HTMLEngine *e, const gchar *title) { if (e->title) g_string_free (e->title, TRUE); e->title = g_string_new (title); gtk_signal_emit_by_name (GTK_OBJECT (e), "title_changed"); }