1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2010-2011 Thibault Duponchelle
5  * Copyright (c) 2011 Benjamin Moody
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * 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, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <gtk/gtk.h>
30 #include <ticalcs.h>
31 #include <tilem.h>
32 
33 #include "gui.h"
34 #include "files.h"
35 #include "msgbox.h"
36 
37 #ifndef CONFIG_FILE
38 #define CONFIG_FILE "config.ini"
39 #endif
40 
41 #define MAX_RECENT_FILES 10
42 
43 /* Store a filename in a GKeyFile.  Any control characters or
44    non-UTF-8 filenames are stored in octal.  Note that
45    g_key_file_set/get_string() can't be used because they only allow
46    UTF-8 */
key_file_set_filename(GKeyFile * gkf,const char * group,const char * key,const char * value)47 static void key_file_set_filename(GKeyFile *gkf, const char *group,
48                                   const char *key, const char *value)
49 {
50 	char *escaped;
51 	const char *p;
52 	char *q;
53 	gunichar uc;
54 	int b;
55 
56 	q = escaped = g_new(char, strlen(value) * 4 + 1);
57 
58 	while (*value != 0) {
59 		uc = g_utf8_get_char_validated(value, -1);
60 		if (uc < 0x20 || uc == 0x7F || !g_unichar_validate(uc)) {
61 			b = (unsigned char) *value;
62 			q[0] = '\\';
63 			q[1] = '0' + (b >> 6);
64 			q[2] = '0' + ((b >> 3) & 7);
65 			q[3] = '0' + (b & 7);
66 			q += 4;
67 			value++;
68 		}
69 		else if (uc == '\\') {
70 			q[0] = q[1] = '\\';
71 			q += 2;
72 			value++;
73 		}
74 		else {
75 			p = g_utf8_next_char(value);
76 			while (value != p)
77 				*q++ = *value++;
78 		}
79 	}
80 
81 	*q = 0;
82 
83 	g_key_file_set_value(gkf, group, key, escaped);
84 	g_free(escaped);
85 }
86 
87 /* Retrieve a filename from a GKeyFile. */
key_file_get_filename(GKeyFile * gkf,const char * group,const char * key,GError ** error)88 static char *key_file_get_filename(GKeyFile *gkf, const char *group,
89                                    const char *key, GError **error)
90 {
91 	char *value, *unescaped;
92 
93 	value = g_key_file_get_value(gkf, group, key, error);
94 	if (!value)
95 		return NULL;
96 
97 	unescaped = g_strcompress(value);
98 	g_free(value);
99 	return unescaped;
100 }
101 
102 /* Load and parse the configuration file. */
load_config(gboolean writable)103 static GKeyFile *load_config(gboolean writable)
104 {
105 	static gboolean warned;
106 	GKeyFile *gkf;
107 	GKeyFileFlags flags;
108 	char *cfname, *dname;
109 	GError *err = NULL;
110 
111 	gkf = g_key_file_new();
112 
113 	cfname = get_shared_file_path(CONFIG_FILE, NULL);
114 	if (!cfname)
115 		return gkf;
116 
117 	if (writable)
118 		flags = (G_KEY_FILE_KEEP_COMMENTS
119 		         | G_KEY_FILE_KEEP_TRANSLATIONS);
120 	else
121 		flags = 0;
122 
123 	if (!g_key_file_load_from_file(gkf, cfname, flags, &err)) {
124 		/* don't bother the user more than once */
125 		if (!warned) {
126 			dname = g_filename_display_name(cfname);
127 			messagebox02(NULL, GTK_MESSAGE_ERROR,
128 			             "Unable to read settings",
129 			             "An error occurred while reading %s: %s",
130 			             dname, err->message);
131 			g_free(dname);
132 			warned = TRUE;
133 		}
134 		g_error_free(err);
135 	}
136 
137 	g_free(cfname);
138 	return gkf;
139 }
140 
141 /* Save the configuration file. */
save_config(GKeyFile * gkf)142 static void save_config(GKeyFile *gkf)
143 {
144 	static gboolean warned;
145 	char *cfname, *dname;
146 	char *data;
147 	gsize length;
148 	GError *err = NULL;
149 
150 	data = g_key_file_to_data(gkf, &length, NULL);
151 
152 	cfname = get_config_file_path(CONFIG_FILE, NULL);
153 
154 	if (!g_file_set_contents(cfname, data, length, &err)) {
155 		/* don't bother the user more than once */
156 		if (!warned) {
157 			dname = g_filename_display_name(cfname);
158 			messagebox02(NULL, GTK_MESSAGE_ERROR,
159 			             "Unable to save settings",
160 			             "An error occurred while writing %s: %s",
161 			             dname, err->message);
162 			g_free(dname);
163 			warned = TRUE;
164 		}
165 		g_error_free(err);
166 	}
167 
168 	g_free(cfname);
169 	g_free(data);
170 }
171 
172 /* Retrieve settings from the configuration file. */
tilem_config_get(const char * group,const char * option,...)173 void tilem_config_get(const char *group, const char *option, ...)
174 {
175 	va_list ap;
176 	GKeyFile *gkf;
177 	const char *type, *defvalue;
178 	GError *err = NULL;
179 	char *key, *p;
180 	char **strp;
181 	int *intp;
182 	double *dblp;
183 	GdkColor *colorp;
184 
185 	g_return_if_fail(group != NULL);
186 	g_return_if_fail(option != NULL);
187 
188 	gkf = load_config(FALSE);
189 
190 	va_start(ap, option);
191 	while (option != NULL) {
192 		type = strrchr(option, '/');
193 		if (type == NULL || type[1] == 0
194 		    || (type[2] != 0 && type[2] != '=')) {
195 			g_critical("invalid argument\n");
196 			break;
197 		}
198 
199 		if (type[2] == '=')
200 			defvalue = &type[3];
201 		else
202 			defvalue = NULL;
203 
204 		key = g_strndup(option, type - option);
205 
206 		if (type[1] == 'f') {
207 			strp = va_arg(ap, char **);
208 			*strp = key_file_get_filename(gkf, group, key, &err);
209 			if (err && defvalue)
210 				*strp = g_strdup(defvalue);
211 		}
212 		else if (type[1] == 's') {
213 			strp = va_arg(ap, char **);
214 			*strp = g_key_file_get_string(gkf, group, key, &err);
215 			if (err && defvalue)
216 				*strp = g_strdup(defvalue);
217 		}
218 		else if (type[1] == 'i') {
219 			intp = va_arg(ap, int *);
220 			*intp = g_key_file_get_integer(gkf, group, key, &err);
221 			if (err && defvalue)
222 				*intp = g_ascii_strtoll(defvalue, NULL, 10);
223 		}
224 		else if (type[1] == 'r') {
225 			dblp = va_arg(ap, double *);
226 			*dblp = g_key_file_get_double(gkf, group, key, &err);
227 			if (err && defvalue)
228 				*dblp = g_ascii_strtod(defvalue, NULL);
229 		}
230 		else if (type[1] == 'b') {
231 			intp = va_arg(ap, int *);
232 			*intp = g_key_file_get_boolean(gkf, group, key, &err);
233 			if (err && defvalue)
234 				*intp = g_ascii_strtoll(defvalue, NULL, 10);
235 		}
236 		else if (type[1] == 'c') {
237 			colorp = va_arg(ap, GdkColor *);
238 			p = g_key_file_get_string(gkf, group, key, &err);
239 			if (p == NULL || !gdk_color_parse(p, colorp)) {
240 				if (defvalue) {
241 					gdk_color_parse(defvalue, colorp);
242 				}
243 				else {
244 					colorp->red = 0;
245 					colorp->green = 0;
246 					colorp->blue = 0;
247 				}
248 			}
249 			g_free(p);
250 		}
251 		else {
252 			g_critical("invalid argument\n");
253 			g_free(key);
254 			break;
255 		}
256 
257 		g_clear_error(&err);
258 		g_free(key);
259 		option = va_arg(ap, const char *);
260 	}
261 	va_end(ap);
262 
263 	g_key_file_free(gkf);
264 }
265 
266 /* Save settings to the configuration file. */
tilem_config_set(const char * group,const char * option,...)267 void tilem_config_set(const char *group, const char *option, ...)
268 {
269 	va_list ap;
270 	GKeyFile *gkf;
271 	const char *type;
272 	char *key;
273 	const char *strv;
274 	int intv;
275 	double dblv;
276 	const GdkColor *colorv;
277 	char *p;
278 
279 	g_return_if_fail(group != NULL);
280 	g_return_if_fail(option != NULL);
281 
282 	gkf = load_config(TRUE);
283 
284 	va_start(ap, option);
285 	while (option != NULL) {
286 		type = strrchr(option, '/');
287 		if (type == NULL || type[1] == 0 || type[2] != 0) {
288 			g_critical("invalid argument\n");
289 			break;
290 		}
291 
292 		key = g_strndup(option, type - option);
293 
294 		if (type[1] == 'f') {
295 			strv = va_arg(ap, const char *);
296 			key_file_set_filename(gkf, group, key, strv);
297 		}
298 		else if (type[1] == 's') {
299 			strv = va_arg(ap, const char *);
300 			g_key_file_set_string(gkf, group, key, strv);
301 		}
302 		else if (type[1] == 'i') {
303 			intv = va_arg(ap, int);
304 			g_key_file_set_integer(gkf, group, key, intv);
305 		}
306 		else if (type[1] == 'r') {
307 			dblv = va_arg(ap, double);
308 			g_key_file_set_double(gkf, group, key, dblv);
309 		}
310 		else if (type[1] == 'b') {
311 			intv = va_arg(ap, int);
312 			g_key_file_set_boolean(gkf, group, key, !!intv);
313 		}
314 		else if (type[1] == 'c') {
315 			colorv = va_arg(ap, const GdkColor *);
316 			p = g_strdup_printf("#%02x%02x%02x",
317 			                    colorv->red >> 8,
318 			                    colorv->green >> 8,
319 			                    colorv->blue >> 8);
320 			g_key_file_set_string(gkf, group, key, p);
321 			g_free(p);
322 		}
323 		else {
324 			g_critical("invalid argument\n");
325 			g_free(key);
326 			break;
327 		}
328 
329 		g_free(key);
330 
331 		option = va_arg(ap, const char *);
332 	}
333 	va_end(ap);
334 
335 	save_config(gkf);
336 	g_key_file_free(gkf);
337 }
338 
339