Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions src/backend/sword_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -925,11 +925,18 @@ GList *BackEnd::parse_verse_list(const char *module_name, const char *list, char
key->setText(current_key);
vs = key->parseVerseList(list, *key, TRUE);

if (!vs.getCount())
int count = vs.getCount();
if (!count)
return retlist;
while (!vs.popError()) {
retlist = g_list_append(retlist, strdup((char *)vs.getText()));
vs++;
/* Use indexed access rather than popError()-driven iteration:
* popError() stops the whole walk as soon as ANY single element
* fails to resolve, silently dropping every element after it,
* even when they are perfectly valid. Indexed access lets us
* simply skip a bad element and keep going. */
for (int i = 0; i < count; i++) {
SWKey *elem = vs.getElement(i);
if (elem)
retlist = g_list_append(retlist, strdup((char *)elem->getText()));
}
return retlist;
}
Expand Down
29 changes: 29 additions & 0 deletions src/gtk/bookmarks_menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,31 @@ G_MODULE_EXPORT void on_crossref_popup_activate(GtkMenuItem *menuitem,
xml_set_value("Xiphos", "misc", "crossref_popup",
settings.crossref_popup ? "1" : "0");
}
/******************************************************************************
* Name
* on_tag_colorize_activate
*
* Synopsis
* #include "gui/bookmarks_menu.h"
*
* void on_tag_colorize_activate(GtkMenuItem *menuitem,
* gpointer user_data)
*
* Description
* toggle verse colorization by bookmark folder color
*
* Return value
* void
*/
G_MODULE_EXPORT void on_tag_colorize_activate(GtkMenuItem *menuitem,
gpointer user_data)
{
settings.tag_colorize =
gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem));
xml_set_value("Xiphos", "misc", "tag_colorize",
settings.tag_colorize ? "1" : "0");
main_display_bible(NULL, settings.currentverse);
}

/******************************************************************************
* Name
Expand Down Expand Up @@ -1263,6 +1288,10 @@ void gui_create_bookmark_menu(void)
gtk_check_menu_item_set_active(
GTK_CHECK_MENU_ITEM(menu.crossref_popup),
settings.crossref_popup);
menu.tag_colorize = UI_GET_ITEM(gxml, "tag_colorize");
gtk_check_menu_item_set_active(
GTK_CHECK_MENU_ITEM(menu.tag_colorize),
settings.tag_colorize);

gtk_widget_set_sensitive(menu.in_tab, FALSE);
gtk_widget_set_sensitive(menu.in_dialog, FALSE);
Expand Down
73 changes: 73 additions & 0 deletions src/gtk/bookmarks_treeview.c
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,54 @@ void bookmark_debug_dump_colors(void)
}


/* Recursively search @folder (and its sub-folders) for a bookmark whose
* key resolves to @versekey_text. On match, returns "<immediate parent
* folder caption>: <bookmark caption>". Helper for
* bookmark_get_tag_info_for_key(). */
static gchar *bookmark_find_in_folder(GtkTreeIter *folder, const gchar *versekey_text)
{
gchar *folder_caption = NULL;
gtk_tree_model_get(GTK_TREE_MODEL(model), folder,
COL_CAPTION, &folder_caption, -1);
GtkTreeIter child;
gchar *result = NULL;
if (gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &child, folder)) {
do {
gchar *node_key = NULL, *node_caption = NULL;
gtk_tree_model_get(GTK_TREE_MODEL(model), &child,
COL_KEY, &node_key,
COL_CAPTION, &node_caption, -1);
if (node_key) {
/* leaf: a bookmark. Resolve its (possibly
* multi-reference) key via Sword and compare
* each resolved verse to versekey_text. */
GList *verses = main_parse_verse_list(
settings.MainWindowModule, node_key,
(char *)settings.currentverse);
for (GList *l = verses; l && !result; l = l->next) {
gchar *v = g_strstrip(g_strdup((const char *)l->data));
gchar *q = g_strstrip(g_strdup(versekey_text));
if (!g_ascii_strcasecmp(v, q))
result = g_strdup_printf("%s: %s",
folder_caption ? folder_caption : "",
node_caption ? node_caption : node_key);
g_free(v);
g_free(q);
}
for (GList *l = verses; l; l = l->next)
g_free(l->data);
g_list_free(verses);
} else {
/* sub-folder: recurse */
result = bookmark_find_in_folder(&child, versekey_text);
}
g_free(node_key);
g_free(node_caption);
} while (!result && gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &child));
}
g_free(folder_caption);
return result;
}
gchar *bookmark_get_tag_color_for_key(const gchar *osiskey)
{
GtkTreeIter folder, child;
Expand Down Expand Up @@ -1102,6 +1150,31 @@ gchar *bookmark_get_tag_color_for_key(const gchar *osiskey)

return NULL;
}
/**
* bookmark_get_tag_info_for_key:
* @versekey_text: a Sword-formatted verse key string, e.g. "Rom 16:23"
*
* Finds the first bookmark, anywhere in the tree, whose (possibly
* multi-reference) key resolves to @versekey_text, and returns
* "<containing folder>: <bookmark name>" -- just the single
* immediate parent folder, not the full nested path.
* Returns NULL if no bookmark matches. Caller must g_free() the result.
*/
gchar *bookmark_get_tag_info_for_key(const gchar *versekey_text)
{
if (!versekey_text || !model)
return NULL;
GtkTreeIter folder;
if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &folder))
return NULL;
gchar *result = NULL;
do {
result = bookmark_find_in_folder(&folder, versekey_text);
if (result)
return result;
} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &folder));
return NULL;
}

