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