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