Committed SF patch #963120: Open tab in window on current desktop.
[nedit.git] / util / misc.c
blobb53afc87b0819618285c3c748d0309e7ef437d45
1 static const char CVSID[] = "$Id: misc.c,v 1.64 2004/06/08 15:30:02 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 #ifndef LESSTIF_VERSION
81 extern void _XmDismissTearOff(Widget w, XtPointer call, XtPointer x);
82 #endif
84 /* structure for passing history-recall data to callbacks */
85 typedef struct {
86 char ***list;
87 int *nItems;
88 int index;
89 } histInfo;
91 typedef Widget (*MotifDialogCreationCall)(Widget, String, ArgList, Cardinal);
93 /* Maximum size of a history-recall list. Typically never invoked, since
94 user must first make this many entries in the text field, limited for
95 safety, to the maximum reasonable number of times user can hit up-arrow
96 before carpal tunnel syndrome sets in */
97 #define HISTORY_LIST_TRIM_TO 1000
98 #define HISTORY_LIST_MAX 2000
100 /* flags to enable/disable delete key remapping and pointer centered dialogs */
101 static int RemapDeleteEnabled = True;
102 static int PointerCenteredDialogsEnabled = False;
104 /* bitmap and mask for waiting (wrist-watch) cursor */
105 #define watch_x_hot 7
106 #define watch_y_hot 7
107 #define watch_width 16
108 #define watch_height 16
109 static unsigned char watch_bits[] = {
110 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0x10, 0x08, 0x08, 0x11,
111 0x04, 0x21, 0x04, 0x21, 0xe4, 0x21, 0x04, 0x20, 0x08, 0x10, 0x10, 0x08,
112 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07
114 #define watch_mask_width 16
115 #define watch_mask_height 16
116 static unsigned char watch_mask_bits[] = {
117 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf8, 0x1f, 0xfc, 0x3f,
118 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f,
119 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f
122 static void addMnemonicGrabs(Widget addTo, Widget w, int unmodified);
123 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event);
124 static void findAndActivateMnemonic(Widget w, unsigned int keycode);
125 static void addAccelGrabs(Widget topWidget, Widget w);
126 static void addAccelGrab(Widget topWidget, Widget w);
127 static int parseAccelString(Display *display, const char *string, KeySym *keysym,
128 unsigned int *modifiers);
129 static void lockCB(Widget w, XtPointer callData, XEvent *event,
130 Boolean *continueDispatch);
131 static int findAndActivateAccel(Widget w, unsigned int keyCode,
132 unsigned int modifiers, XEvent *event);
133 static void removeWhiteSpace(char *string);
134 static int stripCaseCmp(const char *str1, const char *str2);
135 static void warnHandlerCB(String message);
136 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
137 *txtVerStr);
138 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
139 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
140 Boolean *continueDispatch);
141 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
142 Cardinal *argcount);
143 static Widget addParentVisArgsAndCall(MotifDialogCreationCall callRoutine,
144 Widget parent, char *name, ArgList arglist, Cardinal argcount);
145 static void scrollDownAP(Widget w, XEvent *event, String *args,
146 Cardinal *nArgs);
147 static void scrollUpAP(Widget w, XEvent *event, String *args,
148 Cardinal *nArgs);
149 static void pageDownAP(Widget w, XEvent *event, String *args,
150 Cardinal *nArgs);
151 static void pageUpAP(Widget w, XEvent *event, String *args,
152 Cardinal *nArgs);
153 static long queryDesktop(Display *display, Window window, Atom deskTopAtom);
156 ** Set up closeCB to be called when the user selects close from the
157 ** window menu. The close menu item usually activates f.kill which
158 ** sends a WM_DELETE_WINDOW protocol request for the window.
160 void AddMotifCloseCallback(Widget shell, XtCallbackProc closeCB, void *arg)
162 static Atom wmpAtom, dwAtom = 0;
163 Display *display = XtDisplay(shell);
165 /* deactivate the built in delete response of killing the application */
166 XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);
168 /* add a delete window protocol callback instead */
169 if (dwAtom == 0) {
170 wmpAtom = XmInternAtom(display, "WM_PROTOCOLS", FALSE);
171 dwAtom = XmInternAtom(display, "WM_DELETE_WINDOW", FALSE);
173 XmAddProtocolCallback(shell, wmpAtom, dwAtom, closeCB, arg);
177 ** Motif still generates spurious passive grab warnings on both IBM and SGI
178 ** This routine suppresses them.
179 ** (amai, 20011121:)
180 ** And triggers an annoying message on DEC systems on alpha ->
181 ** See Xt sources, xc/lib/Xt/Error.c:DefaultMsg()):
182 ** actually for some obscure reasons they check for XtError/Warning
183 ** handlers being installed when running as a root process!
184 ** Since this handler doesn't help on non-effected systems we should only
185 ** use it if necessary.
187 void SuppressPassiveGrabWarnings(void)
189 #if !defined(__alpha) && !defined(__EMX__)
190 XtSetWarningHandler(warnHandlerCB);
191 #endif
195 ** This routine kludges around the problem of backspace not being mapped
196 ** correctly when Motif is used between a server with a delete key in
197 ** the traditional typewriter backspace position and a client that
198 ** expects a backspace key in that position. Though there are three
199 ** distinct levels of key re-mapping in effect when a user is running
200 ** a Motif application, none of these is really appropriate or effective
201 ** for eliminating the delete v.s. backspace problem. Our solution is,
202 ** sadly, to eliminate the forward delete functionality of the delete key
203 ** in favor of backwards delete for both keys. So as not to prevent the
204 ** user or the application from applying other translation table re-mapping,
205 ** we apply re-map the key as a post-processing step, applied after widget
206 ** creation. As a result, the re-mapping necessarily becomes embedded
207 ** throughout an application (wherever text widgets are created), and
208 ** within library routines, including the Nirvana utility library. To
209 ** make this remapping optional, the SetDeleteRemap function provides a
210 ** way for an application to turn this functionality on and off. It is
211 ** recommended that applications that use this routine provide an
212 ** application resource called remapDeleteKey so savvy users can get
213 ** their forward delete functionality back.
215 void RemapDeleteKey(Widget w)
217 static XtTranslations table = NULL;
218 static char *translations =
219 "~Shift~Ctrl~Meta~Alt<Key>osfDelete: delete-previous-character()\n";
221 if (RemapDeleteEnabled) {
222 if (table == NULL)
223 table = XtParseTranslationTable(translations);
224 XtOverrideTranslations(w, table);
228 void SetDeleteRemap(int state)
230 RemapDeleteEnabled = state;
235 ** The routine adds the passed in top-level Widget's window to our
236 ** window group. On the first call a dummy unmapped window will
237 ** be created to be our leader. This must not be called before the
238 ** Widget has be realized and should be called before the window is
239 ** mapped.
241 void SetWindowGroup(Widget shell) {
242 static int firstTime = True;
243 static Window groupLeader;
244 Display *display = XtDisplay(shell);
245 XWMHints *wmHints;
247 if (firstTime) {
248 /* Create a dummy window to be the group leader for our windows */
249 String name, class;
250 XClassHint *classHint;
252 groupLeader = XCreateSimpleWindow(display,
253 RootWindow(display, DefaultScreen(display)),
254 1, 1, 1, 1, 0, 0, 0);
256 /* Set it's class hint so it will be identified correctly by the
257 window manager */
258 XtGetApplicationNameAndClass(display, &name, &class);
259 classHint = XAllocClassHint();
260 classHint->res_name = name;
261 classHint->res_class = class;
262 XSetClassHint(display, groupLeader, classHint);
263 XFree(classHint);
265 firstTime = False;
268 /* Set the window group hint for this shell's window */
269 wmHints = XGetWMHints(display, XtWindow(shell));
270 wmHints->window_group = groupLeader;
271 wmHints->flags |= WindowGroupHint;
272 XSetWMHints(display, XtWindow(shell), wmHints);
273 XFree(wmHints);
277 ** This routine resolves a window manager protocol incompatibility between
278 ** the X toolkit and several popular window managers. Using this in place
279 ** of XtRealizeWidget will realize the window in a way which allows the
280 ** affected window managers to apply their own placement strategy to the
281 ** window, as opposed to forcing the window to a specific location.
283 ** One of the hints in the WM_NORMAL_HINTS protocol, PPlacement, gets set by
284 ** the X toolkit (probably part of the Core or Shell widget) when a shell
285 ** widget is realized to the value stored in the XmNx and XmNy resources of the
286 ** Core widget. While callers can set these values, there is no "unset" value
287 ** for these resources. On systems which are more Motif aware, a PPosition
288 ** hint of 0,0, which is the default for XmNx and XmNy, is interpreted as,
289 ** "place this as if no hints were specified". Unfortunately the fvwm family
290 ** of window managers, which are now some of the most popular, interpret this
291 ** as "place this window at (0,0)". This routine intervenes between the
292 ** realizing and the mapping of the window to remove the inappropriate
293 ** PPlacement hint.
295 void RealizeWithoutForcingPosition(Widget shell)
297 XSizeHints *hints = XAllocSizeHints();
298 long suppliedHints;
299 Boolean mappedWhenManaged;
301 /* Temporarily set value of XmNmappedWhenManaged
302 to stop the window from popping up right away */
303 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
304 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
306 /* Realize the widget in unmapped state */
307 XtRealizeWidget(shell);
309 /* Get rid of the incorrect WMNormal hint */
310 if (XGetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints,
311 &suppliedHints)) {
312 hints->flags &= ~PPosition;
313 XSetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints);
315 XFree(hints);
317 /* Set WindowGroupHint so the NEdit icons can be grouped; this
318 seems to be necessary starting with Gnome 2.0 */
319 SetWindowGroup(shell);
321 /* Map the widget */
322 XtMapWidget(shell);
324 /* Restore the value of XmNmappedWhenManaged */
325 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
329 ** Older X applications and X servers were mostly designed to operate with
330 ** visual class PseudoColor, because older displays were at most 8 bits
331 ** deep. Modern X servers, however, usually support 24 bit depth and other
332 ** color models. Sun (and others?) still sets their default visual to
333 ** 8-bit PseudoColor, because some of their X applications don't work
334 ** properly with the other color models. The problem with PseudoColor, of
335 ** course, is that users run out of colors in the default colormap, and if
336 ** they install additional colormaps for individual applications, colors
337 ** flash and change weirdly when you change your focus from one application
338 ** to another.
340 ** In addition to the poor choice of default, a design flaw in Xt makes it
341 ** impossible even for savvy users to specify the XtNvisual resource to
342 ** switch to a deeper visual. The problem is that the colormap resource is
343 ** processed independently of the visual resource, and usually results in a
344 ** colormap for the default visual rather than for the user-selected one.
346 ** This routine should be called before creating a shell widget, to
347 ** pre-process the visual, depth, and colormap resources, and return the
348 ** proper values for these three resources to be passed to XtAppCreateShell.
349 ** Applications which actually require a particular color model (i.e. for
350 ** doing color table animation or dynamic color assignment) should not use
351 ** this routine.
353 ** Note that a consequence of using the "best" as opposed to the default
354 ** visual is that some color resources are still converted with the default
355 ** visual (particularly *background), and these must be avoided by widgets
356 ** which are allowed to handle any visual.
358 ** Returns True if the best visual is the default, False otherwise.
360 Boolean FindBestVisual(Display *display, const char *appName, const char *appClass,
361 Visual **visual, int *depth, Colormap *colormap)
363 char rsrcName[256], rsrcClass[256], *valueString, *type, *endPtr;
364 XrmValue value;
365 int screen = DefaultScreen(display);
366 int reqDepth = -1;
367 long reqID = -1; /* should hold a 'VisualID' and a '-1' ... */
368 int reqClass = -1;
369 int installColormap = FALSE;
370 int maxDepth, bestClass, bestVisual, nVis, i, j;
371 XVisualInfo visTemplate, *visList = NULL;
372 static Visual *cachedVisual = NULL;
373 static Colormap cachedColormap;
374 static int cachedDepth = 0;
375 int bestClasses[] = {StaticGray, GrayScale, StaticColor, PseudoColor,
376 DirectColor, TrueColor};
378 /* If results have already been computed, just return them */
379 if (cachedVisual != NULL) {
380 *visual = cachedVisual;
381 *depth = cachedDepth;
382 *colormap = cachedColormap;
383 return (*visual == DefaultVisual(display, screen));
386 /* Read the visualID and installColormap resources for the application.
387 visualID can be specified either as a number (the visual id as
388 shown by xdpyinfo), as a visual class name, or as Best or Default. */
389 sprintf(rsrcName,"%s.%s", appName, "visualID");
390 sprintf(rsrcClass, "%s.%s", appClass, "VisualID");
391 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
392 &value)) {
393 valueString = value.addr;
394 reqID = (int)strtol(valueString, &endPtr, 0);
395 if (endPtr == valueString) {
396 reqID = -1;
397 if (stripCaseCmp(valueString, "Default"))
398 reqID = DefaultVisual(display, screen)->visualid;
399 else if (stripCaseCmp(valueString, "StaticGray"))
400 reqClass = StaticGray;
401 else if (stripCaseCmp(valueString, "StaticColor"))
402 reqClass = StaticColor;
403 else if (stripCaseCmp(valueString, "TrueColor"))
404 reqClass = TrueColor;
405 else if (stripCaseCmp(valueString, "GrayScale"))
406 reqClass = GrayScale;
407 else if (stripCaseCmp(valueString, "PseudoColor"))
408 reqClass = PseudoColor;
409 else if (stripCaseCmp(valueString, "DirectColor"))
410 reqClass = DirectColor;
411 else if (!stripCaseCmp(valueString, "Best"))
412 fprintf(stderr, "Invalid visualID resource value\n");
415 sprintf(rsrcName,"%s.%s", appName, "installColormap");
416 sprintf(rsrcClass, "%s.%s", appClass, "InstallColormap");
417 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
418 &value)) {
419 if (stripCaseCmp(value.addr, "Yes") || stripCaseCmp(value.addr, "True"))
420 installColormap = TRUE;
423 visTemplate.screen = screen;
425 /* Generate a list of visuals to consider. (Note, vestigial code for
426 user-requested visual depth is left in, just in case that function
427 might be needed again, but it does nothing) */
428 if (reqID != -1) {
429 visTemplate.visualid = reqID;
430 visList = XGetVisualInfo(display, VisualScreenMask|VisualIDMask,
431 &visTemplate, &nVis);
432 if (visList == NULL)
433 fprintf(stderr, "VisualID resource value not valid\n");
435 if (visList == NULL && reqClass != -1 && reqDepth != -1) {
436 visTemplate.class = reqClass;
437 visTemplate.depth = reqDepth;
438 visList = XGetVisualInfo(display,
439 VisualScreenMask| VisualClassMask | VisualDepthMask,
440 &visTemplate, &nVis);
441 if (visList == NULL)
442 fprintf(stderr, "Visual class/depth combination not available\n");
444 if (visList == NULL && reqClass != -1) {
445 visTemplate.class = reqClass;
446 visList = XGetVisualInfo(display, VisualScreenMask|VisualClassMask,
447 &visTemplate, &nVis);
448 if (visList == NULL)
449 fprintf(stderr,
450 "Visual Class from resource \"visualID\" not available\n");
452 if (visList == NULL && reqDepth != -1) {
453 visTemplate.depth = reqDepth;
454 visList = XGetVisualInfo(display, VisualScreenMask|VisualDepthMask,
455 &visTemplate, &nVis);
456 if (visList == NULL)
457 fprintf(stderr, "Requested visual depth not available\n");
459 if (visList == NULL) {
460 visList = XGetVisualInfo(display, VisualScreenMask, &visTemplate, &nVis);
461 if (visList == NULL) {
462 fprintf(stderr, "Internal Error: no visuals available?\n");
463 *visual = DefaultVisual(display, screen);
464 *depth = DefaultDepth(display, screen);
465 *colormap = DefaultColormap(display, screen);
466 return True;
470 /* Choose among the visuals in the candidate list. Prefer maximum
471 depth first then matching default, then largest value of bestClass
472 (I'm not sure whether we actually care about class) */
473 maxDepth = 0;
474 bestClass = 0;
475 bestVisual = 0;
476 for (i=0; i < nVis; i++) {
477 if (visList[i].depth > maxDepth) {
478 maxDepth = visList[i].depth;
479 bestClass = 0;
480 bestVisual = i;
482 if (visList[i].depth == maxDepth) {
483 if (visList[i].visual == DefaultVisual(display, screen))
484 bestVisual = i;
485 if (visList[bestVisual].visual != DefaultVisual(display, screen)) {
486 for (j = 0; j < (int)XtNumber(bestClasses); j++) {
487 if (visList[i].class == bestClasses[j] && j > bestClass) {
488 bestClass = j;
489 bestVisual = i;
495 *visual = cachedVisual = visList[bestVisual].visual;
496 *depth = cachedDepth = visList[bestVisual].depth;
498 /* If the chosen visual is not the default, it needs a colormap allocated */
499 if (*visual == DefaultVisual(display, screen) && !installColormap)
500 *colormap = cachedColormap = DefaultColormap(display, screen);
501 else {
502 *colormap = cachedColormap = XCreateColormap(display,
503 RootWindow(display, screen), cachedVisual, AllocNone);
504 XInstallColormap(display, cachedColormap);
506 /* printf("Chose visual with depth %d, class %d, colormap %ld, id 0x%x\n",
507 visList[bestVisual].depth, visList[bestVisual].class,
508 *colormap, cachedVisual->visualid); */
509 /* Fix memory leak */
510 if (visList != NULL) {
511 XFree(visList);
514 return (*visual == DefaultVisual(display, screen));
518 ** If you want to use a non-default visual with Motif, shells all have to be
519 ** created with that visual, depth, and colormap, even if the parent has them
520 ** set up properly. Substituting these routines, will append visual args copied
521 ** from the parent widget (CreatePopupMenu and CreatePulldownMenu), or from the
522 ** best visual, obtained via FindBestVisual above (CreateShellWithBestVis).
524 Widget CreateDialogShell(Widget parent, char *name,
525 ArgList arglist, Cardinal argcount)
527 return addParentVisArgsAndCall(XmCreateDialogShell, parent, name, arglist,
528 argcount);
532 Widget CreatePopupMenu(Widget parent, char *name, ArgList arglist,
533 Cardinal argcount)
535 return addParentVisArgsAndCall(XmCreatePopupMenu, parent, name,
536 arglist, argcount);
540 Widget CreatePulldownMenu(Widget parent, char *name,
541 ArgList arglist, Cardinal argcount)
543 return addParentVisArgsAndCall(XmCreatePulldownMenu, parent, name, arglist,
544 argcount);
548 Widget CreatePromptDialog(Widget parent, char *name,
549 ArgList arglist, Cardinal argcount)
551 return addParentVisArgsAndCall(XmCreatePromptDialog, parent, name, arglist,
552 argcount);
556 Widget CreateSelectionDialog(Widget parent, char *name,
557 ArgList arglist, Cardinal argcount)
559 Widget dialog = addParentVisArgsAndCall(XmCreateSelectionDialog, parent, name,
560 arglist, argcount);
561 AddMouseWheelSupport(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST));
562 return dialog;
566 Widget CreateFormDialog(Widget parent, char *name,
567 ArgList arglist, Cardinal argcount)
569 return addParentVisArgsAndCall(XmCreateFormDialog, parent, name, arglist,
570 argcount);
574 Widget CreateFileSelectionDialog(Widget parent, char *name,
575 ArgList arglist, Cardinal argcount)
577 Widget dialog = addParentVisArgsAndCall(XmCreateFileSelectionDialog, parent,
578 name, arglist, argcount);
580 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_LIST));
581 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_DIR_LIST));
582 return dialog;
586 Widget CreateQuestionDialog(Widget parent, char *name,
587 ArgList arglist, Cardinal argcount)
589 return addParentVisArgsAndCall(XmCreateQuestionDialog, parent, name,
590 arglist, argcount);
594 Widget CreateMessageDialog(Widget parent, char *name,
595 ArgList arglist, Cardinal argcount)
597 return addParentVisArgsAndCall(XmCreateMessageDialog, parent, name,
598 arglist, argcount);
602 Widget CreateErrorDialog(Widget parent, char *name,
603 ArgList arglist, Cardinal argcount)
605 return addParentVisArgsAndCall(XmCreateErrorDialog, parent, name, arglist,
606 argcount);
609 Widget CreateWidget(Widget parent, const char *name, WidgetClass class,
610 ArgList arglist, Cardinal argcount)
612 Widget result;
613 ArgList al = addParentVisArgs(parent, arglist, &argcount);
614 result = XtCreateWidget(name, class, parent, al, argcount);
615 XtFree((char *)al);
616 return result;
619 Widget CreateShellWithBestVis(String appName, String appClass,
620 WidgetClass class, Display *display, ArgList args, Cardinal nArgs)
622 Visual *visual;
623 int depth;
624 Colormap colormap;
625 ArgList al;
626 Cardinal ac = nArgs;
627 Widget result;
629 FindBestVisual(display, appName, appClass, &visual, &depth, &colormap);
630 al = (ArgList)XtMalloc(sizeof(Arg) * (nArgs + 3));
631 if (nArgs != 0)
632 memcpy(al, args, sizeof(Arg) * nArgs);
633 XtSetArg(al[ac], XtNvisual, visual); ac++;
634 XtSetArg(al[ac], XtNdepth, depth); ac++;
635 XtSetArg(al[ac], XtNcolormap, colormap); ac++;
636 result = XtAppCreateShell(appName, appClass, class, display, al, ac);
637 XtFree((char *)al);
638 return result;
642 Widget CreatePopupShellWithBestVis(String shellName, WidgetClass class,
643 Widget parent, ArgList arglist, Cardinal argcount)
645 Widget result;
646 ArgList al = addParentVisArgs(parent, arglist, &argcount);
647 result = XtCreatePopupShell(shellName, class, parent, al, argcount);
648 XtFree((char *)al);
649 return result;
653 ** Extends an argument list for widget creation with additional arguments
654 ** for visual, colormap, and depth. The original argument list is not altered
655 ** and it's the caller's responsability to free the returned list.
657 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
658 Cardinal *argcount)
660 Visual *visual;
661 int depth;
662 Colormap colormap;
663 ArgList al;
664 Widget parentShell = parent;
666 /* Find the application/dialog/menu shell at the top of the widget
667 hierarchy, which has the visual resource being used */
668 while (True) {
669 if (XtIsShell(parentShell))
670 break;
671 if (parentShell == NULL) {
672 fprintf(stderr, "failed to find shell\n");
673 exit(EXIT_FAILURE);
675 parentShell = XtParent(parentShell);
678 /* Add the visual, depth, and colormap resources to the argument list */
679 XtVaGetValues(parentShell, XtNvisual, &visual, XtNdepth, &depth,
680 XtNcolormap, &colormap, NULL);
681 al = (ArgList)XtMalloc(sizeof(Arg) * ((*argcount) + 3));
682 if ((*argcount) != 0)
683 memcpy(al, arglist, sizeof(Arg) * (*argcount));
684 XtSetArg(al[*argcount], XtNvisual, visual); (*argcount)++;
685 XtSetArg(al[*argcount], XtNdepth, depth); (*argcount)++;
686 XtSetArg(al[*argcount], XtNcolormap, colormap); (*argcount)++;
687 return al;
692 ** Calls one of the Motif widget creation routines, splicing in additional
693 ** arguments for visual, colormap, and depth.
695 static Widget addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,
696 Widget parent, char *name, ArgList arglist, Cardinal argcount)
698 Widget result;
699 ArgList al = addParentVisArgs(parent, arglist, &argcount);
700 result = (*createRoutine)(parent, name, al, argcount);
701 XtFree((char *)al);
702 return result;
706 ** ManageDialogCenteredOnPointer is used in place of XtManageChild for
707 ** popping up a dialog to enable the dialog to be centered under the
708 ** mouse pointer. Whether it pops up the dialog centered under the pointer
709 ** or in its default position centered over the parent widget, depends on
710 ** the value set in the SetPointerCenteredDialogs call.
711 ** Additionally, this function constrains the size of the dialog to the
712 ** screen's size, to avoid insanely wide dialogs with obscured buttons.
714 void ManageDialogCenteredOnPointer(Widget dialogChild)
716 Widget shell = XtParent(dialogChild);
717 Window root, child;
718 unsigned int mask;
719 unsigned int width, height, borderWidth, depth;
720 int x, y, winX, winY, maxX, maxY, maxWidth, maxHeight;
721 Dimension xtWidth, xtHeight;
722 Boolean mappedWhenManaged;
723 static const int slop = 25;
725 /* Temporarily set value of XmNmappedWhenManaged
726 to stop the dialog from popping up right away */
727 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
728 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
730 /* Ensure that the dialog doesn't get wider/taller than the screen.
731 We use a hard-coded "slop" size because we don't know the border
732 width until it's on screen, and by then it's too late. Putting
733 this before managing the widgets allows it to get the geometry
734 right on the first pass and also prevents the user from
735 accidentally resizing too wide. */
736 maxWidth = XtScreen(shell)->width - slop;
737 maxHeight = XtScreen(shell)->height - slop;
739 XtVaSetValues(shell,
740 XmNmaxWidth, maxWidth,
741 XmNmaxHeight, maxHeight,
742 NULL);
744 /* Manage the dialog */
745 XtManageChild(dialogChild);
747 /* Check to see if the window manager doesn't respect XmNmaxWidth
748 and XmNmaxHeight on the first geometry pass (sawfish, twm, fvwm).
749 For this to work XmNresizePolicy must be XmRESIZE_NONE, otherwise
750 the dialog will try to expand anyway. */
751 XtVaGetValues(shell, XmNwidth, &xtWidth, XmNheight, &xtHeight, NULL);
752 if (xtWidth > maxWidth)
753 XtVaSetValues(shell, XmNwidth, (Dimension) maxWidth, NULL);
754 if (xtHeight > maxHeight)
755 XtVaSetValues(shell, XmNheight, (Dimension) maxHeight, NULL);
757 /* Only set the x/y position if the centering option is enabled.
758 Avoid getting the coordinates if not so, to save a few round-trips
759 to the server. */
760 if (PointerCenteredDialogsEnabled) {
761 /* Get the pointer position (x, y) */
762 XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child,
763 &x, &y, &winX, &winY, &mask);
765 /* Translate the pointer position (x, y) into a position for the new
766 window that will place the pointer at its center */
767 XGetGeometry(XtDisplay(shell), XtWindow(shell), &root, &winX, &winY,
768 &width, &height, &borderWidth, &depth);
769 width += 2 * borderWidth;
770 height += 2 * borderWidth;
772 x -= width/2;
773 y -= height/2;
775 /* Ensure that the dialog remains on screen */
776 maxX = maxWidth - width;
777 maxY = maxHeight - height;
778 if (x > maxX) x = maxX;
779 if (x < 0) x = 0;
780 if (y > maxY) y = maxY;
781 if (y < 0) y = 0;
783 /* Some window managers (Sawfish) don't appear to respond
784 to the geometry set call in synchronous mode. This causes
785 the window to delay XmNwmTimeout (default 5 seconds) before
786 posting, and it is very annoying. See "man VendorShell" for
787 more info. */
788 XtVaSetValues(shell, XmNuseAsyncGeometry, True, NULL);
790 /* Set desired window position in the DialogShell */
791 XtVaSetValues(shell, XmNx, x, XmNy, y, NULL);
794 /* Map the widget */
795 XtMapWidget(shell);
797 /* Restore the value of XmNmappedWhenManaged */
798 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
802 ** Cause dialogs created by libNUtil.a routines (such as DialogF),
803 ** and dialogs which use ManageDialogCenteredOnPointer to pop up
804 ** over the pointer (state = True), or pop up in their default
805 ** positions (state = False)
807 void SetPointerCenteredDialogs(int state)
809 PointerCenteredDialogsEnabled = state;
814 ** Raise a window to the top and give it the input focus. Setting input focus
815 ** is important on systems which use explict (rather than pointer) focus.
817 ** The X alternatives XMapRaised, and XSetInputFocus both have problems.
818 ** XMapRaised only gives the window the focus if it was initially not visible,
819 ** and XSetInputFocus sets the input focus, but crashes if the window is not
820 ** visible.
822 ** This routine should also be used in the case where a dialog is popped up and
823 ** subsequent calls to the dialog function use a flag, or the XtIsManaged, to
824 ** decide whether to create a new instance of the dialog, because on slower
825 ** systems, events can intervene while a dialog is still on its way up and its
826 ** window is still invisible, causing a subtle crash potential if
827 ** XSetInputFocus is used.
829 void RaiseShellWindow(Widget shell)
831 RaiseWindow(XtDisplay(shell), XtWindow(shell));
835 void RaiseWindow(Display *display, Window w)
837 XWindowAttributes winAttr;
839 XGetWindowAttributes(display, w, &winAttr);
840 if (winAttr.map_state == IsViewable)
841 XSetInputFocus(display, w, RevertToParent, CurrentTime);
843 XMapRaised(display, w);
847 ** Add a handler for mnemonics in a dialog (Motif currently only handles
848 ** mnemonics in menus) following the example of M.S. Windows. To add
849 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
850 ** a menu, on push buttons or toggle buttons, and call this function
851 ** when the dialog is fully constructed. Mnemonics added or changed
852 ** after this call will not be noticed. To add a mnemonic to a text field
853 ** or list, set the XmNmnemonic resource on the appropriate label and set
854 ** the XmNuserData resource of the label to the widget to get the focus
855 ** when the mnemonic is typed.
857 void AddDialogMnemonicHandler(Widget dialog, int unmodifiedToo)
859 XtAddEventHandler(dialog, KeyPressMask, False,
860 (XtEventHandler)mnemonicCB, (XtPointer)0);
861 addMnemonicGrabs(dialog, dialog, unmodifiedToo);
865 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
867 void RemoveDialogMnemonicHandler(Widget dialog)
869 XtUngrabKey(dialog, AnyKey, Mod1Mask);
870 XtRemoveEventHandler(dialog, KeyPressMask, False,
871 (XtEventHandler)mnemonicCB, (XtPointer)0);
875 ** Patch around Motif's poor handling of menu accelerator keys. Motif
876 ** does not process menu accelerators when the caps lock or num lock
877 ** keys are engaged. To enable accelerators in these cases, call this
878 ** routine with the completed menu bar widget as "topMenuContainer", and
879 ** the top level shell widget as "topWidget". It will add key grabs for
880 ** all of the accelerators it finds in the topMenuContainer menu tree, and
881 ** an event handler which can process dropped accelerator events by (again)
882 ** traversing the menu tree looking for matching accelerators, and invoking
883 ** the appropriate button actions. Any dynamic additions to the menus
884 ** require a call to UpdateAccelLockPatch to add the additional grabs.
885 ** Unfortunately, these grabs can not be removed.
887 void AccelLockBugPatch(Widget topWidget, Widget topMenuContainer)
889 XtAddEventHandler(topWidget, KeyPressMask, False, lockCB, topMenuContainer);
890 addAccelGrabs(topWidget, topMenuContainer);
894 ** Add additional key grabs for new menu items added to the menus, for
895 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
896 ** the same widget passed in the original call to AccelLockBugPatch.
898 void UpdateAccelLockPatch(Widget topWidget, Widget newButton)
900 addAccelGrab(topWidget, newButton);
904 ** PopDownBugPatch
906 ** Under some circumstances, popping down a dialog and its parent in
907 ** rapid succession causes a crash. This routine delays and
908 ** processs events until receiving a ReparentNotify event.
909 ** (I have no idea why a ReparentNotify event occurs at all, but it does
910 ** mark the point where it is safe to destroy or pop down the parent, and
911 ** it might have something to do with the bug.) There is a failsafe in
912 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
913 ** Use this sparingly, only when real crashes are observed, and periodically
914 ** check to make sure that it is still necessary.
916 void PopDownBugPatch(Widget w)
918 time_t stopTime;
920 stopTime = time(NULL) + 1;
921 while (time(NULL) <= stopTime) {
922 XEvent event;
923 XtAppContext context = XtWidgetToApplicationContext(w);
924 XtAppPeekEvent(context, &event);
925 if (event.xany.type == ReparentNotify)
926 return;
927 XtAppProcessEvent(context, XtIMAll);
932 ** Convert a compound string to a C style null terminated string.
933 ** Returned string must be freed by the caller.
935 char *GetXmStringText(XmString fromString)
937 XmStringContext context;
938 char *text, *toPtr, *toString, *fromPtr;
939 XmStringCharSet charset;
940 XmStringDirection direction;
941 Boolean separator;
943 /* Malloc a buffer large enough to hold the string. XmStringLength
944 should always be slightly longer than necessary, but won't be
945 shorter than the equivalent null-terminated string */
946 toString = XtMalloc(XmStringLength(fromString));
948 /* loop over all of the segments in the string, copying each segment
949 into the output string and converting separators into newlines */
950 XmStringInitContext(&context, fromString);
951 toPtr = toString;
952 while (XmStringGetNextSegment(context, &text,
953 &charset, &direction, &separator)) {
954 for (fromPtr=text; *fromPtr!='\0'; fromPtr++)
955 *toPtr++ = *fromPtr;
956 if (separator)
957 *toPtr++ = '\n';
960 /* terminate the string, free the context, and return the string */
961 *toPtr++ = '\0';
962 XmStringFreeContext(context);
963 return toString;
967 ** Get the XFontStruct that corresponds to the default (first) font in
968 ** a Motif font list. Since Motif stores this, it saves us from storing
969 ** it or querying it from the X server.
971 XFontStruct *GetDefaultFontStruct(XmFontList font)
973 XFontStruct *fs;
974 XmFontContext context;
975 XmStringCharSet charset;
977 XmFontListInitFontContext(&context, font);
978 XmFontListGetNextFont(context, &charset, &fs);
979 XmFontListFreeFontContext(context);
980 XtFree(charset);
981 return fs;
985 ** Create a string table suitable for passing to XmList widgets
987 XmString* StringTable(int count, ... )
989 va_list ap;
990 XmString *array;
991 int i;
992 char *str;
994 va_start(ap, count);
995 array = (XmString*)XtMalloc((count+1) * sizeof(XmString));
996 for(i = 0; i < count; i++ ) {
997 str = va_arg(ap, char *);
998 array[i] = XmStringCreateSimple(str);
1000 array[i] = (XmString)0;
1001 va_end(ap);
1002 return(array);
1005 void FreeStringTable(XmString *table)
1007 int i;
1009 for(i = 0; table[i] != 0; i++)
1010 XmStringFree(table[i]);
1011 XtFree((char *)table);
1015 ** Simulate a button press. The purpose of this routine is show users what
1016 ** is happening when they take an action with a non-obvious side effect,
1017 ** such as when a user double clicks on a list item. The argument is an
1018 ** XmPushButton widget to "press"
1020 void SimulateButtonPress(Widget widget)
1022 XEvent keyEvent;
1024 memset((char *)&keyEvent, 0, sizeof(XKeyPressedEvent));
1025 keyEvent.type = KeyPress;
1026 keyEvent.xkey.serial = 1;
1027 keyEvent.xkey.send_event = True;
1029 if (XtIsSubclass(widget, xmGadgetClass))
1031 /* On some Motif implementations, asking a gadget for its
1032 window will crash, rather than return the window of its
1033 parent. */
1034 Widget parent = XtParent(widget);
1035 keyEvent.xkey.display = XtDisplay(parent);
1036 keyEvent.xkey.window = XtWindow(parent);
1038 XtCallActionProc(parent, "ManagerGadgetSelect",
1039 &keyEvent, NULL, 0);
1041 else
1043 keyEvent.xkey.display = XtDisplay(widget);
1044 keyEvent.xkey.window = XtWindow(widget);
1046 XtCallActionProc(widget, "ArmAndActivate", &keyEvent, NULL, 0);
1051 ** Add an item to an already established pull-down or pop-up menu, including
1052 ** mnemonics, accelerators and callbacks.
1054 Widget AddMenuItem(Widget parent, char *name, char *label,
1055 char mnemonic, char *acc, char *accText,
1056 XtCallbackProc callback, void *cbArg)
1058 Widget button;
1059 XmString st1, st2;
1061 button = XtVaCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
1062 XmNlabelString, st1=XmStringCreateSimple(label),
1063 XmNmnemonic, mnemonic,
1064 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1065 XmNaccelerator, acc, NULL);
1066 XtAddCallback(button, XmNactivateCallback, callback, cbArg);
1067 XmStringFree(st1);
1068 XmStringFree(st2);
1069 return button;
1073 ** Add a toggle button item to an already established pull-down or pop-up
1074 ** menu, including mnemonics, accelerators and callbacks.
1076 Widget AddMenuToggle(Widget parent, char *name, char *label,
1077 char mnemonic, char *acc, char *accText,
1078 XtCallbackProc callback, void *cbArg, int set)
1080 Widget button;
1081 XmString st1, st2;
1083 button = XtVaCreateManagedWidget(name, xmToggleButtonWidgetClass, parent,
1084 XmNlabelString, st1=XmStringCreateSimple(label),
1085 XmNmnemonic, mnemonic,
1086 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1087 XmNaccelerator, acc,
1088 XmNset, set, NULL);
1089 XtAddCallback(button, XmNvalueChangedCallback, callback, cbArg);
1090 XmStringFree(st1);
1091 XmStringFree(st2);
1092 return button;
1096 ** Add a separator line to a menu
1098 Widget AddMenuSeparator(Widget parent, char *name)
1100 Widget button;
1102 button = XmCreateSeparator(parent, name, NULL, 0);
1103 XtManageChild(button);
1104 return button;
1108 ** Add a sub-menu to an established pull-down or pop-up menu, including
1109 ** mnemonics, accelerators and callbacks. Returns the menu pane of the
1110 ** new sub menu.
1112 Widget AddSubMenu(Widget parent, char *name, char *label, char mnemonic)
1114 Widget menu;
1115 XmString st1;
1117 menu = CreatePulldownMenu(parent, name, NULL, 0);
1118 XtVaCreateManagedWidget(name, xmCascadeButtonWidgetClass, parent,
1119 XmNlabelString, st1=XmStringCreateSimple(label),
1120 XmNmnemonic, mnemonic,
1121 XmNsubMenuId, menu, NULL);
1122 XmStringFree(st1);
1123 return menu;
1127 ** SetIntLabel, SetFloatLabel, SetIntText, SetFloatText
1129 ** Set the text of a motif label or text widget to show an integer or
1130 ** floating number.
1132 void SetIntLabel(Widget label, int value)
1134 char labelString[20];
1135 XmString s1;
1137 sprintf(labelString, "%d", value);
1138 s1=XmStringCreateSimple(labelString);
1139 XtVaSetValues(label, XmNlabelString, s1, NULL);
1140 XmStringFree(s1);
1144 void SetFloatLabel(Widget label, double value)
1146 char labelString[20];
1147 XmString s1;
1149 sprintf(labelString, "%g", value);
1150 s1=XmStringCreateSimple(labelString);
1151 XtVaSetValues(label, XmNlabelString, s1, NULL);
1152 XmStringFree(s1);
1156 void SetIntText(Widget text, int value)
1158 char labelString[20];
1160 sprintf(labelString, "%d", value);
1161 XmTextSetString(text, labelString);
1165 void SetFloatText(Widget text, double value)
1167 char labelString[20];
1169 sprintf(labelString, "%g", value);
1170 XmTextSetString(text, labelString);
1174 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
1176 ** Get the text of a motif text widget as an integer or floating point number.
1177 ** The functions will return TEXT_READ_OK of the value was read correctly.
1178 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER. The
1179 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
1180 ** user that the value could not be read. The argument fieldName is used
1181 ** in the dialog to help the user identify where the problem is. Set
1182 ** warnBlank to true if a blank field is also considered an error.
1184 int GetFloatText(Widget text, double *value)
1186 char *strValue, *endPtr;
1187 int retVal;
1189 strValue = XmTextGetString(text); /* Get Value */
1190 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1191 *value = strtod(strValue, &endPtr); /* Convert string to double */
1192 if (strlen(strValue) == 0) /* String is empty */
1193 retVal = TEXT_IS_BLANK;
1194 else if (*endPtr != '\0') /* Whole string not parsed */
1195 retVal = TEXT_NOT_NUMBER;
1196 else
1197 retVal = TEXT_READ_OK;
1198 XtFree(strValue);
1199 return retVal;
1202 int GetIntText(Widget text, int *value)
1204 char *strValue, *endPtr;
1205 int retVal;
1207 strValue = XmTextGetString(text); /* Get Value */
1208 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1209 *value = strtol(strValue, &endPtr, 10); /* Convert string to long */
1210 if (strlen(strValue) == 0) /* String is empty */
1211 retVal = TEXT_IS_BLANK;
1212 else if (*endPtr != '\0') /* Whole string not parsed */
1213 retVal = TEXT_NOT_NUMBER;
1214 else
1215 retVal = TEXT_READ_OK;
1216 XtFree(strValue);
1217 return retVal;
1220 int GetFloatTextWarn(Widget text, double *value, const char *fieldName,
1221 int warnBlank)
1223 int result;
1224 char *valueStr;
1226 result = GetFloatText(text, value);
1227 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1228 return result;
1229 valueStr = XmTextGetString(text);
1231 if (result == TEXT_IS_BLANK)
1233 DialogF(DF_ERR, text, 1, "Warning", "Please supply %s value", "Dismiss",
1234 fieldName);
1235 } else /* TEXT_NOT_NUMBER */
1237 DialogF (DF_ERR, text, 1, "Warning", "Can't read %s value: \"%s\"",
1238 "Dismiss", fieldName, valueStr);
1241 XtFree(valueStr);
1242 return result;
1245 int GetIntTextWarn(Widget text, int *value, const char *fieldName, int warnBlank)
1247 int result;
1248 char *valueStr;
1250 result = GetIntText(text, value);
1251 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1252 return result;
1253 valueStr = XmTextGetString(text);
1255 if (result == TEXT_IS_BLANK)
1257 DialogF (DF_ERR, text, 1, "Warning", "Please supply a value for %s",
1258 "Dismiss", fieldName);
1259 } else /* TEXT_NOT_NUMBER */
1261 DialogF (DF_ERR, text, 1, "Warning",
1262 "Can't read integer value \"%s\" in %s", "Dismiss", valueStr,
1263 fieldName);
1266 XtFree(valueStr);
1267 return result;
1270 int TextWidgetIsBlank(Widget textW)
1272 char *str;
1273 int retVal;
1275 str = XmTextGetString(textW);
1276 removeWhiteSpace(str);
1277 retVal = *str == '\0';
1278 XtFree(str);
1279 return retVal;
1283 ** Turn a multi-line editing text widget into a fake single line text area
1284 ** by disabling the translation for Return. This is a way to give users
1285 ** extra space, by allowing wrapping, but still prohibiting newlines.
1286 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1287 ** the widget to be one line high).
1289 void MakeSingleLineTextW(Widget textW)
1291 static XtTranslations noReturnTable = NULL;
1292 static char *noReturnTranslations = "<Key>Return: activate()\n";
1294 if (noReturnTable == NULL)
1295 noReturnTable = XtParseTranslationTable(noReturnTranslations);
1296 XtOverrideTranslations(textW, noReturnTable);
1300 ** Add up-arrow/down-arrow recall to a single line text field. When user
1301 ** presses up-arrow, string is cleared and recent entries are presented,
1302 ** moving to older ones as each successive up-arrow is pressed. Down-arrow
1303 ** moves to more recent ones, final down-arrow clears the field. Associated
1304 ** routine, AddToHistoryList, makes maintaining a history list easier.
1306 ** Arguments are the widget, and pointers to the history list and number of
1307 ** items, which are expected to change periodically.
1309 void AddHistoryToTextWidget(Widget textW, char ***historyList, int *nItems)
1311 histInfo *histData;
1313 /* create a data structure for passing history info to the callbacks */
1314 histData = (histInfo *)XtMalloc(sizeof(histInfo));
1315 histData->list = historyList;
1316 histData->nItems = nItems;
1317 histData->index = -1;
1319 /* Add an event handler for handling up/down arrow events */
1320 XtAddEventHandler(textW, KeyPressMask, False,
1321 (XtEventHandler)histArrowKeyEH, histData);
1323 /* Add a destroy callback for freeing history data structure */
1324 XtAddCallback(textW, XmNdestroyCallback, histDestroyCB, histData);
1327 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1329 XtFree((char *)clientData);
1332 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
1333 Boolean *continueDispatch)
1335 histInfo *histData = (histInfo *)callData;
1336 KeySym keysym = XLookupKeysym((XKeyEvent *)event, 0);
1338 /* only process up and down arrow keys */
1339 if (keysym != XK_Up && keysym != XK_Down)
1340 return;
1342 /* increment or decrement the index depending on which arrow was pressed */
1343 histData->index += (keysym == XK_Up) ? 1 : -1;
1345 /* if the index is out of range, beep, fix it up, and return */
1346 if (histData->index < -1) {
1347 histData->index = -1;
1348 XBell(XtDisplay(w), 0);
1349 return;
1351 if (histData->index >= *histData->nItems) {
1352 histData->index = *histData->nItems - 1;
1353 XBell(XtDisplay(w), 0);
1354 return;
1357 /* Change the text field contents */
1358 XmTextSetString(w, histData->index == -1 ? "" :
1359 (*histData->list)[histData->index]);
1363 ** Copies a string on to the end of history list, which may be reallocated
1364 ** to make room. If historyList grows beyond its internally set boundary
1365 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1366 ** (HISTORY_LIST_TRIM_TO). Before adding to the list, checks if the item
1367 ** is a duplicate of the last item. If so, it is not added.
1369 void AddToHistoryList(char *newItem, char ***historyList, int *nItems)
1371 char **newList;
1372 int i;
1374 if (*nItems != 0 && !strcmp(newItem, **historyList))
1375 return;
1376 if (*nItems == HISTORY_LIST_MAX) {
1377 for (i=HISTORY_LIST_TRIM_TO; i<HISTORY_LIST_MAX; i++)
1378 XtFree((*historyList)[i]);
1379 *nItems = HISTORY_LIST_TRIM_TO;
1381 newList = (char **)XtMalloc(sizeof(char *) * (*nItems + 1));
1382 for (i=0; i < *nItems; i++)
1383 newList[i+1] = (*historyList)[i];
1384 if (*nItems != 0 && *historyList != NULL)
1385 XtFree((char *)*historyList);
1386 (*nItems)++;
1387 newList[0] = XtNewString(newItem);
1388 *historyList = newList;
1392 * PasswordText - routine to add a callback to any text widget so that all
1393 * text typed by the user is echoed with asterisks, allowing
1394 * a password to be typed in without being seen.
1396 * parameters: w - text widget to add the callback to
1397 * passTxt - pointer to a string created by caller of this routine.
1398 * **NOTE** The length of this string should be one
1399 * greater than the maximum specified by XmNmaxLength.
1400 * This string is set to empty just before the callback
1401 * is added.
1404 void PasswordText(Widget w, char *passTxt)
1406 passTxt[0] = '\0';
1407 XtAddCallback(w, XmNmodifyVerifyCallback, (XtCallbackProc)passwdCB,passTxt);
1411 ** BeginWait/EndWait
1413 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1415 void BeginWait(Widget topCursorWidget)
1417 Display *display = XtDisplay(topCursorWidget);
1418 Pixmap pixmap;
1419 Pixmap maskPixmap;
1420 XColor xcolors[2];
1421 static Cursor waitCursor = 0;
1423 /* if the watch cursor hasn't been created yet, create it */
1424 if (!waitCursor) {
1425 pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1426 (char *)watch_bits, watch_width, watch_height);
1428 maskPixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1429 (char *)watch_mask_bits, watch_width, watch_height);
1431 xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(display));
1432 xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(display));
1434 XQueryColors(display, DefaultColormapOfScreen(
1435 DefaultScreenOfDisplay(display)), xcolors, 2);
1436 waitCursor = XCreatePixmapCursor(display, pixmap, maskPixmap,
1437 &xcolors[0], &xcolors[1], watch_x_hot, watch_y_hot);
1438 XFreePixmap(display, pixmap);
1439 XFreePixmap(display, maskPixmap);
1442 /* display the cursor */
1443 XDefineCursor(display, XtWindow(topCursorWidget), waitCursor);
1446 void BusyWait(Widget widget)
1448 #ifdef __unix__
1449 static const int timeout = 100000; /* 1/10 sec = 100 ms = 100,000 us */
1450 static struct timeval last = { 0, 0 };
1451 struct timeval current;
1452 gettimeofday(&current, NULL);
1454 if ((current.tv_sec != last.tv_sec) ||
1455 (current.tv_usec - last.tv_usec > timeout))
1457 XmUpdateDisplay(widget);
1458 last = current;
1460 #else
1461 static time_t last = 0;
1462 time_t current;
1463 time(&current);
1465 if (difftime(current, last) > 0)
1467 XmUpdateDisplay(widget);
1468 last = current;
1470 #endif
1473 void EndWait(Widget topCursorWidget)
1475 XUndefineCursor(XtDisplay(topCursorWidget), XtWindow(topCursorWidget));
1479 ** Create an X window geometry string from width, height, x, and y values.
1480 ** This is a complement to the X routine XParseGeometry, and uses the same
1481 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1482 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1483 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1484 ** result. Note that in a geometry string, it is not possible to supply a y
1485 ** position without an x position. Also note that the X/YNegative flags
1486 ** mean "add a '-' and negate the value" which is kind of odd.
1488 void CreateGeometryString(char *string, int x, int y,
1489 int width, int height, int bitmask)
1491 char *ptr = string;
1492 int nChars;
1494 if (bitmask & WidthValue) {
1495 sprintf(ptr, "%d%n", width, &nChars);
1496 ptr += nChars;
1498 if (bitmask & HeightValue) {
1499 sprintf(ptr, "x%d%n", height, &nChars);
1500 ptr += nChars;
1502 if (bitmask & XValue) {
1503 if (bitmask & XNegative)
1504 sprintf(ptr, "-%d%n", -x, &nChars);
1505 else
1506 sprintf(ptr, "+%d%n", x, &nChars);
1507 ptr += nChars;
1509 if (bitmask & YValue) {
1510 if (bitmask & YNegative)
1511 sprintf(ptr, "-%d%n", -y, &nChars);
1512 else
1513 sprintf(ptr, "+%d%n", y, &nChars);
1514 ptr += nChars;
1516 *ptr = '\0';
1519 /* */
1520 /* passwdCB: callback routine added by PasswordText routine. This routine */
1521 /* echoes each character typed as an asterisk (*) and a few other */
1522 /* necessary things so that the password typed in is not visible */
1523 /* */
1524 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
1525 *txtVerStr)
1527 /* XmTextVerifyCallbackStruct: */
1528 /* int reason; should be XmCR_MODIFYING_TEXT_VALUE */
1529 /* XEvent *event; points to XEvent that triggered the callback */
1530 /* Boolean doit; indicates whether action should be performed; setting */
1531 /* this to false negates the action */
1532 /* long currInsert, current position of insert cursor */
1533 /* newInsert; position user attempts to position the insert cursor */
1534 /* long startPos, starting position of the text to modify */
1535 /* endPos; ending position of the text to modify */
1536 /* XmTextBlock text; */
1538 /* XmTextBlock (used to pass text around): */
1539 /* char *ptr; points to text to be inserted */
1540 /* int length; Number of bytes (length) */
1541 /* XmTextFormat format; Representations format */
1543 /* XmTextFormat: either FMT8BIT or FMT16BIT */
1546 int numCharsTyped, i, j, pos;
1548 /* ensure text format is 8-bit characters */
1549 if (txtVerStr->text->format != FMT8BIT)
1550 return;
1552 /* verify assumptions */
1553 /* if (txtVerStr->endPos < txtVerStr->startPos)
1554 fprintf(stderr, "User password callback error: endPos < startPos\n");
1555 if (strlen(passTxt) == 0 && txtVerStr->endPos != 0)
1556 fprintf(stderr, "User password callback error: no txt, but end != 0\n");
1558 printf("passTxt = %s, startPos = %d, endPos = %d, txtBlkAddr = %d\n",
1559 passTxt, txtVerStr->startPos, txtVerStr->endPos, txtVerStr->text);
1560 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1561 printf(" string typed = %s, length = %d\n", txtVerStr->text->ptr,
1562 txtVerStr->text->length);
1564 /* If necessary, expand/compress passTxt and insert any new text */
1565 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1566 numCharsTyped = txtVerStr->text->length;
1567 else
1568 numCharsTyped = 0;
1569 /* numCharsTyped = # chars to insert (that user typed) */
1570 /* j = # chars to expand (+) or compress (-) the password string */
1571 j = numCharsTyped - (txtVerStr->endPos - txtVerStr->startPos);
1572 if (j > 0) /* expand case: start at ending null */
1573 for (pos = strlen(passTxt) + 1; pos >= txtVerStr->endPos; --pos)
1574 passTxt[pos+j] = passTxt[pos];
1575 if (j < 0) /* compress case */
1576 for (pos = txtVerStr->startPos + numCharsTyped;
1577 pos <= (int)strlen(passTxt)+1; ++pos)
1578 passTxt[pos] = passTxt[pos-j];
1579 /* then copy text to be inserted into passTxt */
1580 for (pos = txtVerStr->startPos, i = 0; i < numCharsTyped; ++i) {
1581 passTxt[pos+i] = *(txtVerStr->text->ptr + i);
1582 /* Replace text typed by user with asterisks (*) */
1583 *(txtVerStr->text->ptr + i) = '*';
1585 /* printf(" Password string now = %s\n", passTxt); */
1589 ** Remove the white space (blanks and tabs) from a string
1591 static void removeWhiteSpace(char *string)
1593 char *outPtr = string;
1595 while (TRUE) {
1596 if (*string == 0) {
1597 *outPtr = 0;
1598 return;
1599 } else if (*string != ' ' && *string != '\t')
1600 *(outPtr++) = *(string++);
1601 else
1602 string++;
1607 ** Compares two strings and return TRUE if the two strings
1608 ** are the same, ignoring whitespace and case differences.
1610 static int stripCaseCmp(const char *str1, const char *str2)
1612 const char *c1, *c2;
1614 for (c1=str1, c2=str2; *c1!='\0' && *c2!='\0'; c1++, c2++) {
1615 while (*c1 == ' ' || *c1 == '\t')
1616 c1++;
1617 while (*c2 == ' ' || *c2 == '\t')
1618 c2++;
1619 if (toupper((unsigned char)*c1) != toupper((unsigned char)*c2))
1620 return FALSE;
1622 return *c1 == '\0' && *c2 == '\0';
1625 static void warnHandlerCB(String message)
1627 if (strstr(message, "XtRemoveGrab"))
1628 return;
1629 if (strstr(message, "Attempt to remove non-existant passive grab"))
1630 return;
1631 fputs(message, stderr);
1632 fputc('\n', stderr);
1635 static XModifierKeymap *getKeyboardMapping(Display *display) {
1636 static XModifierKeymap *keyboardMap = NULL;
1638 if (keyboardMap == NULL) {
1639 keyboardMap = XGetModifierMapping(display);
1641 return(keyboardMap);
1645 ** get mask for a modifier
1649 static Modifiers findModifierMapping(Display *display, KeyCode keyCode) {
1650 int i, j;
1651 KeyCode *mapentry;
1652 XModifierKeymap *modMap = getKeyboardMapping(display);
1654 if (modMap == NULL || keyCode == 0) {
1655 return(0);
1658 mapentry = modMap->modifiermap;
1659 for (i = 0; i < 8; ++i) {
1660 for (j = 0; j < (modMap->max_keypermod); ++j) {
1661 if (keyCode == *mapentry) {
1662 return(1 << ((mapentry - modMap->modifiermap) / modMap->max_keypermod));
1664 ++mapentry;
1667 return(0);
1670 Modifiers GetNumLockModMask(Display *display) {
1671 static int numLockMask = -1;
1673 if (numLockMask == -1) {
1674 numLockMask = findModifierMapping(display, XKeysymToKeycode(display, XK_Num_Lock));
1676 return(numLockMask);
1680 ** Grab a key regardless of caps-lock and other silly latching keys.
1684 static void reallyGrabAKey(Widget dialog, int keyCode, Modifiers mask) {
1685 Modifiers numLockMask = GetNumLockModMask(XtDisplay(dialog));
1687 if (keyCode == 0) /* No anykey grabs, sorry */
1688 return;
1690 XtGrabKey(dialog, keyCode, mask, True, GrabModeAsync, GrabModeAsync);
1691 XtGrabKey(dialog, keyCode, mask|LockMask, True, GrabModeAsync, GrabModeAsync);
1692 if (numLockMask && numLockMask != LockMask) {
1693 XtGrabKey(dialog, keyCode, mask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1694 XtGrabKey(dialog, keyCode, mask|LockMask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1699 ** Part of dialog mnemonic processing. Search the widget tree under w
1700 ** for widgets with mnemonics. When found, add a passive grab to the
1701 ** dialog widget for the mnemonic character, thus directing mnemonic
1702 ** events to the dialog widget.
1704 static void addMnemonicGrabs(Widget dialog, Widget w, int unmodifiedToo)
1706 char mneString[2];
1707 WidgetList children;
1708 Cardinal numChildren;
1709 int i, isMenu;
1710 KeySym mnemonic = '\0';
1711 unsigned char rowColType;
1712 unsigned int keyCode;
1714 if (XtIsComposite(w)) {
1715 if (XtClass(w) == xmRowColumnWidgetClass) {
1716 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1717 isMenu = rowColType != XmWORK_AREA;
1718 } else
1719 isMenu = False;
1720 if (!isMenu) {
1721 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1722 &numChildren, NULL);
1723 for (i=0; i<(int)numChildren; i++)
1724 addMnemonicGrabs(dialog, children[i], unmodifiedToo);
1726 } else {
1727 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1728 if (mnemonic != XK_VoidSymbol && mnemonic != '\0') {
1729 mneString[0] = mnemonic; mneString[1] = '\0';
1730 keyCode = XKeysymToKeycode(XtDisplay(dialog),
1731 XStringToKeysym(mneString));
1732 reallyGrabAKey(dialog, keyCode, Mod1Mask);
1733 if (unmodifiedToo)
1734 reallyGrabAKey(dialog, keyCode, 0);
1740 ** Callback routine for dialog mnemonic processing.
1742 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event)
1744 findAndActivateMnemonic(w, event->keycode);
1748 ** Look for a widget in the widget tree w, with a mnemonic matching
1749 ** keycode. When one is found, simulate a button press on that widget
1750 ** and give it the keyboard focus. If the mnemonic is on a label,
1751 ** look in the userData field of the label to see if it points to
1752 ** another widget, and give that the focus. This routine is just
1753 ** sufficient for NEdit, no doubt it will need to be extended for
1754 ** mnemonics on widgets other than just buttons and text fields.
1756 static void findAndActivateMnemonic(Widget w, unsigned int keycode)
1758 WidgetList children;
1759 Cardinal numChildren;
1760 int i, isMenu;
1761 KeySym mnemonic = '\0';
1762 char mneString[2];
1763 Widget userData;
1764 unsigned char rowColType;
1766 if (XtIsComposite(w)) {
1767 if (XtClass(w) == xmRowColumnWidgetClass) {
1768 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1769 isMenu = rowColType != XmWORK_AREA;
1770 } else
1771 isMenu = False;
1772 if (!isMenu) {
1773 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1774 &numChildren, NULL);
1775 for (i=0; i<(int)numChildren; i++)
1776 findAndActivateMnemonic(children[i], keycode);
1778 } else {
1779 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1780 if (mnemonic != '\0') {
1781 mneString[0] = mnemonic; mneString[1] = '\0';
1782 if (XKeysymToKeycode(XtDisplay(XtParent(w)),
1783 XStringToKeysym(mneString)) == keycode) {
1784 if (XtClass(w) == xmLabelWidgetClass ||
1785 XtClass(w) == xmLabelGadgetClass) {
1786 XtVaGetValues(w, XmNuserData, &userData, NULL);
1787 if (userData!=NULL && XtIsWidget(userData) &&
1788 XmIsTraversable(userData))
1789 XmProcessTraversal(userData, XmTRAVERSE_CURRENT);
1790 } else if (XmIsTraversable(w)) {
1791 XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1792 SimulateButtonPress(w);
1800 ** Part of workaround for Motif Caps/Num Lock bug. Search the widget tree
1801 ** under w for widgets with accelerators. When found, add three passive
1802 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1803 ** Lock, one for Num Lock, and one for both, thus directing lock +
1804 ** accelerator events to topWidget.
1806 static void addAccelGrabs(Widget topWidget, Widget w)
1808 WidgetList children;
1809 Widget menu;
1810 Cardinal numChildren;
1811 int i;
1813 if (XtIsComposite(w)) {
1814 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1815 &numChildren, NULL);
1816 for (i=0; i<(int)numChildren; i++)
1817 addAccelGrabs(topWidget, children[i]);
1818 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1819 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1820 if (menu != NULL)
1821 addAccelGrabs(topWidget, menu);
1822 } else
1823 addAccelGrab(topWidget, w);
1827 ** Grabs the key + modifier defined in the widget's accelerator resource,
1828 ** in combination with the Caps Lock and Num Lock accelerators.
1830 static void addAccelGrab(Widget topWidget, Widget w)
1832 char *accelString = NULL;
1833 KeySym keysym;
1834 unsigned int modifiers;
1835 KeyCode code;
1836 Modifiers numLockMask = GetNumLockModMask(XtDisplay(topWidget));
1838 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1839 if (accelString == NULL || *accelString == '\0') {
1840 if (accelString != NULL) XtFree(accelString);
1841 return;
1844 if (!parseAccelString(XtDisplay(topWidget), accelString, &keysym, &modifiers)) {
1845 XtFree(accelString);
1846 return;
1848 XtFree(accelString);
1850 /* Check to see if this server has this key mapped. Some cruddy PC X
1851 servers (Xoftware) have terrible default keymaps. If not,
1852 XKeysymToKeycode will return 0. However, it's bad news to pass
1853 that to XtGrabKey because 0 is really "AnyKey" which is definitely
1854 not what we want!! */
1856 code = XKeysymToKeycode(XtDisplay(topWidget), keysym);
1857 if (code == 0)
1858 return;
1860 XtGrabKey(topWidget, code,
1861 modifiers | LockMask, True, GrabModeAsync, GrabModeAsync);
1862 if (numLockMask && numLockMask != LockMask) {
1863 XtGrabKey(topWidget, code,
1864 modifiers | numLockMask, True, GrabModeAsync, GrabModeAsync);
1865 XtGrabKey(topWidget, code,
1866 modifiers | LockMask | numLockMask, True, GrabModeAsync, GrabModeAsync);
1871 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1872 ** Returns TRUE if the parse was successful, FALSE, if not.
1874 static int parseAccelString(Display *display, const char *string, KeySym *keySym,
1875 unsigned int *modifiers)
1877 #define N_MODIFIERS 12
1878 /*... Is NumLock always Mod3? */
1879 static char *modifierNames[N_MODIFIERS] = {"Ctrl", "Shift", "Alt", "Mod2",
1880 "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1881 "Button5"};
1882 static unsigned int modifierMasks[N_MODIFIERS] = {ControlMask, ShiftMask,
1883 Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, Button1Mask, Button2Mask,
1884 Button3Mask, Button4Mask, Button5Mask};
1885 Modifiers numLockMask = GetNumLockModMask(display);
1886 char modStr[MAX_ACCEL_LEN];
1887 char evtStr[MAX_ACCEL_LEN];
1888 char keyStr[MAX_ACCEL_LEN];
1889 const char *c, *evtStart, *keyStart;
1890 int i;
1892 if (strlen(string) >= MAX_ACCEL_LEN)
1893 return FALSE;
1895 /* Get the modifier part */
1896 for (c = string; *c != '<'; c++)
1897 if (*c == '\0')
1898 return FALSE;
1899 strncpy(modStr, string, c - string);
1900 modStr[c - string] = '\0';
1902 /* Verify the <key> or <keypress> part */
1903 evtStart = c;
1904 for ( ; *c != '>'; c++)
1905 if (*c == '\0')
1906 return FALSE;
1907 c++;
1908 strncpy(evtStr, evtStart, c - evtStart);
1909 evtStr[c - evtStart] = '\0';
1910 if (!stripCaseCmp(evtStr, "<key>") && !stripCaseCmp(evtStr, "<keypress>"))
1911 return FALSE;
1913 /* Get the keysym part */
1914 keyStart = c;
1915 for ( ; *c != '\0'; c++);
1916 strncpy(keyStr, keyStart, c - keyStart);
1917 keyStr[c - keyStart] = '\0';
1918 *keySym = XStringToKeysym(keyStr);
1920 /* Parse the modifier part */
1921 *modifiers = 0;
1922 c = modStr;
1923 while (*c != '\0') {
1924 while (*c == ' ' || *c == '\t')
1925 c++;
1926 if (*c == '\0')
1927 break;
1928 for (i = 0; i < N_MODIFIERS; i++) {
1929 if (!strncmp(c, modifierNames[i], strlen(modifierNames[i]))) {
1930 c += strlen(modifierNames[i]);
1931 if (modifierMasks[i] != numLockMask) {
1932 *modifiers |= modifierMasks[i];
1934 break;
1937 if (i == N_MODIFIERS)
1938 return FALSE;
1941 return TRUE;
1945 ** Event handler for patching around Motif's lock + accelerator problem.
1946 ** Looks for a menu item in the patched menu hierarchy and invokes its
1947 ** ArmAndActivate action.
1949 static void lockCB(Widget w, XtPointer callData, XEvent *event,
1950 Boolean *continueDispatch)
1952 Modifiers numLockMask = GetNumLockModMask(XtDisplay(w));
1953 Widget topMenuWidget = (Widget)callData;
1954 *continueDispatch = TRUE;
1955 if (!(((XKeyEvent *)event)->state & (LockMask | numLockMask)))
1956 return;
1958 if (!findAndActivateAccel(topMenuWidget, ((XKeyEvent *)event)->keycode,
1959 ((XKeyEvent *)event)->state & ~(LockMask | numLockMask), event))
1960 *continueDispatch = FALSE;
1964 ** Search through menu hierarchy under w and look for a button with
1965 ** accelerator matching keyCode + modifiers, and do its action
1967 static int findAndActivateAccel(Widget w, unsigned int keyCode,
1968 unsigned int modifiers, XEvent *event)
1971 WidgetList children;
1972 Widget menu;
1973 Cardinal numChildren;
1974 int i;
1975 char *accelString = NULL;
1976 KeySym keysym;
1977 unsigned int mods;
1979 if (XtIsComposite(w)) {
1980 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1981 &numChildren, NULL);
1982 for (i=0; i<(int)numChildren; i++)
1983 if (findAndActivateAccel(children[i], keyCode, modifiers, event))
1984 return TRUE;
1985 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1986 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1987 if (menu != NULL)
1988 if (findAndActivateAccel(menu, keyCode, modifiers, event))
1989 return TRUE;
1990 } else {
1991 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1992 if (accelString != NULL && *accelString != '\0') {
1993 if (!parseAccelString(XtDisplay(w), accelString, &keysym, &mods))
1994 return FALSE;
1995 if (keyCode == XKeysymToKeycode(XtDisplay(w), keysym) &&
1996 modifiers == mods) {
1997 if (XtIsSensitive(w)) {
1998 XtCallActionProc(w, "ArmAndActivate", event, NULL, 0);
1999 return TRUE;
2004 return FALSE;
2008 ** Global installation of mouse wheel actions for scrolled windows.
2010 void InstallMouseWheelActions(XtAppContext context)
2012 static XtActionsRec Actions[] = {
2013 {"scrolled-window-scroll-up", scrollUpAP},
2014 {"scrolled-window-page-up", pageUpAP},
2015 {"scrolled-window-scroll-down", scrollDownAP},
2016 {"scrolled-window-page-down", pageDownAP}
2019 XtAppAddActions(context, Actions, XtNumber(Actions));
2023 ** Add mouse wheel support to a specific widget, which must be the scrollable
2024 ** widget of a ScrolledWindow.
2026 void AddMouseWheelSupport(Widget w)
2028 if (XmIsScrolledWindow(XtParent(w)))
2030 static const char scrollTranslations[] =
2031 "Shift<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(1)\n"
2032 "Shift<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(1)\n"
2033 "Ctrl<Btn4Down>,<Btn4Up>: scrolled-window-page-up()\n"
2034 "Ctrl<Btn5Down>,<Btn5Up>: scrolled-window-page-down()\n"
2035 "<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(3)\n"
2036 "<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(3)\n";
2037 static XtTranslations trans_table = NULL;
2039 if (trans_table == NULL)
2041 trans_table = XtParseTranslationTable(scrollTranslations);
2043 XtOverrideTranslations(w, trans_table);
2047 static void pageUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2049 Widget scrolledWindow, scrollBar;
2050 String al[1];
2052 al[0] = "Up";
2053 scrolledWindow = XtParent(w);
2054 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2055 if (scrollBar)
2056 XtCallActionProc(scrollBar, "PageUpOrLeft", event, al, 1) ;
2057 return;
2060 static void pageDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2062 Widget scrolledWindow, scrollBar;
2063 String al[1];
2065 al[0] = "Down";
2066 scrolledWindow = XtParent(w);
2067 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2068 if (scrollBar)
2069 XtCallActionProc(scrollBar, "PageDownOrRight", event, al, 1) ;
2070 return;
2073 static void scrollUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2075 Widget scrolledWindow, scrollBar;
2076 String al[1];
2077 int i, nLines;
2079 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2080 return;
2081 al[0] = "Up";
2082 scrolledWindow = XtParent(w);
2083 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2084 if (scrollBar)
2085 for (i=0; i<nLines; i++)
2086 XtCallActionProc(scrollBar, "IncrementUpOrLeft", event, al, 1) ;
2087 return;
2090 static void scrollDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2092 Widget scrolledWindow, scrollBar;
2093 String al[1];
2094 int i, nLines;
2096 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2097 return;
2098 al[0] = "Down";
2099 scrolledWindow = XtParent(w);
2100 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2101 if (scrollBar)
2102 for (i=0; i<nLines; i++)
2103 XtCallActionProc(scrollBar, "IncrementDownOrRight", event, al, 1) ;
2104 return;
2109 ** This is a disguisting hack to work around a bug in OpenMotif.
2110 ** OpenMotif's toggle button Select() action routine remembers the last radio
2111 ** button that was toggled (stored as global state) and refuses to take any
2112 ** action when that button is clicked again. It fails to detect that we may
2113 ** have changed the button state and that clicking that button could make
2114 ** sense. The result is that radio buttons may apparently get stuck, ie.
2115 ** it is not possible to directly select with the mouse the previously
2116 ** selected button without selection another radio button first.
2117 ** The workaround consist of faking a mouse click on the button that we
2118 ** toggled by calling the Arm, Select, and Disarm action procedures.
2120 ** A minor remaining issue is the fact that, if the workaround is used,
2121 ** it is not possible to change the state without notifying potential
2122 ** XmNvalueChangedCallbacks. In practice, this doesn't seem to be a problem.
2125 void RadioButtonChangeState(Widget widget, Boolean state, Boolean notify)
2128 The bug only exists in OpenMotif 2.1.x/2.2.[0-2]. Since it's quite hard
2129 to detect OpenMotif reliably, we make a rough cut by excluding Lesstif
2130 and all Motif versions >= 2.1.x and < 2.2.3.
2132 #ifndef LESSTIF_VERSION
2133 #if XmVersion == 2001 || (XmVersion == 2002 && XmUPDATE_LEVEL < 3)
2134 if (state && XtIsRealized(widget))
2137 Simulate a mouse button click.
2138 The event attributes that matter are the event type and the
2139 coordinates. When the button is managed, the coordinates have to
2140 be inside the button. When the button is not managed, they have to
2141 be (0, 0) to make sure that the Select routine accepts the event.
2143 XEvent ev;
2144 if (XtIsManaged(widget))
2146 Position x, y;
2147 /* Calculate the coordinates in the same way as OM. */
2148 XtTranslateCoords(XtParent(widget), widget->core.x, widget->core.y,
2149 &x, &y);
2150 ev.xbutton.x_root = x + widget->core.border_width;
2151 ev.xbutton.y_root = y + widget->core.border_width;
2153 else
2155 ev.xbutton.x_root = 0;
2156 ev.xbutton.y_root = 0;
2158 /* Default button bindings:
2159 ~c<Btn1Down>: Arm()
2160 ~c<Btn1Up>: Select() Disarm() */
2161 ev.xany.type = ButtonPress;
2162 XtCallActionProc(widget, "Arm", &ev, NULL, 0);
2163 ev.xany.type = ButtonRelease;
2164 XtCallActionProc(widget, "Select", &ev, NULL, 0);
2165 XtCallActionProc(widget, "Disarm", &ev, NULL, 0);
2167 #endif /* XmVersion == 2001 || ... */
2168 #endif /* LESSTIF_VERSION */
2170 /* This is sufficient on non-OM platforms */
2171 XmToggleButtonSetState(widget, state, notify);
2174 /* Workaround for bug in OpenMotif 2.1 and 2.2. If you have an active tear-off
2175 ** menu from a TopLevelShell that is a child of an ApplicationShell, and then
2176 ** close the parent window, Motif crashes. The problem doesn't
2177 ** happen if you close the tear-offs first, so, we programatically close them
2178 ** before destroying the shell widget.
2180 void CloseAllPopupsFor(Widget shell)
2182 #ifndef LESSTIF_VERSION
2183 /* Doesn't happen in LessTif. The tear-off menus are popup-children of
2184 * of the TopLevelShell there, which just works. Motif wants to make
2185 * them popup-children of the ApplicationShell, where it seems to get
2186 * into trouble. */
2188 Widget app = XtParent(shell);
2189 int i;
2191 for (i = 0; i < app->core.num_popups; i++) {
2192 Widget pop = app->core.popup_list[i];
2193 Widget shellFor;
2195 XtVaGetValues(pop, XtNtransientFor, &shellFor, NULL);
2196 if (shell == shellFor)
2197 _XmDismissTearOff(pop, NULL, NULL);
2199 #endif
2202 static long queryDesktop(Display *display, Window window, Atom deskTopAtom)
2204 long deskTopNumber = 0;
2205 Atom actualType;
2206 int actualFormat;
2207 unsigned long nItems, bytesAfter;
2208 unsigned char *prop;
2210 if (XGetWindowProperty(display, window, deskTopAtom, 0, 1,
2211 False, AnyPropertyType, &actualType, &actualFormat, &nItems,
2212 &bytesAfter, &prop) != Success) {
2213 return -1; /* Property not found */
2216 if (actualType == None) {
2217 return -1; /* Property does not exist */
2220 if (actualFormat != 32 || nItems != 1) {
2221 XFree((char*)prop);
2222 return -1; /* Wrong format */
2225 deskTopNumber = *(long*)prop;
2226 XFree((char*)prop);
2227 return deskTopNumber;
2231 ** Returns the current desktop number, or -1 if no desktop information
2232 ** is available.
2234 long QueryCurrentDesktop(Display *display, Window rootWindow)
2236 static Atom currentDesktopAtom = -1;
2238 if (currentDesktopAtom == -1)
2239 currentDesktopAtom = XInternAtom(display, "_NET_CURRENT_DESKTOP", True);
2241 if (currentDesktopAtom != None)
2242 return queryDesktop(display, rootWindow, currentDesktopAtom);
2244 return -1; /* No desktop information */
2248 ** Returns the number of the desktop the given shell window is currently on,
2249 ** or -1 if no desktop information is available (or if the window is sticky,
2250 ** ie., it is on all desktops).
2252 long QueryDesktop(Display *display, Widget shell)
2254 static Atom wmDesktopAtom = -1;
2256 if (wmDesktopAtom == -1)
2257 wmDesktopAtom = XInternAtom(display, "_NET_WM_DESKTOP", True);
2259 if (wmDesktopAtom != None)
2260 return queryDesktop(display, XtWindow(shell), wmDesktopAtom);
2262 return -1; /* No desktop information */