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