Fix for #448402: nedit quits if unable to resolve X locale
[nedit.git] / util / misc.c
blobfdbc6d5b25b2efd8bb867ebeaaa4390a04847862
1 static const char CVSID[] = "$Id: misc.c,v 1.25 2001/08/04 20:49:21 tringali Exp $";
2 /*******************************************************************************
3 * *
4 * misc.c -- Miscelaneous Motif convenience functions *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 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 #include <stdlib.h>
29 #include <string.h>
30 #include <stdarg.h>
31 #include <ctype.h>
32 #include <math.h>
33 #include <stdio.h>
34 #include <time.h>
35 #ifdef VMS
36 #include <types.h>
37 #include <unixio.h>
38 #include <file.h>
39 #endif /*VMS*/
40 #include <X11/Intrinsic.h>
41 #include <X11/Xatom.h>
42 #include <X11/keysym.h>
43 #include <X11/keysymdef.h>
44 #include <Xm/Xm.h>
45 #include <Xm/Label.h>
46 #include <Xm/LabelG.h>
47 #include <Xm/ToggleB.h>
48 #include <Xm/PushB.h>
49 #include <Xm/Separator.h>
50 #include <Xm/RowColumn.h>
51 #include <Xm/CascadeB.h>
52 #include <Xm/AtomMgr.h>
53 #include <Xm/Protocols.h>
54 #include <Xm/Text.h>
55 #include <Xm/MessageB.h>
56 #include <Xm/DialogS.h>
57 #include <Xm/SelectioB.h>
58 #include <Xm/Form.h>
59 #include <Xm/FileSB.h>
60 #include "DialogF.h"
61 #include "misc.h"
63 /* math.h on Sun mysteriously excludes strtod and other functions when
64 POSIX compliance is turned on */
65 extern double strtod();
67 /* structure for passing history-recall data to callbacks */
68 typedef struct {
69 char ***list;
70 int *nItems;
71 int index;
72 } histInfo;
74 typedef Widget (*MotifDialogCreationCall)(Widget, String, ArgList, Cardinal);
76 /* Maximum size of a history-recall list. Typically never invoked, since
77 user must first make this many entries in the text field, limited for
78 safety, to the maximum reasonable number of times user can hit up-arrow
79 before carpal tunnel syndrome sets in */
80 #define HISTORY_LIST_TRIM_TO 1000
81 #define HISTORY_LIST_MAX 2000
83 /* Maximum length for a menu accelerator string which can be parsed by
84 parseAccelString (how many modifier keys can you hold down at once?) */
85 #define MAX_ACCEL_LEN 100
87 /* flags to enable/disable delete key remapping and pointer centered dialogs */
88 static int RemapDeleteEnabled = True;
89 static int PointerCenteredDialogsEnabled = False;
91 /* bitmap and mask for waiting (wrist-watch) cursor */
92 #define watch_x_hot 7
93 #define watch_y_hot 7
94 #define watch_width 16
95 #define watch_height 16
96 static unsigned char watch_bits[] = {
97 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0x10, 0x08, 0x08, 0x11,
98 0x04, 0x21, 0x04, 0x21, 0xe4, 0x21, 0x04, 0x20, 0x08, 0x10, 0x10, 0x08,
99 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07
101 #define watch_mask_width 16
102 #define watch_mask_height 16
103 static unsigned char watch_mask_bits[] = {
104 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf8, 0x1f, 0xfc, 0x3f,
105 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f,
106 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f
109 static void addMnemonicGrabs(Widget addTo, Widget w, int unmodified);
110 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event);
111 static void findAndActivateMnemonic(Widget w, unsigned int keycode);
112 static void addAccelGrabs(Widget topWidget, Widget w);
113 static void addAccelGrab(Widget topWidget, Widget w);
114 static int parseAccelString(Display *display, const char *string, KeySym *keysym,
115 unsigned int *modifiers);
116 static void lockCB(Widget w, XtPointer callData, XEvent *event,
117 Boolean *continueDispatch);
118 static int findAndActivateAccel(Widget w, unsigned int keyCode,
119 unsigned int modifiers, XEvent *event);
120 static void removeWhiteSpace(char *string);
121 static int stripCaseCmp(const char *str1, const char *str2);
122 static void warnHandlerCB(String message);
123 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
124 *txtVerStr);
125 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
126 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
127 Boolean *continueDispatch);
128 static Widget addParentVisArgsAndCall(MotifDialogCreationCall callRoutine,
129 Widget parent, char *name, ArgList arglist, Cardinal argcount);
132 ** Set up closeCB to be called when the user selects close from the
133 ** window menu. The close menu item usually activates f.kill which
134 ** sends a WM_DELETE_WINDOW protocol request for the window.
136 void AddMotifCloseCallback(Widget shell, XtCallbackProc closeCB, void *arg)
138 static Atom wmpAtom, dwAtom = 0;
139 Display *display = XtDisplay(shell);
141 /* deactivate the built in delete response of killing the application */
142 XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);
144 /* add a delete window protocol callback instead */
145 if (dwAtom == 0) {
146 wmpAtom = XmInternAtom(display, "WM_PROTOCOLS", TRUE);
147 dwAtom = XmInternAtom(display, "WM_DELETE_WINDOW", TRUE);
149 XmAddProtocolCallback(shell, wmpAtom, dwAtom, closeCB, arg);
153 ** Motif still generates spurrious passive grab warnings on both IBM and SGI
154 ** This routine suppresses them
156 void SuppressPassiveGrabWarnings(void)
158 XtSetWarningHandler(warnHandlerCB);
162 ** This routine kludges around the problem of backspace not being mapped
163 ** correctly when Motif is used between a server with a delete key in
164 ** the traditional typewriter backspace position and a client that
165 ** expects a backspace key in that position. Though there are three
166 ** distinct levels of key re-mapping in effect when a user is running
167 ** a Motif application, none of these is really appropriate or effective
168 ** for eliminating the delete v.s. backspace problem. Our solution is,
169 ** sadly, to eliminate the forward delete functionality of the delete key
170 ** in favor of backwards delete for both keys. So as not to prevent the
171 ** user or the application from applying other translation table re-mapping,
172 ** we apply re-map the key as a post-processing step, applied after widget
173 ** creation. As a result, the re-mapping necessarily becomes embedded
174 ** throughout an application (wherever text widgets are created), and
175 ** within library routines, including the Nirvana utility library. To
176 ** make this remapping optional, the SetDeleteRemap function provides a
177 ** way for an application to turn this functionality on and off. It is
178 ** recommended that applications that use this routine provide an
179 ** application resource called remapDeleteKey so savvy users can get
180 ** their forward delete functionality back.
182 void RemapDeleteKey(Widget w)
184 static XtTranslations table = NULL;
185 static char *translations =
186 "~Shift~Ctrl~Meta~Alt<Key>osfDelete: delete-previous-character()\n";
188 if (RemapDeleteEnabled) {
189 if (table == NULL)
190 table = XtParseTranslationTable(translations);
191 XtOverrideTranslations(w, table);
195 void SetDeleteRemap(int state)
197 RemapDeleteEnabled = state;
201 ** This routine resolves a window manager protocol incompatibility between
202 ** the X toolkit and several popular window managers. Using this in place
203 ** of XtRealizeWidget will realize the window in a way which allows the
204 ** affected window managers to apply their own placement strategy to the
205 ** window, as opposed to forcing the window to a specific location.
207 ** One of the hints in the WM_NORMAL_HINTS protocol, PPlacement, gets set by
208 ** the X toolkit (probably part of the Core or Shell widget) when a shell
209 ** widget is realized to the value stored in the XmNx and XmNy resources of the
210 ** Core widget. While callers can set these values, there is no "unset" value
211 ** for these resources. On systems which are more Motif aware, a PPosition
212 ** hint of 0,0, which is the default for XmNx and XmNy, is interpreted as,
213 ** "place this as if no hints were specified". Unfortunately the fvwm family
214 ** of window managers, which are now some of the most popular, interpret this
215 ** as "place this window at (0,0)". This routine intervenes between the
216 ** realizing and the mapping of the window to remove the inappropriate
217 ** PPlacement hint.
219 void RealizeWithoutForcingPosition(Widget shell)
221 XSizeHints *hints = XAllocSizeHints();
222 long suppliedHints;
223 Boolean mappedWhenManaged;
225 /* Temporarily set value of XmNmappedWhenManaged
226 to stop the window from popping up right away */
227 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
228 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
230 /* Realize the widget in unmapped state */
231 XtRealizeWidget(shell);
233 /* Get rid of the incorrect WMNormal hint */
234 if (XGetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints,
235 &suppliedHints)) {
236 hints->flags &= ~PPosition;
237 XSetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints);
239 XFree(hints);
241 /* Map the widget */
242 XtMapWidget(shell);
244 /* Restore the value of XmNmappedWhenManaged */
245 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
249 ** Older X applications and X servers were mostly designed to operate with
250 ** visual class PseudoColor, because older displays were at most 8 bits
251 ** deep. Modern X servers, however, usually support 24 bit depth and other
252 ** color models. Sun (and others?) still sets their default visual to
253 ** 8-bit PseudoColor, because some of their X applications don't work
254 ** properly with the other color models. The problem with PseudoColor, of
255 ** course, is that users run out of colors in the default colormap, and if
256 ** they install additional colormaps for individual applications, colors
257 ** flash and change weirdly when you change your focus from one application
258 ** to another.
260 ** In addition to the poor choice of default, a design flaw in Xt makes it
261 ** impossible even for savvy users to specify the XtNvisual resource to
262 ** switch to a deeper visual. The problem is that the colormap resource is
263 ** processed independently of the visual resource, and usually results in a
264 ** colormap for the default visual rather than for the user-selected one.
266 ** This routine should be called before creating a shell widget, to
267 ** pre-process the visual, depth, and colormap resources, and return the
268 ** proper values for these three resources to be passed to XtAppCreateShell.
269 ** Applications which actually require a particular color model (i.e. for
270 ** doing color table animation or dynamic color assignment) should not use
271 ** this routine.
273 ** Note that a consequence of using the "best" as opposed to the default
274 ** visual is that some color resources are still converted with the default
275 ** visual (particularly *background), and these must be avoided by widgets
276 ** which are allowed to handle any visual.
278 void FindBestVisual(Display *display, const char *appName, char *appClass,
279 Visual **visual, int *depth, Colormap *colormap)
281 char rsrcName[256], rsrcClass[256], *valueString, *type, *endPtr;
282 XrmValue value;
283 int screen = DefaultScreen(display);
284 int reqDepth = -1;
285 int reqID = -1;
286 int reqClass = -1;
287 int installColormap = FALSE;
288 int maxDepth, bestClass, bestVisual, nVis, i, j;
289 XVisualInfo visTemplate, *visList = NULL;
290 static Visual *cachedVisual = NULL;
291 static Colormap cachedColormap;
292 static int cachedDepth = 0;
293 int bestClasses[] = {StaticGray, GrayScale, StaticColor, PseudoColor,
294 DirectColor, TrueColor};
296 /* If results have already been computed, just return them */
297 if (cachedVisual != NULL) {
298 *visual = cachedVisual;
299 *depth = cachedDepth;
300 *colormap = cachedColormap;
301 return;
304 /* Read the visualID and installColormap resources for the application.
305 visualID can be specified either as a number (the visual id as
306 shown by xdpyinfo), as a visual class name, or as Best or Default. */
307 sprintf(rsrcName,"%s.%s", appName, "visualID");
308 sprintf(rsrcClass, "%s.%s", appClass, "VisualID");
309 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
310 &value)) {
311 valueString = value.addr;
312 reqID = (int)strtol(valueString, &endPtr, 0);
313 if (endPtr == valueString) {
314 reqID = -1;
315 if (stripCaseCmp(valueString, "Default"))
316 reqID = DefaultVisual(display, screen)->visualid;
317 else if (stripCaseCmp(valueString, "StaticGray"))
318 reqClass = StaticGray;
319 else if (stripCaseCmp(valueString, "StaticColor"))
320 reqClass = StaticColor;
321 else if (stripCaseCmp(valueString, "TrueColor"))
322 reqClass = TrueColor;
323 else if (stripCaseCmp(valueString, "GrayScale"))
324 reqClass = GrayScale;
325 else if (stripCaseCmp(valueString, "PseudoColor"))
326 reqClass = PseudoColor;
327 else if (stripCaseCmp(valueString, "DirectColor"))
328 reqClass = DirectColor;
329 else if (!stripCaseCmp(valueString, "Best"))
330 fprintf(stderr, "Invalid visualID resource value\n");
333 sprintf(rsrcName,"%s.%s", appName, "installColormap");
334 sprintf(rsrcClass, "%s.%s", appClass, "InstallColormap");
335 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
336 &value)) {
337 if (stripCaseCmp(value.addr, "Yes") || stripCaseCmp(value.addr, "True"))
338 installColormap = TRUE;
341 /* Generate a list of visuals to consider. (Note, vestigial code for
342 user-requested visual depth is left in, just in case that function
343 might be needed again, but it does nothing) */
344 if (reqID != -1) {
345 visTemplate.visualid = reqID;
346 visList = XGetVisualInfo(display, VisualIDMask, &visTemplate, &nVis);
347 if (visList == NULL)
348 fprintf(stderr, "VisualID resource value not valid\n");
350 if (visList == NULL && reqClass != -1 && reqDepth != -1) {
351 visTemplate.class = reqClass;
352 visTemplate.depth = reqDepth;
353 visList = XGetVisualInfo(display,
354 VisualClassMask | VisualDepthMask, &visTemplate, &nVis);
355 if (visList == NULL)
356 fprintf(stderr, "Visual class/depth combination not available\n");
358 if (visList == NULL && reqClass != -1) {
359 visTemplate.class = reqClass;
360 visList = XGetVisualInfo(display, VisualClassMask, &visTemplate, &nVis);
361 if (visList == NULL)
362 fprintf(stderr,
363 "Visual Class from resource \"visualID\" not available\n");
365 if (visList == NULL && reqDepth != -1) {
366 visTemplate.depth = reqDepth;
367 visTemplate.depth = reqDepth;
368 visList = XGetVisualInfo(display, VisualDepthMask, &visTemplate, &nVis);
369 if (visList == NULL)
370 fprintf(stderr, "Requested visual depth not available\n");
372 if (visList == NULL) {
373 visList = XGetVisualInfo(display, VisualNoMask, &visTemplate, &nVis);
374 if (visList == NULL) {
375 fprintf(stderr, "Internal Error: no visuals available?\n");
376 *visual = DefaultVisual(display, screen);
377 *depth = DefaultDepth(display, screen);
378 *colormap = DefaultColormap(display, screen);
379 return;
383 /* Choose among the visuals in the candidate list. Prefer maximum
384 depth first then matching default, then largest value of bestClass
385 (I'm not sure whether we actually care about class) */
386 maxDepth = 0;
387 bestClass = 0;
388 bestVisual = 0;
389 for (i=0; i < nVis; i++) {
390 if (visList[i].depth > maxDepth) {
391 maxDepth = visList[i].depth;
392 bestClass = 0;
393 bestVisual = i;
395 if (visList[i].depth == maxDepth) {
396 if (visList[i].visual == DefaultVisual(display, screen))
397 bestVisual = i;
398 if (visList[bestVisual].visual != DefaultVisual(display, screen)) {
399 for (j = 0; j < XtNumber(bestClasses); j++) {
400 if (visList[i].class == bestClasses[j] && j > bestClass) {
401 bestClass = j;
402 bestVisual = i;
408 *visual = cachedVisual = visList[bestVisual].visual;
409 *depth = cachedDepth = visList[bestVisual].depth;
411 /* If the chosen visual is not the default, it needs a colormap allocated */
412 if (*visual == DefaultVisual(display, screen) && !installColormap)
413 *colormap = cachedColormap = DefaultColormap(display, screen);
414 else {
415 *colormap = cachedColormap = XCreateColormap(display,
416 RootWindow(display, screen), cachedVisual, AllocNone);
417 XInstallColormap(display, cachedColormap);
419 /* printf("Chose visual with depth %d, class %d, colormap %ld, id 0x%x\n",
420 visList[bestVisual].depth, visList[bestVisual].class,
421 *colormap, cachedVisual->visualid); */
422 /* Fix memory leak */
423 if (visList != NULL) {
424 XFree(visList);
429 ** If you want to use a non-default visual with Motif, shells all have to be
430 ** created with that visual, depth, and colormap, even if the parent has them
431 ** set up properly. Substituting these routines, will append visual args copied
432 ** from the parent widget (CreatePopupMenu and CreatePulldownMenu), or from the
433 ** best visual, obtained via FindBestVisual above (CreateShellWithBestVis).
435 Widget CreateDialogShell(Widget parent, char *name,
436 ArgList arglist, Cardinal argcount)
438 return addParentVisArgsAndCall(XmCreateDialogShell, parent, name, arglist,
439 argcount);
441 Widget CreatePopupMenu(Widget parent, char *name, ArgList arglist,
442 Cardinal argcount)
444 return addParentVisArgsAndCall(XmCreatePopupMenu, parent, name,
445 arglist, argcount);
447 Widget CreatePulldownMenu(Widget parent, char *name,
448 ArgList arglist, Cardinal argcount)
450 return addParentVisArgsAndCall(XmCreatePulldownMenu, parent, name, arglist,
451 argcount);
453 Widget CreatePromptDialog(Widget parent, char *name,
454 ArgList arglist, Cardinal argcount)
456 return addParentVisArgsAndCall(XmCreatePromptDialog, parent, name, arglist,
457 argcount);
459 Widget CreateSelectionDialog(Widget parent, char *name,
460 ArgList arglist, Cardinal argcount)
462 return addParentVisArgsAndCall(XmCreateSelectionDialog, parent, name,
463 arglist, argcount);
465 Widget CreateFormDialog(Widget parent, char *name,
466 ArgList arglist, Cardinal argcount)
468 return addParentVisArgsAndCall(XmCreateFormDialog, parent, name, arglist,
469 argcount);
471 Widget CreateFileSelectionDialog(Widget parent, char *name,
472 ArgList arglist, Cardinal argcount)
474 return addParentVisArgsAndCall(XmCreateFileSelectionDialog, parent, name,
475 arglist, argcount);
477 Widget CreateQuestionDialog(Widget parent, char *name,
478 ArgList arglist, Cardinal argcount)
480 return addParentVisArgsAndCall(XmCreateQuestionDialog, parent, name,
481 arglist, argcount);
483 Widget CreateMessageDialog(Widget parent, char *name,
484 ArgList arglist, Cardinal argcount)
486 return addParentVisArgsAndCall(XmCreateMessageDialog, parent, name,
487 arglist, argcount);
489 Widget CreateErrorDialog(Widget parent, char *name,
490 ArgList arglist, Cardinal argcount)
492 return addParentVisArgsAndCall(XmCreateErrorDialog, parent, name, arglist,
493 argcount);
495 Widget CreateShellWithBestVis(String appName, String appClass,
496 WidgetClass class, Display *display, ArgList args, Cardinal nArgs)
498 Visual *visual;
499 int depth;
500 Colormap colormap;
501 ArgList al;
502 Cardinal ac = nArgs;
503 Widget result;
505 FindBestVisual(display, appName, appClass, &visual, &depth, &colormap);
506 al = (ArgList)XtMalloc(sizeof(Arg) * (nArgs + 3));
507 if (nArgs != 0)
508 memcpy(al, args, sizeof(Arg) * nArgs);
509 XtSetArg(al[ac], XtNvisual, visual); ac++;
510 XtSetArg(al[ac], XtNdepth, depth); ac++;
511 XtSetArg(al[ac], XtNcolormap, colormap); ac++;
512 result = XtAppCreateShell(appName, appClass, class, display, al, ac);
513 XtFree((char *)al);
514 return result;
518 ** Calls one of the Motif widget creation routines, splicing in additional
519 ** arguments for visual, colormap, and depth.
521 static Widget addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,
522 Widget parent, char *name, ArgList arglist, Cardinal argcount)
524 Visual *visual;
525 int depth;
526 Colormap colormap;
527 ArgList al;
528 Cardinal ac = argcount;
529 Widget result;
530 Widget parentShell = parent;
532 /* Find the application/dialog/menu shell at the top of the widget
533 hierarchy, which has the visual resource being used */
534 while (True) {
535 if (XtIsShell(parentShell))
536 break;
537 if (parentShell == NULL) {
538 fprintf(stderr, "failed to find shell\n");
539 exit(EXIT_FAILURE);
541 parentShell = XtParent(parentShell);
544 /* Add the visual, depth, and colormap resources to the argument list */
545 XtVaGetValues(parentShell, XtNvisual, &visual, XtNdepth, &depth,
546 XtNcolormap, &colormap, NULL);
547 al = (ArgList)XtMalloc(sizeof(Arg) * (argcount + 3));
548 if (argcount != 0)
549 memcpy(al, arglist, sizeof(Arg) * argcount);
550 XtSetArg(al[ac], XtNvisual, visual); ac++;
551 XtSetArg(al[ac], XtNdepth, depth); ac++;
552 XtSetArg(al[ac], XtNcolormap, colormap); ac++;
553 result = (*createRoutine)(parent, name, al, ac);
554 XtFree((char *)al);
555 return result;
559 ** ManageDialogCenteredOnPointer is used in place of XtManageChild for
560 ** popping up a dialog to enable the dialog to be centered under the
561 ** mouse pointer. Whether it pops up the dialog centered under the pointer
562 ** or in its default position centered over the parent widget, depends on
563 ** the value set in the SetPointerCenteredDialogs call.
565 void ManageDialogCenteredOnPointer(Widget dialogChild)
567 Widget shell = XtParent(dialogChild);
568 Window root, child;
569 unsigned int mask;
570 unsigned int width, height, borderWidth, depth;
571 int x, y, winX, winY, maxX, maxY;
572 Boolean mappedWhenManaged;
574 /* If this feature is not enabled, just manage the dialog */
575 if (!PointerCenteredDialogsEnabled) {
576 XtManageChild(dialogChild);
577 return;
580 /* Temporarily set value of XmNmappedWhenManaged
581 to stop the dialog from popping up right away */
582 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
583 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
585 /* Manage the dialog */
586 XtManageChild(dialogChild);
588 /* Get the pointer position (x, y) */
589 XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child,
590 &x, &y, &winX, &winY, &mask);
592 /* Translate the pointer position (x, y) into a position for the new
593 window that will place the pointer at its center */
594 XGetGeometry(XtDisplay(shell), XtWindow(shell), &root, &winX, &winY,
595 &width, &height, &borderWidth, &depth);
596 width += 2 * borderWidth;
597 height += 2 * borderWidth;
598 x -= width/2;
599 y -= height/2;
601 /* Ensure that the dialog remains on screen */
602 maxX = XtScreen(shell)->width - width;
603 maxY = XtScreen(shell)->height - height;
604 if (x < 0) x = 0;
605 if (x > maxX) x = maxX;
606 if (y < 0) y = 0;
607 if (y > maxY) y = maxY;
609 /* Set desired window position in the DialogShell */
610 XtVaSetValues(shell, XmNx, x, XmNy, y, NULL);
612 /* Map the widget */
613 XtMapWidget(shell);
615 /* Restore the value of XmNmappedWhenManaged */
616 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
620 ** Cause dialogs created by libNUtil.a routines (such as DialogF and
621 ** GetNewFilename), and dialogs which use ManageDialogCenteredOnPointer
622 ** to pop up over the pointer (state = True), or pop up in their default
623 ** positions (state = False)
625 void SetPointerCenteredDialogs(int state)
627 PointerCenteredDialogsEnabled = state;
632 ** Raise a window to the top and give it the input focus. Setting input focus
633 ** is important on systems which use explict (rather than pointer) focus.
635 ** The X alternatives XMapRaised, and XSetInputFocus both have problems.
636 ** XMapRaised only gives the window the focus if it was initially not visible,
637 ** and XSetInputFocus sets the input focus, but crashes if the window is not
638 ** visible.
640 ** This routine should also be used in the case where a dialog is popped up and
641 ** subsequent calls to the dialog function use a flag, or the XtIsManaged, to
642 ** decide whether to create a new instance of the dialog, because on slower
643 ** systems, events can intervene while a dialog is still on its way up and its
644 ** window is still invisible, causing a subtle crash potential if
645 ** XSetInputFocus is used.
647 void RaiseShellWindow(Widget shell)
649 RaiseWindow(XtDisplay(shell), XtWindow(shell));
651 void RaiseWindow(Display *display, Window w)
653 XWindowAttributes winAttr;
655 XGetWindowAttributes(display, w, &winAttr);
656 if (winAttr.map_state == IsViewable)
657 XSetInputFocus(display, w, RevertToParent, CurrentTime);
658 XMapRaised(display, w);
662 ** Add a handler for mnemonics in a dialog (Motif currently only handles
663 ** mnemonics in menus) following the example of M.S. Windows. To add
664 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
665 ** a menu, on push buttons or toggle buttons, and call this function
666 ** when the dialog is fully constructed. Mnemonics added or changed
667 ** after this call will not be noticed. To add a mnemonic to a text field
668 ** or list, set the XmNmnemonic resource on the appropriate label and set
669 ** the XmNuserData resource of the label to the widget to get the focus
670 ** when the mnemonic is typed.
672 void AddDialogMnemonicHandler(Widget dialog, int unmodifiedToo)
674 XtAddEventHandler(dialog, KeyPressMask, False,
675 (XtEventHandler)mnemonicCB, (XtPointer)0);
676 addMnemonicGrabs(dialog, dialog, unmodifiedToo);
680 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
682 void RemoveDialogMnemonicHandler(Widget dialog)
684 XtUngrabKey(dialog, AnyKey, Mod1Mask);
685 XtRemoveEventHandler(dialog, KeyPressMask, False,
686 (XtEventHandler)mnemonicCB, (XtPointer)0);
690 ** Patch around Motif's poor handling of menu accelerator keys. Motif
691 ** does not process menu accelerators when the caps lock or num lock
692 ** keys are engaged. To enable accelerators in these cases, call this
693 ** routine with the completed menu bar widget as "topMenuContainer", and
694 ** the top level shell widget as "topWidget". It will add key grabs for
695 ** all of the accelerators it finds in the topMenuContainer menu tree, and
696 ** an event handler which can process dropped accelerator events by (again)
697 ** traversing the menu tree looking for matching accelerators, and invoking
698 ** the appropriate button actions. Any dynamic additions to the menus
699 ** require a call to UpdateAccelLockPatch to add the additional grabs.
700 ** Unfortunately, these grabs can not be removed.
702 void AccelLockBugPatch(Widget topWidget, Widget topMenuContainer)
704 XtAddEventHandler(topWidget, KeyPressMask, False, lockCB, topMenuContainer);
705 addAccelGrabs(topWidget, topMenuContainer);
709 ** Add additional key grabs for new menu items added to the menus, for
710 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
711 ** the same widget passed in the original call to AccelLockBugPatch.
713 void UpdateAccelLockPatch(Widget topWidget, Widget newButton)
715 addAccelGrab(topWidget, newButton);
719 ** PopDownBugPatch
721 ** Under some circumstances, popping down a dialog and its parent in
722 ** rapid succession causes a crash. This routine delays and
723 ** processs events until receiving a ReparentNotify event.
724 ** (I have no idea why a ReparentNotify event occurs at all, but it does
725 ** mark the point where it is safe to destroy or pop down the parent, and
726 ** it might have something to do with the bug.) There is a failsafe in
727 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
728 ** Use this sparingly, only when real crashes are observed, and periodically
729 ** check to make sure that it is still necessary.
731 void PopDownBugPatch(Widget w)
733 time_t stopTime;
735 stopTime = time(NULL) + 1;
736 while (time(NULL) <= stopTime) {
737 XEvent event;
738 XtAppContext context = XtWidgetToApplicationContext(w);
739 XtAppPeekEvent(context, &event);
740 if (event.xany.type == ReparentNotify)
741 return;
742 XtAppProcessEvent(context, XtIMAll);
747 ** Convert a compound string to a C style null terminated string.
748 ** Returned string must be freed by the caller.
750 char *GetXmStringText(XmString fromString)
752 XmStringContext context;
753 char *text, *toPtr, *toString, *fromPtr;
754 XmStringCharSet charset;
755 XmStringDirection direction;
756 Boolean separator;
758 /* Malloc a buffer large enough to hold the string. XmStringLength
759 should always be slightly longer than necessary, but won't be
760 shorter than the equivalent null-terminated string */
761 toString = XtMalloc(XmStringLength(fromString));
763 /* loop over all of the segments in the string, copying each segment
764 into the output string and converting separators into newlines */
765 XmStringInitContext(&context, fromString);
766 toPtr = toString;
767 while (XmStringGetNextSegment(context, &text,
768 &charset, &direction, &separator)) {
769 for (fromPtr=text; *fromPtr!='\0'; fromPtr++)
770 *toPtr++ = *fromPtr;
771 if (separator)
772 *toPtr++ = '\n';
775 /* terminate the string, free the context, and return the string */
776 *toPtr++ = '\0';
777 XmStringFreeContext(context);
778 return toString;
782 ** Get the XFontStruct that corresponds to the default (first) font in
783 ** a Motif font list. Since Motif stores this, it saves us from storing
784 ** it or querying it from the X server.
786 XFontStruct *GetDefaultFontStruct(XmFontList font)
788 XFontStruct *fs;
789 XmFontContext context;
790 XmStringCharSet charset;
792 XmFontListInitFontContext(&context, font);
793 XmFontListGetNextFont(context, &charset, &fs);
794 XmFontListFreeFontContext(context);
795 XtFree(charset);
796 return fs;
800 ** Create a string table suitable for passing to XmList widgets
802 XmString* StringTable(int count, ... )
804 va_list ap;
805 XmString *array;
806 int i;
807 char *str;
809 va_start(ap, count);
810 array = (XmString*)XtMalloc((count+1) * sizeof(XmString));
811 for(i = 0; i < count; i++ ) {
812 str = va_arg(ap, char *);
813 array[i] = XmStringCreateSimple(str);
815 array[i] = (XmString)0;
816 va_end(ap);
817 return(array);
820 void FreeStringTable(XmString *table)
822 int i;
824 for(i = 0; table[i] != 0; i++)
825 XmStringFree(table[i]);
826 XtFree((char *)table);
830 ** Simulate a button press. The purpose of this routine is show users what
831 ** is happening when they take an action with a non-obvious side effect,
832 ** such as when a user double clicks on a list item. The argument is an
833 ** XmPushButton widget to "press"
835 void SimulateButtonPress(Widget widget)
837 XEvent keyEvent;
839 memset((char *)&keyEvent, 0, sizeof(XKeyPressedEvent));
840 keyEvent.type = KeyPress;
841 keyEvent.xkey.serial = 1;
842 keyEvent.xkey.send_event = True;
844 if (XtIsSubclass(widget, xmGadgetClass))
846 /* On some Motif implementations, asking a gadget for its
847 window will crash, rather than return the window of its
848 parent. */
849 Widget parent = XtParent(widget);
850 keyEvent.xkey.display = XtDisplay(parent);
851 keyEvent.xkey.window = XtWindow(parent);
853 XtCallActionProc(parent, "ManagerGadgetSelect",
854 &keyEvent, NULL, 0);
856 else
858 keyEvent.xkey.display = XtDisplay(widget);
859 keyEvent.xkey.window = XtWindow(widget);
861 XtCallActionProc(widget, "ArmAndActivate", &keyEvent, NULL, 0);
866 ** Add an item to an already established pull-down or pop-up menu, including
867 ** mnemonics, accelerators and callbacks.
869 Widget AddMenuItem(Widget parent, char *name, char *label,
870 char mnemonic, char *acc, char *accText,
871 XtCallbackProc callback, void *cbArg)
873 Widget button;
874 XmString st1, st2;
876 button = XtVaCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
877 XmNlabelString, st1=XmStringCreateSimple(label),
878 XmNmnemonic, mnemonic,
879 XmNacceleratorText, st2=XmStringCreateSimple(accText),
880 XmNaccelerator, acc, NULL);
881 XtAddCallback(button, XmNactivateCallback, callback, cbArg);
882 XmStringFree(st1);
883 XmStringFree(st2);
884 return button;
888 ** Add a toggle button item to an already established pull-down or pop-up
889 ** menu, including mnemonics, accelerators and callbacks.
891 Widget AddMenuToggle(Widget parent, char *name, char *label,
892 char mnemonic, char *acc, char *accText,
893 XtCallbackProc callback, void *cbArg, int set)
895 Widget button;
896 XmString st1, st2;
898 button = XtVaCreateManagedWidget(name, xmToggleButtonWidgetClass, parent,
899 XmNlabelString, st1=XmStringCreateSimple(label),
900 XmNmnemonic, mnemonic,
901 XmNacceleratorText, st2=XmStringCreateSimple(accText),
902 XmNaccelerator, acc,
903 XmNset, set, NULL);
904 XtAddCallback(button, XmNvalueChangedCallback, callback, cbArg);
905 XmStringFree(st1);
906 XmStringFree(st2);
907 return button;
911 ** Add a separator line to a menu
913 Widget AddMenuSeparator(Widget parent, char *name)
915 Widget button;
917 button = XmCreateSeparator(parent, name, NULL, 0);
918 XtManageChild(button);
919 return button;
923 ** Add a sub-menu to an established pull-down or pop-up menu, including
924 ** mnemonics, accelerators and callbacks. Returns the menu pane of the
925 ** new sub menu.
927 Widget AddSubMenu(Widget parent, char *name, char *label, char mnemonic)
929 Widget menu;
930 XmString st1;
932 menu = CreatePulldownMenu(parent, name, NULL, 0);
933 XtVaCreateManagedWidget(name, xmCascadeButtonWidgetClass, parent,
934 XmNlabelString, st1=XmStringCreateSimple(label),
935 XmNmnemonic, mnemonic,
936 XmNsubMenuId, menu, NULL);
937 XmStringFree(st1);
938 return menu;
942 ** SetIntLabel, SetFloatLabel, SetIntText, SetFloatText
944 ** Set the text of a motif label or text widget to show an integer or
945 ** floating number.
947 void SetIntLabel(Widget label, int value)
949 char labelString[20];
950 XmString s1;
952 sprintf(labelString, "%d", value);
953 s1=XmStringCreateSimple(labelString);
954 XtVaSetValues(label, XmNlabelString, s1, NULL);
955 XmStringFree(s1);
957 void SetFloatLabel(Widget label, double value)
959 char labelString[20];
960 XmString s1;
962 sprintf(labelString, "%g", value);
963 s1=XmStringCreateSimple(labelString);
964 XtVaSetValues(label, XmNlabelString, s1, NULL);
965 XmStringFree(s1);
967 void SetIntText(Widget text, int value)
969 char labelString[20];
971 sprintf(labelString, "%d", value);
972 XmTextSetString(text, labelString);
974 void SetFloatText(Widget text, double value)
976 char labelString[20];
978 sprintf(labelString, "%g", value);
979 XmTextSetString(text, labelString);
983 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
985 ** Get the text of a motif text widget as an integer or floating point number.
986 ** The functions will return TEXT_READ_OK of the value was read correctly.
987 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER. The
988 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
989 ** user that the value could not be read. The argument fieldName is used
990 ** in the dialog to help the user identify where the problem is. Set
991 ** warnBlank to true if a blank field is also considered an error.
993 int GetFloatText(Widget text, double *value)
995 char *strValue, *endPtr;
996 int retVal;
998 strValue = XmTextGetString(text); /* Get Value */
999 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1000 *value = strtod(strValue, &endPtr); /* Convert string to double */
1001 if (strlen(strValue) == 0) /* String is empty */
1002 retVal = TEXT_IS_BLANK;
1003 else if (*endPtr != '\0') /* Whole string not parsed */
1004 retVal = TEXT_NOT_NUMBER;
1005 else
1006 retVal = TEXT_READ_OK;
1007 XtFree(strValue);
1008 return retVal;
1011 int GetIntText(Widget text, int *value)
1013 char *strValue, *endPtr;
1014 int retVal;
1016 strValue = XmTextGetString(text); /* Get Value */
1017 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1018 *value = strtol(strValue, &endPtr, 10); /* Convert string to long */
1019 if (strlen(strValue) == 0) /* String is empty */
1020 retVal = TEXT_IS_BLANK;
1021 else if (*endPtr != '\0') /* Whole string not parsed */
1022 retVal = TEXT_NOT_NUMBER;
1023 else
1024 retVal = TEXT_READ_OK;
1025 XtFree(strValue);
1026 return retVal;
1029 int GetFloatTextWarn(Widget text, double *value, char *fieldName, int warnBlank)
1031 int result;
1032 char *valueStr;
1034 result = GetFloatText(text, value);
1035 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1036 return result;
1037 valueStr = XmTextGetString(text);
1038 if (result == TEXT_IS_BLANK)
1039 DialogF (DF_ERR, text, 1, "Please supply %s value",
1040 "Dismiss", fieldName);
1041 else /* TEXT_NOT_NUMBER */
1042 DialogF (DF_ERR, text, 1,
1043 "Can't read %s value: \"%s\"",
1044 "Dismiss", fieldName, valueStr);
1045 XtFree(valueStr);
1046 return result;
1049 int GetIntTextWarn(Widget text, int *value, char *fieldName, int warnBlank)
1051 int result;
1052 char *valueStr;
1054 result = GetIntText(text, value);
1055 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1056 return result;
1057 valueStr = XmTextGetString(text);
1058 if (result == TEXT_IS_BLANK)
1059 DialogF (DF_ERR, text, 1, "Please supply a value for %s",
1060 "Dismiss", fieldName);
1061 else /* TEXT_NOT_NUMBER */
1062 DialogF (DF_ERR, text, 1,
1063 "Can't read integer value \"%s\" in %s",
1064 "Dismiss", valueStr, fieldName);
1065 XtFree(valueStr);
1066 return result;
1069 int TextWidgetIsBlank(Widget textW)
1071 char *str;
1072 int retVal;
1074 str = XmTextGetString(textW);
1075 removeWhiteSpace(str);
1076 retVal = *str == '\0';
1077 XtFree(str);
1078 return retVal;
1082 ** Turn a multi-line editing text widget into a fake single line text area
1083 ** by disabling the translation for Return. This is a way to give users
1084 ** extra space, by allowing wrapping, but still prohibiting newlines.
1085 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1086 ** the widget to be one line high).
1088 void MakeSingleLineTextW(Widget textW)
1090 static XtTranslations noReturnTable = NULL;
1091 static char *noReturnTranslations = "<Key>Return: activate()\n";
1093 if (noReturnTable == NULL)
1094 noReturnTable = XtParseTranslationTable(noReturnTranslations);
1095 XtOverrideTranslations(textW, noReturnTable);
1099 ** Add up-arrow/down-arrow recall to a single line text field. When user
1100 ** presses up-arrow, string is cleared and recent entries are presented,
1101 ** moving to older ones as each successive up-arrow is pressed. Down-arrow
1102 ** moves to more recent ones, final down-arrow clears the field. Associated
1103 ** routine, AddToHistoryList, makes maintaining a history list easier.
1105 ** Arguments are the widget, and pointers to the history list and number of
1106 ** items, which are expected to change periodically.
1108 void AddHistoryToTextWidget(Widget textW, char ***historyList, int *nItems)
1110 histInfo *histData;
1112 /* create a data structure for passing history info to the callbacks */
1113 histData = (histInfo *)XtMalloc(sizeof(histInfo));
1114 histData->list = historyList;
1115 histData->nItems = nItems;
1116 histData->index = -1;
1118 /* Add an event handler for handling up/down arrow events */
1119 XtAddEventHandler(textW, KeyPressMask, False,
1120 (XtEventHandler)histArrowKeyEH, histData);
1122 /* Add a destroy callback for freeing history data structure */
1123 XtAddCallback(textW, XmNdestroyCallback, histDestroyCB, histData);
1126 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1128 XtFree((char *)clientData);
1131 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
1132 Boolean *continueDispatch)
1134 histInfo *histData = (histInfo *)callData;
1135 KeySym keysym = XLookupKeysym((XKeyEvent *)event, 0);
1137 /* only process up and down arrow keys */
1138 if (keysym != XK_Up && keysym != XK_Down)
1139 return;
1141 /* increment or decrement the index depending on which arrow was pressed */
1142 histData->index += (keysym == XK_Up) ? 1 : -1;
1144 /* if the index is out of range, beep, fix it up, and return */
1145 if (histData->index < -1) {
1146 histData->index = -1;
1147 XBell(XtDisplay(w), 0);
1148 return;
1150 if (histData->index >= *histData->nItems) {
1151 histData->index = *histData->nItems - 1;
1152 XBell(XtDisplay(w), 0);
1153 return;
1156 /* Change the text field contents */
1157 XmTextSetString(w, histData->index == -1 ? "" :
1158 (*histData->list)[histData->index]);
1162 ** Copies a string on to the end of history list, which may be reallocated
1163 ** to make room. If historyList grows beyond its internally set boundary
1164 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1165 ** (HISTORY_LIST_TRIM_TO). Before adding to the list, checks if the item
1166 ** is a duplicate of the last item. If so, it is not added.
1168 void AddToHistoryList(char *newItem, char ***historyList, int *nItems)
1170 char **newList;
1171 int i;
1173 if (*nItems != 0 && !strcmp(newItem, **historyList))
1174 return;
1175 if (*nItems == HISTORY_LIST_MAX) {
1176 for (i=HISTORY_LIST_TRIM_TO; i<HISTORY_LIST_MAX; i++)
1177 XtFree((*historyList)[i]);
1178 *nItems = HISTORY_LIST_TRIM_TO;
1180 newList = (char **)XtMalloc(sizeof(char *) * (*nItems + 1));
1181 for (i=0; i < *nItems; i++)
1182 newList[i+1] = (*historyList)[i];
1183 if (*nItems != 0 && *historyList != NULL)
1184 XtFree((char *)*historyList);
1185 (*nItems)++;
1186 newList[0] = XtNewString(newItem);
1187 *historyList = newList;
1191 * PasswordText - routine to add a callback to any text widget so that all
1192 * text typed by the user is echoed with asterisks, allowing
1193 * a password to be typed in without being seen.
1195 * parameters: w - text widget to add the callback to
1196 * passTxt - pointer to a string created by caller of this routine.
1197 * **NOTE** The length of this string should be one
1198 * greater than the maximum specified by XmNmaxLength.
1199 * This string is set to empty just before the callback
1200 * is added.
1203 void PasswordText(Widget w, char *passTxt)
1205 passTxt[0] = '\0';
1206 XtAddCallback(w, XmNmodifyVerifyCallback, (XtCallbackProc)passwdCB,passTxt);
1210 ** BeginWait/EndWait
1212 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1214 void BeginWait(Widget topCursorWidget)
1216 Display *display = XtDisplay(topCursorWidget);
1217 Pixmap pixmap;
1218 Pixmap maskPixmap;
1219 XColor xcolors[2];
1220 static Cursor waitCursor = 0;
1222 /* if the watch cursor hasn't been created yet, create it */
1223 if (!waitCursor) {
1224 pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1225 (char *)watch_bits, watch_width, watch_height);
1227 maskPixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1228 (char *)watch_mask_bits, watch_width, watch_height);
1230 xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(display));
1231 xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(display));
1233 XQueryColors(display, DefaultColormapOfScreen(
1234 DefaultScreenOfDisplay(display)), xcolors, 2);
1235 waitCursor = XCreatePixmapCursor(display, pixmap, maskPixmap,
1236 &xcolors[0], &xcolors[1], watch_x_hot, watch_y_hot);
1237 XFreePixmap(display, pixmap);
1238 XFreePixmap(display, maskPixmap);
1241 /* display the cursor */
1242 XDefineCursor(display, XtWindow(topCursorWidget), waitCursor);
1245 void EndWait(Widget topCursorWidget)
1247 XUndefineCursor(XtDisplay(topCursorWidget), XtWindow(topCursorWidget));
1251 ** Create an X window geometry string from width, height, x, and y values.
1252 ** This is a complement to the X routine XParseGeometry, and uses the same
1253 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1254 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1255 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1256 ** result. Note that in a geometry string, it is not possible to supply a y
1257 ** position without an x position. Also note that the X/YNegative flags
1258 ** mean "add a '-' and negate the value" which is kind of odd.
1260 void CreateGeometryString(char *string, short x, short y,
1261 short width, short height, int bitmask)
1263 char *ptr = string;
1264 int nChars;
1266 if (bitmask & WidthValue) {
1267 sprintf(ptr, "%d%n", width, &nChars);
1268 ptr += nChars;
1270 if (bitmask & HeightValue) {
1271 sprintf(ptr, "x%d%n", height, &nChars);
1272 ptr += nChars;
1274 if (bitmask & XValue) {
1275 if (bitmask & XNegative)
1276 sprintf(ptr, "-%d%n", -x, &nChars);
1277 else
1278 sprintf(ptr, "+%d%n", x, &nChars);
1279 ptr += nChars;
1281 if (bitmask & YValue) {
1282 if (bitmask & YNegative)
1283 sprintf(ptr, "-%d%n", -y, &nChars);
1284 else
1285 sprintf(ptr, "+%d%n", y, &nChars);
1286 ptr += nChars;
1288 *ptr = '\0';
1291 /* */
1292 /* passwdCB: callback routine added by PasswordText routine. This routine */
1293 /* echoes each character typed as an asterisk (*) and a few other */
1294 /* necessary things so that the password typed in is not visible */
1295 /* */
1296 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
1297 *txtVerStr)
1299 /* XmTextVerifyCallbackStruct: */
1300 /* int reason; should be XmCR_MODIFYING_TEXT_VALUE */
1301 /* XEvent *event; points to XEvent that triggered the callback */
1302 /* Boolean doit; indicates whether action should be performed; setting */
1303 /* this to false negates the action */
1304 /* long currInsert, current position of insert cursor */
1305 /* newInsert; position user attempts to position the insert cursor */
1306 /* long startPos, starting position of the text to modify */
1307 /* endPos; ending position of the text to modify */
1308 /* XmTextBlock text; */
1310 /* XmTextBlock (used to pass text around): */
1311 /* char *ptr; points to text to be inserted */
1312 /* int length; Number of bytes (length) */
1313 /* XmTextFormat format; Representations format */
1315 /* XmTextFormat: either FMT8BIT or FMT16BIT */
1318 int numCharsTyped, i, j, pos;
1320 /* ensure text format is 8-bit characters */
1321 if (txtVerStr->text->format != FMT8BIT)
1322 return;
1324 /* verify assumptions */
1325 /* if (txtVerStr->endPos < txtVerStr->startPos)
1326 fprintf(stderr, "User password callback error: endPos < startPos\n");
1327 if (strlen(passTxt) == 0 && txtVerStr->endPos != 0)
1328 fprintf(stderr, "User password callback error: no txt, but end != 0\n");
1330 printf("passTxt = %s, startPos = %d, endPos = %d, txtBlkAddr = %d\n",
1331 passTxt, txtVerStr->startPos, txtVerStr->endPos, txtVerStr->text);
1332 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1333 printf(" string typed = %s, length = %d\n", txtVerStr->text->ptr,
1334 txtVerStr->text->length);
1336 /* If necessary, expand/compress passTxt and insert any new text */
1337 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1338 numCharsTyped = txtVerStr->text->length;
1339 else
1340 numCharsTyped = 0;
1341 /* numCharsTyped = # chars to insert (that user typed) */
1342 /* j = # chars to expand (+) or compress (-) the password string */
1343 j = numCharsTyped - (txtVerStr->endPos - txtVerStr->startPos);
1344 if (j > 0) /* expand case: start at ending null */
1345 for (pos = strlen(passTxt) + 1; pos >= txtVerStr->endPos; --pos)
1346 passTxt[pos+j] = passTxt[pos];
1347 if (j < 0) /* compress case */
1348 for (pos = txtVerStr->startPos + numCharsTyped;
1349 pos <= strlen(passTxt)+1; ++pos)
1350 passTxt[pos] = passTxt[pos-j];
1351 /* then copy text to be inserted into passTxt */
1352 for (pos = txtVerStr->startPos, i = 0; i < numCharsTyped; ++i) {
1353 passTxt[pos+i] = *(txtVerStr->text->ptr + i);
1354 /* Replace text typed by user with asterisks (*) */
1355 *(txtVerStr->text->ptr + i) = '*';
1357 /* printf(" Password string now = %s\n", passTxt); */
1361 ** Remove the white space (blanks and tabs) from a string
1363 static void removeWhiteSpace(char *string)
1365 char *outPtr = string;
1367 while (TRUE) {
1368 if (*string == 0) {
1369 *outPtr = 0;
1370 return;
1371 } else if (*string != ' ' && *string != '\t')
1372 *(outPtr++) = *(string++);
1373 else
1374 string++;
1379 ** Compares two strings and return TRUE if the two strings
1380 ** are the same, ignoring whitespace and case differences.
1382 static int stripCaseCmp(const char *str1, const char *str2)
1384 const char *c1, *c2;
1386 for (c1=str1, c2=str2; *c1!='\0' && *c2!='\0'; c1++, c2++) {
1387 while (*c1 == ' ' || *c1 == '\t')
1388 c1++;
1389 while (*c2 == ' ' || *c2 == '\t')
1390 c2++;
1391 if (toupper((unsigned char)*c1) != toupper((unsigned char)*c2))
1392 return FALSE;
1394 return *c1 == '\0' && *c2 == '\0';
1397 static void warnHandlerCB(String message)
1399 if (strstr(message, "XtRemoveGrab"))
1400 return;
1401 if (strstr(message, "Attempt to remove non-existant passive grab"))
1402 return;
1403 fputs(message, stderr);
1404 fputc('\n', stderr);
1407 static XModifierKeymap *getKeyboardMapping(Display *display) {
1408 static XModifierKeymap *keyboardMap = NULL;
1410 if (keyboardMap == NULL) {
1411 keyboardMap = XGetModifierMapping(display);
1413 return(keyboardMap);
1417 ** get mask for a modifier
1421 static Modifiers findModifierMapping(Display *display, KeyCode keyCode) {
1422 int i, j;
1423 KeyCode *mapentry;
1424 XModifierKeymap *modMap = getKeyboardMapping(display);
1426 if (modMap == NULL || keyCode == 0) {
1427 return(0);
1430 mapentry = modMap->modifiermap;
1431 for (i = 0; i < 8; ++i) {
1432 for (j = 0; j < (modMap->max_keypermod); ++j) {
1433 if (keyCode == *mapentry) {
1434 return(1 << ((mapentry - modMap->modifiermap) / modMap->max_keypermod));
1436 ++mapentry;
1439 return(0);
1442 static Modifiers getNumLockModMask(Display *display) {
1443 static int numLockMask = -1;
1445 if (numLockMask == -1) {
1446 numLockMask = findModifierMapping(display, XKeysymToKeycode(display, XK_Num_Lock));
1448 return(numLockMask);
1452 ** Grab a key regardless of caps-lock and other silly latching keys.
1456 static void reallyGrabAKey(Widget dialog, int keyCode, Modifiers mask) {
1457 Modifiers numLockMask = getNumLockModMask(XtDisplay(dialog));
1459 XtGrabKey(dialog, keyCode, mask, True, GrabModeAsync, GrabModeAsync);
1460 XtGrabKey(dialog, keyCode, mask|LockMask, True, GrabModeAsync, GrabModeAsync);
1461 if (numLockMask && numLockMask != LockMask) {
1462 XtGrabKey(dialog, keyCode, mask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1463 XtGrabKey(dialog, keyCode, mask|LockMask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1468 ** Part of dialog mnemonic processing. Search the widget tree under w
1469 ** for widgets with mnemonics. When found, add a passive grab to the
1470 ** dialog widget for the mnemonic character, thus directing mnemonic
1471 ** events to the dialog widget.
1473 static void addMnemonicGrabs(Widget dialog, Widget w, int unmodifiedToo)
1475 char mneString[2];
1476 WidgetList children;
1477 Cardinal numChildren;
1478 int i, isMenu;
1479 KeySym mnemonic = '\0';
1480 unsigned char rowColType;
1481 unsigned int keyCode;
1483 if (XtIsComposite(w)) {
1484 if (XtClass(w) == xmRowColumnWidgetClass) {
1485 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1486 isMenu = rowColType != XmWORK_AREA;
1487 } else
1488 isMenu = False;
1489 if (!isMenu) {
1490 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1491 &numChildren, NULL);
1492 for (i=0; i<numChildren; i++)
1493 addMnemonicGrabs(dialog, children[i], unmodifiedToo);
1495 } else {
1496 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1497 if (mnemonic != XK_VoidSymbol && mnemonic != '\0') {
1498 mneString[0] = mnemonic; mneString[1] = '\0';
1499 keyCode = XKeysymToKeycode(XtDisplay(dialog),
1500 XStringToKeysym(mneString));
1501 reallyGrabAKey(dialog, keyCode, Mod1Mask);
1502 if (unmodifiedToo)
1503 reallyGrabAKey(dialog, keyCode, 0);
1509 ** Callback routine for dialog mnemonic processing.
1511 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event)
1513 findAndActivateMnemonic(w, event->keycode);
1517 ** Look for a widget in the widget tree w, with a mnemonic matching
1518 ** keycode. When one is found, simulate a button press on that widget
1519 ** and give it the keyboard focus. If the mnemonic is on a label,
1520 ** look in the userData field of the label to see if it points to
1521 ** another widget, and give that the focus. This routine is just
1522 ** sufficient for NEdit, no doubt it will need to be extended for
1523 ** mnemonics on widgets other than just buttons and text fields.
1525 static void findAndActivateMnemonic(Widget w, unsigned int keycode)
1527 WidgetList children;
1528 Cardinal numChildren;
1529 int i, isMenu;
1530 KeySym mnemonic = '\0';
1531 char mneString[2];
1532 Widget userData;
1533 unsigned char rowColType;
1535 if (XtIsComposite(w)) {
1536 if (XtClass(w) == xmRowColumnWidgetClass) {
1537 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1538 isMenu = rowColType != XmWORK_AREA;
1539 } else
1540 isMenu = False;
1541 if (!isMenu) {
1542 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1543 &numChildren, NULL);
1544 for (i=0; i<numChildren; i++)
1545 findAndActivateMnemonic(children[i], keycode);
1547 } else {
1548 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1549 if (mnemonic != '\0') {
1550 mneString[0] = mnemonic; mneString[1] = '\0';
1551 if (XKeysymToKeycode(XtDisplay(XtParent(w)),
1552 XStringToKeysym(mneString)) == keycode) {
1553 if (XtClass(w) == xmLabelWidgetClass ||
1554 XtClass(w) == xmLabelGadgetClass) {
1555 XtVaGetValues(w, XmNuserData, &userData, NULL);
1556 if (userData!=NULL && XtIsWidget(userData) &&
1557 XmIsTraversable(userData))
1558 XmProcessTraversal(userData, XmTRAVERSE_CURRENT);
1559 } else if (XmIsTraversable(w)) {
1560 XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1561 SimulateButtonPress(w);
1569 ** Part of workaround for Motif Caps/Num Lock bug. Search the widget tree
1570 ** under w for widgets with accelerators. When found, add three passive
1571 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1572 ** Lock, one for Num Lock, and one for both, thus directing lock +
1573 ** accelerator events to topWidget.
1575 static void addAccelGrabs(Widget topWidget, Widget w)
1577 WidgetList children;
1578 Widget menu;
1579 Cardinal numChildren;
1580 int i;
1582 if (XtIsComposite(w)) {
1583 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1584 &numChildren, NULL);
1585 for (i=0; i<numChildren; i++)
1586 addAccelGrabs(topWidget, children[i]);
1587 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1588 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1589 if (menu != NULL)
1590 addAccelGrabs(topWidget, menu);
1591 } else
1592 addAccelGrab(topWidget, w);
1596 ** Grabs the key + modifier defined in the widget's accelerator resource,
1597 ** in combination with the Caps Lock and Num Lock accelerators.
1599 static void addAccelGrab(Widget topWidget, Widget w)
1601 char *accelString = NULL;
1602 KeySym keysym;
1603 unsigned int modifiers;
1604 Modifiers numLockMask = getNumLockModMask(XtDisplay(topWidget));
1606 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1607 if (accelString == NULL || *accelString == '\0')
1608 return;
1610 if (!parseAccelString(XtDisplay(topWidget), accelString, &keysym, &modifiers))
1611 return;
1612 XtGrabKey(topWidget, XKeysymToKeycode(XtDisplay(topWidget), keysym),
1613 modifiers | LockMask, True, GrabModeAsync, GrabModeAsync);
1614 if (numLockMask && numLockMask != LockMask) {
1615 XtGrabKey(topWidget, XKeysymToKeycode(XtDisplay(topWidget), keysym),
1616 modifiers | numLockMask, True, GrabModeAsync, GrabModeAsync);
1617 XtGrabKey(topWidget, XKeysymToKeycode(XtDisplay(topWidget), keysym),
1618 modifiers | LockMask | numLockMask, True, GrabModeAsync, GrabModeAsync);
1623 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1624 ** Returns TRUE if the parse was successful, FALSE, if not.
1626 static int parseAccelString(Display *display, const char *string, KeySym *keySym,
1627 unsigned int *modifiers)
1629 #define N_MODIFIERS 12
1630 /*... Is NumLock always Mod3? */
1631 static char *modifierNames[N_MODIFIERS] = {"Ctrl", "Shift", "Alt", "Mod2",
1632 "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1633 "Button5"};
1634 static unsigned int modifierMasks[N_MODIFIERS] = {ControlMask, ShiftMask,
1635 Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, Button1Mask, Button2Mask,
1636 Button3Mask, Button4Mask, Button5Mask};
1637 Modifiers numLockMask = getNumLockModMask(display);
1638 char modStr[MAX_ACCEL_LEN];
1639 char evtStr[MAX_ACCEL_LEN];
1640 char keyStr[MAX_ACCEL_LEN];
1641 const char *c, *evtStart, *keyStart;
1642 int i;
1644 if (strlen(string) >= MAX_ACCEL_LEN)
1645 return FALSE;
1647 /* Get the modifier part */
1648 for (c = string; *c != '<'; c++)
1649 if (*c == '\0')
1650 return FALSE;
1651 strncpy(modStr, string, c - string);
1652 modStr[c - string] = '\0';
1654 /* Verify the <key> or <keypress> part */
1655 evtStart = c;
1656 for ( ; *c != '>'; c++)
1657 if (*c == '\0')
1658 return FALSE;
1659 c++;
1660 strncpy(evtStr, evtStart, c - evtStart);
1661 evtStr[c - evtStart] = '\0';
1662 if (!stripCaseCmp(evtStr, "<key>") && !stripCaseCmp(evtStr, "<keypress>"))
1663 return FALSE;
1665 /* Get the keysym part */
1666 keyStart = c;
1667 for ( ; *c != '\0'; c++);
1668 strncpy(keyStr, keyStart, c - keyStart);
1669 keyStr[c - keyStart] = '\0';
1670 *keySym = XStringToKeysym(keyStr);
1672 /* Parse the modifier part */
1673 *modifiers = 0;
1674 c = modStr;
1675 while (*c != '\0') {
1676 while (*c == ' ' || *c == '\t')
1677 c++;
1678 if (*c == '\0')
1679 break;
1680 for (i = 0; i < N_MODIFIERS; i++) {
1681 if (!strncmp(c, modifierNames[i], strlen(modifierNames[i]))) {
1682 c += strlen(modifierNames[i]);
1683 if (modifierMasks[i] != numLockMask) {
1684 *modifiers |= modifierMasks[i];
1686 break;
1689 if (i == N_MODIFIERS)
1690 return FALSE;
1693 return TRUE;
1697 ** Event handler for patching around Motif's lock + accelerator problem.
1698 ** Looks for a menu item in the patched menu hierarchy and invokes its
1699 ** ArmAndActivate action.
1701 static void lockCB(Widget w, XtPointer callData, XEvent *event,
1702 Boolean *continueDispatch)
1704 Modifiers numLockMask = getNumLockModMask(XtDisplay(w));
1705 Widget topMenuWidget = (Widget)callData;
1706 *continueDispatch = TRUE;
1707 if (!(((XKeyEvent *)event)->state & (LockMask | numLockMask)))
1708 return;
1710 if (!findAndActivateAccel(topMenuWidget, ((XKeyEvent *)event)->keycode,
1711 ((XKeyEvent *)event)->state & ~(LockMask | numLockMask), event))
1712 *continueDispatch = FALSE;
1716 ** Search through menu hierarchy under w and look for a button with
1717 ** accelerator matching keyCode + modifiers, and do its action
1719 static int findAndActivateAccel(Widget w, unsigned int keyCode,
1720 unsigned int modifiers, XEvent *event)
1723 WidgetList children;
1724 Widget menu;
1725 Cardinal numChildren;
1726 int i;
1727 char *accelString = NULL;
1728 KeySym keysym;
1729 unsigned int mods;
1731 if (XtIsComposite(w)) {
1732 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1733 &numChildren, NULL);
1734 for (i=0; i<numChildren; i++)
1735 if (findAndActivateAccel(children[i], keyCode, modifiers, event))
1736 return TRUE;
1737 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1738 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1739 if (menu != NULL)
1740 if (findAndActivateAccel(menu, keyCode, modifiers, event))
1741 return TRUE;
1742 } else {
1743 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1744 if (accelString != NULL && *accelString != '\0') {
1745 if (!parseAccelString(XtDisplay(w), accelString, &keysym, &mods))
1746 return FALSE;
1747 if (keyCode == XKeysymToKeycode(XtDisplay(w), keysym) &&
1748 modifiers == mods) {
1749 if (XtIsSensitive(w)) {
1750 XtCallActionProc(w, "ArmAndActivate", event, NULL, 0);
1751 return TRUE;
1756 return FALSE;