ref: 791940b043d500c096a56d57467b4b68ea31ce1f
parent: e72931bfe27e7d7303a22717cce05721551f3e4a
author: Simon Tatham <anakin@pobox.com>
date: Sun May 1 08:53:41 EDT 2005
Introduced a new function in every game which formats a game_state 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]
--- a/cube.c
+++ b/cube.c
@@ -979,6 +979,11 @@
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 @@
new_game,
dup_game,
free_game,
+ NULL, game_text_format,
new_ui,
free_ui,
make_move,
--- a/fifteen.c
+++ b/fifteen.c
@@ -370,6 +370,11 @@
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 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,
--- a/gtk.c
+++ b/gtk.c
@@ -14,10 +14,10 @@
#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 @@
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 @@
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 @@
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 @@
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 @@
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",
--- a/midend.c
+++ b/midend.c
@@ -573,3 +573,11 @@
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;
+}
--- a/net.c
+++ b/net.c
@@ -699,6 +699,11 @@
sfree(state);
}
+static char *game_text_format(game_state *state)
+{
+ return NULL;
+}
+
/* ----------------------------------------------------------------------
* Utility routine.
*/
@@ -1508,6 +1513,7 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,
--- a/netslide.c
+++ b/netslide.c
@@ -740,6 +740,11 @@
sfree(state);
}
+static char *game_text_format(game_state *state)
+{
+ return NULL;
+}
+
/* ----------------------------------------------------------------------
* Utility routine.
*/
@@ -1532,6 +1537,7 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,
--- a/nullgame.c
+++ b/nullgame.c
@@ -121,6 +121,11 @@
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 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,
--- a/osx.m
+++ b/osx.m
@@ -571,6 +571,28 @@
[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)
@@ -1214,6 +1236,8 @@
[menu addItem:[NSMenuItem separatorItem]];
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:));
--- a/pattern.c
+++ b/pattern.c
@@ -648,6 +648,11 @@
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 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,
--- a/puzzles.h
+++ b/puzzles.h
@@ -140,6 +140,7 @@
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 @@
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,
--- a/rect.c
+++ b/rect.c
@@ -997,6 +997,11 @@
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 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,
--- a/sixteen.c
+++ b/sixteen.c
@@ -379,6 +379,11 @@
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 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,
--- a/solo.c
+++ b/solo.c
@@ -1607,6 +1607,68 @@
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 @@
new_game,
dup_game,
free_game,
+ TRUE, game_text_format,
new_ui,
free_ui,
make_move,
@@ -2034,38 +2097,7 @@
}
}
- 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;
}
--- a/twiddle.c
+++ b/twiddle.c
@@ -452,6 +452,11 @@
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 @@
new_game,
dup_game,
free_game,
+ FALSE, game_text_format,
new_ui,
free_ui,
make_move,