Applied the "OpenMotif sticky radio button" workaround to all radio buttons.
[nedit.git] / util / misc.c
bloba6001cf202e88d6dafd6940177dcabd3c387edb6
1 static const char CVSID[] = "$Id: misc.c,v 1.61 2004/01/16 09:18:28 edg 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 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 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "misc.h"
34 #include "DialogF.h"
36 #include <stdlib.h>
37 #include <string.h>
38 #include <stdarg.h>
39 #include <ctype.h>
40 #include <stdio.h>
41 #include <time.h>
43 #ifdef __unix__
44 #include <sys/time.h>
45 #endif
47 #ifdef VMS
48 #include <types.h>
49 #include <unixio.h>
50 #include <file.h>
51 #endif /*VMS*/
53 #include <X11/Intrinsic.h>
54 #include <X11/Xatom.h>
55 #include <X11/keysym.h>
56 #include <X11/keysymdef.h>
57 #include <Xm/Xm.h>
58 #include <Xm/Label.h>
59 #include <Xm/LabelG.h>
60 #include <Xm/ToggleB.h>
61 #include <Xm/PushB.h>
62 #include <Xm/Separator.h>
63 #include <Xm/RowColumn.h>
64 #include <Xm/CascadeB.h>
65 #include <Xm/AtomMgr.h>
66 #include <Xm/Protocols.h>
67 #include <Xm/Text.h>
68 #include <Xm/MessageB.h>
69 #include <Xm/DialogS.h>
70 #include <Xm/SelectioB.h>
71 #include <Xm/Form.h>
72 #include <Xm/FileSB.h>
73 #include <Xm/ScrolledW.h>
74 #include <Xm/PrimitiveP.h>
76 #ifdef HAVE_DEBUG_H
77 #include "../debug.h"
78 #endif
80 /* structure for passing history-recall data to callbacks */
81 typedef struct {
82 char ***list;
83 int *nItems;
84 int index;
85 } histInfo;
87 typedef Widget (*MotifDialogCreationCall)(Widget, String, ArgList, Cardinal);
89 /* Maximum size of a history-recall list. Typically never invoked, since
90 user must first make this many entries in the text field, limited for
91 safety, to the maximum reasonable number of times user can hit up-arrow
92 before carpal tunnel syndrome sets in */
93 #define HISTORY_LIST_TRIM_TO 1000
94 #define HISTORY_LIST_MAX 2000
96 /* flags to enable/disable delete key remapping and pointer centered dialogs */
97 static int RemapDeleteEnabled = True;
98 static int PointerCenteredDialogsEnabled = False;
100 /* bitmap and mask for waiting (wrist-watch) cursor */
101 #define watch_x_hot 7
102 #define watch_y_hot 7
103 #define watch_width 16
104 #define watch_height 16
105 static unsigned char watch_bits[] = {
106 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0x10, 0x08, 0x08, 0x11,
107 0x04, 0x21, 0x04, 0x21, 0xe4, 0x21, 0x04, 0x20, 0x08, 0x10, 0x10, 0x08,
108 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07
110 #define watch_mask_width 16
111 #define watch_mask_height 16
112 static unsigned char watch_mask_bits[] = {
113 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf8, 0x1f, 0xfc, 0x3f,
114 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f,
115 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f
118 static void addMnemonicGrabs(Widget addTo, Widget w, int unmodified);
119 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event);
120 static void findAndActivateMnemonic(Widget w, unsigned int keycode);
121 static void addAccelGrabs(Widget topWidget, Widget w);
122 static void addAccelGrab(Widget topWidget, Widget w);
123 static int parseAccelString(Display *display, const char *string, KeySym *keysym,
124 unsigned int *modifiers);
125 static void lockCB(Widget w, XtPointer callData, XEvent *event,
126 Boolean *continueDispatch);
127 static int findAndActivateAccel(Widget w, unsigned int keyCode,
128 unsigned int modifiers, XEvent *event);
129 static void removeWhiteSpace(char *string);
130 static int stripCaseCmp(const char *str1, const char *str2);
131 static void warnHandlerCB(String message);
132 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
133 *txtVerStr);
134 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
135 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
136 Boolean *continueDispatch);
137 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
138 Cardinal *argcount);
139 static Widget addParentVisArgsAndCall(MotifDialogCreationCall callRoutine,
140 Widget parent, char *name, ArgList arglist, Cardinal argcount);
141 static void scrollDownAP(Widget w, XEvent *event, String *args,
142 Cardinal *nArgs);
143 static void scrollUpAP(Widget w, XEvent *event, String *args,
144 Cardinal *nArgs);
145 static void pageDownAP(Widget w, XEvent *event, String *args,
146 Cardinal *nArgs);
147 static void pageUpAP(Widget w, XEvent *event, String *args,
148 Cardinal *nArgs);
151 ** Set up closeCB to be called when the user selects close from the
152 ** window menu. The close menu item usually activates f.kill which
153 ** sends a WM_DELETE_WINDOW protocol request for the window.
155 void AddMotifCloseCallback(Widget shell, XtCallbackProc closeCB, void *arg)
157 static Atom wmpAtom, dwAtom = 0;
158 Display *display = XtDisplay(shell);
160 /* deactivate the built in delete response of killing the application */
161 XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);
163 /* add a delete window protocol callback instead */
164 if (dwAtom == 0) {
165 wmpAtom = XmInternAtom(display, "WM_PROTOCOLS", FALSE);
166 dwAtom = XmInternAtom(display, "WM_DELETE_WINDOW", FALSE);
168 XmAddProtocolCallback(shell, wmpAtom, dwAtom, closeCB, arg);
172 ** Motif still generates spurious passive grab warnings on both IBM and SGI
173 ** This routine suppresses them.
174 ** (amai, 20011121:)
175 ** And triggers an annoying message on DEC systems on alpha ->
176 ** See Xt sources, xc/lib/Xt/Error.c:DefaultMsg()):
177 ** actually for some obscure reasons they check for XtError/Warning
178 ** handlers being installed when running as a root process!
179 ** Since this handler doesn't help on non-effected systems we should only
180 ** use it if necessary.
182 void SuppressPassiveGrabWarnings(void)
184 #if !defined(__alpha) && !defined(__EMX__)
185 XtSetWarningHandler(warnHandlerCB);
186 #endif
190 ** This routine kludges around the problem of backspace not being mapped
191 ** correctly when Motif is used between a server with a delete key in
192 ** the traditional typewriter backspace position and a client that
193 ** expects a backspace key in that position. Though there are three
194 ** distinct levels of key re-mapping in effect when a user is running
195 ** a Motif application, none of these is really appropriate or effective
196 ** for eliminating the delete v.s. backspace problem. Our solution is,
197 ** sadly, to eliminate the forward delete functionality of the delete key
198 ** in favor of backwards delete for both keys. So as not to prevent the
199 ** user or the application from applying other translation table re-mapping,
200 ** we apply re-map the key as a post-processing step, applied after widget
201 ** creation. As a result, the re-mapping necessarily becomes embedded
202 ** throughout an application (wherever text widgets are created), and
203 ** within library routines, including the Nirvana utility library. To
204 ** make this remapping optional, the SetDeleteRemap function provides a
205 ** way for an application to turn this functionality on and off. It is
206 ** recommended that applications that use this routine provide an
207 ** application resource called remapDeleteKey so savvy users can get
208 ** their forward delete functionality back.
210 void RemapDeleteKey(Widget w)
212 static XtTranslations table = NULL;
213 static char *translations =
214 "~Shift~Ctrl~Meta~Alt<Key>osfDelete: delete-previous-character()\n";
216 if (RemapDeleteEnabled) {
217 if (table == NULL)
218 table = XtParseTranslationTable(translations);
219 XtOverrideTranslations(w, table);
223 void SetDeleteRemap(int state)
225 RemapDeleteEnabled = state;
230 ** The routine adds the passed in top-level Widget's window to our
231 ** window group. On the first call a dummy unmapped window will
232 ** be created to be our leader. This must not be called before the
233 ** Widget has be realized and should be called before the window is
234 ** mapped.
236 void SetWindowGroup(Widget shell) {
237 static int firstTime = True;
238 static Window groupLeader;
239 Display *display = XtDisplay(shell);
240 XWMHints *wmHints;
242 if (firstTime) {
243 /* Create a dummy window to be the group leader for our windows */
244 String name, class;
245 XClassHint *classHint;
247 groupLeader = XCreateSimpleWindow(display,
248 RootWindow(display, DefaultScreen(display)),
249 1, 1, 1, 1, 0, 0, 0);
251 /* Set it's class hint so it will be identified correctly by the
252 window manager */
253 XtGetApplicationNameAndClass(display, &name, &class);
254 classHint = XAllocClassHint();
255 classHint->res_name = name;
256 classHint->res_class = class;
257 XSetClassHint(display, groupLeader, classHint);
258 XFree(classHint);
260 firstTime = False;
263 /* Set the window group hint for this shell's window */
264 wmHints = XGetWMHints(display, XtWindow(shell));
265 wmHints->window_group = groupLeader;
266 wmHints->flags |= WindowGroupHint;
267 XSetWMHints(display, XtWindow(shell), wmHints);
268 XFree(wmHints);
272 ** This routine resolves a window manager protocol incompatibility between
273 ** the X toolkit and several popular window managers. Using this in place
274 ** of XtRealizeWidget will realize the window in a way which allows the
275 ** affected window managers to apply their own placement strategy to the
276 ** window, as opposed to forcing the window to a specific location.
278 ** One of the hints in the WM_NORMAL_HINTS protocol, PPlacement, gets set by
279 ** the X toolkit (probably part of the Core or Shell widget) when a shell
280 ** widget is realized to the value stored in the XmNx and XmNy resources of the
281 ** Core widget. While callers can set these values, there is no "unset" value
282 ** for these resources. On systems which are more Motif aware, a PPosition
283 ** hint of 0,0, which is the default for XmNx and XmNy, is interpreted as,
284 ** "place this as if no hints were specified". Unfortunately the fvwm family
285 ** of window managers, which are now some of the most popular, interpret this
286 ** as "place this window at (0,0)". This routine intervenes between the
287 ** realizing and the mapping of the window to remove the inappropriate
288 ** PPlacement hint.
290 void RealizeWithoutForcingPosition(Widget shell)
292 XSizeHints *hints = XAllocSizeHints();
293 long suppliedHints;
294 Boolean mappedWhenManaged;
296 /* Temporarily set value of XmNmappedWhenManaged
297 to stop the window from popping up right away */
298 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
299 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
301 /* Realize the widget in unmapped state */
302 XtRealizeWidget(shell);
304 /* Get rid of the incorrect WMNormal hint */
305 if (XGetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints,
306 &suppliedHints)) {
307 hints->flags &= ~PPosition;
308 XSetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints);
310 XFree(hints);
312 /* Set WindowGroupHint so the NEdit icons can be grouped; this
313 seems to be necessary starting with Gnome 2.0 */
314 SetWindowGroup(shell);
316 /* Map the widget */
317 XtMapWidget(shell);
319 /* Restore the value of XmNmappedWhenManaged */
320 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
324 ** Older X applications and X servers were mostly designed to operate with
325 ** visual class PseudoColor, because older displays were at most 8 bits
326 ** deep. Modern X servers, however, usually support 24 bit depth and other
327 ** color models. Sun (and others?) still sets their default visual to
328 ** 8-bit PseudoColor, because some of their X applications don't work
329 ** properly with the other color models. The problem with PseudoColor, of
330 ** course, is that users run out of colors in the default colormap, and if
331 ** they install additional colormaps for individual applications, colors
332 ** flash and change weirdly when you change your focus from one application
333 ** to another.
335 ** In addition to the poor choice of default, a design flaw in Xt makes it
336 ** impossible even for savvy users to specify the XtNvisual resource to
337 ** switch to a deeper visual. The problem is that the colormap resource is
338 ** processed independently of the visual resource, and usually results in a
339 ** colormap for the default visual rather than for the user-selected one.
341 ** This routine should be called before creating a shell widget, to
342 ** pre-process the visual, depth, and colormap resources, and return the
343 ** proper values for these three resources to be passed to XtAppCreateShell.
344 ** Applications which actually require a particular color model (i.e. for
345 ** doing color table animation or dynamic color assignment) should not use
346 ** this routine.
348 ** Note that a consequence of using the "best" as opposed to the default
349 ** visual is that some color resources are still converted with the default
350 ** visual (particularly *background), and these must be avoided by widgets
351 ** which are allowed to handle any visual.
353 ** Returns True if the best visual is the default, False otherwise.
355 Boolean FindBestVisual(Display *display, const char *appName, const char *appClass,
356 Visual **visual, int *depth, Colormap *colormap)
358 char rsrcName[256], rsrcClass[256], *valueString, *type, *endPtr;
359 XrmValue value;
360 int screen = DefaultScreen(display);
361 int reqDepth = -1;
362 long reqID = -1; /* should hold a 'VisualID' and a '-1' ... */
363 int reqClass = -1;
364 int installColormap = FALSE;
365 int maxDepth, bestClass, bestVisual, nVis, i, j;
366 XVisualInfo visTemplate, *visList = NULL;
367 static Visual *cachedVisual = NULL;
368 static Colormap cachedColormap;
369 static int cachedDepth = 0;
370 int bestClasses[] = {StaticGray, GrayScale, StaticColor, PseudoColor,
371 DirectColor, TrueColor};
373 /* If results have already been computed, just return them */
374 if (cachedVisual != NULL) {
375 *visual = cachedVisual;
376 *depth = cachedDepth;
377 *colormap = cachedColormap;
378 return (*visual == DefaultVisual(display, screen));
381 /* Read the visualID and installColormap resources for the application.
382 visualID can be specified either as a number (the visual id as
383 shown by xdpyinfo), as a visual class name, or as Best or Default. */
384 sprintf(rsrcName,"%s.%s", appName, "visualID");
385 sprintf(rsrcClass, "%s.%s", appClass, "VisualID");
386 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
387 &value)) {
388 valueString = value.addr;
389 reqID = (int)strtol(valueString, &endPtr, 0);
390 if (endPtr == valueString) {
391 reqID = -1;
392 if (stripCaseCmp(valueString, "Default"))
393 reqID = DefaultVisual(display, screen)->visualid;
394 else if (stripCaseCmp(valueString, "StaticGray"))
395 reqClass = StaticGray;
396 else if (stripCaseCmp(valueString, "StaticColor"))
397 reqClass = StaticColor;
398 else if (stripCaseCmp(valueString, "TrueColor"))
399 reqClass = TrueColor;
400 else if (stripCaseCmp(valueString, "GrayScale"))
401 reqClass = GrayScale;
402 else if (stripCaseCmp(valueString, "PseudoColor"))
403 reqClass = PseudoColor;
404 else if (stripCaseCmp(valueString, "DirectColor"))
405 reqClass = DirectColor;
406 else if (!stripCaseCmp(valueString, "Best"))
407 fprintf(stderr, "Invalid visualID resource value\n");
410 sprintf(rsrcName,"%s.%s", appName, "installColormap");
411 sprintf(rsrcClass, "%s.%s", appClass, "InstallColormap");
412 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
413 &value)) {
414 if (stripCaseCmp(value.addr, "Yes") || stripCaseCmp(value.addr, "True"))
415 installColormap = TRUE;
418 visTemplate.screen = screen;
420 /* Generate a list of visuals to consider. (Note, vestigial code for
421 user-requested visual depth is left in, just in case that function
422 might be needed again, but it does nothing) */
423 if (reqID != -1) {
424 visTemplate.visualid = reqID;
425 visList = XGetVisualInfo(display, VisualScreenMask|VisualIDMask,
426 &visTemplate, &nVis);
427 if (visList == NULL)
428 fprintf(stderr, "VisualID resource value not valid\n");
430 if (visList == NULL && reqClass != -1 && reqDepth != -1) {
431 visTemplate.class = reqClass;
432 visTemplate.depth = reqDepth;
433 visList = XGetVisualInfo(display,
434 VisualScreenMask| VisualClassMask | VisualDepthMask,
435 &visTemplate, &nVis);
436 if (visList == NULL)
437 fprintf(stderr, "Visual class/depth combination not available\n");
439 if (visList == NULL && reqClass != -1) {
440 visTemplate.class = reqClass;
441 visList = XGetVisualInfo(display, VisualScreenMask|VisualClassMask,
442 &visTemplate, &nVis);
443 if (visList == NULL)
444 fprintf(stderr,
445 "Visual Class from resource \"visualID\" not available\n");
447 if (visList == NULL && reqDepth != -1) {
448 visTemplate.depth = reqDepth;
449 visList = XGetVisualInfo(display, VisualScreenMask|VisualDepthMask,
450 &visTemplate, &nVis);
451 if (visList == NULL)
452 fprintf(stderr, "Requested visual depth not available\n");
454 if (visList == NULL) {
455 visList = XGetVisualInfo(display, VisualScreenMask, &visTemplate, &nVis);
456 if (visList == NULL) {
457 fprintf(stderr, "Internal Error: no visuals available?\n");
458 *visual = DefaultVisual(display, screen);
459 *depth = DefaultDepth(display, screen);
460 *colormap = DefaultColormap(display, screen);
461 return True;
465 /* Choose among the visuals in the candidate list. Prefer maximum
466 depth first then matching default, then largest value of bestClass
467 (I'm not sure whether we actually care about class) */
468 maxDepth = 0;
469 bestClass = 0;
470 bestVisual = 0;
471 for (i=0; i < nVis; i++) {
472 if (visList[i].depth > maxDepth) {
473 maxDepth = visList[i].depth;
474 bestClass = 0;
475 bestVisual = i;
477 if (visList[i].depth == maxDepth) {
478 if (visList[i].visual == DefaultVisual(display, screen))
479 bestVisual = i;
480 if (visList[bestVisual].visual != DefaultVisual(display, screen)) {
481 for (j = 0; j < (int)XtNumber(bestClasses); j++) {
482 if (visList[i].class == bestClasses[j] && j > bestClass) {
483 bestClass = j;
484 bestVisual = i;
490 *visual = cachedVisual = visList[bestVisual].visual;
491 *depth = cachedDepth = visList[bestVisual].depth;
493 /* If the chosen visual is not the default, it needs a colormap allocated */
494 if (*visual == DefaultVisual(display, screen) && !installColormap)
495 *colormap = cachedColormap = DefaultColormap(display, screen);
496 else {
497 *colormap = cachedColormap = XCreateColormap(display,
498 RootWindow(display, screen), cachedVisual, AllocNone);
499 XInstallColormap(display, cachedColormap);
501 /* printf("Chose visual with depth %d, class %d, colormap %ld, id 0x%x\n",
502 visList[bestVisual].depth, visList[bestVisual].class,
503 *colormap, cachedVisual->visualid); */
504 /* Fix memory leak */
505 if (visList != NULL) {
506 XFree(visList);
509 return (*visual == DefaultVisual(display, screen));
513 ** If you want to use a non-default visual with Motif, shells all have to be
514 ** created with that visual, depth, and colormap, even if the parent has them
515 ** set up properly. Substituting these routines, will append visual args copied
516 ** from the parent widget (CreatePopupMenu and CreatePulldownMenu), or from the
517 ** best visual, obtained via FindBestVisual above (CreateShellWithBestVis).
519 Widget CreateDialogShell(Widget parent, char *name,
520 ArgList arglist, Cardinal argcount)
522 return addParentVisArgsAndCall(XmCreateDialogShell, parent, name, arglist,
523 argcount);
527 Widget CreatePopupMenu(Widget parent, char *name, ArgList arglist,
528 Cardinal argcount)
530 return addParentVisArgsAndCall(XmCreatePopupMenu, parent, name,
531 arglist, argcount);
535 Widget CreatePulldownMenu(Widget parent, char *name,
536 ArgList arglist, Cardinal argcount)
538 return addParentVisArgsAndCall(XmCreatePulldownMenu, parent, name, arglist,
539 argcount);
543 Widget CreatePromptDialog(Widget parent, char *name,
544 ArgList arglist, Cardinal argcount)
546 return addParentVisArgsAndCall(XmCreatePromptDialog, parent, name, arglist,
547 argcount);
551 Widget CreateSelectionDialog(Widget parent, char *name,
552 ArgList arglist, Cardinal argcount)
554 Widget dialog = addParentVisArgsAndCall(XmCreateSelectionDialog, parent, name,
555 arglist, argcount);
556 AddMouseWheelSupport(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST));
557 return dialog;
561 Widget CreateFormDialog(Widget parent, char *name,
562 ArgList arglist, Cardinal argcount)
564 return addParentVisArgsAndCall(XmCreateFormDialog, parent, name, arglist,
565 argcount);
569 Widget CreateFileSelectionDialog(Widget parent, char *name,
570 ArgList arglist, Cardinal argcount)
572 Widget dialog = addParentVisArgsAndCall(XmCreateFileSelectionDialog, parent,
573 name, arglist, argcount);
575 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_LIST));
576 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_DIR_LIST));
577 return dialog;
581 Widget CreateQuestionDialog(Widget parent, char *name,
582 ArgList arglist, Cardinal argcount)
584 return addParentVisArgsAndCall(XmCreateQuestionDialog, parent, name,
585 arglist, argcount);
589 Widget CreateMessageDialog(Widget parent, char *name,
590 ArgList arglist, Cardinal argcount)
592 return addParentVisArgsAndCall(XmCreateMessageDialog, parent, name,
593 arglist, argcount);
597 Widget CreateErrorDialog(Widget parent, char *name,
598 ArgList arglist, Cardinal argcount)
600 return addParentVisArgsAndCall(XmCreateErrorDialog, parent, name, arglist,
601 argcount);
604 Widget CreateWidget(Widget parent, const char *name, WidgetClass class,
605 ArgList arglist, Cardinal argcount)
607 Widget result;
608 ArgList al = addParentVisArgs(parent, arglist, &argcount);
609 result = XtCreateWidget(name, class, parent, al, argcount);
610 XtFree((char *)al);
611 return result;
614 Widget CreateShellWithBestVis(String appName, String appClass,
615 WidgetClass class, Display *display, ArgList args, Cardinal nArgs)
617 Visual *visual;
618 int depth;
619 Colormap colormap;
620 ArgList al;
621 Cardinal ac = nArgs;
622 Widget result;
624 FindBestVisual(display, appName, appClass, &visual, &depth, &colormap);
625 al = (ArgList)XtMalloc(sizeof(Arg) * (nArgs + 3));
626 if (nArgs != 0)
627 memcpy(al, args, sizeof(Arg) * nArgs);
628 XtSetArg(al[ac], XtNvisual, visual); ac++;
629 XtSetArg(al[ac], XtNdepth, depth); ac++;
630 XtSetArg(al[ac], XtNcolormap, colormap); ac++;
631 result = XtAppCreateShell(appName, appClass, class, display, al, ac);
632 XtFree((char *)al);
633 return result;
637 Widget CreatePopupShellWithBestVis(String shellName, WidgetClass class,
638 Widget parent, ArgList arglist, Cardinal argcount)
640 Widget result;
641 ArgList al = addParentVisArgs(parent, arglist, &argcount);
642 result = XtCreatePopupShell(shellName, class, parent, al, argcount);
643 XtFree((char *)al);
644 return result;
648 ** Extends an argument list for widget creation with additional arguments
649 ** for visual, colormap, and depth. The original argument list is not altered
650 ** and it's the caller's responsability to free the returned list.
652 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
653 Cardinal *argcount)
655 Visual *visual;
656 int depth;
657 Colormap colormap;
658 ArgList al;
659 Widget parentShell = parent;
661 /* Find the application/dialog/menu shell at the top of the widget
662 hierarchy, which has the visual resource being used */
663 while (True) {
664 if (XtIsShell(parentShell))
665 break;
666 if (parentShell == NULL) {
667 fprintf(stderr, "failed to find shell\n");
668 exit(EXIT_FAILURE);
670 parentShell = XtParent(parentShell);
673 /* Add the visual, depth, and colormap resources to the argument list */
674 XtVaGetValues(parentShell, XtNvisual, &visual, XtNdepth, &depth,
675 XtNcolormap, &colormap, NULL);
676 al = (ArgList)XtMalloc(sizeof(Arg) * ((*argcount) + 3));
677 if ((*argcount) != 0)
678 memcpy(al, arglist, sizeof(Arg) * (*argcount));
679 XtSetArg(al[*argcount], XtNvisual, visual); (*argcount)++;
680 XtSetArg(al[*argcount], XtNdepth, depth); (*argcount)++;
681 XtSetArg(al[*argcount], XtNcolormap, colormap); (*argcount)++;
682 return al;
687 ** Calls one of the Motif widget creation routines, splicing in additional
688 ** arguments for visual, colormap, and depth.
690 static Widget addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,
691 Widget parent, char *name, ArgList arglist, Cardinal argcount)
693 Widget result;
694 ArgList al = addParentVisArgs(parent, arglist, &argcount);
695 result = (*createRoutine)(parent, name, al, argcount);
696 XtFree((char *)al);
697 return result;
701 ** ManageDialogCenteredOnPointer is used in place of XtManageChild for
702 ** popping up a dialog to enable the dialog to be centered under the
703 ** mouse pointer. Whether it pops up the dialog centered under the pointer
704 ** or in its default position centered over the parent widget, depends on
705 ** the value set in the SetPointerCenteredDialogs call.
706 ** Additionally, this function constrains the size of the dialog to the
707 ** screen's size, to avoid insanely wide dialogs with obscured buttons.
709 void ManageDialogCenteredOnPointer(Widget dialogChild)
711 Widget shell = XtParent(dialogChild);
712 Window root, child;
713 unsigned int mask;
714 unsigned int width, height, borderWidth, depth;
715 int x, y, winX, winY, maxX, maxY, maxWidth, maxHeight;
716 Dimension xtWidth, xtHeight;
717 Boolean mappedWhenManaged;
718 static const int slop = 25;
720 /* Temporarily set value of XmNmappedWhenManaged
721 to stop the dialog from popping up right away */
722 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
723 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
725 /* Ensure that the dialog doesn't get wider/taller than the screen.
726 We use a hard-coded "slop" size because we don't know the border
727 width until it's on screen, and by then it's too late. Putting
728 this before managing the widgets allows it to get the geometry
729 right on the first pass and also prevents the user from
730 accidentally resizing too wide. */
731 maxWidth = XtScreen(shell)->width - slop;
732 maxHeight = XtScreen(shell)->height - slop;
734 XtVaSetValues(shell,
735 XmNmaxWidth, maxWidth,
736 XmNmaxHeight, maxHeight,
737 NULL);
739 /* Manage the dialog */
740 XtManageChild(dialogChild);
742 /* Check to see if the window manager doesn't respect XmNmaxWidth
743 and XmNmaxHeight on the first geometry pass (sawfish, twm, fvwm).
744 For this to work XmNresizePolicy must be XmRESIZE_NONE, otherwise
745 the dialog will try to expand anyway. */
746 XtVaGetValues(shell, XmNwidth, &xtWidth, XmNheight, &xtHeight, NULL);
747 if (xtWidth > maxWidth)
748 XtVaSetValues(shell, XmNwidth, (Dimension) maxWidth, NULL);
749 if (xtHeight > maxHeight)
750 XtVaSetValues(shell, XmNheight, (Dimension) maxHeight, NULL);
752 /* Only set the x/y position if the centering option is enabled.
753 Avoid getting the coordinates if not so, to save a few round-trips
754 to the server. */
755 if (PointerCenteredDialogsEnabled) {
756 /* Get the pointer position (x, y) */
757 XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child,
758 &x, &y, &winX, &winY, &mask);
760 /* Translate the pointer position (x, y) into a position for the new
761 window that will place the pointer at its center */
762 XGetGeometry(XtDisplay(shell), XtWindow(shell), &root, &winX, &winY,
763 &width, &height, &borderWidth, &depth);
764 width += 2 * borderWidth;
765 height += 2 * borderWidth;
767 x -= width/2;
768 y -= height/2;
770 /* Ensure that the dialog remains on screen */
771 maxX = maxWidth - width;
772 maxY = maxHeight - height;
773 if (x > maxX) x = maxX;
774 if (x < 0) x = 0;
775 if (y > maxY) y = maxY;
776 if (y < 0) y = 0;
778 /* Some window managers (Sawfish) don't appear to respond
779 to the geometry set call in synchronous mode. This causes
780 the window to delay XmNwmTimeout (default 5 seconds) before
781 posting, and it is very annoying. See "man VendorShell" for
782 more info. */
783 XtVaSetValues(shell, XmNuseAsyncGeometry, True, NULL);
785 /* Set desired window position in the DialogShell */
786 XtVaSetValues(shell, XmNx, x, XmNy, y, NULL);
789 /* Map the widget */
790 XtMapWidget(shell);
792 /* Restore the value of XmNmappedWhenManaged */
793 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
797 ** Cause dialogs created by libNUtil.a routines (such as DialogF),
798 ** and dialogs which use ManageDialogCenteredOnPointer to pop up
799 ** over the pointer (state = True), or pop up in their default
800 ** positions (state = False)
802 void SetPointerCenteredDialogs(int state)
804 PointerCenteredDialogsEnabled = state;
809 ** Raise a window to the top and give it the input focus. Setting input focus
810 ** is important on systems which use explict (rather than pointer) focus.
812 ** The X alternatives XMapRaised, and XSetInputFocus both have problems.
813 ** XMapRaised only gives the window the focus if it was initially not visible,
814 ** and XSetInputFocus sets the input focus, but crashes if the window is not
815 ** visible.
817 ** This routine should also be used in the case where a dialog is popped up and
818 ** subsequent calls to the dialog function use a flag, or the XtIsManaged, to
819 ** decide whether to create a new instance of the dialog, because on slower
820 ** systems, events can intervene while a dialog is still on its way up and its
821 ** window is still invisible, causing a subtle crash potential if
822 ** XSetInputFocus is used.
824 void RaiseShellWindow(Widget shell)
826 RaiseWindow(XtDisplay(shell), XtWindow(shell));
830 void RaiseWindow(Display *display, Window w)
832 XWindowAttributes winAttr;
834 XGetWindowAttributes(display, w, &winAttr);
835 if (winAttr.map_state == IsViewable)
836 XSetInputFocus(display, w, RevertToParent, CurrentTime);
838 XMapRaised(display, w);
842 ** Add a handler for mnemonics in a dialog (Motif currently only handles
843 ** mnemonics in menus) following the example of M.S. Windows. To add
844 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
845 ** a menu, on push buttons or toggle buttons, and call this function
846 ** when the dialog is fully constructed. Mnemonics added or changed
847 ** after this call will not be noticed. To add a mnemonic to a text field
848 ** or list, set the XmNmnemonic resource on the appropriate label and set
849 ** the XmNuserData resource of the label to the widget to get the focus
850 ** when the mnemonic is typed.
852 void AddDialogMnemonicHandler(Widget dialog, int unmodifiedToo)
854 XtAddEventHandler(dialog, KeyPressMask, False,
855 (XtEventHandler)mnemonicCB, (XtPointer)0);
856 addMnemonicGrabs(dialog, dialog, unmodifiedToo);
860 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
862 void RemoveDialogMnemonicHandler(Widget dialog)
864 XtUngrabKey(dialog, AnyKey, Mod1Mask);
865 XtRemoveEventHandler(dialog, KeyPressMask, False,
866 (XtEventHandler)mnemonicCB, (XtPointer)0);
870 ** Patch around Motif's poor handling of menu accelerator keys. Motif
871 ** does not process menu accelerators when the caps lock or num lock
872 ** keys are engaged. To enable accelerators in these cases, call this
873 ** routine with the completed menu bar widget as "topMenuContainer", and
874 ** the top level shell widget as "topWidget". It will add key grabs for
875 ** all of the accelerators it finds in the topMenuContainer menu tree, and
876 ** an event handler which can process dropped accelerator events by (again)
877 ** traversing the menu tree looking for matching accelerators, and invoking
878 ** the appropriate button actions. Any dynamic additions to the menus
879 ** require a call to UpdateAccelLockPatch to add the additional grabs.
880 ** Unfortunately, these grabs can not be removed.
882 void AccelLockBugPatch(Widget topWidget, Widget topMenuContainer)
884 XtAddEventHandler(topWidget, KeyPressMask, False, lockCB, topMenuContainer);
885 addAccelGrabs(topWidget, topMenuContainer);
889 ** Add additional key grabs for new menu items added to the menus, for
890 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
891 ** the same widget passed in the original call to AccelLockBugPatch.
893 void UpdateAccelLockPatch(Widget topWidget, Widget newButton)
895 addAccelGrab(topWidget, newButton);
899 ** PopDownBugPatch
901 ** Under some circumstances, popping down a dialog and its parent in
902 ** rapid succession causes a crash. This routine delays and
903 ** processs events until receiving a ReparentNotify event.
904 ** (I have no idea why a ReparentNotify event occurs at all, but it does
905 ** mark the point where it is safe to destroy or pop down the parent, and
906 ** it might have something to do with the bug.) There is a failsafe in
907 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
908 ** Use this sparingly, only when real crashes are observed, and periodically
909 ** check to make sure that it is still necessary.
911 void PopDownBugPatch(Widget w)
913 time_t stopTime;
915 stopTime = time(NULL) + 1;
916 while (time(NULL) <= stopTime) {
917 XEvent event;
918 XtAppContext context = XtWidgetToApplicationContext(w);
919 XtAppPeekEvent(context, &event);
920 if (event.xany.type == ReparentNotify)
921 return;
922 XtAppProcessEvent(context, XtIMAll);
927 ** Convert a compound string to a C style null terminated string.
928 ** Returned string must be freed by the caller.
930 char *GetXmStringText(XmString fromString)
932 XmStringContext context;
933 char *text, *toPtr, *toString, *fromPtr;
934 XmStringCharSet charset;
935 XmStringDirection direction;
936 Boolean separator;
938 /* Malloc a buffer large enough to hold the string. XmStringLength
939 should always be slightly longer than necessary, but won't be
940 shorter than the equivalent null-terminated string */
941 toString = XtMalloc(XmStringLength(fromString));
943 /* loop over all of the segments in the string, copying each segment
944 into the output string and converting separators into newlines */
945 XmStringInitContext(&context, fromString);
946 toPtr = toString;
947 while (XmStringGetNextSegment(context, &text,
948 &charset, &direction, &separator)) {
949 for (fromPtr=text; *fromPtr!='\0'; fromPtr++)
950 *toPtr++ = *fromPtr;
951 if (separator)
952 *toPtr++ = '\n';
955 /* terminate the string, free the context, and return the string */
956 *toPtr++ = '\0';
957 XmStringFreeContext(context);
958 return toString;
962 ** Get the XFontStruct that corresponds to the default (first) font in
963 ** a Motif font list. Since Motif stores this, it saves us from storing
964 ** it or querying it from the X server.
966 XFontStruct *GetDefaultFontStruct(XmFontList font)
968 XFontStruct *fs;
969 XmFontContext context;
970 XmStringCharSet charset;
972 XmFontListInitFontContext(&context, font);
973 XmFontListGetNextFont(context, &charset, &fs);
974 XmFontListFreeFontContext(context);
975 XtFree(charset);
976 return fs;
980 ** Create a string table suitable for passing to XmList widgets
982 XmString* StringTable(int count, ... )
984 va_list ap;
985 XmString *array;
986 int i;
987 char *str;
989 va_start(ap, count);
990 array = (XmString*)XtMalloc((count+1) * sizeof(XmString));
991 for(i = 0; i < count; i++ ) {
992 str = va_arg(ap, char *);
993 array[i] = XmStringCreateSimple(str);
995 array[i] = (XmString)0;
996 va_end(ap);
997 return(array);
1000 void FreeStringTable(XmString *table)
1002 int i;
1004 for(i = 0; table[i] != 0; i++)
1005 XmStringFree(table[i]);
1006 XtFree((char *)table);
1010 ** Simulate a button press. The purpose of this routine is show users what
1011 ** is happening when they take an action with a non-obvious side effect,
1012 ** such as when a user double clicks on a list item. The argument is an
1013 ** XmPushButton widget to "press"
1015 void SimulateButtonPress(Widget widget)
1017 XEvent keyEvent;
1019 memset((char *)&keyEvent, 0, sizeof(XKeyPressedEvent));
1020 keyEvent.type = KeyPress;
1021 keyEvent.xkey.serial = 1;
1022 keyEvent.xkey.send_event = True;
1024 if (XtIsSubclass(widget, xmGadgetClass))
1026 /* On some Motif implementations, asking a gadget for its
1027 window will crash, rather than return the window of its
1028 parent. */
1029 Widget parent = XtParent(widget);
1030 keyEvent.xkey.display = XtDisplay(parent);
1031 keyEvent.xkey.window = XtWindow(parent);
1033 XtCallActionProc(parent, "ManagerGadgetSelect",
1034 &keyEvent, NULL, 0);
1036 else
1038 keyEvent.xkey.display = XtDisplay(widget);
1039 keyEvent.xkey.window = XtWindow(widget);
1041 XtCallActionProc(widget, "ArmAndActivate", &keyEvent, NULL, 0);
1046 ** Add an item to an already established pull-down or pop-up menu, including
1047 ** mnemonics, accelerators and callbacks.
1049 Widget AddMenuItem(Widget parent, char *name, char *label,
1050 char mnemonic, char *acc, char *accText,
1051 XtCallbackProc callback, void *cbArg)
1053 Widget button;
1054 XmString st1, st2;
1056 button = XtVaCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
1057 XmNlabelString, st1=XmStringCreateSimple(label),
1058 XmNmnemonic, mnemonic,
1059 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1060 XmNaccelerator, acc, NULL);
1061 XtAddCallback(button, XmNactivateCallback, callback, cbArg);
1062 XmStringFree(st1);
1063 XmStringFree(st2);
1064 return button;
1068 ** Add a toggle button item to an already established pull-down or pop-up
1069 ** menu, including mnemonics, accelerators and callbacks.
1071 Widget AddMenuToggle(Widget parent, char *name, char *label,
1072 char mnemonic, char *acc, char *accText,
1073 XtCallbackProc callback, void *cbArg, int set)
1075 Widget button;
1076 XmString st1, st2;
1078 button = XtVaCreateManagedWidget(name, xmToggleButtonWidgetClass, parent,
1079 XmNlabelString, st1=XmStringCreateSimple(label),
1080 XmNmnemonic, mnemonic,
1081 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1082 XmNaccelerator, acc,
1083 XmNset, set, NULL);
1084 XtAddCallback(button, XmNvalueChangedCallback, callback, cbArg);
1085 XmStringFree(st1);
1086 XmStringFree(st2);
1087 return button;
1091 ** Add a separator line to a menu
1093 Widget AddMenuSeparator(Widget parent, char *name)
1095 Widget button;
1097 button = XmCreateSeparator(parent, name, NULL, 0);
1098 XtManageChild(button);
1099 return button;
1103 ** Add a sub-menu to an established pull-down or pop-up menu, including
1104 ** mnemonics, accelerators and callbacks. Returns the menu pane of the
1105 ** new sub menu.
1107 Widget AddSubMenu(Widget parent, char *name, char *label, char mnemonic)
1109 Widget menu;
1110 XmString st1;
1112 menu = CreatePulldownMenu(parent, name, NULL, 0);
1113 XtVaCreateManagedWidget(name, xmCascadeButtonWidgetClass, parent,
1114 XmNlabelString, st1=XmStringCreateSimple(label),
1115 XmNmnemonic, mnemonic,
1116 XmNsubMenuId, menu, NULL);
1117 XmStringFree(st1);
1118 return menu;
1122 ** SetIntLabel, SetFloatLabel, SetIntText, SetFloatText
1124 ** Set the text of a motif label or text widget to show an integer or
1125 ** floating number.
1127 void SetIntLabel(Widget label, int value)
1129 char labelString[20];
1130 XmString s1;
1132 sprintf(labelString, "%d", value);
1133 s1=XmStringCreateSimple(labelString);
1134 XtVaSetValues(label, XmNlabelString, s1, NULL);
1135 XmStringFree(s1);
1139 void SetFloatLabel(Widget label, double value)
1141 char labelString[20];
1142 XmString s1;
1144 sprintf(labelString, "%g", value);
1145 s1=XmStringCreateSimple(labelString);
1146 XtVaSetValues(label, XmNlabelString, s1, NULL);
1147 XmStringFree(s1);
1151 void SetIntText(Widget text, int value)
1153 char labelString[20];
1155 sprintf(labelString, "%d", value);
1156 XmTextSetString(text, labelString);
1160 void SetFloatText(Widget text, double value)
1162 char labelString[20];
1164 sprintf(labelString, "%g", value);
1165 XmTextSetString(text, labelString);
1169 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
1171 ** Get the text of a motif text widget as an integer or floating point number.
1172 ** The functions will return TEXT_READ_OK of the value was read correctly.
1173 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER. The
1174 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
1175 ** user that the value could not be read. The argument fieldName is used
1176 ** in the dialog to help the user identify where the problem is. Set
1177 ** warnBlank to true if a blank field is also considered an error.
1179 int GetFloatText(Widget text, double *value)
1181 char *strValue, *endPtr;
1182 int retVal;
1184 strValue = XmTextGetString(text); /* Get Value */
1185 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1186 *value = strtod(strValue, &endPtr); /* Convert string to double */
1187 if (strlen(strValue) == 0) /* String is empty */
1188 retVal = TEXT_IS_BLANK;
1189 else if (*endPtr != '\0') /* Whole string not parsed */
1190 retVal = TEXT_NOT_NUMBER;
1191 else
1192 retVal = TEXT_READ_OK;
1193 XtFree(strValue);
1194 return retVal;
1197 int GetIntText(Widget text, int *value)
1199 char *strValue, *endPtr;
1200 int retVal;
1202 strValue = XmTextGetString(text); /* Get Value */
1203 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1204 *value = strtol(strValue, &endPtr, 10); /* Convert string to long */
1205 if (strlen(strValue) == 0) /* String is empty */
1206 retVal = TEXT_IS_BLANK;
1207 else if (*endPtr != '\0') /* Whole string not parsed */
1208 retVal = TEXT_NOT_NUMBER;
1209 else
1210 retVal = TEXT_READ_OK;
1211 XtFree(strValue);
1212 return retVal;
1215 int GetFloatTextWarn(Widget text, double *value, const char *fieldName,
1216 int warnBlank)
1218 int result;
1219 char *valueStr;
1221 result = GetFloatText(text, value);
1222 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1223 return result;
1224 valueStr = XmTextGetString(text);
1226 if (result == TEXT_IS_BLANK)
1228 DialogF(DF_ERR, text, 1, "Warning", "Please supply %s value", "Dismiss",
1229 fieldName);
1230 } else /* TEXT_NOT_NUMBER */
1232 DialogF (DF_ERR, text, 1, "Warning", "Can't read %s value: \"%s\"",
1233 "Dismiss", fieldName, valueStr);
1236 XtFree(valueStr);
1237 return result;
1240 int GetIntTextWarn(Widget text, int *value, const char *fieldName, int warnBlank)
1242 int result;
1243 char *valueStr;
1245 result = GetIntText(text, value);
1246 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1247 return result;
1248 valueStr = XmTextGetString(text);
1250 if (result == TEXT_IS_BLANK)
1252 DialogF (DF_ERR, text, 1, "Warning", "Please supply a value for %s",
1253 "Dismiss", fieldName);
1254 } else /* TEXT_NOT_NUMBER */
1256 DialogF (DF_ERR, text, 1, "Warning",
1257 "Can't read integer value \"%s\" in %s", "Dismiss", valueStr,
1258 fieldName);
1261 XtFree(valueStr);
1262 return result;
1265 int TextWidgetIsBlank(Widget textW)
1267 char *str;
1268 int retVal;
1270 str = XmTextGetString(textW);
1271 removeWhiteSpace(str);
1272 retVal = *str == '\0';
1273 XtFree(str);
1274 return retVal;
1278 ** Turn a multi-line editing text widget into a fake single line text area
1279 ** by disabling the translation for Return. This is a way to give users
1280 ** extra space, by allowing wrapping, but still prohibiting newlines.
1281 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1282 ** the widget to be one line high).
1284 void MakeSingleLineTextW(Widget textW)
1286 static XtTranslations noReturnTable = NULL;
1287 static char *noReturnTranslations = "<Key>Return: activate()\n";
1289 if (noReturnTable == NULL)
1290 noReturnTable = XtParseTranslationTable(noReturnTranslations);
1291 XtOverrideTranslations(textW, noReturnTable);
1295 ** Add up-arrow/down-arrow recall to a single line text field. When user
1296 ** presses up-arrow, string is cleared and recent entries are presented,
1297 ** moving to older ones as each successive up-arrow is pressed. Down-arrow
1298 ** moves to more recent ones, final down-arrow clears the field. Associated
1299 ** routine, AddToHistoryList, makes maintaining a history list easier.
1301 ** Arguments are the widget, and pointers to the history list and number of
1302 ** items, which are expected to change periodically.
1304 void AddHistoryToTextWidget(Widget textW, char ***historyList, int *nItems)
1306 histInfo *histData;
1308 /* create a data structure for passing history info to the callbacks */
1309 histData = (histInfo *)XtMalloc(sizeof(histInfo));
1310 histData->list = historyList;
1311 histData->nItems = nItems;
1312 histData->index = -1;
1314 /* Add an event handler for handling up/down arrow events */
1315 XtAddEventHandler(textW, KeyPressMask, False,
1316 (XtEventHandler)histArrowKeyEH, histData);
1318 /* Add a destroy callback for freeing history data structure */
1319 XtAddCallback(textW, XmNdestroyCallback, histDestroyCB, histData);
1322 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1324 XtFree((char *)clientData);
1327 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
1328 Boolean *continueDispatch)
1330 histInfo *histData = (histInfo *)callData;
1331 KeySym keysym = XLookupKeysym((XKeyEvent *)event, 0);
1333 /* only process up and down arrow keys */
1334 if (keysym != XK_Up && keysym != XK_Down)
1335 return;
1337 /* increment or decrement the index depending on which arrow was pressed */
1338 histData->index += (keysym == XK_Up) ? 1 : -1;
1340 /* if the index is out of range, beep, fix it up, and return */
1341 if (histData->index < -1) {
1342 histData->index = -1;
1343 XBell(XtDisplay(w), 0);
1344 return;
1346 if (histData->index >= *histData->nItems) {
1347 histData->index = *histData->nItems - 1;
1348 XBell(XtDisplay(w), 0);
1349 return;
1352 /* Change the text field contents */
1353 XmTextSetString(w, histData->index == -1 ? "" :
1354 (*histData->list)[histData->index]);
1358 ** Copies a string on to the end of history list, which may be reallocated
1359 ** to make room. If historyList grows beyond its internally set boundary
1360 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1361 ** (HISTORY_LIST_TRIM_TO). Before adding to the list, checks if the item
1362 ** is a duplicate of the last item. If so, it is not added.
1364 void AddToHistoryList(char *newItem, char ***historyList, int *nItems)
1366 char **newList;
1367 int i;
1369 if (*nItems != 0 && !strcmp(newItem, **historyList))
1370 return;
1371 if (*nItems == HISTORY_LIST_MAX) {
1372 for (i=HISTORY_LIST_TRIM_TO; i<HISTORY_LIST_MAX; i++)
1373 XtFree((*historyList)[i]);
1374 *nItems = HISTORY_LIST_TRIM_TO;
1376 newList = (char **)XtMalloc(sizeof(char *) * (*nItems + 1));
1377 for (i=0; i < *nItems; i++)
1378 newList[i+1] = (*historyList)[i];
1379 if (*nItems != 0 && *historyList != NULL)
1380 XtFree((char *)*historyList);
1381 (*nItems)++;
1382 newList[0] = XtNewString(newItem);
1383 *historyList = newList;
1387 * PasswordText - routine to add a callback to any text widget so that all
1388 * text typed by the user is echoed with asterisks, allowing
1389 * a password to be typed in without being seen.
1391 * parameters: w - text widget to add the callback to
1392 * passTxt - pointer to a string created by caller of this routine.
1393 * **NOTE** The length of this string should be one
1394 * greater than the maximum specified by XmNmaxLength.
1395 * This string is set to empty just before the callback
1396 * is added.
1399 void PasswordText(Widget w, char *passTxt)
1401 passTxt[0] = '\0';
1402 XtAddCallback(w, XmNmodifyVerifyCallback, (XtCallbackProc)passwdCB,passTxt);
1406 ** BeginWait/EndWait
1408 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1410 void BeginWait(Widget topCursorWidget)
1412 Display *display = XtDisplay(topCursorWidget);
1413 Pixmap pixmap;
1414 Pixmap maskPixmap;
1415 XColor xcolors[2];
1416 static Cursor waitCursor = 0;
1418 /* if the watch cursor hasn't been created yet, create it */
1419 if (!waitCursor) {
1420 pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1421 (char *)watch_bits, watch_width, watch_height);
1423 maskPixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1424 (char *)watch_mask_bits, watch_width, watch_height);
1426 xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(display));
1427 xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(display));
1429 XQueryColors(display, DefaultColormapOfScreen(
1430 DefaultScreenOfDisplay(display)), xcolors, 2);
1431 waitCursor = XCreatePixmapCursor(display, pixmap, maskPixmap,
1432 &xcolors[0], &xcolors[1], watch_x_hot, watch_y_hot);
1433 XFreePixmap(display, pixmap);
1434 XFreePixmap(display, maskPixmap);
1437 /* display the cursor */
1438 XDefineCursor(display, XtWindow(topCursorWidget), waitCursor);
1441 void BusyWait(Widget widget)
1443 #ifdef __unix__
1444 static const int timeout = 100000; /* 1/10 sec = 100 ms = 100,000 us */
1445 static struct timeval last = { 0, 0 };
1446 struct timeval current;
1447 gettimeofday(&current, NULL);
1449 if ((current.tv_sec != last.tv_sec) ||
1450 (current.tv_usec - last.tv_usec > timeout))
1452 XmUpdateDisplay(widget);
1453 last = current;
1455 #else
1456 static time_t last = 0;
1457 time_t current;
1458 time(&current);
1460 if (difftime(current, last) > 0)
1462 XmUpdateDisplay(widget);
1463 last = current;
1465 #endif
1468 void EndWait(Widget topCursorWidget)
1470 XUndefineCursor(XtDisplay(topCursorWidget), XtWindow(topCursorWidget));
1474 ** Create an X window geometry string from width, height, x, and y values.
1475 ** This is a complement to the X routine XParseGeometry, and uses the same
1476 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1477 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1478 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1479 ** result. Note that in a geometry string, it is not possible to supply a y
1480 ** position without an x position. Also note that the X/YNegative flags
1481 ** mean "add a '-' and negate the value" which is kind of odd.
1483 void CreateGeometryString(char *string, int x, int y,
1484 int width, int height, int bitmask)
1486 char *ptr = string;
1487 int nChars;
1489 if (bitmask & WidthValue) {
1490 sprintf(ptr, "%d%n", width, &nChars);
1491 ptr += nChars;
1493 if (bitmask & HeightValue) {
1494 sprintf(ptr, "x%d%n", height, &nChars);
1495 ptr += nChars;
1497 if (bitmask & XValue) {
1498 if (bitmask & XNegative)
1499 sprintf(ptr, "-%d%n", -x, &nChars);
1500 else
1501 sprintf(ptr, "+%d%n", x, &nChars);
1502 ptr += nChars;
1504 if (bitmask & YValue) {
1505 if (bitmask & YNegative)
1506 sprintf(ptr, "-%d%n", -y, &nChars);
1507 else
1508 sprintf(ptr, "+%d%n", y, &nChars);
1509 ptr += nChars;
1511 *ptr = '\0';
1514 /* */
1515 /* passwdCB: callback routine added by PasswordText routine. This routine */
1516 /* echoes each character typed as an asterisk (*) and a few other */
1517 /* necessary things so that the password typed in is not visible */
1518 /* */
1519 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
1520 *txtVerStr)
1522 /* XmTextVerifyCallbackStruct: */
1523 /* int reason; should be XmCR_MODIFYING_TEXT_VALUE */
1524 /* XEvent *event; points to XEvent that triggered the callback */
1525 /* Boolean doit; indicates whether action should be performed; setting */
1526 /* this to false negates the action */
1527 /* long currInsert, current position of insert cursor */
1528 /* newInsert; position user attempts to position the insert cursor */
1529 /* long startPos, starting position of the text to modify */
1530 /* endPos; ending position of the text to modify */
1531 /* XmTextBlock text; */
1533 /* XmTextBlock (used to pass text around): */
1534 /* char *ptr; points to text to be inserted */
1535 /* int length; Number of bytes (length) */
1536 /* XmTextFormat format; Representations format */
1538 /* XmTextFormat: either FMT8BIT or FMT16BIT */
1541 int numCharsTyped, i, j, pos;
1543 /* ensure text format is 8-bit characters */
1544 if (txtVerStr->text->format != FMT8BIT)
1545 return;
1547 /* verify assumptions */
1548 /* if (txtVerStr->endPos < txtVerStr->startPos)
1549 fprintf(stderr, "User password callback error: endPos < startPos\n");
1550 if (strlen(passTxt) == 0 && txtVerStr->endPos != 0)
1551 fprintf(stderr, "User password callback error: no txt, but end != 0\n");
1553 printf("passTxt = %s, startPos = %d, endPos = %d, txtBlkAddr = %d\n",
1554 passTxt, txtVerStr->startPos, txtVerStr->endPos, txtVerStr->text);
1555 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1556 printf(" string typed = %s, length = %d\n", txtVerStr->text->ptr,
1557 txtVerStr->text->length);
1559 /* If necessary, expand/compress passTxt and insert any new text */
1560 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1561 numCharsTyped = txtVerStr->text->length;
1562 else
1563 numCharsTyped = 0;
1564 /* numCharsTyped = # chars to insert (that user typed) */
1565 /* j = # chars to expand (+) or compress (-) the password string */
1566 j = numCharsTyped - (txtVerStr->endPos - txtVerStr->startPos);
1567 if (j > 0) /* expand case: start at ending null */
1568 for (pos = strlen(passTxt) + 1; pos >= txtVerStr->endPos; --pos)
1569 passTxt[pos+j] = passTxt[pos];
1570 if (j < 0) /* compress case */
1571 for (pos = txtVerStr->startPos + numCharsTyped;
1572 pos <= (int)strlen(passTxt)+1; ++pos)
1573 passTxt[pos] = passTxt[pos-j];
1574 /* then copy text to be inserted into passTxt */
1575 for (pos = txtVerStr->startPos, i = 0; i < numCharsTyped; ++i) {
1576 passTxt[pos+i] = *(txtVerStr->text->ptr + i);
1577 /* Replace text typed by user with asterisks (*) */
1578 *(txtVerStr->text->ptr + i) = '*';
1580 /* printf(" Password string now = %s\n", passTxt); */
1584 ** Remove the white space (blanks and tabs) from a string
1586 static void removeWhiteSpace(char *string)
1588 char *outPtr = string;
1590 while (TRUE) {
1591 if (*string == 0) {
1592 *outPtr = 0;
1593 return;
1594 } else if (*string != ' ' && *string != '\t')
1595 *(outPtr++) = *(string++);
1596 else
1597 string++;
1602 ** Compares two strings and return TRUE if the two strings
1603 ** are the same, ignoring whitespace and case differences.
1605 static int stripCaseCmp(const char *str1, const char *str2)
1607 const char *c1, *c2;
1609 for (c1=str1, c2=str2; *c1!='\0' && *c2!='\0'; c1++, c2++) {
1610 while (*c1 == ' ' || *c1 == '\t')
1611 c1++;
1612 while (*c2 == ' ' || *c2 == '\t')
1613 c2++;
1614 if (toupper((unsigned char)*c1) != toupper((unsigned char)*c2))
1615 return FALSE;
1617 return *c1 == '\0' && *c2 == '\0';
1620 static void warnHandlerCB(String message)
1622 if (strstr(message, "XtRemoveGrab"))
1623 return;
1624 if (strstr(message, "Attempt to remove non-existant passive grab"))
1625 return;
1626 fputs(message, stderr);
1627 fputc('\n', stderr);
1630 static XModifierKeymap *getKeyboardMapping(Display *display) {
1631 static XModifierKeymap *keyboardMap = NULL;
1633 if (keyboardMap == NULL) {
1634 keyboardMap = XGetModifierMapping(display);
1636 return(keyboardMap);
1640 ** get mask for a modifier
1644 static Modifiers findModifierMapping(Display *display, KeyCode keyCode) {
1645 int i, j;
1646 KeyCode *mapentry;
1647 XModifierKeymap *modMap = getKeyboardMapping(display);
1649 if (modMap == NULL || keyCode == 0) {
1650 return(0);
1653 mapentry = modMap->modifiermap;
1654 for (i = 0; i < 8; ++i) {
1655 for (j = 0; j < (modMap->max_keypermod); ++j) {
1656 if (keyCode == *mapentry) {
1657 return(1 << ((mapentry - modMap->modifiermap) / modMap->max_keypermod));
1659 ++mapentry;
1662 return(0);
1665 Modifiers GetNumLockModMask(Display *display) {
1666 static int numLockMask = -1;
1668 if (numLockMask == -1) {
1669 numLockMask = findModifierMapping(display, XKeysymToKeycode(display, XK_Num_Lock));
1671 return(numLockMask);
1675 ** Grab a key regardless of caps-lock and other silly latching keys.
1679 static void reallyGrabAKey(Widget dialog, int keyCode, Modifiers mask) {
1680 Modifiers numLockMask = GetNumLockModMask(XtDisplay(dialog));
1682 if (keyCode == 0) /* No anykey grabs, sorry */
1683 return;
1685 XtGrabKey(dialog, keyCode, mask, True, GrabModeAsync, GrabModeAsync);
1686 XtGrabKey(dialog, keyCode, mask|LockMask, True, GrabModeAsync, GrabModeAsync);
1687 if (numLockMask && numLockMask != LockMask) {
1688 XtGrabKey(dialog, keyCode, mask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1689 XtGrabKey(dialog, keyCode, mask|LockMask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1694 ** Part of dialog mnemonic processing. Search the widget tree under w
1695 ** for widgets with mnemonics. When found, add a passive grab to the
1696 ** dialog widget for the mnemonic character, thus directing mnemonic
1697 ** events to the dialog widget.
1699 static void addMnemonicGrabs(Widget dialog, Widget w, int unmodifiedToo)
1701 char mneString[2];
1702 WidgetList children;
1703 Cardinal numChildren;
1704 int i, isMenu;
1705 KeySym mnemonic = '\0';
1706 unsigned char rowColType;
1707 unsigned int keyCode;
1709 if (XtIsComposite(w)) {
1710 if (XtClass(w) == xmRowColumnWidgetClass) {
1711 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1712 isMenu = rowColType != XmWORK_AREA;
1713 } else
1714 isMenu = False;
1715 if (!isMenu) {
1716 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1717 &numChildren, NULL);
1718 for (i=0; i<(int)numChildren; i++)
1719 addMnemonicGrabs(dialog, children[i], unmodifiedToo);
1721 } else {
1722 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1723 if (mnemonic != XK_VoidSymbol && mnemonic != '\0') {
1724 mneString[0] = mnemonic; mneString[1] = '\0';
1725 keyCode = XKeysymToKeycode(XtDisplay(dialog),
1726 XStringToKeysym(mneString));
1727 reallyGrabAKey(dialog, keyCode, Mod1Mask);
1728 if (unmodifiedToo)
1729 reallyGrabAKey(dialog, keyCode, 0);
1735 ** Callback routine for dialog mnemonic processing.
1737 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event)
1739 findAndActivateMnemonic(w, event->keycode);
1743 ** Look for a widget in the widget tree w, with a mnemonic matching
1744 ** keycode. When one is found, simulate a button press on that widget
1745 ** and give it the keyboard focus. If the mnemonic is on a label,
1746 ** look in the userData field of the label to see if it points to
1747 ** another widget, and give that the focus. This routine is just
1748 ** sufficient for NEdit, no doubt it will need to be extended for
1749 ** mnemonics on widgets other than just buttons and text fields.
1751 static void findAndActivateMnemonic(Widget w, unsigned int keycode)
1753 WidgetList children;
1754 Cardinal numChildren;
1755 int i, isMenu;
1756 KeySym mnemonic = '\0';
1757 char mneString[2];
1758 Widget userData;
1759 unsigned char rowColType;
1761 if (XtIsComposite(w)) {
1762 if (XtClass(w) == xmRowColumnWidgetClass) {
1763 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1764 isMenu = rowColType != XmWORK_AREA;
1765 } else
1766 isMenu = False;
1767 if (!isMenu) {
1768 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1769 &numChildren, NULL);
1770 for (i=0; i<(int)numChildren; i++)
1771 findAndActivateMnemonic(children[i], keycode);
1773 } else {
1774 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1775 if (mnemonic != '\0') {
1776 mneString[0] = mnemonic; mneString[1] = '\0';
1777 if (XKeysymToKeycode(XtDisplay(XtParent(w)),
1778 XStringToKeysym(mneString)) == keycode) {
1779 if (XtClass(w) == xmLabelWidgetClass ||
1780 XtClass(w) == xmLabelGadgetClass) {
1781 XtVaGetValues(w, XmNuserData, &userData, NULL);
1782 if (userData!=NULL && XtIsWidget(userData) &&
1783 XmIsTraversable(userData))
1784 XmProcessTraversal(userData, XmTRAVERSE_CURRENT);
1785 } else if (XmIsTraversable(w)) {
1786 XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1787 SimulateButtonPress(w);
1795 ** Part of workaround for Motif Caps/Num Lock bug. Search the widget tree
1796 ** under w for widgets with accelerators. When found, add three passive
1797 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1798 ** Lock, one for Num Lock, and one for both, thus directing lock +
1799 ** accelerator events to topWidget.
1801 static void addAccelGrabs(Widget topWidget, Widget w)
1803 WidgetList children;
1804 Widget menu;
1805 Cardinal numChildren;
1806 int i;
1808 if (XtIsComposite(w)) {
1809 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1810 &numChildren, NULL);
1811 for (i=0; i<(int)numChildren; i++)
1812 addAccelGrabs(topWidget, children[i]);
1813 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1814 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1815 if (menu != NULL)
1816 addAccelGrabs(topWidget, menu);
1817 } else
1818 addAccelGrab(topWidget, w);
1822 ** Grabs the key + modifier defined in the widget's accelerator resource,
1823 ** in combination with the Caps Lock and Num Lock accelerators.
1825 static void addAccelGrab(Widget topWidget, Widget w)
1827 char *accelString = NULL;
1828 KeySym keysym;
1829 unsigned int modifiers;
1830 KeyCode code;
1831 Modifiers numLockMask = GetNumLockModMask(XtDisplay(topWidget));
1833 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1834 if (accelString == NULL || *accelString == '\0') {
1835 if (accelString != NULL) XtFree(accelString);
1836 return;
1839 if (!parseAccelString(XtDisplay(topWidget), accelString, &keysym, &modifiers)) {
1840 XtFree(accelString);
1841 return;
1843 XtFree(accelString);
1845 /* Check to see if this server has this key mapped. Some cruddy PC X
1846 servers (Xoftware) have terrible default keymaps. If not,
1847 XKeysymToKeycode will return 0. However, it's bad news to pass
1848 that to XtGrabKey because 0 is really "AnyKey" which is definitely
1849 not what we want!! */
1851 code = XKeysymToKeycode(XtDisplay(topWidget), keysym);
1852 if (code == 0)
1853 return;
1855 XtGrabKey(topWidget, code,
1856 modifiers | LockMask, True, GrabModeAsync, GrabModeAsync);
1857 if (numLockMask && numLockMask != LockMask) {
1858 XtGrabKey(topWidget, code,
1859 modifiers | numLockMask, True, GrabModeAsync, GrabModeAsync);
1860 XtGrabKey(topWidget, code,
1861 modifiers | LockMask | numLockMask, True, GrabModeAsync, GrabModeAsync);
1866 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1867 ** Returns TRUE if the parse was successful, FALSE, if not.
1869 static int parseAccelString(Display *display, const char *string, KeySym *keySym,
1870 unsigned int *modifiers)
1872 #define N_MODIFIERS 12
1873 /*... Is NumLock always Mod3? */
1874 static char *modifierNames[N_MODIFIERS] = {"Ctrl", "Shift", "Alt", "Mod2",
1875 "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1876 "Button5"};
1877 static unsigned int modifierMasks[N_MODIFIERS] = {ControlMask, ShiftMask,
1878 Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, Button1Mask, Button2Mask,
1879 Button3Mask, Button4Mask, Button5Mask};
1880 Modifiers numLockMask = GetNumLockModMask(display);
1881 char modStr[MAX_ACCEL_LEN];
1882 char evtStr[MAX_ACCEL_LEN];
1883 char keyStr[MAX_ACCEL_LEN];
1884 const char *c, *evtStart, *keyStart;
1885 int i;
1887 if (strlen(string) >= MAX_ACCEL_LEN)
1888 return FALSE;
1890 /* Get the modifier part */
1891 for (c = string; *c != '<'; c++)
1892 if (*c == '\0')
1893 return FALSE;
1894 strncpy(modStr, string, c - string);
1895 modStr[c - string] = '\0';
1897 /* Verify the <key> or <keypress> part */
1898 evtStart = c;
1899 for ( ; *c != '>'; c++)
1900 if (*c == '\0')
1901 return FALSE;
1902 c++;
1903 strncpy(evtStr, evtStart, c - evtStart);
1904 evtStr[c - evtStart] = '\0';
1905 if (!stripCaseCmp(evtStr, "<key>") && !stripCaseCmp(evtStr, "<keypress>"))
1906 return FALSE;
1908 /* Get the keysym part */
1909 keyStart = c;
1910 for ( ; *c != '\0'; c++);
1911 strncpy(keyStr, keyStart, c - keyStart);
1912 keyStr[c - keyStart] = '\0';
1913 *keySym = XStringToKeysym(keyStr);
1915 /* Parse the modifier part */
1916 *modifiers = 0;
1917 c = modStr;
1918 while (*c != '\0') {
1919 while (*c == ' ' || *c == '\t')
1920 c++;
1921 if (*c == '\0')
1922 break;
1923 for (i = 0; i < N_MODIFIERS; i++) {
1924 if (!strncmp(c, modifierNames[i], strlen(modifierNames[i]))) {
1925 c += strlen(modifierNames[i]);
1926 if (modifierMasks[i] != numLockMask) {
1927 *modifiers |= modifierMasks[i];
1929 break;
1932 if (i == N_MODIFIERS)
1933 return FALSE;
1936 return TRUE;
1940 ** Event handler for patching around Motif's lock + accelerator problem.
1941 ** Looks for a menu item in the patched menu hierarchy and invokes its
1942 ** ArmAndActivate action.
1944 static void lockCB(Widget w, XtPointer callData, XEvent *event,
1945 Boolean *continueDispatch)
1947 Modifiers numLockMask = GetNumLockModMask(XtDisplay(w));
1948 Widget topMenuWidget = (Widget)callData;
1949 *continueDispatch = TRUE;
1950 if (!(((XKeyEvent *)event)->state & (LockMask | numLockMask)))
1951 return;
1953 if (!findAndActivateAccel(topMenuWidget, ((XKeyEvent *)event)->keycode,
1954 ((XKeyEvent *)event)->state & ~(LockMask | numLockMask), event))
1955 *continueDispatch = FALSE;
1959 ** Search through menu hierarchy under w and look for a button with
1960 ** accelerator matching keyCode + modifiers, and do its action
1962 static int findAndActivateAccel(Widget w, unsigned int keyCode,
1963 unsigned int modifiers, XEvent *event)
1966 WidgetList children;
1967 Widget menu;
1968 Cardinal numChildren;
1969 int i;
1970 char *accelString = NULL;
1971 KeySym keysym;
1972 unsigned int mods;
1974 if (XtIsComposite(w)) {
1975 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1976 &numChildren, NULL);
1977 for (i=0; i<(int)numChildren; i++)
1978 if (findAndActivateAccel(children[i], keyCode, modifiers, event))
1979 return TRUE;
1980 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1981 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1982 if (menu != NULL)
1983 if (findAndActivateAccel(menu, keyCode, modifiers, event))
1984 return TRUE;
1985 } else {
1986 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1987 if (accelString != NULL && *accelString != '\0') {
1988 if (!parseAccelString(XtDisplay(w), accelString, &keysym, &mods))
1989 return FALSE;
1990 if (keyCode == XKeysymToKeycode(XtDisplay(w), keysym) &&
1991 modifiers == mods) {
1992 if (XtIsSensitive(w)) {
1993 XtCallActionProc(w, "ArmAndActivate", event, NULL, 0);
1994 return TRUE;
1999 return FALSE;
2003 ** Global installation of mouse wheel actions for scrolled windows.
2005 void InstallMouseWheelActions(XtAppContext context)
2007 static XtActionsRec Actions[] = {
2008 {"scrolled-window-scroll-up", scrollUpAP},
2009 {"scrolled-window-page-up", pageUpAP},
2010 {"scrolled-window-scroll-down", scrollDownAP},
2011 {"scrolled-window-page-down", pageDownAP}
2014 XtAppAddActions(context, Actions, XtNumber(Actions));
2018 ** Add mouse wheel support to a specific widget, which must be the scrollable
2019 ** widget of a ScrolledWindow.
2021 void AddMouseWheelSupport(Widget w)
2023 if (XmIsScrolledWindow(XtParent(w)))
2025 static const char scrollTranslations[] =
2026 "Shift<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(1)\n"
2027 "Shift<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(1)\n"
2028 "Ctrl<Btn4Down>,<Btn4Up>: scrolled-window-page-up()\n"
2029 "Ctrl<Btn5Down>,<Btn5Up>: scrolled-window-page-down()\n"
2030 "<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(3)\n"
2031 "<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(3)\n";
2032 static XtTranslations trans_table = NULL;
2034 if (trans_table == NULL)
2036 trans_table = XtParseTranslationTable(scrollTranslations);
2038 XtOverrideTranslations(w, trans_table);
2042 static void pageUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2044 Widget scrolledWindow, scrollBar;
2045 String al[1];
2047 al[0] = "Up";
2048 scrolledWindow = XtParent(w);
2049 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2050 if (scrollBar)
2051 XtCallActionProc(scrollBar, "PageUpOrLeft", event, al, 1) ;
2052 return;
2055 static void pageDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2057 Widget scrolledWindow, scrollBar;
2058 String al[1];
2060 al[0] = "Down";
2061 scrolledWindow = XtParent(w);
2062 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2063 if (scrollBar)
2064 XtCallActionProc(scrollBar, "PageDownOrRight", event, al, 1) ;
2065 return;
2068 static void scrollUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2070 Widget scrolledWindow, scrollBar;
2071 String al[1];
2072 int i, nLines;
2074 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2075 return;
2076 al[0] = "Up";
2077 scrolledWindow = XtParent(w);
2078 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2079 if (scrollBar)
2080 for (i=0; i<nLines; i++)
2081 XtCallActionProc(scrollBar, "IncrementUpOrLeft", event, al, 1) ;
2082 return;
2085 static void scrollDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2087 Widget scrolledWindow, scrollBar;
2088 String al[1];
2089 int i, nLines;
2091 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2092 return;
2093 al[0] = "Down";
2094 scrolledWindow = XtParent(w);
2095 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2096 if (scrollBar)
2097 for (i=0; i<nLines; i++)
2098 XtCallActionProc(scrollBar, "IncrementDownOrRight", event, al, 1) ;
2099 return;
2104 ** This is a disguisting hack to work around a bug in OpenMotif.
2105 ** OpenMotif's toggle button Select() action routine remembers the last radio
2106 ** button that was toggled (stored as global state) and refuses to take any
2107 ** action when that button is clicked again. It fails to detect that we may
2108 ** have changed the button state and that clicking that button could make
2109 ** sense. The result is that radio buttons may apparently get stuck, ie.
2110 ** it is not possible to directly select with the mouse the previously
2111 ** selected button without selection another radio button first.
2112 ** The workaround consist of faking a mouse click on the button that we
2113 ** toggled by calling the Arm, Select, and Disarm action procedures.
2115 ** A minor remaining issue is the fact that, if the workaround is used,
2116 ** it is not possible to change the state without notifying potential
2117 ** XmNvalueChangedCallbacks. In practice, this doesn't seem to be a problem.
2120 void RadioButtonChangeState(Widget widget, Boolean state, Boolean notify)
2123 The bug only exists in OpenMotif 2.x. Since it's quite hard to detect
2124 OpenMotif reliably, we make a rough cut by excluding Lesstif and all
2125 Motif versions < 2.x.
2127 #ifndef LESSTIF_VERSION
2128 #if XmVersion >= 2000
2129 if (state && XtIsRealized(widget))
2132 Simulate a mouse button click.
2133 The event attributes that matter are the event type and the
2134 coordinates. When the button is managed, the coordinates have to
2135 be inside the button. When the button is not managed, they have to
2136 be (0, 0) to make sure that the Select routine accepts the event.
2138 XEvent ev;
2139 if (XtIsManaged(widget))
2141 Position x, y;
2142 /* Calculate the coordinates in the same way as OM. */
2143 XtTranslateCoords(XtParent(widget), widget->core.x, widget->core.y,
2144 &x, &y);
2145 ev.xbutton.x_root = x + widget->core.border_width;
2146 ev.xbutton.y_root = y + widget->core.border_width;
2148 else
2150 ev.xbutton.x_root = 0;
2151 ev.xbutton.y_root = 0;
2153 /* Default button bindings:
2154 ~c<Btn1Down>: Arm()
2155 ~c<Btn1Up>: Select() Disarm() */
2156 ev.xany.type = ButtonPress;
2157 XtCallActionProc(widget, "Arm", &ev, NULL, 0);
2158 ev.xany.type = ButtonRelease;
2159 XtCallActionProc(widget, "Select", &ev, NULL, 0);
2160 XtCallActionProc(widget, "Disarm", &ev, NULL, 0);
2162 #endif /* XmVersion >= 2000 */
2163 #endif /* LESSTIF_VERSION */
2165 /* This is sufficient on non-OM platforms */
2166 XmToggleButtonSetState(widget, state, notify);