From: Ian Jackson Date: Tue, 3 Oct 2017 19:48:34 +0000 (+0100) Subject: Merge branch 'master' of https://git.tartarus.org/simon/puzzles into widelines X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=commitdiff_plain;ds=sidebyside;h=c58e0c245845ba619c52ed1f63bd6db4388d729a;hp=-c;p=sgt-puzzles.git Merge branch 'master' of https://git.tartarus.org/simon/puzzles into widelines --- c58e0c245845ba619c52ed1f63bd6db4388d729a diff --combined devel.but index 361add8,131678c..bf1c74f --- a/devel.but +++ b/devel.but @@@ -555,16 -555,15 +555,15 @@@ The \cw{config_item} structure contain \c char *name; \c int type; - \c char *sval; - \c int ival; + \c union { /* type-specific fields */ } u; + \e iiiiiiiiiiiiiiiiiiiiiiiiii \c{name} is an ASCII string giving the textual label for a GUI control. It is \e{not} expected to be dynamically allocated. \c{type} contains one of a small number of \c{enum} values defining - what type of control is being described. The meaning of the \c{sval} - and \c{ival} fields depends on the value in \c{type}. The valid - values are: + what type of control is being described. The usable member of the + union field \c{u} depends on \c{type}. The valid type values are: \dt \c{C_STRING} @@@ -572,38 -571,64 +571,64 @@@ input. The back end does not bother informing the front end that the box is numeric rather than textual; some front ends do have the capacity to take this into account, but I decided it wasn't worth - the extra complexity in the interface.) For this type, \c{ival} is - unused, and \c{sval} contains a dynamically allocated string - representing the contents of the input box. + the extra complexity in the interface.) + + \lcont{ + + For controls of this type, \c{u.string} contains a single field + + \c char *sval; + + which stores a dynamically allocated string representing the contents + of the input box. + + } \dt \c{C_BOOLEAN} - \dd Describes a simple checkbox. For this type, \c{sval} is unused, - and \c{ival} is \cw{TRUE} or \cw{FALSE}. + \dd Describes a simple checkbox. + + \lcont{ + + For controls of this type, \c{u.boolean} contains a single field + + \c int bval; + + which is either \cw{TRUE} or \cw{FALSE}. + + } \dt \c{C_CHOICES} \dd Describes a drop-down list presenting one of a small number of - fixed choices. For this type, \c{sval} contains a list of strings - describing the choices; the very first character of \c{sval} is used - as a delimiter when processing the rest (so that the strings - \cq{:zero:one:two}, \cq{!zero!one!two} and \cq{xzeroxonextwo} all - define a three-element list containing \cq{zero}, \cq{one} and - \cq{two}). \c{ival} contains the index of the currently selected - element, numbering from zero (so that in the above example, 0 would - mean \cq{zero} and 2 would mean \cq{two}). + fixed choices. \lcont{ - Note that for this control type, \c{sval} is \e{not} dynamically - allocated, whereas it was for \c{C_STRING}. + For controls of this type, \c{u.choices} contains two fields: + + \c const char *choicenames; + \c int selected; + + \c{choicenames} contains a list of strings describing the choices. The + very first character of \c{sval} is used as a delimiter when + processing the rest (so that the strings \cq{:zero:one:two}, + \cq{!zero!one!two} and \cq{xzeroxonextwo} all define a three-element + list containing \cq{zero}, \cq{one} and \cq{two}). + + \c{selected} contains the index of the currently selected element, + numbering from zero (so that in the above example, 0 would mean + \cq{zero} and 2 would mean \cq{two}). + + Note that \c{u.choices.choicenames} is \e{not} dynamically allocated, + unlike \c{u.string.sval}. } \dt \c{C_END} - \dd Marks the end of the array of \c{config_item}s. All other fields - are unused. + \dd Marks the end of the array of \c{config_item}s. There is no + associated member of the union field \c{u} for this type. The array returned from this function is expected to have filled in the initial values of all the controls according to the input @@@ -639,7 -664,8 +664,8 @@@ function is never called and need not d \S{backend-validate-params} \cw{validate_params()} - \c char *(*validate_params)(const game_params *params, int full); + \c const char *(*validate_params)(const game_params *params, + \c int full); This function takes a \c{game_params} structure as input, and checks that the parameters described in it fall within sensible limits. (At @@@ -724,7 -750,8 +750,8 @@@ again in the game description \S{backend-validate-desc} \cw{validate_desc()} - \c char *(*validate_desc)(const game_params *params, const char *desc); + \c const char *(*validate_desc)(const game_params *params, + \c const char *desc); This function is given a game description, and its job is to validate that it describes a puzzle which makes sense. @@@ -907,10 -934,10 +934,10 @@@ divide mouse coordinates by it. in response to the input event; the puzzle was not interested in it at all. - \b Returning the empty string (\cw{""}) indicates that the input + \b Returning the special value \cw{UI_UPDATE} indicates that the input event has resulted in a change being made to the \c{game_ui} which - will require a redraw of the game window, but that no actual - \e{move} was made (i.e. no new \c{game_state} needs to be created). + will require a redraw of the game window, but that no actual \e{move} + was made (i.e. no new \c{game_state} needs to be created). \b Returning anything else indicates that a move was made and that a new \c{game_state} must be created. However, instead of actually @@@ -925,7 -952,7 +952,7 @@@ strings can be written to disk when sav The return value from \cw{interpret_move()} is expected to be dynamically allocated if and only if it is not either \cw{NULL} - \e{or} the empty string. + \e{or} the special string constant \c{UI_UPDATE}. After this function is called, the back end is permitted to rely on some subsequent operations happening in sequence: @@@ -1028,7 -1055,7 +1055,7 @@@ not even offer the \q{Solve} menu optio \S{backend-solve} \cw{solve()} \c char *(*solve)(const game_state *orig, const game_state *curr, - \c const char *aux, char **error); + \c const char *aux, const char **error); This function is called when the user selects the \q{Solve} option from the menu. @@@ -1934,7 -1961,8 +1961,8 @@@ This ensures that thin lines are visibl \S{drawing-draw-text} \cw{draw_text()} \c void draw_text(drawing *dr, int x, int y, int fonttype, - \c int fontsize, int align, int colour, char *text); + \c int fontsize, int align, int colour, + \c const char *text); Draws text in the puzzle window. @@@ -2095,7 -2123,7 +2123,7 @@@ printing routines, that code may safel \S{drawing-status-bar} \cw{status_bar()} - \c void status_bar(drawing *dr, char *text); + \c void status_bar(drawing *dr, const char *text); Sets the text in the game's status bar to \c{text}. The text is copied from the supplied buffer, so the caller is free to deallocate or @@@ -2366,7 -2394,8 +2394,8 @@@ function \cw{drawing_new()} (see \k{dra \S{drawingapi-draw-text} \cw{draw_text()} \c void (*draw_text)(void *handle, int x, int y, int fonttype, - \c int fontsize, int align, int colour, char *text); + \c int fontsize, int align, int colour, + \c const char *text); This function behaves exactly like the back end \cw{draw_text()} function; see \k{drawing-draw-text}. @@@ -2469,7 -2498,7 +2498,7 @@@ called unless drawing is attempted \S{drawingapi-status-bar} \cw{status_bar()} - \c void (*status_bar)(void *handle, char *text); + \c void (*status_bar)(void *handle, const char *text); This function behaves exactly like the back end \cw{status_bar()} function; see \k{drawing-status-bar}. @@@ -3129,8 -3158,8 +3158,8 @@@ will probably need to pass it to \cw{mi \H{midend-set-config} \cw{midend_set_config()} - \c char *midend_set_config(midend *me, int which, - \c config_item *cfg); + \c const char *midend_set_config(midend *me, int which, + \c config_item *cfg); Passes the mid-end the results of a configuration dialog box. \c{which} should have the same value which it had when @@@ -3151,7 -3180,7 +3180,7 @@@ using \cw{midend_size()} and eventuall \H{midend-game-id} \cw{midend_game_id()} - \c char *midend_game_id(midend *me, char *id); + \c const char *midend_game_id(midend *me, const char *id); Passes the mid-end a string game ID (of any of the valid forms \cq{params}, \cq{params:description} or \cq{params#seed}) which the @@@ -3219,7 -3248,7 +3248,7 @@@ conversion \H{midend-solve} \cw{midend_solve()} - \c char *midend_solve(midend *me); + \c const char *midend_solve(midend *me); Requests the mid-end to perform a Solve operation. @@@ -3267,8 -3296,7 +3296,7 @@@ visually activate and deactivate a red \H{midend-serialise} \cw{midend_serialise()} \c void midend_serialise(midend *me, - \c void (*write)(void *ctx, void *buf, int len), - \c void *wctx); + \c void (*write)(void *ctx, const void *buf, int len), void *wctx); Calling this function causes the mid-end to convert its entire internal state into a long ASCII text string, and to pass that @@@ -3291,9 -3319,8 +3319,8 @@@ output string \H{midend-deserialise} \cw{midend_deserialise()} - \c char *midend_deserialise(midend *me, - \c int (*read)(void *ctx, void *buf, int len), - \c void *rctx); + \c const char *midend_deserialise(midend *me, + \c int (*read)(void *ctx, void *buf, int len), void *rctx); This function is the counterpart to \cw{midend_serialise()}. It calls the supplied \cw{read} function repeatedly to read a quantity @@@ -3330,9 -3357,8 +3357,8 @@@ place \H{identify-game} \cw{identify_game()} - \c char *identify_game(char **name, - \c int (*read)(void *ctx, void *buf, int len), - \c void *rctx); + \c const char *identify_game(char **name, + \c int (*read)(void *ctx, void *buf, int len), void *rctx); This function examines a serialised midend stream, of the same kind used by \cw{midend_serialise()} and \cw{midend_deserialise()}, and @@@ -3482,7 -3508,7 +3508,7 @@@ calling \cw{midend_timer()} \H{frontend-fatal} \cw{fatal()} - \c void fatal(char *fmt, ...); + \c void fatal(const char *fmt, ...); This is called by some utility functions if they encounter a genuinely fatal error such as running out of memory. It is a @@@ -3737,10 -3763,10 +3763,10 @@@ quite everywhere. \c void free_cfg(config_item *cfg); - This function correctly frees an array of \c{config_item}s, - including walking the array until it gets to the end and freeing - precisely those \c{sval} fields which are expected to be dynamically - allocated. + This function correctly frees an array of \c{config_item}s, including + walking the array until it gets to the end and freeing any subsidiary + data items in each \c{u} sub-union which are expected to be + dynamically allocated. (See \k{backend-configure} for details of the \c{config_item} structure.) @@@ -4896,5 -4922,3 +4922,5 @@@ that the redraw and the animations (if to animate undo properly seems to be a common error. Other than that, just use your common sense. + +\versionid Simon Tatham's Portable Puzzle Collection, version 20161228.7cae89f diff --combined gtk.c index dd2ebe6,37ba807..30257cb --- a/gtk.c +++ b/gtk.c @@@ -2,10 -2,6 +2,10 @@@ * gtk.c: GTK front end for my puzzle collection. */ +#ifndef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 1 /* for PATH_MAX */ +#endif + #include #include #include @@@ -14,9 -10,6 +14,9 @@@ #include #include #include +#include +#include +#include #include #include @@@ -78,7 -71,7 +78,7 @@@ #ifdef DEBUGGING static FILE *debug_fp = NULL; - void dputs(char *buf) + void dputs(const char *buf) { if (!debug_fp) { debug_fp = fopen("debug.log", "w"); @@@ -92,7 -85,7 +92,7 @@@ } } - void debug_printf(char *fmt, ...) + void debug_printf(const char *fmt, ...) { char buf[4096]; va_list ap; @@@ -108,7 -101,7 +108,7 @@@ * Error reporting functions used elsewhere. */ - void fatal(char *fmt, ...) + void fatal(const char *fmt, ...) { va_list ap; @@@ -196,6 -189,38 +196,38 @@@ struct frontend int drawing_area_shrink_pending; int menubar_is_local; #endif + #if GTK_CHECK_VERSION(3,0,0) + /* + * This is used to get round an annoying lack of GTK notification + * message. If we request a window resize with + * gtk_window_resize(), we normally get back a "configure" event + * on the window and on its drawing area, and we respond to the + * latter by doing an appropriate resize of the puzzle. If the + * window is maximised, so that gtk_window_resize() _doesn't_ + * change its size, then that configure event never shows up. But + * if we requested the resize in response to a change of puzzle + * parameters (say, the user selected a differently-sized preset + * from the menu), then we would still like to be _notified_ that + * the window size was staying the same, so that we can respond by + * choosing an appropriate tile size for the new puzzle preset in + * the existing window size. + * + * Fortunately, in GTK 3, we may not get a "configure" event on + * the drawing area in this situation, but we still get a + * "size_allocate" event on the whole window (which, in other + * situations when we _do_ get a "configure" on the area, turns up + * second). So we treat _that_ event as indicating that if the + * "configure" event hasn't already shown up then it's not going + * to arrive. + * + * This flag is where we bookkeep this system. On + * gtk_window_resize we set this flag to true; the area's + * configure handler sets it back to false; then if that doesn't + * happen, the window's size_allocate handler does a fallback + * puzzle resize when it sees this flag still set to true. + */ + int awaiting_resize_ack; + #endif }; struct blitter { @@@ -239,7 -264,7 +271,7 @@@ void frontend_default_colour(frontend * #endif } - void gtk_status_bar(void *handle, char *text) + void gtk_status_bar(void *handle, const char *text) { frontend *fe = (frontend *)handle; @@@ -975,7 -1000,7 +1007,7 @@@ void gtk_unclip(void *handle } void gtk_draw_text(void *handle, int x, int y, int fonttype, int fontsize, - int align, int colour, char *text) + int align, int colour, const char *text) { frontend *fe = (frontend *)handle; int i; @@@ -1345,15 -1370,10 +1377,10 @@@ static gint map_window(GtkWidget *widge return TRUE; } - static gint configure_area(GtkWidget *widget, - GdkEventConfigure *event, gpointer data) + static void resize_puzzle_to_area(frontend *fe, int x, int y) { - frontend *fe = (frontend *)data; - int x, y; int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph; - x = event->width; - y = event->height; fe->w = x; fe->h = y; midend_size(fe->me, &x, &y, TRUE); @@@ -1370,10 -1390,31 +1397,31 @@@ } midend_force_redraw(fe->me); + } + static gint configure_area(GtkWidget *widget, + GdkEventConfigure *event, gpointer data) + { + frontend *fe = (frontend *)data; + resize_puzzle_to_area(fe, event->width, event->height); + fe->awaiting_resize_ack = FALSE; return TRUE; } + #if GTK_CHECK_VERSION(3,0,0) + static void window_size_alloc(GtkWidget *widget, GtkAllocation *allocation, + gpointer data) + { + frontend *fe = (frontend *)data; + if (fe->awaiting_resize_ack) { + GtkAllocation a; + gtk_widget_get_allocation(fe->area, &a); + resize_puzzle_to_area(fe, a.width, a.height); + fe->awaiting_resize_ack = FALSE; + } + } + #endif + static gint timer_func(gpointer data) { frontend *fe = (frontend *)data; @@@ -1451,8 -1492,8 +1499,8 @@@ static void align_label(GtkLabel *label } #if GTK_CHECK_VERSION(3,0,0) - int message_box(GtkWidget *parent, char *title, char *msg, int centre, - int type) + int message_box(GtkWidget *parent, const char *title, const char *msg, + int centre, int type) { GtkWidget *window; gint ret; @@@ -1546,7 -1587,7 +1594,7 @@@ int message_box(GtkWidget *parent, cha } #endif /* GTK_CHECK_VERSION(3,0,0) */ - void error_box(GtkWidget *parent, char *msg) + void error_box(GtkWidget *parent, const char *msg) { message_box(parent, "Error", msg, FALSE, MB_OK); } @@@ -1554,7 -1595,7 +1602,7 @@@ static void config_ok_button_clicked(GtkButton *button, gpointer data) { frontend *fe = (frontend *)data; - char *err; + const char *err; err = midend_set_config(fe->me, fe->cfg_which, fe->cfg); @@@ -1600,22 -1641,25 +1648,25 @@@ static void editbox_changed(GtkEditabl { config_item *i = (config_item *)data; - sfree(i->sval); - i->sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed))); + assert(i->type == C_STRING); + sfree(i->u.string.sval); + i->u.string.sval = dupstr(gtk_entry_get_text(GTK_ENTRY(ed))); } static void button_toggled(GtkToggleButton *tb, gpointer data) { config_item *i = (config_item *)data; - i->ival = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb)); + assert(i->type == C_BOOLEAN); + i->u.boolean.bval = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(tb)); } static void droplist_sel(GtkComboBox *combo, gpointer data) { config_item *i = (config_item *)data; - i->ival = gtk_combo_box_get_active(combo); + assert(i->type == C_CHOICES); + i->u.choices.selected = gtk_combo_box_get_active(combo); } static int get_config(frontend *fe, int which) @@@ -1710,7 -1754,7 +1761,7 @@@ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 3, 3); #endif - gtk_entry_set_text(GTK_ENTRY(w), i->sval); + gtk_entry_set_text(GTK_ENTRY(w), i->u.string.sval); g_signal_connect(G_OBJECT(w), "changed", G_CALLBACK(editbox_changed), i); g_signal_connect(G_OBJECT(w), "key_press_event", @@@ -1735,7 -1779,8 +1786,8 @@@ GTK_EXPAND | GTK_SHRINK | GTK_FILL, 3, 3); #endif - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), i->ival); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), + i->u.boolean.bval); gtk_widget_show(w); break; @@@ -1758,15 -1803,16 +1810,16 @@@ { int c; - char *p, *q, *name; + const char *p, *q; + char *name; GtkListStore *model; GtkCellRenderer *cr; GtkTreeIter iter; model = gtk_list_store_new(1, G_TYPE_STRING); - c = *i->sval; - p = i->sval+1; + c = *i->u.choices.choicenames; + p = i->u.choices.choicenames+1; while (*p) { q = p; @@@ -1787,7 -1833,8 +1840,8 @@@ w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(model)); - gtk_combo_box_set_active(GTK_COMBO_BOX(w), i->ival); + gtk_combo_box_set_active(GTK_COMBO_BOX(w), + i->u.choices.selected); cr = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), cr, TRUE); @@@ -1888,8 -1935,7 +1942,7 @@@ static void changed_preset(frontend *fe struct preset_menu_entry *entry = (struct preset_menu_entry *)g_object_get_data( G_OBJECT(gs->data), "user-data"); - - if (entry && entry->id != n) + if (!entry || entry->id != n) gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(gs->data), FALSE); else @@@ -1897,7 -1943,7 +1950,7 @@@ } if (found) gtk_check_menu_item_set_active( - GTK_CHECK_MENU_ITEM(found->data), FALSE); + GTK_CHECK_MENU_ITEM(found->data), TRUE); } fe->preset_threaded = FALSE; @@@ -2003,6 -2049,7 +2056,7 @@@ static void resize_fe(frontend *fe #if GTK_CHECK_VERSION(3,0,0) gtk_window_resize(GTK_WINDOW(fe->window), x, y + window_extra_height(fe)); + fe->awaiting_resize_ack = TRUE; #else fe->drawing_area_shrink_pending = FALSE; gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); @@@ -2121,7 -2168,7 +2175,7 @@@ static void filesel_ok(GtkButton *butto fe->filesel_name = dupstr(name); } - static char *file_selector(frontend *fe, char *title, int save) + static char *file_selector(frontend *fe, const char *title, int save) { GtkWidget *filesel = gtk_file_selection_new(title); @@@ -2152,7 -2199,7 +2206,7 @@@ #else - static char *file_selector(frontend *fe, char *title, int save) + static char *file_selector(frontend *fe, const char *title, int save) { char *filesel_name = NULL; @@@ -2184,7 -2231,7 +2238,7 @@@ struct savefile_write_ctx int error; }; - static void savefile_write(void *wctx, void *buf, int len) + static void savefile_write(void *wctx, const void *buf, int len) { struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)wctx; if (fwrite(buf, 1, len, ctx->fp) < len) @@@ -2251,7 -2298,8 +2305,8 @@@ static void menu_save_event(GtkMenuIte static void menu_load_event(GtkMenuItem *menuitem, gpointer data) { frontend *fe = (frontend *)data; - char *name, *err; + char *name; + const char *err; name = file_selector(fe, "Enter name of saved game file to load", FALSE); @@@ -2282,7 -2330,7 +2337,7 @@@ static void menu_solve_event(GtkMenuItem *menuitem, gpointer data) { frontend *fe = (frontend *)data; - char *msg; + const char *msg; msg = midend_solve(fe->me); @@@ -2316,89 -2364,6 +2371,89 @@@ static void menu_config_event(GtkMenuIt midend_redraw(fe->me); } +#ifndef HELP_BROWSER_PATH +#define HELP_BROWSER_PATH "xdg-open:sensible-browser" +#endif + +static void show_help(frontend *fe, const char *topic) +{ + const char *list = HELP_BROWSER_PATH; + char path[PATH_MAX + 1]; + struct { + const char *s; + int len; + } lang[3]; + int i; + + /* + * Search for help file, trying: + * 1. Version for this locale, ignoring encoding (HTML browsers + * must handle multiple encodings) + * 2. Version for this locale, ignoring encoding and country + * 3. English version + */ + lang[0].s = setlocale(LC_MESSAGES, NULL); + lang[0].len = strcspn(lang[0].s, ".@"); + lang[1].s = lang[0].s; + lang[1].len = strcspn(lang[1].s, "_"); + if (lang[1].len > lang[0].len) + lang[1].len = lang[0].len; + lang[2].s = "en"; + lang[2].len = 2; + for (i = 0; i < lenof(lang); i++) { + sprintf(path, "%s/sgt-puzzles/help/%.*s/%s.html", + SHAREDIR, lang[i].len, lang[i].s, topic); + if (access(path, R_OK) == 0) + break; + } + if (i == lenof(lang)) { + error_box(fe->window, "Help file is not installed"); + return; + } + + for (;;) { + size_t len; + char buf[PATH_MAX + 1]; + const char *command; + const char *argv[3]; + + len = strcspn(list, ":"); + if (len <= PATH_MAX) { + memcpy(buf, list, len); + buf[len] = 0; + if (buf[0] == '$') + command = getenv(buf + 1); + else + command = buf; + if (command) { + argv[0] = command; + argv[1] = path; + argv[2] = NULL; + if (g_spawn_async(NULL, (char **)argv, NULL, + G_SPAWN_SEARCH_PATH, + NULL, NULL, NULL, NULL)) + return; + } + } + + if (!list[len]) + break; + list += len + 1; + } + + error_box(fe->window, "Failed to start a help browser"); +} + +static void menu_help_contents_event(GtkMenuItem *menuitem, gpointer data) +{ + show_help((frontend *)data, "index"); +} + +static void menu_help_specific_event(GtkMenuItem *menuitem, gpointer data) +{ + show_help((frontend *)data, thegame.htmlhelp_topic); +} + static void menu_about_event(GtkMenuItem *menuitem, gpointer data) { frontend *fe = (frontend *)data; @@@ -2431,7 -2396,7 +2486,7 @@@ } static GtkWidget *add_menu_ui_item( - frontend *fe, GtkContainer *cont, char *text, int action, + frontend *fe, GtkContainer *cont, const char *text, int action, int accel_key, int accel_keyqual) { GtkWidget *menuitem = gtk_menu_item_new_with_label(text); @@@ -2524,7 -2489,7 +2579,7 @@@ static frontend *new_window(char *arg, fe->me = midend_new(fe, &thegame, >k_drawing, fe); if (arg) { - char *err; + const char *err; FILE *fp; errbuf[0] = '\0'; @@@ -2612,6 -2577,10 +2667,10 @@@ } #endif + #if GTK_CHECK_VERSION(3,0,0) + fe->awaiting_resize_ack = FALSE; + #endif + fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name); @@@ -2742,25 -2711,6 +2801,25 @@@ menu = gtk_menu_new(); gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu); + menuitem = gtk_menu_item_new_with_label("Contents"); + gtk_container_add(GTK_CONTAINER(menu), menuitem); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(menu_help_contents_event), fe); + gtk_widget_show(menuitem); + + if (thegame.htmlhelp_topic) { + char *item; + assert(thegame.name); + item = snewn(9+strlen(thegame.name), char); /*ick*/ + sprintf(item, "Help on %s", thegame.name); + menuitem = gtk_menu_item_new_with_label(item); + sfree(item); + gtk_container_add(GTK_CONTAINER(menu), menuitem); + g_signal_connect(G_OBJECT(menuitem), "activate", + G_CALLBACK(menu_help_specific_event), fe); + gtk_widget_show(menuitem); + } + menuitem = gtk_menu_item_new_with_label("About"); gtk_container_add(GTK_CONTAINER(menu), menuitem); g_signal_connect(G_OBJECT(menuitem), "activate", @@@ -2895,6 -2845,10 +2954,10 @@@ G_CALLBACK(configure_area), fe); g_signal_connect(G_OBJECT(fe->window), "configure_event", G_CALLBACK(configure_window), fe); + #if GTK_CHECK_VERSION(3,0,0) + g_signal_connect(G_OBJECT(fe->window), "size_allocate", + G_CALLBACK(window_size_alloc), fe); + #endif gtk_widget_add_events(GTK_WIDGET(fe->area), GDK_BUTTON_PRESS_MASK | @@@ -2974,7 -2928,7 +3037,7 @@@ int main(int argc, char **argv int soln = FALSE, colour = FALSE; float scale = 1.0F; float redo_proportion = 0.0F; - char *savefile = NULL, *savesuffix = NULL; + const char *savefile = NULL, *savesuffix = NULL; char *arg = NULL; int argtype = ARG_EITHER; char *screenshot_file = NULL; @@@ -3217,7 -3171,8 +3280,8 @@@ * generated descriptive game IDs.) */ while (ngenerate == 0 || i < n) { - char *pstr, *err, *seed; + char *pstr, *seed; + const char *err; struct rusage before, after; if (ngenerate == 0) { @@@ -3271,7 -3226,7 +3335,7 @@@ * re-entering the same game id, and then try to solve * it. */ - char *game_id, *err; + char *game_id; game_id = midend_get_game_id(me); err = midend_game_id(me, game_id); @@@ -3316,7 -3271,7 +3380,7 @@@ sprintf(realname, "%s%d%s", savefile, i, savesuffix); if (soln) { - char *err = midend_solve(me); + const char *err = midend_solve(me); if (err) { fprintf(stderr, "%s: unable to show solution: %s\n", realname, err); diff --combined pearl.c index ce04154,4f3be50..164ff48 --- a/pearl.c +++ b/pearl.c @@@ -234,29 -234,24 +234,24 @@@ static config_item *game_configure(cons ret[0].name = "Width"; ret[0].type = C_STRING; sprintf(buf, "%d", params->w); - ret[0].sval = dupstr(buf); - ret[0].ival = 0; + ret[0].u.string.sval = dupstr(buf); ret[1].name = "Height"; ret[1].type = C_STRING; sprintf(buf, "%d", params->h); - ret[1].sval = dupstr(buf); - ret[1].ival = 0; + ret[1].u.string.sval = dupstr(buf); ret[2].name = "Difficulty"; ret[2].type = C_CHOICES; - ret[2].sval = DIFFCONFIG; - ret[2].ival = params->difficulty; + ret[2].u.choices.choicenames = DIFFCONFIG; + ret[2].u.choices.selected = params->difficulty; ret[3].name = "Allow unsoluble"; ret[3].type = C_BOOLEAN; - ret[3].sval = NULL; - ret[3].ival = params->nosolve; + ret[3].u.boolean.bval = params->nosolve; ret[4].name = NULL; ret[4].type = C_END; - ret[4].sval = NULL; - ret[4].ival = 0; return ret; } @@@ -265,22 -260,20 +260,22 @@@ static game_params *custom_params(cons { game_params *ret = snew(game_params); - ret->w = atoi(cfg[0].sval); - ret->h = atoi(cfg[1].sval); - ret->difficulty = cfg[2].ival; - ret->nosolve = cfg[3].ival; + ret->w = atoi(cfg[0].u.string.sval); + ret->h = atoi(cfg[1].u.string.sval); + ret->difficulty = cfg[2].u.choices.selected; + ret->nosolve = cfg[3].u.boolean.bval; return ret; } - static char *validate_params(const game_params *params, int full) + static const char *validate_params(const game_params *params, int full) { if (params->w < 5) return "Width must be at least five"; if (params->h < 5) return "Height must be at least five"; if (params->difficulty < 0 || params->difficulty >= DIFFCOUNT) return "Unknown difficulty level"; + if (params->difficulty >= DIFF_TRICKY && params->w + params->h < 11) + return "Width or height must be at least six for Tricky"; return NULL; } @@@ -1394,7 -1387,7 +1389,7 @@@ static char *new_game_desc(const game_p return desc; } - static char *validate_desc(const game_params *params, const char *desc) + static const char *validate_desc(const game_params *params, const char *desc) { int i, sizesofar; const int totalsize = params->w * params->h; @@@ -1728,7 -1721,7 +1723,7 @@@ static char *solve_for_diff(game_state } static char *solve_game(const game_state *state, const game_state *currstate, - const char *aux, char **error) + const char *aux, const char **error) { game_state *solved = dup_game(state); int i, ret, sz = state->shared->sz; @@@ -2024,11 -2017,11 +2019,11 @@@ static char *mark_in_direction(const ga char ch = primary ? 'F' : 'M', *other; - if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return ""; + if (!INGRID(state, x, y) || !INGRID(state, x2, y2)) return UI_UPDATE; /* disallow laying a mark over a line, or vice versa. */ other = primary ? state->marks : state->lines; - if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return ""; + if (other[y*w+x] & dir || other[y2*w+x2] & dir2) return UI_UPDATE; sprintf(buf, "%c%d,%d,%d;%c%d,%d,%d", ch, dir, x, y, ch, dir2, x2, y2); return dupstr(buf); @@@ -2062,12 -2055,12 +2057,12 @@@ static char *interpret_move(const game_ ui->dragcoords[0] = gy * w + gx; ui->ndragcoords = 0; /* will be 1 once drag is confirmed */ - return ""; + return UI_UPDATE; } if (button == LEFT_DRAG && ui->ndragcoords >= 0) { update_ui_drag(state, ui, gx, gy); - return ""; + return UI_UPDATE; } if (IS_MOUSE_RELEASE(button)) release = TRUE; @@@ -2089,30 -2082,30 +2084,30 @@@ if (ui->ndragcoords >= 0) update_ui_drag(state, ui, ui->curx, ui->cury); } - return ""; + return UI_UPDATE; } if (IS_CURSOR_SELECT(button)) { if (!ui->cursor_active) { ui->cursor_active = TRUE; - return ""; + return UI_UPDATE; } else if (button == CURSOR_SELECT) { if (ui->ndragcoords == -1) { ui->ndragcoords = 0; ui->dragcoords[0] = ui->cury * w + ui->curx; ui->clickx = CENTERED_COORD(ui->curx); ui->clicky = CENTERED_COORD(ui->cury); - return ""; + return UI_UPDATE; } else release = TRUE; } else if (button == CURSOR_SELECT2 && ui->ndragcoords >= 0) { ui->ndragcoords = -1; - return ""; + return UI_UPDATE; } } if (button == 27 || button == '\b') { ui->ndragcoords = -1; - return ""; + return UI_UPDATE; } if (release) { @@@ -2144,7 -2137,7 +2139,7 @@@ ui->ndragcoords = -1; - return buf ? buf : ""; + return buf ? buf : UI_UPDATE; } else if (ui->ndragcoords == 0) { /* Click (or tiny drag). Work out which edge we were * closest to. */ @@@ -2165,12 -2158,12 +2160,12 @@@ cx = CENTERED_COORD(gx); cy = CENTERED_COORD(gy); - if (!INGRID(state, gx, gy)) return ""; + if (!INGRID(state, gx, gy)) return UI_UPDATE; if (max(abs(x-cx),abs(y-cy)) < TILE_SIZE/4) { /* TODO closer to centre of grid: process as a cell click not an edge click. */ - return ""; + return UI_UPDATE; } else { int direction; if (abs(x-cx) < abs(y-cy)) { @@@ -2714,7 -2707,8 +2709,8 @@@ int main(int argc, const char *argv[] game_params *p = NULL; random_state *rs = NULL; time_t seed = time(NULL); - char *id = NULL, *err; + char *id = NULL; + const char *err; setvbuf(stdout, NULL, _IONBF, 0); diff --combined slant.c index e45a2ea,3fd6611..c9b6b40 --- a/slant.c +++ b/slant.c @@@ -184,24 -184,20 +184,20 @@@ static config_item *game_configure(cons ret[0].name = "Width"; ret[0].type = C_STRING; sprintf(buf, "%d", params->w); - ret[0].sval = dupstr(buf); - ret[0].ival = 0; + ret[0].u.string.sval = dupstr(buf); ret[1].name = "Height"; ret[1].type = C_STRING; sprintf(buf, "%d", params->h); - ret[1].sval = dupstr(buf); - ret[1].ival = 0; + ret[1].u.string.sval = dupstr(buf); ret[2].name = "Difficulty"; ret[2].type = C_CHOICES; - ret[2].sval = DIFFCONFIG; - ret[2].ival = params->diff; + ret[2].u.choices.choicenames = DIFFCONFIG; + ret[2].u.choices.selected = params->diff; ret[3].name = NULL; ret[3].type = C_END; - ret[3].sval = NULL; - ret[3].ival = 0; return ret; } @@@ -210,14 -206,14 +206,14 @@@ static game_params *custom_params(cons { game_params *ret = snew(game_params); - ret->w = atoi(cfg[0].sval); - ret->h = atoi(cfg[1].sval); - ret->diff = cfg[2].ival; + ret->w = atoi(cfg[0].u.string.sval); + ret->h = atoi(cfg[1].u.string.sval); + ret->diff = cfg[2].u.choices.selected; return ret; } - static char *validate_params(const game_params *params, int full) + static const char *validate_params(const game_params *params, int full) { /* * (At least at the time of writing this comment) The grid @@@ -417,7 -413,7 +413,7 @@@ static void fill_square(int w, int h, i } static int vbitmap_clear(int w, int h, struct solver_scratch *sc, - int x, int y, int vbits, char *reason, ...) + int x, int y, int vbits, const char *reason, ...) { int done_something = FALSE; int vbit; @@@ -738,7 -734,7 +734,7 @@@ static int slant_solve(int w, int h, co int fs, bs, v; int c1, c2; #ifdef SOLVER_DIAGNOSTICS - char *reason = ""; + const char *reason = ""; #endif if (soln[y*w+x]) @@@ -1216,7 -1212,7 +1212,7 @@@ static char *new_game_desc(const game_p return desc; } - static char *validate_desc(const game_params *params, const char *desc) + static const char *validate_desc(const game_params *params, const char *desc) { int w = params->w, h = params->h, W = w+1, H = h+1; int area = W*H; @@@ -1460,7 -1456,7 +1456,7 @@@ static int check_completion(game_state } static char *solve_game(const game_state *state, const game_state *currstate, - const char *aux, char **error) + const char *aux, const char **error) { int w = state->p.w, h = state->p.h; signed char *soln; @@@ -1683,7 -1679,7 +1679,7 @@@ static char *interpret_move(const game_ } else if (IS_CURSOR_SELECT(button)) { if (!ui->cur_visible) { ui->cur_visible = 1; - return ""; + return UI_UPDATE; } x = ui->cur_x; y = ui->cur_y; @@@ -1692,7 -1688,7 +1688,7 @@@ } else if (IS_CURSOR_MOVE(button)) { move_cursor(button, &ui->cur_x, &ui->cur_y, w, h, 0); ui->cur_visible = 1; - return ""; + return UI_UPDATE; } else if (button == '\\' || button == '\b' || button == '/') { int x = ui->cur_x, y = ui->cur_y; if (button == ("\\" "\b" "/")[state->soln[y*w + x] + 1]) return NULL; @@@ -1793,7 -1789,11 +1789,7 @@@ static float *game_colours(frontend *fe float *ret = snewn(3 * NCOLOURS, float); /* CURSOR colour is a background highlight. */ - game_mkhighlight(fe, ret, COL_BACKGROUND, COL_CURSOR, -1); - - ret[COL_FILLEDSQUARE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0]; - ret[COL_FILLEDSQUARE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1]; - ret[COL_FILLEDSQUARE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; + game_mkhighlight(fe, ret, COL_BACKGROUND, COL_CURSOR, COL_FILLEDSQUARE); ret[COL_GRID * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.7F; ret[COL_GRID * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.7F; @@@ -2189,7 -2189,8 +2185,8 @@@ int main(int argc, char **argv { game_params *p; game_state *s; - char *id = NULL, *desc, *err; + char *id = NULL, *desc; + const char *err; int grade = FALSE; int ret, diff, really_verbose = FALSE; struct solver_scratch *sc; diff --combined towers.c index 656b8ad,9ccc6ae..3862639 --- a/towers.c +++ b/towers.c @@@ -212,18 -212,15 +212,15 @@@ static config_item *game_configure(cons ret[0].name = "Grid size"; ret[0].type = C_STRING; sprintf(buf, "%d", params->w); - ret[0].sval = dupstr(buf); - ret[0].ival = 0; + ret[0].u.string.sval = dupstr(buf); ret[1].name = "Difficulty"; ret[1].type = C_CHOICES; - ret[1].sval = DIFFCONFIG; - ret[1].ival = params->diff; + ret[1].u.choices.choicenames = DIFFCONFIG; + ret[1].u.choices.selected = params->diff; ret[2].name = NULL; ret[2].type = C_END; - ret[2].sval = NULL; - ret[2].ival = 0; return ret; } @@@ -232,13 -229,13 +229,13 @@@ static game_params *custom_params(cons { game_params *ret = snew(game_params); - ret->w = atoi(cfg[0].sval); - ret->diff = cfg[1].ival; + ret->w = atoi(cfg[0].u.string.sval); + ret->diff = cfg[1].u.choices.selected; return ret; } - static char *validate_params(const game_params *params, int full) + static const char *validate_params(const game_params *params, int full) { if (params->w < 3 || params->w > 9) return "Grid size must be between 3 and 9"; @@@ -388,12 -385,12 +385,12 @@@ static int solver_easy(struct latin_sol return ret; #ifdef STANDALONE_SOLVER - if (solver_show_working) - sprintf(prefix, "%*slower bounds for clue %s %d:\n", - solver_recurse_depth*4, "", - cluepos[c/w], c%w+1); - else - prefix[0] = '\0'; /* placate optimiser */ + if (solver_show_working) + sprintf(prefix, "%*slower bounds for clue %s %d:\n", + solver_recurse_depth*4, "", + cluepos[c/w], c%w+1); + else + prefix[0] = '\0'; /* placate optimiser */ #endif i = 0; @@@ -802,7 -799,7 +799,7 @@@ don * Gameplay. */ - static char *validate_desc(const game_params *params, const char *desc) + static const char *validate_desc(const game_params *params, const char *desc) { int w = params->w, a = w*w; const char *p = desc; @@@ -970,7 -967,7 +967,7 @@@ static void free_game(game_state *state } static char *solve_game(const game_state *state, const game_state *currstate, - const char *aux, char **error) + const char *aux, const char **error) { int w = state->par.w, a = w*w; int i, ret; @@@ -1349,7 -1346,7 +1346,7 @@@ static char *interpret_move(const game_ ui->hpencil = 0; } ui->hcursor = 0; - return ""; /* UI activity occurred */ + return UI_UPDATE; } if (button == RIGHT_BUTTON) { /* @@@ -1369,7 -1366,7 +1366,7 @@@ ui->hshow = 0; } ui->hcursor = 0; - return ""; /* UI activity occurred */ + return UI_UPDATE; } } else if (button == LEFT_BUTTON) { if (is_clue(state, tx, ty)) { @@@ -1394,13 -1391,13 +1391,13 @@@ } move_cursor(button, &ui->hx, &ui->hy, w, w, 0); ui->hshow = ui->hcursor = 1; - return ""; + return UI_UPDATE; } if (ui->hshow && (button == CURSOR_SELECT)) { ui->hpencil = 1 - ui->hpencil; ui->hcursor = 1; - return ""; + return UI_UPDATE; } if (ui->hshow && @@@ -2021,7 -2018,8 +2018,8 @@@ int main(int argc, char **argv { game_params *p; game_state *s; - char *id = NULL, *desc, *err; + char *id = NULL, *desc; + const char *err; int grade = FALSE; int ret, diff, really_show_working = FALSE;