diff --git a/tool/gcn64cfg.glade b/tool/gcn64cfg.glade index 5565dd4..a05310d 100644 --- a/tool/gcn64cfg.glade +++ b/tool/gcn64cfg.glade @@ -188,7 +188,7 @@ Author: Raphaël Assénat False Open mempak editor True - + @@ -394,6 +394,29 @@ Author: Raphaël Assénat 3 + + + True + False + end + Controller type: + right + + + 0 + 4 + + + + + True + False + + + 1 + 4 + + @@ -817,8 +840,9 @@ Author: Raphaël Assénat update_cancel_button - + False + N64 Mempak editor True mainWindow @@ -828,6 +852,7 @@ Author: Raphaël Assénat True False True + True vertical @@ -896,7 +921,7 @@ Author: Raphaël Assénat False True True - + @@ -956,6 +981,7 @@ Author: Raphaël Assénat True True + True n64_notes False both @@ -979,6 +1005,7 @@ Author: Raphaël Assénat True Game Data + True @@ -1007,6 +1034,25 @@ Author: Raphaël Assénat 1 + + + True + False + 10 + 10 + 10 + 10 + 6 + 6 + vertical + 2 + + + False + True + 2 + + diff --git a/tool/gcn64ctl_gui.c b/tool/gcn64ctl_gui.c index 5742e75..0b7a045 100644 --- a/tool/gcn64ctl_gui.c +++ b/tool/gcn64ctl_gui.c @@ -348,10 +348,12 @@ static void updateGuiFromAdapter(struct application *app) { CFG_PARAM_INVERT_TRIG, GET_ELEMENT(GtkToggleButton, chkbtn_gc_invert_trig) }, { }, }; + int controller_type; GET_UI_ELEMENT(GtkLabel, label_product_name); GET_UI_ELEMENT(GtkLabel, label_firmware_version); GET_UI_ELEMENT(GtkLabel, label_usb_id); GET_UI_ELEMENT(GtkLabel, label_device_path); + GET_UI_ELEMENT(GtkLabel, label_controller_type); int i; struct gcn64_info *info = &app->current_adapter_info; @@ -389,6 +391,11 @@ static void updateGuiFromAdapter(struct application *app) gtk_label_set_text(label_usb_id, (char*)buf); gtk_label_set_text(label_device_path, info->str_path); + + controller_type = gcn64lib_getControllerType(app->current_adapter_handle, 0); + gtk_label_set_text(label_controller_type, gcn64lib_controllerName(controller_type)); + + } G_MODULE_EXPORT void pollIntervalChanged(GtkWidget *win, gpointer data) diff --git a/tool/gcn64ctl_gui_mpkedit.c b/tool/gcn64ctl_gui_mpkedit.c index a08c5a8..6356f24 100644 --- a/tool/gcn64ctl_gui_mpkedit.c +++ b/tool/gcn64ctl_gui_mpkedit.c @@ -1,5 +1,6 @@ #include #include +#include #include "gcn64ctl_gui.h" #include "mempak.h" @@ -7,6 +8,8 @@ void mpke_syncModel(struct application *app); struct mpkedit_data { struct mempak_structure *mpk; + char *filename; + int modified; }; struct mpkedit_data *mpkedit_new(struct application *app) @@ -35,21 +38,89 @@ void mpkedit_free(struct mpkedit_data *mpke) } } -void mpke_replaceMpk(struct application *app, mempak_structure_t *mpk) +int mpke_getSelection(struct application *app) { + GET_UI_ELEMENT(GtkTreeView, n64_notes_treeview); + GtkTreeSelection *sel = gtk_tree_view_get_selection(n64_notes_treeview); + GList *selected; + int sel_id = -1; + + if (!sel) { + return -1; + } + + selected = gtk_tree_selection_get_selected_rows(sel, NULL); + if (selected && selected->data) { + GtkTreePath *path; + gint *indices; + + path = (GtkTreePath*)selected->data; + indices = gtk_tree_path_get_indices(path); + sel_id = indices[0]; + + } else { + return -1; + } + + if (selected) + g_list_free_full(selected, (GDestroyNotify)gtk_tree_path_free); + + printf("Current selection: %d\n", sel_id); + return sel_id; +} + +void mpke_syncTitle(struct application *app) +{ + GET_UI_ELEMENT(GtkWindow, win_mempak_edit); + char titlebuf[64]; + + if (app->mpke->filename) { + char *bn = g_path_get_basename(app->mpke->filename); + + snprintf(titlebuf, sizeof(titlebuf), "N64 Mempak editor - %s%s", + bn, + app->mpke->modified ? " [MODIFIED]":"" + ); + g_free(bn); + printf("New title: %s\n", titlebuf); + gtk_window_set_title(win_mempak_edit, titlebuf); + } else { + snprintf(titlebuf, sizeof(titlebuf), "N64 Mempak editor%s", + app->mpke->modified ? " [NOT SAVED]" : ""); + } +} + +void mpke_updateFilename(struct application *app, char *filename) +{ + if (app->mpke->filename) { + // The filename always comes from gtk_file_chooser_get_filename + g_free(app->mpke->filename); + } + + app->mpke->filename = filename; + mpke_syncTitle(app); +} + +void mpke_replaceMpk(struct application *app, mempak_structure_t *mpk, char *filename) +{ + if (app->mpke->mpk) { mempak_free(app->mpke->mpk); } app->mpke->mpk = mpk; + mpke_syncModel(app); + mpke_updateFilename(app, filename); } void mpke_syncModel(struct application *app) { GET_UI_ELEMENT(GtkListStore, n64_notes); GET_UI_ELEMENT(GtkTreeView, n64_notes_treeview); + GET_UI_ELEMENT(GtkStatusbar, mempak_status_bar); int i, res; + char statusbuf[64]; gtk_list_store_clear(n64_notes); if (!app->mpke->mpk) { @@ -76,34 +147,148 @@ void mpke_syncModel(struct application *app) gtk_tree_view_set_model(n64_notes_treeview, GTK_TREE_MODEL(n64_notes)); + snprintf(statusbuf, sizeof(statusbuf), "Blocks used: %d / %d", 123-get_mempak_free_space(app->mpke->mpk), 123); + gtk_statusbar_push(mempak_status_bar, gtk_statusbar_get_context_id(mempak_status_bar, "free blocks"), statusbuf); + } G_MODULE_EXPORT void mpke_export_note(GtkWidget *win, gpointer data) { + struct application *app = data; + int selection; + GtkWidget *dialog; + GtkFileChooser *chooser; + GET_UI_ELEMENT(GtkWindow, win_mempak_edit); + GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE; + GET_UI_ELEMENT(GtkFileFilter, n64_note_filter); + int res; + + selection = mpke_getSelection(app); + + if (selection <0) { + printf("No selection"); + return; + } + + if (app->mpke->mpk) { + entry_structure_t entry; + if (0==get_mempak_entry(app->mpke->mpk, selection, &entry)) { + char namebuf[64]; + if (!entry.valid) { + errorPopop(app, "Please select a non-empty note"); + return; + } + + dialog = gtk_file_chooser_dialog_new("Save File", + win_mempak_edit, + action, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Save", + GTK_RESPONSE_ACCEPT, + NULL); + chooser = GTK_FILE_CHOOSER(dialog); + snprintf(namebuf, sizeof(namebuf), "%s.note", entry.name); + gtk_file_chooser_set_current_name(chooser, namebuf); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), n64_note_filter); + + res = gtk_dialog_run (GTK_DIALOG(dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + + filename = gtk_file_chooser_get_filename(chooser); + if (mempak_exportNote(app->mpke->mpk, selection, filename)) { + errorPopop(app, "Could not export note"); + } else { + printf("Note saved to %s\n", filename); + } + } + + gtk_widget_destroy(dialog); + } + } } G_MODULE_EXPORT void mpke_insert_note(GtkWidget *win, gpointer data) { + struct application *app = data; + + GtkWidget *dialog; + GET_UI_ELEMENT(GtkWindow, win_mempak_edit); + GET_UI_ELEMENT(GtkFileFilter, n64_note_filter); + + GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; + int res; + + dialog = gtk_file_chooser_dialog_new("Load N64 mempak image", + win_mempak_edit, + action, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Open", + GTK_RESPONSE_ACCEPT, + NULL); + + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), n64_note_filter); + res = gtk_dialog_run (GTK_DIALOG(dialog)); + + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog); + entry_structure_t entry; + int used_note_id; + int dst_id; + + filename = gtk_file_chooser_get_filename(chooser); + + dst_id = mpke_getSelection(app); + + if (0 == get_mempak_entry(app->mpke->mpk, dst_id, &entry)) { + if (entry.valid) { + // Ask confirmation + printf("Ask confirmation\n"); + } + } + + res = mempak_importNote(app->mpke->mpk, filename, dst_id, &used_note_id); + if (res) { + switch(res) + { + default: + case -1: errorPopop(app, "Error loading file or inserting note\n"); break; + case -2: errorPopop(app, "Not enough free blocks to insert note\n"); break; + } + } else { + // Success + app->mpke->modified =1; + mpke_syncModel(app); + mpke_syncTitle(app); + } + } + + gtk_widget_destroy(dialog); + } G_MODULE_EXPORT void mpke_new(GtkWidget *win, gpointer data) { struct application *app = data; - mpke_replaceMpk(app, mempak_new()); + app->mpke->modified = 0; + mpke_replaceMpk(app, mempak_new(), NULL); } G_MODULE_EXPORT void mpke_open(GtkWidget *win, gpointer data) { struct application *app = data; GtkWidget *dialog; - GET_UI_ELEMENT(GtkWindow, n64_mempak_window_editor); + GET_UI_ELEMENT(GtkWindow, win_mempak_edit); GET_UI_ELEMENT(GtkFileFilter, n64_mempak_filter); GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN; int res; dialog = gtk_file_chooser_dialog_new("Load N64 mempak image", - n64_mempak_window_editor, + win_mempak_edit, action, "_Cancel", GTK_RESPONSE_CANCEL, @@ -121,7 +306,8 @@ G_MODULE_EXPORT void mpke_open(GtkWidget *win, gpointer data) filename = gtk_file_chooser_get_filename(chooser); mpk = mempak_loadFromFile(filename); if (mpk) { - mpke_replaceMpk(app, mpk); + app->mpke->modified = 0; + mpke_replaceMpk(app, mpk, filename); } else { errorPopop(app, "Failed to load mempak"); } @@ -132,14 +318,93 @@ G_MODULE_EXPORT void mpke_open(GtkWidget *win, gpointer data) G_MODULE_EXPORT void mpke_saveas(GtkWidget *win, gpointer data) { + struct application *app = data; + GtkWidget *dialog; + GtkFileChooser *chooser; + GET_UI_ELEMENT(GtkWindow, win_mempak_edit); + GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_SAVE; + GET_UI_ELEMENT(GtkFileFilter, n64_mempak_filter); + int res; + + dialog = gtk_file_chooser_dialog_new("Save File", + win_mempak_edit, + action, + "_Cancel", + GTK_RESPONSE_CANCEL, + "_Save", + GTK_RESPONSE_ACCEPT, + NULL); + chooser = GTK_FILE_CHOOSER(dialog); + + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), n64_mempak_filter); + if (app->mpke->filename) { + gchar *bs = g_path_get_basename(app->mpke->filename); + gchar *dn = g_path_get_dirname(app->mpke->filename); + gtk_file_chooser_set_current_name(chooser, bs); + gtk_file_chooser_set_current_folder(chooser, dn); + g_free(bs); + g_free(dn); + } else { + gtk_file_chooser_set_current_name(chooser, "mempak.n64"); + } + + res = gtk_dialog_run (GTK_DIALOG(dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + char *filename; + int fmt; + + filename = gtk_file_chooser_get_filename(chooser); + fmt = mempak_getFilenameFormat(filename); + if (fmt!= MPK_FORMAT_INVALID) { + mempak_saveToFile(app->mpke->mpk, filename, fmt); + printf("Saved to %s\n", filename); + app->mpke->modified = 0; + mpke_updateFilename(app,filename); + } else { + errorPopop(app, "Unknown file format specified"); + } + } + + gtk_widget_destroy(dialog); } G_MODULE_EXPORT void mpke_save(GtkWidget *win, gpointer data) { + struct application *app = data; + + if (!app->mpke->mpk) + return; + + if (!app->mpke->filename) { + mpke_saveas(win, data); + } else { + mempak_saveToFile(app->mpke->mpk, app->mpke->filename, app->mpke->mpk->file_format); + app->mpke->modified = 0; + mpke_syncTitle(app); + } } G_MODULE_EXPORT void mpke_delete(GtkWidget *win, gpointer data) { + struct application *app = data; + int selection; + + selection = mpke_getSelection(app); + + if (selection <0) { + printf("No selection"); + return; + } + + if (app->mpke->mpk) { + entry_structure_t entry; + if (0==get_mempak_entry(app->mpke->mpk, selection, &entry)) { + delete_mempak_entry(app->mpke->mpk, &entry); + mpke_syncModel(app); + app->mpke->modified = 1; + mpke_syncTitle(app); + } + } } G_MODULE_EXPORT void onMempakWindowShow(GtkWidget *win, gpointer data) diff --git a/tool/mempak.c b/tool/mempak.c index 013e7f6..be3a015 100644 --- a/tool/mempak.c +++ b/tool/mempak.c @@ -55,6 +55,7 @@ static int mempak_findFreeNote(mempak_structure_t *mpk, entry_structure_t *entry * \param notefile The filename of the note to load * \param dst_note_id 0-15: (Over)write to specific note, -1: auto (first free) * \param note_id Stores the id of the note that was used + * \return -1: Error, -2: Not enough space in mempak */ int mempak_importNote(mempak_structure_t *mpk, const char *notefile, int dst_note_id, int *note_id) { @@ -128,7 +129,7 @@ int mempak_importNote(mempak_structure_t *mpk, const char *notefile, int dst_not fprintf(stderr, "Not enough space (note is %d blocks and only %d free blocks in mempak)\n", entry.blocks, free_blocks); fclose(fptr); - return -1; + return -2; } data = calloc(1, entry.blocks * MEMPAK_BLOCK_SIZE);