From 9b4b03d31e30089e9f45f6ea166561c4c0c25a9c Mon Sep 17 00:00:00 2001 From: simon Date: Sun, 1 May 2005 12:53:41 +0000 Subject: [PATCH] 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. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@5724 cda61777-01e9-0310-a592-d414129be87e --- cube.c | 6 +++ fifteen.c | 6 +++ gtk.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- midend.c | 8 ++++ net.c | 6 +++ netslide.c | 6 +++ nullgame.c | 6 +++ osx.m | 24 ++++++++++++ pattern.c | 6 +++ puzzles.h | 3 ++ rect.c | 6 +++ sixteen.c | 6 +++ solo.c | 96 ++++++++++++++++++++++++++++++---------------- twiddle.c | 6 +++ 14 files changed, 279 insertions(+), 34 deletions(-) diff --git a/cube.c b/cube.c index 33144b6..d2ef882 100644 --- 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, diff --git a/fifteen.c b/fifteen.c index dc265be..3fad78a 100644 --- 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 b85cfa9..8c6a634 100644 --- a/gtk.c +++ b/gtk.c @@ -14,10 +14,10 @@ #include #include -#if GTK_CHECK_VERSION(2,0,0) && !defined HAVE_SENSIBLE_ABSOLUTE_SIZE_FUNCTION #include #include -#endif +#include +#include #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", diff --git a/midend.c b/midend.c index 30ad32b..efe8111 100644 --- 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 d41cebe..78df30d 100644 --- 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, diff --git a/netslide.c b/netslide.c index 9f334f6..11ca2ef 100644 --- a/netslide.c +++ b/netslide.c @@ -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, diff --git a/nullgame.c b/nullgame.c index a071971..9e2ea32 100644 --- a/nullgame.c +++ b/nullgame.c @@ -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 1fd689f..28af613 100644 --- 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"); diff --git a/pattern.c b/pattern.c index 61f15ff..6dda1f4 100644 --- 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, diff --git a/puzzles.h b/puzzles.h index e62a27c..d5c8924 100644 --- 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 5ab296a..052b284 100644 --- 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, diff --git a/sixteen.c b/sixteen.c index c90062b..3a5d722 100644 --- 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 c60cd66..c453b26 100644 --- 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; } diff --git a/twiddle.c b/twiddle.c index 0bfdabe..4bfd30c 100644 --- 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, -- 2.11.0