1 /*******************************************************************************
2 *									       *
3 * misc.c -- Miscelaneous Motif convenience functions			       *
4 *									       *
5 * Copyright (C) 1999 Mark Edel						       *
6 *									       *
7 * This is free software; you can redistribute it and/or modify it under the    *
8 * terms of the GNU General Public License as published by the Free Software    *
9 * Foundation; either version 2 of the License, or (at your option) any later   *
10 * version. In addition, you may distribute version of this program linked to   *
11 * Motif or Open Motif. See README for details.                                 *
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 * July 28, 1992								       *
24 *									       *
25 * Written by Mark Edel							       *
26 *									       *
27 *******************************************************************************/
28 
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32 
33 #include "misc.h"
34 #include "DialogF.h"
35 #include "nedit_malloc.h"
36 
37 #include <stdlib.h>
38 #include <string.h>
39 #include <stdarg.h>
40 #include <ctype.h>
41 #include <stdio.h>
42 #include <time.h>
43 
44 #ifdef __unix__
45 #include <sys/time.h>
46 #include <sys/select.h>
47 #endif
48 
49 #ifdef __APPLE__
50 #ifdef __MACH__
51 #include <sys/select.h>
52 #endif
53 #endif
54 
55 #ifdef VMS
56 #include <types.h>
57 #include <unixio.h>
58 #include <file.h>
59 #endif /*VMS*/
60 
61 #include <X11/Intrinsic.h>
62 #include <X11/Xatom.h>
63 #include <X11/keysym.h>
64 #include <X11/keysymdef.h>
65 #include <Xm/Xm.h>
66 #include <Xm/Label.h>
67 #include <Xm/LabelG.h>
68 #include <Xm/ToggleB.h>
69 #include <Xm/PushB.h>
70 #include <Xm/Separator.h>
71 #include <Xm/RowColumn.h>
72 #include <Xm/CascadeB.h>
73 #include <Xm/AtomMgr.h>
74 #include <Xm/Protocols.h>
75 #include <Xm/Text.h>
76 #include <Xm/MessageB.h>
77 #include <Xm/DialogS.h>
78 #include <Xm/SelectioB.h>
79 #include <Xm/Form.h>
80 #include <Xm/FileSB.h>
81 #include <Xm/ScrolledW.h>
82 #include <Xm/PrimitiveP.h>
83 
84 #ifdef HAVE_DEBUG_H
85 #include "../debug.h"
86 #endif
87 
88 #ifndef LESSTIF_VERSION
89 extern void _XmDismissTearOff(Widget w, XtPointer call, XtPointer x);
90 #endif
91 
92 /* structure for passing history-recall data to callbacks */
93 typedef struct {
94     char ***list;
95     int *nItems;
96     int index;
97 } histInfo;
98 
99 typedef Widget (*MotifDialogCreationCall)(Widget, String, ArgList, Cardinal);
100 
101 /* Maximum size of a history-recall list.  Typically never invoked, since
102    user must first make this many entries in the text field, limited for
103    safety, to the maximum reasonable number of times user can hit up-arrow
104    before carpal tunnel syndrome sets in */
105 #define HISTORY_LIST_TRIM_TO 1000
106 #define HISTORY_LIST_MAX 2000
107 
108 /* flags to enable/disable delete key remapping and pointer centered dialogs */
109 static int RemapDeleteEnabled = True;
110 static int PointerCenteredDialogsEnabled = False;
111 
112 /* bitmap and mask for waiting (wrist-watch) cursor */
113 #define watch_x_hot 7
114 #define watch_y_hot 7
115 #define watch_width 16
116 #define watch_height 16
117 static unsigned char watch_bits[] = {
118    0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0x10, 0x08, 0x08, 0x11,
119    0x04, 0x21, 0x04, 0x21, 0xe4, 0x21, 0x04, 0x20, 0x08, 0x10, 0x10, 0x08,
120    0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07
121 };
122 #define watch_mask_width 16
123 #define watch_mask_height 16
124 static unsigned char watch_mask_bits[] = {
125    0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf8, 0x1f, 0xfc, 0x3f,
126    0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f,
127    0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f
128 };
129 
130 static void addMnemonicGrabs(Widget addTo, Widget w, int unmodified);
131 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event);
132 static void findAndActivateMnemonic(Widget w, unsigned int keycode);
133 static void addAccelGrabs(Widget topWidget, Widget w);
134 static void addAccelGrab(Widget topWidget, Widget w);
135 static int parseAccelString(Display *display, const char *string, KeySym *keysym,
136 	unsigned int *modifiers);
137 static void lockCB(Widget w, XtPointer callData, XEvent *event,
138 	Boolean *continueDispatch);
139 static int findAndActivateAccel(Widget w, unsigned int keyCode,
140 	unsigned int modifiers, XEvent *event);
141 static void removeWhiteSpace(char *string);
142 static int stripCaseCmp(const char *str1, const char *str2);
143 static void warnHandlerCB(String message);
144 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
145 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
146 	Boolean *continueDispatch);
147 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
148    Cardinal *argcount);
149 static Widget addParentVisArgsAndCall(MotifDialogCreationCall callRoutine,
150 	Widget parent, char *name, ArgList arglist, Cardinal  argcount);
151 static void scrollDownAP(Widget w, XEvent *event, String *args,
152 	Cardinal *nArgs);
153 static void scrollUpAP(Widget w, XEvent *event, String *args,
154 	Cardinal *nArgs);
155 static void pageDownAP(Widget w, XEvent *event, String *args,
156 	Cardinal *nArgs);
157 static void pageUpAP(Widget w, XEvent *event, String *args,
158 	Cardinal *nArgs);
159 static long queryDesktop(Display *display, Window window, Atom deskTopAtom);
160 static void warning(const char* mesg);
161 static void microsleep(long usecs);
162 
163 /*
164 ** Set up closeCB to be called when the user selects close from the
165 ** window menu.  The close menu item usually activates f.kill which
166 ** sends a WM_DELETE_WINDOW protocol request for the window.
167 */
AddMotifCloseCallback(Widget shell,XtCallbackProc closeCB,void * arg)168 void AddMotifCloseCallback(Widget shell, XtCallbackProc closeCB, void *arg)
169 {
170     static Atom wmpAtom, dwAtom = 0;
171     Display *display = XtDisplay(shell);
172 
173     /* deactivate the built in delete response of killing the application */
174     XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);
175 
176     /* add a delete window protocol callback instead */
177     if (dwAtom == 0) {
178     	wmpAtom = XmInternAtom(display, "WM_PROTOCOLS", FALSE);
179     	dwAtom = XmInternAtom(display, "WM_DELETE_WINDOW", FALSE);
180     }
181     XmAddProtocolCallback(shell, wmpAtom, dwAtom, closeCB, arg);
182 }
183 
184 /*
185 ** Motif still generates spurious passive grab warnings on both IBM and SGI
186 ** This routine suppresses them.
187 ** (amai, 20011121:)
188 ** And triggers an annoying message on DEC systems on alpha ->
189 ** See Xt sources, xc/lib/Xt/Error.c:DefaultMsg()):
190 **    actually for some obscure reasons they check for XtError/Warning
191 **    handlers being installed when running as a root process!
192 ** Since this handler doesn't help on non-effected systems we should only
193 ** use it if necessary.
194 */
SuppressPassiveGrabWarnings(void)195 void SuppressPassiveGrabWarnings(void)
196 {
197 #if !defined(__alpha) && !defined(__EMX__)
198     XtSetWarningHandler(warnHandlerCB);
199 #endif
200 }
201 
202 /*
203 ** This routine kludges around the problem of backspace not being mapped
204 ** correctly when Motif is used between a server with a delete key in
205 ** the traditional typewriter backspace position and a client that
206 ** expects a backspace key in that position.  Though there are three
207 ** distinct levels of key re-mapping in effect when a user is running
208 ** a Motif application, none of these is really appropriate or effective
209 ** for eliminating the delete v.s. backspace problem.  Our solution is,
210 ** sadly, to eliminate the forward delete functionality of the delete key
211 ** in favor of backwards delete for both keys.  So as not to prevent the
212 ** user or the application from applying other translation table re-mapping,
213 ** we apply re-map the key as a post-processing step, applied after widget
214 ** creation.  As a result, the re-mapping necessarily becomes embedded
215 ** throughout an application (wherever text widgets are created), and
216 ** within library routines, including the Nirvana utility library.  To
217 ** make this remapping optional, the SetDeleteRemap function provides a
218 ** way for an application to turn this functionality on and off.  It is
219 ** recommended that applications that use this routine provide an
220 ** application resource called remapDeleteKey so savvy users can get
221 ** their forward delete functionality back.
222 */
RemapDeleteKey(Widget w)223 void RemapDeleteKey(Widget w)
224 {
225     static XtTranslations table = NULL;
226     static char *translations =
227     	"~Shift~Ctrl~Meta~Alt<Key>osfDelete: delete-previous-character()\n";
228 
229     if (RemapDeleteEnabled) {
230     	if (table == NULL)
231     	    table = XtParseTranslationTable(translations);
232     	XtOverrideTranslations(w, table);
233     }
234 }
235 
SetDeleteRemap(int state)236 void SetDeleteRemap(int state)
237 {
238     RemapDeleteEnabled = state;
239 }
240 
241 
242 /*
243 ** The routine adds the passed in top-level Widget's window to our
244 ** window group.  On the first call a dummy unmapped window will
245 ** be created to be our leader.  This must not be called before the
246 ** Widget has be realized and should be called before the window is
247 ** mapped.
248 */
setWindowGroup(Widget shell)249 static void setWindowGroup(Widget shell) {
250     static int firstTime = True;
251     static Window groupLeader;
252     Display *display = XtDisplay(shell);
253     XWMHints *wmHints;
254 
255     if (firstTime) {
256     	/* Create a dummy window to be the group leader for our windows */
257         String name, class;
258     	XClassHint *classHint;
259 
260     	groupLeader = XCreateSimpleWindow(display,
261                 RootWindow(display, DefaultScreen(display)),
262 		1, 1, 1, 1, 0, 0, 0);
263 
264 	/* Set it's class hint so it will be identified correctly by the
265 	   window manager */
266     	XtGetApplicationNameAndClass(display, &name, &class);
267 	classHint = XAllocClassHint();
268 	classHint->res_name = name;
269 	classHint->res_class = class;
270 	XSetClassHint(display, groupLeader, classHint);
271 	XFree(classHint);
272 
273     	firstTime = False;
274     }
275 
276     /* Set the window group hint for this shell's window */
277     wmHints = XGetWMHints(display, XtWindow(shell));
278     wmHints->window_group = groupLeader;
279     wmHints->flags |= WindowGroupHint;
280     XSetWMHints(display, XtWindow(shell), wmHints);
281     XFree(wmHints);
282 }
283 
284 /*
285 ** This routine resolves a window manager protocol incompatibility between
286 ** the X toolkit and several popular window managers.  Using this in place
287 ** of XtRealizeWidget will realize the window in a way which allows the
288 ** affected window managers to apply their own placement strategy to the
289 ** window, as opposed to forcing the window to a specific location.
290 **
291 ** One of the hints in the WM_NORMAL_HINTS protocol, PPlacement, gets set by
292 ** the X toolkit (probably part of the Core or Shell widget) when a shell
293 ** widget is realized to the value stored in the XmNx and XmNy resources of the
294 ** Core widget.  While callers can set these values, there is no "unset" value
295 ** for these resources.  On systems which are more Motif aware, a PPosition
296 ** hint of 0,0, which is the default for XmNx and XmNy, is interpreted as,
297 ** "place this as if no hints were specified".  Unfortunately the fvwm family
298 ** of window managers, which are now some of the most popular, interpret this
299 ** as "place this window at (0,0)".  This routine intervenes between the
300 ** realizing and the mapping of the window to remove the inappropriate
301 ** PPlacement hint.
302 */
303 
RemovePPositionHint(Widget shell)304 void RemovePPositionHint(Widget shell)
305 {
306     XSizeHints *hints = XAllocSizeHints();
307     long suppliedHints;
308 
309     /* Get rid of the incorrect WMNormal hint */
310     if (XGetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints,
311     	    &suppliedHints))
312     {
313 	hints->flags &= ~PPosition;
314 	XSetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints);
315     }
316 
317     XFree(hints);
318 }
319 
RealizeWithoutForcingPosition(Widget shell)320 void RealizeWithoutForcingPosition(Widget shell)
321 {
322     Boolean mappedWhenManaged;
323 
324     /* Temporarily set value of XmNmappedWhenManaged
325        to stop the window from popping up right away */
326     XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
327     XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
328 
329     /* Realize the widget in unmapped state */
330     XtRealizeWidget(shell);
331 
332     /* Remove the hint */
333     RemovePPositionHint(shell);
334 
335     /* Set WindowGroupHint so the NEdit icons can be grouped; this
336        seems to be necessary starting with Gnome 2.0  */
337     setWindowGroup(shell);
338 
339     /* Map the widget */
340     XtMapWidget(shell);
341 
342     /* Restore the value of XmNmappedWhenManaged */
343     XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
344 }
345 
346 /*
347 ** Older X applications and X servers were mostly designed to operate with
348 ** visual class PseudoColor, because older displays were at most 8 bits
349 ** deep.  Modern X servers, however, usually support 24 bit depth and other
350 ** color models.  Sun (and others?) still sets their default visual to
351 ** 8-bit PseudoColor, because some of their X applications don't work
352 ** properly with the other color models.  The problem with PseudoColor, of
353 ** course, is that users run out of colors in the default colormap, and if
354 ** they install additional colormaps for individual applications, colors
355 ** flash and change weirdly when you change your focus from one application
356 ** to another.
357 **
358 ** In addition to the poor choice of default, a design flaw in Xt makes it
359 ** impossible even for savvy users to specify the XtNvisual resource to
360 ** switch to a deeper visual.  The problem is that the colormap resource is
361 ** processed independently of the visual resource, and usually results in a
362 ** colormap for the default visual rather than for the user-selected one.
363 **
364 ** This routine should be called before creating a shell widget, to
365 ** pre-process the visual, depth, and colormap resources, and return the
366 ** proper values for these three resources to be passed to XtAppCreateShell.
367 ** Applications which actually require a particular color model (i.e. for
368 ** doing color table animation or dynamic color assignment) should not use
369 ** this routine.
370 **
371 ** Note that a consequence of using the "best" as opposed to the default
372 ** visual is that some color resources are still converted with the default
373 ** visual (particularly *background), and these must be avoided by widgets
374 ** which are allowed to handle any visual.
375 **
376 ** Returns True if the best visual is the default, False otherwise.
377 */
FindBestVisual(Display * display,const char * appName,const char * appClass,Visual ** visual,int * depth,Colormap * colormap)378 Boolean FindBestVisual(Display *display, const char *appName, const char *appClass,
379 	Visual **visual, int *depth, Colormap *colormap)
380 {
381     char rsrcName[256], rsrcClass[256], *valueString, *type, *endPtr;
382     XrmValue value;
383     int screen = DefaultScreen(display);
384     int reqDepth = -1;
385     long reqID = -1; /* should hold a 'VisualID' and a '-1' ... */
386     int reqClass = -1;
387     int installColormap = FALSE;
388     int maxDepth, bestClass, bestVisual, nVis, i, j;
389     XVisualInfo visTemplate, *visList = NULL;
390     static Visual *cachedVisual = NULL;
391     static Colormap cachedColormap;
392     static int cachedDepth = 0;
393     int bestClasses[] = {StaticGray, GrayScale, StaticColor, PseudoColor,
394     	    DirectColor, TrueColor};
395 
396     /* If results have already been computed, just return them */
397     if (cachedVisual != NULL) {
398 	*visual = cachedVisual;
399 	*depth = cachedDepth;
400 	*colormap = cachedColormap;
401 	return (*visual == DefaultVisual(display, screen));
402     }
403 
404     /* Set "Default" visual to avoid crashes with the detected best ones. */
405     reqID = DefaultVisual(display, screen)->visualid;
406 
407     /* Read the visualID and installColormap resources for the application.
408        visualID can be specified either as a number (the visual id as
409        shown by xdpyinfo), as a visual class name, or as Best or Default. */
410     sprintf(rsrcName,"%s.%s", appName, "visualID");
411     sprintf(rsrcClass, "%s.%s", appClass, "VisualID");
412     if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
413 	    &value)) {
414 	valueString = value.addr;
415 	reqID = (int)strtol(valueString, &endPtr, 0);
416 	if (endPtr == valueString) {
417 	    reqID = -1;
418 	    if (stripCaseCmp(valueString, "Default"))
419 		reqID = DefaultVisual(display, screen)->visualid;
420 	    else if (stripCaseCmp(valueString, "StaticGray"))
421 		reqClass = StaticGray;
422 	    else if (stripCaseCmp(valueString, "StaticColor"))
423 		reqClass = StaticColor;
424 	    else if (stripCaseCmp(valueString, "TrueColor"))
425 		reqClass = TrueColor;
426 	    else if (stripCaseCmp(valueString, "GrayScale"))
427 		reqClass = GrayScale;
428 	    else if (stripCaseCmp(valueString, "PseudoColor"))
429 		reqClass = PseudoColor;
430 	    else if (stripCaseCmp(valueString, "DirectColor"))
431 		reqClass = DirectColor;
432 	    else if (!stripCaseCmp(valueString, "Best"))
433 		fprintf(stderr, "Invalid visualID resource value\n");
434 	}
435     }
436     sprintf(rsrcName,"%s.%s", appName, "installColormap");
437     sprintf(rsrcClass, "%s.%s", appClass, "InstallColormap");
438     if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
439 	    &value)) {
440 	if (stripCaseCmp(value.addr, "Yes") || stripCaseCmp(value.addr, "True"))
441 	    installColormap = TRUE;
442     }
443 
444     visTemplate.screen = screen;
445 
446     /* Generate a list of visuals to consider.  (Note, vestigial code for
447        user-requested visual depth is left in, just in case that function
448        might be needed again, but it does nothing). */
449     if (reqID != -1) {
450 	visTemplate.visualid = reqID;
451 	visList = XGetVisualInfo(display, VisualScreenMask|VisualIDMask,
452                                  &visTemplate, &nVis);
453 	if (visList == NULL)
454 	    fprintf(stderr, "VisualID resource value not valid\n");
455     }
456     if (visList == NULL && reqClass != -1 && reqDepth != -1) {
457 	visTemplate.class = reqClass;
458 	visTemplate.depth = reqDepth;
459     	visList = XGetVisualInfo(display,
460 		                 VisualScreenMask| VisualClassMask | VisualDepthMask,
461                                  &visTemplate, &nVis);
462     	if (visList == NULL)
463 	    fprintf(stderr, "Visual class/depth combination not available\n");
464     }
465     if (visList == NULL && reqClass != -1) {
466  	visTemplate.class = reqClass;
467     	visList = XGetVisualInfo(display, VisualScreenMask|VisualClassMask,
468                                  &visTemplate, &nVis);
469     	if (visList == NULL)
470 	    fprintf(stderr,
471 		    "Visual Class from resource \"visualID\" not available\n");
472     }
473     if (visList == NULL && reqDepth != -1) {
474 	visTemplate.depth = reqDepth;
475     	visList = XGetVisualInfo(display, VisualScreenMask|VisualDepthMask,
476                                  &visTemplate, &nVis);
477     	if (visList == NULL)
478 	    fprintf(stderr, "Requested visual depth not available\n");
479     }
480     if (visList == NULL) {
481 	visList = XGetVisualInfo(display, VisualScreenMask, &visTemplate, &nVis);
482 	if (visList == NULL) {
483 	    fprintf(stderr, "Internal Error: no visuals available?\n");
484 	    *visual = DefaultVisual(display, screen);
485 	    *depth =  DefaultDepth(display, screen);
486     	    *colormap = DefaultColormap(display, screen);
487     	    return True;
488 	}
489     }
490 
491     /* Choose among the visuals in the candidate list.  Prefer maximum
492        depth first then matching default, then largest value of bestClass
493        (I'm not sure whether we actually care about class) */
494     maxDepth = 0;
495     bestClass = 0;
496     bestVisual = 0;
497     for (i=0; i < nVis; i++) {
498         /* X.Org 6.8+ 32-bit visuals (with alpha-channel) cause a lot of
499            problems, so we have to skip them. We already try this by setting
500            the environment variable XLIB_SKIP_ARGB_VISUALS at startup (in
501            nedit.c), but that doesn't cover the case where NEdit is running on
502            a host that doesn't use the X.Org X libraries but is displaying
503            remotely on an X.Org server. Therefore, this additional check is
504            added.
505            Note that this check in itself is not sufficient. There have been
506            bug reports that seemed to indicate that non-32-bit visuals with an
507            alpha-channel exist. The combined approach (env. var. + 32-bit
508            check) should cover the vast majority of the cases, though. */
509 	if (visList[i].depth >= 32 &&
510 	    strstr(ServerVendor(display), "X.Org") != 0) {
511 	    continue;
512 	}
513 	if (visList[i].depth > maxDepth) {
514 	    maxDepth = visList[i].depth;
515 	    bestClass = 0;
516 	    bestVisual = i;
517 	}
518 	if (visList[i].depth == maxDepth) {
519 	    if (visList[i].visual == DefaultVisual(display, screen))
520 		bestVisual = i;
521 	    if (visList[bestVisual].visual != DefaultVisual(display, screen)) {
522 		for (j = 0; j < (int)XtNumber(bestClasses); j++) {
523 		    if (visList[i].class == bestClasses[j] && j > bestClass) {
524 			bestClass = j;
525 			bestVisual = i;
526 		    }
527 		}
528 	    }
529 	}
530     }
531     *visual = cachedVisual = visList[bestVisual].visual;
532     *depth = cachedDepth = visList[bestVisual].depth;
533 
534     /* If the chosen visual is not the default, it needs a colormap allocated */
535     if (*visual == DefaultVisual(display, screen) && !installColormap)
536 	*colormap = cachedColormap = DefaultColormap(display, screen);
537     else {
538 	*colormap = cachedColormap = XCreateColormap(display,
539 		RootWindow(display, screen), cachedVisual, AllocNone);
540 	XInstallColormap(display, cachedColormap);
541     }
542     /* printf("Chose visual with depth %d, class %d, colormap %ld, id 0x%x\n",
543 	    visList[bestVisual].depth, visList[bestVisual].class,
544 	    *colormap, cachedVisual->visualid); */
545     /* Fix memory leak */
546     if (visList != NULL) {
547        XFree(visList);
548     }
549 
550     return (*visual == DefaultVisual(display, screen));
551 }
552 
553 /*
554 ** If you want to use a non-default visual with Motif, shells all have to be
555 ** created with that visual, depth, and colormap, even if the parent has them
556 ** set up properly. Substituting these routines, will append visual args copied
557 ** from the parent widget (CreatePopupMenu and CreatePulldownMenu), or from the
558 ** best visual, obtained via FindBestVisual above (CreateShellWithBestVis).
559 */
CreateDialogShell(Widget parent,char * name,ArgList arglist,Cardinal argcount)560 Widget CreateDialogShell(Widget parent, char *name,
561 	ArgList arglist, Cardinal  argcount)
562 {
563     return addParentVisArgsAndCall(XmCreateDialogShell, parent, name, arglist,
564 	    argcount);
565 }
566 
567 
CreatePopupMenu(Widget parent,char * name,ArgList arglist,Cardinal argcount)568 Widget CreatePopupMenu(Widget parent, char *name, ArgList arglist,
569 	Cardinal argcount)
570 {
571     return addParentVisArgsAndCall(XmCreatePopupMenu, parent, name,
572 	    arglist, argcount);
573 }
574 
575 
CreatePulldownMenu(Widget parent,char * name,ArgList arglist,Cardinal argcount)576 Widget CreatePulldownMenu(Widget parent, char *name,
577 	ArgList arglist, Cardinal  argcount)
578 {
579     return addParentVisArgsAndCall(XmCreatePulldownMenu, parent, name, arglist,
580 	    argcount);
581 }
582 
583 
CreatePromptDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)584 Widget CreatePromptDialog(Widget parent, char *name,
585 	ArgList arglist, Cardinal  argcount)
586 {
587     return addParentVisArgsAndCall(XmCreatePromptDialog, parent, name, arglist,
588 	    argcount);
589 }
590 
591 
CreateSelectionDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)592 Widget CreateSelectionDialog(Widget parent, char *name,
593 	ArgList arglist, Cardinal  argcount)
594 {
595     Widget dialog = addParentVisArgsAndCall(XmCreateSelectionDialog, parent, name,
596 	    arglist, argcount);
597     AddMouseWheelSupport(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST));
598     return dialog;
599 }
600 
601 
CreateFormDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)602 Widget CreateFormDialog(Widget parent, char *name,
603 	ArgList arglist, Cardinal  argcount)
604 {
605     return addParentVisArgsAndCall(XmCreateFormDialog, parent, name, arglist,
606 	    argcount);
607 }
608 
609 
CreateFileSelectionDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)610 Widget CreateFileSelectionDialog(Widget parent, char *name,
611 	ArgList arglist, Cardinal  argcount)
612 {
613     Widget dialog = addParentVisArgsAndCall(XmCreateFileSelectionDialog, parent,
614             name, arglist, argcount);
615 
616     AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_LIST));
617     AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_DIR_LIST));
618     return dialog;
619 }
620 
621 
CreateQuestionDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)622 Widget CreateQuestionDialog(Widget parent, char *name,
623 	ArgList arglist, Cardinal  argcount)
624 {
625     return addParentVisArgsAndCall(XmCreateQuestionDialog, parent, name,
626 	    arglist, argcount);
627 }
628 
629 
CreateMessageDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)630 Widget CreateMessageDialog(Widget parent, char *name,
631 	ArgList arglist, Cardinal  argcount)
632 {
633     return addParentVisArgsAndCall(XmCreateMessageDialog, parent, name,
634 	    arglist, argcount);
635 }
636 
637 
CreateErrorDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)638 Widget CreateErrorDialog(Widget parent, char *name,
639 	ArgList arglist, Cardinal  argcount)
640 {
641     return addParentVisArgsAndCall(XmCreateErrorDialog, parent, name, arglist,
642 	    argcount);
643 }
644 
CreateWidget(Widget parent,const char * name,WidgetClass class,ArgList arglist,Cardinal argcount)645 Widget CreateWidget(Widget parent, const char *name, WidgetClass class,
646 	ArgList arglist, Cardinal  argcount)
647 {
648     Widget result;
649     ArgList al = addParentVisArgs(parent, arglist, &argcount);
650     result = XtCreateWidget(name, class, parent, al, argcount);
651     NEditFree((char *)al);
652     return result;
653 }
654 
CreateShellWithBestVis(String appName,String appClass,WidgetClass class,Display * display,ArgList args,Cardinal nArgs)655 Widget CreateShellWithBestVis(String appName, String appClass,
656 	   WidgetClass class, Display *display, ArgList args, Cardinal nArgs)
657 {
658     Visual *visual;
659     int depth;
660     Colormap colormap;
661     ArgList al;
662     Cardinal ac = nArgs;
663     Widget result;
664 
665     FindBestVisual(display, appName, appClass, &visual, &depth, &colormap);
666     al = (ArgList)NEditMalloc(sizeof(Arg) * (nArgs + 3));
667     if (nArgs != 0)
668     	memcpy(al, args, sizeof(Arg) * nArgs);
669     XtSetArg(al[ac], XtNvisual, visual); ac++;
670     XtSetArg(al[ac], XtNdepth, depth); ac++;
671     XtSetArg(al[ac], XtNcolormap, colormap); ac++;
672     result = XtAppCreateShell(appName, appClass, class, display, al, ac);
673     NEditFree((char *)al);
674     return result;
675 }
676 
677 
CreatePopupShellWithBestVis(String shellName,WidgetClass class,Widget parent,ArgList arglist,Cardinal argcount)678 Widget CreatePopupShellWithBestVis(String shellName, WidgetClass class,
679     Widget parent, ArgList arglist, Cardinal argcount)
680 {
681    Widget result;
682    ArgList al = addParentVisArgs(parent, arglist, &argcount);
683    result = XtCreatePopupShell(shellName, class, parent, al, argcount);
684    NEditFree((char *)al);
685    return result;
686 }
687 
688 /*
689 ** Extends an argument list for widget creation with additional arguments
690 ** for visual, colormap, and depth. The original argument list is not altered
691 ** and it's the caller's responsability to free the returned list.
692 */
addParentVisArgs(Widget parent,ArgList arglist,Cardinal * argcount)693 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
694    Cardinal *argcount)
695 {
696     Visual *visual;
697     int depth;
698     Colormap colormap;
699     ArgList al;
700     Widget parentShell = parent;
701 
702     /* Find the application/dialog/menu shell at the top of the widget
703        hierarchy, which has the visual resource being used */
704     while (True) {
705     	if (XtIsShell(parentShell))
706     	    break;
707     	if (parentShell == NULL) {
708 	    fprintf(stderr, "failed to find shell\n");
709 	    exit(EXIT_FAILURE);
710 	}
711     	parentShell = XtParent(parentShell);
712     }
713 
714     /* Add the visual, depth, and colormap resources to the argument list */
715     XtVaGetValues(parentShell, XtNvisual, &visual, XtNdepth, &depth,
716 	    XtNcolormap, &colormap, NULL);
717     al = (ArgList)NEditMalloc(sizeof(Arg) * ((*argcount) + 3));
718     if ((*argcount) != 0)
719     	memcpy(al, arglist, sizeof(Arg) * (*argcount));
720 
721     /* For non-Lesstif versions, the visual, depth, and colormap are now set
722        globally via the resource database. So strictly spoken, it is no
723        longer necessary to set them explicitly for every shell widget.
724 
725        For Lesstif, however, this doesn't work. Luckily, Lesstif handles
726        non-default visuals etc. properly for its own shells and
727        we can take care of things for our shells (eg, call tips) here. */
728     XtSetArg(al[*argcount], XtNvisual, visual); (*argcount)++;
729     XtSetArg(al[*argcount], XtNdepth, depth); (*argcount)++;
730     XtSetArg(al[*argcount], XtNcolormap, colormap); (*argcount)++;
731     return al;
732 }
733 
734 
735 /*
736 ** Calls one of the Motif widget creation routines, splicing in additional
737 ** arguments for visual, colormap, and depth.
738 */
addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,Widget parent,char * name,ArgList arglist,Cardinal argcount)739 static Widget addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,
740 	Widget parent, char *name, ArgList arglist, Cardinal argcount)
741 {
742     Widget result;
743     ArgList al = addParentVisArgs(parent, arglist, &argcount);
744     result = (*createRoutine)(parent, name, al, argcount);
745     NEditFree((char *)al);
746     return result;
747 }
748 
749 /*
750 ** ManageDialogCenteredOnPointer is used in place of XtManageChild for
751 ** popping up a dialog to enable the dialog to be centered under the
752 ** mouse pointer.  Whether it pops up the dialog centered under the pointer
753 ** or in its default position centered over the parent widget, depends on
754 ** the value set in the SetPointerCenteredDialogs call.
755 ** Additionally, this function constrains the size of the dialog to the
756 ** screen's size, to avoid insanely wide dialogs with obscured buttons.
757 */
ManageDialogCenteredOnPointer(Widget dialogChild)758 void ManageDialogCenteredOnPointer(Widget dialogChild)
759 {
760     Widget shell = XtParent(dialogChild);
761     Window root, child;
762     unsigned int mask;
763     unsigned int width, height, borderWidth, depth;
764     int x, y, winX, winY, maxX, maxY, maxWidth, maxHeight;
765     Dimension xtWidth, xtHeight;
766     Boolean mappedWhenManaged;
767     static const int slop = 25;
768 
769     /* Temporarily set value of XmNmappedWhenManaged
770        to stop the dialog from popping up right away */
771     XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
772     XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
773 
774     /* Ensure that the dialog doesn't get wider/taller than the screen.
775        We use a hard-coded "slop" size because we don't know the border
776        width until it's on screen, and by then it's too late.  Putting
777        this before managing the widgets allows it to get the geometry
778        right on the first pass and also prevents the user from
779        accidentally resizing too wide. */
780     maxWidth = XtScreen(shell)->width - slop;
781     maxHeight = XtScreen(shell)->height - slop;
782 
783     XtVaSetValues(shell,
784                   XmNmaxWidth, maxWidth,
785                   XmNmaxHeight, maxHeight,
786                   NULL);
787 
788     /* Manage the dialog */
789     XtManageChild(dialogChild);
790 
791     /* Check to see if the window manager doesn't respect XmNmaxWidth
792        and XmNmaxHeight on the first geometry pass (sawfish, twm, fvwm).
793        For this to work XmNresizePolicy must be XmRESIZE_NONE, otherwise
794        the dialog will try to expand anyway. */
795     XtVaGetValues(shell, XmNwidth, &xtWidth, XmNheight, &xtHeight, NULL);
796     if (xtWidth > maxWidth)
797         XtVaSetValues(shell, XmNwidth, (Dimension) maxWidth, NULL);
798     if (xtHeight > maxHeight)
799         XtVaSetValues(shell, XmNheight, (Dimension) maxHeight, NULL);
800 
801     /* Only set the x/y position if the centering option is enabled.
802        Avoid getting the coordinates if not so, to save a few round-trips
803        to the server. */
804     if (PointerCenteredDialogsEnabled) {
805         /* Get the pointer position (x, y) */
806         XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child,
807 	        &x, &y, &winX, &winY, &mask);
808 
809         /* Translate the pointer position (x, y) into a position for the new
810            window that will place the pointer at its center */
811         XGetGeometry(XtDisplay(shell), XtWindow(shell), &root, &winX, &winY,
812     	        &width, &height, &borderWidth, &depth);
813         width += 2 * borderWidth;
814         height += 2 * borderWidth;
815 
816 	x -= width/2;
817 	y -= height/2;
818 
819 	/* Ensure that the dialog remains on screen */
820 	maxX = maxWidth - width;
821 	maxY = maxHeight - height;
822 	if (x > maxX) x = maxX;
823 	if (x < 0) x = 0;
824 	if (y > maxY) y = maxY;
825 	if (y < 0) y = 0;
826 
827         /* Some window managers (Sawfish) don't appear to respond
828            to the geometry set call in synchronous mode.  This causes
829            the window to delay XmNwmTimeout (default 5 seconds) before
830            posting, and it is very annoying.  See "man VendorShell" for
831            more info. */
832         XtVaSetValues(shell, XmNuseAsyncGeometry, True, NULL);
833 
834         /* Set desired window position in the DialogShell */
835         XtVaSetValues(shell, XmNx, x, XmNy, y, NULL);
836     }
837 
838     /* Map the widget */
839     XtMapWidget(shell);
840 
841     /* Restore the value of XmNmappedWhenManaged */
842     XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
843 }
844 
845 /*
846 ** Cause dialogs created by libNUtil.a routines (such as DialogF),
847 ** and dialogs which use ManageDialogCenteredOnPointer to pop up
848 ** over the pointer (state = True), or pop up in their default
849 ** positions (state = False)
850 */
SetPointerCenteredDialogs(int state)851 void SetPointerCenteredDialogs(int state)
852 {
853     PointerCenteredDialogsEnabled = state;
854 }
855 
856 
857 /*
858 ** Raise a window to the top and give it the input focus.  Setting input focus
859 ** is important on systems which use explict (rather than pointer) focus.
860 **
861 ** The X alternatives XMapRaised, and XSetInputFocus both have problems.
862 ** XMapRaised only gives the window the focus if it was initially not visible,
863 ** and XSetInputFocus sets the input focus, but crashes if the window is not
864 ** visible.
865 **
866 ** This routine should also be used in the case where a dialog is popped up and
867 ** subsequent calls to the dialog function use a flag, or the XtIsManaged, to
868 ** decide whether to create a new instance of the dialog, because on slower
869 ** systems, events can intervene while a dialog is still on its way up and its
870 ** window is still invisible, causing a subtle crash potential if
871 ** XSetInputFocus is used.
872 */
RaiseDialogWindow(Widget shell)873 void RaiseDialogWindow(Widget shell)
874 {
875     RaiseWindow(XtDisplay(shell), XtWindow(shell), True);
876 }
877 
RaiseShellWindow(Widget shell,Boolean focus)878 void RaiseShellWindow(Widget shell, Boolean focus)
879 {
880     RaiseWindow(XtDisplay(shell), XtWindow(shell), focus);
881 }
882 
RaiseWindow(Display * display,Window w,Boolean focus)883 void RaiseWindow(Display *display, Window w, Boolean focus)
884 {
885     if (focus) {
886         XWindowAttributes winAttr;
887 
888         XGetWindowAttributes(display, w, &winAttr);
889         if (winAttr.map_state == IsViewable)
890             XSetInputFocus(display, w, RevertToParent, CurrentTime);
891     }
892 
893     WmClientMsg(display, w, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
894     XMapRaised(display, w);
895 }
896 
897 /*
898 ** Add a handler for mnemonics in a dialog (Motif currently only handles
899 ** mnemonics in menus) following the example of M.S. Windows.  To add
900 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
901 ** a menu, on push buttons or toggle buttons, and call this function
902 ** when the dialog is fully constructed.  Mnemonics added or changed
903 ** after this call will not be noticed.  To add a mnemonic to a text field
904 ** or list, set the XmNmnemonic resource on the appropriate label and set
905 ** the XmNuserData resource of the label to the widget to get the focus
906 ** when the mnemonic is typed.
907 */
AddDialogMnemonicHandler(Widget dialog,int unmodifiedToo)908 void AddDialogMnemonicHandler(Widget dialog, int unmodifiedToo)
909 {
910     XtAddEventHandler(dialog, KeyPressMask, False,
911     	    (XtEventHandler)mnemonicCB, (XtPointer)0);
912     addMnemonicGrabs(dialog, dialog, unmodifiedToo);
913 }
914 
915 /*
916 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
917 */
RemoveDialogMnemonicHandler(Widget dialog)918 void RemoveDialogMnemonicHandler(Widget dialog)
919 {
920     XtUngrabKey(dialog, AnyKey, Mod1Mask);
921     XtRemoveEventHandler(dialog, KeyPressMask, False,
922     	    (XtEventHandler)mnemonicCB, (XtPointer)0);
923 }
924 
925 /*
926 ** Patch around Motif's poor handling of menu accelerator keys.  Motif
927 ** does not process menu accelerators when the caps lock or num lock
928 ** keys are engaged.  To enable accelerators in these cases, call this
929 ** routine with the completed menu bar widget as "topMenuContainer", and
930 ** the top level shell widget as "topWidget".  It will add key grabs for
931 ** all of the accelerators it finds in the topMenuContainer menu tree, and
932 ** an event handler which can process dropped accelerator events by (again)
933 ** traversing the menu tree looking for matching accelerators, and invoking
934 ** the appropriate button actions.  Any dynamic additions to the menus
935 ** require a call to UpdateAccelLockPatch to add the additional grabs.
936 ** Unfortunately, these grabs can not be removed.
937 */
AccelLockBugPatch(Widget topWidget,Widget topMenuContainer)938 void AccelLockBugPatch(Widget topWidget, Widget topMenuContainer)
939 {
940     XtAddEventHandler(topWidget, KeyPressMask, False, lockCB, topMenuContainer);
941     addAccelGrabs(topWidget, topMenuContainer);
942 }
943 
944 /*
945 ** Add additional key grabs for new menu items added to the menus, for
946 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
947 ** the same widget passed in the original call to AccelLockBugPatch.
948 */
UpdateAccelLockPatch(Widget topWidget,Widget newButton)949 void UpdateAccelLockPatch(Widget topWidget, Widget newButton)
950 {
951     addAccelGrab(topWidget, newButton);
952 }
953 
954 /*
955 ** PopDownBugPatch
956 **
957 ** Under some circumstances, popping down a dialog and its parent in
958 ** rapid succession causes a crash.  This routine delays and
959 ** processs events until receiving a ReparentNotify event.
960 ** (I have no idea why a ReparentNotify event occurs at all, but it does
961 ** mark the point where it is safe to destroy or pop down the parent, and
962 ** it might have something to do with the bug.)  There is a failsafe in
963 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
964 ** Use this sparingly, only when real crashes are observed, and periodically
965 ** check to make sure that it is still necessary.
966 */
PopDownBugPatch(Widget w)967 void PopDownBugPatch(Widget w)
968 {
969     time_t stopTime;
970 
971     stopTime = time(NULL) + 1;
972     while (time(NULL) <= stopTime) {
973     	XEvent event;
974     	XtAppContext context = XtWidgetToApplicationContext(w);
975     	XtAppPeekEvent(context, &event);
976     	if (event.xany.type == ReparentNotify)
977     	    return;
978     	XtAppProcessEvent(context, XtIMAll);
979     }
980 }
981 
982 /*
983 ** Convert a compound string to a C style null terminated string.
984 ** Returned string must be freed by the caller.
985 */
GetXmStringText(XmString fromString)986 char *GetXmStringText(XmString fromString)
987 {
988     XmStringContext context;
989     char *text, *toPtr, *toString, *fromPtr;
990     XmStringCharSet charset;
991     XmStringDirection direction;
992     Boolean separator;
993 
994     /* Malloc a buffer large enough to hold the string.  XmStringLength
995        should always be slightly longer than necessary, but won't be
996        shorter than the equivalent null-terminated string */
997     toString = (char*)NEditMalloc(XmStringLength(fromString));
998 
999     /* loop over all of the segments in the string, copying each segment
1000        into the output string and converting separators into newlines */
1001     XmStringInitContext(&context, fromString);
1002     toPtr = toString;
1003     while (XmStringGetNextSegment(context, &text,
1004     	    &charset, &direction, &separator)) {
1005     	for (fromPtr=text; *fromPtr!='\0'; fromPtr++)
1006     	    *toPtr++ = *fromPtr;
1007     	if (separator)
1008     	    *toPtr++ = '\n';
1009 	NEditFree(text);
1010 	NEditFree(charset);
1011     }
1012 
1013     /* terminate the string, free the context, and return the string */
1014     *toPtr++ = '\0';
1015     XmStringFreeContext(context);
1016     return toString;
1017 }
1018 
1019 /*
1020 ** Get the XFontStruct that corresponds to the default (first) font in
1021 ** a Motif font list.  Since Motif stores this, it saves us from storing
1022 ** it or querying it from the X server.
1023 */
GetDefaultFontStruct(Display * d,XmFontList font)1024 XFontStruct *GetDefaultFontStruct(Display *d, XmFontList font)
1025 {
1026     XFontStruct *fs;
1027     XmFontContext context;
1028     XmStringCharSet charset;
1029 
1030     XmFontListInitFontContext(&context, font);
1031     XmFontListGetNextFont(context, &charset, &fs);
1032     XmFontListFreeFontContext(context);
1033     NEditFree(charset);
1034 
1035     /* FontList might be a render table with no only XFT fonts */
1036     if (fs == NULL) {
1037         fs = XLoadQueryFont(d, "fixed");
1038     }
1039 
1040     if (fs == NULL) {
1041         fprintf(stderr, "Unabled to load any fallback fonts.\n");
1042         exit(EXIT_FAILURE);
1043     }
1044 
1045     return fs;
1046 }
1047 
1048 /*
1049 ** Create a string table suitable for passing to XmList widgets
1050 */
StringTable(int count,...)1051 XmString* StringTable(int count, ... )
1052 {
1053     va_list ap;
1054     XmString *array;
1055     int i;
1056     char *str;
1057 
1058     va_start(ap, count);
1059     array = (XmString*)NEditMalloc((count+1) * sizeof(XmString));
1060     for(i = 0;  i < count; i++ ) {
1061     	str = va_arg(ap, char *);
1062 	array[i] = XmStringCreateSimple(str);
1063     }
1064     array[i] = (XmString)0;
1065     va_end(ap);
1066     return(array);
1067 }
1068 
FreeStringTable(XmString * table)1069 void FreeStringTable(XmString *table)
1070 {
1071     int i;
1072 
1073     for(i = 0; table[i] != 0; i++)
1074 	XmStringFree(table[i]);
1075     NEditFree((char *)table);
1076 }
1077 
1078 /*
1079 ** Simulate a button press.  The purpose of this routine is show users what
1080 ** is happening when they take an action with a non-obvious side effect,
1081 ** such as when a user double clicks on a list item.  The argument is an
1082 ** XmPushButton widget to "press"
1083 */
SimulateButtonPress(Widget widget)1084 void SimulateButtonPress(Widget widget)
1085 {
1086     XEvent keyEvent;
1087 
1088     memset((char *)&keyEvent, 0, sizeof(XKeyPressedEvent));
1089     keyEvent.type = KeyPress;
1090     keyEvent.xkey.serial = 1;
1091     keyEvent.xkey.send_event = True;
1092 
1093     if (XtIsSubclass(widget, xmGadgetClass))
1094     {
1095         /* On some Motif implementations, asking a gadget for its
1096            window will crash, rather than return the window of its
1097            parent. */
1098         Widget parent = XtParent(widget);
1099         keyEvent.xkey.display = XtDisplay(parent);
1100         keyEvent.xkey.window = XtWindow(parent);
1101 
1102         XtCallActionProc(parent, "ManagerGadgetSelect",
1103                          &keyEvent, NULL, 0);
1104     }
1105     else
1106     {
1107         keyEvent.xkey.display = XtDisplay(widget);
1108         keyEvent.xkey.window = XtWindow(widget);
1109 
1110         XtCallActionProc(widget, "ArmAndActivate", &keyEvent, NULL, 0);
1111     }
1112 }
1113 
1114 /*
1115 ** Add an item to an already established pull-down or pop-up menu, including
1116 ** mnemonics, accelerators and callbacks.
1117 */
AddMenuItem(Widget parent,char * name,char * label,char mnemonic,char * acc,char * accText,XtCallbackProc callback,void * cbArg)1118 Widget AddMenuItem(Widget parent, char *name, char *label,
1119 			  char mnemonic, char *acc, char *accText,
1120 			  XtCallbackProc callback, void *cbArg)
1121 {
1122     Widget button;
1123     XmString st1, st2;
1124 
1125     button = XtVaCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
1126     	XmNlabelString, st1=XmStringCreateSimple(label),
1127     	XmNmnemonic, mnemonic,
1128     	XmNacceleratorText, st2=XmStringCreateSimple(accText),
1129     	XmNaccelerator, acc, NULL);
1130     XtAddCallback(button, XmNactivateCallback, callback, cbArg);
1131     XmStringFree(st1);
1132     XmStringFree(st2);
1133     return button;
1134 }
1135 
1136 /*
1137 ** Add a toggle button item to an already established pull-down or pop-up
1138 ** menu, including mnemonics, accelerators and callbacks.
1139 */
AddMenuToggle(Widget parent,char * name,char * label,char mnemonic,char * acc,char * accText,XtCallbackProc callback,void * cbArg,int set)1140 Widget AddMenuToggle(Widget parent, char *name, char *label,
1141 		 	    char mnemonic, char *acc, char *accText,
1142 		  	    XtCallbackProc callback, void *cbArg, int set)
1143 {
1144     Widget button;
1145     XmString st1, st2;
1146 
1147     button = XtVaCreateManagedWidget(name, xmToggleButtonWidgetClass, parent,
1148     	XmNlabelString, st1=XmStringCreateSimple(label),
1149     	XmNmnemonic, mnemonic,
1150     	XmNacceleratorText, st2=XmStringCreateSimple(accText),
1151     	XmNaccelerator, acc,
1152     	XmNset, set, NULL);
1153     XtAddCallback(button, XmNvalueChangedCallback, callback, cbArg);
1154     XmStringFree(st1);
1155     XmStringFree(st2);
1156     return button;
1157 }
1158 
1159 /*
1160 ** Add a sub-menu to an established pull-down or pop-up menu, including
1161 ** mnemonics, accelerators and callbacks.  Returns the menu pane of the
1162 ** new sub menu.
1163 */
AddSubMenu(Widget parent,char * name,char * label,char mnemonic)1164 Widget AddSubMenu(Widget parent, char *name, char *label, char mnemonic)
1165 {
1166     Widget menu;
1167     XmString st1;
1168 
1169     menu = CreatePulldownMenu(parent, name, NULL, 0);
1170     XtVaCreateManagedWidget(name, xmCascadeButtonWidgetClass, parent,
1171     	XmNlabelString, st1=XmStringCreateSimple(label),
1172     	XmNmnemonic, mnemonic,
1173     	XmNsubMenuId, menu, NULL);
1174     XmStringFree(st1);
1175     return menu;
1176 }
1177 
1178 /*
1179 ** SetIntText
1180 **
1181 ** Set the text of a motif label to show an integer
1182 */
SetIntText(Widget text,int value)1183 void SetIntText(Widget text, int value)
1184 {
1185     char labelString[20];
1186 
1187     sprintf(labelString, "%d", value);
1188     XmTextSetString(text, labelString);
1189 }
1190 
1191 /*
1192 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
1193 **
1194 ** Get the text of a motif text widget as an integer or floating point number.
1195 ** The functions will return TEXT_READ_OK of the value was read correctly.
1196 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER.  The
1197 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
1198 ** user that the value could not be read.  The argument fieldName is used
1199 ** in the dialog to help the user identify where the problem is.  Set
1200 ** warnBlank to true if a blank field is also considered an error.
1201 */
GetFloatText(Widget text,double * value)1202 int GetFloatText(Widget text, double *value)
1203 {
1204     char *strValue, *endPtr;
1205     int retVal;
1206 
1207     strValue = XmTextGetString(text);	/* Get Value */
1208     removeWhiteSpace(strValue);		/* Remove blanks and tabs */
1209     *value = strtod(strValue, &endPtr);	/* Convert string to double */
1210     if (strlen(strValue) == 0)		/* String is empty */
1211 	retVal = TEXT_IS_BLANK;
1212     else if (*endPtr != '\0')		/* Whole string not parsed */
1213     	retVal = TEXT_NOT_NUMBER;
1214     else
1215 	retVal = TEXT_READ_OK;
1216     NEditFree(strValue);
1217     return retVal;
1218 }
1219 
GetIntText(Widget text,int * value)1220 int GetIntText(Widget text, int *value)
1221 {
1222     char *strValue, *endPtr;
1223     int retVal;
1224 
1225     strValue = XmTextGetString(text);		/* Get Value */
1226     removeWhiteSpace(strValue);			/* Remove blanks and tabs */
1227     *value = strtol(strValue, &endPtr, 10);	/* Convert string to long */
1228     if (strlen(strValue) == 0)			/* String is empty */
1229 	retVal = TEXT_IS_BLANK;
1230     else if (*endPtr != '\0')			/* Whole string not parsed */
1231     	retVal = TEXT_NOT_NUMBER;
1232     else
1233 	retVal = TEXT_READ_OK;
1234     NEditFree(strValue);
1235     return retVal;
1236 }
1237 
GetFloatTextWarn(Widget text,double * value,const char * fieldName,int warnBlank)1238 int GetFloatTextWarn(Widget text, double *value, const char *fieldName,
1239                      int warnBlank)
1240 {
1241     int result;
1242     char *valueStr;
1243 
1244     result = GetFloatText(text, value);
1245     if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1246     	return result;
1247     valueStr = XmTextGetString(text);
1248 
1249     if (result == TEXT_IS_BLANK)
1250     {
1251         DialogF(DF_ERR, text, 1, "Warning", "Please supply %s value", "OK",
1252                 fieldName);
1253     } else /* TEXT_NOT_NUMBER */
1254     {
1255         DialogF (DF_ERR, text, 1, "Warning", "Can't read %s value: \"%s\"",
1256                 "OK", fieldName, valueStr);
1257     }
1258 
1259     NEditFree(valueStr);
1260     return result;
1261 }
1262 
GetIntTextWarn(Widget text,int * value,const char * fieldName,int warnBlank)1263 int GetIntTextWarn(Widget text, int *value, const char *fieldName, int warnBlank)
1264 {
1265     int result;
1266     char *valueStr;
1267 
1268     result = GetIntText(text, value);
1269     if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1270     	return result;
1271     valueStr = XmTextGetString(text);
1272 
1273     if (result == TEXT_IS_BLANK)
1274     {
1275         DialogF (DF_ERR, text, 1, "Warning", "Please supply a value for %s",
1276                 "OK", fieldName);
1277     } else /* TEXT_NOT_NUMBER */
1278     {
1279         DialogF (DF_ERR, text, 1, "Warning",
1280                 "Can't read integer value \"%s\" in %s", "OK", valueStr,
1281                 fieldName);
1282     }
1283 
1284     NEditFree(valueStr);
1285     return result;
1286 }
1287 
TextWidgetIsBlank(Widget textW)1288 int TextWidgetIsBlank(Widget textW)
1289 {
1290     char *str;
1291     int retVal;
1292 
1293     str = XmTextGetString(textW);
1294     removeWhiteSpace(str);
1295     retVal = *str == '\0';
1296     NEditFree(str);
1297     return retVal;
1298 }
1299 
1300 /*
1301 ** Turn a multi-line editing text widget into a fake single line text area
1302 ** by disabling the translation for Return.  This is a way to give users
1303 ** extra space, by allowing wrapping, but still prohibiting newlines.
1304 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1305 ** the widget to be one line high).
1306 */
MakeSingleLineTextW(Widget textW)1307 void MakeSingleLineTextW(Widget textW)
1308 {
1309     static XtTranslations noReturnTable = NULL;
1310     static char *noReturnTranslations = "<Key>Return: activate()\n";
1311 
1312     if (noReturnTable == NULL)
1313     	noReturnTable = XtParseTranslationTable(noReturnTranslations);
1314     XtOverrideTranslations(textW, noReturnTable);
1315 }
1316 
1317 /*
1318 ** Add up-arrow/down-arrow recall to a single line text field.  When user
1319 ** presses up-arrow, string is cleared and recent entries are presented,
1320 ** moving to older ones as each successive up-arrow is pressed.  Down-arrow
1321 ** moves to more recent ones, final down-arrow clears the field.  Associated
1322 ** routine, AddToHistoryList, makes maintaining a history list easier.
1323 **
1324 ** Arguments are the widget, and pointers to the history list and number of
1325 ** items, which are expected to change periodically.
1326 */
AddHistoryToTextWidget(Widget textW,char *** historyList,int * nItems)1327 void AddHistoryToTextWidget(Widget textW, char ***historyList, int *nItems)
1328 {
1329     histInfo *histData;
1330 
1331     /* create a data structure for passing history info to the callbacks */
1332     histData = (histInfo *)NEditMalloc(sizeof(histInfo));
1333     histData->list = historyList;
1334     histData->nItems = nItems;
1335     histData->index = -1;
1336 
1337     /* Add an event handler for handling up/down arrow events */
1338     XtAddEventHandler(textW, KeyPressMask, False,
1339     	    (XtEventHandler)histArrowKeyEH, histData);
1340 
1341     /* Add a destroy callback for freeing history data structure */
1342     XtAddCallback(textW, XmNdestroyCallback, histDestroyCB, histData);
1343 }
1344 
histDestroyCB(Widget w,XtPointer clientData,XtPointer callData)1345 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1346 {
1347     NEditFree((char *)clientData);
1348 }
1349 
histArrowKeyEH(Widget w,XtPointer callData,XEvent * event,Boolean * continueDispatch)1350 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
1351 	Boolean *continueDispatch)
1352 {
1353     histInfo *histData = (histInfo *)callData;
1354     KeySym keysym = XLookupKeysym((XKeyEvent *)event, 0);
1355 
1356     /* only process up and down arrow keys */
1357     if (keysym != XK_Up && keysym != XK_Down)
1358     	return;
1359 
1360     /* increment or decrement the index depending on which arrow was pressed */
1361     histData->index += (keysym == XK_Up) ? 1 : -1;
1362 
1363     /* if the index is out of range, beep, fix it up, and return */
1364     if (histData->index < -1) {
1365     	histData->index = -1;
1366 	XBell(XtDisplay(w), 0);
1367     	return;
1368     }
1369     if (histData->index >= *histData->nItems) {
1370     	histData->index = *histData->nItems - 1;
1371 	XBell(XtDisplay(w), 0);
1372     	return;
1373     }
1374 
1375     /* Change the text field contents */
1376     XmTextSetString(w, histData->index == -1 ? "" :
1377 	    (*histData->list)[histData->index]);
1378 }
1379 
1380 /*
1381 ** Copies a string on to the end of history list, which may be reallocated
1382 ** to make room.  If historyList grows beyond its internally set boundary
1383 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1384 ** (HISTORY_LIST_TRIM_TO).  Before adding to the list, checks if the item
1385 ** is a duplicate of the last item.  If so, it is not added.
1386 */
AddToHistoryList(char * newItem,char *** historyList,int * nItems)1387 void AddToHistoryList(char *newItem, char ***historyList, int *nItems)
1388 {
1389     char **newList;
1390     int i;
1391 
1392     if (*nItems != 0 && !strcmp(newItem, **historyList))
1393 	return;
1394     if (*nItems == HISTORY_LIST_MAX) {
1395 	for (i=HISTORY_LIST_TRIM_TO; i<HISTORY_LIST_MAX; i++)
1396 	    NEditFree((*historyList)[i]);
1397 	*nItems = HISTORY_LIST_TRIM_TO;
1398     }
1399     newList = (char **)NEditMalloc(sizeof(char *) * (*nItems + 1));
1400     for (i=0; i < *nItems; i++)
1401 	newList[i+1] = (*historyList)[i];
1402     if (*nItems != 0 && *historyList != NULL)
1403 	NEditFree(*historyList);
1404     (*nItems)++;
1405     newList[0] = NEditStrdup(newItem);
1406     *historyList = newList;
1407 }
1408 
1409 /*
1410 ** BeginWait/EndWait
1411 **
1412 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1413 */
BeginWait(Widget topCursorWidget)1414 void BeginWait(Widget topCursorWidget)
1415 {
1416     Display *display = XtDisplay(topCursorWidget);
1417     Pixmap pixmap;
1418     Pixmap maskPixmap;
1419     XColor xcolors[2];
1420     static Cursor  waitCursor = 0;
1421 
1422     /* if the watch cursor hasn't been created yet, create it */
1423     if (!waitCursor) {
1424 	pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1425 		(char *)watch_bits, watch_width, watch_height);
1426 
1427 	maskPixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1428 		(char *)watch_mask_bits, watch_width, watch_height);
1429 
1430 	xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(display));
1431 	xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(display));
1432 
1433 	XQueryColors(display, DefaultColormapOfScreen(
1434 		DefaultScreenOfDisplay(display)), xcolors, 2);
1435 	waitCursor = XCreatePixmapCursor(display, pixmap, maskPixmap,
1436 		&xcolors[0], &xcolors[1], watch_x_hot, watch_y_hot);
1437 	XFreePixmap(display, pixmap);
1438 	XFreePixmap(display, maskPixmap);
1439     }
1440 
1441     /* display the cursor */
1442     XDefineCursor(display, XtWindow(topCursorWidget), waitCursor);
1443 }
1444 
BusyWait(Widget widget)1445 void BusyWait(Widget widget)
1446 {
1447 #ifdef __unix__
1448     static const int timeout = 100000;  /* 1/10 sec = 100 ms = 100,000 us */
1449     static struct timeval last = { 0, 0 };
1450     struct timeval current;
1451     gettimeofday(&current, NULL);
1452 
1453     if ((current.tv_sec != last.tv_sec) ||
1454         (current.tv_usec - last.tv_usec > timeout))
1455     {
1456         XmUpdateDisplay(widget);
1457         last = current;
1458     }
1459 #else
1460     static time_t last = 0;
1461     time_t current;
1462     time(&current);
1463 
1464     if (difftime(current, last) > 0)
1465     {
1466         XmUpdateDisplay(widget);
1467         last = current;
1468     }
1469 #endif
1470 }
1471 
EndWait(Widget topCursorWidget)1472 void EndWait(Widget topCursorWidget)
1473 {
1474     XUndefineCursor(XtDisplay(topCursorWidget), XtWindow(topCursorWidget));
1475 }
1476 
1477 /*
1478 ** Create an X window geometry string from width, height, x, and y values.
1479 ** This is a complement to the X routine XParseGeometry, and uses the same
1480 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1481 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1482 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1483 ** result.  Note that in a geometry string, it is not possible to supply a y
1484 ** position without an x position.  Also note that the X/YNegative flags
1485 ** mean "add a '-' and negate the value" which is kind of odd.
1486 */
CreateGeometryString(char * string,int x,int y,int width,int height,int bitmask)1487 void CreateGeometryString(char *string, int x, int y,
1488 	int width, int height, int bitmask)
1489 {
1490     char *ptr = string;
1491     int nChars;
1492 
1493     if (bitmask & WidthValue) {
1494     	sprintf(ptr, "%d%n", width, &nChars);
1495 	ptr += nChars;
1496     }
1497     if (bitmask & HeightValue) {
1498 	sprintf(ptr, "x%d%n", height, &nChars);
1499 	ptr += nChars;
1500     }
1501     if (bitmask & XValue) {
1502 	if (bitmask & XNegative)
1503     	    sprintf(ptr, "-%d%n", -x, &nChars);
1504 	else
1505     	    sprintf(ptr, "+%d%n", x, &nChars);
1506 	ptr += nChars;
1507     }
1508     if (bitmask & YValue) {
1509 	if (bitmask & YNegative)
1510     	    sprintf(ptr, "-%d%n", -y, &nChars);
1511 	else
1512     	    sprintf(ptr, "+%d%n", y, &nChars);
1513 	ptr += nChars;
1514     }
1515     *ptr = '\0';
1516 }
1517 
1518 /*
1519 ** Remove the white space (blanks and tabs) from a string
1520 */
removeWhiteSpace(char * string)1521 static void removeWhiteSpace(char *string)
1522 {
1523     char *outPtr = string;
1524 
1525     while (TRUE) {
1526     	if (*string == 0) {
1527 	    *outPtr = 0;
1528 	    return;
1529     	} else if (*string != ' ' && *string != '\t')
1530 	    *(outPtr++) = *(string++);
1531 	else
1532 	    string++;
1533     }
1534 }
1535 
1536 /*
1537 ** Compares two strings and return TRUE if the two strings
1538 ** are the same, ignoring whitespace and case differences.
1539 */
stripCaseCmp(const char * str1,const char * str2)1540 static int stripCaseCmp(const char *str1, const char *str2)
1541 {
1542     const char *c1, *c2;
1543 
1544     for (c1=str1, c2=str2; *c1!='\0' && *c2!='\0'; c1++, c2++) {
1545 	while (*c1 == ' ' || *c1 == '\t')
1546 	    c1++;
1547 	while (*c2 == ' ' || *c2 == '\t')
1548 	    c2++;
1549     	if (toupper((unsigned char)*c1) != toupper((unsigned char)*c2))
1550     	    return FALSE;
1551     }
1552     return *c1 == '\0' && *c2 == '\0';
1553 }
1554 
warnHandlerCB(String message)1555 static void warnHandlerCB(String message)
1556 {
1557     if (strstr(message, "XtRemoveGrab"))
1558     	return;
1559     if (strstr(message, "Attempt to remove non-existant passive grab"))
1560     	return;
1561     fputs(message, stderr);
1562     fputc('\n', stderr);
1563 }
1564 
getKeyboardMapping(Display * display)1565 static XModifierKeymap *getKeyboardMapping(Display *display) {
1566     static XModifierKeymap *keyboardMap = NULL;
1567 
1568     if (keyboardMap == NULL) {
1569         keyboardMap = XGetModifierMapping(display);
1570     }
1571     return(keyboardMap);
1572 }
1573 
1574 /*
1575 ** get mask for a modifier
1576 **
1577 */
1578 
findModifierMapping(Display * display,KeyCode keyCode)1579 static Modifiers findModifierMapping(Display *display, KeyCode keyCode) {
1580     int i, j;
1581     KeyCode *mapentry;
1582     XModifierKeymap *modMap = getKeyboardMapping(display);
1583 
1584     if (modMap == NULL || keyCode == 0) {
1585         return(0);
1586     }
1587 
1588     mapentry = modMap->modifiermap;
1589     for (i = 0; i < 8; ++i) {
1590         for (j = 0; j < (modMap->max_keypermod); ++j) {
1591             if (keyCode == *mapentry) {
1592                 return(1 << ((mapentry - modMap->modifiermap) / modMap->max_keypermod));
1593             }
1594             ++mapentry;
1595         }
1596     }
1597     return(0);
1598 }
1599 
GetNumLockModMask(Display * display)1600 Modifiers GetNumLockModMask(Display *display) {
1601     static int numLockMask = -1;
1602 
1603     if (numLockMask == -1) {
1604         numLockMask = findModifierMapping(display, XKeysymToKeycode(display, XK_Num_Lock));
1605     }
1606     return(numLockMask);
1607 }
1608 
1609 /*
1610 ** Grab a key regardless of caps-lock and other silly latching keys.
1611 **
1612 */
1613 
reallyGrabAKey(Widget dialog,int keyCode,Modifiers mask)1614 static void reallyGrabAKey(Widget dialog, int keyCode, Modifiers mask) {
1615     Modifiers numLockMask = GetNumLockModMask(XtDisplay(dialog));
1616 
1617     if (keyCode == 0)  /* No anykey grabs, sorry */
1618         return;
1619 
1620     XtGrabKey(dialog, keyCode, mask, True, GrabModeAsync, GrabModeAsync);
1621     XtGrabKey(dialog, keyCode, mask|LockMask, True, GrabModeAsync, GrabModeAsync);
1622     if (numLockMask && numLockMask != LockMask) {
1623         XtGrabKey(dialog, keyCode, mask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1624         XtGrabKey(dialog, keyCode, mask|LockMask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1625     }
1626 }
1627 
1628 /*
1629 ** Part of dialog mnemonic processing.  Search the widget tree under w
1630 ** for widgets with mnemonics.  When found, add a passive grab to the
1631 ** dialog widget for the mnemonic character, thus directing mnemonic
1632 ** events to the dialog widget.
1633 */
addMnemonicGrabs(Widget dialog,Widget w,int unmodifiedToo)1634 static void addMnemonicGrabs(Widget dialog, Widget w, int unmodifiedToo)
1635 {
1636     char mneString[2];
1637     WidgetList children;
1638     Cardinal numChildren;
1639     int i, isMenu;
1640     KeySym mnemonic = '\0';
1641     unsigned char rowColType;
1642     unsigned int keyCode;
1643 
1644     if (XtIsComposite(w)) {
1645 	if (XtClass(w) == xmRowColumnWidgetClass) {
1646 	    XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1647 	    isMenu = rowColType != XmWORK_AREA;
1648 	} else
1649 	    isMenu = False;
1650 	if (!isMenu) {
1651 	    XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1652 		    &numChildren, NULL);
1653 	    for (i=0; i<(int)numChildren; i++)
1654     		addMnemonicGrabs(dialog, children[i], unmodifiedToo);
1655     	}
1656     } else {
1657 	XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1658 	if (mnemonic != XK_VoidSymbol && mnemonic != '\0') {
1659 	    mneString[0] = mnemonic; mneString[1] = '\0';
1660 	    keyCode = XKeysymToKeycode(XtDisplay(dialog),
1661 	    	    XStringToKeysym(mneString));
1662             reallyGrabAKey(dialog, keyCode, Mod1Mask);
1663             if (unmodifiedToo)
1664                 reallyGrabAKey(dialog, keyCode, 0);
1665 	}
1666     }
1667 }
1668 
1669 /*
1670 ** Callback routine for dialog mnemonic processing.
1671 */
mnemonicCB(Widget w,XtPointer callData,XKeyEvent * event)1672 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event)
1673 {
1674     findAndActivateMnemonic(w, event->keycode);
1675 }
1676 
1677 /*
1678 ** Look for a widget in the widget tree w, with a mnemonic matching
1679 ** keycode.  When one is found, simulate a button press on that widget
1680 ** and give it the keyboard focus.  If the mnemonic is on a label,
1681 ** look in the userData field of the label to see if it points to
1682 ** another widget, and give that the focus.  This routine is just
1683 ** sufficient for NEdit, no doubt it will need to be extended for
1684 ** mnemonics on widgets other than just buttons and text fields.
1685 */
findAndActivateMnemonic(Widget w,unsigned int keycode)1686 static void findAndActivateMnemonic(Widget w, unsigned int keycode)
1687 {
1688     WidgetList children;
1689     Cardinal numChildren;
1690     int i, isMenu;
1691     KeySym mnemonic = '\0';
1692     char mneString[2];
1693     Widget userData;
1694     unsigned char rowColType;
1695 
1696     if (XtIsComposite(w)) {
1697 	if (XtClass(w) == xmRowColumnWidgetClass) {
1698 	    XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1699 	    isMenu = rowColType != XmWORK_AREA;
1700 	} else
1701 	    isMenu = False;
1702 	if (!isMenu) {
1703 	    XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1704 		    &numChildren, NULL);
1705 	    for (i=0; i<(int)numChildren; i++)
1706     		findAndActivateMnemonic(children[i], keycode);
1707     	}
1708     } else {
1709 	XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1710 	if (mnemonic != '\0') {
1711 	    mneString[0] = mnemonic; mneString[1] = '\0';
1712 	    if (XKeysymToKeycode(XtDisplay(XtParent(w)),
1713 	    	    XStringToKeysym(mneString)) == keycode) {
1714 	    	if (XtClass(w) == xmLabelWidgetClass ||
1715 	    		XtClass(w) == xmLabelGadgetClass) {
1716 	    	    XtVaGetValues(w, XmNuserData, &userData, NULL);
1717 	    	    if (userData!=NULL && XtIsWidget(userData) &&
1718                         XmIsTraversable(userData))
1719 	    	    	XmProcessTraversal(userData, XmTRAVERSE_CURRENT);
1720 	    	} else if (XmIsTraversable(w)) {
1721 	    	    XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1722 	    	    SimulateButtonPress(w);
1723 	    	}
1724 	    }
1725 	}
1726     }
1727 }
1728 
1729 /*
1730 ** Part of workaround for Motif Caps/Num Lock bug.  Search the widget tree
1731 ** under w for widgets with accelerators.  When found, add three passive
1732 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1733 ** Lock, one for Num Lock, and one for both, thus directing lock +
1734 ** accelerator events to topWidget.
1735 */
addAccelGrabs(Widget topWidget,Widget w)1736 static void addAccelGrabs(Widget topWidget, Widget w)
1737 {
1738     WidgetList children;
1739     Widget menu;
1740     Cardinal numChildren;
1741     int i;
1742 
1743     if (XtIsComposite(w)) {
1744 	XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1745 		&numChildren, NULL);
1746 	for (i=0; i<(int)numChildren; i++)
1747     	    addAccelGrabs(topWidget, children[i]);
1748     } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1749 	XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1750 	if (menu != NULL)
1751 	    addAccelGrabs(topWidget, menu);
1752     } else
1753 	addAccelGrab(topWidget, w);
1754 }
1755 
1756 /*
1757 ** Grabs the key + modifier defined in the widget's accelerator resource,
1758 ** in combination with the Caps Lock and Num Lock accelerators.
1759 */
addAccelGrab(Widget topWidget,Widget w)1760 static void addAccelGrab(Widget topWidget, Widget w)
1761 {
1762     char *accelString = NULL;
1763     KeySym keysym;
1764     unsigned int modifiers;
1765     KeyCode code;
1766     Modifiers numLockMask = GetNumLockModMask(XtDisplay(topWidget));
1767 
1768     XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1769     if (accelString == NULL || *accelString == '\0') {
1770         NEditFree(accelString);
1771         return;
1772     }
1773 
1774     if (!parseAccelString(XtDisplay(topWidget), accelString, &keysym, &modifiers)) {
1775         NEditFree(accelString);
1776 	return;
1777     }
1778     NEditFree(accelString);
1779 
1780     /* Check to see if this server has this key mapped.  Some cruddy PC X
1781        servers (Xoftware) have terrible default keymaps. If not,
1782        XKeysymToKeycode will return 0.  However, it's bad news to pass
1783        that to XtGrabKey because 0 is really "AnyKey" which is definitely
1784        not what we want!! */
1785 
1786     code = XKeysymToKeycode(XtDisplay(topWidget), keysym);
1787     if (code == 0)
1788         return;
1789 
1790     XtGrabKey(topWidget, code,
1791 	    modifiers | LockMask, True, GrabModeAsync, GrabModeAsync);
1792     if (numLockMask && numLockMask != LockMask) {
1793         XtGrabKey(topWidget, code,
1794 	        modifiers | numLockMask, True, GrabModeAsync, GrabModeAsync);
1795         XtGrabKey(topWidget, code,
1796 	        modifiers | LockMask | numLockMask, True, GrabModeAsync, GrabModeAsync);
1797     }
1798 }
1799 
1800 /*
1801 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1802 ** Returns TRUE if the parse was successful, FALSE, if not.
1803 */
parseAccelString(Display * display,const char * string,KeySym * keySym,unsigned int * modifiers)1804 static int parseAccelString(Display *display, const char *string, KeySym *keySym,
1805 	unsigned int *modifiers)
1806 {
1807 #define N_MODIFIERS 12
1808     /*... Is NumLock always Mod3? */
1809     static char *modifierNames[N_MODIFIERS] = {"Ctrl", "Shift", "Alt", "Mod2",
1810 	    "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1811 	    "Button5"};
1812     static unsigned int modifierMasks[N_MODIFIERS] = {ControlMask, ShiftMask,
1813 	    Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, Button1Mask, Button2Mask,
1814 	    Button3Mask, Button4Mask, Button5Mask};
1815     Modifiers numLockMask = GetNumLockModMask(display);
1816     char modStr[MAX_ACCEL_LEN];
1817     char evtStr[MAX_ACCEL_LEN];
1818     char keyStr[MAX_ACCEL_LEN];
1819     const char *c, *evtStart, *keyStart;
1820     int i;
1821 
1822     if (strlen(string) >= MAX_ACCEL_LEN)
1823 	return FALSE;
1824 
1825     /* Get the modifier part */
1826     for (c = string; *c != '<'; c++)
1827 	if (*c == '\0')
1828 	    return FALSE;
1829     strncpy(modStr, string, c - string);
1830     modStr[c - string] = '\0';
1831 
1832     /* Verify the <key> or <keypress> part */
1833     evtStart = c;
1834     for ( ; *c != '>'; c++)
1835 	if (*c == '\0')
1836 	    return FALSE;
1837     c++;
1838     strncpy(evtStr, evtStart, c - evtStart);
1839     evtStr[c - evtStart] = '\0';
1840     if (!stripCaseCmp(evtStr, "<key>") && !stripCaseCmp(evtStr, "<keypress>"))
1841 	return FALSE;
1842 
1843     /* Get the keysym part */
1844     keyStart = c;
1845     for ( ; *c != '\0' && !(c != keyStart && *c == ':'); c++);
1846     strncpy(keyStr, keyStart, c - keyStart);
1847     keyStr[c - keyStart] = '\0';
1848     *keySym = XStringToKeysym(keyStr);
1849 
1850     /* Parse the modifier part */
1851     *modifiers = 0;
1852     c = modStr;
1853     while (*c != '\0') {
1854 	while (*c == ' ' || *c == '\t')
1855 	    c++;
1856 	if (*c == '\0')
1857 	    break;
1858 	for (i = 0; i < N_MODIFIERS; i++) {
1859 	    if (!strncmp(c, modifierNames[i], strlen(modifierNames[i]))) {
1860 	    	c += strlen(modifierNames[i]);
1861                 if (modifierMasks[i] != numLockMask) {
1862 		    *modifiers |= modifierMasks[i];
1863                 }
1864 		break;
1865 	    }
1866 	}
1867 	if (i == N_MODIFIERS)
1868 	    return FALSE;
1869     }
1870 
1871     return TRUE;
1872 }
1873 
1874 /*
1875 ** Event handler for patching around Motif's lock + accelerator problem.
1876 ** Looks for a menu item in the patched menu hierarchy and invokes its
1877 ** ArmAndActivate action.
1878 */
lockCB(Widget w,XtPointer callData,XEvent * event,Boolean * continueDispatch)1879 static void lockCB(Widget w, XtPointer callData, XEvent *event,
1880 	Boolean *continueDispatch)
1881 {
1882     Modifiers numLockMask = GetNumLockModMask(XtDisplay(w));
1883     Widget topMenuWidget = (Widget)callData;
1884     *continueDispatch = TRUE;
1885 
1886     if (!(((XKeyEvent *)event)->state & (LockMask | numLockMask)))
1887 	return;
1888 
1889     if (findAndActivateAccel(topMenuWidget, ((XKeyEvent*) event)->keycode,
1890             ((XKeyEvent*) event)->state & ~(LockMask | numLockMask), event)) {
1891         *continueDispatch = FALSE;
1892     }
1893 }
1894 
1895 /*
1896 ** Search through menu hierarchy under w and look for a button with
1897 ** accelerator matching keyCode + modifiers, and do its action
1898 */
findAndActivateAccel(Widget w,unsigned int keyCode,unsigned int modifiers,XEvent * event)1899 static int findAndActivateAccel(Widget w, unsigned int keyCode,
1900 	unsigned int modifiers, XEvent *event)
1901 {
1902     WidgetList children;
1903     Widget menu;
1904     Cardinal numChildren;
1905     int i;
1906     char *accelString = NULL;
1907     KeySym keysym;
1908     unsigned int mods;
1909 
1910     if (XtIsComposite(w)) {
1911 	XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1912 		&numChildren, NULL);
1913 	for (i=0; i<(int)numChildren; i++)
1914     	    if (findAndActivateAccel(children[i], keyCode, modifiers, event))
1915 		return TRUE;
1916     } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1917 	XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1918 	if (menu != NULL)
1919 	    if (findAndActivateAccel(menu, keyCode, modifiers, event))
1920 		return TRUE;
1921     } else {
1922 	XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1923 	if (accelString != NULL && *accelString != '\0') {
1924 	    if (!parseAccelString(XtDisplay(w), accelString, &keysym, &mods))
1925 		return FALSE;
1926 	    if (keyCode == XKeysymToKeycode(XtDisplay(w), keysym) &&
1927 		    modifiers == mods) {
1928 		if (XtIsSensitive(w)) {
1929 		    XtCallActionProc(w, "ArmAndActivate", event, NULL, 0);
1930 		    return TRUE;
1931 		}
1932 	    }
1933 	}
1934     }
1935     return FALSE;
1936 }
1937 
1938 /*
1939 ** Global installation of mouse wheel actions for scrolled windows.
1940 */
InstallMouseWheelActions(XtAppContext context)1941 void InstallMouseWheelActions(XtAppContext context)
1942 {
1943     static XtActionsRec Actions[] = {
1944       	{"scrolled-window-scroll-up",   scrollUpAP},
1945       	{"scrolled-window-page-up",     pageUpAP},
1946       	{"scrolled-window-scroll-down", scrollDownAP},
1947       	{"scrolled-window-page-down",   pageDownAP}
1948     };
1949 
1950     XtAppAddActions(context, Actions, XtNumber(Actions));
1951 }
1952 
1953 /*
1954 ** Add mouse wheel support to a specific widget, which must be the scrollable
1955 ** widget of a ScrolledWindow.
1956 */
AddMouseWheelSupport(Widget w)1957 void AddMouseWheelSupport(Widget w)
1958 {
1959     if (XmIsScrolledWindow(XtParent(w)))
1960     {
1961         static const char scrollTranslations[] =
1962            "Shift<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(1)\n"
1963            "Shift<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(1)\n"
1964            "Ctrl<Btn4Down>,<Btn4Up>:  scrolled-window-page-up()\n"
1965            "Ctrl<Btn5Down>,<Btn5Up>:  scrolled-window-page-down()\n"
1966            "<Btn4Down>,<Btn4Up>:      scrolled-window-scroll-up(3)\n"
1967            "<Btn5Down>,<Btn5Up>:      scrolled-window-scroll-down(3)\n";
1968         static XtTranslations trans_table = NULL;
1969 
1970         if (trans_table == NULL)
1971         {
1972             trans_table = XtParseTranslationTable(scrollTranslations);
1973         }
1974         XtOverrideTranslations(w, trans_table);
1975     }
1976 }
1977 
pageUpAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)1978 static void pageUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
1979 {
1980     Widget scrolledWindow, scrollBar;
1981     String al[1];
1982 
1983     al[0] = "Up";
1984     scrolledWindow = XtParent(w);
1985     scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
1986     if (scrollBar)
1987         XtCallActionProc(scrollBar, "PageUpOrLeft", event, al, 1) ;
1988     return;
1989 }
1990 
pageDownAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)1991 static void pageDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
1992 {
1993     Widget scrolledWindow, scrollBar;
1994     String al[1];
1995 
1996     al[0] = "Down";
1997     scrolledWindow = XtParent(w);
1998     scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
1999     if (scrollBar)
2000         XtCallActionProc(scrollBar, "PageDownOrRight", event, al, 1) ;
2001     return;
2002 }
2003 
scrollUpAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)2004 static void scrollUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2005 {
2006     Widget scrolledWindow, scrollBar;
2007     String al[1];
2008     int i, nLines;
2009 
2010     if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2011        return;
2012     al[0] = "Up";
2013     scrolledWindow = XtParent(w);
2014     scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2015     if (scrollBar)
2016         for (i=0; i<nLines; i++)
2017             XtCallActionProc(scrollBar, "IncrementUpOrLeft", event, al, 1) ;
2018     return;
2019 }
2020 
scrollDownAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)2021 static void scrollDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2022 {
2023     Widget scrolledWindow, scrollBar;
2024     String al[1];
2025     int i, nLines;
2026 
2027     if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2028        return;
2029     al[0] = "Down";
2030     scrolledWindow = XtParent(w);
2031     scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2032     if (scrollBar)
2033         for (i=0; i<nLines; i++)
2034             XtCallActionProc(scrollBar, "IncrementDownOrRight", event, al, 1) ;
2035     return;
2036 }
2037 
2038 
2039 /*
2040 ** This is a disguisting hack to work around a bug in OpenMotif.
2041 ** OpenMotif's toggle button Select() action routine remembers the last radio
2042 ** button that was toggled (stored as global state) and refuses to take any
2043 ** action when that button is clicked again. It fails to detect that we may
2044 ** have changed the button state and that clicking that button could make
2045 ** sense. The result is that radio buttons may apparently get stuck, ie.
2046 ** it is not possible to directly select with the mouse the previously
2047 ** selected button without selection another radio button first.
2048 ** The workaround consist of faking a mouse click on the button that we
2049 ** toggled by calling the Arm, Select, and Disarm action procedures.
2050 **
2051 ** A minor remaining issue is the fact that, if the workaround is used,
2052 ** it is not possible to change the state without notifying potential
2053 ** XmNvalueChangedCallbacks. In practice, this doesn't seem to be a problem.
2054 **
2055 */
RadioButtonChangeState(Widget widget,Boolean state,Boolean notify)2056 void RadioButtonChangeState(Widget widget, Boolean state, Boolean notify)
2057 {
2058     /*
2059       The bug only exists in OpenMotif 2.1.x/2.2.[0-2]. Since it's quite hard
2060       to detect OpenMotif reliably, we make a rough cut by excluding Lesstif
2061       and all Motif versions >= 2.1.x and < 2.2.3.
2062     */
2063 #ifndef LESSTIF_VERSION
2064 #if XmVersion == 2001 || (XmVersion == 2002 && XmUPDATE_LEVEL < 3)
2065     /* save the widget with current focus in case it moves */
2066     Widget focusW, shellW = widget;
2067     while (shellW && !XtIsShell(shellW)) {
2068         shellW = XtParent(shellW);
2069     }
2070     focusW = XtGetKeyboardFocusWidget(shellW);
2071 
2072     if (state && XtIsRealized(widget))
2073     {
2074         /*
2075            Simulate a mouse button click.
2076            The event attributes that matter are the event type and the
2077            coordinates. When the button is managed, the coordinates have to
2078            be inside the button. When the button is not managed, they have to
2079            be (0, 0) to make sure that the Select routine accepts the event.
2080         */
2081         XEvent ev;
2082         if (XtIsManaged(widget))
2083         {
2084             Position x, y;
2085             /* Calculate the coordinates in the same way as OM. */
2086             XtTranslateCoords(XtParent(widget), widget->core.x, widget->core.y,
2087                               &x, &y);
2088             ev.xbutton.x_root = x + widget->core.border_width;
2089             ev.xbutton.y_root = y + widget->core.border_width;
2090         }
2091         else
2092         {
2093             ev.xbutton.x_root = 0;
2094             ev.xbutton.y_root = 0;
2095         }
2096         /* Default button bindings:
2097               ~c<Btn1Down>: Arm()
2098               ~c<Btn1Up>: Select() Disarm() */
2099         ev.xany.type = ButtonPress;
2100         XtCallActionProc(widget, "Arm", &ev, NULL, 0);
2101         ev.xany.type = ButtonRelease;
2102         XtCallActionProc(widget, "Select", &ev, NULL, 0);
2103         XtCallActionProc(widget, "Disarm", &ev, NULL, 0);
2104     }
2105     /* restore focus to the originator */
2106     if (focusW) {
2107         XtSetKeyboardFocus(shellW, focusW);
2108     }
2109 #endif /* XmVersion == 2001 || ... */
2110 #endif /* LESSTIF_VERSION */
2111 
2112     /* This is sufficient on non-OM platforms */
2113     XmToggleButtonSetState(widget, state, notify);
2114 }
2115 
2116 /* Workaround for bug in OpenMotif 2.1 and 2.2.  If you have an active tear-off
2117 ** menu from a TopLevelShell that is a child of an ApplicationShell, and then
2118 ** close the parent window, Motif crashes.  The problem doesn't
2119 ** happen if you close the tear-offs first, so, we programatically close them
2120 ** before destroying the shell widget.
2121 */
CloseAllPopupsFor(Widget shell)2122 void CloseAllPopupsFor(Widget shell)
2123 {
2124 #ifndef LESSTIF_VERSION
2125     /* Doesn't happen in LessTif.  The tear-off menus are popup-children of
2126      * of the TopLevelShell there, which just works.  Motif wants to make
2127      * them popup-children of the ApplicationShell, where it seems to get
2128      * into trouble. */
2129 
2130     Widget app = XtParent(shell);
2131     int i;
2132 
2133     for (i = 0; i < app->core.num_popups; i++) {
2134         Widget pop = app->core.popup_list[i];
2135         Widget shellFor;
2136 
2137         XtVaGetValues(pop, XtNtransientFor, &shellFor, NULL);
2138         if (shell == shellFor)
2139             _XmDismissTearOff(pop, NULL, NULL);
2140     }
2141 #endif
2142 }
2143 
queryDesktop(Display * display,Window window,Atom deskTopAtom)2144 static long queryDesktop(Display *display, Window window, Atom deskTopAtom)
2145 {
2146     long deskTopNumber = 0;
2147     Atom actualType;
2148     int actualFormat;
2149     unsigned long nItems, bytesAfter;
2150     unsigned char *prop;
2151 
2152     if (XGetWindowProperty(display, window, deskTopAtom, 0, 1,
2153            False, AnyPropertyType, &actualType, &actualFormat, &nItems,
2154            &bytesAfter, &prop) != Success) {
2155         return -1; /* Property not found */
2156     }
2157 
2158     if (actualType == None) {
2159         return -1; /* Property does not exist */
2160     }
2161 
2162     if (actualFormat != 32 || nItems != 1) {
2163         XFree((char*)prop);
2164         return -1; /* Wrong format */
2165     }
2166 
2167     deskTopNumber = *(long*)prop;
2168     XFree((char*)prop);
2169     return deskTopNumber;
2170 }
2171 
2172 /*
2173 ** Returns the current desktop number, or -1 if no desktop information
2174 ** is available.
2175 */
QueryCurrentDesktop(Display * display,Window rootWindow)2176 long QueryCurrentDesktop(Display *display, Window rootWindow)
2177 {
2178     static Atom currentDesktopAtom = (Atom)-1;
2179 
2180     if (currentDesktopAtom == (Atom)-1)
2181         currentDesktopAtom = XInternAtom(display, "_NET_CURRENT_DESKTOP", True);
2182 
2183     if (currentDesktopAtom != None)
2184         return queryDesktop(display, rootWindow, currentDesktopAtom);
2185 
2186     return -1; /* No desktop information */
2187 }
2188 
2189 /*
2190 ** Returns the number of the desktop the given shell window is currently on,
2191 ** or -1 if no desktop information is available.  Note that windows shown
2192 ** on all desktops (sometimes called sticky windows) should return 0xFFFFFFFF.
2193 */
QueryDesktop(Display * display,Widget shell)2194 long QueryDesktop(Display *display, Widget shell)
2195 {
2196     static Atom wmDesktopAtom = (Atom)-1;
2197 
2198     if (wmDesktopAtom == (Atom)-1)
2199         wmDesktopAtom = XInternAtom(display, "_NET_WM_DESKTOP", True);
2200 
2201     if (wmDesktopAtom != None)
2202         return queryDesktop(display, XtWindow(shell), wmDesktopAtom);
2203 
2204     return -1;  /* No desktop information */
2205 }
2206 
2207 
2208 /*
2209 ** Clipboard wrapper functions that call the Motif clipboard functions
2210 ** a number of times before giving up. The interfaces are similar to the
2211 ** native Motif functions.
2212 */
2213 
2214 #define SPINCOUNT  10   /* Try at most 10 times */
2215 #define USLEEPTIME 1000 /* 1 ms between retries */
2216 
2217 /*
2218 ** Warning reporting
2219 */
warning(const char * mesg)2220 static void warning(const char* mesg)
2221 {
2222   fprintf(stderr, "NEdit warning:\n%s\n", mesg);
2223 }
2224 
2225 /*
2226 ** Sleep routine
2227 */
microsleep(long usecs)2228 static void microsleep(long usecs)
2229 {
2230   static struct timeval timeoutVal;
2231   timeoutVal.tv_sec = usecs/1000000;
2232   timeoutVal.tv_usec = usecs - timeoutVal.tv_sec*1000000;
2233   select(0, NULL, NULL, NULL, &timeoutVal);
2234 }
2235 
2236 /*
2237 ** XmClipboardStartCopy spinlock wrapper.
2238 */
SpinClipboardStartCopy(Display * display,Window window,XmString clip_label,Time timestamp,Widget widget,XmCutPasteProc callback,long * item_id)2239 int SpinClipboardStartCopy(Display *display, Window window,
2240         XmString clip_label, Time timestamp, Widget widget,
2241         XmCutPasteProc callback, long *item_id)
2242 {
2243     int i, res;
2244     for (i=0; i<SPINCOUNT; ++i) {
2245         res = XmClipboardStartCopy(display, window, clip_label, timestamp,
2246                                    widget, callback, item_id);
2247         if (res == XmClipboardSuccess) {
2248             return res;
2249         }
2250         microsleep(USLEEPTIME);
2251     }
2252     warning("XmClipboardStartCopy() failed: clipboard locked.");
2253     return res;
2254 }
2255 
2256 /*
2257 ** XmClipboardCopy spinlock wrapper.
2258 */
SpinClipboardCopy(Display * display,Window window,long item_id,char * format_name,XtPointer buffer,unsigned long length,long private_id,long * data_id)2259 int SpinClipboardCopy(Display *display, Window window, long item_id,
2260         char *format_name, XtPointer buffer, unsigned long length,
2261         long private_id, long *data_id)
2262 {
2263     int i, res;
2264     for (i=0; i<SPINCOUNT; ++i) {
2265         res = XmClipboardCopy(display, window, item_id, format_name,
2266                               buffer, length, private_id, data_id);
2267         if (res == XmClipboardSuccess) {
2268             return res;
2269         }
2270         if (res == XmClipboardFail) {
2271             warning("XmClipboardCopy() failed: XmClipboardStartCopy not "
2272                     "called or too many formats.");
2273             return res;
2274         }
2275         microsleep(USLEEPTIME);
2276     }
2277     warning("XmClipboardCopy() failed: clipboard locked.");
2278     return res;
2279 }
2280 
2281 /*
2282 ** XmClipboardEndCopy spinlock wrapper.
2283 */
SpinClipboardEndCopy(Display * display,Window window,long item_id)2284 int SpinClipboardEndCopy(Display *display, Window window, long item_id)
2285 {
2286     int i, res;
2287     for (i=0; i<SPINCOUNT; ++i) {
2288         res = XmClipboardEndCopy(display, window, item_id);
2289         if (res == XmClipboardSuccess) {
2290             return res;
2291         }
2292         if (res == XmClipboardFail) {
2293             warning("XmClipboardEndCopy() failed: XmClipboardStartCopy not "
2294                     "called.");
2295             return res;
2296         }
2297         microsleep(USLEEPTIME);
2298     }
2299     warning("XmClipboardEndCopy() failed: clipboard locked.");
2300     return res;
2301 }
2302 
2303 /*
2304 ** XmClipboardInquireLength spinlock wrapper.
2305 */
SpinClipboardInquireLength(Display * display,Window window,char * format_name,unsigned long * length)2306 int SpinClipboardInquireLength(Display *display, Window window,
2307         char *format_name, unsigned long *length)
2308 {
2309     int i, res;
2310     for (i=0; i<SPINCOUNT; ++i) {
2311         res = XmClipboardInquireLength(display, window, format_name, length);
2312         if (res == XmClipboardSuccess) {
2313             return res;
2314         }
2315         if (res == XmClipboardNoData) {
2316             return res;
2317         }
2318         microsleep(USLEEPTIME);
2319     }
2320     warning("XmClipboardInquireLength() failed: clipboard locked.");
2321     return res;
2322 }
2323 
2324 /*
2325 ** XmClipboardRetrieve spinlock wrapper.
2326 */
SpinClipboardRetrieve(Display * display,Window window,char * format_name,XtPointer buffer,unsigned long length,unsigned long * num_bytes,long * private_id)2327 int SpinClipboardRetrieve(Display *display, Window window, char *format_name,
2328         XtPointer buffer, unsigned long length, unsigned long *num_bytes,
2329         long *private_id)
2330 {
2331     int i, res;
2332     for (i=0; i<SPINCOUNT; ++i) {
2333         res = XmClipboardRetrieve(display, window, format_name, buffer,
2334                                   length, num_bytes, private_id);
2335         if (res == XmClipboardSuccess) {
2336             return res;
2337         }
2338         if (res == XmClipboardTruncate) {
2339             warning("XmClipboardRetrieve() failed: buffer too small.");
2340             return res;
2341         }
2342         if (res == XmClipboardNoData) {
2343             return res;
2344         }
2345         microsleep(USLEEPTIME);
2346     }
2347     warning("XmClipboardRetrieve() failed: clipboard locked.");
2348     return res;
2349 }
2350 
2351 /*
2352 ** XmClipboardLock spinlock wrapper.
2353 */
SpinClipboardLock(Display * display,Window window)2354 int SpinClipboardLock(Display *display, Window window)
2355 {
2356     int i, res;
2357     for (i=0; i<SPINCOUNT; ++i) {
2358         res = XmClipboardLock(display, window);
2359         if (res == XmClipboardSuccess) {
2360             return res;
2361         }
2362         microsleep(USLEEPTIME);
2363     }
2364     warning("XmClipboardLock() failed: clipboard locked.");
2365     return res;
2366 }
2367 
2368 /*
2369 ** XmClipboardUnlock spinlock wrapper.
2370 */
SpinClipboardUnlock(Display * display,Window window)2371 int SpinClipboardUnlock(Display *display, Window window)
2372 {
2373     int i, res;
2374     /* Spinning doesn't make much sense in this case, I think. */
2375     for (i=0; i<SPINCOUNT; ++i) {
2376         /* Remove ALL locks (we don't use nested locking in NEdit) */
2377         res = XmClipboardUnlock(display, window, True);
2378         if (res == XmClipboardSuccess) {
2379             return res;
2380         }
2381         microsleep(USLEEPTIME);
2382     }
2383     /*
2384      * This warning doesn't make much sense in practice. It's usually
2385      * triggered when we try to unlock the clipboard after a failed clipboard
2386      * operation, in an attempt to work around possible *tif clipboard locking
2387      * bugs. In these cases, failure _is_ the expected outcome and the warning
2388      * is bogus. Therefore, the warning is disabled.
2389     warning("XmClipboardUnlock() failed: clipboard not locked or locked "
2390             "by another application.");
2391      */
2392     return res;
2393 }
2394 
2395 /*
2396 ** Send a client message to a EWMH/NetWM compatible X Window Manager.
2397 ** Code taken from wmctrl-1.07 (GPL licensed)
2398 */
WmClientMsg(Display * disp,Window win,const char * msg,unsigned long data0,unsigned long data1,unsigned long data2,unsigned long data3,unsigned long data4)2399 void WmClientMsg(Display *disp, Window win, const char *msg,
2400         unsigned long data0, unsigned long data1,
2401         unsigned long data2, unsigned long data3,
2402         unsigned long data4)
2403 {
2404     XEvent event;
2405     long mask = SubstructureRedirectMask | SubstructureNotifyMask;
2406 
2407     event.xclient.type = ClientMessage;
2408     event.xclient.serial = 0;
2409     event.xclient.send_event = True;
2410     event.xclient.message_type = XInternAtom(disp, msg, False);
2411     event.xclient.window = win;
2412     event.xclient.format = 32;
2413     event.xclient.data.l[0] = data0;
2414     event.xclient.data.l[1] = data1;
2415     event.xclient.data.l[2] = data2;
2416     event.xclient.data.l[3] = data3;
2417     event.xclient.data.l[4] = data4;
2418 
2419     if (!XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event)) {
2420         fprintf(stderr, "nedit: cannot send %s EWMH event.\n", msg);
2421     }
2422 }
2423