X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/60d42abc19c696300bceea9fcb6cd91eb28ca2d3..60302b597520eefdc4e00e8dab2350ab1cfd57b0:/gtk.c diff --git a/gtk.c b/gtk.c index 87f1151..b85cfa9 100644 --- a/gtk.c +++ b/gtk.c @@ -14,6 +14,11 @@ #include #include +#if GTK_CHECK_VERSION(2,0,0) && !defined HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION +#include +#include +#endif + #include "puzzles.h" /* ---------------------------------------------------------------------- @@ -73,6 +78,14 @@ struct frontend { GtkWidget *cfgbox; }; +void get_random_seed(void **randseed, int *randseedsize) +{ + time_t *tp = snew(time_t); + time(tp); + *randseed = (void *)tp; + *randseedsize = sizeof(time_t); +} + void frontend_default_colour(frontend *fe, float *output) { GdkColor col = fe->window->style->bg[GTK_STATE_NORMAL]; @@ -145,11 +158,60 @@ void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize, fe->fonts[i].type = fonttype; fe->fonts[i].size = fontsize; +#if GTK_CHECK_VERSION(2,0,0) /* - * FIXME: Really I should make at least _some_ effort to - * pick the correct font. + * Use Pango to find the closest match to the requested + * font. */ - fe->fonts[i].font = gdk_font_load("variable"); + { + PangoFontDescription *fd; + + fd = pango_font_description_new(); + /* `Monospace' and `Sans' are meta-families guaranteed to exist */ + pango_font_description_set_family(fd, fonttype == FONT_FIXED ? + "Monospace" : "Sans"); + pango_font_description_set_weight(fd, PANGO_WEIGHT_BOLD); + /* + * I found some online Pango documentation which + * described a function called + * pango_font_description_set_absolute_size(), which is + * _exactly_ what I want here. Unfortunately, none of + * my local Pango installations have it (presumably + * they're too old), so I'm going to have to hack round + * it by figuring out the point size myself. This + * limits me to X and probably also breaks in later + * Pango installations, so ideally I should add another + * CHECK_VERSION type ifdef and use set_absolute_size + * where available. All very annoying. + */ +#ifdef HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION + pango_font_description_set_absolute_size(fd, PANGO_SCALE*fontsize); +#else + { + Display *d = GDK_DISPLAY(); + int s = DefaultScreen(d); + double resolution = + (PANGO_SCALE * 72.27 / 25.4) * + ((double) DisplayWidthMM(d, s) / DisplayWidth (d, s)); + pango_font_description_set_size(fd, resolution * fontsize); + } +#endif + fe->fonts[i].font = gdk_font_from_description(fd); + pango_font_description_free(fd); + } + + if (!fe->fonts[i].font) +#endif + /* + * In GTK 1.2, I don't know of any plausible way to + * pick a suitable font, so I'm just going to be + * tedious. + * + * This is also fallback code called if the Pango + * approach fails to find an appropriate font. + */ + fe->fonts[i].font = gdk_font_load(fonttype == FONT_FIXED ? + "fixed" : "variable"); } /* @@ -158,11 +220,24 @@ void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize, { int lb, rb, wid, asc, desc; - gdk_string_extents(fe->fonts[i].font, text, + /* + * Measure vertical string extents with respect to the same + * string always... + */ + gdk_string_extents(fe->fonts[i].font, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", &lb, &rb, &wid, &asc, &desc); if (align & ALIGN_VCENTRE) y += asc - (asc+desc)/2; + /* + * ... but horizontal extents with respect to the provided + * string. This means that multiple pieces of text centred + * on the same y-coordinate don't have different baselines. + */ + gdk_string_extents(fe->fonts[i].font, text, + &lb, &rb, &wid, &asc, &desc); + if (align & ALIGN_HCENTRE) x -= wid / 2; else if (align & ALIGN_HRIGHT) @@ -231,6 +306,8 @@ void end_draw(frontend *fe) static void destroy(GtkWidget *widget, gpointer data) { + frontend *fe = (frontend *)data; + deactivate_timer(fe); gtk_main_quit(); } @@ -283,7 +360,7 @@ static gint button_event(GtkWidget *widget, GdkEventButton *event, if (!fe->pixmap) return TRUE; - if (event->type != GDK_BUTTON_PRESS) + if (event->type != GDK_BUTTON_PRESS && event->type != GDK_BUTTON_RELEASE) return TRUE; if (event->button == 2 || (event->state & GDK_SHIFT_MASK)) @@ -295,6 +372,33 @@ static gint button_event(GtkWidget *widget, GdkEventButton *event, else return FALSE; /* don't even know what button! */ + if (event->type == GDK_BUTTON_RELEASE) + button += LEFT_RELEASE - LEFT_BUTTON; + + if (!midend_process_key(fe->me, event->x, event->y, button)) + gtk_widget_destroy(fe->window); + + return TRUE; +} + +static gint motion_event(GtkWidget *widget, GdkEventMotion *event, + gpointer data) +{ + frontend *fe = (frontend *)data; + int button; + + if (!fe->pixmap) + return TRUE; + + if (event->state & (GDK_BUTTON2_MASK | GDK_SHIFT_MASK)) + button = MIDDLE_DRAG; + else if (event->state & GDK_BUTTON1_MASK) + button = LEFT_DRAG; + else if (event->state & GDK_BUTTON3_MASK) + button = RIGHT_DRAG; + else + return FALSE; /* don't even know what button! */ + if (!midend_process_key(fe->me, event->x, event->y, button)) gtk_widget_destroy(fe->window); @@ -434,6 +538,8 @@ void error_box(GtkWidget *parent, char *msg) gtk_window_set_default(GTK_WINDOW(window), ok); gtk_signal_connect(GTK_OBJECT(ok), "clicked", GTK_SIGNAL_FUNC(errmsg_button_clicked), window); + gtk_signal_connect(GTK_OBJECT(window), "destroy", + GTK_SIGNAL_FUNC(window_destroy), NULL); gtk_signal_connect(GTK_OBJECT(window), "key_press_event", GTK_SIGNAL_FUNC(win_key_press), ok); gtk_window_set_modal(GTK_WINDOW(window), TRUE); @@ -735,22 +841,28 @@ static void add_menu_separator(GtkContainer *cont) gtk_widget_show(menuitem); } -static frontend *new_window(void) +static frontend *new_window(char *game_id, char **error) { frontend *fe; GtkBox *vbox; GtkWidget *menubar, *menu, *menuitem; int x, y, n; - time_t t; fe = snew(frontend); - time(&t); - fe->me = midend_new(fe, &t, sizeof(t)); + fe->me = midend_new(fe, &thegame); + if (game_id) { + *error = midend_game_id(fe->me, game_id, FALSE); + if (*error) { + midend_free(fe->me); + sfree(fe); + return NULL; + } + } midend_new_game(fe->me); fe->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(fe->window), game_name); + gtk_window_set_title(GTK_WINDOW(fe->window), thegame.name); #if 0 gtk_window_set_resizable(GTK_WINDOW(fe->window), FALSE); #else @@ -782,7 +894,7 @@ static frontend *new_window(void) GTK_SIGNAL_FUNC(menu_config_event), fe); gtk_widget_show(menuitem); - if ((n = midend_num_presets(fe->me)) > 0 || game_can_configure) { + if ((n = midend_num_presets(fe->me)) > 0 || thegame.can_configure) { GtkWidget *submenu; int i; @@ -807,7 +919,7 @@ static frontend *new_window(void) gtk_widget_show(menuitem); } - if (game_can_configure) { + if (thegame.can_configure) { menuitem = gtk_menu_item_new_with_label("Custom..."); gtk_object_set_data(GTK_OBJECT(menuitem), "user-data", GPOINTER_TO_INT(CFG_SETTINGS)); @@ -893,6 +1005,10 @@ static frontend *new_window(void) GTK_SIGNAL_FUNC(key_event), fe); gtk_signal_connect(GTK_OBJECT(fe->area), "button_press_event", GTK_SIGNAL_FUNC(button_event), fe); + gtk_signal_connect(GTK_OBJECT(fe->area), "button_release_event", + GTK_SIGNAL_FUNC(button_event), fe); + gtk_signal_connect(GTK_OBJECT(fe->area), "motion_notify_event", + GTK_SIGNAL_FUNC(motion_event), fe); gtk_signal_connect(GTK_OBJECT(fe->area), "expose_event", GTK_SIGNAL_FUNC(expose_area), fe); gtk_signal_connect(GTK_OBJECT(fe->window), "map_event", @@ -900,7 +1016,10 @@ static frontend *new_window(void) gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event", GTK_SIGNAL_FUNC(configure_area), fe); - gtk_widget_add_events(GTK_WIDGET(fe->area), GDK_BUTTON_PRESS_MASK); + gtk_widget_add_events(GTK_WIDGET(fe->area), + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK); gtk_widget_show(fe->area); gtk_widget_show(fe->window); @@ -910,9 +1029,73 @@ static frontend *new_window(void) int main(int argc, char **argv) { - gtk_init(&argc, &argv); - (void) new_window(); - gtk_main(); + char *pname = argv[0]; + char *error; + + /* + * Special standalone mode for generating puzzle IDs on the + * command line. Useful for generating puzzles to be printed + * out and solved offline (for puzzles where that even makes + * sense - Solo, for example, is a lot more pencil-and-paper + * friendly than Net!) + * + * Usage: + * + * --generate [ []] + * + * , if present, is the number of puzzle IDs to generate. + * , if present, is the same type of parameter string + * you would pass to the puzzle when running it in GUI mode, + * including optional extras such as the expansion factor in + * Rectangles and the difficulty level in Solo. + * + * If you specify , you must also specify (although + * you may specify it to be 1). Sorry; that was the + * simplest-to-parse command-line syntax I came up with. + */ + if (argc > 1 && !strcmp(argv[1], "--generate")) { + int n = 1; + char *params = NULL; + game_params *par; + random_state *rs; + char *parstr; + + { + void *seed; + int seedlen; + get_random_seed(&seed, &seedlen); + rs = random_init(seed, seedlen); + } + + if (argc > 2) + n = atoi(argv[2]); + if (argc > 3) + params = argv[3]; + + if (params) + par = thegame.decode_params(params); + else + par = thegame.default_params(); + parstr = thegame.encode_params(par); + + while (n-- > 0) { + char *seed = thegame.new_seed(par, rs); + printf("%s:%s\n", parstr, seed); + sfree(seed); + } + + return 0; + } else { + + gtk_init(&argc, &argv); + + if (!new_window(argc > 1 ? argv[1] : NULL, &error)) { + fprintf(stderr, "%s: %s\n", pname, error); + return 1; + } + + gtk_main(); + } return 0; }