X11workbench Toolkit  1.0
menu_popup.c
Go to the documentation of this file.
1 // _ __ ___ ___ _ __ _ _ _ __ ___ _ __ _ _ _ __ ___ //
3 // | '_ ` _ \ / _ \| '_ \ | | | | | '_ \ / _ \ | '_ \ | | | || '_ \ / __| //
4 // | | | | | || __/| | | || |_| | | |_) || (_) || |_) || |_| || |_) |_| (__ //
5 // |_| |_| |_| \___||_| |_| \__,_|_____ | .__/ \___/ | .__/ \__,_|| .__/(_)\___| //
6 // |_____||_| |_| |_| //
7 // //
8 // generic popup menu implementation //
9 // //
11 
12 /*****************************************************************************
13 
14  X11workbench - X11 programmer's 'work bench' application and toolkit
15  Copyright (c) 2010-2018 by Bob Frazier (aka 'Big Bad Bombastic Bob')
16  all rights reserved
17 
18  DISCLAIMER: The X11workbench application and toolkit software are supplied
19  'as-is', with no warranties, either implied or explicit.
20  Any claims to alleged functionality or features should be
21  considered 'preliminary', and might not function as advertised.
22 
23  BSD-like license:
24 
25  There is no restriction as to what you can do with this software, so long
26  as you include the above copyright notice and DISCLAIMER for any distributed
27  work that is equal to or derived from this one, along with this paragraph
28  that explains the terms of the license if the source is also being made
29  available. A "derived work" describes a work that uses a significant portion
30  of the source files or algorithms that are included with this one.
31  Specifically excluded from this are files that were generated by the software,
32  or anything that is included with the software that is part of another package
33  (such as files that were created or added during the 'configure' process).
34  Specifically included is the use of part or all of any of the X11 workbench
35  toolkit source or header files in your distributed application. If you do not
36  ship the source, the above copyright statement is still required to be placed
37  in a reasonably prominent place, such as documentation, splash screens, and/or
38  'about the application' dialog boxes.
39 
40  Use and distribution are in accordance with GPL, LGPL, and/or the above
41  BSD-like license. See COPYING and README files for more information.
42 
43 
44  Additional information at http://sourceforge.net/projects/X11workbench
45 
46 ******************************************************************************/
47 
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <unistd.h>
59 #include <memory.h>
60 #include <string.h>
61 #include <strings.h>
62 #include <signal.h>
63 #include <time.h>
64 #include <X11/cursorfont.h>
65 
66 #ifndef XK_Delete /* moslty for interix */
67 #define XK_MISCELLANY /* mostly for interix */
68 #include <X11/keysymdef.h> // some platforms don't automatically include this with X headers
69 #endif // XK_Delete
70 
71 #include "window_helper.h"
72 #include "pixmap_helper.h" // pixmap helpers, including pre-defined icons
73 #include "frame_window.h"
74 #include "menu_popup.h"
75 #include "conf_help.h"
76 
77 
78 static int MBMenuPopupEvent(Window wID, XEvent *pEvent);
79 
80 #define HEIGHT_SPACING 4 /* pixels between menu items */
81 #define SEPARATOR_HEIGHT 5 /* height of menu item separator */
82 #define SEPARATOR_POS 2 /* position of separator from 'iPosition' */
83 
84 #define WB_MOUSE_FAR 24 /* 24 pixels, about 2 'W' characters on hi-res display */
85 
86 static void __SetFirstSelection(WBMenuPopupWindow *pSelf, WBMenu *pMenu)
87 {
88 int i1;
89 
90  if(!pMenu)
91  {
92  return;
93  }
94 
95  for(i1=0; pMenu->ppItems && i1 < pMenu->nItems; i1++)
96  {
97  WBMenuItem *pItem = pMenu->ppItems[i1];
98 
99  if(pItem && // invalid
100  pItem->iAction != WBMENU_SEPARATOR) // separator
101  {
102  pSelf->iSelected = i1;
103  break;
104  }
105  }
106 }
107 
108 static void __SetNextSelection(WBMenuPopupWindow *pSelf, WBMenu *pMenu)
109 {
110 int i1;
111 
112  if(!pMenu)
113  {
114  return;
115  }
116 
117  i1 = pSelf->iSelected;
118 
119  if(i1 < 0 || i1 >= pMenu->nItems)
120  {
121  i1 = 0;
122  }
123 
124  for(i1++; pMenu->ppItems && i1 < pMenu->nItems; i1++)
125  {
126  WBMenuItem *pItem = pMenu->ppItems[i1];
127 
128  if(pItem && // invalid
129  pItem->iAction != WBMENU_SEPARATOR) // separator
130  {
131  pSelf->iSelected = i1;
132  return;
133  }
134  }
135 }
136 
137 static void __SetPrevSelection(WBMenuPopupWindow *pSelf, WBMenu *pMenu)
138 {
139 int i1;
140 
141  if(!pMenu)
142  {
143  return;
144  }
145 
146  i1 = pSelf->iSelected;
147 
148  if(i1 < 0 || i1 >= pMenu->nItems)
149  {
150  i1 = 0;
151  }
152 
153  for(i1--; pMenu->ppItems && i1 >= 0; i1--)
154  {
155  WBMenuItem *pItem = pMenu->ppItems[i1];
156 
157  if(pItem && // invalid
158  pItem->iAction != WBMENU_SEPARATOR) // separator
159  {
160  pSelf->iSelected = i1;
161  return;
162  }
163  }
164 }
165 
166 static void __SetLastSelection(WBMenuPopupWindow *pSelf, WBMenu *pMenu)
167 {
168 int i1;
169 
170  if(!pMenu)
171  {
172  return;
173  }
174 
175  for(i1 = pMenu->nItems - 1; pMenu->ppItems && i1 >= 0; i1--)
176  {
177  WBMenuItem *pItem = pMenu->ppItems[i1];
178 
179  if(pItem && // invalid
180  pItem->iAction != WBMENU_SEPARATOR) // separator
181  {
182  pSelf->iSelected = i1;
183  return;
184  }
185  }
186 }
187 
188 static WBMenuItem * __GetCurrentSelection(WBMenuPopupWindow *pSelf, WBMenu *pMenu)
189 {
190  if(pSelf->iSelected >= 0 && pSelf->iSelected < pMenu->nItems)
191  {
192  return pMenu->ppItems[pSelf->iSelected];
193  }
194 
195  return NULL;
196 }
197 
198 
199 WBMenuPopupWindow *MBCreateMenuPopupWindow(Window wIDBar, Window wIDOwner, WBMenu *pMenu,
200  int iX, int iY, int iFlags)
201 {
202  Display *pDisplay = WBGetWindowDisplay(wIDBar);
203  XFontStruct *pDefaultMenuFont = MBGetDefaultMenuFont();
204  WBMenuPopupWindow *pRval = NULL;
205  unsigned long fg, bg, bd; /* Pixel values */
206  XSetWindowAttributes xswa; /* Temporary Set Window Attribute struct */
207  XSizeHints xsh; /* Size hints for window manager - width of owner + 2 * height of font */
208  XWMHints xwmh;
209  WB_GEOM geom;
210  XFontStruct *pFS;
211  int i1, iHPos, iVPos, iHBorder, iSelected = -1;
212  Atom a1;
213  unsigned long ul1;
214  char tbuf[256];
215 
216 
217  // check/initialize global menu objects
218  if(!MBInitGlobal())
219  {
220  return NULL;
221  }
222 
223  // step 1: create the window
224 
225  bd = clrMenuBorder1.pixel; // BlackPixel(pDisplay, DefaultScreen(pDisplay));
226  fg = clrMenuFG.pixel;// BlackPixel(pDisplay, DefaultScreen(pDisplay)); // black foreground for menus (always)
227  bg = clrMenuBG.pixel;// WhitePixel(pDisplay, DefaultScreen(pDisplay)); // white background for menus (for now)
228 
229  // menu orientation and size depend upon the size of the owner menu's parent window
230  // and the size of the popup menu itself. For now assume orientation top left at iX iY
231 
232  pFS = WBGetWindowFontStruct(wIDBar);
233 
234  if(!pDefaultMenuFont && !pFS)
235  {
236  WB_WARN_PRINT("%s - * BUG * no font!\n", __FUNCTION__);
237  return 0;
238  }
239  else if(pDefaultMenuFont)
240  {
241  pFS = pDefaultMenuFont;
242  }
243 
244  // get absolute position of "parent"
245  WBGetWindowGeom2(wIDBar, &geom);
246 
247  // set size hints to match client area, upper left corner (always)
248  // as translated from the position of the "parent"
249 
250  xsh.flags = (PPosition | PSize);
251  xsh.x = iX + geom.x;
252  xsh.y = iY + geom.y;
253 
254  // get the dimensions of each menu item. separators are 3 pixels.
255 
256  iVPos = 4; // 2 pixels for border + 2 pixels for spacing
257  iHPos = iHBorder = XTextWidth(pFS, " ", 2); // width of 2 spaces
258 
259  for(i1=0; pMenu && pMenu->ppItems && i1 < pMenu->nItems; i1++)
260  {
261  WBMenuItem *pItem = pMenu->ppItems[i1];
262  const char *szText;
263 // int iU1=0, iU2=0;
264 
265  if(!pItem)
266  {
267  continue;
268  }
269 
270  if(pItem->iAction == -1) // separator
271  {
272  pItem->iPosition = iVPos;
273  pItem->iTextWidth = 0;
274  iVPos += SEPARATOR_HEIGHT;
275  continue;
276  }
277 
278  if(iSelected < 0 || iSelected >= pMenu->nItems)
279  {
280  // set focus to first non-separator item in menu
281  iSelected = i1; // force first item to select
282  }
283 
284  szText = pItem->data + pItem->iMenuItemText;
285 
286  if(strchr(szText, '_'))
287  {
288  char *p1;
289  strncpy(tbuf, szText, sizeof(tbuf)/2);
290  p1 = tbuf;
291  while(*p1)
292  {
293  if(*p1 == '_')
294  {
295  *p1 = 0;
296 
297 // NOTE: iU1 not being used; commented out because of linux gcc warnings
298 // if(p1 == tbuf)
299 // iU1 = 0;
300 // else
301 // iU1 = XTextWidth(pFS, tbuf, strlen(tbuf));
302 
303  if(p1[1])
304  {
305 // NOTE: iU2 not being used; commented out because of linux gcc warnings
306 // iU2 = XTextWidth(pFS, p1, 1);
307  strcpy(p1, p1 + 1);
308  }
309 // else
310 // {
311 // iU2 = iU1; // shouldn't happen
312 // }
313  }
314  p1++;
315  }
316  }
317  else
318  {
319  strncpy(tbuf, szText, sizeof(tbuf)/2);
320  }
321 
322  strcat(tbuf, " ");
323  if(pItem->iHotKey >= 0)
324  strcat(tbuf, pItem->data + pItem->iHotKey);
325 
326  pItem->iPosition = iVPos; // also needed for mousie/clickie
327  pItem->iTextWidth = XTextWidth(pFS, tbuf, strlen(tbuf));
328 
329  iVPos += pFS->max_bounds.ascent + pFS->max_bounds.descent + HEIGHT_SPACING;
330 
331  if(iHPos < 2 * iHBorder + pItem->iTextWidth)
332  iHPos = 2 * iHBorder + pItem->iTextWidth;
333  }
334 
335  xsh.width = iHPos;
336  xsh.height = iVPos + 2; // 2 extra pixels for the bottom border
337 
338  memset(&xswa, 0, sizeof(xswa));
339 
340  xswa.border_pixel = bd;
341  xswa.background_pixel = bg;
342  xswa.colormap = DefaultColormap(pDisplay, DefaultScreen(pDisplay));
343  xswa.bit_gravity = CenterGravity;
344  xswa.override_redirect = True; // so window manager won't mess with it
345 
346  pRval = (WBMenuPopupWindow *)WBAlloc(sizeof(*pRval));
347 
348  if(!pRval)
349  {
350  WB_ERROR_PRINT("%s - not enough memory to allocate structure\n", __FUNCTION__);
351  return NULL;
352  }
353 
354  pRval->ulTag = MENU_POPUP_WINDOW_TAG;
355 
356  pRval->pMenu = pMenu;
357 
358  if(!pRval->pMenu)
359  {
360  WB_WARN_PRINT("%s - * BUG * pMenu is NULL!\n", __FUNCTION__);
361  }
362 
363  // this window will be owned by the root, so it will require mouse and keyboard grabs
364  // in order to prevent changing the focus.
365 
366 // pRval->wSelf = XCreateWindow(pDisplay, DefaultRootWindow(pDisplay),
367 // xsh.x, xsh.y, xsh.width, xsh.height,
368 // 0, // no border
369 // DefaultDepth(pDisplay, DefaultScreen(pDisplay)),
370 // InputOutput,
371 // DefaultVisual(pDisplay, DefaultScreen(pDisplay)),
372 // CWBorderPixel | CWBackPixel | CWColormap | CWBitGravity | CWOverrideRedirect,
373 // &xswa);
374 
375  pRval->wSelf = WBCreateWindow(pDisplay, DefaultRootWindow(pDisplay), MBMenuPopupEvent, "MenuPopup",
376  xsh.x, xsh.y, xsh.width, xsh.height, 0,
377  InputOutput,
378  CWBorderPixel | CWBackPixel | CWColormap | CWBitGravity | CWOverrideRedirect,
379  &xswa);
380  if(pRval->wSelf == -1)
381  {
382  WB_WARN_PRINT("%s - WARNING: unable to create window for popup menu\n", __FUNCTION__);
383 
384  WBFree(pRval);
385  return NULL;
386  }
387 
388  pRval->wBar = wIDBar; // for now do it this way
389  pRval->wOwner = wIDOwner;
390  pRval->iSelected = iSelected;
391  pRval->iFlags = iFlags; // make a copy of them (for now)
392 
393  // calculate the initial position and height of the menu bar within the window
394  pRval->iX = /*xsh.x + */2;
395  pRval->iY = /*xsh.y + */2;
396  pRval->iWidth = xsh.width - 4;
397  pRval->iHeight = xsh.height - 4;
398 
399 // WBRegisterWindowCallback(pRval->wSelf, MBMenuPopupEvent); // this must happen first
400 // WBSetWindowClassName(pRval->wSelf, "MenuPopup");
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
411 
412  WBCreateWindowDefaultGC(pRval->wSelf, fg, bg);
413  WBSetWindowFontStruct(pRval->wSelf, WBCopyFont(pFS));
414 
415  XSelectInput(pDisplay, pRval->wSelf,
417 
418  // before mapping the window, set some properties
419  a1 = XInternAtom(pDisplay, "_NET_WM_WINDOW_TYPE", False);
420  ul1 = XInternAtom(pDisplay, "_NET_WM_WINDOW_TYPE_MENU", False);
421  XChangeProperty(pDisplay, pRval->wSelf, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)&ul1, 1);
422 
423  a1 = XInternAtom(pDisplay, "_NET_WM_STATE", False);
424  ul1 = XInternAtom(pDisplay, "_NET_WM_STATE_MODAL", False);
425  XChangeProperty(pDisplay, pRval->wSelf, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)&ul1, 1);
426 
427  a1 = XInternAtom(pDisplay, "WM_TRANSIENT_FOR", False);
428  XChangeProperty(pDisplay, pRval->wSelf, a1, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wIDOwner, 1);//&wIDBar, 1);
429 
430 
431  // TODO: other properties... ?
432 
433  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_Menu,
434  "%s - mapping popup menu window\n", __FUNCTION__);
435 
436  WBMapWindow(pDisplay, pRval->wSelf); // make window visible
437 
438  WBGetParentWindow(pRval->wSelf); // this syncs everything up (return value not needed)
439 
440  // TODO: set up font, GS, callback, etc.
441 
442  return(pRval);
443 }
444 
445 static int __FindMenuPopupWindowCallback(Window wID, void *pData)
446 {
448 
449  if(pMP && (void *)(pMP->pMenu) == pData)
450  {
451  return 1;
452  }
453 
454  return 0;
455 }
456 
457 WBMenuPopupWindow *MBFindMenuPopupWindow(WBMenu *pMenu) // find first (active) window that uses 'pMenu'
458 {
459  Window wID = WBLocateWindow(__FindMenuPopupWindowCallback, (void *)pMenu);
460 
461  if(wID)
462  {
463  return MBGetMenuPopupWindowStruct(wID);
464  }
465 
466  return NULL;
467 }
468 
470 {
471  if(!pPopup)
472  return -1;
473 
474  return WBShowModal(pPopup->wSelf, 1); // will only return when the window is destroyed
475 }
476 
478 {
479  if(!pMenuPopupWindow || pMenuPopupWindow->ulTag != MENU_POPUP_WINDOW_TAG)
480  {
481  return;
482  }
483 
484  WBDestroyWindow(pMenuPopupWindow->wSelf); // destroy the window (this should clean the rest up automatically)
485 }
486 
487 
488 static int MBMenuPopupHandleMenuItemUI(Display *pDisplay, WBMenuPopupWindow *pSelf, WBMenu *pMenu, WBMenuItem *pItem)
489 {
490 XClientMessageEvent evt;
491 int iRval;
492 
493 
494  bzero(&evt, sizeof(evt));
495  evt.type = ClientMessage;
496 
497  evt.display = pDisplay;
498  evt.window = pSelf->wOwner;
499  evt.message_type = aMENU_UI_COMMAND;
500  evt.format = 32; // always
501 
502  // sending pointer data - since this is internally 'sent' I don't have to
503  // worry about async causing pointers to become invalid
504 
505  evt.data.l[0] = pItem->iAction; // menu command message ID (needed to identify menu)
506 
507  evt.data.l[1] = WBCreatePointerHash(pMenu);
508  evt.data.l[2] = WBCreatePointerHash(pItem);
509  evt.data.l[3] = 0; // this is a sort of 'flag' saying I'm using a pointer hash
510  evt.data.l[4] = 0;
511 
513  // TODO: handle things differently for a DYNAMIC menu UI handler?
515 
516  iRval = WBWindowDispatch(pSelf->wOwner, (XEvent *)&evt); // 'send event'
517 
518 // if(iRval < 1)
519 // {
520 // WB_ERROR_PRINT("TEMPORARY: %s - \"%s\" returning %d\n", __FUNCTION__,
521 // (const char *)(pItem->data + pItem->iMenuItemText), iRval);
522 // }
523 
524  WBDestroyPointerHashPtr(pMenu); // destroying hash based on pointer value [not hash value]
525  WBDestroyPointerHashPtr(pItem); // clean them up as I'm done with them now
526 
527  return iRval;
528 }
529 
530 
531 // NOTE: this function will NOT invoke the handler because it sends a MENU_UI_COMMAND before posting the handler event
532 
533 static void MBMenuPopupHandleMenuItem(Display *pDisplay, Window wID, WBMenuPopupWindow *pSelf, WBMenu *pMenu, WBMenuItem *pItem)
534 {
535  if(pItem->iAction & WBMENU_POPUP_HIGH_BIT)
536  {
537  XClientMessageEvent evt;
538 
539  // post a high-priority message to myself to display the menu
540 
541  bzero(&evt, sizeof(evt));
542  evt.type = ClientMessage;
543  evt.display = pDisplay;
544  evt.window = wID;
545  evt.message_type = aMENU_DISPLAY_POPUP;
546  evt.format = 32;
547  evt.data.l[0] = pItem->iAction & WBMENU_POPUP_MASK;
548  evt.data.l[1] = pItem->iPosition;
549 
550  WBPostPriorityEvent(wID, (XEvent *)&evt);
551  }
552  else // regular menu item. do a 'menu command'
553  {
554  int iUIState = MBMenuPopupHandleMenuItemUI(pDisplay, pSelf, pMenu, pItem);
555 
556  if(iUIState > 0) // a handler exists AND the menu is NOT disabled
557  {
558  XClientMessageEvent evt;
559 
560  bzero(&evt, sizeof(evt));
561  evt.type = ClientMessage;
562 
563  evt.display = pDisplay;
564  evt.window = pSelf->wOwner;
565  evt.message_type = aMENU_COMMAND;
566  evt.format = 32; // always
567  evt.data.l[0] = pItem->iAction; // menu command message ID
568  evt.data.l[1] = WBCreatePointerHash(pMenu); // pointer to menu object
569  evt.data.l[2] = wID; // window ID of menu bar
570 
571  WBPostEvent(pSelf->wOwner, (XEvent *)&evt);
572 
573  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_Menu,
574  "%s - Post Event: %08xH %08xH %pH %08xH\n",
575  __FUNCTION__, (int)aMENU_COMMAND, (int)pItem->iAction,
576  pMenu, (int)wID);
577  }
578  else
579  {
580  XBell(pDisplay, -100); // indicate that the menu is disabled so I know I didn't activate it
581  }
582 
583  WBEndModal(wID, pItem->iAction);
584  }
585 }
586 
587 static int MenuPopupDoExposeEvent(XExposeEvent *pEvent, WBMenu *pMenu,
588  Display *pDisplay, Window wID,
589  WBMenuPopupWindow *pSelf)
590 {
591  int i1, /* i2, */ iHPos, iVPos, iHeight;
592  XWindowAttributes xwa; /* Temp Get Window Attribute struct */
593  XFontStruct *pOldFont, *pFont, *pDefaultMenuFont;
594  XPoint xpt[3];
595  GC gc; // = WBGetWindowDefaultGC(wID);
596  XGCValues xgc;
597  WB_GEOM geomPaint;
598  char tbuf[128];
599 
600  if (XGetWindowAttributes(pDisplay, wID, &xwa) == 0)
601  {
602  WB_WARN_PRINT("%s - * BUG * unable to get window attributes!\n", __FUNCTION__);
603  return 0;
604  }
605 
606  pFont = pOldFont = WBGetWindowFontStruct(wID);
607  pDefaultMenuFont = MBGetDefaultMenuFont();
608 
609  if(!pDefaultMenuFont && !pOldFont)
610  {
611  WB_WARN_PRINT("%s - * BUG * no font!\n", __FUNCTION__);
612  return 0;
613  }
614  else if(pDefaultMenuFont)
615  pFont = pDefaultMenuFont;
616 
617  // get graphics context copy and begin painting
618  gc = WBBeginPaint(wID, pEvent, &geomPaint);
619  if(!gc)
620  {
621  WB_WARN_PRINT("%s - * BUG * no graphics context!\n", __FUNCTION__);
622  return 0;
623  }
624 
625  xgc.font = pFont->fid;
627  XChangeGC(pDisplay, gc, GCFont, &xgc);
629 
631 // XClearArea(pDisplay, wID, geomPaint.x, geomPaint.y, geomPaint.width, geomPaint.height, 0);
632  WBClearWindow(wID, gc);
633 
634  // paint a 3D-looking border
635  XSetForeground(pDisplay, gc, clrMenuBorder2.pixel);
636  xpt[0].x=xwa.border_width;
637  xpt[0].y=xwa.height-1-2*xwa.border_width - 1; // exclude first point
638  xpt[1].x=xwa.border_width;
639  xpt[1].y=xwa.border_width;
640  xpt[2].x=xwa.width-1-2*xwa.border_width - 1; // exclude last point
641  xpt[2].y=xwa.border_width;
642 
643  XDrawLines(pDisplay, wID, gc, xpt, 3, CoordModeOrigin);
644 
645  XSetForeground(pDisplay, gc, clrMenuBorder3.pixel);
646  xpt[0].x=xwa.width-1-2*xwa.border_width;
647  xpt[0].y=xwa.border_width + 1; // exclude first point
648  xpt[1].x=xwa.width-1-2*xwa.border_width;
649  xpt[1].y=xwa.height-1-2*xwa.border_width;
650  xpt[2].x=xwa.border_width + 1; // exclude final point
651  xpt[2].y=xwa.height-1-2*xwa.border_width;
652 
653  XDrawLines(pDisplay, wID, gc, xpt, 3, CoordModeOrigin);
654 
655  // painting the menu items
656 
657  iHeight = pFont->max_bounds.ascent + pFont->max_bounds.descent;
658 
659  iVPos = 4; // 2 pixels for border + 2 pixels
660  iHPos = XTextWidth(pFont, " ", 2); // width of 2 spaces
661 
662  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
663 
664  for(i1=0; pMenu && pMenu->ppItems && i1 < pMenu->nItems; i1++)
665  {
666  WBMenuItem *pItem = pMenu->ppItems[i1];
667  const char *szText;
668  int iU1=0, iU2=0;
669 
670  int iUIState = 0;
671 
672  if(!pItem)
673  {
674  continue;
675  }
676 
677  if(pItem->iPosition < 0)
678  {
679  pItem->iPosition = iVPos; // also needed for mousie/clickie
680  }
681 
682  if(pItem->iAction == WBMENU_SEPARATOR) // separator
683  {
684  xpt[0].x=xwa.border_width + 1;
685  xpt[0].y=pItem->iPosition + SEPARATOR_POS - 1;
686  xpt[1].x=xwa.width-1-2*xwa.border_width;
687  xpt[1].y=xpt[0].y;
688 
689  XDrawLines(pDisplay, wID, gc, xpt, 2, CoordModeOrigin);
690 
691  XSetForeground(pDisplay, gc, clrMenuBorder2.pixel);
692  xpt[1].y=++(xpt[0].y);
693  XDrawLines(pDisplay, wID, gc, xpt, 2, CoordModeOrigin);
694 
695  iVPos += SEPARATOR_HEIGHT;
696  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
697  continue;
698  }
699  else if(pItem->iAction & WBMENU_DYNAMIC_HIGH_BIT)
700  {
702  // TODO: HANDLE DYNAMIC MENU
704 
705  WB_ERROR_PRINT("TODO: %s - handle dynamic menu\n", __FUNCTION__);
706  }
707 
708  iUIState = MBMenuPopupHandleMenuItemUI(pDisplay, pSelf, pMenu, pItem);
709  // iUIState is 0 if menu NOT handled (default action, disable it)
710  // iUIState is < 0 to disable, > 0 to enable. See aMENU_UI_ITEM docs for more info
711 
712  // TODO: do I cache the state so I don't allow activation?
713 
714  if(i1 == pSelf->iSelected) // selected item
715  {
716  int iItemHeight = pFont->max_bounds.ascent + pFont->max_bounds.descent + HEIGHT_SPACING;
717 
718  XSetForeground(pDisplay, gc, clrMenuActiveBG.pixel);
719  XSetBackground(pDisplay, gc, clrMenuActiveBG.pixel);
720 
721  XFillRectangle(pDisplay, wID, gc,
722  xwa.border_width + 2, pItem->iPosition - 1,
723  xwa.width-4-2*xwa.border_width, iItemHeight - 1);
724 
725  if(iUIState > 0)
726  {
727  // TODO: 'checked' state
728  XSetForeground(pDisplay, gc, clrMenuActiveFG.pixel);
729  }
730  else
731  {
732  XSetForeground(pDisplay, gc, clrMenuActiveDisabledFG.pixel);
733  }
734  }
735  else
736  {
737  if(iUIState > 0)
738  {
739  // TODO: 'checked' state
740  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
741  }
742  else
743  {
744  XSetForeground(pDisplay, gc, clrMenuDisabledFG.pixel);
745  }
746  }
747 
748  szText = pItem->data + pItem->iMenuItemText;
749 
750  if(pItem->iUnderscore >= 0) //strchr(szText, '_'))
751  {
752  // locate the first (only the first) underscore
753  char *p1;
754 
755  strcpy(tbuf, szText);
756  p1 = tbuf + pItem->iUnderscore - pItem->iMenuItemText; // position of underscore
757 
758  if(*p1 == '_') // TODO: allow multiple underscores? Not much value in it (could loop)
759  {
760  *p1 = 0;
761 
762  if(p1 == tbuf)
763  iU1 = 0;
764  else
765  iU1 = XTextWidth(pFont, tbuf, p1 - tbuf);
766 
767  if(p1[1]) // character just past underscore
768  {
769  iU2 = XTextWidth(pFont, p1 + 1, 1);
770  strcpy(p1, p1 + 1); // adjust actual text so there is no underscore
771  }
772  else
773  {
774  iU2 = iU1; // shouldn't happen
775  }
776  }
777  else
778  {
779  WB_ERROR_PRINT("%s - ERROR: cannot locate underscore\n", __FUNCTION__);
780  }
781 
782  szText = tbuf; // modified text without '_' in it
783  }
784 
785  if(pItem->iTextWidth < 0)
786  {
787  pItem->iTextWidth = XTextWidth(pFont, szText, strlen(szText));
788  }
789 
790  //***************************************************************//
791  // TODO: handle 'checked' menu items, both enabled AND disabled //
792  //***************************************************************//
793 
794  // TODO: change string into a series of XTextItem structures and
795  // then call XDrawText to draw the array of 'XTextItem's
796  if(*szText)
797  {
798  XDrawString(pDisplay, wID, gc, iHPos,
799  pItem->iPosition + pFont->max_bounds.ascent,
800  szText, strlen(szText));
801  }
802 
803  // next I want to indicate what the hotkey is. This text must be right-justified
804  if(pItem->iHotKey >= 0)
805  {
806  const char *p2 = pItem->data + pItem->iHotKey;
807  if(*p2)
808  {
809  int iLen = strlen(p2);
810  int iWidth = XTextWidth(pFont, p2, iLen)
811  + XTextWidth(pFont, " ", 1) * 2; // white space on right side
812 
813  XDrawString(pDisplay, wID, gc,
814  xwa.width + xwa.border_width - 2 - iWidth,
815  pItem->iPosition + pFont->max_bounds.ascent,
816  p2, iLen);
817  }
818  }
819 
820  if(pItem->iUnderscore >= 0)//strlen(szText) < strlen(pItem->data + pItem->iMenuItemText))
821  {
822  xpt[0].x=iHPos + iU1 - 1;
823  xpt[0].y=pItem->iPosition + iHeight - 1;
824  xpt[1].x=iHPos + iU1 + iU2;
825  xpt[1].y=xpt[0].y;
826 
827  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_Menu,
828  "%s - drawing underscore at %d,%d to %d,%d\n",
829  __FUNCTION__, xpt[0].x, xpt[0].y, xpt[1].x, xpt[1].y);
830 
831  XDrawLines(pDisplay, wID, gc, xpt, 2, CoordModeOrigin);
832  }
833 
834  if(i1 == pSelf->iSelected) // selected item
835  {
836  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
837  XSetBackground(pDisplay, gc, clrMenuBG.pixel);
838  }
839 
840  iVPos += iHeight + HEIGHT_SPACING;
841  }
842 
843  // by convention, restore original objects/state
844 
845  xgc.font = pOldFont->fid;
847  XChangeGC(pDisplay, gc, GCFont, &xgc);
848 
849  XSetForeground(pDisplay, gc, WBGetWindowFGColor(wID)); // restore it at the end
851 
852  WBEndPaint(wID, gc);
853 
854  return 1;
855 }
856 
857 static WBMenuItem * __SelectMenuItemFromMousePos(Window wID, WBMenuPopupWindow *pSelf, WBMenu *pMenu, int iX, int iY)
858 {
859 int i1;
860 int iMaxY;
861 WB_GEOM geom;
862 
863 
864  // first I must invalidate the currently selected item
865 
866  for(i1=0; pMenu->ppItems && i1 < pMenu->nItems; i1++)
867  {
868  WBMenuItem *pItem = pMenu->ppItems[i1];
869 
870  if(!pItem)
871  {
872  continue;
873  }
874 
875  if((i1 + 1) < pMenu->nItems)
876  {
877  iMaxY = pMenu->ppItems[i1 + 1]->iPosition - 1;
878  }
879  else
880  {
881  iMaxY = pSelf->iY + pSelf->iHeight;
882  }
883 
884  if(pItem->iPosition <= iY && iMaxY >= iY) // between them
885  {
886 // XFontStruct *pFont, *pOldFont, *pDefaultMenuFont;
887  int iHPos;
888 
889  if(pSelf->iSelected == i1) // already selected?
890  {
891  return pItem; // just return (nothing else to do)
892  }
893 
894  // WBMenuPopupHandleMenuItemiUI returns 0 if menu NOT handled (default action, disable it)
895  // it returns < 0 to disable, > 0 to enable. See aMENU_UI_ITEM docs for more info
896 
897  if(pItem->iAction == WBMENU_SEPARATOR)// ||
898 // 0 >= MBMenuPopupHandleMenuItemUI(WBGetWindowDisplay(wID), pSelf, pMenu, pItem)) // not handled or 'disable'
899  {
900  if(pSelf->iSelected >= 0 && pSelf->iSelected < pMenu->nItems)
901  {
902  return pMenu->ppItems[pSelf->iSelected];
903  }
904 
905  return NULL;
906  }
907 
908 // TODO: if I need the font, uncomment this later. otherwise, it causes a warning in linux gcc
909 // pFont = pOldFont = WBGetWindowFontStruct(wID);
910 // pDefaultMenuFont = MBGetDefaultMenuFont();
911 //
912 // if(!pDefaultMenuFont && !pOldFont)
913 // {
914 // WB_WARN_PRINT("%s - * BUG * no font!\n", __FUNCTION__);
915 // return 0;
916 // }
917 // else if(pDefaultMenuFont)
918 // {
919 // pFont = pDefaultMenuFont;
920 // }
921 
922  iHPos = 1; // XTextWidth(pFont, " ", 2); // width of 2 spaces
923 
924  // if something is selected it will NOT be the same one I'm trying
925  // to select now, so invalidate it so that it's re-painted
926  if(pSelf->iSelected >= 0 && pSelf->iSelected < pMenu->nItems)
927  {
928  WBMenuItem *pItem0 = pMenu->ppItems[pSelf->iSelected];
929 
930  if(pItem0) // the old selected item
931  {
932  geom.x = iHPos;
933  geom.y = pItem0->iPosition - 1; // actual practice suggests going outside the box a bit
934  geom.width = pSelf->iWidth + 1;
935  geom.height = iMaxY + 2;
936  geom.border = 0;
937  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
938  }
939  }
940 
941  pSelf->iSelected = i1; // indicate the item that's NOW selected
942 
943  // rectangle for new selected item
944  geom.x = iHPos;
945  geom.y = pItem->iPosition - 1; // actual practice suggests going outside the box a bit
946  geom.width = pSelf->iWidth + 1; //pItem->iTextWidth;
947  geom.height = iMaxY + 2;
948  geom.border = 0;
949 
950  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
951 
952  return pItem;
953  }
954  }
955 
956  return NULL;
957 }
958 
959 static void __PostActivatePrevNextEvent(WBMenuPopupWindow *pSelf, int iPrevNext)
960 {
961 XClientMessageEvent evt;
962 
963  bzero(&evt, sizeof(evt));
964  evt.type = ClientMessage;
965  evt.display = WBGetWindowDisplay(pSelf->wBar);
966  evt.window = pSelf->wBar;
967  evt.message_type = aMENU_ACTIVATE;
968  evt.format = 32;
969  evt.data.l[0] = 0;
970  evt.data.l[1] = iPrevNext; // previous/next indicator
971 
972  WBPostPriorityEvent(pSelf->wBar, (XEvent *)&evt);
973 
974  WB_DEBUG_PRINT(DebugLevel_Chatty | DebugSubSystem_Menu | DebugSubSystem_Keyboard,
975  "%s - posting Prev/Next %d to menu bar %d (%08xH)\n",
976  __FUNCTION__, iPrevNext, (int)pSelf->wBar, (int)pSelf->wBar);
977 }
978 
979 // this next callback is assigned via WBRegisterWindowCallback
980 static int MBMenuPopupEvent(Window wID, XEvent *pEvent)
981 {
982  Display *pDisplay = WBGetWindowDisplay(wID);
984  WBMenuItem *pItem;
985  WBMenu *pMenu = pSelf ? pSelf->pMenu : NULL;
986  int i1, iPrevSel;
987  WB_GEOM geom;
988 
989 
990 
991  if(!pSelf)
992  {
993  WB_WARN_PRINT("%s - pSelf is NULL\n", __FUNCTION__);
994  }
995  else
996  {
997  // process 'destroy' events
998  if(pEvent->type == DestroyNotify)
999  {
1000  if(pEvent->xdestroywindow.window == wID) // destroying myself?
1001  {
1002  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_Menu,
1003  "%s - DestroyNotify\n", __FUNCTION__);
1004 
1005  WBSetWindowData(pSelf->wSelf, 0, 0); // clear the 'back pointer' now
1006 
1007  WBFree(pSelf); // on destroy I always do this (no owned objects so it's easy)
1008 
1009  return 1; // processed
1010  }
1011  }
1012 
1013  // process 'FocusOut' events (this will destroy the window)
1014 
1015  if(pEvent->type == FocusOut)
1016  {
1017  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_Menu,
1018  "%s - FocusOut\n", __FUNCTION__);
1019 
1020  WBEndModal(wID, -1); // return '-1' meaning "canceled"
1021  return 1; // processed
1022  }
1023 
1024  // process 'expose' events
1025 
1026  if(pEvent->type == Expose)
1027  {
1028  return MenuPopupDoExposeEvent((XExposeEvent *)pEvent, pMenu, pDisplay, wID, pSelf);
1029  }
1030 
1031  // process keyboard events
1032 
1033  if((pEvent->type == KeyPress ||
1034  pEvent->type == KeyRelease))
1035  {
1036  int iACS=0;
1037  int iKey = WBKeyEventProcessKey((XKeyEvent *)pEvent, NULL, NULL, &iACS);
1038 
1039  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_Menu | DebugSubSystem_Keyboard | DebugSubSystem_Event,
1040  "%s - key press/release %x (%d) iACS=%x\n", __FUNCTION__, iKey, iKey, iACS);
1041 
1042  if(pEvent->type == KeyPress)
1043  {
1044  if(iACS & WB_KEYEVENT_KEYSYM)
1045  {
1046  if((iACS & WB_KEYEVENT_ALT)
1047  && (iKey >= XK_F1 && iKey <= XK_F35))
1048  {
1049  XKeyEvent kevt;
1050 
1051  // re-post message to owner as soon as the popup menu goes away
1052 
1053  memcpy(&kevt, &(pEvent->xkey), sizeof(kevt));
1054  kevt.window = kevt.subwindow = pSelf->wOwner;
1055 
1056  WBEndModal(wID, -1); // canceled menu
1057 
1058  WBSetInputFocus(pSelf->wOwner);
1059  XPutBackEvent(pDisplay, (XEvent *)&kevt); // I want this processed correctly
1060 
1061  return 1; // processed
1062  }
1063  else if(iKey == XK_Left)
1064  {
1065  // post an activate message to the menu bar to move to next left and open popup
1066  // and then close this one.
1067 
1068  __PostActivatePrevNextEvent(pSelf, -1);
1069 
1070  WBEndModal(wID, -1); // canceled menu
1071  return 1; // processed
1072  }
1073  else if(iKey == XK_Right)
1074  {
1075  // post an activate message to the menu bar to move to next left and open popup
1076  // and then close this one.
1077 
1078  __PostActivatePrevNextEvent(pSelf, 1);
1079 
1080  WBEndModal(wID, -1); // canceled menu
1081  return 1; // processed
1082  }
1083  else if(iKey == XK_Up)
1084  {
1085  i1 = pSelf->iSelected;
1086 
1087  __SetPrevSelection(pSelf, pMenu);
1088 
1089  if(i1 != pSelf->iSelected)
1090  {
1091  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1092  }
1093  return 1; // handled
1094  }
1095  else if(iKey == XK_Down)
1096  {
1097  i1 = pSelf->iSelected;
1098 
1099  __SetNextSelection(pSelf, pMenu);
1100 
1101  if(i1 != pSelf->iSelected)
1102  {
1103  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1104  }
1105  return 1; // handled
1106  }
1107  else if(iKey == XK_Prior)
1108  {
1109  i1 = pSelf->iSelected;
1110 
1111  __SetFirstSelection(pSelf, pMenu);
1112 
1113  if(i1 != pSelf->iSelected)
1114  {
1115  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1116  }
1117  return 1; // handled
1118  }
1119  else if(iKey == XK_Next)
1120  {
1121  i1 = pSelf->iSelected;
1122 
1123  __SetLastSelection(pSelf, pMenu);
1124 
1125  if(i1 != pSelf->iSelected)
1126  {
1127  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1128  }
1129  return 1; // handled
1130  }
1131  }
1132  else if(iKey == '\x1b' ||
1133  (((iACS & WB_KEYEVENT_ALT) || (iACS & WB_KEYEVENT_CTRL))// checking for ctrl+TAB or ALT+TAB
1134  && iKey=='\t'))
1135  {
1136  XKeyEvent kevt;
1137 
1138  memcpy(&kevt, &(pEvent->xkey), sizeof(kevt));
1139  kevt.window = kevt.subwindow = pSelf->wOwner;
1140 
1141  WBEndModal(wID, -1); // canceled menu
1142 
1143  if(iKey == '\t') // for the ALT+TAB or CTRL+TAB
1144  {
1145  WBSetInputFocus(pSelf->wOwner);
1146  XPutBackEvent(pDisplay, (XEvent *)&kevt); // I want this processed correctly
1147  }
1148 
1149  return 1; // processed
1150  }
1151 
1152  if(MBMenuProcessHotKey(pMenu, (XKeyEvent *)pEvent) > 0)
1153  {
1154  return 1; // activated
1155  }
1156  }
1157  else if(pEvent->type == KeyRelease)
1158  {
1159  // up/down arrow keys, home, end, pgup, pgdown - choose active item
1160 
1161  // <ENTER>, space bar - activate current item
1162 
1163  if(!iACS && (iKey == '\n' || iKey == '\r' || iKey == ' '))
1164  {
1165  pItem = __GetCurrentSelection(pSelf, pMenu);
1166 
1167  MBMenuPopupHandleMenuItem(pDisplay, wID, pSelf, pMenu, pItem);
1168  return 1; // handled
1169  }
1170  }
1171  }
1172 
1173  // process mouse events
1174 
1175  if((pEvent->type == ButtonPress ||
1176  pEvent->type == ButtonRelease ||
1177  pEvent->type == MotionNotify))
1178  {
1179  int iX, iY; // mousie coordinates (when needed)
1180 
1181  // mousie clickie - yay!
1182 
1183  if(pEvent->type == MotionNotify)
1184  {
1185  WBXlatCoordPoint(pEvent->xmotion.window, pEvent->xmotion.x, pEvent->xmotion.y,
1186  wID, &iX, &iY);
1187  if(pEvent->xmotion.state & Button1Mask) // left drag?
1188  {
1189  // todo - highlight selections by simulating button press, drop into a popup, etc.
1190  }
1191  else if(!(pEvent->xmotion.state & (Button1Mask | Button2Mask | Button3Mask)))
1192  {
1193  // "hover select" - hover far left or far far right, close popup
1194  // vertical hover over, select (immediate)
1195  // hover over far right, open popup (as needed)
1196  // hover below, scroll (as needed)
1197  // hover above, scroll (as needed)
1198 
1199  // hover far left or far far right
1200 
1201  WB_WARN_PRINT("TEMPORARY: %d %d %d\n",
1202  iX, pSelf->iX, pSelf->iX + pSelf->iWidth);
1203  if(iX < pSelf->iX - WB_MOUSE_FAR || iX > pSelf->iX + pSelf->iWidth + 2 * WB_MOUSE_FAR)
1204  {
1205  WBEndModal(wID, -1); // canceled menu
1206 
1207  WBSetInputFocus(pSelf->wOwner);
1208  return 1; // no further processing on this one
1209  }
1210 
1211  // vertical 'hover over'
1212  iPrevSel = pSelf->iSelected;
1213  __SelectMenuItemFromMousePos(wID, pSelf, pMenu, iX, iY);
1214 
1215  if(iPrevSel != pSelf->iSelected)
1216  {
1217  // re-set timers for hover
1218  }
1219 
1220  return 0; // this is just advisory
1221  }
1222  }
1223  else if(pEvent->type == ButtonPress && pMenu)
1224  {
1225  WBXlatCoordPoint(pEvent->xbutton.window, pEvent->xbutton.x, pEvent->xbutton.y,
1226  wID, &iX, &iY);
1227 
1228  if(pEvent->xbutton.state & (Button2Mask | Button3Mask | Button4Mask | Button5Mask
1229  | ShiftMask | LockMask | ControlMask))
1230  {
1231  // this click I ignore (for now)
1232  }
1233  else if(iY >= pSelf->iY && iY <= (pSelf->iY + pSelf->iHeight))
1234  {
1235  // I'm within the dimensions of the 'menu area' so I must now
1236  // which menu item I'm pointing at
1237 
1238  if(__SelectMenuItemFromMousePos(wID, pSelf, pMenu, iX, iY))
1239  {
1240  return 1; // item was found
1241  }
1242 
1243  WB_WARN_PRINT("%s - couldn't find the menu - %d, %d\n", __FUNCTION__, iX, iY);
1244  }
1245  else
1246  {
1247  WB_DEBUG_PRINT(DebugLevel_WARN | DebugSubSystem_Menu | DebugSubSystem_Mouse,
1248  "%s - Mouse is out of range - %d, %d, %d, %d, %d, %d\n",
1249  __FUNCTION__, iX, iY, pSelf->iX, pSelf->iY, pSelf->iWidth, pSelf->iHeight);
1250  }
1251  }
1252  else // if(pEvent->type == ButtonRelease)
1253  {
1254  i1 = pSelf->iSelected;
1255  pSelf->iSelected = -1;
1256 
1257  if(i1 < 0 || i1 >= pMenu->nItems)
1258  return 0; // nothing selected
1259 
1260  pItem = pMenu->ppItems[i1];
1261  if(!pItem)
1262  return 0;
1263 
1264  geom.x = pItem->iPosition;
1265  geom.y = pSelf->iY;
1266  geom.width = pItem->iTextWidth;
1267  geom.height = pSelf->iHeight;
1268  geom.border = 0;
1269 
1270  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
1271 
1272  // determine what the correct action for this menu item is...
1273  if(pItem->iAction == -1)
1274  return 0; // "not handled"
1275 
1276  MBMenuPopupHandleMenuItem(pDisplay, wID, pSelf, pMenu, pItem);
1277 
1278  return 1;
1279  }
1280  }
1281  }
1282 
1283  if(pEvent && pEvent->type == ClientMessage)
1284  {
1285  if(pMenu &&
1286  ((XClientMessageEvent *)pEvent)->message_type == aMENU_ACTIVATE)
1287  {
1288  int iMenuItemIndex = ((XClientMessageEvent *)pEvent)->data.l[1];
1289  WBMenuItem *pItem = NULL;
1290 
1291  if(iMenuItemIndex >= 0 && iMenuItemIndex < pMenu->nItems)
1292  {
1293  pItem = pMenu->ppItems[iMenuItemIndex];
1294  if((unsigned long)pItem == (unsigned long)(((XClientMessageEvent *)pEvent)->data.l[0]))
1295  {
1296  // NOTE: this will NOT invoke the handler because it sends a MENU_UI_COMMAND before
1297  // posting the handler event
1298  MBMenuPopupHandleMenuItem(pDisplay, wID, pSelf, pMenu, pItem);
1299 
1300  return 1; // handled
1301  }
1302  }
1303 
1304  WB_WARN_PRINT("%s - MENU_ACTIVATE event, invalid menu information, %d %d %p %p\n",
1305  __FUNCTION__, iMenuItemIndex, pMenu->nItems,
1306  (void *)pItem, (void *)(((XClientMessageEvent *)pEvent)->data.l[0]));
1307  }
1308  else if(((XClientMessageEvent *)pEvent)->message_type == aMENU_DISPLAY_POPUP)
1309  {
1310  WBMenuPopupWindow *pPopup;
1311  int iRval = -1;
1312  int iMenuItem = ((XClientMessageEvent *)pEvent)->data.l[0];
1313  int iPosition = ((XClientMessageEvent *)pEvent)->data.l[1];
1314 
1315  for(i1=0; i1 < pMenu->nPopups; i1++)
1316  {
1317  WB_DEBUG_PRINT(DebugLevel_Excessive | DebugSubSystem_Menu,
1318  "%s - popup menu id = %d\n",
1319  __FUNCTION__, (pMenu->ppPopups[i1]->iMenuID & WBMENU_POPUP_MASK));
1320 
1321  if(pMenu->ppPopups[i1] &&
1322  (pMenu->ppPopups[i1]->iMenuID & WBMENU_POPUP_MASK) == iMenuItem)
1323  {
1324  pPopup = MBCreateMenuPopupWindow(pSelf->wSelf, pSelf->wOwner, pMenu->ppPopups[i1],
1325  iPosition, pSelf->iY + pSelf->iHeight, 0);
1326 
1327  if(pPopup)
1328  {
1329  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_Menu,
1330  "%s - Displaying popup menu %d\n", __FUNCTION__, iMenuItem);
1331 
1332  iRval = MBMenuDoModal(pPopup);
1333 
1334  WB_DEBUG_PRINT(DebugLevel_Heavy | DebugSubSystem_Menu,
1335  "%s - done with popup menu %d, return value %d\n", __FUNCTION__, iMenuItem, iRval);
1336  }
1337  else
1338  {
1339  WB_ERROR_PRINT("%s - Unable to create popup menu %d\n", __FUNCTION__, iMenuItem);
1340  }
1341 
1342  WBInvalidateGeom(pSelf->wSelf, NULL, 1); // re-paint (sort of a bug fix)
1343  WBEndModal(wID, iRval);
1344  }
1345  }
1346  if(i1 >= pMenu->nPopups)
1347  {
1348  WB_ERROR_PRINT("%s - Unable to locate popup menu %d\n", __FUNCTION__, iMenuItem);
1349  }
1350 
1351  // in all cases, exit anyway
1352  WBEndModal(wID, -1);
1353  }
1354  }
1355 
1356  return 0; // temporary
1357 }
1358 
GC WBBeginPaint(Window wID, XExposeEvent *pEvent, WB_GEOM *pgBounds)
&#39;Paint&#39; helper, creates a GC for use in updating the window in an Expose event handler ...
#define WBMENU_POPUP_MASK
Definition: menu.h:80
structure for managing menu items
Definition: menu.h:185
Atom aMENU_UI_COMMAND
UI notifications sent by menus to owning Frame windows via ClientMessage using &#39;WBWindowDispatch&#39;.
&#39;window helper&#39; main header file for the X11workbench Toolkit API
int iX
popup menu &#39;X&#39; position within the owner&#39;s client area
Definition: menu_popup.h:117
int MBInitGlobal(void)
Initialize global resources for Menu Bar windows.
Definition: menu_bar.c:177
int iFlags
flags that determine behavior (reserved)
Definition: menu_popup.h:126
Atom aMENU_DISPLAY_POPUP
Internal Client Message Atom for &#39;DISPLAY POPUP&#39; action.
Definition: menu_bar.c:165
#define WB_DEBUG_PRINT(L,...)
Preferred method of implementing conditional debug output.
Definition: debug_helper.h:299
int iAction
Definition: menu.h:139
int WBKeyEventProcessKey(const XKeyEvent *pEvent, char *pBuf, int *pcbLen, int *piAltCtrlShift)
Generic keyboard event translation utility.
int iPosition
horizontal/vertical position of menu (in pixels; assign &#39;-1&#39; to calculate it)
Definition: menu.h:143
int nPopups
The number of popup menu entries in the &#39;ppPopups&#39; array.
Definition: menu.h:196
#define WBMENU_DYNAMIC_HIGH_BIT
Definition: menu.h:79
#define WB_KEYEVENT_ALT
&#39;AltCtrlShift&#39; bit flag for ALT modifier for WBKeyEventProcessKey()
#define WBMENU_SEPARATOR
Definition: menu.h:81
int WBShowModal(Window wID, int bMenuSplashFlag)
Shows a &#39;modal&#39; window by processing events until the window closes.
Window WBLocateWindow(WBLocateWindowCallback callback, void *pData)
Locate a window by enumerating with a callback function.
WBMenuPopupWindow * MBFindMenuPopupWindow(WBMenu *pMenu)
Find the first WBMenuPopupWindow that references a WBMenu.
Definition: menu_popup.c:457
int iHotKey
hotkey description (-1 if none)
Definition: menu.h:136
#define WB_MOUSE_INPUT_MASK
&#39;Mouse&#39; input mask, bit flag for window creation
int WBWindowDispatch(Window wID, XEvent *pEvent)
Dispatches a window XEvent. May be called directly.
#define WB_KEYBOARD_INPUT_MASK
&#39;Keyboard&#39; input mask, bit flag for window creation
structure for managing menu items
Definition: menu.h:127
unsigned int border
structure for managing a popup menu window
Definition: menu_popup.h:108
int WBPostPriorityEvent(Window wID, XEvent *pEvent)
Places a copy of the specified event at the end of the priority (internal) event queue.
WB_UINT32 WBCreatePointerHash(void *pPointer)
Create/obtain a 32-bit &#39;secure&#39; hash for a pointer.
int iWidth
popup menu width
Definition: menu_popup.h:119
&#39;configuration helper&#39; main header file for the X11 Work Bench Toolkit API
int iMenuID
menu identifier specified when menu was created (high bit set for popup)
Definition: menu.h:189
#define WB_STANDARD_INPUT_MASK
&#39;Standard&#39; input mask, bit flag for window creation
Atom aMENU_COMMAND
commands sent by menus via ClientMessage
void WBSetWindowFontStruct(Window wID, XFontStruct *pFontStruct)
assigns the default XFontStruct to a window
void WBInvalidateGeom(Window wID, const WB_GEOM *pGeom, int bPaintNow)
&#39;Paint&#39; helper, invalidates a geometry for asynchronous Expose event generation
int iTextWidth
width of menu text (in pixels; assign &#39;-1&#39; to calculate it)
Definition: menu.h:142
void WBClearWindow(Window wID, GC gc)
&#39;Paint&#39; helper, erases background by painting the background color within the clipping region ...
void WBCreateWindowDefaultGC(Window wID, unsigned long clrFG, unsigned long clrBG)
creates a default GC for a window
unsigned long WBGetWindowFGColor(Window wID)
Returns the currently assigned foreground color.
int nItems
The number of menu item entries in the &#39;ppItems&#39; array.
Definition: menu.h:192
void WBDestroyPointerHashPtr(void *pPointer)
Destroy a 32-bit &#39;secure&#39; hash for a pointer regardless of reference count.
int iHeight
popup menu height
Definition: menu_popup.h:120
void * WBAlloc(int nSize)
High performance memory sub-allocator &#39;allocate&#39;.
unsigned int width
Window WBGetParentWindow(Window wID)
Returns the window&#39;s parent (or None if there is no parent)
#define END_XCALL_DEBUG_WRAPPER
wrapper macro for calls into the X11 library. This macro follows the call(s)
int iMenuItemText
offset in &#39;data&#39; to null-byte terminated strings (-1 if none)
Definition: menu.h:133
void WBSetWindowDefaultCursor(Window wID, int idStandardCursor)
Assigns a default cursor (by ID) to a window.
unsigned int height
#define WBMENU_POPUP_HIGH_BIT
Definition: menu.h:78
#define WB_ERROR_PRINT(...)
Preferred method of implementing an &#39;error level&#39; debug message for all subsystems.
Definition: debug_helper.h:356
int iY
popup menu &#39;Y&#39; position within the owner&#39;s client area
Definition: menu_popup.h:118
void WBFree(void *pBuf)
High performance memory sub-allocator &#39;free&#39;.
int WBPostEvent(Window wID, XEvent *pEvent)
Places a copy of the specified event at the end of the regular (internal) event queue.
WBMenu * pMenu
a pointer to the associated WBMenu structure
Definition: menu_popup.h:115
void WBDestroyWindow(Window wID)
Destroy a window.
void MBDestroyMenuPopupWindow(WBMenuPopupWindow *pMenuPopupWindow)
Destroy a WBMenuPopupWindow structure.
Definition: menu_popup.c:477
int MBMenuDoModal(WBMenuPopupWindow *pPopup)
display a Menu Popup window in a &#39;modal&#39; loop
Definition: menu_popup.c:469
Atom aMENU_ACTIVATE
Internal Client Message Atom for &#39;ACTIVATE&#39; notification.
Definition: menu_bar.c:150
unsigned int ulTag
tag indicating I&#39;m a &#39;Menu Popup&#39; window
Definition: menu_popup.h:110
void WBXlatCoordPoint(Window wIDSrc, int iXSrc, int iYSrc, Window wIDDest, int *piXDest, int *piYDest)
Translate X,Y point coordinates relative to a window.
XColor clrMenuFG
menu foreground color
Definition: menu_bar.c:113
WBMenuItem ** ppItems
An allocated array of menu items.
Definition: menu.h:191
int iUnderscore
offset of (first) &#39;underscore&#39; within menu text (-1 if none)
Definition: menu.h:134
WBMenuPopupWindow * MBCreateMenuPopupWindow(Window wIDBar, Window wIDOwner, WBMenu *pMenu, int iX, int iY, int iFlags)
Create a WBMenuPopupWindow object and associated window.
Definition: menu_popup.c:199
int iSelected
currently selected menu (for display purposes)
Definition: menu_popup.h:125
Window wOwner
window ID for the owner window
Definition: menu_popup.h:113
Window wBar
window ID for the associated Menu Bar window
Definition: menu_popup.h:112
char data[4]
data follows
Definition: menu.h:146
Window wSelf
window ID for the Menu Popup window
Definition: menu_popup.h:111
int MBMenuProcessHotKey(WBMenu *pMenu, XKeyEvent *pEvent)
Event handler for menu hotkeys.
Definition: menu.c:1228
Display * WBGetWindowDisplay(Window wID)
returns the Display associated with a window
void WBSetWindowData(Window wID, int iIndex, void *pData)
assign &#39;data pointer&#39; for a window and specified index value
#define BEGIN_XCALL_DEBUG_WRAPPER
wrapper macro for calls into the X11 library. This macro precedes the call(s)
void WBSetInputFocus(Window wID)
set input focus to a specific window
Window WBCreateWindow(Display *pDisplay, Window wIDParent, WBWinEvent pProc, const char *szClass, int iX, int iY, int iWidth, int iHeight, int iBorder, int iIO, int iFlags, XSetWindowAttributes *pXSWA)
Create a window.
XFontStruct * MBGetDefaultMenuFont(void)
Get a pointer to the default &#39;Menu Bar&#39; font structure.
Definition: menu_bar.c:327
#define WB_KEYEVENT_CTRL
&#39;AltCtrlShift&#39; bit flag for Control modifier for WBKeyEventProcessKey()
void WBEndModal(Window wID, int iRval)
End a modal window with a specific return value.
void WBSetWMProperties(Window wID, const char *szTitle, XSizeHints *pNormalHints, XWMHints *pWMHints, XClassHint *pClassHints)
assign standard WM (Window Manager) properties via XSetWMProperties
XFontStruct * WBGetWindowFontStruct(Window wID)
Returns the current XFontStruct pointer assigned to the window (may be NULL)
XFontStruct * WBCopyFont(XFontStruct *pOldFont)
make a copy of an existing font (best when assigning to a window)
Definition: font_helper.c:97
int WBMapWindow(Display *pDisplay, Window wID)
Wrapper for XMapWindow, makes window visible.
Frame Window API functions and definitions.
internal wrapper struct for X11 &#39;geometry&#39; definition
void WBEndPaint(Window wID, GC gc)
&#39;Paint&#39; helper, frees resources and marks the update region &#39;valid&#39;
#define WB_WARN_PRINT(...)
Preferred method of implementing a &#39;warning level&#39; debug message for all subsystems.
Definition: debug_helper.h:349
static __inline__ WBMenuPopupWindow * MBGetMenuPopupWindowStruct(Window wID)
Get the associated WBMenuPopupWindow structure from a Menu Popup window&#39;s window ID.
Definition: menu_popup.h:201
struct __WBMenu ** ppPopups
An allocated array of &#39;popup&#39; menus contained by this menu.
Definition: menu.h:195
void WBGetWindowGeom2(Window wID, WB_GEOM *pGeom)
Returns the geometry of the window relative to the root window.
#define WB_KEYEVENT_KEYSYM
&#39;AltCtrlShift&#39; bit flag for &#39;VK_&#39; keys for WBKeyEventProcessKey()