diff --git a/src/gtk/bookmark_dialog.c b/src/gtk/bookmark_dialog.c index 824c1eeb2..6262f974a 100644 --- a/src/gtk/bookmark_dialog.c +++ b/src/gtk/bookmark_dialog.c @@ -33,6 +33,7 @@ #include "gui/bookmarks_treeview.h" #include "gui/dialog.h" #include "gui/utilities.h" +#include "gui/widgets.h" #include "main/display.hh" #include "main/sword.h" @@ -54,7 +55,7 @@ static GtkWidget *entry_module; static GtkWidget *textview; static GtkTextBuffer *textbuffer; static gchar *note; - +static gchar *global_module_name = NULL; GtkWidget *bookmark_dialog; GtkWidget *mark_verse_dialog; @@ -71,6 +72,13 @@ void on_buffer_changed(GtkTextBuffer *textbuffer, gpointer user_data) XI_message(("note: %s", note)); } +static void toggle_color_clicked(GtkButton *btn, GtkWidget *colorbtn) +{ + gboolean active = gtk_widget_is_sensitive(colorbtn); + gtk_widget_set_sensitive(colorbtn, !active); + gtk_button_set_label(btn, active ? _("Add color") : _("No color")); +} + /****************************************************************************** * Name * add_bookmark_button @@ -93,36 +101,48 @@ static void add_bookmark_button(void) GtkTreeIter iter; BOOKMARK_DATA *data; GtkTreeSelection *selection; + const gchar *module_from_entry; + const gchar *module_to_use; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); if (!gtk_tree_selection_get_selected(selection, NULL, &selected)) return; - data = g_new(BOOKMARK_DATA, 1); + data = g_new0(BOOKMARK_DATA, 1); data->caption = g_strdup((gchar *)gtk_entry_get_text(GTK_ENTRY(entry_label))); data->key = g_strdup((gchar *)gtk_entry_get_text(GTK_ENTRY(entry_key))); - data->module = g_strdup((gchar *)gtk_entry_get_text(GTK_ENTRY(entry_module))); - if (!strcmp(data->module, "studypad")) - data->module_desc = "studypad"; - else - data->module_desc = - g_strdup(main_get_module_description(data->module)); - data->description = g_strdup((gchar *)gtk_entry_get_text(GTK_ENTRY(entry_label))); + module_from_entry = gtk_entry_get_text(GTK_ENTRY(entry_module)); + if (module_from_entry && strlen(module_from_entry) > 0) { + module_to_use = module_from_entry; + } else if (global_module_name && strlen(global_module_name) > 0) { + module_to_use = global_module_name; + } else { + module_to_use = ""; + } + data->module = g_strdup(module_to_use); + + if (data->module && strlen(data->module) > 0) { + if (!strcmp(data->module, "studypad")) + data->module_desc = g_strdup("studypad"); + else + data->module_desc = g_strdup(main_get_module_description(data->module)); + } else { + data->module_desc = g_strdup(""); + } + + data->description = g_strdup((gchar *)gtk_entry_get_text(GTK_ENTRY(entry_label))); data->is_leaf = TRUE; + data->color = NULL; data->opened = bm_pixbufs->pixbuf_helpdoc; data->closed = NULL; + gui_add_item_to_tree(&iter, &selected, data); bookmarks_changed = TRUE; gui_save_bookmarks(NULL, NULL); - g_free(data->caption); - g_free(data->key); - g_free(data->module); - g_free(data->description); - g_free(data); } /****************************************************************************** @@ -147,56 +167,100 @@ static void add_folder_button(void) GtkTreeIter iter; BOOKMARK_DATA *data; GtkTreeSelection *selection; - char *t; - gint test; - GS_DIALOG *info; - GString *str; selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)); if (!gtk_tree_selection_get_selected(selection, NULL, &selected)) return; - t = "|"; - str = g_string_new(""); - info = gui_new_dialog(); - //info->stock_icon = GTK_STOCK_OPEN; - info->title = _("Bookmark"); - g_string_printf(str, "%s", - _("Enter Folder Name")); - info->label_top = str->str; - info->text1 = g_strdup(_("Folder Name")); - info->label1 = _("Folder: "); - info->ok = TRUE; - info->cancel = TRUE; - - data = g_new(BOOKMARK_DATA, 1); - /*** open dialog to get name for new folder ***/ - test = gui_gs_dialog(info); - if (test == GS_OK) { - char *buf = g_strdelimit(info->text1, t, ' '); - data->caption = g_strdup(buf); - data->key = NULL; - data->module = NULL; - data->module_desc = NULL; - data->description = NULL; + gchar *glade_file = gui_general_user_file("folder" UI_SUFFIX, TRUE); + if (!glade_file) return; + +#ifdef USE_GTKBUILDER + GtkBuilder *gxml = gtk_builder_new(); + gtk_builder_add_from_file(gxml, glade_file, NULL); +#else + GladeXML *gxml = glade_xml_new(glade_file, NULL, NULL); +#endif + g_free(glade_file); + + GtkWidget *entry = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_entry_name")); + GtkWidget *dialog = GTK_WIDGET(UI_GET_ITEM(gxml, "dialog_folder")); + GtkWidget *colorbtn = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_color_button")); + GtkWidget *clearbtn = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_clear_color")); + +#ifdef USE_GTKBUILDER + if (!dialog || !entry || !colorbtn || !clearbtn) { + g_object_unref(gxml); + return; + } +#else + if (!dialog || !entry || !colorbtn || !clearbtn) return; + + /* Détruire les boutons inactifs de Glade et ajouter les vrais boutons GTK2 */ + GtkWidget *btn_ok = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_ok")); + GtkWidget *btn_cancel = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_cancel")); + if (btn_ok) gtk_widget_destroy(btn_ok); + if (btn_cancel) gtk_widget_destroy(btn_cancel); + + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); +#endif + + gtk_window_set_title(GTK_WINDOW(dialog), _("New Tag")); + gtk_entry_set_text(GTK_ENTRY(entry), ""); + + /* Cette ligne est commune, on la sort du #ifdef */ + gtk_button_set_label(GTK_BUTTON(clearbtn), _("Add color")); + g_signal_connect(clearbtn, "clicked", + G_CALLBACK(toggle_color_clicked), colorbtn); + g_signal_connect(colorbtn, "color-set", + G_CALLBACK(toggle_color_clicked), clearbtn); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { + const gchar *name = gtk_entry_get_text(GTK_ENTRY(entry)); + gchar *color = NULL; + + if (g_strcmp0(gtk_button_get_label(GTK_BUTTON(clearbtn)), _("No color")) == 0) { +#if defined(USE_GTKBUILDER) && GTK_CHECK_VERSION(3, 4, 0) + GdkRGBA rgba; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(colorbtn), &rgba); + if (rgba.red < 0.99 || rgba.green < 0.99 || rgba.blue < 0.99) + color = g_strdup_printf("#%02X%02X%02X", + (guint)(rgba.red * 255), + (guint)(rgba.green * 255), + (guint)(rgba.blue * 255)); +#else + /* API de récupération de couleur pour GTK2 */ + GdkColor gdk_color; + gtk_color_button_get_color(GTK_COLOR_BUTTON(colorbtn), &gdk_color); + if (gdk_color.red < 65000 || gdk_color.green < 65000 || gdk_color.blue < 65000) + color = g_strdup_printf("#%02X%02X%02X", + gdk_color.red >> 8, + gdk_color.green >> 8, + gdk_color.blue >> 8); +#endif + } + + data = g_new0(BOOKMARK_DATA, 1); + data->caption = g_strdelimit(g_strdup(name), "/|><.'`\"", ' '); + data->color = color; data->is_leaf = FALSE; data->opened = bm_pixbufs->pixbuf_opened; data->closed = bm_pixbufs->pixbuf_closed; gui_add_item_to_tree(&iter, &selected, data); bookmarks_changed = TRUE; gui_save_bookmarks(NULL, NULL); - g_free(data->caption); - GtkTreePath *path = - gtk_tree_model_get_path(GTK_TREE_MODEL(model), &iter); - gtk_tree_view_expand_to_path(GTK_TREE_VIEW(treeview), - path); + + GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &iter); + gtk_tree_view_expand_to_path(GTK_TREE_VIEW(treeview), path); gtk_tree_selection_select_path(selection, path); gtk_tree_path_free(path); } - g_free(data); - g_free(info->text1); - g_free(info); - g_string_free(str, TRUE); + gtk_widget_destroy(dialog); +#ifdef USE_GTKBUILDER + g_object_unref(gxml); +#endif } /****************************************************************************** @@ -232,7 +296,6 @@ void on_dialog_response(GtkDialog *dialog, break; } } - /****************************************************************************** * Name * on_dialog_enter @@ -249,8 +312,8 @@ void on_dialog_response(GtkDialog *dialog, void on_dialog_enter(void) { - on_dialog_response(GTK_DIALOG(bookmark_dialog), - GTK_RESPONSE_OK, NULL); + if (bookmark_dialog) + gtk_dialog_response(GTK_DIALOG(bookmark_dialog), GTK_RESPONSE_OK); } /****************************************************************************** @@ -441,8 +504,6 @@ static GtkWidget *_create_bookmark_dialog(gchar *label, /* lookup the root widget */ bookmark_dialog = UI_GET_ITEM(gxml, "dialog"); - g_signal_connect(bookmark_dialog, "response", - G_CALLBACK(on_dialog_response), NULL); /* treeview */ treeview = UI_GET_ITEM(gxml, "treeview"); @@ -577,11 +638,37 @@ static GtkWidget *_create_mark_verse_dialog(gchar *module, gchar *key) void gui_bookmark_dialog(gchar *label, gchar *module_name, gchar *key) { - GtkWidget *dialog = - _create_bookmark_dialog(label, module_name, key); + GtkWidget *dialog; + gint response; + + if (global_module_name != NULL) { + g_free(global_module_name); + global_module_name = NULL; + } + + if (module_name != NULL) { + global_module_name = g_strdup(module_name); + } + + dialog = _create_bookmark_dialog(label, module_name, key); if (!dialog) return; - gtk_dialog_run(GTK_DIALOG(dialog)); + + while (TRUE) { + response = gtk_dialog_run(GTK_DIALOG(dialog)); + if (response == GTK_RESPONSE_ACCEPT) { + /* New folder — keep dialog open */ + add_folder_button(); + } else if (response == GTK_RESPONSE_OK) { + /* Add bookmark — close dialog */ + add_bookmark_button(); + break; + } else { + /* Cancel or destroy */ + break; + } + } + gtk_widget_destroy(dialog); } /****************************************************************************** diff --git a/src/gtk/bookmarks_menu.c b/src/gtk/bookmarks_menu.c index 66fd28e24..677f402c0 100644 --- a/src/gtk/bookmarks_menu.c +++ b/src/gtk/bookmarks_menu.c @@ -50,8 +50,8 @@ #include "gui/widgets.h" #include "main/settings.h" -#include "main/sidebar.h" #include "main/sword.h" +#include "main/sidebar.h" #include "main/xml.h" #include "main/module_dialogs.h" #include "main/url.hh" @@ -68,6 +68,21 @@ BOOKMARK_MENU menu; gboolean bookmarks_changed; +static void toggle_color_clicked(GtkButton *btn, GtkWidget *other) +{ + if (GTK_IS_COLOR_BUTTON(btn)) { + /* colorbtn clicked — enable "No color" */ + gtk_button_set_label(GTK_BUTTON(other), _("No color")); + } else { + /* clearbtn clicked — toggle */ + gboolean has_color = + g_strcmp0(gtk_button_get_label(GTK_BUTTON(btn)), + _("No color")) == 0; + gtk_button_set_label(GTK_BUTTON(btn), + has_color ? _("Add color") : _("No color")); + } +} + /****************************************************************************** * Name * save_treeview_to_xml_bookmarks @@ -90,12 +105,12 @@ static void save_treeview_to_xml_bookmarks(GtkTreeIter *iter, xmlNodePtr root_node = NULL; xmlNodePtr cur_node = NULL; xmlDocPtr root_doc; - // xmlAttrPtr root_attr; gchar *caption = NULL; gchar *key = NULL; gchar *module = NULL; gchar *mod_desc = NULL; gchar *description = NULL; + gchar *color = NULL; if (!bookmarks_changed) return; @@ -104,7 +119,6 @@ static void save_treeview_to_xml_bookmarks(GtkTreeIter *iter, if (root_doc != NULL) { root_node = xmlNewNode(NULL, (const xmlChar *)"SwordBookmarks"); - //root_attr = xmlNewProp(root_node, (const xmlChar *)"syntaxVersion", (const xmlChar *)"1"); xmlDocSetRootElement(root_doc, root_node); @@ -112,24 +126,31 @@ static void save_treeview_to_xml_bookmarks(GtkTreeIter *iter, do { gtk_tree_model_get(GTK_TREE_MODEL(model), iter, - 2, &caption, - 3, &key, - 4, &module, - 5, &mod_desc, 6, &description, -1); + COL_CAPTION, &caption, + COL_KEY, &key, + COL_MODULE, &module, + COL_MODULE_DESC, &mod_desc, + COL_DESCRIPTION, &description, + COL_COLOR, &color, + -1); if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(model), iter)) { - cur_node = - xml_add_folder_to_parent(root_node, caption); + /* folder node — write color attribute when present */ + cur_node = xml_add_folder_to_parent_colored(root_node, + caption, + color); utilities_parse_treeview(cur_node, iter, GTK_TREE_MODEL(model)); - } else + } else { xml_add_bookmark_to_parent(root_node, description, key, module, mod_desc); - g_free(caption); - g_free(key); - g_free(module); - g_free(mod_desc); - g_free(description); + } +// g_free(caption); +// g_free(key); +// g_free(module); +// g_free(mod_desc); +// g_free(description); +// g_free(color); } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), iter)); xmlSaveFormatFile(filename, root_doc, 1); @@ -167,7 +188,9 @@ static void add_item_to_tree(GtkTreeIter *iter, GtkTreeIter *parent, COL_KEY, data->key, COL_MODULE, data->module, COL_MODULE_DESC, data->module_desc, - COL_DESCRIPTION, data->description, -1); + COL_DESCRIPTION, data->description, + COL_COLOR, data->color, + -1); } /****************************************************************************** @@ -296,8 +319,8 @@ G_MODULE_EXPORT void on_dialog_activate(GtkMenuItem *menuitem, if (module && (main_get_mod_type(module) == PERCOM_TYPE)) { editor_create_new(module, key, TRUE); use_dialog = FALSE; - g_free(module); - g_free(key); +// g_free(module); +// g_free(key); return; } @@ -310,8 +333,8 @@ G_MODULE_EXPORT void on_dialog_activate(GtkMenuItem *menuitem, g_free(url); } use_dialog = FALSE; - g_free(module); - g_free(key); +// g_free(module); +// g_free(key); } /****************************************************************************** @@ -333,76 +356,139 @@ G_MODULE_EXPORT void on_dialog_activate(GtkMenuItem *menuitem, G_MODULE_EXPORT void on_edit_item_activate(GtkMenuItem *menuitem, gpointer user_data) { - GS_DIALOG *info; - gint test; GtkTreeSelection *selection; GtkTreeIter selected; - // GtkTreeIter iter; - gchar *caption = NULL; - gchar *key = NULL; - gchar *module = NULL; - gchar *mod_desc = NULL; - gchar *description = NULL; - gboolean is_leaf; - GString *str; - - str = g_string_new(NULL); - g_string_printf(str, "%s", _("Edit")); + gchar *caption = NULL, *key = NULL, *module = NULL; + gchar *mod_desc = NULL, *description = NULL, *current_color = NULL; selection = gtk_tree_view_get_selection(bookmark_tree); if (!gtk_tree_selection_get_selected(selection, NULL, &selected)) return; gtk_tree_model_get(GTK_TREE_MODEL(model), &selected, - 2, &caption, - 3, &key, - 4, &module, 5, &mod_desc, 6, &description, -1); + COL_CAPTION, &caption, COL_KEY, &key, + COL_MODULE, &module, COL_MODULE_DESC, &mod_desc, + COL_DESCRIPTION, &description, + COL_COLOR, ¤t_color, -1); - info = gui_new_dialog(); - info->title = _("Bookmark"); - info->label_top = str->str; - if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(model), &selected)) { - info->label1 = _("Folder name: "); - is_leaf = FALSE; - } else { - info->label1 = _("Bookmark name: "); - info->text2 = g_strdup(key); - info->text3 = g_strdup(module); - info->label2 = _("Verse: "); - info->label3 = _("Module: "); - is_leaf = TRUE; - } + if (!key || !*key) { - info->text1 = g_strdup(caption); - info->ok = TRUE; - info->cancel = TRUE; + /* --- Folder: use the dedicated folder dialog --- */ + gchar *glade_file = gui_general_user_file("folder" UI_SUFFIX, TRUE); + if (!glade_file) goto cleanup; +#ifdef USE_GTKBUILDER + GtkBuilder *gxml = gtk_builder_new(); + gtk_builder_add_from_file(gxml, glade_file, NULL); +#else + GladeXML *gxml = glade_xml_new(glade_file, NULL, NULL); +#endif + GtkWidget *dialog = GTK_WIDGET(UI_GET_ITEM(gxml, "dialog_folder")); + GtkWidget *entry = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_entry_name")); + GtkWidget *colorbtn = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_color_button")); + GtkWidget *clearbtn = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_clear_color")); - test = gui_gs_dialog(info); - if (test == GS_OK) { - BOOKMARK_DATA *data = g_new(BOOKMARK_DATA, 1); - data->caption = info->text1; - data->key = NULL; - data->module = NULL; - data->module_desc = NULL; - data->description = NULL; - if (is_leaf) { - data->opened = bm_pixbufs->pixbuf_helpdoc; - data->closed = NULL; - data->key = info->text2; - data->module = info->text3; - data->module_desc = - g_strdup(main_get_module_description(info->text3)); - if ((strlen(description) > 1) || (strcmp(caption, info->text1))) { - data->description = info->text1; - } else - data->description = NULL; - data->is_leaf = TRUE; +#ifdef USE_GTKBUILDER + if (!dialog || !entry || !colorbtn || !clearbtn) { + g_object_unref(gxml); goto cleanup; + } +#else + if (!dialog || !entry || !colorbtn || !clearbtn) goto cleanup; + + GtkWidget *btn_ok = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_ok")); + GtkWidget *btn_cancel = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_cancel")); + if (btn_ok) gtk_widget_destroy(btn_ok); + if (btn_cancel) gtk_widget_destroy(btn_cancel); + + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); +#endif + gtk_window_set_title(GTK_WINDOW(dialog), _("Edit Tag")); + gtk_entry_set_text(GTK_ENTRY(entry), caption ? caption : ""); + + if (current_color && *current_color) { +#if defined(USE_GTKBUILDER) && GTK_CHECK_VERSION(3, 4, 0) + GdkRGBA rgba; + if (gdk_rgba_parse(&rgba, current_color)) + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(colorbtn), &rgba); +#else + GdkColor gdk_color; + if (gdk_color_parse(current_color, &gdk_color)) + gtk_color_button_set_color(GTK_COLOR_BUTTON(colorbtn), &gdk_color); +#endif } else { - data->opened = bm_pixbufs->pixbuf_opened; - data->closed = bm_pixbufs->pixbuf_closed; - data->is_leaf = FALSE; + gtk_button_set_label(GTK_BUTTON(clearbtn), _("Add color")); } - gtk_tree_store_set(GTK_TREE_STORE(model), &selected, + g_signal_connect(clearbtn, "clicked", + G_CALLBACK(toggle_color_clicked), colorbtn); + g_signal_connect(colorbtn, "color-set", + G_CALLBACK(toggle_color_clicked), clearbtn); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { + const gchar *name = gtk_entry_get_text(GTK_ENTRY(entry)); + gchar *new_color = NULL; + gchar *new_caption; + if (g_strcmp0(gtk_button_get_label(GTK_BUTTON(clearbtn)), _("No color")) == 0) { +#if defined(USE_GTKBUILDER) && GTK_CHECK_VERSION(3, 4, 0) + GdkRGBA rgba; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(colorbtn), &rgba); + if (rgba.red < 0.99 || rgba.green < 0.99 || rgba.blue < 0.99) + new_color = g_strdup_printf("#%02X%02X%02X", + (guint)(rgba.red * 255), + (guint)(rgba.green * 255), + (guint)(rgba.blue * 255)); +#else + GdkColor gdk_color; + gtk_color_button_get_color(GTK_COLOR_BUTTON(colorbtn), &gdk_color); + if (gdk_color.red < 65000 || gdk_color.green < 65000 || gdk_color.blue < 65000) + new_color = g_strdup_printf("#%02X%02X%02X", + gdk_color.red >> 8, + gdk_color.green >> 8, + gdk_color.blue >> 8); +#endif + } + new_caption = g_strdelimit(g_strdup(name), "/|><.'`\"", ' '); + + bookmarks_changed = TRUE; + gtk_tree_store_set(GTK_TREE_STORE(model), &selected, + COL_CAPTION, new_caption, + COL_COLOR, new_color, -1); + gui_save_bookmarks(NULL, NULL); + main_display_bible(NULL, settings.currentverse); +// g_free(new_caption); +// g_free(new_color); + } + gtk_widget_destroy(dialog); +#ifdef USE_GTKBUILDER + g_object_unref(gxml); +#endif + } else { + /* --- Leaf bookmark: generic dialog --- */ + GS_DIALOG *info = gui_new_dialog(); + GString *str = g_string_new(NULL); + g_string_printf(str, "%s", _("Edit")); + info->title = _("Bookmark"); + info->label_top = str->str; + info->label1 = _("Bookmark name: "); + info->text1 = g_strdup(caption); + info->text2 = g_strdup(key); + info->text3 = g_strdup(module); + info->label2 = _("Verse: "); + info->label3 = _("Module: "); + info->ok = TRUE; + info->cancel = TRUE; + if (gui_gs_dialog(info) == GS_OK) { + BOOKMARK_DATA *data = g_new0(BOOKMARK_DATA, 1); + data->caption = g_strdup(info->text1); + data->key = g_strdup(info->text2); + data->module = g_strdup(info->text3); + data->module_desc = g_strdup(main_get_module_description(info->text3)); + data->description = ((description && strlen(description) > 1) || + (caption && strcmp(caption, info->text1))) + ? g_strdup(info->text1) : NULL; + data->is_leaf = TRUE; + data->opened = bm_pixbufs->pixbuf_helpdoc; + gtk_tree_store_set(GTK_TREE_STORE(model), &selected, COL_OPEN_PIXBUF, data->opened, COL_CLOSED_PIXBUF, data->closed, COL_CAPTION, data->caption, @@ -410,26 +496,22 @@ G_MODULE_EXPORT void on_edit_item_activate(GtkMenuItem *menuitem, COL_MODULE, data->module, COL_MODULE_DESC, data->module_desc, COL_DESCRIPTION, data->description, -1); - bookmarks_changed = TRUE; - gui_save_bookmarks(NULL, NULL); - g_free(data); + bookmarks_changed = TRUE; + gui_save_bookmarks(NULL, NULL); + } +// g_free(info->text1); +// if (info->text2) g_free(info->text2); +// if (info->text3) g_free(info->text3); +// g_free(info); +// g_string_free(str, TRUE); } - g_free(info->text1); // we used g_strdup() - if (info->text2) - g_free(info->text2); - if (info->text3) - g_free(info->text3); - g_free(info); - g_free(caption); - g_free(key); - g_free(module); - g_free(mod_desc); - g_free(description); - g_string_free(str, TRUE); +cleanup: +// g_free(caption); g_free(key); g_free(module); +// g_free(mod_desc); g_free(description); g_free(current_color); +(void)0; } -//dialog_export_bookmarks_response_cb -//dialog_export_bookmarks_close_cb + /****************************************************************************** * Name * on_remove_folder_activate @@ -508,10 +590,10 @@ G_MODULE_EXPORT void on_delete_item_activate(GtkMenuItem *menuitem, bookmarks_changed = TRUE; gui_save_bookmarks(NULL, NULL); } - g_free(caption); - g_free(key); - g_free(module); - g_free(str); +// g_free(caption); +// g_free(key); +// g_free(module); +// g_free(str); } /****************************************************************************** @@ -665,15 +747,16 @@ void on_add_bookmark_activate(GtkMenuItem *menuitem, gpointer user_data) test = gui_gs_dialog(info); if (test == GS_OK) { - data->caption = info->text1; - data->key = info->text2; - data->module = info->text3; + data->caption = g_strdup(info->text1); + data->key = g_strdup(info->text2); + data->module = g_strdup(info->text3); data->module_desc = g_strdup(main_get_module_description(info->text3)); if (!strcmp(data->caption, buf)) data->description = NULL; else - data->description = info->text1; + data->description = g_strdup(info->text1); + data->color = NULL; /* bookmark leaves never have a color */ data->is_leaf = TRUE; data->opened = bm_pixbufs->pixbuf_helpdoc; data->closed = NULL; @@ -681,12 +764,11 @@ void on_add_bookmark_activate(GtkMenuItem *menuitem, gpointer user_data) bookmarks_changed = TRUE; gui_save_bookmarks(NULL, NULL); } - g_free(info->text1); /* we used g_strdup() */ - g_free(info->text2); - g_free(info->text3); - g_free(info); - g_free(data); - g_string_free(str, TRUE); +// g_free(info->text1); /* we used g_strdup() */ +// g_free(info->text2); +// g_free(info->text3); +// g_free(info); +// g_string_free(str, TRUE); } /****************************************************************************** @@ -734,53 +816,92 @@ G_MODULE_EXPORT void on_new_folder_activate(GtkMenuItem *menuitem, { GtkTreeIter selected; GtkTreeIter iter; - // gchar *caption = NULL; - // gchar *key = NULL; - // gchar *module = NULL; - char *t; - gint test; - GS_DIALOG *info; BOOKMARK_DATA *data; - GString *str; if (!gtk_tree_selection_get_selected(current_selection, NULL, &selected)) return; - t = "/|><.'`\""; - str = g_string_new(""); - info = gui_new_dialog(); - //info->stock_icon = GTK_STOCK_OPEN; - info->title = _("Bookmark"); - g_string_printf(str, "%s", - _("Enter Folder Name")); - info->label_top = str->str; - info->text1 = g_strdup(_("Folder Name")); - info->label1 = _("Folder: "); - info->ok = TRUE; - info->cancel = TRUE; + gchar *glade_file = gui_general_user_file("folder" UI_SUFFIX, TRUE); + g_return_if_fail(glade_file != NULL); +#ifdef USE_GTKBUILDER + GtkBuilder *gxml = gtk_builder_new(); + gtk_builder_add_from_file(gxml, glade_file, NULL); +#else + GladeXML *gxml = glade_xml_new(glade_file, NULL, NULL); +#endif + g_free(glade_file); - data = g_new(BOOKMARK_DATA, 1); - /*** open dialog to get name for new folder ***/ - test = gui_gs_dialog(info); - if (test == GS_OK) { - char *buf = g_strdelimit(info->text1, t, ' '); - data->caption = g_strdup(buf); - data->key = NULL; - data->module = NULL; - data->module_desc = NULL; - data->description = NULL; + GtkWidget *dialog = GTK_WIDGET(UI_GET_ITEM(gxml, "dialog_folder")); + GtkWidget *entry = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_entry_name")); + GtkWidget *colorbtn = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_color_button")); + GtkWidget *clearbtn = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_clear_color")); + +#ifdef USE_GTKBUILDER + if (!dialog || !entry || !colorbtn || !clearbtn) { + g_printerr("ERROR: dialog_folder widgets not found\n"); + g_object_unref(gxml); + return; + } +#else + if (!dialog || !entry || !colorbtn || !clearbtn) return; + + GtkWidget *btn_ok = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_ok")); + GtkWidget *btn_cancel = GTK_WIDGET(UI_GET_ITEM(gxml, "folder_cancel")); + if (btn_ok) gtk_widget_destroy(btn_ok); + if (btn_cancel) gtk_widget_destroy(btn_cancel); + + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_OK, GTK_RESPONSE_OK); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); +#endif + + gtk_window_set_title(GTK_WINDOW(dialog), _("New Folder")); + gtk_entry_set_text(GTK_ENTRY(entry), ""); + gtk_button_set_label(GTK_BUTTON(clearbtn), _("Add color")); + g_signal_connect(clearbtn, "clicked", + G_CALLBACK(toggle_color_clicked), colorbtn); + g_signal_connect(colorbtn, "color-set", + G_CALLBACK(toggle_color_clicked), clearbtn); + + gint response = gtk_dialog_run(GTK_DIALOG(dialog)); + if (response == GTK_RESPONSE_OK) { + const gchar *name = gtk_entry_get_text(GTK_ENTRY(entry)); + gchar *color = NULL; + + if (g_strcmp0(gtk_button_get_label(GTK_BUTTON(clearbtn)), _("No color")) == 0) { +#if defined(USE_GTKBUILDER) && GTK_CHECK_VERSION(3, 4, 0) + GdkRGBA rgba; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(colorbtn), &rgba); + if (rgba.red < 0.99 || rgba.green < 0.99 || rgba.blue < 0.99) + color = g_strdup_printf("#%02X%02X%02X", + (guint)(rgba.red * 255), + (guint)(rgba.green * 255), + (guint)(rgba.blue * 255)); +#else + GdkColor gdk_color; + gtk_color_button_get_color(GTK_COLOR_BUTTON(colorbtn), &gdk_color); + if (gdk_color.red < 65000 || gdk_color.green < 65000 || gdk_color.blue < 65000) + color = g_strdup_printf("#%02X%02X%02X", + gdk_color.red >> 8, + gdk_color.green >> 8, + gdk_color.blue >> 8); +#endif + } + + data = g_new0(BOOKMARK_DATA, 1); + data->caption = g_strdelimit(g_strdup(name), "/|><.'`\"", ' '); + data->color = color; data->is_leaf = FALSE; data->opened = bm_pixbufs->pixbuf_opened; data->closed = bm_pixbufs->pixbuf_closed; - add_item_to_tree(&iter, &selected, data); bookmarks_changed = TRUE; + add_item_to_tree(&iter, &selected, data); gui_save_bookmarks(NULL, NULL); - g_free(data->caption); } - g_free(data); - g_free(info->text1); - g_free(info); - g_string_free(str, TRUE); + gtk_widget_destroy(dialog); +#ifdef USE_GTKBUILDER + g_object_unref(gxml); +#endif } /****************************************************************************** @@ -820,9 +941,9 @@ G_MODULE_EXPORT void on_open_in_tab_activate(GtkMenuItem *menuitem, main_url_encode(key), main_url_encode(module)); main_url_handler(url, TRUE); - g_free(key); - g_free(module); - g_free(url); +// g_free(key); +// g_free(module); +// g_free(url); } /****************************************************************************** @@ -842,6 +963,53 @@ G_MODULE_EXPORT void on_open_in_tab_activate(GtkMenuItem *menuitem, * void */ +#if GTK_CHECK_VERSION(3, 4, 0) +G_MODULE_EXPORT void on_set_tag_color_activate(GtkMenuItem *menuitem, + gpointer user_data) +{ + GtkTreeIter selected; + gchar *color = NULL; + GtkTreeSelection *selection = gtk_tree_view_get_selection(bookmark_tree); + + if (!gtk_tree_selection_get_selected(selection, NULL, &selected)) + return; + + /* Only folders get a color */ + if (!gtk_tree_model_iter_has_child(GTK_TREE_MODEL(model), &selected)) + return; + + GtkWidget *dialog = gtk_color_chooser_dialog_new( + _("Choose folder color"), GTK_WINDOW(widgets.app)); + + /* Pre-load existing color if any */ + gtk_tree_model_get(GTK_TREE_MODEL(model), &selected, + COL_COLOR, &color, -1); + if (color && *color) { + GdkRGBA rgba; + if (gdk_rgba_parse(&rgba, color)) + gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(dialog), &rgba); + } +// g_free(color); + + if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { + GdkRGBA rgba; + gchar *hex; + gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(dialog), &rgba); + hex = g_strdup_printf("#%02X%02X%02X", + (guint)(rgba.red * 255), + (guint)(rgba.green * 255), + (guint)(rgba.blue * 255)); + bookmarks_changed = TRUE; + gtk_tree_store_set(GTK_TREE_STORE(model), &selected, + COL_COLOR, hex, -1); +// g_free(hex); + gui_save_bookmarks(NULL, NULL); + main_display_bible(NULL, settings.currentverse); + } + gtk_widget_destroy(dialog); +} +#endif + void gui_create_bookmark_menu(void) { gchar *glade_file; @@ -873,6 +1041,7 @@ void gui_create_bookmark_menu(void) menu.reorder = UI_GET_ITEM(gxml, "allow_reordering"); menu.bibletime = UI_GET_ITEM(gxml, "import_bibletime_bookmarks1"); menu.remove = UI_GET_ITEM(gxml, "remove_folder"); + menu.set_color = UI_GET_ITEM(gxml, "set_tag_color"); gtk_widget_set_sensitive(menu.in_tab, FALSE); gtk_widget_set_sensitive(menu.in_dialog, FALSE); diff --git a/src/gtk/bookmarks_treeview.c b/src/gtk/bookmarks_treeview.c index 91b70dff7..e47eeda4d 100644 --- a/src/gtk/bookmarks_treeview.c +++ b/src/gtk/bookmarks_treeview.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -153,26 +154,47 @@ void gui_verselist_to_bookmarks(GList *verses, gint save_as_single) bm_pixbufs->pixbuf_opened, COL_CLOSED_PIXBUF, bm_pixbufs->pixbuf_closed, - COL_CAPTION, info->text1, + COL_CAPTION, g_strdup(info->text1), COL_KEY, NULL, COL_MODULE, NULL, -1); // set_results_position((char) 1); // TOP GString *str = g_string_new(" "); while (verses) { + BOOKMARK_DATA *data; + list_item = (RESULTS *)verses->data; module_name = list_item->module; gchar *tmpbuf = list_item->key; g_string_printf(str, "%s, %s", tmpbuf, module_name); XI_message(("bookmark: %s", str->str)); + + data = g_new(BOOKMARK_DATA, 1); + data->caption = g_strdup(str->str); + data->key = g_strdup(tmpbuf); + data->module = g_strdup(module_name); + if (!strcmp(data->module, "studypad")) + data->module_desc = g_strdup("studypad"); + else + data->module_desc = g_strdup(main_get_module_description(data->module)); + data->description = g_strdup(""); + data->is_leaf = TRUE; + data->opened = bm_pixbufs->pixbuf_helpdoc; + data->closed = NULL; + data->color = NULL; + gtk_tree_store_append(GTK_TREE_STORE(model), &iter, &parent); gtk_tree_store_set(GTK_TREE_STORE(model), &iter, - COL_OPEN_PIXBUF, - bm_pixbufs->pixbuf_helpdoc, - COL_CLOSED_PIXBUF, NULL, - COL_CAPTION, str->str, - COL_KEY, tmpbuf, - COL_MODULE, module_name, -1); + COL_OPEN_PIXBUF, data->opened, + COL_CLOSED_PIXBUF, data->closed, + COL_CAPTION, data->caption, + COL_KEY, data->key, + COL_MODULE, data->module, + COL_MODULE_DESC, data->module_desc, + COL_DESCRIPTION, data->description, + COL_COLOR, data->color, + -1); + verses = g_list_next(verses); } g_string_free(str, TRUE); @@ -203,6 +225,7 @@ void gui_verselist_to_bookmarks(GList *verses, gint save_as_single) static void get_xml_folder_data(xmlNodePtr cur, BOOKMARK_DATA *data) { xmlChar *folder; + gchar *color; folder = xmlGetProp(cur, (const xmlChar *)"caption"); data->caption = g_strdup((char *)folder); @@ -213,6 +236,12 @@ static void get_xml_folder_data(xmlNodePtr cur, BOOKMARK_DATA *data) data->is_leaf = FALSE; data->opened = bm_pixbufs->pixbuf_opened; data->closed = bm_pixbufs->pixbuf_closed; + + /* color is optional — NULL when the folder has no tag color assigned */ + color = xml_get_folder_color(cur); + data->color = color ? g_strdup(color) : NULL; + if (color) + xmlFree((xmlChar *)color); } /****************************************************************************** @@ -261,6 +290,7 @@ static void get_xml_bookmark_data(xmlNodePtr cur, BOOKMARK_DATA *data) data->description = g_strdup((char *)description); data->module_desc = g_strdup((char *)mod_desc); data->is_leaf = TRUE; + data->color = NULL; /* leaves (bookmarks) never carry a color */ } /****************************************************************************** @@ -281,16 +311,6 @@ static void get_xml_bookmark_data(xmlNodePtr cur, BOOKMARK_DATA *data) static void free_bookmark_data(BOOKMARK_DATA *data) { - if (data->caption) - g_free(data->caption); - if (data->key) - g_free(data->key); - if (data->module) - g_free(data->module); - if (data->module_desc) - g_free(data->module_desc); - if (data->description) - g_free(data->description); } /****************************************************************************** @@ -322,7 +342,9 @@ void gui_add_item_to_tree(GtkTreeIter *iter, GtkTreeIter *parent, COL_KEY, data->key, COL_MODULE, data->module, COL_MODULE_DESC, data->module_desc, - COL_DESCRIPTION, data->description, -1); + COL_DESCRIPTION, data->description, + COL_COLOR, data->color, + -1); } /****************************************************************************** @@ -345,22 +367,18 @@ void gui_add_item_to_tree(GtkTreeIter *iter, GtkTreeIter *parent, static void add_node(xmlNodePtr cur, GtkTreeIter *parent) { GtkTreeIter iter; - BOOKMARK_DATA data, *p = NULL; + BOOKMARK_DATA *p = NULL; xmlNodePtr work = NULL; - p = &data; - if (cur == NULL) - return; - for (work = cur->xmlChildrenNode; work; work = work->next) { if (!xmlStrcmp(work->name, (const xmlChar *)"Bookmark")) { + p = g_new(BOOKMARK_DATA, 1); get_xml_bookmark_data(work, p); gui_add_item_to_tree(&iter, parent, p); - free_bookmark_data(p); } else if (!xmlStrcmp(work->name, (const xmlChar *)"Folder")) { + p = g_new(BOOKMARK_DATA, 1); get_xml_folder_data(work, p); gui_add_item_to_tree(&iter, parent, p); - free_bookmark_data(p); add_node(work, &iter); } } @@ -386,32 +404,28 @@ void gui_parse_bookmarks(GtkTreeView *tree, const xmlChar *file, GtkTreeIter *parent) { xmlNodePtr cur = NULL; - BOOKMARK_DATA data, *p = NULL; + BOOKMARK_DATA *p = NULL; GtkTreeIter iter; GtkTreePath *path; - p = &data; cur = xml_load_bookmark_file(file); - //cur = cur->xmlChildrenNode; + while (cur != NULL) { if (!xmlStrcmp(cur->name, (const xmlChar *)"Bookmark")) { + p = g_new(BOOKMARK_DATA, 1); get_xml_bookmark_data(cur, p); gui_add_item_to_tree(&iter, parent, p); - free_bookmark_data(p); } else { + p = g_new(BOOKMARK_DATA, 1); get_xml_folder_data(cur, p); if (p->caption) { gui_add_item_to_tree(&iter, parent, p); } - free_bookmark_data(p); add_node(cur, &iter); } - - if (cur->next) - cur = cur->next; - else - break; + cur = cur->next; } + xml_free_bookmark_doc(); path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), parent); gtk_tree_view_expand_to_path(tree, path); @@ -512,8 +526,7 @@ static void row_deleted(GtkTreeModel *treemodel, GtkTreePath *arg1, static void create_pixbufs(void) { - GtkTextDirection dir = - gtk_widget_get_direction(GTK_WIDGET(widgets.app)); + GtkTextDirection dir = gtk_widget_get_direction(GTK_WIDGET(widgets.app)); bm_pixbufs = g_new0(BookMarksPixbufs, 1); @@ -578,6 +591,94 @@ static void create_pixbufs(void) * void */ +/* Creates a round color swatch pixbuf using Cairo (14x14 px) */ +#ifdef USE_GTK_3 +static GdkPixbuf *make_color_dot(const gchar *hex_color) +{ + const gint SIZE = 14; + cairo_surface_t *surface; + cairo_t *cr; + GdkPixbuf *pixbuf; + GdkRGBA rgba; + + if (!hex_color || !*hex_color) + return NULL; + if (!gdk_rgba_parse(&rgba, hex_color)) + return NULL; + + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, SIZE, SIZE); + cr = cairo_create(surface); + + /* transparent background */ + cairo_set_source_rgba(cr, 0, 0, 0, 0); + cairo_paint(cr); + + /* filled circle */ + cairo_set_source_rgba(cr, rgba.red, rgba.green, rgba.blue, 1.0); + cairo_arc(cr, SIZE/2.0, SIZE/2.0, SIZE/2.0 - 1, 0, 2 * G_PI); + cairo_fill_preserve(cr); + + /* thin dark border */ + cairo_set_source_rgba(cr, 0, 0, 0, 0.35); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + + cairo_destroy(cr); + pixbuf = gdk_pixbuf_get_from_surface(surface, 0, 0, SIZE, SIZE); + cairo_surface_destroy(surface); + return pixbuf; +} + +/* Cell data function: generates the color dot pixbuf on the fly */ +static void color_dot_cell_func(GtkTreeViewColumn *col, + GtkCellRenderer *renderer, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *color = NULL; + GdkPixbuf *dot = NULL; + gtk_tree_model_get(tree_model, iter, COL_COLOR, &color, -1); + dot = make_color_dot(color); + g_object_set(renderer, "pixbuf", dot, NULL); + if (dot) g_object_unref(dot); + g_free(color); +} +#endif + +#ifndef USE_GTK_3 +static void color_dot_cell_func_gtk2(GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *color_str = NULL; + gtk_tree_model_get(tree_model, iter, COL_COLOR, &color_str, -1); + if (color_str && *color_str) { + GdkColor color; + if (gdk_color_parse(color_str, &color)) { + /* Crée un petit carré de couleur de 12x12 pixels */ + GdkPixbuf *pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, 12, 12); + if (pixbuf) { + guint32 r = color.red >> 8; + guint32 g = color.green >> 8; + guint32 b = color.blue >> 8; + guint32 pixel = (r << 24) | (g << 16) | (b << 8) | 0xFF; + gdk_pixbuf_fill(pixbuf, pixel); + g_object_set(cell, "pixbuf", pixbuf, NULL); + g_object_unref(pixbuf); + } + } else { + g_object_set(cell, "pixbuf", NULL, NULL); + } + } else { + g_object_set(cell, "pixbuf", NULL, NULL); + } + g_free(color_str); +} +#endif + void gui_add_columns(GtkTreeView *tree) { GtkTreeViewColumn *column; @@ -588,10 +689,27 @@ void gui_add_columns(GtkTreeView *tree) renderer = GTK_CELL_RENDERER(gtk_cell_renderer_pixbuf_new()); gtk_tree_view_column_pack_start(column, renderer, FALSE); gtk_tree_view_column_set_attributes(column, renderer, - "pixbuf", COL_OPEN_PIXBUF, - "pixbuf-expander-open", COL_OPEN_PIXBUF, - "pixbuf-expander-closed", COL_CLOSED_PIXBUF, NULL); + "pixbuf", COL_OPEN_PIXBUF, + "pixbuf-expander-open", COL_OPEN_PIXBUF, + "pixbuf-expander-closed", COL_CLOSED_PIXBUF, NULL); + +#ifdef USE_GTK_3 + /* Color dot renderer — round swatch drawn with Cairo */ + renderer = GTK_CELL_RENDERER(gtk_cell_renderer_pixbuf_new()); + gtk_tree_view_column_pack_start(column, renderer, FALSE); + gtk_tree_view_column_set_cell_data_func(column, renderer, + color_dot_cell_func, + NULL, NULL); +#else + /* GTK2 Windows — colored square drawn via pixbuf */ + renderer = GTK_CELL_RENDERER(gtk_cell_renderer_pixbuf_new()); + gtk_tree_view_column_pack_start(column, renderer, FALSE); + gtk_tree_view_column_set_cell_data_func(column, renderer, + color_dot_cell_func_gtk2, + NULL, NULL); +#endif + /* Caption renderer */ renderer = GTK_CELL_RENDERER(gtk_cell_renderer_text_new()); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_attributes(column, renderer, @@ -653,12 +771,14 @@ static GtkTreeModel *create_model(void) { /* create tree store */ model = gtk_tree_store_new(N_COLUMNS, - GDK_TYPE_PIXBUF, - GDK_TYPE_PIXBUF, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_STRING, G_TYPE_STRING); + GDK_TYPE_PIXBUF, /* COL_OPEN_PIXBUF */ + GDK_TYPE_PIXBUF, /* COL_CLOSED_PIXBUF */ + G_TYPE_STRING, /* COL_CAPTION */ + G_TYPE_STRING, /* COL_KEY */ + G_TYPE_STRING, /* COL_MODULE */ + G_TYPE_STRING, /* COL_MODULE_DESC */ + G_TYPE_STRING, /* COL_DESCRIPTION */ + G_TYPE_STRING); /* COL_COLOR */ return GTK_TREE_MODEL(model); } @@ -690,6 +810,7 @@ static gboolean button_release_event(GtkWidget *widget, gchar *key = NULL; gchar *module = NULL; gchar *mod_desc = NULL; + gchar *range_start = NULL; gchar *description = NULL; button_one = FALSE; @@ -831,6 +952,87 @@ static gboolean button_release_event(GtkWidget *widget, * GtkWidget* */ +/** + * bookmark_get_tag_color_for_key: + * @osiskey: a verse key string, e.g. "Gen 1:1" + * + * Walks the bookmark GtkTreeStore and returns the color of the + * first tag-group folder that contains a bookmark matching @osiskey. + * Returns NULL if none found. Caller must g_free() the result. + */ +static void debug_dump_recursive(GtkTreeIter *parent, int depth) +{ + GtkTreeIter child; + if (!gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &child, parent)) + return; + do { + gchar *caption = NULL, *color = NULL; + gtk_tree_model_get(GTK_TREE_MODEL(model), &child, + COL_CAPTION, &caption, COL_COLOR, &color, -1); + debug_dump_recursive(&child, depth + 1); + g_free(caption); g_free(color); + } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &child)); +} + +void bookmark_debug_dump_colors(void) +{ + GtkTreeIter root; + if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &root)) { + } + debug_dump_recursive(&root, 0); +} + + +gchar *bookmark_get_tag_color_for_key(const gchar *osiskey) +{ + GtkTreeIter folder, child; + gchar *color = NULL; + + if (!osiskey || !model) + return NULL; + + /* iterate top-level folders */ + if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &folder)) + return NULL; + + do { + gchar *folder_color = NULL; + gtk_tree_model_get(GTK_TREE_MODEL(model), &folder, + COL_COLOR, &folder_color, -1); + if (!folder_color || !*folder_color) { + g_free(folder_color); + continue; + } + /* scan children of this colored folder */ + if (gtk_tree_model_iter_children(GTK_TREE_MODEL(model), + &child, &folder)) { + do { + gchar *key = NULL; + gtk_tree_model_get(GTK_TREE_MODEL(model), + &child, + COL_KEY, &key, -1); + /* compare case-insensitively; + * also try stripping trailing spaces */ + gchar *k = key ? g_strstrip(g_strdup(key)) : NULL; + gchar *q = osiskey ? g_strstrip(g_strdup(osiskey)) : NULL; + gboolean match = (k && q && !g_ascii_strcasecmp(k, q)); + g_free(k); g_free(q); + if (match) { + color = g_strdup(folder_color); + g_free(key); + g_free(folder_color); + return color; + } + g_free(key); + } while (gtk_tree_model_iter_next( + GTK_TREE_MODEL(model), &child)); + } + g_free(folder_color); + } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &folder)); + + return NULL; +} + GtkWidget *gui_create_bookmark_tree(void) { GtkWidget *tree; @@ -858,9 +1060,10 @@ GtkWidget *gui_create_bookmark_tree(void) GINT_TO_POINTER(0)); use_dialog = FALSE; bookmark_tree = GTK_TREE_VIEW(tree); + gtk_tree_view_set_reorderable(bookmark_tree, TRUE); g_signal_connect(model, "row-changed", - G_CALLBACK(row_changed), NULL); + G_CALLBACK(row_changed), NULL); g_signal_connect(model, "row-deleted", G_CALLBACK(row_deleted), NULL); diff --git a/src/gtk/utilities.c b/src/gtk/utilities.c index e72c9f64c..d26ac3aa9 100644 --- a/src/gtk/utilities.c +++ b/src/gtk/utilities.c @@ -55,6 +55,7 @@ #include "main/sword.h" #include "main/url.hh" #include "main/xml.h" +#include "gui/bookmarks_treeview.h" #ifdef WIN32 #undef DATADIR @@ -153,32 +154,34 @@ void utilities_parse_treeview(xmlNodePtr parent, GtkTreeIter *tree_parent, gchar *module = NULL; gchar *mod_desc = NULL; gchar *description = NULL; - + gchar *color = NULL; gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &child, tree_parent); - do { gtk_tree_model_get(GTK_TREE_MODEL(model), &child, - 2, &caption, - 3, &key, - 4, &module, - 5, &mod_desc, 6, &description, -1); + COL_CAPTION, &caption, + COL_KEY, &key, + COL_MODULE, &module, + COL_MODULE_DESC, &mod_desc, + COL_DESCRIPTION, &description, + COL_COLOR, &color, + -1); if (gtk_tree_model_iter_has_child(GTK_TREE_MODEL(model), &child)) { - cur_node = xml_add_folder_to_parent(parent, - caption); + cur_node = xml_add_folder_to_parent_colored(parent, + caption, + color); utilities_parse_treeview(cur_node, &child, model); - } else xml_add_bookmark_to_parent(parent, description, key, module, mod_desc); - g_free(caption); g_free(key); g_free(module); g_free(mod_desc); g_free(description); + g_free(color); } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &child)); } diff --git a/src/gui/bookmarks_menu.h b/src/gui/bookmarks_menu.h index 0a62b7969..41d1109ce 100644 --- a/src/gui/bookmarks_menu.h +++ b/src/gui/bookmarks_menu.h @@ -41,6 +41,7 @@ struct _bookmark_menu GtkWidget *bibletime; GtkWidget *rr_submenu; GtkWidget *remove; + GtkWidget *set_color; }; typedef struct _bookmark_menu BOOKMARK_MENU; extern BOOKMARK_MENU menu; @@ -75,6 +76,9 @@ void on_insert_bookmark_activate(GtkMenuItem *menuitem, gpointer user_data); void on_new_folder_activate(GtkMenuItem *menuitem, gpointer user_data); +#if GTK_CHECK_VERSION(3, 4, 0) +void on_set_tag_color_activate(GtkMenuItem *menuitem, gpointer user_data); +#endif void on_open_in_tab_activate(GtkMenuItem *menuitem, gpointer user_data); diff --git a/src/gui/bookmarks_treeview.h b/src/gui/bookmarks_treeview.h index f7be52e60..7464737b7 100644 --- a/src/gui/bookmarks_treeview.h +++ b/src/gui/bookmarks_treeview.h @@ -27,6 +27,7 @@ extern "C" { #endif #include #include + enum { COL_OPEN_PIXBUF, COL_CLOSED_PIXBUF, @@ -35,6 +36,8 @@ enum { COL_MODULE, COL_MODULE_DESC, COL_DESCRIPTION, + COL_COLOR, /* hex color string for tag groups, e.g. "#378ADD" + * NULL or "" means no color assigned (plain bookmark folder) */ N_COLUMNS }; @@ -53,6 +56,7 @@ struct _bookmark_data gchar *module; gchar *module_desc; gchar *description; + gchar *color; /* hex color string for folders/tag groups, NULL for leaves */ gboolean is_leaf; GdkPixbuf *opened; GdkPixbuf *closed; @@ -69,6 +73,8 @@ void gui_add_item_to_tree(GtkTreeIter *iter, GtkTreeIter *parent, void gui_verselist_to_bookmarks(GList *verses, gint save_as_single); GtkWidget *gui_create_bookmark_tree(void); +void bookmark_debug_dump_colors(void); +gchar *bookmark_get_tag_color_for_key(const gchar *osiskey); void gui_parse_bookmarks(GtkTreeView *tree, const xmlChar *file, GtkTreeIter *parent); GtkWidget *gui_create_dialog_add_bookmark(gchar *label, diff --git a/src/main/main.c b/src/main/main.c index ef18f02b1..155ac597c 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -251,6 +251,28 @@ int main(int argc, char *argv[]) */ settings_init(argc, argv, newconfigs, newbookmarks); + /* Phase 2: backup bookmarks.xml once on first run after upgrade. + * The backup is skipped if it already exists. */ + { + gchar *bm = g_strdup_printf("%s/bookmarks/bookmarks.xml", + settings.gSwordDir); + gchar *bmbak = g_strdup_printf("%s/bookmarks/bookmarks.xml.bak", + settings.gSwordDir); + if (g_file_test(bm, G_FILE_TEST_EXISTS) && + !g_file_test(bmbak, G_FILE_TEST_EXISTS)) { + GError *err = NULL; + GFile *src_f = g_file_new_for_path(bm); + GFile *dst_f = g_file_new_for_path(bmbak); + g_file_copy(src_f, dst_f, + G_FILE_COPY_NONE, NULL, NULL, NULL, &err); + if (err) g_error_free(err); + g_object_unref(src_f); + g_object_unref(dst_f); + } + g_free(bm); + g_free(bmbak); + } + gui_init(argc, argv); gui_splash_init(); diff --git a/src/main/settings.c b/src/main/settings.c index 1c9b9c3ca..e5f099b6a 100644 --- a/src/main/settings.c +++ b/src/main/settings.c @@ -182,6 +182,7 @@ int settings_init(int argc, char **argv, int new_configs, g_free(sword_dir); /* moved here from crud locations in backend. */ + main_init_language_map(); language_init(); diff --git a/src/main/xml.c b/src/main/xml.c index 4bd694779..1587d0d1b 100644 --- a/src/main/xml.c +++ b/src/main/xml.c @@ -24,6 +24,7 @@ #endif #include +#include #include #include #include @@ -141,6 +142,62 @@ xmlNodePtr xml_add_folder_to_parent(xmlNodePtr parent, gchar *caption) return cur_node; } +/****************************************************************************** + * Name + * xml_add_folder_to_parent_colored + * + * Synopsis + * #include "main/xml.h" + * + * xmlNodePtr xml_add_folder_to_parent_colored(xmlNodePtr parent, + * gchar *caption, + * const gchar *color) + * + * Description + * Like xml_add_folder_to_parent() but also writes the optional "color" + * attribute (e.g. "#378ADD") used by tag groups. Pass NULL or "" to + * omit the attribute (behaves identically to the plain variant). + * + * Return value + * xmlNodePtr - the new Folder node + */ + +xmlNodePtr xml_add_folder_to_parent_colored(xmlNodePtr parent, + gchar *caption, + const gchar *color) +{ + xmlNodePtr cur_node = xml_add_folder_to_parent(parent, caption); + + if (color && *color) + (void)xmlNewProp(cur_node, + (const xmlChar *)"color", + (const xmlChar *)color); + return cur_node; +} + +/****************************************************************************** + * Name + * xml_get_folder_color + * + * Synopsis + * #include "main/xml.h" + * + * gchar *xml_get_folder_color(xmlNodePtr node) + * + * Description + * Returns the "color" attribute of a Folder node, or NULL when absent. + * The caller must free the returned string with xmlFree() (it is an + * xmlChar * cast to gchar *). + * + * Return value + * gchar * (xmlChar * underneath) — caller must xmlFree(), or NULL + */ + +gchar *xml_get_folder_color(xmlNodePtr node) +{ + return (gchar *)xmlGetProp(node, (const xmlChar *)"color"); +} + /****************************************************************************** * Name * xml_add_bookmark_to_parent diff --git a/src/main/xml.h b/src/main/xml.h index eb3d84ff4..143cb6778 100644 --- a/src/main/xml.h +++ b/src/main/xml.h @@ -32,6 +32,10 @@ void xml_add_new_section_to_settings_doc(char *section); void xml_new_bookmark_file(void); xmlNodePtr xml_add_folder_to_parent(xmlNodePtr parent, gchar *caption); +xmlNodePtr xml_add_folder_to_parent_colored(xmlNodePtr parent, + gchar *caption, + const gchar *color); +gchar *xml_get_folder_color(xmlNodePtr node); void xml_add_bookmark_to_parent(xmlNodePtr parent, gchar *caption, gchar *key, gchar *module, const gchar *mod_desc); diff --git a/ui/CMakeLists.txt b/ui/CMakeLists.txt index 0079ef804..045a106a4 100644 --- a/ui/CMakeLists.txt +++ b/ui/CMakeLists.txt @@ -21,6 +21,7 @@ if (NOT GTK2) # Gtk3 only stuff install (FILES bookmarks.gtkbuilder + folder.gtkbuilder editor_link_dialog.gtkbuilder export-dialog.gtkbuilder markverse.gtkbuilder @@ -40,6 +41,7 @@ else (NOT GTK2) # Gtk2 only stuff install (FILES bookmarks.glade + folder.glade editor_link_dialog.glade export-dialog.glade markverse.glade diff --git a/ui/folder.glade b/ui/folder.glade new file mode 100644 index 000000000..ae93b6b81 --- /dev/null +++ b/ui/folder.glade @@ -0,0 +1,120 @@ + + + + False + Folder + 320 + 150 + GDK_WINDOW_TYPE_HINT_DIALOG + + + True + 8 + 10 + + + + + True + GTK_BUTTONBOX_END + + + True + True + gtk-cancel + True + + + + + True + True + gtk-ok + True + + + + + False + GTK_PACK_END + + + + + + + True + 8 + + + True + Name: + 0 + + + False + False + + + + + True + True + + + True + True + + + + + + + + + True + 8 + + + True + Tag color: + 0 + + + False + False + + + + + True + True + False + + + False + False + + + + + True + True + No color + + + False + False + + + + + False + True + + + + + + + diff --git a/ui/folder.gtkbuilder b/ui/folder.gtkbuilder new file mode 100644 index 000000000..15ab50b7e --- /dev/null +++ b/ui/folder.gtkbuilder @@ -0,0 +1,152 @@ + + + + + False + False + Folder + 320 + 150 + dialog + + + True + False + vertical + 8 + 10 + + + True + False + end + + + gtk-cancel + True + True + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + + + False + False + 1 + + + + + False + True + end + 0 + + + + + True + False + 8 + + + True + False + Name: + 0 + + + False + False + 0 + + + + + True + True + True + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + 8 + + + True + False + Tag color: + 0 + + + False + False + 0 + + + + + True + True + Choose a highlight color for this tag group + False + + + False + False + 1 + + + + + No color + True + True + Remove tag color from this folder + + + False + False + 2 + + + + + False + True + 2 + + + + + + folder_cancel + folder_ok + + +