Fix our use of $em_tab_dist after it was changed to 0 for 'turned of'.
[nedit.git] / source / textSel.c
blob5ec034b726dd71a15f9f5a7ad06c95c3dc3bba23
1 static const char CVSID[] = "$Id: textSel.c,v 1.19 2008/01/04 22:11: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"
39 #include "../util/misc.h"
41 #include <stdio.h>
42 #include <string.h>
43 #include <limits.h>
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
53 #ifdef HAVE_DEBUG_H
54 #include "../debug.h"
55 #endif
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};
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, 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);
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 (SpinClipboardLock(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 = 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;
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 */
190 if (SpinClipboardCopy(XtDisplay(w), XtWindow(w), itemID, "STRING",
191 text, length, 0, NULL) != ClipboardSuccess) {
192 XtFree(text);
193 SpinClipboardEndCopy(XtDisplay(w), XtWindow(w), itemID);
194 SpinClipboardUnlock(XtDisplay(w), XtWindow(w));
195 return;
197 XtFree(text);
198 SpinClipboardEndCopy(XtDisplay(w), XtWindow(w), itemID);
199 SpinClipboardUnlock(XtDisplay(w), XtWindow(w));
203 ** Insert the X PRIMARY selection (from whatever window currently owns it)
204 ** at the cursor position.
206 void InsertPrimarySelection(Widget w, Time time, int isColumnar)
208 static int isColFlag;
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);
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.
226 void SendSecondarySelection(Widget w, Time time, int removeAfter)
228 sendSecondary(w, time, getAtom(XtDisplay(w), A_MOTIF_DESTINATION),
229 removeAfter ? REMOVE_SECONDARY : UNSELECT_SECONDARY, NULL, 0);
233 ** Exchange Primary and secondary selections (to be called by the widget
234 ** with the secondary selection)
236 void ExchangeSelections(Widget w, Time time)
238 if (!((TextWidget)w)->text.textD->buffer->secondary.selected)
239 return;
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);
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).
260 void MovePrimarySelection(Widget w, Time time, int isColumnar)
262 static Atom targets[2] = {XA_STRING};
263 static int isColFlag;
264 static XtPointer clientData[2] =
265 {(XtPointer)&isColFlag, (XtPointer)&isColFlag};
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);
276 ** Insert the X CLIPBOARD selection at the cursor position. If isColumnar,
277 ** do an BufInsertCol for a columnar paste instead of BufInsert.
279 void InsertClipboard(Widget w, int isColumnar)
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;
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) {
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.
304 SpinClipboardUnlock(XtDisplay(w), XtWindow(w));
305 return;
307 string = XtMalloc(length+1);
308 if (SpinClipboardRetrieve(XtDisplay(w), XtWindow(w), "STRING", string,
309 length, &retLength, &id) != ClipboardSuccess || retLength == 0) {
310 XtFree(string);
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.
315 SpinClipboardUnlock(XtDisplay(w), XtWindow(w));
316 return;
318 string[retLength] = '\0';
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 XtFree(string);
325 return;
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);
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 XtFree(string);
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.
355 void TakeMotifDestination(Widget w, Time time)
357 if (((TextWidget)w)->text.motifDestOwner || ((TextWidget)w)->text.readOnly)
358 return;
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;
365 ((TextWidget)w)->text.motifDestOwner = True;
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).
379 static void modifiedCB(int pos, int nInserted, int nDeleted,
380 int nRestyled, const char *deletedText, void *cbArg)
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;
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;
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. */
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;
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.
412 static void sendSecondary(Widget w, Time time, Atom sel, int action,
413 char *actionText, int actionTextLen)
415 static Atom selInfoProp[2] = {XA_SECONDARY, XA_STRING};
416 Display *disp = XtDisplay(w);
417 selectNotifyInfo *cbInfo;
418 XtAppContext context = XtWidgetToApplicationContext((Widget)w);
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;
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? */);
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 *)XtMalloc(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);
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.
459 static void getSelectionCB(Widget w, XtPointer clientData, Atom *selType,
460 Atom *type, XtPointer value, unsigned long *length, int *format)
462 textDisp *textD = ((TextWidget)w)->text.textD;
463 int isColumnar = *(int *)clientData;
464 int cursorLineStart, cursorPos, column, row;
465 char *string;
467 /* Confirm that the returned value is of the correct type */
468 if (*type != XA_STRING || *format != 8) {
469 XtFree((char*) value);
470 return;
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 = XtMalloc(*length + 1);
477 memcpy(string, (char *)value, *length);
478 string[*length] = '\0';
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 XtFree(string);
485 XtFree((char *)value);
486 return;
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 XtFree(string);
502 /* The selection requstor is required to free the memory passed
503 to it via value */
504 XtFree((char *)value);
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.
514 static void getInsertSelectionCB(Widget w, XtPointer clientData,Atom *selType,
515 Atom *type, XtPointer value, unsigned long *length, int *format)
517 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
518 char *string;
519 int *resultFlag = (int *)clientData;
521 /* Confirm that the returned value is of the correct type */
522 if (*type != XA_STRING || *format != 8 || value == NULL) {
523 XtFree((char*) value);
524 *resultFlag = UNSUCCESSFUL_INSERT;
525 return;
528 /* Copy the string just to make space for the null character */
529 string = XtMalloc(*length + 1);
530 memcpy(string, (char *)value, *length);
531 string[*length] = '\0';
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 XtFree(string);
538 XtFree((char *)value);
539 return;
542 /* Insert it in the text widget */
543 TextInsertAtCursor(w, string, NULL, True,
544 ((TextWidget)w)->text.autoWrapPastedText);
545 XtFree(string);
546 *resultFlag = SUCCESSFUL_INSERT;
548 /* This callback is required to free the memory passed to it thru value */
549 XtFree((char *)value);
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.
559 static void getExchSelCB(Widget w, XtPointer clientData, Atom *selType,
560 Atom *type, XtPointer value, unsigned long *length, int *format)
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 XtFree((char*) value);
565 XBell(XtDisplay(w), 0);
566 BufSecondaryUnselect(((TextWidget)w)->text.textD->buffer);
567 return;
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);
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.
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
585 static Boolean convertSelectionCB(Widget w, Atom *selType, Atom *target,
586 Atom *type, XtPointer *value, unsigned long *length, int *format)
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;
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;
611 /* target is "TARGETS", return a list of targets we can handle */
612 if (*target == getAtom(display, A_TARGETS)) {
613 targets = (Atom *)XtMalloc(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;
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);
651 *type = getAtom(display, A_INSERT_SELECTION);
652 *format = 8;
653 *value = NULL;
654 *length = 0;
655 return result == SUCCESSFUL_INSERT;
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;
668 /* targets TIMESTAMP and MULTIPLE are handled by the toolkit, any
669 others are unrecognized, return False */
670 return False;
673 static void loseSelectionCB(Widget w, Atom *selType)
675 TextWidget tw = (TextWidget)w;
676 selection *sel = &tw->text.textD->buffer->primary;
677 char zeroWidth = sel->rectangular ? sel->zeroWidth : 0;
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;
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.
691 static Boolean convertSecondaryCB(Widget w, Atom *selType, Atom *target,
692 Atom *type, XtPointer *value, unsigned long *length, int *format)
694 textBuffer *buf = ((TextWidget)w)->text.textD->buffer;
696 /* target must be string */
697 if (*target != XA_STRING && *target != getAtom(XtDisplay(w), A_TEXT))
698 return False;
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;
710 static void loseSecondaryCB(Widget w, Atom *selType)
712 /* do nothing, secondary selections are transient anyhow, and it
713 will go away on its own */
717 ** Selection converter procedure used by the widget when it owns the Motif
718 ** destination, to handle INSERT_SELECTION requests.
720 static Boolean convertMotifDestCB(Widget w, Atom *selType, Atom *target,
721 Atom *type, XtPointer *value, unsigned long *length, int *format)
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;
731 /* target is "TARGETS", return a list of targets it can handle */
732 if (*target == getAtom(display, A_TARGETS)) {
733 targets = (Atom *)XtMalloc(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;
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);
767 *type = getAtom(display, A_INSERT_SELECTION);
768 *format = 8;
769 *value = NULL;
770 *length = 0;
771 return result == SUCCESSFUL_INSERT;
774 /* target TIMESTAMP is handled by the toolkit and not passed here, any
775 others are unrecognized */
776 return False;
779 static void loseMotifDestCB(Widget w, Atom *selType)
781 ((TextWidget)w)->text.motifDestOwner = False;
782 if (((TextWidget)w)->text.textD->cursorStyle == CARET_CURSOR)
783 TextDSetCursorStyle(((TextWidget)w)->text.textD, DIM_CURSOR);
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.)
796 static void selectNotifyEH(Widget w, XtPointer data, XEvent *event,
797 Boolean *continueDispatch)
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;
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;
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);
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 XtFree((char*) cbInfo->actionText);
822 XtFree((char *)cbInfo);
823 return;
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 = XtMalloc(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);
847 } else
848 fprintf(stderr, "Too much binary data\n");
849 XtFree(string);
851 BufSecondaryUnselect(buf);
852 XtDisownSelection(w, XA_SECONDARY, e->time);
853 XtFree((char *)cbInfo->actionText);
854 XtFree((char *)cbInfo);
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
862 static void selectNotifyTimerProc(XtPointer clientData, XtIntervalId *id)
864 selectNotifyInfo *cbInfo = (selectNotifyInfo *)clientData;
865 textBuffer *buf = ((TextWidget)cbInfo->widget)->text.textD->buffer;
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 XtFree((char*) cbInfo->actionText);
872 XtFree((char *)cbInfo);
876 ** Maintain a cache of interned atoms. To reference one, use the constant
877 ** from the enum, atomIndex, above.
879 static Atom getAtom(Display *display, int atomNum)
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"};
886 if (atomList[atomNum] == 0)
887 atomList[atomNum] = XInternAtom(display, atomNames[atomNum], False);
888 return atomList[atomNum];