Reinstated the 32-bit visual check to try to avoid running into ARGB visuals
[nedit.git] / util / misc.c
blobbc0e0d630830627b051476b8ef890973ff1ac32a
1 static const char CVSID[] = "$Id: misc.c,v 1.86 2007/07/20 16:09:19 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. In addition, you may distribute version of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * July 28, 1992 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "misc.h"
35 #include "DialogF.h"
37 #include <stdlib.h>
38 #include <string.h>
39 #include <stdarg.h>
40 #include <ctype.h>
41 #include <stdio.h>
42 #include <time.h>
44 #ifdef __unix__
45 #include <sys/time.h>
46 #include <sys/select.h>
47 #endif
49 #ifdef __APPLE__
50 #ifdef __MACH__
51 #include <sys/select.h>
52 #endif
53 #endif
55 #ifdef VMS
56 #include <types.h>
57 #include <unixio.h>
58 #include <file.h>
59 #endif /*VMS*/
61 #include <X11/Intrinsic.h>
62 #include <X11/Xatom.h>
63 #include <X11/keysym.h>
64 #include <X11/keysymdef.h>
65 #include <Xm/Xm.h>
66 #include <Xm/Label.h>
67 #include <Xm/LabelG.h>
68 #include <Xm/ToggleB.h>
69 #include <Xm/PushB.h>
70 #include <Xm/Separator.h>
71 #include <Xm/RowColumn.h>
72 #include <Xm/CascadeB.h>
73 #include <Xm/AtomMgr.h>
74 #include <Xm/Protocols.h>
75 #include <Xm/Text.h>
76 #include <Xm/MessageB.h>
77 #include <Xm/DialogS.h>
78 #include <Xm/SelectioB.h>
79 #include <Xm/Form.h>
80 #include <Xm/FileSB.h>
81 #include <Xm/ScrolledW.h>
82 #include <Xm/PrimitiveP.h>
84 #ifdef HAVE_DEBUG_H
85 #include "../debug.h"
86 #endif
88 #ifndef LESSTIF_VERSION
89 extern void _XmDismissTearOff(Widget w, XtPointer call, XtPointer x);
90 #endif
92 /* structure for passing history-recall data to callbacks */
93 typedef struct {
94 char ***list;
95 int *nItems;
96 int index;
97 } histInfo;
99 typedef Widget (*MotifDialogCreationCall)(Widget, String, ArgList, Cardinal);
101 /* Maximum size of a history-recall list. Typically never invoked, since
102 user must first make this many entries in the text field, limited for
103 safety, to the maximum reasonable number of times user can hit up-arrow
104 before carpal tunnel syndrome sets in */
105 #define HISTORY_LIST_TRIM_TO 1000
106 #define HISTORY_LIST_MAX 2000
108 /* flags to enable/disable delete key remapping and pointer centered dialogs */
109 static int RemapDeleteEnabled = True;
110 static int PointerCenteredDialogsEnabled = False;
112 /* bitmap and mask for waiting (wrist-watch) cursor */
113 #define watch_x_hot 7
114 #define watch_y_hot 7
115 #define watch_width 16
116 #define watch_height 16
117 static unsigned char watch_bits[] = {
118 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0x10, 0x08, 0x08, 0x11,
119 0x04, 0x21, 0x04, 0x21, 0xe4, 0x21, 0x04, 0x20, 0x08, 0x10, 0x10, 0x08,
120 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07
122 #define watch_mask_width 16
123 #define watch_mask_height 16
124 static unsigned char watch_mask_bits[] = {
125 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf8, 0x1f, 0xfc, 0x3f,
126 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfc, 0x3f, 0xf8, 0x1f,
127 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f
130 static void addMnemonicGrabs(Widget addTo, Widget w, int unmodified);
131 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event);
132 static void findAndActivateMnemonic(Widget w, unsigned int keycode);
133 static void addAccelGrabs(Widget topWidget, Widget w);
134 static void addAccelGrab(Widget topWidget, Widget w);
135 static int parseAccelString(Display *display, const char *string, KeySym *keysym,
136 unsigned int *modifiers);
137 static void lockCB(Widget w, XtPointer callData, XEvent *event,
138 Boolean *continueDispatch);
139 static int findAndActivateAccel(Widget w, unsigned int keyCode,
140 unsigned int modifiers, XEvent *event);
141 static void removeWhiteSpace(char *string);
142 static int stripCaseCmp(const char *str1, const char *str2);
143 static void warnHandlerCB(String message);
144 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
145 *txtVerStr);
146 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
147 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
148 Boolean *continueDispatch);
149 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
150 Cardinal *argcount);
151 static Widget addParentVisArgsAndCall(MotifDialogCreationCall callRoutine,
152 Widget parent, char *name, ArgList arglist, Cardinal argcount);
153 static void scrollDownAP(Widget w, XEvent *event, String *args,
154 Cardinal *nArgs);
155 static void scrollUpAP(Widget w, XEvent *event, String *args,
156 Cardinal *nArgs);
157 static void pageDownAP(Widget w, XEvent *event, String *args,
158 Cardinal *nArgs);
159 static void pageUpAP(Widget w, XEvent *event, String *args,
160 Cardinal *nArgs);
161 static long queryDesktop(Display *display, Window window, Atom deskTopAtom);
162 static void warning(const char* mesg);
163 static void microsleep(long usecs);
166 ** Set up closeCB to be called when the user selects close from the
167 ** window menu. The close menu item usually activates f.kill which
168 ** sends a WM_DELETE_WINDOW protocol request for the window.
170 void AddMotifCloseCallback(Widget shell, XtCallbackProc closeCB, void *arg)
172 static Atom wmpAtom, dwAtom = 0;
173 Display *display = XtDisplay(shell);
175 /* deactivate the built in delete response of killing the application */
176 XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);
178 /* add a delete window protocol callback instead */
179 if (dwAtom == 0) {
180 wmpAtom = XmInternAtom(display, "WM_PROTOCOLS", FALSE);
181 dwAtom = XmInternAtom(display, "WM_DELETE_WINDOW", FALSE);
183 XmAddProtocolCallback(shell, wmpAtom, dwAtom, closeCB, arg);
187 ** Motif still generates spurious passive grab warnings on both IBM and SGI
188 ** This routine suppresses them.
189 ** (amai, 20011121:)
190 ** And triggers an annoying message on DEC systems on alpha ->
191 ** See Xt sources, xc/lib/Xt/Error.c:DefaultMsg()):
192 ** actually for some obscure reasons they check for XtError/Warning
193 ** handlers being installed when running as a root process!
194 ** Since this handler doesn't help on non-effected systems we should only
195 ** use it if necessary.
197 void SuppressPassiveGrabWarnings(void)
199 #if !defined(__alpha) && !defined(__EMX__)
200 XtSetWarningHandler(warnHandlerCB);
201 #endif
205 ** This routine kludges around the problem of backspace not being mapped
206 ** correctly when Motif is used between a server with a delete key in
207 ** the traditional typewriter backspace position and a client that
208 ** expects a backspace key in that position. Though there are three
209 ** distinct levels of key re-mapping in effect when a user is running
210 ** a Motif application, none of these is really appropriate or effective
211 ** for eliminating the delete v.s. backspace problem. Our solution is,
212 ** sadly, to eliminate the forward delete functionality of the delete key
213 ** in favor of backwards delete for both keys. So as not to prevent the
214 ** user or the application from applying other translation table re-mapping,
215 ** we apply re-map the key as a post-processing step, applied after widget
216 ** creation. As a result, the re-mapping necessarily becomes embedded
217 ** throughout an application (wherever text widgets are created), and
218 ** within library routines, including the Nirvana utility library. To
219 ** make this remapping optional, the SetDeleteRemap function provides a
220 ** way for an application to turn this functionality on and off. It is
221 ** recommended that applications that use this routine provide an
222 ** application resource called remapDeleteKey so savvy users can get
223 ** their forward delete functionality back.
225 void RemapDeleteKey(Widget w)
227 static XtTranslations table = NULL;
228 static char *translations =
229 "~Shift~Ctrl~Meta~Alt<Key>osfDelete: delete-previous-character()\n";
231 if (RemapDeleteEnabled) {
232 if (table == NULL)
233 table = XtParseTranslationTable(translations);
234 XtOverrideTranslations(w, table);
238 void SetDeleteRemap(int state)
240 RemapDeleteEnabled = state;
245 ** The routine adds the passed in top-level Widget's window to our
246 ** window group. On the first call a dummy unmapped window will
247 ** be created to be our leader. This must not be called before the
248 ** Widget has be realized and should be called before the window is
249 ** mapped.
251 static void setWindowGroup(Widget shell) {
252 static int firstTime = True;
253 static Window groupLeader;
254 Display *display = XtDisplay(shell);
255 XWMHints *wmHints;
257 if (firstTime) {
258 /* Create a dummy window to be the group leader for our windows */
259 String name, class;
260 XClassHint *classHint;
262 groupLeader = XCreateSimpleWindow(display,
263 RootWindow(display, DefaultScreen(display)),
264 1, 1, 1, 1, 0, 0, 0);
266 /* Set it's class hint so it will be identified correctly by the
267 window manager */
268 XtGetApplicationNameAndClass(display, &name, &class);
269 classHint = XAllocClassHint();
270 classHint->res_name = name;
271 classHint->res_class = class;
272 XSetClassHint(display, groupLeader, classHint);
273 XFree(classHint);
275 firstTime = False;
278 /* Set the window group hint for this shell's window */
279 wmHints = XGetWMHints(display, XtWindow(shell));
280 wmHints->window_group = groupLeader;
281 wmHints->flags |= WindowGroupHint;
282 XSetWMHints(display, XtWindow(shell), wmHints);
283 XFree(wmHints);
287 ** This routine resolves a window manager protocol incompatibility between
288 ** the X toolkit and several popular window managers. Using this in place
289 ** of XtRealizeWidget will realize the window in a way which allows the
290 ** affected window managers to apply their own placement strategy to the
291 ** window, as opposed to forcing the window to a specific location.
293 ** One of the hints in the WM_NORMAL_HINTS protocol, PPlacement, gets set by
294 ** the X toolkit (probably part of the Core or Shell widget) when a shell
295 ** widget is realized to the value stored in the XmNx and XmNy resources of the
296 ** Core widget. While callers can set these values, there is no "unset" value
297 ** for these resources. On systems which are more Motif aware, a PPosition
298 ** hint of 0,0, which is the default for XmNx and XmNy, is interpreted as,
299 ** "place this as if no hints were specified". Unfortunately the fvwm family
300 ** of window managers, which are now some of the most popular, interpret this
301 ** as "place this window at (0,0)". This routine intervenes between the
302 ** realizing and the mapping of the window to remove the inappropriate
303 ** PPlacement hint.
305 void RealizeWithoutForcingPosition(Widget shell)
307 XSizeHints *hints = XAllocSizeHints();
308 long suppliedHints;
309 Boolean mappedWhenManaged;
311 /* Temporarily set value of XmNmappedWhenManaged
312 to stop the window from popping up right away */
313 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
314 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
316 /* Realize the widget in unmapped state */
317 XtRealizeWidget(shell);
319 /* Get rid of the incorrect WMNormal hint */
320 if (XGetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints,
321 &suppliedHints)) {
322 hints->flags &= ~PPosition;
323 XSetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints);
325 XFree(hints);
327 /* Set WindowGroupHint so the NEdit icons can be grouped; this
328 seems to be necessary starting with Gnome 2.0 */
329 setWindowGroup(shell);
331 /* Map the widget */
332 XtMapWidget(shell);
334 /* Restore the value of XmNmappedWhenManaged */
335 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
339 ** Older X applications and X servers were mostly designed to operate with
340 ** visual class PseudoColor, because older displays were at most 8 bits
341 ** deep. Modern X servers, however, usually support 24 bit depth and other
342 ** color models. Sun (and others?) still sets their default visual to
343 ** 8-bit PseudoColor, because some of their X applications don't work
344 ** properly with the other color models. The problem with PseudoColor, of
345 ** course, is that users run out of colors in the default colormap, and if
346 ** they install additional colormaps for individual applications, colors
347 ** flash and change weirdly when you change your focus from one application
348 ** to another.
350 ** In addition to the poor choice of default, a design flaw in Xt makes it
351 ** impossible even for savvy users to specify the XtNvisual resource to
352 ** switch to a deeper visual. The problem is that the colormap resource is
353 ** processed independently of the visual resource, and usually results in a
354 ** colormap for the default visual rather than for the user-selected one.
356 ** This routine should be called before creating a shell widget, to
357 ** pre-process the visual, depth, and colormap resources, and return the
358 ** proper values for these three resources to be passed to XtAppCreateShell.
359 ** Applications which actually require a particular color model (i.e. for
360 ** doing color table animation or dynamic color assignment) should not use
361 ** this routine.
363 ** Note that a consequence of using the "best" as opposed to the default
364 ** visual is that some color resources are still converted with the default
365 ** visual (particularly *background), and these must be avoided by widgets
366 ** which are allowed to handle any visual.
368 ** Returns True if the best visual is the default, False otherwise.
370 Boolean FindBestVisual(Display *display, const char *appName, const char *appClass,
371 Visual **visual, int *depth, Colormap *colormap)
373 char rsrcName[256], rsrcClass[256], *valueString, *type, *endPtr;
374 XrmValue value;
375 int screen = DefaultScreen(display);
376 int reqDepth = -1;
377 long reqID = -1; /* should hold a 'VisualID' and a '-1' ... */
378 int reqClass = -1;
379 int installColormap = FALSE;
380 int maxDepth, bestClass, bestVisual, nVis, i, j;
381 XVisualInfo visTemplate, *visList = NULL;
382 static Visual *cachedVisual = NULL;
383 static Colormap cachedColormap;
384 static int cachedDepth = 0;
385 int bestClasses[] = {StaticGray, GrayScale, StaticColor, PseudoColor,
386 DirectColor, TrueColor};
388 /* If results have already been computed, just return them */
389 if (cachedVisual != NULL) {
390 *visual = cachedVisual;
391 *depth = cachedDepth;
392 *colormap = cachedColormap;
393 return (*visual == DefaultVisual(display, screen));
396 /* Read the visualID and installColormap resources for the application.
397 visualID can be specified either as a number (the visual id as
398 shown by xdpyinfo), as a visual class name, or as Best or Default. */
399 sprintf(rsrcName,"%s.%s", appName, "visualID");
400 sprintf(rsrcClass, "%s.%s", appClass, "VisualID");
401 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
402 &value)) {
403 valueString = value.addr;
404 reqID = (int)strtol(valueString, &endPtr, 0);
405 if (endPtr == valueString) {
406 reqID = -1;
407 if (stripCaseCmp(valueString, "Default"))
408 reqID = DefaultVisual(display, screen)->visualid;
409 else if (stripCaseCmp(valueString, "StaticGray"))
410 reqClass = StaticGray;
411 else if (stripCaseCmp(valueString, "StaticColor"))
412 reqClass = StaticColor;
413 else if (stripCaseCmp(valueString, "TrueColor"))
414 reqClass = TrueColor;
415 else if (stripCaseCmp(valueString, "GrayScale"))
416 reqClass = GrayScale;
417 else if (stripCaseCmp(valueString, "PseudoColor"))
418 reqClass = PseudoColor;
419 else if (stripCaseCmp(valueString, "DirectColor"))
420 reqClass = DirectColor;
421 else if (!stripCaseCmp(valueString, "Best"))
422 fprintf(stderr, "Invalid visualID resource value\n");
425 sprintf(rsrcName,"%s.%s", appName, "installColormap");
426 sprintf(rsrcClass, "%s.%s", appClass, "InstallColormap");
427 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
428 &value)) {
429 if (stripCaseCmp(value.addr, "Yes") || stripCaseCmp(value.addr, "True"))
430 installColormap = TRUE;
433 visTemplate.screen = screen;
435 /* Generate a list of visuals to consider. (Note, vestigial code for
436 user-requested visual depth is left in, just in case that function
437 might be needed again, but it does nothing). */
438 if (reqID != -1) {
439 visTemplate.visualid = reqID;
440 visList = XGetVisualInfo(display, VisualScreenMask|VisualIDMask,
441 &visTemplate, &nVis);
442 if (visList == NULL)
443 fprintf(stderr, "VisualID resource value not valid\n");
445 if (visList == NULL && reqClass != -1 && reqDepth != -1) {
446 visTemplate.class = reqClass;
447 visTemplate.depth = reqDepth;
448 visList = XGetVisualInfo(display,
449 VisualScreenMask| VisualClassMask | VisualDepthMask,
450 &visTemplate, &nVis);
451 if (visList == NULL)
452 fprintf(stderr, "Visual class/depth combination not available\n");
454 if (visList == NULL && reqClass != -1) {
455 visTemplate.class = reqClass;
456 visList = XGetVisualInfo(display, VisualScreenMask|VisualClassMask,
457 &visTemplate, &nVis);
458 if (visList == NULL)
459 fprintf(stderr,
460 "Visual Class from resource \"visualID\" not available\n");
462 if (visList == NULL && reqDepth != -1) {
463 visTemplate.depth = reqDepth;
464 visList = XGetVisualInfo(display, VisualScreenMask|VisualDepthMask,
465 &visTemplate, &nVis);
466 if (visList == NULL)
467 fprintf(stderr, "Requested visual depth not available\n");
469 if (visList == NULL) {
470 visList = XGetVisualInfo(display, VisualScreenMask, &visTemplate, &nVis);
471 if (visList == NULL) {
472 fprintf(stderr, "Internal Error: no visuals available?\n");
473 *visual = DefaultVisual(display, screen);
474 *depth = DefaultDepth(display, screen);
475 *colormap = DefaultColormap(display, screen);
476 return True;
480 /* Choose among the visuals in the candidate list. Prefer maximum
481 depth first then matching default, then largest value of bestClass
482 (I'm not sure whether we actually care about class) */
483 maxDepth = 0;
484 bestClass = 0;
485 bestVisual = 0;
486 for (i=0; i < nVis; i++) {
487 /* X.Org 6.8+ 32-bit visuals (with alpha-channel) cause a lot of
488 problems, so we have to skip them. We already try this by setting
489 the environment variable XLIB_SKIP_ARGB_VISUALS at startup (in
490 nedit.c), but that doesn't cover the case where NEdit is running on
491 a host that doesn't use the X.Org X libraries but is displaying
492 remotely on an X.Org server. Therefore, this additional check is
493 added.
494 Note that this check in itself is not sufficient. There have been
495 bug reports that seemed to indicate that non-32-bit visuals with an
496 alpha-channel exist. The combined approach (env. var. + 32-bit
497 check) should cover the vast majority of the cases, though. */
498 if (visList[i].depth >= 32 &&
499 strstr(ServerVendor(display), "X.Org") != 0) {
500 continue;
502 if (visList[i].depth > maxDepth) {
503 maxDepth = visList[i].depth;
504 bestClass = 0;
505 bestVisual = i;
507 if (visList[i].depth == maxDepth) {
508 if (visList[i].visual == DefaultVisual(display, screen))
509 bestVisual = i;
510 if (visList[bestVisual].visual != DefaultVisual(display, screen)) {
511 for (j = 0; j < (int)XtNumber(bestClasses); j++) {
512 if (visList[i].class == bestClasses[j] && j > bestClass) {
513 bestClass = j;
514 bestVisual = i;
520 *visual = cachedVisual = visList[bestVisual].visual;
521 *depth = cachedDepth = visList[bestVisual].depth;
523 /* If the chosen visual is not the default, it needs a colormap allocated */
524 if (*visual == DefaultVisual(display, screen) && !installColormap)
525 *colormap = cachedColormap = DefaultColormap(display, screen);
526 else {
527 *colormap = cachedColormap = XCreateColormap(display,
528 RootWindow(display, screen), cachedVisual, AllocNone);
529 XInstallColormap(display, cachedColormap);
531 /* printf("Chose visual with depth %d, class %d, colormap %ld, id 0x%x\n",
532 visList[bestVisual].depth, visList[bestVisual].class,
533 *colormap, cachedVisual->visualid); */
534 /* Fix memory leak */
535 if (visList != NULL) {
536 XFree(visList);
539 return (*visual == DefaultVisual(display, screen));
543 ** If you want to use a non-default visual with Motif, shells all have to be
544 ** created with that visual, depth, and colormap, even if the parent has them
545 ** set up properly. Substituting these routines, will append visual args copied
546 ** from the parent widget (CreatePopupMenu and CreatePulldownMenu), or from the
547 ** best visual, obtained via FindBestVisual above (CreateShellWithBestVis).
549 Widget CreateDialogShell(Widget parent, char *name,
550 ArgList arglist, Cardinal argcount)
552 return addParentVisArgsAndCall(XmCreateDialogShell, parent, name, arglist,
553 argcount);
557 Widget CreatePopupMenu(Widget parent, char *name, ArgList arglist,
558 Cardinal argcount)
560 return addParentVisArgsAndCall(XmCreatePopupMenu, parent, name,
561 arglist, argcount);
565 Widget CreatePulldownMenu(Widget parent, char *name,
566 ArgList arglist, Cardinal argcount)
568 return addParentVisArgsAndCall(XmCreatePulldownMenu, parent, name, arglist,
569 argcount);
573 Widget CreatePromptDialog(Widget parent, char *name,
574 ArgList arglist, Cardinal argcount)
576 return addParentVisArgsAndCall(XmCreatePromptDialog, parent, name, arglist,
577 argcount);
581 Widget CreateSelectionDialog(Widget parent, char *name,
582 ArgList arglist, Cardinal argcount)
584 Widget dialog = addParentVisArgsAndCall(XmCreateSelectionDialog, parent, name,
585 arglist, argcount);
586 AddMouseWheelSupport(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST));
587 return dialog;
591 Widget CreateFormDialog(Widget parent, char *name,
592 ArgList arglist, Cardinal argcount)
594 return addParentVisArgsAndCall(XmCreateFormDialog, parent, name, arglist,
595 argcount);
599 Widget CreateFileSelectionDialog(Widget parent, char *name,
600 ArgList arglist, Cardinal argcount)
602 Widget dialog = addParentVisArgsAndCall(XmCreateFileSelectionDialog, parent,
603 name, arglist, argcount);
605 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_LIST));
606 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_DIR_LIST));
607 return dialog;
611 Widget CreateQuestionDialog(Widget parent, char *name,
612 ArgList arglist, Cardinal argcount)
614 return addParentVisArgsAndCall(XmCreateQuestionDialog, parent, name,
615 arglist, argcount);
619 Widget CreateMessageDialog(Widget parent, char *name,
620 ArgList arglist, Cardinal argcount)
622 return addParentVisArgsAndCall(XmCreateMessageDialog, parent, name,
623 arglist, argcount);
627 Widget CreateErrorDialog(Widget parent, char *name,
628 ArgList arglist, Cardinal argcount)
630 return addParentVisArgsAndCall(XmCreateErrorDialog, parent, name, arglist,
631 argcount);
634 Widget CreateWidget(Widget parent, const char *name, WidgetClass class,
635 ArgList arglist, Cardinal argcount)
637 Widget result;
638 ArgList al = addParentVisArgs(parent, arglist, &argcount);
639 result = XtCreateWidget(name, class, parent, al, argcount);
640 XtFree((char *)al);
641 return result;
644 Widget CreateShellWithBestVis(String appName, String appClass,
645 WidgetClass class, Display *display, ArgList args, Cardinal nArgs)
647 Visual *visual;
648 int depth;
649 Colormap colormap;
650 ArgList al;
651 Cardinal ac = nArgs;
652 Widget result;
654 FindBestVisual(display, appName, appClass, &visual, &depth, &colormap);
655 al = (ArgList)XtMalloc(sizeof(Arg) * (nArgs + 3));
656 if (nArgs != 0)
657 memcpy(al, args, sizeof(Arg) * nArgs);
658 XtSetArg(al[ac], XtNvisual, visual); ac++;
659 XtSetArg(al[ac], XtNdepth, depth); ac++;
660 XtSetArg(al[ac], XtNcolormap, colormap); ac++;
661 result = XtAppCreateShell(appName, appClass, class, display, al, ac);
662 XtFree((char *)al);
663 return result;
667 Widget CreatePopupShellWithBestVis(String shellName, WidgetClass class,
668 Widget parent, ArgList arglist, Cardinal argcount)
670 Widget result;
671 ArgList al = addParentVisArgs(parent, arglist, &argcount);
672 result = XtCreatePopupShell(shellName, class, parent, al, argcount);
673 XtFree((char *)al);
674 return result;
678 ** Extends an argument list for widget creation with additional arguments
679 ** for visual, colormap, and depth. The original argument list is not altered
680 ** and it's the caller's responsability to free the returned list.
682 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
683 Cardinal *argcount)
685 Visual *visual;
686 int depth;
687 Colormap colormap;
688 ArgList al;
689 Widget parentShell = parent;
691 /* Find the application/dialog/menu shell at the top of the widget
692 hierarchy, which has the visual resource being used */
693 while (True) {
694 if (XtIsShell(parentShell))
695 break;
696 if (parentShell == NULL) {
697 fprintf(stderr, "failed to find shell\n");
698 exit(EXIT_FAILURE);
700 parentShell = XtParent(parentShell);
703 /* Add the visual, depth, and colormap resources to the argument list */
704 XtVaGetValues(parentShell, XtNvisual, &visual, XtNdepth, &depth,
705 XtNcolormap, &colormap, NULL);
706 al = (ArgList)XtMalloc(sizeof(Arg) * ((*argcount) + 3));
707 if ((*argcount) != 0)
708 memcpy(al, arglist, sizeof(Arg) * (*argcount));
710 /* For non-Lesstif versions, the visual, depth, and colormap are now set
711 globally via the resource database. So strictly spoken, it is no
712 longer necessary to set them explicitly for every shell widget.
714 For Lesstif, however, this doesn't work. Luckily, Lesstif handles
715 non-default visuals etc. properly for its own shells and
716 we can take care of things for our shells (eg, call tips) here. */
717 XtSetArg(al[*argcount], XtNvisual, visual); (*argcount)++;
718 XtSetArg(al[*argcount], XtNdepth, depth); (*argcount)++;
719 XtSetArg(al[*argcount], XtNcolormap, colormap); (*argcount)++;
720 return al;
725 ** Calls one of the Motif widget creation routines, splicing in additional
726 ** arguments for visual, colormap, and depth.
728 static Widget addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,
729 Widget parent, char *name, ArgList arglist, Cardinal argcount)
731 Widget result;
732 ArgList al = addParentVisArgs(parent, arglist, &argcount);
733 result = (*createRoutine)(parent, name, al, argcount);
734 XtFree((char *)al);
735 return result;
739 ** ManageDialogCenteredOnPointer is used in place of XtManageChild for
740 ** popping up a dialog to enable the dialog to be centered under the
741 ** mouse pointer. Whether it pops up the dialog centered under the pointer
742 ** or in its default position centered over the parent widget, depends on
743 ** the value set in the SetPointerCenteredDialogs call.
744 ** Additionally, this function constrains the size of the dialog to the
745 ** screen's size, to avoid insanely wide dialogs with obscured buttons.
747 void ManageDialogCenteredOnPointer(Widget dialogChild)
749 Widget shell = XtParent(dialogChild);
750 Window root, child;
751 unsigned int mask;
752 unsigned int width, height, borderWidth, depth;
753 int x, y, winX, winY, maxX, maxY, maxWidth, maxHeight;
754 Dimension xtWidth, xtHeight;
755 Boolean mappedWhenManaged;
756 static const int slop = 25;
758 /* Temporarily set value of XmNmappedWhenManaged
759 to stop the dialog from popping up right away */
760 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
761 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
763 /* Ensure that the dialog doesn't get wider/taller than the screen.
764 We use a hard-coded "slop" size because we don't know the border
765 width until it's on screen, and by then it's too late. Putting
766 this before managing the widgets allows it to get the geometry
767 right on the first pass and also prevents the user from
768 accidentally resizing too wide. */
769 maxWidth = XtScreen(shell)->width - slop;
770 maxHeight = XtScreen(shell)->height - slop;
772 XtVaSetValues(shell,
773 XmNmaxWidth, maxWidth,
774 XmNmaxHeight, maxHeight,
775 NULL);
777 /* Manage the dialog */
778 XtManageChild(dialogChild);
780 /* Check to see if the window manager doesn't respect XmNmaxWidth
781 and XmNmaxHeight on the first geometry pass (sawfish, twm, fvwm).
782 For this to work XmNresizePolicy must be XmRESIZE_NONE, otherwise
783 the dialog will try to expand anyway. */
784 XtVaGetValues(shell, XmNwidth, &xtWidth, XmNheight, &xtHeight, NULL);
785 if (xtWidth > maxWidth)
786 XtVaSetValues(shell, XmNwidth, (Dimension) maxWidth, NULL);
787 if (xtHeight > maxHeight)
788 XtVaSetValues(shell, XmNheight, (Dimension) maxHeight, NULL);
790 /* Only set the x/y position if the centering option is enabled.
791 Avoid getting the coordinates if not so, to save a few round-trips
792 to the server. */
793 if (PointerCenteredDialogsEnabled) {
794 /* Get the pointer position (x, y) */
795 XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child,
796 &x, &y, &winX, &winY, &mask);
798 /* Translate the pointer position (x, y) into a position for the new
799 window that will place the pointer at its center */
800 XGetGeometry(XtDisplay(shell), XtWindow(shell), &root, &winX, &winY,
801 &width, &height, &borderWidth, &depth);
802 width += 2 * borderWidth;
803 height += 2 * borderWidth;
805 x -= width/2;
806 y -= height/2;
808 /* Ensure that the dialog remains on screen */
809 maxX = maxWidth - width;
810 maxY = maxHeight - height;
811 if (x > maxX) x = maxX;
812 if (x < 0) x = 0;
813 if (y > maxY) y = maxY;
814 if (y < 0) y = 0;
816 /* Some window managers (Sawfish) don't appear to respond
817 to the geometry set call in synchronous mode. This causes
818 the window to delay XmNwmTimeout (default 5 seconds) before
819 posting, and it is very annoying. See "man VendorShell" for
820 more info. */
821 XtVaSetValues(shell, XmNuseAsyncGeometry, True, NULL);
823 /* Set desired window position in the DialogShell */
824 XtVaSetValues(shell, XmNx, x, XmNy, y, NULL);
827 /* Map the widget */
828 XtMapWidget(shell);
830 /* Restore the value of XmNmappedWhenManaged */
831 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
835 ** Cause dialogs created by libNUtil.a routines (such as DialogF),
836 ** and dialogs which use ManageDialogCenteredOnPointer to pop up
837 ** over the pointer (state = True), or pop up in their default
838 ** positions (state = False)
840 void SetPointerCenteredDialogs(int state)
842 PointerCenteredDialogsEnabled = state;
847 ** Raise a window to the top and give it the input focus. Setting input focus
848 ** is important on systems which use explict (rather than pointer) focus.
850 ** The X alternatives XMapRaised, and XSetInputFocus both have problems.
851 ** XMapRaised only gives the window the focus if it was initially not visible,
852 ** and XSetInputFocus sets the input focus, but crashes if the window is not
853 ** visible.
855 ** This routine should also be used in the case where a dialog is popped up and
856 ** subsequent calls to the dialog function use a flag, or the XtIsManaged, to
857 ** decide whether to create a new instance of the dialog, because on slower
858 ** systems, events can intervene while a dialog is still on its way up and its
859 ** window is still invisible, causing a subtle crash potential if
860 ** XSetInputFocus is used.
862 void RaiseDialogWindow(Widget shell)
864 RaiseWindow(XtDisplay(shell), XtWindow(shell), True);
867 void RaiseShellWindow(Widget shell, Boolean focus)
869 RaiseWindow(XtDisplay(shell), XtWindow(shell), focus);
872 void RaiseWindow(Display *display, Window w, Boolean focus)
874 if (focus) {
875 XWindowAttributes winAttr;
877 XGetWindowAttributes(display, w, &winAttr);
878 if (winAttr.map_state == IsViewable)
879 XSetInputFocus(display, w, RevertToParent, CurrentTime);
882 XMapRaised(display, w);
886 ** Add a handler for mnemonics in a dialog (Motif currently only handles
887 ** mnemonics in menus) following the example of M.S. Windows. To add
888 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
889 ** a menu, on push buttons or toggle buttons, and call this function
890 ** when the dialog is fully constructed. Mnemonics added or changed
891 ** after this call will not be noticed. To add a mnemonic to a text field
892 ** or list, set the XmNmnemonic resource on the appropriate label and set
893 ** the XmNuserData resource of the label to the widget to get the focus
894 ** when the mnemonic is typed.
896 void AddDialogMnemonicHandler(Widget dialog, int unmodifiedToo)
898 XtAddEventHandler(dialog, KeyPressMask, False,
899 (XtEventHandler)mnemonicCB, (XtPointer)0);
900 addMnemonicGrabs(dialog, dialog, unmodifiedToo);
904 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
906 void RemoveDialogMnemonicHandler(Widget dialog)
908 XtUngrabKey(dialog, AnyKey, Mod1Mask);
909 XtRemoveEventHandler(dialog, KeyPressMask, False,
910 (XtEventHandler)mnemonicCB, (XtPointer)0);
914 ** Patch around Motif's poor handling of menu accelerator keys. Motif
915 ** does not process menu accelerators when the caps lock or num lock
916 ** keys are engaged. To enable accelerators in these cases, call this
917 ** routine with the completed menu bar widget as "topMenuContainer", and
918 ** the top level shell widget as "topWidget". It will add key grabs for
919 ** all of the accelerators it finds in the topMenuContainer menu tree, and
920 ** an event handler which can process dropped accelerator events by (again)
921 ** traversing the menu tree looking for matching accelerators, and invoking
922 ** the appropriate button actions. Any dynamic additions to the menus
923 ** require a call to UpdateAccelLockPatch to add the additional grabs.
924 ** Unfortunately, these grabs can not be removed.
926 void AccelLockBugPatch(Widget topWidget, Widget topMenuContainer)
928 XtAddEventHandler(topWidget, KeyPressMask, False, lockCB, topMenuContainer);
929 addAccelGrabs(topWidget, topMenuContainer);
933 ** Add additional key grabs for new menu items added to the menus, for
934 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
935 ** the same widget passed in the original call to AccelLockBugPatch.
937 void UpdateAccelLockPatch(Widget topWidget, Widget newButton)
939 addAccelGrab(topWidget, newButton);
943 ** PopDownBugPatch
945 ** Under some circumstances, popping down a dialog and its parent in
946 ** rapid succession causes a crash. This routine delays and
947 ** processs events until receiving a ReparentNotify event.
948 ** (I have no idea why a ReparentNotify event occurs at all, but it does
949 ** mark the point where it is safe to destroy or pop down the parent, and
950 ** it might have something to do with the bug.) There is a failsafe in
951 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
952 ** Use this sparingly, only when real crashes are observed, and periodically
953 ** check to make sure that it is still necessary.
955 void PopDownBugPatch(Widget w)
957 time_t stopTime;
959 stopTime = time(NULL) + 1;
960 while (time(NULL) <= stopTime) {
961 XEvent event;
962 XtAppContext context = XtWidgetToApplicationContext(w);
963 XtAppPeekEvent(context, &event);
964 if (event.xany.type == ReparentNotify)
965 return;
966 XtAppProcessEvent(context, XtIMAll);
971 ** Convert a compound string to a C style null terminated string.
972 ** Returned string must be freed by the caller.
974 char *GetXmStringText(XmString fromString)
976 XmStringContext context;
977 char *text, *toPtr, *toString, *fromPtr;
978 XmStringCharSet charset;
979 XmStringDirection direction;
980 Boolean separator;
982 /* Malloc a buffer large enough to hold the string. XmStringLength
983 should always be slightly longer than necessary, but won't be
984 shorter than the equivalent null-terminated string */
985 toString = XtMalloc(XmStringLength(fromString));
987 /* loop over all of the segments in the string, copying each segment
988 into the output string and converting separators into newlines */
989 XmStringInitContext(&context, fromString);
990 toPtr = toString;
991 while (XmStringGetNextSegment(context, &text,
992 &charset, &direction, &separator)) {
993 for (fromPtr=text; *fromPtr!='\0'; fromPtr++)
994 *toPtr++ = *fromPtr;
995 if (separator)
996 *toPtr++ = '\n';
997 XtFree(text);
998 XtFree(charset);
1001 /* terminate the string, free the context, and return the string */
1002 *toPtr++ = '\0';
1003 XmStringFreeContext(context);
1004 return toString;
1008 ** Get the XFontStruct that corresponds to the default (first) font in
1009 ** a Motif font list. Since Motif stores this, it saves us from storing
1010 ** it or querying it from the X server.
1012 XFontStruct *GetDefaultFontStruct(XmFontList font)
1014 XFontStruct *fs;
1015 XmFontContext context;
1016 XmStringCharSet charset;
1018 XmFontListInitFontContext(&context, font);
1019 XmFontListGetNextFont(context, &charset, &fs);
1020 XmFontListFreeFontContext(context);
1021 XtFree(charset);
1022 return fs;
1026 ** Create a string table suitable for passing to XmList widgets
1028 XmString* StringTable(int count, ... )
1030 va_list ap;
1031 XmString *array;
1032 int i;
1033 char *str;
1035 va_start(ap, count);
1036 array = (XmString*)XtMalloc((count+1) * sizeof(XmString));
1037 for(i = 0; i < count; i++ ) {
1038 str = va_arg(ap, char *);
1039 array[i] = XmStringCreateSimple(str);
1041 array[i] = (XmString)0;
1042 va_end(ap);
1043 return(array);
1046 void FreeStringTable(XmString *table)
1048 int i;
1050 for(i = 0; table[i] != 0; i++)
1051 XmStringFree(table[i]);
1052 XtFree((char *)table);
1056 ** Simulate a button press. The purpose of this routine is show users what
1057 ** is happening when they take an action with a non-obvious side effect,
1058 ** such as when a user double clicks on a list item. The argument is an
1059 ** XmPushButton widget to "press"
1061 void SimulateButtonPress(Widget widget)
1063 XEvent keyEvent;
1065 memset((char *)&keyEvent, 0, sizeof(XKeyPressedEvent));
1066 keyEvent.type = KeyPress;
1067 keyEvent.xkey.serial = 1;
1068 keyEvent.xkey.send_event = True;
1070 if (XtIsSubclass(widget, xmGadgetClass))
1072 /* On some Motif implementations, asking a gadget for its
1073 window will crash, rather than return the window of its
1074 parent. */
1075 Widget parent = XtParent(widget);
1076 keyEvent.xkey.display = XtDisplay(parent);
1077 keyEvent.xkey.window = XtWindow(parent);
1079 XtCallActionProc(parent, "ManagerGadgetSelect",
1080 &keyEvent, NULL, 0);
1082 else
1084 keyEvent.xkey.display = XtDisplay(widget);
1085 keyEvent.xkey.window = XtWindow(widget);
1087 XtCallActionProc(widget, "ArmAndActivate", &keyEvent, NULL, 0);
1092 ** Add an item to an already established pull-down or pop-up menu, including
1093 ** mnemonics, accelerators and callbacks.
1095 Widget AddMenuItem(Widget parent, char *name, char *label,
1096 char mnemonic, char *acc, char *accText,
1097 XtCallbackProc callback, void *cbArg)
1099 Widget button;
1100 XmString st1, st2;
1102 button = XtVaCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
1103 XmNlabelString, st1=XmStringCreateSimple(label),
1104 XmNmnemonic, mnemonic,
1105 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1106 XmNaccelerator, acc, NULL);
1107 XtAddCallback(button, XmNactivateCallback, callback, cbArg);
1108 XmStringFree(st1);
1109 XmStringFree(st2);
1110 return button;
1114 ** Add a toggle button item to an already established pull-down or pop-up
1115 ** menu, including mnemonics, accelerators and callbacks.
1117 Widget AddMenuToggle(Widget parent, char *name, char *label,
1118 char mnemonic, char *acc, char *accText,
1119 XtCallbackProc callback, void *cbArg, int set)
1121 Widget button;
1122 XmString st1, st2;
1124 button = XtVaCreateManagedWidget(name, xmToggleButtonWidgetClass, parent,
1125 XmNlabelString, st1=XmStringCreateSimple(label),
1126 XmNmnemonic, mnemonic,
1127 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1128 XmNaccelerator, acc,
1129 XmNset, set, NULL);
1130 XtAddCallback(button, XmNvalueChangedCallback, callback, cbArg);
1131 XmStringFree(st1);
1132 XmStringFree(st2);
1133 return button;
1137 ** Add a separator line to a menu
1139 Widget AddMenuSeparator(Widget parent, char *name)
1141 Widget button;
1143 button = XmCreateSeparator(parent, name, NULL, 0);
1144 XtManageChild(button);
1145 return button;
1149 ** Add a sub-menu to an established pull-down or pop-up menu, including
1150 ** mnemonics, accelerators and callbacks. Returns the menu pane of the
1151 ** new sub menu.
1153 Widget AddSubMenu(Widget parent, char *name, char *label, char mnemonic)
1155 Widget menu;
1156 XmString st1;
1158 menu = CreatePulldownMenu(parent, name, NULL, 0);
1159 XtVaCreateManagedWidget(name, xmCascadeButtonWidgetClass, parent,
1160 XmNlabelString, st1=XmStringCreateSimple(label),
1161 XmNmnemonic, mnemonic,
1162 XmNsubMenuId, menu, NULL);
1163 XmStringFree(st1);
1164 return menu;
1168 ** SetIntLabel, SetFloatLabel, SetIntText, SetFloatText
1170 ** Set the text of a motif label or text widget to show an integer or
1171 ** floating number.
1173 void SetIntLabel(Widget label, int value)
1175 char labelString[20];
1176 XmString s1;
1178 sprintf(labelString, "%d", value);
1179 s1=XmStringCreateSimple(labelString);
1180 XtVaSetValues(label, XmNlabelString, s1, NULL);
1181 XmStringFree(s1);
1185 void SetFloatLabel(Widget label, double value)
1187 char labelString[20];
1188 XmString s1;
1190 sprintf(labelString, "%g", value);
1191 s1=XmStringCreateSimple(labelString);
1192 XtVaSetValues(label, XmNlabelString, s1, NULL);
1193 XmStringFree(s1);
1197 void SetIntText(Widget text, int value)
1199 char labelString[20];
1201 sprintf(labelString, "%d", value);
1202 XmTextSetString(text, labelString);
1206 void SetFloatText(Widget text, double value)
1208 char labelString[20];
1210 sprintf(labelString, "%g", value);
1211 XmTextSetString(text, labelString);
1215 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
1217 ** Get the text of a motif text widget as an integer or floating point number.
1218 ** The functions will return TEXT_READ_OK of the value was read correctly.
1219 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER. The
1220 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
1221 ** user that the value could not be read. The argument fieldName is used
1222 ** in the dialog to help the user identify where the problem is. Set
1223 ** warnBlank to true if a blank field is also considered an error.
1225 int GetFloatText(Widget text, double *value)
1227 char *strValue, *endPtr;
1228 int retVal;
1230 strValue = XmTextGetString(text); /* Get Value */
1231 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1232 *value = strtod(strValue, &endPtr); /* Convert string to double */
1233 if (strlen(strValue) == 0) /* String is empty */
1234 retVal = TEXT_IS_BLANK;
1235 else if (*endPtr != '\0') /* Whole string not parsed */
1236 retVal = TEXT_NOT_NUMBER;
1237 else
1238 retVal = TEXT_READ_OK;
1239 XtFree(strValue);
1240 return retVal;
1243 int GetIntText(Widget text, int *value)
1245 char *strValue, *endPtr;
1246 int retVal;
1248 strValue = XmTextGetString(text); /* Get Value */
1249 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1250 *value = strtol(strValue, &endPtr, 10); /* Convert string to long */
1251 if (strlen(strValue) == 0) /* String is empty */
1252 retVal = TEXT_IS_BLANK;
1253 else if (*endPtr != '\0') /* Whole string not parsed */
1254 retVal = TEXT_NOT_NUMBER;
1255 else
1256 retVal = TEXT_READ_OK;
1257 XtFree(strValue);
1258 return retVal;
1261 int GetFloatTextWarn(Widget text, double *value, const char *fieldName,
1262 int warnBlank)
1264 int result;
1265 char *valueStr;
1267 result = GetFloatText(text, value);
1268 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1269 return result;
1270 valueStr = XmTextGetString(text);
1272 if (result == TEXT_IS_BLANK)
1274 DialogF(DF_ERR, text, 1, "Warning", "Please supply %s value", "OK",
1275 fieldName);
1276 } else /* TEXT_NOT_NUMBER */
1278 DialogF (DF_ERR, text, 1, "Warning", "Can't read %s value: \"%s\"",
1279 "OK", fieldName, valueStr);
1282 XtFree(valueStr);
1283 return result;
1286 int GetIntTextWarn(Widget text, int *value, const char *fieldName, int warnBlank)
1288 int result;
1289 char *valueStr;
1291 result = GetIntText(text, value);
1292 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1293 return result;
1294 valueStr = XmTextGetString(text);
1296 if (result == TEXT_IS_BLANK)
1298 DialogF (DF_ERR, text, 1, "Warning", "Please supply a value for %s",
1299 "OK", fieldName);
1300 } else /* TEXT_NOT_NUMBER */
1302 DialogF (DF_ERR, text, 1, "Warning",
1303 "Can't read integer value \"%s\" in %s", "OK", valueStr,
1304 fieldName);
1307 XtFree(valueStr);
1308 return result;
1311 int TextWidgetIsBlank(Widget textW)
1313 char *str;
1314 int retVal;
1316 str = XmTextGetString(textW);
1317 removeWhiteSpace(str);
1318 retVal = *str == '\0';
1319 XtFree(str);
1320 return retVal;
1324 ** Turn a multi-line editing text widget into a fake single line text area
1325 ** by disabling the translation for Return. This is a way to give users
1326 ** extra space, by allowing wrapping, but still prohibiting newlines.
1327 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1328 ** the widget to be one line high).
1330 void MakeSingleLineTextW(Widget textW)
1332 static XtTranslations noReturnTable = NULL;
1333 static char *noReturnTranslations = "<Key>Return: activate()\n";
1335 if (noReturnTable == NULL)
1336 noReturnTable = XtParseTranslationTable(noReturnTranslations);
1337 XtOverrideTranslations(textW, noReturnTable);
1341 ** Add up-arrow/down-arrow recall to a single line text field. When user
1342 ** presses up-arrow, string is cleared and recent entries are presented,
1343 ** moving to older ones as each successive up-arrow is pressed. Down-arrow
1344 ** moves to more recent ones, final down-arrow clears the field. Associated
1345 ** routine, AddToHistoryList, makes maintaining a history list easier.
1347 ** Arguments are the widget, and pointers to the history list and number of
1348 ** items, which are expected to change periodically.
1350 void AddHistoryToTextWidget(Widget textW, char ***historyList, int *nItems)
1352 histInfo *histData;
1354 /* create a data structure for passing history info to the callbacks */
1355 histData = (histInfo *)XtMalloc(sizeof(histInfo));
1356 histData->list = historyList;
1357 histData->nItems = nItems;
1358 histData->index = -1;
1360 /* Add an event handler for handling up/down arrow events */
1361 XtAddEventHandler(textW, KeyPressMask, False,
1362 (XtEventHandler)histArrowKeyEH, histData);
1364 /* Add a destroy callback for freeing history data structure */
1365 XtAddCallback(textW, XmNdestroyCallback, histDestroyCB, histData);
1368 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1370 XtFree((char *)clientData);
1373 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
1374 Boolean *continueDispatch)
1376 histInfo *histData = (histInfo *)callData;
1377 KeySym keysym = XLookupKeysym((XKeyEvent *)event, 0);
1379 /* only process up and down arrow keys */
1380 if (keysym != XK_Up && keysym != XK_Down)
1381 return;
1383 /* increment or decrement the index depending on which arrow was pressed */
1384 histData->index += (keysym == XK_Up) ? 1 : -1;
1386 /* if the index is out of range, beep, fix it up, and return */
1387 if (histData->index < -1) {
1388 histData->index = -1;
1389 XBell(XtDisplay(w), 0);
1390 return;
1392 if (histData->index >= *histData->nItems) {
1393 histData->index = *histData->nItems - 1;
1394 XBell(XtDisplay(w), 0);
1395 return;
1398 /* Change the text field contents */
1399 XmTextSetString(w, histData->index == -1 ? "" :
1400 (*histData->list)[histData->index]);
1404 ** Copies a string on to the end of history list, which may be reallocated
1405 ** to make room. If historyList grows beyond its internally set boundary
1406 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1407 ** (HISTORY_LIST_TRIM_TO). Before adding to the list, checks if the item
1408 ** is a duplicate of the last item. If so, it is not added.
1410 void AddToHistoryList(char *newItem, char ***historyList, int *nItems)
1412 char **newList;
1413 int i;
1415 if (*nItems != 0 && !strcmp(newItem, **historyList))
1416 return;
1417 if (*nItems == HISTORY_LIST_MAX) {
1418 for (i=HISTORY_LIST_TRIM_TO; i<HISTORY_LIST_MAX; i++)
1419 XtFree((*historyList)[i]);
1420 *nItems = HISTORY_LIST_TRIM_TO;
1422 newList = (char **)XtMalloc(sizeof(char *) * (*nItems + 1));
1423 for (i=0; i < *nItems; i++)
1424 newList[i+1] = (*historyList)[i];
1425 if (*nItems != 0 && *historyList != NULL)
1426 XtFree((char *)*historyList);
1427 (*nItems)++;
1428 newList[0] = XtNewString(newItem);
1429 *historyList = newList;
1433 * PasswordText - routine to add a callback to any text widget so that all
1434 * text typed by the user is echoed with asterisks, allowing
1435 * a password to be typed in without being seen.
1437 * parameters: w - text widget to add the callback to
1438 * passTxt - pointer to a string created by caller of this routine.
1439 * **NOTE** The length of this string should be one
1440 * greater than the maximum specified by XmNmaxLength.
1441 * This string is set to empty just before the callback
1442 * is added.
1445 void PasswordText(Widget w, char *passTxt)
1447 passTxt[0] = '\0';
1448 XtAddCallback(w, XmNmodifyVerifyCallback, (XtCallbackProc)passwdCB,passTxt);
1452 ** BeginWait/EndWait
1454 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1456 void BeginWait(Widget topCursorWidget)
1458 Display *display = XtDisplay(topCursorWidget);
1459 Pixmap pixmap;
1460 Pixmap maskPixmap;
1461 XColor xcolors[2];
1462 static Cursor waitCursor = 0;
1464 /* if the watch cursor hasn't been created yet, create it */
1465 if (!waitCursor) {
1466 pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1467 (char *)watch_bits, watch_width, watch_height);
1469 maskPixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1470 (char *)watch_mask_bits, watch_width, watch_height);
1472 xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(display));
1473 xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(display));
1475 XQueryColors(display, DefaultColormapOfScreen(
1476 DefaultScreenOfDisplay(display)), xcolors, 2);
1477 waitCursor = XCreatePixmapCursor(display, pixmap, maskPixmap,
1478 &xcolors[0], &xcolors[1], watch_x_hot, watch_y_hot);
1479 XFreePixmap(display, pixmap);
1480 XFreePixmap(display, maskPixmap);
1483 /* display the cursor */
1484 XDefineCursor(display, XtWindow(topCursorWidget), waitCursor);
1487 void BusyWait(Widget widget)
1489 #ifdef __unix__
1490 static const int timeout = 100000; /* 1/10 sec = 100 ms = 100,000 us */
1491 static struct timeval last = { 0, 0 };
1492 struct timeval current;
1493 gettimeofday(&current, NULL);
1495 if ((current.tv_sec != last.tv_sec) ||
1496 (current.tv_usec - last.tv_usec > timeout))
1498 XmUpdateDisplay(widget);
1499 last = current;
1501 #else
1502 static time_t last = 0;
1503 time_t current;
1504 time(&current);
1506 if (difftime(current, last) > 0)
1508 XmUpdateDisplay(widget);
1509 last = current;
1511 #endif
1514 void EndWait(Widget topCursorWidget)
1516 XUndefineCursor(XtDisplay(topCursorWidget), XtWindow(topCursorWidget));
1520 ** Create an X window geometry string from width, height, x, and y values.
1521 ** This is a complement to the X routine XParseGeometry, and uses the same
1522 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1523 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1524 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1525 ** result. Note that in a geometry string, it is not possible to supply a y
1526 ** position without an x position. Also note that the X/YNegative flags
1527 ** mean "add a '-' and negate the value" which is kind of odd.
1529 void CreateGeometryString(char *string, int x, int y,
1530 int width, int height, int bitmask)
1532 char *ptr = string;
1533 int nChars;
1535 if (bitmask & WidthValue) {
1536 sprintf(ptr, "%d%n", width, &nChars);
1537 ptr += nChars;
1539 if (bitmask & HeightValue) {
1540 sprintf(ptr, "x%d%n", height, &nChars);
1541 ptr += nChars;
1543 if (bitmask & XValue) {
1544 if (bitmask & XNegative)
1545 sprintf(ptr, "-%d%n", -x, &nChars);
1546 else
1547 sprintf(ptr, "+%d%n", x, &nChars);
1548 ptr += nChars;
1550 if (bitmask & YValue) {
1551 if (bitmask & YNegative)
1552 sprintf(ptr, "-%d%n", -y, &nChars);
1553 else
1554 sprintf(ptr, "+%d%n", y, &nChars);
1555 ptr += nChars;
1557 *ptr = '\0';
1560 /* */
1561 /* passwdCB: callback routine added by PasswordText routine. This routine */
1562 /* echoes each character typed as an asterisk (*) and a few other */
1563 /* necessary things so that the password typed in is not visible */
1564 /* */
1565 static void passwdCB(Widget w, char * passTxt, XmTextVerifyCallbackStruct
1566 *txtVerStr)
1568 /* XmTextVerifyCallbackStruct: */
1569 /* int reason; should be XmCR_MODIFYING_TEXT_VALUE */
1570 /* XEvent *event; points to XEvent that triggered the callback */
1571 /* Boolean doit; indicates whether action should be performed; setting */
1572 /* this to false negates the action */
1573 /* long currInsert, current position of insert cursor */
1574 /* newInsert; position user attempts to position the insert cursor */
1575 /* long startPos, starting position of the text to modify */
1576 /* endPos; ending position of the text to modify */
1577 /* XmTextBlock text; */
1579 /* XmTextBlock (used to pass text around): */
1580 /* char *ptr; points to text to be inserted */
1581 /* int length; Number of bytes (length) */
1582 /* XmTextFormat format; Representations format */
1584 /* XmTextFormat: either FMT8BIT or FMT16BIT */
1587 int numCharsTyped, i, j, pos;
1589 /* ensure text format is 8-bit characters */
1590 if (txtVerStr->text->format != FMT8BIT)
1591 return;
1593 /* verify assumptions */
1594 /* if (txtVerStr->endPos < txtVerStr->startPos)
1595 fprintf(stderr, "User password callback error: endPos < startPos\n");
1596 if (strlen(passTxt) == 0 && txtVerStr->endPos != 0)
1597 fprintf(stderr, "User password callback error: no txt, but end != 0\n");
1599 printf("passTxt = %s, startPos = %d, endPos = %d, txtBlkAddr = %d\n",
1600 passTxt, txtVerStr->startPos, txtVerStr->endPos, txtVerStr->text);
1601 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1602 printf(" string typed = %s, length = %d\n", txtVerStr->text->ptr,
1603 txtVerStr->text->length);
1605 /* If necessary, expand/compress passTxt and insert any new text */
1606 if (txtVerStr->text != NULL && txtVerStr->text->ptr != NULL)
1607 numCharsTyped = txtVerStr->text->length;
1608 else
1609 numCharsTyped = 0;
1610 /* numCharsTyped = # chars to insert (that user typed) */
1611 /* j = # chars to expand (+) or compress (-) the password string */
1612 j = numCharsTyped - (txtVerStr->endPos - txtVerStr->startPos);
1613 if (j > 0) /* expand case: start at ending null */
1614 for (pos = strlen(passTxt) + 1; pos >= txtVerStr->endPos; --pos)
1615 passTxt[pos+j] = passTxt[pos];
1616 if (j < 0) /* compress case */
1617 for (pos = txtVerStr->startPos + numCharsTyped;
1618 pos <= (int)strlen(passTxt)+1; ++pos)
1619 passTxt[pos] = passTxt[pos-j];
1620 /* then copy text to be inserted into passTxt */
1621 for (pos = txtVerStr->startPos, i = 0; i < numCharsTyped; ++i) {
1622 passTxt[pos+i] = *(txtVerStr->text->ptr + i);
1623 /* Replace text typed by user with asterisks (*) */
1624 *(txtVerStr->text->ptr + i) = '*';
1626 /* printf(" Password string now = %s\n", passTxt); */
1630 ** Remove the white space (blanks and tabs) from a string
1632 static void removeWhiteSpace(char *string)
1634 char *outPtr = string;
1636 while (TRUE) {
1637 if (*string == 0) {
1638 *outPtr = 0;
1639 return;
1640 } else if (*string != ' ' && *string != '\t')
1641 *(outPtr++) = *(string++);
1642 else
1643 string++;
1648 ** Compares two strings and return TRUE if the two strings
1649 ** are the same, ignoring whitespace and case differences.
1651 static int stripCaseCmp(const char *str1, const char *str2)
1653 const char *c1, *c2;
1655 for (c1=str1, c2=str2; *c1!='\0' && *c2!='\0'; c1++, c2++) {
1656 while (*c1 == ' ' || *c1 == '\t')
1657 c1++;
1658 while (*c2 == ' ' || *c2 == '\t')
1659 c2++;
1660 if (toupper((unsigned char)*c1) != toupper((unsigned char)*c2))
1661 return FALSE;
1663 return *c1 == '\0' && *c2 == '\0';
1666 static void warnHandlerCB(String message)
1668 if (strstr(message, "XtRemoveGrab"))
1669 return;
1670 if (strstr(message, "Attempt to remove non-existant passive grab"))
1671 return;
1672 fputs(message, stderr);
1673 fputc('\n', stderr);
1676 static XModifierKeymap *getKeyboardMapping(Display *display) {
1677 static XModifierKeymap *keyboardMap = NULL;
1679 if (keyboardMap == NULL) {
1680 keyboardMap = XGetModifierMapping(display);
1682 return(keyboardMap);
1686 ** get mask for a modifier
1690 static Modifiers findModifierMapping(Display *display, KeyCode keyCode) {
1691 int i, j;
1692 KeyCode *mapentry;
1693 XModifierKeymap *modMap = getKeyboardMapping(display);
1695 if (modMap == NULL || keyCode == 0) {
1696 return(0);
1699 mapentry = modMap->modifiermap;
1700 for (i = 0; i < 8; ++i) {
1701 for (j = 0; j < (modMap->max_keypermod); ++j) {
1702 if (keyCode == *mapentry) {
1703 return(1 << ((mapentry - modMap->modifiermap) / modMap->max_keypermod));
1705 ++mapentry;
1708 return(0);
1711 Modifiers GetNumLockModMask(Display *display) {
1712 static int numLockMask = -1;
1714 if (numLockMask == -1) {
1715 numLockMask = findModifierMapping(display, XKeysymToKeycode(display, XK_Num_Lock));
1717 return(numLockMask);
1721 ** Grab a key regardless of caps-lock and other silly latching keys.
1725 static void reallyGrabAKey(Widget dialog, int keyCode, Modifiers mask) {
1726 Modifiers numLockMask = GetNumLockModMask(XtDisplay(dialog));
1728 if (keyCode == 0) /* No anykey grabs, sorry */
1729 return;
1731 XtGrabKey(dialog, keyCode, mask, True, GrabModeAsync, GrabModeAsync);
1732 XtGrabKey(dialog, keyCode, mask|LockMask, True, GrabModeAsync, GrabModeAsync);
1733 if (numLockMask && numLockMask != LockMask) {
1734 XtGrabKey(dialog, keyCode, mask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1735 XtGrabKey(dialog, keyCode, mask|LockMask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1740 ** Part of dialog mnemonic processing. Search the widget tree under w
1741 ** for widgets with mnemonics. When found, add a passive grab to the
1742 ** dialog widget for the mnemonic character, thus directing mnemonic
1743 ** events to the dialog widget.
1745 static void addMnemonicGrabs(Widget dialog, Widget w, int unmodifiedToo)
1747 char mneString[2];
1748 WidgetList children;
1749 Cardinal numChildren;
1750 int i, isMenu;
1751 KeySym mnemonic = '\0';
1752 unsigned char rowColType;
1753 unsigned int keyCode;
1755 if (XtIsComposite(w)) {
1756 if (XtClass(w) == xmRowColumnWidgetClass) {
1757 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1758 isMenu = rowColType != XmWORK_AREA;
1759 } else
1760 isMenu = False;
1761 if (!isMenu) {
1762 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1763 &numChildren, NULL);
1764 for (i=0; i<(int)numChildren; i++)
1765 addMnemonicGrabs(dialog, children[i], unmodifiedToo);
1767 } else {
1768 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1769 if (mnemonic != XK_VoidSymbol && mnemonic != '\0') {
1770 mneString[0] = mnemonic; mneString[1] = '\0';
1771 keyCode = XKeysymToKeycode(XtDisplay(dialog),
1772 XStringToKeysym(mneString));
1773 reallyGrabAKey(dialog, keyCode, Mod1Mask);
1774 if (unmodifiedToo)
1775 reallyGrabAKey(dialog, keyCode, 0);
1781 ** Callback routine for dialog mnemonic processing.
1783 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event)
1785 findAndActivateMnemonic(w, event->keycode);
1789 ** Look for a widget in the widget tree w, with a mnemonic matching
1790 ** keycode. When one is found, simulate a button press on that widget
1791 ** and give it the keyboard focus. If the mnemonic is on a label,
1792 ** look in the userData field of the label to see if it points to
1793 ** another widget, and give that the focus. This routine is just
1794 ** sufficient for NEdit, no doubt it will need to be extended for
1795 ** mnemonics on widgets other than just buttons and text fields.
1797 static void findAndActivateMnemonic(Widget w, unsigned int keycode)
1799 WidgetList children;
1800 Cardinal numChildren;
1801 int i, isMenu;
1802 KeySym mnemonic = '\0';
1803 char mneString[2];
1804 Widget userData;
1805 unsigned char rowColType;
1807 if (XtIsComposite(w)) {
1808 if (XtClass(w) == xmRowColumnWidgetClass) {
1809 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1810 isMenu = rowColType != XmWORK_AREA;
1811 } else
1812 isMenu = False;
1813 if (!isMenu) {
1814 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1815 &numChildren, NULL);
1816 for (i=0; i<(int)numChildren; i++)
1817 findAndActivateMnemonic(children[i], keycode);
1819 } else {
1820 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1821 if (mnemonic != '\0') {
1822 mneString[0] = mnemonic; mneString[1] = '\0';
1823 if (XKeysymToKeycode(XtDisplay(XtParent(w)),
1824 XStringToKeysym(mneString)) == keycode) {
1825 if (XtClass(w) == xmLabelWidgetClass ||
1826 XtClass(w) == xmLabelGadgetClass) {
1827 XtVaGetValues(w, XmNuserData, &userData, NULL);
1828 if (userData!=NULL && XtIsWidget(userData) &&
1829 XmIsTraversable(userData))
1830 XmProcessTraversal(userData, XmTRAVERSE_CURRENT);
1831 } else if (XmIsTraversable(w)) {
1832 XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1833 SimulateButtonPress(w);
1841 ** Part of workaround for Motif Caps/Num Lock bug. Search the widget tree
1842 ** under w for widgets with accelerators. When found, add three passive
1843 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1844 ** Lock, one for Num Lock, and one for both, thus directing lock +
1845 ** accelerator events to topWidget.
1847 static void addAccelGrabs(Widget topWidget, Widget w)
1849 WidgetList children;
1850 Widget menu;
1851 Cardinal numChildren;
1852 int i;
1854 if (XtIsComposite(w)) {
1855 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1856 &numChildren, NULL);
1857 for (i=0; i<(int)numChildren; i++)
1858 addAccelGrabs(topWidget, children[i]);
1859 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1860 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1861 if (menu != NULL)
1862 addAccelGrabs(topWidget, menu);
1863 } else
1864 addAccelGrab(topWidget, w);
1868 ** Grabs the key + modifier defined in the widget's accelerator resource,
1869 ** in combination with the Caps Lock and Num Lock accelerators.
1871 static void addAccelGrab(Widget topWidget, Widget w)
1873 char *accelString = NULL;
1874 KeySym keysym;
1875 unsigned int modifiers;
1876 KeyCode code;
1877 Modifiers numLockMask = GetNumLockModMask(XtDisplay(topWidget));
1879 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1880 if (accelString == NULL || *accelString == '\0') {
1881 XtFree(accelString);
1882 return;
1885 if (!parseAccelString(XtDisplay(topWidget), accelString, &keysym, &modifiers)) {
1886 XtFree(accelString);
1887 return;
1889 XtFree(accelString);
1891 /* Check to see if this server has this key mapped. Some cruddy PC X
1892 servers (Xoftware) have terrible default keymaps. If not,
1893 XKeysymToKeycode will return 0. However, it's bad news to pass
1894 that to XtGrabKey because 0 is really "AnyKey" which is definitely
1895 not what we want!! */
1897 code = XKeysymToKeycode(XtDisplay(topWidget), keysym);
1898 if (code == 0)
1899 return;
1901 XtGrabKey(topWidget, code,
1902 modifiers | LockMask, True, GrabModeAsync, GrabModeAsync);
1903 if (numLockMask && numLockMask != LockMask) {
1904 XtGrabKey(topWidget, code,
1905 modifiers | numLockMask, True, GrabModeAsync, GrabModeAsync);
1906 XtGrabKey(topWidget, code,
1907 modifiers | LockMask | numLockMask, True, GrabModeAsync, GrabModeAsync);
1912 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1913 ** Returns TRUE if the parse was successful, FALSE, if not.
1915 static int parseAccelString(Display *display, const char *string, KeySym *keySym,
1916 unsigned int *modifiers)
1918 #define N_MODIFIERS 12
1919 /*... Is NumLock always Mod3? */
1920 static char *modifierNames[N_MODIFIERS] = {"Ctrl", "Shift", "Alt", "Mod2",
1921 "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1922 "Button5"};
1923 static unsigned int modifierMasks[N_MODIFIERS] = {ControlMask, ShiftMask,
1924 Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, Button1Mask, Button2Mask,
1925 Button3Mask, Button4Mask, Button5Mask};
1926 Modifiers numLockMask = GetNumLockModMask(display);
1927 char modStr[MAX_ACCEL_LEN];
1928 char evtStr[MAX_ACCEL_LEN];
1929 char keyStr[MAX_ACCEL_LEN];
1930 const char *c, *evtStart, *keyStart;
1931 int i;
1933 if (strlen(string) >= MAX_ACCEL_LEN)
1934 return FALSE;
1936 /* Get the modifier part */
1937 for (c = string; *c != '<'; c++)
1938 if (*c == '\0')
1939 return FALSE;
1940 strncpy(modStr, string, c - string);
1941 modStr[c - string] = '\0';
1943 /* Verify the <key> or <keypress> part */
1944 evtStart = c;
1945 for ( ; *c != '>'; c++)
1946 if (*c == '\0')
1947 return FALSE;
1948 c++;
1949 strncpy(evtStr, evtStart, c - evtStart);
1950 evtStr[c - evtStart] = '\0';
1951 if (!stripCaseCmp(evtStr, "<key>") && !stripCaseCmp(evtStr, "<keypress>"))
1952 return FALSE;
1954 /* Get the keysym part */
1955 keyStart = c;
1956 for ( ; *c != '\0' && !(c != keyStart && *c == ':'); c++);
1957 strncpy(keyStr, keyStart, c - keyStart);
1958 keyStr[c - keyStart] = '\0';
1959 *keySym = XStringToKeysym(keyStr);
1961 /* Parse the modifier part */
1962 *modifiers = 0;
1963 c = modStr;
1964 while (*c != '\0') {
1965 while (*c == ' ' || *c == '\t')
1966 c++;
1967 if (*c == '\0')
1968 break;
1969 for (i = 0; i < N_MODIFIERS; i++) {
1970 if (!strncmp(c, modifierNames[i], strlen(modifierNames[i]))) {
1971 c += strlen(modifierNames[i]);
1972 if (modifierMasks[i] != numLockMask) {
1973 *modifiers |= modifierMasks[i];
1975 break;
1978 if (i == N_MODIFIERS)
1979 return FALSE;
1982 return TRUE;
1986 ** Event handler for patching around Motif's lock + accelerator problem.
1987 ** Looks for a menu item in the patched menu hierarchy and invokes its
1988 ** ArmAndActivate action.
1990 static void lockCB(Widget w, XtPointer callData, XEvent *event,
1991 Boolean *continueDispatch)
1993 Modifiers numLockMask = GetNumLockModMask(XtDisplay(w));
1994 Widget topMenuWidget = (Widget)callData;
1995 *continueDispatch = TRUE;
1997 if (!(((XKeyEvent *)event)->state & (LockMask | numLockMask)))
1998 return;
2000 if (findAndActivateAccel(topMenuWidget, ((XKeyEvent*) event)->keycode,
2001 ((XKeyEvent*) event)->state & ~(LockMask | numLockMask), event)) {
2002 *continueDispatch = FALSE;
2007 ** Search through menu hierarchy under w and look for a button with
2008 ** accelerator matching keyCode + modifiers, and do its action
2010 static int findAndActivateAccel(Widget w, unsigned int keyCode,
2011 unsigned int modifiers, XEvent *event)
2013 WidgetList children;
2014 Widget menu;
2015 Cardinal numChildren;
2016 int i;
2017 char *accelString = NULL;
2018 KeySym keysym;
2019 unsigned int mods;
2021 if (XtIsComposite(w)) {
2022 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
2023 &numChildren, NULL);
2024 for (i=0; i<(int)numChildren; i++)
2025 if (findAndActivateAccel(children[i], keyCode, modifiers, event))
2026 return TRUE;
2027 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
2028 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
2029 if (menu != NULL)
2030 if (findAndActivateAccel(menu, keyCode, modifiers, event))
2031 return TRUE;
2032 } else {
2033 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
2034 if (accelString != NULL && *accelString != '\0') {
2035 if (!parseAccelString(XtDisplay(w), accelString, &keysym, &mods))
2036 return FALSE;
2037 if (keyCode == XKeysymToKeycode(XtDisplay(w), keysym) &&
2038 modifiers == mods) {
2039 if (XtIsSensitive(w)) {
2040 XtCallActionProc(w, "ArmAndActivate", event, NULL, 0);
2041 return TRUE;
2046 return FALSE;
2050 ** Global installation of mouse wheel actions for scrolled windows.
2052 void InstallMouseWheelActions(XtAppContext context)
2054 static XtActionsRec Actions[] = {
2055 {"scrolled-window-scroll-up", scrollUpAP},
2056 {"scrolled-window-page-up", pageUpAP},
2057 {"scrolled-window-scroll-down", scrollDownAP},
2058 {"scrolled-window-page-down", pageDownAP}
2061 XtAppAddActions(context, Actions, XtNumber(Actions));
2065 ** Add mouse wheel support to a specific widget, which must be the scrollable
2066 ** widget of a ScrolledWindow.
2068 void AddMouseWheelSupport(Widget w)
2070 if (XmIsScrolledWindow(XtParent(w)))
2072 static const char scrollTranslations[] =
2073 "Shift<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(1)\n"
2074 "Shift<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(1)\n"
2075 "Ctrl<Btn4Down>,<Btn4Up>: scrolled-window-page-up()\n"
2076 "Ctrl<Btn5Down>,<Btn5Up>: scrolled-window-page-down()\n"
2077 "<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(3)\n"
2078 "<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(3)\n";
2079 static XtTranslations trans_table = NULL;
2081 if (trans_table == NULL)
2083 trans_table = XtParseTranslationTable(scrollTranslations);
2085 XtOverrideTranslations(w, trans_table);
2089 static void pageUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2091 Widget scrolledWindow, scrollBar;
2092 String al[1];
2094 al[0] = "Up";
2095 scrolledWindow = XtParent(w);
2096 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2097 if (scrollBar)
2098 XtCallActionProc(scrollBar, "PageUpOrLeft", event, al, 1) ;
2099 return;
2102 static void pageDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2104 Widget scrolledWindow, scrollBar;
2105 String al[1];
2107 al[0] = "Down";
2108 scrolledWindow = XtParent(w);
2109 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2110 if (scrollBar)
2111 XtCallActionProc(scrollBar, "PageDownOrRight", event, al, 1) ;
2112 return;
2115 static void scrollUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2117 Widget scrolledWindow, scrollBar;
2118 String al[1];
2119 int i, nLines;
2121 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2122 return;
2123 al[0] = "Up";
2124 scrolledWindow = XtParent(w);
2125 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2126 if (scrollBar)
2127 for (i=0; i<nLines; i++)
2128 XtCallActionProc(scrollBar, "IncrementUpOrLeft", event, al, 1) ;
2129 return;
2132 static void scrollDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2134 Widget scrolledWindow, scrollBar;
2135 String al[1];
2136 int i, nLines;
2138 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2139 return;
2140 al[0] = "Down";
2141 scrolledWindow = XtParent(w);
2142 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2143 if (scrollBar)
2144 for (i=0; i<nLines; i++)
2145 XtCallActionProc(scrollBar, "IncrementDownOrRight", event, al, 1) ;
2146 return;
2151 ** This is a disguisting hack to work around a bug in OpenMotif.
2152 ** OpenMotif's toggle button Select() action routine remembers the last radio
2153 ** button that was toggled (stored as global state) and refuses to take any
2154 ** action when that button is clicked again. It fails to detect that we may
2155 ** have changed the button state and that clicking that button could make
2156 ** sense. The result is that radio buttons may apparently get stuck, ie.
2157 ** it is not possible to directly select with the mouse the previously
2158 ** selected button without selection another radio button first.
2159 ** The workaround consist of faking a mouse click on the button that we
2160 ** toggled by calling the Arm, Select, and Disarm action procedures.
2162 ** A minor remaining issue is the fact that, if the workaround is used,
2163 ** it is not possible to change the state without notifying potential
2164 ** XmNvalueChangedCallbacks. In practice, this doesn't seem to be a problem.
2167 void RadioButtonChangeState(Widget widget, Boolean state, Boolean notify)
2170 The bug only exists in OpenMotif 2.1.x/2.2.[0-2]. Since it's quite hard
2171 to detect OpenMotif reliably, we make a rough cut by excluding Lesstif
2172 and all Motif versions >= 2.1.x and < 2.2.3.
2174 #ifndef LESSTIF_VERSION
2175 #if XmVersion == 2001 || (XmVersion == 2002 && XmUPDATE_LEVEL < 3)
2176 /* save the widget with current focus in case it moves */
2177 Widget focusW, shellW = widget;
2178 while (shellW && !XtIsShell(shellW)) {
2179 shellW = XtParent(shellW);
2181 focusW = XtGetKeyboardFocusWidget(shellW);
2183 if (state && XtIsRealized(widget))
2186 Simulate a mouse button click.
2187 The event attributes that matter are the event type and the
2188 coordinates. When the button is managed, the coordinates have to
2189 be inside the button. When the button is not managed, they have to
2190 be (0, 0) to make sure that the Select routine accepts the event.
2192 XEvent ev;
2193 if (XtIsManaged(widget))
2195 Position x, y;
2196 /* Calculate the coordinates in the same way as OM. */
2197 XtTranslateCoords(XtParent(widget), widget->core.x, widget->core.y,
2198 &x, &y);
2199 ev.xbutton.x_root = x + widget->core.border_width;
2200 ev.xbutton.y_root = y + widget->core.border_width;
2202 else
2204 ev.xbutton.x_root = 0;
2205 ev.xbutton.y_root = 0;
2207 /* Default button bindings:
2208 ~c<Btn1Down>: Arm()
2209 ~c<Btn1Up>: Select() Disarm() */
2210 ev.xany.type = ButtonPress;
2211 XtCallActionProc(widget, "Arm", &ev, NULL, 0);
2212 ev.xany.type = ButtonRelease;
2213 XtCallActionProc(widget, "Select", &ev, NULL, 0);
2214 XtCallActionProc(widget, "Disarm", &ev, NULL, 0);
2216 /* restore focus to the originator */
2217 if (focusW) {
2218 XtSetKeyboardFocus(shellW, focusW);
2220 #endif /* XmVersion == 2001 || ... */
2221 #endif /* LESSTIF_VERSION */
2223 /* This is sufficient on non-OM platforms */
2224 XmToggleButtonSetState(widget, state, notify);
2227 /* Workaround for bug in OpenMotif 2.1 and 2.2. If you have an active tear-off
2228 ** menu from a TopLevelShell that is a child of an ApplicationShell, and then
2229 ** close the parent window, Motif crashes. The problem doesn't
2230 ** happen if you close the tear-offs first, so, we programatically close them
2231 ** before destroying the shell widget.
2233 void CloseAllPopupsFor(Widget shell)
2235 #ifndef LESSTIF_VERSION
2236 /* Doesn't happen in LessTif. The tear-off menus are popup-children of
2237 * of the TopLevelShell there, which just works. Motif wants to make
2238 * them popup-children of the ApplicationShell, where it seems to get
2239 * into trouble. */
2241 Widget app = XtParent(shell);
2242 int i;
2244 for (i = 0; i < app->core.num_popups; i++) {
2245 Widget pop = app->core.popup_list[i];
2246 Widget shellFor;
2248 XtVaGetValues(pop, XtNtransientFor, &shellFor, NULL);
2249 if (shell == shellFor)
2250 _XmDismissTearOff(pop, NULL, NULL);
2252 #endif
2255 static long queryDesktop(Display *display, Window window, Atom deskTopAtom)
2257 long deskTopNumber = 0;
2258 Atom actualType;
2259 int actualFormat;
2260 unsigned long nItems, bytesAfter;
2261 unsigned char *prop;
2263 if (XGetWindowProperty(display, window, deskTopAtom, 0, 1,
2264 False, AnyPropertyType, &actualType, &actualFormat, &nItems,
2265 &bytesAfter, &prop) != Success) {
2266 return -1; /* Property not found */
2269 if (actualType == None) {
2270 return -1; /* Property does not exist */
2273 if (actualFormat != 32 || nItems != 1) {
2274 XFree((char*)prop);
2275 return -1; /* Wrong format */
2278 deskTopNumber = *(long*)prop;
2279 XFree((char*)prop);
2280 return deskTopNumber;
2284 ** Returns the current desktop number, or -1 if no desktop information
2285 ** is available.
2287 long QueryCurrentDesktop(Display *display, Window rootWindow)
2289 static Atom currentDesktopAtom = (Atom)-1;
2291 if (currentDesktopAtom == (Atom)-1)
2292 currentDesktopAtom = XInternAtom(display, "_NET_CURRENT_DESKTOP", True);
2294 if (currentDesktopAtom != None)
2295 return queryDesktop(display, rootWindow, currentDesktopAtom);
2297 return -1; /* No desktop information */
2301 ** Returns the number of the desktop the given shell window is currently on,
2302 ** or -1 if no desktop information is available. Note that windows shown
2303 ** on all desktops (sometimes called sticky windows) should return 0xFFFFFFFF.
2305 long QueryDesktop(Display *display, Widget shell)
2307 static Atom wmDesktopAtom = (Atom)-1;
2309 if (wmDesktopAtom == (Atom)-1)
2310 wmDesktopAtom = XInternAtom(display, "_NET_WM_DESKTOP", True);
2312 if (wmDesktopAtom != None)
2313 return queryDesktop(display, XtWindow(shell), wmDesktopAtom);
2315 return -1; /* No desktop information */
2320 ** Clipboard wrapper functions that call the Motif clipboard functions
2321 ** a number of times before giving up. The interfaces are similar to the
2322 ** native Motif functions.
2325 #define SPINCOUNT 10 /* Try at most 10 times */
2326 #define USLEEPTIME 1000 /* 1 ms between retries */
2329 ** Warning reporting
2331 static void warning(const char* mesg)
2333 fprintf(stderr, "NEdit warning:\n%s\n", mesg);
2337 ** Sleep routine
2339 static void microsleep(long usecs)
2341 static struct timeval timeoutVal;
2342 timeoutVal.tv_sec = usecs/1000000;
2343 timeoutVal.tv_usec = usecs - timeoutVal.tv_sec*1000000;
2344 select(0, NULL, NULL, NULL, &timeoutVal);
2348 ** XmClipboardStartCopy spinlock wrapper.
2350 int SpinClipboardStartCopy(Display *display, Window window,
2351 XmString clip_label, Time timestamp, Widget widget,
2352 XmCutPasteProc callback, long *item_id)
2354 int i, res;
2355 for (i=0; i<SPINCOUNT; ++i) {
2356 res = XmClipboardStartCopy(display, window, clip_label, timestamp,
2357 widget, callback, item_id);
2358 if (res == XmClipboardSuccess) {
2359 return res;
2361 microsleep(USLEEPTIME);
2363 warning("XmClipboardStartCopy() failed: clipboard locked.");
2364 return res;
2368 ** XmClipboardCopy spinlock wrapper.
2370 int SpinClipboardCopy(Display *display, Window window, long item_id,
2371 char *format_name, XtPointer buffer, unsigned long length,
2372 long private_id, long *data_id)
2374 int i, res;
2375 for (i=0; i<SPINCOUNT; ++i) {
2376 res = XmClipboardCopy(display, window, item_id, format_name,
2377 buffer, length, private_id, data_id);
2378 if (res == XmClipboardSuccess) {
2379 return res;
2381 if (res == XmClipboardFail) {
2382 warning("XmClipboardCopy() failed: XmClipboardStartCopy not "
2383 "called or too many formats.");
2384 return res;
2386 microsleep(USLEEPTIME);
2388 warning("XmClipboardCopy() failed: clipboard locked.");
2389 return res;
2393 ** XmClipboardEndCopy spinlock wrapper.
2395 int SpinClipboardEndCopy(Display *display, Window window, long item_id)
2397 int i, res;
2398 for (i=0; i<SPINCOUNT; ++i) {
2399 res = XmClipboardEndCopy(display, window, item_id);
2400 if (res == XmClipboardSuccess) {
2401 return res;
2403 if (res == XmClipboardFail) {
2404 warning("XmClipboardEndCopy() failed: XmClipboardStartCopy not "
2405 "called.");
2406 return res;
2408 microsleep(USLEEPTIME);
2410 warning("XmClipboardEndCopy() failed: clipboard locked.");
2411 return res;
2415 ** XmClipboardInquireLength spinlock wrapper.
2417 int SpinClipboardInquireLength(Display *display, Window window,
2418 char *format_name, unsigned long *length)
2420 int i, res;
2421 for (i=0; i<SPINCOUNT; ++i) {
2422 res = XmClipboardInquireLength(display, window, format_name, length);
2423 if (res == XmClipboardSuccess) {
2424 return res;
2426 if (res == XmClipboardNoData) {
2427 return res;
2429 microsleep(USLEEPTIME);
2431 warning("XmClipboardInquireLength() failed: clipboard locked.");
2432 return res;
2436 ** XmClipboardRetrieve spinlock wrapper.
2438 int SpinClipboardRetrieve(Display *display, Window window, char *format_name,
2439 XtPointer buffer, unsigned long length, unsigned long *num_bytes,
2440 long *private_id)
2442 int i, res;
2443 for (i=0; i<SPINCOUNT; ++i) {
2444 res = XmClipboardRetrieve(display, window, format_name, buffer,
2445 length, num_bytes, private_id);
2446 if (res == XmClipboardSuccess) {
2447 return res;
2449 if (res == XmClipboardTruncate) {
2450 warning("XmClipboardRetrieve() failed: buffer too small.");
2451 return res;
2453 if (res == XmClipboardNoData) {
2454 return res;
2456 microsleep(USLEEPTIME);
2458 warning("XmClipboardRetrieve() failed: clipboard locked.");
2459 return res;
2463 ** XmClipboardLock spinlock wrapper.
2465 int SpinClipboardLock(Display *display, Window window)
2467 int i, res;
2468 for (i=0; i<SPINCOUNT; ++i) {
2469 res = XmClipboardLock(display, window);
2470 if (res == XmClipboardSuccess) {
2471 return res;
2473 microsleep(USLEEPTIME);
2475 warning("XmClipboardLock() failed: clipboard locked.");
2476 return res;
2480 ** XmClipboardUnlock spinlock wrapper.
2482 int SpinClipboardUnlock(Display *display, Window window)
2484 int i, res;
2485 /* Spinning doesn't make much sense in this case, I think. */
2486 for (i=0; i<SPINCOUNT; ++i) {
2487 /* Remove ALL locks (we don't use nested locking in NEdit) */
2488 res = XmClipboardUnlock(display, window, True);
2489 if (res == XmClipboardSuccess) {
2490 return res;
2492 microsleep(USLEEPTIME);
2495 * This warning doesn't make much sense in practice. It's usually
2496 * triggered when we try to unlock the clipboard after a failed clipboard
2497 * operation, in an attempt to work around possible *tif clipboard locking
2498 * bugs. In these cases, failure _is_ the expected outcome and the warning
2499 * is bogus. Therefore, the warning is disabled.
2500 warning("XmClipboardUnlock() failed: clipboard not locked or locked "
2501 "by another application.");
2503 return res;