1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2010-2011 Thibault Duponchelle
5  * Copyright (c) 2010-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 <stdlib.h>
27 #include <string.h>
28 #include <gtk/gtk.h>
29 #include <ticalcs.h>
30 #include <tilem.h>
31 
32 #include "gui.h"
33 #include "emucore.h"
34 #include "filedlg.h"
35 #include "ti81prg.h"
36 
37 
38 /* Send a series of files */
send_files(TilemCalcEmulator * emu,char ** filenames,int * slots)39 static void send_files(TilemCalcEmulator *emu, char **filenames, int *slots)
40 {
41 	int i;
42 
43 	for (i = 0; filenames[i]; i++) {
44 		tilem_link_send_file(emu, filenames[i],
45 		                     slots ? slots[i] : -1,
46 		                     (i == 0),
47 		                     (filenames[i + 1] == NULL));
48 
49 		/* FIXME: macros should record slot numbers */
50 		if (emu->isMacroRecording)
51 			tilem_macro_add_action(emu->macro, 1, filenames[i]);
52 	}
53 }
54 
string_to_slot(const char * str)55 static int string_to_slot(const char *str)
56 {
57 	if (!g_ascii_strncasecmp(str, "prgm", 4))
58 		str += 4;
59 	else if (!g_ascii_strncasecmp(str, "ti81_", 5))
60 		str += 5;
61 	else
62 		return TI81_SLOT_AUTO;
63 
64 	if (g_ascii_isdigit(str[0]) && !g_ascii_isalnum(str[1]))
65 		return TI81_SLOT_0 + str[0] - '0';
66 	else if (g_ascii_isalpha(str[0]) && !g_ascii_isalnum(str[1]))
67 		return TI81_SLOT_A + g_ascii_toupper(str[0]) - 'A';
68 	else if (str[0] == '@'
69 	         || !g_ascii_strncasecmp(str, "theta", 5)
70 	         || !strncmp(str, "\316\270", 2)
71 	         || !strncmp(str, "\316\230", 2))
72 		return TI81_SLOT_THETA;
73 	else
74 		return TI81_SLOT_AUTO;
75 }
76 
77 /* Guess program slot for a filename */
guess_slot(const char * filename)78 static int guess_slot(const char *filename)
79 {
80 	char *base;
81 	int slot;
82 	base = g_filename_display_basename(filename);
83 	slot = string_to_slot(base);
84 	g_free(base);
85 	return slot;
86 }
87 
display_index_to_slot(int i)88 static int display_index_to_slot(int i)
89 {
90 	if (i < 9)
91 		return i + 1;
92 	else if (i == 9)
93 		return 0;
94 	else
95 		return i;
96 }
97 
98 struct slotdialog {
99 	int nfiles;
100 	char **filenames;
101 	int *slots;
102 	TI81ProgInfo info[TI81_SLOT_MAX + 1];
103 	GtkTreeModel *prgm_model;
104 	GtkTreeModel *slot_model;
105 };
106 
slot_edited(G_GNUC_UNUSED GtkCellRendererText * cell,gchar * pathstr,gchar * text,gpointer data)107 static void slot_edited(G_GNUC_UNUSED GtkCellRendererText *cell,
108                         gchar *pathstr, gchar *text, gpointer data)
109 {
110 	struct slotdialog *slotdlg = data;
111 	GtkTreeIter iter;
112 	int n, slot;
113 	char *end;
114 
115 	slot = string_to_slot(text);
116 	if (slot < 0)
117 		return;
118 
119 	n = strtol(pathstr, &end, 10);
120 	gtk_tree_model_iter_nth_child(slotdlg->prgm_model, &iter, NULL, n);
121 	gtk_list_store_set(GTK_LIST_STORE(slotdlg->prgm_model),
122 	                   &iter, 1, text, -1);
123 
124 	slotdlg->slots[n] = slot;
125 }
126 
127 /* Prompt user to assign program slots to filenames */
prompt_program_slots(TilemCalcEmulator * emu,struct slotdialog * slotdlg)128 static void prompt_program_slots(TilemCalcEmulator *emu,
129                                  struct slotdialog *slotdlg)
130 {
131 	GtkWidget *parent, *dlg, *vbox, *vbox2, *sw, *tv, *lbl;
132 	GtkListStore *prgmstore, *slotstore;
133 	GtkTreeIter iter;
134 	GtkCellRenderer *cell;
135 	GtkTreeViewColumn *col;
136 	int i, j, slot;
137 	int used[TI81_SLOT_MAX + 1];
138 	char *slotstr, *namestr;
139 	char *slotlabel[TI81_SLOT_MAX + 1];
140 
141 	if (emu->ewin)
142 		parent = emu->ewin->window;
143 	else
144 		parent = NULL;
145 
146 	/* Generate list of existing programs */
147 
148 	slotstore = gtk_list_store_new(1, G_TYPE_STRING);
149 	slotdlg->slot_model = GTK_TREE_MODEL(slotstore);
150 
151 	for (i = 0; i <= TI81_SLOT_MAX; i++) {
152 		slot = display_index_to_slot(i);
153 		slotstr = ti81_program_slot_to_string(slot);
154 		namestr = ti81_program_name_to_string(slotdlg->info[slot].name);
155 
156 		if (slotdlg->info[slot].size == 0) {
157 			slotlabel[slot] = g_strdup(slotstr);
158 			used[slot] = 0;
159 		}
160 		else if (namestr && namestr[0]) {
161 			slotlabel[slot] = g_strdup_printf("%s (in use: %s)",
162 			                                  slotstr, namestr);
163 			used[slot] = 1;
164 		}
165 		else {
166 			slotlabel[slot] = g_strdup_printf("%s (in use)", slotstr);
167 			used[slot] = 1;
168 		}
169 
170 		gtk_list_store_append(slotstore, &iter);
171 		gtk_list_store_set(slotstore, &iter, 0, slotlabel[slot], -1);
172 		g_free(slotstr);
173 		g_free(namestr);
174 	}
175 
176 	/* Assign default slots to files */
177 
178 	for (i = 0; i < slotdlg->nfiles; i++) {
179 		slot = guess_slot(slotdlg->filenames[i]);
180 		if (slotdlg->slots[i] < 0)
181 			slotdlg->slots[i] = slot;
182 		if (slot >= 0)
183 			used[slot] = 1;
184 	}
185 
186 	for (i = 0; i < slotdlg->nfiles; i++) {
187 		if (slotdlg->slots[i] < 0) {
188 			for (j = 0; j <= TI81_SLOT_MAX; j++) {
189 				slot = display_index_to_slot(j);
190 				if (!used[slot]) {
191 					slotdlg->slots[i] = slot;
192 					used[slot] = 1;
193 					break;
194 				}
195 			}
196 		}
197 
198 		if (slotdlg->slots[i] < 0)
199 			slotdlg->slots[i] = TI81_SLOT_1;
200 	}
201 
202 	/* Generate list of filenames and assigned slots */
203 
204 	prgmstore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
205 	slotdlg->prgm_model = GTK_TREE_MODEL(prgmstore);
206 
207 	for (i = 0; i < slotdlg->nfiles; i++) {
208 		namestr = g_filename_display_basename(slotdlg->filenames[i]);
209 		slot = slotdlg->slots[i];
210 
211 		gtk_list_store_append(prgmstore, &iter);
212 		gtk_list_store_set(prgmstore, &iter,
213 		                   0, namestr,
214 		                   1, slotlabel[slot],
215 		                   -1);
216 		g_free(namestr);
217 	}
218 
219 	for (i = 0; i <= TI81_SLOT_MAX; i++)
220 		g_free(slotlabel[i]);
221 
222 	/* Create tree view */
223 
224 	tv = gtk_tree_view_new_with_model(slotdlg->prgm_model);
225 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), TRUE);
226 
227 	cell = gtk_cell_renderer_text_new();
228 	col = gtk_tree_view_column_new_with_attributes
229 		("File", cell, "text", 0, NULL);
230 	gtk_tree_view_column_set_expand(col, TRUE);
231 	gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col);
232 
233 	cell = gtk_cell_renderer_combo_new();
234 	g_object_set(cell, "model", slotstore, "text-column", 0,
235 	             "editable", TRUE, "has-entry", FALSE, NULL);
236 	col = gtk_tree_view_column_new_with_attributes
237 		("Slot", cell, "text", 1, NULL);
238 	gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col);
239 
240 	g_signal_connect(cell, "edited", G_CALLBACK(slot_edited), slotdlg);
241 
242 	/* Create dialog */
243 
244 	dlg = gtk_dialog_new_with_buttons("Select Program Slots",
245 	                                  GTK_WINDOW(parent), GTK_DIALOG_MODAL,
246 	                                  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
247 	                                  GTK_STOCK_OK, GTK_RESPONSE_OK,
248 	                                  NULL);
249 	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
250 	                                        GTK_RESPONSE_OK,
251 	                                        GTK_RESPONSE_CANCEL,
252 	                                        -1);
253 	gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
254 
255 	gtk_window_set_default_size(GTK_WINDOW(dlg), -1, 250);
256 
257 	sw = gtk_scrolled_window_new(NULL, NULL);
258 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
259 	                               GTK_POLICY_NEVER,
260 	                               GTK_POLICY_AUTOMATIC);
261 	gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
262 	                                    GTK_SHADOW_IN);
263 	gtk_container_add(GTK_CONTAINER(sw), tv);
264 
265 	vbox = gtk_vbox_new(FALSE, 6);
266 	gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
267 
268 	lbl = gtk_label_new("Select a slot where each program should be"
269 	                    " loaded.  If a program slot is already in use,"
270 	                    " its contents will be overwritten.");
271 	gtk_misc_set_alignment(GTK_MISC(lbl), 0.0, 0.0);
272 	gtk_label_set_line_wrap(GTK_LABEL(lbl), TRUE);
273 	gtk_label_set_width_chars(GTK_LABEL(lbl), 45);
274 	gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 0);
275 
276 	gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
277 	gtk_widget_show_all(vbox);
278 
279 	vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
280 	gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
281 
282 	if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK)
283 		send_files(emu, slotdlg->filenames, slotdlg->slots);
284 
285 	gtk_widget_destroy(dlg);
286 }
287 
288 /* Check status of existing programs */
check_prog_slots_main(TilemCalcEmulator * emu,gpointer data)289 static gboolean check_prog_slots_main(TilemCalcEmulator *emu, gpointer data)
290 {
291 	struct slotdialog *slotdlg = data;
292 	int i;
293 
294 	tilem_em_wake_up(emu, TRUE);
295 	for (i = 0; i <= TI81_SLOT_MAX; i++)
296 		ti81_get_program_info(emu->calc, i, &slotdlg->info[i]);
297 
298 	return TRUE;
299 }
300 
check_prog_slots_finished(TilemCalcEmulator * emu,gpointer data,gboolean cancelled)301 static void check_prog_slots_finished(TilemCalcEmulator *emu, gpointer data,
302                                       gboolean cancelled)
303 {
304 	struct slotdialog *slotdlg = data;
305 
306 	if (!cancelled)
307 		prompt_program_slots(emu, slotdlg);
308 
309 	g_free(slotdlg->slots);
310 	g_strfreev(slotdlg->filenames);
311 	g_slice_free(struct slotdialog, slotdlg);
312 }
313 
314 
315 #define PAT_TI81       "*.prg"
316 #define PAT_TI73       "*.73?"
317 #define PAT_TI73_NUM   "*.73n;*.73l;*.73m;*.73i"
318 #define PAT_TI82       "*.82?"
319 #define PAT_TI82_NUM   "*.82n;*.82l;*.82m;*.82i"
320 #define PAT_TI82_TEXT  "*.82s;*.82y;*.82p"
321 #define PAT_TI83       "*.83?"
322 #define PAT_TI83_NUM   "*.83n;*.83l;*.83m;*.83i"
323 #define PAT_TI83_TEXT  "*.83s;*.83y;*.83p"
324 #define PAT_TI83P      "*.8x?;*.8xgrp"
325 #define PAT_TI83P_NUM  "*.8xn;*.8xl;*.8xm;*.8xi"
326 #define PAT_TI83P_TEXT "*.8xs;*.8xy;*.8xp"
327 #define PAT_TI85       "*.85?"
328 #define PAT_TI86       "*.86?"
329 #define PAT_TIG        "*.tig"
330 
331 #define FLT_TI81       "TI-81 programs", PAT_TI81
332 #define FLT_TI73       "TI-73 files", PAT_TI73
333 #define FLT_TI82       "TI-82 files", PAT_TI82
334 #define FLT_TI83       "TI-83 files", PAT_TI83
335 #define FLT_TI83P      "TI-83 Plus files", PAT_TI83P
336 #define FLT_TI85       "TI-85 files", PAT_TI85
337 #define FLT_TI86       "TI-86 files", PAT_TI86
338 #define FLT_TIG        "TIGroup files", PAT_TIG
339 #define FLT_ALL        "All files", "*"
340 
341 #define DESC_COMPAT "All compatible files"
342 
343 #define FLT_TI73_COMPAT    DESC_COMPAT, (PAT_TI73 ";" PAT_TIG ";" \
344                                          PAT_TI82_NUM ";" \
345                                          PAT_TI83_NUM ";" \
346                                          PAT_TI83P_NUM)
347 
348 #define FLT_TI82_COMPAT    DESC_COMPAT, (PAT_TI82 ";" PAT_TIG ";" \
349                                          PAT_TI83_TEXT ";" PAT_TI83_NUM ";" \
350                                          PAT_TI83P_TEXT ";" PAT_TI83P_NUM ";" \
351                                          PAT_TI73_NUM)
352 
353 #define FLT_TI83_COMPAT    DESC_COMPAT, (PAT_TI83 ";" PAT_TIG ";" \
354                                          PAT_TI82_TEXT ";" PAT_TI82_NUM ";" \
355                                          PAT_TI83P_TEXT ";" PAT_TI83P_NUM ";" \
356                                          PAT_TI73_NUM)
357 
358 #define FLT_TI83P_COMPAT   DESC_COMPAT, (PAT_TI83P ";" PAT_TIG ";" \
359                                          PAT_TI82_TEXT ";" PAT_TI82_NUM ";" \
360                                          PAT_TI83_TEXT ";" PAT_TI83_NUM ";" \
361                                          PAT_TI73_NUM)
362 
363 #define FLT_TI8586_COMPAT  DESC_COMPAT, (PAT_TI85 ";" PAT_TI86 ";" PAT_TIG)
364 
prompt_link_files(const char * title,GtkWindow * parent,const char * dir,int model)365 static char ** prompt_link_files(const char *title,
366                                  GtkWindow *parent,
367                                  const char *dir,
368                                  int model)
369 {
370 	switch (model) {
371 	case TILEM_CALC_TI73:
372 		return prompt_open_files(title, parent, dir,
373 		                         FLT_TI73_COMPAT, FLT_TI73,
374 		                         FLT_TI82, FLT_TI83, FLT_TI83P,
375 		                         FLT_TIG, FLT_ALL, NULL);
376 	case TILEM_CALC_TI81:
377 		return prompt_open_files(title, parent, dir,
378 		                         FLT_TI81, FLT_ALL, NULL);
379 	case TILEM_CALC_TI82:
380 		return prompt_open_files(title, parent, dir,
381 		                         FLT_TI82_COMPAT, FLT_TI73,
382 		                         FLT_TI82, FLT_TI83, FLT_TI83P,
383 		                         FLT_TIG, FLT_ALL, NULL);
384 	case TILEM_CALC_TI83:
385 	case TILEM_CALC_TI76:
386 		return prompt_open_files(title, parent, dir,
387 		                         FLT_TI83_COMPAT, FLT_TI73,
388 		                         FLT_TI82, FLT_TI83, FLT_TI83P,
389 		                         FLT_TIG, FLT_ALL, NULL);
390 	case TILEM_CALC_TI83P:
391 	case TILEM_CALC_TI83P_SE:
392 	case TILEM_CALC_TI84P:
393 	case TILEM_CALC_TI84P_SE:
394 	case TILEM_CALC_TI84P_NSPIRE:
395 		return prompt_open_files(title, parent, dir,
396 		                         FLT_TI83P_COMPAT, FLT_TI73,
397 		                         FLT_TI82, FLT_TI83, FLT_TI83P,
398 		                         FLT_TIG, FLT_ALL, NULL);
399 	case TILEM_CALC_TI85:
400 	case TILEM_CALC_TI86:
401 		return prompt_open_files(title, parent, dir,
402 		                         FLT_TI8586_COMPAT, FLT_TI85,
403 		                         FLT_TI86, FLT_TIG, FLT_ALL, NULL);
404 	default:
405 		return prompt_open_files(title, parent, dir, FLT_ALL, NULL);
406 	}
407 }
408 
409 /* Load a list of files through the GUI.  The list of filenames must
410    end with NULL. */
load_files(TilemEmulatorWindow * ewin,char ** filenames)411 void load_files(TilemEmulatorWindow *ewin, char **filenames)
412 {
413 	struct slotdialog *slotdlg;
414 	int i;
415 
416 	g_return_if_fail(ewin->emu->calc != NULL);
417 
418 	if (ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
419 		slotdlg = g_slice_new0(struct slotdialog);
420 		slotdlg->filenames = g_strdupv(filenames);
421 		slotdlg->nfiles = g_strv_length(filenames);
422 		slotdlg->slots = g_new(int, slotdlg->nfiles);
423 		for (i = 0; i < slotdlg->nfiles; i++)
424 			slotdlg->slots[i] = TI81_SLOT_AUTO;
425 		tilem_calc_emulator_begin(ewin->emu, &check_prog_slots_main,
426 		                          &check_prog_slots_finished, slotdlg);
427 	}
428 	else {
429 		send_files(ewin->emu, filenames, NULL);
430 	}
431 }
432 
get_cmdline_slot(const char * str,const char ** name)433 static int get_cmdline_slot(const char *str, const char **name)
434 {
435 	char *e;
436 	int n;
437 
438 	n = strtol(str, &e, 10);
439 	if (*e == '=') {
440 		*name = e + 1;
441 		return n;
442 	}
443 
444 	if (g_ascii_isalpha(str[0]) && str[1] == '=') {
445 		*name = str + 2;
446 		return TI81_SLOT_A + g_ascii_toupper(str[0]) - 'A';
447 	}
448 
449 	if (str[0] == '@' && str[1] == '=') {
450 		*name = str + 2;
451 		return TI81_SLOT_THETA;
452 	}
453 
454 	if (!g_ascii_strncasecmp(str, "theta=", 6)) {
455 		*name = str + 6;
456 		return TI81_SLOT_THETA;
457 	}
458 
459 	*name = str;
460 	return TI81_SLOT_AUTO;
461 }
462 
463 /* Load a list of files from the command line.  Filenames may begin
464    with an optional slot designation. */
load_files_cmdline(TilemEmulatorWindow * ewin,char ** filenames)465 void load_files_cmdline(TilemEmulatorWindow *ewin, char **filenames)
466 {
467 	struct slotdialog *slotdlg;
468 	int i;
469 	gboolean need_prompt = FALSE;
470 	const char *name;
471 
472 	g_return_if_fail(ewin->emu->calc != NULL);
473 
474 	slotdlg = g_slice_new0(struct slotdialog);
475 	slotdlg->nfiles = g_strv_length(filenames);
476 	slotdlg->slots = g_new(int, slotdlg->nfiles);
477 	slotdlg->filenames = g_new0(char *, slotdlg->nfiles + 1);
478 
479 	for (i = 0; i < slotdlg->nfiles; i++) {
480 		slotdlg->slots[i] = get_cmdline_slot(filenames[i], &name);
481 		slotdlg->filenames[i] = g_strdup(name);
482 
483 		if (slotdlg->slots[i] < 0)
484 			need_prompt = TRUE;
485 	}
486 
487 	if (need_prompt && ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
488 		tilem_calc_emulator_begin(ewin->emu, &check_prog_slots_main,
489 		                          &check_prog_slots_finished, slotdlg);
490 	}
491 	else {
492 		send_files(ewin->emu, slotdlg->filenames, slotdlg->slots);
493 		g_free(slotdlg->slots);
494 		g_strfreev(slotdlg->filenames);
495 		g_slice_free(struct slotdialog, slotdlg);
496 	}
497 }
498 
499 /* Prompt user to load a file */
load_file_dialog(TilemEmulatorWindow * ewin)500 void load_file_dialog(TilemEmulatorWindow *ewin)
501 {
502 	char **filenames, *dir;
503 
504 	tilem_config_get("upload",
505 	                 "sendfile_recentdir/f", &dir,
506 	                 NULL);
507 
508 	filenames = prompt_link_files("Send File",
509 	                              GTK_WINDOW(ewin->window),
510 	                              dir, ewin->emu->calc->hw.model_id);
511 	g_free(dir);
512 
513 	if (!filenames || !filenames[0]) {
514 		g_free(filenames);
515 		return;
516 	}
517 
518 	dir = g_path_get_dirname(filenames[0]);
519 	tilem_config_set("upload",
520 	                 "sendfile_recentdir/f", dir,
521 	                 NULL);
522 	g_free(dir);
523 
524 	load_files(ewin, filenames);
525 	g_strfreev(filenames);
526 }
527