X11 Work Bench Toolkit  1.0
menu_bar.c
Go to the documentation of this file.
1 
2 // _ //
3 // _ __ ___ ___ _ __ _ _ | |__ __ _ _ __ ___ //
4 // | '_ ` _ \ / _ \| '_ \ | | | | | '_ \ / _` || '__|/ __| //
5 // | | | | | || __/| | | || |_| | | |_) || (_| || | _| (__ //
6 // |_| |_| |_| \___||_| |_| \__,_|_____|_.__/ \__,_||_|(_)\___| //
7 // |_____| //
8 // //
9 // generic menu bar implementation //
10 // //
12 
13 /*****************************************************************************
14 
15  X11workbench - X11 programmer's 'work bench' application and toolkit
16  Copyright (c) 2010-2016 by Bob Frazier (aka 'Big Bad Bombastic Bob')
17  all rights reserved
18 
19  DISCLAIMER: The X11workbench application and toolkit software are supplied
20  'as-is', with no warranties, either implied or explicit.
21  Any claims to alleged functionality or features should be
22  considered 'preliminary', and might not function as advertised.
23 
24  BSD-like license:
25 
26  There is no restriction as to what you can do with this software, so long
27  as you include the above copyright notice and DISCLAIMER for any distributed
28  work that is equal to or derived from this one, along with this paragraph
29  that explains the terms of the license if the source is also being made
30  available. A "derived work" describes a work that uses a significant portion
31  of the source files or algorithms that are included with this one.
32  Specifically excluded from this are files that were generated by the software,
33  or anything that is included with the software that is part of another package
34  (such as files that were created or added during the 'configure' process).
35  Specifically included is the use of part or all of any of the X11 workbench
36  toolkit source or header files in your distributed application. If you do not
37  ship the source, the above copyright statement is still required to be placed
38  in a reasonably prominent place, such as documentation, splash screens, and/or
39  'about the application' dialog boxes.
40 
41  Use and distribution are in accordance with GPL, LGPL, and/or the above
42  BSD-like license. See COPYING and README files for more information.
43 
44 
45  Additional information at http://sourceforge.net/projects/X11workbench
46 
47 ******************************************************************************/
48 
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <unistd.h>
61 #include <memory.h>
62 #include <string.h>
63 #include <strings.h>
64 #include <signal.h>
65 #include <time.h>
66 #include <X11/cursorfont.h>
67 
68 #include "window_helper.h"
69 #include "pixmap_helper.h" // pixmap helpers, including pre-defined icons
70 #include "frame_window.h"
71 #include "menu_bar.h"
72 #include "menu_popup.h"
73 #include "conf_help.h"
74 
75 
76 // sample menu resource format
77 //
78 // 1\n
79 // _File\tpopup\t2\n
80 // \tseparator\n
81 // _Help\t100\n
82 //
83 // 2\tpopup\n
84 // _Open\t102\Open File\n
85 // _Save\t103\Save File\n
86 // \tseparator\n
87 // E_xit\t104\Close Application\n
88 //
89 // this describes 2 separate menu resource strings. The first has ID 1, the 2nd has ID 2.
90 // The ID values are relative only to the menu itself.
91 
92 // generic description of format:
93 // <menu ID>[<tab>popup]<newline>
94 // <menu text><tab>[separator|popup<tab><submenu ID>|<notify ID>]<newline>
95 // <menu text><tab>[separator|popup<tab><submenu ID>|<notify ID>]<newline>
96 // (etc.)
97 //
98 
99 int MBMenuParentEvent(Window wIDMenu, XEvent *pEvent); // callback for parent windows that attach this menu
100 int MBMenuWinEvent(Window wID, XEvent *pEvent); // callback for menu events
101 
102 // message handlers
103 static int MenuBarDoExposeEvent(XExposeEvent *pEvent, WBMenu *pMenu, Display *pDisplay,
104  Window wID, WBMenuBarWindow *pSelf);
105 
106 
107 #define BORDER 32 /* was 1 */
108 #define MENU_FONT "fixed"
109 #define MENU_ALT_FONT "*-fixed-*"
110 #define MENU_FONT_SIZE 13
111 
112 // global color definitions
115 
116 static int iInitColorFlag = 0;
117 static XFontStruct *pDefaultMenuFont = NULL; // default menu font
118 
130 Atom aMENU_RESIZE = 0;
131 
150 Atom aMENU_ACTIVATE = 0;
151 
166 
167 
168 // for release code, define DEBUG_VALIDATE(X) as X
169 #ifdef NO_DEBUG
170 #define DEBUG_VALIDATE(X) X
171 #else // NO_DEBUG
172 #define DEBUG_VALIDATE(X) if(!(X)) { WB_WARN_PRINT("%s:%d - %s\n", __FUNCTION__, __LINE__, "WARNING - " #X " failed!\n"); }
173 #endif // NO_DEBUG
174 
175 #define LOAD_COLOR(X,Y,Z) if(CHGetResourceString(WBGetDefaultDisplay(), X, Y, sizeof(Y)) <= 0){ WB_WARN_PRINT("%s - WARNING: can't find color %s, using default value %s\n", __FUNCTION__, X, Z); strcpy(Y,Z); }
176 
177 int MBInitGlobal(void)
178 {
179  Colormap colormap;
180 
181  if(!pDefaultMenuFont)
182  {
183 // pDefaultMenuFont = XLoadQueryFont(WBGetDefaultDisplay(), MENU_FONT);
184  pDefaultMenuFont = WBLoadFont(WBGetDefaultDisplay(), MENU_FONT, MENU_FONT_SIZE, WBFontFlag_WT_BOLD);
185  if(!pDefaultMenuFont)
186  {
187  // sometimes there is no font alias, so try again
188  pDefaultMenuFont = WBLoadFont(WBGetDefaultDisplay(), MENU_ALT_FONT, MENU_FONT_SIZE, WBFontFlag_WT_BOLD);
189 
190  if(!pDefaultMenuFont) // still nothing?
191  {
192  pDefaultMenuFont = WBLoadFont(WBGetDefaultDisplay(), "*", MENU_FONT_SIZE,
194  if(!pDefaultMenuFont) // still nothing?
195  {
196  pDefaultMenuFont = WBLoadFont(WBGetDefaultDisplay(), "*", MENU_FONT_SIZE,
198  if(!pDefaultMenuFont) // still nothing?
199  {
200  pDefaultMenuFont = WBLoadFont(WBGetDefaultDisplay(), MENU_FONT, MENU_FONT_SIZE, 0); // "ANY" style
201 
202  if(!pDefaultMenuFont) // still nothing?
203  {
204  pDefaultMenuFont = WBLoadFont(WBGetDefaultDisplay(), "*", MENU_FONT_SIZE,
206  if(!pDefaultMenuFont) // still nothing? REALLY????
207  {
208  pDefaultMenuFont = WBLoadFont(WBGetDefaultDisplay(), "*", MENU_FONT_SIZE, 0); // just give me something THAT size
209  }
210  }
211  }
212  }
213  }
214  }
215  }
216 
217  if(!pDefaultMenuFont)
218  {
219  pDefaultMenuFont = WBCopyFont(WBGetDefaultFont());
220 
221  if(!pDefaultMenuFont)
222  {
223  // MY give up. ~facepalm~
224 
225  WB_ERROR_PRINT("%s - no font, MENU FONT is %s,%s %d (menus will not be displayed)\n", __FUNCTION__, MENU_FONT, MENU_ALT_FONT, 13);
226  WBDumpFontInfo("*");
227 
228  return 0;
229  }
230  }
231 
232  if(!iInitColorFlag)
233  {
234  char szMenuFG[16], szMenuBG[16], szMenuActiveFG[16], szMenuActiveBG[16],
235  szMenuBorder1[16], szMenuActiveDisabledFG[16], szMenuDisabledFG[16];
236  static const char*szMenuBorder2="#FFFFFF", *szMenuBorder3="#9C9A94";
237 
238  colormap = DefaultColormap(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
239  LOAD_COLOR("*Menu.foreground",szMenuFG,"#000000");
240  LOAD_COLOR("*Menu.background",szMenuBG,"#DCDAD5");
241  LOAD_COLOR("*Menu.activeForeground",szMenuActiveFG,"#FFFFFF");
242  LOAD_COLOR("*Menu.activeBackground",szMenuActiveBG,"#4B6983");
243  LOAD_COLOR("*Menu.disabledForeground",szMenuDisabledFG,"#808080"); // TODO: verify these Menu.xxx strings for DISABLED
244  LOAD_COLOR("*Menu.disabledForeground",szMenuActiveDisabledFG,"#808080");
245  LOAD_COLOR("*borderColor",szMenuBorder1,"#000000");
246 
247  // NOTE: 'DEBUG_VALIDATE' (defined above) simply validates the return and prints a message if it failed.
248  // in a release build, the code is still executed, but no error checks are performed on the return value
249 
250  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuFG, &clrMenuFG));
251  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuFG)); // NOTE: do I need 'XFreeColors' for these ?
252 
253  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuBG, &clrMenuBG));
254  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuBG));
255 
256  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuActiveFG, &clrMenuActiveFG));
257  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuActiveFG)); // NOTE: do I need 'XFreeColors' for these ?
258 
259  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuActiveBG, &clrMenuActiveBG));
260  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuActiveBG));
261 
262  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuDisabledFG, &clrMenuDisabledFG));
263  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuDisabledFG));
264 
265  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuActiveDisabledFG, &clrMenuActiveDisabledFG));
266  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuActiveDisabledFG));
267 
268  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuBorder1, &clrMenuBorder1));
269  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuBorder1));
270 
271  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuBorder2, &clrMenuBorder2));
272  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuBorder2));
273 
274  DEBUG_VALIDATE(XParseColor(WBGetDefaultDisplay(), colormap, szMenuBorder3, &clrMenuBorder3));
275  DEBUG_VALIDATE(XAllocColor(WBGetDefaultDisplay(), colormap, &clrMenuBorder3));
276 
277  // TODO: make sure I was able to actually allocate these colors
278 
279  iInitColorFlag = 1;
280  }
281 
282  if(aMENU_RESIZE == None)
283  {
284  aMENU_RESIZE = WBGetAtom(WBGetDefaultDisplay(), "WB_MENU_RESIZE");
285  }
286 
287  if(aMENU_ACTIVATE == None)
288  {
289  aMENU_ACTIVATE = WBGetAtom(WBGetDefaultDisplay(), "WB_MENU_ACTIVATE");
290  }
291 
292  if(aMENU_DISPLAY_POPUP == None)
293  {
294  aMENU_DISPLAY_POPUP = WBGetAtom(WBGetDefaultDisplay(), "WB_MENU_DISPLAY_POPUP");
295  }
296 
297  return 1;
298 }
299 
300 XFontStruct *MBGetDefaultMenuFont(void)
301 {
302  return pDefaultMenuFont;
303 }
304 
305 WBMenuBarWindow *MBCreateMenuBarWindow(Window wIDParent, const char *pszResource,
306 // int iX, int iY, int *piWidth, int *piHeight,
307  int iFlags)
308 {
309  Display *pDisplay;
310  WBMenuBarWindow *pRval = NULL;
311  unsigned long fg, bg, bd; /* Pixel values */
312  XSetWindowAttributes xswa; /* Temporary Set Window Attribute struct */
313  XSizeHints xsh; /* Size hints for window manager - width of owner + 2 * height of font */
314  XWMHints xwmh;
315  WB_RECT rct;
316  XFontStruct *pFS;
317 
318 
319  // initialize global menu objects
320  if(!MBInitGlobal())
321  return NULL;
322 
323  if(wIDParent > 0)
324  pDisplay = WBGetWindowDisplay(wIDParent);
325  else
326  pDisplay = WBGetDefaultDisplay();
327 
328  // step 1: create the window
329 
330  bd = clrMenuBorder1.pixel; // BlackPixel(pDisplay, DefaultScreen(pDisplay));
331  fg = clrMenuFG.pixel;// BlackPixel(pDisplay, DefaultScreen(pDisplay)); // black foreground for menus (always)
332  bg = clrMenuBG.pixel;// WhitePixel(pDisplay, DefaultScreen(pDisplay)); // white background for menus (for now)
333 
334  WBGetClientRect(wIDParent, &rct);
335  pFS = WBGetWindowFontStruct(wIDParent);
336 
337  if(!pDefaultMenuFont && !pFS)
338  {
339  WB_ERROR_PRINT("%s - * BUG * no font!\n", __FUNCTION__);
340  return 0;
341  }
342  else if(pDefaultMenuFont)
343  pFS = pDefaultMenuFont;
344 
345 
346  // set size hints to match client area, upper left corner (always)
347  xsh.flags = (PPosition | PSize);
348  xsh.x = rct.left;
349  xsh.y = rct.top;
350  xsh.width = rct.right - rct.left;
351  if(!pFS)
352  xsh.height = 32;
353  else
354  xsh.height = 2 * (pFS->max_bounds.ascent + pFS->max_bounds.descent);
355 
356  if(xsh.height > (rct.bottom - rct.top))
357  xsh.height = rct.bottom - rct.top;
358 
359  memset(&xswa, 0, sizeof(xswa));
360 
361  xswa.border_pixel = bd;
362  xswa.background_pixel = bg;
363  xswa.colormap = DefaultColormap(pDisplay, DefaultScreen(pDisplay));
364  xswa.bit_gravity = CenterGravity;
365 
366  pRval = (WBMenuBarWindow *)WBAlloc(sizeof(*pRval));
367 
368  if(!pRval)
369  return NULL;
370 
371  pRval->ulTag = MENU_WINDOW_TAG;
372 
373  pRval->pMenu = MBCreateMenu(-1, 0, pszResource, WBMENU_RESERVE_DEFAULT);
374 
375  if(!pRval->pMenu)
376  {
377  WB_WARN_PRINT("%s - WARNING: pMenu is NULL in WBMenuBarWindow object\n", __FUNCTION__);
378  }
379 
380  pRval->wSelf = WBCreateWindow(pDisplay, wIDParent, MBMenuWinEvent, "MenuBar",
381  xsh.x, xsh.y, xsh.width, xsh.height, 0,
382  InputOutput,
383  CWBorderPixel | CWBackPixel | CWColormap | CWBitGravity,
384  &xswa);
385 
386  if(pRval->wSelf == -1)
387  {
388  WBFree(pRval);
389  return NULL;
390  }
391 
392  pRval->wOwner = wIDParent;
393  pRval->iPrevSel = pRval->iSelected = -1; // 'none'
394  pRval->iFlags = iFlags; // make a copy of them (for now)
395 
396  // calculate the initial position and height of the menu bar within the window
397  pRval->iX = xsh.x + 4;
398  pRval->iY = xsh.y + 4;
399  pRval->iWidth = xsh.width - 8;
400  pRval->iHeight = xsh.height - 8;
401 
402  // establish this window as NEVER getting the input focus
403  bzero(&xwmh, sizeof(xwmh));
404  xwmh.flags = InputHint;
405  xwmh.input = 0; // this represents 'None' (or 'Globally Active' if WM_TAKE_FOCUS is present)
406 
407  WBSetWMProperties(pRval->wSelf, NULL, NULL, &xwmh, NULL);
408 
409  WBSetWindowDefaultCursor(pRval->wSelf, XC_hand2); //XC_top_left_arrow);
410  WBSetWindowData(pRval->wSelf, 0, pRval); // a pointer back to me. must do before next part...
411  WBRegisterMenuCallback(pRval->wSelf, MBMenuParentEvent);
412 
413  WBCreateWindowDefaultGC(pRval->wSelf, fg, bg);
414 // WBSetWindowFontStruct(pRval->wSelf, XLoadQueryFont(WBGetDefaultDisplay(), MENU_FONT));
415  WBSetWindowFontStruct(pRval->wSelf, WBCopyFont(pFS));
416 
417  // TODO: should I _NOT_ do this if I clear the input focus flag?
418  XSelectInput(pDisplay, pRval->wSelf,
420 
421  WBAddMenuWindow(wIDParent, pRval->wSelf); // add this menu to this window
422  // this handles menu hotkeys and resize events for me
423 // {
424 // Atom a1, a2;
425 // a1 = XInternAtom(pDisplay, "_NET_WM_WINDOW_TYPE", False);
426 // a2 = XInternAtom(pDisplay, "_NET_WM_WINDOW_TYPE_MENU", False);
427 // XChangeProperty(pDisplay, pRval->wSelf, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)&a2, 1);
428 // a1 = XInternAtom(pDisplay, "WM_TRANSIENT_FOR", False);
429 // XChangeProperty(pDisplay, pRval->wSelf, a1, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wIDParent, 1);
430 // }
431 
432  WBMapWindow(pDisplay, pRval->wSelf); // make window visible
433 
434 
435  // TODO: set up font, GS, callback, etc.
436 
437 
438  return(pRval);
439 }
440 
441 static int __FindMenuBarWindowCallback(Window wID, void *pData)
442 {
444 
445  if(pMB && (void *)(pMB->pMenu) == pData)
446  {
447  return 1;
448  }
449 
450  return 0;
451 }
452 
453 WBMenuBarWindow *MBFindMenuBarWindow(WBMenu *pMenu) // find first (active) window that uses 'pMenu'
454 {
455  Window wID = WBLocateWindow(__FindMenuBarWindowCallback, (void *)pMenu);
456 
457  if(wID)
458  {
459  return MBGetMenuBarWindowStruct(wID);
460  }
461 
462  return NULL;
463 }
464 
465 void MBReCalcMenuBarWindow(WBMenuBarWindow *pMenuBar /*, int iX, int iY, int *piWidth, int *piHeight*/)
466 {
467  // TODO: update structure's position/size cache?
468 
469  WBInvalidateGeom(pMenuBar->wSelf, NULL, 1); // force a re-paint
470 }
471 
473 {
474  if(!pMenuBar || pMenuBar->ulTag != MENU_WINDOW_TAG)
475  {
476  return;
477  }
478 
479  // TODO: any special notifications??
480 
481  if(pMenuBar->pMenu)
482  {
483  MBDestroyMenu(pMenuBar->pMenu); // TODO: make this do reference counting?
484  pMenuBar->pMenu = NULL; // by convention, do this
485  }
486 
487  WBDestroyWindow(pMenuBar->wSelf);
488  WBFree(pMenuBar);
489 }
490 
491 void MBSetMenuBarMenuResource(WBMenuBarWindow *pMenuBar, const char *pszResource)
492 {
493 WBMenu *pMenu;
494 
495  if(!pMenuBar || pMenuBar->ulTag != MENU_WINDOW_TAG)
496  {
497  return;
498  }
499 
500  pMenu = MBCreateMenu(-1, 0, pszResource, WBMENU_RESERVE_DEFAULT);
501 
502  if(!pMenu)
503  {
504  WB_ERROR_PRINT("ERROR: %s - unable to create menu\n", __FUNCTION__);
505  return;
506  }
507 
508  if(pMenuBar->pMenu)
509  {
510  MBDestroyMenu(pMenuBar->pMenu);
511  }
512 
513  pMenuBar->pMenu = pMenu;
514 
515  pMenuBar->iSelected = -1;
516  pMenuBar->iPrevSel = -1;
517 
518  MBReCalcMenuBarWindow(pMenuBar);
519 }
520 
521 
522 // this next callback is assigned via WBRegisterMenuCallback
523 int MBMenuParentEvent(Window wIDMenu, XEvent *pEvent)
524 {
525  Display *pDisplay = WBGetWindowDisplay(wIDMenu);
526  WBMenuBarWindow *pSelf = MBGetMenuBarWindowStruct(wIDMenu);
527 
528  if(!pSelf)
529  {
530  WB_ERROR_PRINT("%s - pSelf is NULL\n", __FUNCTION__);
531  return 0;
532  }
533 
534  if(pEvent->type == ConfigureNotify &&
535  pSelf->wOwner == pEvent->xconfigure.window)
536  {
537  XWindowChanges chg;
538  chg.width = pEvent->xconfigure.width;
539  XConfigureWindow(pDisplay, wIDMenu, CWWidth, &chg);
540 
541  WBInvalidateGeom(wIDMenu, NULL, 0);
542 
543  return 0;
544  }
545 
546  // TODO: menu hot keys
547 
548  // TODO: mouse motion ?
549 
550  return 0; // not handled
551 }
552 
553 static void MBMenuHandleMenuItem(Display *pDisplay, WBMenuBarWindow *pSelf, WBMenu *pMenu, WBMenuItem *pItem)
554 {
555 Window wID = pSelf->wSelf;
556 
557  if(pItem->iAction & WBMENU_POPUP_HIGH_BIT)
558  {
559  XClientMessageEvent evt;
560 
561  // post a high-priority message to myself to display the menu
562 
563  bzero(&evt, sizeof(evt));
564  evt.type = ClientMessage;
565  evt.display = pDisplay;
566  evt.window = wID;
567  evt.message_type = aMENU_DISPLAY_POPUP;
568  evt.format = 32;
569  evt.data.l[0] = pItem->iAction & WBMENU_POPUP_MASK;
570  evt.data.l[1] = pItem->iPosition; // minimum extent
571  evt.data.l[2] = pItem->iPosition + pItem->iTextWidth; // maximum extent
572 
573  WBPostPriorityEvent(wID, (XEvent *)&evt);
574 
576  "%s - posting client event message to display popup menu\n", __FUNCTION__);
577  }
578  else
579  {
580  XClientMessageEvent evt;
581 
582  bzero(&evt, sizeof(evt));
583  evt.type = ClientMessage;
584 
585  evt.display = pDisplay;
586  evt.window = pSelf->wOwner;
587  evt.message_type = aMENU_COMMAND;
588  evt.format = 32; // always
589  evt.data.l[0] = pItem->iAction; // menu command message ID
590  evt.data.l[1] = (long)pMenu; // pointer to menu object
591  evt.data.l[2] = wID; // window ID of menu bar
592 
593  WBPostEvent(pSelf->wOwner, (XEvent *)&evt);
594 
595  pSelf->iSelected = -1; // after handling the message I de-select the menu item
596 
598  "%s - Post Event: %08xH %08xH %pH %08xH\n", __FUNCTION__,
599  (int)aMENU_COMMAND, (int)pItem->iAction,
600  pMenu, (int)wID);
601 
603  }
604 }
605 
606 #if 0 /* currently not used, reserved, consider removing it if not needed */
607 static int MBMenuHandleMenuItemUI(Display *pDisplay, WBMenuBarWindow *pSelf, WBMenu *pMenu, WBMenuItem *pItem)
608 {
609  XClientMessageEvent evt;
610 
611  bzero(&evt, sizeof(evt));
612  evt.type = ClientMessage;
613 
614  evt.display = pDisplay;
615  evt.window = pSelf->wOwner;
616  evt.message_type = aMENU_UI_COMMAND;
617  evt.format = 32; // always
618 
619  // sending pointer data - since this is internally 'sent' I don't have to
620  // worry about async causing pointers to become invalid
621 
622  evt.data.l[0] = pItem->iAction; // menu command message ID (needed to identify menu)
623 #if !defined(__SIZEOF_POINTER__) // TODO find a better way
624 #define __SIZEOF_POINTER_ 0
625 #endif
626 #if __SIZEOF_POINTER__ == 4 /* to avoid warnings in 32-bit linux */
627  evt.data.l[1] = (long)pMenu;
628  evt.data.l[2] = 0;
629  evt.data.l[3] = (long)pItem;
630  evt.data.l[4] = 0;
631 #else
632  evt.data.l[1] = (long)((unsigned long long)pMenu & 0xffffffffL);
633  evt.data.l[2] = (long)(((unsigned long long)pMenu >> 32) & 0xffffffffL);
634  evt.data.l[3] = (long)((unsigned long long)pItem & 0xffffffffL);
635  evt.data.l[4] = (long)(((unsigned long long)pItem >> 32) & 0xffffffffL);
636 #endif
637 
638  return WBWindowDispatch(pSelf->wOwner, (XEvent *)&evt); // 'send event'
639 }
640 #endif // 0
641 
642 
643 // this next callback is assigned via WBRegisterWindowCallback
644 int MBMenuWinEvent(Window wID, XEvent *pEvent)
645 {
646 Display *pDisplay = WBGetWindowDisplay(wID);
648 WBMenu *pMenu = pSelf ? pSelf->pMenu : NULL;
649 WBMenuItem *pItem;
650 WB_GEOM geom;
651 Window wIDParent;
652 XEvent xevt;
653 int i1; //, iHPos, iVPos;
654 
655 
656  if(pSelf && pEvent->type == Expose)
657  {
658  return MenuBarDoExposeEvent((XExposeEvent *)pEvent, pMenu, pDisplay, wID, pSelf);
659  }
660 
661  if(pSelf && pEvent->type == ClientMessage && pEvent->xclient.message_type == aMENU_RESIZE)
662  {
663  WB_RECT rct;
664  XWindowAttributes xswa;
665 
667  "%s - Resize Request Message for %d (%08xH)\n", __FUNCTION__, (int)wID, (int)wID);
668 
669  WBGetClientRect(pSelf->wOwner, &rct);
670 
671  XGetWindowAttributes(pDisplay, wID, &xswa);
672  xswa.width = rct.right - rct.left - xswa.border_width;
673  // assume height doesn't change
674 
676  "%s - perform resize request for %d (%08xH) %d, %d\n",
677  __FUNCTION__, (int)wID, (int)wID, xswa.width, xswa.height);
678 
679  XResizeWindow(pDisplay, wID, xswa.width, xswa.height);
680  return 0; // still requesting default handling on this one
681  }
682 
683  // process 'destroy' events
684  if(pSelf && pEvent->type == DestroyNotify)
685  {
686  if(pEvent->xdestroywindow.window == wID) // destroying myself?
687  {
688  WBRemoveMenuWindow(-1, wID); // removes from ALL windows
689  return 1; // processed
690  }
691  }
692 
693 
694  wIDParent = WBGetParentWindow(wID); // grab the parent window's ID
695 
696  if(pSelf &&
697  (pEvent->type == KeyPress ||
698  pEvent->type == KeyRelease))
699  {
701  "%s - keypress/release in menu_bar\n", __FUNCTION__);
702 
703  if(pEvent->type == KeyPress)
704  {
705  // see if this is a hotkey for a menu item
706 // if(pEvent->xkey.
707  }
708 
709  // send to parent window for processing
710 
711  memcpy(&xevt, pEvent, sizeof(*pEvent));
712  xevt.xkey.window = wIDParent;
713 
714  WBWindowDispatch(wIDParent, &xevt); // have the parent process it
715 
716  // NEXT, post a 'set focus' request to the parent so it gets the other events
717 
718  bzero(&xevt, sizeof(xevt));
719  xevt.type = ClientMessage;
720 
721  xevt.xclient.display = pDisplay;
722  xevt.xclient.window = wIDParent;
723  xevt.xclient.message_type = aSET_FOCUS;
724  xevt.xclient.format = 32; // always
725  xevt.xclient.data.l[0] = 0; // default
726 
727  WBPostEvent(wIDParent, &xevt); // post this (parent should fix it now, asynchronously)
728  }
729 
730  if(pSelf &&
731  (pEvent->type == ButtonPress ||
732  pEvent->type == ButtonRelease ||
733  pEvent->type == MotionNotify))
734  {
735  int iX, iY; // mousie coordinates (when needed)
736 
737  // mousie clickie - yay!
738 
739  if(pEvent->type == MotionNotify)
740  {
741  WBXlatCoordPoint(pEvent->xmotion.window, pEvent->xmotion.x, pEvent->xmotion.y,
742  wID, &iX, &iY);
743  if(pEvent->xmotion.state & Button1Mask) // left drag?
744  {
745  // todo - highlight selections by simulating button press, drop into a popup, etc.
746  }
747  }
748  else if(pEvent->type == ButtonPress && pMenu)
749  {
750  WBXlatCoordPoint(pEvent->xbutton.window, pEvent->xbutton.x, pEvent->xbutton.y,
751  wID, &iX, &iY);
752  if(pEvent->xbutton.state & (Button2Mask | Button3Mask | Button4Mask | Button5Mask
753  | ShiftMask | LockMask | ControlMask))
754  {
755  // this click I ignore (for now)
756  }
757  else if(iY >= pSelf->iY && iY <= (pSelf->iY + pSelf->iHeight))
758  {
759  for(i1=0; pMenu->ppItems && i1 < pMenu->nItems; i1++)
760  {
761  WBMenuItem *pItem = pMenu->ppItems[i1];
762  if(!pItem)
763  continue;
764 
765  if(pItem->iPosition <= iX && pItem->iTextWidth + pItem->iPosition >= iX) // between them
766  {
767  WB_GEOM geom;
768 
769  pSelf->iPrevSel = pSelf->iSelected = i1; // indicate the item that's selected
770 
771  geom.x = pItem->iPosition;
772  geom.y = pSelf->iY;
773  geom.width = pItem->iTextWidth;
774  geom.height = pSelf->iHeight;
775  geom.border = 0;
776 
777  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
778 
779  return 1;
780  }
781  }
782 
784  "%s - couldn't find the menu item - mouse at %d, %d\n", __FUNCTION__, iX, iY);
785 
786 let_parent_process_it:
787 
788  // mouse outside of menu means parent should re-take the focus
789  // So, post a 'set focus' request to the parent so it gets the other events
790 
791  bzero(&xevt, sizeof(xevt));
792  xevt.type = ClientMessage;
793 
794  xevt.xclient.display = pDisplay;
795  xevt.xclient.window = wIDParent;
796  xevt.xclient.message_type = aSET_FOCUS;
797  xevt.xclient.format = 32; // always
798  xevt.xclient.data.l[0] = 0; // default
799 
800  WBPostEvent(wIDParent, &xevt); // post this (parent should fix it now, asynchronously)
801  }
802  else
803  {
805  "%s - Mouse pointer is out of range - %d, %d, %d, %d, %d, %d\n",
806  __FUNCTION__, iX, iY, pSelf->iX, pSelf->iY, pSelf->iWidth, pSelf->iHeight);
807 
808  goto let_parent_process_it;
809  }
810  }
811  else // if(pEvent->type == ButtonRelease)
812  {
813  i1 = pSelf->iSelected;
814 
815  if(i1 < 0 || i1 >= pMenu->nItems)
816  {
817  pSelf->iSelected = -1;
818  WBInvalidateGeom(wID, NULL, TRUE);
819  return 0; // nothing selected
820  }
821 
822  pItem = pMenu->ppItems[i1];
823  if(!pItem)
824  {
825  pSelf->iSelected = -1;
826  WBInvalidateGeom(wID, NULL, TRUE);
827  return 0;
828  }
829 
830  geom.x = pItem->iPosition;
831  geom.y = pSelf->iY;
832  geom.width = pItem->iTextWidth;
833  geom.height = pSelf->iHeight;
834  geom.border = 0;
835 
836  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
837 
838  // determine what the correct action for this menu item is...
839  if(pItem->iAction == -1)
840  {
841  pSelf->iSelected = -1;
842  WBInvalidateGeom(wID, NULL, TRUE);
843  return 0; // "not handled"
844  }
845 
846  MBMenuHandleMenuItem(pDisplay, pSelf, pMenu, pItem);
847 
848  WBInvalidateGeom(wID, &geom, TRUE); // force re-paint AGAIN
849 
850  return 1;
851  }
852  }
853 
854  if(pEvent && pEvent->type == ClientMessage)
855  {
856  if(((XClientMessageEvent *)pEvent)->message_type == aMENU_ACTIVATE)
857  {
858  int iMenuItemIndex = ((XClientMessageEvent *)pEvent)->data.l[1]; // index, prev, or next
859 
861  // TODO: deal with dynamic menu elements
863 
864  pItem = NULL;
865 
866  if(iMenuItemIndex >= 0 && iMenuItemIndex < pMenu->nItems &&
867  ((XClientMessageEvent *)pEvent)->data.l[0]) // prev/next or 'absolute' indicator - *NOT* equal to 'NULL'
868  {
869  // select the menu item specified by the index in data.l[1]
870 
871  if(pSelf->iSelected != iMenuItemIndex &&
872  pSelf->iSelected >= 0 && pSelf->iSelected < pMenu->nItems)
873  {
874  pItem = pMenu->ppItems[pSelf->iSelected];
875  geom.x = pItem->iPosition;
876  geom.y = pSelf->iY;
877  geom.width = pItem->iTextWidth;
878  geom.height = pSelf->iHeight;
879  geom.border = 0;
880 
881  WBInvalidateGeom(wID, &geom, FALSE); // invalidate rect for currently selected menu item
882  }
883 
884  pSelf->iSelected = iMenuItemIndex;
885  pItem = pMenu->ppItems[pSelf->iSelected];
886 
887  // this next part is a 'self-check' which helps to validate the menu item pointer
888  // it does not actually USE the menu item pointer passed in the event (it's likely to get truncated)
889 #warning potentially dangerous code - this should be reviewed an re-written
890  if((unsigned long)pItem == (unsigned long)(((XClientMessageEvent *)pEvent)->data.l[0]))
891  {
892  geom.x = pItem->iPosition;
893  geom.y = pSelf->iY;
894  geom.width = pItem->iTextWidth;
895  geom.height = pSelf->iHeight;
896  geom.border = 0;
897 
898  pSelf->iPrevSel = pSelf->iSelected;
899 
900  MBMenuHandleMenuItem(pDisplay, pSelf, pMenu, pItem);
901 
902  WBInvalidateGeom(wID, &geom, TRUE); // force window update
903 
905  "%s - MENU_ACTIVATE event, activate menu item, %d %p\n",
906  __FUNCTION__, iMenuItemIndex, pItem);
907 
908  return 1; // handled
909  }
910  else
911  {
912  pSelf->iSelected = -1;
913  WBInvalidateGeom(wID, NULL, TRUE);
914  }
915  }
916  else if(iMenuItemIndex && !((XClientMessageEvent *)pEvent)->data.l[0])
917  {
918  // select the PREVIOUS or the NEXT menu item
919 
920  int iOldSel = pSelf->iSelected;
921 
922  if(iOldSel < 0 || iOldSel >= pMenu->nItems)
923  {
924  iOldSel = pSelf->iPrevSel;
925  }
926  if(iOldSel >=0 && iOldSel < pMenu->nItems)
927  {
928  pSelf->iSelected = iOldSel; // re-initialize
929 
930  pItem = NULL; // initially
931 
932  if(iMenuItemIndex > 0)
933  {
934  while(pSelf->iSelected < (pMenu->nItems - 1))
935  {
936  pSelf->iSelected++;
937  pItem = pMenu->ppItems[pSelf->iSelected];
938 
939  if(pItem->iAction != WBMENU_SEPARATOR)
940  {
941  break;
942  }
943 
944  pItem = NULL;
945  }
946  }
947  else
948  {
949  while(pSelf->iSelected > 0)
950  {
951  pSelf->iSelected--;
952  pItem = pMenu->ppItems[pSelf->iSelected];
953 
954  if(pItem->iAction != WBMENU_SEPARATOR)
955  {
956  break;
957  }
958 
959  pItem = NULL;
960  }
961  }
962 
963  if(!pItem)
964  {
965  pSelf->iSelected = iOldSel;
966  pItem = pMenu->ppItems[pSelf->iSelected];
967  }
968 
969  pSelf->iPrevSel = pSelf->iSelected;
970  }
971  else
972  {
973  pSelf->iSelected = pSelf->iPrevSel = 0;
974  while(pSelf->iSelected < pMenu->nItems)
975  {
976  pItem = pMenu->ppItems[pSelf->iSelected];
977  if(pItem->iAction != WBMENU_SEPARATOR)
978  {
979  break;
980  }
981  pItem = NULL;
982  }
983 
984  if(!pItem)
985  {
986  pSelf->iSelected = -1; // fallback for bad menus
987  // leave pItem set to NULL;
988  }
989  else
990  {
991  pSelf->iPrevSel = pSelf->iSelected;
992  }
993  }
994 
995  if(pItem)
996  {
997  MBMenuHandleMenuItem(pDisplay, pSelf, pMenu, pItem);
998  WBInvalidateGeom(wID, NULL, FALSE); // re-paint, at some point in time
999 
1001  "%s - MENU_ACTIVATE event, select prev/next, %d %d\n",
1002  __FUNCTION__, iMenuItemIndex, pSelf->iSelected);
1003  return 1; // handled
1004  }
1005  }
1006 
1007  WB_WARN_PRINT("%s - MENU_ACTIVATE event, invalid menu information, %d %d %p %p\n",
1008  __FUNCTION__, iMenuItemIndex, pMenu->nItems,
1009  (void *)pItem, (void *)(((XClientMessageEvent *)pEvent)->data.l[0]));
1010  }
1011  else if(((XClientMessageEvent *)pEvent)->message_type == aMENU_DISPLAY_POPUP)
1012  {
1013  WBMenuPopupWindow *pPopup;
1014  int iMenuItem = ((XClientMessageEvent *)pEvent)->data.l[0];
1015  int iPosition = ((XClientMessageEvent *)pEvent)->data.l[1];
1016  int iRightPos = ((XClientMessageEvent *)pEvent)->data.l[2];
1017 
1018  for(i1=0; i1 < pMenu->nPopups; i1++)
1019  {
1021  "%s - popup menu id = %d\n", __FUNCTION__,
1022  (pMenu->ppPopups[i1]->iMenuID & WBMENU_POPUP_MASK));
1023 
1024  if(pMenu->ppPopups[i1] &&
1025  (pMenu->ppPopups[i1]->iMenuID & WBMENU_POPUP_MASK) == iMenuItem)
1026  {
1027  // TODO: before moving mouse, see if it's already within "the band"
1028  // move the mouse cursor to the center of the menu item (if it's in another window)
1030  XWarpPointer(pDisplay, None, pSelf->wSelf, 0, 0, 0, 0,
1031  (iPosition + iRightPos) / 2, pSelf->iY + pSelf->iHeight / 2);
1033 
1034  pPopup = MBCreateMenuPopupWindow(pSelf->wSelf, pSelf->wOwner, pMenu->ppPopups[i1],
1035  iPosition, pSelf->iY + pSelf->iHeight, 0);
1036 
1037  if(pPopup)
1038  {
1039 #ifndef NO_DEBUG
1040  int i2;
1041 #endif // NO_DEBUG
1042 
1044  "%s - Displaying popup menu id %d\n", __FUNCTION__, iMenuItem);
1045 
1046 #ifndef NO_DEBUG
1047  i2 =
1048 #endif // NO_DEBUG
1049  MBMenuDoModal(pPopup);
1050 
1051 #ifndef NO_DEBUG
1053  "%s - Done with popup menu id %d, return %d\n", __FUNCTION__, iMenuItem, i2);
1054 #endif // NO_DEBUG
1055  }
1056  else
1057  {
1058  WB_WARN_PRINT("%s - Unable to create popup menu %d\n", __FUNCTION__, iMenuItem);
1059  }
1060 
1061  pSelf->iSelected = -1; // necessary after modality returns
1062  WBInvalidateGeom(pSelf->wSelf, NULL, 1); // re-paint (sort of a bug fix)
1063 
1064  break;
1065  }
1066  }
1067 
1068  if(i1 >= pMenu->nPopups)
1069  {
1070  WB_WARN_PRINT("%s - Unable to locate popup menu %d\n", __FUNCTION__, iMenuItem);
1071  }
1072  }
1073  }
1074 
1075 
1076  return 0; // not handled
1077 }
1078 
1079 
1080 static int MenuBarDoExposeEvent(XExposeEvent *pEvent, WBMenu *pMenu, Display *pDisplay,
1081  Window wID, WBMenuBarWindow *pSelf)
1082 {
1083  int i1, i2, iHPos, iVPos;
1084  XWindowAttributes xwa; /* Temp Get Window Attribute struct */
1085  XFontStruct *pOldFont, *pFont;
1086  XPoint xpt[3];
1087  GC gc; // = WBGetWindowDefaultGC(wID);
1088  XGCValues xgc;
1089  WB_GEOM geomPaint;
1090  char tbuf[128];
1091 
1092 // // Remove any other pending Expose events from the queue to avoid multiple repaints.
1093 // // (I don't do this now since I'm already consolidating expose events)
1094 // while(!bQuitFlag && XCheckTypedWindowEvent(pDisplay, wID, Expose, pEvent))
1095 // ;
1096 
1097 // if(pEvent->xexpose.count != 0)
1098 // {
1099 //
1100 // }
1101 
1102  if (XGetWindowAttributes(pDisplay, wID, &xwa) == 0)
1103  {
1104  WB_ERROR_PRINT("%s - * BUG * unable to get window attributes\n", __FUNCTION__);
1105  return 0;
1106  }
1107 
1108  pFont = pOldFont = WBGetWindowFontStruct(wID);
1109 
1110  if(!pDefaultMenuFont && !pOldFont)
1111  {
1112  WB_ERROR_PRINT("%s - * BUG * no font!\n", __FUNCTION__);
1113  return 0;
1114  }
1115  else if(pDefaultMenuFont)
1116  pFont = pDefaultMenuFont;
1117 
1118  // get graphics context copy and begin painting
1119  gc = WBBeginPaint(wID, pEvent, &geomPaint);
1120  if(!gc)
1121  {
1122  WB_ERROR_PRINT("%s - * BUG * no graphics context!\n", __FUNCTION__);
1123  return 0;
1124  }
1125 
1126  WBClearWindow(wID, gc);
1127 
1128  xgc.font = pFont->fid;
1130  XChangeGC(pDisplay, gc, GCFont, &xgc);
1131 
1133 
1134  // paint the 3D-looking border
1135  XSetForeground(pDisplay, gc, clrMenuBorder2.pixel);
1136  xpt[0].x=xwa.border_width;
1137  xpt[0].y=xwa.height-1-2*xwa.border_width - 1; // exclude first point
1138  xpt[1].x=xwa.border_width;
1139  xpt[1].y=xwa.border_width;
1140  xpt[2].x=xwa.width-1-2*xwa.border_width - 1; // exclude last point
1141  xpt[2].y=xwa.border_width;
1142 
1143  XDrawLines(pDisplay, wID, gc, xpt, 3, CoordModeOrigin);
1144 
1145  XSetForeground(pDisplay, gc, clrMenuBorder3.pixel);
1146  xpt[0].x=xwa.width-1-2*xwa.border_width;
1147  xpt[0].y=xwa.border_width + 1; // exclude first point
1148  xpt[1].x=xwa.width-1-2*xwa.border_width;
1149  xpt[1].y=xwa.height-1-2*xwa.border_width;
1150  xpt[2].x=xwa.border_width + 1; // exclude final point
1151  xpt[2].y=xwa.height-1-2*xwa.border_width;
1152 
1153  XDrawLines(pDisplay, wID, gc, xpt, 3, CoordModeOrigin);
1154 
1155  // painting the menu items
1156 
1157  i2 = XTextWidth(pFont, " ", 2); // width of 2 spaces
1158  iVPos = pFont->max_bounds.ascent + pFont->max_bounds.descent; // font height
1159  iVPos = (xwa.height - iVPos) >> 1; // half of the difference - top of text
1160  iVPos += pFont->max_bounds.ascent;
1161 
1162  // update the width and height of the menu bar within the window
1163  pSelf->iX = i2;
1164  pSelf->iY = iVPos - (pFont->max_bounds.ascent + 1);
1165  pSelf->iWidth = i2; // initially
1166  pSelf->iHeight = pFont->max_bounds.ascent + pFont->max_bounds.descent + 1;
1167 
1168  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
1169 
1170  for(i1=0, iHPos = i2; pMenu && pMenu->ppItems && i1 < pMenu->nItems; i1++)
1171  {
1172  WBMenuItem *pItem = pMenu->ppItems[i1];
1173  const char *szText;
1174  int iU1=0, iU2=0;
1175 
1176  if(!pItem)
1177  continue;
1178 
1179 // MBMenuHandleMenuItemUI(pDisplay, pSelf, pMenu, pItem); TODO - grey things maybe?
1180 
1181  if(i1 == pSelf->iSelected) // selected item
1182  {
1183  XSetForeground(pDisplay, gc, clrMenuActiveBG.pixel);
1184  XSetBackground(pDisplay, gc, clrMenuActiveBG.pixel);
1185 
1186  XFillRectangle(pDisplay, wID, gc, pItem->iPosition, pSelf->iY, pItem->iTextWidth, pSelf->iHeight + 2);
1187 
1188  XSetForeground(pDisplay, gc, clrMenuActiveFG.pixel);
1189  }
1190 
1191  szText = pItem->data + pItem->iMenuItemText;
1192 
1193  if(strchr(szText, '_'))
1194  {
1195  char *p1;
1196  strcpy(tbuf, szText);
1197  p1 = tbuf;
1198  while(*p1)
1199  {
1200  if(*p1 == '_')
1201  {
1202  *p1 = 0;
1203 
1204  if(p1 == tbuf)
1205  iU1 = 0;
1206  else
1207  iU1 = XTextWidth(pFont, tbuf, p1 - tbuf);
1208 
1209  if(p1[1])
1210  {
1211  iU2 = XTextWidth(pFont, p1, 1);
1212  strcpy(p1, p1 + 1);
1213  }
1214  else
1215  {
1216  iU2 = iU1; // shouldn't happen
1217  break;
1218  }
1219  }
1220  p1++;
1221  }
1222 
1223  szText = tbuf;
1224  }
1225 
1226  if(pItem->iPosition < 0)
1227  pItem->iPosition = iHPos; // also needed for mousie/clickie
1228  if(pItem->iTextWidth < 0)
1229  pItem->iTextWidth = XTextWidth(pFont, szText, strlen(szText));
1230 
1231  // TODO: change string into a series of XTextItem structures and
1232  // then call XDrawText to draw the array of 'XTextItem's
1233  if(*szText)
1234  XDrawString(pDisplay, wID, gc, iHPos, iVPos, szText, strlen(szText));
1235 
1236  if(strlen(szText) < strlen(pItem->data + pItem->iMenuItemText))
1237  {
1238  xpt[0].x=iHPos + iU1 - 1;
1239  xpt[0].y=pSelf->iY + pSelf->iHeight - 1;
1240  xpt[1].x=iHPos + iU1 + iU2;
1241  xpt[1].y=xpt[0].y;
1242 
1243  XDrawLines(pDisplay, wID, gc, xpt, 2, CoordModeOrigin);
1244  }
1245 
1246  if(i1 == pSelf->iSelected) // selected item
1247  {
1248  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
1249  XSetBackground(pDisplay, gc, clrMenuBG.pixel);
1250  }
1251 
1252  iHPos += pItem->iTextWidth + i2;
1253  }
1254 
1255  pSelf->iWidth = iHPos - i2; // update the width of the menu bar
1256 
1257  // by convention, restore original objects/state
1258 
1259  xgc.font = pOldFont->fid;
1261  XChangeGC(pDisplay, gc, GCFont, &xgc);
1262 
1263  XSetForeground(pDisplay, gc, WBGetWindowFGColor(wID)); // restore it at the end
1265 
1266  WBEndPaint(wID, gc);
1267 
1268  return 1; // processed
1269 }
1270 
1271