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