From 668be019ada6a154bfa60dbebea2774ca84d0b1e Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 30 Jun 2005 18:00:37 +0000 Subject: [PATCH] Load and Save are now supported on all three desktop platforms, and documented. (This means the GTK temporary dependency on an environment variable is now gone.) git-svn-id: svn://svn.tartarus.org/sgt/puzzles@6042 cda61777-01e9-0310-a592-d414129be87e --- Recipe | 2 +- gtk.c | 108 +++++++++++++++++++++++++++++++++++++++--------------- osx.m | 87 ++++++++++++++++++++++++++++++++++++++++---- puzzles.but | 16 ++++++++ windows.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 291 insertions(+), 41 deletions(-) diff --git a/Recipe b/Recipe index a772e80..a6fde21 100644 --- a/Recipe +++ b/Recipe @@ -13,7 +13,7 @@ !makefile cygwin Makefile.cyg !makefile osx Makefile.osx -WINDOWS = windows user32.lib gdi32.lib comctl32.lib +WINDOWS = windows user32.lib gdi32.lib comctl32.lib comdlg32.lib COMMON = midend misc malloc random version NET = net tree234 NETSLIDE = netslide tree234 diff --git a/gtk.c b/gtk.c index 40ee308..ef795a4 100644 --- a/gtk.c +++ b/gtk.c @@ -660,8 +660,15 @@ static void window_destroy(GtkWidget *widget, gpointer data) gtk_main_quit(); } -static void errmsg_button_clicked(GtkButton *button, gpointer data) +static void msgbox_button_clicked(GtkButton *button, gpointer data) { + GtkWidget *window = GTK_WIDGET(data); + int v, *ip; + + ip = (int *)gtk_object_get_data(GTK_OBJECT(window), "user-data"); + v = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(button), "user-data")); + *ip = v; + gtk_widget_destroy(GTK_WIDGET(data)); } @@ -680,9 +687,14 @@ static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) return FALSE; } -void message_box(GtkWidget *parent, char *title, char *msg, int centre) +enum { MB_OK, MB_YESNO }; + +int message_box(GtkWidget *parent, char *title, char *msg, int centre, + int type) { - GtkWidget *window, *hbox, *text, *ok; + GtkWidget *window, *hbox, *text, *button; + char *titles; + int i, def, cancel; window = gtk_dialog_new(); text = gtk_label_new(msg); @@ -695,28 +707,54 @@ void message_box(GtkWidget *parent, char *title, char *msg, int centre) gtk_widget_show(hbox); gtk_window_set_title(GTK_WINDOW(window), title); gtk_label_set_line_wrap(GTK_LABEL(text), TRUE); - ok = gtk_button_new_with_label("OK"); - gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area), - ok, FALSE, FALSE, 0); - gtk_widget_show(ok); - GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT); - gtk_window_set_default(GTK_WINDOW(window), ok); - gtk_signal_connect(GTK_OBJECT(ok), "clicked", - GTK_SIGNAL_FUNC(errmsg_button_clicked), window); + + if (type == MB_OK) { + titles = "OK\0"; + def = cancel = 0; + } else { + assert(type == MB_YESNO); + titles = "Yes\0No\0"; + def = 0; + cancel = 1; + } + i = 0; + + while (*titles) { + button = gtk_button_new_with_label(titles); + gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area), + button, FALSE, FALSE, 0); + gtk_widget_show(button); + if (i == def) { + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_window_set_default(GTK_WINDOW(window), button); + } + if (i == cancel) { + gtk_signal_connect(GTK_OBJECT(window), "key_press_event", + GTK_SIGNAL_FUNC(win_key_press), button); + } + gtk_signal_connect(GTK_OBJECT(button), "clicked", + GTK_SIGNAL_FUNC(msgbox_button_clicked), window); + gtk_object_set_data(GTK_OBJECT(button), "user-data", + GINT_TO_POINTER(i)); + titles += strlen(titles)+1; + i++; + } + gtk_object_set_data(GTK_OBJECT(window), "user-data", + GINT_TO_POINTER(&i)); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(window_destroy), NULL); - gtk_signal_connect(GTK_OBJECT(window), "key_press_event", - GTK_SIGNAL_FUNC(win_key_press), ok); gtk_window_set_modal(GTK_WINDOW(window), TRUE); gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent)); /* set_transient_window_pos(parent, window); */ gtk_widget_show(window); + i = -1; gtk_main(); + return (type == MB_YESNO ? i == 0 : TRUE); } void error_box(GtkWidget *parent, char *msg) { - message_box(parent, "Error", msg, FALSE); + message_box(parent, "Error", msg, FALSE, MB_OK); } static void config_ok_button_clicked(GtkButton *button, gpointer data) @@ -1180,7 +1218,21 @@ static void menu_save_event(GtkMenuItem *menuitem, gpointer data) name = file_selector(fe, "Enter name of game file to save", TRUE); if (name) { - FILE *fp = fopen(name, "w"); + FILE *fp; + + if ((fp = fopen(name, "r")) != NULL) { + char buf[256 + FILENAME_MAX]; + fclose(fp); + /* file exists */ + + sprintf(buf, "Are you sure you want to overwrite the" + " file \"%.*s\"?", + FILENAME_MAX, name); + if (!message_box(fe->window, "Question", buf, TRUE, MB_YESNO)) + return; + } + + fp = fopen(name, "w"); sfree(name); if (!fp) { @@ -1285,7 +1337,7 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data) "from Simon Tatham's Portable Puzzle Collection\n\n" "%.500s", thegame.name, ver); - message_box(fe->window, titlebuf, textbuf, TRUE); + message_box(fe->window, titlebuf, textbuf, TRUE, MB_OK); } static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, @@ -1414,19 +1466,17 @@ static frontend *new_window(char *game_id, char **error) } } - if (getenv("PUZZLES_EXPERIMENTAL_SAVE") != NULL) { - add_menu_separator(GTK_CONTAINER(menu)); - menuitem = gtk_menu_item_new_with_label("Load"); - gtk_container_add(GTK_CONTAINER(menu), menuitem); - gtk_signal_connect(GTK_OBJECT(menuitem), "activate", - GTK_SIGNAL_FUNC(menu_load_event), fe); - gtk_widget_show(menuitem); - menuitem = gtk_menu_item_new_with_label("Save"); - gtk_container_add(GTK_CONTAINER(menu), menuitem); - gtk_signal_connect(GTK_OBJECT(menuitem), "activate", - GTK_SIGNAL_FUNC(menu_save_event), fe); - gtk_widget_show(menuitem); - } + add_menu_separator(GTK_CONTAINER(menu)); + menuitem = gtk_menu_item_new_with_label("Load"); + gtk_container_add(GTK_CONTAINER(menu), menuitem); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(menu_load_event), fe); + gtk_widget_show(menuitem); + menuitem = gtk_menu_item_new_with_label("Save"); + gtk_container_add(GTK_CONTAINER(menu), menuitem); + gtk_signal_connect(GTK_OBJECT(menuitem), "activate", + GTK_SIGNAL_FUNC(menu_save_event), fe); + gtk_widget_show(menuitem); add_menu_separator(GTK_CONTAINER(menu)); add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Undo", 'u'); add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Redo", '\x12'); diff --git a/osx.m b/osx.m index bc87021..b009cd7 100644 --- a/osx.m +++ b/osx.m @@ -138,6 +138,21 @@ void get_random_seed(void **randseed, int *randseedsize) *randseedsize = sizeof(time_t); } +static void savefile_write(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + fwrite(buf, 1, len, fp); +} + +static int savefile_read(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + int ret; + + ret = fread(buf, 1, len, fp); + return (ret == len); +} + /* ---------------------------------------------------------------------- * Tiny extension to NSMenuItem which carries a payload of a `void * *', allowing several menu items to invoke the same message but @@ -384,6 +399,7 @@ struct frontend { - (void)activateTimer; - (void)deactivateTimer; - (void)setStatusLine:(char *)text; +- (void)resizeForNewGameParams; @end @implementation MyImageView @@ -659,6 +675,17 @@ struct frontend { last_time = now; } +- (void)showError:(char *)message +{ + NSAlert *alert; + + alert = [[[NSAlert alloc] init] autorelease]; + [alert addButtonWithTitle:@"Bah"]; + [alert setInformativeText:[NSString stringWithCString:message]]; + [alert beginSheetModalForWindow:self modalDelegate:nil + didEndSelector:nil contextInfo:nil]; +} + - (void)newGame:(id)sender { [self processButton:'n' x:-1 y:-1]; @@ -667,6 +694,54 @@ struct frontend { { midend_restart_game(me); } +- (void)saveGame:(id)sender +{ + NSSavePanel *sp = [NSSavePanel savePanel]; + + if ([sp runModal] == NSFileHandlingPanelOKButton) { + const char *name = [[sp filename] cString]; + + FILE *fp = fopen(name, "w"); + + if (!fp) { + [self showError:"Unable to open save file"]; + return; + } + + midend_serialise(me, savefile_write, fp); + + fclose(fp); + } +} +- (void)loadSavedGame:(id)sender +{ + NSOpenPanel *op = [NSOpenPanel openPanel]; + + [op setAllowsMultipleSelection:NO]; + + if ([op runModalForTypes:nil] == NSOKButton) { + const char *name = [[[op filenames] objectAtIndex:0] cString]; + char *err; + + FILE *fp = fopen(name, "r"); + + if (!fp) { + [self showError:"Unable to open saved game file"]; + return; + } + + err = midend_deserialise(me, savefile_read, fp); + + fclose(fp); + + if (err) { + [self showError:err]; + return; + } + + [self resizeForNewGameParams]; + } +} - (void)undoMove:(id)sender { [self processButton:'u' x:-1 y:-1]; @@ -693,17 +768,11 @@ struct frontend { - (void)solveGame:(id)sender { char *msg; - NSAlert *alert; msg = midend_solve(me); - if (msg) { - alert = [[[NSAlert alloc] init] autorelease]; - [alert addButtonWithTitle:@"Bah"]; - [alert setInformativeText:[NSString stringWithCString:msg]]; - [alert beginSheetModalForWindow:self modalDelegate:nil - didEndSelector:nil contextInfo:nil]; - } + if (msg) + [self showError:msg]; } - (BOOL)validateMenuItem:(NSMenuItem *)item @@ -1421,6 +1490,8 @@ int main(int argc, char **argv) [NSApp setAppleMenu: menu]; menu = newsubmenu([NSApp mainMenu], "File"); + item = newitem(menu, "Open", "o", NULL, @selector(loadSavedGame:)); + item = newitem(menu, "Save As", "s", NULL, @selector(saveGame:)); item = newitem(menu, "New Game", "n", NULL, @selector(newGame:)); item = newitem(menu, "Restart Game", "r", NULL, @selector(restartGame:)); item = newitem(menu, "Specific Game", "", NULL, @selector(specificGame:)); diff --git a/puzzles.but b/puzzles.but index 71ff48f..ed46e60 100644 --- a/puzzles.but +++ b/puzzles.but @@ -94,6 +94,22 @@ menu}\q{Edit} menus instead.) \dd Resets the current game to its initial state. (This can be undone.) +\dt \ii\e{Load} + +\dd Loads a saved game from a file on disk. + +\dt \ii\e{Save} + +\dd Saves the current state of your game to a file on disk. + +\lcont{ + +The Load and Save operations should preserve your entire game +history (so you can save, reload, and still Undo and Redo things you +had done before saving). + +} + \dt \ii\e{Undo} (\q{U}, Ctrl+\q{Z}, Ctrl+\q{_}) \dd Undoes a single move. (You can undo moves back to the start of the diff --git a/windows.c b/windows.c index ae93983..df3de01 100644 --- a/windows.c +++ b/windows.c @@ -28,6 +28,8 @@ #define IDM_HELPC 0x00B0 #define IDM_GAMEHELP 0x00C0 #define IDM_ABOUT 0x00D0 +#define IDM_SAVE 0x00E0 +#define IDM_LOAD 0x00F0 #define IDM_PRESETS 0x0100 #define HELP_FILE_NAME "puzzles.hlp" @@ -670,6 +672,9 @@ static frontend *new_window(HINSTANCE inst, char *game_id, char **error) } AppendMenu(menu, MF_SEPARATOR, 0, 0); + AppendMenu(menu, MF_ENABLED, IDM_LOAD, "Load"); + AppendMenu(menu, MF_ENABLED, IDM_SAVE, "Save"); + AppendMenu(menu, MF_SEPARATOR, 0, 0); AppendMenu(menu, MF_ENABLED, IDM_UNDO, "Undo"); AppendMenu(menu, MF_ENABLED, IDM_REDO, "Redo"); if (thegame.can_format_as_text) { @@ -1214,13 +1219,12 @@ static int get_config(frontend *fe, int which) return (fe->dlg_done == 2); } -static void new_game_type(frontend *fe) +static void new_game_size(frontend *fe) { RECT r, sr; HDC hdc; int x, y; - midend_new_game(fe->me); x = y = INT_MAX; midend_size(fe->me, &x, &y, FALSE); @@ -1257,6 +1261,12 @@ static void new_game_type(frontend *fe) midend_redraw(fe->me); } +static void new_game_type(frontend *fe) +{ + midend_new_game(fe->me); + new_game_size(fe); +} + static int is_alt_pressed(void) { BYTE keystate[256]; @@ -1270,17 +1280,34 @@ static int is_alt_pressed(void) return FALSE; } +static void savefile_write(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + fwrite(buf, 1, len, fp); +} + +static int savefile_read(void *wctx, void *buf, int len) +{ + FILE *fp = (FILE *)wctx; + int ret; + + ret = fread(buf, 1, len, fp); + return (ret == len); +} + static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA); + int cmd; switch (message) { case WM_CLOSE: DestroyWindow(hwnd); return 0; case WM_COMMAND: - switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */ + cmd = wParam & ~0xF; /* low 4 bits reserved to Windows */ + switch (cmd) { case IDM_NEW: if (!midend_process_key(fe->me, 0, 0, 'n')) PostQuitMessage(0); @@ -1333,6 +1360,92 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, case IDM_ABOUT: about(fe); break; + case IDM_LOAD: + case IDM_SAVE: + { + OPENFILENAME of; + char filename[FILENAME_MAX]; + int ret; + + memset(&of, 0, sizeof(of)); + of.hwndOwner = hwnd; + of.lpstrFilter = "All Files (*.*)\0*\0\0\0"; + of.lpstrCustomFilter = NULL; + of.nFilterIndex = 1; + of.lpstrFile = filename; + filename[0] = '\0'; + of.nMaxFile = lenof(filename); + of.lpstrFileTitle = NULL; + of.lpstrTitle = (cmd == IDM_SAVE ? + "Enter name of game file to save" : + "Enter name of saved game file to load"); + of.Flags = 0; +#ifdef OPENFILENAME_SIZE_VERSION_400 + of.lStructSize = OPENFILENAME_SIZE_VERSION_400; +#else + of.lStructSize = sizeof(of); +#endif + of.lpstrInitialDir = NULL; + + if (cmd == IDM_SAVE) + ret = GetSaveFileName(&of); + else + ret = GetOpenFileName(&of); + + if (ret) { + if (cmd == IDM_SAVE) { + FILE *fp; + + if ((fp = fopen(filename, "r")) != NULL) { + char buf[256 + FILENAME_MAX]; + fclose(fp); + /* file exists */ + + sprintf(buf, "Are you sure you want to overwrite" + " the file \"%.*s\"?", + FILENAME_MAX, filename); + if (MessageBox(hwnd, buf, "Question", + MB_YESNO | MB_ICONQUESTION) + != IDYES) + break; + } + + fp = fopen(filename, "w"); + + if (!fp) { + MessageBox(hwnd, "Unable to open save file", + "Error", MB_ICONERROR | MB_OK); + break; + } + + midend_serialise(fe->me, savefile_write, fp); + + fclose(fp); + } else { + FILE *fp = fopen(filename, "r"); + char *err; + + if (!fp) { + MessageBox(hwnd, "Unable to open saved game file", + "Error", MB_ICONERROR | MB_OK); + break; + } + + err = midend_deserialise(fe->me, savefile_read, fp); + + fclose(fp); + + if (err) { + MessageBox(hwnd, err, "Error", MB_ICONERROR|MB_OK); + break; + } + + new_game_size(fe); + } + } + } + + break; case IDM_HELPC: assert(fe->help_path); WinHelp(hwnd, fe->help_path, -- 2.11.0