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 @@
+
+
+
+
+ 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 @@
+
+
+
+
+
+