1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2010-2011 Thibault Duponchelle
5  * Copyright (c) 2010 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 <string.h>
27 #include <errno.h>
28 #include <gtk/gtk.h>
29 #include <glib/gstdio.h>
30 #include <ticalcs.h>
31 #include <tilem.h>
32 
33 #include "gui.h"
34 
35 /* Create a frame around the given widget, with a boldface label in
36    the GNOME style */
new_frame(const gchar * label,GtkWidget * contents)37 GtkWidget* new_frame(const gchar* label, GtkWidget* contents)
38 {
39 	GtkWidget *frame, *align;
40 	char *str;
41 
42 	str = g_strconcat("<b>", label, "</b>", NULL);
43 	frame = gtk_frame_new(str);
44 	g_free(str);
45 
46 	g_object_set(gtk_frame_get_label_widget(GTK_FRAME(frame)),
47 	             "use-markup", TRUE, NULL);
48 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
49 
50 	align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
51 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 6, 0, 12, 0);
52 	gtk_widget_show(align);
53 	gtk_container_add(GTK_CONTAINER(frame), align);
54 	gtk_container_add(GTK_CONTAINER(align), contents);
55 	gtk_widget_show(frame);
56 
57 	return frame;
58 }
59 
60 /* Get model name (abbreviation) for a TilEm model ID. */
model_to_name(int model)61 const char * model_to_name(int model)
62 {
63 	const TilemHardware **models;
64 	int nmodels, i;
65 
66 	tilem_get_supported_hardware(&models, &nmodels);
67 	for (i = 0; i < nmodels; i++)
68 		if (models[i]->model_id == model)
69 			return models[i]->name;
70 
71 	return NULL;
72 }
73 
74 /* Convert model name to a model ID. */
name_to_model(const char * name)75 int name_to_model(const char *name)
76 {
77 	char *s;
78 	const TilemHardware **models;
79 	int nmodels, i, j;
80 
81 	s = g_new(char, strlen(name) + 1);
82 	for (i = j = 0; name[i]; i++) {
83 		if (name[i] == '+')
84 			s[j++] = 'p';
85 		else if (name[i] != '-')
86 			s[j++] = g_ascii_tolower(name[i]);
87 	}
88 	s[j] = 0;
89 
90 	tilem_get_supported_hardware(&models, &nmodels);
91 	for (i = 0; i < nmodels; i++) {
92 		if (!strcmp(s, models[i]->name)) {
93 			g_free(s);
94 			return models[i]->model_id;
95 		}
96 	}
97 
98 	g_free(s);
99 	return 0;
100 }
101 
102 /* Convert TilEm model ID to tifiles2 model ID. */
model_to_calcmodel(int model)103 CalcModel model_to_calcmodel(int model)
104 {
105 	switch (model) {
106 	case TILEM_CALC_TI73:
107 		return CALC_TI73;
108 
109 	case TILEM_CALC_TI82:
110 		return CALC_TI82;
111 
112 	case TILEM_CALC_TI83:
113 	case TILEM_CALC_TI76:
114 		return CALC_TI83;
115 
116 	case TILEM_CALC_TI83P:
117 	case TILEM_CALC_TI83P_SE:
118 		return CALC_TI83P;
119 
120 	case TILEM_CALC_TI84P:
121 	case TILEM_CALC_TI84P_SE:
122 	case TILEM_CALC_TI84P_NSPIRE:
123 		return CALC_TI84P;
124 
125 	case TILEM_CALC_TI85:
126 		return CALC_TI85;
127 
128 	case TILEM_CALC_TI86:
129 		return CALC_TI86;
130 
131 	default:
132 		return CALC_NONE;
133 	}
134 }
135 
136 /* Convert tifiles2 model ID to TilEm model ID. */
calcmodel_to_model(CalcModel model)137 int calcmodel_to_model(CalcModel model)
138 {
139 	switch (model) {
140 	case CALC_TI73:
141 		return TILEM_CALC_TI73;
142 	case CALC_TI82:
143 		return TILEM_CALC_TI82;
144 	case CALC_TI83:
145 		return TILEM_CALC_TI83;
146 	case CALC_TI83P:
147 		return TILEM_CALC_TI83P;
148 	case CALC_TI84P:
149 		return TILEM_CALC_TI84P;
150 	case CALC_TI85:
151 		return TILEM_CALC_TI85;
152 	case CALC_TI86:
153 		return TILEM_CALC_TI86;
154 	default:
155 		return 0;
156 	}
157 }
158 
159 /* Get model ID for a given file. */
file_to_model(const char * name)160 int file_to_model(const char *name)
161 {
162 	const char *p;
163 	TigContent *tig;
164 	int model;
165 
166 	p = strrchr(name, '.');
167 	if (!p || strlen(p) < 4 || strchr(p, '/') || strchr(p, '\\'))
168 		return 0;
169 	p++;
170 
171 	if (!g_ascii_strcasecmp(p, "prg"))
172 		return TILEM_CALC_TI81;
173 
174 	if (!g_ascii_strncasecmp(p, "73", 2))
175 		return TILEM_CALC_TI73;
176 	if (!g_ascii_strncasecmp(p, "82", 2))
177 		return TILEM_CALC_TI82;
178 	if (!g_ascii_strncasecmp(p, "83", 2))
179 		return TILEM_CALC_TI83;
180 	if (!g_ascii_strncasecmp(p, "8x", 2))
181 		return TILEM_CALC_TI83P;
182 	if (!g_ascii_strncasecmp(p, "85", 2))
183 		return TILEM_CALC_TI85;
184 	if (!g_ascii_strncasecmp(p, "86", 2))
185 		return TILEM_CALC_TI86;
186 
187 	if (!g_ascii_strcasecmp(p, "tig")
188 	    || !g_ascii_strcasecmp(p, "zip")) {
189 		/* read file and see what tifiles thinks the type is */
190 		tig = tifiles_content_create_tigroup(CALC_NONE, 0);
191 		tifiles_file_read_tigroup(name, tig);
192 		model = calcmodel_to_model(tig->model);
193 		tifiles_content_delete_tigroup(tig);
194 		return model;
195 	}
196 
197 	return 0;
198 }
199 
200 /* Get "base" model for file type support. */
model_to_base_model(int calc_model)201 int model_to_base_model(int calc_model)
202 {
203 	switch (calc_model) {
204 	case TILEM_CALC_TI83:
205 	case TILEM_CALC_TI76:
206 		return TILEM_CALC_TI83;
207 
208 	case TILEM_CALC_TI83P:
209 	case TILEM_CALC_TI83P_SE:
210 	case TILEM_CALC_TI84P:
211 	case TILEM_CALC_TI84P_SE:
212 	case TILEM_CALC_TI84P_NSPIRE:
213 		return TILEM_CALC_TI83P;
214 
215 	default:
216 		return calc_model;
217 	}
218 }
219 
220 /* Check if calc is compatible with given file type. */
model_supports_file(int calc_model,int file_model)221 gboolean model_supports_file(int calc_model, int file_model)
222 {
223 	calc_model = model_to_base_model(calc_model);
224 	file_model = model_to_base_model(file_model);
225 
226 	if (file_model == calc_model)
227 		return TRUE;
228 
229 	if (file_model == TILEM_CALC_TI82
230 	    && (calc_model == TILEM_CALC_TI83
231 	        || calc_model == TILEM_CALC_TI83P))
232 		return TRUE;
233 
234 	if (file_model == TILEM_CALC_TI83
235 	    && (calc_model == TILEM_CALC_TI83P))
236 		return TRUE;
237 
238 	if (file_model == TILEM_CALC_TI85
239 	    && (calc_model == TILEM_CALC_TI86))
240 		return TRUE;
241 
242 	return FALSE;
243 }
244 
245 /* A popup which is used to let the user choose the model at startup */
choose_rom_popup(GtkWidget * parent_window,const char * filename,char default_model)246 char choose_rom_popup(GtkWidget *parent_window, const char *filename,
247                       char default_model)
248 {
249 	const TilemHardware **models;
250 	GtkWidget *dlg, *vbox, *frame, *btn;
251 	GtkToggleButton **btns;
252 	char *ids, id = 0;
253 	int nmodels, noptions, i, j, defoption = 0, response;
254 	dword romsize;
255 	char *fn, *msg;
256 
257 	tilem_get_supported_hardware(&models, &nmodels);
258 
259 	/* determine ROM size for default model */
260 	for (i = 0; i < nmodels; i++)
261 		if (models[i]->model_id == default_model)
262 			break;
263 
264 	g_return_val_if_fail(i < nmodels, 0);
265 
266 	romsize = models[i]->romsize;
267 
268 	/* all other models with same ROM size are candidates */
269 	noptions = 0;
270 	for (i = 0; i < nmodels; i++) {
271 		if (models[i]->model_id == default_model)
272 			defoption = noptions;
273 		if (models[i]->romsize == romsize)
274 			noptions++;
275 	}
276 
277 	if (noptions < 2) /* no choice */
278 		return default_model;
279 
280 	dlg = gtk_dialog_new_with_buttons("Select Calculator Type",
281 	                                  GTK_WINDOW(parent_window),
282 	                                  GTK_DIALOG_MODAL,
283 	                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
284 	                                  GTK_STOCK_OK, GTK_RESPONSE_OK,
285 	                                  NULL);
286 	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
287 	                                        GTK_RESPONSE_OK,
288 	                                        GTK_RESPONSE_CANCEL,
289 	                                        -1);
290 	gtk_dialog_set_default_response(GTK_DIALOG(dlg),
291 	                                GTK_RESPONSE_OK);
292 
293 	vbox = gtk_vbox_new(TRUE, 0);
294 
295 	/* create radio buttons */
296 
297 	btns = g_new(GtkToggleButton*, noptions);
298 	ids = g_new(char, noptions);
299 	btn = NULL;
300 	for (i = j = 0; i < nmodels; i++) {
301 		if (models[i]->romsize == romsize) {
302 			btn = gtk_radio_button_new_with_label_from_widget
303 				(GTK_RADIO_BUTTON(btn), models[i]->desc);
304 			btns[j] = GTK_TOGGLE_BUTTON(btn);
305 			ids[j] = models[i]->model_id;
306 			gtk_box_pack_start(GTK_BOX(vbox), btn, TRUE, TRUE, 3);
307 			j++;
308 		}
309 	}
310 
311 	gtk_toggle_button_set_active(btns[defoption], TRUE);
312 
313 	fn = g_filename_display_basename(filename);
314 	msg = g_strdup_printf("Calculator type for %s:", fn);
315 	frame = new_frame(msg, vbox);
316 	g_free(fn);
317 	g_free(msg);
318 
319 	gtk_container_set_border_width(GTK_CONTAINER(frame), 6);
320 	gtk_widget_show_all(frame);
321 
322 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
323 	gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
324 
325 	response = gtk_dialog_run(GTK_DIALOG(dlg));
326 
327 	if (response == GTK_RESPONSE_OK) {
328 		for (i = 0; i < noptions; i++) {
329 			if (gtk_toggle_button_get_active(btns[i])) {
330 				id = ids[i];
331 				break;
332 			}
333 		}
334 	}
335 	else {
336 		id = 0;
337 	}
338 
339 	gtk_widget_destroy(dlg);
340 	g_free(btns);
341 	g_free(ids);
342 
343 	return id;
344 }
345 
346 /* Convert UTF-8 to filename encoding.  Use ASCII digits in place of
347    subscripts if necessary.  If conversion fails utterly, fall back to
348    the UTF-8 name, which is broken but better than nothing. */
utf8_to_filename(const char * utf8str)349 char * utf8_to_filename(const char *utf8str)
350 {
351 	gchar *result, *ibuf, *obuf, *p;
352 	gsize icount, ocount;
353 	const gchar **charsets;
354 	GIConv ic;
355 	gunichar c;
356 
357 	if (g_get_filename_charsets(&charsets))
358 		return g_strdup(utf8str);
359 
360 	ic = g_iconv_open(charsets[0], "UTF-8");
361 	if (!ic) {
362 		g_warning("utf8_to_filename: unsupported charset %s",
363 		          charsets[0]);
364 		return g_strdup(utf8str);
365 	}
366 
367 	ibuf = (gchar*) utf8str;
368 	icount = strlen(utf8str);
369 	ocount = icount * 2; /* be generous */
370 	result = obuf = g_new(gchar, ocount + 1);
371 
372 	while (g_iconv(ic, &ibuf, &icount, &obuf, &ocount) == (gsize) -1) {
373 		if (errno != EILSEQ) {
374 			g_warning("utf8_to_filename: error in conversion");
375 			g_free(result);
376 			g_iconv_close(ic);
377 			return g_strdup(utf8str);
378 		}
379 
380 		c = g_utf8_get_char(ibuf);
381 		if (c >= 0x2080 && c <= 0x2089)
382 			*obuf = c - 0x2080 + '0';
383 		else
384 			*obuf = '_';
385 		obuf++;
386 		ocount--;
387 
388 		p = g_utf8_next_char(ibuf);
389 		icount -= p - ibuf;
390 		ibuf = p;
391 	}
392 
393 	*obuf = 0;
394 	g_iconv_close(ic);
395 	return result;
396 }
397 
398 /* Convert UTF-8 to a subset of UTF-8 that is compatible with the
399    locale */
utf8_to_restricted_utf8(const char * utf8str)400 char * utf8_to_restricted_utf8(const char *utf8str)
401 {
402 	char *p, *q;
403 	p = utf8_to_filename(utf8str);
404 	q = g_filename_to_utf8(p, -1, NULL, NULL, NULL);
405 	g_free(p);
406 	if (q)
407 		return q;
408 	else
409 		return g_strdup(utf8str);
410 }
411 
412 /* Generate default filename (UTF-8) for a variable */
get_default_filename(const TilemVarEntry * tve)413 char * get_default_filename(const TilemVarEntry *tve)
414 {
415 	GString *str = g_string_new("");
416 
417 	if (tve->slot_str) {
418 		g_string_append(str, tve->slot_str);
419 		if (tve->name_str && tve->name_str[0]) {
420 			g_string_append_c(str, '-');
421 			g_string_append(str, tve->name_str);
422 		}
423 	}
424 	else if (tve->name_str && tve->name_str[0]) {
425 		g_string_append(str, tve->name_str);
426 	}
427 	else {
428 		g_string_append(str, "untitled");
429 	}
430 	g_string_append_c(str, '.');
431 	g_string_append(str, tve->file_ext);
432 	return g_string_free(str, FALSE);
433 }
434