1 /*******************************************************************************
2 * *
3 * misc.c -- Miscelaneous Motif convenience functions *
4 * *
5 * Copyright (C) 1999 Mark Edel *
6 * *
7 * This is free software; you can redistribute it and/or modify it under the *
8 * terms of the GNU General Public License as published by the Free Software *
9 * Foundation; either version 2 of the License, or (at your option) any later *
10 * version. In addition, you may distribute version of this program linked to *
11 * Motif or Open Motif. See README for details. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * July 28, 1992 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32
33 #include "misc.h"
34 #include "DialogF.h"
35 #include "nedit_malloc.h"
36
37 #include <stdlib.h>
38 #include <string.h>
39 #include <stdarg.h>
40 #include <ctype.h>
41 #include <stdio.h>
42 #include <time.h>
43
44 #ifdef __unix__
45 #include <sys/time.h>
46 #include <sys/select.h>
47 #endif
48
49 #ifdef __APPLE__
50 #ifdef __MACH__
51 #include <sys/select.h>
52 #endif
53 #endif
54
55 #ifdef VMS
56 #include <types.h>
57 #include <unixio.h>
58 #include <file.h>
59 #endif /*VMS*/
60
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>
83
84 #ifdef HAVE_DEBUG_H
85 #include "../debug.h"
86 #endif
87
88 #ifndef LESSTIF_VERSION
89 extern void _XmDismissTearOff(Widget w, XtPointer call, XtPointer x);
90 #endif
91
92 /* structure for passing history-recall data to callbacks */
93 typedef struct {
94 char ***list;
95 int *nItems;
96 int index;
97 } histInfo;
98
99 typedef Widget (*MotifDialogCreationCall)(Widget, String, ArgList, Cardinal);
100
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
107
108 /* flags to enable/disable delete key remapping and pointer centered dialogs */
109 static int RemapDeleteEnabled = True;
110 static int PointerCenteredDialogsEnabled = False;
111
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
121 };
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
128 };
129
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);
162
163 /*
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.
167 */
AddMotifCloseCallback(Widget shell,XtCallbackProc closeCB,void * arg)168 void AddMotifCloseCallback(Widget shell, XtCallbackProc closeCB, void *arg)
169 {
170 static Atom wmpAtom, dwAtom = 0;
171 Display *display = XtDisplay(shell);
172
173 /* deactivate the built in delete response of killing the application */
174 XtVaSetValues(shell, XmNdeleteResponse, XmDO_NOTHING, NULL);
175
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);
180 }
181 XmAddProtocolCallback(shell, wmpAtom, dwAtom, closeCB, arg);
182 }
183
184 /*
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.
194 */
SuppressPassiveGrabWarnings(void)195 void SuppressPassiveGrabWarnings(void)
196 {
197 #if !defined(__alpha) && !defined(__EMX__)
198 XtSetWarningHandler(warnHandlerCB);
199 #endif
200 }
201
202 /*
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.
222 */
RemapDeleteKey(Widget w)223 void RemapDeleteKey(Widget w)
224 {
225 static XtTranslations table = NULL;
226 static char *translations =
227 "~Shift~Ctrl~Meta~Alt<Key>osfDelete: delete-previous-character()\n";
228
229 if (RemapDeleteEnabled) {
230 if (table == NULL)
231 table = XtParseTranslationTable(translations);
232 XtOverrideTranslations(w, table);
233 }
234 }
235
SetDeleteRemap(int state)236 void SetDeleteRemap(int state)
237 {
238 RemapDeleteEnabled = state;
239 }
240
241
242 /*
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.
248 */
setWindowGroup(Widget shell)249 static void setWindowGroup(Widget shell) {
250 static int firstTime = True;
251 static Window groupLeader;
252 Display *display = XtDisplay(shell);
253 XWMHints *wmHints;
254
255 if (firstTime) {
256 /* Create a dummy window to be the group leader for our windows */
257 String name, class;
258 XClassHint *classHint;
259
260 groupLeader = XCreateSimpleWindow(display,
261 RootWindow(display, DefaultScreen(display)),
262 1, 1, 1, 1, 0, 0, 0);
263
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);
272
273 firstTime = False;
274 }
275
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);
282 }
283
284 /*
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.
290 **
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.
302 */
303
RemovePPositionHint(Widget shell)304 void RemovePPositionHint(Widget shell)
305 {
306 XSizeHints *hints = XAllocSizeHints();
307 long suppliedHints;
308
309 /* Get rid of the incorrect WMNormal hint */
310 if (XGetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints,
311 &suppliedHints))
312 {
313 hints->flags &= ~PPosition;
314 XSetWMNormalHints(XtDisplay(shell), XtWindow(shell), hints);
315 }
316
317 XFree(hints);
318 }
319
RealizeWithoutForcingPosition(Widget shell)320 void RealizeWithoutForcingPosition(Widget shell)
321 {
322 Boolean mappedWhenManaged;
323
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);
328
329 /* Realize the widget in unmapped state */
330 XtRealizeWidget(shell);
331
332 /* Remove the hint */
333 RemovePPositionHint(shell);
334
335 /* Set WindowGroupHint so the NEdit icons can be grouped; this
336 seems to be necessary starting with Gnome 2.0 */
337 setWindowGroup(shell);
338
339 /* Map the widget */
340 XtMapWidget(shell);
341
342 /* Restore the value of XmNmappedWhenManaged */
343 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
344 }
345
346 /*
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.
357 **
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.
363 **
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.
370 **
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.
375 **
376 ** Returns True if the best visual is the default, False otherwise.
377 */
FindBestVisual(Display * display,const char * appName,const char * appClass,Visual ** visual,int * depth,Colormap * colormap)378 Boolean FindBestVisual(Display *display, const char *appName, const char *appClass,
379 Visual **visual, int *depth, Colormap *colormap)
380 {
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};
395
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));
402 }
403
404 /* Set "Default" visual to avoid crashes with the detected best ones. */
405 reqID = DefaultVisual(display, screen)->visualid;
406
407 /* Read the visualID and installColormap resources for the application.
408 visualID can be specified either as a number (the visual id as
409 shown by xdpyinfo), as a visual class name, or as Best or Default. */
410 sprintf(rsrcName,"%s.%s", appName, "visualID");
411 sprintf(rsrcClass, "%s.%s", appClass, "VisualID");
412 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
413 &value)) {
414 valueString = value.addr;
415 reqID = (int)strtol(valueString, &endPtr, 0);
416 if (endPtr == valueString) {
417 reqID = -1;
418 if (stripCaseCmp(valueString, "Default"))
419 reqID = DefaultVisual(display, screen)->visualid;
420 else if (stripCaseCmp(valueString, "StaticGray"))
421 reqClass = StaticGray;
422 else if (stripCaseCmp(valueString, "StaticColor"))
423 reqClass = StaticColor;
424 else if (stripCaseCmp(valueString, "TrueColor"))
425 reqClass = TrueColor;
426 else if (stripCaseCmp(valueString, "GrayScale"))
427 reqClass = GrayScale;
428 else if (stripCaseCmp(valueString, "PseudoColor"))
429 reqClass = PseudoColor;
430 else if (stripCaseCmp(valueString, "DirectColor"))
431 reqClass = DirectColor;
432 else if (!stripCaseCmp(valueString, "Best"))
433 fprintf(stderr, "Invalid visualID resource value\n");
434 }
435 }
436 sprintf(rsrcName,"%s.%s", appName, "installColormap");
437 sprintf(rsrcClass, "%s.%s", appClass, "InstallColormap");
438 if (XrmGetResource(XtDatabase(display), rsrcName, rsrcClass, &type,
439 &value)) {
440 if (stripCaseCmp(value.addr, "Yes") || stripCaseCmp(value.addr, "True"))
441 installColormap = TRUE;
442 }
443
444 visTemplate.screen = screen;
445
446 /* Generate a list of visuals to consider. (Note, vestigial code for
447 user-requested visual depth is left in, just in case that function
448 might be needed again, but it does nothing). */
449 if (reqID != -1) {
450 visTemplate.visualid = reqID;
451 visList = XGetVisualInfo(display, VisualScreenMask|VisualIDMask,
452 &visTemplate, &nVis);
453 if (visList == NULL)
454 fprintf(stderr, "VisualID resource value not valid\n");
455 }
456 if (visList == NULL && reqClass != -1 && reqDepth != -1) {
457 visTemplate.class = reqClass;
458 visTemplate.depth = reqDepth;
459 visList = XGetVisualInfo(display,
460 VisualScreenMask| VisualClassMask | VisualDepthMask,
461 &visTemplate, &nVis);
462 if (visList == NULL)
463 fprintf(stderr, "Visual class/depth combination not available\n");
464 }
465 if (visList == NULL && reqClass != -1) {
466 visTemplate.class = reqClass;
467 visList = XGetVisualInfo(display, VisualScreenMask|VisualClassMask,
468 &visTemplate, &nVis);
469 if (visList == NULL)
470 fprintf(stderr,
471 "Visual Class from resource \"visualID\" not available\n");
472 }
473 if (visList == NULL && reqDepth != -1) {
474 visTemplate.depth = reqDepth;
475 visList = XGetVisualInfo(display, VisualScreenMask|VisualDepthMask,
476 &visTemplate, &nVis);
477 if (visList == NULL)
478 fprintf(stderr, "Requested visual depth not available\n");
479 }
480 if (visList == NULL) {
481 visList = XGetVisualInfo(display, VisualScreenMask, &visTemplate, &nVis);
482 if (visList == NULL) {
483 fprintf(stderr, "Internal Error: no visuals available?\n");
484 *visual = DefaultVisual(display, screen);
485 *depth = DefaultDepth(display, screen);
486 *colormap = DefaultColormap(display, screen);
487 return True;
488 }
489 }
490
491 /* Choose among the visuals in the candidate list. Prefer maximum
492 depth first then matching default, then largest value of bestClass
493 (I'm not sure whether we actually care about class) */
494 maxDepth = 0;
495 bestClass = 0;
496 bestVisual = 0;
497 for (i=0; i < nVis; i++) {
498 /* X.Org 6.8+ 32-bit visuals (with alpha-channel) cause a lot of
499 problems, so we have to skip them. We already try this by setting
500 the environment variable XLIB_SKIP_ARGB_VISUALS at startup (in
501 nedit.c), but that doesn't cover the case where NEdit is running on
502 a host that doesn't use the X.Org X libraries but is displaying
503 remotely on an X.Org server. Therefore, this additional check is
504 added.
505 Note that this check in itself is not sufficient. There have been
506 bug reports that seemed to indicate that non-32-bit visuals with an
507 alpha-channel exist. The combined approach (env. var. + 32-bit
508 check) should cover the vast majority of the cases, though. */
509 if (visList[i].depth >= 32 &&
510 strstr(ServerVendor(display), "X.Org") != 0) {
511 continue;
512 }
513 if (visList[i].depth > maxDepth) {
514 maxDepth = visList[i].depth;
515 bestClass = 0;
516 bestVisual = i;
517 }
518 if (visList[i].depth == maxDepth) {
519 if (visList[i].visual == DefaultVisual(display, screen))
520 bestVisual = i;
521 if (visList[bestVisual].visual != DefaultVisual(display, screen)) {
522 for (j = 0; j < (int)XtNumber(bestClasses); j++) {
523 if (visList[i].class == bestClasses[j] && j > bestClass) {
524 bestClass = j;
525 bestVisual = i;
526 }
527 }
528 }
529 }
530 }
531 *visual = cachedVisual = visList[bestVisual].visual;
532 *depth = cachedDepth = visList[bestVisual].depth;
533
534 /* If the chosen visual is not the default, it needs a colormap allocated */
535 if (*visual == DefaultVisual(display, screen) && !installColormap)
536 *colormap = cachedColormap = DefaultColormap(display, screen);
537 else {
538 *colormap = cachedColormap = XCreateColormap(display,
539 RootWindow(display, screen), cachedVisual, AllocNone);
540 XInstallColormap(display, cachedColormap);
541 }
542 /* printf("Chose visual with depth %d, class %d, colormap %ld, id 0x%x\n",
543 visList[bestVisual].depth, visList[bestVisual].class,
544 *colormap, cachedVisual->visualid); */
545 /* Fix memory leak */
546 if (visList != NULL) {
547 XFree(visList);
548 }
549
550 return (*visual == DefaultVisual(display, screen));
551 }
552
553 /*
554 ** If you want to use a non-default visual with Motif, shells all have to be
555 ** created with that visual, depth, and colormap, even if the parent has them
556 ** set up properly. Substituting these routines, will append visual args copied
557 ** from the parent widget (CreatePopupMenu and CreatePulldownMenu), or from the
558 ** best visual, obtained via FindBestVisual above (CreateShellWithBestVis).
559 */
CreateDialogShell(Widget parent,char * name,ArgList arglist,Cardinal argcount)560 Widget CreateDialogShell(Widget parent, char *name,
561 ArgList arglist, Cardinal argcount)
562 {
563 return addParentVisArgsAndCall(XmCreateDialogShell, parent, name, arglist,
564 argcount);
565 }
566
567
CreatePopupMenu(Widget parent,char * name,ArgList arglist,Cardinal argcount)568 Widget CreatePopupMenu(Widget parent, char *name, ArgList arglist,
569 Cardinal argcount)
570 {
571 return addParentVisArgsAndCall(XmCreatePopupMenu, parent, name,
572 arglist, argcount);
573 }
574
575
CreatePulldownMenu(Widget parent,char * name,ArgList arglist,Cardinal argcount)576 Widget CreatePulldownMenu(Widget parent, char *name,
577 ArgList arglist, Cardinal argcount)
578 {
579 return addParentVisArgsAndCall(XmCreatePulldownMenu, parent, name, arglist,
580 argcount);
581 }
582
583
CreatePromptDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)584 Widget CreatePromptDialog(Widget parent, char *name,
585 ArgList arglist, Cardinal argcount)
586 {
587 return addParentVisArgsAndCall(XmCreatePromptDialog, parent, name, arglist,
588 argcount);
589 }
590
591
CreateSelectionDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)592 Widget CreateSelectionDialog(Widget parent, char *name,
593 ArgList arglist, Cardinal argcount)
594 {
595 Widget dialog = addParentVisArgsAndCall(XmCreateSelectionDialog, parent, name,
596 arglist, argcount);
597 AddMouseWheelSupport(XmSelectionBoxGetChild(dialog, XmDIALOG_LIST));
598 return dialog;
599 }
600
601
CreateFormDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)602 Widget CreateFormDialog(Widget parent, char *name,
603 ArgList arglist, Cardinal argcount)
604 {
605 return addParentVisArgsAndCall(XmCreateFormDialog, parent, name, arglist,
606 argcount);
607 }
608
609
CreateFileSelectionDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)610 Widget CreateFileSelectionDialog(Widget parent, char *name,
611 ArgList arglist, Cardinal argcount)
612 {
613 Widget dialog = addParentVisArgsAndCall(XmCreateFileSelectionDialog, parent,
614 name, arglist, argcount);
615
616 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_LIST));
617 AddMouseWheelSupport(XmFileSelectionBoxGetChild(dialog, XmDIALOG_DIR_LIST));
618 return dialog;
619 }
620
621
CreateQuestionDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)622 Widget CreateQuestionDialog(Widget parent, char *name,
623 ArgList arglist, Cardinal argcount)
624 {
625 return addParentVisArgsAndCall(XmCreateQuestionDialog, parent, name,
626 arglist, argcount);
627 }
628
629
CreateMessageDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)630 Widget CreateMessageDialog(Widget parent, char *name,
631 ArgList arglist, Cardinal argcount)
632 {
633 return addParentVisArgsAndCall(XmCreateMessageDialog, parent, name,
634 arglist, argcount);
635 }
636
637
CreateErrorDialog(Widget parent,char * name,ArgList arglist,Cardinal argcount)638 Widget CreateErrorDialog(Widget parent, char *name,
639 ArgList arglist, Cardinal argcount)
640 {
641 return addParentVisArgsAndCall(XmCreateErrorDialog, parent, name, arglist,
642 argcount);
643 }
644
CreateWidget(Widget parent,const char * name,WidgetClass class,ArgList arglist,Cardinal argcount)645 Widget CreateWidget(Widget parent, const char *name, WidgetClass class,
646 ArgList arglist, Cardinal argcount)
647 {
648 Widget result;
649 ArgList al = addParentVisArgs(parent, arglist, &argcount);
650 result = XtCreateWidget(name, class, parent, al, argcount);
651 NEditFree((char *)al);
652 return result;
653 }
654
CreateShellWithBestVis(String appName,String appClass,WidgetClass class,Display * display,ArgList args,Cardinal nArgs)655 Widget CreateShellWithBestVis(String appName, String appClass,
656 WidgetClass class, Display *display, ArgList args, Cardinal nArgs)
657 {
658 Visual *visual;
659 int depth;
660 Colormap colormap;
661 ArgList al;
662 Cardinal ac = nArgs;
663 Widget result;
664
665 FindBestVisual(display, appName, appClass, &visual, &depth, &colormap);
666 al = (ArgList)NEditMalloc(sizeof(Arg) * (nArgs + 3));
667 if (nArgs != 0)
668 memcpy(al, args, sizeof(Arg) * nArgs);
669 XtSetArg(al[ac], XtNvisual, visual); ac++;
670 XtSetArg(al[ac], XtNdepth, depth); ac++;
671 XtSetArg(al[ac], XtNcolormap, colormap); ac++;
672 result = XtAppCreateShell(appName, appClass, class, display, al, ac);
673 NEditFree((char *)al);
674 return result;
675 }
676
677
CreatePopupShellWithBestVis(String shellName,WidgetClass class,Widget parent,ArgList arglist,Cardinal argcount)678 Widget CreatePopupShellWithBestVis(String shellName, WidgetClass class,
679 Widget parent, ArgList arglist, Cardinal argcount)
680 {
681 Widget result;
682 ArgList al = addParentVisArgs(parent, arglist, &argcount);
683 result = XtCreatePopupShell(shellName, class, parent, al, argcount);
684 NEditFree((char *)al);
685 return result;
686 }
687
688 /*
689 ** Extends an argument list for widget creation with additional arguments
690 ** for visual, colormap, and depth. The original argument list is not altered
691 ** and it's the caller's responsability to free the returned list.
692 */
addParentVisArgs(Widget parent,ArgList arglist,Cardinal * argcount)693 static ArgList addParentVisArgs(Widget parent, ArgList arglist,
694 Cardinal *argcount)
695 {
696 Visual *visual;
697 int depth;
698 Colormap colormap;
699 ArgList al;
700 Widget parentShell = parent;
701
702 /* Find the application/dialog/menu shell at the top of the widget
703 hierarchy, which has the visual resource being used */
704 while (True) {
705 if (XtIsShell(parentShell))
706 break;
707 if (parentShell == NULL) {
708 fprintf(stderr, "failed to find shell\n");
709 exit(EXIT_FAILURE);
710 }
711 parentShell = XtParent(parentShell);
712 }
713
714 /* Add the visual, depth, and colormap resources to the argument list */
715 XtVaGetValues(parentShell, XtNvisual, &visual, XtNdepth, &depth,
716 XtNcolormap, &colormap, NULL);
717 al = (ArgList)NEditMalloc(sizeof(Arg) * ((*argcount) + 3));
718 if ((*argcount) != 0)
719 memcpy(al, arglist, sizeof(Arg) * (*argcount));
720
721 /* For non-Lesstif versions, the visual, depth, and colormap are now set
722 globally via the resource database. So strictly spoken, it is no
723 longer necessary to set them explicitly for every shell widget.
724
725 For Lesstif, however, this doesn't work. Luckily, Lesstif handles
726 non-default visuals etc. properly for its own shells and
727 we can take care of things for our shells (eg, call tips) here. */
728 XtSetArg(al[*argcount], XtNvisual, visual); (*argcount)++;
729 XtSetArg(al[*argcount], XtNdepth, depth); (*argcount)++;
730 XtSetArg(al[*argcount], XtNcolormap, colormap); (*argcount)++;
731 return al;
732 }
733
734
735 /*
736 ** Calls one of the Motif widget creation routines, splicing in additional
737 ** arguments for visual, colormap, and depth.
738 */
addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,Widget parent,char * name,ArgList arglist,Cardinal argcount)739 static Widget addParentVisArgsAndCall(MotifDialogCreationCall createRoutine,
740 Widget parent, char *name, ArgList arglist, Cardinal argcount)
741 {
742 Widget result;
743 ArgList al = addParentVisArgs(parent, arglist, &argcount);
744 result = (*createRoutine)(parent, name, al, argcount);
745 NEditFree((char *)al);
746 return result;
747 }
748
749 /*
750 ** ManageDialogCenteredOnPointer is used in place of XtManageChild for
751 ** popping up a dialog to enable the dialog to be centered under the
752 ** mouse pointer. Whether it pops up the dialog centered under the pointer
753 ** or in its default position centered over the parent widget, depends on
754 ** the value set in the SetPointerCenteredDialogs call.
755 ** Additionally, this function constrains the size of the dialog to the
756 ** screen's size, to avoid insanely wide dialogs with obscured buttons.
757 */
ManageDialogCenteredOnPointer(Widget dialogChild)758 void ManageDialogCenteredOnPointer(Widget dialogChild)
759 {
760 Widget shell = XtParent(dialogChild);
761 Window root, child;
762 unsigned int mask;
763 unsigned int width, height, borderWidth, depth;
764 int x, y, winX, winY, maxX, maxY, maxWidth, maxHeight;
765 Dimension xtWidth, xtHeight;
766 Boolean mappedWhenManaged;
767 static const int slop = 25;
768
769 /* Temporarily set value of XmNmappedWhenManaged
770 to stop the dialog from popping up right away */
771 XtVaGetValues(shell, XmNmappedWhenManaged, &mappedWhenManaged, NULL);
772 XtVaSetValues(shell, XmNmappedWhenManaged, False, NULL);
773
774 /* Ensure that the dialog doesn't get wider/taller than the screen.
775 We use a hard-coded "slop" size because we don't know the border
776 width until it's on screen, and by then it's too late. Putting
777 this before managing the widgets allows it to get the geometry
778 right on the first pass and also prevents the user from
779 accidentally resizing too wide. */
780 maxWidth = XtScreen(shell)->width - slop;
781 maxHeight = XtScreen(shell)->height - slop;
782
783 XtVaSetValues(shell,
784 XmNmaxWidth, maxWidth,
785 XmNmaxHeight, maxHeight,
786 NULL);
787
788 /* Manage the dialog */
789 XtManageChild(dialogChild);
790
791 /* Check to see if the window manager doesn't respect XmNmaxWidth
792 and XmNmaxHeight on the first geometry pass (sawfish, twm, fvwm).
793 For this to work XmNresizePolicy must be XmRESIZE_NONE, otherwise
794 the dialog will try to expand anyway. */
795 XtVaGetValues(shell, XmNwidth, &xtWidth, XmNheight, &xtHeight, NULL);
796 if (xtWidth > maxWidth)
797 XtVaSetValues(shell, XmNwidth, (Dimension) maxWidth, NULL);
798 if (xtHeight > maxHeight)
799 XtVaSetValues(shell, XmNheight, (Dimension) maxHeight, NULL);
800
801 /* Only set the x/y position if the centering option is enabled.
802 Avoid getting the coordinates if not so, to save a few round-trips
803 to the server. */
804 if (PointerCenteredDialogsEnabled) {
805 /* Get the pointer position (x, y) */
806 XQueryPointer(XtDisplay(shell), XtWindow(shell), &root, &child,
807 &x, &y, &winX, &winY, &mask);
808
809 /* Translate the pointer position (x, y) into a position for the new
810 window that will place the pointer at its center */
811 XGetGeometry(XtDisplay(shell), XtWindow(shell), &root, &winX, &winY,
812 &width, &height, &borderWidth, &depth);
813 width += 2 * borderWidth;
814 height += 2 * borderWidth;
815
816 x -= width/2;
817 y -= height/2;
818
819 /* Ensure that the dialog remains on screen */
820 maxX = maxWidth - width;
821 maxY = maxHeight - height;
822 if (x > maxX) x = maxX;
823 if (x < 0) x = 0;
824 if (y > maxY) y = maxY;
825 if (y < 0) y = 0;
826
827 /* Some window managers (Sawfish) don't appear to respond
828 to the geometry set call in synchronous mode. This causes
829 the window to delay XmNwmTimeout (default 5 seconds) before
830 posting, and it is very annoying. See "man VendorShell" for
831 more info. */
832 XtVaSetValues(shell, XmNuseAsyncGeometry, True, NULL);
833
834 /* Set desired window position in the DialogShell */
835 XtVaSetValues(shell, XmNx, x, XmNy, y, NULL);
836 }
837
838 /* Map the widget */
839 XtMapWidget(shell);
840
841 /* Restore the value of XmNmappedWhenManaged */
842 XtVaSetValues(shell, XmNmappedWhenManaged, mappedWhenManaged, NULL);
843 }
844
845 /*
846 ** Cause dialogs created by libNUtil.a routines (such as DialogF),
847 ** and dialogs which use ManageDialogCenteredOnPointer to pop up
848 ** over the pointer (state = True), or pop up in their default
849 ** positions (state = False)
850 */
SetPointerCenteredDialogs(int state)851 void SetPointerCenteredDialogs(int state)
852 {
853 PointerCenteredDialogsEnabled = state;
854 }
855
856
857 /*
858 ** Raise a window to the top and give it the input focus. Setting input focus
859 ** is important on systems which use explict (rather than pointer) focus.
860 **
861 ** The X alternatives XMapRaised, and XSetInputFocus both have problems.
862 ** XMapRaised only gives the window the focus if it was initially not visible,
863 ** and XSetInputFocus sets the input focus, but crashes if the window is not
864 ** visible.
865 **
866 ** This routine should also be used in the case where a dialog is popped up and
867 ** subsequent calls to the dialog function use a flag, or the XtIsManaged, to
868 ** decide whether to create a new instance of the dialog, because on slower
869 ** systems, events can intervene while a dialog is still on its way up and its
870 ** window is still invisible, causing a subtle crash potential if
871 ** XSetInputFocus is used.
872 */
RaiseDialogWindow(Widget shell)873 void RaiseDialogWindow(Widget shell)
874 {
875 RaiseWindow(XtDisplay(shell), XtWindow(shell), True);
876 }
877
RaiseShellWindow(Widget shell,Boolean focus)878 void RaiseShellWindow(Widget shell, Boolean focus)
879 {
880 RaiseWindow(XtDisplay(shell), XtWindow(shell), focus);
881 }
882
RaiseWindow(Display * display,Window w,Boolean focus)883 void RaiseWindow(Display *display, Window w, Boolean focus)
884 {
885 if (focus) {
886 XWindowAttributes winAttr;
887
888 XGetWindowAttributes(display, w, &winAttr);
889 if (winAttr.map_state == IsViewable)
890 XSetInputFocus(display, w, RevertToParent, CurrentTime);
891 }
892
893 WmClientMsg(display, w, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
894 XMapRaised(display, w);
895 }
896
897 /*
898 ** Add a handler for mnemonics in a dialog (Motif currently only handles
899 ** mnemonics in menus) following the example of M.S. Windows. To add
900 ** mnemonics to a dialog, set the XmNmnemonic resource, as you would in
901 ** a menu, on push buttons or toggle buttons, and call this function
902 ** when the dialog is fully constructed. Mnemonics added or changed
903 ** after this call will not be noticed. To add a mnemonic to a text field
904 ** or list, set the XmNmnemonic resource on the appropriate label and set
905 ** the XmNuserData resource of the label to the widget to get the focus
906 ** when the mnemonic is typed.
907 */
AddDialogMnemonicHandler(Widget dialog,int unmodifiedToo)908 void AddDialogMnemonicHandler(Widget dialog, int unmodifiedToo)
909 {
910 XtAddEventHandler(dialog, KeyPressMask, False,
911 (XtEventHandler)mnemonicCB, (XtPointer)0);
912 addMnemonicGrabs(dialog, dialog, unmodifiedToo);
913 }
914
915 /*
916 ** Removes the event handler and key-grabs added by AddDialogMnemonicHandler
917 */
RemoveDialogMnemonicHandler(Widget dialog)918 void RemoveDialogMnemonicHandler(Widget dialog)
919 {
920 XtUngrabKey(dialog, AnyKey, Mod1Mask);
921 XtRemoveEventHandler(dialog, KeyPressMask, False,
922 (XtEventHandler)mnemonicCB, (XtPointer)0);
923 }
924
925 /*
926 ** Patch around Motif's poor handling of menu accelerator keys. Motif
927 ** does not process menu accelerators when the caps lock or num lock
928 ** keys are engaged. To enable accelerators in these cases, call this
929 ** routine with the completed menu bar widget as "topMenuContainer", and
930 ** the top level shell widget as "topWidget". It will add key grabs for
931 ** all of the accelerators it finds in the topMenuContainer menu tree, and
932 ** an event handler which can process dropped accelerator events by (again)
933 ** traversing the menu tree looking for matching accelerators, and invoking
934 ** the appropriate button actions. Any dynamic additions to the menus
935 ** require a call to UpdateAccelLockPatch to add the additional grabs.
936 ** Unfortunately, these grabs can not be removed.
937 */
AccelLockBugPatch(Widget topWidget,Widget topMenuContainer)938 void AccelLockBugPatch(Widget topWidget, Widget topMenuContainer)
939 {
940 XtAddEventHandler(topWidget, KeyPressMask, False, lockCB, topMenuContainer);
941 addAccelGrabs(topWidget, topMenuContainer);
942 }
943
944 /*
945 ** Add additional key grabs for new menu items added to the menus, for
946 ** patching around the Motif Caps/Num Lock problem. "topWidget" must be
947 ** the same widget passed in the original call to AccelLockBugPatch.
948 */
UpdateAccelLockPatch(Widget topWidget,Widget newButton)949 void UpdateAccelLockPatch(Widget topWidget, Widget newButton)
950 {
951 addAccelGrab(topWidget, newButton);
952 }
953
954 /*
955 ** PopDownBugPatch
956 **
957 ** Under some circumstances, popping down a dialog and its parent in
958 ** rapid succession causes a crash. This routine delays and
959 ** processs events until receiving a ReparentNotify event.
960 ** (I have no idea why a ReparentNotify event occurs at all, but it does
961 ** mark the point where it is safe to destroy or pop down the parent, and
962 ** it might have something to do with the bug.) There is a failsafe in
963 ** the form of a ~1.5 second timeout in case no ReparentNotify arrives.
964 ** Use this sparingly, only when real crashes are observed, and periodically
965 ** check to make sure that it is still necessary.
966 */
PopDownBugPatch(Widget w)967 void PopDownBugPatch(Widget w)
968 {
969 time_t stopTime;
970
971 stopTime = time(NULL) + 1;
972 while (time(NULL) <= stopTime) {
973 XEvent event;
974 XtAppContext context = XtWidgetToApplicationContext(w);
975 XtAppPeekEvent(context, &event);
976 if (event.xany.type == ReparentNotify)
977 return;
978 XtAppProcessEvent(context, XtIMAll);
979 }
980 }
981
982 /*
983 ** Convert a compound string to a C style null terminated string.
984 ** Returned string must be freed by the caller.
985 */
GetXmStringText(XmString fromString)986 char *GetXmStringText(XmString fromString)
987 {
988 XmStringContext context;
989 char *text, *toPtr, *toString, *fromPtr;
990 XmStringCharSet charset;
991 XmStringDirection direction;
992 Boolean separator;
993
994 /* Malloc a buffer large enough to hold the string. XmStringLength
995 should always be slightly longer than necessary, but won't be
996 shorter than the equivalent null-terminated string */
997 toString = (char*)NEditMalloc(XmStringLength(fromString));
998
999 /* loop over all of the segments in the string, copying each segment
1000 into the output string and converting separators into newlines */
1001 XmStringInitContext(&context, fromString);
1002 toPtr = toString;
1003 while (XmStringGetNextSegment(context, &text,
1004 &charset, &direction, &separator)) {
1005 for (fromPtr=text; *fromPtr!='\0'; fromPtr++)
1006 *toPtr++ = *fromPtr;
1007 if (separator)
1008 *toPtr++ = '\n';
1009 NEditFree(text);
1010 NEditFree(charset);
1011 }
1012
1013 /* terminate the string, free the context, and return the string */
1014 *toPtr++ = '\0';
1015 XmStringFreeContext(context);
1016 return toString;
1017 }
1018
1019 /*
1020 ** Get the XFontStruct that corresponds to the default (first) font in
1021 ** a Motif font list. Since Motif stores this, it saves us from storing
1022 ** it or querying it from the X server.
1023 */
GetDefaultFontStruct(Display * d,XmFontList font)1024 XFontStruct *GetDefaultFontStruct(Display *d, XmFontList font)
1025 {
1026 XFontStruct *fs;
1027 XmFontContext context;
1028 XmStringCharSet charset;
1029
1030 XmFontListInitFontContext(&context, font);
1031 XmFontListGetNextFont(context, &charset, &fs);
1032 XmFontListFreeFontContext(context);
1033 NEditFree(charset);
1034
1035 /* FontList might be a render table with no only XFT fonts */
1036 if (fs == NULL) {
1037 fs = XLoadQueryFont(d, "fixed");
1038 }
1039
1040 if (fs == NULL) {
1041 fprintf(stderr, "Unabled to load any fallback fonts.\n");
1042 exit(EXIT_FAILURE);
1043 }
1044
1045 return fs;
1046 }
1047
1048 /*
1049 ** Create a string table suitable for passing to XmList widgets
1050 */
StringTable(int count,...)1051 XmString* StringTable(int count, ... )
1052 {
1053 va_list ap;
1054 XmString *array;
1055 int i;
1056 char *str;
1057
1058 va_start(ap, count);
1059 array = (XmString*)NEditMalloc((count+1) * sizeof(XmString));
1060 for(i = 0; i < count; i++ ) {
1061 str = va_arg(ap, char *);
1062 array[i] = XmStringCreateSimple(str);
1063 }
1064 array[i] = (XmString)0;
1065 va_end(ap);
1066 return(array);
1067 }
1068
FreeStringTable(XmString * table)1069 void FreeStringTable(XmString *table)
1070 {
1071 int i;
1072
1073 for(i = 0; table[i] != 0; i++)
1074 XmStringFree(table[i]);
1075 NEditFree((char *)table);
1076 }
1077
1078 /*
1079 ** Simulate a button press. The purpose of this routine is show users what
1080 ** is happening when they take an action with a non-obvious side effect,
1081 ** such as when a user double clicks on a list item. The argument is an
1082 ** XmPushButton widget to "press"
1083 */
SimulateButtonPress(Widget widget)1084 void SimulateButtonPress(Widget widget)
1085 {
1086 XEvent keyEvent;
1087
1088 memset((char *)&keyEvent, 0, sizeof(XKeyPressedEvent));
1089 keyEvent.type = KeyPress;
1090 keyEvent.xkey.serial = 1;
1091 keyEvent.xkey.send_event = True;
1092
1093 if (XtIsSubclass(widget, xmGadgetClass))
1094 {
1095 /* On some Motif implementations, asking a gadget for its
1096 window will crash, rather than return the window of its
1097 parent. */
1098 Widget parent = XtParent(widget);
1099 keyEvent.xkey.display = XtDisplay(parent);
1100 keyEvent.xkey.window = XtWindow(parent);
1101
1102 XtCallActionProc(parent, "ManagerGadgetSelect",
1103 &keyEvent, NULL, 0);
1104 }
1105 else
1106 {
1107 keyEvent.xkey.display = XtDisplay(widget);
1108 keyEvent.xkey.window = XtWindow(widget);
1109
1110 XtCallActionProc(widget, "ArmAndActivate", &keyEvent, NULL, 0);
1111 }
1112 }
1113
1114 /*
1115 ** Add an item to an already established pull-down or pop-up menu, including
1116 ** mnemonics, accelerators and callbacks.
1117 */
AddMenuItem(Widget parent,char * name,char * label,char mnemonic,char * acc,char * accText,XtCallbackProc callback,void * cbArg)1118 Widget AddMenuItem(Widget parent, char *name, char *label,
1119 char mnemonic, char *acc, char *accText,
1120 XtCallbackProc callback, void *cbArg)
1121 {
1122 Widget button;
1123 XmString st1, st2;
1124
1125 button = XtVaCreateManagedWidget(name, xmPushButtonWidgetClass, parent,
1126 XmNlabelString, st1=XmStringCreateSimple(label),
1127 XmNmnemonic, mnemonic,
1128 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1129 XmNaccelerator, acc, NULL);
1130 XtAddCallback(button, XmNactivateCallback, callback, cbArg);
1131 XmStringFree(st1);
1132 XmStringFree(st2);
1133 return button;
1134 }
1135
1136 /*
1137 ** Add a toggle button item to an already established pull-down or pop-up
1138 ** menu, including mnemonics, accelerators and callbacks.
1139 */
AddMenuToggle(Widget parent,char * name,char * label,char mnemonic,char * acc,char * accText,XtCallbackProc callback,void * cbArg,int set)1140 Widget AddMenuToggle(Widget parent, char *name, char *label,
1141 char mnemonic, char *acc, char *accText,
1142 XtCallbackProc callback, void *cbArg, int set)
1143 {
1144 Widget button;
1145 XmString st1, st2;
1146
1147 button = XtVaCreateManagedWidget(name, xmToggleButtonWidgetClass, parent,
1148 XmNlabelString, st1=XmStringCreateSimple(label),
1149 XmNmnemonic, mnemonic,
1150 XmNacceleratorText, st2=XmStringCreateSimple(accText),
1151 XmNaccelerator, acc,
1152 XmNset, set, NULL);
1153 XtAddCallback(button, XmNvalueChangedCallback, callback, cbArg);
1154 XmStringFree(st1);
1155 XmStringFree(st2);
1156 return button;
1157 }
1158
1159 /*
1160 ** Add a sub-menu to an established pull-down or pop-up menu, including
1161 ** mnemonics, accelerators and callbacks. Returns the menu pane of the
1162 ** new sub menu.
1163 */
AddSubMenu(Widget parent,char * name,char * label,char mnemonic)1164 Widget AddSubMenu(Widget parent, char *name, char *label, char mnemonic)
1165 {
1166 Widget menu;
1167 XmString st1;
1168
1169 menu = CreatePulldownMenu(parent, name, NULL, 0);
1170 XtVaCreateManagedWidget(name, xmCascadeButtonWidgetClass, parent,
1171 XmNlabelString, st1=XmStringCreateSimple(label),
1172 XmNmnemonic, mnemonic,
1173 XmNsubMenuId, menu, NULL);
1174 XmStringFree(st1);
1175 return menu;
1176 }
1177
1178 /*
1179 ** SetIntText
1180 **
1181 ** Set the text of a motif label to show an integer
1182 */
SetIntText(Widget text,int value)1183 void SetIntText(Widget text, int value)
1184 {
1185 char labelString[20];
1186
1187 sprintf(labelString, "%d", value);
1188 XmTextSetString(text, labelString);
1189 }
1190
1191 /*
1192 ** GetIntText, GetFloatText, GetIntTextWarn, GetFloatTextWarn
1193 **
1194 ** Get the text of a motif text widget as an integer or floating point number.
1195 ** The functions will return TEXT_READ_OK of the value was read correctly.
1196 ** If not, they will return either TEXT_IS_BLANK, or TEXT_NOT_NUMBER. The
1197 ** GetIntTextWarn and GetFloatTextWarn will display a dialog warning the
1198 ** user that the value could not be read. The argument fieldName is used
1199 ** in the dialog to help the user identify where the problem is. Set
1200 ** warnBlank to true if a blank field is also considered an error.
1201 */
GetFloatText(Widget text,double * value)1202 int GetFloatText(Widget text, double *value)
1203 {
1204 char *strValue, *endPtr;
1205 int retVal;
1206
1207 strValue = XmTextGetString(text); /* Get Value */
1208 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1209 *value = strtod(strValue, &endPtr); /* Convert string to double */
1210 if (strlen(strValue) == 0) /* String is empty */
1211 retVal = TEXT_IS_BLANK;
1212 else if (*endPtr != '\0') /* Whole string not parsed */
1213 retVal = TEXT_NOT_NUMBER;
1214 else
1215 retVal = TEXT_READ_OK;
1216 NEditFree(strValue);
1217 return retVal;
1218 }
1219
GetIntText(Widget text,int * value)1220 int GetIntText(Widget text, int *value)
1221 {
1222 char *strValue, *endPtr;
1223 int retVal;
1224
1225 strValue = XmTextGetString(text); /* Get Value */
1226 removeWhiteSpace(strValue); /* Remove blanks and tabs */
1227 *value = strtol(strValue, &endPtr, 10); /* Convert string to long */
1228 if (strlen(strValue) == 0) /* String is empty */
1229 retVal = TEXT_IS_BLANK;
1230 else if (*endPtr != '\0') /* Whole string not parsed */
1231 retVal = TEXT_NOT_NUMBER;
1232 else
1233 retVal = TEXT_READ_OK;
1234 NEditFree(strValue);
1235 return retVal;
1236 }
1237
GetFloatTextWarn(Widget text,double * value,const char * fieldName,int warnBlank)1238 int GetFloatTextWarn(Widget text, double *value, const char *fieldName,
1239 int warnBlank)
1240 {
1241 int result;
1242 char *valueStr;
1243
1244 result = GetFloatText(text, value);
1245 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1246 return result;
1247 valueStr = XmTextGetString(text);
1248
1249 if (result == TEXT_IS_BLANK)
1250 {
1251 DialogF(DF_ERR, text, 1, "Warning", "Please supply %s value", "OK",
1252 fieldName);
1253 } else /* TEXT_NOT_NUMBER */
1254 {
1255 DialogF (DF_ERR, text, 1, "Warning", "Can't read %s value: \"%s\"",
1256 "OK", fieldName, valueStr);
1257 }
1258
1259 NEditFree(valueStr);
1260 return result;
1261 }
1262
GetIntTextWarn(Widget text,int * value,const char * fieldName,int warnBlank)1263 int GetIntTextWarn(Widget text, int *value, const char *fieldName, int warnBlank)
1264 {
1265 int result;
1266 char *valueStr;
1267
1268 result = GetIntText(text, value);
1269 if (result == TEXT_READ_OK || (result == TEXT_IS_BLANK && !warnBlank))
1270 return result;
1271 valueStr = XmTextGetString(text);
1272
1273 if (result == TEXT_IS_BLANK)
1274 {
1275 DialogF (DF_ERR, text, 1, "Warning", "Please supply a value for %s",
1276 "OK", fieldName);
1277 } else /* TEXT_NOT_NUMBER */
1278 {
1279 DialogF (DF_ERR, text, 1, "Warning",
1280 "Can't read integer value \"%s\" in %s", "OK", valueStr,
1281 fieldName);
1282 }
1283
1284 NEditFree(valueStr);
1285 return result;
1286 }
1287
TextWidgetIsBlank(Widget textW)1288 int TextWidgetIsBlank(Widget textW)
1289 {
1290 char *str;
1291 int retVal;
1292
1293 str = XmTextGetString(textW);
1294 removeWhiteSpace(str);
1295 retVal = *str == '\0';
1296 NEditFree(str);
1297 return retVal;
1298 }
1299
1300 /*
1301 ** Turn a multi-line editing text widget into a fake single line text area
1302 ** by disabling the translation for Return. This is a way to give users
1303 ** extra space, by allowing wrapping, but still prohibiting newlines.
1304 ** (SINGLE_LINE_EDIT mode can't be used, in this case, because it forces
1305 ** the widget to be one line high).
1306 */
MakeSingleLineTextW(Widget textW)1307 void MakeSingleLineTextW(Widget textW)
1308 {
1309 static XtTranslations noReturnTable = NULL;
1310 static char *noReturnTranslations = "<Key>Return: activate()\n";
1311
1312 if (noReturnTable == NULL)
1313 noReturnTable = XtParseTranslationTable(noReturnTranslations);
1314 XtOverrideTranslations(textW, noReturnTable);
1315 }
1316
1317 /*
1318 ** Add up-arrow/down-arrow recall to a single line text field. When user
1319 ** presses up-arrow, string is cleared and recent entries are presented,
1320 ** moving to older ones as each successive up-arrow is pressed. Down-arrow
1321 ** moves to more recent ones, final down-arrow clears the field. Associated
1322 ** routine, AddToHistoryList, makes maintaining a history list easier.
1323 **
1324 ** Arguments are the widget, and pointers to the history list and number of
1325 ** items, which are expected to change periodically.
1326 */
AddHistoryToTextWidget(Widget textW,char *** historyList,int * nItems)1327 void AddHistoryToTextWidget(Widget textW, char ***historyList, int *nItems)
1328 {
1329 histInfo *histData;
1330
1331 /* create a data structure for passing history info to the callbacks */
1332 histData = (histInfo *)NEditMalloc(sizeof(histInfo));
1333 histData->list = historyList;
1334 histData->nItems = nItems;
1335 histData->index = -1;
1336
1337 /* Add an event handler for handling up/down arrow events */
1338 XtAddEventHandler(textW, KeyPressMask, False,
1339 (XtEventHandler)histArrowKeyEH, histData);
1340
1341 /* Add a destroy callback for freeing history data structure */
1342 XtAddCallback(textW, XmNdestroyCallback, histDestroyCB, histData);
1343 }
1344
histDestroyCB(Widget w,XtPointer clientData,XtPointer callData)1345 static void histDestroyCB(Widget w, XtPointer clientData, XtPointer callData)
1346 {
1347 NEditFree((char *)clientData);
1348 }
1349
histArrowKeyEH(Widget w,XtPointer callData,XEvent * event,Boolean * continueDispatch)1350 static void histArrowKeyEH(Widget w, XtPointer callData, XEvent *event,
1351 Boolean *continueDispatch)
1352 {
1353 histInfo *histData = (histInfo *)callData;
1354 KeySym keysym = XLookupKeysym((XKeyEvent *)event, 0);
1355
1356 /* only process up and down arrow keys */
1357 if (keysym != XK_Up && keysym != XK_Down)
1358 return;
1359
1360 /* increment or decrement the index depending on which arrow was pressed */
1361 histData->index += (keysym == XK_Up) ? 1 : -1;
1362
1363 /* if the index is out of range, beep, fix it up, and return */
1364 if (histData->index < -1) {
1365 histData->index = -1;
1366 XBell(XtDisplay(w), 0);
1367 return;
1368 }
1369 if (histData->index >= *histData->nItems) {
1370 histData->index = *histData->nItems - 1;
1371 XBell(XtDisplay(w), 0);
1372 return;
1373 }
1374
1375 /* Change the text field contents */
1376 XmTextSetString(w, histData->index == -1 ? "" :
1377 (*histData->list)[histData->index]);
1378 }
1379
1380 /*
1381 ** Copies a string on to the end of history list, which may be reallocated
1382 ** to make room. If historyList grows beyond its internally set boundary
1383 ** for size (HISTORY_LIST_MAX), it is trimmed back to a smaller size
1384 ** (HISTORY_LIST_TRIM_TO). Before adding to the list, checks if the item
1385 ** is a duplicate of the last item. If so, it is not added.
1386 */
AddToHistoryList(char * newItem,char *** historyList,int * nItems)1387 void AddToHistoryList(char *newItem, char ***historyList, int *nItems)
1388 {
1389 char **newList;
1390 int i;
1391
1392 if (*nItems != 0 && !strcmp(newItem, **historyList))
1393 return;
1394 if (*nItems == HISTORY_LIST_MAX) {
1395 for (i=HISTORY_LIST_TRIM_TO; i<HISTORY_LIST_MAX; i++)
1396 NEditFree((*historyList)[i]);
1397 *nItems = HISTORY_LIST_TRIM_TO;
1398 }
1399 newList = (char **)NEditMalloc(sizeof(char *) * (*nItems + 1));
1400 for (i=0; i < *nItems; i++)
1401 newList[i+1] = (*historyList)[i];
1402 if (*nItems != 0 && *historyList != NULL)
1403 NEditFree(*historyList);
1404 (*nItems)++;
1405 newList[0] = NEditStrdup(newItem);
1406 *historyList = newList;
1407 }
1408
1409 /*
1410 ** BeginWait/EndWait
1411 **
1412 ** Display/Remove a watch cursor over topCursorWidget and its descendents
1413 */
BeginWait(Widget topCursorWidget)1414 void BeginWait(Widget topCursorWidget)
1415 {
1416 Display *display = XtDisplay(topCursorWidget);
1417 Pixmap pixmap;
1418 Pixmap maskPixmap;
1419 XColor xcolors[2];
1420 static Cursor waitCursor = 0;
1421
1422 /* if the watch cursor hasn't been created yet, create it */
1423 if (!waitCursor) {
1424 pixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1425 (char *)watch_bits, watch_width, watch_height);
1426
1427 maskPixmap = XCreateBitmapFromData(display, DefaultRootWindow(display),
1428 (char *)watch_mask_bits, watch_width, watch_height);
1429
1430 xcolors[0].pixel = BlackPixelOfScreen(DefaultScreenOfDisplay(display));
1431 xcolors[1].pixel = WhitePixelOfScreen(DefaultScreenOfDisplay(display));
1432
1433 XQueryColors(display, DefaultColormapOfScreen(
1434 DefaultScreenOfDisplay(display)), xcolors, 2);
1435 waitCursor = XCreatePixmapCursor(display, pixmap, maskPixmap,
1436 &xcolors[0], &xcolors[1], watch_x_hot, watch_y_hot);
1437 XFreePixmap(display, pixmap);
1438 XFreePixmap(display, maskPixmap);
1439 }
1440
1441 /* display the cursor */
1442 XDefineCursor(display, XtWindow(topCursorWidget), waitCursor);
1443 }
1444
BusyWait(Widget widget)1445 void BusyWait(Widget widget)
1446 {
1447 #ifdef __unix__
1448 static const int timeout = 100000; /* 1/10 sec = 100 ms = 100,000 us */
1449 static struct timeval last = { 0, 0 };
1450 struct timeval current;
1451 gettimeofday(¤t, NULL);
1452
1453 if ((current.tv_sec != last.tv_sec) ||
1454 (current.tv_usec - last.tv_usec > timeout))
1455 {
1456 XmUpdateDisplay(widget);
1457 last = current;
1458 }
1459 #else
1460 static time_t last = 0;
1461 time_t current;
1462 time(¤t);
1463
1464 if (difftime(current, last) > 0)
1465 {
1466 XmUpdateDisplay(widget);
1467 last = current;
1468 }
1469 #endif
1470 }
1471
EndWait(Widget topCursorWidget)1472 void EndWait(Widget topCursorWidget)
1473 {
1474 XUndefineCursor(XtDisplay(topCursorWidget), XtWindow(topCursorWidget));
1475 }
1476
1477 /*
1478 ** Create an X window geometry string from width, height, x, and y values.
1479 ** This is a complement to the X routine XParseGeometry, and uses the same
1480 ** bitmask values (XValue, YValue, WidthValue, HeightValue, XNegative, and
1481 ** YNegative) as defined in <X11/Xutil.h> and documented under XParseGeometry.
1482 ** It expects a string of at least MAX_GEOMETRY_STRING_LEN in which to write
1483 ** result. Note that in a geometry string, it is not possible to supply a y
1484 ** position without an x position. Also note that the X/YNegative flags
1485 ** mean "add a '-' and negate the value" which is kind of odd.
1486 */
CreateGeometryString(char * string,int x,int y,int width,int height,int bitmask)1487 void CreateGeometryString(char *string, int x, int y,
1488 int width, int height, int bitmask)
1489 {
1490 char *ptr = string;
1491 int nChars;
1492
1493 if (bitmask & WidthValue) {
1494 sprintf(ptr, "%d%n", width, &nChars);
1495 ptr += nChars;
1496 }
1497 if (bitmask & HeightValue) {
1498 sprintf(ptr, "x%d%n", height, &nChars);
1499 ptr += nChars;
1500 }
1501 if (bitmask & XValue) {
1502 if (bitmask & XNegative)
1503 sprintf(ptr, "-%d%n", -x, &nChars);
1504 else
1505 sprintf(ptr, "+%d%n", x, &nChars);
1506 ptr += nChars;
1507 }
1508 if (bitmask & YValue) {
1509 if (bitmask & YNegative)
1510 sprintf(ptr, "-%d%n", -y, &nChars);
1511 else
1512 sprintf(ptr, "+%d%n", y, &nChars);
1513 ptr += nChars;
1514 }
1515 *ptr = '\0';
1516 }
1517
1518 /*
1519 ** Remove the white space (blanks and tabs) from a string
1520 */
removeWhiteSpace(char * string)1521 static void removeWhiteSpace(char *string)
1522 {
1523 char *outPtr = string;
1524
1525 while (TRUE) {
1526 if (*string == 0) {
1527 *outPtr = 0;
1528 return;
1529 } else if (*string != ' ' && *string != '\t')
1530 *(outPtr++) = *(string++);
1531 else
1532 string++;
1533 }
1534 }
1535
1536 /*
1537 ** Compares two strings and return TRUE if the two strings
1538 ** are the same, ignoring whitespace and case differences.
1539 */
stripCaseCmp(const char * str1,const char * str2)1540 static int stripCaseCmp(const char *str1, const char *str2)
1541 {
1542 const char *c1, *c2;
1543
1544 for (c1=str1, c2=str2; *c1!='\0' && *c2!='\0'; c1++, c2++) {
1545 while (*c1 == ' ' || *c1 == '\t')
1546 c1++;
1547 while (*c2 == ' ' || *c2 == '\t')
1548 c2++;
1549 if (toupper((unsigned char)*c1) != toupper((unsigned char)*c2))
1550 return FALSE;
1551 }
1552 return *c1 == '\0' && *c2 == '\0';
1553 }
1554
warnHandlerCB(String message)1555 static void warnHandlerCB(String message)
1556 {
1557 if (strstr(message, "XtRemoveGrab"))
1558 return;
1559 if (strstr(message, "Attempt to remove non-existant passive grab"))
1560 return;
1561 fputs(message, stderr);
1562 fputc('\n', stderr);
1563 }
1564
getKeyboardMapping(Display * display)1565 static XModifierKeymap *getKeyboardMapping(Display *display) {
1566 static XModifierKeymap *keyboardMap = NULL;
1567
1568 if (keyboardMap == NULL) {
1569 keyboardMap = XGetModifierMapping(display);
1570 }
1571 return(keyboardMap);
1572 }
1573
1574 /*
1575 ** get mask for a modifier
1576 **
1577 */
1578
findModifierMapping(Display * display,KeyCode keyCode)1579 static Modifiers findModifierMapping(Display *display, KeyCode keyCode) {
1580 int i, j;
1581 KeyCode *mapentry;
1582 XModifierKeymap *modMap = getKeyboardMapping(display);
1583
1584 if (modMap == NULL || keyCode == 0) {
1585 return(0);
1586 }
1587
1588 mapentry = modMap->modifiermap;
1589 for (i = 0; i < 8; ++i) {
1590 for (j = 0; j < (modMap->max_keypermod); ++j) {
1591 if (keyCode == *mapentry) {
1592 return(1 << ((mapentry - modMap->modifiermap) / modMap->max_keypermod));
1593 }
1594 ++mapentry;
1595 }
1596 }
1597 return(0);
1598 }
1599
GetNumLockModMask(Display * display)1600 Modifiers GetNumLockModMask(Display *display) {
1601 static int numLockMask = -1;
1602
1603 if (numLockMask == -1) {
1604 numLockMask = findModifierMapping(display, XKeysymToKeycode(display, XK_Num_Lock));
1605 }
1606 return(numLockMask);
1607 }
1608
1609 /*
1610 ** Grab a key regardless of caps-lock and other silly latching keys.
1611 **
1612 */
1613
reallyGrabAKey(Widget dialog,int keyCode,Modifiers mask)1614 static void reallyGrabAKey(Widget dialog, int keyCode, Modifiers mask) {
1615 Modifiers numLockMask = GetNumLockModMask(XtDisplay(dialog));
1616
1617 if (keyCode == 0) /* No anykey grabs, sorry */
1618 return;
1619
1620 XtGrabKey(dialog, keyCode, mask, True, GrabModeAsync, GrabModeAsync);
1621 XtGrabKey(dialog, keyCode, mask|LockMask, True, GrabModeAsync, GrabModeAsync);
1622 if (numLockMask && numLockMask != LockMask) {
1623 XtGrabKey(dialog, keyCode, mask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1624 XtGrabKey(dialog, keyCode, mask|LockMask|numLockMask, True, GrabModeAsync, GrabModeAsync);
1625 }
1626 }
1627
1628 /*
1629 ** Part of dialog mnemonic processing. Search the widget tree under w
1630 ** for widgets with mnemonics. When found, add a passive grab to the
1631 ** dialog widget for the mnemonic character, thus directing mnemonic
1632 ** events to the dialog widget.
1633 */
addMnemonicGrabs(Widget dialog,Widget w,int unmodifiedToo)1634 static void addMnemonicGrabs(Widget dialog, Widget w, int unmodifiedToo)
1635 {
1636 char mneString[2];
1637 WidgetList children;
1638 Cardinal numChildren;
1639 int i, isMenu;
1640 KeySym mnemonic = '\0';
1641 unsigned char rowColType;
1642 unsigned int keyCode;
1643
1644 if (XtIsComposite(w)) {
1645 if (XtClass(w) == xmRowColumnWidgetClass) {
1646 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1647 isMenu = rowColType != XmWORK_AREA;
1648 } else
1649 isMenu = False;
1650 if (!isMenu) {
1651 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1652 &numChildren, NULL);
1653 for (i=0; i<(int)numChildren; i++)
1654 addMnemonicGrabs(dialog, children[i], unmodifiedToo);
1655 }
1656 } else {
1657 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1658 if (mnemonic != XK_VoidSymbol && mnemonic != '\0') {
1659 mneString[0] = mnemonic; mneString[1] = '\0';
1660 keyCode = XKeysymToKeycode(XtDisplay(dialog),
1661 XStringToKeysym(mneString));
1662 reallyGrabAKey(dialog, keyCode, Mod1Mask);
1663 if (unmodifiedToo)
1664 reallyGrabAKey(dialog, keyCode, 0);
1665 }
1666 }
1667 }
1668
1669 /*
1670 ** Callback routine for dialog mnemonic processing.
1671 */
mnemonicCB(Widget w,XtPointer callData,XKeyEvent * event)1672 static void mnemonicCB(Widget w, XtPointer callData, XKeyEvent *event)
1673 {
1674 findAndActivateMnemonic(w, event->keycode);
1675 }
1676
1677 /*
1678 ** Look for a widget in the widget tree w, with a mnemonic matching
1679 ** keycode. When one is found, simulate a button press on that widget
1680 ** and give it the keyboard focus. If the mnemonic is on a label,
1681 ** look in the userData field of the label to see if it points to
1682 ** another widget, and give that the focus. This routine is just
1683 ** sufficient for NEdit, no doubt it will need to be extended for
1684 ** mnemonics on widgets other than just buttons and text fields.
1685 */
findAndActivateMnemonic(Widget w,unsigned int keycode)1686 static void findAndActivateMnemonic(Widget w, unsigned int keycode)
1687 {
1688 WidgetList children;
1689 Cardinal numChildren;
1690 int i, isMenu;
1691 KeySym mnemonic = '\0';
1692 char mneString[2];
1693 Widget userData;
1694 unsigned char rowColType;
1695
1696 if (XtIsComposite(w)) {
1697 if (XtClass(w) == xmRowColumnWidgetClass) {
1698 XtVaGetValues(w, XmNrowColumnType, &rowColType, NULL);
1699 isMenu = rowColType != XmWORK_AREA;
1700 } else
1701 isMenu = False;
1702 if (!isMenu) {
1703 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1704 &numChildren, NULL);
1705 for (i=0; i<(int)numChildren; i++)
1706 findAndActivateMnemonic(children[i], keycode);
1707 }
1708 } else {
1709 XtVaGetValues(w, XmNmnemonic, &mnemonic, NULL);
1710 if (mnemonic != '\0') {
1711 mneString[0] = mnemonic; mneString[1] = '\0';
1712 if (XKeysymToKeycode(XtDisplay(XtParent(w)),
1713 XStringToKeysym(mneString)) == keycode) {
1714 if (XtClass(w) == xmLabelWidgetClass ||
1715 XtClass(w) == xmLabelGadgetClass) {
1716 XtVaGetValues(w, XmNuserData, &userData, NULL);
1717 if (userData!=NULL && XtIsWidget(userData) &&
1718 XmIsTraversable(userData))
1719 XmProcessTraversal(userData, XmTRAVERSE_CURRENT);
1720 } else if (XmIsTraversable(w)) {
1721 XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1722 SimulateButtonPress(w);
1723 }
1724 }
1725 }
1726 }
1727 }
1728
1729 /*
1730 ** Part of workaround for Motif Caps/Num Lock bug. Search the widget tree
1731 ** under w for widgets with accelerators. When found, add three passive
1732 ** grabs to topWidget, one for the accelerator keysym + modifiers + Caps
1733 ** Lock, one for Num Lock, and one for both, thus directing lock +
1734 ** accelerator events to topWidget.
1735 */
addAccelGrabs(Widget topWidget,Widget w)1736 static void addAccelGrabs(Widget topWidget, Widget w)
1737 {
1738 WidgetList children;
1739 Widget menu;
1740 Cardinal numChildren;
1741 int i;
1742
1743 if (XtIsComposite(w)) {
1744 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1745 &numChildren, NULL);
1746 for (i=0; i<(int)numChildren; i++)
1747 addAccelGrabs(topWidget, children[i]);
1748 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1749 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1750 if (menu != NULL)
1751 addAccelGrabs(topWidget, menu);
1752 } else
1753 addAccelGrab(topWidget, w);
1754 }
1755
1756 /*
1757 ** Grabs the key + modifier defined in the widget's accelerator resource,
1758 ** in combination with the Caps Lock and Num Lock accelerators.
1759 */
addAccelGrab(Widget topWidget,Widget w)1760 static void addAccelGrab(Widget topWidget, Widget w)
1761 {
1762 char *accelString = NULL;
1763 KeySym keysym;
1764 unsigned int modifiers;
1765 KeyCode code;
1766 Modifiers numLockMask = GetNumLockModMask(XtDisplay(topWidget));
1767
1768 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1769 if (accelString == NULL || *accelString == '\0') {
1770 NEditFree(accelString);
1771 return;
1772 }
1773
1774 if (!parseAccelString(XtDisplay(topWidget), accelString, &keysym, &modifiers)) {
1775 NEditFree(accelString);
1776 return;
1777 }
1778 NEditFree(accelString);
1779
1780 /* Check to see if this server has this key mapped. Some cruddy PC X
1781 servers (Xoftware) have terrible default keymaps. If not,
1782 XKeysymToKeycode will return 0. However, it's bad news to pass
1783 that to XtGrabKey because 0 is really "AnyKey" which is definitely
1784 not what we want!! */
1785
1786 code = XKeysymToKeycode(XtDisplay(topWidget), keysym);
1787 if (code == 0)
1788 return;
1789
1790 XtGrabKey(topWidget, code,
1791 modifiers | LockMask, True, GrabModeAsync, GrabModeAsync);
1792 if (numLockMask && numLockMask != LockMask) {
1793 XtGrabKey(topWidget, code,
1794 modifiers | numLockMask, True, GrabModeAsync, GrabModeAsync);
1795 XtGrabKey(topWidget, code,
1796 modifiers | LockMask | numLockMask, True, GrabModeAsync, GrabModeAsync);
1797 }
1798 }
1799
1800 /*
1801 ** Read a Motif accelerator string and translate it into a keysym + modifiers.
1802 ** Returns TRUE if the parse was successful, FALSE, if not.
1803 */
parseAccelString(Display * display,const char * string,KeySym * keySym,unsigned int * modifiers)1804 static int parseAccelString(Display *display, const char *string, KeySym *keySym,
1805 unsigned int *modifiers)
1806 {
1807 #define N_MODIFIERS 12
1808 /*... Is NumLock always Mod3? */
1809 static char *modifierNames[N_MODIFIERS] = {"Ctrl", "Shift", "Alt", "Mod2",
1810 "Mod3", "Mod4", "Mod5", "Button1", "Button2", "Button3", "Button4",
1811 "Button5"};
1812 static unsigned int modifierMasks[N_MODIFIERS] = {ControlMask, ShiftMask,
1813 Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask, Button1Mask, Button2Mask,
1814 Button3Mask, Button4Mask, Button5Mask};
1815 Modifiers numLockMask = GetNumLockModMask(display);
1816 char modStr[MAX_ACCEL_LEN];
1817 char evtStr[MAX_ACCEL_LEN];
1818 char keyStr[MAX_ACCEL_LEN];
1819 const char *c, *evtStart, *keyStart;
1820 int i;
1821
1822 if (strlen(string) >= MAX_ACCEL_LEN)
1823 return FALSE;
1824
1825 /* Get the modifier part */
1826 for (c = string; *c != '<'; c++)
1827 if (*c == '\0')
1828 return FALSE;
1829 strncpy(modStr, string, c - string);
1830 modStr[c - string] = '\0';
1831
1832 /* Verify the <key> or <keypress> part */
1833 evtStart = c;
1834 for ( ; *c != '>'; c++)
1835 if (*c == '\0')
1836 return FALSE;
1837 c++;
1838 strncpy(evtStr, evtStart, c - evtStart);
1839 evtStr[c - evtStart] = '\0';
1840 if (!stripCaseCmp(evtStr, "<key>") && !stripCaseCmp(evtStr, "<keypress>"))
1841 return FALSE;
1842
1843 /* Get the keysym part */
1844 keyStart = c;
1845 for ( ; *c != '\0' && !(c != keyStart && *c == ':'); c++);
1846 strncpy(keyStr, keyStart, c - keyStart);
1847 keyStr[c - keyStart] = '\0';
1848 *keySym = XStringToKeysym(keyStr);
1849
1850 /* Parse the modifier part */
1851 *modifiers = 0;
1852 c = modStr;
1853 while (*c != '\0') {
1854 while (*c == ' ' || *c == '\t')
1855 c++;
1856 if (*c == '\0')
1857 break;
1858 for (i = 0; i < N_MODIFIERS; i++) {
1859 if (!strncmp(c, modifierNames[i], strlen(modifierNames[i]))) {
1860 c += strlen(modifierNames[i]);
1861 if (modifierMasks[i] != numLockMask) {
1862 *modifiers |= modifierMasks[i];
1863 }
1864 break;
1865 }
1866 }
1867 if (i == N_MODIFIERS)
1868 return FALSE;
1869 }
1870
1871 return TRUE;
1872 }
1873
1874 /*
1875 ** Event handler for patching around Motif's lock + accelerator problem.
1876 ** Looks for a menu item in the patched menu hierarchy and invokes its
1877 ** ArmAndActivate action.
1878 */
lockCB(Widget w,XtPointer callData,XEvent * event,Boolean * continueDispatch)1879 static void lockCB(Widget w, XtPointer callData, XEvent *event,
1880 Boolean *continueDispatch)
1881 {
1882 Modifiers numLockMask = GetNumLockModMask(XtDisplay(w));
1883 Widget topMenuWidget = (Widget)callData;
1884 *continueDispatch = TRUE;
1885
1886 if (!(((XKeyEvent *)event)->state & (LockMask | numLockMask)))
1887 return;
1888
1889 if (findAndActivateAccel(topMenuWidget, ((XKeyEvent*) event)->keycode,
1890 ((XKeyEvent*) event)->state & ~(LockMask | numLockMask), event)) {
1891 *continueDispatch = FALSE;
1892 }
1893 }
1894
1895 /*
1896 ** Search through menu hierarchy under w and look for a button with
1897 ** accelerator matching keyCode + modifiers, and do its action
1898 */
findAndActivateAccel(Widget w,unsigned int keyCode,unsigned int modifiers,XEvent * event)1899 static int findAndActivateAccel(Widget w, unsigned int keyCode,
1900 unsigned int modifiers, XEvent *event)
1901 {
1902 WidgetList children;
1903 Widget menu;
1904 Cardinal numChildren;
1905 int i;
1906 char *accelString = NULL;
1907 KeySym keysym;
1908 unsigned int mods;
1909
1910 if (XtIsComposite(w)) {
1911 XtVaGetValues(w, XmNchildren, &children, XmNnumChildren,
1912 &numChildren, NULL);
1913 for (i=0; i<(int)numChildren; i++)
1914 if (findAndActivateAccel(children[i], keyCode, modifiers, event))
1915 return TRUE;
1916 } else if (XtClass(w) == xmCascadeButtonWidgetClass) {
1917 XtVaGetValues(w, XmNsubMenuId, &menu, NULL);
1918 if (menu != NULL)
1919 if (findAndActivateAccel(menu, keyCode, modifiers, event))
1920 return TRUE;
1921 } else {
1922 XtVaGetValues(w, XmNaccelerator, &accelString, NULL);
1923 if (accelString != NULL && *accelString != '\0') {
1924 if (!parseAccelString(XtDisplay(w), accelString, &keysym, &mods))
1925 return FALSE;
1926 if (keyCode == XKeysymToKeycode(XtDisplay(w), keysym) &&
1927 modifiers == mods) {
1928 if (XtIsSensitive(w)) {
1929 XtCallActionProc(w, "ArmAndActivate", event, NULL, 0);
1930 return TRUE;
1931 }
1932 }
1933 }
1934 }
1935 return FALSE;
1936 }
1937
1938 /*
1939 ** Global installation of mouse wheel actions for scrolled windows.
1940 */
InstallMouseWheelActions(XtAppContext context)1941 void InstallMouseWheelActions(XtAppContext context)
1942 {
1943 static XtActionsRec Actions[] = {
1944 {"scrolled-window-scroll-up", scrollUpAP},
1945 {"scrolled-window-page-up", pageUpAP},
1946 {"scrolled-window-scroll-down", scrollDownAP},
1947 {"scrolled-window-page-down", pageDownAP}
1948 };
1949
1950 XtAppAddActions(context, Actions, XtNumber(Actions));
1951 }
1952
1953 /*
1954 ** Add mouse wheel support to a specific widget, which must be the scrollable
1955 ** widget of a ScrolledWindow.
1956 */
AddMouseWheelSupport(Widget w)1957 void AddMouseWheelSupport(Widget w)
1958 {
1959 if (XmIsScrolledWindow(XtParent(w)))
1960 {
1961 static const char scrollTranslations[] =
1962 "Shift<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(1)\n"
1963 "Shift<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(1)\n"
1964 "Ctrl<Btn4Down>,<Btn4Up>: scrolled-window-page-up()\n"
1965 "Ctrl<Btn5Down>,<Btn5Up>: scrolled-window-page-down()\n"
1966 "<Btn4Down>,<Btn4Up>: scrolled-window-scroll-up(3)\n"
1967 "<Btn5Down>,<Btn5Up>: scrolled-window-scroll-down(3)\n";
1968 static XtTranslations trans_table = NULL;
1969
1970 if (trans_table == NULL)
1971 {
1972 trans_table = XtParseTranslationTable(scrollTranslations);
1973 }
1974 XtOverrideTranslations(w, trans_table);
1975 }
1976 }
1977
pageUpAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)1978 static void pageUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
1979 {
1980 Widget scrolledWindow, scrollBar;
1981 String al[1];
1982
1983 al[0] = "Up";
1984 scrolledWindow = XtParent(w);
1985 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
1986 if (scrollBar)
1987 XtCallActionProc(scrollBar, "PageUpOrLeft", event, al, 1) ;
1988 return;
1989 }
1990
pageDownAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)1991 static void pageDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
1992 {
1993 Widget scrolledWindow, scrollBar;
1994 String al[1];
1995
1996 al[0] = "Down";
1997 scrolledWindow = XtParent(w);
1998 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
1999 if (scrollBar)
2000 XtCallActionProc(scrollBar, "PageDownOrRight", event, al, 1) ;
2001 return;
2002 }
2003
scrollUpAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)2004 static void scrollUpAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2005 {
2006 Widget scrolledWindow, scrollBar;
2007 String al[1];
2008 int i, nLines;
2009
2010 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2011 return;
2012 al[0] = "Up";
2013 scrolledWindow = XtParent(w);
2014 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2015 if (scrollBar)
2016 for (i=0; i<nLines; i++)
2017 XtCallActionProc(scrollBar, "IncrementUpOrLeft", event, al, 1) ;
2018 return;
2019 }
2020
scrollDownAP(Widget w,XEvent * event,String * args,Cardinal * nArgs)2021 static void scrollDownAP(Widget w, XEvent *event, String *args, Cardinal *nArgs)
2022 {
2023 Widget scrolledWindow, scrollBar;
2024 String al[1];
2025 int i, nLines;
2026
2027 if (*nArgs == 0 || sscanf(args[0], "%d", &nLines) != 1)
2028 return;
2029 al[0] = "Down";
2030 scrolledWindow = XtParent(w);
2031 scrollBar = XtNameToWidget (scrolledWindow, "VertScrollBar");
2032 if (scrollBar)
2033 for (i=0; i<nLines; i++)
2034 XtCallActionProc(scrollBar, "IncrementDownOrRight", event, al, 1) ;
2035 return;
2036 }
2037
2038
2039 /*
2040 ** This is a disguisting hack to work around a bug in OpenMotif.
2041 ** OpenMotif's toggle button Select() action routine remembers the last radio
2042 ** button that was toggled (stored as global state) and refuses to take any
2043 ** action when that button is clicked again. It fails to detect that we may
2044 ** have changed the button state and that clicking that button could make
2045 ** sense. The result is that radio buttons may apparently get stuck, ie.
2046 ** it is not possible to directly select with the mouse the previously
2047 ** selected button without selection another radio button first.
2048 ** The workaround consist of faking a mouse click on the button that we
2049 ** toggled by calling the Arm, Select, and Disarm action procedures.
2050 **
2051 ** A minor remaining issue is the fact that, if the workaround is used,
2052 ** it is not possible to change the state without notifying potential
2053 ** XmNvalueChangedCallbacks. In practice, this doesn't seem to be a problem.
2054 **
2055 */
RadioButtonChangeState(Widget widget,Boolean state,Boolean notify)2056 void RadioButtonChangeState(Widget widget, Boolean state, Boolean notify)
2057 {
2058 /*
2059 The bug only exists in OpenMotif 2.1.x/2.2.[0-2]. Since it's quite hard
2060 to detect OpenMotif reliably, we make a rough cut by excluding Lesstif
2061 and all Motif versions >= 2.1.x and < 2.2.3.
2062 */
2063 #ifndef LESSTIF_VERSION
2064 #if XmVersion == 2001 || (XmVersion == 2002 && XmUPDATE_LEVEL < 3)
2065 /* save the widget with current focus in case it moves */
2066 Widget focusW, shellW = widget;
2067 while (shellW && !XtIsShell(shellW)) {
2068 shellW = XtParent(shellW);
2069 }
2070 focusW = XtGetKeyboardFocusWidget(shellW);
2071
2072 if (state && XtIsRealized(widget))
2073 {
2074 /*
2075 Simulate a mouse button click.
2076 The event attributes that matter are the event type and the
2077 coordinates. When the button is managed, the coordinates have to
2078 be inside the button. When the button is not managed, they have to
2079 be (0, 0) to make sure that the Select routine accepts the event.
2080 */
2081 XEvent ev;
2082 if (XtIsManaged(widget))
2083 {
2084 Position x, y;
2085 /* Calculate the coordinates in the same way as OM. */
2086 XtTranslateCoords(XtParent(widget), widget->core.x, widget->core.y,
2087 &x, &y);
2088 ev.xbutton.x_root = x + widget->core.border_width;
2089 ev.xbutton.y_root = y + widget->core.border_width;
2090 }
2091 else
2092 {
2093 ev.xbutton.x_root = 0;
2094 ev.xbutton.y_root = 0;
2095 }
2096 /* Default button bindings:
2097 ~c<Btn1Down>: Arm()
2098 ~c<Btn1Up>: Select() Disarm() */
2099 ev.xany.type = ButtonPress;
2100 XtCallActionProc(widget, "Arm", &ev, NULL, 0);
2101 ev.xany.type = ButtonRelease;
2102 XtCallActionProc(widget, "Select", &ev, NULL, 0);
2103 XtCallActionProc(widget, "Disarm", &ev, NULL, 0);
2104 }
2105 /* restore focus to the originator */
2106 if (focusW) {
2107 XtSetKeyboardFocus(shellW, focusW);
2108 }
2109 #endif /* XmVersion == 2001 || ... */
2110 #endif /* LESSTIF_VERSION */
2111
2112 /* This is sufficient on non-OM platforms */
2113 XmToggleButtonSetState(widget, state, notify);
2114 }
2115
2116 /* Workaround for bug in OpenMotif 2.1 and 2.2. If you have an active tear-off
2117 ** menu from a TopLevelShell that is a child of an ApplicationShell, and then
2118 ** close the parent window, Motif crashes. The problem doesn't
2119 ** happen if you close the tear-offs first, so, we programatically close them
2120 ** before destroying the shell widget.
2121 */
CloseAllPopupsFor(Widget shell)2122 void CloseAllPopupsFor(Widget shell)
2123 {
2124 #ifndef LESSTIF_VERSION
2125 /* Doesn't happen in LessTif. The tear-off menus are popup-children of
2126 * of the TopLevelShell there, which just works. Motif wants to make
2127 * them popup-children of the ApplicationShell, where it seems to get
2128 * into trouble. */
2129
2130 Widget app = XtParent(shell);
2131 int i;
2132
2133 for (i = 0; i < app->core.num_popups; i++) {
2134 Widget pop = app->core.popup_list[i];
2135 Widget shellFor;
2136
2137 XtVaGetValues(pop, XtNtransientFor, &shellFor, NULL);
2138 if (shell == shellFor)
2139 _XmDismissTearOff(pop, NULL, NULL);
2140 }
2141 #endif
2142 }
2143
queryDesktop(Display * display,Window window,Atom deskTopAtom)2144 static long queryDesktop(Display *display, Window window, Atom deskTopAtom)
2145 {
2146 long deskTopNumber = 0;
2147 Atom actualType;
2148 int actualFormat;
2149 unsigned long nItems, bytesAfter;
2150 unsigned char *prop;
2151
2152 if (XGetWindowProperty(display, window, deskTopAtom, 0, 1,
2153 False, AnyPropertyType, &actualType, &actualFormat, &nItems,
2154 &bytesAfter, &prop) != Success) {
2155 return -1; /* Property not found */
2156 }
2157
2158 if (actualType == None) {
2159 return -1; /* Property does not exist */
2160 }
2161
2162 if (actualFormat != 32 || nItems != 1) {
2163 XFree((char*)prop);
2164 return -1; /* Wrong format */
2165 }
2166
2167 deskTopNumber = *(long*)prop;
2168 XFree((char*)prop);
2169 return deskTopNumber;
2170 }
2171
2172 /*
2173 ** Returns the current desktop number, or -1 if no desktop information
2174 ** is available.
2175 */
QueryCurrentDesktop(Display * display,Window rootWindow)2176 long QueryCurrentDesktop(Display *display, Window rootWindow)
2177 {
2178 static Atom currentDesktopAtom = (Atom)-1;
2179
2180 if (currentDesktopAtom == (Atom)-1)
2181 currentDesktopAtom = XInternAtom(display, "_NET_CURRENT_DESKTOP", True);
2182
2183 if (currentDesktopAtom != None)
2184 return queryDesktop(display, rootWindow, currentDesktopAtom);
2185
2186 return -1; /* No desktop information */
2187 }
2188
2189 /*
2190 ** Returns the number of the desktop the given shell window is currently on,
2191 ** or -1 if no desktop information is available. Note that windows shown
2192 ** on all desktops (sometimes called sticky windows) should return 0xFFFFFFFF.
2193 */
QueryDesktop(Display * display,Widget shell)2194 long QueryDesktop(Display *display, Widget shell)
2195 {
2196 static Atom wmDesktopAtom = (Atom)-1;
2197
2198 if (wmDesktopAtom == (Atom)-1)
2199 wmDesktopAtom = XInternAtom(display, "_NET_WM_DESKTOP", True);
2200
2201 if (wmDesktopAtom != None)
2202 return queryDesktop(display, XtWindow(shell), wmDesktopAtom);
2203
2204 return -1; /* No desktop information */
2205 }
2206
2207
2208 /*
2209 ** Clipboard wrapper functions that call the Motif clipboard functions
2210 ** a number of times before giving up. The interfaces are similar to the
2211 ** native Motif functions.
2212 */
2213
2214 #define SPINCOUNT 10 /* Try at most 10 times */
2215 #define USLEEPTIME 1000 /* 1 ms between retries */
2216
2217 /*
2218 ** Warning reporting
2219 */
warning(const char * mesg)2220 static void warning(const char* mesg)
2221 {
2222 fprintf(stderr, "NEdit warning:\n%s\n", mesg);
2223 }
2224
2225 /*
2226 ** Sleep routine
2227 */
microsleep(long usecs)2228 static void microsleep(long usecs)
2229 {
2230 static struct timeval timeoutVal;
2231 timeoutVal.tv_sec = usecs/1000000;
2232 timeoutVal.tv_usec = usecs - timeoutVal.tv_sec*1000000;
2233 select(0, NULL, NULL, NULL, &timeoutVal);
2234 }
2235
2236 /*
2237 ** XmClipboardStartCopy spinlock wrapper.
2238 */
SpinClipboardStartCopy(Display * display,Window window,XmString clip_label,Time timestamp,Widget widget,XmCutPasteProc callback,long * item_id)2239 int SpinClipboardStartCopy(Display *display, Window window,
2240 XmString clip_label, Time timestamp, Widget widget,
2241 XmCutPasteProc callback, long *item_id)
2242 {
2243 int i, res;
2244 for (i=0; i<SPINCOUNT; ++i) {
2245 res = XmClipboardStartCopy(display, window, clip_label, timestamp,
2246 widget, callback, item_id);
2247 if (res == XmClipboardSuccess) {
2248 return res;
2249 }
2250 microsleep(USLEEPTIME);
2251 }
2252 warning("XmClipboardStartCopy() failed: clipboard locked.");
2253 return res;
2254 }
2255
2256 /*
2257 ** XmClipboardCopy spinlock wrapper.
2258 */
SpinClipboardCopy(Display * display,Window window,long item_id,char * format_name,XtPointer buffer,unsigned long length,long private_id,long * data_id)2259 int SpinClipboardCopy(Display *display, Window window, long item_id,
2260 char *format_name, XtPointer buffer, unsigned long length,
2261 long private_id, long *data_id)
2262 {
2263 int i, res;
2264 for (i=0; i<SPINCOUNT; ++i) {
2265 res = XmClipboardCopy(display, window, item_id, format_name,
2266 buffer, length, private_id, data_id);
2267 if (res == XmClipboardSuccess) {
2268 return res;
2269 }
2270 if (res == XmClipboardFail) {
2271 warning("XmClipboardCopy() failed: XmClipboardStartCopy not "
2272 "called or too many formats.");
2273 return res;
2274 }
2275 microsleep(USLEEPTIME);
2276 }
2277 warning("XmClipboardCopy() failed: clipboard locked.");
2278 return res;
2279 }
2280
2281 /*
2282 ** XmClipboardEndCopy spinlock wrapper.
2283 */
SpinClipboardEndCopy(Display * display,Window window,long item_id)2284 int SpinClipboardEndCopy(Display *display, Window window, long item_id)
2285 {
2286 int i, res;
2287 for (i=0; i<SPINCOUNT; ++i) {
2288 res = XmClipboardEndCopy(display, window, item_id);
2289 if (res == XmClipboardSuccess) {
2290 return res;
2291 }
2292 if (res == XmClipboardFail) {
2293 warning("XmClipboardEndCopy() failed: XmClipboardStartCopy not "
2294 "called.");
2295 return res;
2296 }
2297 microsleep(USLEEPTIME);
2298 }
2299 warning("XmClipboardEndCopy() failed: clipboard locked.");
2300 return res;
2301 }
2302
2303 /*
2304 ** XmClipboardInquireLength spinlock wrapper.
2305 */
SpinClipboardInquireLength(Display * display,Window window,char * format_name,unsigned long * length)2306 int SpinClipboardInquireLength(Display *display, Window window,
2307 char *format_name, unsigned long *length)
2308 {
2309 int i, res;
2310 for (i=0; i<SPINCOUNT; ++i) {
2311 res = XmClipboardInquireLength(display, window, format_name, length);
2312 if (res == XmClipboardSuccess) {
2313 return res;
2314 }
2315 if (res == XmClipboardNoData) {
2316 return res;
2317 }
2318 microsleep(USLEEPTIME);
2319 }
2320 warning("XmClipboardInquireLength() failed: clipboard locked.");
2321 return res;
2322 }
2323
2324 /*
2325 ** XmClipboardRetrieve spinlock wrapper.
2326 */
SpinClipboardRetrieve(Display * display,Window window,char * format_name,XtPointer buffer,unsigned long length,unsigned long * num_bytes,long * private_id)2327 int SpinClipboardRetrieve(Display *display, Window window, char *format_name,
2328 XtPointer buffer, unsigned long length, unsigned long *num_bytes,
2329 long *private_id)
2330 {
2331 int i, res;
2332 for (i=0; i<SPINCOUNT; ++i) {
2333 res = XmClipboardRetrieve(display, window, format_name, buffer,
2334 length, num_bytes, private_id);
2335 if (res == XmClipboardSuccess) {
2336 return res;
2337 }
2338 if (res == XmClipboardTruncate) {
2339 warning("XmClipboardRetrieve() failed: buffer too small.");
2340 return res;
2341 }
2342 if (res == XmClipboardNoData) {
2343 return res;
2344 }
2345 microsleep(USLEEPTIME);
2346 }
2347 warning("XmClipboardRetrieve() failed: clipboard locked.");
2348 return res;
2349 }
2350
2351 /*
2352 ** XmClipboardLock spinlock wrapper.
2353 */
SpinClipboardLock(Display * display,Window window)2354 int SpinClipboardLock(Display *display, Window window)
2355 {
2356 int i, res;
2357 for (i=0; i<SPINCOUNT; ++i) {
2358 res = XmClipboardLock(display, window);
2359 if (res == XmClipboardSuccess) {
2360 return res;
2361 }
2362 microsleep(USLEEPTIME);
2363 }
2364 warning("XmClipboardLock() failed: clipboard locked.");
2365 return res;
2366 }
2367
2368 /*
2369 ** XmClipboardUnlock spinlock wrapper.
2370 */
SpinClipboardUnlock(Display * display,Window window)2371 int SpinClipboardUnlock(Display *display, Window window)
2372 {
2373 int i, res;
2374 /* Spinning doesn't make much sense in this case, I think. */
2375 for (i=0; i<SPINCOUNT; ++i) {
2376 /* Remove ALL locks (we don't use nested locking in NEdit) */
2377 res = XmClipboardUnlock(display, window, True);
2378 if (res == XmClipboardSuccess) {
2379 return res;
2380 }
2381 microsleep(USLEEPTIME);
2382 }
2383 /*
2384 * This warning doesn't make much sense in practice. It's usually
2385 * triggered when we try to unlock the clipboard after a failed clipboard
2386 * operation, in an attempt to work around possible *tif clipboard locking
2387 * bugs. In these cases, failure _is_ the expected outcome and the warning
2388 * is bogus. Therefore, the warning is disabled.
2389 warning("XmClipboardUnlock() failed: clipboard not locked or locked "
2390 "by another application.");
2391 */
2392 return res;
2393 }
2394
2395 /*
2396 ** Send a client message to a EWMH/NetWM compatible X Window Manager.
2397 ** Code taken from wmctrl-1.07 (GPL licensed)
2398 */
WmClientMsg(Display * disp,Window win,const char * msg,unsigned long data0,unsigned long data1,unsigned long data2,unsigned long data3,unsigned long data4)2399 void WmClientMsg(Display *disp, Window win, const char *msg,
2400 unsigned long data0, unsigned long data1,
2401 unsigned long data2, unsigned long data3,
2402 unsigned long data4)
2403 {
2404 XEvent event;
2405 long mask = SubstructureRedirectMask | SubstructureNotifyMask;
2406
2407 event.xclient.type = ClientMessage;
2408 event.xclient.serial = 0;
2409 event.xclient.send_event = True;
2410 event.xclient.message_type = XInternAtom(disp, msg, False);
2411 event.xclient.window = win;
2412 event.xclient.format = 32;
2413 event.xclient.data.l[0] = data0;
2414 event.xclient.data.l[1] = data1;
2415 event.xclient.data.l[2] = data2;
2416 event.xclient.data.l[3] = data3;
2417 event.xclient.data.l[4] = data4;
2418
2419 if (!XSendEvent(disp, DefaultRootWindow(disp), False, mask, &event)) {
2420 fprintf(stderr, "nedit: cannot send %s EWMH event.\n", msg);
2421 }
2422 }
2423