Fix our use of $em_tab_dist after it was changed to 0 for 'turned of'.
[nedit.git] / util / misc.c
blob6f5989d3238771c57a09e47b6c65a672becf79fb
1 static const char CVSID[] = "$Id: misc.c,v 1.89 2010/07/05 06:23:59 lebert 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 histDestroyCB(Widget w, XtPointer clientData, XtPointer callData);
145 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
146 Boolean *continueDispatch);
147 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
148 Cardinal *argcount);
149 static Widget addParentVisArgsAndCall(MotifDialogCreationCall callRoutine,
150 Widget parent, char *name, ArgList arglist, Cardinal argcount);
151 static void scrollDownAP(Widget w, XEvent *event, String *args,
152 Cardinal *nArgs);
153 static void scrollUpAP(Widget w, XEvent *event, String *args,
154 Cardinal *nArgs);
155 static void pageDownAP(Widget w, XEvent *event, String *args,
156 Cardinal *nArgs);
157 static void pageUpAP(Widget w, XEvent *event, String *args,
158 Cardinal *nArgs);
159 static long queryDesktop(Display *display, Window window, Atom deskTopAtom);
160 static void warning(const char* mesg);
161 static void microsleep(long usecs);
164 ** Set up closeCB to be called when the user selects close from the
165 ** window menu. The close menu item usually activates f.kill which
166 ** sends a WM_DELETE_WINDOW protocol request for the window.
168 void AddMotifCloseCallback(Widget shell, XtCallbackProc closeCB, void *arg)
170 static Atom wmpAtom, dwAtom = 0;
171 Display *display = XtDisplay(shell);
173 /* deactivate the built in delete response of killing the application */
174 XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);
176 /* add a delete window protocol callback instead */
177 if (dwAtom == 0) {
178 wmpAtom = XmInternAtom(display, "WM_PROTOCOLS", FALSE);
179 dwAtom = XmInternAtom(display, "WM_DELETE_WINDOW", FALSE);
181 XmAddProtocolCallback(shell, wmpAtom, dwAtom, closeCB, arg);
185 ** Motif still generates spurious passive grab warnings on both IBM and SGI
186 ** This routine suppresses them.
187 ** (amai, 20011121:)
188 ** And triggers an annoying message on DEC systems on alpha ->
189 ** See Xt sources, xc/lib/Xt/Error.c:DefaultMsg()):
190 ** actually for some obscure reasons they check for XtError/Warning
191 ** handlers being installed when running as a root process!
192 ** Since this handler doesn't help on non-effected systems we should only
193 ** use it if necessary.
195 void SuppressPassiveGrabWarnings(void)
197 #if !defined(__alpha) && !defined(__EMX__)
198 XtSetWarningHandler(warnHandlerCB);
199 #endif
203 ** This routine kludges around the problem of backspace not being mapped
204 ** correctly when Motif is used between a server with a delete key in
205 ** the traditional typewriter backspace position and a client that
206 ** expects a backspace key in that position. Though there are three
207 ** distinct levels of key re-mapping in effect when a user is running
208 ** a Motif application, none of these is really appropriate or effective
209 ** for eliminating the delete v.s. backspace problem. Our solution is,
210 ** sadly, to eliminate the forward delete functionality of the delete key
211 ** in favor of backwards delete for both keys. So as not to prevent the
212 ** user or the application from applying other translation table re-mapping,
213 ** we apply re-map the key as a post-processing step, applied after widget
214 ** creation. As a result, the re-mapping necessarily becomes embedded
215 ** throughout an application (wherever text widgets are created), and
216 ** within library routines, including the Nirvana utility library. To
217 ** make this remapping optional, the SetDeleteRemap function provides a
218 ** way for an application to turn this functionality on and off. It is
219 ** recommended that applications that use this routine provide an
220 ** application resource called remapDeleteKey so savvy users can get
221 ** their forward delete functionality back.
223 void RemapDeleteKey(Widget w)
225 static XtTranslations table = NULL;
226 static char *translations =
227 "~Shift~Ctrl~Meta~Alt<Key>osfDelete: delete-previous-character()\n";
229 if (RemapDeleteEnabled) {
230 if (table == NULL)
231 table = XtParseTranslationTable(translations);
232 XtOverrideTranslations(w, table);
236 void SetDeleteRemap(int state)
238 RemapDeleteEnabled = state;
243 ** The routine adds the passed in top-level Widget's window to our
244 ** window group. On the first call a dummy unmapped window will
245 ** be created to be our leader. This must not be called before the
246 ** Widget has be realized and should be called before the window is
247 ** mapped.
249 static void setWindowGroup(Widget shell) {
250 static int firstTime = True;
251 static Window groupLeader;
252 Display *display = XtDisplay(shell);
253 XWMHints *wmHints;
255 if (firstTime) {
256 /* Create a dummy window to be the group leader for our windows */
257 String name, class;
258 XClassHint *classHint;
260 groupLeader = XCreateSimpleWindow(display,
261 RootWindow(display, DefaultScreen(display)),
262 1, 1, 1, 1, 0, 0, 0);
264 /* Set it's class hint so it will be identified correctly by the
265 window manager */
266 XtGetApplicationNameAndClass(display, &name, &class);
267 classHint = XAllocClassHint();
268 classHint->res_name = name;
269 classHint->res_class = class;
270 XSetClassHint(display, groupLeader, classHint);
271 XFree(classHint);
273 firstTime = False;
276 /* Set the window group hint for this shell's window */
277 wmHints = XGetWMHints(display, XtWindow(shell));
278 wmHints->window_group = groupLeader;
279 wmHints->flags |= WindowGroupHint;
280 XSetWMHints(display, XtWindow(shell), wmHints);
281 XFree(wmHints);
285 ** This routine resolves a window manager protocol incompatibility between
286 ** the X toolkit and several popular window managers. Using this in place
287 ** of XtRealizeWidget will realize the window in a way which allows the
288 ** affected window managers to apply their own placement strategy to the
289 ** window, as opposed to forcing the window to a specific location.
291 ** One of the hints in the WM_NORMAL_HINTS protocol, PPlacement, gets set by
292 ** the X toolkit (probably part of the Core or Shell widget) when a shell
293 ** widget is realized to the value stored in the XmNx and XmNy resources of the
294 ** Core widget. While callers can set these values, there is no "unset" value
295 ** for these resources. On systems which are more Motif aware, a PPosition
296 ** hint of 0,0, which is the default for XmNx and XmNy, is interpreted as,
297 ** "place this as if no hints were specified". Unfortunately the fvwm family
298 ** of window managers, which are now some of the most popular, interpret this
299 ** as "place this window at (0,0)". This routine intervenes between the
300 ** realizing and the mapping of the window to remove the inappropriate
301 ** PPlacement hint.
304 void RemovePPositionHint(Widget shell)
306 XSizeHints *hints = XAllocSizeHints();
307 long suppliedHints;
309 /* Get rid of the incorrect WMNormal hint */
310 if (XGetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints,
311 &suppliedHints))
313 hints->flags &= ~PPosition;
314 XSetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints);
317 XFree(hints);
320 void RealizeWithoutForcingPosition(Widget shell)
322 Boolean mappedWhenManaged;
324 /* Temporarily set value of XmNmappedWhenManaged
325 to stop the window from popping up right away */
326 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
327 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
329 /* Realize the widget in unmapped state */
330 XtRealizeWidget(shell);
332 /* Remove the hint */
333 RemovePPositionHint(shell);
335 /* Set WindowGroupHint so the NEdit icons can be grouped; this
336 seems to be necessary starting with Gnome 2.0 */
337 setWindowGroup(shell);
339 /* Map the widget */
340 XtMapWidget(shell);
342 /* Restore the value of XmNmappedWhenManaged */
343 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
347 ** Older X applications and X servers were mostly designed to operate with
348 ** visual class PseudoColor, because older displays were at most 8 bits
349 ** deep. Modern X servers, however, usually support 24 bit depth and other
350 ** color models. Sun (and others?) still sets their default visual to
351 ** 8-bit PseudoColor, because some of their X applications don't work
352 ** properly with the other color models. The problem with PseudoColor, of
353 ** course, is that users run out of colors in the default colormap, and if
354 ** they install additional colormaps for individual applications, colors
355 ** flash and change weirdly when you change your focus from one application
356 ** to another.
358 ** In addition to the poor choice of default, a design flaw in Xt makes it
359 ** impossible even for savvy users to specify the XtNvisual resource to
360 ** switch to a deeper visual. The problem is that the colormap resource is
361 ** processed independently of the visual resource, and usually results in a
362 ** colormap for the default visual rather than for the user-selected one.
364 ** This routine should be called before creating a shell widget, to
365 ** pre-process the visual, depth, and colormap resources, and return the
366 ** proper values for these three resources to be passed to XtAppCreateShell.
367 ** Applications which actually require a particular color model (i.e. for
368 ** doing color table animation or dynamic color assignment) should not use
369 ** this routine.
371 ** Note that a consequence of using the "best" as opposed to the default
372 ** visual is that some color resources are still converted with the default
373 ** visual (particularly *background), and these must be avoided by widgets
374 ** which are allowed to handle any visual.
376 ** Returns True if the best visual is the default, False otherwise.
378 Boolean FindBestVisual(Display *display, const char *appName, const char *appClass,
379 Visual **visual, int *depth, Colormap *colormap)
381 char rsrcName[256], rsrcClass[256], *valueString, *type, *endPtr;
382 XrmValue value;
383 int screen = DefaultScreen(display);
384 int reqDepth = -1;
385 long reqID = -1; /* should hold a 'VisualID' and a '-1' ... */
386 int reqClass = -1;
387 int installColormap = FALSE;
388 int maxDepth, bestClass, bestVisual, nVis, i, j;
389 XVisualInfo visTemplate, *visList = NULL;
390 static Visual *cachedVisual = NULL;
391 static Colormap cachedColormap;
392 static int cachedDepth = 0;
393 int bestClasses[] = {StaticGray, GrayScale, StaticColor, PseudoColor,
394 DirectColor, TrueColor};
396 /* If results have already been computed, just return them */
397 if (cachedVisual != NULL) {
398 *visual = cachedVisual;
399 *depth = cachedDepth;
400 *colormap = cachedColormap;
401 return (*visual == DefaultVisual(display, screen));
404 /* Read the visualID and installColormap resources for the application.
405 visualID can be specified either as a number (the visual id as
406 shown by xdpyinfo), as a visual class name, or as Best or Default. */
407 sprintf(rsrcName,"%s.%s", appName, "visualID");
408 sprintf(rsrcClass, "%s.%s", appClass, "VisualID");
409 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
410 &value)) {
411 valueString = value.addr;
412 reqID = (int)strtol(valueString, &endPtr, 0);
413 if (endPtr == valueString) {
414 reqID = -1;
415 if (stripCaseCmp(valueString, "Default"))
416 reqID = DefaultVisual(display, screen)->visualid;
417 else if (stripCaseCmp(valueString, "StaticGray"))
418 reqClass = StaticGray;
419 else if (stripCaseCmp(valueString, "StaticColor"))
420 reqClass = StaticColor;
421 else if (stripCaseCmp(valueString, "TrueColor"))
422 reqClass = TrueColor;
423 else if (stripCaseCmp(valueString, "GrayScale"))
424 reqClass = GrayScale;
425 else if (stripCaseCmp(valueString, "PseudoColor"))
426 reqClass = PseudoColor;
427 else if (stripCaseCmp(valueString, "DirectColor"))
428 reqClass = DirectColor;
429 else if (!stripCaseCmp(valueString, "Best"))
430 fprintf(stderr, "Invalid visualID resource value\n");
433 sprintf(rsrcName,"%s.%s", appName, "installColormap");
434 sprintf(rsrcClass, "%s.%s", appClass, "InstallColormap");
435 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
436 &value)) {
437 if (stripCaseCmp(value.addr, "Yes") || stripCaseCmp(value.addr, "True"))
438 installColormap = TRUE;
441 visTemplate.screen = screen;
443 /* Generate a list of visuals to consider. (Note, vestigial code for
444 user-requested visual depth is left in, just in case that function
445 might be needed again, but it does nothing). */
446 if (reqID != -1) {
447 visTemplate.visualid = reqID;
448 visList = XGetVisualInfo(display, VisualScreenMask|VisualIDMask,
449 &visTemplate, &nVis);
450 if (visList == NULL)
451 fprintf(stderr, "VisualID resource value not valid\n");
453 if (visList == NULL && reqClass != -1 && reqDepth != -1) {
454 visTemplate.class = reqClass;
455 visTemplate.depth = reqDepth;
456 visList = XGetVisualInfo(display,
457 VisualScreenMask| VisualClassMask | VisualDepthMask,
458 &visTemplate, &nVis);
459 if (visList == NULL)
460 fprintf(stderr, "Visual class/depth combination not available\n");
462 if (visList == NULL && reqClass != -1) {
463 visTemplate.class = reqClass;
464 visList = XGetVisualInfo(display, VisualScreenMask|VisualClassMask,
465 &visTemplate, &nVis);
466 if (visList == NULL)
467 fprintf(stderr,
468 "Visual Class from resource \"visualID\" not available\n");
470 if (visList == NULL && reqDepth != -1) {
471 visTemplate.depth = reqDepth;
472 visList = XGetVisualInfo(display, VisualScreenMask|VisualDepthMask,
473 &visTemplate, &nVis);
474 if (visList == NULL)
475 fprintf(stderr, "Requested visual depth not available\n");
477 if (visList == NULL) {
478 visList = XGetVisualInfo(display, VisualScreenMask, &visTemplate, &nVis);
479 if (visList == NULL) {
480 fprintf(stderr, "Internal Error: no visuals available?\n");
481 *visual = DefaultVisual(display, screen);
482 *depth = DefaultDepth(display, screen);
483 *colormap = DefaultColormap(display, screen);
484 return True;
488 /* Choose among the visuals in the candidate list. Prefer maximum
489 depth first then matching default, then largest value of bestClass
490 (I'm not sure whether we actually care about class) */
491 maxDepth = 0;
492 bestClass = 0;
493 bestVisual = 0;
494 for (i=0; i < nVis; i++) {
495 /* X.Org 6.8+ 32-bit visuals (with alpha-channel) cause a lot of
496 problems, so we have to skip them. We already try this by setting
497 the environment variable XLIB_SKIP_ARGB_VISUALS at startup (in
498 nedit.c), but that doesn't cover the case where NEdit is running on
499 a host that doesn't use the X.Org X libraries but is displaying
500 remotely on an X.Org server. Therefore, this additional check is
501 added.
502 Note that this check in itself is not sufficient. There have been
503 bug reports that seemed to indicate that non-32-bit visuals with an
504 alpha-channel exist. The combined approach (env. var. + 32-bit
505 check) should cover the vast majority of the cases, though. */
506 if (visList[i].depth >= 32 &&
507 strstr(ServerVendor(display), "X.Org") != 0) {
508 continue;
510 if (visList[i].depth > maxDepth) {
511 maxDepth = visList[i].depth;
512 bestClass = 0;
513 bestVisual = i;
515 if (visList[i].depth == maxDepth) {
516 if (visList[i].visual == DefaultVisual(display, screen))
517 bestVisual = i;
518 if (visList[bestVisual].visual != DefaultVisual(display, screen)) {
519 for (j = 0; j < (int)XtNumber(bestClasses); j++) {
520 if (visList[i].class == bestClasses[j] && j > bestClass) {
521 bestClass = j;
522 bestVisual = i;
528 *visual = cachedVisual = visList[bestVisual].visual;
529 *depth = cachedDepth = visList[bestVisual].depth;
531 /* If the chosen visual is not the default, it needs a colormap allocated */
532 if (*visual == DefaultVisual(display, screen) && !installColormap)
533 *colormap = cachedColormap = DefaultColormap(display, screen);
534 else {
535 *colormap = cachedColormap = XCreateColormap(display,
536 RootWindow(display, screen), cachedVisual, AllocNone);
537 XInstallColormap(display, cachedColormap);
539 /* printf("Chose visual with depth %d, class %d, colormap %ld, id 0x%x\n",
540 visList[bestVisual].depth, visList[bestVisual].class,
541 *colormap, cachedVisual->visualid); */
542 /* Fix memory leak */
543 if (visList != NULL) {
544 XFree(visList);
547 return (*visual == DefaultVisual(display, screen));
551 ** If you want to use a non-default visual with Motif, shells all have to be
552 ** created with that visual, depth, and colormap, even if the parent has them
553 ** set up properly. Substituting these routines, will append visual args copied
554 ** from the parent widget (CreatePopupMenu and CreatePulldownMenu), or from the
555 ** best visual, obtained via FindBestVisual above (CreateShellWithBestVis).
557 Widget CreateDialogShell(Widget parent, char *name,
558 ArgList arglist, Cardinal argcount)
560 return addParentVisArgsAndCall(XmCreateDialogShell, parent, name, arglist,
561 argcount);
565 Widget CreatePopupMenu(Widget parent, char *name, ArgList arglist,
566 Cardinal argcount)
568 return addParentVisArgsAndCall(XmCreatePopupMenu, parent, name,
569 arglist, argcount);
573 Widget CreatePulldownMenu(Widget parent, char *name,
574 ArgList arglist, Cardinal argcount)
576 return addParentVisArgsAndCall(XmCreatePulldownMenu, parent, name, arglist,
577 argcount);
581 Widget CreatePromptDialog(Widget parent, char *name,
582 ArgList arglist, Cardinal argcount)
584 return addParentVisArgsAndCall(XmCreatePromptDialog, parent, name, arglist,
585 argcount);
589 Widget CreateSelectionDialog(Widget parent, char *name,
590 ArgList arglist, Cardinal argcount)
592 Widget dialog = addParentVisArgsAndCall(XmCreateSelectionDialog, parent, name,
593 arglist, argcount);
594 AddMouseWheelSupport(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST));
595 return dialog;
599 Widget CreateFormDialog(Widget parent, char *name,
600 ArgList arglist, Cardinal argcount)
602 return addParentVisArgsAndCall(XmCreateFormDialog, parent, name, arglist,
603 argcount);
607 Widget CreateFileSelectionDialog(Widget parent, char *name,
608 ArgList arglist, Cardinal argcount)
610 Widget dialog = addParentVisArgsAndCall(XmCreateFileSelectionDialog, parent,
611 name, arglist, argcount);
613 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_LIST));
614 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_DIR_LIST));
615 return dialog;
619 Widget CreateQuestionDialog(Widget parent, char *name,
620 ArgList arglist, Cardinal argcount)
622 return addParentVisArgsAndCall(XmCreateQuestionDialog, parent, name,
623 arglist, argcount);
627 Widget CreateMessageDialog(Widget parent, char *name,
628 ArgList arglist, Cardinal argcount)
630 return addParentVisArgsAndCall(XmCreateMessageDialog, parent, name,
631 arglist, argcount);
635 Widget CreateErrorDialog(Widget parent, char *name,
636 ArgList arglist, Cardinal argcount)
638 return addParentVisArgsAndCall(XmCreateErrorDialog, parent, name, arglist,
639 argcount);
642 Widget CreateWidget(Widget parent, const char *name, WidgetClass class,
643 ArgList arglist, Cardinal argcount)
645 Widget result;
646 ArgList al = addParentVisArgs(parent, arglist, &argcount);
647 result = XtCreateWidget(name, class, parent, al, argcount);
648 XtFree((char *)al);
649 return result;
652 Widget CreateShellWithBestVis(String appName, String appClass,
653 WidgetClass class, Display *display, ArgList args, Cardinal nArgs)
655 Visual *visual;
656 int depth;
657 Colormap colormap;
658 ArgList al;
659 Cardinal ac = nArgs;
660 Widget result;
662 FindBestVisual(display, appName, appClass, &visual, &depth, &colormap);
663 al = (ArgList)XtMalloc(sizeof(Arg) * (nArgs + 3));
664 if (nArgs != 0)
665 memcpy(al, args, sizeof(Arg) * nArgs);
666 XtSetArg(al[ac], XtNvisual, visual); ac++;
667 XtSetArg(al[ac], XtNdepth, depth); ac++;
668 XtSetArg(al[ac], XtNcolormap, colormap); ac++;
669 result = XtAppCreateShell(appName, appClass, class, display, al, ac);
670 XtFree((char *)al);
671 return result;
675 Widget CreatePopupShellWithBestVis(String shellName, WidgetClass class,
676 Widget parent, ArgList arglist, Cardinal argcount)
678 Widget result;
679 ArgList al = addParentVisArgs(parent, arglist, &argcount);
680 result = XtCreatePopupShell(shellName, class, parent, al, argcount);
681 XtFree((char *)al);
682 return result;
686 ** Extends an argument list for widget creation with additional arguments
687 ** for visual, colormap, and depth. The original argument list is not altered
688 ** and it's the caller's responsability to free the returned list.
690 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
691 Cardinal *argcount)
693 Visual *visual;
694 int depth;
695 Colormap colormap;
696 ArgList al;
697 Widget parentShell = parent;
699 /* Find the application/dialog/menu shell at the top of the widget
700 hierarchy, which has the visual resource being used */
701 while (True) {
702 if (XtIsShell(parentShell))
703 break;
704 if (parentShell == NULL) {
705 fprintf(stderr, "failed to find shell\n");
706 exit(EXIT_FAILURE);
708 parentShell = XtParent(parentShell);
711 /* Add the visual, depth, and colormap resources to the argument list */
712 XtVaGetValues(parentShell, XtNvisual, &visual, XtNdepth, &depth,
713 XtNcolormap, &colormap, NULL);
714 al = (ArgList)XtMalloc(sizeof(Arg) * ((*argcount) + 3));
715 if ((*argcount) != 0)
716 memcpy(al, arglist, sizeof(Arg) * (*argcount));
718 /* For non-Lesstif versions, the visual, depth, and colormap are now set
719 globally via the resource database. So strictly spoken, it is no
720 longer necessary to set them explicitly for every shell widget.
722 For Lesstif, however, this doesn't work. Luckily, Lesstif handles
723 non-default visuals etc. properly for its own shells and
724 we can take care of things for our shells (eg, call tips) here. */
725 XtSetArg(al[*argcount], XtNvisual, visual); (*argcount)++;
726 XtSetArg(al[*argcount], XtNdepth, depth); (*argcount)++;
727 XtSetArg(al[*argcount], XtNcolormap, colormap); (*argcount)++;
728 return al;
733 ** Calls one of the Motif widget creation routines, splicing in additional
734 ** arguments for visual, colormap, and depth.
736 static Widget addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,
737 Widget parent, char *name, ArgList arglist, Cardinal argcount)
739 Widget result;
740 ArgList al = addParentVisArgs(parent, arglist, &argcount);
741 result = (*createRoutine)(parent, name, al, argcount);
742 XtFree((char *)al);
743 return result;
747 ** ManageDialogCenteredOnPointer is used in place of XtManageChild for
748 ** popping up a dialog to enable the dialog to be centered under the
749 ** mouse pointer. Whether it pops up the dialog centered under the pointer
750 ** or in its default position centered over the parent widget, depends on
751 ** the value set in the SetPointerCenteredDialogs call.
752 ** Additionally, this function constrains the size of the dialog to the
753 ** screen's size, to avoid insanely wide dialogs with obscured buttons.
755 void ManageDialogCenteredOnPointer(Widget dialogChild)
757 Widget shell = XtParent(dialogChild);
758 Window root, child;
759 unsigned int mask;
760 unsigned int width, height, borderWidth, depth;
761 int x, y, winX, winY, maxX, maxY, maxWidth, maxHeight;
762 Dimension xtWidth, xtHeight;
763 Boolean mappedWhenManaged;
764 static const int slop = 25;
766 /* Temporarily set value of XmNmappedWhenManaged
767 to stop the dialog from popping up right away */
768 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
769 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
771 /* Ensure that the dialog doesn't get wider/taller than the screen.
772 We use a hard-coded "slop" size because we don't know the border
773 width until it's on screen, and by then it's too late. Putting
774 this before managing the widgets allows it to get the geometry
775 right on the first pass and also prevents the user from
776 accidentally resizing too wide. */
777 maxWidth = XtScreen(shell)->width - slop;
778 maxHeight = XtScreen(shell)->height - slop;
780 XtVaSetValues(shell,
781 XmNmaxWidth, maxWidth,
782 XmNmaxHeight, maxHeight,
783 NULL);
785 /* Manage the dialog */
786 XtManageChild(dialogChild);
788 /* Check to see if the window manager doesn't respect XmNmaxWidth
789 and XmNmaxHeight on the first geometry pass (sawfish, twm, fvwm).
790 For this to work XmNresizePolicy must be XmRESIZE_NONE, otherwise
791 the dialog will try to expand anyway. */
792 XtVaGetValues(shell, XmNwidth, &xtWidth, XmNheight, &xtHeight, NULL);
793 if (xtWidth > maxWidth)
794 XtVaSetValues(shell, XmNwidth, (Dimension) maxWidth, NULL);
795 if (xtHeight > maxHeight)
796 XtVaSetValues(shell, XmNheight, (Dimension) maxHeight, NULL);
798 /* Only set the x/y position if the centering option is enabled.
799 Avoid getting the coordinates if not so, to save a few round-trips
800 to the server. */
801 if (PointerCenteredDialogsEnabled) {
802 /* Get the pointer position (x, y) */
803 XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child,
804 &x, &y, &winX, &winY, &mask);
806 /* Translate the pointer position (x, y) into a position for the new
807 window that will place the pointer at its center */
808 XGetGeometry(XtDisplay(shell), XtWindow(shell), &root, &winX, &winY,
809 &width, &height, &borderWidth, &depth);
810 width += 2 * borderWidth;
811 height += 2 * borderWidth;
813 x -= width/2;
814 y -= height/2;
816 /* Ensure that the dialog remains on screen */
817 maxX = maxWidth - width;
818 maxY = maxHeight - height;
819 if (x > maxX) x = maxX;
820 if (x < 0) x = 0;
821 if (y > maxY) y = maxY;
822 if (y < 0) y = 0;
824 /* Some window managers (Sawfish) don't appear to respond
825 to the geometry set call in synchronous mode. This causes
826 the window to delay XmNwmTimeout (default 5 seconds) before
827 posting, and it is very annoying. See "man VendorShell" for
828 more info. */
829 XtVaSetValues(shell, XmNuseAsyncGeometry, True, NULL);
831 /* Set desired window position in the DialogShell */
832 XtVaSetValues(shell, XmNx, x, XmNy, y, NULL);
835 /* Map the widget */
836 XtMapWidget(shell);
838 /* Restore the value of XmNmappedWhenManaged */
839 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
843 ** Cause dialogs created by libNUtil.a routines (such as DialogF),
844 ** and dialogs which use ManageDialogCenteredOnPointer to pop up
845 ** over the pointer (state = True), or pop up in their default
846 ** positions (state = False)
848 void SetPointerCenteredDialogs(int state)
850 PointerCenteredDialogsEnabled = state;
855 ** Raise a window to the top and give it the input focus. Setting input focus
856 ** is important on systems which use explict (rather than pointer) focus.
858 ** The X alternatives XMapRaised, and XSetInputFocus both have problems.
859 ** XMapRaised only gives the window the focus if it was initially not visible,
860 ** and XSetInputFocus sets the input focus, but crashes if the window is not
861 ** visible.
863 ** This routine should also be used in the case where a dialog is popped up and
864 ** subsequent calls to the dialog function use a flag, or the XtIsManaged, to
865 ** decide whether to create a new instance of the dialog, because on slower
866 ** systems, events can intervene while a dialog is still on its way up and its
867 ** window is still invisible, causing a subtle crash potential if
868 ** XSetInputFocus is used.
870 void RaiseDialogWindow(Widget shell)
872 RaiseWindow(XtDisplay(shell), XtWindow(shell), True);
875 void RaiseShellWindow(Widget shell, Boolean focus)
877 RaiseWindow(XtDisplay(shell), XtWindow(shell), focus);
880 void RaiseWindow(Display *display, Window w, Boolean focus)
882 if (focus) {
883 XWindowAttributes winAttr;
885 XGetWindowAttributes(display, w, &winAttr);
886 if (winAttr.map_state == IsViewable)
887 XSetInputFocus(display, w, RevertToParent, CurrentTime);
890 WmClientMsg(display, w, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
891 XMapRaised(display, w);
895 ** Add a handler for mnemonics in a dialog (Motif currently only handles
896 ** mnemonics in menus) following the example of M.S. Windows. To add
897 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
898 ** a menu, on push buttons or toggle buttons, and call this function
899 ** when the dialog is fully constructed. Mnemonics added or changed
900 ** after this call will not be noticed. To add a mnemonic to a text field
901 ** or list, set the XmNmnemonic resource on the appropriate label and set
902 ** the XmNuserData resource of the label to the widget to get the focus
903 ** when the mnemonic is typed.
905 void AddDialogMnemonicHandler(Widget dialog, int unmodifiedToo)
907 XtAddEventHandler(dialog, KeyPressMask, False,
908 (XtEventHandler)mnemonicCB, (XtPointer)0);
909 addMnemonicGrabs(dialog, dialog, unmodifiedToo);
913 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
915 void RemoveDialogMnemonicHandler(Widget dialog)
917 XtUngrabKey(dialog, AnyKey, Mod1Mask);
918 XtRemoveEventHandler(dialog, KeyPressMask, False,
919 (XtEventHandler)mnemonicCB, (XtPointer)0);
923 ** Patch around Motif's poor handling of menu accelerator keys. Motif
924 ** does not process menu accelerators when the caps lock or num lock
925 ** keys are engaged. To enable accelerators in these cases, call this
926 ** routine with the completed menu bar widget as "topMenuContainer", and
927 ** the top level shell widget as "topWidget". It will add key grabs for
928 ** all of the accelerators it finds in the topMenuContainer menu tree, and
929 ** an event handler which can process dropped accelerator events by (again)
930 ** traversing the menu tree looking for matching accelerators, and invoking
931 ** the appropriate button actions. Any dynamic additions to the menus
932 ** require a call to UpdateAccelLockPatch to add the additional grabs.
933 ** Unfortunately, these grabs can not be removed.
935 void AccelLockBugPatch(Widget topWidget, Widget topMenuContainer)
937 XtAddEventHandler(topWidget, KeyPressMask, False, lockCB, topMenuContainer);
938 addAccelGrabs(topWidget, topMenuContainer);
942 ** Add additional key grabs for new menu items added to the menus, for
943 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
944 ** the same widget passed in the original call to AccelLockBugPatch.
946 void UpdateAccelLockPatch(Widget topWidget, Widget newButton)
948 addAccelGrab(topWidget, newButton);
952 ** PopDownBugPatch
954 ** Under some circumstances, popping down a dialog and its parent in
955 ** rapid succession causes a crash. This routine delays and
956 ** processs events until receiving a ReparentNotify event.
957 ** (I have no idea why a ReparentNotify event occurs at all, but it does
958 ** mark the point where it is safe to destroy or pop down the parent, and
959 ** it might have something to do with the bug.) There is a failsafe in
960 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
961 ** Use this sparingly, only when real crashes are observed, and periodically
962 ** check to make sure that it is still necessary.
964 void PopDownBugPatch(Widget w)
966 time_t stopTime;
968 stopTime = time(NULL) + 1;
969 while (time(NULL) <= stopTime) {
970 XEvent event;
971 XtAppContext context = XtWidgetToApplicationContext(w);
972 XtAppPeekEvent(context, &event);
973 if (event.xany.type == ReparentNotify)
974 return;
975 XtAppProcessEvent(context, XtIMAll);
980 ** Convert a compound string to a C style null terminated string.
981 ** Returned string must be freed by the caller.
983 char *GetXmStringText(XmString fromString)
985 XmStringContext context;
986 char *text, *toPtr, *toString, *fromPtr;
987 XmStringCharSet charset;
988 XmStringDirection direction;
989 Boolean separator;
991 /* Malloc a buffer large enough to hold the string. XmStringLength
992 should always be slightly longer than necessary, but won't be
993 shorter than the equivalent null-terminated string */
994 toString = XtMalloc(XmStringLength(fromString));
996 /* loop over all of the segments in the string, copying each segment
997 into the output string and converting separators into newlines */
998 XmStringInitContext(&context, fromString);
999 toPtr = toString;
1000 while (XmStringGetNextSegment(context, &text,
1001 &charset, &direction, &separator)) {
1002 for (fromPtr=text; *fromPtr!='\0'; fromPtr++)
1003 *toPtr++ = *fromPtr;
1004 if (separator)
1005 *toPtr++ = '\n';
1006 XtFree(text);
1007 XtFree(charset);
1010 /* terminate the string, free the context, and return the string */
1011 *toPtr++ = '\0';
1012 XmStringFreeContext(context);
1013 return toString;
1017 ** Get the XFontStruct that corresponds to the default (first) font in
1018 ** a Motif font list. Since Motif stores this, it saves us from storing
1019 ** it or querying it from the X server.
1021 XFontStruct *GetDefaultFontStruct(XmFontList font)
1023 XFontStruct *fs;
1024 XmFontContext context;
1025 XmStringCharSet charset;
1027 XmFontListInitFontContext(&context, font);
1028 XmFontListGetNextFont(context, &charset, &fs);
1029 XmFontListFreeFontContext(context);
1030 XtFree(charset);
1031 return fs;
1035 ** Create a string table suitable for passing to XmList widgets
1037 XmString* StringTable(int count, ... )
1039 va_list ap;
1040 XmString *array;
1041 int i;
1042 char *str;
1044 va_start(ap, count);
1045 array = (XmString*)XtMalloc((count+1) * sizeof(XmString));
1046 for(i = 0; i < count; i++ ) {
1047 str = va_arg(ap, char *);
1048 array[i] = XmStringCreateSimple(str);
1050 array[i] = (XmString)0;
1051 va_end(ap);
1052 return(array);
1055 void FreeStringTable(XmString *table)
1057 int i;
1059 for(i = 0; table[i] != 0; i++)
1060 XmStringFree(table[i]);
1061 XtFree((char *)table);
1065 ** Simulate a button press. The purpose of this routine is show users what
1066 ** is happening when they take an action with a non-obvious side effect,
1067 ** such as when a user double clicks on a list item. The argument is an
1068 ** XmPushButton widget to "press"
1070 void SimulateButtonPress(Widget widget)
1072 XEvent keyEvent;
1074 memset((char *)&keyEvent, 0, sizeof(XKeyPressedEvent));
1075 keyEvent.type = KeyPress;
1076 keyEvent.xkey.serial = 1;
1077 keyEvent.xkey.send_event = True;
1079 if (XtIsSubclass(widget, xmGadgetClass))
1081 /* On some Motif implementations, asking a gadget for its
1082 window will crash, rather than return the window of its
1083 parent. */
1084 Widget parent = XtParent(widget);
1085 keyEvent.xkey.display = XtDisplay(parent);
1086 keyEvent.xkey.window = XtWindow(parent);
1088 XtCallActionProc(parent, "ManagerGadgetSelect",
1089 &keyEvent, NULL, 0);
1091 else
1093 keyEvent.xkey.display = XtDisplay(widget);
1094 keyEvent.xkey.window = XtWindow(widget);
1096 XtCallActionProc(widget, "ArmAndActivate", &keyEvent, NULL, 0);
1101 ** Add an item to an already established pull-down or pop-up menu, including
1102 ** mnemonics, accelerators and callbacks.
1104 Widget AddMenuItem(Widget parent, char *name, char *label,
1105 char mnemonic, char *acc, char *accText,
1106 XtCallbackProc callback, void *cbArg)
1108 Widget button;
1109 XmString st1, st2;
1111 button = XtVaCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
1112 XmNlabelString, st1=XmStringCreateSimple(label),
1113 XmNmnemonic, mnemonic,
1114 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1115 XmNaccelerator, acc, NULL);
1116 XtAddCallback(button, XmNactivateCallback, callback, cbArg);
1117 XmStringFree(st1);
1118 XmStringFree(st2);
1119 return button;
1123 ** Add a toggle button item to an already established pull-down or pop-up
1124 ** menu, including mnemonics, accelerators and callbacks.
1126 Widget AddMenuToggle(Widget parent, char *name, char *label,
1127 char mnemonic, char *acc, char *accText,
1128 XtCallbackProc callback, void *cbArg, int set)
1130 Widget button;
1131 XmString st1, st2;
1133 button = XtVaCreateManagedWidget(name, xmToggleButtonWidgetClass, parent,
1134 XmNlabelString, st1=XmStringCreateSimple(label),
1135 XmNmnemonic, mnemonic,
1136 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1137 XmNaccelerator, acc,
1138 XmNset, set, NULL);
1139 XtAddCallback(button, XmNvalueChangedCallback, callback, cbArg);
1140 XmStringFree(st1);
1141 XmStringFree(st2);
1142 return button;
1146 ** Add a sub-menu to an established pull-down or pop-up menu, including
1147 ** mnemonics, accelerators and callbacks. Returns the menu pane of the
1148 ** new sub menu.
1150 Widget AddSubMenu(Widget parent, char *name, char *label, char mnemonic)
1152 Widget menu;
1153 XmString st1;
1155 menu = CreatePulldownMenu(parent, name, NULL, 0);
1156 XtVaCreateManagedWidget(name, xmCascadeButtonWidgetClass, parent,
1157 XmNlabelString, st1=XmStringCreateSimple(label),
1158 XmNmnemonic, mnemonic,
1159 XmNsubMenuId, menu, NULL);
1160 XmStringFree(st1);
1161 return menu;
1165 ** SetIntText
1167 ** Set the text of a motif label to show an integer
1169 void SetIntText(Widget text, int value)
1171 char labelString[20];
1173 sprintf(labelString, "%d", value);
1174 XmTextSetString(text, labelString);
1178 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
1180 ** Get the text of a motif text widget as an integer or floating point number.
1181 ** The functions will return TEXT_READ_OK of the value was read correctly.
1182 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER. The
1183 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
1184 ** user that the value could not be read. The argument fieldName is used
1185 ** in the dialog to help the user identify where the problem is. Set
1186 ** warnBlank to true if a blank field is also considered an error.
1188 int GetFloatText(Widget text, double *value)
1190 char *strValue, *endPtr;
1191 int retVal;
1193 strValue = XmTextGetString(text); /* Get Value */
1194 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1195 *value = strtod(strValue, &endPtr); /* Convert string to double */
1196 if (strlen(strValue) == 0) /* String is empty */
1197 retVal = TEXT_IS_BLANK;
1198 else if (*endPtr != '\0') /* Whole string not parsed */
1199 retVal = TEXT_NOT_NUMBER;
1200 else
1201 retVal = TEXT_READ_OK;
1202 XtFree(strValue);
1203 return retVal;
1206 int GetIntText(Widget text, int *value)
1208 char *strValue, *endPtr;
1209 int retVal;
1211 strValue = XmTextGetString(text); /* Get Value */
1212 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1213 *value = strtol(strValue, &endPtr, 10); /* Convert string to long */
1214 if (strlen(strValue) == 0) /* String is empty */
1215 retVal = TEXT_IS_BLANK;
1216 else if (*endPtr != '\0') /* Whole string not parsed */
1217 retVal = TEXT_NOT_NUMBER;
1218 else
1219 retVal = TEXT_READ_OK;
1220 XtFree(strValue);
1221 return retVal;
1224 int GetFloatTextWarn(Widget text, double *value, const char *fieldName,
1225 int warnBlank)
1227 int result;
1228 char *valueStr;
1230 result = GetFloatText(text, value);
1231 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1232 return result;
1233 valueStr = XmTextGetString(text);
1235 if (result == TEXT_IS_BLANK)
1237 DialogF(DF_ERR, text, 1, "Warning", "Please supply %s value", "OK",
1238 fieldName);
1239 } else /* TEXT_NOT_NUMBER */
1241 DialogF (DF_ERR, text, 1, "Warning", "Can't read %s value: \"%s\"",
1242 "OK", fieldName, valueStr);
1245 XtFree(valueStr);
1246 return result;
1249 int GetIntTextWarn(Widget text, int *value, const char *fieldName, int warnBlank)
1251 int result;
1252 char *valueStr;
1254 result = GetIntText(text, value);
1255 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1256 return result;
1257 valueStr = XmTextGetString(text);
1259 if (result == TEXT_IS_BLANK)
1261 DialogF (DF_ERR, text, 1, "Warning", "Please supply a value for %s",
1262 "OK", fieldName);
1263 } else /* TEXT_NOT_NUMBER */
1265 DialogF (DF_ERR, text, 1, "Warning",
1266 "Can't read integer value \"%s\" in %s", "OK", valueStr,
1267 fieldName);
1270 XtFree(valueStr);
1271 return result;
1274 int TextWidgetIsBlank(Widget textW)
1276 char *str;
1277 int retVal;
1279 str = XmTextGetString(textW);
1280 removeWhiteSpace(str);
1281 retVal = *str == '\0';
1282 XtFree(str);
1283 return retVal;
1287 ** Turn a multi-line editing text widget into a fake single line text area
1288 ** by disabling the translation for Return. This is a way to give users
1289 ** extra space, by allowing wrapping, but still prohibiting newlines.
1290 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1291 ** the widget to be one line high).
1293 void MakeSingleLineTextW(Widget textW)
1295 static XtTranslations noReturnTable = NULL;
1296 static char *noReturnTranslations = "<Key>Return: activate()\n";
1298 if (noReturnTable == NULL)
1299 noReturnTable = XtParseTranslationTable(noReturnTranslations);
1300 XtOverrideTranslations(textW, noReturnTable);
1304 ** Add up-arrow/down-arrow recall to a single line text field. When user
1305 ** presses up-arrow, string is cleared and recent entries are presented,
1306 ** moving to older ones as each successive up-arrow is pressed. Down-arrow
1307 ** moves to more recent ones, final down-arrow clears the field. Associated
1308 ** routine, AddToHistoryList, makes maintaining a history list easier.
1310 ** Arguments are the widget, and pointers to the history list and number of
1311 ** items, which are expected to change periodically.
1313 void AddHistoryToTextWidget(Widget textW, char ***historyList, int *nItems)
1315 histInfo *histData;
1317 /* create a data structure for passing history info to the callbacks */
1318 histData = (histInfo *)XtMalloc(sizeof(histInfo));
1319 histData->list = historyList;
1320 histData->nItems = nItems;
1321 histData->index = -1;
1323 /* Add an event handler for handling up/down arrow events */
1324 XtAddEventHandler(textW, KeyPressMask, False,
1325 (XtEventHandler)histArrowKeyEH, histData);
1327 /* Add a destroy callback for freeing history data structure */
1328 XtAddCallback(textW, XmNdestroyCallback, histDestroyCB, histData);
1331 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1333 XtFree((char *)clientData);
1336 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
1337 Boolean *continueDispatch)
1339 histInfo *histData = (histInfo *)callData;
1340 KeySym keysym = XLookupKeysym((XKeyEvent *)event, 0);
1342 /* only process up and down arrow keys */
1343 if (keysym != XK_Up && keysym != XK_Down)
1344 return;
1346 /* increment or decrement the index depending on which arrow was pressed */
1347 histData->index += (keysym == XK_Up) ? 1 : -1;
1349 /* if the index is out of range, beep, fix it up, and return */
1350 if (histData->index < -1) {
1351 histData->index = -1;
1352 XBell(XtDisplay(w), 0);
1353 return;
1355 if (histData->index >= *histData->nItems) {
1356 histData->index = *histData->nItems - 1;
1357 XBell(XtDisplay(w), 0);
1358 return;
1361 /* Change the text field contents */
1362 XmTextSetString(w, histData->index == -1 ? "" :
1363 (*histData->list)[histData->index]);
1367 ** Copies a string on to the end of history list, which may be reallocated
1368 ** to make room. If historyList grows beyond its internally set boundary
1369 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1370 ** (HISTORY_LIST_TRIM_TO). Before adding to the list, checks if the item
1371 ** is a duplicate of the last item. If so, it is not added.
1373 void AddToHistoryList(char *newItem, char ***historyList, int *nItems)
1375 char **newList;
1376 int i;
1378 if (*nItems != 0 && !strcmp(newItem, **historyList))
1379 return;
1380 if (*nItems == HISTORY_LIST_MAX) {
1381 for (i=HISTORY_LIST_TRIM_TO; i<HISTORY_LIST_MAX; i++)
1382 XtFree((*historyList)[i]);
1383 *nItems = HISTORY_LIST_TRIM_TO;
1385 newList = (char **)XtMalloc(sizeof(char *) * (*nItems + 1));
1386 for (i=0; i < *nItems; i++)
1387 newList[i+1] = (*historyList)[i];
1388 if (*nItems != 0 && *historyList != NULL)
1389 XtFree((char *)*historyList);
1390 (*nItems)++;
1391 newList[0] = XtNewString(newItem);
1392 *historyList = newList;
1396 ** BeginWait/EndWait
1398 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1400 void BeginWait(Widget topCursorWidget)
1402 Display *display = XtDisplay(topCursorWidget);
1403 Pixmap pixmap;
1404 Pixmap maskPixmap;
1405 XColor xcolors[2];
1406 static Cursor waitCursor = 0;
1408 /* if the watch cursor hasn't been created yet, create it */
1409 if (!waitCursor) {
1410 pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1411 (char *)watch_bits, watch_width, watch_height);
1413 maskPixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1414 (char *)watch_mask_bits, watch_width, watch_height);
1416 xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(display));
1417 xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(display));
1419 XQueryColors(display, DefaultColormapOfScreen(
1420 DefaultScreenOfDisplay(display)), xcolors, 2);
1421 waitCursor = XCreatePixmapCursor(display, pixmap, maskPixmap,
1422 &xcolors[0], &xcolors[1], watch_x_hot, watch_y_hot);
1423 XFreePixmap(display, pixmap);
1424 XFreePixmap(display, maskPixmap);
1427 /* display the cursor */
1428 XDefineCursor(display, XtWindow(topCursorWidget), waitCursor);
1431 void BusyWait(Widget widget)
1433 #ifdef __unix__
1434 static const int timeout = 100000; /* 1/10 sec = 100 ms = 100,000 us */
1435 static struct timeval last = { 0, 0 };
1436 struct timeval current;
1437 gettimeofday(&current, NULL);
1439 if ((current.tv_sec != last.tv_sec) ||
1440 (current.tv_usec - last.tv_usec > timeout))
1442 XmUpdateDisplay(widget);
1443 last = current;
1445 #else
1446 static time_t last = 0;
1447 time_t current;
1448 time(&current);
1450 if (difftime(current, last) > 0)
1452 XmUpdateDisplay(widget);
1453 last = current;
1455 #endif
1458 void EndWait(Widget topCursorWidget)
1460 XUndefineCursor(XtDisplay(topCursorWidget), XtWindow(topCursorWidget));
1464 ** Create an X window geometry string from width, height, x, and y values.
1465 ** This is a complement to the X routine XParseGeometry, and uses the same
1466 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1467 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1468 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1469 ** result. Note that in a geometry string, it is not possible to supply a y
1470 ** position without an x position. Also note that the X/YNegative flags
1471 ** mean "add a '-' and negate the value" which is kind of odd.
1473 void CreateGeometryString(char *string, int x, int y,
1474 int width, int height, int bitmask)
1476 char *ptr = string;
1477 int nChars;
1479 if (bitmask & WidthValue) {
1480 sprintf(ptr, "%d%n", width, &nChars);
1481 ptr += nChars;
1483 if (bitmask & HeightValue) {
1484 sprintf(ptr, "x%d%n", height, &nChars);
1485 ptr += nChars;
1487 if (bitmask & XValue) {
1488 if (bitmask & XNegative)
1489 sprintf(ptr, "-%d%n", -x, &nChars);
1490 else
1491 sprintf(ptr, "+%d%n", x, &nChars);
1492 ptr += nChars;
1494 if (bitmask & YValue) {
1495 if (bitmask & YNegative)
1496 sprintf(ptr, "-%d%n", -y, &nChars);
1497 else
1498 sprintf(ptr, "+%d%n", y, &nChars);
1499 ptr += nChars;
1501 *ptr = '\0';
1505 ** Remove the white space (blanks and tabs) from a string
1507 static void removeWhiteSpace(char *string)
1509 char *outPtr = string;
1511 while (TRUE) {
1512 if (*string == 0) {
1513 *outPtr = 0;
1514 return;
1515 } else if (*string != ' ' && *string != '\t')
1516 *(outPtr++) = *(string++);
1517 else
1518 string++;
1523 ** Compares two strings and return TRUE if the two strings
1524 ** are the same, ignoring whitespace and case differences.
1526 static int stripCaseCmp(const char *str1, const char *str2)
1528 const char *c1, *c2;
1530 for (c1=str1, c2=str2; *c1!='\0' && *c2!='\0'; c1++, c2++) {
1531 while (*c1 == ' ' || *c1 == '\t')
1532 c1++;
1533 while (*c2 == ' ' || *c2 == '\t')
1534 c2++;
1535 if (toupper((unsigned char)*c1) != toupper((unsigned char)*c2))
1536 return FALSE;
1538 return *c1 == '\0' && *c2 == '\0';
1541 static void warnHandlerCB(String message)
1543 if (strstr(message, "XtRemoveGrab"))
1544 return;
1545 if (strstr(message, "Attempt to remove non-existant passive grab"))
1546 return;
1547 fputs(message, stderr);
1548 fputc('\n', stderr);
1551 static XModifierKeymap *getKeyboardMapping(Display *display) {
1552 static XModifierKeymap *keyboardMap = NULL;
1554 if (keyboardMap == NULL) {
1555 keyboardMap = XGetModifierMapping(display);
1557 return(keyboardMap);
1561 ** get mask for a modifier
1565 static Modifiers findModifierMapping(Display *display, KeyCode keyCode) {
1566 int i, j;
1567 KeyCode *mapentry;
1568 XModifierKeymap *modMap = getKeyboardMapping(display);
1570 if (modMap == NULL || keyCode == 0) {
1571 return(0);
1574 mapentry = modMap->modifiermap;
1575 for (i = 0; i < 8; ++i) {
1576 for (j = 0; j < (modMap->max_keypermod); ++j) {
1577 if (keyCode == *mapentry) {
1578 return(1 << ((mapentry - modMap->modifiermap) / modMap->max_keypermod));
1580 ++mapentry;
1583 return(0);
1586 Modifiers GetNumLockModMask(Display *display) {
1587 static int numLockMask = -1;
1589 if (numLockMask == -1) {
1590 numLockMask = findModifierMapping(display, XKeysymToKeycode(display, XK_Num_Lock));
1592 return(numLockMask);
1596 ** Grab a key regardless of caps-lock and other silly latching keys.
1600 static void reallyGrabAKey(Widget dialog, int keyCode, Modifiers mask) {
1601 Modifiers numLockMask = GetNumLockModMask(XtDisplay(dialog));
1603 if (keyCode == 0) /* No anykey grabs, sorry */
1604 return;
1606 XtGrabKey(dialog, keyCode, mask, True, GrabModeAsync, GrabModeAsync);
1607 XtGrabKey(dialog, keyCode, mask|LockMask, True, GrabModeAsync, GrabModeAsync);
1608 if (numLockMask && numLockMask != LockMask) {
1609 XtGrabKey(dialog, keyCode, mask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1610 XtGrabKey(dialog, keyCode, mask|LockMask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1615 ** Part of dialog mnemonic processing. Search the widget tree under w
1616 ** for widgets with mnemonics. When found, add a passive grab to the
1617 ** dialog widget for the mnemonic character, thus directing mnemonic
1618 ** events to the dialog widget.
1620 static void addMnemonicGrabs(Widget dialog, Widget w, int unmodifiedToo)
1622 char mneString[2];
1623 WidgetList children;
1624 Cardinal numChildren;
1625 int i, isMenu;
1626 KeySym mnemonic = '\0';
1627 unsigned char rowColType;
1628 unsigned int keyCode;
1630 if (XtIsComposite(w)) {
1631 if (XtClass(w) == xmRowColumnWidgetClass) {
1632 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1633 isMenu = rowColType != XmWORK_AREA;
1634 } else
1635 isMenu = False;
1636 if (!isMenu) {
1637 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1638 &numChildren, NULL);
1639 for (i=0; i<(int)numChildren; i++)
1640 addMnemonicGrabs(dialog, children[i], unmodifiedToo);
1642 } else {
1643 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1644 if (mnemonic != XK_VoidSymbol && mnemonic != '\0') {
1645 mneString[0] = mnemonic; mneString[1] = '\0';
1646 keyCode = XKeysymToKeycode(XtDisplay(dialog),
1647 XStringToKeysym(mneString));
1648 reallyGrabAKey(dialog, keyCode, Mod1Mask);
1649 if (unmodifiedToo)
1650 reallyGrabAKey(dialog, keyCode, 0);
1656 ** Callback routine for dialog mnemonic processing.
1658 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event)
1660 findAndActivateMnemonic(w, event->keycode);
1664 ** Look for a widget in the widget tree w, with a mnemonic matching
1665 ** keycode. When one is found, simulate a button press on that widget
1666 ** and give it the keyboard focus. If the mnemonic is on a label,
1667 ** look in the userData field of the label to see if it points to
1668 ** another widget, and give that the focus. This routine is just
1669 ** sufficient for NEdit, no doubt it will need to be extended for
1670 ** mnemonics on widgets other than just buttons and text fields.
1672 static void findAndActivateMnemonic(Widget w, unsigned int keycode)
1674 WidgetList children;
1675 Cardinal numChildren;
1676 int i, isMenu;
1677 KeySym mnemonic = '\0';
1678 char mneString[2];
1679 Widget userData;
1680 unsigned char rowColType;
1682 if (XtIsComposite(w)) {
1683 if (XtClass(w) == xmRowColumnWidgetClass) {
1684 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1685 isMenu = rowColType != XmWORK_AREA;
1686 } else
1687 isMenu = False;
1688 if (!isMenu) {
1689 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1690 &numChildren, NULL);
1691 for (i=0; i<(int)numChildren; i++)
1692 findAndActivateMnemonic(children[i], keycode);
1694 } else {
1695 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1696 if (mnemonic != '\0') {
1697 mneString[0] = mnemonic; mneString[1] = '\0';
1698 if (XKeysymToKeycode(XtDisplay(XtParent(w)),
1699 XStringToKeysym(mneString)) == keycode) {
1700 if (XtClass(w) == xmLabelWidgetClass ||
1701 XtClass(w) == xmLabelGadgetClass) {
1702 XtVaGetValues(w, XmNuserData, &userData, NULL);
1703 if (userData!=NULL && XtIsWidget(userData) &&
1704 XmIsTraversable(userData))
1705 XmProcessTraversal(userData, XmTRAVERSE_CURRENT);
1706 } else if (XmIsTraversable(w)) {
1707 XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1708 SimulateButtonPress(w);
1716 ** Part of workaround for Motif Caps/Num Lock bug. Search the widget tree
1717 ** under w for widgets with accelerators. When found, add three passive
1718 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1719 ** Lock, one for Num Lock, and one for both, thus directing lock +
1720 ** accelerator events to topWidget.
1722 static void addAccelGrabs(Widget topWidget, Widget w)
1724 WidgetList children;
1725 Widget menu;
1726 Cardinal numChildren;
1727 int i;
1729 if (XtIsComposite(w)) {
1730 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1731 &numChildren, NULL);
1732 for (i=0; i<(int)numChildren; i++)
1733 addAccelGrabs(topWidget, children[i]);
1734 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1735 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1736 if (menu != NULL)
1737 addAccelGrabs(topWidget, menu);
1738 } else
1739 addAccelGrab(topWidget, w);
1743 ** Grabs the key + modifier defined in the widget's accelerator resource,
1744 ** in combination with the Caps Lock and Num Lock accelerators.
1746 static void addAccelGrab(Widget topWidget, Widget w)
1748 char *accelString = NULL;
1749 KeySym keysym;
1750 unsigned int modifiers;
1751 KeyCode code;
1752 Modifiers numLockMask = GetNumLockModMask(XtDisplay(topWidget));
1754 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1755 if (accelString == NULL || *accelString == '\0') {
1756 XtFree(accelString);
1757 return;
1760 if (!parseAccelString(XtDisplay(topWidget), accelString, &keysym, &modifiers)) {
1761 XtFree(accelString);
1762 return;
1764 XtFree(accelString);
1766 /* Check to see if this server has this key mapped. Some cruddy PC X
1767 servers (Xoftware) have terrible default keymaps. If not,
1768 XKeysymToKeycode will return 0. However, it's bad news to pass
1769 that to XtGrabKey because 0 is really "AnyKey" which is definitely
1770 not what we want!! */
1772 code = XKeysymToKeycode(XtDisplay(topWidget), keysym);
1773 if (code == 0)
1774 return;
1776 XtGrabKey(topWidget, code,
1777 modifiers | LockMask, True, GrabModeAsync, GrabModeAsync);
1778 if (numLockMask && numLockMask != LockMask) {
1779 XtGrabKey(topWidget, code,
1780 modifiers | numLockMask, True, GrabModeAsync, GrabModeAsync);
1781 XtGrabKey(topWidget, code,
1782 modifiers | LockMask | numLockMask, True, GrabModeAsync, GrabModeAsync);
1787 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1788 ** Returns TRUE if the parse was successful, FALSE, if not.
1790 static int parseAccelString(Display *display, const char *string, KeySym *keySym,
1791 unsigned int *modifiers)
1793 #define N_MODIFIERS 12
1794 /*... Is NumLock always Mod3? */
1795 static char *modifierNames[N_MODIFIERS] = {"Ctrl", "Shift", "Alt", "Mod2",
1796 "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1797 "Button5"};
1798 static unsigned int modifierMasks[N_MODIFIERS] = {ControlMask, ShiftMask,
1799 Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, Button1Mask, Button2Mask,
1800 Button3Mask, Button4Mask, Button5Mask};
1801 Modifiers numLockMask = GetNumLockModMask(display);
1802 char modStr[MAX_ACCEL_LEN];
1803 char evtStr[MAX_ACCEL_LEN];
1804 char keyStr[MAX_ACCEL_LEN];
1805 const char *c, *evtStart, *keyStart;
1806 int i;
1808 if (strlen(string) >= MAX_ACCEL_LEN)
1809 return FALSE;
1811 /* Get the modifier part */
1812 for (c = string; *c != '<'; c++)
1813 if (*c == '\0')
1814 return FALSE;
1815 strncpy(modStr, string, c - string);
1816 modStr[c - string] = '\0';
1818 /* Verify the <key> or <keypress> part */
1819 evtStart = c;
1820 for ( ; *c != '>'; c++)
1821 if (*c == '\0')
1822 return FALSE;
1823 c++;
1824 strncpy(evtStr, evtStart, c - evtStart);
1825 evtStr[c - evtStart] = '\0';
1826 if (!stripCaseCmp(evtStr, "<key>") && !stripCaseCmp(evtStr, "<keypress>"))
1827 return FALSE;
1829 /* Get the keysym part */
1830 keyStart = c;
1831 for ( ; *c != '\0' && !(c != keyStart && *c == ':'); c++);
1832 strncpy(keyStr, keyStart, c - keyStart);
1833 keyStr[c - keyStart] = '\0';
1834 *keySym = XStringToKeysym(keyStr);
1836 /* Parse the modifier part */
1837 *modifiers = 0;
1838 c = modStr;
1839 while (*c != '\0') {
1840 while (*c == ' ' || *c == '\t')
1841 c++;
1842 if (*c == '\0')
1843 break;
1844 for (i = 0; i < N_MODIFIERS; i++) {
1845 if (!strncmp(c, modifierNames[i], strlen(modifierNames[i]))) {
1846 c += strlen(modifierNames[i]);
1847 if (modifierMasks[i] != numLockMask) {
1848 *modifiers |= modifierMasks[i];
1850 break;
1853 if (i == N_MODIFIERS)
1854 return FALSE;
1857 return TRUE;
1861 ** Event handler for patching around Motif's lock + accelerator problem.
1862 ** Looks for a menu item in the patched menu hierarchy and invokes its
1863 ** ArmAndActivate action.
1865 static void lockCB(Widget w, XtPointer callData, XEvent *event,
1866 Boolean *continueDispatch)
1868 Modifiers numLockMask = GetNumLockModMask(XtDisplay(w));
1869 Widget topMenuWidget = (Widget)callData;
1870 *continueDispatch = TRUE;
1872 if (!(((XKeyEvent *)event)->state & (LockMask | numLockMask)))
1873 return;
1875 if (findAndActivateAccel(topMenuWidget, ((XKeyEvent*) event)->keycode,
1876 ((XKeyEvent*) event)->state & ~(LockMask | numLockMask), event)) {
1877 *continueDispatch = FALSE;
1882 ** Search through menu hierarchy under w and look for a button with
1883 ** accelerator matching keyCode + modifiers, and do its action
1885 static int findAndActivateAccel(Widget w, unsigned int keyCode,
1886 unsigned int modifiers, XEvent *event)
1888 WidgetList children;
1889 Widget menu;
1890 Cardinal numChildren;
1891 int i;
1892 char *accelString = NULL;
1893 KeySym keysym;
1894 unsigned int mods;
1896 if (XtIsComposite(w)) {
1897 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1898 &numChildren, NULL);
1899 for (i=0; i<(int)numChildren; i++)
1900 if (findAndActivateAccel(children[i], keyCode, modifiers, event))
1901 return TRUE;
1902 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1903 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1904 if (menu != NULL)
1905 if (findAndActivateAccel(menu, keyCode, modifiers, event))
1906 return TRUE;
1907 } else {
1908 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1909 if (accelString != NULL && *accelString != '\0') {
1910 if (!parseAccelString(XtDisplay(w), accelString, &keysym, &mods))
1911 return FALSE;
1912 if (keyCode == XKeysymToKeycode(XtDisplay(w), keysym) &&
1913 modifiers == mods) {
1914 if (XtIsSensitive(w)) {
1915 XtCallActionProc(w, "ArmAndActivate", event, NULL, 0);
1916 return TRUE;
1921 return FALSE;
1925 ** Global installation of mouse wheel actions for scrolled windows.
1927 void InstallMouseWheelActions(XtAppContext context)
1929 static XtActionsRec Actions[] = {
1930 {"scrolled-window-scroll-up", scrollUpAP},
1931 {"scrolled-window-page-up", pageUpAP},
1932 {"scrolled-window-scroll-down", scrollDownAP},
1933 {"scrolled-window-page-down", pageDownAP}
1936 XtAppAddActions(context, Actions, XtNumber(Actions));
1940 ** Add mouse wheel support to a specific widget, which must be the scrollable
1941 ** widget of a ScrolledWindow.
1943 void AddMouseWheelSupport(Widget w)
1945 if (XmIsScrolledWindow(XtParent(w)))
1947 static const char scrollTranslations[] =
1948 "Shift<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(1)\n"
1949 "Shift<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(1)\n"
1950 "Ctrl<Btn4Down>,<Btn4Up>: scrolled-window-page-up()\n"
1951 "Ctrl<Btn5Down>,<Btn5Up>: scrolled-window-page-down()\n"
1952 "<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(3)\n"
1953 "<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(3)\n";
1954 static XtTranslations trans_table = NULL;
1956 if (trans_table == NULL)
1958 trans_table = XtParseTranslationTable(scrollTranslations);
1960 XtOverrideTranslations(w, trans_table);
1964 static void pageUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
1966 Widget scrolledWindow, scrollBar;
1967 String al[1];
1969 al[0] = "Up";
1970 scrolledWindow = XtParent(w);
1971 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
1972 if (scrollBar)
1973 XtCallActionProc(scrollBar, "PageUpOrLeft", event, al, 1) ;
1974 return;
1977 static void pageDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
1979 Widget scrolledWindow, scrollBar;
1980 String al[1];
1982 al[0] = "Down";
1983 scrolledWindow = XtParent(w);
1984 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
1985 if (scrollBar)
1986 XtCallActionProc(scrollBar, "PageDownOrRight", event, al, 1) ;
1987 return;
1990 static void scrollUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
1992 Widget scrolledWindow, scrollBar;
1993 String al[1];
1994 int i, nLines;
1996 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
1997 return;
1998 al[0] = "Up";
1999 scrolledWindow = XtParent(w);
2000 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2001 if (scrollBar)
2002 for (i=0; i<nLines; i++)
2003 XtCallActionProc(scrollBar, "IncrementUpOrLeft", event, al, 1) ;
2004 return;
2007 static void scrollDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2009 Widget scrolledWindow, scrollBar;
2010 String al[1];
2011 int i, nLines;
2013 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2014 return;
2015 al[0] = "Down";
2016 scrolledWindow = XtParent(w);
2017 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2018 if (scrollBar)
2019 for (i=0; i<nLines; i++)
2020 XtCallActionProc(scrollBar, "IncrementDownOrRight", event, al, 1) ;
2021 return;
2026 ** This is a disguisting hack to work around a bug in OpenMotif.
2027 ** OpenMotif's toggle button Select() action routine remembers the last radio
2028 ** button that was toggled (stored as global state) and refuses to take any
2029 ** action when that button is clicked again. It fails to detect that we may
2030 ** have changed the button state and that clicking that button could make
2031 ** sense. The result is that radio buttons may apparently get stuck, ie.
2032 ** it is not possible to directly select with the mouse the previously
2033 ** selected button without selection another radio button first.
2034 ** The workaround consist of faking a mouse click on the button that we
2035 ** toggled by calling the Arm, Select, and Disarm action procedures.
2037 ** A minor remaining issue is the fact that, if the workaround is used,
2038 ** it is not possible to change the state without notifying potential
2039 ** XmNvalueChangedCallbacks. In practice, this doesn't seem to be a problem.
2042 void RadioButtonChangeState(Widget widget, Boolean state, Boolean notify)
2045 The bug only exists in OpenMotif 2.1.x/2.2.[0-2]. Since it's quite hard
2046 to detect OpenMotif reliably, we make a rough cut by excluding Lesstif
2047 and all Motif versions >= 2.1.x and < 2.2.3.
2049 #ifndef LESSTIF_VERSION
2050 #if XmVersion == 2001 || (XmVersion == 2002 && XmUPDATE_LEVEL < 3)
2051 /* save the widget with current focus in case it moves */
2052 Widget focusW, shellW = widget;
2053 while (shellW && !XtIsShell(shellW)) {
2054 shellW = XtParent(shellW);
2056 focusW = XtGetKeyboardFocusWidget(shellW);
2058 if (state && XtIsRealized(widget))
2061 Simulate a mouse button click.
2062 The event attributes that matter are the event type and the
2063 coordinates. When the button is managed, the coordinates have to
2064 be inside the button. When the button is not managed, they have to
2065 be (0, 0) to make sure that the Select routine accepts the event.
2067 XEvent ev;
2068 if (XtIsManaged(widget))
2070 Position x, y;
2071 /* Calculate the coordinates in the same way as OM. */
2072 XtTranslateCoords(XtParent(widget), widget->core.x, widget->core.y,
2073 &x, &y);
2074 ev.xbutton.x_root = x + widget->core.border_width;
2075 ev.xbutton.y_root = y + widget->core.border_width;
2077 else
2079 ev.xbutton.x_root = 0;
2080 ev.xbutton.y_root = 0;
2082 /* Default button bindings:
2083 ~c<Btn1Down>: Arm()
2084 ~c<Btn1Up>: Select() Disarm() */
2085 ev.xany.type = ButtonPress;
2086 XtCallActionProc(widget, "Arm", &ev, NULL, 0);
2087 ev.xany.type = ButtonRelease;
2088 XtCallActionProc(widget, "Select", &ev, NULL, 0);
2089 XtCallActionProc(widget, "Disarm", &ev, NULL, 0);
2091 /* restore focus to the originator */
2092 if (focusW) {
2093 XtSetKeyboardFocus(shellW, focusW);
2095 #endif /* XmVersion == 2001 || ... */
2096 #endif /* LESSTIF_VERSION */
2098 /* This is sufficient on non-OM platforms */
2099 XmToggleButtonSetState(widget, state, notify);
2102 /* Workaround for bug in OpenMotif 2.1 and 2.2. If you have an active tear-off
2103 ** menu from a TopLevelShell that is a child of an ApplicationShell, and then
2104 ** close the parent window, Motif crashes. The problem doesn't
2105 ** happen if you close the tear-offs first, so, we programatically close them
2106 ** before destroying the shell widget.
2108 void CloseAllPopupsFor(Widget shell)
2110 #ifndef LESSTIF_VERSION
2111 /* Doesn't happen in LessTif. The tear-off menus are popup-children of
2112 * of the TopLevelShell there, which just works. Motif wants to make
2113 * them popup-children of the ApplicationShell, where it seems to get
2114 * into trouble. */
2116 Widget app = XtParent(shell);
2117 int i;
2119 for (i = 0; i < app->core.num_popups; i++) {
2120 Widget pop = app->core.popup_list[i];
2121 Widget shellFor;
2123 XtVaGetValues(pop, XtNtransientFor, &shellFor, NULL);
2124 if (shell == shellFor)
2125 _XmDismissTearOff(pop, NULL, NULL);
2127 #endif
2130 static long queryDesktop(Display *display, Window window, Atom deskTopAtom)
2132 long deskTopNumber = 0;
2133 Atom actualType;
2134 int actualFormat;
2135 unsigned long nItems, bytesAfter;
2136 unsigned char *prop;
2138 if (XGetWindowProperty(display, window, deskTopAtom, 0, 1,
2139 False, AnyPropertyType, &actualType, &actualFormat, &nItems,
2140 &bytesAfter, &prop) != Success) {
2141 return -1; /* Property not found */
2144 if (actualType == None) {
2145 return -1; /* Property does not exist */
2148 if (actualFormat != 32 || nItems != 1) {
2149 XFree((char*)prop);
2150 return -1; /* Wrong format */
2153 deskTopNumber = *(long*)prop;
2154 XFree((char*)prop);
2155 return deskTopNumber;
2159 ** Returns the current desktop number, or -1 if no desktop information
2160 ** is available.
2162 long QueryCurrentDesktop(Display *display, Window rootWindow)
2164 static Atom currentDesktopAtom = (Atom)-1;
2166 if (currentDesktopAtom == (Atom)-1)
2167 currentDesktopAtom = XInternAtom(display, "_NET_CURRENT_DESKTOP", True);
2169 if (currentDesktopAtom != None)
2170 return queryDesktop(display, rootWindow, currentDesktopAtom);
2172 return -1; /* No desktop information */
2176 ** Returns the number of the desktop the given shell window is currently on,
2177 ** or -1 if no desktop information is available. Note that windows shown
2178 ** on all desktops (sometimes called sticky windows) should return 0xFFFFFFFF.
2180 long QueryDesktop(Display *display, Widget shell)
2182 static Atom wmDesktopAtom = (Atom)-1;
2184 if (wmDesktopAtom == (Atom)-1)
2185 wmDesktopAtom = XInternAtom(display, "_NET_WM_DESKTOP", True);
2187 if (wmDesktopAtom != None)
2188 return queryDesktop(display, XtWindow(shell), wmDesktopAtom);
2190 return -1; /* No desktop information */
2195 ** Clipboard wrapper functions that call the Motif clipboard functions
2196 ** a number of times before giving up. The interfaces are similar to the
2197 ** native Motif functions.
2200 #define SPINCOUNT 10 /* Try at most 10 times */
2201 #define USLEEPTIME 1000 /* 1 ms between retries */
2204 ** Warning reporting
2206 static void warning(const char* mesg)
2208 fprintf(stderr, "NEdit warning:\n%s\n", mesg);
2212 ** Sleep routine
2214 static void microsleep(long usecs)
2216 static struct timeval timeoutVal;
2217 timeoutVal.tv_sec = usecs/1000000;
2218 timeoutVal.tv_usec = usecs - timeoutVal.tv_sec*1000000;
2219 select(0, NULL, NULL, NULL, &timeoutVal);
2223 ** XmClipboardStartCopy spinlock wrapper.
2225 int SpinClipboardStartCopy(Display *display, Window window,
2226 XmString clip_label, Time timestamp, Widget widget,
2227 XmCutPasteProc callback, long *item_id)
2229 int i, res;
2230 for (i=0; i<SPINCOUNT; ++i) {
2231 res = XmClipboardStartCopy(display, window, clip_label, timestamp,
2232 widget, callback, item_id);
2233 if (res == XmClipboardSuccess) {
2234 return res;
2236 microsleep(USLEEPTIME);
2238 warning("XmClipboardStartCopy() failed: clipboard locked.");
2239 return res;
2243 ** XmClipboardCopy spinlock wrapper.
2245 int SpinClipboardCopy(Display *display, Window window, long item_id,
2246 char *format_name, XtPointer buffer, unsigned long length,
2247 long private_id, long *data_id)
2249 int i, res;
2250 for (i=0; i<SPINCOUNT; ++i) {
2251 res = XmClipboardCopy(display, window, item_id, format_name,
2252 buffer, length, private_id, data_id);
2253 if (res == XmClipboardSuccess) {
2254 return res;
2256 if (res == XmClipboardFail) {
2257 warning("XmClipboardCopy() failed: XmClipboardStartCopy not "
2258 "called or too many formats.");
2259 return res;
2261 microsleep(USLEEPTIME);
2263 warning("XmClipboardCopy() failed: clipboard locked.");
2264 return res;
2268 ** XmClipboardEndCopy spinlock wrapper.
2270 int SpinClipboardEndCopy(Display *display, Window window, long item_id)
2272 int i, res;
2273 for (i=0; i<SPINCOUNT; ++i) {
2274 res = XmClipboardEndCopy(display, window, item_id);
2275 if (res == XmClipboardSuccess) {
2276 return res;
2278 if (res == XmClipboardFail) {
2279 warning("XmClipboardEndCopy() failed: XmClipboardStartCopy not "
2280 "called.");
2281 return res;
2283 microsleep(USLEEPTIME);
2285 warning("XmClipboardEndCopy() failed: clipboard locked.");
2286 return res;
2290 ** XmClipboardInquireLength spinlock wrapper.
2292 int SpinClipboardInquireLength(Display *display, Window window,
2293 char *format_name, unsigned long *length)
2295 int i, res;
2296 for (i=0; i<SPINCOUNT; ++i) {
2297 res = XmClipboardInquireLength(display, window, format_name, length);
2298 if (res == XmClipboardSuccess) {
2299 return res;
2301 if (res == XmClipboardNoData) {
2302 return res;
2304 microsleep(USLEEPTIME);
2306 warning("XmClipboardInquireLength() failed: clipboard locked.");
2307 return res;
2311 ** XmClipboardRetrieve spinlock wrapper.
2313 int SpinClipboardRetrieve(Display *display, Window window, char *format_name,
2314 XtPointer buffer, unsigned long length, unsigned long *num_bytes,
2315 long *private_id)
2317 int i, res;
2318 for (i=0; i<SPINCOUNT; ++i) {
2319 res = XmClipboardRetrieve(display, window, format_name, buffer,
2320 length, num_bytes, private_id);
2321 if (res == XmClipboardSuccess) {
2322 return res;
2324 if (res == XmClipboardTruncate) {
2325 warning("XmClipboardRetrieve() failed: buffer too small.");
2326 return res;
2328 if (res == XmClipboardNoData) {
2329 return res;
2331 microsleep(USLEEPTIME);
2333 warning("XmClipboardRetrieve() failed: clipboard locked.");
2334 return res;
2338 ** XmClipboardLock spinlock wrapper.
2340 int SpinClipboardLock(Display *display, Window window)
2342 int i, res;
2343 for (i=0; i<SPINCOUNT; ++i) {
2344 res = XmClipboardLock(display, window);
2345 if (res == XmClipboardSuccess) {
2346 return res;
2348 microsleep(USLEEPTIME);
2350 warning("XmClipboardLock() failed: clipboard locked.");
2351 return res;
2355 ** XmClipboardUnlock spinlock wrapper.
2357 int SpinClipboardUnlock(Display *display, Window window)
2359 int i, res;
2360 /* Spinning doesn't make much sense in this case, I think. */
2361 for (i=0; i<SPINCOUNT; ++i) {
2362 /* Remove ALL locks (we don't use nested locking in NEdit) */
2363 res = XmClipboardUnlock(display, window, True);
2364 if (res == XmClipboardSuccess) {
2365 return res;
2367 microsleep(USLEEPTIME);
2370 * This warning doesn't make much sense in practice. It's usually
2371 * triggered when we try to unlock the clipboard after a failed clipboard
2372 * operation, in an attempt to work around possible *tif clipboard locking
2373 * bugs. In these cases, failure _is_ the expected outcome and the warning
2374 * is bogus. Therefore, the warning is disabled.
2375 warning("XmClipboardUnlock() failed: clipboard not locked or locked "
2376 "by another application.");
2378 return res;
2382 ** Send a client message to a EWMH/NetWM compatible X Window Manager.
2383 ** Code taken from wmctrl-1.07 (GPL licensed)
2385 void WmClientMsg(Display *disp, Window win, const char *msg,
2386 unsigned long data0, unsigned long data1,
2387 unsigned long data2, unsigned long data3,
2388 unsigned long data4)
2390 XEvent event;
2391 long mask = SubstructureRedirectMask | SubstructureNotifyMask;
2393 event.xclient.type = ClientMessage;
2394 event.xclient.serial = 0;
2395 event.xclient.send_event = True;
2396 event.xclient.message_type = XInternAtom(disp, msg, False);
2397 event.xclient.window = win;
2398 event.xclient.format = 32;
2399 event.xclient.data.l[0] = data0;
2400 event.xclient.data.l[1] = data1;
2401 event.xclient.data.l[2] = data2;
2402 event.xclient.data.l[3] = data3;
2403 event.xclient.data.l[4] = data4;
2405 if (!XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event)) {
2406 fprintf(stderr, "nedit: cannot send %s EWMH event.\n", msg);