X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/40570a4e392a0bc6b5985c90129c6281d38d1669..HEAD:/gtk.c diff --git a/gtk.c b/gtk.c index 0b3355e..4222bd4 100644 --- a/gtk.c +++ b/gtk.c @@ -40,6 +40,8 @@ # define USE_CAIRO #endif +/* #undef USE_CAIRO */ +/* #define NO_THICK_LINE */ #ifdef DEBUGGING static FILE *debug_fp = NULL; @@ -115,6 +117,7 @@ struct frontend { GtkAccelGroup *accelgroup; GtkWidget *area; GtkWidget *statusbar; + GtkWidget *menubar; guint statusctx; int w, h; midend *me; @@ -123,11 +126,13 @@ struct frontend { cairo_t *cr; cairo_surface_t *image; GdkPixmap *pixmap; + GdkColor background; /* for painting outside puzzle area */ #else GdkPixmap *pixmap; GdkGC *gc; GdkColor *colours; GdkColormap *colmap; + int backgroundindex; /* which of colours[] is background */ #endif int ncolours; int bbox_l, bbox_r, bbox_u, bbox_d; @@ -145,6 +150,7 @@ struct frontend { #ifdef OLD_FILESEL char *filesel_name; #endif + int drawing_area_shrink_pending; GSList *preset_radio; int n_preset_menu_items; int preset_threaded; @@ -236,18 +242,18 @@ static void set_colour(frontend *fe, int colour) static void set_window_background(frontend *fe, int colour) { GdkColormap *colmap; - GdkColor backg; colmap = gdk_colormap_get_system(); - backg.red = fe->colours[3*colour + 0] * 65535; - backg.green = fe->colours[3*colour + 1] * 65535; - backg.blue = fe->colours[3*colour + 2] * 65535; - if (!gdk_colormap_alloc_color(colmap, &backg, FALSE, FALSE)) { + fe->background.red = fe->colours[3*colour + 0] * 65535; + fe->background.green = fe->colours[3*colour + 1] * 65535; + fe->background.blue = fe->colours[3*colour + 2] * 65535; + if (!gdk_colormap_alloc_color(colmap, &fe->background, FALSE, FALSE)) { g_error("couldn't allocate background (#%02x%02x%02x)\n", - backg.red >> 8, backg.green >> 8, backg.blue >> 8); + fe->background.red >> 8, fe->background.green >> 8, + fe->background.blue >> 8); } - gdk_window_set_background(fe->area->window, &backg); - gdk_window_set_background(fe->window->window, &backg); + gdk_window_set_background(fe->area->window, &fe->background); + gdk_window_set_background(fe->window->window, &fe->background); } static PangoLayout *make_pango_layout(frontend *fe) @@ -297,6 +303,18 @@ static void do_draw_line(frontend *fe, int x1, int y1, int x2, int y2) cairo_stroke(fe->cr); } +static void do_draw_thick_line(frontend *fe, float thickness, + float x1, float y1, float x2, float y2) +{ + cairo_save(fe->cr); + cairo_set_line_width(fe->cr, thickness); + cairo_new_path(fe->cr); + cairo_move_to(fe->cr, x1, y1); + cairo_line_to(fe->cr, x2, y2); + cairo_stroke(fe->cr); + cairo_restore(fe->cr); +} + static void do_draw_poly(frontend *fe, int *coords, int npoints, int fillcolour, int outlinecolour) { @@ -394,15 +412,6 @@ static void teardown_backing_store(frontend *fe) fe->image = NULL; } -static void repaint_rectangle(frontend *fe, GtkWidget *widget, - int x, int y, int w, int h) -{ - gdk_draw_pixmap(widget->window, - widget->style->fg_gc[GTK_WIDGET_STATE(fe->area)], - fe->pixmap, - x - fe->ox, y - fe->oy, x, y, w, h); -} - #endif /* ---------------------------------------------------------------------- @@ -452,6 +461,7 @@ static void snaffle_colours(frontend *fe) static void set_window_background(frontend *fe, int colour) { + fe->backgroundindex = colour; gdk_window_set_background(fe->area->window, &fe->colours[colour]); gdk_window_set_background(fe->window->window, &fe->colours[colour]); } @@ -518,6 +528,25 @@ static void do_draw_line(frontend *fe, int x1, int y1, int x2, int y2) gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2); } +static void do_draw_thick_line(frontend *fe, float thickness, + float x1, float y1, float x2, float y2) +{ + GdkGCValues save; + + gdk_gc_get_values(fe->gc, &save); + gdk_gc_set_line_attributes(fe->gc, + thickness, + GDK_LINE_SOLID, + GDK_CAP_BUTT, + GDK_JOIN_BEVEL); + gdk_draw_line(fe->pixmap, fe->gc, x1, y1, x2, y2); + gdk_gc_set_line_attributes(fe->gc, + save.line_width, + save.line_style, + save.cap_style, + save.join_style); +} + static void do_draw_poly(frontend *fe, int *coords, int npoints, int fillcolour, int outlinecolour) { @@ -631,17 +660,44 @@ static void teardown_backing_store(frontend *fe) fe->pixmap = NULL; } +#endif + static void repaint_rectangle(frontend *fe, GtkWidget *widget, int x, int y, int w, int h) { - gdk_draw_pixmap(widget->window, - widget->style->fg_gc[GTK_WIDGET_STATE(fe->area)], - fe->pixmap, + GdkGC *gc = gdk_gc_new(widget->window); +#ifdef USE_CAIRO + gdk_gc_set_foreground(gc, &fe->background); +#else + gdk_gc_set_foreground(gc, &fe->colours[fe->backgroundindex]); +#endif + if (x < fe->ox) { + gdk_draw_rectangle(widget->window, gc, + TRUE, x, y, fe->ox - x, h); + w -= (fe->ox - x); + x = fe->ox; + } + if (y < fe->oy) { + gdk_draw_rectangle(widget->window, gc, + TRUE, x, y, w, fe->oy - y); + h -= (fe->oy - y); + y = fe->oy; + } + if (w > fe->pw) { + gdk_draw_rectangle(widget->window, gc, + TRUE, x + fe->pw, y, w - fe->pw, h); + w = fe->pw; + } + if (h > fe->ph) { + gdk_draw_rectangle(widget->window, gc, + TRUE, x, y + fe->ph, w, h - fe->ph); + h = fe->ph; + } + gdk_draw_pixmap(widget->window, gc, fe->pixmap, x - fe->ox, y - fe->oy, x, y, w, h); + gdk_gc_unref(gc); } -#endif - /* ---------------------------------------------------------------------- * Pango font functions. */ @@ -850,6 +906,14 @@ void gtk_draw_line(void *handle, int x1, int y1, int x2, int y2, int colour) do_draw_line(fe, x1, y1, x2, y2); } +void gtk_draw_thick_line(void *handle, float thickness, + float x1, float y1, float x2, float y2, int colour) +{ + frontend *fe = (frontend *)handle; + set_colour(fe, colour); + do_draw_thick_line(fe, thickness, x1, y1, x2, y2); +} + void gtk_draw_poly(void *handle, int *coords, int npoints, int fillcolour, int outlinecolour) { @@ -955,6 +1019,11 @@ const struct drawing_api gtk_drawing = { #else NULL, #endif +#ifdef NO_THICK_LINE + NULL, +#else + gtk_draw_thick_line, +#endif }; static void destroy(GtkWidget *widget, gpointer data) @@ -1594,6 +1663,69 @@ static void changed_preset(frontend *fe) } } +static gboolean not_size_allocated_yet(GtkWidget *w) +{ + /* + * This function tests whether a widget has not yet taken up space + * on the screen which it will occupy in future. (Therefore, it + * returns true only if the widget does exist but does not have a + * size allocation. A null widget is already taking up all the + * space it ever will.) + */ + if (!w) + return FALSE; /* nonexistent widgets aren't a problem */ + +#if GTK_CHECK_VERSION(2,18,0) /* skip if no gtk_widget_get_allocation */ + { + GtkAllocation a; + gtk_widget_get_allocation(w, &a); + if (a.height == 0 || a.width == 0) + return TRUE; /* widget exists but has no size yet */ + } +#endif + + return FALSE; +} + +static void try_shrink_drawing_area(frontend *fe) +{ + if (fe->drawing_area_shrink_pending && + !not_size_allocated_yet(fe->menubar) && + !not_size_allocated_yet(fe->statusbar)) { + /* + * In order to permit the user to resize the window smaller as + * well as bigger, we call this function after the window size + * has ended up where we want it. This shouldn't shrink the + * window immediately; it just arranges that the next time the + * user tries to shrink it, they can. + * + * However, at puzzle creation time, we defer the first of + * these operations until after the menu bar and status bar + * are actually visible. On Ubuntu 12.04 I've found that these + * can take a while to be displayed, and that it's a mistake + * to reduce the drawing area's size allocation before they've + * turned up or else the drawing area makes room for them by + * shrinking to less than the size we intended. + */ + gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1); + fe->drawing_area_shrink_pending = FALSE; + } +} + +static gint configure_window(GtkWidget *widget, + GdkEventConfigure *event, gpointer data) +{ + frontend *fe = (frontend *)data; + /* + * When the main puzzle window changes size, it might be because + * the menu bar or status bar has turned up after starting off + * absent, in which case we should have another go at enacting a + * pending shrink of the drawing area. + */ + try_shrink_drawing_area(fe); + return FALSE; +} + static void resize_fe(frontend *fe) { int x, y; @@ -1601,18 +1733,15 @@ static void resize_fe(frontend *fe) get_size(fe, &x, &y); fe->w = x; fe->h = y; + fe->drawing_area_shrink_pending = FALSE; gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); { GtkRequisition req; gtk_widget_size_request(GTK_WIDGET(fe->window), &req); gtk_window_resize(GTK_WINDOW(fe->window), req.width, req.height); } - /* - * Now that we've established the preferred size of the window, - * reduce the drawing area's size request so the user can shrink - * the window. - */ - gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1); + fe->drawing_area_shrink_pending = TRUE; + try_shrink_drawing_area(fe); } static void menu_preset_event(GtkMenuItem *menuitem, gpointer data) @@ -1969,7 +2098,7 @@ static frontend *new_window(char *arg, int argtype, char **error) { frontend *fe; GtkBox *vbox, *hbox; - GtkWidget *menubar, *menu, *menuitem; + GtkWidget *menu, *menuitem; GdkPixmap *iconpm; GList *iconlist; int x, y, n; @@ -2058,12 +2187,12 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_box_pack_start(vbox, GTK_WIDGET(hbox), FALSE, FALSE, 0); gtk_widget_show(GTK_WIDGET(hbox)); - menubar = gtk_menu_bar_new(); - gtk_box_pack_start(hbox, menubar, TRUE, TRUE, 0); - gtk_widget_show(menubar); + fe->menubar = gtk_menu_bar_new(); + gtk_box_pack_start(hbox, fe->menubar, TRUE, TRUE, 0); + gtk_widget_show(fe->menubar); menuitem = gtk_menu_item_new_with_mnemonic("_Game"); - gtk_container_add(GTK_CONTAINER(menubar), menuitem); + gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem); gtk_widget_show(menuitem); menu = gtk_menu_new(); @@ -2102,7 +2231,7 @@ static frontend *new_window(char *arg, int argtype, char **error) int i; menuitem = gtk_menu_item_new_with_mnemonic("_Type"); - gtk_container_add(GTK_CONTAINER(menubar), menuitem); + gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem); gtk_widget_show(menuitem); submenu = gtk_menu_new(); @@ -2181,7 +2310,7 @@ static frontend *new_window(char *arg, int argtype, char **error) add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q'); menuitem = gtk_menu_item_new_with_mnemonic("_Help"); - gtk_container_add(GTK_CONTAINER(menubar), menuitem); + gtk_container_add(GTK_CONTAINER(fe->menubar), menuitem); gtk_widget_show(menuitem); menu = gtk_menu_new(); @@ -2261,6 +2390,7 @@ static frontend *new_window(char *arg, int argtype, char **error) GTK_WIDGET_UNSET_FLAGS(fe->area, GTK_DOUBLE_BUFFERED); #endif get_size(fe, &x, &y); + fe->drawing_area_shrink_pending = FALSE; gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), x, y); fe->w = x; fe->h = y; @@ -2294,6 +2424,8 @@ static frontend *new_window(char *arg, int argtype, char **error) GTK_SIGNAL_FUNC(map_window), fe); gtk_signal_connect(GTK_OBJECT(fe->area), "configure_event", GTK_SIGNAL_FUNC(configure_area), fe); + gtk_signal_connect(GTK_OBJECT(fe->window), "configure_event", + GTK_SIGNAL_FUNC(configure_window), fe); gtk_widget_add_events(GTK_WIDGET(fe->area), GDK_BUTTON_PRESS_MASK | @@ -2319,12 +2451,8 @@ static frontend *new_window(char *arg, int argtype, char **error) gtk_widget_show(fe->area); gtk_widget_show(fe->window); - /* - * Now that we've established the preferred size of the window, - * reduce the drawing area's size request so the user can shrink - * the window. - */ - gtk_drawing_area_size(GTK_DRAWING_AREA(fe->area), 1, 1); + fe->drawing_area_shrink_pending = TRUE; + try_shrink_drawing_area(fe); set_window_background(fe, 0); return fe; @@ -2527,11 +2655,6 @@ int main(int argc, char **argv) } } - if (*errbuf) { - fputs(errbuf, stderr); - return 1; - } - /* * Special standalone mode for generating puzzle IDs on the * command line. Useful for generating puzzles to be printed @@ -2559,6 +2682,16 @@ int main(int argc, char **argv) char *id; document *doc = NULL; + /* + * If we're in this branch, we should display any pending + * error message from the command line, since GTK isn't going + * to take another crack at making sense of it. + */ + if (*errbuf) { + fputs(errbuf, stderr); + return 1; + } + n = ngenerate; me = midend_new(NULL, &thegame, NULL, NULL); @@ -2631,13 +2764,23 @@ int main(int argc, char **argv) char *realname = snewn(40 + strlen(savefile) + strlen(savesuffix), char); sprintf(realname, "%s%d%s", savefile, i, savesuffix); + + if (soln) { + char *err = midend_solve(me); + if (err) { + fprintf(stderr, "%s: unable to show solution: %s\n", + realname, err); + return 1; + } + } + ctx.fp = fopen(realname, "w"); if (!ctx.fp) { fprintf(stderr, "%s: open: %s\n", realname, strerror(errno)); return 1; } - sfree(realname); + ctx.error = 0; midend_serialise(me, savefile_write, &ctx); if (ctx.error) { fprintf(stderr, "%s: write: %s\n", realname, @@ -2649,6 +2792,7 @@ int main(int argc, char **argv) strerror(errno)); return 1; } + sfree(realname); } if (!doc && !savefile) { id = midend_get_game_id(me);