From 57d2b3b3c3a452135794f066723cec3923a4bb9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Tr=C3=B6ger?= Date: Sun, 11 Oct 2015 23:32:48 +0200 Subject: [PATCH] Implement top-level spelling suggestions in editor menu (closes #287) Add a new option to enable to show spelling suggestions directly in the editor menu by not creating a dedicated sub menu. To retain the previous behaviour, this option is disabled by default. --- spellcheck/src/gui.c | 254 ++++++++++++++++++++++++++------------ spellcheck/src/gui.h | 2 +- spellcheck/src/scplugin.c | 48 +++++-- spellcheck/src/scplugin.h | 2 + 4 files changed, 220 insertions(+), 86 deletions(-) diff --git a/spellcheck/src/gui.c b/spellcheck/src/gui.c index e64d197b1..da24b80f0 100644 --- a/spellcheck/src/gui.c +++ b/spellcheck/src/gui.c @@ -238,17 +238,24 @@ static GtkWidget *image_menu_item_new(const gchar *stock_id, const gchar *label) static GtkWidget *init_editor_submenu(void) { - if (sc_info->edit_menu_sub != NULL && GTK_IS_WIDGET(sc_info->edit_menu_sub)) - gtk_widget_destroy(sc_info->edit_menu_sub); + if (sc_info->show_editor_menu_item_sub_menu) + { + if (sc_info->edit_menu_sub != NULL && GTK_IS_WIDGET(sc_info->edit_menu_sub)) + gtk_widget_destroy(sc_info->edit_menu_sub); - sc_info->edit_menu_sub = gtk_menu_new(); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(sc_info->edit_menu), sc_info->edit_menu_sub); + sc_info->edit_menu_sub = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(sc_info->edit_menu), sc_info->edit_menu_sub); - gtk_widget_show(sc_info->edit_menu); - gtk_widget_show(sc_info->edit_menu_sep); - gtk_widget_show(sc_info->edit_menu_sub); + gtk_widget_show(sc_info->edit_menu); + gtk_widget_show(sc_info->edit_menu_sep); + gtk_widget_show(sc_info->edit_menu_sub); - return sc_info->edit_menu_sub; + return sc_info->edit_menu_sub; + } + else + { + return geany->main_widgets->editor_menu; + } } @@ -286,6 +293,109 @@ void sc_gui_document_open_cb(GObject *obj, GeanyDocument *doc, gpointer user_dat } +static void menu_item_ref(GtkWidget *menu_item) +{ + if (! sc_info->show_editor_menu_item_sub_menu) + sc_info->edit_menu_items = g_slist_append(sc_info->edit_menu_items, menu_item); +} + + +static void update_editor_menu_items(const gchar *search_word, const gchar **suggs, gsize n_suggs) +{ + GtkWidget *menu_item, *menu, *sub_menu; + GSList *node; + gchar *label; + gsize i; + + menu = init_editor_submenu(); + sub_menu = menu; + + /* display 5 suggestions on top level, 20 more in sub menu */ + for (i = 0; i < MIN(n_suggs, 25); i++) + { + if (i >= 5 && menu == sub_menu) + { + /* create "More..." sub menu */ + if (sc_info->show_editor_menu_item_sub_menu) + { + menu_item = gtk_separator_menu_item_new(); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(sub_menu), menu_item); + } + + menu_item = gtk_menu_item_new_with_label(_("More...")); + gtk_widget_show(menu_item); + gtk_menu_shell_append(GTK_MENU_SHELL(sub_menu), menu_item); + menu_item_ref(menu_item); + + sub_menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu); + } + menu_item = gtk_menu_item_new_with_label(suggs[i]); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(sub_menu), menu_item); + if (menu == sub_menu) + { + /* Remember menu items to delete only for the top-level, the nested menu items are + * destroyed recursively via the sub menu */ + menu_item_ref(menu_item); + } + g_signal_connect(menu_item, "activate", G_CALLBACK(menu_suggestion_item_activate_cb), NULL); + } + if (suggs == NULL) + { + menu_item = gtk_menu_item_new_with_label(_("(No Suggestions)")); + gtk_widget_set_sensitive(menu_item, FALSE); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + menu_item_ref(menu_item); + } + if (sc_info->show_editor_menu_item_sub_menu) + { + menu_item = gtk_separator_menu_item_new(); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + } + + label = g_strdup_printf(_("Add \"%s\" to Dictionary"), search_word); + menu_item = image_menu_item_new(GTK_STOCK_ADD, label); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + menu_item_ref(menu_item); + g_signal_connect(menu_item, "activate", + G_CALLBACK(menu_addword_item_activate_cb), GINT_TO_POINTER(FALSE)); + + menu_item = image_menu_item_new(GTK_STOCK_REMOVE, _("Ignore All")); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + menu_item_ref(menu_item); + g_signal_connect(menu_item, "activate", + G_CALLBACK(menu_addword_item_activate_cb), GINT_TO_POINTER(TRUE)); + + g_free(label); + + /* re-order menu items: above all menu items are append but for the top-level menu items + * we want them to appear at the top of the editor menu */ + if (! sc_info->show_editor_menu_item_sub_menu) + { + gpointer child; + /* final separator */ + menu_item = gtk_separator_menu_item_new(); + gtk_widget_show(menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + menu_item_ref(menu_item); + /* re-order */ + i = 0; + foreach_slist(node, sc_info->edit_menu_items) + { + child = node->data; + gtk_menu_reorder_child(GTK_MENU(menu), GTK_WIDGET(child), i); + i++; + } + } +} + + void sc_gui_update_editor_menu_cb(GObject *obj, const gchar *word, gint pos, GeanyDocument *doc, gpointer user_data) { @@ -294,8 +404,16 @@ void sc_gui_update_editor_menu_cb(GObject *obj, const gchar *word, gint pos, g_return_if_fail(doc != NULL && doc->is_valid); /* hide the submenu in any case, we will reshow it again if we actually found something */ - gtk_widget_hide(sc_info->edit_menu); - gtk_widget_hide(sc_info->edit_menu_sep); + if (sc_info->edit_menu != NULL) + gtk_widget_hide(sc_info->edit_menu); + if (sc_info->edit_menu_sep != NULL) + gtk_widget_hide(sc_info->edit_menu_sep); + /* clean previously added items to the editor menu */ + if (sc_info->edit_menu_items != NULL) + { + g_slist_free_full(sc_info->edit_menu_items, (GDestroyNotify) gtk_widget_destroy); + sc_info->edit_menu_items = NULL; + } if (! sc_info->show_editor_menu_item) return; @@ -320,18 +438,20 @@ void sc_gui_update_editor_menu_cb(GObject *obj, const gchar *word, gint pos, /* ignore too long search words */ if (strlen(search_word) > 100) { - GtkWidget *menu_item; + GtkWidget *menu_item, *menu; - init_editor_submenu(); + menu = init_editor_submenu(); menu_item = gtk_menu_item_new_with_label( _("Search term is too long to provide\nspelling suggestions in the editor menu.")); gtk_widget_set_sensitive(menu_item, FALSE); gtk_widget_show(menu_item); - gtk_container_add(GTK_CONTAINER(sc_info->edit_menu_sub), menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + menu_item_ref(menu_item); menu_item = gtk_menu_item_new_with_label(_("Perform Spell Check")); gtk_widget_show(menu_item); - gtk_container_add(GTK_CONTAINER(sc_info->edit_menu_sub), menu_item); + gtk_container_add(GTK_CONTAINER(menu), menu_item); + menu_item_ref(menu_item); g_signal_connect(menu_item, "activate", G_CALLBACK(perform_spell_check_cb), doc); g_free(search_word); @@ -340,9 +460,7 @@ void sc_gui_update_editor_menu_cb(GObject *obj, const gchar *word, gint pos, if (sc_speller_dict_check(search_word) != 0) { - GtkWidget *menu_item, *menu; - gchar *label; - gsize n_suggs, i; + gsize n_suggs; gchar **suggs; suggs = sc_speller_dict_suggest(search_word, &n_suggs); @@ -351,61 +469,14 @@ void sc_gui_update_editor_menu_cb(GObject *obj, const gchar *word, gint pos, clickinfo.doc = doc; setptr(clickinfo.word, search_word); - menu = init_editor_submenu(); - - for (i = 0; i < n_suggs; i++) - { - if (i > 0 && i % 10 == 0) - { - menu_item = gtk_menu_item_new(); - gtk_widget_show(menu_item); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); - - menu_item = gtk_menu_item_new_with_label(_("More...")); - gtk_widget_show(menu_item); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); - - menu = gtk_menu_new(); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu); - } - menu_item = gtk_menu_item_new_with_label(suggs[i]); - gtk_widget_show(menu_item); - gtk_container_add(GTK_CONTAINER(menu), menu_item); - g_signal_connect(menu_item, "activate", - G_CALLBACK(menu_suggestion_item_activate_cb), NULL); - } - if (suggs == NULL) - { - menu_item = gtk_menu_item_new_with_label(_("(No Suggestions)")); - gtk_widget_set_sensitive(menu_item, FALSE); - gtk_widget_show(menu_item); - gtk_container_add(GTK_CONTAINER(sc_info->edit_menu_sub), menu_item); - } - menu_item = gtk_separator_menu_item_new(); - gtk_widget_show(menu_item); - gtk_container_add(GTK_CONTAINER(sc_info->edit_menu_sub), menu_item); - - label = g_strdup_printf(_("Add \"%s\" to Dictionary"), search_word); - menu_item = image_menu_item_new(GTK_STOCK_ADD, label); - gtk_widget_show(menu_item); - gtk_container_add(GTK_CONTAINER(sc_info->edit_menu_sub), menu_item); - g_signal_connect(menu_item, "activate", - G_CALLBACK(menu_addword_item_activate_cb), GINT_TO_POINTER(FALSE)); - - menu_item = image_menu_item_new(GTK_STOCK_REMOVE, _("Ignore All")); - gtk_widget_show(menu_item); - gtk_container_add(GTK_CONTAINER(sc_info->edit_menu_sub), menu_item); - g_signal_connect(menu_item, "activate", - G_CALLBACK(menu_addword_item_activate_cb), GINT_TO_POINTER(TRUE)); + update_editor_menu_items(search_word, (const gchar**) suggs, n_suggs); if (suggs != NULL) sc_speller_dict_free_string_list(suggs); - - g_free(label); } else { - g_free(search_word); + g_free(search_word); /* search_word is free'd via clickinfo.word otherwise */ } } @@ -602,17 +673,41 @@ void sc_gui_kb_toggle_typing_activate_cb(guint key_id) } -void sc_gui_create_edit_menu(void) +static void free_editor_menu_items(void) +{ + if (sc_info->edit_menu != NULL) + { + gtk_widget_destroy(sc_info->edit_menu); + sc_info->edit_menu = NULL; + } + if (sc_info->edit_menu_sep != NULL) + { + gtk_widget_destroy(sc_info->edit_menu_sep); + sc_info->edit_menu_sep = NULL; + } + if (sc_info->edit_menu_items != NULL) + { + g_slist_free_full(sc_info->edit_menu_items, (GDestroyNotify) gtk_widget_destroy); + sc_info->edit_menu_items = NULL; + } +} + + +void sc_gui_recreate_editor_menu(void) { - sc_info->edit_menu = ui_image_menu_item_new(GTK_STOCK_SPELL_CHECK, _("Spelling Suggestions")); - gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), sc_info->edit_menu); - gtk_menu_reorder_child(GTK_MENU(geany->main_widgets->editor_menu), sc_info->edit_menu, 0); + free_editor_menu_items(); + if (sc_info->show_editor_menu_item_sub_menu) + { + sc_info->edit_menu = ui_image_menu_item_new(GTK_STOCK_SPELL_CHECK, _("Spelling Suggestions")); + gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), sc_info->edit_menu); + gtk_menu_reorder_child(GTK_MENU(geany->main_widgets->editor_menu), sc_info->edit_menu, 0); - sc_info->edit_menu_sep = gtk_separator_menu_item_new(); - gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), sc_info->edit_menu_sep); - gtk_menu_reorder_child(GTK_MENU(geany->main_widgets->editor_menu), sc_info->edit_menu_sep, 1); + sc_info->edit_menu_sep = gtk_separator_menu_item_new(); + gtk_container_add(GTK_CONTAINER(geany->main_widgets->editor_menu), sc_info->edit_menu_sep); + gtk_menu_reorder_child(GTK_MENU(geany->main_widgets->editor_menu), sc_info->edit_menu_sep, 1); - gtk_widget_show_all(sc_info->edit_menu); + gtk_widget_show_all(sc_info->edit_menu); + } } @@ -665,6 +760,12 @@ void sc_gui_update_menu(void) void sc_gui_init(void) { clickinfo.word = NULL; + sc_info->edit_menu_items = NULL; + sc_info->edit_menu = NULL; + sc_info->edit_menu_sep = NULL; + sc_info->edit_menu_items = NULL; + + sc_gui_recreate_editor_menu(); } @@ -672,7 +773,8 @@ void sc_gui_free(void) { g_free(clickinfo.word); if (check_line_data.check_while_typing_idle_source_id != 0) - { g_source_remove(check_line_data.check_while_typing_idle_source_id); - } + if (sc_info->toolbar_button != NULL) + gtk_widget_destroy(GTK_WIDGET(sc_info->toolbar_button)); + free_editor_menu_items(); } diff --git a/spellcheck/src/gui.h b/spellcheck/src/gui.h index 87a709843..840200cce 100644 --- a/spellcheck/src/gui.h +++ b/spellcheck/src/gui.h @@ -31,7 +31,7 @@ void sc_gui_kb_run_activate_cb(guint key_id); void sc_gui_kb_toggle_typing_activate_cb(guint key_id); -void sc_gui_create_edit_menu(void); +void sc_gui_recreate_editor_menu(void); void sc_gui_update_editor_menu_cb(GObject *obj, const gchar *word, gint pos, GeanyDocument *doc, gpointer user_data); diff --git a/spellcheck/src/scplugin.c b/spellcheck/src/scplugin.c index a150c8bb6..44a88bebc 100644 --- a/spellcheck/src/scplugin.c +++ b/spellcheck/src/scplugin.c @@ -126,6 +126,9 @@ static void configure_response_cb(GtkDialog *dialog, gint response, gpointer use sc_info->show_editor_menu_item = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( g_object_get_data(G_OBJECT(dialog), "check_editor_menu")))); + sc_info->show_editor_menu_item_sub_menu = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( + g_object_get_data(G_OBJECT(dialog), "check_editor_menu_sub_menu")))); + g_key_file_load_from_file(config, sc_info->config_file, G_KEY_FILE_NONE, NULL); if (sc_info->default_language != NULL) /* lang may be NULL */ g_key_file_set_string(config, "spellcheck", "language", sc_info->default_language); @@ -139,10 +142,13 @@ static void configure_response_cb(GtkDialog *dialog, gint response, gpointer use sc_info->show_toolbar_item); g_key_file_set_boolean(config, "spellcheck", "show_editor_menu_item", sc_info->show_editor_menu_item); - if (sc_info->dictionary_dir != NULL) + g_key_file_set_boolean(config, "spellcheck", "show_editor_menu_item_sub_menu", + sc_info->show_editor_menu_item_sub_menu); + if (sc_info->dictionary_dir != NULL) g_key_file_set_string(config, "spellcheck", "dictionary_dir", sc_info->dictionary_dir); + sc_gui_recreate_editor_menu(); sc_gui_update_toolbar(); sc_gui_update_menu(); populate_dict_combo(combo); @@ -188,6 +194,8 @@ void plugin_init(GeanyData *data) "spellcheck", "show_toolbar_item", TRUE); sc_info->show_editor_menu_item = utils_get_setting_boolean(config, "spellcheck", "show_editor_menu_item", TRUE); + sc_info->show_editor_menu_item_sub_menu = utils_get_setting_boolean(config, + "spellcheck", "show_editor_menu_item_sub_menu", TRUE); sc_info->dictionary_dir = utils_get_setting_string(config, "spellcheck", "dictionary_dir", NULL); sc_info->use_msgwin = utils_get_setting_boolean(config, "spellcheck", "use_msgwin", FALSE); @@ -202,7 +210,6 @@ void plugin_init(GeanyData *data) sc_gui_init(); sc_speller_init(); - sc_gui_create_edit_menu(); sc_gui_update_menu(); gtk_widget_show_all(sc_info->menu_item); @@ -250,10 +257,21 @@ static void dictionary_dir_button_clicked_cb(GtkButton *button, gpointer item) #endif +static void configure_frame_editor_menu_toggled_cb(GtkToggleButton *togglebutton, gpointer data) +{ + gboolean sens = gtk_toggle_button_get_active(togglebutton); + + gtk_widget_set_sensitive(g_object_get_data(G_OBJECT(data), + "check_editor_menu_sub_menu"), sens); +} + + GtkWidget *plugin_configure(GtkDialog *dialog) { GtkWidget *label_language, *label_dir, *vbox; - GtkWidget *combo, *check_type, *check_on_open, *check_msgwin, *check_toolbar, *check_editor_menu; + GtkWidget *combo, *check_type, *check_on_open, *check_msgwin, *check_toolbar; + GtkWidget *frame_editor_menu, *check_editor_menu; + GtkWidget *check_editor_menu_sub_menu, *align_editor_menu_sub_menu; GtkWidget *vbox_interface, *frame_interface, *label_interface; GtkWidget *vbox_behavior, *frame_behavior, *label_behavior; #ifdef HAVE_ENCHANT_1_5 @@ -271,13 +289,28 @@ GtkWidget *plugin_configure(GtkDialog *dialog) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_editor_menu), sc_info->show_editor_menu_item); + check_editor_menu_sub_menu = gtk_check_button_new_with_label( + _("Show suggestions in a sub menu of the editor menu")); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_editor_menu_sub_menu), + sc_info->show_editor_menu_item_sub_menu); + align_editor_menu_sub_menu = gtk_alignment_new(0.5, 0.5, 1, 1); + gtk_alignment_set_padding(GTK_ALIGNMENT(align_editor_menu_sub_menu), 0, 0, 9, 0); + gtk_container_add(GTK_CONTAINER(align_editor_menu_sub_menu), check_editor_menu_sub_menu); + + frame_editor_menu = gtk_frame_new(NULL); + gtk_frame_set_label_widget(GTK_FRAME(frame_editor_menu), check_editor_menu); + gtk_container_set_border_width(GTK_CONTAINER(frame_editor_menu), 3); + gtk_container_add(GTK_CONTAINER(frame_editor_menu), align_editor_menu_sub_menu); + g_signal_connect(check_editor_menu, "toggled", + G_CALLBACK(configure_frame_editor_menu_toggled_cb), dialog); + check_msgwin = gtk_check_button_new_with_label( _("Print misspelled words and suggestions in the messages window")); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_msgwin), sc_info->use_msgwin); vbox_interface = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox_interface), check_toolbar, FALSE, FALSE, 3); - gtk_box_pack_start(GTK_BOX(vbox_interface), check_editor_menu, TRUE, TRUE, 3); + gtk_box_pack_start(GTK_BOX(vbox_interface), frame_editor_menu, TRUE, TRUE, 3); gtk_box_pack_start(GTK_BOX(vbox_interface), check_msgwin, TRUE, TRUE, 3); label_interface = gtk_label_new(NULL); @@ -362,8 +395,10 @@ GtkWidget *plugin_configure(GtkDialog *dialog) g_object_set_data(G_OBJECT(dialog), "check_msgwin", check_msgwin); g_object_set_data(G_OBJECT(dialog), "check_toolbar", check_toolbar); g_object_set_data(G_OBJECT(dialog), "check_editor_menu", check_editor_menu); + g_object_set_data(G_OBJECT(dialog), "check_editor_menu_sub_menu", check_editor_menu_sub_menu); g_signal_connect(dialog, "response", G_CALLBACK(configure_response_cb), NULL); + configure_frame_editor_menu_toggled_cb(GTK_TOGGLE_BUTTON(check_editor_menu), dialog); gtk_widget_show_all(vbox); return vbox; @@ -391,11 +426,6 @@ void plugin_help(void) void plugin_cleanup(void) { - gtk_widget_destroy(sc_info->edit_menu); - gtk_widget_destroy(sc_info->edit_menu_sep); - if (sc_info->toolbar_button != NULL) - gtk_widget_destroy(GTK_WIDGET(sc_info->toolbar_button)); - sc_gui_free(); sc_speller_free(); diff --git a/spellcheck/src/scplugin.h b/spellcheck/src/scplugin.h index 7bf2ae8dc..18ea69054 100644 --- a/spellcheck/src/scplugin.h +++ b/spellcheck/src/scplugin.h @@ -38,6 +38,7 @@ typedef struct gboolean check_on_document_open; gboolean show_toolbar_item; gboolean show_editor_menu_item; + gboolean show_editor_menu_item_sub_menu; GPtrArray *dicts; GtkWidget *main_menu; GtkWidget *menu_item; @@ -46,6 +47,7 @@ typedef struct GtkWidget *edit_menu_sep; GtkWidget *edit_menu_sub; GtkToolItem *toolbar_button; + GSList *edit_menu_items; } SpellCheck;