1 /*
2  * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <stdlib.h>
20 #include <stdint.h>
21 #include <math.h>
22 #include <string.h>
23 
24 #include "utils/messages.h"
25 #include "utils/nsoption.h"
26 #include "utils/file.h"
27 #include "utils/log.h"
28 #include "utils/nsurl.h"
29 #include "netsurf/browser_window.h"
30 #include "desktop/searchweb.h"
31 
32 #include "gtk/compat.h"
33 #include "gtk/toolbar_items.h"
34 #include "gtk/window.h"
35 #include "gtk/gui.h"
36 #include "gtk/scaffolding.h"
37 #include "gtk/resources.h"
38 #include "gtk/preferences.h"
39 
40 /* private prefs */
41 struct ppref {
42 	/** dialog handle created when window first accessed */
43 	GObject *dialog;
44 
45 	struct browser_window *bw;
46 
47 	/* widgets which are accessed from outside their own signal handlers */
48 	GtkEntry *entryHomePageURL;
49 	GtkEntry *entryProxyHost;
50 	GtkEntry *entryProxyUser;
51 	GtkEntry *entryProxyPassword;
52 	GtkEntry *entryProxyNoproxy;
53 	GtkSpinButton *spinProxyPort;
54 
55 	/* dynamic list stores */
56 	GtkListStore *content_language;
57 	GtkListStore *search_providers;
58 };
59 
60 static struct ppref ppref;
61 
62 
63 /* Set netsurf option based on toggle button state
64  *
65  * This works for any widget which subclasses togglebutton (checkbox,
66  * radiobutton etc.)
67  */
68 #define TOGGLEBUTTON_SIGNALS(WIDGET, OPTION)				\
69 G_MODULE_EXPORT void							\
70 nsgtk_preferences_##WIDGET##_toggled(GtkToggleButton *togglebutton,	\
71 				     struct ppref *priv);		\
72 G_MODULE_EXPORT void							\
73 nsgtk_preferences_##WIDGET##_toggled(GtkToggleButton *togglebutton,	\
74 				     struct ppref *priv)		\
75 {									\
76 	nsoption_set_bool(OPTION,					\
77 			  gtk_toggle_button_get_active(togglebutton));	\
78 }									\
79 									\
80 G_MODULE_EXPORT void							\
81 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget,			\
82 				     struct ppref *priv);		\
83 G_MODULE_EXPORT void							\
84 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget,			\
85 				     struct ppref *priv)		\
86 {									\
87 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),		\
88 				     nsoption_bool(OPTION));		\
89 }
90 
91 #define SPINBUTTON_SIGNALS(WIDGET, OPTION, MULTIPLIER)			\
92 G_MODULE_EXPORT void							\
93 nsgtk_preferences_##WIDGET##_valuechanged(GtkSpinButton *spinbutton,	\
94 					  struct ppref *priv);		\
95 G_MODULE_EXPORT void							\
96 nsgtk_preferences_##WIDGET##_valuechanged(GtkSpinButton *spinbutton,	\
97 					  struct ppref *priv)		\
98 {									\
99 	nsoption_set_int(OPTION,					\
100 		round(gtk_spin_button_get_value(spinbutton) * MULTIPLIER)); \
101 }									\
102 									\
103 G_MODULE_EXPORT void							\
104 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget, struct ppref *priv); \
105 G_MODULE_EXPORT void							\
106 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget, struct ppref *priv) \
107 {									\
108 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget),		\
109 		((gdouble)nsoption_int(OPTION)) / MULTIPLIER);		\
110 }
111 
112 #define SPINBUTTON_UINT_SIGNALS(WIDGET, OPTION, MULTIPLIER)		\
113 G_MODULE_EXPORT void							\
114 nsgtk_preferences_##WIDGET##_valuechanged(GtkSpinButton *spinbutton,	\
115 					  struct ppref *priv);		\
116 G_MODULE_EXPORT void							\
117 nsgtk_preferences_##WIDGET##_valuechanged(GtkSpinButton *spinbutton,	\
118 					  struct ppref *priv)		\
119 {									\
120 	nsoption_set_uint(OPTION,					\
121 		round(gtk_spin_button_get_value(spinbutton) * MULTIPLIER)); \
122 }									\
123 									\
124 G_MODULE_EXPORT void							\
125 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget, struct ppref *priv); \
126 G_MODULE_EXPORT void							\
127 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget, struct ppref *priv) \
128 {									\
129 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget),		\
130 		((gdouble)nsoption_uint(OPTION)) / MULTIPLIER);		\
131 }
132 
133 #define ENTRY_SIGNALS(WIDGET, OPTION)					\
134 G_MODULE_EXPORT void							\
135 nsgtk_preferences_##WIDGET##_changed(GtkEditable *editable, struct ppref *priv); \
136 G_MODULE_EXPORT void							\
137 nsgtk_preferences_##WIDGET##_changed(GtkEditable *editable, struct ppref *priv)\
138 {									\
139 	nsoption_set_charp(OPTION,					\
140 		strdup(gtk_entry_get_text(GTK_ENTRY(editable))));	\
141 }									\
142 									\
143 G_MODULE_EXPORT void							\
144 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget,	struct ppref *priv); \
145 G_MODULE_EXPORT void							\
146 nsgtk_preferences_##WIDGET##_realize(GtkWidget *widget,	struct ppref *priv) \
147 {									\
148 	const char *OPTION;						\
149 	OPTION = nsoption_charp(OPTION);				\
150 	if (OPTION != NULL) {						\
151 		gtk_entry_set_text(GTK_ENTRY(widget), OPTION);		\
152 	}								\
153 }
154 
155 /* GTK module requires these to be exported symbols so these all need
156  * forward declaring to avoid warnings
157  */
158 G_MODULE_EXPORT void nsgtk_preferences_comboProxyType_changed(GtkComboBox *combo, struct ppref *priv);
159 G_MODULE_EXPORT void nsgtk_preferences_comboProxyType_realize(GtkWidget *widget, struct ppref *priv);
160 G_MODULE_EXPORT void nsgtk_preferences_comboboxLoadImages_changed(GtkComboBox *combo, struct ppref *priv);
161 G_MODULE_EXPORT void nsgtk_preferences_comboboxLoadImages_realize(GtkWidget *widget, struct ppref *priv);
162 G_MODULE_EXPORT void nsgtk_preferences_comboDefault_changed(GtkComboBox *combo, struct ppref *priv);
163 G_MODULE_EXPORT void nsgtk_preferences_comboDefault_realize(GtkWidget *widget, struct ppref *priv);
164 G_MODULE_EXPORT void nsgtk_preferences_fontPreview_clicked(GtkButton *button, struct ppref *priv);
165 G_MODULE_EXPORT void nsgtk_preferences_comboboxLanguage_changed(GtkComboBox *combo, struct ppref *priv);
166 G_MODULE_EXPORT void nsgtk_preferences_comboboxLanguage_realize(GtkWidget *widget, struct ppref *priv);
167 G_MODULE_EXPORT void nsgtk_preferences_checkShowSingleTab_toggled(GtkToggleButton *togglebutton, struct ppref *priv);
168 G_MODULE_EXPORT void nsgtk_preferences_checkShowSingleTab_realize(GtkWidget *widget, struct ppref *priv);
169 G_MODULE_EXPORT void nsgtk_preferences_comboTabPosition_changed(GtkComboBox *widget, struct ppref *priv);
170 G_MODULE_EXPORT void nsgtk_preferences_comboTabPosition_realize(GtkWidget *widget, struct ppref *priv);
171 G_MODULE_EXPORT void nsgtk_preferences_comboDeveloperView_changed(GtkComboBox *widget, struct ppref *priv);
172 G_MODULE_EXPORT void nsgtk_preferences_comboDeveloperView_realize(GtkWidget *widget, struct ppref *priv);
173 G_MODULE_EXPORT void nsgtk_preferences_comboButtonType_changed(GtkComboBox *widget, struct ppref *priv);
174 G_MODULE_EXPORT void nsgtk_preferences_comboButtonType_realize(GtkWidget *widget, struct ppref *priv);
175 G_MODULE_EXPORT void nsgtk_preferences_setCurrentPage_clicked(GtkButton *button, struct ppref *priv);
176 G_MODULE_EXPORT void nsgtk_preferences_setDefaultPage_clicked(GtkButton *button, struct ppref *priv);
177 G_MODULE_EXPORT void nsgtk_preferences_comboSearch_changed(GtkComboBox *widget, struct ppref *priv);
178 G_MODULE_EXPORT void nsgtk_preferences_comboSearch_realize(GtkWidget *widget, struct ppref *priv);
179 G_MODULE_EXPORT void nsgtk_preferences_fileChooserDownloads_selectionchanged(GtkFileChooser *chooser, struct ppref *priv);
180 G_MODULE_EXPORT void nsgtk_preferences_fileChooserDownloads_realize(GtkWidget *widget, struct ppref *priv);
181 G_MODULE_EXPORT void nsgtk_preferences_dialogPreferences_response(GtkDialog *dlg, gint resid);
182 G_MODULE_EXPORT gboolean nsgtk_preferences_dialogPreferences_deleteevent(GtkDialog *dlg, struct ppref *priv);
183 G_MODULE_EXPORT void nsgtk_preferences_dialogPreferences_destroy(GtkDialog *dlg, struct ppref *priv);
184 
185 
186 /********* PDF **********/
187 
188 /* Appearance */
189 
190 /* no images in output */
TOGGLEBUTTON_SIGNALS(checkSuppressImages,suppress_images)191 TOGGLEBUTTON_SIGNALS(checkSuppressImages, suppress_images)
192 
193 /* no background images */
194 TOGGLEBUTTON_SIGNALS(checkRemoveBackgrounds, remove_backgrounds)
195 
196 /* scale to fit page */
197 TOGGLEBUTTON_SIGNALS(checkFitPage, enable_loosening)
198 
199 /* port */
200 SPINBUTTON_SIGNALS(spinExportScale, export_scale, 1.0)
201 
202 /* Margins */
203 SPINBUTTON_SIGNALS(spinMarginTop, margin_top, 1.0)
204 SPINBUTTON_SIGNALS(spinMarginBottom, margin_bottom, 1.0)
205 SPINBUTTON_SIGNALS(spinMarginLeft, margin_left, 1.0)
206 SPINBUTTON_SIGNALS(spinMarginRight, margin_right, 1.0)
207 
208 
209 /* Generation */
210 
211 /* output is compressed */
212 TOGGLEBUTTON_SIGNALS(checkCompressPDF, enable_PDF_compression)
213 
214 /* output has a password */
215 TOGGLEBUTTON_SIGNALS(checkPasswordPDF, enable_PDF_password)
216 
217 /********* Network **********/
218 
219 /* HTTP proxy */
220 static void set_proxy_widgets_sensitivity(int proxyval, struct ppref *priv)
221 {
222 	gboolean host;
223 	gboolean port;
224 	gboolean user;
225 	gboolean pass;
226 	gboolean noproxy;
227 
228 	switch (proxyval) {
229 	case 0: /* no proxy */
230 		host = FALSE;
231 		port = FALSE;
232 		user = FALSE;
233 		pass = FALSE;
234 		noproxy = FALSE;
235 		break;
236 
237 	case 1: /* proxy with no auth */
238 		host = TRUE;
239 		port = TRUE;
240 		user = FALSE;
241 		pass = FALSE;
242 		noproxy = TRUE;
243 		break;
244 
245 	case 2: /* proxy with basic auth */
246 		host = TRUE;
247 		port = TRUE;
248 		user = TRUE;
249 		pass = TRUE;
250 		noproxy = TRUE;
251 		break;
252 
253 	case 3: /* proxy with ntlm auth */
254 		host = TRUE;
255 		port = TRUE;
256 		user = TRUE;
257 		pass = TRUE;
258 		noproxy = TRUE;
259 		break;
260 
261 	case 4: /* system proxy */
262 		host = FALSE;
263 		port = FALSE;
264 		user = FALSE;
265 		pass = FALSE;
266 		noproxy = FALSE;
267 		break;
268 
269 	default:
270 		return;
271 	}
272 
273 	gtk_widget_set_sensitive(GTK_WIDGET(priv->entryProxyHost), host);
274 	gtk_widget_set_sensitive(GTK_WIDGET(priv->spinProxyPort), port);
275 	gtk_widget_set_sensitive(GTK_WIDGET(priv->entryProxyUser), user);
276 	gtk_widget_set_sensitive(GTK_WIDGET(priv->entryProxyPassword), pass);
277 	gtk_widget_set_sensitive(GTK_WIDGET(priv->entryProxyNoproxy), noproxy);
278 
279 }
280 
281 G_MODULE_EXPORT void
nsgtk_preferences_comboProxyType_changed(GtkComboBox * combo,struct ppref * priv)282 nsgtk_preferences_comboProxyType_changed(GtkComboBox *combo, struct ppref *priv)
283 {
284 	int proxy_sel;
285 	proxy_sel = gtk_combo_box_get_active(combo);
286 
287 	switch (proxy_sel) {
288 	case 0: /* no proxy */
289 		nsoption_set_bool(http_proxy, false);
290 		break;
291 
292 	case 1: /* proxy with no auth */
293 		nsoption_set_bool(http_proxy, true);
294 		nsoption_set_int(http_proxy_auth, OPTION_HTTP_PROXY_AUTH_NONE);
295 		break;
296 
297 	case 2: /* proxy with basic auth */
298 		nsoption_set_bool(http_proxy, true);
299 		nsoption_set_int(http_proxy_auth, OPTION_HTTP_PROXY_AUTH_BASIC);
300 		break;
301 
302 	case 3: /* proxy with ntlm auth */
303 		nsoption_set_bool(http_proxy, true);
304 		nsoption_set_int(http_proxy_auth, OPTION_HTTP_PROXY_AUTH_NTLM);
305 		break;
306 
307 	case 4: /* system proxy */
308 		nsoption_set_bool(http_proxy, true);
309 		nsoption_set_int(http_proxy_auth, OPTION_HTTP_PROXY_AUTH_NONE);
310 		break;
311 	}
312 
313 	set_proxy_widgets_sensitivity(proxy_sel, priv);
314 }
315 
316 G_MODULE_EXPORT void
nsgtk_preferences_comboProxyType_realize(GtkWidget * widget,struct ppref * priv)317 nsgtk_preferences_comboProxyType_realize(GtkWidget *widget, struct ppref *priv)
318 {
319 	int proxytype = 0; /* no proxy by default */
320 
321 	if (nsoption_bool(http_proxy) == true) {
322 		/* proxy type combo box starts with disabled, to allow
323 		 * for this the http_proxy option needs combining with
324 		 * the http_proxy_auth option
325 		 */
326 		proxytype = nsoption_int(http_proxy_auth) + 1;
327 		if (nsoption_charp(http_proxy_host) == NULL) {
328 			/* set to use a proxy without a host, turn proxy off */
329 			proxytype = 0;
330 		} else if (((proxytype == 2) ||
331 			    (proxytype == 3)) &&
332 			   ((nsoption_charp(http_proxy_auth_user) == NULL) ||
333 			    (nsoption_charp(http_proxy_auth_pass) == NULL))) {
334 			/* authentication selected with empty credentials, turn proxy off */
335 			proxytype = 0;
336 		}
337 	}
338 	gtk_combo_box_set_active(GTK_COMBO_BOX(widget), proxytype);
339 
340 	set_proxy_widgets_sensitivity(proxytype, priv);
341 }
342 
343 /* host */
ENTRY_SIGNALS(entryProxyHost,http_proxy_host)344 ENTRY_SIGNALS(entryProxyHost, http_proxy_host)
345 
346 /* port */
347 SPINBUTTON_SIGNALS(spinProxyPort, http_proxy_port, 1.0)
348 
349 /* user */
350 ENTRY_SIGNALS(entryProxyUser, http_proxy_auth_user)
351 
352 /* password */
353 ENTRY_SIGNALS(entryProxyPassword, http_proxy_auth_pass)
354 
355 /* no proxy */
356 ENTRY_SIGNALS(entryProxyNoproxy, http_proxy_noproxy)
357 
358 /* Fetching */
359 
360 /* maximum fetchers */
361 SPINBUTTON_SIGNALS(spinMaxFetchers, max_fetchers, 1.0)
362 
363 /* fetches per host */
364 SPINBUTTON_SIGNALS(spinFetchesPerHost, max_fetchers_per_host, 1.0)
365 
366 /* cached connections */
367 SPINBUTTON_SIGNALS(spinCachedConnections, max_cached_fetch_handles, 1.0)
368 
369 
370 /********* Privacy **********/
371 
372 /* General */
373 
374 /* enable referral submission */
375 TOGGLEBUTTON_SIGNALS(checkSendReferer, send_referer)
376 
377 /* send do not track */
378 TOGGLEBUTTON_SIGNALS(checkSendDNT, do_not_track)
379 
380 /* History */
381 
382 /* local history shows url tooltips */
383 TOGGLEBUTTON_SIGNALS(checkHoverURLs, hover_urls)
384 
385 /* remember browsing history */
386 SPINBUTTON_SIGNALS(spinHistoryAge, history_age, 1.0)
387 
388 /* Cache */
389 
390 /* memory cache size */
391 SPINBUTTON_SIGNALS(spinMemoryCacheSize, memory_cache_size, (1024*1024))
392 
393 /* disc cache size */
394 SPINBUTTON_UINT_SIGNALS(spinDiscCacheSize, disc_cache_size, (1024*1024))
395 
396 
397 /* disc cache age */
398 SPINBUTTON_SIGNALS(spinDiscCacheAge, disc_cache_age, 1.0)
399 
400 
401 /********* Content **********/
402 
403 /* Control */
404 
405 
406 /* prevent popups */
407 TOGGLEBUTTON_SIGNALS(checkDisablePopups, disable_popups)
408 
409 /* hide adverts */
410 TOGGLEBUTTON_SIGNALS(checkHideAdverts, block_advertisements)
411 
412 /* enable javascript */
413 TOGGLEBUTTON_SIGNALS(checkEnableJavascript, enable_javascript)
414 
415 /* load and display of images */
416 G_MODULE_EXPORT void
417 nsgtk_preferences_comboboxLoadImages_changed(GtkComboBox *combo,
418 					     struct ppref *priv)
419 {
420 	int img_sel;
421 	/* get the row number for the selection */
422 	img_sel = gtk_combo_box_get_active(combo);
423 	switch (img_sel) {
424 	case 0:
425 		/* background and foreground */
426 		nsoption_set_bool(foreground_images, true);
427 		nsoption_set_bool(background_images, true);
428 		break;
429 
430 	case 1:
431 		/* foreground only */
432 		nsoption_set_bool(foreground_images, true);
433 		nsoption_set_bool(background_images, false);
434 		break;
435 
436 	case 2:
437 		/* background only */
438 		nsoption_set_bool(foreground_images, false);
439 		nsoption_set_bool(background_images, true);
440 		break;
441 
442 	case 3:
443 		/* no images */
444 		nsoption_set_bool(foreground_images, false);
445 		nsoption_set_bool(background_images, false);
446 		break;
447 	}
448 }
449 
450 G_MODULE_EXPORT void
nsgtk_preferences_comboboxLoadImages_realize(GtkWidget * widget,struct ppref * priv)451 nsgtk_preferences_comboboxLoadImages_realize(GtkWidget *widget,
452 					   struct ppref *priv)
453 {
454 	if (nsoption_bool(foreground_images)) {
455 		if (nsoption_bool(background_images)) {
456 			/* background and foreground */
457 			gtk_combo_box_set_active(GTK_COMBO_BOX(widget), 0);
458 		} else {
459 			/* foreground only */
460 			gtk_combo_box_set_active(GTK_COMBO_BOX(widget), 1);
461 		}
462 	} else {
463 		if (nsoption_bool(background_images)) {
464 			/* background only */
465 			gtk_combo_box_set_active(GTK_COMBO_BOX(widget), 2);
466 		} else {
467 			/* no images */
468 			gtk_combo_box_set_active(GTK_COMBO_BOX(widget), 3);
469 		}
470 	}
471 }
472 
473 /* Animation */
474 
475 /* enable animation */
TOGGLEBUTTON_SIGNALS(checkEnableAnimations,animate_images)476 TOGGLEBUTTON_SIGNALS(checkEnableAnimations, animate_images)
477 
478 /* Fonts */
479 
480 /* default font */
481 G_MODULE_EXPORT void
482 nsgtk_preferences_comboDefault_changed(GtkComboBox *combo, struct ppref *priv)
483 {
484 	int font_sel;
485 	/* get the row number for the selection */
486 	font_sel = gtk_combo_box_get_active(combo);
487 	if ((font_sel >= 0) && (font_sel <= 4)) {
488 		nsoption_set_int(font_default, font_sel);
489 	}
490 }
491 
492 G_MODULE_EXPORT void
nsgtk_preferences_comboDefault_realize(GtkWidget * widget,struct ppref * priv)493 nsgtk_preferences_comboDefault_realize(GtkWidget *widget, struct ppref *priv)
494 {
495 	gtk_combo_box_set_active(GTK_COMBO_BOX(widget),
496 				 nsoption_int(font_default));
497 }
498 
499 /* default font size */
500 SPINBUTTON_SIGNALS(spinDefaultSize, font_size, 10.0)
501 
502 /* preview - actually reflow all views */
503 G_MODULE_EXPORT void
nsgtk_preferences_fontPreview_clicked(GtkButton * button,struct ppref * priv)504 nsgtk_preferences_fontPreview_clicked(GtkButton *button, struct ppref *priv)
505 {
506 	nsgtk_window_update_all();
507 }
508 
509 
510 /* Language */
511 
512 /* accept language */
513 G_MODULE_EXPORT void
nsgtk_preferences_comboboxLanguage_changed(GtkComboBox * combo,struct ppref * priv)514 nsgtk_preferences_comboboxLanguage_changed(GtkComboBox *combo,
515 					   struct ppref *priv)
516 {
517 	gchar *lang = NULL;
518 	GtkTreeIter iter;
519 	GtkTreeModel *model;
520 
521 	/* Obtain currently selected item from combo box.
522 	 * If nothing is selected, do nothing.
523 	 */
524 	if (gtk_combo_box_get_active_iter(combo, &iter)) {
525 		/* Obtain data model from combo box. */
526 		model = gtk_combo_box_get_model(combo);
527 
528 		/* Obtain string from model. */
529 		gtk_tree_model_get(model, &iter, 0, &lang, -1);
530 	}
531 
532 	if (lang != NULL) {
533 		nsoption_set_charp(accept_language, strdup(lang));
534 		g_free(lang);
535 	}
536 }
537 
538 /**
539  * populate language combo from data
540  */
541 static nserror
comboboxLanguage_add_from_data(GtkListStore * liststore,GtkComboBox * combobox,const char * accept_language,const uint8_t * data,size_t data_size)542 comboboxLanguage_add_from_data(GtkListStore *liststore,
543 			       GtkComboBox *combobox,
544 			       const char *accept_language,
545 			       const uint8_t *data,
546 			       size_t data_size)
547 {
548 	int active_language = -1;
549 	GtkTreeIter iter;
550 	int combo_row_count = 0;
551 	const uint8_t *s;
552 	const uint8_t *nl;
553 	char buf[50];
554 	int bufi = 0;
555 
556 	gtk_list_store_clear(liststore);
557 	s = data;
558 
559 	while (s < (data + data_size)) {
560 		/* find nl and copy buffer */
561 		for (nl = s; nl < data + data_size; nl++) {
562 			if ((*nl == '\n') || (bufi == (sizeof(buf) - 2))) {
563 				buf[bufi] = 0; /* null terminate */
564 				break;
565 			}
566 			buf[bufi++] = *nl;
567 		}
568 		if (bufi > 0) {
569 			gtk_list_store_append(liststore, &iter);
570 			gtk_list_store_set(liststore, &iter, 0, buf, -1 );
571 
572 			if (strcmp(buf, accept_language) == 0) {
573 				active_language = combo_row_count;
574 			}
575 
576 			combo_row_count++;
577 		}
578 		bufi = 0;
579 		s = nl + 1; /* skip newline */
580 	}
581 
582 	/* if configured language was not in list, add it */
583 	if (active_language == -1) {
584 		gtk_list_store_append(liststore, &iter);
585 		gtk_list_store_set(liststore, &iter, 0, accept_language, -1);
586 		active_language = combo_row_count;
587 	}
588 
589 	gtk_combo_box_set_active(combobox, active_language);
590 
591 	return NSERROR_OK;
592 }
593 
594 /**
595  * populate language combo from file
596  */
597 static nserror
comboboxLanguage_add_from_file(GtkListStore * liststore,GtkComboBox * combobox,const char * accept_language,const char * file_location)598 comboboxLanguage_add_from_file(GtkListStore *liststore,
599 			       GtkComboBox *combobox,
600 			       const char *accept_language,
601 			       const char *file_location)
602 {
603 	int active_language = -1;
604 	GtkTreeIter iter;
605 	int combo_row_count = 0;
606 	FILE *fp;
607 	char buf[50];
608 
609 	fp = fopen(file_location, "r");
610 	if (fp == NULL) {
611 		return NSERROR_NOT_FOUND;
612 	}
613 
614 	gtk_list_store_clear(liststore);
615 
616 	NSLOG(netsurf, INFO, "Used %s for languages", file_location);
617 	while (fgets(buf, sizeof(buf), fp)) {
618 		/* Ignore blank lines */
619 		if (buf[0] == '\0')
620 			continue;
621 
622 		/* Remove trailing \n */
623 		buf[strlen(buf) - 1] = '\0';
624 
625 		gtk_list_store_append(liststore, &iter);
626 		gtk_list_store_set(liststore, &iter, 0, buf, -1 );
627 
628 		if (strcmp(buf, accept_language) == 0) {
629 			active_language = combo_row_count;
630 		}
631 
632 		combo_row_count++;
633 	}
634 
635 	/* if configured language was not in list, add it */
636 	if (active_language == -1) {
637 		gtk_list_store_append(liststore, &iter);
638 		gtk_list_store_set(liststore, &iter, 0, accept_language, -1);
639 		active_language = combo_row_count;
640 	}
641 
642 	fclose(fp);
643 
644 	gtk_combo_box_set_active(combobox, active_language);
645 
646 	return NSERROR_OK;
647 }
648 
649 /**
650  * Fill content language list store.
651  */
652 G_MODULE_EXPORT void
nsgtk_preferences_comboboxLanguage_realize(GtkWidget * widget,struct ppref * priv)653 nsgtk_preferences_comboboxLanguage_realize(GtkWidget *widget,
654 					   struct ppref *priv)
655 {
656 	nserror res;
657 	const uint8_t *data;
658 	size_t data_size;
659 	const char *languages_file;
660 	const char *accept_language;
661 
662 	if (priv->content_language == NULL) {
663 		NSLOG(netsurf, INFO, "content language list store unavailable");
664 		return;
665 	}
666 
667 	/* get current accept language */
668 	accept_language = nsoption_charp(accept_language);
669 	if (accept_language == NULL) {
670 		accept_language = "en";
671 	}
672 
673 	/* attempt to read languages from inline resource */
674 	res = nsgtk_data_from_resname("languages", &data, &data_size);
675 	if (res == NSERROR_OK) {
676 		res = comboboxLanguage_add_from_data(priv->content_language,
677 						     GTK_COMBO_BOX(widget),
678 						     accept_language,
679 						     data,
680 						     data_size);
681 	} else {
682 		/* attempt to read languages from file */
683 		res = nsgtk_path_from_resname("languages", &languages_file);
684 		if (res == NSERROR_OK) {
685 			res = comboboxLanguage_add_from_file(priv->content_language,
686 					GTK_COMBO_BOX(widget),
687 					accept_language,
688 					languages_file);
689 		}
690 	}
691 	if (res != NSERROR_OK) {
692 		NSLOG(netsurf, INFO, "error populatiung languages combo");
693 	}
694 }
695 
696 
697 /********* Apperance **********/
698 
699 /* Tabs */
700 
701 /* always show tab bar */
702 G_MODULE_EXPORT void
nsgtk_preferences_checkShowSingleTab_toggled(GtkToggleButton * togglebutton,struct ppref * priv)703 nsgtk_preferences_checkShowSingleTab_toggled(GtkToggleButton *togglebutton,
704 					     struct ppref *priv)
705 {
706 	nsoption_set_bool(show_single_tab,
707 			  gtk_toggle_button_get_active(togglebutton));
708 	nsgtk_window_update_all();
709 }
710 
711 G_MODULE_EXPORT void
nsgtk_preferences_checkShowSingleTab_realize(GtkWidget * widget,struct ppref * priv)712 nsgtk_preferences_checkShowSingleTab_realize(GtkWidget *widget,
713 				     struct ppref *priv)
714 {
715 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
716 				     nsoption_bool(show_single_tab));
717 }
718 
719 /* switch to newly opened tabs immediately */
TOGGLEBUTTON_SIGNALS(checkFocusNew,focus_new)720 TOGGLEBUTTON_SIGNALS(checkFocusNew, focus_new)
721 
722 /* newly opened tabs are blank */
723 TOGGLEBUTTON_SIGNALS(checkNewBlank, new_blank)
724 
725 /* tab position */
726 G_MODULE_EXPORT void
727 nsgtk_preferences_comboTabPosition_changed(GtkComboBox *widget,
728 					   struct ppref *priv)
729 {
730 	/* set the option */
731 	nsoption_set_int(position_tab, gtk_combo_box_get_active(widget));
732 
733 	/* update all windows */
734 	nsgtk_window_update_all();
735 }
736 
737 G_MODULE_EXPORT void
nsgtk_preferences_comboTabPosition_realize(GtkWidget * widget,struct ppref * priv)738 nsgtk_preferences_comboTabPosition_realize(GtkWidget *widget,
739 					   struct ppref *priv)
740 {
741 	gtk_combo_box_set_active(GTK_COMBO_BOX(widget),
742 				 nsoption_int(position_tab));
743 }
744 
745 /* Tools */
746 
747 /* developer view opening */
748 G_MODULE_EXPORT void
nsgtk_preferences_comboDeveloperView_changed(GtkComboBox * widget,struct ppref * priv)749 nsgtk_preferences_comboDeveloperView_changed(GtkComboBox *widget,
750 					   struct ppref *priv)
751 {
752 	/* set the option */
753 	nsoption_set_int(developer_view, gtk_combo_box_get_active(widget));
754 }
755 
756 G_MODULE_EXPORT void
nsgtk_preferences_comboDeveloperView_realize(GtkWidget * widget,struct ppref * priv)757 nsgtk_preferences_comboDeveloperView_realize(GtkWidget *widget,
758 					   struct ppref *priv)
759 {
760 	gtk_combo_box_set_active(GTK_COMBO_BOX(widget),
761 				 nsoption_int(developer_view));
762 }
763 
764 
765 /* URLbar */
766 
767 /* show recently visited urls as you type */
TOGGLEBUTTON_SIGNALS(checkDisplayRecentURLs,url_suggestion)768 TOGGLEBUTTON_SIGNALS(checkDisplayRecentURLs, url_suggestion)
769 
770 /* Toolbar */
771 
772 /* button position */
773 G_MODULE_EXPORT void
774 nsgtk_preferences_comboButtonType_changed(GtkComboBox *widget,
775 					   struct ppref *priv)
776 {
777 	nsoption_set_int(button_type, gtk_combo_box_get_active(widget) + 1);
778 
779 	/* update all windows to adopt change */
780 	nsgtk_window_update_all();
781 }
782 
783 G_MODULE_EXPORT void
nsgtk_preferences_comboButtonType_realize(GtkWidget * widget,struct ppref * priv)784 nsgtk_preferences_comboButtonType_realize(GtkWidget *widget,
785 					   struct ppref *priv)
786 {
787 	gtk_combo_box_set_active(GTK_COMBO_BOX(widget),
788 				 nsoption_int(button_type) - 1);
789 }
790 
791 
792 
793 /************ Main ************/
794 
795 /* Startup */
796 
797 /* entry HomePageURL widget */
ENTRY_SIGNALS(entryHomePageURL,homepage_url)798 ENTRY_SIGNALS(entryHomePageURL, homepage_url)
799 
800 /* put current page into homepage url */
801 G_MODULE_EXPORT void
802 nsgtk_preferences_setCurrentPage_clicked(GtkButton *button, struct ppref *priv)
803 {
804 	const gchar *url = nsurl_access(browser_window_access_url(priv->bw));
805 
806 	if (priv->entryHomePageURL != NULL) {
807 		gtk_entry_set_text(GTK_ENTRY(priv->entryHomePageURL), url);
808 		nsoption_set_charp(homepage_url, strdup(url));
809 	}
810 }
811 
812 /* put default page into homepage */
813 G_MODULE_EXPORT void
nsgtk_preferences_setDefaultPage_clicked(GtkButton * button,struct ppref * priv)814 nsgtk_preferences_setDefaultPage_clicked(GtkButton *button, struct ppref *priv)
815 {
816 	const gchar *url = NETSURF_HOMEPAGE;
817 
818 	if (priv->entryHomePageURL != NULL) {
819 		gtk_entry_set_text(GTK_ENTRY(priv->entryHomePageURL), url);
820 		nsoption_set_charp(homepage_url, strdup(url));
821 	}
822 }
823 
824 /* Search */
825 
826 /* Url Search widget */
TOGGLEBUTTON_SIGNALS(checkUrlSearch,search_url_bar)827 TOGGLEBUTTON_SIGNALS(checkUrlSearch, search_url_bar)
828 
829 /* provider combo */
830 G_MODULE_EXPORT void
831 nsgtk_preferences_comboSearch_changed(GtkComboBox *widget, struct ppref *priv)
832 {
833 	int provider;
834 
835 	provider = gtk_combo_box_get_active(widget);
836 
837 	/* set the option */
838 	nsoption_set_int(search_provider, provider);
839 
840 	/* set search provider */
841 	search_web_select_provider(provider);
842 }
843 
844 G_MODULE_EXPORT void
nsgtk_preferences_comboSearch_realize(GtkWidget * widget,struct ppref * priv)845 nsgtk_preferences_comboSearch_realize(GtkWidget *widget, struct ppref *priv)
846 {
847 	int iter;
848 	const char *name;
849 	int provider = nsoption_int(search_provider);
850 
851 	if (priv->search_providers != NULL) {
852 		gtk_list_store_clear(priv->search_providers);
853 		for (iter = search_web_iterate_providers(0, &name);
854 		     iter != -1;
855 		     iter = search_web_iterate_providers(iter, &name)) {
856 			gtk_list_store_insert_with_values(priv->search_providers,
857 							  NULL, -1,
858 							  0, name, -1);
859 		}
860 	}
861 
862 	gtk_combo_box_set_active(GTK_COMBO_BOX(widget), provider);
863 }
864 
865 
866 /* Downloads */
867 
868 /* clear downloads */
TOGGLEBUTTON_SIGNALS(checkClearDownloads,downloads_clear)869 TOGGLEBUTTON_SIGNALS(checkClearDownloads, downloads_clear)
870 
871 /* request overwite */
872 TOGGLEBUTTON_SIGNALS(checkRequestOverwrite, request_overwrite)
873 
874 /* download location
875  *
876  * note selection-changed is used instead of file-set as the returned
877  * filename when that signal are used is incorrect. Though this signal
878  * does update frequently often with the same data.
879  */
880 G_MODULE_EXPORT void
881 nsgtk_preferences_fileChooserDownloads_selectionchanged(GtkFileChooser *chooser,
882 					       struct ppref *priv)
883 {
884 	gchar *dir;
885 	dir = gtk_file_chooser_get_filename(chooser);
886 	nsoption_set_charp(downloads_directory, strdup(dir));
887 	g_free(dir);
888 }
889 
890 G_MODULE_EXPORT void
nsgtk_preferences_fileChooserDownloads_realize(GtkWidget * widget,struct ppref * priv)891 nsgtk_preferences_fileChooserDownloads_realize(GtkWidget *widget,
892 					       struct ppref *priv)
893 {
894 	gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(widget),
895 					   nsoption_charp(downloads_directory));
896 }
897 
898 
899 /************* Dialog window ***********/
900 
901 /* dialog close and destroy events */
902 G_MODULE_EXPORT void
nsgtk_preferences_dialogPreferences_response(GtkDialog * dlg,gint resid)903 nsgtk_preferences_dialogPreferences_response(GtkDialog *dlg, gint resid)
904 {
905 	char *choices = NULL;
906 
907 	if (resid == GTK_RESPONSE_CLOSE) {
908 		netsurf_mkpath(&choices, NULL, 2, nsgtk_config_home, "Choices");
909 		if (choices != NULL) {
910 			nsoption_write(choices, NULL, NULL);
911 			free(choices);
912 		}
913 		gtk_widget_hide(GTK_WIDGET(dlg));
914 	}
915 }
916 
917 G_MODULE_EXPORT gboolean
nsgtk_preferences_dialogPreferences_deleteevent(GtkDialog * dlg,struct ppref * priv)918 nsgtk_preferences_dialogPreferences_deleteevent(GtkDialog *dlg,
919 						struct ppref *priv)
920 {
921 	char *choices = NULL;
922 
923 	netsurf_mkpath(&choices, NULL, 2, nsgtk_config_home, "Choices");
924 	if (choices != NULL) {
925 		nsoption_write(choices, NULL, NULL);
926 		free(choices);
927 	}
928 
929 	gtk_widget_hide(GTK_WIDGET(dlg));
930 
931 	/* Delt with it by hiding window, no need to destory widget by
932 	 * default.
933 	 */
934 	return TRUE;
935 }
936 
937 G_MODULE_EXPORT void
nsgtk_preferences_dialogPreferences_destroy(GtkDialog * dlg,struct ppref * priv)938 nsgtk_preferences_dialogPreferences_destroy(GtkDialog *dlg, struct ppref *priv)
939 {
940 	char *choices = NULL;
941 
942 	netsurf_mkpath(&choices, NULL, 2, nsgtk_config_home, "Choices");
943 	if (choices != NULL) {
944 		nsoption_write(choices, NULL, NULL);
945 		free(choices);
946 	}
947 }
948 
949 
950 /* exported interface documented in gtk/preferences.h */
nsgtk_preferences(struct browser_window * bw,GtkWindow * parent)951 GtkWidget* nsgtk_preferences(struct browser_window *bw, GtkWindow *parent)
952 {
953 	GtkBuilder *preferences_builder;
954 	struct ppref *priv = &ppref;
955 	nserror res;
956 
957 	priv->bw = bw; /* for setting "current" page */
958 
959 	/* memoised dialog creation */
960 	if (priv->dialog != NULL) {
961 		gtk_window_set_transient_for(GTK_WINDOW(priv->dialog), parent);
962 		return GTK_WIDGET(priv->dialog);
963 	}
964 
965 	const char *client_lang;
966 	char ui_lang[10] = "en";
967         client_lang = getenv("LANG");
968 
969 	if (strlen(client_lang)>1)
970 	{
971 		strncpy(ui_lang,client_lang,2);
972 	}
973 
974 	strcat(ui_lang,"options");
975 
976 	res = nsgtk_builder_new_from_resname(ui_lang, &preferences_builder);
977         if (res != NSERROR_OK) {
978                 //LANG not found, so fall back to english i guess
979                 res = nsgtk_builder_new_from_resname("enoption", &preferences_builder);
980         }
981         if (res != NSERROR_OK) {
982                 NSLOG(netsurf, INFO, "Preferences UI builder init failed");
983                 return NULL;
984         }
985 
986 	priv->dialog = gtk_builder_get_object(preferences_builder,
987 					       "dialogPreferences");
988 	if (priv->dialog == NULL) {
989 		NSLOG(netsurf, INFO,
990 		      "Unable to get object for preferences dialog");
991 		/* release builder as were done with it */
992 		g_object_unref(G_OBJECT(preferences_builder));
993 		return NULL;
994 	}
995 
996 	/* need to explicitly obtain handles for some widgets enabling
997 	 * updates by other widget events
998 	 */
999 #define GB(TYPE, NAME) GTK_##TYPE(gtk_builder_get_object(preferences_builder, #NAME))
1000 	priv->entryHomePageURL = GB(ENTRY, entryHomePageURL);
1001 	priv->content_language = GB(LIST_STORE, liststore_content_language);
1002 	priv->search_providers = GB(LIST_STORE, liststore_search_provider);
1003 	priv->entryProxyHost = GB(ENTRY, entryProxyHost);
1004 	priv->spinProxyPort = GB(SPIN_BUTTON, spinProxyPort);
1005 	priv->entryProxyUser = GB(ENTRY, entryProxyUser);
1006 	priv->entryProxyPassword = GB(ENTRY, entryProxyPassword);
1007 	priv->entryProxyNoproxy = GB(ENTRY, entryProxyNoproxy);
1008 #undef GB
1009 
1010 	/* connect all signals ready to use */
1011 	gtk_builder_connect_signals(preferences_builder, priv);
1012 
1013 	/* release builder as were done with it */
1014 	g_object_unref(G_OBJECT(preferences_builder));
1015 
1016 	/* mark dialog as transient on parent */
1017 	gtk_window_set_transient_for(GTK_WINDOW(priv->dialog), parent);
1018 
1019 	return GTK_WIDGET(priv->dialog);
1020 }
1021 
1022