UNIXworkcode

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