From 66f64ac6fbfad1420b6224749c0738401a65fb27 Mon Sep 17 00:00:00 2001 From: lafricain79 Date: Sun, 26 Jul 2026 17:01:40 +0200 Subject: [PATCH] =?UTF-8?q?Added=20a=20=E2=80=9CFavorites=E2=80=9D=20entry?= =?UTF-8?q?=20to=20the=20module=20tree=20and=20added=20the=20ability=20to?= =?UTF-8?q?=20hide=20certain=20modules.=20The=20options=20are=20available?= =?UTF-8?q?=20in=20the=20module=20tree's=20context=20menu.=20modules:=20ad?= =?UTF-8?q?d=20configurable=20module-tree=20grouping=20(category/language/?= =?UTF-8?q?both)=20-=20new=20setting=20modules/grouping=20in=20settings.co?= =?UTF-8?q?nf=20(0/1/2)=20-=20main=5Fload=5Fmodule=5Ftree=5Fflat()=20and?= =?UTF-8?q?=20main=5Fload=5Fmodule=5Ftree=5Fby=5Flanguage()=20=20=20in=20m?= =?UTF-8?q?ain/sidebar.cc,=20dispatched=20from=20main=5Fload=5Fmodule=5Ftr?= =?UTF-8?q?ee()=20-=20new=20combobox=20in=20Preferences=20>=20Modules=20Mi?= =?UTF-8?q?sc=20(prefs.glade=20+=20prefs.gtkbuilder)=20-=20right-click=20o?= =?UTF-8?q?n=20a=20category/language=20folder=20also=20offers=20the=203=20?= =?UTF-8?q?modes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses part of the display-customization ideas raised in #1130. --- src/gtk/preferences_dialog.c | 35 +++- src/gtk/sidebar.c | 82 +++++++++ src/gui/preferences_dialog.h | 6 + src/gui/sidebar.h | 4 + src/main/settings.c | 31 ++++ src/main/settings.h | 10 ++ src/main/sidebar.cc | 339 +++++++++++++++++++++++++++++++++-- src/main/sidebar.h | 6 + ui/prefs.glade | 45 +++++ ui/prefs.gtkbuilder | 80 +++++++++ ui/xi-menus-popup.gtkbuilder | 20 +++ ui/xi-menus.glade | 18 ++ 12 files changed, 662 insertions(+), 14 deletions(-) diff --git a/src/gtk/preferences_dialog.c b/src/gtk/preferences_dialog.c index b8863c25b..9cfa7e376 100644 --- a/src/gtk/preferences_dialog.c +++ b/src/gtk/preferences_dialog.c @@ -81,6 +81,7 @@ struct _preferences_combo GtkWidget *special_locale; GtkWidget *font_prefs; GtkWidget *display_columns; + GtkWidget *module_grouping; }; typedef struct _preferences_color_pickers COLOR_PICKERS; @@ -119,7 +120,7 @@ struct _preferences_check_buttons GtkWidget *statusbar; GtkWidget *alternation; GtkWidget *justify_margins; - + GtkWidget *show_hidden_modules; GtkWidget *show_in_viewer; GtkWidget *show_in_dictionary; GtkWidget *show_devotion; @@ -1214,6 +1215,14 @@ on_justifybutton_toggled(GtkToggleButton *togglebutton, gpointer user_data) redisplay_to_realign(); } +void +on_show_hidden_modules_toggled(GtkToggleButton *togglebutton, gpointer user_data) +{ + xml_set_value("Xiphos", "modules", "show_hidden", + (gtk_toggle_button_get_active(togglebutton) ? "1" : "0")); + settings.show_hidden_modules = gtk_toggle_button_get_active(togglebutton); + main_load_module_tree(sidebar.module_list); + } /****************************************************************************** * Name * on_checkbutton_scroll_toggled @@ -1604,6 +1613,18 @@ void on_columncountvalue_changed(GtkComboBox *combobox, gpointer user_data) redisplay_to_realign(); } +void on_combobox_module_grouping_changed(GtkComboBox *combobox, gpointer user_data) +{ + gint mode = gtk_combo_box_get_active(combobox); + gchar buf[4]; + if (mode < 0) + return; + g_snprintf(buf, sizeof(buf), "%d", mode); + xml_set_value("Xiphos", "modules", "grouping", buf); + settings.module_tree_grouping = mode; + main_load_module_tree(sidebar.module_list); +} + /****************************************************************************** * Name * on_basecombobox1_changed @@ -3339,6 +3360,18 @@ static void create_preferences_dialog(void) g_signal_connect(combo.display_columns, "changed", G_CALLBACK(on_columncountvalue_changed), NULL); + combo.module_grouping = UI_GET_ITEM(gxml, "combobox_module_grouping"); + gtk_combo_box_set_active(GTK_COMBO_BOX(combo.module_grouping), + settings.module_tree_grouping); + g_signal_connect(combo.module_grouping, "changed", + G_CALLBACK(on_combobox_module_grouping_changed), NULL); + check_button.show_hidden_modules = UI_GET_ITEM(gxml, "checkbutton_show_hidden_modules"); + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button.show_hidden_modules), + settings.show_hidden_modules); + g_signal_connect(check_button.show_hidden_modules, "toggled", + G_CALLBACK(on_show_hidden_modules_toggled), NULL); + /* module combos */ combo.default_dictionary_module = UI_GET_ITEM(gxml, "combobox5"); combo.percomm_module = UI_GET_ITEM(gxml, "combobox6"); diff --git a/src/gtk/sidebar.c b/src/gtk/sidebar.c index a077d9ee1..fac6e561a 100644 --- a/src/gtk/sidebar.c +++ b/src/gtk/sidebar.c @@ -541,6 +541,21 @@ static void on_search_results_activate(GtkToggleButton *button, * gboolean */ +static void on_module_grouping_menu_toggled(GtkCheckMenuItem *item, gpointer user_data) +{ + gint mode; + gchar buf[4]; + + if (!gtk_check_menu_item_get_active(item)) + return; + + mode = GPOINTER_TO_INT(user_data); + g_snprintf(buf, sizeof(buf), "%d", mode); + xml_set_value("Xiphos", "modules", "grouping", buf); + settings.module_tree_grouping = mode; + main_load_module_tree(sidebar.module_list); +} + static gboolean on_modules_list_button_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data) @@ -651,6 +666,40 @@ gint depth = gtk_tree_path_get_depth(path); g_free(caption); return FALSE; } + if (!mod && caption) { + GtkWidget *group_menu = gtk_menu_new(); + GtkWidget *item_catlang = gtk_radio_menu_item_new_with_label(NULL, _("Category, then Language")); + GtkWidget *item_cat = gtk_radio_menu_item_new_with_label_from_widget( + GTK_RADIO_MENU_ITEM(item_catlang), _("Category only")); + GtkWidget *item_langcat = gtk_radio_menu_item_new_with_label_from_widget( + GTK_RADIO_MENU_ITEM(item_catlang), _("Language, then Category")); + + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_catlang), + settings.module_tree_grouping == 0); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_cat), + settings.module_tree_grouping == 1); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item_langcat), + settings.module_tree_grouping == 2); + + gtk_menu_shell_append(GTK_MENU_SHELL(group_menu), item_catlang); + gtk_menu_shell_append(GTK_MENU_SHELL(group_menu), item_cat); + gtk_menu_shell_append(GTK_MENU_SHELL(group_menu), item_langcat); + gtk_widget_show_all(group_menu); + + g_signal_connect(item_catlang, "toggled", + G_CALLBACK(on_module_grouping_menu_toggled), GINT_TO_POINTER(0)); + g_signal_connect(item_cat, "toggled", + G_CALLBACK(on_module_grouping_menu_toggled), GINT_TO_POINTER(1)); + g_signal_connect(item_langcat, "toggled", + G_CALLBACK(on_module_grouping_menu_toggled), GINT_TO_POINTER(2)); +#if GTK_CHECK_VERSION(3, 22, 0) + gtk_menu_popup_at_pointer(GTK_MENU(group_menu), (GdkEvent *)event); +#else + gtk_menu_popup(GTK_MENU(group_menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); +#endif + g_free(caption); + return FALSE; + } break; } @@ -922,6 +971,22 @@ on_about2_activate(GtkMenuItem *menuitem, gpointer user_data) buf_module = NULL; } +G_MODULE_EXPORT void +on_toggle_favorite_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + module_toggle_favorite(buf_module); + g_free(buf_module); + buf_module = NULL; +} + +G_MODULE_EXPORT void +on_hide_module_activate(GtkMenuItem *menuitem, gpointer user_data) +{ + module_toggle_hidden(buf_module); + g_free(buf_module); + buf_module = NULL; +} + G_MODULE_EXPORT void on_save_list_as_a_single_bookmark_activate(GtkMenuItem *menuitem, gpointer user_data) @@ -1103,6 +1168,23 @@ static GtkWidget *create_menu_modules(void) g_return_val_if_fail((gxml != NULL), NULL); GtkWidget *menu = UI_GET_ITEM(gxml, "menu_modules"); +#ifdef USE_GTKBUILDER + GtkWidget *fav_item = UI_GET_ITEM(gxml, "toggle_favorite1"); + GtkWidget *hide_item = UI_GET_ITEM(gxml, "hide_module1"); +#else + GtkWidget *fav_item = UI_GET_ITEM(gxml, "toggle_favorite"); + GtkWidget *hide_item = UI_GET_ITEM(gxml, "hide_module"); +#endif + if (fav_item) + gtk_menu_item_set_label(GTK_MENU_ITEM(fav_item), + module_is_favorite(buf_module) + ? _("Remove from Favorites") + : _("Add to Favorites")); + if (hide_item) + gtk_menu_item_set_label(GTK_MENU_ITEM(hide_item), + module_is_hidden(buf_module) + ? _("Show this module") + : _("Hide this module")); #ifdef USE_GTKBUILDER gtk_builder_connect_signals(gxml, NULL); /*gtk_builder_connect_signals_full diff --git a/src/gui/preferences_dialog.h b/src/gui/preferences_dialog.h index 120c11854..950866e63 100644 --- a/src/gui/preferences_dialog.h +++ b/src/gui/preferences_dialog.h @@ -50,6 +50,10 @@ void on_checkbutton9_toggled(GtkToggleButton *togglebutton, gpointer user_data); void on_justifybutton_toggled(GtkToggleButton *togglebutton, gpointer user_data); +void on_show_hidden_modules_toggled(GtkToggleButton *togglebutton, + gpointer user_data); +void on_show_hidden_modules_toggled(GtkToggleButton *togglebutton, + gpointer user_data); void on_folder_changed(GtkFileChooser *filechooser, gpointer user_data); @@ -94,6 +98,8 @@ void on_combobox20_changed(GtkComboBox *combobox, gpointer user_data); void on_columncountvalue_changed(GtkComboBox *combobox, gpointer user_data); +void on_combobox_module_grouping_changed(GtkComboBox *combobox, + gpointer user_data); void on_colorbutton1_color_set(GtkColorButton *colorbutton, gpointer user_data); diff --git a/src/gui/sidebar.h b/src/gui/sidebar.h index 912f2cdb1..d40ed76dc 100644 --- a/src/gui/sidebar.h +++ b/src/gui/sidebar.h @@ -106,6 +106,10 @@ void on_open_in_tab_activate2(GtkMenuItem *menuitem, gpointer user_data); void on_about2_activate(GtkMenuItem *menuitem, gpointer user_data); +void on_toggle_favorite_activate(GtkMenuItem *menuitem, + gpointer user_data); +void on_hide_module_activate(GtkMenuItem *menuitem, + gpointer user_data); void on_simple_activate(GtkMenuItem *menuitem, gpointer user_data); void on_subject_activate(GtkMenuItem *menuitem, diff --git a/src/main/settings.c b/src/main/settings.c index 1c9b9c3ca..18e59c8f1 100644 --- a/src/main/settings.c +++ b/src/main/settings.c @@ -518,6 +518,37 @@ if (!settings.morph_heb_lex || strlen(settings.morph_heb_lex) == 0) { settings.mod_mgr_remote_source_index = 0; } + /* module tree display grouping */ + /* note: the "modules" section already exists (bible/comm/dict/...), + so we only add the item, never re-create the section. */ + if ((buf = xml_get_value("modules", "grouping"))) + settings.module_tree_grouping = atoi(buf); + else { + xml_add_new_item_to_section("modules", "grouping", "0"); + settings.module_tree_grouping = 0; + } + + if (xml_get_value("modules", "favorites")) + settings.favorite_modules = xml_get_value("modules", "favorites"); + else { + xml_add_new_item_to_section("modules", "favorites", ""); + settings.favorite_modules = xml_get_value("modules", "favorites"); + } + + if (xml_get_value("modules", "hidden")) + settings.hidden_modules = xml_get_value("modules", "hidden"); + else { + xml_add_new_item_to_section("modules", "hidden", ""); + settings.hidden_modules = xml_get_value("modules", "hidden"); + } + + if ((buf = xml_get_value("modules", "show_hidden"))) + settings.show_hidden_modules = atoi(buf); + else { + xml_add_new_item_to_section("modules", "show_hidden", "0"); + settings.show_hidden_modules = 0; + } + /* current verse & keys */ settings.currentverse = xml_get_value("keys", "verse"); settings.dictkey = xml_get_value("keys", "dictionary"); diff --git a/src/main/settings.h b/src/main/settings.h index 19283f31c..db51733b2 100644 --- a/src/main/settings.h +++ b/src/main/settings.h @@ -208,6 +208,16 @@ struct _settings int mod_mgr_destination; /* if = 0 destination is .sword else sword directory */ + /* module tree display grouping: 0 = category+language (default), + 1 = category only, 2 = language+category */ + int module_tree_grouping; + + /* semicolon-delimited lists of module names: ";modname;modname;" */ + char *favorite_modules; + char *hidden_modules; + /* if true, modules in hidden_modules are shown anyway (temporarily) */ + int show_hidden_modules; + /* if true use prayerlist */ int prayerlist; diff --git a/src/main/sidebar.cc b/src/main/sidebar.cc index b2911c1d8..6a99dcd6e 100644 --- a/src/main/sidebar.cc +++ b/src/main/sidebar.cc @@ -388,8 +388,6 @@ static void add_verses_to_chapter(GtkTreeModel *model, GtkTreeIter iter, const gchar *key) { gchar **work_buf = NULL; - gchar *book = NULL; - gint chapter; gint verses; gint i; GtkTreeIter child_iter; @@ -401,19 +399,17 @@ static void add_verses_to_chapter(GtkTreeModel *model, } gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, -1); - book = backend->key_get_book(settings.MainWindowModule, work_buf[3]); - chapter = backend->key_get_chapter(settings.MainWindowModule, work_buf[3]); verses = backend->key_verse_count(settings.MainWindowModule, work_buf[3]); for (i = 1; i < (verses + 1); i++) { gchar *num = main_format_number(i); gchar *buf = g_strdup_printf("%s %s", _("verse"), num); g_free(num); - gchar *key = g_strdup_printf("sword://%s/%s %d:%d", + gchar *ref = backend->key_get_verse_ref(settings.MainWindowModule, + work_buf[3], i); + gchar *key = g_strdup_printf("sword://%s/%s", work_buf[2], - book, - chapter, - i); + ref); gtk_tree_store_append(GTK_TREE_STORE(model), &child_iter, &iter); gtk_tree_store_set(GTK_TREE_STORE(model), &child_iter, @@ -424,6 +420,7 @@ static void add_verses_to_chapter(GtkTreeModel *model, COL_OFFSET, (gchar *)key, -1); g_free(key); + g_free(ref); g_free(buf); } g_strfreev(work_buf); @@ -450,7 +447,6 @@ static void add_chapters_to_book(GtkTreeModel *model, GtkTreeIter iter, const gchar *key) { gchar **work_buf = NULL; - gchar *book = NULL; gint chapters; gint i; GtkTreeIter child_iter; @@ -462,17 +458,17 @@ static void add_chapters_to_book(GtkTreeModel *model, GtkTreeIter iter, } gtk_tree_store_set(GTK_TREE_STORE(model), &iter, COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, -1); - book = backend->key_get_book(settings.MainWindowModule, work_buf[3]); chapters = backend->key_chapter_count(settings.MainWindowModule, work_buf[3]); for (i = 1; i < (chapters + 1); i++) { gchar *num = main_format_number(i); gchar *buf = g_strdup_printf("%s %s", _("chapter"), num); g_free(num); - gchar *key = g_strdup_printf("chapter://%s/%s %d:1", + gchar *ref = backend->key_get_chapter_ref(settings.MainWindowModule, + work_buf[3], i); + gchar *key = g_strdup_printf("chapter://%s/%s", work_buf[2], - book, - i); + ref); gtk_tree_store_append(GTK_TREE_STORE(model), &child_iter, &iter); gtk_tree_store_set(GTK_TREE_STORE(model), &child_iter, @@ -483,6 +479,7 @@ static void add_chapters_to_book(GtkTreeModel *model, GtkTreeIter iter, COL_OFFSET, (gchar *)key, -1); g_free(key); + g_free(ref); g_free(buf); } g_strfreev(work_buf); @@ -911,6 +908,292 @@ static void add_module_to_language_folder(GtkTreeModel *model, } } +static gboolean semilist_contains(const char *list, const char *name) +{ + gchar *needle; + gboolean found; + if (!list || !*list || !name) + return FALSE; + needle = g_strdup_printf(";%s;", name); + found = (strstr(list, needle) != NULL); + g_free(needle); + return found; +} + +static char *semilist_add(char *list, const char *name) +{ + gchar *result; + if (semilist_contains(list, name)) + return list; + if (!list || !*list) + result = g_strdup_printf(";%s;", name); + else + result = g_strdup_printf("%s%s;", list, name); + g_free(list); + return result; +} + +static char *semilist_remove(char *list, const char *name) +{ + gchar *needle, *pos, *result; + if (!semilist_contains(list, name)) + return list; + needle = g_strdup_printf(";%s;", name); + pos = strstr(list, needle); + result = g_strdup_printf("%.*s%s", (int)(pos - list + 1), list, pos + strlen(needle)); + g_free(needle); + g_free(list); + return result; +} + +gboolean module_is_favorite(const gchar *name) +{ + return semilist_contains(settings.favorite_modules, name); +} + +gboolean module_is_hidden(const gchar *name) +{ + return semilist_contains(settings.hidden_modules, name); +} + +void module_toggle_favorite(const gchar *name) +{ + if (module_is_favorite(name)) + settings.favorite_modules = semilist_remove(settings.favorite_modules, name); + else + settings.favorite_modules = semilist_add(settings.favorite_modules, name); + xml_set_value("Xiphos", "modules", "favorites", settings.favorite_modules); + main_load_module_tree(sidebar.module_list); +} + +void module_toggle_hidden(const gchar *name) +{ + if (module_is_hidden(name)) + settings.hidden_modules = semilist_remove(settings.hidden_modules, name); + else + settings.hidden_modules = semilist_add(settings.hidden_modules, name); + xml_set_value("Xiphos", "modules", "hidden", settings.hidden_modules); + main_load_module_tree(sidebar.module_list); +} + +static const gchar *category_caption(MOD_MGR *info) +{ + if (info->is_cult) + return _("Cult/Unorthodox"); + if (info->type[0] == 'B') + return _("Biblical Texts"); + if (info->type[0] == 'C') + return _("Commentaries"); + if (info->is_maps) + return _("Maps"); + if (info->is_images) + return _("Images"); + if (info->is_devotional) + return _("Daily Devotionals"); + if (info->is_glossary) + return _("Glossaries"); + if (info->type[0] == 'L') + return _("Dictionaries"); + if (info->type[0] == 'G') + return _("General Books"); + return NULL; +} + +static const gchar *module_language_caption(MOD_MGR *info) +{ + const gchar *lang = info->language; + if (!lang || !g_utf8_validate(lang, -1, NULL)) + return _("Unknown"); + if (!g_unichar_isalnum(g_utf8_get_char(lang))) + return _("Unknown"); + return lang; +} + +static GtkTreeIter get_or_create_folder(GtkTreeStore *store, + GtkTreeIter *parent, + const gchar *caption) +{ + GtkTreeIter iter; + gboolean valid = gtk_tree_model_iter_children(GTK_TREE_MODEL(store), &iter, parent); + while (valid) { + gchar *str_data = NULL; + gboolean match; + gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, COL_CAPTION, &str_data, -1); + match = (str_data && !strcmp(str_data, caption)); + g_free(str_data); + if (match) + return iter; + valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter); + } + gtk_tree_store_append(store, &iter, parent); + gtk_tree_store_set(store, &iter, + COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, + COL_CLOSED_PIXBUF, pixbufs->pixbuf_closed, + COL_CAPTION, caption, + COL_MODULE, NULL, + COL_OFFSET, caption, -1); + return iter; +} + +static void append_module_row(GtkTreeStore *store, GtkTreeIter *parent, MOD_MGR *info) +{ + GtkTreeIter child; + const gchar *abbreviation = main_name_to_abbrev(info->name); + gchar *content = g_strdup_printf("%s: %s", + (abbreviation ? abbreviation : info->name), + info->description); + gtk_tree_store_append(store, &child, parent); + gtk_tree_store_set(store, &child, + COL_OPEN_PIXBUF, pixbufs->pixbuf_closed, + COL_CLOSED_PIXBUF, pixbufs->pixbuf_closed, + COL_CAPTION, (gchar *)content, + COL_MODULE, (gchar *)info->name, + COL_OFFSET, NULL, -1); + g_free(content); +} + +static int module_lang_cmpstringp(gconstpointer p1, gconstpointer p2) +{ + return ucol_strcollUTF8(collator, (const char *)p1, -1, (const char *)p2, -1, &collator_status); +} + +void main_load_module_tree_flat(GtkWidget *tree) +{ + GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, + GDK_TYPE_PIXBUF, GDK_TYPE_PIXBUF, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + GList *tmp = mod_mgr_list_local_modules(settings.path_to_mods, TRUE); + GList *tmp2; + GHashTable *cat_iters = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + GtkTreeIter favorites; + gboolean have_favorites = FALSE; + for (tmp2 = tmp; tmp2; tmp2 = g_list_next(tmp2)) { + MOD_MGR *info = (MOD_MGR *)tmp2->data; + const gchar *cat = category_caption(info); + GtkTreeIter *cat_iter; + gboolean hidden = module_is_hidden(info->name) && !settings.show_hidden_modules; + if (!cat) { + XI_warning(("mod `%s' unknown type `%s'", info->name, info->type)); + continue; + } + if (!hidden) { + cat_iter = (GtkTreeIter *)g_hash_table_lookup(cat_iters, cat); + if (!cat_iter) { + cat_iter = g_new(GtkTreeIter, 1); + gtk_tree_store_append(store, cat_iter, NULL); + gtk_tree_store_set(store, cat_iter, + COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, + COL_CLOSED_PIXBUF, pixbufs->pixbuf_closed, + COL_CAPTION, cat, + COL_MODULE, NULL, + COL_OFFSET, cat, -1); + g_hash_table_insert(cat_iters, (gpointer)cat, cat_iter); + } + append_module_row(store, cat_iter, info); + + if (module_is_favorite(info->name)) { + if (!have_favorites) { + gtk_tree_store_prepend(store, &favorites, NULL); + gtk_tree_store_set(store, &favorites, + COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, + COL_CLOSED_PIXBUF, pixbufs->pixbuf_closed, + COL_CAPTION, _("Favorites"), + COL_MODULE, NULL, + COL_OFFSET, _("Favorites"), -1); + have_favorites = TRUE; + } + append_module_row(store, &favorites, info); + } + } + g_free(info->name); + g_free(info->type); + g_free(info->new_version); + g_free(info->old_version); + g_free(info->installsize); + g_free(info); + } + g_list_free(tmp); + g_hash_table_destroy(cat_iters); + gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store)); +} + +void main_load_module_tree_by_language(GtkWidget *tree) +{ + GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, + GDK_TYPE_PIXBUF, GDK_TYPE_PIXBUF, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + GList *tmp = mod_mgr_list_local_modules(settings.path_to_mods, TRUE); + GList *tmp2, *languages = NULL; + GHashTable *lang_iters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + GtkTreeIter favorites; + gboolean have_favorites = FALSE; + if (!collator) { + char *locale = getenv("LANG"); + collator = ucol_open((locale ? locale : ""), &collator_status); + } + for (tmp2 = tmp; tmp2; tmp2 = g_list_next(tmp2)) { + MOD_MGR *info = (MOD_MGR *)tmp2->data; + const gchar *lang = module_language_caption(info); + if (!g_list_find_custom(languages, lang, (GCompareFunc)strcmp)) { + languages = g_list_insert_sorted(languages, g_strdup(lang), + (GCompareFunc)module_lang_cmpstringp); + } + } + for (tmp2 = languages; tmp2; tmp2 = g_list_next(tmp2)) { + gchar *lang = (gchar *)tmp2->data; + GtkTreeIter *iter = g_new(GtkTreeIter, 1); + gtk_tree_store_append(store, iter, NULL); + gtk_tree_store_set(store, iter, + COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, + COL_CLOSED_PIXBUF, pixbufs->pixbuf_closed, + COL_CAPTION, lang, + COL_MODULE, NULL, + COL_OFFSET, lang, -1); + g_hash_table_insert(lang_iters, g_strdup(lang), iter); + } + g_list_free_full(languages, g_free); + for (tmp2 = tmp; tmp2; tmp2 = g_list_next(tmp2)) { + MOD_MGR *info = (MOD_MGR *)tmp2->data; + const gchar *cat = category_caption(info); + const gchar *lang = module_language_caption(info); + GtkTreeIter *lang_iter; + GtkTreeIter cat_iter; + gboolean hidden = module_is_hidden(info->name) && !settings.show_hidden_modules; + if (!cat) { + XI_warning(("mod `%s' unknown type `%s'", info->name, info->type)); + continue; + } + if (!hidden) { + lang_iter = (GtkTreeIter *)g_hash_table_lookup(lang_iters, lang); + cat_iter = get_or_create_folder(store, lang_iter, cat); + append_module_row(store, &cat_iter, info); + + if (module_is_favorite(info->name)) { + if (!have_favorites) { + gtk_tree_store_prepend(store, &favorites, NULL); + gtk_tree_store_set(store, &favorites, + COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, + COL_CLOSED_PIXBUF, pixbufs->pixbuf_closed, + COL_CAPTION, _("Favorites"), + COL_MODULE, NULL, + COL_OFFSET, _("Favorites"), -1); + have_favorites = TRUE; + } + append_module_row(store, &favorites, info); + } + } + g_free(info->name); + g_free(info->type); + g_free(info->new_version); + g_free(info->old_version); + g_free(info->installsize); + g_free(info); + } + g_list_free(tmp); + g_hash_table_destroy(lang_iters); + gtk_tree_view_set_model(GTK_TREE_VIEW(tree), GTK_TREE_MODEL(store)); +} + /****************************************************************************** * Name * main_load_module_tree @@ -929,6 +1212,16 @@ static void add_module_to_language_folder(GtkTreeModel *model, void main_load_module_tree(GtkWidget *tree) { + switch (settings.module_tree_grouping) { + case 1: + main_load_module_tree_flat(tree); + return; + case 2: + main_load_module_tree_by_language(tree); + return; + default: + break; /* mode 0: category+language, unchanged below */ + } GtkTreeStore *store; GtkTreeIter text; @@ -952,6 +1245,9 @@ void main_load_module_tree(GtkWidget *tree) GtkTreeIter prayerlist; gboolean need_prayerlist = 0; + GtkTreeIter favorites; + gboolean have_favorites = FALSE; + GtkTreeIter child_iter; GList *tmp = NULL; GList *tmp2 = NULL; @@ -1138,7 +1434,9 @@ void main_load_module_tree(GtkWidget *tree) tmp2 = tmp; while (tmp2 != NULL) { MOD_MGR *info = (MOD_MGR *)tmp2->data; + gboolean hidden = module_is_hidden(info->name) && !settings.show_hidden_modules; + if (!hidden) { if (info->is_cult) { add_module_to_language_folder(GTK_TREE_MODEL(store), cult, info->language, @@ -1180,6 +1478,21 @@ void main_load_module_tree(GtkWidget *tree) info->name, info->type)); } + if (module_is_favorite(info->name)) { + if (!have_favorites) { + gtk_tree_store_prepend(store, &favorites, NULL); + gtk_tree_store_set(store, &favorites, + COL_OPEN_PIXBUF, pixbufs->pixbuf_opened, + COL_CLOSED_PIXBUF, pixbufs->pixbuf_closed, + COL_CAPTION, _("Favorites"), + COL_MODULE, NULL, + COL_OFFSET, _("Favorites"), -1); + have_favorites = TRUE; + } + append_module_row(store, &favorites, info); + } + } + g_free(info->name); g_free(info->type); g_free(info->new_version); diff --git a/src/main/sidebar.h b/src/main/sidebar.h index 292ee0d69..2434487fd 100644 --- a/src/main/sidebar.h +++ b/src/main/sidebar.h @@ -48,6 +48,12 @@ void main_display_verse_list_in_sidebar(gchar *key, gchar *verse_list); void main_create_pixbufs(void); void main_load_module_tree(GtkWidget *tree); +void main_load_module_tree_flat(GtkWidget *tree); +void main_load_module_tree_by_language(GtkWidget *tree); +gboolean module_is_favorite(const gchar *name); +gboolean module_is_hidden(const gchar *name); +void module_toggle_favorite(const gchar *name); +void module_toggle_hidden(const gchar *name); void main_add_mod_tree_columns(GtkTreeView *tree); void main_mod_treeview_button_one(GtkTreeModel *model, GtkTreeIter selected); diff --git a/ui/prefs.glade b/ui/prefs.glade index 7a69ed182..44a381c29 100644 --- a/ui/prefs.glade +++ b/ui/prefs.glade @@ -2334,6 +2334,51 @@ 7 + + + True + True + + + True + 0 + Module List Grouping + + + False + 0 + + + + + True + Category, then Language +Category only +Language, then Category + + Module List Grouping Combo + + + + 1 + + + + + 8 + + + + + True + Show hidden modules + True + True + + + 9 + + False diff --git a/ui/prefs.gtkbuilder b/ui/prefs.gtkbuilder index 798a8caf1..7a9e3b0da 100644 --- a/ui/prefs.gtkbuilder +++ b/ui/prefs.gtkbuilder @@ -66,6 +66,23 @@ + + + + + + + + Category, then Language + + + Category only + + + Language, then Category + + + @@ -2838,6 +2855,69 @@ 7 + + + True + False + True + + + True + False + 0 + Module List Grouping + + + False + True + 0 + + + + + True + False + model_module_grouping + + + + 0 + + + + + Module List Grouping Combo + + + + + True + True + 1 + + + + + True + True + 8 + + + + + Show hidden modules + True + True + False + True + True + + + False + True + 9 + + False diff --git a/ui/xi-menus-popup.gtkbuilder b/ui/xi-menus-popup.gtkbuilder index ffe9822f3..4f84d7956 100644 --- a/ui/xi-menus-popup.gtkbuilder +++ b/ui/xi-menus-popup.gtkbuilder @@ -243,6 +243,26 @@ + + + Add to Favorites + False + True + False + True + + + + + + Hide this module + False + True + False + True + + + False diff --git a/ui/xi-menus.glade b/ui/xi-menus.glade index c023a63bf..e2ae1e14b 100644 --- a/ui/xi-menus.glade +++ b/ui/xi-menus.glade @@ -1889,6 +1889,24 @@ + + + + True + Add to Favorites + True + + + + + + + True + Hide this module + True + + +