X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=gtk.c;h=b1175efd03c6d8a2b1ebc932f17d2a2f0e64d470;hb=db313b3948d27244dd7c34c2609c66d6204d8931;hp=559ff827ad8928225172febcdde054b422b86609;hpb=a800ff16bb10d41ed749d9b16e3eb3505bf9345e;p=sgt-puzzles.git diff --git a/gtk.c b/gtk.c index 559ff82..b1175ef 100644 --- a/gtk.c +++ b/gtk.c @@ -140,10 +140,13 @@ struct font { */ 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; @@ -179,7 +182,6 @@ struct frontend { char *filesel_name; #endif GSList *preset_radio; - int n_preset_menu_items; int preset_threaded; GtkWidget *preset_custom; GtkWidget *copy_menu_item; @@ -187,6 +189,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 { @@ -290,7 +324,27 @@ static void set_colour(frontend *fe, int colour) 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]; @@ -439,11 +493,13 @@ static void clear_backing_store(frontend *fe) 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) @@ -455,12 +511,29 @@ 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) @@ -1119,16 +1192,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; @@ -1175,6 +1238,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 @@ -1205,10 +1270,14 @@ static gint button_event(GtkWidget *widget, GdkEventButton *event, 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, @@ -1301,35 +1370,51 @@ static gint map_window(GtkWidget *widget, GdkEvent *event, 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); + fe->pw = x; + fe->ph = y; + fe->ox = (fe->w - fe->pw) / 2; + fe->oy = (fe->h - fe->ph) / 2; - if (x != fe->w || y != fe->h || !backing_store_ok(fe)) { + 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); } 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; @@ -1372,18 +1457,6 @@ static void window_destroy(GtkWidget *widget, gpointer data) 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); @@ -1401,6 +1474,54 @@ static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer 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) { @@ -1410,7 +1531,7 @@ int message_box(GtkWidget *parent, char *title, char *msg, int centre, 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 @@ -1464,6 +1585,7 @@ int message_box(GtkWidget *parent, char *title, char *msg, int centre, gtk_main(); return (type == MB_YESNO ? i == 1 : TRUE); } +#endif /* GTK_CHECK_VERSION(3,0,0) */ void error_box(GtkWidget *parent, char *msg) { @@ -1540,6 +1662,7 @@ static void droplist_sel(GtkComboBox *combo, gpointer data) static int get_config(frontend *fe, int which) { GtkWidget *w, *table, *cancel; + GtkBox *content_box, *button_box; char *title; config_item *i; int y; @@ -1548,23 +1671,38 @@ static int get_config(frontend *fe, int which) 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); @@ -1577,9 +1715,7 @@ static int get_config(frontend *fe, int which) 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++) { @@ -1594,7 +1730,7 @@ static int get_config(frontend *fe, int which) */ 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 @@ -1650,7 +1786,7 @@ static int get_config(frontend *fe, int which) */ 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 @@ -1787,20 +1923,21 @@ static void changed_preset(frontend *fe) 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), TRUE); } fe->preset_threaded = FALSE; @@ -1880,6 +2017,24 @@ static gint configure_window(GtkWidget *widget, 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; @@ -1887,7 +2042,8 @@ static void resize_fe(frontend *fe) 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)); + fe->awaiting_resize_ack = TRUE; #else fe->drawing_area_shrink_pending = FALSE; gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); @@ -1904,17 +2060,19 @@ static void resize_fe(frontend *fe) 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; @@ -2050,8 +2208,9 @@ static char *file_selector(frontend *fe, char *title, int save) 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); @@ -2101,15 +2260,14 @@ static void menu_save_event(GtkMenuItem *menuitem, gpointer data) " 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; } { @@ -2123,10 +2281,11 @@ static void menu_save_event(GtkMenuItem *menuitem, gpointer data) sprintf(boxmsg, "Error writing save file: %.400s", strerror(errno)); error_box(fe->window, boxmsg); - return; + goto free_and_return; } } - + free_and_return: + sfree(name); } } @@ -2157,6 +2316,7 @@ static void menu_load_event(GtkMenuItem *menuitem, gpointer data) changed_preset(fe); resize_fe(fe); + midend_redraw(fe->me); } } @@ -2194,11 +2354,27 @@ static void menu_config_event(GtkMenuItem *menuitem, gpointer data) midend_new_game(fe->me); resize_fe(fe); + midend_redraw(fe->me); } 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]; @@ -2209,34 +2385,37 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data) "%.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; } @@ -2248,6 +2427,36 @@ static void add_menu_separator(GtkContainer *cont) 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) @@ -2260,8 +2469,12 @@ 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; @@ -2357,6 +2570,10 @@ static frontend *new_window(char *arg, int argtype, char **error) } #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); @@ -2364,8 +2581,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); @@ -2382,7 +2602,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); @@ -2408,11 +2628,11 @@ static frontend *new_window(char *arg, int argtype, char **error) 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); @@ -2421,23 +2641,7 @@ static frontend *new_window(char *arg, int argtype, char **error) 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 = @@ -2468,8 +2672,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)); @@ -2491,7 +2695,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); @@ -2509,7 +2713,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); @@ -2517,7 +2721,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); @@ -2576,15 +2780,23 @@ static frontend *new_window(char *arg, int argtype, char **error) #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); @@ -2626,6 +2838,10 @@ static frontend *new_window(char *arg, int argtype, char **error) 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 | @@ -2680,6 +2896,22 @@ char *fgetline(FILE *fp) 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]; @@ -3083,23 +3315,12 @@ int main(int argc, char **argv) * 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 {