Stop the analysis pass in Loopy's redraw routine from being
[sgt/puzzles] / gtk.c
diff --git a/gtk.c b/gtk.c
index 0b3355e..4222bd4 100644 (file)
--- 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);