GtkWidget *gui_create_bookmark_tree(void)
{
Expand Down
3 changes: 3 additions & 0 deletions src/gui/bookmarks_menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ struct _bookmark_menu
GtkWidget *remove;
GtkWidget *set_color;
GtkWidget *crossref_popup;
GtkWidget *tag_colorize;
};
typedef struct _bookmark_menu BOOKMARK_MENU;
extern BOOKMARK_MENU menu;
Expand All @@ -64,6 +65,8 @@ void on_allow_reordering_activate(GtkMenuItem *menuitem,
gpointer user_data);
void on_crossref_popup_activate(GtkMenuItem *menuitem,
gpointer user_data);
void on_tag_colorize_activate(GtkMenuItem *menuitem,
gpointer user_data);
void on_dialog_activate(GtkMenuItem *menuitem,
gpointer user_data);
void on_edit_item_activate(GtkMenuItem *menuitem,
Expand Down
1 change: 1 addition & 0 deletions src/gui/bookmarks_treeview.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ void gui_verselist_to_bookmarks(GList *verses,
GtkWidget *gui_create_bookmark_tree(void);
void bookmark_debug_dump_colors(void);
gchar *bookmark_get_tag_color_for_key(const gchar *osiskey);
gchar *bookmark_get_tag_info_for_key(const gchar *versekey_text);
void gui_parse_bookmarks(GtkTreeView *tree, const xmlChar *file,
GtkTreeIter *parent);
GtkWidget *gui_create_dialog_add_bookmark(gchar *label,
Expand Down
137 changes: 136 additions & 1 deletion src/main/display.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
#include "main/xml.h"

#include "gui/utilities.h"
#include "gui/bookmarks_treeview.h"
#include "gui/widgets.h"
#include "gui/dialog.h"

Expand Down Expand Up @@ -96,6 +97,7 @@ body { background-color:%s; color:%s; -webkit-column-count: %d ; margin-top: 0.1
td { %s } \
.introMaterial { font-style: italic; } \
a:link{ color:%s } %s %s \
.tagcolor a:link{ color:inherit !important } \
h3 { font-style: %s } --> \
%s</style> %s </head><body>"
// 11 interpolable values: bg/txt/link colors, columns, justify (2x, body+td),
Expand Down Expand Up @@ -1335,6 +1337,115 @@ GTKChapDisp::getVerseAfter(SWModule &imodule)
buf = NULL;
}

/* Returns white or black depending on background luminance */
static const char *text_color_for_bg(const gchar *hex_color)
{
if (!hex_color || hex_color[0] != '#' || strlen(hex_color) < 7)
return "#000000";
int r = 0, g = 0, b = 0;
sscanf(hex_color + 1, "%02x%02x%02x", &r, &g, &b);
/* relative luminance (ITU-R BT.709) */
double lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
return (lum < 128.0) ? "#FFFFFF" : "#000000";
}

/* Returns the tag color for a given VerseKey by comparing OSIS refs.
* Walks the GtkTreeStore directly from C++. */
static gchar *get_tag_color_for_versekey(VerseKey *vk)
{
extern GtkTreeStore *model;
if (!settings.tag_colorize) return NULL;
if (!model || !vk) return NULL;

gchar *osisref = g_strdup_printf("%s.%d.%d",
vk->getOSISBookName(),
vk->getChapter(),
vk->getVerse());

GtkTreeIter root;
if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(model), &root)) {
g_free(osisref); return NULL;
}

GQueue *stack = g_queue_new();
GQueue *colors = g_queue_new();
GtkTreeIter child;
if (gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &child, &root)) {
do {
GtkTreeIter *copy = g_new(GtkTreeIter, 1);
*copy = child;
g_queue_push_tail(stack, copy);
g_queue_push_tail(colors, NULL);
} while (gtk_tree_model_iter_next(GTK_TREE_MODEL(model), &child));
}

gchar *result = NULL;
while (!g_queue_is_empty(stack) && !result) {
GtkTreeIter *iter = (GtkTreeIter *)g_queue_pop_head(stack);
gchar *inherited = (gchar *)g_queue_pop_head(colors);
gchar *node_color = NULL, *node_key = NULL;
gtk_tree_model_get(GTK_TREE_MODEL(model), iter,
COL_COLOR, &node_color,
COL_KEY, &node_key, -1);
const gchar *effective = (node_color && *node_color)
? node_color : inherited;
if (node_key) {
/* Let Sword resolve the (possibly multi-reference, possibly
* partial) bookmark key into individual, fully-qualified
* verses -- same mechanism used for the verse-list popup. */
if (effective) {
/* parse_verse_list() repositions the SWModule\'s own
* key, which is the SAME key object being iterated
* by the enclosing display loop. Save and restore
* it around the call so we don\'t corrupt that
* outer iteration (was causing freezes/GTK asserts
* with multi-reference bookmarks). */
gchar *saved_pos = g_strdup((const char *)vk->getText());
GList *verses = backend->parse_verse_list(
settings.MainWindowModule, node_key,
(char *)settings.currentverse);
vk->setText(saved_pos);
g_free(saved_pos);
for (GList *l = verses; l && !result; l = l->next) {
VerseKey vk2;
vk2.setLocale(vk->getLocale());
vk2.setText((const char *)l->data);
gchar *ref2 = g_strdup_printf("%s.%d.%d",
vk2.getOSISBookName(),
vk2.getChapter(),
vk2.getVerse());
if (!g_ascii_strcasecmp(osisref, ref2))
result = g_strdup(effective);
g_free(ref2);
}
for (GList *l = verses; l; l = l->next)
g_free(l->data);
g_list_free(verses);
}
g_free(node_key);
} else {
if (gtk_tree_model_iter_children(GTK_TREE_MODEL(model),
&child, iter)) {
do {
GtkTreeIter *copy = g_new(GtkTreeIter, 1);
*copy = child;
g_queue_push_tail(stack, copy);
g_queue_push_tail(colors,
effective ? g_strdup(effective) : NULL);
} while (gtk_tree_model_iter_next(
GTK_TREE_MODEL(model), &child));
}
}
g_free(node_color);
g_free(inherited);
g_free(iter);
}
g_queue_free_full(stack, g_free);
g_queue_free_full(colors, g_free);
g_free(osisref);
return result;
}

