1 /*
2  * Xiphos Bible Study Tool
3  * previewer.cc -
4  *
5  * Copyright (C) 2000-2020 Xiphos Developer Team
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <gtk/gtk.h>
27 #include <swmgr.h>
28 #include <swmodule.h>
29 #include <stringmgr.h>
30 #include <localemgr.h>
31 
32 extern "C" {
33 #include "gui/bibletext.h"
34 }
35 
36 #include <ctype.h>
37 #include <time.h>
38 #include "gui/widgets.h"
39 #include "gui/sidebar.h"
40 #include "gui/utilities.h"
41 #include "main/previewer.h"
42 #include "main/settings.h"
43 #include "main/sword.h"
44 #include "main/xml.h"
45 #include "backend/sword_main.hh"
46 
47 #include "xiphos_html/xiphos_html.h"
48 
49 #include "gui/debug_glib_null.h"
50 
51 #define HTML_START "<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\
52 <style type=\"text/css\"><!-- \
53 A { text-decoration:none } \
54 *[dir=rtl] { text-align: right;} \
55 body {background-color:%s;color:%s;} \
56 a:link{color:%s} -->\
57 </style></head><body>"
58 
59 using namespace std;
60 
61 static GtkWidget *previewer_html_widget;
62 
main_set_previewer_widget(int in_sidebar)63 void main_set_previewer_widget(int in_sidebar)
64 {
65 	if (in_sidebar)
66 		previewer_html_widget = sidebar.html_viewer_widget;
67 	else
68 		previewer_html_widget = widgets.html_previewer_text;
69 }
70 
71 /******************************************************************************
72  * Name
73  *   main_init_previewer
74  *
75  * Synopsis
76  *   #include "main/previewer.h"
77  *
78  *   void main_init_previewer(void)
79  *
80  * Description
81  *   clear the information viewer
82  *
83  * Return value
84  *   void
85  */
86 
main_init_previewer(void)87 void main_init_previewer(void)
88 {
89 	GString *str = g_string_new(NULL);
90 	gchar *buf = _("Previewer");
91 	MOD_FONT *mf = get_font(settings.MainWindowModule);
92 
93 	g_string_printf(str,
94 			HTML_START
95 			"<font color=\"grey\" face=\"%s\" size=\"%+d\"><b>%s</b></font><hr/></body></html>",
96 			settings.bible_bg_color, settings.bible_text_color,
97 			settings.link_color,
98 			((mf->old_font) ? mf->old_font : ""),
99 			mf->old_font_size_value - 1,
100 			buf);
101 	free_font(mf);
102 	HtmlOutput(str->str, previewer_html_widget, NULL, NULL);
103 	g_string_free(str, TRUE);
104 }
105 
106 /******************************************************************************
107  * Name
108  *   main_information_viewer
109  *
110  * Synopsis
111  *   #include "main/previewer.h"
112  *
113  *   void main_information_viewer(GtkWidget * html_widget, gchar * mod_name,
114  *		    gchar * text, gchar *key, gchar * type)
115  *
116  * Description
117  *   display information in the information previewer
118  *
119  * Return value
120  *   void
121  */
122 
main_information_viewer(const gchar * mod_name,const gchar * text,const gchar * key,const gchar * action,const gchar * type,const gchar * morph_text,const gchar * morph)123 void main_information_viewer(const gchar *mod_name,
124 			     const gchar *text,
125 			     const gchar *key,
126 			     const gchar *action,
127 			     const gchar *type,
128 			     const gchar *morph_text,
129 			     const gchar *morph)
130 {
131 	GString *tmp_str = g_string_new(NULL);
132 	GString *str;
133 	MOD_FONT *mf = get_font((gchar *)mod_name);
134 
135 	g_string_printf(tmp_str,
136 			HTML_START
137 			"<font face=\"%s\" size=\"%+d\">",
138 			settings.bible_bg_color, settings.bible_text_color,
139 			settings.link_color,
140 			(mf->old_font ? mf->old_font : "none"),
141 			mf->old_font_size_value - 1);
142 
143 	str = g_string_new(tmp_str->str);
144 
145 	if (type) {
146 		if (*type == 'n') {
147 			g_string_printf(tmp_str,
148 					"<font color=\"grey\">%s</font><hr/>",
149 					_("Footnote"));
150 			str = g_string_append(str, tmp_str->str);
151 		} else if (*type == 'u') {
152 			g_string_printf(tmp_str,
153 					"<font color=\"grey\">%s:<br/>%s</font><hr/>",
154 					_("User Annotation"), key);
155 			str = g_string_append(str, tmp_str->str);
156 		} else if (*type == 'x') {
157 			g_string_printf(tmp_str,
158 					"<font color=\"grey\">%s</font><hr/>",
159 					_("Cross Reference"));
160 			str = g_string_append(str, tmp_str->str);
161 		} else if (!strcmp(action, "showStrongs")) {
162 			g_string_printf(tmp_str,
163 					"<font color=\"grey\">%s: %s</font><hr/>",
164 					_("Strongs"), key);
165 			str = g_string_append(str, tmp_str->str);
166 		} else if (!strcmp(action, "showMorph")) {
167 			g_string_printf(tmp_str,
168 					"<font color=\"grey\">%s: %s</font><hr/>",
169 					_("Morphology"), key);
170 			str = g_string_append(str, tmp_str->str);
171 		}
172 	} else {
173 		const char *abbreviation = main_get_abbreviation(mod_name);
174 		g_string_printf(tmp_str,
175 				"<font color=\"grey\">%s: %s</font><hr/>",
176 				(abbreviation ? abbreviation : mod_name),
177 				key);
178 		str = g_string_append(str, tmp_str->str);
179 	}
180 
181 	if (action && (!strcmp(action, "showStrongsMorph"))) {
182 		g_string_printf(tmp_str,
183 				"<font color=\"grey\">%s: %s</font><hr/>",
184 				_("Strongs"), key);
185 		str = g_string_append(str, tmp_str->str);
186 		str = g_string_append(str, text);
187 
188 		g_string_printf(tmp_str,
189 				"<font color=\"grey\"><br/><br/>%s: %s</font><hr/>",
190 				_("Morphology"), morph);
191 		str = g_string_append(str, tmp_str->str);
192 		str = g_string_append(str, morph_text);
193 	} else {
194 		str = g_string_append(str, text);
195 	}
196 
197 	str = g_string_append(str, "</font></body></html>");
198 
199 	HtmlOutput((char *)AnalyzeForImageSize(str->str,
200 					       GDK_WINDOW(gtk_widget_get_window(previewer_html_widget))),
201 		   previewer_html_widget, mf, NULL);
202 	free_font(mf);
203 	g_string_free(str, TRUE);
204 	g_string_free(tmp_str, TRUE);
205 }
206 
207 /******************************************************************************
208  * Name
209  *   mark_search_words
210  *
211  * Synopsis
212  *   #include "main/previewer.h"
213  *
214  *   void mark_search_words(GString *str)
215  *
216  * Description
217  *    highlights ("purplifies," formerly) search terms in results.
218  *
219  * Return value
220  *   void
221  */
222 
mark_search_words(GString * str)223 void mark_search_words(GString *str)
224 {
225 	gchar *tmpbuf, *buf, *searchbuf;
226 	gint len_overall, len_word, len_tail, len_prefix;
227 	gchar closestr[40], openstr[80];
228 
229 	/* regular expression search results         **fixme** */
230 	if ((settings.searchType == 0) ||
231 	    (settings.searchText[0] == '\0')) {
232 		return;
233 	}
234 	XI_message(("%s", settings.searchText));
235 
236 	/* open and close tags */
237 	sprintf(openstr,
238 		"<span style=\"background-color: %s; color: %s\">",
239 		settings.highlight_fg, settings.highlight_bg);
240 	sprintf(closestr, "</span>");
241 
242 	searchbuf = g_utf8_casefold(g_strdup(settings.searchText), -1);
243 	if (g_str_has_prefix(searchbuf, "\"")) {
244 		searchbuf = g_strdelimit(searchbuf, "\"", ' ');
245 		g_strstrip(searchbuf);
246 	}
247 
248 	buf = g_utf8_casefold(str->str, -1);
249 
250 	/* if we have a muti word search go here */
251 	if (settings.searchType == -2 || settings.searchType == -4) {
252 		char *token;
253 		GList *list, *listbase;
254 		gint count = 0, i = 0;
255 
256 		list = NULL;
257 		/* separate the search words and add them to a glist */
258 		if ((token = strtok(searchbuf, " ")) != NULL) {
259 			if (!isalnum(*token) && isalnum(*(token + 1)))
260 				token++; // skip leading punctuation.
261 			if (!strncmp(token, "lemma:", 6))
262 				token += 7; // skip the H/G qualifier.
263 			list = g_list_append(list, token);
264 			++count;
265 			while ((token = strtok(NULL, " ")) != NULL) {
266 				if (!isalnum(*token) && isalnum(*(token + 1)))
267 					token++;
268 				if (!strncmp(token, "lemma:", 6))
269 					token += 7; // skip the H/G qualifier.
270 				list = g_list_append(list, token);
271 				++count;
272 			}
273 			/* if we have only one word */
274 		} else {
275 			list = g_list_append(list, searchbuf);
276 			count = 1;
277 		}
278 
279 		listbase = list = g_list_first(list);
280 
281 		for (i = 0; i < count; i++) {
282 			len_overall = strlen(buf);
283 			if (settings.searchType == -4) {
284 				// remove metachars and anything following ("WORD*")
285 				for (tmpbuf = (gchar *)list->data;
286 				     *tmpbuf && isalnum(*tmpbuf);
287 				     ++tmpbuf)
288 					; // nothing, just skipping to end or non-alnum
289 				if ((tmpbuf != list->data) && *tmpbuf)
290 					*tmpbuf = '\0';
291 				else if (!sword::stricmp((gchar *)list->data, "and") ||
292 					 !sword::stricmp((gchar *)list->data, "or") ||
293 					 !sword::stricmp((gchar *)list->data, "not")) {
294 					// don't color boolean ops.
295 					goto next_word;
296 				}
297 			}
298 			len_word = strlen((gchar *)list->data);
299 
300 			/* find search word in verse */
301 			while ((tmpbuf = g_strrstr(buf, (gchar *)list->data)) != NULL) {
302 				char *angle_open, *angle_close;
303 
304 				len_tail = strlen(tmpbuf);
305 				len_prefix = len_overall - len_tail;
306 
307 				// find html <> delimiters preceding this word.
308 				*tmpbuf = '\0';
309 				angle_open = strrchr(buf, '<');
310 				angle_close = strrchr(buf, '>');
311 
312 				// contortions about that preceding markup.
313 				// note placement of <> in good/bad examples.
314 				// good: anything WORD anything [none]
315 				// good: <token> anything WORD anything [outside]
316 				// bad:  token> anything <token WORD anything [inside]
317 				if (!angle_open ||		  // no open (safe), or
318 				    (angle_close &&		  // there exists close and
319 				     (angle_open < angle_close))) // open is before close
320 				{
321 					// add end tag first
322 					str = g_string_insert(str,
323 							      (len_prefix + len_word),
324 							      closestr);
325 					// then add start tag
326 					str = g_string_insert(str,
327 							      len_prefix,
328 							      openstr);
329 				}
330 
331 				len_overall = len_prefix;
332 			}
333 		next_word:
334 			g_free(buf);
335 			if ((list = g_list_next(list)))
336 				buf = g_utf8_casefold(str->str, -1);
337 		}
338 		g_list_free(listbase);
339 
340 		/* else we have a phrase and only need to mark it */
341 	} else {
342 		len_overall = strlen(buf);
343 		len_word = strlen(searchbuf);
344 		tmpbuf = strstr(buf, searchbuf);
345 		if (tmpbuf) {
346 			len_tail = strlen(tmpbuf);
347 			len_prefix = len_overall - len_tail;
348 			/* place end tag first */
349 			str = g_string_insert(str, (len_prefix + len_word),
350 					      closestr);
351 			/* then place start tag */
352 			/* don't re-assign str here, to keep cppcheck happy */
353 			(void) g_string_insert(str, len_prefix, openstr);
354 		}
355 	}
356 	g_free(searchbuf);
357 }
358 
359 /******************************************************************************
360  * Name
361  *   main_entry_display
362  *
363  * Synopsis
364  *   #include ".h"
365  *
366  *   void main_entry_display(gpointer data, gchar * mod_name,
367  *		   gchar * text, gchar * key, gboolean show_key)
368  *
369  * Description
370  *   display Sword modules one verse (entry) at a time
371  *
372  * Return value
373  *   void
374  */
375 
main_entry_display(gpointer data,gchar * mod_name,gchar * text,gchar * key,gboolean show_key)376 void main_entry_display(gpointer data, gchar *mod_name,
377 			gchar *text, gchar *key, gboolean show_key)
378 {
379 	GtkWidget *html_widget = (GtkWidget *)data;
380 	GString *tmp_str = g_string_new(NULL);
381 	GString *str;
382 	MOD_FONT *mf = get_font(mod_name);
383 
384 	g_string_printf(tmp_str,
385 			HTML_START,
386 			settings.bible_bg_color, settings.bible_text_color,
387 			settings.link_color);
388 
389 	str = g_string_new(tmp_str->str);
390 
391 	g_string_printf(tmp_str,
392 			"<font face=\"%s\" size=\"%+d\">",
393 			(mf->old_font ? mf->old_font : "none"),
394 			mf->old_font_size_value - 1);
395 	str = g_string_append(str, tmp_str->str);
396 
397 	/* show key in html widget  */
398 	if (show_key) {
399 		const char *abbreviation = main_get_abbreviation(mod_name);
400 		if ((settings.displaySearchResults)) {
401 			g_string_printf(tmp_str,
402 					"<a href=\"sword://%s/%s\">"
403 					"<font color=\"%s\">[%s] %s </font></a><br />",
404 					mod_name,
405 					key,
406 					settings.link_color,
407 					(abbreviation ? abbreviation : mod_name),
408 					key);
409 		} else {
410 			g_string_printf(tmp_str,
411 					"<a href=\"passagestudy.jsp?action=showModInfo&value=%s&module=%s\">"
412 					"<font color=\"%s\">[%s]</a></font><br>[%s]<br />",
413 					backend->module_description(mod_name),
414 					mod_name,
415 					settings.link_color,
416 					(abbreviation ? abbreviation : mod_name),
417 					key);
418 		}
419 		str = g_string_append(str, tmp_str->str);
420 	}
421 
422 	if (settings.displaySearchResults) {
423 		GString *search_str = g_string_new(text);
424 		mark_search_words(search_str);
425 		str = g_string_append(str, search_str->str);
426 		g_string_free(search_str, TRUE);
427 	} else {
428 		str = g_string_append(str, text);
429 	}
430 
431 	g_string_printf(tmp_str, " %s", "</font></body></html>");
432 	str = g_string_append(str, tmp_str->str);
433 
434 	HtmlOutput((char *)AnalyzeForImageSize(str->str,
435 					       GDK_WINDOW(gtk_widget_get_window(html_widget))),
436 		   html_widget, mf, NULL);
437 	free_font(mf);
438 	g_string_free(str, TRUE);
439 	g_string_free(tmp_str, TRUE);
440 }
441