chiark / gitweb /
Introduced a new function in every game which formats a game_state
authorSimon Tatham <anakin@pobox.com>
Sun, 1 May 2005 12:53:41 +0000 (12:53 +0000)
committerSimon Tatham <anakin@pobox.com>
Sun, 1 May 2005 12:53:41 +0000 (12:53 +0000)
as text. This is used by front ends to implement copy-to-clipboard.
Currently the function does nothing (and is disabled) in every game
except Solo, but it's a start.

[originally from svn r5724]

14 files changed:
cube.c
fifteen.c
gtk.c
midend.c
net.c
netslide.c
nullgame.c
osx.m
pattern.c
puzzles.h
rect.c
sixteen.c
solo.c
twiddle.c

diff --git a/cube.c b/cube.c
index 33144b6102b400ddc2928237779c3825365dca18..d2ef882d175a7cbe84c9e87618569fb7c1e59bea 100644 (file)
--- a/cube.c
+++ b/cube.c
@@ -979,6 +979,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 static game_ui *new_ui(game_state *state)
 {
     return NULL;
@@ -1545,6 +1550,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    NULL, game_text_format,
     new_ui,
     free_ui,
     make_move,
index dc265bed5fe6c2bed9113c3cf672a2df20dadb3d..3fad78a956e984d4c32fa73f74a322e60dd77b0e 100644 (file)
--- a/fifteen.c
+++ b/fifteen.c
@@ -370,6 +370,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 static game_ui *new_ui(game_state *state)
 {
     return NULL;
@@ -738,6 +743,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,
diff --git a/gtk.c b/gtk.c
index b85cfa9bc2d3829c082ad95f3ac5c72999f561e7..8c6a6340b7da6f96d2be8412d6fb0a102ef35ebd 100644 (file)
--- a/gtk.c
+++ b/gtk.c
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
 
-#if GTK_CHECK_VERSION(2,0,0) && !defined HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION
 #include <gdk/gdkx.h>
 #include <X11/Xlib.h>
-#endif
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
 
 #include "puzzles.h"
 
@@ -76,6 +76,8 @@ struct frontend {
     config_item *cfg;
     int cfg_which, cfgret;
     GtkWidget *cfgbox;
+    char *paste_data;
+    int paste_data_len;
 };
 
 void get_random_seed(void **randseed, int *randseedsize)
@@ -804,6 +806,113 @@ static void menu_preset_event(GtkMenuItem *menuitem, gpointer data)
     fe->h = y;
 }
 
+GdkAtom compound_text_atom, utf8_string_atom;
+int paste_initialised = FALSE;
+
+void init_paste()
+{
+    if (paste_initialised)
+       return;
+
+    if (!compound_text_atom)
+        compound_text_atom = gdk_atom_intern("COMPOUND_TEXT", FALSE);
+    if (!utf8_string_atom)
+        utf8_string_atom = gdk_atom_intern("UTF8_STRING", FALSE);
+
+    /*
+     * Ensure that all the cut buffers exist - according to the
+     * ICCCM, we must do this before we start using cut buffers.
+     */
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, "", 0);
+    XChangeProperty(GDK_DISPLAY(), GDK_ROOT_WINDOW(),
+                   XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, "", 0);
+}
+
+/* Store data in a cut-buffer. */
+void store_cutbuffer(char *ptr, int len)
+{
+    /* ICCCM says we must rotate the buffers before storing to buffer 0. */
+    XRotateBuffers(GDK_DISPLAY(), 1);
+    XStoreBytes(GDK_DISPLAY(), ptr, len);
+}
+
+void write_clip(frontend *fe, char *data)
+{
+    init_paste();
+
+    if (fe->paste_data)
+       sfree(fe->paste_data);
+
+    /*
+     * For this simple application we can safely assume that the
+     * data passed to this function is pure ASCII, which means we
+     * can return precisely the same stuff for types STRING,
+     * COMPOUND_TEXT or UTF8_STRING.
+     */
+
+    fe->paste_data = data;
+    fe->paste_data_len = strlen(data);
+
+    store_cutbuffer(fe->paste_data, fe->paste_data_len);
+
+    if (gtk_selection_owner_set(fe->area, GDK_SELECTION_PRIMARY,
+                               CurrentTime)) {
+       gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY,
+                                GDK_SELECTION_TYPE_STRING, 1);
+       gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY,
+                                compound_text_atom, 1);
+       gtk_selection_add_target(fe->area, GDK_SELECTION_PRIMARY,
+                                utf8_string_atom, 1);
+    }
+}
+
+void selection_get(GtkWidget *widget, GtkSelectionData *seldata,
+                  guint info, guint time_stamp, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    gtk_selection_data_set(seldata, seldata->target, 8,
+                          fe->paste_data, fe->paste_data_len);
+}
+
+gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata,
+                    gpointer data)
+{
+    frontend *fe = (frontend *)data;
+
+    if (fe->paste_data)
+       sfree(fe->paste_data);
+    fe->paste_data = NULL;
+    fe->paste_data_len = 0;
+    return TRUE;
+}
+
+static void menu_copy_event(GtkMenuItem *menuitem, gpointer data)
+{
+    frontend *fe = (frontend *)data;
+    char *text;
+
+    text = midend_text_format(fe->me);
+
+    if (text) {
+       write_clip(fe, text);
+    } else {
+       gdk_beep();
+    }
+}
+
 static void menu_config_event(GtkMenuItem *menuitem, gpointer data)
 {
     frontend *fe = (frontend *)data;
@@ -933,6 +1042,14 @@ static frontend *new_window(char *game_id, char **error)
     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) {
+       add_menu_separator(GTK_CONTAINER(menu));
+       menuitem = gtk_menu_item_new_with_label("Copy");
+       gtk_container_add(GTK_CONTAINER(menu), menuitem);
+       gtk_signal_connect(GTK_OBJECT(menuitem), "activate",
+                          GTK_SIGNAL_FUNC(menu_copy_event), fe);
+       gtk_widget_show(menuitem);
+    }
     add_menu_separator(GTK_CONTAINER(menu));
     add_menu_item_with_key(fe, GTK_CONTAINER(menu), "Exit", 'q');
 
