Add HACKING to main doc build.
[sgt/puzzles] / gtk.c
diff --git a/gtk.c b/gtk.c
index 88f4a0a..1a79f0e 100644 (file)
--- a/gtk.c
+++ b/gtk.c
@@ -118,6 +118,7 @@ struct frontend {
     char *laststatus;
     int pw, ph;                        /* pixmap size (w, h are area size) */
     int ox, oy;                        /* offset of pixmap in drawing area */
+    char *filesel_name;
 };
 
 void get_random_seed(void **randseed, int *randseedsize)
@@ -347,7 +348,7 @@ void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
 }
 
 void draw_polygon(frontend *fe, int *coords, int npoints,
-                  int fill, int colour)
+                  int fillcolour, int outlinecolour)
 {
     GdkPoint *points = snewn(npoints, GdkPoint);
     int i;
@@ -357,19 +358,32 @@ void draw_polygon(frontend *fe, int *coords, int npoints,
         points[i].y = coords[i*2+1];
     }
 
-    gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
-    gdk_draw_polygon(fe->pixmap, fe->gc, fill, points, npoints);
+    if (fillcolour >= 0) {
+       gdk_gc_set_foreground(fe->gc, &fe->colours[fillcolour]);
+       gdk_draw_polygon(fe->pixmap, fe->gc, TRUE, points, npoints);
+    }
+    assert(outlinecolour >= 0);
+    gdk_gc_set_foreground(fe->gc, &fe->colours[outlinecolour]);
+    gdk_draw_polygon(fe->pixmap, fe->gc, FALSE, points, npoints);
 
     sfree(points);
 }
 
 void draw_circle(frontend *fe, int cx, int cy, int radius,
-                 int fill, int colour)
+                 int fillcolour, int outlinecolour)
 {
-    gdk_gc_set_foreground(fe->gc, &fe->colours[colour]);
-    gdk_draw_arc(fe->pixmap, fe->gc, fill,
-                 cx - radius, cy - radius,
-                 2 * radius, 2 * radius, 0, 360 * 64);
+    if (fillcolour >= 0) {
+       gdk_gc_set_foreground(fe->gc, &fe->colours[fillcolour]);
+       gdk_draw_arc(fe->pixmap, fe->gc, TRUE,
+                    cx - radius, cy - radius,
+                    2 * radius, 2 * radius, 0, 360 * 64);
+    }
+
+    assert(outlinecolour >= 0);
+    gdk_gc_set_foreground(fe->gc, &fe->colours[outlinecolour]);
+    gdk_draw_arc(fe->pixmap, fe->gc, FALSE,
+                cx - radius, cy - radius,
+                2 * radius, 2 * radius, 0, 360 * 64);
 }
 
 struct blitter {
@@ -450,6 +464,7 @@ static void destroy(GtkWidget *widget, gpointer data)
 {
     frontend *fe = (frontend *)data;
     deactivate_timer(fe);
+    midend_free(fe->me);
     gtk_main_quit();
 }
 
@@ -658,8 +673,15 @@ static void window_destroy(GtkWidget *widget, gpointer data)
     gtk_main_quit();
 }
 
-static void errmsg_button_clicked(GtkButton *button, gpointer data)
+static void msgbox_button_clicked(GtkButton *button, gpointer data)
 {
+    GtkWidget *window = GTK_WIDGET(data);
+    int v, *ip;
+
+    ip = (int *)gtk_object_get_data(GTK_OBJECT(window), "user-data");
+    v = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(button), "user-data"));
+    *ip = v;
+
     gtk_widget_destroy(GTK_WIDGET(data));
 }
 
@@ -678,9 +700,14 @@ static int win_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data)
     return FALSE;
 }
 
-void message_box(GtkWidget *parent, char *title, char *msg, int centre)
+enum { MB_OK, MB_YESNO };
+
+int message_box(GtkWidget *parent, char *title, char *msg, int centre,
+               int type)
 {
-    GtkWidget *window, *hbox, *text, *ok;
+    GtkWidget *window, *hbox, *text, *button;
+    char *titles;
+    int i, def, cancel;
 
     window = gtk_dialog_new();
     text = gtk_label_new(msg);
@@ -693,28 +720,54 @@ void message_box(GtkWidget *parent, char *title, char *msg, int centre)
     gtk_widget_show(hbox);
     gtk_window_set_title(GTK_WINDOW(window), title);
     gtk_label_set_line_wrap(GTK_LABEL(text), TRUE);
-    ok = gtk_button_new_with_label("OK");
-    gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area),
-                     ok, FALSE, FALSE, 0);
-    gtk_widget_show(ok);
-    GTK_WIDGET_SET_FLAGS(ok, GTK_CAN_DEFAULT);
-    gtk_window_set_default(GTK_WINDOW(window), ok);
-    gtk_signal_connect(GTK_OBJECT(ok), "clicked",
-                       GTK_SIGNAL_FUNC(errmsg_button_clicked), window);
+
+    if (type == MB_OK) {
+       titles = "OK\0";
+       def = cancel = 0;
+    } else {
+       assert(type == MB_YESNO);
+       titles = "Yes\0No\0";
+       def = 0;
+       cancel = 1;
+    }
+    i = 0;
+    
+    while (*titles) {
+       button = gtk_button_new_with_label(titles);
+       gtk_box_pack_end(GTK_BOX(GTK_DIALOG(window)->action_area),
+                        button, FALSE, FALSE, 0);
+       gtk_widget_show(button);
+       if (i == def) {
+           GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
+           gtk_window_set_default(GTK_WINDOW(window), button);
+       }
+       if (i == cancel) {
+           gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
+                              GTK_SIGNAL_FUNC(win_key_press), button);
+       }
+       gtk_signal_connect(GTK_OBJECT(button), "clicked",
+                          GTK_SIGNAL_FUNC(msgbox_button_clicked), window);
+       gtk_object_set_data(GTK_OBJECT(button), "user-data",
+                           GINT_TO_POINTER(i));
+       titles += strlen(titles)+1;
+       i++;
+    }
+    gtk_object_set_data(GTK_OBJECT(window), "user-data",
+                       GINT_TO_POINTER(&i));
     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);
     gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(parent));
     /* set_transient_window_pos(parent, window); */
     gtk_widget_show(window);
