* gtk.c: GTK front end for my puzzle collection.
*/
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 1 /* for PATH_MAX */
+#endif
+
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>
+#include <limits.h>
+#include <unistd.h>
+#include <locale.h>
#include <sys/time.h>
#include <sys/resource.h>
*/
struct frontend {
GtkWidget *window;
- GtkAccelGroup *accelgroup;
+ GtkAccelGroup *dummy_accelgroup;
GtkWidget *area;
GtkWidget *statusbar;
GtkWidget *menubar;
+#if GTK_CHECK_VERSION(3,20,0)
+ GtkCssProvider *css_provider;
+#endif
guint statusctx;
int w, h;
midend *me;
char *filesel_name;
#endif
GSList *preset_radio;
- int n_preset_menu_items;
int preset_threaded;
GtkWidget *preset_custom;
GtkWidget *copy_menu_item;
static void set_window_background(frontend *fe, int colour)
{
-#if GTK_CHECK_VERSION(3,0,0)
+#if GTK_CHECK_VERSION(3,20,0)
+ char css_buf[512];
+ sprintf(css_buf, ".background { "
+ "background-color: #%02x%02x%02x; }",
+ (unsigned)(fe->colours[3*colour + 0] * 255),
+ (unsigned)(fe->colours[3*colour + 1] * 255),
+ (unsigned)(fe->colours[3*colour + 2] * 255));
+ if (!fe->css_provider)
+ fe->css_provider = gtk_css_provider_new();
+ if (!gtk_css_provider_load_from_data(
+ GTK_CSS_PROVIDER(fe->css_provider), css_buf, -1, NULL))
+ assert(0 && "Couldn't load CSS");
+ gtk_style_context_add_provider(
+ gtk_widget_get_style_context(fe->window),
+ GTK_STYLE_PROVIDER(fe->css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_style_context_add_provider(
+ gtk_widget_get_style_context(fe->area),
+ GTK_STYLE_PROVIDER(fe->css_provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+#elif GTK_CHECK_VERSION(3,0,0)
GdkRGBA rgba;
rgba.red = fe->colours[3*colour + 0];
rgba.green = fe->colours[3*colour + 1];
fe->image = NULL;
}
-static void wipe_and_destroy_cairo(frontend *fe, cairo_t *cr)
+static void wipe_and_maybe_destroy_cairo(frontend *fe, cairo_t *cr,
+ int destroy)
{
cairo_set_source_rgb(cr, fe->colours[0], fe->colours[1], fe->colours[2]);
cairo_paint(cr);
- cairo_destroy(cr);
+ if (destroy)
+ cairo_destroy(cr);
}
static void setup_backing_store(frontend *fe)
fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
fe->pw, fe->ph);
- wipe_and_destroy_cairo(fe, cairo_create(fe->image));
+ wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), TRUE);
#ifndef USE_CAIRO_WITHOUT_PIXMAP
- wipe_and_destroy_cairo(fe, gdk_cairo_create(fe->pixmap));
+ wipe_and_maybe_destroy_cairo(fe, gdk_cairo_create(fe->pixmap), TRUE);
+#endif
+#if GTK_CHECK_VERSION(3,22,0)
+ {
+ GdkWindow *gdkwin;
+ cairo_region_t *region;
+ GdkDrawingContext *drawctx;
+ cairo_t *cr;
+
+ gdkwin = gtk_widget_get_window(fe->area);
+ region = gdk_window_get_clip_region(gdkwin);
+ drawctx = gdk_window_begin_draw_frame(gdkwin, region);
+ cr = gdk_drawing_context_get_cairo_context(drawctx);
+ wipe_and_maybe_destroy_cairo(fe, cr, FALSE);
+ gdk_window_end_draw_frame(gdkwin, drawctx);
+ cairo_region_destroy(region);
+ }
+#else
+ wipe_and_maybe_destroy_cairo(
+ fe, gdk_cairo_create(gtk_widget_get_window(fe->area)), TRUE);
#endif
- wipe_and_destroy_cairo(fe, gdk_cairo_create
- (gtk_widget_get_window(fe->area)));
}
static int backing_store_ok(frontend *fe)
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;
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
button = RIGHT_BUTTON;
else if (event->button == 1)
button = LEFT_BUTTON;
+ else if (event->button == 8 && event->type == GDK_BUTTON_PRESS)
+ button = 'u';
+ else if (event->button == 9 && event->type == GDK_BUTTON_PRESS)
+ button = 'r';
else
return FALSE; /* don't even know what button! */
- if (event->type == GDK_BUTTON_RELEASE)
+ if (event->type == GDK_BUTTON_RELEASE && button >= LEFT_BUTTON)
button += LEFT_RELEASE - LEFT_BUTTON;
if (!midend_process_key(fe->me, event->x - fe->ox,
{
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;
-
- if (x != fe->w || y != fe->h || !backing_store_ok(fe)) {
+ fe->w = x;
+ fe->h = y;
+ midend_size(fe->me, &x, &y, TRUE);
+ fe->pw = x;
+ fe->ph = y;
+ fe->ox = (fe->w - fe->pw) / 2;
+ fe->oy = (fe->h - fe->ph) / 2;
+
+ if (oldw != fe->w || oldpw != fe->pw ||
+ oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) {
if (backing_store_ok(fe))
teardown_backing_store(fe);
-
- fe->w = x;
- fe->h = y;
- midend_size(fe->me, &x, &y, TRUE);
- fe->pw = x;
- fe->ph = y;
- fe->ox = (fe->w - fe->pw) / 2;
- fe->oy = (fe->h - fe->ph) / 2;
-
setup_backing_store(fe);
}
gtk_main_quit();
}
-static void msgbox_button_clicked(GtkButton *button, gpointer data)
-{
- GtkWidget *window = GTK_WIDGET(data);
- int v, *ip;
-
- ip = (int *)g_object_get_data(G_OBJECT(window), "user-data");
- v = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "user-data"));
- *ip = v;
-
- gtk_widget_destroy(GTK_WIDGET(data));
-}
-
static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
GObject *cancelbutton = G_OBJECT(data);
enum { MB_OK, MB_YESNO };
+static void align_label(GtkLabel *label, double x, double y)
+{
+#if GTK_CHECK_VERSION(3,16,0)
+ gtk_label_set_xalign(label, x);
+ gtk_label_set_yalign(label, y);
+#elif GTK_CHECK_VERSION(3,14,0)
+ gtk_widget_set_halign(GTK_WIDGET(label),
+ x == 0 ? GTK_ALIGN_START :
+ x == 1 ? GTK_ALIGN_END : GTK_ALIGN_CENTER);
+ gtk_widget_set_valign(GTK_WIDGET(label),
+ y == 0 ? GTK_ALIGN_START :
+ y == 1 ? GTK_ALIGN_END : GTK_ALIGN_CENTER);
+#else
+ gtk_misc_set_alignment(GTK_MISC(label), x, y);
+#endif
+}
+
+#if GTK_CHECK_VERSION(3,0,0)
+int message_box(GtkWidget *parent, char *title, char *msg, int centre,
+ int type)
+{
+ GtkWidget *window;
+ gint ret;
+
+ window = gtk_message_dialog_new
+ (GTK_WINDOW(parent),
+ (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
+ (type == MB_OK ? GTK_MESSAGE_INFO : GTK_MESSAGE_QUESTION),
+ (type == MB_OK ? GTK_BUTTONS_OK : GTK_BUTTONS_YES_NO),
+ "%s", msg);
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ ret = gtk_dialog_run(GTK_DIALOG(window));
+ gtk_widget_destroy(window);
+ return (type == MB_OK ? TRUE : (ret == GTK_RESPONSE_YES));
+}
+#else /* GTK_CHECK_VERSION(3,0,0) */
+static void msgbox_button_clicked(GtkButton *button, gpointer data)
+{
+ GtkWidget *window = GTK_WIDGET(data);
+ int v, *ip;
+
+ ip = (int *)g_object_get_data(G_OBJECT(window), "user-data");
+ v = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "user-data"));
+ *ip = v;
+
+ gtk_widget_destroy(GTK_WIDGET(data));
+}
+
int message_box(GtkWidget *parent, char *title, char *msg, int centre,
int type)
{
window = gtk_dialog_new();
text = gtk_label_new(msg);
- gtk_misc_set_alignment(GTK_MISC(text), 0.0, 0.0);
+ align_label(GTK_LABEL(text), 0.0, 0.0);
hbox = gtk_hbox_new(FALSE, 0);
gtk_box_pack_start(GTK_BOX(hbox), text, FALSE, FALSE, 20);
gtk_box_pack_start
gtk_main();
return (type == MB_YESNO ? i == 1 : TRUE);
}
+#endif /* GTK_CHECK_VERSION(3,0,0) */
void error_box(GtkWidget *parent, char *msg)
{
static int get_config(frontend *fe, int which)
{
GtkWidget *w, *table, *cancel;
+ GtkBox *content_box, *button_box;
char *title;
config_item *i;
int y;
fe->cfg_which = which;
fe->cfgret = FALSE;
+#if GTK_CHECK_VERSION(3,0,0)
+ /* GtkDialog isn't quite flexible enough */
+ fe->cfgbox = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ content_box = GTK_BOX(gtk_vbox_new(FALSE, 8));
+ g_object_set(G_OBJECT(content_box), "margin", 8, (const char *)NULL);
+ gtk_widget_show(GTK_WIDGET(content_box));
+ gtk_container_add(GTK_CONTAINER(fe->cfgbox), GTK_WIDGET(content_box));
+ button_box = GTK_BOX(gtk_hbox_new(FALSE, 8));
+ gtk_widget_show(GTK_WIDGET(button_box));
+ gtk_box_pack_end(content_box, GTK_WIDGET(button_box), FALSE, FALSE, 0);
+ {
+ GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show(sep);
+ gtk_box_pack_end(content_box, sep, FALSE, FALSE, 0);
+ }
+#else
fe->cfgbox = gtk_dialog_new();
+ content_box = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(fe->cfgbox)));
+ button_box = GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(fe->cfgbox)));
+#endif
gtk_window_set_title(GTK_WINDOW(fe->cfgbox), title);
sfree(title);
w = gtk_button_new_with_our_label(LABEL_CANCEL);
- gtk_box_pack_end
- (GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(fe->cfgbox))),
- w, FALSE, FALSE, 0);
+ gtk_box_pack_end(button_box, w, FALSE, FALSE, 0);
gtk_widget_show(w);
g_signal_connect(G_OBJECT(w), "clicked",
G_CALLBACK(config_cancel_button_clicked), fe);
cancel = w;
w = gtk_button_new_with_our_label(LABEL_OK);
- gtk_box_pack_end
- (GTK_BOX(gtk_dialog_get_action_area(GTK_DIALOG(fe->cfgbox))),
- w, FALSE, FALSE, 0);
+ gtk_box_pack_end(button_box, w, FALSE, FALSE, 0);
gtk_widget_show(w);
gtk_widget_set_can_default(w, TRUE);
gtk_window_set_default(GTK_WINDOW(fe->cfgbox), w);
table = gtk_table_new(1, 2, FALSE);
#endif
y = 0;
- gtk_box_pack_start
- (GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(fe->cfgbox))),
- table, FALSE, FALSE, 0);
+ gtk_box_pack_start(content_box, table, FALSE, FALSE, 0);
gtk_widget_show(table);
for (i = fe->cfg; i->type != C_END; i++) {
*/
w = gtk_label_new(i->name);
- gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5);
+ align_label(GTK_LABEL(w), 0.0, 0.5);
#if GTK_CHECK_VERSION(3,0,0)
gtk_grid_attach(GTK_GRID(table), w, 0, y, 1, 1);
#else
*/
w = gtk_label_new(i->name);
- gtk_misc_set_alignment(GTK_MISC(w), 0.0, 0.5);
+ align_label(GTK_LABEL(w), 0.0, 0.5);
#if GTK_CHECK_VERSION(3,0,0)
gtk_grid_attach(GTK_GRID(table), w, 0, y, 1, 1);
#else
TRUE);
} else {
GSList *gs = fe->preset_radio;
- int i = fe->n_preset_menu_items - 1 - n;
- if (fe->preset_custom)
- gs = gs->next;
- while (i && gs) {
- i--;
- gs = gs->next;
- }
- if (gs) {
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gs->data),
- TRUE);
- } else for (gs = fe->preset_radio; gs; gs = gs->next) {
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(gs->data),
- FALSE);
- }
+ GSList *found = NULL;
+
+ for (gs = fe->preset_radio; gs; gs = gs->next) {
+ struct preset_menu_entry *entry =
+ (struct preset_menu_entry *)g_object_get_data(
+ G_OBJECT(gs->data), "user-data");
+
+ if (entry && entry->id != n)
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(gs->data), FALSE);
+ else
+ found = gs;
+ }
+ if (found)
+ gtk_check_menu_item_set_active(
+ GTK_CHECK_MENU_ITEM(found->data), FALSE);
}
fe->preset_threaded = FALSE;
return FALSE;
}
+#if GTK_CHECK_VERSION(3,0,0)
+static int window_extra_height(frontend *fe)
+{
+ int ret = 0;
+ if (fe->menubar) {
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(fe->menubar, &req, NULL);
+ ret += req.height;
+ }
+ if (fe->statusbar) {
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(fe->statusbar, &req, NULL);
+ ret += req.height;
+ }
+ return ret;
+}
+#endif
+
static void resize_fe(frontend *fe)
{
int x, y;
get_size(fe, &x, &y);
#if GTK_CHECK_VERSION(3,0,0)
- gtk_window_resize_to_geometry(GTK_WINDOW(fe->window), x, y);
+ gtk_window_resize(GTK_WINDOW(fe->window), x, y + window_extra_height(fe));
#else
- fe->w = x;
- fe->h = y;
fe->drawing_area_shrink_pending = FALSE;
gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
{
static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
{
frontend *fe = (frontend *)data;
- game_params *params =
- (game_params *)g_object_get_data(G_OBJECT(menuitem), "user-data");
+ struct preset_menu_entry *entry =
+ (struct preset_menu_entry *)g_object_get_data(
+ G_OBJECT(menuitem), "user-data");
if (fe->preset_threaded ||
(GTK_IS_CHECK_MENU_ITEM(menuitem) &&
!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem))))
return;
- midend_set_params(fe->me, params);
+ midend_set_params(fe->me, entry->params);
midend_new_game(fe->me);
changed_preset(fe);
resize_fe(fe);
+ midend_redraw(fe->me);
}
GdkAtom compound_text_atom, utf8_string_atom;
NULL);
if (gtk_dialog_run(GTK_DIALOG(filesel)) == GTK_RESPONSE_ACCEPT) {
- const char *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
+ char *name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(filesel));
filesel_name = dupstr(name);
+ g_free(name);
}
gtk_widget_destroy(filesel);
" file \"%.*s\"?",
FILENAME_MAX, name);
if (!message_box(fe->window, "Question", buf, TRUE, MB_YESNO))
- return;
+ goto free_and_return;
}
fp = fopen(name, "w");
- sfree(name);
if (!fp) {
error_box(fe->window, "Unable to open save file");
- return;
+ goto free_and_return;
}
{
sprintf(boxmsg, "Error writing save file: %.400s",
strerror(errno));
error_box(fe->window, boxmsg);
- return;
+ goto free_and_return;
}
}
-
+ free_and_return:
+ sfree(name);
}
}
changed_preset(fe);
resize_fe(fe);
+ midend_redraw(fe->me);
}
}
midend_new_game(fe->me);
resize_fe(fe);
+ 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;
+
+#if GTK_CHECK_VERSION(3,0,0)
+ extern char *const *const xpm_icons[];
+ extern const int n_xpm_icons;
+ GdkPixbuf *icon = gdk_pixbuf_new_from_xpm_data
+ ((const gchar **)xpm_icons[n_xpm_icons-1]);
+ gtk_show_about_dialog
+ (GTK_WINDOW(fe->window),
+ "program-name", thegame.name,
+ "version", ver,
+ "comments", "Part of Simon Tatham's Portable Puzzle Collection",
+ "logo", icon,
+ (const gchar *)NULL);
+ g_object_unref(G_OBJECT(icon));
+#else
char titlebuf[256];
char textbuf[1024];
"%.500s", thegame.name, ver);
message_box(fe->window, titlebuf, textbuf, TRUE, MB_OK);
+#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;
}
gtk_widget_show(menuitem);
}
+static void populate_gtk_preset_menu(frontend *fe, struct preset_menu *menu,
+ GtkWidget *gtkmenu)
+{
+ int i;
+
+ for (i = 0; i < menu->n_entries; i++) {
+ struct preset_menu_entry *entry = &menu->entries[i];
+ GtkWidget *menuitem;
+
+ if (entry->params) {
+ menuitem = gtk_radio_menu_item_new_with_label(
+ fe->preset_radio, entry->title);
+ fe->preset_radio = gtk_radio_menu_item_get_group(
+ GTK_RADIO_MENU_ITEM(menuitem));
+ g_object_set_data(G_OBJECT(menuitem), "user-data", entry);
+ g_signal_connect(G_OBJECT(menuitem), "activate",
+ G_CALLBACK(menu_preset_event), fe);
+ } else {
+ GtkWidget *submenu;
+ menuitem = gtk_menu_item_new_with_label(entry->title);
+ submenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
+ populate_gtk_preset_menu(fe, entry->submenu, submenu);
+ }
+
+ gtk_container_add(GTK_CONTAINER(gtkmenu), menuitem);
+ gtk_widget_show(menuitem);
+ }
+}
+
enum { ARG_EITHER, ARG_SAVE, ARG_ID }; /* for argtype */
static frontend *new_window(char *arg, int argtype, char **error)
char errbuf[1024];
extern char *const *const xpm_icons[];
extern const int n_xpm_icons;
+ struct preset_menu *preset_menu;
fe = snew(frontend);
+#if GTK_CHECK_VERSION(3,20,0)
+ fe->css_provider = NULL;
+#endif
fe->timer_active = FALSE;
fe->timer_id = -1;
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);
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);
fe->preset_radio = NULL;
fe->preset_custom = NULL;
- fe->n_preset_menu_items = 0;
fe->preset_threaded = FALSE;
- if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) {
+
+ preset_menu = midend_get_presets(fe->me, NULL);
+ if (preset_menu->n_entries > 0 || thegame.can_configure) {
GtkWidget *submenu;
- int i;
menuitem = gtk_menu_item_new_with_mnemonic("_Type");
gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem);
submenu = gtk_menu_new();
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
- for (i = 0; i < n; i++) {
- char *name;
- game_params *params;
-
- midend_fetch_preset(fe->me, i, &name, ¶ms);
-
- menuitem =
- gtk_radio_menu_item_new_with_label(fe->preset_radio, name);
- fe->preset_radio =
- gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
- fe->n_preset_menu_items++;
- gtk_container_add(GTK_CONTAINER(submenu), menuitem);
- g_object_set_data(G_OBJECT(menuitem), "user-data", params);
- g_signal_connect(G_OBJECT(menuitem), "activate",
- G_CALLBACK(menu_preset_event), fe);
- gtk_widget_show(menuitem);
- }
+ populate_gtk_preset_menu(fe, preset_menu, submenu);
if (thegame.can_configure) {
menuitem = fe->preset_custom =
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));
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);
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",
#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);
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);
fe->statusctx = gtk_statusbar_get_context_id
(GTK_STATUSBAR(fe->statusbar), "game");
gtk_statusbar_push(GTK_STATUSBAR(fe->statusbar), fe->statusctx,
- "test");
+ DEFAULT_STATUSBAR_TEXT);
#if GTK_CHECK_VERSION(3,0,0)
gtk_widget_get_preferred_size(fe->statusbar, &req, NULL);
#else
#endif
{
GdkGeometry geom;
- geom.base_width = geom.base_height = 0;
+ geom.base_width = 0;
+#if GTK_CHECK_VERSION(3,0,0)
+ geom.base_height = window_extra_height(fe);
+ gtk_window_set_geometry_hints(GTK_WINDOW(fe->window), NULL,
+ &geom, GDK_HINT_BASE_SIZE);
+#else
+ geom.base_height = 0;
gtk_window_set_geometry_hints(GTK_WINDOW(fe->window), fe->area,
&geom, GDK_HINT_BASE_SIZE);
+#endif
}
+ fe->w = -1;
+ fe->h = -1;
get_size(fe, &x, &y);
#if GTK_CHECK_VERSION(3,0,0)
- gtk_window_set_default_geometry(GTK_WINDOW(fe->window), x, y);
+ gtk_window_set_default_size(GTK_WINDOW(fe->window),
+ x, y + window_extra_height(fe));
#else
fe->drawing_area_shrink_pending = FALSE;
gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y);
#endif
- fe->w = x;
- fe->h = y;
gtk_box_pack_end(vbox, fe->area, TRUE, TRUE, 0);
return ret;
}
+static void list_presets_from_menu(struct preset_menu *menu)
+{
+ int i;
+
+ for (i = 0; i < menu->n_entries; i++) {
+ if (menu->entries[i].params) {
+ char *paramstr = thegame.encode_params(
+ menu->entries[i].params, TRUE);
+ printf("%s %s\n", paramstr, menu->entries[i].title);
+ sfree(paramstr);
+ } else {
+ list_presets_from_menu(menu->entries[i].submenu);
+ }
+ }
+}
+
int main(int argc, char **argv)
{
char *pname = argv[0];
* Another specialist mode which causes the puzzle to list the
* game_params strings for all its preset configurations.
*/
- int i, npresets;
midend *me;
+ struct preset_menu *menu;
me = midend_new(NULL, &thegame, NULL, NULL);
- npresets = midend_num_presets(me);
-
- for (i = 0; i < npresets; i++) {
- game_params *params;
- char *name, *paramstr;
-
- midend_fetch_preset(me, i, &name, ¶ms);
- paramstr = thegame.encode_params(params, TRUE);
-
- printf("%s %s\n", paramstr, name);
- sfree(paramstr);
- }
-
+ menu = midend_get_presets(me, NULL);
+ list_presets_from_menu(menu);
midend_free(me);
return 0;
} else {