@@ -999,6 +1116,9 @@ static frontend *new_window(char *game_id, char **error)
 
     fe->timer_active = FALSE;
 
+    fe->paste_data = NULL;
+    fe->paste_data_len = 0;
+
     gtk_signal_connect(GTK_OBJECT(fe->window), "destroy",
                       GTK_SIGNAL_FUNC(destroy), fe);
     gtk_signal_connect(GTK_OBJECT(fe->window), "key_press_event",
@@ -1009,6 +1129,10 @@ static frontend *new_window(char *game_id, char **error)
                       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), "selection_get",
+                      GTK_SIGNAL_FUNC(selection_get), fe);
+    gtk_signal_connect(GTK_OBJECT(fe->area), "selection_clear_event",
+                      GTK_SIGNAL_FUNC(selection_clear), 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",
index 30ad32bb8007538f85e08cfd8e26c9f38c1b9289..efe8111c51b80e46008a800f645e5f0507d64326 100644 (file)
--- a/midend.c
+++ b/midend.c
@@ -573,3 +573,11 @@ char *midend_set_config(midend_data *me, int which, config_item *cfg)
 
     return NULL;
 }
+
+char *midend_text_format(midend_data *me)
+{
+    if (me->ourgame->can_format_as_text && me->statepos > 0)
+       return me->ourgame->text_format(me->states[me->statepos-1]);
+    else
+       return NULL;
+}
diff --git a/net.c b/net.c
index d41cebed97edeb8f505efc40a824c290aafca640..78df30d9af52cd0b6077366339ad9b526454c609 100644 (file)
--- a/net.c
+++ b/net.c
@@ -699,6 +699,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 /* ----------------------------------------------------------------------
  * Utility routine.
  */
@@ -1508,6 +1513,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,
index 9f334f6229a0c82efa80e9f7649ba0e972854095..11ca2efdaa79538391ec8200b85fc76420a89f9c 100644 (file)
@@ -740,6 +740,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 /* ----------------------------------------------------------------------
  * Utility routine.
  */