// part of the deep ugliness below, for extracting bad paragraph endings.
// this is empirically observed in some marginally broken module content.
struct paragraph_endings
Expand Down Expand Up @@ -1406,6 +1517,18 @@ GTKChapDisp::RenderOneChapter(SWModule &imodule,
if (*rework->str == '\0')
continue; // no verse content there.

// tag-group (bookmark folder) color highlight -- wraps the
// verse number, user-note reference, and verse text below.
gchar *tag_color = get_tag_color_for_versekey(key);
if (tag_color) {
const char *tc_fg = text_color_for_bg(tag_color);
swbuf.appendFormatted(
"<span class=\"tagcolor\" style=\"background-color: %s; "
"color: %s; "
"border-left: 3px solid %s; padding-left: 2px\">",
tag_color, tc_fg, tag_color);
}

// generate the verse number with color and decoration.
gchar *num = main_format_number(key->getVerse());
swbuf.appendFormatted((settings.showversenum
Expand Down Expand Up @@ -1440,11 +1563,16 @@ GTKChapDisp::RenderOneChapter(SWModule &imodule,
color_chosen_bg = NULL;

// but then decide on the special cases for this verse.
// (current-verse highlighting is suppressed when a bookmark
// tag color is active, so it can't override the contrast
// color already applied to the tag background; annotation
// highlighting still takes precedence and coexists with it,
// per review feedback.)
if (settings.annotate_highlight && e) {
color_choices = COLOR_BOTH;
color_chosen_fg = settings.highlight_bg;
color_chosen_bg = settings.highlight_fg;
} else if ((curChapter == thisChapter) && (curVerse == k)) {
} else if ((curChapter == thisChapter) && (curVerse == k) && !tag_color) {
if (settings.versehighlight) {
color_choices = COLOR_BOTH;
color_chosen_fg = settings.highlight_fg;
Expand Down Expand Up @@ -1503,6 +1631,13 @@ GTKChapDisp::RenderOneChapter(SWModule &imodule,
if (color_choices == COLOR_BOTH)
swbuf.append("</span>");

// close tag-group color span
if (tag_color) {
swbuf.append("</span>");
g_free(tag_color);
tag_color = NULL;
}

if (paragraph_end_pending)
swbuf.append(para_type);

Expand Down
Loading