1 static const char CVSID
[] = "$Id: misc.c,v 1.88 2008/01/04 22:11:05 yooden Exp $";
2 /*******************************************************************************
4 * misc.c -- Miscelaneous Motif convenience functions *
6 * Copyright (C) 1999 Mark Edel *
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. *
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 *
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 *
23 * Nirvana Text Editor *
26 * Written by Mark Edel *
28 *******************************************************************************/
31 #include "../config.h"
46 #include <sys/select.h>
51 #include <sys/select.h>
61 #include <X11/Intrinsic.h>
62 #include <X11/Xatom.h>
63 #include <X11/keysym.h>
64 #include <X11/keysymdef.h>
67 #include <Xm/LabelG.h>
68 #include <Xm/ToggleB.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>
76 #include <Xm/MessageB.h>
77 #include <Xm/DialogS.h>
78 #include <Xm/SelectioB.h>
80 #include <Xm/FileSB.h>
81 #include <Xm/ScrolledW.h>
82 #include <Xm/PrimitiveP.h>
88 #ifndef LESSTIF_VERSION
89 extern void _XmDismissTearOff(Widget w
, XtPointer call
, XtPointer x
);
92 /* structure for passing history-recall data to callbacks */
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
,
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
,
153 static void scrollUpAP(Widget w
, XEvent
*event
, String
*args
,
155 static void pageDownAP(Widget w
, XEvent
*event
, String
*args
,
157 static void pageUpAP(Widget w
, XEvent
*event
, String
*args
,
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 */
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.
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
);
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
) {
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
249 static void setWindowGroup(Widget shell
) {
250 static int firstTime
= True
;
251 static Window groupLeader
;
252 Display
*display
= XtDisplay(shell
);
256 /* Create a dummy window to be the group leader for our windows */
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
266 XtGetApplicationNameAndClass(display
, &name
, &class);
267 classHint
= XAllocClassHint();
268 classHint
->res_name
= name
;
269 classHint
->res_class
= class;
270 XSetClassHint(display
, groupLeader
, classHint
);
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
);
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
304 void RemovePPositionHint(Widget shell
)
306 XSizeHints
*hints
= XAllocSizeHints();
309 /* Get rid of the incorrect WMNormal hint */
310 if (XGetWMNormalHints(XtDisplay(shell
), XtWindow(shell
), hints
,
313 hints
->flags
&= ~PPosition
;
314 XSetWMNormalHints(XtDisplay(shell
), XtWindow(shell
), 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
);
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
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
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
;
383 int screen
= DefaultScreen(display
);
385 long reqID
= -1; /* should hold a 'VisualID' and a '-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
,
411 valueString
= value
.addr
;
412 reqID
= (int)strtol(valueString
, &endPtr
, 0);
413 if (endPtr
== valueString
) {
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
,
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). */
447 visTemplate
.visualid
= reqID
;
448 visList
= XGetVisualInfo(display
, VisualScreenMask
|VisualIDMask
,
449 &visTemplate
, &nVis
);
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
);
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
);
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
);
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
);
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) */
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
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) {
510 if (visList
[i
].depth
> maxDepth
) {
511 maxDepth
= visList
[i
].depth
;
515 if (visList
[i
].depth
== maxDepth
) {
516 if (visList
[i
].visual
== DefaultVisual(display
, screen
))
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
) {
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
);
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
) {
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
,
565 Widget
CreatePopupMenu(Widget parent
, char *name
, ArgList arglist
,
568 return addParentVisArgsAndCall(XmCreatePopupMenu
, parent
, name
,
573 Widget
CreatePulldownMenu(Widget parent
, char *name
,
574 ArgList arglist
, Cardinal argcount
)
576 return addParentVisArgsAndCall(XmCreatePulldownMenu
, parent
, name
, arglist
,
581 Widget
CreatePromptDialog(Widget parent
, char *name
,
582 ArgList arglist
, Cardinal argcount
)
584 return addParentVisArgsAndCall(XmCreatePromptDialog
, parent
, name
, arglist
,
589 Widget
CreateSelectionDialog(Widget parent
, char *name
,
590 ArgList arglist
, Cardinal argcount
)
592 Widget dialog
= addParentVisArgsAndCall(XmCreateSelectionDialog
, parent
, name
,
594 AddMouseWheelSupport(XmSelectionBoxGetChild(dialog
, XmDIALOG_LIST
));
599 Widget
CreateFormDialog(Widget parent
, char *name
,
600 ArgList arglist
, Cardinal argcount
)
602 return addParentVisArgsAndCall(XmCreateFormDialog
, parent
, name
, arglist
,
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
));
619 Widget
CreateQuestionDialog(Widget parent
, char *name
,
620 ArgList arglist
, Cardinal argcount
)
622 return addParentVisArgsAndCall(XmCreateQuestionDialog
, parent
, name
,
627 Widget
CreateMessageDialog(Widget parent
, char *name
,
628 ArgList arglist
, Cardinal argcount
)
630 return addParentVisArgsAndCall(XmCreateMessageDialog
, parent
, name
,
635 Widget
CreateErrorDialog(Widget parent
, char *name
,
636 ArgList arglist
, Cardinal argcount
)
638 return addParentVisArgsAndCall(XmCreateErrorDialog
, parent
, name
, arglist
,
642 Widget
CreateWidget(Widget parent
, const char *name
, WidgetClass
class,
643 ArgList arglist
, Cardinal argcount
)
646 ArgList al
= addParentVisArgs(parent
, arglist
, &argcount
);
647 result
= XtCreateWidget(name
, class, parent
, al
, argcount
);
652 Widget
CreateShellWithBestVis(String appName
, String appClass
,
653 WidgetClass
class, Display
*display
, ArgList args
, Cardinal nArgs
)
662 FindBestVisual(display
, appName
, appClass
, &visual
, &depth
, &colormap
);
663 al
= (ArgList
)XtMalloc(sizeof(Arg
) * (nArgs
+ 3));
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
);
675 Widget
CreatePopupShellWithBestVis(String shellName
, WidgetClass
class,
676 Widget parent
, ArgList arglist
, Cardinal argcount
)
679 ArgList al
= addParentVisArgs(parent
, arglist
, &argcount
);
680 result
= XtCreatePopupShell(shellName
, class, parent
, al
, argcount
);
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
,
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 */
702 if (XtIsShell(parentShell
))
704 if (parentShell
== NULL
) {
705 fprintf(stderr
, "failed to find shell\n");
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
)++;
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
)
740 ArgList al
= addParentVisArgs(parent
, arglist
, &argcount
);
741 result
= (*createRoutine
)(parent
, name
, al
, argcount
);
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
);
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
;
781 XmNmaxWidth
, maxWidth
,
782 XmNmaxHeight
, maxHeight
,
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
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
;
816 /* Ensure that the dialog remains on screen */
817 maxX
= maxWidth
- width
;
818 maxY
= maxHeight
- height
;
819 if (x
> maxX
) x
= maxX
;
821 if (y
> maxY
) y
= maxY
;
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
829 XtVaSetValues(shell
, XmNuseAsyncGeometry
, True
, NULL
);
831 /* Set desired window position in the DialogShell */
832 XtVaSetValues(shell
, XmNx
, x
, XmNy
, y
, NULL
);
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
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
)
883 XWindowAttributes winAttr
;
885 XGetWindowAttributes(display
, w
, &winAttr
);
886 if (winAttr
.map_state
== IsViewable
)
887 XSetInputFocus(display
, w
, RevertToParent
, CurrentTime
);
890 XMapRaised(display
, w
);
894 ** Add a handler for mnemonics in a dialog (Motif currently only handles
895 ** mnemonics in menus) following the example of M.S. Windows. To add
896 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
897 ** a menu, on push buttons or toggle buttons, and call this function
898 ** when the dialog is fully constructed. Mnemonics added or changed
899 ** after this call will not be noticed. To add a mnemonic to a text field
900 ** or list, set the XmNmnemonic resource on the appropriate label and set
901 ** the XmNuserData resource of the label to the widget to get the focus
902 ** when the mnemonic is typed.
904 void AddDialogMnemonicHandler(Widget dialog
, int unmodifiedToo
)
906 XtAddEventHandler(dialog
, KeyPressMask
, False
,
907 (XtEventHandler
)mnemonicCB
, (XtPointer
)0);
908 addMnemonicGrabs(dialog
, dialog
, unmodifiedToo
);
912 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
914 void RemoveDialogMnemonicHandler(Widget dialog
)
916 XtUngrabKey(dialog
, AnyKey
, Mod1Mask
);
917 XtRemoveEventHandler(dialog
, KeyPressMask
, False
,
918 (XtEventHandler
)mnemonicCB
, (XtPointer
)0);
922 ** Patch around Motif's poor handling of menu accelerator keys. Motif
923 ** does not process menu accelerators when the caps lock or num lock
924 ** keys are engaged. To enable accelerators in these cases, call this
925 ** routine with the completed menu bar widget as "topMenuContainer", and
926 ** the top level shell widget as "topWidget". It will add key grabs for
927 ** all of the accelerators it finds in the topMenuContainer menu tree, and
928 ** an event handler which can process dropped accelerator events by (again)
929 ** traversing the menu tree looking for matching accelerators, and invoking
930 ** the appropriate button actions. Any dynamic additions to the menus
931 ** require a call to UpdateAccelLockPatch to add the additional grabs.
932 ** Unfortunately, these grabs can not be removed.
934 void AccelLockBugPatch(Widget topWidget
, Widget topMenuContainer
)
936 XtAddEventHandler(topWidget
, KeyPressMask
, False
, lockCB
, topMenuContainer
);
937 addAccelGrabs(topWidget
, topMenuContainer
);
941 ** Add additional key grabs for new menu items added to the menus, for
942 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
943 ** the same widget passed in the original call to AccelLockBugPatch.
945 void UpdateAccelLockPatch(Widget topWidget
, Widget newButton
)
947 addAccelGrab(topWidget
, newButton
);
953 ** Under some circumstances, popping down a dialog and its parent in
954 ** rapid succession causes a crash. This routine delays and
955 ** processs events until receiving a ReparentNotify event.
956 ** (I have no idea why a ReparentNotify event occurs at all, but it does
957 ** mark the point where it is safe to destroy or pop down the parent, and
958 ** it might have something to do with the bug.) There is a failsafe in
959 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
960 ** Use this sparingly, only when real crashes are observed, and periodically
961 ** check to make sure that it is still necessary.
963 void PopDownBugPatch(Widget w
)
967 stopTime
= time(NULL
) + 1;
968 while (time(NULL
) <= stopTime
) {
970 XtAppContext context
= XtWidgetToApplicationContext(w
);
971 XtAppPeekEvent(context
, &event
);
972 if (event
.xany
.type
== ReparentNotify
)
974 XtAppProcessEvent(context
, XtIMAll
);
979 ** Convert a compound string to a C style null terminated string.
980 ** Returned string must be freed by the caller.
982 char *GetXmStringText(XmString fromString
)
984 XmStringContext context
;
985 char *text
, *toPtr
, *toString
, *fromPtr
;
986 XmStringCharSet charset
;
987 XmStringDirection direction
;
990 /* Malloc a buffer large enough to hold the string. XmStringLength
991 should always be slightly longer than necessary, but won't be
992 shorter than the equivalent null-terminated string */
993 toString
= XtMalloc(XmStringLength(fromString
));
995 /* loop over all of the segments in the string, copying each segment
996 into the output string and converting separators into newlines */
997 XmStringInitContext(&context
, fromString
);
999 while (XmStringGetNextSegment(context
, &text
,
1000 &charset
, &direction
, &separator
)) {
1001 for (fromPtr
=text
; *fromPtr
!='\0'; fromPtr
++)
1002 *toPtr
++ = *fromPtr
;
1009 /* terminate the string, free the context, and return the string */
1011 XmStringFreeContext(context
);
1016 ** Get the XFontStruct that corresponds to the default (first) font in
1017 ** a Motif font list. Since Motif stores this, it saves us from storing
1018 ** it or querying it from the X server.
1020 XFontStruct
*GetDefaultFontStruct(XmFontList font
)
1023 XmFontContext context
;
1024 XmStringCharSet charset
;
1026 XmFontListInitFontContext(&context
, font
);
1027 XmFontListGetNextFont(context
, &charset
, &fs
);
1028 XmFontListFreeFontContext(context
);
1034 ** Create a string table suitable for passing to XmList widgets
1036 XmString
* StringTable(int count
, ... )
1043 va_start(ap
, count
);
1044 array
= (XmString
*)XtMalloc((count
+1) * sizeof(XmString
));
1045 for(i
= 0; i
< count
; i
++ ) {
1046 str
= va_arg(ap
, char *);
1047 array
[i
] = XmStringCreateSimple(str
);
1049 array
[i
] = (XmString
)0;
1054 void FreeStringTable(XmString
*table
)
1058 for(i
= 0; table
[i
] != 0; i
++)
1059 XmStringFree(table
[i
]);
1060 XtFree((char *)table
);
1064 ** Simulate a button press. The purpose of this routine is show users what
1065 ** is happening when they take an action with a non-obvious side effect,
1066 ** such as when a user double clicks on a list item. The argument is an
1067 ** XmPushButton widget to "press"
1069 void SimulateButtonPress(Widget widget
)
1073 memset((char *)&keyEvent
, 0, sizeof(XKeyPressedEvent
));
1074 keyEvent
.type
= KeyPress
;
1075 keyEvent
.xkey
.serial
= 1;
1076 keyEvent
.xkey
.send_event
= True
;
1078 if (XtIsSubclass(widget
, xmGadgetClass
))
1080 /* On some Motif implementations, asking a gadget for its
1081 window will crash, rather than return the window of its
1083 Widget parent
= XtParent(widget
);
1084 keyEvent
.xkey
.display
= XtDisplay(parent
);
1085 keyEvent
.xkey
.window
= XtWindow(parent
);
1087 XtCallActionProc(parent
, "ManagerGadgetSelect",
1088 &keyEvent
, NULL
, 0);
1092 keyEvent
.xkey
.display
= XtDisplay(widget
);
1093 keyEvent
.xkey
.window
= XtWindow(widget
);
1095 XtCallActionProc(widget
, "ArmAndActivate", &keyEvent
, NULL
, 0);
1100 ** Add an item to an already established pull-down or pop-up menu, including
1101 ** mnemonics, accelerators and callbacks.
1103 Widget
AddMenuItem(Widget parent
, char *name
, char *label
,
1104 char mnemonic
, char *acc
, char *accText
,
1105 XtCallbackProc callback
, void *cbArg
)
1110 button
= XtVaCreateManagedWidget(name
, xmPushButtonWidgetClass
, parent
,
1111 XmNlabelString
, st1
=XmStringCreateSimple(label
),
1112 XmNmnemonic
, mnemonic
,
1113 XmNacceleratorText
, st2
=XmStringCreateSimple(accText
),
1114 XmNaccelerator
, acc
, NULL
);
1115 XtAddCallback(button
, XmNactivateCallback
, callback
, cbArg
);
1122 ** Add a toggle button item to an already established pull-down or pop-up
1123 ** menu, including mnemonics, accelerators and callbacks.
1125 Widget
AddMenuToggle(Widget parent
, char *name
, char *label
,
1126 char mnemonic
, char *acc
, char *accText
,
1127 XtCallbackProc callback
, void *cbArg
, int set
)
1132 button
= XtVaCreateManagedWidget(name
, xmToggleButtonWidgetClass
, parent
,
1133 XmNlabelString
, st1
=XmStringCreateSimple(label
),
1134 XmNmnemonic
, mnemonic
,
1135 XmNacceleratorText
, st2
=XmStringCreateSimple(accText
),
1136 XmNaccelerator
, acc
,
1138 XtAddCallback(button
, XmNvalueChangedCallback
, callback
, cbArg
);
1145 ** Add a sub-menu to an established pull-down or pop-up menu, including
1146 ** mnemonics, accelerators and callbacks. Returns the menu pane of the
1149 Widget
AddSubMenu(Widget parent
, char *name
, char *label
, char mnemonic
)
1154 menu
= CreatePulldownMenu(parent
, name
, NULL
, 0);
1155 XtVaCreateManagedWidget(name
, xmCascadeButtonWidgetClass
, parent
,
1156 XmNlabelString
, st1
=XmStringCreateSimple(label
),
1157 XmNmnemonic
, mnemonic
,
1158 XmNsubMenuId
, menu
, NULL
);
1166 ** Set the text of a motif label to show an integer
1168 void SetIntText(Widget text
, int value
)
1170 char labelString
[20];
1172 sprintf(labelString
, "%d", value
);
1173 XmTextSetString(text
, labelString
);
1177 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
1179 ** Get the text of a motif text widget as an integer or floating point number.
1180 ** The functions will return TEXT_READ_OK of the value was read correctly.
1181 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER. The
1182 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
1183 ** user that the value could not be read. The argument fieldName is used
1184 ** in the dialog to help the user identify where the problem is. Set
1185 ** warnBlank to true if a blank field is also considered an error.
1187 int GetFloatText(Widget text
, double *value
)
1189 char *strValue
, *endPtr
;
1192 strValue
= XmTextGetString(text
); /* Get Value */
1193 removeWhiteSpace(strValue
); /* Remove blanks and tabs */
1194 *value
= strtod(strValue
, &endPtr
); /* Convert string to double */
1195 if (strlen(strValue
) == 0) /* String is empty */
1196 retVal
= TEXT_IS_BLANK
;
1197 else if (*endPtr
!= '\0') /* Whole string not parsed */
1198 retVal
= TEXT_NOT_NUMBER
;
1200 retVal
= TEXT_READ_OK
;
1205 int GetIntText(Widget text
, int *value
)
1207 char *strValue
, *endPtr
;
1210 strValue
= XmTextGetString(text
); /* Get Value */
1211 removeWhiteSpace(strValue
); /* Remove blanks and tabs */
1212 *value
= strtol(strValue
, &endPtr
, 10); /* Convert string to long */
1213 if (strlen(strValue
) == 0) /* String is empty */
1214 retVal
= TEXT_IS_BLANK
;
1215 else if (*endPtr
!= '\0') /* Whole string not parsed */
1216 retVal
= TEXT_NOT_NUMBER
;
1218 retVal
= TEXT_READ_OK
;
1223 int GetFloatTextWarn(Widget text
, double *value
, const char *fieldName
,
1229 result
= GetFloatText(text
, value
);
1230 if (result
== TEXT_READ_OK
|| (result
== TEXT_IS_BLANK
&& !warnBlank
))
1232 valueStr
= XmTextGetString(text
);
1234 if (result
== TEXT_IS_BLANK
)
1236 DialogF(DF_ERR
, text
, 1, "Warning", "Please supply %s value", "OK",
1238 } else /* TEXT_NOT_NUMBER */
1240 DialogF (DF_ERR
, text
, 1, "Warning", "Can't read %s value: \"%s\"",
1241 "OK", fieldName
, valueStr
);
1248 int GetIntTextWarn(Widget text
, int *value
, const char *fieldName
, int warnBlank
)
1253 result
= GetIntText(text
, value
);
1254 if (result
== TEXT_READ_OK
|| (result
== TEXT_IS_BLANK
&& !warnBlank
))
1256 valueStr
= XmTextGetString(text
);
1258 if (result
== TEXT_IS_BLANK
)
1260 DialogF (DF_ERR
, text
, 1, "Warning", "Please supply a value for %s",
1262 } else /* TEXT_NOT_NUMBER */
1264 DialogF (DF_ERR
, text
, 1, "Warning",
1265 "Can't read integer value \"%s\" in %s", "OK", valueStr
,
1273 int TextWidgetIsBlank(Widget textW
)
1278 str
= XmTextGetString(textW
);
1279 removeWhiteSpace(str
);
1280 retVal
= *str
== '\0';
1286 ** Turn a multi-line editing text widget into a fake single line text area
1287 ** by disabling the translation for Return. This is a way to give users
1288 ** extra space, by allowing wrapping, but still prohibiting newlines.
1289 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1290 ** the widget to be one line high).
1292 void MakeSingleLineTextW(Widget textW
)
1294 static XtTranslations noReturnTable
= NULL
;
1295 static char *noReturnTranslations
= "<Key>Return: activate()\n";
1297 if (noReturnTable
== NULL
)
1298 noReturnTable
= XtParseTranslationTable(noReturnTranslations
);
1299 XtOverrideTranslations(textW
, noReturnTable
);
1303 ** Add up-arrow/down-arrow recall to a single line text field. When user
1304 ** presses up-arrow, string is cleared and recent entries are presented,
1305 ** moving to older ones as each successive up-arrow is pressed. Down-arrow
1306 ** moves to more recent ones, final down-arrow clears the field. Associated
1307 ** routine, AddToHistoryList, makes maintaining a history list easier.
1309 ** Arguments are the widget, and pointers to the history list and number of
1310 ** items, which are expected to change periodically.
1312 void AddHistoryToTextWidget(Widget textW
, char ***historyList
, int *nItems
)
1316 /* create a data structure for passing history info to the callbacks */
1317 histData
= (histInfo
*)XtMalloc(sizeof(histInfo
));
1318 histData
->list
= historyList
;
1319 histData
->nItems
= nItems
;
1320 histData
->index
= -1;
1322 /* Add an event handler for handling up/down arrow events */
1323 XtAddEventHandler(textW
, KeyPressMask
, False
,
1324 (XtEventHandler
)histArrowKeyEH
, histData
);
1326 /* Add a destroy callback for freeing history data structure */
1327 XtAddCallback(textW
, XmNdestroyCallback
, histDestroyCB
, histData
);
1330 static void histDestroyCB(Widget w
, XtPointer clientData
, XtPointer callData
)
1332 XtFree((char *)clientData
);
1335 static void histArrowKeyEH(Widget w
, XtPointer callData
, XEvent
*event
,
1336 Boolean
*continueDispatch
)
1338 histInfo
*histData
= (histInfo
*)callData
;
1339 KeySym keysym
= XLookupKeysym((XKeyEvent
*)event
, 0);
1341 /* only process up and down arrow keys */
1342 if (keysym
!= XK_Up
&& keysym
!= XK_Down
)
1345 /* increment or decrement the index depending on which arrow was pressed */
1346 histData
->index
+= (keysym
== XK_Up
) ? 1 : -1;
1348 /* if the index is out of range, beep, fix it up, and return */
1349 if (histData
->index
< -1) {
1350 histData
->index
= -1;
1351 XBell(XtDisplay(w
), 0);
1354 if (histData
->index
>= *histData
->nItems
) {
1355 histData
->index
= *histData
->nItems
- 1;
1356 XBell(XtDisplay(w
), 0);
1360 /* Change the text field contents */
1361 XmTextSetString(w
, histData
->index
== -1 ? "" :
1362 (*histData
->list
)[histData
->index
]);
1366 ** Copies a string on to the end of history list, which may be reallocated
1367 ** to make room. If historyList grows beyond its internally set boundary
1368 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1369 ** (HISTORY_LIST_TRIM_TO). Before adding to the list, checks if the item
1370 ** is a duplicate of the last item. If so, it is not added.
1372 void AddToHistoryList(char *newItem
, char ***historyList
, int *nItems
)
1377 if (*nItems
!= 0 && !strcmp(newItem
, **historyList
))
1379 if (*nItems
== HISTORY_LIST_MAX
) {
1380 for (i
=HISTORY_LIST_TRIM_TO
; i
<HISTORY_LIST_MAX
; i
++)
1381 XtFree((*historyList
)[i
]);
1382 *nItems
= HISTORY_LIST_TRIM_TO
;
1384 newList
= (char **)XtMalloc(sizeof(char *) * (*nItems
+ 1));
1385 for (i
=0; i
< *nItems
; i
++)
1386 newList
[i
+1] = (*historyList
)[i
];
1387 if (*nItems
!= 0 && *historyList
!= NULL
)
1388 XtFree((char *)*historyList
);
1390 newList
[0] = XtNewString(newItem
);
1391 *historyList
= newList
;
1395 ** BeginWait/EndWait
1397 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1399 void BeginWait(Widget topCursorWidget
)
1401 Display
*display
= XtDisplay(topCursorWidget
);
1405 static Cursor waitCursor
= 0;
1407 /* if the watch cursor hasn't been created yet, create it */
1409 pixmap
= XCreateBitmapFromData(display
, DefaultRootWindow(display
),
1410 (char *)watch_bits
, watch_width
, watch_height
);
1412 maskPixmap
= XCreateBitmapFromData(display
, DefaultRootWindow(display
),
1413 (char *)watch_mask_bits
, watch_width
, watch_height
);
1415 xcolors
[0].pixel
= BlackPixelOfScreen(DefaultScreenOfDisplay(display
));
1416 xcolors
[1].pixel
= WhitePixelOfScreen(DefaultScreenOfDisplay(display
));
1418 XQueryColors(display
, DefaultColormapOfScreen(
1419 DefaultScreenOfDisplay(display
)), xcolors
, 2);
1420 waitCursor
= XCreatePixmapCursor(display
, pixmap
, maskPixmap
,
1421 &xcolors
[0], &xcolors
[1], watch_x_hot
, watch_y_hot
);
1422 XFreePixmap(display
, pixmap
);
1423 XFreePixmap(display
, maskPixmap
);
1426 /* display the cursor */
1427 XDefineCursor(display
, XtWindow(topCursorWidget
), waitCursor
);
1430 void BusyWait(Widget widget
)
1433 static const int timeout
= 100000; /* 1/10 sec = 100 ms = 100,000 us */
1434 static struct timeval last
= { 0, 0 };
1435 struct timeval current
;
1436 gettimeofday(¤t
, NULL
);
1438 if ((current
.tv_sec
!= last
.tv_sec
) ||
1439 (current
.tv_usec
- last
.tv_usec
> timeout
))
1441 XmUpdateDisplay(widget
);
1445 static time_t last
= 0;
1449 if (difftime(current
, last
) > 0)
1451 XmUpdateDisplay(widget
);
1457 void EndWait(Widget topCursorWidget
)
1459 XUndefineCursor(XtDisplay(topCursorWidget
), XtWindow(topCursorWidget
));
1463 ** Create an X window geometry string from width, height, x, and y values.
1464 ** This is a complement to the X routine XParseGeometry, and uses the same
1465 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1466 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1467 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1468 ** result. Note that in a geometry string, it is not possible to supply a y
1469 ** position without an x position. Also note that the X/YNegative flags
1470 ** mean "add a '-' and negate the value" which is kind of odd.
1472 void CreateGeometryString(char *string
, int x
, int y
,
1473 int width
, int height
, int bitmask
)
1478 if (bitmask
& WidthValue
) {
1479 sprintf(ptr
, "%d%n", width
, &nChars
);
1482 if (bitmask
& HeightValue
) {
1483 sprintf(ptr
, "x%d%n", height
, &nChars
);
1486 if (bitmask
& XValue
) {
1487 if (bitmask
& XNegative
)
1488 sprintf(ptr
, "-%d%n", -x
, &nChars
);
1490 sprintf(ptr
, "+%d%n", x
, &nChars
);
1493 if (bitmask
& YValue
) {
1494 if (bitmask
& YNegative
)
1495 sprintf(ptr
, "-%d%n", -y
, &nChars
);
1497 sprintf(ptr
, "+%d%n", y
, &nChars
);
1504 ** Remove the white space (blanks and tabs) from a string
1506 static void removeWhiteSpace(char *string
)
1508 char *outPtr
= string
;
1514 } else if (*string
!= ' ' && *string
!= '\t')
1515 *(outPtr
++) = *(string
++);
1522 ** Compares two strings and return TRUE if the two strings
1523 ** are the same, ignoring whitespace and case differences.
1525 static int stripCaseCmp(const char *str1
, const char *str2
)
1527 const char *c1
, *c2
;
1529 for (c1
=str1
, c2
=str2
; *c1
!='\0' && *c2
!='\0'; c1
++, c2
++) {
1530 while (*c1
== ' ' || *c1
== '\t')
1532 while (*c2
== ' ' || *c2
== '\t')
1534 if (toupper((unsigned char)*c1
) != toupper((unsigned char)*c2
))
1537 return *c1
== '\0' && *c2
== '\0';
1540 static void warnHandlerCB(String message
)
1542 if (strstr(message
, "XtRemoveGrab"))
1544 if (strstr(message
, "Attempt to remove non-existant passive grab"))
1546 fputs(message
, stderr
);
1547 fputc('\n', stderr
);
1550 static XModifierKeymap
*getKeyboardMapping(Display
*display
) {
1551 static XModifierKeymap
*keyboardMap
= NULL
;
1553 if (keyboardMap
== NULL
) {
1554 keyboardMap
= XGetModifierMapping(display
);
1556 return(keyboardMap
);
1560 ** get mask for a modifier
1564 static Modifiers
findModifierMapping(Display
*display
, KeyCode keyCode
) {
1567 XModifierKeymap
*modMap
= getKeyboardMapping(display
);
1569 if (modMap
== NULL
|| keyCode
== 0) {
1573 mapentry
= modMap
->modifiermap
;
1574 for (i
= 0; i
< 8; ++i
) {
1575 for (j
= 0; j
< (modMap
->max_keypermod
); ++j
) {
1576 if (keyCode
== *mapentry
) {
1577 return(1 << ((mapentry
- modMap
->modifiermap
) / modMap
->max_keypermod
));
1585 Modifiers
GetNumLockModMask(Display
*display
) {
1586 static int numLockMask
= -1;
1588 if (numLockMask
== -1) {
1589 numLockMask
= findModifierMapping(display
, XKeysymToKeycode(display
, XK_Num_Lock
));
1591 return(numLockMask
);
1595 ** Grab a key regardless of caps-lock and other silly latching keys.
1599 static void reallyGrabAKey(Widget dialog
, int keyCode
, Modifiers mask
) {
1600 Modifiers numLockMask
= GetNumLockModMask(XtDisplay(dialog
));
1602 if (keyCode
== 0) /* No anykey grabs, sorry */
1605 XtGrabKey(dialog
, keyCode
, mask
, True
, GrabModeAsync
, GrabModeAsync
);
1606 XtGrabKey(dialog
, keyCode
, mask
|LockMask
, True
, GrabModeAsync
, GrabModeAsync
);
1607 if (numLockMask
&& numLockMask
!= LockMask
) {
1608 XtGrabKey(dialog
, keyCode
, mask
|numLockMask
, True
, GrabModeAsync
, GrabModeAsync
);
1609 XtGrabKey(dialog
, keyCode
, mask
|LockMask
|numLockMask
, True
, GrabModeAsync
, GrabModeAsync
);
1614 ** Part of dialog mnemonic processing. Search the widget tree under w
1615 ** for widgets with mnemonics. When found, add a passive grab to the
1616 ** dialog widget for the mnemonic character, thus directing mnemonic
1617 ** events to the dialog widget.
1619 static void addMnemonicGrabs(Widget dialog
, Widget w
, int unmodifiedToo
)
1622 WidgetList children
;
1623 Cardinal numChildren
;
1625 KeySym mnemonic
= '\0';
1626 unsigned char rowColType
;
1627 unsigned int keyCode
;
1629 if (XtIsComposite(w
)) {
1630 if (XtClass(w
) == xmRowColumnWidgetClass
) {
1631 XtVaGetValues(w
, XmNrowColumnType
, &rowColType
, NULL
);
1632 isMenu
= rowColType
!= XmWORK_AREA
;
1636 XtVaGetValues(w
, XmNchildren
, &children
, XmNnumChildren
,
1637 &numChildren
, NULL
);
1638 for (i
=0; i
<(int)numChildren
; i
++)
1639 addMnemonicGrabs(dialog
, children
[i
], unmodifiedToo
);
1642 XtVaGetValues(w
, XmNmnemonic
, &mnemonic
, NULL
);
1643 if (mnemonic
!= XK_VoidSymbol
&& mnemonic
!= '\0') {
1644 mneString
[0] = mnemonic
; mneString
[1] = '\0';
1645 keyCode
= XKeysymToKeycode(XtDisplay(dialog
),
1646 XStringToKeysym(mneString
));
1647 reallyGrabAKey(dialog
, keyCode
, Mod1Mask
);
1649 reallyGrabAKey(dialog
, keyCode
, 0);
1655 ** Callback routine for dialog mnemonic processing.
1657 static void mnemonicCB(Widget w
, XtPointer callData
, XKeyEvent
*event
)
1659 findAndActivateMnemonic(w
, event
->keycode
);
1663 ** Look for a widget in the widget tree w, with a mnemonic matching
1664 ** keycode. When one is found, simulate a button press on that widget
1665 ** and give it the keyboard focus. If the mnemonic is on a label,
1666 ** look in the userData field of the label to see if it points to
1667 ** another widget, and give that the focus. This routine is just
1668 ** sufficient for NEdit, no doubt it will need to be extended for
1669 ** mnemonics on widgets other than just buttons and text fields.
1671 static void findAndActivateMnemonic(Widget w
, unsigned int keycode
)
1673 WidgetList children
;
1674 Cardinal numChildren
;
1676 KeySym mnemonic
= '\0';
1679 unsigned char rowColType
;
1681 if (XtIsComposite(w
)) {
1682 if (XtClass(w
) == xmRowColumnWidgetClass
) {
1683 XtVaGetValues(w
, XmNrowColumnType
, &rowColType
, NULL
);
1684 isMenu
= rowColType
!= XmWORK_AREA
;
1688 XtVaGetValues(w
, XmNchildren
, &children
, XmNnumChildren
,
1689 &numChildren
, NULL
);
1690 for (i
=0; i
<(int)numChildren
; i
++)
1691 findAndActivateMnemonic(children
[i
], keycode
);
1694 XtVaGetValues(w
, XmNmnemonic
, &mnemonic
, NULL
);
1695 if (mnemonic
!= '\0') {
1696 mneString
[0] = mnemonic
; mneString
[1] = '\0';
1697 if (XKeysymToKeycode(XtDisplay(XtParent(w
)),
1698 XStringToKeysym(mneString
)) == keycode
) {
1699 if (XtClass(w
) == xmLabelWidgetClass
||
1700 XtClass(w
) == xmLabelGadgetClass
) {
1701 XtVaGetValues(w
, XmNuserData
, &userData
, NULL
);
1702 if (userData
!=NULL
&& XtIsWidget(userData
) &&
1703 XmIsTraversable(userData
))
1704 XmProcessTraversal(userData
, XmTRAVERSE_CURRENT
);
1705 } else if (XmIsTraversable(w
)) {
1706 XmProcessTraversal(w
, XmTRAVERSE_CURRENT
);
1707 SimulateButtonPress(w
);
1715 ** Part of workaround for Motif Caps/Num Lock bug. Search the widget tree
1716 ** under w for widgets with accelerators. When found, add three passive
1717 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1718 ** Lock, one for Num Lock, and one for both, thus directing lock +
1719 ** accelerator events to topWidget.
1721 static void addAccelGrabs(Widget topWidget
, Widget w
)
1723 WidgetList children
;
1725 Cardinal numChildren
;
1728 if (XtIsComposite(w
)) {
1729 XtVaGetValues(w
, XmNchildren
, &children
, XmNnumChildren
,
1730 &numChildren
, NULL
);
1731 for (i
=0; i
<(int)numChildren
; i
++)
1732 addAccelGrabs(topWidget
, children
[i
]);
1733 } else if (XtClass(w
) == xmCascadeButtonWidgetClass
) {
1734 XtVaGetValues(w
, XmNsubMenuId
, &menu
, NULL
);
1736 addAccelGrabs(topWidget
, menu
);
1738 addAccelGrab(topWidget
, w
);
1742 ** Grabs the key + modifier defined in the widget's accelerator resource,
1743 ** in combination with the Caps Lock and Num Lock accelerators.
1745 static void addAccelGrab(Widget topWidget
, Widget w
)
1747 char *accelString
= NULL
;
1749 unsigned int modifiers
;
1751 Modifiers numLockMask
= GetNumLockModMask(XtDisplay(topWidget
));
1753 XtVaGetValues(w
, XmNaccelerator
, &accelString
, NULL
);
1754 if (accelString
== NULL
|| *accelString
== '\0') {
1755 XtFree(accelString
);
1759 if (!parseAccelString(XtDisplay(topWidget
), accelString
, &keysym
, &modifiers
)) {
1760 XtFree(accelString
);
1763 XtFree(accelString
);
1765 /* Check to see if this server has this key mapped. Some cruddy PC X
1766 servers (Xoftware) have terrible default keymaps. If not,
1767 XKeysymToKeycode will return 0. However, it's bad news to pass
1768 that to XtGrabKey because 0 is really "AnyKey" which is definitely
1769 not what we want!! */
1771 code
= XKeysymToKeycode(XtDisplay(topWidget
), keysym
);
1775 XtGrabKey(topWidget
, code
,
1776 modifiers
| LockMask
, True
, GrabModeAsync
, GrabModeAsync
);
1777 if (numLockMask
&& numLockMask
!= LockMask
) {
1778 XtGrabKey(topWidget
, code
,
1779 modifiers
| numLockMask
, True
, GrabModeAsync
, GrabModeAsync
);
1780 XtGrabKey(topWidget
, code
,
1781 modifiers
| LockMask
| numLockMask
, True
, GrabModeAsync
, GrabModeAsync
);
1786 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1787 ** Returns TRUE if the parse was successful, FALSE, if not.
1789 static int parseAccelString(Display
*display
, const char *string
, KeySym
*keySym
,
1790 unsigned int *modifiers
)
1792 #define N_MODIFIERS 12
1793 /*... Is NumLock always Mod3? */
1794 static char *modifierNames
[N_MODIFIERS
] = {"Ctrl", "Shift", "Alt", "Mod2",
1795 "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1797 static unsigned int modifierMasks
[N_MODIFIERS
] = {ControlMask
, ShiftMask
,
1798 Mod1Mask
, Mod2Mask
, Mod3Mask
, Mod4Mask
, Mod5Mask
, Button1Mask
, Button2Mask
,
1799 Button3Mask
, Button4Mask
, Button5Mask
};
1800 Modifiers numLockMask
= GetNumLockModMask(display
);
1801 char modStr
[MAX_ACCEL_LEN
];
1802 char evtStr
[MAX_ACCEL_LEN
];
1803 char keyStr
[MAX_ACCEL_LEN
];
1804 const char *c
, *evtStart
, *keyStart
;
1807 if (strlen(string
) >= MAX_ACCEL_LEN
)
1810 /* Get the modifier part */
1811 for (c
= string
; *c
!= '<'; c
++)
1814 strncpy(modStr
, string
, c
- string
);
1815 modStr
[c
- string
] = '\0';
1817 /* Verify the <key> or <keypress> part */
1819 for ( ; *c
!= '>'; c
++)
1823 strncpy(evtStr
, evtStart
, c
- evtStart
);
1824 evtStr
[c
- evtStart
] = '\0';
1825 if (!stripCaseCmp(evtStr
, "<key>") && !stripCaseCmp(evtStr
, "<keypress>"))
1828 /* Get the keysym part */
1830 for ( ; *c
!= '\0' && !(c
!= keyStart
&& *c
== ':'); c
++);
1831 strncpy(keyStr
, keyStart
, c
- keyStart
);
1832 keyStr
[c
- keyStart
] = '\0';
1833 *keySym
= XStringToKeysym(keyStr
);
1835 /* Parse the modifier part */
1838 while (*c
!= '\0') {
1839 while (*c
== ' ' || *c
== '\t')
1843 for (i
= 0; i
< N_MODIFIERS
; i
++) {
1844 if (!strncmp(c
, modifierNames
[i
], strlen(modifierNames
[i
]))) {
1845 c
+= strlen(modifierNames
[i
]);
1846 if (modifierMasks
[i
] != numLockMask
) {
1847 *modifiers
|= modifierMasks
[i
];
1852 if (i
== N_MODIFIERS
)
1860 ** Event handler for patching around Motif's lock + accelerator problem.
1861 ** Looks for a menu item in the patched menu hierarchy and invokes its
1862 ** ArmAndActivate action.
1864 static void lockCB(Widget w
, XtPointer callData
, XEvent
*event
,
1865 Boolean
*continueDispatch
)
1867 Modifiers numLockMask
= GetNumLockModMask(XtDisplay(w
));
1868 Widget topMenuWidget
= (Widget
)callData
;
1869 *continueDispatch
= TRUE
;
1871 if (!(((XKeyEvent
*)event
)->state
& (LockMask
| numLockMask
)))
1874 if (findAndActivateAccel(topMenuWidget
, ((XKeyEvent
*) event
)->keycode
,
1875 ((XKeyEvent
*) event
)->state
& ~(LockMask
| numLockMask
), event
)) {
1876 *continueDispatch
= FALSE
;
1881 ** Search through menu hierarchy under w and look for a button with
1882 ** accelerator matching keyCode + modifiers, and do its action
1884 static int findAndActivateAccel(Widget w
, unsigned int keyCode
,
1885 unsigned int modifiers
, XEvent
*event
)
1887 WidgetList children
;
1889 Cardinal numChildren
;
1891 char *accelString
= NULL
;
1895 if (XtIsComposite(w
)) {
1896 XtVaGetValues(w
, XmNchildren
, &children
, XmNnumChildren
,
1897 &numChildren
, NULL
);
1898 for (i
=0; i
<(int)numChildren
; i
++)
1899 if (findAndActivateAccel(children
[i
], keyCode
, modifiers
, event
))
1901 } else if (XtClass(w
) == xmCascadeButtonWidgetClass
) {
1902 XtVaGetValues(w
, XmNsubMenuId
, &menu
, NULL
);
1904 if (findAndActivateAccel(menu
, keyCode
, modifiers
, event
))
1907 XtVaGetValues(w
, XmNaccelerator
, &accelString
, NULL
);
1908 if (accelString
!= NULL
&& *accelString
!= '\0') {
1909 if (!parseAccelString(XtDisplay(w
), accelString
, &keysym
, &mods
))
1911 if (keyCode
== XKeysymToKeycode(XtDisplay(w
), keysym
) &&
1912 modifiers
== mods
) {
1913 if (XtIsSensitive(w
)) {
1914 XtCallActionProc(w
, "ArmAndActivate", event
, NULL
, 0);
1924 ** Global installation of mouse wheel actions for scrolled windows.
1926 void InstallMouseWheelActions(XtAppContext context
)
1928 static XtActionsRec Actions
[] = {
1929 {"scrolled-window-scroll-up", scrollUpAP
},
1930 {"scrolled-window-page-up", pageUpAP
},
1931 {"scrolled-window-scroll-down", scrollDownAP
},
1932 {"scrolled-window-page-down", pageDownAP
}
1935 XtAppAddActions(context
, Actions
, XtNumber(Actions
));
1939 ** Add mouse wheel support to a specific widget, which must be the scrollable
1940 ** widget of a ScrolledWindow.
1942 void AddMouseWheelSupport(Widget w
)
1944 if (XmIsScrolledWindow(XtParent(w
)))
1946 static const char scrollTranslations
[] =
1947 "Shift<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(1)\n"
1948 "Shift<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(1)\n"
1949 "Ctrl<Btn4Down>,<Btn4Up>: scrolled-window-page-up()\n"
1950 "Ctrl<Btn5Down>,<Btn5Up>: scrolled-window-page-down()\n"
1951 "<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(3)\n"
1952 "<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(3)\n";
1953 static XtTranslations trans_table
= NULL
;
1955 if (trans_table
== NULL
)
1957 trans_table
= XtParseTranslationTable(scrollTranslations
);
1959 XtOverrideTranslations(w
, trans_table
);
1963 static void pageUpAP(Widget w
, XEvent
*event
, String
*args
, Cardinal
*nArgs
)
1965 Widget scrolledWindow
, scrollBar
;
1969 scrolledWindow
= XtParent(w
);
1970 scrollBar
= XtNameToWidget (scrolledWindow
, "VertScrollBar");
1972 XtCallActionProc(scrollBar
, "PageUpOrLeft", event
, al
, 1) ;
1976 static void pageDownAP(Widget w
, XEvent
*event
, String
*args
, Cardinal
*nArgs
)
1978 Widget scrolledWindow
, scrollBar
;
1982 scrolledWindow
= XtParent(w
);
1983 scrollBar
= XtNameToWidget (scrolledWindow
, "VertScrollBar");
1985 XtCallActionProc(scrollBar
, "PageDownOrRight", event
, al
, 1) ;
1989 static void scrollUpAP(Widget w
, XEvent
*event
, String
*args
, Cardinal
*nArgs
)
1991 Widget scrolledWindow
, scrollBar
;
1995 if (*nArgs
== 0 || sscanf(args
[0], "%d", &nLines
) != 1)
1998 scrolledWindow
= XtParent(w
);
1999 scrollBar
= XtNameToWidget (scrolledWindow
, "VertScrollBar");
2001 for (i
=0; i
<nLines
; i
++)
2002 XtCallActionProc(scrollBar
, "IncrementUpOrLeft", event
, al
, 1) ;
2006 static void scrollDownAP(Widget w
, XEvent
*event
, String
*args
, Cardinal
*nArgs
)
2008 Widget scrolledWindow
, scrollBar
;
2012 if (*nArgs
== 0 || sscanf(args
[0], "%d", &nLines
) != 1)
2015 scrolledWindow
= XtParent(w
);
2016 scrollBar
= XtNameToWidget (scrolledWindow
, "VertScrollBar");
2018 for (i
=0; i
<nLines
; i
++)
2019 XtCallActionProc(scrollBar
, "IncrementDownOrRight", event
, al
, 1) ;
2025 ** This is a disguisting hack to work around a bug in OpenMotif.
2026 ** OpenMotif's toggle button Select() action routine remembers the last radio
2027 ** button that was toggled (stored as global state) and refuses to take any
2028 ** action when that button is clicked again. It fails to detect that we may
2029 ** have changed the button state and that clicking that button could make
2030 ** sense. The result is that radio buttons may apparently get stuck, ie.
2031 ** it is not possible to directly select with the mouse the previously
2032 ** selected button without selection another radio button first.
2033 ** The workaround consist of faking a mouse click on the button that we
2034 ** toggled by calling the Arm, Select, and Disarm action procedures.
2036 ** A minor remaining issue is the fact that, if the workaround is used,
2037 ** it is not possible to change the state without notifying potential
2038 ** XmNvalueChangedCallbacks. In practice, this doesn't seem to be a problem.
2041 void RadioButtonChangeState(Widget widget
, Boolean state
, Boolean notify
)
2044 The bug only exists in OpenMotif 2.1.x/2.2.[0-2]. Since it's quite hard
2045 to detect OpenMotif reliably, we make a rough cut by excluding Lesstif
2046 and all Motif versions >= 2.1.x and < 2.2.3.
2048 #ifndef LESSTIF_VERSION
2049 #if XmVersion == 2001 || (XmVersion == 2002 && XmUPDATE_LEVEL < 3)
2050 /* save the widget with current focus in case it moves */
2051 Widget focusW
, shellW
= widget
;
2052 while (shellW
&& !XtIsShell(shellW
)) {
2053 shellW
= XtParent(shellW
);
2055 focusW
= XtGetKeyboardFocusWidget(shellW
);
2057 if (state
&& XtIsRealized(widget
))
2060 Simulate a mouse button click.
2061 The event attributes that matter are the event type and the
2062 coordinates. When the button is managed, the coordinates have to
2063 be inside the button. When the button is not managed, they have to
2064 be (0, 0) to make sure that the Select routine accepts the event.
2067 if (XtIsManaged(widget
))
2070 /* Calculate the coordinates in the same way as OM. */
2071 XtTranslateCoords(XtParent(widget
), widget
->core
.x
, widget
->core
.y
,
2073 ev
.xbutton
.x_root
= x
+ widget
->core
.border_width
;
2074 ev
.xbutton
.y_root
= y
+ widget
->core
.border_width
;
2078 ev
.xbutton
.x_root
= 0;
2079 ev
.xbutton
.y_root
= 0;
2081 /* Default button bindings:
2083 ~c<Btn1Up>: Select() Disarm() */
2084 ev
.xany
.type
= ButtonPress
;
2085 XtCallActionProc(widget
, "Arm", &ev
, NULL
, 0);
2086 ev
.xany
.type
= ButtonRelease
;
2087 XtCallActionProc(widget
, "Select", &ev
, NULL
, 0);
2088 XtCallActionProc(widget
, "Disarm", &ev
, NULL
, 0);
2090 /* restore focus to the originator */
2092 XtSetKeyboardFocus(shellW
, focusW
);
2094 #endif /* XmVersion == 2001 || ... */
2095 #endif /* LESSTIF_VERSION */
2097 /* This is sufficient on non-OM platforms */
2098 XmToggleButtonSetState(widget
, state
, notify
);
2101 /* Workaround for bug in OpenMotif 2.1 and 2.2. If you have an active tear-off
2102 ** menu from a TopLevelShell that is a child of an ApplicationShell, and then
2103 ** close the parent window, Motif crashes. The problem doesn't
2104 ** happen if you close the tear-offs first, so, we programatically close them
2105 ** before destroying the shell widget.
2107 void CloseAllPopupsFor(Widget shell
)
2109 #ifndef LESSTIF_VERSION
2110 /* Doesn't happen in LessTif. The tear-off menus are popup-children of
2111 * of the TopLevelShell there, which just works. Motif wants to make
2112 * them popup-children of the ApplicationShell, where it seems to get
2115 Widget app
= XtParent(shell
);
2118 for (i
= 0; i
< app
->core
.num_popups
; i
++) {
2119 Widget pop
= app
->core
.popup_list
[i
];
2122 XtVaGetValues(pop
, XtNtransientFor
, &shellFor
, NULL
);
2123 if (shell
== shellFor
)
2124 _XmDismissTearOff(pop
, NULL
, NULL
);
2129 static long queryDesktop(Display
*display
, Window window
, Atom deskTopAtom
)
2131 long deskTopNumber
= 0;
2134 unsigned long nItems
, bytesAfter
;
2135 unsigned char *prop
;
2137 if (XGetWindowProperty(display
, window
, deskTopAtom
, 0, 1,
2138 False
, AnyPropertyType
, &actualType
, &actualFormat
, &nItems
,
2139 &bytesAfter
, &prop
) != Success
) {
2140 return -1; /* Property not found */
2143 if (actualType
== None
) {
2144 return -1; /* Property does not exist */
2147 if (actualFormat
!= 32 || nItems
!= 1) {
2149 return -1; /* Wrong format */
2152 deskTopNumber
= *(long*)prop
;
2154 return deskTopNumber
;
2158 ** Returns the current desktop number, or -1 if no desktop information
2161 long QueryCurrentDesktop(Display
*display
, Window rootWindow
)
2163 static Atom currentDesktopAtom
= (Atom
)-1;
2165 if (currentDesktopAtom
== (Atom
)-1)
2166 currentDesktopAtom
= XInternAtom(display
, "_NET_CURRENT_DESKTOP", True
);
2168 if (currentDesktopAtom
!= None
)
2169 return queryDesktop(display
, rootWindow
, currentDesktopAtom
);
2171 return -1; /* No desktop information */
2175 ** Returns the number of the desktop the given shell window is currently on,
2176 ** or -1 if no desktop information is available. Note that windows shown
2177 ** on all desktops (sometimes called sticky windows) should return 0xFFFFFFFF.
2179 long QueryDesktop(Display
*display
, Widget shell
)
2181 static Atom wmDesktopAtom
= (Atom
)-1;
2183 if (wmDesktopAtom
== (Atom
)-1)
2184 wmDesktopAtom
= XInternAtom(display
, "_NET_WM_DESKTOP", True
);
2186 if (wmDesktopAtom
!= None
)
2187 return queryDesktop(display
, XtWindow(shell
), wmDesktopAtom
);
2189 return -1; /* No desktop information */
2194 ** Clipboard wrapper functions that call the Motif clipboard functions
2195 ** a number of times before giving up. The interfaces are similar to the
2196 ** native Motif functions.
2199 #define SPINCOUNT 10 /* Try at most 10 times */
2200 #define USLEEPTIME 1000 /* 1 ms between retries */
2203 ** Warning reporting
2205 static void warning(const char* mesg
)
2207 fprintf(stderr
, "NEdit warning:\n%s\n", mesg
);
2213 static void microsleep(long usecs
)
2215 static struct timeval timeoutVal
;
2216 timeoutVal
.tv_sec
= usecs
/1000000;
2217 timeoutVal
.tv_usec
= usecs
- timeoutVal
.tv_sec
*1000000;
2218 select(0, NULL
, NULL
, NULL
, &timeoutVal
);
2222 ** XmClipboardStartCopy spinlock wrapper.
2224 int SpinClipboardStartCopy(Display
*display
, Window window
,
2225 XmString clip_label
, Time timestamp
, Widget widget
,
2226 XmCutPasteProc callback
, long *item_id
)
2229 for (i
=0; i
<SPINCOUNT
; ++i
) {
2230 res
= XmClipboardStartCopy(display
, window
, clip_label
, timestamp
,
2231 widget
, callback
, item_id
);
2232 if (res
== XmClipboardSuccess
) {
2235 microsleep(USLEEPTIME
);
2237 warning("XmClipboardStartCopy() failed: clipboard locked.");
2242 ** XmClipboardCopy spinlock wrapper.
2244 int SpinClipboardCopy(Display
*display
, Window window
, long item_id
,
2245 char *format_name
, XtPointer buffer
, unsigned long length
,
2246 long private_id
, long *data_id
)
2249 for (i
=0; i
<SPINCOUNT
; ++i
) {
2250 res
= XmClipboardCopy(display
, window
, item_id
, format_name
,
2251 buffer
, length
, private_id
, data_id
);
2252 if (res
== XmClipboardSuccess
) {
2255 if (res
== XmClipboardFail
) {
2256 warning("XmClipboardCopy() failed: XmClipboardStartCopy not "
2257 "called or too many formats.");
2260 microsleep(USLEEPTIME
);
2262 warning("XmClipboardCopy() failed: clipboard locked.");
2267 ** XmClipboardEndCopy spinlock wrapper.
2269 int SpinClipboardEndCopy(Display
*display
, Window window
, long item_id
)
2272 for (i
=0; i
<SPINCOUNT
; ++i
) {
2273 res
= XmClipboardEndCopy(display
, window
, item_id
);
2274 if (res
== XmClipboardSuccess
) {
2277 if (res
== XmClipboardFail
) {
2278 warning("XmClipboardEndCopy() failed: XmClipboardStartCopy not "
2282 microsleep(USLEEPTIME
);
2284 warning("XmClipboardEndCopy() failed: clipboard locked.");
2289 ** XmClipboardInquireLength spinlock wrapper.
2291 int SpinClipboardInquireLength(Display
*display
, Window window
,
2292 char *format_name
, unsigned long *length
)
2295 for (i
=0; i
<SPINCOUNT
; ++i
) {
2296 res
= XmClipboardInquireLength(display
, window
, format_name
, length
);
2297 if (res
== XmClipboardSuccess
) {
2300 if (res
== XmClipboardNoData
) {
2303 microsleep(USLEEPTIME
);
2305 warning("XmClipboardInquireLength() failed: clipboard locked.");
2310 ** XmClipboardRetrieve spinlock wrapper.
2312 int SpinClipboardRetrieve(Display
*display
, Window window
, char *format_name
,
2313 XtPointer buffer
, unsigned long length
, unsigned long *num_bytes
,
2317 for (i
=0; i
<SPINCOUNT
; ++i
) {
2318 res
= XmClipboardRetrieve(display
, window
, format_name
, buffer
,
2319 length
, num_bytes
, private_id
);
2320 if (res
== XmClipboardSuccess
) {
2323 if (res
== XmClipboardTruncate
) {
2324 warning("XmClipboardRetrieve() failed: buffer too small.");
2327 if (res
== XmClipboardNoData
) {
2330 microsleep(USLEEPTIME
);
2332 warning("XmClipboardRetrieve() failed: clipboard locked.");
2337 ** XmClipboardLock spinlock wrapper.
2339 int SpinClipboardLock(Display
*display
, Window window
)
2342 for (i
=0; i
<SPINCOUNT
; ++i
) {
2343 res
= XmClipboardLock(display
, window
);
2344 if (res
== XmClipboardSuccess
) {
2347 microsleep(USLEEPTIME
);
2349 warning("XmClipboardLock() failed: clipboard locked.");
2354 ** XmClipboardUnlock spinlock wrapper.
2356 int SpinClipboardUnlock(Display
*display
, Window window
)
2359 /* Spinning doesn't make much sense in this case, I think. */
2360 for (i
=0; i
<SPINCOUNT
; ++i
) {
2361 /* Remove ALL locks (we don't use nested locking in NEdit) */
2362 res
= XmClipboardUnlock(display
, window
, True
);
2363 if (res
== XmClipboardSuccess
) {
2366 microsleep(USLEEPTIME
);
2369 * This warning doesn't make much sense in practice. It's usually
2370 * triggered when we try to unlock the clipboard after a failed clipboard
2371 * operation, in an attempt to work around possible *tif clipboard locking
2372 * bugs. In these cases, failure _is_ the expected outcome and the warning
2373 * is bogus. Therefore, the warning is disabled.
2374 warning("XmClipboardUnlock() failed: clipboard not locked or locked "
2375 "by another application.");