Minor fix.
[nedit.git] / source / textSel.c
blob3df4a7abe60f1bac6f0f2bb25b3b57eab9ed7682
1 static const char CVSID[] = "$Id: textSel.c,v 1.14 2004/07/21 11:32:05 yooden 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. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * Dec. 15, 1995 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "textSel.h"
35 #include "textP.h"
36 #include "text.h"
37 #include "textDisp.h"
38 #include "textBuf.h"
40 #include <stdio.h>
41 #include <string.h>
42 #include <limits.h>
44 #include <Xm/Xm.h>
45 #include <Xm/CutPaste.h>
46 #include <Xm/Text.h>
47 #include <X11/Xatom.h>
48 #if XmVersion >= 1002
49 #include <Xm/PrimitiveP.h>
50 #endif
52 #ifdef HAVE_DEBUG_H
53 #include "../debug.h"
54 #endif
57 #define N_SELECT_TARGETS 7
58 #define N_CLIP_TARGETS 4
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};
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};
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};
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;
85 static void modifiedCB(int pos, int nInserted, int nDeleted,
86 int nRestyled, 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);
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).
113 void HandleXSelections(Widget w)
115 int i;
116 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
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;
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);
131 ** Discontinue ownership of selections for widget "w"'s attached buffer
132 ** (if "w" was the designated selection owner)
134 void StopHandlingXSelections(Widget w)
136 int i;
137 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
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;
148 ** Copy the primary selection to the clipboard
150 void CopyToClipboard(Widget w, Time time)
152 char *text;
153 long itemID = 0;
154 XmString s;
155 int stat, length;
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 XtFree(text);
161 return;
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);
169 /* Shut up LessTif */
170 if (XmClipboardLock(XtDisplay(w), XtWindow(w)) != ClipboardSuccess) {
171 XtFree(text);
172 return;
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 = XmClipboardStartCopy(XtDisplay(w), XtWindow(w), s,
179 time, w, NULL, &itemID);
180 XmStringFree(s);
181 if (stat != ClipboardSuccess)
182 return;
184 /* Note that we were previously passing length + 1 here, but I suspect
185 that this was inconsistent with the somewhat ambiguous policy of
186 including a terminating null but not mentioning it in the length */
188 if (XmClipboardCopy(XtDisplay(w), XtWindow(w), itemID, "STRING",
189 text, length, 0, NULL) != ClipboardSuccess) {
190 XtFree(text);
191 return;
193 XtFree(text);
194 XmClipboardEndCopy(XtDisplay(w), XtWindow(w), itemID);
195 XmClipboardUnlock(XtDisplay(w), XtWindow(w), False);
199 ** Insert the X PRIMARY selection (from whatever window currently owns it)
200 ** at the cursor position.
202 void InsertPrimarySelection(Widget w, Time time, int isColumnar)
204 static int isColFlag;
206 /* Theoretically, strange things could happen if the user managed to get
207 in any events between requesting receiving the selection data, however,
208 getSelectionCB simply inserts the selection at the cursor. Don't
209 bother with further measures until real problems are observed. */
210 isColFlag = isColumnar;
211 XtGetSelectionValue(w, XA_PRIMARY, XA_STRING, getSelectionCB, &isColFlag,
212 time);
216 ** Insert the secondary selection at the motif destination by initiating
217 ** an INSERT_SELECTION request to the current owner of the MOTIF_DESTINATION
218 ** selection. Upon completion, unselect the secondary selection. If
219 ** "removeAfter" is true, also delete the secondary selection from the
220 ** widget's buffer upon completion.
222 void SendSecondarySelection(Widget w, Time time, int removeAfter)
224 sendSecondary(w, time, getAtom(XtDisplay(w), A_MOTIF_DESTINATION),
225 removeAfter ? REMOVE_SECONDARY : UNSELECT_SECONDARY, NULL, 0);
229 ** Exchange Primary and secondary selections (to be called by the widget
230 ** with the secondary selection)
232 void ExchangeSelections(Widget w, Time time)
234 if (!((TextWidget)w)->text.textD->buffer->secondary.selected)
235 return;
237 /* Initiate an long series of events: 1) get the primary selection,
238 2) replace the primary selection with this widget's secondary, 3) replace
239 this widget's secondary with the text returned from getting the primary
240 selection. This could be done with a much more efficient MULTIPLE
241 request following ICCCM conventions, but the X toolkit MULTIPLE handling
242 routines can't handle INSERT_SELECTION requests inside of MULTIPLE
243 requests, because they don't allow access to the requested property atom
244 in inside of an XtConvertSelectionProc. It's simply not worth
245 duplicating all of Xt's selection handling routines for a little
246 performance, and this would make the code incompatible with Motif text
247 widgets */
248 XtGetSelectionValue(w, XA_PRIMARY, XA_STRING, getExchSelCB, NULL, time);
252 ** Insert the contents of the PRIMARY selection at the cursor position in
253 ** widget "w" and delete the contents of the selection in its current owner
254 ** (if the selection owner supports DELETE targets).
256 void MovePrimarySelection(Widget w, Time time, int isColumnar)
258 static Atom targets[2] = {XA_STRING};
259 static int isColFlag;
260 static XtPointer clientData[2] =
261 {(XtPointer)&isColFlag, (XtPointer)&isColFlag};
263 targets[1] = getAtom(XtDisplay(w), A_DELETE);
264 isColFlag = isColumnar;
265 /* some strangeness here: the selection callback appears to be getting
266 clientData[1] for targets[0] */
267 XtGetSelectionValues(w, XA_PRIMARY, targets, 2, getSelectionCB,
268 clientData, time);
272 ** Insert the X CLIPBOARD selection at the cursor position. If isColumnar,
273 ** do an BufInsertCol for a columnar paste instead of BufInsert.
275 void InsertClipboard(Widget w, int isColumnar)
277 unsigned long length, retLength;
278 textDisp *textD = ((TextWidget)w)->text.textD;
279 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
280 int cursorLineStart, column, cursorPos;
281 char *string;
282 long id = 0;
284 /* Get the clipboard contents. Note: this code originally used the
285 CLIPBOARD selection, rather than the Motif clipboard interface. It
286 was changed because Motif widgets in the same application would hang
287 when users pasted data from nedit text widgets. This happened because
288 the XmClipboard routines used by the widgets do blocking event reads,
289 preventing a response by a selection owner in the same application.
290 While the Motif clipboard routines as they are used below, limit the
291 size of the data that be transferred via the clipboard, and are
292 generally slower and buggier, they do preserve the clipboard across
293 widget destruction and even program termination. */
294 if (XmClipboardInquireLength(XtDisplay(w), XtWindow(w), "STRING", &length)
295 != ClipboardSuccess || length == 0)
296 return;
297 string = XtMalloc(length+1);
298 if (XmClipboardRetrieve(XtDisplay(w), XtWindow(w), "STRING", string,
299 length, &retLength, &id) != ClipboardSuccess || retLength == 0) {
300 XtFree(string);
301 return;
303 string[retLength] = '\0';
305 /* If the string contains ascii-nul characters, substitute something
306 else, or give up, warn, and refuse */
307 if (!BufSubstituteNullChars(string, retLength, buf)) {
308 fprintf(stderr, "Too much binary data, text not pasted\n");
309 XtFree(string);
310 return;
313 /* Insert it in the text widget */
314 if (isColumnar && !buf->primary.selected) {
315 cursorPos = TextDGetInsertPosition(textD);
316 cursorLineStart = BufStartOfLine(buf, cursorPos);
317 column = BufCountDispChars(buf, cursorLineStart, cursorPos);
318 if (((TextWidget)w)->text.overstrike) {
319 BufOverlayRect(buf, cursorLineStart, column, -1, string, NULL,
320 NULL);
321 } else {
322 BufInsertCol(buf, column, cursorLineStart, string, NULL, NULL);
324 TextDSetInsertPosition(textD,
325 BufCountForwardDispChars(buf, cursorLineStart, column));
326 if (((TextWidget)w)->text.autoShowInsertPos)
327 TextDMakeInsertPosVisible(textD);
328 } else
329 TextInsertAtCursor(w, string, NULL, True,
330 ((TextWidget)w)->text.autoWrapPastedText);
331 XtFree(string);
335 ** Take ownership of the MOTIF_DESTINATION selection. This is Motif's private
336 ** selection type for designating a widget to receive the result of
337 ** secondary quick action requests. The NEdit text widget uses this also
338 ** for compatibility with Motif text widgets.
340 void TakeMotifDestination(Widget w, Time time)
342 if (((TextWidget)w)->text.motifDestOwner || ((TextWidget)w)->text.readOnly)
343 return;
345 /* Take ownership of the MOTIF_DESTINATION selection */
346 if (!XtOwnSelection(w, getAtom(XtDisplay(w), A_MOTIF_DESTINATION), time,
347 convertMotifDestCB, loseMotifDestCB, NULL)) {
348 return;
350 ((TextWidget)w)->text.motifDestOwner = True;
354 ** This routine is called every time there is a modification made to the
355 ** buffer to which this callback is attached, with an argument of the text
356 ** widget that has been designated (by HandleXSelections) to handle its
357 ** selections. It checks if the status of the selection in the buffer
358 ** has changed since last time, and owns or disowns the X selection depending
359 ** on the status of the primary selection in the buffer. If it is not allowed
360 ** to take ownership of the selection, it unhighlights the text in the buffer
361 ** (Being in the middle of a modify callback, this has a somewhat complicated
362 ** result, since later callbacks will see the second modifications first).
364 static void modifiedCB(int pos, int nInserted, int nDeleted,
365 int nRestyled, char *deletedText, void *cbArg)
367 TextWidget w = (TextWidget)cbArg;
368 Time time = XtLastTimestampProcessed(XtDisplay((Widget)w));
369 int selected = w->text.textD->buffer->primary.selected;
370 int isOwner = w->text.selectionOwner;
372 /* If the widget owns the selection and the buffer text is still selected,
373 or if the widget doesn't own it and there's no selection, do nothing */
374 if ((isOwner && selected) || (!isOwner && !selected))
375 return;
377 /* If we own the selection and the selection is now empty, give it up */
378 if (isOwner && !selected) {
379 XtDisownSelection((Widget)w, XA_PRIMARY, time);
380 w->text.selectionOwner = False;
381 return;
384 /* Take ownership of the selection */
385 if (!XtOwnSelection((Widget)w, XA_PRIMARY, time, convertSelectionCB,
386 loseSelectionCB, NULL))
387 BufUnselect(w->text.textD->buffer);
388 else
389 w->text.selectionOwner = True;
393 ** Send an INSERT_SELECTION request to "sel".
394 ** Upon completion, do the action specified by "action" (one of enum
395 ** selectNotifyActions) using "actionText" and freeing actionText (if
396 ** not NULL) when done.
398 static void sendSecondary(Widget w, Time time, Atom sel, int action,
399 char *actionText, int actionTextLen)
401 static Atom selInfoProp[2] = {XA_SECONDARY, XA_STRING};
402 Display *disp = XtDisplay(w);
403 selectNotifyInfo *cbInfo;
404 XtAppContext context = XtWidgetToApplicationContext((Widget)w);
406 /* Take ownership of the secondary selection, give up if we can't */
407 if (!XtOwnSelection(w, XA_SECONDARY, time, convertSecondaryCB,
408 loseSecondaryCB, NULL)) {
409 BufSecondaryUnselect(((TextWidget)w)->text.textD->buffer);
410 return;
413 /* Set up a property on this window to pass along with the
414 INSERT_SELECTION request to tell the MOTIF_DESTINATION owner what
415 selection and what target from that selection to insert */
416 XChangeProperty(disp, XtWindow(w), getAtom(disp, A_INSERT_INFO),
417 getAtom(disp, A_ATOM_PAIR), 32, PropModeReplace,
418 (unsigned char *)selInfoProp, 2 /* 1? */);
420 /* Make INSERT_SELECTION request to the owner of selection "sel"
421 to do the insert. This must be done using XLib calls to specify
422 the property with the information about what to insert. This
423 means it also requires an event handler to see if the request
424 succeeded or not, and a backup timer to clean up if the select
425 notify event is never returned */
426 XConvertSelection(XtDisplay(w), sel, getAtom(disp, A_INSERT_SELECTION),
427 getAtom(disp, A_INSERT_INFO), XtWindow(w), time);
428 cbInfo = (selectNotifyInfo *)XtMalloc(sizeof(selectNotifyInfo));
429 cbInfo->action = action;
430 cbInfo->timeStamp = time;
431 cbInfo->widget = (Widget)w;
432 cbInfo->actionText = actionText;
433 cbInfo->length = actionTextLen;
434 XtAddEventHandler(w, 0, True, selectNotifyEH, (XtPointer)cbInfo);
435 cbInfo->timeoutProcID = XtAppAddTimeOut(context,
436 XtAppGetSelectionTimeout(context),
437 selectNotifyTimerProc, (XtPointer)cbInfo);
441 ** Called when data arrives from a request for the PRIMARY selection. If
442 ** everything is in order, it inserts it at the cursor in the requesting
443 ** widget.
445 static void getSelectionCB(Widget w, XtPointer clientData, Atom *selType,
446 Atom *type, XtPointer value, unsigned long *length, int *format)
448 textDisp *textD = ((TextWidget)w)->text.textD;
449 int isColumnar = *(int *)clientData;
450 int cursorLineStart, cursorPos, column, row;
451 char *string;
453 /* Confirm that the returned value is of the correct type */
454 if (*type != XA_STRING || *format != 8) {
455 if (value != NULL)
456 XtFree((char *)value);
457 return;
460 /* Copy the string just to make space for the null character (this may
461 not be necessary, XLib documentation claims a NULL is already added,
462 but the Xt documentation for this routine makes no such claim) */
463 string = XtMalloc(*length + 1);
464 memcpy(string, (char *)value, *length);
465 string[*length] = '\0';
467 /* If the string contains ascii-nul characters, substitute something
468 else, or give up, warn, and refuse */
469 if (!BufSubstituteNullChars(string, *length, textD->buffer)) {
470 fprintf(stderr, "Too much binary data, giving up\n");
471 XtFree(string);
472 XtFree((char *)value);
473 return;
476 /* Insert it in the text widget */
477 if (isColumnar) {
478 cursorPos = TextDGetInsertPosition(textD);
479 cursorLineStart = BufStartOfLine(textD->buffer, cursorPos);
480 TextDXYToUnconstrainedPosition(textD, ((TextWidget)w)->text.btnDownX,
481 ((TextWidget)w)->text.btnDownY, &row, &column);
482 BufInsertCol(textD->buffer, column, cursorLineStart, string, NULL,NULL);
483 TextDSetInsertPosition(textD, textD->buffer->cursorPosHint);
484 } else
485 TextInsertAtCursor(w, string, NULL, False,
486 ((TextWidget)w)->text.autoWrapPastedText);
487 XtFree(string);
489 /* The selection requstor is required to free the memory passed
490 to it via value */
491 XtFree((char *)value);
495 ** Called when data arrives from request resulting from processing an
496 ** INSERT_SELECTION request. If everything is in order, inserts it at
497 ** the cursor or replaces pending delete selection in widget "w", and sets
498 ** the flag passed in clientData to SUCCESSFUL_INSERT or UNSUCCESSFUL_INSERT
499 ** depending on the success of the operation.
501 static void getInsertSelectionCB(Widget w, XtPointer clientData,Atom *selType,
502 Atom *type, XtPointer value, unsigned long *length, int *format)
504 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
505 char *string;
506 int *resultFlag = (int *)clientData;
508 /* Confirm that the returned value is of the correct type */
509 if (*type != XA_STRING || *format != 8 || value == NULL) {
510 if (value != NULL)
511 XtFree((char *)value);
512 *resultFlag = UNSUCCESSFUL_INSERT;
513 return;
516 /* Copy the string just to make space for the null character */
517 string = XtMalloc(*length + 1);
518 memcpy(string, (char *)value, *length);
519 string[*length] = '\0';
521 /* If the string contains ascii-nul characters, substitute something
522 else, or give up, warn, and refuse */
523 if (!BufSubstituteNullChars(string, *length, buf)) {
524 fprintf(stderr, "Too much binary data, giving up\n");
525 XtFree(string);
526 XtFree((char *)value);
527 return;
530 /* Insert it in the text widget */
531 TextInsertAtCursor(w, string, NULL, True,
532 ((TextWidget)w)->text.autoWrapPastedText);
533 XtFree(string);
534 *resultFlag = SUCCESSFUL_INSERT;
536 /* This callback is required to free the memory passed to it thru value */
537 XtFree((char *)value);
541 ** Called when data arrives from an X primary selection request for the
542 ** purpose of exchanging the primary and secondary selections.
543 ** If everything is in order, stores the retrieved text temporarily and
544 ** initiates a request to replace the primary selection with this widget's
545 ** secondary selection.
547 static void getExchSelCB(Widget w, XtPointer clientData, Atom *selType,
548 Atom *type, XtPointer value, unsigned long *length, int *format)
550 /* Confirm that there is a value and it is of the correct type */
551 if (*length == 0 || value == NULL || *type != XA_STRING || *format != 8) {
552 if (value != NULL)
553 XtFree((char *)value);
554 XBell(XtDisplay(w), 0);
555 BufSecondaryUnselect(((TextWidget)w)->text.textD->buffer);
556 return;
559 /* Request the selection owner to replace the primary selection with
560 this widget's secondary selection. When complete, replace this
561 widget's secondary selection with text "value" and free it. */
562 sendSecondary(w, XtLastTimestampProcessed(XtDisplay(w)), XA_PRIMARY,
563 EXCHANGE_SECONDARY, (char *)value, *length);
567 ** Selection converter procedure used by the widget when it is the selection
568 ** owner to provide data in the format requested by the selection requestor.
570 ** Note: Memory left in the *value field is freed by Xt as long as there is no
571 ** done_proc procedure registered in the XtOwnSelection call where this
572 ** procdeure is registered
574 static Boolean convertSelectionCB(Widget w, Atom *selType, Atom *target,
575 Atom *type, XtPointer *value, unsigned long *length, int *format)
577 XSelectionRequestEvent *event = XtGetSelectionRequest(w, *selType, 0);
578 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
579 Display *display = XtDisplay(w);
580 Atom *targets, dummyAtom;
581 unsigned long nItems, dummyULong;
582 Atom *reqAtoms;
583 int getFmt, result = INSERT_WAITING;
584 XEvent nextEvent;
586 /* target is text, string, or compound text */
587 if (*target == XA_STRING || *target == getAtom(display, A_TEXT) ||
588 *target == getAtom(display, A_COMPOUND_TEXT)) {
589 /* We really don't directly support COMPOUND_TEXT, but recent
590 versions gnome-terminal incorrectly ask for it, even though
591 don't declare that we do. Just reply in string format. */
592 *type = XA_STRING;
593 *value = (XtPointer)BufGetSelectionText(buf);
594 *length = strlen((char *)*value);
595 *format = 8;
596 BufUnsubstituteNullChars(*value, buf);
597 return True;
600 /* target is "TARGETS", return a list of targets we can handle */
601 if (*target == getAtom(display, A_TARGETS)) {
602 targets = (Atom *)XtMalloc(sizeof(Atom) * N_SELECT_TARGETS);
603 targets[0] = XA_STRING;
604 targets[1] = getAtom(display, A_TEXT);
605 targets[2] = getAtom(display, A_TARGETS);
606 targets[3] = getAtom(display, A_MULTIPLE);
607 targets[4] = getAtom(display, A_TIMESTAMP);
608 targets[5] = getAtom(display, A_INSERT_SELECTION);
609 targets[6] = getAtom(display, A_DELETE);
610 *type = XA_ATOM;
611 *value = (XtPointer)targets;
612 *length = N_SELECT_TARGETS;
613 *format = 32;
614 return True;
617 /* target is "INSERT_SELECTION": 1) get the information about what
618 selection and target to use to get the text to insert, from the
619 property named in the property field of the selection request event.
620 2) initiate a get value request for the selection and target named
621 in the property, and WAIT until it completes */
622 if (*target == getAtom(display, A_INSERT_SELECTION)) {
623 if (((TextWidget)w)->text.readOnly)
624 return False;
625 if (XGetWindowProperty(event->display, event->requestor,
626 event->property, 0, 2, False, AnyPropertyType, &dummyAtom,
627 &getFmt, &nItems, &dummyULong,
628 (unsigned char **)&reqAtoms) != Success ||
629 getFmt != 32 || nItems != 2)
630 return False;
631 if (reqAtoms[1] != XA_STRING)
632 return False;
633 XtGetSelectionValue(w, reqAtoms[0], reqAtoms[1],
634 getInsertSelectionCB, &result, event->time);
635 XFree((char *)reqAtoms);
636 while (result == INSERT_WAITING) {
637 XtAppNextEvent(XtWidgetToApplicationContext(w), &nextEvent);
638 XtDispatchEvent(&nextEvent);
640 *type = getAtom(display, A_INSERT_SELECTION);
641 *format = 8;
642 *value = NULL;
643 *length = 0;
644 return result == SUCCESSFUL_INSERT;
647 /* target is "DELETE": delete primary selection */
648 if (*target == getAtom(display, A_DELETE)) {
649 BufRemoveSelected(buf);
650 *length = 0;
651 *format = 8;
652 *type = getAtom(display, A_DELETE);
653 *value = NULL;
654 return True;
657 /* targets TIMESTAMP and MULTIPLE are handled by the toolkit, any
658 others are unrecognized, return False */
659 return False;
662 static void loseSelectionCB(Widget w, Atom *selType)
664 TextWidget tw = (TextWidget)w;
665 selection *sel = &tw->text.textD->buffer->primary;
666 char zeroWidth = sel->rectangular ? sel->zeroWidth : 0;
668 /* For zero width rect. sel. we give up the selection but keep the
669 zero width tag. */
670 tw->text.selectionOwner = False;
671 BufUnselect(tw->text.textD->buffer);
672 sel->zeroWidth = zeroWidth;
676 ** Selection converter procedure used by the widget to (temporarily) provide
677 ** the secondary selection data to a single requestor who has been asked
678 ** to insert it.
680 static Boolean convertSecondaryCB(Widget w, Atom *selType, Atom *target,
681 Atom *type, XtPointer *value, unsigned long *length, int *format)
683 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
685 /* target must be string */
686 if (*target != XA_STRING && *target != getAtom(XtDisplay(w), A_TEXT))
687 return False;
689 /* Return the contents of the secondary selection. The memory allocated
690 here is freed by the X toolkit */
691 *type = XA_STRING;
692 *value = (XtPointer)BufGetSecSelectText(buf);
693 *length = strlen((char *)*value);
694 *format = 8;
695 BufUnsubstituteNullChars(*value, buf);
696 return True;
699 static void loseSecondaryCB(Widget w, Atom *selType)
701 /* do nothing, secondary selections are transient anyhow, and it
702 will go away on its own */
706 ** Selection converter procedure used by the widget when it owns the Motif
707 ** destination, to handle INSERT_SELECTION requests.
709 static Boolean convertMotifDestCB(Widget w, Atom *selType, Atom *target,
710 Atom *type, XtPointer *value, unsigned long *length, int *format)
712 XSelectionRequestEvent *event = XtGetSelectionRequest(w, *selType, 0);
713 Display *display = XtDisplay(w);
714 Atom *targets, dummyAtom;
715 unsigned long nItems, dummyULong;
716 Atom *reqAtoms;
717 int getFmt, result = INSERT_WAITING;
718 XEvent nextEvent;
720 /* target is "TARGETS", return a list of targets it can handle */
721 if (*target == getAtom(display, A_TARGETS)) {
722 targets = (Atom *)XtMalloc(sizeof(Atom) * 3);
723 targets[0] = getAtom(display, A_TARGETS);
724 targets[1] = getAtom(display, A_TIMESTAMP);
725 targets[2] = getAtom(display, A_INSERT_SELECTION);
726 *type = XA_ATOM;
727 *value = (XtPointer)targets;
728 *length = 3;
729 *format = 32;
730 return True;
733 /* target is "INSERT_SELECTION": 1) get the information about what
734 selection and target to use to get the text to insert, from the
735 property named in the property field of the selection request event.
736 2) initiate a get value request for the selection and target named
737 in the property, and WAIT until it completes */
738 if (*target == getAtom(display, A_INSERT_SELECTION)) {
739 if (((TextWidget)w)->text.readOnly)
740 return False;
741 if (XGetWindowProperty(event->display, event->requestor,
742 event->property, 0, 2, False, AnyPropertyType, &dummyAtom,
743 &getFmt, &nItems, &dummyULong,
744 (unsigned char **)&reqAtoms) != Success ||
745 getFmt != 32 || nItems != 2)
746 return False;
747 if (reqAtoms[1] != XA_STRING)
748 return False;
749 XtGetSelectionValue(w, reqAtoms[0], reqAtoms[1],
750 getInsertSelectionCB, &result, event->time);
751 XFree((char *)reqAtoms);
752 while (result == INSERT_WAITING) {
753 XtAppNextEvent(XtWidgetToApplicationContext(w), &nextEvent);
754 XtDispatchEvent(&nextEvent);
756 *type = getAtom(display, A_INSERT_SELECTION);
757 *format = 8;
758 *value = NULL;
759 *length = 0;
760 return result == SUCCESSFUL_INSERT;
763 /* target TIMESTAMP is handled by the toolkit and not passed here, any
764 others are unrecognized */
765 return False;
768 static void loseMotifDestCB(Widget w, Atom *selType)
770 ((TextWidget)w)->text.motifDestOwner = False;
771 if (((TextWidget)w)->text.textD->cursorStyle == CARET_CURSOR)
772 TextDSetCursorStyle(((TextWidget)w)->text.textD, DIM_CURSOR);
776 ** Event handler for SelectionNotify events, to finish off INSERT_SELECTION
777 ** requests which must be done through the lower
778 ** level (and more complicated) XLib selection mechanism. Matches the
779 ** time stamp in the request against the time stamp stored when the selection
780 ** request was made to find the selectionNotify event that it was installed
781 ** to catch. When it finds the correct event, it does the action it was
782 ** installed to do, and removes itself and its backup timer (which would do
783 ** the clean up if the selectionNotify event never arrived.)
785 static void selectNotifyEH(Widget w, XtPointer data, XEvent *event,
786 Boolean *continueDispatch)
788 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
789 XSelectionEvent *e = (XSelectionEvent *)event;
790 selectNotifyInfo *cbInfo = (selectNotifyInfo *)data;
791 int selStart, selEnd;
792 char *string;
794 /* Check if this was the selection request for which this handler was
795 set up, if not, do nothing */
796 if (event->type != SelectionNotify || e->time != cbInfo->timeStamp)
797 return;
799 /* The time stamp matched, remove this event handler and its
800 backup timer procedure */
801 XtRemoveEventHandler(w, 0, True, selectNotifyEH, data);
802 XtRemoveTimeOut(cbInfo->timeoutProcID);
804 /* Check if the request succeeded, if not, beep, remove any existing
805 secondary selection, and return */
806 if (e->property == None) {
807 XBell(XtDisplay(w), 0);
808 BufSecondaryUnselect(buf);
809 XtDisownSelection(w, XA_SECONDARY, e->time);
810 if (cbInfo->actionText != NULL)
811 XtFree((char *)cbInfo->actionText);
812 XtFree((char *)cbInfo);
813 return;
816 /* Do the requested action, if the action is exchange, also clean up
817 the properties created for returning the primary selection and making
818 the MULTIPLE target request */
819 if (cbInfo->action == REMOVE_SECONDARY) {
820 BufRemoveSecSelect(buf);
821 } else if (cbInfo->action == EXCHANGE_SECONDARY) {
822 string = XtMalloc(cbInfo->length + 1);
823 memcpy(string, cbInfo->actionText, cbInfo->length);
824 string[cbInfo->length] = '\0';
825 selStart = buf->secondary.start;
826 if (BufSubstituteNullChars(string, cbInfo->length, buf)) {
827 BufReplaceSecSelect(buf, string);
828 if (buf->secondary.rectangular) {
829 /*... it would be nice to re-select, but probably impossible */
830 TextDSetInsertPosition(((TextWidget)w)->text.textD,
831 buf->cursorPosHint);
832 } else {
833 selEnd = selStart + cbInfo->length;
834 BufSelect(buf, selStart, selEnd);
835 TextDSetInsertPosition(((TextWidget)w)->text.textD, selEnd);
837 } else
838 fprintf(stderr, "Too much binary data\n");
839 XtFree(string);
841 BufSecondaryUnselect(buf);
842 XtDisownSelection(w, XA_SECONDARY, e->time);
843 if (cbInfo->actionText != NULL)
844 XtFree((char *)cbInfo->actionText);
845 XtFree((char *)cbInfo);
849 ** Xt timer procedure for timeouts on XConvertSelection requests, cleans up
850 ** after a complete failure of the selection mechanism to return a selection
851 ** notify event for a convert selection request
853 static void selectNotifyTimerProc(XtPointer clientData, XtIntervalId *id)
855 selectNotifyInfo *cbInfo = (selectNotifyInfo *)clientData;
856 textBuffer *buf = ((TextWidget)cbInfo->widget)->text.textD->buffer;
858 fprintf(stderr, "NEdit: timeout on selection request\n");
859 XtRemoveEventHandler(cbInfo->widget, 0, True, selectNotifyEH, cbInfo);
860 BufSecondaryUnselect(buf);
861 XtDisownSelection(cbInfo->widget, XA_SECONDARY, cbInfo->timeStamp);
862 if (cbInfo->actionText != NULL)
863 XtFree((char *)cbInfo->actionText);
864 XtFree((char *)cbInfo);
868 ** Maintain a cache of interned atoms. To reference one, use the constant
869 ** from the enum, atomIndex, above.
871 static Atom getAtom(Display *display, int atomNum)
873 static Atom atomList[N_ATOMS] = {0};
874 static char *atomNames[N_ATOMS] = {"TEXT", "TARGETS", "MULTIPLE",
875 "TIMESTAMP", "INSERT_SELECTION", "DELETE", "CLIPBOARD",
876 "INSERT_INFO", "ATOM_PAIR", "MOTIF_DESTINATION", "COMPOUND_TEXT"};
878 if (atomList[atomNum] == 0)
879 atomList[atomNum] = XInternAtom(display, atomNames[atomNum], False);
880 return atomList[atomNum];