diff --git a/apps/plugins/puzzles/src/Buildscr b/apps/plugins/puzzles/src/Buildscr index c72084477b..b8a585b43e 100644 --- a/apps/plugins/puzzles/src/Buildscr +++ b/apps/plugins/puzzles/src/Buildscr @@ -54,24 +54,30 @@ in puzzles do make -f Makefile.doc clean in puzzles do make -f Makefile.doc # build help files for installer in puzzles do mason.pl --args '{"version":"$(Version)","descfile":"gamedesc.txt"}' winwix.mc > puzzles.wxs in puzzles do perl winiss.pl $(Version) gamedesc.txt > puzzles.iss -delegate windows - # FIXME: Cygwin alternative? - in puzzles with visualstudio do/win nmake -f Makefile.vc clean - in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version) +ifneq "$(VISUAL_STUDIO)" "yes" then + in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl clean + in puzzles with clangcl64 do Platform=x64 make -f Makefile.clangcl VER=-DVER=$(Version) # Code-sign the binaries, if the local bob config provides a script # to do so. We assume here that the script accepts an -i option to # provide a 'more info' URL, and an optional -n option to provide a # program name, and that it can take multiple .exe filename # arguments and sign them all in place. - ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe + ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe # Build installers. - in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj - in puzzles with innosetup do/win iscc puzzles.iss - ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi Output/installer.exe - return puzzles/*.exe - return puzzles/Output/installer.exe - return puzzles/puzzles.msi -enddelegate + in puzzles with wixonlinux do candle -arch x64 puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj + ifneq "$(cross_winsigncode)" "" in puzzles do $(cross_winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ -n "Simon Tatham's Portable Puzzle Collection Installer" puzzles.msi +else + delegate windows + in puzzles with visualstudio do/win nmake -f Makefile.vc clean + in puzzles with visualstudio do/win nmake -f Makefile.vc VER=-DVER=$(Version) + ifneq "$(winsigncode)" "" in puzzles do $(winsigncode) -i https://www.chiark.greenend.org.uk/~sgtatham/puzzles/ *.exe + # Build installers. + in puzzles with wix do/win candle puzzles.wxs && light -ext WixUIExtension -sval puzzles.wixobj + in puzzles with innosetup do/win iscc puzzles.iss + return puzzles/*.exe + return puzzles/puzzles.msi + enddelegate +endif in puzzles do chmod +x *.exe # Build the Pocket PC binaries and CAB. @@ -152,13 +158,22 @@ delegate emscripten return puzzles/js/*.js enddelegate +# Build a set of wrapping HTML pages for easy testing of the +# Javascript puzzles. These aren't quite the same as the versions that +# will go on my live website, because those ones will substitute in a +# different footer, and not have to link to the .js files with the +# ../js/ prefix. But these ones should be good enough to just open +# using a file:// URL in a browser after running a build, and make +# sure the main functionality works. +in puzzles do mkdir jstest +in puzzles/jstest do ../html/jspage.pl --jspath=../js/ /dev/null ../html/*.html + # Set up .htaccess containing a redirect for the archive filename. in puzzles do echo "AddType application/octet-stream .chm" > .htaccess in puzzles do echo "AddType application/octet-stream .hlp" >> .htaccess in puzzles do echo "AddType application/octet-stream .cnt" >> .htaccess in . do set -- puzzles*.tar.gz; echo RedirectMatch temp '(.*/)'puzzles.tar.gz '$$1'"$$1" >> puzzles/.htaccess in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.msi '$$1'puzzles-$(Version)-installer.msi >> .htaccess -in puzzles do echo RedirectMatch temp '(.*/)'puzzles-installer.exe '$$1'puzzles-$(Version)-installer.exe >> .htaccess # Phew, we're done. Deliver everything! deliver puzzles/icons/*-web.png $@ @@ -172,9 +187,9 @@ deliver puzzles/puzzles.hlp $@ deliver puzzles/puzzles.cnt $@ deliver puzzles/puzzles.zip $@ deliver puzzles/puzzles.msi puzzles-$(Version)-installer.msi -deliver puzzles/Output/installer.exe puzzles-$(Version)-installer.exe deliver puzzles/*.jar java/$@ deliver puzzles/js/*.js js/$@ +deliver puzzles/jstest/*.html jstest/$@ deliver puzzles/html/*.html html/$@ deliver puzzles/html/*.pl html/$@ deliver puzzles/wwwspans.html $@ diff --git a/apps/plugins/puzzles/src/PuzzleApplet.java b/apps/plugins/puzzles/src/PuzzleApplet.java index 512aede580..8455734dd1 100644 --- a/apps/plugins/puzzles/src/PuzzleApplet.java +++ b/apps/plugins/puzzles/src/PuzzleApplet.java @@ -61,19 +61,19 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { JMenuBar menubar = new JMenuBar(); JMenu jm; menubar.add(jm = new JMenu("Game")); - addMenuItemWithKey(jm, "New", 'n'); + addMenuItemCallback(jm, "New", "jcallback_newgame_event"); addMenuItemCallback(jm, "Restart", "jcallback_restart_event"); addMenuItemCallback(jm, "Specific...", "jcallback_config_event", CFG_DESC); addMenuItemCallback(jm, "Random Seed...", "jcallback_config_event", CFG_SEED); jm.addSeparator(); - addMenuItemWithKey(jm, "Undo", 'u'); - addMenuItemWithKey(jm, "Redo", 'r'); + addMenuItemCallback(jm, "Undo", "jcallback_undo_event"); + addMenuItemCallback(jm, "Redo", "jcallback_redo_event"); jm.addSeparator(); solveCommand = addMenuItemCallback(jm, "Solve", "jcallback_solve_event"); solveCommand.setEnabled(false); if (mainWindow != null) { jm.addSeparator(); - addMenuItemWithKey(jm, "Exit", 'q'); + addMenuItemCallback(jm, "Exit", "jcallback_quit_event"); } menubar.add(typeMenu = new JMenu("Type")); typeMenu.setVisible(false); @@ -126,7 +126,12 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { } } public void keyTyped(KeyEvent e) { - runtimeCall("jcallback_key_event", new int[] {0, 0, e.getKeyChar()}); + int key = e.getKeyChar(); + if (key == 26 && e.isShiftDown() && e.isControlDown()) { + runtimeCall("jcallback_redo_event", new int[0]); + return; + } + runtimeCall("jcallback_key_event", new int[] {0, 0, key}); } }); pp.addMouseListener(new MouseAdapter() { @@ -217,10 +222,6 @@ public class PuzzleApplet extends JApplet implements Runtime.CallJavaCB { runtimeCall("jcallback_resize", new int[] {pp.getWidth(), pp.getHeight()}); } - private void addMenuItemWithKey(JMenu jm, String name, int key) { - addMenuItemCallback(jm, name, "jcallback_menu_key_event", key); - } - private JMenuItem addMenuItemCallback(JMenu jm, String name, final String callback, final int arg) { return addMenuItemCallback(jm, name, callback, new int[] {arg}, false); } diff --git a/apps/plugins/puzzles/src/Recipe b/apps/plugins/puzzles/src/Recipe index ba8317f51a..3b57ef5e54 100644 --- a/apps/plugins/puzzles/src/Recipe +++ b/apps/plugins/puzzles/src/Recipe @@ -17,6 +17,7 @@ !makefile gnustep Makefile.gnustep !makefile nestedvm Makefile.nestedvm !makefile emcc Makefile.emcc +!makefile clangcl Makefile.clangcl !srcdir icons/ diff --git a/apps/plugins/puzzles/src/chm.but b/apps/plugins/puzzles/src/chm.but deleted file mode 100644 index e0237044e4..0000000000 --- a/apps/plugins/puzzles/src/chm.but +++ /dev/null @@ -1,21 +0,0 @@ -\# File containing the magic HTML configuration directives to create -\# an MS HTML Help project. We put this on the end of the Puzzles -\# docs build command line to build the HHP and friends. - -\cfg{html-leaf-level}{infinite} -\cfg{html-leaf-contains-contents}{false} -\cfg{html-suppress-navlinks}{true} -\cfg{html-suppress-address}{true} - -\cfg{html-contents-filename}{index.html} -\cfg{html-template-filename}{%k.html} -\cfg{html-template-fragment}{%k} - -\cfg{html-mshtmlhelp-chm}{puzzles.chm} -\cfg{html-mshtmlhelp-project}{puzzles.hhp} -\cfg{html-mshtmlhelp-contents}{puzzles.hhc} -\cfg{html-mshtmlhelp-index}{puzzles.hhk} - -\cfg{html-body-end}{} - -\cfg{html-head-end}{} diff --git a/apps/plugins/puzzles/src/devel.but b/apps/plugins/puzzles/src/devel.but index a38fdda5d0..25a6c62dfa 100644 --- a/apps/plugins/puzzles/src/devel.but +++ b/apps/plugins/puzzles/src/devel.but @@ -1928,6 +1928,9 @@ Indeed, even horizontal or vertical lines may be anti-aliased. This function may be used for both drawing and printing. +If the specified thickness is less than 1.0, 1.0 is used. +This ensures that thin lines are visible even at small scales. + \S{drawing-draw-text} \cw{draw_text()} \c void draw_text(drawing *dr, int x, int y, int fonttype, diff --git a/apps/plugins/puzzles/src/drawing.c b/apps/plugins/puzzles/src/drawing.c index 7f4a6cf674..a10a7f06d6 100644 --- a/apps/plugins/puzzles/src/drawing.c +++ b/apps/plugins/puzzles/src/drawing.c @@ -90,6 +90,8 @@ void draw_line(drawing *dr, int x1, int y1, int x2, int y2, int colour) void draw_thick_line(drawing *dr, float thickness, float x1, float y1, float x2, float y2, int colour) { + if (thickness < 1.0) + thickness = 1.0; if (dr->api->draw_thick_line) { dr->api->draw_thick_line(dr->handle, thickness, x1, y1, x2, y2, colour); diff --git a/apps/plugins/puzzles/src/emcc.c b/apps/plugins/puzzles/src/emcc.c index ca033cbd47..23ab333f5d 100644 --- a/apps/plugins/puzzles/src/emcc.c +++ b/apps/plugins/puzzles/src/emcc.c @@ -310,6 +310,8 @@ void key(int keycode, int charcode, const char *key, const char *chr, keyevent = MOD_NUM_KEYPAD | '7'; } else if (!strnullcmp(key, "PageUp") || keycode==33) { keyevent = MOD_NUM_KEYPAD | '9'; + } else if (shift && ctrl && (keycode & 0x1F) == 26) { + keyevent = UI_REDO; } else if (chr && chr[0] && !chr[1]) { keyevent = chr[0] & 0xFF; } else if (keycode >= 96 && keycode < 106) { @@ -323,10 +325,10 @@ void key(int keycode, int charcode, const char *key, const char *chr, } if (keyevent >= 0) { - if (shift && keyevent >= 0x100) + if (shift && (keyevent >= 0x100 && !IS_UI_FAKE_KEY(keyevent))) keyevent |= MOD_SHFT; - if (ctrl) { + if (ctrl && !IS_UI_FAKE_KEY(keyevent)) { if (keyevent >= 0x100) keyevent |= MOD_CTRL; else @@ -725,7 +727,7 @@ void command(int n) update_undo_redo(); break; case 5: /* New Game */ - midend_process_key(me, 0, 0, 'n'); + midend_process_key(me, 0, 0, UI_NEWGAME); update_undo_redo(); js_focus_canvas(); break; @@ -735,12 +737,12 @@ void command(int n) js_focus_canvas(); break; case 7: /* Undo */ - midend_process_key(me, 0, 0, 'u'); + midend_process_key(me, 0, 0, UI_UNDO); update_undo_redo(); js_focus_canvas(); break; case 8: /* Redo */ - midend_process_key(me, 0, 0, 'r'); + midend_process_key(me, 0, 0, UI_REDO); update_undo_redo(); js_focus_canvas(); break; @@ -756,6 +758,83 @@ void command(int n) } } +/* ---------------------------------------------------------------------- + * Called from JS to prepare a save-game file, and free one after it's + * been used. + */ + +struct savefile_write_ctx { + char *buffer; + size_t pos; +}; + +static void savefile_write(void *vctx, void *buf, int len) +{ + struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx; + if (ctx->buffer) + memcpy(ctx->buffer + ctx->pos, buf, len); + ctx->pos += len; +} + +char *get_save_file(void) +{ + struct savefile_write_ctx ctx; + size_t size; + + /* First pass, to count up the size */ + ctx.buffer = NULL; + ctx.pos = 0; + midend_serialise(me, savefile_write, &ctx); + size = ctx.pos; + + /* Second pass, to actually write out the data */ + ctx.buffer = snewn(size, char); + ctx.pos = 0; + midend_serialise(me, savefile_write, &ctx); + assert(ctx.pos == size); + + return ctx.buffer; +} + +void free_save_file(char *buffer) +{ + sfree(buffer); +} + +struct savefile_read_ctx { + const char *buffer; + int len_remaining; +}; + +static int savefile_read(void *vctx, void *buf, int len) +{ + struct savefile_read_ctx *ctx = (struct savefile_read_ctx *)vctx; + if (ctx->len_remaining < len) + return FALSE; + memcpy(buf, ctx->buffer, len); + ctx->len_remaining -= len; + ctx->buffer += len; + return TRUE; +} + +void load_game(const char *buffer, int len) +{ + struct savefile_read_ctx ctx; + const char *err; + + ctx.buffer = buffer; + ctx.len_remaining = len; + err = midend_deserialise(me, savefile_read, &ctx); + + if (err) { + js_error_box(err); + } else { + select_appropriate_preset(); + resize(); + midend_redraw(me); + } +} + /* ---------------------------------------------------------------------- * Setup function called at page load time. It's called main() because * that's the most convenient thing in Emscripten, but it's not main() diff --git a/apps/plugins/puzzles/src/emcclib.js b/apps/plugins/puzzles/src/emcclib.js index cd8876e76d..907dc19995 100644 --- a/apps/plugins/puzzles/src/emcclib.js +++ b/apps/plugins/puzzles/src/emcclib.js @@ -108,7 +108,6 @@ mergeInto(LibraryManager.library, { item.appendChild(tick); item.appendChild(document.createTextNode(name)); var submenu = document.createElement("ul"); - submenu.className = "left"; item.appendChild(submenu); gametypesubmenus[menuid].appendChild(item); var toret = gametypesubmenus.length; @@ -575,38 +574,7 @@ mergeInto(LibraryManager.library, { * overlay on top of the rest of the puzzle web page. */ js_dialog_init: function(titletext) { - // Create an overlay on the page which darkens everything - // beneath it. - dlg_dimmer = document.createElement("div"); - dlg_dimmer.style.width = "100%"; - dlg_dimmer.style.height = "100%"; - dlg_dimmer.style.background = '#000000'; - dlg_dimmer.style.position = 'fixed'; - dlg_dimmer.style.opacity = 0.3; - dlg_dimmer.style.top = dlg_dimmer.style.left = 0; - dlg_dimmer.style["z-index"] = 99; - - // Now create a form which sits on top of that in turn. - dlg_form = document.createElement("form"); - dlg_form.style.width = (window.innerWidth * 2 / 3) + "px"; - dlg_form.style.opacity = 1; - dlg_form.style.background = '#ffffff'; - dlg_form.style.color = '#000000'; - dlg_form.style.position = 'absolute'; - dlg_form.style.border = "2px solid black"; - dlg_form.style.padding = "20px"; - dlg_form.style.top = (window.innerHeight / 10) + "px"; - dlg_form.style.left = (window.innerWidth / 6) + "px"; - dlg_form.style["z-index"] = 100; - - var title = document.createElement("p"); - title.style.marginTop = "0px"; - title.appendChild(document.createTextNode - (Pointer_stringify(titletext))); - dlg_form.appendChild(title); - - dlg_return_funcs = []; - dlg_next_id = 0; + dialog_init(Pointer_stringify(titletext)); }, /* @@ -701,29 +669,13 @@ mergeInto(LibraryManager.library, { * everything else on the page. */ js_dialog_launch: function() { - // Put in the OK and Cancel buttons at the bottom. - var button; - - button = document.createElement("input"); - button.type = "button"; - button.value = "OK"; - button.onclick = function(event) { + dialog_launch(function(event) { for (var i in dlg_return_funcs) dlg_return_funcs[i](); - command(3); - } - dlg_form.appendChild(button); - - button = document.createElement("input"); - button.type = "button"; - button.value = "Cancel"; - button.onclick = function(event) { - command(4); - } - dlg_form.appendChild(button); - - document.body.appendChild(dlg_dimmer); - document.body.appendChild(dlg_form); + command(3); // OK + }, function(event) { + command(4); // Cancel + }); }, /* @@ -733,10 +685,7 @@ mergeInto(LibraryManager.library, { * associated with it. */ js_dialog_cleanup: function() { - document.body.removeChild(dlg_dimmer); - document.body.removeChild(dlg_form); - dlg_dimmer = dlg_form = null; - onscreen_canvas.focus(); + dialog_cleanup(); }, /* diff --git a/apps/plugins/puzzles/src/emccpre.js b/apps/plugins/puzzles/src/emccpre.js index d715858883..5082555617 100644 --- a/apps/plugins/puzzles/src/emccpre.js +++ b/apps/plugins/puzzles/src/emccpre.js @@ -129,6 +129,72 @@ function disable_menu_item(item, disabledFlag) { item.className = ""; } +// Dialog-box functions called from both C and JS. +function dialog_init(titletext) { + // Create an overlay on the page which darkens everything + // beneath it. + dlg_dimmer = document.createElement("div"); + dlg_dimmer.style.width = "100%"; + dlg_dimmer.style.height = "100%"; + dlg_dimmer.style.background = '#000000'; + dlg_dimmer.style.position = 'fixed'; + dlg_dimmer.style.opacity = 0.3; + dlg_dimmer.style.top = dlg_dimmer.style.left = 0; + dlg_dimmer.style["z-index"] = 99; + + // Now create a form which sits on top of that in turn. + dlg_form = document.createElement("form"); + dlg_form.style.width = (window.innerWidth * 2 / 3) + "px"; + dlg_form.style.opacity = 1; + dlg_form.style.background = '#ffffff'; + dlg_form.style.color = '#000000'; + dlg_form.style.position = 'absolute'; + dlg_form.style.border = "2px solid black"; + dlg_form.style.padding = "20px"; + dlg_form.style.top = (window.innerHeight / 10) + "px"; + dlg_form.style.left = (window.innerWidth / 6) + "px"; + dlg_form.style["z-index"] = 100; + + var title = document.createElement("p"); + title.style.marginTop = "0px"; + title.appendChild(document.createTextNode(titletext)); + dlg_form.appendChild(title); + + dlg_return_funcs = []; + dlg_next_id = 0; +} + +function dialog_launch(ok_function, cancel_function) { + // Put in the OK and Cancel buttons at the bottom. + var button; + + if (ok_function) { + button = document.createElement("input"); + button.type = "button"; + button.value = "OK"; + button.onclick = ok_function; + dlg_form.appendChild(button); + } + + if (cancel_function) { + button = document.createElement("input"); + button.type = "button"; + button.value = "Cancel"; + button.onclick = cancel_function; + dlg_form.appendChild(button); + } + + document.body.appendChild(dlg_dimmer); + document.body.appendChild(dlg_form); +} + +function dialog_cleanup() { + document.body.removeChild(dlg_dimmer); + document.body.removeChild(dlg_form); + dlg_dimmer = dlg_form = null; + onscreen_canvas.focus(); +} + // Init function called from body.onload. function initPuzzle() { // Construct the off-screen canvas used for double buffering. @@ -230,6 +296,58 @@ function initPuzzle() { command(9); }; + // 'number' is used for C pointers + get_save_file = Module.cwrap('get_save_file', 'number', []); + free_save_file = Module.cwrap('free_save_file', 'void', ['number']); + load_game = Module.cwrap('load_game', 'void', ['string', 'number']); + + document.getElementById("save").onclick = function(event) { + if (dlg_dimmer === null) { + var savefile_ptr = get_save_file(); + var savefile_text = Pointer_stringify(savefile_ptr); + free_save_file(savefile_ptr); + dialog_init("Download saved-game file"); + dlg_form.appendChild(document.createTextNode( + "Click to download the ")); + var a = document.createElement("a"); + a.download = "puzzle.sav"; + a.href = "data:application/octet-stream," + + encodeURIComponent(savefile_text); + a.appendChild(document.createTextNode("saved-game file")); + dlg_form.appendChild(a); + dlg_form.appendChild(document.createTextNode(".")); + dlg_form.appendChild(document.createElement("br")); + dialog_launch(function(event) { + dialog_cleanup(); + }); + } + }; + + document.getElementById("load").onclick = function(event) { + if (dlg_dimmer === null) { + dialog_init("Upload saved-game file"); + var input = document.createElement("input"); + input.type = "file"; + input.multiple = false; + dlg_form.appendChild(input); + dlg_form.appendChild(document.createElement("br")); + dialog_launch(function(event) { + if (input.files.length == 1) { + var file = input.files.item(0); + var reader = new FileReader(); + reader.addEventListener("loadend", function() { + var string = reader.result; + load_game(string, string.length); + }); + reader.readAsBinaryString(file); + } + dialog_cleanup(); + }, function(event) { + dialog_cleanup(); + }); + } + }; + gametypelist = document.getElementById("gametype"); gametypesubmenus.push(gametypelist); diff --git a/apps/plugins/puzzles/src/emccx.json b/apps/plugins/puzzles/src/emccx.json index e03f7e25c7..bdab346d79 100644 --- a/apps/plugins/puzzles/src/emccx.json +++ b/apps/plugins/puzzles/src/emccx.json @@ -18,6 +18,10 @@ '_timer_callback', // Callback from button presses in the UI outside the canvas '_command', + // Game-saving and game-loading functions + '_get_save_file', + '_free_save_file', + '_load_game', // Callbacks to return values from dialog boxes '_dlg_return_sval', '_dlg_return_ival', diff --git a/apps/plugins/puzzles/src/gtk.c b/apps/plugins/puzzles/src/gtk.c index c5e3d1c997..c212522957 100644 --- a/apps/plugins/puzzles/src/gtk.c +++ b/apps/plugins/puzzles/src/gtk.c @@ -140,7 +140,7 @@ struct font { */ struct frontend { GtkWidget *window; - GtkAccelGroup *accelgroup; + GtkAccelGroup *dummy_accelgroup; GtkWidget *area; GtkWidget *statusbar; GtkWidget *menubar; @@ -1160,16 +1160,6 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) if (!backing_store_ok(fe)) return TRUE; -#if !GTK_CHECK_VERSION(2,0,0) - /* Gtk 1.2 passes a key event to this function even if it's also - * defined as an accelerator. - * Gtk 2 doesn't do this, and this function appears not to exist there. */ - if (fe->accelgroup && - gtk_accel_group_get_entry(fe->accelgroup, - event->keyval, event->state)) - return TRUE; -#endif - /* Handle mnemonics. */ if (gtk_window_activate_key(GTK_WINDOW(fe->window), event)) return TRUE; @@ -1216,6 +1206,8 @@ static gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_KP_Delete) keyval = '\177'; + else if ((event->keyval == 'z' || event->keyval == 'Z') && shift && ctrl) + keyval = UI_REDO; else if (event->string[0] && !event->string[1]) keyval = (unsigned char)event->string[0]; else @@ -2348,32 +2340,34 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data) #endif } -static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont, - char *text, int key) +static GtkWidget *add_menu_ui_item( + frontend *fe, GtkContainer *cont, char *text, int action, + int accel_key, int accel_keyqual) { GtkWidget *menuitem = gtk_menu_item_new_with_label(text); - int keyqual; gtk_container_add(cont, menuitem); - g_object_set_data(G_OBJECT(menuitem), "user-data", GINT_TO_POINTER(key)); + g_object_set_data(G_OBJECT(menuitem), "user-data", + GINT_TO_POINTER(action)); g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_key_event), fe); - switch (key & ~0x1F) { - case 0x00: - key += 0x60; - keyqual = GDK_CONTROL_MASK; - break; - case 0x40: - key += 0x20; - keyqual = GDK_SHIFT_MASK; - break; - default: - keyqual = 0; - break; + + if (accel_key) { + /* + * Display a keyboard accelerator alongside this menu item. + * Actually this won't be processed via the usual GTK + * accelerator system, because we add it to a dummy + * accelerator group which is never actually activated on the + * main window; this permits back ends to override special + * keys like 'n' and 'r' and 'u' in some UI states. So + * whatever keystroke we display here will still go to + * key_event and be handled in the normal way. + */ + gtk_widget_add_accelerator(menuitem, + "activate", fe->dummy_accelgroup, + accel_key, accel_keyqual, + GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED); } - gtk_widget_add_accelerator(menuitem, - "activate", fe->accelgroup, - key, keyqual, - GTK_ACCEL_VISIBLE); + gtk_widget_show(menuitem); return menuitem; } @@ -2535,8 +2529,11 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_container_add(GTK_CONTAINER(fe->window), GTK_WIDGET(vbox)); gtk_widget_show(GTK_WIDGET(vbox)); - fe->accelgroup = gtk_accel_group_new(); - gtk_window_add_accel_group(GTK_WINDOW(fe->window), fe->accelgroup); + fe->dummy_accelgroup = gtk_accel_group_new(); + /* + * Intentionally _not_ added to the window via + * gtk_window_add_accel_group; see menu_key_event + */ hbox = GTK_BOX(gtk_hbox_new(FALSE, 0)); gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, FALSE, 0); @@ -2553,7 +2550,7 @@ static frontend *new_window(char *arg, int argtype, char **error) menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); - add_menu_item_with_key(fe, GTK_CONTAINER(menu), "New", 'n'); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "New", UI_NEWGAME, 'n', 0); menuitem = gtk_menu_item_new_with_label("Restart"); gtk_container_add(GTK_CONTAINER(menu), menuitem); @@ -2623,8 +2620,8 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_widget_show(menuitem); #ifndef STYLUS_BASED 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", 'r'); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "Undo", UI_UNDO, 'u', 0); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "Redo", UI_REDO, 'r', 0); #endif if (thegame.can_format_as_text_ever) { add_menu_separator(GTK_CONTAINER(menu)); @@ -2646,7 +2643,7 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_widget_show(menuitem); } add_menu_separator(GTK_CONTAINER(menu)); - add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q'); + add_menu_ui_item(fe, GTK_CONTAINER(menu), "Exit", UI_QUIT, 'q', 0); menuitem = gtk_menu_item_new_with_mnemonic("_Help"); gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem); @@ -2664,7 +2661,7 @@ static frontend *new_window(char *arg, int argtype, char **error) #ifdef STYLUS_BASED menuitem=gtk_button_new_with_mnemonic("_Redo"); g_object_set_data(G_OBJECT(menuitem), "user-data", - GINT_TO_POINTER((int)('r'))); + GINT_TO_POINTER(UI_REDO)); g_signal_connect(G_OBJECT(menuitem), "clicked", G_CALLBACK(menu_key_event), fe); gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); @@ -2672,7 +2669,7 @@ static frontend *new_window(char *arg, int argtype, char **error) menuitem=gtk_button_new_with_mnemonic("_Undo"); g_object_set_data(G_OBJECT(menuitem), "user-data", - GINT_TO_POINTER((int)('u'))); + GINT_TO_POINTER(UI_UNDO)); g_signal_connect(G_OBJECT(menuitem), "clicked", G_CALLBACK(menu_key_event), fe); gtk_box_pack_end(hbox, menuitem, FALSE, FALSE, 0); diff --git a/apps/plugins/puzzles/src/html/jspage.pl b/apps/plugins/puzzles/src/html/jspage.pl index a21f977166..b409783f15 100755 --- a/apps/plugins/puzzles/src/html/jspage.pl +++ b/apps/plugins/puzzles/src/html/jspage.pl @@ -3,6 +3,17 @@ use strict; use warnings; +my $jspath = ""; +while ($ARGV[0] =~ /^-/) { + my $opt = shift @ARGV; + last if $opt eq "--"; + if ($opt =~ /^--jspath=(.+)$/) { + $jspath = $1; + } else { + die "jspage.pl: unrecognised option '$opt'\n"; + } +} + open my $footerfile, "<", shift @ARGV or die "footer: open: $!\n"; my $footer = ""; $footer .= $_ while <$footerfile>; @@ -62,7 +73,7 @@ EOF