@@ -1532,6 +1537,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,
index a071971b8d1ee07c745a6d83b750575f59453fe6..9e2ea323d3c4ac66a9a232b3c5b3622fbc23eecf 100644 (file)
@@ -121,6 +121,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 static game_ui *new_ui(game_state *state)
 {
     return NULL;
@@ -222,6 +227,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,
diff --git a/osx.m b/osx.m
index 1fd689f571ea06730b51e5095ff155bd41741b33..28af613020e042d1bf65dabe66df83f517f9345e 100644 (file)
--- a/osx.m
+++ b/osx.m
@@ -571,6 +571,28 @@ struct frontend {
     [self processButton:'r'&0x1F x:-1 y:-1];
 }
 
+- (void)copy:(id)sender
+{
+    char *text;
+
+    if ((text = midend_text_format(me)) != NULL) {
+       NSPasteboard *pb = [NSPasteboard generalPasteboard];
+       NSArray *a = [NSArray arrayWithObject:NSStringPboardType];
+       [pb declareTypes:a owner:nil];
+       [pb setString:[NSString stringWithCString:text]
+        forType:NSStringPboardType];
+    } else
+       NSBeep();
+}
+
+- (BOOL)validateMenuItem:(NSMenuItem *)item
+{
+    if ([item action] == @selector(copy:))
+       return (ourgame->can_format_as_text ? YES : NO);
+    else
+       return [super validateMenuItem:item];
+}
+
 - (void)clearTypeMenu
 {
     while ([typemenu numberOfItems] > 1)
@@ -1215,6 +1237,8 @@ int main(int argc, char **argv)
     item = newitem(menu, "Undo", "z", NULL, @selector(undoMove:));
     item = newitem(menu, "Redo", "S-z", NULL, @selector(redoMove:));
     [menu addItem:[NSMenuItem separatorItem]];
+    item = newitem(menu, "Copy", "c", NULL, @selector(copy:));
+    [menu addItem:[NSMenuItem separatorItem]];
     item = newitem(menu, "Close", "w", NULL, @selector(performClose:));
 
     menu = newsubmenu([NSApp mainMenu], "Type");
index 61f15ff5a8501290926acea2ea346f0698d90fe9..6dda1f48499c4faa1d281c3091e9baa641564e17 100644 (file)
--- a/pattern.c
+++ b/pattern.c
@@ -648,6 +648,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 struct game_ui {
     int dragging;
     int drag_start_x;
@@ -1029,6 +1034,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,
index e62a27cd2130500d97623d951675760a987cd2c9..d5c8924c7d3ac9bfb56a29d30cf6174475ff9e72 100644 (file)
--- a/puzzles.h
+++ b/puzzles.h
@@ -140,6 +140,7 @@ enum { CFG_SETTINGS, CFG_SEED };
 config_item *midend_get_config(midend_data *me, int which, char **wintitle);
 char *midend_set_config(midend_data *me, int which, config_item *cfg);
 char *midend_game_id(midend_data *me, char *id, int def_seed);
+char *midend_text_format(midend_data *me);
 
 /*
  * malloc.c
@@ -193,6 +194,8 @@ struct game {
     game_state *(*new_game)(game_params *params, char *seed);
     game_state *(*dup_game)(game_state *state);
     void (*free_game)(game_state *state);
+    int can_format_as_text;
+    char *(*text_format)(game_state *state);
     game_ui *(*new_ui)(game_state *state);
     void (*free_ui)(game_ui *ui);
     game_state *(*make_move)(game_state *from, game_ui *ui, int x, int y,
diff --git a/rect.c b/rect.c
index 5ab296a34a4241ed988c112eac548d816878c850..052b28464104e1cf240be8c23f3051448f950c64 100644 (file)
--- a/rect.c
+++ b/rect.c
@@ -997,6 +997,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 static unsigned char *get_correct(game_state *state)
 {
     unsigned char *ret;
@@ -1614,6 +1619,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,
index c90062bd8b7412b19ac497250d57071e2243136e..3a5d722b54504c2cf9f74dcfb21b8e71b4a32e08 100644 (file)
--- a/sixteen.c
+++ b/sixteen.c
@@ -379,6 +379,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 static game_ui *new_ui(game_state *state)
 {
     return NULL;
@@ -788,6 +793,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,
diff --git a/solo.c b/solo.c
index c60cd66c86c773daf48273d1f0fba563e68892bd..c453b26e8bc802590e74022426788c23a4216e22 100644 (file)
--- a/solo.c
+++ b/solo.c
@@ -1607,6 +1607,68 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *grid_text_format(int c, int r, digit *grid)
+{
+    int cr = c*r;
+    int x, y;
+    int maxlen;
+    char *ret, *p;
+
+    /*
+     * There are cr lines of digits, plus r-1 lines of block
+     * separators. Each line contains cr digits, cr-1 separating
+     * spaces, and c-1 two-character block separators. Thus, the
+     * total length of a line is 2*cr+2*c-3 (not counting the
+     * newline), and there are cr+r-1 of them.
+     */
+    maxlen = (cr+r-1) * (2*cr+2*c-2);
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y < cr; y++) {
+        for (x = 0; x < cr; x++) {
+            int ch = grid[y * cr + x];
+            if (ch == 0)
+                ch = ' ';
+            else if (ch <= 9)
+                ch = '0' + ch;
+            else
+                ch = 'a' + ch-10;
+            *p++ = ch;
+            if (x+1 < cr) {
+               *p++ = ' ';
+                if ((x+1) % r == 0) {
+                    *p++ = '|';
+                   *p++ = ' ';
+               }
+            }
+        }
+       *p++ = '\n';
+        if (y+1 < cr && (y+1) % c == 0) {
+            for (x = 0; x < cr; x++) {
+                *p++ = '-';
+                if (x+1 < cr) {
+                   *p++ = '-';
+                    if ((x+1) % r == 0) {
+                       *p++ = '+';
+                       *p++ = '-';
+                   }
+                }
+            }
+           *p++ = '\n';
+        }
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
+static char *game_text_format(game_state *state)
+{
+    return grid_text_format(state->c, state->r, state->grid);
+}
+
 struct game_ui {
     /*
      * These are the coordinates of the currently highlighted
@@ -1901,6 +1963,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    TRUE, game_text_format,
     new_ui,
     free_ui,
     make_move,
@@ -2034,38 +2097,7 @@ int main(int argc, char **argv)
         }
     }
 
-    for (y = 0; y < p->c * p->r; y++) {
-        for (x = 0; x < p->c * p->r; x++) {
-            int c = s->grid[y * p->c * p->r + x];
-            if (c == 0)
-                c = ' ';
-            else if (c <= 9)
-                c = '0' + c;
-            else
-                c = 'a' + c-10;
-            printf("%c", c);
-            if (x+1 < p->c * p->r) {
-                if ((x+1) % p->r)
-                    printf(" ");
-                else
-                    printf(" | ");
-            }
-        }
-        printf("\n");
-        if (y+1 < p->c * p->r && (y+1) % p->c == 0) {
-            for (x = 0; x < p->c * p->r; x++) {
-                printf("-");
-                if (x+1 < p->c * p->r) {
-                    if ((x+1) % p->r)
-                        printf("-");
-                    else
-                        printf("-+-");
-                }
-            }
-            printf("\n");
-        }
-    }
-    printf("\n");
+    printf("%s\n", grid_text_format(p->c, p->r, s->grid));
 
     return 0;
 }
index 0bfdabe24dc6a37012b544d094bd9d25b4c7512e..4bfd30cd78549d2955646cd76b016611252a45d1 100644 (file)
--- a/twiddle.c
+++ b/twiddle.c
@@ -452,6 +452,11 @@ static void free_game(game_state *state)
     sfree(state);
 }
 
+static char *game_text_format(game_state *state)
+{
+    return NULL;
+}
+
 static game_ui *new_ui(game_state *state)
 {
     return NULL;
@@ -942,6 +947,7 @@ const struct game thegame = {
     new_game,
     dup_game,
     free_game,
+    FALSE, game_text_format,
     new_ui,
     free_ui,
     make_move,