/* Katoob * Copyright (c) 2002,2003 Arabeyes, Mohammed Sameer. * * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "katoob.h" #include #include #include #include #include "katoobdocument.h" #include "katooblabel.h" #include "katoobmarshalers.h" #include "fribidi.h" #include "bidi.h" #include "mdi.h" #include "undoredo.h" #include "misc.h" #include "katoobstatusbar.h" #include "dnd.h" #ifdef HAVE_SPELL #include "spell-private.h" #include "spell.h" #endif /* HAVE_SPELL */ #ifdef ENABLE_HIGHLIGHT #include #include "highlight.h" #endif /* ENABLE_HIGHLIGHT */ extern conf *config; extern UI *katoob; struct _KatoobDocumentPrivate { GtkWidget *textview; GtkWidget *scrolledwin; GtkWidget *menu; GtkTextWindowType line_numbers_window; gboolean textwrap; gboolean readonly; gboolean linenumbers; gint encoding; GtkTextTag *tag_rtl; GtkTextTag *tag_ltr; gint text_dir; gchar *file; GList *UNDO; GList *REDO; gboolean can_undo; gboolean can_redo; GtkWidget *label; /* KatoobLabel */ GtkTextMark *mark_insert; /* Search, Replace stuff */ gchar *last_searched; gchar *last_replaced; gboolean match_case; gboolean beginning; GtkTextMark *mark; #ifdef HAVE_SPELL GtkTextTag *tag_highlight; AspellSpeller *speller; gboolean spell_check; gint dictionary; #endif #ifdef ENABLE_HIGHLIGHT GtkSourceLanguage *hl_language; KatoobHighlightType hl; gboolean spell_was_enabled; #endif /* ENABLE_HIGHLIGHT */ }; enum { FILE_CHANGED, BUFFER_MODIFIED, CURSOR_MOVED, READONLY_SET, READONLY_UNSET, ENCODING_CHANGED, TEXT_DIR_CHANGED, MATCH_CASE_CHANGED, BEGINNING_CHANGED, CAN_UNDO, CAN_REDO, #ifdef HAVE_SPELL SPELL_TOGGLED, #endif /* HAVE_SPELL */ LAST_SIGNAL }; static void katoob_document_class_init (KatoobDocumentClass * class); static void katoob_document_init (KatoobDocument * document); static void katoob_document_finalize (GObject * object); static void katoob_document_set_dir (KatoobDocument * doc, gint kdir, gint dir); static void katoob_document_set_rtl (KatoobDocument * doc); static void katoob_document_set_ltr (KatoobDocument * doc); static void katoob_document_set_aut (KatoobDocument * doc); static void katoob_set_doc_dir_ltr (GtkCheckMenuItem * item, KatoobDocument * doc); static void katoob_set_doc_dir_rtl (GtkCheckMenuItem * item, KatoobDocument * doc); static void katoob_set_doc_dir_aut (GtkCheckMenuItem * item, KatoobDocument * doc); static void katoob_bidi_insert_text_cb (GtkTextBuffer * buffer, GtkTextIter * iter, gchar * text, gint len, KatoobDocument * doc); static void katoob_bidi_delete_text_cb (GtkTextBuffer * buffer, GtkTextIter * start, GtkTextIter * end, KatoobDocument * doc); static void katoob_document_set_line_direction (KatoobDocument * doc, GtkTextIter start, GtkTextIter end); static void katoob_document_mark_insertion_position (GtkTextBuffer * buffer, GtkTextIter * iter, gchar * text, gint len, KatoobDocument * doc); static void katoob_document_menu_set_modified (KatoobDocument * doc, gboolean modified); static void katoob_document_menu_set_text (KatoobDocument * doc, gchar * title); static gchar *katoob_document_menu_get_text (KatoobDocument * doc); static GtkWidget *katoob_document_get_label_close_button (KatoobDocument * doc); static void katoob_document_disable_undoredo (KatoobDocument * doc); static void katoob_document_enable_undoredo (KatoobDocument * doc); static void katoob_document_populate_popup (GtkTextView * textview, GtkMenu * menu, KatoobDocument * doc); static void katoob_document_free_undoredo (KatoobDocument * doc); static gboolean katoob_document_textview_expose_event_cb (GtkWidget * widget, GdkEventExpose * event, KatoobDocument * doc); static void katoob_document_insert_text_cb (GtkTextBuffer * buffer, GtkTextIter * iter, gchar * text, gint len, KatoobDocument * doc); static void katoob_document_delete_range_cb (GtkTextBuffer * buffer, GtkTextIter * start, GtkTextIter * end, KatoobDocument * doc); static void katoob_document_cursor_moved (GtkTextView * textview, GtkMovementStep arg1, gint arg2, gboolean arg3, KatoobDocument * doc); static gint katoob_document_get_column_pos (GtkTextIter * iter); static void katoob_document_changed (GtkTextBuffer * buffer, const GtkTextIter * new_location, GtkTextMark * mark, KatoobDocument * doc); static void katoob_document_set_line_numbers_dir (KatoobDocument * doc, gint text_dir); #ifdef HAVE_SPELL static void katoob_spell_check_range (KatoobDocument * doc, GtkTextIter start, GtkTextIter end); static void katoob_spell_check_word (KatoobDocument * doc, GtkTextIter * start, GtkTextIter * end); static GtkWidget *spell_build_suggestion_menu (KatoobDocument * doc, gchar * word); static gboolean spell_button_press_event (GtkTextView * view, GdkEventButton * event, KatoobDocument * doc); #endif /* HAVE_SPELL */ static void katoob_document_can_undo_cb (KatoobDocument * doc, gboolean state); static void katoob_document_can_redo_cb (KatoobDocument * doc, gboolean state); static gint katoob_document_signals[LAST_SIGNAL] = { 0 }; static GtkVBoxClass *parent_class; GType katoob_document_get_type (void) { static GType document_type = 0; katoob_debug (__FUNCTION__); if (!document_type) { static const GTypeInfo document_info = { sizeof (KatoobDocumentClass), NULL, NULL, (GClassInitFunc) katoob_document_class_init, NULL, NULL, sizeof (KatoobDocument), 0, (GInstanceInitFunc) katoob_document_init, }; document_type = g_type_register_static (GTK_TYPE_VBOX, "KatoobDocument", &document_info, 0); } return document_type; } static void katoob_document_class_init (KatoobDocumentClass * klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); katoob_debug (__FUNCTION__); object_class->finalize = katoob_document_finalize; klass->file_changed = NULL; klass->buffer_modified = NULL; klass->cursor_moved = NULL; klass->readonly_set = NULL; klass->readonly_unset = NULL; klass->encoding_changed = NULL; klass->text_dir_changed = NULL; klass->match_case_changed = NULL; klass->beginning_changed = NULL; klass->can_undo = katoob_document_can_undo_cb; klass->can_redo = katoob_document_can_redo_cb; #ifdef HAVE_SPELL klass->spell_toggled = NULL; #endif /* HAVE_SPELL */ katoob_document_signals[FILE_CHANGED] = g_signal_new ("file_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, file_changed), NULL, NULL, katoob_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); katoob_document_signals[BUFFER_MODIFIED] = g_signal_new ("buffer_modified", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, buffer_modified), NULL, NULL, katoob_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); katoob_document_signals[CURSOR_MOVED] = g_signal_new ("cursor_moved", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, cursor_moved), NULL, NULL, katoob_marshal_VOID__INT_INT, G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); katoob_document_signals[READONLY_SET] = g_signal_new ("readonly_set", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, readonly_set), NULL, NULL, katoob_marshal_VOID__VOID, G_TYPE_NONE, 0); katoob_document_signals[READONLY_UNSET] = g_signal_new ("readonly_unset", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, readonly_unset), NULL, NULL, katoob_marshal_VOID__VOID, G_TYPE_NONE, 0); katoob_document_signals[ENCODING_CHANGED] = g_signal_new ("encoding_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, encoding_changed), NULL, NULL, katoob_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); katoob_document_signals[TEXT_DIR_CHANGED] = g_signal_new ("text_dir_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, text_dir_changed), NULL, NULL, katoob_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); /* FIXME: USE THE ENUM */ katoob_document_signals[MATCH_CASE_CHANGED] = g_signal_new ("match_case_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, match_case_changed), NULL, NULL, katoob_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); katoob_document_signals[BEGINNING_CHANGED] = g_signal_new ("beginning_changed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, beginning_changed), NULL, NULL, katoob_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); katoob_document_signals[CAN_UNDO] = g_signal_new ("can_undo", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, can_undo), NULL, NULL, katoob_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); katoob_document_signals[CAN_REDO] = g_signal_new ("can_redo", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, can_redo), NULL, NULL, katoob_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); #ifdef HAVE_SPELL katoob_document_signals[SPELL_TOGGLED] = g_signal_new ("spell_toggled", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (KatoobDocumentClass, spell_toggled), NULL, NULL, katoob_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); #endif /* HAVE_SPELL */ } static void katoob_document_init (KatoobDocument * document) { GtkTextIter start; katoob_debug (__FUNCTION__); document->priv = g_new0 (KatoobDocumentPrivate, 1); document->priv->textwrap = config->textwrap; document->priv->readonly = FALSE; document->priv->encoding = katoob_encodings_get_by_name ("UTF-8"); document->priv->file = NULL; document->priv->UNDO = NULL; document->priv->REDO = NULL; document->priv->can_undo = FALSE; document->priv->can_redo = FALSE; document->priv->last_searched = NULL; document->priv->last_replaced = NULL; document->priv->beginning = TRUE; document->priv->match_case = TRUE; #ifdef HAVE_SPELL document->priv->spell_check = config->spell_check; document->priv->dictionary = -1; #endif /* HAVE_SPELL */ document->priv->text_dir = -1; document->priv->linenumbers = config->linenumbers; document->priv->scrolledwin = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (document->priv->scrolledwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (document->priv->scrolledwin), GTK_SHADOW_IN); #ifdef ENABLE_HIGHLIGHT document->priv->textview = gtk_source_view_new (); /* We want to handle all the undo & redo stuff. */ gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (document->priv->textview)-> buffer)); #else document->priv->textview = gtk_text_view_new (); #endif /* ENABLE_HIGHLIGHT */ document->priv->label = katoob_label_new (""); gtk_text_view_set_left_margin (GTK_TEXT_VIEW (document->priv->textview), 10); gtk_text_view_set_right_margin (GTK_TEXT_VIEW (document->priv->textview), 10); GTK_WIDGET_SET_FLAGS (document->priv->textview, GTK_CAN_FOCUS); gtk_widget_grab_focus (document->priv->textview); if (config->textwrap) { gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (document->priv->textview), GTK_WRAP_WORD); } else { gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (document->priv->textview), GTK_WRAP_NONE); } gtk_container_add (GTK_CONTAINER (document->priv->scrolledwin), document->priv->textview); document->priv->tag_rtl = gtk_text_buffer_create_tag (GTK_TEXT_VIEW (document->priv->textview)-> buffer, "rtl", "wrap-mode", GTK_WRAP_WORD, "direction", GTK_TEXT_DIR_RTL, NULL); document->priv->tag_ltr = gtk_text_buffer_create_tag (GTK_TEXT_VIEW (document->priv->textview)-> buffer, "ltr", "justification", GTK_JUSTIFY_LEFT, "direction", GTK_TEXT_DIR_LTR, NULL); gtk_text_buffer_get_start_iter (GTK_TEXT_VIEW (document->priv->textview)-> buffer, &start); document->priv->mark_insert = gtk_text_buffer_create_mark (GTK_TEXT_VIEW (document->priv->textview)-> buffer, "mark-insert", &start, TRUE); document->priv->mark = gtk_text_buffer_create_mark (GTK_TEXT_VIEW (document->priv->textview)-> buffer, "mark", &start, TRUE); g_signal_connect_after (G_OBJECT (document), "buffer-modified", G_CALLBACK (katoob_document_set_modified), NULL); /****************************************************************** * Since the gtktextbuffer default insertion handler reinitialise * * iter to point to the end of the inserted text, We have to mark * * the insertion position before insertion * ******************************************************************/ g_signal_connect (G_OBJECT (GTK_TEXT_VIEW (document->priv->textview)->buffer), "insert-text", G_CALLBACK (katoob_document_mark_insertion_position), document); #ifdef HAVE_SPELL document->priv->tag_highlight = gtk_text_buffer_create_tag (GTK_TEXT_VIEW (document->priv->textview)-> buffer, "mispelled", "foreground-gdk", &katoob->mispelled_color, "underline", PANGO_UNDERLINE_SINGLE, NULL); /* We are setting the dictionary either the spell checker is enabled or not. */ if (katoob_document_set_dictionary (document, config->default_dict)) { document->priv->spell_check = config->spell_check; } else { /* We failed to set the dictionary, We are going to disable the checker. */ document->priv->spell_check = FALSE; } g_signal_connect (G_OBJECT (document->priv->textview), "button-press-event", G_CALLBACK (spell_button_press_event), document); g_signal_connect (G_OBJECT (document), "spell-toggled", G_CALLBACK (katoob_toggle_spell_cb), NULL); #endif /* HAVE_SPELL */ if (document->priv->textwrap) { katoob_document_enable_text_wrap (document); } else { katoob_document_disable_text_wrap (document); } document->priv->menu = gtk_menu_item_new_with_label (""); gtk_container_add (GTK_CONTAINER (document), document->priv->scrolledwin); g_signal_connect_after (G_OBJECT (GTK_TEXT_VIEW (document->priv->textview)->buffer), "mark_set", G_CALLBACK (katoob_document_changed), document); g_signal_connect_after (G_OBJECT (document->priv->textview), "move-cursor", G_CALLBACK (katoob_document_cursor_moved), document); katoob_document_set_bidi (document, config->text_dir); /* NOTE: This should be here, So line_numbers_window is set */ if (document->priv->linenumbers) { katoob_document_enable_line_numbers (document); } if (config->showclose) { g_signal_connect (G_OBJECT (katoob_document_get_label_close_button (document)), "clicked", G_CALLBACK (katoob_close_button_handler), document); } if (config->undo) { katoob_document_enable_undoredo (document); } g_signal_connect_after (G_OBJECT (document->priv->textview), "populate-popup", G_CALLBACK (katoob_document_populate_popup), document); GTK_WIDGET_SET_FLAGS (document->priv->textview, GTK_CAN_FOCUS); katoob_document_grab_focus (document); g_signal_connect_after (G_OBJECT (GTK_TEXT_VIEW (document->priv->textview)->buffer), "insert-text", G_CALLBACK (katoob_document_insert_text_cb), document); g_signal_connect_after (G_OBJECT (GTK_TEXT_VIEW (document->priv->textview)->buffer), "delete-range", G_CALLBACK (katoob_document_delete_range_cb), document); /* Modify the textview font */ if (!(config->default_font) && (config->font)) { PangoFontDescription *fd = pango_font_description_from_string (config->font); gtk_widget_modify_font (GTK_WIDGET (document->priv->textview), fd); pango_font_description_free (fd); } } static GtkWidget * katoob_document_get_label_close_button (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return katoob_label_get_close_button (KATOOB_LABEL (doc->priv->label)); } static void katoob_document_finalize (GObject * object) { KatoobDocument *doc; katoob_debug (__FUNCTION__); doc = KATOOB_DOCUMENT (object); g_return_if_fail (doc->priv != NULL); gtk_widget_hide (doc->priv->menu); gtk_widget_destroy (doc->priv->menu); katoob_document_free_undoredo (doc); if (doc->priv->file) { g_free (doc->priv->file); } if (doc->priv->last_searched) { g_free (doc->priv->last_searched); } if (doc->priv->last_replaced) { g_free (doc->priv->last_replaced); } #ifdef HAVE_SPELL if (doc->priv->speller) { delete_aspell_speller (doc->priv->speller); } #endif /* HAVE_SPELL */ g_free (doc->priv); G_OBJECT_CLASS (parent_class)->finalize (object); } GtkWidget * katoob_document_new (gchar * title) { KatoobDocument *doc = g_object_new (KATOOB_TYPE_DOCUMENT, NULL); katoob_debug (__FUNCTION__); katoob_document_set_title (doc, title); return GTK_WIDGET (doc); } GtkWidget * katoob_document_new_from_text (gchar * text, gchar * title) { GtkWidget *doc; katoob_debug (__FUNCTION__); doc = katoob_document_new (title); katoob_document_set_text (KATOOB_DOCUMENT (doc), text); return doc; } GtkWidget * katoob_document_new_from_file (gchar * file) { GtkWidget *doc = katoob_document_new (g_path_get_basename (file)); katoob_debug (__FUNCTION__); /* don't use katoob_document_set_file() to avoid any signal */ KATOOB_DOCUMENT (doc)->priv->file = g_strdup (file); return doc; } GtkWidget * katoob_document_get_menu (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->menu; } void katoob_document_set_file (KatoobDocument * doc, gchar * file) { gchar *f; katoob_debug (__FUNCTION__); if (doc->priv->file) { g_free (doc->priv->file); } doc->priv->file = g_strdup (file); f = g_path_get_basename (doc->priv->file); katoob_document_set_title (doc, f); /* FIXME: why free() here gives error ?? g_free (f); */ g_signal_emit (G_OBJECT (doc), katoob_document_signals[FILE_CHANGED], 0, doc->priv->file); } gchar * katoob_document_get_file (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->file; } void katoob_document_set_title (KatoobDocument * doc, gchar * title) { gchar *txt = g_strdup (title); katoob_debug (__FUNCTION__); katoob_document_menu_set_text (doc, txt); katoob_label_set_text (KATOOB_LABEL (doc->priv->label), txt); } GtkWidget * katoob_document_get_label (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->label; } const gchar * katoob_document_get_label_text (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return katoob_label_get_text (KATOOB_LABEL (doc->priv->label)); } static void katoob_document_menu_set_text (KatoobDocument * doc, gchar * title) { katoob_debug (__FUNCTION__); gtk_label_set_text (GTK_LABEL (GTK_BIN (doc->priv->menu)->child), title); } static gchar * katoob_document_menu_get_text (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return (gchar *) gtk_label_get_text (GTK_LABEL (GTK_BIN (doc->priv->menu)->child)); } static void katoob_document_free_undoredo (KatoobDocument * doc) { undo *un; gint x, y; katoob_debug (__FUNCTION__); x = g_list_length (doc->priv->UNDO); for (y = 0; y < x; y++) { un = (undo *) g_list_nth_data (doc->priv->UNDO, y); if (un) { g_free (un->text); if (un->text2) { g_free (un->text2); } } } x = g_list_length (doc->priv->REDO); for (y = 0; y < x; y++) { un = (undo *) g_list_nth_data (doc->priv->REDO, y); if (un) { g_free (un->text); if (un->text2) { g_free (un->text2); } } } } void katoob_document_set_readonly (KatoobDocument * doc) { gchar *_tmp; katoob_debug (__FUNCTION__); gtk_text_view_set_editable (GTK_TEXT_VIEW (doc->priv->textview), FALSE); doc->priv->readonly = TRUE; _tmp = g_strdup_printf ("R - %s", katoob_label_get_text (KATOOB_LABEL (doc->priv->label))); katoob_label_set_text (KATOOB_LABEL (doc->priv->label), _tmp); g_free (_tmp); g_signal_emit (G_OBJECT (doc), katoob_document_signals[READONLY_SET], 0); } void katoob_document_unset_readonly (KatoobDocument * doc) { katoob_debug (__FUNCTION__); doc->priv->readonly = FALSE; g_signal_emit (G_OBJECT (doc), katoob_document_signals[READONLY_UNSET], 0); } gboolean katoob_document_get_readonly (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->readonly; } GtkTextBuffer * katoob_document_get_buffer (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return GTK_TEXT_VIEW (doc->priv->textview)->buffer; } GtkTextView * katoob_document_get_text_view (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return GTK_TEXT_VIEW (doc->priv->textview); } GtkTextTag * katoob_document_get_ltr_tag (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->tag_ltr; } GtkTextTag * katoob_document_get_rtl_tag (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->tag_rtl; } static void katoob_document_cursor_moved (GtkTextView * textview, GtkMovementStep arg1, gint arg2, gboolean arg3, KatoobDocument * doc) { GtkTextIter iter; katoob_debug (__FUNCTION__); gtk_text_buffer_get_iter_at_mark (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, &iter, gtk_text_buffer_get_insert (GTK_TEXT_VIEW (doc->priv-> textview)-> buffer)); g_signal_emit (G_OBJECT (doc), katoob_document_signals[CURSOR_MOVED], 0, katoob_document_get_column_pos (&iter) + 1, gtk_text_iter_get_line (&iter) + 1); } static gint katoob_document_get_column_pos (GtkTextIter * iter) { gint tab_width = 8; /* FIXME: How to get the actual tab width ? */ gint column = 0; GtkTextIter start; katoob_debug (__FUNCTION__); start = *iter; gtk_text_iter_set_line_offset (&start, 0); while (!gtk_text_iter_equal (&start, iter)) { if (gtk_text_iter_get_char (&start) == '\t') { column += tab_width; } else { ++column; } gtk_text_iter_forward_char (&start); } return column; } static void katoob_document_changed (GtkTextBuffer * buffer, const GtkTextIter * new_location, GtkTextMark * mark, KatoobDocument * doc) { GtkTextIter iter; katoob_debug (__FUNCTION__); gtk_text_buffer_get_iter_at_mark (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter, gtk_text_buffer_get_insert (GTK_TEXT_VIEW (doc->priv->textview)->buffer)); g_signal_emit (G_OBJECT (doc), katoob_document_signals[CURSOR_MOVED], 0, katoob_document_get_column_pos (&iter) + 1, gtk_text_iter_get_line (&iter) + 1); } void katoob_document_get_cursor_position (KatoobDocument * doc, gint * col, gint * lin) { GtkTextIter iter; katoob_debug (__FUNCTION__); gtk_text_buffer_get_iter_at_mark (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, &iter, gtk_text_buffer_get_insert (GTK_TEXT_VIEW (doc->priv-> textview)-> buffer)); *col = katoob_document_get_column_pos (&iter); *lin = gtk_text_iter_get_line (&iter); } void katoob_document_set_encoding (KatoobDocument * doc, gint enc) { katoob_debug (__FUNCTION__); if (doc->priv->encoding == enc) { return; } doc->priv->encoding = enc; g_signal_emit (G_OBJECT (doc), katoob_document_signals[ENCODING_CHANGED], 0, enc); } gint katoob_document_get_encoding (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->encoding; } gboolean katoob_document_get_textwrap (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->textwrap; } gboolean katoob_document_get_linenumbers (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->linenumbers; } static void katoob_document_mark_insertion_position (GtkTextBuffer * buffer, GtkTextIter * iter, gchar * text, gint len, KatoobDocument * doc) { katoob_debug (__FUNCTION__); gtk_text_buffer_move_mark (buffer, doc->priv->mark_insert, iter); /************************************************************************** * NOTE: This should be called before the actual insertion, otherwise the * * menu won't work correctly and it'll segfault! * **************************************************************************/ g_signal_emit (G_OBJECT (doc), katoob_document_signals[BUFFER_MODIFIED], 0, TRUE); } /* BiDi/Text direction stuff */ void katoob_document_set_bidi (KatoobDocument * doc, gint text_dir) { katoob_debug (__FUNCTION__); if (doc->priv->text_dir == text_dir) { return; } switch (text_dir) { case KATOOB_BIDI_RTL: { katoob_document_set_rtl (doc); break; } case KATOOB_BIDI_LTR: { katoob_document_set_ltr (doc); break; } default: { katoob_document_set_aut (doc); break; } } if (doc->priv->linenumbers) { katoob_document_set_line_numbers_dir (doc, text_dir); } g_signal_emit (G_OBJECT (doc), katoob_document_signals[TEXT_DIR_CHANGED], 0, text_dir); } static void katoob_document_set_dir (KatoobDocument * doc, gint kdir, gint dir) { GtkTextIter start, end; katoob_debug (__FUNCTION__); doc->priv->text_dir = kdir; gtk_text_buffer_get_bounds (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end); g_signal_handlers_disconnect_by_func (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), G_CALLBACK (katoob_bidi_insert_text_cb), doc); g_signal_handlers_disconnect_by_func (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), G_CALLBACK (katoob_bidi_delete_text_cb), doc); gtk_text_buffer_remove_tag (GTK_TEXT_VIEW (doc->priv->textview)->buffer, doc->priv->tag_rtl, &start, &end); gtk_text_buffer_remove_tag (GTK_TEXT_VIEW (doc->priv->textview)->buffer, doc->priv->tag_ltr, &start, &end); gtk_widget_set_direction (doc->priv->textview, dir); } static void katoob_document_set_rtl (KatoobDocument * doc) { katoob_debug (__FUNCTION__); katoob_document_set_dir (doc, KATOOB_BIDI_RTL, GTK_TEXT_DIR_RTL); } static void katoob_document_set_ltr (KatoobDocument * doc) { katoob_debug (__FUNCTION__); katoob_document_set_dir (doc, KATOOB_BIDI_LTR, GTK_TEXT_DIR_LTR); } static void katoob_document_set_aut (KatoobDocument * doc) { GtkTextIter start, end; katoob_debug (__FUNCTION__); doc->priv->text_dir = KATOOB_BIDI_AUT; g_signal_connect_after (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)->buffer), "insert-text", G_CALLBACK (katoob_bidi_insert_text_cb), doc); g_signal_connect_after (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)->buffer), "delete-range", G_CALLBACK (katoob_bidi_delete_text_cb), doc); /* Now we should process the whole buffer */ gtk_text_buffer_get_start_iter (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start); gtk_text_buffer_get_start_iter (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &end); gtk_text_iter_forward_to_line_end (&end); katoob_bidi_modify_line_dir_from_utf8_string (doc, gtk_text_buffer_get_text (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end, FALSE), start, end); /* Now start & end mark the beginning and end of the first line */ while (gtk_text_iter_forward_line (&start)) { gtk_text_iter_forward_line (&end); gtk_text_iter_forward_to_line_end (&end); katoob_bidi_modify_line_dir_from_utf8_string (doc, gtk_text_buffer_get_text (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end, FALSE), start, end); } } static void katoob_document_set_line_direction (KatoobDocument * doc, GtkTextIter start, GtkTextIter end) { /* * TODO: Complete this. * The idea: * We are going to get the first character in the line, Examine it. * If it's in the RTL or LTR Range, Then that's OK. Otherwise we are going to * examin the next characters till we find a suitable one. * If we didn't find We are going to inherit the previous line direction * Otherwise, We are going to assume LTR. * * NOW: We are going to search for the RTL or LTR property and leave the case * in which we won't find a strong character in the whole line later to be * implemented. */ gunichar ch; FriBidiCharType dir; GtkTextBuffer *buffer = GTK_TEXT_VIEW (doc->priv->textview)->buffer; katoob_debug (__FUNCTION__); ch = gtk_text_iter_get_char (&start); dir = fribidi_get_type (ch); if ((dir != FRIBIDI_TYPE_RTL) || (dir != FRIBIDI_TYPE_LTR) || (dir != FRIBIDI_TYPE_AL)) { while (gtk_text_iter_forward_char (&start)) { ch = gtk_text_iter_get_char (&start); dir = fribidi_get_type (ch); if ((dir == FRIBIDI_TYPE_RTL) || (dir == FRIBIDI_TYPE_LTR) || (dir == FRIBIDI_TYPE_AL)) { break; } } } /* Reset the iterators again. We didn't touch the end iterator. */ gtk_text_iter_set_line_offset (&start, 0); if ((dir == FRIBIDI_TYPE_RTL) || (dir == FRIBIDI_TYPE_AL)) { katoob_debug ("RTL"); gtk_text_buffer_apply_tag (buffer, doc->priv->tag_rtl, &start, &end); } else /* if (dir == FRIBIDI_TYPE_LTR) */ { katoob_debug ("LTR"); gtk_text_buffer_apply_tag (buffer, doc->priv->tag_ltr, &start, &end); } } void katoob_bidi_insert_text_cb (GtkTextBuffer * buffer, GtkTextIter * iter, gchar * text, gint len, KatoobDocument * doc) { /****************************************************************************** * We get the iter where the insertion occured "before" * * And we are passed the iter after the last inserted character "iter" * * We have 2 cases here: * * 1) before & iter are in the same line. * * Only handle this line. * * 2) before and iter are not in the same line. * * Handle all the lines. * ******************************************************************************/ GtkTextIter start, end, before; katoob_debug (__FUNCTION__); gtk_text_buffer_get_iter_at_mark (buffer, &before, doc->priv->mark_insert); gtk_text_buffer_get_iter_at_line (buffer, &start, gtk_text_iter_get_line (&before)); gtk_text_buffer_get_iter_at_line (buffer, &end, gtk_text_iter_get_line (&before)); if (!gtk_text_iter_ends_line (&end)) { gtk_text_iter_forward_to_line_end (&end); } /* * Now start is our start iterator and end is our end iterator, We are going * to remove ant bidi tags as we are going to revalidate all the lines between * start and end. */ gtk_text_buffer_remove_tag (buffer, doc->priv->tag_rtl, &start, &end); gtk_text_buffer_remove_tag (buffer, doc->priv->tag_ltr, &start, &end); if (gtk_text_iter_get_line (iter) == gtk_text_iter_get_line (&before)) { /* See 1 */ katoob_debug ("BIDI CASE 1"); katoob_document_set_line_direction (doc, start, end); } else { katoob_debug ("BIDI CASE 2"); /* See 2 */ while (1) { /****************************************************************************** * start and end mark the line to be modified, go on then move start to * * the beginning of the next line, If start and insert are on the same line * * "if so, they should be identical since both are at the start of the line", * * then don't continue, Else move end to the end of that line and continue! * ******************************************************************************/ { gchar *tmp = g_strdup_printf ("Processing line: %i", gtk_text_iter_get_line (&start)); katoob_debug (tmp); g_free (tmp); } katoob_document_set_line_direction (doc, start, end); gtk_text_iter_forward_line (&start); if (gtk_text_iter_get_line (&start) == gtk_text_iter_get_line (iter)) { /* The last line now */ katoob_debug ("Processing last line"); gtk_text_iter_forward_line (&end); if (!gtk_text_iter_ends_line (&end)) { gtk_text_iter_forward_to_line_end (&end); } katoob_document_set_line_direction (doc, start, end); katoob_debug ("Processed all lines!"); break; } gtk_text_iter_forward_line (&end); if (!gtk_text_iter_ends_line (&end)) { gtk_text_iter_forward_to_line_end (&end); } } } /* Done! */ } void katoob_bidi_delete_text_cb (GtkTextBuffer * buffer, GtkTextIter * start, GtkTextIter * end, KatoobDocument * doc) { /***************************************************************************** * NOTE: This function modifies the start iter, Thus cause problems with the * * spell checker stuff. * * We are copying the iterator to avoid moving it, As if we move it to the * * start of the line. It'll cause any inserted text at that iter to be * * inserted at the beginning of the line. * *****************************************************************************/ GtkTextIter iter = *start; katoob_debug (__FUNCTION__); if (!gtk_text_iter_ends_line (end)) { gtk_text_iter_forward_to_line_end (end); } if (!gtk_text_iter_starts_line (&iter)) { gtk_text_iter_set_line_offset (&iter, 0); } katoob_bidi_modify_line_dir_from_utf8_string (doc, gtk_text_buffer_get_text (buffer, start, end, FALSE), *start, *end); } void katoob_undo_insert_cb (GtkTextBuffer * buffer, GtkTextIter * iter, gchar * text, gint len, KatoobDocument * doc) { extern conf *config; guint i = g_list_length (katoob_document_get_undo (doc)); gint p = gtk_text_iter_get_offset (iter); katoob_debug (__FUNCTION__); if ((i > config->undono) && (config->undono != 0)) { GList *tmp = g_list_first (katoob_document_get_undo (doc)); if (tmp) { undo *tmp2; g_list_remove_link (katoob_document_get_undo (doc), tmp); tmp2 = tmp->data; if (tmp2->text) { g_free (tmp2->text); } if (tmp2->text2) { g_free (tmp2->text2); } g_list_free_1 (tmp); } } katoob_document_add_undo (doc, KATOOB_UNDO_TYPE_INSERT, text, NULL, p); } void katoob_undo_delete_cb (GtkTextBuffer * buffer, GtkTextIter * start, GtkTextIter * end, KatoobDocument * doc) { extern conf *config; gchar *text; guint i = g_list_length (katoob_document_get_undo (doc)); gint p = gtk_text_iter_get_offset (start); text = gtk_text_buffer_get_text (buffer, start, end, FALSE); katoob_debug (__FUNCTION__); if ((i > config->undono) && (config->undono != 0)) { GList *tmp = g_list_first (katoob_document_get_undo (doc)); if (tmp) { undo *tmp2; g_list_remove_link (katoob_document_get_undo (doc), tmp); tmp2 = tmp->data; if (tmp2->text) { g_free (tmp2->text); } if (tmp2->text2) { g_free (tmp2->text2); } g_list_free_1 (tmp); } } katoob_document_add_undo (doc, KATOOB_UNDO_TYPE_DELETE, text, NULL, p); } GList * katoob_document_get_undo (KatoobDocument * doc) { return doc->priv->UNDO; } GList * katoob_document_get_redo (KatoobDocument * doc) { return doc->priv->REDO; } gboolean katoob_document_get_can_undo (KatoobDocument * doc) { return doc->priv->can_undo; } gboolean katoob_document_get_can_redo (KatoobDocument * doc) { return doc->priv->can_redo; } static void katoob_document_can_undo_cb (KatoobDocument * doc, gboolean state) { doc->priv->can_undo = state; } static void katoob_document_can_redo_cb (KatoobDocument * doc, gboolean state) { doc->priv->can_redo = state; } void katoob_document_set_text (KatoobDocument * doc, gchar * text) { katoob_document_disable_undoredo (doc); gtk_text_buffer_set_text (GTK_TEXT_VIEW (doc->priv->textview)->buffer, text, -1); katoob_document_enable_undoredo (doc); } gchar * katoob_document_get_text (KatoobDocument * doc) { GtkTextIter start; GtkTextIter end; gtk_text_buffer_get_bounds (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end); return gtk_text_buffer_get_text (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, &start, &end, FALSE); } /* Update menu and label on modified change */ void katoob_document_set_modified (KatoobDocument * doc, gboolean modified) { katoob_debug (__FUNCTION__); if (katoob_document_get_modified (doc) != modified) { katoob_label_set_modified (KATOOB_LABEL (doc->priv->label), modified); katoob_document_menu_set_modified (doc, modified); katoob_statusbar_set_modified (KATOOB_STATUSBAR (katoob_get_statusbar ()), modified); gtk_text_buffer_set_modified (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, modified); } } gboolean katoob_document_get_modified (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return gtk_text_buffer_get_modified (GTK_TEXT_VIEW (doc->priv->textview)-> buffer); } static void katoob_document_disable_undoredo (KatoobDocument * doc) { katoob_debug (__FUNCTION__); g_signal_handlers_disconnect_by_func (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)->buffer), G_CALLBACK (katoob_undo_insert_cb), doc); g_signal_handlers_disconnect_by_func (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)->buffer), G_CALLBACK (katoob_undo_delete_cb), doc); } static void katoob_document_enable_undoredo (KatoobDocument * doc) { katoob_debug (__FUNCTION__); g_signal_connect (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)->buffer), "insert_text", G_CALLBACK (katoob_undo_insert_cb), doc); g_signal_connect (G_OBJECT (GTK_TEXT_VIEW (doc->priv->textview)->buffer), "delete_range", G_CALLBACK (katoob_undo_delete_cb), doc); } static void katoob_document_menu_set_modified (KatoobDocument * doc, gboolean modified) { gchar *label = NULL; gchar *tmp = NULL; katoob_debug (__FUNCTION__); if (modified) { label = g_strdup_printf ("%s *", katoob_document_menu_get_text (doc)); } else { tmp = katoob_document_menu_get_text (doc); label = g_strndup (tmp, strlen (tmp) - 2); /* g_free (tmp); DON'T FREE */ } katoob_document_menu_set_text (doc, label); g_free (label); } gboolean katoob_document_has_focus (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return GTK_WIDGET_HAS_FOCUS (doc->priv->textview); } GtkTextIter katoob_document_get_iter_at_insertion_mark (KatoobDocument * doc) { GtkTextIter iter; katoob_debug (__FUNCTION__); gtk_text_buffer_get_iter_at_mark (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, &iter, gtk_text_buffer_get_mark (GTK_TEXT_VIEW (doc->priv-> textview)-> buffer, "insert")); return iter; } void katoob_document_insert_text (KatoobDocument * doc, GtkTextIter iter, gchar * text, gint len) { katoob_debug (__FUNCTION__); gtk_text_buffer_insert (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter, text, len); } void katoob_document_add_undo (KatoobDocument * doc, KatoobUndoType type, gchar * text, gchar * text2, gint start) { undo *Undo; gboolean to_emit_signal = FALSE; katoob_debug (__FUNCTION__); if ((type != KATOOB_UNDO_TYPE_REPLACE) && (text2)) { g_warning ("This should never happen"); } Undo = g_malloc (sizeof (undo)); Undo->type = type; Undo->text = NULL; Undo->text2 = NULL; Undo->text = g_strdup (text); if (text2) { Undo->text2 = g_strdup (text2); } Undo->start = start; if (!doc->priv->UNDO) { to_emit_signal = TRUE; } doc->priv->UNDO = g_list_append (doc->priv->UNDO, Undo); if (to_emit_signal) { g_signal_emit (G_OBJECT (doc), katoob_document_signals[CAN_UNDO], 0, TRUE); } } /* TODO: The next 2 functions should be combined. */ void katoob_document_undo (KatoobDocument * doc) { GList *_Undo = NULL; undo *Undo; gboolean to_emit_signal = FALSE; katoob_debug (__FUNCTION__); _Undo = g_list_last (doc->priv->UNDO); if (!_Undo) { return; } Undo = (undo *) _Undo->data; katoob_document_disable_undoredo (doc); switch (Undo->type) { case KATOOB_UNDO_TYPE_INSERT: { GtkTextIter iter1; GtkTextIter iter2; Undo->type = KATOOB_UNDO_TYPE_DELETE; gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, Undo->start); gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter2, Undo->start + g_utf8_strlen (Undo->text, -1)); gtk_text_buffer_delete (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, &iter2); break; } case KATOOB_UNDO_TYPE_DELETE: { GtkTextIter iter; gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter, Undo->start); Undo->type = KATOOB_UNDO_TYPE_INSERT; gtk_text_buffer_insert (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter, Undo->text, -1); break; } case KATOOB_UNDO_TYPE_REPLACE: { GtkTextIter iter1; GtkTextIter iter2; gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, Undo->start); gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter2, Undo->start + g_utf8_strlen (Undo->text2, -1)); gtk_text_buffer_delete (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, &iter2); gtk_text_buffer_insert (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter2, Undo->text, -1); break; } default: g_warning ("This shouldn't happen"); break; } katoob_document_enable_undoredo (doc); doc->priv->UNDO = g_list_remove (doc->priv->UNDO, Undo); if (!doc->priv->UNDO) { g_signal_emit (G_OBJECT (doc), katoob_document_signals[CAN_UNDO], 0, FALSE); } if (!doc->priv->REDO) { to_emit_signal = TRUE; } doc->priv->REDO = g_list_append (doc->priv->REDO, Undo); if (to_emit_signal) { g_signal_emit (G_OBJECT (doc), katoob_document_signals[CAN_REDO], 0, TRUE); } } void katoob_document_redo (KatoobDocument * doc) { GList *_Redo = NULL; undo *Redo; gboolean to_emit_signal = FALSE; katoob_debug (__FUNCTION__); _Redo = g_list_last (doc->priv->REDO); if (_Redo == NULL) { return; } Redo = (undo *) _Redo->data; katoob_document_disable_undoredo (doc); switch (Redo->type) { case KATOOB_UNDO_TYPE_INSERT: { GtkTextIter iter1; GtkTextIter iter2; Redo->type = KATOOB_UNDO_TYPE_DELETE; gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, Redo->start); gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter2, Redo->start + g_utf8_strlen (Redo->text, -1)); gtk_text_buffer_delete (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, &iter2); break; } case KATOOB_UNDO_TYPE_DELETE: { GtkTextIter iter; gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter, Redo->start); Redo->type = KATOOB_UNDO_TYPE_INSERT; gtk_text_buffer_insert (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter, Redo->text, -1); break; } case KATOOB_UNDO_TYPE_REPLACE: { GtkTextIter iter1; GtkTextIter iter2; gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, Redo->start); gtk_text_buffer_get_iter_at_offset (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter2, Redo->start + g_utf8_strlen (Redo->text, -1)); gtk_text_buffer_delete (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, &iter2); gtk_text_buffer_insert (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &iter1, Redo->text2, -1); break; } default: g_warning ("This shouldn't happen"); break; } katoob_document_enable_undoredo (doc); doc->priv->REDO = g_list_remove (doc->priv->REDO, Redo); if (!doc->priv->REDO) { g_signal_emit (G_OBJECT (doc), katoob_document_signals[CAN_REDO], 0, FALSE); } if (!doc->priv->UNDO) { to_emit_signal = TRUE; } doc->priv->UNDO = g_list_append (doc->priv->UNDO, Redo); if (to_emit_signal) { g_signal_emit (G_OBJECT (doc), katoob_document_signals[CAN_UNDO], 0, TRUE); } } static void katoob_document_populate_popup (GtkTextView * textview, GtkMenu * menu, KatoobDocument * doc) { GtkWidget *item, *sub_menu; GSList *rg = NULL; #ifdef ENABLE_HIGHLIGHT /* Let's destroy the undo & redo items added by gtksourceview ;-) */ GList *children = GTK_MENU_SHELL (menu)->children; gtk_widget_destroy (GTK_WIDGET (g_list_nth_data (children, 0))); children = GTK_MENU_SHELL (menu)->children; gtk_widget_destroy (GTK_WIDGET (g_list_nth_data (children, 0))); children = GTK_MENU_SHELL (menu)->children; gtk_widget_destroy (GTK_WIDGET (g_list_nth_data (children, 0))); #endif /* ENABLE_HIGHLIGHT */ katoob_debug (__FUNCTION__); item = gtk_menu_item_new (); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); item = gtk_menu_item_new_with_label (("Text direction")); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); sub_menu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), sub_menu); item = gtk_radio_menu_item_new_with_mnemonic (rg, _("_Left to right")); if (doc->priv->text_dir == KATOOB_BIDI_LTR) { gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); } g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (katoob_set_doc_dir_ltr), doc); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (sub_menu), item); rg = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); item = gtk_radio_menu_item_new_with_mnemonic (rg, _("_Right to left")); if (doc->priv->text_dir == KATOOB_BIDI_RTL) { gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); } g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (katoob_set_doc_dir_rtl), doc); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (sub_menu), item); rg = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); item = gtk_radio_menu_item_new_with_mnemonic (rg, _("_Automatic")); if (doc->priv->text_dir == KATOOB_BIDI_AUT) { gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); } g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (katoob_set_doc_dir_aut), doc); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (sub_menu), item); rg = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); #ifdef HAVE_SPELL if (doc->priv->spell_check) { GtkWidget *img, *mi; GtkWidget *suggestion_menu = NULL; GtkTextIter start, end; char *word; /* we need to figure out if they picked a mispelled word. */ gtk_text_buffer_get_iter_at_mark (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, &start, gtk_text_buffer_get_insert (GTK_TEXT_VIEW (doc->priv->textview)-> buffer)); if (!gtk_text_iter_starts_word (&start)) { gtk_text_iter_backward_word_start (&start); } end = start; if (gtk_text_iter_inside_word (&end)) { gtk_text_iter_forward_word_end (&end); } /* if our highlight algorithm ever messes up, * this isn't correct, either. */ if (!gtk_text_iter_has_tag (&start, doc->priv->tag_highlight)) { return; /* word wasn't mispelled. */ } /* menu separator comes first. */ mi = gtk_menu_item_new (); gtk_widget_show (mi); gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); /* then, on top of it, the suggestions menu. */ img = gtk_image_new_from_stock (GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU); mi = gtk_image_menu_item_new_with_label (_("Spelling Suggestions")); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), img); word = gtk_text_buffer_get_text (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end, FALSE); if (((unsigned char) word[0] == 0xd8) || ((unsigned char) word[0] == 0xd9)) { /* TODO: Duali support here! */ } else { suggestion_menu = spell_build_suggestion_menu (doc, word); } gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), suggestion_menu); g_free (word); gtk_widget_show_all (mi); gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); } #endif /* HAVE_SPELL */ return; } static void katoob_set_doc_dir_ltr (GtkCheckMenuItem * item, KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (item->active) { katoob_document_set_bidi (doc, KATOOB_BIDI_LTR); } } static void katoob_set_doc_dir_rtl (GtkCheckMenuItem * item, KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (item->active) { katoob_document_set_bidi (doc, KATOOB_BIDI_RTL); } } static void katoob_set_doc_dir_aut (GtkCheckMenuItem * item, KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (item->active) { katoob_document_set_bidi (doc, KATOOB_BIDI_AUT); } } void katoob_document_cut (KatoobDocument * doc) { extern UI *katoob; katoob_debug (__FUNCTION__); gtk_text_buffer_cut_clipboard (GTK_TEXT_VIEW (doc->priv->textview)->buffer, katoob->clipboard, TRUE); } void katoob_document_copy (KatoobDocument * doc) { extern UI *katoob; katoob_debug (__FUNCTION__); gtk_text_buffer_copy_clipboard (GTK_TEXT_VIEW (doc->priv->textview)->buffer, katoob->clipboard); } void katoob_document_delete (KatoobDocument * doc) { katoob_debug (__FUNCTION__); gtk_text_buffer_delete_selection (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, TRUE, TRUE); } void katoob_document_paste (KatoobDocument * doc) { extern UI *katoob; katoob_debug (__FUNCTION__); gtk_text_buffer_paste_clipboard (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, katoob->clipboard, NULL, TRUE); } void katoob_document_select_all (KatoobDocument * doc) { GtkTextIter start; GtkTextIter end; katoob_debug (__FUNCTION__); gtk_text_buffer_get_start_iter (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start); gtk_text_buffer_get_end_iter (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &end); gtk_text_buffer_move_mark_by_name (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, "selection_bound", &start); gtk_text_buffer_move_mark_by_name (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, "insert", &end); } void katoob_document_goto_line (KatoobDocument * doc, gint n) { gint count; GtkTextIter iter; katoob_debug (__FUNCTION__); count = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer)); if (n > count) { n = count; } gtk_text_buffer_get_iter_at_line (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, &iter, n); gtk_text_buffer_place_cursor (GTK_TEXT_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), &iter); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (doc->priv->textview), gtk_text_buffer_get_insert (GTK_TEXT_VIEW (doc->priv->textview)-> buffer)); } void katoob_document_reset_undo_redo (KatoobDocument * doc) { katoob_debug (__FUNCTION__); katoob_document_free_undoredo (doc); g_list_free (doc->priv->UNDO); g_list_free (doc->priv->REDO); doc->priv->UNDO = NULL; doc->priv->REDO = NULL; } void katoob_document_grab_focus (KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (doc) { gtk_widget_grab_focus (doc->priv->textview); } } void katoob_document_enable_line_numbers (KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (!doc) { return; } if (doc->priv->text_dir == KATOOB_BIDI_RTL) { doc->priv->line_numbers_window = GTK_TEXT_WINDOW_RIGHT; } else { doc->priv->line_numbers_window = GTK_TEXT_WINDOW_LEFT; } gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (doc->priv->textview), doc->priv->line_numbers_window, 20); g_signal_connect (G_OBJECT (doc->priv->textview), "expose-event", G_CALLBACK (katoob_document_textview_expose_event_cb), doc); doc->priv->linenumbers = TRUE; } static void katoob_document_set_line_numbers_dir (KatoobDocument * doc, gint text_dir) { katoob_debug (__FUNCTION__); switch (text_dir) { case KATOOB_BIDI_RTL: { if (doc->priv->line_numbers_window == GTK_TEXT_WINDOW_RIGHT) { return; } gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (doc->priv->textview), GTK_TEXT_WINDOW_LEFT, 0); gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (doc->priv->textview), GTK_TEXT_WINDOW_RIGHT, 20); doc->priv->line_numbers_window = GTK_TEXT_WINDOW_RIGHT; break; } default: { if (doc->priv->line_numbers_window == GTK_TEXT_WINDOW_LEFT) { return; } gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (doc->priv->textview), GTK_TEXT_WINDOW_RIGHT, 0); gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (doc->priv->textview), GTK_TEXT_WINDOW_LEFT, 20); doc->priv->line_numbers_window = GTK_TEXT_WINDOW_LEFT; } } } void katoob_document_disable_line_numbers (KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (!doc) { return; } gtk_text_view_set_border_window_size (GTK_TEXT_VIEW (doc->priv->textview), doc->priv->line_numbers_window, 0); g_signal_handlers_disconnect_by_func (G_OBJECT (doc->priv->textview), G_CALLBACK (katoob_document_textview_expose_event_cb), doc); doc->priv->linenumbers = FALSE; } /* With aid from Bluefish, Thanks! */ static gboolean katoob_document_textview_expose_event_cb (GtkWidget * widget, GdkEventExpose * event, KatoobDocument * doc) { gchar *_tmp; GdkRectangle rect; GtkTextIter start, end, it, tmp; gint top1, top2; PangoLayout *pl; gchar *pomstr; gint numlines, sz, i; GtkTextView *view = (GtkTextView *) widget; GdkWindow *win = gtk_text_view_get_window (view, doc->priv->line_numbers_window); GtkTextBuffer *buffer = GTK_TEXT_VIEW (doc->priv->textview)->buffer; katoob_debug (__FUNCTION__); if (win != event->window) { return FALSE; } gtk_text_buffer_get_end_iter (buffer, &tmp); gtk_text_view_get_visible_rect (view, &rect); gtk_text_view_get_line_at_y (view, &start, rect.y, &top1); gtk_text_view_get_line_at_y (view, &end, rect.y + rect.height, &top2); pl = gtk_widget_create_pango_layout (widget, ""); numlines = gtk_text_buffer_get_line_count (buffer); _tmp = g_strdup_printf ("%d", gtk_text_iter_get_line (&tmp)); pango_layout_set_text (pl, _tmp, -1); g_free (_tmp); pango_layout_get_pixel_size (pl, &sz, NULL); gtk_text_view_set_border_window_size (view, doc->priv->line_numbers_window, sz + 4); it = start; for (i = gtk_text_iter_get_line (&start); i <= gtk_text_iter_get_line (&end); i++) { gtk_text_iter_set_line (&it, i); gtk_text_view_get_line_yrange (view, &it, &sz, NULL); gtk_text_view_buffer_to_window_coords (view, doc->priv->line_numbers_window, 0, sz, NULL, &sz); pomstr = g_strdup_printf ("%d", i + 1); pango_layout_set_text (pl, pomstr, -1); gtk_paint_layout (widget->style, win, GTK_WIDGET_STATE (widget), FALSE, NULL, widget, NULL, 2, sz, pl); g_free (pomstr); } g_object_unref (G_OBJECT (pl)); return TRUE; } void katoob_document_enable_text_wrap (KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (!doc) { return; } gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (doc->priv->textview), GTK_WRAP_WORD); doc->priv->textwrap = TRUE; } void katoob_document_disable_text_wrap (KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (!doc) { return; } gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (doc->priv->textview), GTK_WRAP_NONE); doc->priv->textwrap = FALSE; } static void katoob_document_insert_text_cb (GtkTextBuffer * buffer, GtkTextIter * iter, gchar * text, gint len, KatoobDocument * doc) { #ifdef HAVE_SPELL GtkTextIter start; #endif /* HAVE_SPELL */ /* g_signal_emit (G_OBJECT (doc), katoob_document_signals[BUFFER_MODIFIED], 0, TRUE); */ katoob_debug (__FUNCTION__); #ifdef HAVE_SPELL if (doc->priv->spell_check) { gtk_text_buffer_get_iter_at_mark (buffer, &start, doc->priv->mark_insert); katoob_spell_check_range (doc, start, *iter); } #endif /* HAVE_SPELL */ } static void katoob_document_delete_range_cb (GtkTextBuffer * buffer, GtkTextIter * start, GtkTextIter * end, KatoobDocument * doc) { katoob_debug (__FUNCTION__); g_signal_emit (G_OBJECT (doc), katoob_document_signals[BUFFER_MODIFIED], 0, TRUE); #ifdef HAVE_SPELL if (doc->priv->spell_check) { katoob_spell_check_range (doc, *start, *end); } #endif /* HAVE_SPELL */ } gchar * katoob_document_get_last_searched (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->last_searched; } gchar * katoob_document_get_last_replaced (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->last_replaced; } gboolean katoob_document_get_match_case (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->match_case; } gboolean katoob_document_get_beginning (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->beginning; } void katoob_document_set_last_searched (KatoobDocument * doc, gchar * last_searched) { katoob_debug (__FUNCTION__); if (last_searched) { if (doc->priv->last_searched) { g_free (doc->priv->last_searched); } doc->priv->last_searched = g_strdup (last_searched); } } void katoob_document_set_last_replaced (KatoobDocument * doc, gchar * last_replaced) { katoob_debug (__FUNCTION__); if (last_replaced) { if (doc->priv->last_replaced) { g_free (doc->priv->last_replaced); } doc->priv->last_replaced = g_strdup (last_replaced); } } void katoob_document_set_match_case (KatoobDocument * doc, gboolean match_case) { katoob_debug (__FUNCTION__); if (doc->priv->match_case != match_case) { doc->priv->match_case = match_case; g_signal_emit (G_OBJECT (doc), katoob_document_signals[MATCH_CASE_CHANGED], 0, match_case); } } void katoob_document_set_beginning (KatoobDocument * doc, gboolean beginning) { katoob_debug (__FUNCTION__); if (doc->priv->beginning != beginning) { doc->priv->beginning = beginning; g_signal_emit (G_OBJECT (doc), katoob_document_signals[BEGINNING_CHANGED], 0, beginning); } } /**************************************************************************** * Search a document for text, Returns TRUE if found or FALSE if not, The * * found text is highlighted, And the text view is scrolled to the text. * ****************************************************************************/ /***************************************************************************** * One of the most stupid functions I have ever wrote, Damn! Do you know how * * much do I hate this function? ;-) * *****************************************************************************/ gboolean katoob_document_search (KatoobDocument * doc) { /* TODO: to be revised */ GtkTextIter from; GtkTextIter start; GtkTextIter end; GtkTextBuffer *buffer; GtkTextSearchFlags search_flags = GTK_TEXT_SEARCH_VISIBLE_ONLY | GTK_TEXT_SEARCH_TEXT_ONLY; gboolean found = FALSE; katoob_debug (__FUNCTION__); buffer = GTK_TEXT_VIEW (doc->priv->textview)->buffer; if (doc->priv->beginning) { gtk_text_buffer_get_start_iter (buffer, &from); } else { gtk_text_buffer_get_iter_at_mark (buffer, &from, gtk_text_buffer_get_mark (buffer, "insert")); } if (doc->priv->match_case) { found = gtk_text_iter_forward_search (&from, doc->priv->last_searched, search_flags, &start, &end, NULL); } else { GtkTextBuffer *buff; GtkTextIter tstart; GtkTextIter tend; GtkTextIter iter; gint fromo; GtkTextTagTable *tt; gchar *bb, *b, *txt; tt = gtk_text_buffer_get_tag_table (buffer); buff = gtk_text_buffer_new (tt); gtk_text_buffer_get_start_iter (buffer, &tstart); gtk_text_buffer_get_end_iter (buffer, &tend); bb = gtk_text_buffer_get_text (buffer, &tstart, &tend, TRUE); b = g_utf8_strup (bb, -1); txt = g_utf8_strup (doc->priv->last_searched, -1); gtk_text_buffer_get_iter_at_offset (buff, &iter, 0); gtk_text_buffer_insert (buff, &iter, b, -1); fromo = gtk_text_iter_get_offset (&from); gtk_text_buffer_get_iter_at_offset (buff, &from, fromo); found = gtk_text_iter_forward_search (&from, txt, search_flags, &tstart, &tend, NULL); if (found) { fromo = gtk_text_iter_get_offset (&tstart); gtk_text_buffer_get_iter_at_offset (buffer, &start, fromo); fromo = gtk_text_iter_get_offset (&tend); gtk_text_buffer_get_iter_at_offset (buffer, &end, fromo); } g_free (b); g_free (buff); } if (!found) { return FALSE; } else { gtk_text_buffer_move_mark_by_name (buffer, "selection_bound", &start); gtk_text_buffer_move_mark_by_name (buffer, "insert", &end); /* Scroll the buffer till the highlighted position is onscreen */ gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (doc->priv->textview), gtk_text_buffer_get_insert (GTK_TEXT_VIEW (doc->priv->textview)->buffer)); return TRUE; } } gboolean katoob_document_get_selection_bounds (KatoobDocument * doc, GtkTextIter * start, GtkTextIter * end) { katoob_debug (__FUNCTION__); return gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), start, end); } /************************************************************************ * Replace every occurance of "doc->priv->last_searched" with * * "doc->priv->last_replaced", Returns the number of * * replaces, 0 if no replaces, -1 in case of error? * ***********************************************************************/ gint katoob_document_replace_all (KatoobDocument * doc) { gint x = 0; katoob_debug (__FUNCTION__); if ((!doc->priv->last_searched) || (!doc->priv->last_replaced)) { return -1; } while (katoob_document_search (doc)) { katoob_document_replace (doc); ++x; } return x; } /****************************************************************************** * Replace the highlighted part of the document with doc->priv->last_replaced * ******************************************************************************/ gboolean katoob_document_replace (KatoobDocument * doc) { GtkTextIter start, end; gboolean x = katoob_document_get_selection_bounds (doc, &start, &end); gint p; katoob_debug (__FUNCTION__); if (!x) { return FALSE; } if ((!doc->priv->last_searched) || (!doc->priv->last_replaced)) { return FALSE; } p = gtk_text_iter_get_offset (&start); katoob_document_disable_undoredo (doc); gtk_text_buffer_insert (GTK_TEXT_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), &start, doc->priv->last_replaced, strlen (doc->priv->last_replaced)); gtk_text_buffer_delete_selection (GTK_TEXT_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), FALSE, TRUE); katoob_document_enable_undoredo (doc); /* Now add the undo entry */ katoob_document_add_undo (doc, KATOOB_UNDO_TYPE_REPLACE, doc->priv->last_searched, doc->priv->last_replaced, p); return TRUE; } void katoob_document_set_font (KatoobDocument * doc, PangoFontDescription * fd) { katoob_debug (__FUNCTION__); if (fd) { gtk_widget_modify_font (GTK_WIDGET (doc->priv->textview), fd); } else { /* Set the default font */ PangoFontDescription *fd2; GtkWidget *foo = gtk_text_view_new (); fd2 = pango_font_description_copy (foo->style->font_desc); gtk_widget_destroy (foo); gtk_widget_modify_font (GTK_WIDGET (doc->priv->textview), fd2); pango_font_description_free (fd2); } } /* Our spell checker interface */ /* Many parts are ripped from gtkspell ;-) Thanks! */ #ifdef HAVE_SPELL gpointer katoob_document_get_speller (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->speller; } GtkTextTag * katoob_document_get_highlight_tag (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->tag_highlight; } GtkTextMark * katoob_document_get_mark (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->mark; } void katoob_document_enable_spell_checker (KatoobDocument * doc) { GtkTextIter start, end; katoob_debug (__FUNCTION__); if (!doc) { return; } if (doc->priv->spell_check) { return; } doc->priv->spell_check = TRUE; g_signal_emit (G_OBJECT (doc), katoob_document_signals[SPELL_TOGGLED], 0, TRUE); gtk_text_buffer_get_bounds (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end); katoob_spell_check_range (doc, start, end); } void katoob_document_disable_spell_checker (KatoobDocument * doc) { GtkTextIter start, end; katoob_debug (__FUNCTION__); if (!doc) { return; } if (!doc->priv->spell_check) { return; } doc->priv->spell_check = FALSE; g_signal_emit (G_OBJECT (doc), katoob_document_signals[SPELL_TOGGLED], 0, FALSE); gtk_text_buffer_get_bounds (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end); gtk_text_buffer_remove_tag (GTK_TEXT_VIEW (doc->priv->textview)->buffer, doc->priv->tag_highlight, &start, &end); } static gboolean spell_button_press_event (GtkTextView * view, GdkEventButton * event, KatoobDocument * doc) { katoob_debug (__FUNCTION__); if (event->button == 3) { gint x, y; GtkTextIter iter; GtkTextIter start, end; gtk_text_view_window_to_buffer_coords (view, GTK_TEXT_WINDOW_TEXT, event->x, event->y, &x, &y); gtk_text_view_get_iter_at_location (view, &iter, x, y); /* In case of text selection, DON'T move the cursor otherwise we'll destroy it. */ if (! (gtk_text_buffer_get_selection_bounds (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end) && gtk_text_iter_in_range (&iter, &start, &end))) { gtk_text_buffer_place_cursor (GTK_TEXT_VIEW (view)->buffer, &iter); } } return FALSE; /* false: let gtk process this event, too. we don't want to eat any events. */ } gint katoob_document_get_dictionary (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->dictionary; } gboolean katoob_document_get_spell_checker (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->spell_check; } gboolean katoob_document_set_dictionary (KatoobDocument * doc, gchar * lang) { gchar *_tmp; AspellConfig *cfg; AspellCanHaveError *err; extern conf *config; katoob_debug (__FUNCTION__); if (!lang) { return FALSE; } cfg = new_aspell_config (); aspell_config_replace (cfg, "language-tag", lang); aspell_config_replace (cfg, "encoding", "utf-8"); err = new_aspell_speller (cfg); delete_aspell_config (cfg); if (aspell_error_number (err) != 0) { _tmp = g_strdup_printf ("Couldn't the spell checker language to %s, Reason: %s", lang, aspell_error_message (err)); g_warning (_tmp); g_free (_tmp); return FALSE; } if (doc->priv->speller) { delete_aspell_speller (doc->priv->speller); } doc->priv->speller = to_aspell_speller (err); doc->priv->dictionary = g_slist_position (config->dicts, g_slist_find_custom (config->dicts, lang, (GCompareFunc) strcmp)); return TRUE; } void katoob_document_recheck_spell (KatoobDocument * doc) { GtkTextIter start, end; katoob_debug (__FUNCTION__); if (!doc) { return; } gtk_text_buffer_get_bounds (GTK_TEXT_VIEW (doc->priv->textview)->buffer, &start, &end); katoob_spell_check_range (doc, start, end); } static void katoob_spell_check_range (KatoobDocument * doc, GtkTextIter start, GtkTextIter end) { GtkTextIter wstart, wend; GtkTextBuffer *buffer = GTK_TEXT_VIEW (doc->priv->textview)->buffer; katoob_debug (__FUNCTION__); if (gtk_text_iter_inside_word (&end)) { gtk_text_iter_forward_word_end (&end); } if (!gtk_text_iter_starts_word (&start)) { if (gtk_text_iter_inside_word (&start) || gtk_text_iter_ends_word (&start)) { gtk_text_iter_backward_word_start (&start); } else { /* if we're neither at the beginning nor inside a word, * me must be in some spaces. * skip forward to the beginning of the next word. */ if (gtk_text_iter_forward_word_end (&start)) gtk_text_iter_backward_word_start (&start); } } gtk_text_buffer_remove_tag (buffer, doc->priv->tag_highlight, &start, &end); wstart = start; while (gtk_text_iter_compare (&wstart, &end) < 0) { /* move wend to the end of the current word. */ wend = wstart; gtk_text_iter_forward_word_end (&wend); katoob_spell_check_word (doc, &wstart, &wend); /* now move wend to the beginning of the next word, */ gtk_text_iter_forward_word_end (&wend); gtk_text_iter_backward_word_start (&wend); /* make sure we've actually advanced * (we don't advance in some corner cases), */ if (gtk_text_iter_equal (&wstart, &wend)) break; /* we're done in these cases.. */ /* and then pick this as the new next word beginning. */ wstart = wend; } } static void katoob_spell_check_word (KatoobDocument * doc, GtkTextIter * start, GtkTextIter * end) { /* Arabic letters begin with 0xd8 0xd9 */ gchar *text = gtk_text_buffer_get_text (GTK_TEXT_VIEW (doc->priv->textview)->buffer, start, end, FALSE); katoob_debug (__FUNCTION__); if (((unsigned char) text[0] == 0xd8) || ((unsigned char) text[0] == 0xd9)) { /* TODO: Duali code should be here */ katoob_debug ("Skipping checking arabic text!"); } else { if (!aspell_speller_check (doc->priv->speller, text, -1)) { gtk_text_buffer_apply_tag (GTK_TEXT_VIEW (doc->priv->textview)-> buffer, doc->priv->tag_highlight, start, end); } } g_free (text); } static GtkWidget * spell_build_suggestion_menu (KatoobDocument * doc, gchar * word) { gchar *_tmp; const gchar *suggestion; GtkWidget *topmenu, *menu; GtkWidget *mi; int count = 0; const AspellWordList *suggestions; AspellStringEnumeration *elements; char *label; katoob_debug (__FUNCTION__); topmenu = menu = gtk_menu_new (); /* + Add to Dictionary */ label = g_strdup_printf (_("Add \"%s\" to Dictionary"), word); mi = gtk_image_menu_item_new_with_label (label); g_free (label); gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_MENU)); g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (spell_add_to_dictionary), doc); gtk_widget_show_all (mi); gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); /* Separator */ mi = gtk_menu_item_new (); gtk_widget_show (mi); gtk_menu_shell_append (GTK_MENU_SHELL (topmenu), mi); suggestions = aspell_speller_suggest (doc->priv->speller, word, -1); elements = aspell_word_list_elements (suggestions); suggestion = aspell_string_enumeration_next (elements); if (!suggestion) { /* no suggestions. put something in the menu anyway... */ GtkWidget *label; label = gtk_label_new (""); _tmp = g_strdup_printf ("(%s)", _("no suggestions")); gtk_label_set_markup (GTK_LABEL (label), _tmp); g_free (_tmp); mi = gtk_menu_item_new (); gtk_container_add (GTK_CONTAINER (mi), label); gtk_widget_show_all (mi); gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), mi); } else { /* build a set of menus with suggestions. */ while (suggestion) { if (count == 10) { mi = gtk_menu_item_new (); gtk_widget_show (mi); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); mi = gtk_menu_item_new_with_label (_("More...")); gtk_widget_show (mi); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); menu = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu); count = 0; } mi = gtk_menu_item_new_with_label (suggestion); g_signal_connect (G_OBJECT (mi), "activate", G_CALLBACK (spell_replace_word), doc); gtk_widget_show (mi); gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi); count++; suggestion = aspell_string_enumeration_next (elements); } } delete_aspell_string_enumeration (elements); return topmenu; } #endif /* HAVE_SPELL */ #ifdef ENABLE_HIGHLIGHT void katoob_document_set_highlight (KatoobDocument * doc, GtkSourceLanguagesManager * manager, KatoobHighlightType t) { katoob_debug (__FUNCTION__); if (doc->priv->hl == t) { return; } doc->priv->hl = t; switch (t) { case KATOOB_HIGHLIGHT_NONE: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), NULL); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), FALSE); if (config->spell_check) { katoob_document_enable_spell_checker (doc); } katoob_document_set_bidi (doc, config->text_dir); return; } case KATOOB_HIGHLIGHT_ADA: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-ada")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_C: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-c")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_CPP: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-c++")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_DESKTOP: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "application/x-desktop")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_DIFF: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-diff")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_HTML: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/html")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_IDL: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-idl")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_JAVA: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-java")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_LATEX: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-tex")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_PERL: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-perl")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_PO: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-po")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_PYTHON: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/x-python")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } case KATOOB_HIGHLIGHT_XML: { gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)-> buffer), gtk_source_languages_manager_get_language_from_mime_type (manager, "text/xml")); gtk_source_buffer_set_highlight (GTK_SOURCE_BUFFER (GTK_TEXT_VIEW (doc->priv->textview)->buffer), TRUE); break; } } katoob_document_disable_spell_checker (doc); katoob_document_set_bidi (doc, KATOOB_BIDI_LTR); } KatoobHighlightType katoob_document_get_highlight_language (KatoobDocument * doc) { katoob_debug (__FUNCTION__); return doc->priv->hl; } #endif /* ENABLE_HIGHLIGHT */