+    i = -1;
     gtk_main();
+    return (type == MB_YESNO ? i == 0 : TRUE);
 }
 
 void error_box(GtkWidget *parent, char *msg)
 {
-    message_box(parent, "Error", msg, FALSE);
+    message_box(parent, "Error", msg, FALSE, MB_OK);
 }
 
 static void config_ok_button_clicked(GtkButton *button, gpointer data)
@@ -1114,6 +1167,137 @@ static void menu_copy_event(GtkMenuItem *menuitem, gpointer data)
     }
 }
 
+static void filesel_ok(GtkButton *button, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    gpointer filesel = gtk_object_get_data(GTK_OBJECT(button), "user-data");
+
+    const char *name =
+        gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel));
+
+    fe->filesel_name = dupstr(name);
+}
+
+static char *file_selector(frontend *fe, char *title, int save)
+{
+    GtkWidget *filesel =
+        gtk_file_selection_new(title);
+
+    fe->filesel_name = NULL;
+
+    gtk_window_set_modal(GTK_WINDOW(filesel), TRUE);
+    gtk_object_set_data
+        (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "user-data",
+         (gpointer)filesel);
+    gtk_signal_connect
+        (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+         GTK_SIGNAL_FUNC(filesel_ok), fe);
+    gtk_signal_connect_object
+        (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->ok_button), "clicked",
+         GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
+    gtk_signal_connect_object
+        (GTK_OBJECT(GTK_FILE_SELECTION(filesel)->cancel_button), "clicked",
+         GTK_SIGNAL_FUNC(gtk_widget_destroy), (gpointer)filesel);
+    gtk_signal_connect(GTK_OBJECT(filesel), "destroy",
+                       GTK_SIGNAL_FUNC(window_destroy), NULL);
+    gtk_widget_show(filesel);
+    gtk_window_set_transient_for(GTK_WINDOW(filesel), GTK_WINDOW(fe->window));
+    gtk_main();
+
+    return fe->filesel_name;
+}
+
+static void savefile_write(void *wctx, void *buf, int len)
+{
+    FILE *fp = (FILE *)wctx;
+    fwrite(buf, 1, len, fp);
+}
+
+static int savefile_read(void *wctx, void *buf, int len)
+{
+    FILE *fp = (FILE *)wctx;
+    int ret;
+
+    ret = fread(buf, 1, len, fp);
+    return (ret == len);
+}
+
+static void menu_save_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *name;
+
+    name = file_selector(fe, "Enter name of game file to save", TRUE);
+
+    if (name) {
+        FILE *fp;
+
+       if ((fp = fopen(name, "r")) != NULL) {
+           char buf[256 + FILENAME_MAX];
+           fclose(fp);
+           /* file exists */
+
+           sprintf(buf, "Are you sure you want to overwrite the"
+                   " file \"%.*s\"?",
+                   FILENAME_MAX, name);
+           if (!message_box(fe->window, "Question", buf, TRUE, MB_YESNO))
+               return;
+       }
+
+       fp = fopen(name, "w");
+        sfree(name);
+
+        if (!fp) {
+            error_box(fe->window, "Unable to open save file");
+            return;
+        }
+
+        midend_serialise(fe->me, savefile_write, fp);
+
+        fclose(fp);
+    }
+}
+
+static void menu_load_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *name, *err;
+    int x, y;
+
+    name = file_selector(fe, "Enter name of saved game file to load", FALSE);
+
+    if (name) {
+        FILE *fp = fopen(name, "r");
+        sfree(name);
+
+        if (!fp) {
+            error_box(fe->window, "Unable to open saved game file");
+            return;
+        }
+
+        err = midend_deserialise(fe->me, savefile_read, fp);
+
+        fclose(fp);
+
+        if (err) {
+            error_box(fe->window, err);
+            return;
+        }
+
+        get_size(fe, &x, &y);
+        fe->w = x;
+        fe->h = y;
+        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);
+        }
+
+    }
+}
+
 static void menu_solve_event(GtkMenuItem *menuitem, gpointer data)
 {
     frontend *fe = (frontend *)data;
@@ -1166,7 +1350,7 @@ static void menu_about_event(GtkMenuItem *menuitem, gpointer data)
            "from Simon Tatham's Portable Puzzle Collection\n\n"
            "%.500s", thegame.name, ver);
 
-    message_box(fe->window, titlebuf, textbuf, TRUE);
+    message_box(fe->window, titlebuf, textbuf, TRUE, MB_OK);
 }
 
 static GtkWidget *add_menu_item_with_key(frontend *fe, GtkContainer *cont,
@@ -1296,6 +1480,17 @@ static frontend *new_window(char *game_id, char **error)
     }
 
     add_menu_separator(GTK_CONTAINER(menu));
+    menuitem = gtk_menu_item_new_with_label("Load");
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+                      GTK_SIGNAL_FUNC(menu_load_event), fe);
+    gtk_widget_show(menuitem);
+    menuitem = gtk_menu_item_new_with_label("Save");
+    gtk_container_add(GTK_CONTAINER(menu), menuitem);
+    gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+                      GTK_SIGNAL_FUNC(menu_save_event), fe);
+    gtk_widget_show(menuitem);
+    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", '\x12');
     if (thegame.can_format_as_text) {