/* * Copyright (C) 2004-2005 Vadim Berezniker * http://www.kryptolus.com * * 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, 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 GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "stdafx.h" #include "common.h" #include "sabbu.h" #include "kryString.h" #include "kryArray.h" #include "krySSACommandParser.h" #include "krySSACommandHighlighter.h" #include "spell.h" //#include "gui_spell.h" #include "gui_text_editor.h" #include "gui_main_karaoke.h" extern struct sabbu app; static GTimeVal text_changed_time; static gboolean text_timer_on = FALSE; static gboolean gui_text_editor_syntax_highlight_timer(struct KryTextEditor *editor); void gui_tex_editor_insert_text_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, int len, struct KryTextEditor *editor); void gui_tex_editor_delete_text_cb(GtkTextBuffer *buffer, GtkTextIter *iter1, GtkTextIter *iter2, struct KryTextEditor *editor); void gui_text_editor_spellcheck(struct KryTextEditor *editor, char *text, GtkTextBuffer *buffer, int *offset_start, int *offset_end); void gui_text_editor_spellcheck_syntax_off(struct KryTextEditor *editor, char *text); void gui_text_editor_range_free(void *item, void *param) { kry_free(item); } int gui_text_editor_range_compare(const void *a, const void *b, void *param) { struct KryRange *range1 = (struct KryRange *) a; struct KryRange *range2 = (struct KryRange *) b; if(range1->start < range2->start) return -1; else if(range1->start > range2->start) return 1; else return 0; } void gui_text_editor_text_changed_cb(GtkWidget *widget, struct KryTextEditor *editor) { gtk_widget_queue_draw(GTK_WIDGET(app.ui.video_area)); g_get_current_time(&text_changed_time); if(!text_timer_on) { text_timer_on = TRUE; g_timeout_add(500, (GSourceFunc) gui_text_editor_syntax_highlight_timer, editor); } } void gui_text_editor_set_text(struct KryTextEditor *editor, char *text) { GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); g_signal_handlers_disconnect_by_func(G_OBJECT(buffer), (void *) gui_text_editor_text_changed_cb, editor); GtkTextIter start_iter; gtk_text_buffer_set_text (buffer, text, -1); gtk_text_buffer_get_start_iter(buffer, &start_iter); gtk_text_buffer_place_cursor(buffer, &start_iter); gui_text_editor_do_syntax_highlighting(editor, FALSE); /*if(editor->syntax_highlight_mode == SYNTAX_HIGHLIGHT_NONE) { int offset_start, offset_end; gui_text_editor_spellcheck(editor, text, buffer, &offset_start, &offset_end); }*/ g_signal_connect(G_OBJECT(buffer), "changed", G_CALLBACK(gui_text_editor_text_changed_cb), editor); } char *gui_text_editor_replace_placeholders(char *text, krySSACommandHighlighterHiddenPartList *parts_obj, int *insert_cursor, int *selection_cursor) { GList *parts = (parts_obj ? parts_obj->GetGList() : NULL); GList *string_parts = NULL; GList *ptr; gchar *text_ptr; gchar *last_start; int len; int pos = 0; int total_len = 0; int offset = 0; int orig_insert_cursor = 0; int orig_selection_cursor = 0; if(insert_cursor) orig_insert_cursor = *insert_cursor; if(selection_cursor) orig_selection_cursor = *selection_cursor; if(!parts_obj) return kry_strdup(text); last_start = text_ptr = text; len = strlen((const char *) text); do { gunichar cur_char = g_utf8_get_char(text_ptr); if(cur_char == 0) break; if(cur_char == 0xFFFC) { char *part_copy = (char *) kry_malloc(text_ptr - last_start + 1); memcpy(part_copy, last_start, text_ptr - last_start); part_copy[text_ptr - last_start] = 0; string_parts = g_list_append(string_parts, part_copy); total_len += (text_ptr - last_start); if(insert_cursor && pos < orig_insert_cursor) (*insert_cursor)--; if(selection_cursor && pos < orig_selection_cursor) (*selection_cursor)--; krySSACommandHighlighterHiddenPart *part = (krySSACommandHighlighterHiddenPart *) parts->data; parts = parts->next; for(GList *ptr = part->GetStringGList(); ptr; ptr = ptr->next) { part_copy = kry_strdup((const gchar *) (ptr->data)); string_parts = g_list_append(string_parts, part_copy); total_len += strlen((const char *) ptr->data); if(insert_cursor && pos < orig_insert_cursor) *insert_cursor += g_utf8_strlen((const gchar *) ptr->data, -1); if(selection_cursor && pos < orig_selection_cursor) *selection_cursor += g_utf8_strlen((const gchar *) ptr->data, -1); } last_start = text_ptr + 3; } pos++; } while((text_ptr = g_utf8_next_char(text_ptr))); if(last_start < text_ptr) { char *part_copy = kry_strdup(last_start); string_parts = g_list_append(string_parts, part_copy); total_len += strlen(last_start); } text = (gchar *) kry_new(char, total_len + 1); text[total_len] = 0; for(ptr = string_parts; ptr; ptr = ptr->next) { int len = strlen((const char *) ptr->data); strncpy((char *) text + offset, (const char *) ptr->data, len); offset += len; kry_free(ptr->data); } g_list_free(string_parts); return (char *) text; } /* * Returns the full text in the text editor. * If the "Icon Highlighting" mode is turned on, SSA commands are replaced with "Gear" icons. * This function replaces those icons with the original commands and returns the full string. */ char *gui_text_editor_get_text(struct KryTextEditor *editor, int *insert_cursor, int *selection_cursor) { GtkTextIter start_iter, end_iter; GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); krySSACommandHighlighterHiddenPartList *parts_obj = (krySSACommandHighlighterHiddenPartList *) g_object_get_data(G_OBJECT(buffer), "hidden-parts"); gtk_text_buffer_get_start_iter(buffer, &start_iter); gtk_text_buffer_get_end_iter(buffer, &end_iter); if(!parts_obj) return KRY_TS(gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE)); char *text = KRY_TS(gtk_text_buffer_get_slice(buffer, &start_iter, &end_iter, TRUE)); char *rv = gui_text_editor_replace_placeholders(text, parts_obj, insert_cursor, selection_cursor); kry_free(text); return rv; } gboolean gui_text_editor_syntax_highlight_timer(struct KryTextEditor *editor) { GTimeVal curtime; gdk_threads_enter(); g_get_current_time(&curtime); if((curtime.tv_sec * 1000000 + curtime.tv_usec) - (text_changed_time.tv_sec * 1000000 + text_changed_time.tv_usec) > 400000) { gui_text_editor_do_syntax_highlighting(editor, TRUE); text_timer_on = FALSE; } else { gdk_threads_leave(); return TRUE; } gdk_threads_leave(); return FALSE; } void gui_text_editor_do_syntax_highlighting(struct KryTextEditor *editor, gboolean user_action) { GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); if(!buffer) return; if(!user_action) { struct pavl_table *table = editor->table_incorrect_words; if(table) pavl_destroy(editor->table_incorrect_words, gui_text_editor_range_free); editor->table_incorrect_words = pavl_create(gui_text_editor_range_compare, NULL, NULL); g_list_foreach(editor->recheck_ranges, (GFunc) kry_free_minimal, NULL); g_list_free(editor->recheck_ranges); editor->recheck_ranges = NULL; } g_signal_handlers_disconnect_by_func(G_OBJECT(buffer), (void *) gui_text_editor_text_changed_cb, editor); g_signal_handlers_disconnect_by_func(G_OBJECT(buffer), (void *) gui_tex_editor_insert_text_cb, editor); g_signal_handlers_disconnect_by_func(G_OBJECT(buffer), (void *) gui_tex_editor_delete_text_cb, editor); GtkTextIter start_iter, end_iter; unsigned char *text; // struct tag_list tag_list; int last_start = 0; int offset = 0; int cursor; int cursor_select; krySSACommandHighlighterHiddenPartList *parts = (krySSACommandHighlighterHiddenPartList *) g_object_get_data(G_OBJECT(buffer), "hidden-parts"); GtkTextIter mark_iter; GtkTextMark *insert_mark = gtk_text_buffer_get_mark(buffer, "insert"); GtkTextMark *selection_mark = gtk_text_buffer_get_mark(buffer, "selection_bound"); gtk_text_buffer_get_iter_at_mark(buffer, &mark_iter, insert_mark); cursor = gtk_text_iter_get_offset(&mark_iter); gtk_text_buffer_get_iter_at_mark(buffer, &mark_iter, selection_mark); cursor_select = gtk_text_iter_get_offset(&mark_iter); gtk_text_buffer_get_start_iter(buffer, &start_iter); gtk_text_buffer_get_end_iter(buffer, &end_iter); text = (unsigned char *) KRY_TS(gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE)); if(parts) { kry_free(text); text = (unsigned char *) gui_text_editor_get_text(editor, &cursor, &cursor_select); gtk_text_buffer_set_text(buffer, (const gchar *) text, -1); delete parts; g_object_set_data(G_OBJECT(buffer), "hidden-parts", NULL); parts = NULL; } if(editor->syntax_highlight_mode != SYNTAX_HIGHLIGHT_NONE) { last_start = 0; offset = 0; GList *cursors = NULL; cursors = g_list_append(cursors, &cursor); cursors = g_list_append(cursors, &cursor_select); krySSACommandHighlighter highlighter((char *) text, editor->syntax_highlight_mode == SYNTAX_HIGHLIGHT_CODE, cursors); gui_text_editor_set_text_from_highlighter(editor, highlighter); g_list_free(cursors); buffer = gui_text_editor_get_buffer(editor); { gtk_text_buffer_get_start_iter(buffer, &start_iter); gtk_text_buffer_get_end_iter(buffer, &end_iter); char *after_text; if(editor->syntax_highlight_mode == SYNTAX_HIGHLIGHT_CODE) after_text = gui_text_editor_get_text(editor); else after_text = KRY_TS(gtk_text_buffer_get_text(buffer, &start_iter, &end_iter, FALSE)); if(strcmp((const char *) text, (const char *) after_text)) { gui_error(_("An internal error was detected in the syntax highlighting code. Highlighting will be disabled for this line.\n\nPlease send the contents of this line to the author of Sabbu.")); gtk_text_buffer_set_text(buffer, (const gchar *) text, -1); } kry_free(after_text); } } gtk_text_buffer_get_iter_at_offset(buffer, &mark_iter, cursor); gtk_text_buffer_move_mark_by_name(buffer, "insert", &mark_iter); gtk_text_buffer_get_iter_at_offset(buffer, &mark_iter, cursor_select); gtk_text_buffer_move_mark_by_name(buffer, "selection_bound", &mark_iter); if(editor->syntax_highlight_mode == SYNTAX_HIGHLIGHT_NONE) { gtk_text_buffer_get_start_iter(buffer, &start_iter); gtk_text_buffer_get_end_iter(buffer, &end_iter); gtk_text_buffer_remove_tag(buffer, editor->tag_command1, &start_iter, &end_iter); gtk_text_buffer_remove_tag(buffer, editor->tag_command2, &start_iter, &end_iter); gtk_text_buffer_remove_tag(buffer, editor->tag_command_error, &start_iter, &end_iter); gtk_text_buffer_remove_tag(buffer, editor->tag_gear, &start_iter, &end_iter); gtk_text_buffer_remove_tag(buffer, editor->tag_text, &start_iter, &end_iter); #ifdef HAVE_ASPELL gui_text_editor_spellcheck_syntax_off(editor, (char *) text); #endif } g_list_foreach(editor->recheck_ranges, (GFunc) kry_free_minimal, NULL); g_list_free(editor->recheck_ranges); editor->recheck_ranges = NULL; kry_free(text); g_signal_connect(buffer, "insert-text", G_CALLBACK(gui_tex_editor_insert_text_cb), editor); g_signal_connect(buffer, "delete-range", G_CALLBACK(gui_tex_editor_delete_text_cb), editor); g_signal_connect(buffer, "changed", G_CALLBACK(gui_text_editor_text_changed_cb), editor); } void gui_text_editor_syntax_highlight_mode_cb(GtkRadioButton *widget, struct KryTextEditor *editor) { if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) return; enum KryTextEditorSyntaxHighlightMode type = SYNTAX_HIGHLIGHT_NONE; if(widget == editor->button_syntax_highlight_none) type = SYNTAX_HIGHLIGHT_NONE; else if(widget == editor->button_syntax_highlight_syntax) type = SYNTAX_HIGHLIGHT_SYNTAX; else if(widget == editor->button_syntax_highlight_code) type = SYNTAX_HIGHLIGHT_CODE; editor->syntax_highlight_mode = type; gui_text_editor_do_syntax_highlighting(editor, FALSE); gtk_widget_grab_focus(GTK_WIDGET(editor->view)); } enum KryTextEditorSyntaxHighlightMode gui_text_editor_get_syntax_highlight_mode(KryTextEditor *editor) { return editor->syntax_highlight_mode; } GtkWidget *gui_text_editor_get_widget(struct KryTextEditor *editor) { return GTK_WIDGET(editor->hbox_text_editor); } GtkTextView *gui_text_editor_get_view(struct KryTextEditor *editor) { return editor->view; } GtkTextBuffer *gui_text_editor_get_buffer(struct KryTextEditor *editor) { return gtk_text_view_get_buffer(editor->view); } GtkWidget *gui_text_editor_get_syntax_highlight_controls(struct KryTextEditor *editor) { return GTK_WIDGET(editor->vbox_syntax_highlight_controls); } void gui_text_editor_set_syntax_highlight_mode(struct KryTextEditor *editor, enum KryTextEditorSyntaxHighlightMode mode) { if(mode == SYNTAX_HIGHLIGHT_NONE) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(editor->button_syntax_highlight_none), TRUE); else if(mode == SYNTAX_HIGHLIGHT_SYNTAX) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(editor->button_syntax_highlight_syntax), TRUE); else if(mode == SYNTAX_HIGHLIGHT_CODE) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(editor->button_syntax_highlight_code), TRUE); } void gui_text_editor_highlight_tag_color_changed_cb(kryColor *color, void *param, struct KryTextEditor *editor) { GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer); char *tag_name = ""; if(color->GetID() == HIGHLIGHT_COLOR_COMMAND1) tag_name = "command1"; else if(color->GetID() == HIGHLIGHT_COLOR_COMMAND2) tag_name = "command2"; else if(color->GetID() == HIGHLIGHT_COLOR_TEXT) tag_name = "text"; else if(color->GetID() == HIGHLIGHT_COLOR_ERROR) tag_name = "command_error"; GtkTextTag *tag = gtk_text_tag_table_lookup(table, tag_name); if(!tag) { g_warning("tag not found"); return; } char *str = kry_strdup_printf(KRY_LOC "#%02X%02X%02X", color->GetRed(), color->GetGreen(), color->GetBlue()); g_object_set(tag, "foreground", str, NULL); kry_free(str); } void gui_text_editor_create_tags(struct KryTextEditor *editor) { GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); GtkTextTagTable *table; table = gtk_text_buffer_get_tag_table(buffer); kryColor *color = app.ui.highlight_color_table->Get(HIGHLIGHT_COLOR_ERROR); char *str = kry_strdup_printf(KRY_LOC "#%02X%02X%02X", color->GetRed(), color->GetGreen(), color->GetBlue()); editor->tag_command_error = gtk_text_buffer_create_tag(buffer, "command_error", "foreground", str, "weight", PANGO_WEIGHT_BOLD, NULL); kry_free(str); editor->tag_spelling_error = gtk_text_buffer_create_tag(buffer, "spelling_error", "underline", PANGO_UNDERLINE_ERROR, NULL); color = app.ui.highlight_color_table->Get(HIGHLIGHT_COLOR_COMMAND1); str = kry_strdup_printf(KRY_LOC "#%02X%02X%02X", color->GetRed(), color->GetGreen(), color->GetBlue()); editor->tag_command1 = gtk_text_buffer_create_tag(buffer, "command1", "foreground", str, NULL); kry_free(str); color = app.ui.highlight_color_table->Get(HIGHLIGHT_COLOR_COMMAND2); str = kry_strdup_printf(KRY_LOC "#%02X%02X%02X", color->GetRed(), color->GetGreen(), color->GetBlue()); editor->tag_command2 = gtk_text_buffer_create_tag(buffer, "command2", "foreground", str, NULL); kry_free(str); color = app.ui.highlight_color_table->Get(HIGHLIGHT_COLOR_TEXT); str = kry_strdup_printf(KRY_LOC "#%02X%02X%02X", color->GetRed(), color->GetGreen(), color->GetBlue()); editor->tag_text = gtk_text_buffer_create_tag(buffer, "text", "weight", PANGO_WEIGHT_BOLD, "foreground", str, NULL); kry_free(str); editor->tag_gear = gtk_text_buffer_create_tag(buffer, "gear", "editable", FALSE, NULL); } void gui_text_editor_spell_word_cb(GtkMenuItem *item, struct KryTextEditor *editor) { GtkLabel *label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(item))); GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); GtkTextIter word_start, word_end; const char *text = gtk_label_get_text(label); KryRange *range_clicked = editor->spell_word_clicked; int diff = ((g_utf8_strlen(text, -1) - (range_clicked->end - range_clicked->start))); gboolean matched = FALSE; struct pavl_traverser trav; pavl_t_init(&trav, editor->table_incorrect_words); while(KryRange *range = (KryRange *) pavl_t_next(&trav)) { if(range == range_clicked) { matched = TRUE; continue; } if(matched) { range->start += diff; range->end += diff; } } pavl_delete(editor->table_incorrect_words, range_clicked); gtk_text_buffer_get_iter_at_offset(buffer, &word_start, range_clicked->start); gtk_text_buffer_get_iter_at_offset(buffer, &word_end, range_clicked->end); g_signal_handlers_block_by_func(G_OBJECT(buffer), (void *) gui_tex_editor_insert_text_cb, editor); g_signal_handlers_block_by_func(G_OBJECT(buffer), (void *) gui_tex_editor_delete_text_cb, editor); gtk_text_buffer_delete(buffer, &word_start, &word_end); gtk_text_buffer_insert(buffer, &word_start, text, -1); g_signal_handlers_unblock_by_func(G_OBJECT(buffer), (void *) gui_tex_editor_insert_text_cb, editor); g_signal_handlers_unblock_by_func(G_OBJECT(buffer), (void *) gui_tex_editor_delete_text_cb, editor); kry_free(range_clicked); gtk_widget_queue_draw(GTK_WIDGET(editor->view)); } #ifdef HAVE_ASPELL void gui_text_editor_spell_add(GtkMenuItem *item, struct KryTextEditor *editor, gboolean personal) { GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); GtkTextIter word_start, word_end; gtk_text_buffer_get_iter_at_offset(buffer, &word_start, editor->spell_word_clicked->start); gtk_text_buffer_get_iter_at_offset(buffer, &word_end, editor->spell_word_clicked->end); char *word = gtk_text_buffer_get_text(buffer, &word_start, &word_end, FALSE); if(personal) spell_add_to_personal(app.ui.speller, word); else spell_add_to_session(app.ui.speller, word); struct pavl_traverser trav; pavl_t_first(&trav, editor->table_incorrect_words); while(KryRange *range = (KryRange *) pavl_t_cur(&trav)) { pavl_t_next(&trav); char *word_at_range; gtk_text_buffer_get_iter_at_offset(buffer, &word_start, range->start); gtk_text_buffer_get_iter_at_offset(buffer, &word_end, range->end); word_at_range = gtk_text_buffer_get_text(buffer, &word_start, &word_end, FALSE); if(!strcmp(word, word_at_range)) { gtk_text_buffer_remove_tag_by_name(buffer, "spelling_error", &word_start, &word_end); pavl_delete(editor->table_incorrect_words, range); kry_free(range); } kry_free(word_at_range); } kry_free(word); } void gui_text_editor_spell_add_session_cb(GtkMenuItem *item, struct KryTextEditor *editor) { gui_text_editor_spell_add(item, editor, FALSE); } void gui_text_editor_spell_add_dictionary_cb(GtkMenuItem *item, struct KryTextEditor *editor) { gui_text_editor_spell_add(item, editor, TRUE); } gboolean gui_text_editor_clicked_cb(GtkWidget *view, GdkEventButton *event, struct KryTextEditor *editor) { GtkTextBuffer *buffer = gtk_text_view_get_buffer(editor->view); if(event->button != 3 || !app.ui.speller || !editor->table_incorrect_words || editor->spell_disable_right_click) return FALSE; GtkTextIter iter; int trailing, x, y; int offset; gtk_text_view_window_to_buffer_coords(editor->view, GTK_TEXT_WINDOW_WIDGET, (int) event->x, (int) event->y, &x, &y); gtk_text_view_get_iter_at_position(editor->view, &iter, &trailing, x, y); offset = gtk_text_iter_get_offset(&iter); struct pavl_traverser trav; pavl_t_init(&trav, editor->table_incorrect_words); KryRange *range_clicked = NULL; while(struct KryRange *range = (struct KryRange *) pavl_t_next(&trav)) { if(offset >= range->start && offset < range->end) { range_clicked = range; break; } } if(!range_clicked) return FALSE; GtkTextIter word_start, word_end; gtk_text_buffer_get_iter_at_offset(buffer, &word_start, range_clicked->start); gtk_text_buffer_get_iter_at_offset(buffer, &word_end, range_clicked->end); char *word = gtk_text_buffer_get_text(buffer, &word_start, &word_end, FALSE); if(word) { editor->spell_word_clicked = range_clicked; int counter = 0; GtkMenu *menu = GTK_MENU(gtk_menu_new()); GtkMenu *menu_more = GTK_MENU(gtk_menu_new()); GList *words = spell_get_suggestions(app.ui.speller, word); for(GList *ptr = words; ptr; ptr = ptr->next) { GtkMenuItem *item = GTK_MENU_ITEM(gtk_menu_item_new_with_label((char *) ptr->data)); if(counter < 5) gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item)); else gtk_menu_shell_append(GTK_MENU_SHELL(menu_more), GTK_WIDGET(item)); g_signal_connect(item, "activate", G_CALLBACK(gui_text_editor_spell_word_cb), editor); kry_free(ptr->data); counter++; } if(counter > 5) { GtkMenuItem *item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(_("More Suggestions"))); gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item)); gtk_menu_item_set_submenu(item, GTK_WIDGET(menu_more)); } else if(counter > 0) { gtk_object_sink(GTK_OBJECT(menu_more)); } else { GtkMenuItem *item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(_("No Suggestions"))); gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE); gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item)); } GtkMenuItem *item; item = GTK_MENU_ITEM(gtk_separator_menu_item_new()); gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item)); item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(_("Add to Session"))); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gui_text_editor_spell_add_session_cb), editor); gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item)); item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(_("Add to Dictionary"))); g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(gui_text_editor_spell_add_dictionary_cb), editor); gtk_menu_shell_append(GTK_MENU_SHELL(menu), GTK_WIDGET(item)); gtk_widget_show_all(GTK_WIDGET(menu)); gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time); kry_free(word); } return TRUE; } #endif struct KryTextEditor *gui_text_editor_new() { GtkFrame *frame_text_inner = GTK_FRAME(gtk_frame_new(NULL)); GtkHBox *hbox_text_editor = GTK_HBOX(gtk_hbox_new(FALSE, 0)); GtkHBox *hbox_syntax_highlight_controls = GTK_HBOX(gtk_hbox_new(FALSE, 0)); GtkVBox *vbox_syntax_highlight_controls = GTK_VBOX(gtk_vbox_new(FALSE, 0)); GtkScrolledWindow *sw = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL)); GtkImage *image_gear = GTK_IMAGE(gtk_image_new_from_pixbuf(app.ui.pixbufs.gear)); GtkImage *image_syntax = GTK_IMAGE(gtk_image_new_from_pixbuf(app.ui.pixbufs.syntax)); GtkImage *image_syntax_off = GTK_IMAGE(gtk_image_new_from_pixbuf(app.ui.pixbufs.syntax_off)); GtkRadioButton *button_syntax_highlight_none = GTK_RADIO_BUTTON(gtk_radio_button_new(NULL)); GtkRadioButton *button_syntax_highlight_syntax = GTK_RADIO_BUTTON(gtk_radio_button_new(NULL)); GtkRadioButton *button_syntax_highlight_code = GTK_RADIO_BUTTON(gtk_radio_button_new(NULL)); GtkTextView *text_view = GTK_TEXT_VIEW(gtk_text_view_new()); GtkTextBuffer *text_buffer; struct KryTextEditor *editor = kry_new0(struct KryTextEditor); kryColorTable *table = app.ui.highlight_color_table; editor->highlight_color_changed_signal_id[0] = table->Get(HIGHLIGHT_COLOR_COMMAND1)->ConnectSignal(kryColor::SIGNAL_CHANGED, (krySignalFunc1) gui_text_editor_highlight_tag_color_changed_cb, editor); editor->highlight_color_changed_signal_id[1] = table->Get(HIGHLIGHT_COLOR_COMMAND2)->ConnectSignal(kryColor::SIGNAL_CHANGED, (krySignalFunc1) gui_text_editor_highlight_tag_color_changed_cb, editor); editor->highlight_color_changed_signal_id[2] = table->Get(HIGHLIGHT_COLOR_TEXT)->ConnectSignal(kryColor::SIGNAL_CHANGED, (krySignalFunc1) gui_text_editor_highlight_tag_color_changed_cb, editor); editor->highlight_color_changed_signal_id[3] = table->Get(HIGHLIGHT_COLOR_ERROR)->ConnectSignal(kryColor::SIGNAL_CHANGED, (krySignalFunc1) gui_text_editor_highlight_tag_color_changed_cb, editor); editor->hbox_text_editor = hbox_text_editor; editor->view = text_view; editor->vbox_syntax_highlight_controls = vbox_syntax_highlight_controls; editor->button_syntax_highlight_none = button_syntax_highlight_none; editor->button_syntax_highlight_syntax = button_syntax_highlight_syntax; editor->button_syntax_highlight_code = button_syntax_highlight_code; gui_text_editor_create_tags(editor); gtk_text_view_set_wrap_mode(text_view, GTK_WRAP_CHAR); gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(button_syntax_highlight_none), FALSE); gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(button_syntax_highlight_syntax), FALSE); gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(button_syntax_highlight_code), FALSE); gtk_container_add(GTK_CONTAINER(button_syntax_highlight_none), GTK_WIDGET(image_syntax_off)); gtk_container_add(GTK_CONTAINER(button_syntax_highlight_syntax), GTK_WIDGET(image_syntax)); gtk_container_add(GTK_CONTAINER(button_syntax_highlight_code), GTK_WIDGET(image_gear)); gtk_radio_button_set_group(button_syntax_highlight_syntax, gtk_radio_button_get_group(button_syntax_highlight_none)); gtk_radio_button_set_group(button_syntax_highlight_code, gtk_radio_button_get_group(button_syntax_highlight_none)); gtk_tooltips_set_tip(app.ui.tooltips, GTK_WIDGET(button_syntax_highlight_none), __("TextSyntaxHighlightNoneTooltip|Command Hiding Off"), NULL); gtk_tooltips_set_tip(app.ui.tooltips, GTK_WIDGET(button_syntax_highlight_syntax), __("TextSyntaxHighlightSyntaxTooltip|Syntax Highlighting"), NULL); gtk_tooltips_set_tip(app.ui.tooltips, GTK_WIDGET(button_syntax_highlight_code), __("TextSyntaxCodeIconsTooltip|Command Hiding (Icons)"), NULL); gtk_scrolled_window_set_policy(sw, GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_container_add(GTK_CONTAINER(sw), GTK_WIDGET(text_view)); gtk_container_add(GTK_CONTAINER(frame_text_inner), GTK_WIDGET(sw)); gtk_text_view_set_left_margin(text_view, 2); gtk_frame_set_shadow_type(frame_text_inner, GTK_SHADOW_IN); text_buffer = gtk_text_view_get_buffer(text_view); g_signal_connect(G_OBJECT(text_buffer), "changed", G_CALLBACK(gui_text_editor_text_changed_cb), editor); #ifdef HAVE_ASPELL g_signal_connect(G_OBJECT(text_view), "button-press-event", G_CALLBACK(gui_text_editor_clicked_cb), editor); #endif gtk_box_pack_start(GTK_BOX(hbox_syntax_highlight_controls), GTK_WIDGET(button_syntax_highlight_none), FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox_syntax_highlight_controls), GTK_WIDGET(button_syntax_highlight_syntax), FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox_syntax_highlight_controls), GTK_WIDGET(button_syntax_highlight_code), FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox_syntax_highlight_controls), GTK_WIDGET(hbox_syntax_highlight_controls), FALSE, TRUE, 2); gtk_box_pack_start(GTK_BOX(hbox_text_editor), GTK_WIDGET(vbox_syntax_highlight_controls), FALSE, TRUE, 0); gtk_box_pack_start(GTK_BOX(hbox_text_editor), GTK_WIDGET(frame_text_inner), TRUE, TRUE, 3); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_syntax_highlight_none), TRUE); g_signal_connect(button_syntax_highlight_none, "toggled", G_CALLBACK(gui_text_editor_syntax_highlight_mode_cb), editor); g_signal_connect(button_syntax_highlight_syntax, "toggled", G_CALLBACK(gui_text_editor_syntax_highlight_mode_cb), editor); g_signal_connect(button_syntax_highlight_code, "toggled", G_CALLBACK(gui_text_editor_syntax_highlight_mode_cb), editor); return editor; } void gui_text_editor_free(struct KryTextEditor *editor) { kryColorTable *table = app.ui.highlight_color_table; table->Get(HIGHLIGHT_COLOR_COMMAND1)->DisconnectSignal(kryColor::SIGNAL_CHANGED, editor->highlight_color_changed_signal_id[0]); table->Get(HIGHLIGHT_COLOR_COMMAND2)->DisconnectSignal(kryColor::SIGNAL_CHANGED, editor->highlight_color_changed_signal_id[1]); table->Get(HIGHLIGHT_COLOR_TEXT)->DisconnectSignal(kryColor::SIGNAL_CHANGED, editor->highlight_color_changed_signal_id[2]); table->Get(HIGHLIGHT_COLOR_ERROR)->DisconnectSignal(kryColor::SIGNAL_CHANGED, editor->highlight_color_changed_signal_id[3]); } void gui_text_editor_spell_disable_right_click(struct KryTextEditor *editor) { editor->spell_disable_right_click = TRUE; } void gui_text_editor_disable_spellcheck(struct KryTextEditor *editor) { editor->disable_spellcheck = TRUE; } void gui_tex_editor_insert_text_cb(GtkTextBuffer *buffer, GtkTextIter *iter, gchar *text, int len, struct KryTextEditor *editor) { int pos = gtk_text_iter_get_offset(iter); struct pavl_table *table = editor->table_incorrect_words; struct pavl_traverser trav; pavl_t_first(&trav, table); while(struct KryRange *range = (struct KryRange *) pavl_t_cur(&trav)) { pavl_t_next(&trav); if(pos >= range->start && pos <= range->end) { GtkTextIter iter_start, iter_end; gtk_text_buffer_get_iter_at_offset(buffer, &iter_start, range->start); gtk_text_buffer_get_iter_at_offset(buffer, &iter_end, range->end); gtk_text_buffer_remove_tag(buffer, editor->tag_spelling_error, &iter_start, &iter_end); pavl_delete(table, range); kry_free(range); } else if(pos < range->start) { range->start += len; range->end += len; } } gboolean new_range = TRUE; GList *sibling = NULL; for(GList *ptr = editor->recheck_ranges; ptr; ptr = ptr->next) { struct KryRange *range = (struct KryRange *) ptr->data; if(pos < range->start) { range->start+=len; range->end+=len; sibling = ptr; break; } else if(pos >= range->start && pos <= range->end) { new_range = FALSE; range->end += len; } else if(pos > range->end) { sibling = ptr->next; } } if(new_range) { KryRange *range = kry_new(struct KryRange, 1); range->start = pos; range->end = pos + len; if(sibling) editor->recheck_ranges = g_list_insert_before(editor->recheck_ranges, sibling, range); else editor->recheck_ranges = g_list_append(editor->recheck_ranges, range); } gtk_widget_queue_draw(GTK_WIDGET(editor->view)); } void gui_tex_editor_delete_text_cb(GtkTextBuffer *buffer, GtkTextIter *iter1, GtkTextIter *iter2, struct KryTextEditor *editor) { int pos_start = gtk_text_iter_get_offset(iter1); int pos_end = gtk_text_iter_get_offset(iter2); struct pavl_table *table = editor->table_incorrect_words; struct pavl_traverser trav; pavl_t_first(&trav, table); while(struct KryRange *range = (struct KryRange *) pavl_t_cur(&trav)) { pavl_t_next(&trav); if(pos_start < range->start && pos_end >= range->start || pos_start >= range->start && pos_start < range->end) { GtkTextIter iter_start, iter_end; gtk_text_buffer_get_iter_at_offset(buffer, &iter_start, range->start); gtk_text_buffer_get_iter_at_offset(buffer, &iter_end, range->end); gtk_text_buffer_remove_tag(buffer, editor->tag_spelling_error, &iter_start, &iter_end); pavl_delete(table, range); kry_free(range); } else if(pos_start < range->start) { range->start -= (pos_end - pos_start); range->end -= (pos_end - pos_start); } } gboolean new_range = TRUE; GList *sibling = NULL; for(GList *ptr = editor->recheck_ranges; ptr; ptr = ptr->next) { struct KryRange *range = (struct KryRange *) ptr->data; if(pos_end < range->start) { range->start -= (pos_end - pos_start); range->end -= (pos_end - pos_start); sibling = ptr; break; } else if(pos_start < range->start && pos_end > range->start || pos_start >= range->start && pos_end <= range->end || pos_start >= range->start && pos_start <= range->end && pos_end > range->end) { new_range = FALSE; if(pos_start < range->start && pos_end > range->start) range->start = pos_start; range->end -= (pos_start - pos_end); } else if(pos_start > range->end) { sibling = ptr->next; } } if(new_range) { KryRange *range = kry_new(struct KryRange, 1); range->start = pos_start; range->end = pos_start + 1; if(sibling) editor->recheck_ranges = g_list_insert_before(editor->recheck_ranges, sibling, range); else editor->recheck_ranges = g_list_append(editor->recheck_ranges, range); } gtk_widget_queue_draw(GTK_WIDGET(editor->view)); } void gui_text_editor_spellcheck_mark(struct KryTextEditor *editor, GtkTextBuffer *buffer, int *offset_start, int *offset_end) { GtkTextIter iter_start, iter_end; struct KryRange *range = kry_new(KryRange, 1); range->start = *offset_start; range->end = *offset_end; pavl_insert(editor->table_incorrect_words, range); gtk_text_buffer_get_iter_at_offset(buffer, &iter_start, range->start); gtk_text_buffer_get_iter_at_offset(buffer, &iter_end, range->end); gtk_text_buffer_apply_tag(buffer, editor->tag_spelling_error, &iter_start, &iter_end); } #ifdef HAVE_ASPELL void gui_text_editor_spellcheck(struct KryTextEditor *editor, char *text, GtkTextBuffer *buffer, int *offset_start, int *offset_end) { if(!app.ui.speller) return; if(editor->recheck_ranges) { g_warning("start"); for(GList *ptr = editor->recheck_ranges; ptr; ptr = ptr->next) { gboolean first_word = TRUE; KryRange *range = (struct KryRange *) ptr->data; int start = (range->start > 0 ? range->start -1 : 0); *offset_start = *offset_end = start; char *text_ptr = text + start; int end_bound = range->end; while(char *word = spell_get_word(&text_ptr, offset_start, offset_end, &end_bound, first_word)) { g_warning("check: %s", word); if(!spell_check_word(app.ui.speller, word)) gui_text_editor_spellcheck_mark(editor, buffer, offset_start, offset_end); kry_free(word); first_word = FALSE; } } gtk_widget_queue_draw(GTK_WIDGET(editor->view)); } else { while(char *word = spell_get_word(&text, offset_start, offset_end, NULL, FALSE)) { if(!spell_check_word(app.ui.speller, word)) gui_text_editor_spellcheck_mark(editor, buffer, offset_start, offset_end); kry_free(word); } } } void gui_text_editor_spellcheck_syntax_off(struct KryTextEditor *editor, char *text) { if(!app.ui.speller) return; GtkTextBuffer *buffer = gtk_text_view_get_buffer(editor->view); int offset_start = 0; int offset_end = 0; if(editor->recheck_ranges) { gui_text_editor_spellcheck(editor, text, buffer, &offset_start, &offset_end); return; } int curpos = 0; for(;;) { char *brace = strchr(text + curpos, '{'); int bracepos = (brace ? brace - text : strlen(text)); char tmp = text[bracepos]; int offset_start = curpos; int offset_end = curpos; text[bracepos] = 0; gui_text_editor_spellcheck(editor, text + curpos, buffer, &offset_start, &offset_end); text[bracepos] = tmp; if(!brace) break; char *cmdend = strchr(text + curpos, '}'); if(!cmdend) break; curpos = cmdend - text + 1; } } #endif void gui_text_editor_set_text_from_highlighter(struct KryTextEditor *editor, krySSACommandHighlighter & highlighter) { GtkTextBuffer *buffer = gui_text_editor_get_buffer(editor); GtkTextTagTable *tagtable = gtk_text_buffer_get_tag_table(buffer); GtkTextBuffer *new_buffer = gtk_text_buffer_new(tagtable); int offset_start = 0, offset_end = 0; GList *list = (GList *) g_object_get_data(G_OBJECT(buffer), "hidden-parts"); if(list) g_warning("hidden parts still exist"); /*struct pavl_table *table = editor->table_incorrect_words; if(table) pavl_destroy(table, gui_text_editor_range_free); table = pavl_create(gui_text_editor_range_compare, NULL, NULL); editor->table_incorrect_words = table;*/ g_object_set_data(G_OBJECT(new_buffer), "hidden-parts", highlighter.GetHiddenPartList()); gtk_text_view_set_buffer(editor->view, new_buffer); buffer = new_buffer; for(GList *ptr = highlighter.GetPartList(); ptr; ptr = ptr->next) { krySSACommandHighlighterPart *part = (krySSACommandHighlighterPart *) ptr->data; GtkTextTag *tag = NULL; GtkTextIter end_iter; if(part->type == HIGHLIGHT_TEXT) tag = editor->tag_text; else if(part->type == HIGHLIGHT_ERROR) tag = editor->tag_command_error; else if(part->type == HIGHLIGHT_COMMAND1) tag = editor->tag_command1; else if(part->type == HIGHLIGHT_COMMAND2) tag = editor->tag_command2; gtk_text_buffer_get_end_iter(new_buffer, &end_iter); if(part->type == HIGHLIGHT_GEAR) { gtk_text_buffer_insert_pixbuf(new_buffer, &end_iter, app.ui.pixbufs.gear); } else { gtk_text_buffer_insert_with_tags(new_buffer, &end_iter, part->text, -1, tag, NULL); } if(part->type == HIGHLIGHT_GEAR) { offset_start++; offset_end++; } else if(part->type != HIGHLIGHT_TEXT) { offset_start += g_utf8_strlen(part->text, -1); offset_end += g_utf8_strlen(part->text, -1); } #ifdef HAVE_ASPELL if(!editor->recheck_ranges && app.ui.speller && part->type == HIGHLIGHT_TEXT) gui_text_editor_spellcheck(editor, part->text, new_buffer, &offset_start, &offset_end); #endif } #ifdef HAVE_ASPELL if(editor->recheck_ranges) { char *text = highlighter.GetStringFromPartList(); gui_text_editor_spellcheck(editor, text, new_buffer, &offset_start, &offset_end); kry_free(text); } #endif }