UNIXworkcode

1 /******************************************************************************* 2 * * 3 * textSel.c - Selection and clipboard routines for NEdit text widget * 4 * * 5 * Copyright (C) 1999 Mark Edel * 6 * * 7 * This is free software; you can redistribute it and/or modify it under the * 8 * terms of the GNU General Public License as published by the Free Software * 9 * Foundation; either version 2 of the License, or (at your option) any later * 10 * version. In addition, you may distribute version of this program linked to * 11 * Motif or Open Motif. See README for details. * 12 * * 13 * This software is distributed in the hope that it will be useful, but WITHOUT * 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * 16 * for more details. * 17 * * 18 * You should have received a copy of the GNU General Public License along with * 19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple * 20 * Place, Suite 330, Boston, MA 02111-1307 USA * 21 * * 22 * Nirvana Text Editor * 23 * Dec. 15, 1995 * 24 * * 25 * Written by Mark Edel * 26 * * 27 *******************************************************************************/ 28 29 #ifdef HAVE_CONFIG_H 30 #include "../config.h" 31 #endif 32 33 #include "textSel.h" 34 #include "textP.h" 35 #include "text.h" 36 #include "textDisp.h" 37 #include "textBuf.h" 38 #include "../util/misc.h" 39 #include "../util/nedit_malloc.h" 40 41 #include <stdio.h> 42 #include <string.h> 43 #include <limits.h> 44 45 #include <Xm/Xm.h> 46 #include <Xm/CutPaste.h> 47 #include <Xm/Text.h> 48 #include <X11/Xatom.h> 49 #if XmVersion >= 1002 50 #include <Xm/PrimitiveP.h> 51 #endif 52 53 #ifdef HAVE_DEBUG_H 54 #include "../debug.h" 55 #endif 56 57 58 static Time selectionTime = 0; 59 60 #define N_SELECT_TARGETS 8 61 #define N_ATOMS 12 62 enum atomIndex {A_TEXT, A_TARGETS, A_MULTIPLE, A_TIMESTAMP, 63 A_INSERT_SELECTION, A_DELETE, A_CLIPBOARD, A_INSERT_INFO, 64 A_ATOM_PAIR, A_MOTIF_DESTINATION, A_COMPOUND_TEXT, A_UTF8_STRING}; 65 66 /* Results passed back to the convert proc processing an INSERT_SELECTION 67 request, by getInsertSelection when the selection to insert has been 68 received and processed */ 69 enum insertResultFlags {INSERT_WAITING, UNSUCCESSFUL_INSERT, SUCCESSFUL_INSERT}; 70 71 /* Actions for selection notify event handler upon receiving confermation 72 of a successful convert selection request */ 73 enum selectNotifyActions {UNSELECT_SECONDARY, REMOVE_SECONDARY, 74 EXCHANGE_SECONDARY}; 75 76 /* temporary structure for passing data to the event handler for completing 77 selection requests (the hard way, via xlib calls) */ 78 typedef struct { 79 int action; 80 XtIntervalId timeoutProcID; 81 Time timeStamp; 82 Widget widget; 83 char *actionText; 84 int length; 85 } selectNotifyInfo; 86 87 typedef struct { 88 char *utf8String; 89 char *string; 90 size_t utf8slen; 91 size_t slen; 92 int isColFlag; 93 int cbCount; 94 } stringSelection; 95 96 static void modifiedCB(int pos, int nInserted, int nDeleted, 97 int nRestyled, const char *deletedText, void *cbArg); 98 static void sendSecondary(Widget w, Time time, Atom sel, int action, 99 char *actionText, int actionTextLen); 100 static void getSelectionCB(Widget w, XtPointer clientData, Atom *selType, 101 Atom *type, XtPointer value, unsigned long *length, int *format); 102 static void getInsertSelectionCB(Widget w, XtPointer clientData,Atom *selType, 103 Atom *type, XtPointer value, unsigned long *length, int *format); 104 static void getExchSelCB(Widget w, XtPointer clientData, Atom *selType, 105 Atom *type, XtPointer value, unsigned long *length, int *format); 106 static Boolean convertSelectionCB(Widget w, Atom *selType, Atom *target, 107 Atom *type, XtPointer *value, unsigned long *length, int *format); 108 static void loseSelectionCB(Widget w, Atom *selType); 109 static Boolean convertSecondaryCB(Widget w, Atom *selType, Atom *target, 110 Atom *type, XtPointer *value, unsigned long *length, int *format); 111 static void loseSecondaryCB(Widget w, Atom *selType); 112 static Boolean convertMotifDestCB(Widget w, Atom *selType, Atom *target, 113 Atom *type, XtPointer *value, unsigned long *length, int *format); 114 static void loseMotifDestCB(Widget w, Atom *selType); 115 static void selectNotifyEH(Widget w, XtPointer data, XEvent *event, 116 Boolean *continueDispatch); 117 static void selectNotifyTimerProc(XtPointer clientData, XtIntervalId *id); 118 static Atom getAtom(Display *display, int atomNum); 119 120 /* 121 ** Designate text widget "w" to be the selection owner for primary selections 122 ** in its attached buffer (a buffer can be attached to multiple text widgets). 123 */ 124 void HandleXSelections(Widget w) 125 { 126 int i; 127 textBuffer *buf = ((TextWidget)w)->text.textD->buffer; 128 129 /* Remove any existing selection handlers for other widgets */ 130 for (i=0; i<buf->nModifyProcs; i++) { 131 if (buf->modifyProcs[i] == modifiedCB) { 132 BufRemoveModifyCB(buf, modifiedCB, buf->cbArgs[i]); 133 break; 134 } 135 } 136 137 /* Add a handler with this widget as the CB arg (and thus the sel. owner) */ 138 BufAddModifyCB(((TextWidget)w)->text.textD->buffer, modifiedCB, w); 139 } 140 141 /* 142 ** Discontinue ownership of selections for widget "w"'s attached buffer 143 ** (if "w" was the designated selection owner) 144 */ 145 void StopHandlingXSelections(Widget w) 146 { 147 int i; 148 textBuffer *buf = ((TextWidget)w)->text.textD->buffer; 149 150 for (i=0; i<buf->nModifyProcs; i++) { 151 if (buf->modifyProcs[i] == modifiedCB && buf->cbArgs[i] == w) { 152 BufRemoveModifyCB(buf, modifiedCB, buf->cbArgs[i]); 153 return; 154 } 155 } 156 } 157 158 159 160 void CopyStringToClipboard(Widget w, Time time, const char *text, size_t length) { 161 long itemID = 0; 162 XmString s; 163 int stat; 164 int res; 165 166 if(!text || text[0] == '\0') 167 { 168 return; 169 } 170 171 char *locale_text = NULL; 172 if(!IsUtf8Locale()) { 173 locale_text = ConvertEncodingLen(text, length, GetLocaleEncoding(), "UTF-8"); 174 } 175 176 /* Shut up LessTif */ 177 if (SpinClipboardLock(XtDisplay(w), XtWindow(w)) != ClipboardSuccess) { 178 if(locale_text) NEditFree(locale_text); 179 return; 180 } 181 182 /* Use the XmClipboard routines to copy the text to the clipboard. 183 If errors occur, just give up. */ 184 s = XmStringCreateSimple("NEdit"); 185 stat = SpinClipboardStartCopy(XtDisplay(w), XtWindow(w), s, 186 time, w, NULL, &itemID); 187 XmStringFree(s); 188 if (stat != ClipboardSuccess) { 189 SpinClipboardUnlock(XtDisplay(w), XtWindow(w)); 190 return; 191 } 192 193 /* Note that we were previously passing length + 1 here, but I suspect 194 that this was inconsistent with the somewhat ambiguous policy of 195 including a terminating null but not mentioning it in the length */ 196 197 res = SpinClipboardCopy(XtDisplay(w), XtWindow(w), itemID, "UTF8_STRING", 198 (char*)text, length, 0, NULL); 199 if(res == ClipboardSuccess) { 200 int l_length = length; 201 const char *l_text = text; 202 if(locale_text) { 203 l_text = locale_text; 204 l_length = strlen(locale_text); 205 } 206 res = SpinClipboardCopy(XtDisplay(w), XtWindow(w), itemID, "STRING", 207 (char*)l_text, l_length, 0, NULL); 208 } 209 210 if(locale_text) { 211 NEditFree(locale_text); 212 } 213 214 SpinClipboardEndCopy(XtDisplay(w), XtWindow(w), itemID); 215 SpinClipboardUnlock(XtDisplay(w), XtWindow(w)); 216 } 217 218 /* 219 ** Copy the primary selection to the clipboard 220 */ 221 void CopyToClipboard(Widget w, Time time) 222 { 223 char *text = BufGetSelectionText(((TextWidget)w)->text.textD->buffer); 224 size_t length = strlen(text); 225 BufUnsubstituteNullChars(text, ((TextWidget)w)->text.textD->buffer); 226 227 CopyStringToClipboard(w, time, text, length); 228 NEditFree(text); 229 } 230 231 /* 232 ** Insert the X PRIMARY selection (from whatever window currently owns it) 233 ** at the cursor position. 234 */ 235 void InsertPrimarySelection(Widget w, Time time, int isColumnar) 236 { 237 /* Theoretically, strange things could happen if the user managed to get 238 in any events between requesting receiving the selection data, however, 239 getSelectionCB simply inserts the selection at the cursor. Don't 240 bother with further measures until real problems are observed. */ 241 242 stringSelection *sel = NEditMalloc(sizeof(stringSelection)); 243 sel->utf8String = NULL; 244 sel->string = NULL; 245 sel->utf8slen = 0; 246 sel->slen = 0; 247 sel->cbCount = 0; 248 sel->isColFlag = isColumnar; 249 250 Atom targets[2] = {XA_STRING, getAtom(XtDisplay(w), A_UTF8_STRING)}; 251 252 selectionTime = time; 253 254 XtGetSelectionValue(w, XA_PRIMARY, targets[1], getSelectionCB, sel, time); 255 XtGetSelectionValue(w, XA_PRIMARY, targets[0], getSelectionCB, sel, time); 256 } 257 258 /* 259 ** Insert the secondary selection at the motif destination by initiating 260 ** an INSERT_SELECTION request to the current owner of the MOTIF_DESTINATION 261 ** selection. Upon completion, unselect the secondary selection. If 262 ** "removeAfter" is true, also delete the secondary selection from the 263 ** widget's buffer upon completion. 264 */ 265 void SendSecondarySelection(Widget w, Time time, int removeAfter) 266 { 267 sendSecondary(w, time, getAtom(XtDisplay(w), A_MOTIF_DESTINATION), 268 removeAfter ? REMOVE_SECONDARY : UNSELECT_SECONDARY, NULL, 0); 269 } 270 271 /* 272 ** Exchange Primary and secondary selections (to be called by the widget 273 ** with the secondary selection) 274 */ 275 void ExchangeSelections(Widget w, Time time) 276 { 277 if (!((TextWidget)w)->text.textD->buffer->secondary.selected) 278 return; 279 280 /* Initiate an long series of events: 1) get the primary selection, 281 2) replace the primary selection with this widget's secondary, 3) replace 282 this widget's secondary with the text returned from getting the primary 283 selection. This could be done with a much more efficient MULTIPLE 284 request following ICCCM conventions, but the X toolkit MULTIPLE handling 285 routines can't handle INSERT_SELECTION requests inside of MULTIPLE 286 requests, because they don't allow access to the requested property atom 287 in inside of an XtConvertSelectionProc. It's simply not worth 288 duplicating all of Xt's selection handling routines for a little 289 performance, and this would make the code incompatible with Motif text 290 widgets */ 291 XtGetSelectionValue(w, XA_PRIMARY, XA_STRING, getExchSelCB, NULL, time); 292 } 293 294 /* 295 ** Insert the contents of the PRIMARY selection at the cursor position in 296 ** widget "w" and delete the contents of the selection in its current owner 297 ** (if the selection owner supports DELETE targets). 298 */ 299 void MovePrimarySelection(Widget w, Time time, int isColumnar) 300 { 301 stringSelection *sel = NEditMalloc(sizeof(stringSelection)); 302 sel->utf8String = NULL; 303 sel->string = NULL; 304 sel->cbCount = 0; 305 sel->isColFlag = isColumnar; 306 307 Atom targets[3] = { 308 XA_STRING, 309 getAtom(XtDisplay(w), A_UTF8_STRING), 310 getAtom(XtDisplay(w), A_DELETE)}; 311 void *data[3] = { sel, sel, sel}; 312 313 selectionTime = time; 314 315 XtGetSelectionValues(w, XA_PRIMARY, targets, 3, getSelectionCB, data, time); 316 } 317 318 319 char* GetClipboard(Widget w) { 320 unsigned long length, retLength; 321 int res; 322 char *string; 323 char *type = "UTF8_STRING"; 324 long id = 0; 325 int convert = FALSE; 326 327 /* Get the clipboard contents. Note: this code originally used the 328 CLIPBOARD selection, rather than the Motif clipboard interface. It 329 was changed because Motif widgets in the same application would hang 330 when users pasted data from nedit text widgets. This happened because 331 the XmClipboard routines used by the widgets do blocking event reads, 332 preventing a response by a selection owner in the same application. 333 While the Motif clipboard routines as they are used below, limit the 334 size of the data that be transferred via the clipboard, and are 335 generally slower and buggier, they do preserve the clipboard across 336 widget destruction and even program termination. */ 337 338 res = SpinClipboardInquireLength(XtDisplay(w), XtWindow(w), 339 type, &length); 340 if(res != ClipboardSuccess || length == 0) { 341 type = "STRING"; 342 res = SpinClipboardInquireLength(XtDisplay(w), XtWindow(w), 343 type, &length); 344 if(!IsUtf8Locale()) { 345 convert = TRUE; 346 } 347 } 348 349 if (res != ClipboardSuccess || length == 0) { 350 /* 351 * Possibly, the clipboard can remain in a locked state after 352 * a failure, so we try to remove the lock, just to be sure. 353 */ 354 SpinClipboardUnlock(XtDisplay(w), XtWindow(w)); 355 return NULL; 356 } 357 string = (char*)NEditMalloc(length+1); 358 if (SpinClipboardRetrieve(XtDisplay(w), XtWindow(w), type, string, 359 length, &retLength, &id) != ClipboardSuccess || retLength == 0) { 360 NEditFree(string); 361 /* 362 * Possibly, the clipboard can remain in a locked state after 363 * a failure, so we try to remove the lock, just to be sure. 364 */ 365 SpinClipboardUnlock(XtDisplay(w), XtWindow(w)); 366 return NULL; 367 } 368 string[retLength] = '\0'; 369 370 if(convert) { 371 char *c_string = ConvertEncoding(string, "UTF-8", GetLocaleEncoding()); 372 if(c_string) { 373 NEditFree(string); 374 string = c_string; 375 retLength = strlen(c_string); 376 } 377 } 378 379 return string; 380 } 381 382 /* 383 ** Insert the X CLIPBOARD selection at the cursor position. If isColumnar, 384 ** do an BufInsertCol for a columnar paste instead of BufInsert. 385 */ 386 void InsertClipboard(Widget w, int isColumnar) 387 { 388 unsigned long retLength; 389 textDisp *textD = ((TextWidget)w)->text.textD; 390 textBuffer *buf = ((TextWidget)w)->text.textD->buffer; 391 int cursorLineStart, column, cursorPos; 392 393 char *string = GetClipboard(w); 394 if(!string) { 395 return; 396 } 397 retLength = strlen(string); 398 399 /* If the string contains ascii-nul characters, substitute something 400 else, or give up, warn, and refuse */ 401 if (!BufSubstituteNullChars(string, retLength, buf)) { 402 fprintf(stderr, "Too much binary data, text not pasted\n"); 403 NEditFree(string); 404 return; 405 } 406 407 /* Insert it in the text widget */ 408 if (isColumnar && !buf->primary.selected) { 409 cursorPos = TextDGetInsertPosition(textD); 410 cursorLineStart = BufStartOfLine(buf, cursorPos); 411 column = BufCountDispChars(buf, cursorLineStart, cursorPos); 412 if (((TextWidget)w)->text.overstrike) { 413 BufOverlayRect(buf, cursorLineStart, column, -1, string, NULL, 414 NULL); 415 } else { 416 BufInsertCol(buf, column, cursorLineStart, string, NULL, NULL); 417 } 418 TextDSetInsertPosition(textD, 419 BufCountForwardDispChars(buf, cursorLineStart, column)); 420 if (((TextWidget)w)->text.autoShowInsertPos) 421 TextDMakeInsertPosVisible(textD); 422 } else 423 TextInsertAtCursor(w, string, NULL, True, 424 ((TextWidget)w)->text.autoWrapPastedText); 425 NEditFree(string); 426 } 427 428 /* 429 ** Take ownership of the MOTIF_DESTINATION selection. This is Motif's private 430 ** selection type for designating a widget to receive the result of 431 ** secondary quick action requests. The NEdit text widget uses this also 432 ** for compatibility with Motif text widgets. 433 */ 434 void TakeMotifDestination(Widget w, Time time) 435 { 436 if (((TextWidget)w)->text.motifDestOwner || ((TextWidget)w)->text.readOnly) 437 return; 438 439 /* Take ownership of the MOTIF_DESTINATION selection */ 440 if (!XtOwnSelection(w, getAtom(XtDisplay(w), A_MOTIF_DESTINATION), time, 441 convertMotifDestCB, loseMotifDestCB, NULL)) { 442 return; 443 } 444 ((TextWidget)w)->text.motifDestOwner = True; 445 } 446 447 /* 448 ** This routine is called every time there is a modification made to the 449 ** buffer to which this callback is attached, with an argument of the text 450 ** widget that has been designated (by HandleXSelections) to handle its 451 ** selections. It checks if the status of the selection in the buffer 452 ** has changed since last time, and owns or disowns the X selection depending 453 ** on the status of the primary selection in the buffer. If it is not allowed 454 ** to take ownership of the selection, it unhighlights the text in the buffer 455 ** (Being in the middle of a modify callback, this has a somewhat complicated 456 ** result, since later callbacks will see the second modifications first). 457 */ 458 static void modifiedCB(int pos, int nInserted, int nDeleted, 459 int nRestyled, const char *deletedText, void *cbArg) 460 { 461 TextWidget w = (TextWidget)cbArg; 462 Time time = XtLastTimestampProcessed(XtDisplay((Widget)w)); 463 int selected = w->text.textD->buffer->primary.selected; 464 int isOwner = w->text.selectionOwner; 465 466 /* If the widget owns the selection and the buffer text is still selected, 467 or if the widget doesn't own it and there's no selection, do nothing */ 468 if ((isOwner && selected) || (!isOwner && !selected)) 469 return; 470 471 /* Don't disown the selection here. Another application (namely: klipper) 472 may try to take it when it thinks nobody has the selection. We then 473 lose it, making selection-based macro operations fail. Disowning 474 is really only for when the widget is destroyed to avoid a convert 475 callback from firing at a bad time. */ 476 477 /* Take ownership of the selection */ 478 if (!XtOwnSelection((Widget)w, XA_PRIMARY, time, convertSelectionCB, 479 loseSelectionCB, NULL)) 480 BufUnselect(w->text.textD->buffer); 481 else 482 w->text.selectionOwner = True; 483 } 484 485 /* 486 ** Send an INSERT_SELECTION request to "sel". 487 ** Upon completion, do the action specified by "action" (one of enum 488 ** selectNotifyActions) using "actionText" and freeing actionText (if 489 ** not NULL) when done. 490 */ 491 static void sendSecondary(Widget w, Time time, Atom sel, int action, 492 char *actionText, int actionTextLen) 493 { 494 static Atom selInfoProp[2] = {XA_SECONDARY, XA_STRING}; 495 Display *disp = XtDisplay(w); 496 selectNotifyInfo *cbInfo; 497 XtAppContext context = XtWidgetToApplicationContext((Widget)w); 498 499 /* Take ownership of the secondary selection, give up if we can't */ 500 if (!XtOwnSelection(w, XA_SECONDARY, time, convertSecondaryCB, 501 loseSecondaryCB, NULL)) { 502 BufSecondaryUnselect(((TextWidget)w)->text.textD->buffer); 503 return; 504 } 505 506 /* Set up a property on this window to pass along with the 507 INSERT_SELECTION request to tell the MOTIF_DESTINATION owner what 508 selection and what target from that selection to insert */ 509 XChangeProperty(disp, XtWindow(w), getAtom(disp, A_INSERT_INFO), 510 getAtom(disp, A_ATOM_PAIR), 32, PropModeReplace, 511 (unsigned char *)selInfoProp, 2 /* 1? */); 512 513 /* Make INSERT_SELECTION request to the owner of selection "sel" 514 to do the insert. This must be done using XLib calls to specify 515 the property with the information about what to insert. This 516 means it also requires an event handler to see if the request 517 succeeded or not, and a backup timer to clean up if the select 518 notify event is never returned */ 519 XConvertSelection(XtDisplay(w), sel, getAtom(disp, A_INSERT_SELECTION), 520 getAtom(disp, A_INSERT_INFO), XtWindow(w), time); 521 cbInfo = (selectNotifyInfo *)NEditMalloc(sizeof(selectNotifyInfo)); 522 cbInfo->action = action; 523 cbInfo->timeStamp = time; 524 cbInfo->widget = (Widget)w; 525 cbInfo->actionText = actionText; 526 cbInfo->length = actionTextLen; 527 XtAddEventHandler(w, 0, True, selectNotifyEH, (XtPointer)cbInfo); 528 cbInfo->timeoutProcID = XtAppAddTimeOut(context, 529 XtAppGetSelectionTimeout(context), 530 selectNotifyTimerProc, (XtPointer)cbInfo); 531 } 532 533 534 535 static void selectionSetValue( 536 Widget w, 537 stringSelection *selection, 538 Atom selType, 539 Atom type, 540 XtPointer value, 541 unsigned long length, 542 int format) 543 { 544 selection->cbCount++; 545 546 Atom utf8 = getAtom(XtDisplay(w), A_UTF8_STRING); 547 548 if(value && (type == XA_STRING || type == utf8) && format == 8) { 549 char *string = NEditMalloc(length+1); 550 memcpy(string, value, length); 551 string[length] = '\0'; 552 553 if(type == XA_STRING) { 554 selection->string = string; 555 selection->slen = length; 556 } else { 557 selection->utf8String = string; 558 selection->utf8slen = length; 559 } 560 } 561 562 XtFree(value); 563 564 if(selection->cbCount == 2) { 565 char *insertStr; 566 size_t insertStrLen; 567 if(selection->utf8String) { 568 insertStr = selection->utf8String; 569 insertStrLen = selection->utf8slen; 570 } else { 571 insertStr = selection->string; 572 insertStrLen = selection->slen; 573 } 574 if(insertStr) { 575 textDisp *textD = ((TextWidget)w)->text.textD; 576 577 if (!BufSubstituteNullChars(insertStr, insertStrLen, textD->buffer)) { 578 fprintf(stderr, "Too much binary data, giving up\n"); 579 } if (selection->isColFlag) { 580 /* Insert it in the text widget */ 581 int cursorPos = TextDGetInsertPosition(textD); 582 int cursorLineStart = BufStartOfLine(textD->buffer, cursorPos); 583 int row, column; 584 TextDXYToUnconstrainedPosition(textD, ((TextWidget)w)->text.btnDownX, 585 ((TextWidget)w)->text.btnDownY, &row, &column); 586 BufInsertCol(textD->buffer, column, cursorLineStart, insertStr, NULL,NULL); 587 TextDSetInsertPosition(textD, textD->buffer->cursorPosHint); 588 } else { 589 TextInsertAtCursor(w, insertStr, NULL, False, 590 ((TextWidget)w)->text.autoWrapPastedText); 591 } 592 } 593 594 if(selection->utf8String) { 595 NEditFree(selection->utf8String); 596 } 597 if(selection->string) { 598 NEditFree(selection->string); 599 } 600 NEditFree(selection); 601 } 602 } 603 604 /* 605 ** Called when data arrives from a request for the PRIMARY selection. If 606 ** everything is in order, it inserts it at the cursor in the requesting 607 ** widget. 608 */ 609 static void getSelectionCB(Widget w, XtPointer clientData, Atom *selType, 610 Atom *type, XtPointer value, unsigned long *length, int *format) 611 { 612 selectionSetValue(w, clientData, *selType, *type, value, *length, *format); 613 } 614 615 /* 616 ** Called when data arrives from request resulting from processing an 617 ** INSERT_SELECTION request. If everything is in order, inserts it at 618 ** the cursor or replaces pending delete selection in widget "w", and sets 619 ** the flag passed in clientData to SUCCESSFUL_INSERT or UNSUCCESSFUL_INSERT 620 ** depending on the success of the operation. 621 */ 622 static void getInsertSelectionCB(Widget w, XtPointer clientData,Atom *selType, 623 Atom *type, XtPointer value, unsigned long *length, int *format) 624 { 625 textBuffer *buf = ((TextWidget)w)->text.textD->buffer; 626 char *string; 627 int *resultFlag = (int *)clientData; 628 629 /* Confirm that the returned value is of the correct type */ 630 if (*type != XA_STRING || *format != 8 || value == NULL) { 631 NEditFree(value); 632 *resultFlag = UNSUCCESSFUL_INSERT; 633 return; 634 } 635 636 /* Copy the string just to make space for the null character */ 637 string = (char*)NEditMalloc(*length + 1); 638 memcpy(string, (char *)value, *length); 639 string[*length] = '\0'; 640 641 /* If the string contains ascii-nul characters, substitute something 642 else, or give up, warn, and refuse */ 643 if (!BufSubstituteNullChars(string, *length, buf)) { 644 fprintf(stderr, "Too much binary data, giving up\n"); 645 NEditFree(string); 646 NEditFree(value); 647 return; 648 } 649 650 /* Insert it in the text widget */ 651 TextInsertAtCursor(w, string, NULL, True, 652 ((TextWidget)w)->text.autoWrapPastedText); 653 NEditFree(string); 654 *resultFlag = SUCCESSFUL_INSERT; 655 656 /* This callback is required to free the memory passed to it thru value */ 657 NEditFree(value); 658 } 659 660 /* 661 ** Called when data arrives from an X primary selection request for the 662 ** purpose of exchanging the primary and secondary selections. 663 ** If everything is in order, stores the retrieved text temporarily and 664 ** initiates a request to replace the primary selection with this widget's 665 ** secondary selection. 666 */ 667 static void getExchSelCB(Widget w, XtPointer clientData, Atom *selType, 668 Atom *type, XtPointer value, unsigned long *length, int *format) 669 { 670 /* Confirm that there is a value and it is of the correct type */ 671 if (*length == 0 || value == NULL || *type != XA_STRING || *format != 8) { 672 NEditFree(value); 673 XBell(XtDisplay(w), 0); 674 BufSecondaryUnselect(((TextWidget)w)->text.textD->buffer); 675 return; 676 } 677 678 /* Request the selection owner to replace the primary selection with 679 this widget's secondary selection. When complete, replace this 680 widget's secondary selection with text "value" and free it. */ 681 sendSecondary(w, XtLastTimestampProcessed(XtDisplay(w)), XA_PRIMARY, 682 EXCHANGE_SECONDARY, (char *)value, *length); 683 } 684 685 /* 686 ** Selection converter procedure used by the widget when it is the selection 687 ** owner to provide data in the format requested by the selection requestor. 688 ** 689 ** Note: Memory left in the *value field is freed by Xt as long as there is no 690 ** done_proc procedure registered in the XtOwnSelection call where this 691 ** procdeure is registered 692 */ 693 static Boolean convertSelectionCB(Widget w, Atom *selType, Atom *target, 694 Atom *type, XtPointer *value, unsigned long *length, int *format) 695 { 696 XSelectionRequestEvent *event = XtGetSelectionRequest(w, *selType, 0); 697 textBuffer *buf = ((TextWidget)w)->text.textD->buffer; 698 Display *display = XtDisplay(w); 699 Atom *targets, dummyAtom; 700 unsigned long nItems, dummyULong; 701 Atom *reqAtoms; 702 int getFmt, result = INSERT_WAITING; 703 XEvent nextEvent; 704 705 Atom utf8 = getAtom(display, A_UTF8_STRING); 706 707 /* target is text, string, or compound text */ 708 if (*target == XA_STRING || *target == getAtom(display, A_TEXT) || 709 *target == getAtom(display, A_COMPOUND_TEXT) || *target == utf8) { 710 /* We really don't directly support COMPOUND_TEXT, but recent 711 versions gnome-terminal incorrectly ask for it, even though 712 don't declare that we do. Just reply in string format. */ 713 *type = *target == utf8 ? utf8 : XA_STRING; 714 *value = (XtPointer)BufGetSelectionText(buf); 715 *length = strlen((char *)*value); 716 *format = 8; 717 BufUnsubstituteNullChars(*value, buf); 718 return True; 719 } 720 721 /* target is "TARGETS", return a list of targets we can handle */ 722 if (*target == getAtom(display, A_TARGETS)) { 723 targets = (Atom *)NEditMalloc(sizeof(Atom) * N_SELECT_TARGETS); 724 targets[0] = XA_STRING; 725 targets[1] = utf8; 726 targets[2] = getAtom(display, A_TEXT); 727 targets[3] = getAtom(display, A_TARGETS); 728 targets[4] = getAtom(display, A_MULTIPLE); 729 targets[5] = getAtom(display, A_TIMESTAMP); 730 targets[6] = getAtom(display, A_INSERT_SELECTION); 731 targets[7] = getAtom(display, A_DELETE); 732 *type = XA_ATOM; 733 *value = (XtPointer)targets; 734 *length = N_SELECT_TARGETS; 735 *format = 32; 736 return True; 737 } 738 739 /* target is "INSERT_SELECTION": 1) get the information about what 740 selection and target to use to get the text to insert, from the 741 property named in the property field of the selection request event. 742 2) initiate a get value request for the selection and target named 743 in the property, and WAIT until it completes */ 744 if (*target == getAtom(display, A_INSERT_SELECTION)) { 745 if (((TextWidget)w)->text.readOnly) 746 return False; 747 if (XGetWindowProperty(event->display, event->requestor, 748 event->property, 0, 2, False, AnyPropertyType, &dummyAtom, 749 &getFmt, &nItems, &dummyULong, 750 (unsigned char **)&reqAtoms) != Success || 751 getFmt != 32 || nItems != 2) 752 return False; 753 if (reqAtoms[1] != XA_STRING) 754 return False; 755 XtGetSelectionValue(w, reqAtoms[0], reqAtoms[1], 756 getInsertSelectionCB, &result, event->time); 757 XFree((char *)reqAtoms); 758 while (result == INSERT_WAITING) { 759 XtAppNextEvent(XtWidgetToApplicationContext(w), &nextEvent); 760 XtDispatchEvent(&nextEvent); 761 } 762 *type = getAtom(display, A_INSERT_SELECTION); 763 *format = 8; 764 *value = NULL; 765 *length = 0; 766 return result == SUCCESSFUL_INSERT; 767 } 768 769 /* target is "DELETE": delete primary selection */ 770 if (*target == getAtom(display, A_DELETE)) { 771 BufRemoveSelected(buf); 772 *length = 0; 773 *format = 8; 774 *type = getAtom(display, A_DELETE); 775 *value = NULL; 776 return True; 777 } 778 779 /* targets TIMESTAMP and MULTIPLE are handled by the toolkit, any 780 others are unrecognized, return False */ 781 return False; 782 } 783 784 static void loseSelectionCB(Widget w, Atom *selType) 785 { 786 TextWidget tw = (TextWidget)w; 787 selection *sel = &tw->text.textD->buffer->primary; 788 char zeroWidth = sel->rectangular ? sel->zeroWidth : 0; 789 790 /* For zero width rect. sel. we give up the selection but keep the 791 zero width tag. */ 792 tw->text.selectionOwner = False; 793 BufUnselect(tw->text.textD->buffer); 794 sel->zeroWidth = zeroWidth; 795 } 796 797 /* 798 ** Selection converter procedure used by the widget to (temporarily) provide 799 ** the secondary selection data to a single requestor who has been asked 800 ** to insert it. 801 */ 802 static Boolean convertSecondaryCB(Widget w, Atom *selType, Atom *target, 803 Atom *type, XtPointer *value, unsigned long *length, int *format) 804 { 805 textBuffer *buf = ((TextWidget)w)->text.textD->buffer; 806 807 /* target must be string */ 808 if (*target != XA_STRING && *target != getAtom(XtDisplay(w), A_TEXT)) 809 return False; 810 811 /* Return the contents of the secondary selection. The memory allocated 812 here is freed by the X toolkit */ 813 *type = XA_STRING; 814 *value = (XtPointer)BufGetSecSelectText(buf); 815 *length = strlen((char *)*value); 816 *format = 8; 817 BufUnsubstituteNullChars(*value, buf); 818 return True; 819 } 820 821 static void loseSecondaryCB(Widget w, Atom *selType) 822 { 823 /* do nothing, secondary selections are transient anyhow, and it 824 will go away on its own */ 825 } 826 827 /* 828 ** Selection converter procedure used by the widget when it owns the Motif 829 ** destination, to handle INSERT_SELECTION requests. 830 */ 831 static Boolean convertMotifDestCB(Widget w, Atom *selType, Atom *target, 832 Atom *type, XtPointer *value, unsigned long *length, int *format) 833 { 834 XSelectionRequestEvent *event = XtGetSelectionRequest(w, *selType, 0); 835 Display *display = XtDisplay(w); 836 Atom *targets, dummyAtom; 837 unsigned long nItems, dummyULong; 838 Atom *reqAtoms; 839 int getFmt, result = INSERT_WAITING; 840 XEvent nextEvent; 841 842 /* target is "TARGETS", return a list of targets it can handle */ 843 if (*target == getAtom(display, A_TARGETS)) { 844 targets = (Atom *)NEditMalloc(sizeof(Atom) * 3); 845 targets[0] = getAtom(display, A_TARGETS); 846 targets[1] = getAtom(display, A_TIMESTAMP); 847 targets[2] = getAtom(display, A_INSERT_SELECTION); 848 *type = XA_ATOM; 849 *value = (XtPointer)targets; 850 *length = 3; 851 *format = 32; 852 return True; 853 } 854 855 /* target is "INSERT_SELECTION": 1) get the information about what 856 selection and target to use to get the text to insert, from the 857 property named in the property field of the selection request event. 858 2) initiate a get value request for the selection and target named 859 in the property, and WAIT until it completes */ 860 if (*target == getAtom(display, A_INSERT_SELECTION)) { 861 if (((TextWidget)w)->text.readOnly) 862 return False; 863 if (XGetWindowProperty(event->display, event->requestor, 864 event->property, 0, 2, False, AnyPropertyType, &dummyAtom, 865 &getFmt, &nItems, &dummyULong, 866 (unsigned char **)&reqAtoms) != Success || 867 getFmt != 32 || nItems != 2) 868 return False; 869 if (reqAtoms[1] != XA_STRING) 870 return False; 871 XtGetSelectionValue(w, reqAtoms[0], reqAtoms[1], 872 getInsertSelectionCB, &result, event->time); 873 XFree((char *)reqAtoms); 874 while (result == INSERT_WAITING) { 875 XtAppNextEvent(XtWidgetToApplicationContext(w), &nextEvent); 876 XtDispatchEvent(&nextEvent); 877 } 878 *type = getAtom(display, A_INSERT_SELECTION); 879 *format = 8; 880 *value = NULL; 881 *length = 0; 882 return result == SUCCESSFUL_INSERT; 883 } 884 885 /* target TIMESTAMP is handled by the toolkit and not passed here, any 886 others are unrecognized */ 887 return False; 888 } 889 890 static void loseMotifDestCB(Widget w, Atom *selType) 891 { 892 ((TextWidget)w)->text.motifDestOwner = False; 893 if (((TextWidget)w)->text.textD->cursorStyle == CARET_CURSOR) 894 TextDSetCursorStyle(((TextWidget)w)->text.textD, DIM_CURSOR); 895 } 896 897 /* 898 ** Event handler for SelectionNotify events, to finish off INSERT_SELECTION 899 ** requests which must be done through the lower 900 ** level (and more complicated) XLib selection mechanism. Matches the 901 ** time stamp in the request against the time stamp stored when the selection 902 ** request was made to find the selectionNotify event that it was installed 903 ** to catch. When it finds the correct event, it does the action it was 904 ** installed to do, and removes itself and its backup timer (which would do 905 ** the clean up if the selectionNotify event never arrived.) 906 */ 907 static void selectNotifyEH(Widget w, XtPointer data, XEvent *event, 908 Boolean *continueDispatch) 909 { 910 textBuffer *buf = ((TextWidget)w)->text.textD->buffer; 911 XSelectionEvent *e = (XSelectionEvent *)event; 912 selectNotifyInfo *cbInfo = (selectNotifyInfo *)data; 913 int selStart, selEnd; 914 char *string; 915 916 /* Check if this was the selection request for which this handler was 917 set up, if not, do nothing */ 918 if (event->type != SelectionNotify || e->time != cbInfo->timeStamp) 919 return; 920 921 /* The time stamp matched, remove this event handler and its 922 backup timer procedure */ 923 XtRemoveEventHandler(w, 0, True, selectNotifyEH, data); 924 XtRemoveTimeOut(cbInfo->timeoutProcID); 925 926 /* Check if the request succeeded, if not, beep, remove any existing 927 secondary selection, and return */ 928 if (e->property == None) { 929 XBell(XtDisplay(w), 0); 930 BufSecondaryUnselect(buf); 931 XtDisownSelection(w, XA_SECONDARY, e->time); 932 NEditFree(cbInfo->actionText); 933 NEditFree(cbInfo); 934 return; 935 } 936 937 /* Do the requested action, if the action is exchange, also clean up 938 the properties created for returning the primary selection and making 939 the MULTIPLE target request */ 940 if (cbInfo->action == REMOVE_SECONDARY) { 941 BufRemoveSecSelect(buf); 942 } else if (cbInfo->action == EXCHANGE_SECONDARY) { 943 string = (char*)NEditMalloc(cbInfo->length + 1); 944 memcpy(string, cbInfo->actionText, cbInfo->length); 945 string[cbInfo->length] = '\0'; 946 selStart = buf->secondary.start; 947 if (BufSubstituteNullChars(string, cbInfo->length, buf)) { 948 BufReplaceSecSelect(buf, string); 949 if (buf->secondary.rectangular) { 950 /*... it would be nice to re-select, but probably impossible */ 951 TextDSetInsertPosition(((TextWidget)w)->text.textD, 952 buf->cursorPosHint); 953 } else { 954 selEnd = selStart + cbInfo->length; 955 BufSelect(buf, selStart, selEnd); 956 TextDSetInsertPosition(((TextWidget)w)->text.textD, selEnd); 957 } 958 } else 959 fprintf(stderr, "Too much binary data\n"); 960 NEditFree(string); 961 } 962 BufSecondaryUnselect(buf); 963 XtDisownSelection(w, XA_SECONDARY, e->time); 964 NEditFree(cbInfo->actionText); 965 NEditFree(cbInfo); 966 } 967 968 /* 969 ** Xt timer procedure for timeouts on XConvertSelection requests, cleans up 970 ** after a complete failure of the selection mechanism to return a selection 971 ** notify event for a convert selection request 972 */ 973 static void selectNotifyTimerProc(XtPointer clientData, XtIntervalId *id) 974 { 975 selectNotifyInfo *cbInfo = (selectNotifyInfo *)clientData; 976 textBuffer *buf = ((TextWidget)cbInfo->widget)->text.textD->buffer; 977 978 fprintf(stderr, "NEdit: timeout on selection request\n"); 979 XtRemoveEventHandler(cbInfo->widget, 0, True, selectNotifyEH, cbInfo); 980 BufSecondaryUnselect(buf); 981 XtDisownSelection(cbInfo->widget, XA_SECONDARY, cbInfo->timeStamp); 982 NEditFree(cbInfo->actionText); 983 NEditFree(cbInfo); 984 } 985 986 /* 987 ** Maintain a cache of interned atoms. To reference one, use the constant 988 ** from the enum, atomIndex, above. 989 */ 990 static Atom getAtom(Display *display, int atomNum) 991 { 992 static Atom atomList[N_ATOMS] = {0}; 993 static char *atomNames[N_ATOMS] = {"TEXT", "TARGETS", "MULTIPLE", 994 "TIMESTAMP", "INSERT_SELECTION", "DELETE", "CLIPBOARD", 995 "INSERT_INFO", "ATOM_PAIR", "MOTIF_DESTINATION", "COMPOUND_TEXT", 996 "UTF8_STRING"}; 997 998 if (atomList[atomNum] == 0) 999 atomList[atomNum] = XInternAtom(display, atomNames[atomNum], False); 1000 return atomList[atomNum]; 1001 } 1002