Committed SF patch #963120: Open tab in window on current desktop.
[nedit.git] / source / textSel.c
blobbaad073e2e3c091c216fbb2747a6762eb7cee064
1 static const char CVSID[] = "$Id: textSel.c,v 1.13 2003/10/22 20:05:13 tringali Exp $";
2 /*******************************************************************************
3 * *
4 * textSel.c - Selection and clipboard routines for NEdit text widget *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
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 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "textSel.h"
34 #include "textP.h"
35 #include "text.h"
36 #include "textDisp.h"
37 #include "textBuf.h"
39 #include <stdio.h>
40 #include <string.h>
41 #include <limits.h>
43 #include <Xm/Xm.h>
44 #include <Xm/CutPaste.h>
45 #include <Xm/Text.h>
46 #include <X11/Xatom.h>
47 #if XmVersion >= 1002
48 #include <Xm/PrimitiveP.h>
49 #endif
51 #ifdef HAVE_DEBUG_H
52 #include "../debug.h"
53 #endif
56 #define N_SELECT_TARGETS 7
57 #define N_CLIP_TARGETS 4
58 #define N_ATOMS 11
59 enum atomIndex {A_TEXT, A_TARGETS, A_MULTIPLE, A_TIMESTAMP,
60 A_INSERT_SELECTION, A_DELETE, A_CLIPBOARD, A_INSERT_INFO,
61 A_ATOM_PAIR, A_MOTIF_DESTINATION, A_COMPOUND_TEXT};
63 /* Results passed back to the convert proc processing an INSERT_SELECTION
64 request, by getInsertSelection when the selection to insert has been
65 received and processed */
66 enum insertResultFlags {INSERT_WAITING, UNSUCCESSFUL_INSERT, SUCCESSFUL_INSERT};
68 /* Actions for selection notify event handler upon receiving confermation
69 of a successful convert selection request */
70 enum selectNotifyActions {UNSELECT_SECONDARY, REMOVE_SECONDARY,
71 EXCHANGE_SECONDARY};
73 /* temporary structure for passing data to the event handler for completing
74 selection requests (the hard way, via xlib calls) */
75 typedef struct {
76 int action;
77 XtIntervalId timeoutProcID;
78 Time timeStamp;
79 Widget widget;
80 char *actionText;
81 int length;
82 } selectNotifyInfo;
84 static void modifiedCB(int pos, int nInserted, int nDeleted,
85 int nRestyled, char *deletedText, void *cbArg);
86 static void sendSecondary(Widget w, Time time, Atom sel, int action,
87 char *actionText, int actionTextLen);
88 static void getSelectionCB(Widget w, XtPointer clientData, Atom *selType,
89 Atom *type, XtPointer value, unsigned long *length, int *format);
90 static void getInsertSelectionCB(Widget w, XtPointer clientData,Atom *selType,
91 Atom *type, XtPointer value, unsigned long *length, int *format);
92 static void getExchSelCB(Widget w, XtPointer clientData, Atom *selType,
93 Atom *type, XtPointer value, unsigned long *length, int *format);
94 static Boolean convertSelectionCB(Widget w, Atom *selType, Atom *target,
95 Atom *type, XtPointer *value, unsigned long *length, int *format);
96 static void loseSelectionCB(Widget w, Atom *selType);
97 static Boolean convertSecondaryCB(Widget w, Atom *selType, Atom *target,
98 Atom *type, XtPointer *value, unsigned long *length, int *format);
99 static void loseSecondaryCB(Widget w, Atom *selType);
100 static Boolean convertMotifDestCB(Widget w, Atom *selType, Atom *target,
101 Atom *type, XtPointer *value, unsigned long *length, int *format);
102 static void loseMotifDestCB(Widget w, Atom *selType);
103 static void selectNotifyEH(Widget w, XtPointer data, XEvent *event,
104 Boolean *continueDispatch);
105 static void selectNotifyTimerProc(XtPointer clientData, XtIntervalId *id);
106 static Atom getAtom(Display *display, int atomNum);
109 ** Designate text widget "w" to be the selection owner for primary selections
110 ** in its attached buffer (a buffer can be attached to multiple text widgets).
112 void HandleXSelections(Widget w)
114 int i;
115 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
117 /* Remove any existing selection handlers for other widgets */
118 for (i=0; i<buf->nModifyProcs; i++) {
119 if (buf->modifyProcs[i] == modifiedCB) {
120 BufRemoveModifyCB(buf, modifiedCB, buf->cbArgs[i]);
121 break;
125 /* Add a handler with this widget as the CB arg (and thus the sel. owner) */
126 BufAddModifyCB(((TextWidget)w)->text.textD->buffer, modifiedCB, w);
130 ** Discontinue ownership of selections for widget "w"'s attached buffer
131 ** (if "w" was the designated selection owner)
133 void StopHandlingXSelections(Widget w)
135 int i;
136 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
138 for (i=0; i<buf->nModifyProcs; i++) {
139 if (buf->modifyProcs[i] == modifiedCB && buf->cbArgs[i] == w) {
140 BufRemoveModifyCB(buf, modifiedCB, buf->cbArgs[i]);
141 return;
147 ** Copy the primary selection to the clipboard
149 void CopyToClipboard(Widget w, Time time)
151 char *text;
152 long itemID = 0;
153 XmString s;
154 int stat, length;
156 /* Get the selected text, if there's no selection, do nothing */
157 text = BufGetSelectionText(((TextWidget)w)->text.textD->buffer);
158 if (*text == '\0') {
159 XtFree(text);
160 return;
163 /* If the string contained ascii-nul characters, something else was
164 substituted in the buffer. Put the nulls back */
165 length = strlen(text);
166 BufUnsubstituteNullChars(text, ((TextWidget)w)->text.textD->buffer);
168 /* Shut up LessTif */
169 if (XmClipboardLock(XtDisplay(w), XtWindow(w)) != ClipboardSuccess) {
170 XtFree(text);
171 return;
174 /* Use the XmClipboard routines to copy the text to the clipboard.
175 If errors occur, just give up. */
176 s = XmStringCreateSimple("NEdit");
177 stat = XmClipboardStartCopy(XtDisplay(w), XtWindow(w), s,
178 time, w, NULL, &itemID);
179 XmStringFree(s);
180 if (stat != ClipboardSuccess)
181 return;
183 /* Note that we were previously passing length + 1 here, but I suspect
184 that this was inconsistent with the somewhat ambiguous policy of
185 including a terminating null but not mentioning it in the length */
187 if (XmClipboardCopy(XtDisplay(w), XtWindow(w), itemID, "STRING",
188 text, length, 0, NULL) != ClipboardSuccess) {
189 XtFree(text);
190 return;
192 XtFree(text);
193 XmClipboardEndCopy(XtDisplay(w), XtWindow(w), itemID);
194 XmClipboardUnlock(XtDisplay(w), XtWindow(w), False);
198 ** Insert the X PRIMARY selection (from whatever window currently owns it)
199 ** at the cursor position.
201 void InsertPrimarySelection(Widget w, Time time, int isColumnar)
203 static int isColFlag;
205 /* Theoretically, strange things could happen if the user managed to get
206 in any events between requesting receiving the selection data, however,
207 getSelectionCB simply inserts the selection at the cursor. Don't
208 bother with further measures until real problems are observed. */
209 isColFlag = isColumnar;
210 XtGetSelectionValue(w, XA_PRIMARY, XA_STRING, getSelectionCB, &isColFlag,
211 time);
215 ** Insert the secondary selection at the motif destination by initiating
216 ** an INSERT_SELECTION request to the current owner of the MOTIF_DESTINATION
217 ** selection. Upon completion, unselect the secondary selection. If
218 ** "removeAfter" is true, also delete the secondary selection from the
219 ** widget's buffer upon completion.
221 void SendSecondarySelection(Widget w, Time time, int removeAfter)
223 sendSecondary(w, time, getAtom(XtDisplay(w), A_MOTIF_DESTINATION),
224 removeAfter ? REMOVE_SECONDARY : UNSELECT_SECONDARY, NULL, 0);
228 ** Exchange Primary and secondary selections (to be called by the widget
229 ** with the secondary selection)
231 void ExchangeSelections(Widget w, Time time)
233 if (!((TextWidget)w)->text.textD->buffer->secondary.selected)
234 return;
236 /* Initiate an long series of events: 1) get the primary selection,
237 2) replace the primary selection with this widget's secondary, 3) replace
238 this widget's secondary with the text returned from getting the primary
239 selection. This could be done with a much more efficient MULTIPLE
240 request following ICCCM conventions, but the X toolkit MULTIPLE handling
241 routines can't handle INSERT_SELECTION requests inside of MULTIPLE
242 requests, because they don't allow access to the requested property atom
243 in inside of an XtConvertSelectionProc. It's simply not worth
244 duplicating all of Xt's selection handling routines for a little
245 performance, and this would make the code incompatible with Motif text
246 widgets */
247 XtGetSelectionValue(w, XA_PRIMARY, XA_STRING, getExchSelCB, NULL, time);
251 ** Insert the contents of the PRIMARY selection at the cursor position in
252 ** widget "w" and delete the contents of the selection in its current owner
253 ** (if the selection owner supports DELETE targets).
255 void MovePrimarySelection(Widget w, Time time, int isColumnar)
257 static Atom targets[2] = {XA_STRING};
258 static int isColFlag;
259 static XtPointer clientData[2] =
260 {(XtPointer)&isColFlag, (XtPointer)&isColFlag};
262 targets[1] = getAtom(XtDisplay(w), A_DELETE);
263 isColFlag = isColumnar;
264 /* some strangeness here: the selection callback appears to be getting
265 clientData[1] for targets[0] */
266 XtGetSelectionValues(w, XA_PRIMARY, targets, 2, getSelectionCB,
267 clientData, time);
271 ** Insert the X CLIPBOARD selection at the cursor position. If isColumnar,
272 ** do an BufInsertCol for a columnar paste instead of BufInsert.
274 void InsertClipboard(Widget w, int isColumnar)
276 unsigned long length, retLength;
277 textDisp *textD = ((TextWidget)w)->text.textD;
278 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
279 int cursorLineStart, column, cursorPos;
280 char *string;
281 long id = 0;
283 /* Get the clipboard contents. Note: this code originally used the
284 CLIPBOARD selection, rather than the Motif clipboard interface. It
285 was changed because Motif widgets in the same application would hang
286 when users pasted data from nedit text widgets. This happened because
287 the XmClipboard routines used by the widgets do blocking event reads,
288 preventing a response by a selection owner in the same application.
289 While the Motif clipboard routines as they are used below, limit the
290 size of the data that be transferred via the clipboard, and are
291 generally slower and buggier, they do preserve the clipboard across
292 widget destruction and even program termination. */
293 if (XmClipboardInquireLength(XtDisplay(w), XtWindow(w), "STRING", &length)
294 != ClipboardSuccess || length == 0)
295 return;
296 string = XtMalloc(length+1);
297 if (XmClipboardRetrieve(XtDisplay(w), XtWindow(w), "STRING", string,
298 length, &retLength, &id) != ClipboardSuccess || retLength == 0) {
299 XtFree(string);
300 return;
302 string[retLength] = '\0';
304 /* If the string contains ascii-nul characters, substitute something
305 else, or give up, warn, and refuse */
306 if (!BufSubstituteNullChars(string, retLength, buf)) {
307 fprintf(stderr, "Too much binary data, text not pasted\n");
308 XtFree(string);
309 return;
312 /* Insert it in the text widget */
313 if (isColumnar && !buf->primary.selected) {
314 cursorPos = TextDGetInsertPosition(textD);
315 cursorLineStart = BufStartOfLine(buf, cursorPos);
316 column = BufCountDispChars(buf, cursorLineStart, cursorPos);
317 if (((TextWidget)w)->text.overstrike) {
318 BufOverlayRect(buf, cursorLineStart, column, -1, string, NULL,
319 NULL);
320 } else {
321 BufInsertCol(buf, column, cursorLineStart, string, NULL, NULL);
323 TextDSetInsertPosition(textD,
324 BufCountForwardDispChars(buf, cursorLineStart, column));
325 if (((TextWidget)w)->text.autoShowInsertPos)
326 TextDMakeInsertPosVisible(textD);
327 } else
328 TextInsertAtCursor(w, string, NULL, True,
329 ((TextWidget)w)->text.autoWrapPastedText);
330 XtFree(string);
334 ** Take ownership of the MOTIF_DESTINATION selection. This is Motif's private
335 ** selection type for designating a widget to receive the result of
336 ** secondary quick action requests. The NEdit text widget uses this also
337 ** for compatibility with Motif text widgets.
339 void TakeMotifDestination(Widget w, Time time)
341 if (((TextWidget)w)->text.motifDestOwner || ((TextWidget)w)->text.readOnly)
342 return;
344 /* Take ownership of the MOTIF_DESTINATION selection */
345 if (!XtOwnSelection(w, getAtom(XtDisplay(w), A_MOTIF_DESTINATION), time,
346 convertMotifDestCB, loseMotifDestCB, NULL)) {
347 return;
349 ((TextWidget)w)->text.motifDestOwner = True;
353 ** This routine is called every time there is a modification made to the
354 ** buffer to which this callback is attached, with an argument of the text
355 ** widget that has been designated (by HandleXSelections) to handle its
356 ** selections. It checks if the status of the selection in the buffer
357 ** has changed since last time, and owns or disowns the X selection depending
358 ** on the status of the primary selection in the buffer. If it is not allowed
359 ** to take ownership of the selection, it unhighlights the text in the buffer
360 ** (Being in the middle of a modify callback, this has a somewhat complicated
361 ** result, since later callbacks will see the second modifications first).
363 static void modifiedCB(int pos, int nInserted, int nDeleted,
364 int nRestyled, char *deletedText, void *cbArg)
366 TextWidget w = (TextWidget)cbArg;
367 Time time = XtLastTimestampProcessed(XtDisplay((Widget)w));
368 int selected = w->text.textD->buffer->primary.selected;
369 int isOwner = w->text.selectionOwner;
371 /* If the widget owns the selection and the buffer text is still selected,
372 or if the widget doesn't own it and there's no selection, do nothing */
373 if ((isOwner && selected) || (!isOwner && !selected))
374 return;
376 /* If we own the selection and the selection is now empty, give it up */
377 if (isOwner && !selected) {
378 XtDisownSelection((Widget)w, XA_PRIMARY, time);
379 w->text.selectionOwner = False;
380 return;
383 /* Take ownership of the selection */
384 if (!XtOwnSelection((Widget)w, XA_PRIMARY, time, convertSelectionCB,
385 loseSelectionCB, NULL))
386 BufUnselect(w->text.textD->buffer);
387 else
388 w->text.selectionOwner = True;
392 ** Send an INSERT_SELECTION request to "sel".
393 ** Upon completion, do the action specified by "action" (one of enum
394 ** selectNotifyActions) using "actionText" and freeing actionText (if
395 ** not NULL) when done.
397 static void sendSecondary(Widget w, Time time, Atom sel, int action,
398 char *actionText, int actionTextLen)
400 static Atom selInfoProp[2] = {XA_SECONDARY, XA_STRING};
401 Display *disp = XtDisplay(w);
402 selectNotifyInfo *cbInfo;
403 XtAppContext context = XtWidgetToApplicationContext((Widget)w);
405 /* Take ownership of the secondary selection, give up if we can't */
406 if (!XtOwnSelection(w, XA_SECONDARY, time, convertSecondaryCB,
407 loseSecondaryCB, NULL)) {
408 BufSecondaryUnselect(((TextWidget)w)->text.textD->buffer);
409 return;
412 /* Set up a property on this window to pass along with the
413 INSERT_SELECTION request to tell the MOTIF_DESTINATION owner what
414 selection and what target from that selection to insert */
415 XChangeProperty(disp, XtWindow(w), getAtom(disp, A_INSERT_INFO),
416 getAtom(disp, A_ATOM_PAIR), 32, PropModeReplace,
417 (unsigned char *)selInfoProp, 2 /* 1? */);
419 /* Make INSERT_SELECTION request to the owner of selection "sel"
420 to do the insert. This must be done using XLib calls to specify
421 the property with the information about what to insert. This
422 means it also requires an event handler to see if the request
423 succeeded or not, and a backup timer to clean up if the select
424 notify event is never returned */
425 XConvertSelection(XtDisplay(w), sel, getAtom(disp, A_INSERT_SELECTION),
426 getAtom(disp, A_INSERT_INFO), XtWindow(w), time);
427 cbInfo = (selectNotifyInfo *)XtMalloc(sizeof(selectNotifyInfo));
428 cbInfo->action = action;
429 cbInfo->timeStamp = time;
430 cbInfo->widget = (Widget)w;
431 cbInfo->actionText = actionText;
432 cbInfo->length = actionTextLen;
433 XtAddEventHandler(w, 0, True, selectNotifyEH, (XtPointer)cbInfo);
434 cbInfo->timeoutProcID = XtAppAddTimeOut(context,
435 XtAppGetSelectionTimeout(context),
436 selectNotifyTimerProc, (XtPointer)cbInfo);
440 ** Called when data arrives from a request for the PRIMARY selection. If
441 ** everything is in order, it inserts it at the cursor in the requesting
442 ** widget.
444 static void getSelectionCB(Widget w, XtPointer clientData, Atom *selType,
445 Atom *type, XtPointer value, unsigned long *length, int *format)
447 textDisp *textD = ((TextWidget)w)->text.textD;
448 int isColumnar = *(int *)clientData;
449 int cursorLineStart, cursorPos, column, row;
450 char *string;
452 /* Confirm that the returned value is of the correct type */
453 if (*type != XA_STRING || *format != 8) {
454 if (value != NULL)
455 XtFree((char *)value);
456 return;
459 /* Copy the string just to make space for the null character (this may
460 not be necessary, XLib documentation claims a NULL is already added,
461 but the Xt documentation for this routine makes no such claim) */
462 string = XtMalloc(*length + 1);
463 memcpy(string, (char *)value, *length);
464 string[*length] = '\0';
466 /* If the string contains ascii-nul characters, substitute something
467 else, or give up, warn, and refuse */
468 if (!BufSubstituteNullChars(string, *length, textD->buffer)) {
469 fprintf(stderr, "Too much binary data, giving up\n");
470 XtFree(string);
471 XtFree((char *)value);
472 return;
475 /* Insert it in the text widget */
476 if (isColumnar) {
477 cursorPos = TextDGetInsertPosition(textD);
478 cursorLineStart = BufStartOfLine(textD->buffer, cursorPos);
479 TextDXYToUnconstrainedPosition(textD, ((TextWidget)w)->text.btnDownX,
480 ((TextWidget)w)->text.btnDownY, &row, &column);
481 BufInsertCol(textD->buffer, column, cursorLineStart, string, NULL,NULL);
482 TextDSetInsertPosition(textD, textD->buffer->cursorPosHint);
483 } else
484 TextInsertAtCursor(w, string, NULL, False,
485 ((TextWidget)w)->text.autoWrapPastedText);
486 XtFree(string);
488 /* The selection requstor is required to free the memory passed
489 to it via value */
490 XtFree((char *)value);
494 ** Called when data arrives from request resulting from processing an
495 ** INSERT_SELECTION request. If everything is in order, inserts it at
496 ** the cursor or replaces pending delete selection in widget "w", and sets
497 ** the flag passed in clientData to SUCCESSFUL_INSERT or UNSUCCESSFUL_INSERT
498 ** depending on the success of the operation.
500 static void getInsertSelectionCB(Widget w, XtPointer clientData,Atom *selType,
501 Atom *type, XtPointer value, unsigned long *length, int *format)
503 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
504 char *string;
505 int *resultFlag = (int *)clientData;
507 /* Confirm that the returned value is of the correct type */
508 if (*type != XA_STRING || *format != 8 || value == NULL) {
509 if (value != NULL)
510 XtFree((char *)value);
511 *resultFlag = UNSUCCESSFUL_INSERT;
512 return;
515 /* Copy the string just to make space for the null character */
516 string = XtMalloc(*length + 1);
517 memcpy(string, (char *)value, *length);
518 string[*length] = '\0';
520 /* If the string contains ascii-nul characters, substitute something
521 else, or give up, warn, and refuse */
522 if (!BufSubstituteNullChars(string, *length, buf)) {
523 fprintf(stderr, "Too much binary data, giving up\n");
524 XtFree(string);
525 XtFree((char *)value);
526 return;
529 /* Insert it in the text widget */
530 TextInsertAtCursor(w, string, NULL, True,
531 ((TextWidget)w)->text.autoWrapPastedText);
532 XtFree(string);
533 *resultFlag = SUCCESSFUL_INSERT;
535 /* This callback is required to free the memory passed to it thru value */
536 XtFree((char *)value);
540 ** Called when data arrives from an X primary selection request for the
541 ** purpose of exchanging the primary and secondary selections.
542 ** If everything is in order, stores the retrieved text temporarily and
543 ** initiates a request to replace the primary selection with this widget's
544 ** secondary selection.
546 static void getExchSelCB(Widget w, XtPointer clientData, Atom *selType,
547 Atom *type, XtPointer value, unsigned long *length, int *format)
549 /* Confirm that there is a value and it is of the correct type */
550 if (*length == 0 || value == NULL || *type != XA_STRING || *format != 8) {
551 if (value != NULL)
552 XtFree((char *)value);
553 XBell(XtDisplay(w), 0);
554 BufSecondaryUnselect(((TextWidget)w)->text.textD->buffer);
555 return;
558 /* Request the selection owner to replace the primary selection with
559 this widget's secondary selection. When complete, replace this
560 widget's secondary selection with text "value" and free it. */
561 sendSecondary(w, XtLastTimestampProcessed(XtDisplay(w)), XA_PRIMARY,
562 EXCHANGE_SECONDARY, (char *)value, *length);
566 ** Selection converter procedure used by the widget when it is the selection
567 ** owner to provide data in the format requested by the selection requestor.
569 ** Note: Memory left in the *value field is freed by Xt as long as there is no
570 ** done_proc procedure registered in the XtOwnSelection call where this
571 ** procdeure is registered
573 static Boolean convertSelectionCB(Widget w, Atom *selType, Atom *target,
574 Atom *type, XtPointer *value, unsigned long *length, int *format)
576 XSelectionRequestEvent *event = XtGetSelectionRequest(w, *selType, 0);
577 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
578 Display *display = XtDisplay(w);
579 Atom *targets, dummyAtom;
580 unsigned long nItems, dummyULong;
581 Atom *reqAtoms;
582 int getFmt, result = INSERT_WAITING;
583 XEvent nextEvent;
585 /* target is text, string, or compound text */
586 if (*target == XA_STRING || *target == getAtom(display, A_TEXT) ||
587 *target == getAtom(display, A_COMPOUND_TEXT)) {
588 /* We really don't directly support COMPOUND_TEXT, but recent
589 versions gnome-terminal incorrectly ask for it, even though
590 don't declare that we do. Just reply in string format. */
591 *type = XA_STRING;
592 *value = (XtPointer)BufGetSelectionText(buf);
593 *length = strlen((char *)*value);
594 *format = 8;
595 BufUnsubstituteNullChars(*value, buf);
596 return True;
599 /* target is "TARGETS", return a list of targets we can handle */
600 if (*target == getAtom(display, A_TARGETS)) {
601 targets = (Atom *)XtMalloc(sizeof(Atom) * N_SELECT_TARGETS);
602 targets[0] = XA_STRING;
603 targets[1] = getAtom(display, A_TEXT);
604 targets[2] = getAtom(display, A_TARGETS);
605 targets[3] = getAtom(display, A_MULTIPLE);
606 targets[4] = getAtom(display, A_TIMESTAMP);
607 targets[5] = getAtom(display, A_INSERT_SELECTION);
608 targets[6] = getAtom(display, A_DELETE);
609 *type = XA_ATOM;
610 *value = (XtPointer)targets;
611 *length = N_SELECT_TARGETS;
612 *format = 32;
613 return True;
616 /* target is "INSERT_SELECTION": 1) get the information about what
617 selection and target to use to get the text to insert, from the
618 property named in the property field of the selection request event.
619 2) initiate a get value request for the selection and target named
620 in the property, and WAIT until it completes */
621 if (*target == getAtom(display, A_INSERT_SELECTION)) {
622 if (((TextWidget)w)->text.readOnly)
623 return False;
624 if (XGetWindowProperty(event->display, event->requestor,
625 event->property, 0, 2, False, AnyPropertyType, &dummyAtom,
626 &getFmt, &nItems, &dummyULong,
627 (unsigned char **)&reqAtoms) != Success ||
628 getFmt != 32 || nItems != 2)
629 return False;
630 if (reqAtoms[1] != XA_STRING)
631 return False;
632 XtGetSelectionValue(w, reqAtoms[0], reqAtoms[1],
633 getInsertSelectionCB, &result, event->time);
634 XFree((char *)reqAtoms);
635 while (result == INSERT_WAITING) {
636 XtAppNextEvent(XtWidgetToApplicationContext(w), &nextEvent);
637 XtDispatchEvent(&nextEvent);
639 *type = getAtom(display, A_INSERT_SELECTION);
640 *format = 8;
641 *value = NULL;
642 *length = 0;
643 return result == SUCCESSFUL_INSERT;
646 /* target is "DELETE": delete primary selection */
647 if (*target == getAtom(display, A_DELETE)) {
648 BufRemoveSelected(buf);
649 *length = 0;
650 *format = 8;
651 *type = getAtom(display, A_DELETE);
652 *value = NULL;
653 return True;
656 /* targets TIMESTAMP and MULTIPLE are handled by the toolkit, any
657 others are unrecognized, return False */
658 return False;
661 static void loseSelectionCB(Widget w, Atom *selType)
663 TextWidget tw = (TextWidget)w;
664 selection *sel = &tw->text.textD->buffer->primary;
665 char zeroWidth = sel->rectangular ? sel->zeroWidth : 0;
667 /* For zero width rect. sel. we give up the selection but keep the
668 zero width tag. */
669 tw->text.selectionOwner = False;
670 BufUnselect(tw->text.textD->buffer);
671 sel->zeroWidth = zeroWidth;
675 ** Selection converter procedure used by the widget to (temporarily) provide
676 ** the secondary selection data to a single requestor who has been asked
677 ** to insert it.
679 static Boolean convertSecondaryCB(Widget w, Atom *selType, Atom *target,
680 Atom *type, XtPointer *value, unsigned long *length, int *format)
682 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
684 /* target must be string */
685 if (*target != XA_STRING && *target != getAtom(XtDisplay(w), A_TEXT))
686 return False;
688 /* Return the contents of the secondary selection. The memory allocated
689 here is freed by the X toolkit */
690 *type = XA_STRING;
691 *value = (XtPointer)BufGetSecSelectText(buf);
692 *length = strlen((char *)*value);
693 *format = 8;
694 BufUnsubstituteNullChars(*value, buf);
695 return True;
698 static void loseSecondaryCB(Widget w, Atom *selType)
700 /* do nothing, secondary selections are transient anyhow, and it
701 will go away on its own */
705 ** Selection converter procedure used by the widget when it owns the Motif
706 ** destination, to handle INSERT_SELECTION requests.
708 static Boolean convertMotifDestCB(Widget w, Atom *selType, Atom *target,
709 Atom *type, XtPointer *value, unsigned long *length, int *format)
711 XSelectionRequestEvent *event = XtGetSelectionRequest(w, *selType, 0);
712 Display *display = XtDisplay(w);
713 Atom *targets, dummyAtom;
714 unsigned long nItems, dummyULong;
715 Atom *reqAtoms;
716 int getFmt, result = INSERT_WAITING;
717 XEvent nextEvent;
719 /* target is "TARGETS", return a list of targets it can handle */
720 if (*target == getAtom(display, A_TARGETS)) {
721 targets = (Atom *)XtMalloc(sizeof(Atom) * 3);
722 targets[0] = getAtom(display, A_TARGETS);
723 targets[1] = getAtom(display, A_TIMESTAMP);
724 targets[2] = getAtom(display, A_INSERT_SELECTION);
725 *type = XA_ATOM;
726 *value = (XtPointer)targets;
727 *length = 3;
728 *format = 32;
729 return True;
732 /* target is "INSERT_SELECTION": 1) get the information about what
733 selection and target to use to get the text to insert, from the
734 property named in the property field of the selection request event.
735 2) initiate a get value request for the selection and target named
736 in the property, and WAIT until it completes */
737 if (*target == getAtom(display, A_INSERT_SELECTION)) {
738 if (((TextWidget)w)->text.readOnly)
739 return False;
740 if (XGetWindowProperty(event->display, event->requestor,
741 event->property, 0, 2, False, AnyPropertyType, &dummyAtom,
742 &getFmt, &nItems, &dummyULong,
743 (unsigned char **)&reqAtoms) != Success ||
744 getFmt != 32 || nItems != 2)
745 return False;
746 if (reqAtoms[1] != XA_STRING)
747 return False;
748 XtGetSelectionValue(w, reqAtoms[0], reqAtoms[1],
749 getInsertSelectionCB, &result, event->time);
750 XFree((char *)reqAtoms);
751 while (result == INSERT_WAITING) {
752 XtAppNextEvent(XtWidgetToApplicationContext(w), &nextEvent);
753 XtDispatchEvent(&nextEvent);
755 *type = getAtom(display, A_INSERT_SELECTION);
756 *format = 8;
757 *value = NULL;
758 *length = 0;
759 return result == SUCCESSFUL_INSERT;
762 /* target TIMESTAMP is handled by the toolkit and not passed here, any
763 others are unrecognized */
764 return False;
767 static void loseMotifDestCB(Widget w, Atom *selType)
769 ((TextWidget)w)->text.motifDestOwner = False;
770 if (((TextWidget)w)->text.textD->cursorStyle == CARET_CURSOR)
771 TextDSetCursorStyle(((TextWidget)w)->text.textD, DIM_CURSOR);
775 ** Event handler for SelectionNotify events, to finish off INSERT_SELECTION
776 ** requests which must be done through the lower
777 ** level (and more complicated) XLib selection mechanism. Matches the
778 ** time stamp in the request against the time stamp stored when the selection
779 ** request was made to find the selectionNotify event that it was installed
780 ** to catch. When it finds the correct event, it does the action it was
781 ** installed to do, and removes itself and its backup timer (which would do
782 ** the clean up if the selectionNotify event never arrived.)
784 static void selectNotifyEH(Widget w, XtPointer data, XEvent *event,
785 Boolean *continueDispatch)
787 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
788 XSelectionEvent *e = (XSelectionEvent *)event;
789 selectNotifyInfo *cbInfo = (selectNotifyInfo *)data;
790 int selStart, selEnd;
791 char *string;
793 /* Check if this was the selection request for which this handler was
794 set up, if not, do nothing */
795 if (event->type != SelectionNotify || e->time != cbInfo->timeStamp)
796 return;
798 /* The time stamp matched, remove this event handler and its
799 backup timer procedure */
800 XtRemoveEventHandler(w, 0, True, selectNotifyEH, data);
801 XtRemoveTimeOut(cbInfo->timeoutProcID);
803 /* Check if the request succeeded, if not, beep, remove any existing
804 secondary selection, and return */
805 if (e->property == None) {
806 XBell(XtDisplay(w), 0);
807 BufSecondaryUnselect(buf);
808 XtDisownSelection(w, XA_SECONDARY, e->time);
809 if (cbInfo->actionText != NULL)
810 XtFree((char *)cbInfo->actionText);
811 XtFree((char *)cbInfo);
812 return;
815 /* Do the requested action, if the action is exchange, also clean up
816 the properties created for returning the primary selection and making
817 the MULTIPLE target request */
818 if (cbInfo->action == REMOVE_SECONDARY) {
819 BufRemoveSecSelect(buf);
820 } else if (cbInfo->action == EXCHANGE_SECONDARY) {
821 string = XtMalloc(cbInfo->length + 1);
822 memcpy(string, cbInfo->actionText, cbInfo->length);
823 string[cbInfo->length] = '\0';
824 selStart = buf->secondary.start;
825 if (BufSubstituteNullChars(string, cbInfo->length, buf)) {
826 BufReplaceSecSelect(buf, string);
827 if (buf->secondary.rectangular) {
828 /*... it would be nice to re-select, but probably impossible */
829 TextDSetInsertPosition(((TextWidget)w)->text.textD,
830 buf->cursorPosHint);
831 } else {
832 selEnd = selStart + cbInfo->length;
833 BufSelect(buf, selStart, selEnd);
834 TextDSetInsertPosition(((TextWidget)w)->text.textD, selEnd);
836 } else
837 fprintf(stderr, "Too much binary data\n");
838 XtFree(string);
840 BufSecondaryUnselect(buf);
841 XtDisownSelection(w, XA_SECONDARY, e->time);
842 if (cbInfo->actionText != NULL)
843 XtFree((char *)cbInfo->actionText);
844 XtFree((char *)cbInfo);
848 ** Xt timer procedure for timeouts on XConvertSelection requests, cleans up
849 ** after a complete failure of the selection mechanism to return a selection
850 ** notify event for a convert selection request
852 static void selectNotifyTimerProc(XtPointer clientData, XtIntervalId *id)
854 selectNotifyInfo *cbInfo = (selectNotifyInfo *)clientData;
855 textBuffer *buf = ((TextWidget)cbInfo->widget)->text.textD->buffer;
857 fprintf(stderr, "NEdit: timeout on selection request\n");
858 XtRemoveEventHandler(cbInfo->widget, 0, True, selectNotifyEH, cbInfo);
859 BufSecondaryUnselect(buf);
860 XtDisownSelection(cbInfo->widget, XA_SECONDARY, cbInfo->timeStamp);
861 if (cbInfo->actionText != NULL)
862 XtFree((char *)cbInfo->actionText);
863 XtFree((char *)cbInfo);
867 ** Maintain a cache of interned atoms. To reference one, use the constant
868 ** from the enum, atomIndex, above.
870 static Atom getAtom(Display *display, int atomNum)
872 static Atom atomList[N_ATOMS] = {0};
873 static char *atomNames[N_ATOMS] = {"TEXT", "TARGETS", "MULTIPLE",
874 "TIMESTAMP", "INSERT_SELECTION", "DELETE", "CLIPBOARD",
875 "INSERT_INFO", "ATOM_PAIR", "MOTIF_DESTINATION", "COMPOUND_TEXT"};
877 if (atomList[atomNum] == 0)
878 atomList[atomNum] = XInternAtom(display, atomNames[atomNum], False);
879 return atomList[atomNum];