527 lines
15 KiB
C
527 lines
15 KiB
C
/*
|
|
* TilEm II
|
|
*
|
|
* Copyright (c) 2010-2011 Thibault Duponchelle
|
|
* Copyright (c) 2010-2011 Benjamin Moody
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <gtk/gtk.h>
|
|
#include <ticalcs.h>
|
|
#include <tilem.h>
|
|
|
|
#include "gui.h"
|
|
#include "emucore.h"
|
|
#include "filedlg.h"
|
|
#include "ti81prg.h"
|
|
|
|
|
|
/* Send a series of files */
|
|
static void send_files(TilemCalcEmulator *emu, char **filenames, int *slots)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; filenames[i]; i++) {
|
|
tilem_link_send_file(emu, filenames[i],
|
|
slots ? slots[i] : -1,
|
|
(i == 0),
|
|
(filenames[i + 1] == NULL));
|
|
|
|
/* FIXME: macros should record slot numbers */
|
|
if (emu->isMacroRecording)
|
|
tilem_macro_add_action(emu->macro, 1, filenames[i]);
|
|
}
|
|
}
|
|
|
|
static int string_to_slot(const char *str)
|
|
{
|
|
if (!g_ascii_strncasecmp(str, "prgm", 4))
|
|
str += 4;
|
|
else if (!g_ascii_strncasecmp(str, "ti81_", 5))
|
|
str += 5;
|
|
else
|
|
return TI81_SLOT_AUTO;
|
|
|
|
if (g_ascii_isdigit(str[0]) && !g_ascii_isalnum(str[1]))
|
|
return TI81_SLOT_0 + str[0] - '0';
|
|
else if (g_ascii_isalpha(str[0]) && !g_ascii_isalnum(str[1]))
|
|
return TI81_SLOT_A + g_ascii_toupper(str[0]) - 'A';
|
|
else if (str[0] == '@'
|
|
|| !g_ascii_strncasecmp(str, "theta", 5)
|
|
|| !strncmp(str, "\316\270", 2)
|
|
|| !strncmp(str, "\316\230", 2))
|
|
return TI81_SLOT_THETA;
|
|
else
|
|
return TI81_SLOT_AUTO;
|
|
}
|
|
|
|
/* Guess program slot for a filename */
|
|
static int guess_slot(const char *filename)
|
|
{
|
|
char *base;
|
|
int slot;
|
|
base = g_filename_display_basename(filename);
|
|
slot = string_to_slot(base);
|
|
g_free(base);
|
|
return slot;
|
|
}
|
|
|
|
static int display_index_to_slot(int i)
|
|
{
|
|
if (i < 9)
|
|
return i + 1;
|
|
else if (i == 9)
|
|
return 0;
|
|
else
|
|
return i;
|
|
}
|
|
|
|
struct slotdialog {
|
|
int nfiles;
|
|
char **filenames;
|
|
int *slots;
|
|
TI81ProgInfo info[TI81_SLOT_MAX + 1];
|
|
GtkTreeModel *prgm_model;
|
|
GtkTreeModel *slot_model;
|
|
};
|
|
|
|
static void slot_edited(G_GNUC_UNUSED GtkCellRendererText *cell,
|
|
gchar *pathstr, gchar *text, gpointer data)
|
|
{
|
|
struct slotdialog *slotdlg = data;
|
|
GtkTreeIter iter;
|
|
int n, slot;
|
|
char *end;
|
|
|
|
slot = string_to_slot(text);
|
|
if (slot < 0)
|
|
return;
|
|
|
|
n = strtol(pathstr, &end, 10);
|
|
gtk_tree_model_iter_nth_child(slotdlg->prgm_model, &iter, NULL, n);
|
|
gtk_list_store_set(GTK_LIST_STORE(slotdlg->prgm_model),
|
|
&iter, 1, text, -1);
|
|
|
|
slotdlg->slots[n] = slot;
|
|
}
|
|
|
|
/* Prompt user to assign program slots to filenames */
|
|
static void prompt_program_slots(TilemCalcEmulator *emu,
|
|
struct slotdialog *slotdlg)
|
|
{
|
|
GtkWidget *parent, *dlg, *vbox, *vbox2, *sw, *tv, *lbl;
|
|
GtkListStore *prgmstore, *slotstore;
|
|
GtkTreeIter iter;
|
|
GtkCellRenderer *cell;
|
|
GtkTreeViewColumn *col;
|
|
int i, j, slot;
|
|
int used[TI81_SLOT_MAX + 1];
|
|
char *slotstr, *namestr;
|
|
char *slotlabel[TI81_SLOT_MAX + 1];
|
|
|
|
if (emu->ewin)
|
|
parent = emu->ewin->window;
|
|
else
|
|
parent = NULL;
|
|
|
|
/* Generate list of existing programs */
|
|
|
|
slotstore = gtk_list_store_new(1, G_TYPE_STRING);
|
|
slotdlg->slot_model = GTK_TREE_MODEL(slotstore);
|
|
|
|
for (i = 0; i <= TI81_SLOT_MAX; i++) {
|
|
slot = display_index_to_slot(i);
|
|
slotstr = ti81_program_slot_to_string(slot);
|
|
namestr = ti81_program_name_to_string(slotdlg->info[slot].name);
|
|
|
|
if (slotdlg->info[slot].size == 0) {
|
|
slotlabel[slot] = g_strdup(slotstr);
|
|
used[slot] = 0;
|
|
}
|
|
else if (namestr && namestr[0]) {
|
|
slotlabel[slot] = g_strdup_printf("%s (in use: %s)",
|
|
slotstr, namestr);
|
|
used[slot] = 1;
|
|
}
|
|
else {
|
|
slotlabel[slot] = g_strdup_printf("%s (in use)", slotstr);
|
|
used[slot] = 1;
|
|
}
|
|
|
|
gtk_list_store_append(slotstore, &iter);
|
|
gtk_list_store_set(slotstore, &iter, 0, slotlabel[slot], -1);
|
|
g_free(slotstr);
|
|
g_free(namestr);
|
|
}
|
|
|
|
/* Assign default slots to files */
|
|
|
|
for (i = 0; i < slotdlg->nfiles; i++) {
|
|
slot = guess_slot(slotdlg->filenames[i]);
|
|
if (slotdlg->slots[i] < 0)
|
|
slotdlg->slots[i] = slot;
|
|
if (slot >= 0)
|
|
used[slot] = 1;
|
|
}
|
|
|
|
for (i = 0; i < slotdlg->nfiles; i++) {
|
|
if (slotdlg->slots[i] < 0) {
|
|
for (j = 0; j <= TI81_SLOT_MAX; j++) {
|
|
slot = display_index_to_slot(j);
|
|
if (!used[slot]) {
|
|
slotdlg->slots[i] = slot;
|
|
used[slot] = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (slotdlg->slots[i] < 0)
|
|
slotdlg->slots[i] = TI81_SLOT_1;
|
|
}
|
|
|
|
/* Generate list of filenames and assigned slots */
|
|
|
|
prgmstore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
|
|
slotdlg->prgm_model = GTK_TREE_MODEL(prgmstore);
|
|
|
|
for (i = 0; i < slotdlg->nfiles; i++) {
|
|
namestr = g_filename_display_basename(slotdlg->filenames[i]);
|
|
slot = slotdlg->slots[i];
|
|
|
|
gtk_list_store_append(prgmstore, &iter);
|
|
gtk_list_store_set(prgmstore, &iter,
|
|
0, namestr,
|
|
1, slotlabel[slot],
|
|
-1);
|
|
g_free(namestr);
|
|
}
|
|
|
|
for (i = 0; i <= TI81_SLOT_MAX; i++)
|
|
g_free(slotlabel[i]);
|
|
|
|
/* Create tree view */
|
|
|
|
tv = gtk_tree_view_new_with_model(slotdlg->prgm_model);
|
|
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tv), TRUE);
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
col = gtk_tree_view_column_new_with_attributes
|
|
("File", cell, "text", 0, NULL);
|
|
gtk_tree_view_column_set_expand(col, TRUE);
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col);
|
|
|
|
cell = gtk_cell_renderer_combo_new();
|
|
g_object_set(cell, "model", slotstore, "text-column", 0,
|
|
"editable", TRUE, "has-entry", FALSE, NULL);
|
|
col = gtk_tree_view_column_new_with_attributes
|
|
("Slot", cell, "text", 1, NULL);
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW(tv), col);
|
|
|
|
g_signal_connect(cell, "edited", G_CALLBACK(slot_edited), slotdlg);
|
|
|
|
/* Create dialog */
|
|
|
|
dlg = gtk_dialog_new_with_buttons("Select Program Slots",
|
|
GTK_WINDOW(parent), GTK_DIALOG_MODAL,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OK, GTK_RESPONSE_OK,
|
|
NULL);
|
|
gtk_dialog_set_alternative_button_order(GTK_DIALOG(dlg),
|
|
GTK_RESPONSE_OK,
|
|
GTK_RESPONSE_CANCEL,
|
|
-1);
|
|
gtk_dialog_set_default_response(GTK_DIALOG(dlg), GTK_RESPONSE_OK);
|
|
|
|
gtk_window_set_default_size(GTK_WINDOW(dlg), -1, 250);
|
|
|
|
sw = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
|
|
GTK_POLICY_NEVER,
|
|
GTK_POLICY_AUTOMATIC);
|
|
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
|
|
GTK_SHADOW_IN);
|
|
gtk_container_add(GTK_CONTAINER(sw), tv);
|
|
|
|
vbox = gtk_vbox_new(FALSE, 6);
|
|
gtk_container_set_border_width(GTK_CONTAINER(vbox), 6);
|
|
|
|
lbl = gtk_label_new("Select a slot where each program should be"
|
|
" loaded. If a program slot is already in use,"
|
|
" its contents will be overwritten.");
|
|
gtk_misc_set_alignment(GTK_MISC(lbl), 0.0, 0.0);
|
|
gtk_label_set_line_wrap(GTK_LABEL(lbl), TRUE);
|
|
gtk_label_set_width_chars(GTK_LABEL(lbl), 45);
|
|
gtk_box_pack_start(GTK_BOX(vbox), lbl, FALSE, FALSE, 0);
|
|
|
|
gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 0);
|
|
gtk_widget_show_all(vbox);
|
|
|
|
vbox2 = gtk_dialog_get_content_area(GTK_DIALOG(dlg));
|
|
gtk_box_pack_start(GTK_BOX(vbox2), vbox, TRUE, TRUE, 0);
|
|
|
|
if (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_OK)
|
|
send_files(emu, slotdlg->filenames, slotdlg->slots);
|
|
|
|
gtk_widget_destroy(dlg);
|
|
}
|
|
|
|
/* Check status of existing programs */
|
|
static gboolean check_prog_slots_main(TilemCalcEmulator *emu, gpointer data)
|
|
{
|
|
struct slotdialog *slotdlg = data;
|
|
int i;
|
|
|
|
tilem_em_wake_up(emu, TRUE);
|
|
for (i = 0; i <= TI81_SLOT_MAX; i++)
|
|
ti81_get_program_info(emu->calc, i, &slotdlg->info[i]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void check_prog_slots_finished(TilemCalcEmulator *emu, gpointer data,
|
|
gboolean cancelled)
|
|
{
|
|
struct slotdialog *slotdlg = data;
|
|
|
|
if (!cancelled)
|
|
prompt_program_slots(emu, slotdlg);
|
|
|
|
g_free(slotdlg->slots);
|
|
g_strfreev(slotdlg->filenames);
|
|
g_slice_free(struct slotdialog, slotdlg);
|
|
}
|
|
|
|
|
|
#define PAT_TI81 "*.prg"
|
|
#define PAT_TI73 "*.73?"
|
|
#define PAT_TI73_NUM "*.73n;*.73l;*.73m;*.73i"
|
|
#define PAT_TI82 "*.82?"
|
|
#define PAT_TI82_NUM "*.82n;*.82l;*.82m;*.82i"
|
|
#define PAT_TI82_TEXT "*.82s;*.82y;*.82p"
|
|
#define PAT_TI83 "*.83?"
|
|
#define PAT_TI83_NUM "*.83n;*.83l;*.83m;*.83i"
|
|
#define PAT_TI83_TEXT "*.83s;*.83y;*.83p"
|
|
#define PAT_TI83P "*.8x?;*.8xgrp"
|
|
#define PAT_TI83P_NUM "*.8xn;*.8xl;*.8xm;*.8xi"
|
|
#define PAT_TI83P_TEXT "*.8xs;*.8xy;*.8xp"
|
|
#define PAT_TI85 "*.85?"
|
|
#define PAT_TI86 "*.86?"
|
|
#define PAT_TIG "*.tig"
|
|
|
|
#define FLT_TI81 "TI-81 programs", PAT_TI81
|
|
#define FLT_TI73 "TI-73 files", PAT_TI73
|
|
#define FLT_TI82 "TI-82 files", PAT_TI82
|
|
#define FLT_TI83 "TI-83 files", PAT_TI83
|
|
#define FLT_TI83P "TI-83 Plus files", PAT_TI83P
|
|
#define FLT_TI85 "TI-85 files", PAT_TI85
|
|
#define FLT_TI86 "TI-86 files", PAT_TI86
|
|
#define FLT_TIG "TIGroup files", PAT_TIG
|
|
#define FLT_ALL "All files", "*"
|
|
|
|
#define DESC_COMPAT "All compatible files"
|
|
|
|
#define FLT_TI73_COMPAT DESC_COMPAT, (PAT_TI73 ";" PAT_TIG ";" \
|
|
PAT_TI82_NUM ";" \
|
|
PAT_TI83_NUM ";" \
|
|
PAT_TI83P_NUM)
|
|
|
|
#define FLT_TI82_COMPAT DESC_COMPAT, (PAT_TI82 ";" PAT_TIG ";" \
|
|
PAT_TI83_TEXT ";" PAT_TI83_NUM ";" \
|
|
PAT_TI83P_TEXT ";" PAT_TI83P_NUM ";" \
|
|
PAT_TI73_NUM)
|
|
|
|
#define FLT_TI83_COMPAT DESC_COMPAT, (PAT_TI83 ";" PAT_TIG ";" \
|
|
PAT_TI82_TEXT ";" PAT_TI82_NUM ";" \
|
|
PAT_TI83P_TEXT ";" PAT_TI83P_NUM ";" \
|
|
PAT_TI73_NUM)
|
|
|
|
#define FLT_TI83P_COMPAT DESC_COMPAT, (PAT_TI83P ";" PAT_TIG ";" \
|
|
PAT_TI82_TEXT ";" PAT_TI82_NUM ";" \
|
|
PAT_TI83_TEXT ";" PAT_TI83_NUM ";" \
|
|
PAT_TI73_NUM)
|
|
|
|
#define FLT_TI8586_COMPAT DESC_COMPAT, (PAT_TI85 ";" PAT_TI86 ";" PAT_TIG)
|
|
|
|
static char ** prompt_link_files(const char *title,
|
|
GtkWindow *parent,
|
|
const char *dir,
|
|
int model)
|
|
{
|
|
switch (model) {
|
|
case TILEM_CALC_TI73:
|
|
return prompt_open_files(title, parent, dir,
|
|
FLT_TI73_COMPAT, FLT_TI73,
|
|
FLT_TI82, FLT_TI83, FLT_TI83P,
|
|
FLT_TIG, FLT_ALL, NULL);
|
|
case TILEM_CALC_TI81:
|
|
return prompt_open_files(title, parent, dir,
|
|
FLT_TI81, FLT_ALL, NULL);
|
|
case TILEM_CALC_TI82:
|
|
return prompt_open_files(title, parent, dir,
|
|
FLT_TI82_COMPAT, FLT_TI73,
|
|
FLT_TI82, FLT_TI83, FLT_TI83P,
|
|
FLT_TIG, FLT_ALL, NULL);
|
|
case TILEM_CALC_TI83:
|
|
case TILEM_CALC_TI76:
|
|
return prompt_open_files(title, parent, dir,
|
|
FLT_TI83_COMPAT, FLT_TI73,
|
|
FLT_TI82, FLT_TI83, FLT_TI83P,
|
|
FLT_TIG, FLT_ALL, NULL);
|
|
case TILEM_CALC_TI83P:
|
|
case TILEM_CALC_TI83P_SE:
|
|
case TILEM_CALC_TI84P:
|
|
case TILEM_CALC_TI84P_SE:
|
|
case TILEM_CALC_TI84P_NSPIRE:
|
|
return prompt_open_files(title, parent, dir,
|
|
FLT_TI83P_COMPAT, FLT_TI73,
|
|
FLT_TI82, FLT_TI83, FLT_TI83P,
|
|
FLT_TIG, FLT_ALL, NULL);
|
|
case TILEM_CALC_TI85:
|
|
case TILEM_CALC_TI86:
|
|
return prompt_open_files(title, parent, dir,
|
|
FLT_TI8586_COMPAT, FLT_TI85,
|
|
FLT_TI86, FLT_TIG, FLT_ALL, NULL);
|
|
default:
|
|
return prompt_open_files(title, parent, dir, FLT_ALL, NULL);
|
|
}
|
|
}
|
|
|
|
/* Load a list of files through the GUI. The list of filenames must
|
|
end with NULL. */
|
|
void load_files(TilemEmulatorWindow *ewin, char **filenames)
|
|
{
|
|
struct slotdialog *slotdlg;
|
|
int i;
|
|
|
|
g_return_if_fail(ewin->emu->calc != NULL);
|
|
|
|
if (ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
|
|
slotdlg = g_slice_new0(struct slotdialog);
|
|
slotdlg->filenames = g_strdupv(filenames);
|
|
slotdlg->nfiles = g_strv_length(filenames);
|
|
slotdlg->slots = g_new(int, slotdlg->nfiles);
|
|
for (i = 0; i < slotdlg->nfiles; i++)
|
|
slotdlg->slots[i] = TI81_SLOT_AUTO;
|
|
tilem_calc_emulator_begin(ewin->emu, &check_prog_slots_main,
|
|
&check_prog_slots_finished, slotdlg);
|
|
}
|
|
else {
|
|
send_files(ewin->emu, filenames, NULL);
|
|
}
|
|
}
|
|
|
|
static int get_cmdline_slot(const char *str, const char **name)
|
|
{
|
|
char *e;
|
|
int n;
|
|
|
|
n = strtol(str, &e, 10);
|
|
if (*e == '=') {
|
|
*name = e + 1;
|
|
return n;
|
|
}
|
|
|
|
if (g_ascii_isalpha(str[0]) && str[1] == '=') {
|
|
*name = str + 2;
|
|
return TI81_SLOT_A + g_ascii_toupper(str[0]) - 'A';
|
|
}
|
|
|
|
if (str[0] == '@' && str[1] == '=') {
|
|
*name = str + 2;
|
|
return TI81_SLOT_THETA;
|
|
}
|
|
|
|
if (!g_ascii_strncasecmp(str, "theta=", 6)) {
|
|
*name = str + 6;
|
|
return TI81_SLOT_THETA;
|
|
}
|
|
|
|
*name = str;
|
|
return TI81_SLOT_AUTO;
|
|
}
|
|
|
|
/* Load a list of files from the command line. Filenames may begin
|
|
with an optional slot designation. */
|
|
void load_files_cmdline(TilemEmulatorWindow *ewin, char **filenames)
|
|
{
|
|
struct slotdialog *slotdlg;
|
|
int i;
|
|
gboolean need_prompt = FALSE;
|
|
const char *name;
|
|
|
|
g_return_if_fail(ewin->emu->calc != NULL);
|
|
|
|
slotdlg = g_slice_new0(struct slotdialog);
|
|
slotdlg->nfiles = g_strv_length(filenames);
|
|
slotdlg->slots = g_new(int, slotdlg->nfiles);
|
|
slotdlg->filenames = g_new0(char *, slotdlg->nfiles + 1);
|
|
|
|
for (i = 0; i < slotdlg->nfiles; i++) {
|
|
slotdlg->slots[i] = get_cmdline_slot(filenames[i], &name);
|
|
slotdlg->filenames[i] = g_strdup(name);
|
|
|
|
if (slotdlg->slots[i] < 0)
|
|
need_prompt = TRUE;
|
|
}
|
|
|
|
if (need_prompt && ewin->emu->calc->hw.model_id == TILEM_CALC_TI81) {
|
|
tilem_calc_emulator_begin(ewin->emu, &check_prog_slots_main,
|
|
&check_prog_slots_finished, slotdlg);
|
|
}
|
|
else {
|
|
send_files(ewin->emu, slotdlg->filenames, slotdlg->slots);
|
|
g_free(slotdlg->slots);
|
|
g_strfreev(slotdlg->filenames);
|
|
g_slice_free(struct slotdialog, slotdlg);
|
|
}
|
|
}
|
|
|
|
/* Prompt user to load a file */
|
|
void load_file_dialog(TilemEmulatorWindow *ewin)
|
|
{
|
|
char **filenames, *dir;
|
|
|
|
tilem_config_get("upload",
|
|
"sendfile_recentdir/f", &dir,
|
|
NULL);
|
|
|
|
filenames = prompt_link_files("Send File",
|
|
GTK_WINDOW(ewin->window),
|
|
dir, ewin->emu->calc->hw.model_id);
|
|
g_free(dir);
|
|
|
|
if (!filenames || !filenames[0]) {
|
|
g_free(filenames);
|
|
return;
|
|
}
|
|
|
|
dir = g_path_get_dirname(filenames[0]);
|
|
tilem_config_set("upload",
|
|
"sendfile_recentdir/f", dir,
|
|
NULL);
|
|
g_free(dir);
|
|
|
|
load_files(ewin, filenames);
|
|
g_strfreev(filenames);
|
|
}
|