From: mogami Date: Tue, 17 Jun 2003 12:56:51 +0000 (+0000) Subject: X11ポートにマウスによるcopy&paste機能をscthangbandより移植。 X-Git-Tag: v2.1.2~1359 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=5efa576ad901c2870d891d2ab7b90a951b4672f4;p=hengband%2Fhengband.git X11ポートにマウスによるcopy&paste機能をscthangbandより移植。 --- diff --git a/src/externs.h b/src/externs.h index a62b2ab5f..7c8b1f7b0 100644 --- a/src/externs.h +++ b/src/externs.h @@ -1217,6 +1217,7 @@ extern void pause_line(int row); extern void request_command(int shopping); extern bool is_a_vowel(int ch); extern int get_keymap_dir(char ch); +extern errr type_string(cptr str, uint len); extern void roff_to_buf(cptr str, int wlen, char *tbuf, size_t bufsize); #ifdef SORT_R_INFO diff --git a/src/main-x11.c b/src/main-x11.c index 733946f2c..a5ec992df 100644 --- a/src/main-x11.c +++ b/src/main-x11.c @@ -123,6 +123,7 @@ #if 0 char *XSetIMValues(XIM, ...); /* Hack for XFree86 4.0 */ #endif +#include #endif /* __MAKEDEPEND__ */ @@ -1790,6 +1791,38 @@ struct term_data static term_data data[MAX_TERM_DATA]; +/* Use short names for the most commonly used elements of various structures. */ +#define DPY (Metadpy->dpy) +#define WIN (Infowin->win) + + +/* Describe a set of co-ordinates. */ +typedef struct co_ord co_ord; +struct co_ord +{ + int x; + int y; +}; + + +/* + * A special structure to store information about the text currently + * selected. + */ +typedef struct x11_selection_type x11_selection_type; +struct x11_selection_type +{ + bool select; /* The selection is currently in use. */ + bool drawn; /* The selection is currently displayed. */ + term *t; /* The window where the selection is found. */ + co_ord init; /* The starting co-ordinates. */ + co_ord cur; /* The end co-ordinates (the current ones if still copying). */ + co_ord old; /* The previous end co-ordinates. */ + Time time; /* The time at which the selection was finalised. */ +}; + +static x11_selection_type s_ptr[1]; + /* * Process a keypress event @@ -1935,6 +1968,405 @@ static void react_keypress(XKeyEvent *xev) } +/* + * Find the square a particular pixel is part of. + */ +static void pixel_to_square(int * const x, int * const y, + const int ox, const int oy) +{ + (*x) = (ox - Infowin->ox) / Infofnt->wid; + (*y) = (oy - Infowin->oy) / Infofnt->hgt; +} + +/* + * Find the pixel at the top-left corner of a square. + */ +static void square_to_pixel(int * const x, int * const y, + const int ox, const int oy) +{ + (*x) = ox * Infofnt->wid + Infowin->ox; + (*y) = oy * Infofnt->hgt + Infowin->oy; +} + +/* + * Convert co-ordinates from starting corner/opposite corner to minimum/maximum. + */ +static void sort_co_ord(co_ord *min, co_ord *max, + const co_ord *b, const co_ord *a) +{ + min->x = MIN(a->x, b->x); + min->y = MIN(a->y, b->y); + max->x = MAX(a->x, b->x); + max->y = MAX(a->y, b->y); +} + +/* + * Remove the selection by redrawing it. + */ +static void mark_selection_clear(int x1, int y1, int x2, int y2) +{ + Term_redraw_section(x1,y1,x2,y2); +} + +/* + * Select an area by drawing a grey box around it. + * NB. These two functions can cause flicker as the selection is modified, + * as the game redraws the entire marked section. + */ +static void mark_selection_mark(int x1, int y1, int x2, int y2) +{ + square_to_pixel(&x1, &y1, x1, y1); + square_to_pixel(&x2, &y2, x2, y2); + XDrawRectangle(Metadpy->dpy, Infowin->win, clr[2]->gc, x1, y1, + x2-x1+Infofnt->wid - 1, y2-y1+Infofnt->hgt - 1); +} + +/* + * Mark a selection by drawing boxes around it (for now). + */ +static void mark_selection(void) +{ + co_ord min, max; + term *old = Term; + bool draw = s_ptr->select; + bool clear = s_ptr->drawn; + + /* Open the correct term if necessary. */ + if (s_ptr->t != old) Term_activate(s_ptr->t); + + if (clear) + { + sort_co_ord(&min, &max, &s_ptr->init, &s_ptr->old); + mark_selection_clear(min.x, min.y, max.x, max.y); + } + if (draw) + { + sort_co_ord(&min, &max, &s_ptr->init, &s_ptr->cur); + mark_selection_mark(min.x, min.y, max.x, max.y); + } + + /* Finish on the current term. */ + if (s_ptr->t != old) Term_activate(old); + + s_ptr->old.x = s_ptr->cur.x; + s_ptr->old.y = s_ptr->cur.y; + s_ptr->drawn = s_ptr->select; +} + +/* + * Forget a selection for one reason or another. + */ +static void copy_x11_release(void) +{ + /* Deselect the current selection. */ + s_ptr->select = FALSE; + + /* Remove its graphical represesntation. */ + mark_selection(); +} + +/* + * Start to select some text on the screen. + */ +static void copy_x11_start(int x, int y) +{ + if (s_ptr->select) copy_x11_release(); + + /* Remember where the selection started. */ + s_ptr->t = Term; + s_ptr->init.x = s_ptr->cur.x = s_ptr->old.x = x; + s_ptr->init.y = s_ptr->cur.y = s_ptr->old.y = y; +} + +/* + * Respond to movement of the mouse when selecting text. + */ +static void copy_x11_cont(int x, int y, unsigned int buttons) +{ + /* Use the nearest square within bounds if the mouse is outside. */ + x = MIN(MAX(x, 0), Term->wid-1); + y = MIN(MAX(y, 0), Term->hgt-1); + + /* The left mouse button isn't pressed. */ + if (~buttons & Button1Mask) return; + + /* Not a selection in this window. */ + if (s_ptr->t != Term) return; + + /* Not enough movement. */ + if (x == s_ptr->old.x && y == s_ptr->old.y && s_ptr->select) return; + + /* Something is being selected. */ + s_ptr->select = TRUE; + + /* Track the selection. */ + s_ptr->cur.x = x; + s_ptr->cur.y = y; + + /* Hack - display it inefficiently. */ + mark_selection(); +} + +/* + * Respond to release of the left mouse button by putting the selected text in + * the primary buffer. + */ +static void copy_x11_end(const Time time) +{ + /* No selection. */ + if (!s_ptr->select) return; + + /* Not a selection in this window. */ + if (s_ptr->t != Term) return; + + /* Remember when the selection was finalised. */ + s_ptr->time = time; + + /* Acquire the primary selection. */ + XSetSelectionOwner(Metadpy->dpy, XA_PRIMARY, Infowin->win, time); + if (XGetSelectionOwner(Metadpy->dpy, XA_PRIMARY) != Infowin->win) + { + /* Failed to acquire the selection, so forget it. */ + /* bell("Failed to acquire primary buffer."); */ + s_ptr->select = FALSE; + mark_selection(); + } +} + +/* + * Send a message to request that the PRIMARY buffer be sent here. + */ +static void paste_x11_request(const Time time) +{ + /* Check the owner. */ + if (XGetSelectionOwner(Metadpy->dpy, XA_PRIMARY) == None) + { + /* No selection. */ + /* bell("No selection found."); */ + return; + } + + /* Request the event as a STRING. */ + XConvertSelection(DPY, XA_PRIMARY, XA_STRING, XA_STRING, WIN, time); +} + +/* + * Add a character to a string in preparation for sending it to another + * client as a STRING. + * This doesn't change anything, as clients tend not to have difficulty in + * receiving this format (although the standard specifies a restricted set). + * Strings do not have a colour. + */ +static int add_char_string(char *buf, byte a, char c) +{ + (void)a; + + *buf = c; + return 1; +} + +static bool paste_x11_send_text(XSelectionRequestEvent *rq, int (*add)(char *, byte, char)) +{ + char buf[1024]; + co_ord max, min; + int x,y,l; + byte a; + char c; + + /* Too old, or incorrect call. */ + if (rq->time < s_ptr->time || !add) return FALSE; + + /* Work out which way around to paste. */ + sort_co_ord(&min, &max, &s_ptr->init, &s_ptr->cur); + + /* Paranoia. */ + if (XGetSelectionOwner(DPY, XA_PRIMARY) != WIN) + { + /* bell("Someone stole my selection!"); */ + return FALSE; + } + + /* Delete the old value of the property. */ + XDeleteProperty(DPY, rq->requestor, rq->property); + + for (y = 0; y < Term->hgt; y++) + { + if (y < min.y) continue; + if (y > max.y) break; + + for (x = l = 0; x < Term->wid; x++) + { + if (x < min.x) continue; + if (x > max.x) break; + + /* Find the character. */ + Term_what(x, y, &a, &c); + + /* Add it. */ + l += (*add)(buf+l, a, c); + } + +#if 0 + /* Terminate all but the last line in an appropriate way. */ + if (y != max.y) l += (*add)(buf+l, TERM_WHITE, '\n'); +#else + /* Terminate all line unless single line message. */ + if (min.y != max.y) l += (*add)(buf+l, TERM_WHITE, '\n'); +#endif + + /* Send the (non-empty) string. */ + XChangeProperty(DPY, rq->requestor, rq->property, rq->target, 8, + PropModeAppend, (byte*)buf, l); + } + return TRUE; +} + +static Atom xa_targets, xa_timestamp; + +/* + * Set the required variable atoms at start-up to avoid errors later. + */ +static void set_atoms(void) +{ + xa_targets = XInternAtom(DPY, "TARGETS", False); + xa_timestamp = XInternAtom(DPY, "TIMESTAMP", False); +} + +/* + * Send some text requested by another X client. + */ +static void paste_x11_send(XSelectionRequestEvent *rq) +{ + XEvent event; + XSelectionEvent *ptr = &(event.xselection); + + /* Set the event parameters. */ + ptr->type = SelectionNotify; + ptr->property = rq->property; + ptr->display = rq->display; + ptr->requestor = rq->requestor; + ptr->selection = rq->selection; + ptr->target = rq->target; + ptr->time = rq->time; + + /* Paste the appropriate information for each target type. + * Note that this currently rejects MULTIPLE targets. + */ + + if (rq->target == XA_STRING) + { + if (!paste_x11_send_text(rq, add_char_string)) + ptr->property = None; + } + else if (rq->target == xa_targets) + { + Atom target_list[2]; + target_list[0] = XA_STRING; + target_list[1] = xa_targets; + XChangeProperty(DPY, rq->requestor, rq->property, rq->target, + (8 * sizeof(target_list[0])), PropModeReplace, + (unsigned char *)target_list, + (sizeof(target_list) / sizeof(target_list[0]))); + } + else if (rq->target == xa_timestamp) + { + XChangeProperty(DPY, rq->requestor, rq->property, rq->target, + (8 * sizeof(Time)), PropModeReplace, + (unsigned char *)s_ptr->time, 1); + } + else + { + ptr->property = None; + } + + /* Send whatever event we're left with. */ + XSendEvent(DPY, rq->requestor, FALSE, NoEventMask, &event); +} + +/* + * Add the contents of the PRIMARY buffer to the input queue. + * + * Hack - This doesn't use the "time" of the event, and so accepts anything a + * client tries to send it. + */ +static void paste_x11_accept(const XSelectionEvent *ptr) +{ + unsigned long offset, left; + + /* Failure. */ + if (ptr->property == None) + { + /* bell("Paste failure (remote client could not send)."); */ + return; + } + + if (ptr->selection != XA_PRIMARY) + { + /* bell("Paste failure (remote client did not send primary selection)."); */ + return; + } + + if (ptr->target != XA_STRING) + { + /* bell("Paste failure (selection in unknown format)."); */ + return; + } + + for (offset = 0; ; offset += left) + { + errr err; + + /* A pointer for the pasted information. */ + unsigned char *data; + + Atom type; + int fmt; + unsigned long nitems; + + /* Set data to the string, and catch errors. */ + if (XGetWindowProperty(Metadpy->dpy, Infowin->win, XA_STRING, offset, + 32767, TRUE, XA_STRING, &type, &fmt, &nitems, &left, &data) + != Success) break; + + /* Paste the text. */ + err = type_string((char*)data, nitems); + + /* Free the data pasted. */ + XFree(data); + + /* No room. */ + if (err == PARSE_ERROR_OUT_OF_MEMORY) + { + /* bell("Paste failure (too much text selected)."); */ + break; + } + /* Paranoia? - strange errors. */ + else if (err) + { + break; + } + + /* Pasted everything. */ + if (!left) return; + } + + /* An error has occurred, so free the last bit of data before returning. */ + XFree(data); +} + +/* + * Handle various events conditional on presses of a mouse button. + */ +static void handle_button(Time time, int x, int y, int button, + bool press) +{ + /* The co-ordinates are only used in Angband format. */ + pixel_to_square(&x, &y, x, y); + + if (press && button == 1) copy_x11_start(x, y); + if (!press && button == 1) copy_x11_end(time); + if (!press && button == 2) paste_x11_request(time); +} /* @@ -1949,7 +2381,7 @@ static errr CheckEvent(bool wait) term_data *td = NULL; infowin *iwin = NULL; - int i, x, y; + int i; #ifdef USE_XIM redo_checkevent: @@ -1958,6 +2390,13 @@ static errr CheckEvent(bool wait) /* Do not wait unless requested */ if (!wait && !XPending(Metadpy->dpy)) return (1); + /* + * Hack - redraw the selection, if needed. + * This doesn't actually check that one of its squares was drawn to, + * only that this may have happened. + */ + if (s_ptr->select && !s_ptr->drawn) mark_selection(); + /* Load the Event */ XNextEvent(Metadpy->dpy, xev); @@ -2041,13 +2480,16 @@ static errr CheckEvent(bool wait) /* Switch on the Type */ switch (xev->type) { - -#if 0 - case ButtonPress: case ButtonRelease: { - int z = 0; + bool press = (xev->type == ButtonPress); + + /* Where is the mouse */ + int x = xev->xbutton.x; + int y = xev->xbutton.y; + + int z; /* Which button is involved */ if (xev->xbutton.button == Button1) z = 1; @@ -2055,12 +2497,10 @@ static errr CheckEvent(bool wait) else if (xev->xbutton.button == Button3) z = 3; else if (xev->xbutton.button == Button4) z = 4; else if (xev->xbutton.button == Button5) z = 5; - - /* Where is the mouse */ - x = xev->xbutton.x; - y = xev->xbutton.y; + else z = 0; /* XXX Handle */ + handle_button(xev->xbutton.time, x, y, z, press); break; } @@ -2068,10 +2508,6 @@ static errr CheckEvent(bool wait) case EnterNotify: case LeaveNotify: { - /* Where is the mouse */ - x = xev->xcrossing.x; - y = xev->xcrossing.y; - /* XXX Handle */ break; @@ -2080,28 +2516,51 @@ static errr CheckEvent(bool wait) case MotionNotify: { /* Where is the mouse */ - x = xev->xmotion.x; - y = xev->xmotion.y; + int x = xev->xmotion.x; + int y = xev->xmotion.y; + unsigned int z = xev->xmotion.state; + + /* Convert to co-ordinates Angband understands. */ + pixel_to_square(&x, &y, x, y); + + /* Highlight the current square, if appropriate. */ + /* highlight_square(window, y, x); */ + + /* Alter the selection if appropriate. */ + copy_x11_cont(x, y, z); /* XXX Handle */ break; } + case SelectionNotify: + { + paste_x11_accept(&(xev->xselection)); + break; + } + + case SelectionRequest: + { + paste_x11_send(&(xev->xselectionrequest)); + break; + } + + case SelectionClear: + { + s_ptr->select = FALSE; + mark_selection(); + break; + } + case KeyRelease: { /* Nothing */ break; } -#endif - case KeyPress: { - /* Save the mouse location */ - x = xev->xkey.x; - y = xev->xkey.y; - /* Hack -- use "old" term */ Term_activate(&old_td->t); @@ -2405,7 +2864,7 @@ static errr Term_xtra_x11(int n, int v) case TERM_XTRA_LEVEL: return (Term_xtra_x11_level(v)); /* Clear the screen */ - case TERM_XTRA_CLEAR: Infowin_wipe(); return (0); + case TERM_XTRA_CLEAR: Infowin_wipe(); s_ptr->drawn = FALSE; return (0); /* Delay for some milliseconds */ case TERM_XTRA_DELAY: usleep(1000 * v); return (0); @@ -2491,6 +2950,9 @@ static errr Term_wipe_x11(int x, int y, int n) /* Mega-Hack -- Erase some space */ Infofnt_text_non(x, y, "", n); + /* Redraw the selection if any, as it may have been obscured. (later) */ + s_ptr->drawn = FALSE; + /* Success */ return (0); } @@ -2507,6 +2969,9 @@ static errr Term_text_x11(int x, int y, int n, byte a, cptr s) /* Draw the text */ Infofnt_text_std(x, y, s, n); + /* Redraw the selection if any, as it may have been obscured. (later) */ + s_ptr->drawn = FALSE; + /* Success */ return (0); } @@ -2637,6 +3102,9 @@ static errr Term_pict_x11(int x, int y, int n, const byte *ap, const char *cp) #endif /* USE_TRANSPARENCY */ } + /* Redraw the selection if any, as it may have been obscured. (later) */ + s_ptr->drawn = FALSE; + /* Success */ return (0); } @@ -2966,9 +3434,9 @@ static errr term_data_init(term_data *td, int i) /* Ask for certain events */ #if defined(USE_XIM) - Infowin_set_mask(ExposureMask | StructureNotifyMask | KeyPressMask | FocusChangeMask); + Infowin_set_mask(ExposureMask | StructureNotifyMask | KeyPressMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask | FocusChangeMask); #else - Infowin_set_mask(ExposureMask | StructureNotifyMask | KeyPressMask); + Infowin_set_mask(ExposureMask | StructureNotifyMask | KeyPressMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask); #endif /* Set the window name */ @@ -3221,6 +3689,10 @@ errr init_x11(int argc, char *argv[]) } + /* Prepare required atoms. */ + set_atoms(); + + /* Initialize the windows */ for (i = 0; i < num_term; i++) { diff --git a/src/util.c b/src/util.c index 8bcfc699f..a0514082a 100644 --- a/src/util.c +++ b/src/util.c @@ -4848,6 +4848,52 @@ void build_gamma_table(int gamma) #endif /* SUPPORT_GAMMA */ + +/* + * Add a series of keypresses to the "queue". + * + * Return any errors generated by Term_keypress() in doing so, or SUCCESS + * if there are none. + * + * Catch the "out of space" error before anything is printed. + * + * NB: The keys added here will be interpreted by any macros or keymaps. + */ +errr type_string(cptr str, uint len) +{ + errr err = 0; + cptr s; + + term *old = Term; + + /* Paranoia - no string. */ + if (!str) return -1; + + /* Hack - calculate the string length here if none given. */ + if (!len) len = strlen(str); + + /* Activate the main window, as all pastes go there. */ + Term_activate(term_screen); + + for (s = str; s < str+len; s++) + { + /* Catch end of string */ + if (*s == '\0') break; + + err = Term_keypress(*s); + + /* Catch errors */ + if (err) break; + } + + /* Activate the original window. */ + Term_activate(old); + + return err; +} + + + void roff_to_buf(cptr str, int maxlen, char *tbuf, size_t bufsize) { int read_pt = 0;