X11 Work Bench Toolkit  1.0
menu_popup.c
Go to the documentation of this file.
1 
2 // _ __ ___ ___ _ __ _ _ _ __ ___ _ __ _ _ _ __ ___ //
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-2016 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 
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 #warning potentially dangerous code. this should be re-written to NOT use a 'pMenu' or 'pItem' pointer in an event
508 
509 #if !defined(__SIZEOF_POINTER__) // TODO find a better way
510 #define __SIZEOF_POINTER_ 0
511 #endif
512 #if __SIZEOF_POINTER__ == 4 /* to avoid warnings in 32-bit linux */
513  evt.data.l[1] = (long)pMenu;
514  evt.data.l[2] = 0;
515  evt.data.l[3] = (long)pItem;
516  evt.data.l[4] = 0;
517 #else
518  evt.data.l[1] = (long)((unsigned long long)pMenu & 0xffffffffL);
519  evt.data.l[2] = (long)(((unsigned long long)pMenu >> 32) & 0xffffffffL);
520  evt.data.l[3] = (long)((unsigned long long)pItem & 0xffffffffL);
521  evt.data.l[4] = (long)(((unsigned long long)pItem >> 32) & 0xffffffffL);
522 #endif
523 
525  // TODO: handle things differently for a DYNAMIC menu UI handler?
527 
528  iRval = WBWindowDispatch(pSelf->wOwner, (XEvent *)&evt); // 'send event'
529 
530 
531 // if(iRval < 1)
532 // {
533 // WB_ERROR_PRINT("TEMPORARY: %s - \"%s\" returning %d\n", __FUNCTION__,
534 // (const char *)(pItem->data + pItem->iMenuItemText), iRval);
535 // }
536 
537  return iRval;
538 }
539 
540 
541 // NOTE: this function will NOT invoke the handler because it sends a MENU_UI_COMMAND before posting the handler event
542 
543 static void MBMenuPopupHandleMenuItem(Display *pDisplay, Window wID, WBMenuPopupWindow *pSelf, WBMenu *pMenu, WBMenuItem *pItem)
544 {
545  if(pItem->iAction & WBMENU_POPUP_HIGH_BIT)
546  {
547  XClientMessageEvent evt;
548 
549  // post a high-priority message to myself to display the menu
550 
551  bzero(&evt, sizeof(evt));
552  evt.type = ClientMessage;
553  evt.display = pDisplay;
554  evt.window = wID;
555  evt.message_type = aMENU_DISPLAY_POPUP;
556  evt.format = 32;
557  evt.data.l[0] = pItem->iAction & WBMENU_POPUP_MASK;
558  evt.data.l[1] = pItem->iPosition;
559 
560  WBPostPriorityEvent(wID, (XEvent *)&evt);
561  }
562  else // regular menu item. do a 'menu command'
563  {
564  int iUIState = MBMenuPopupHandleMenuItemUI(pDisplay, pSelf, pMenu, pItem);
565 
566  if(iUIState > 0) // a handler exists AND the menu is NOT disabled
567  {
568  XClientMessageEvent evt;
569 
570  bzero(&evt, sizeof(evt));
571  evt.type = ClientMessage;
572 
573  evt.display = pDisplay;
574  evt.window = pSelf->wOwner;
575  evt.message_type = aMENU_COMMAND;
576  evt.format = 32; // always
577  evt.data.l[0] = pItem->iAction; // menu command message ID
578 #warning potentially dangerous code. this should be re-written to NOT use a 'pMenu' pointer in an event
579  evt.data.l[1] = (long)pMenu; // pointer to menu object
580  evt.data.l[2] = wID; // window ID of menu bar
581 
582  WBPostEvent(pSelf->wOwner, (XEvent *)&evt);
583 
585  "%s - Post Event: %08xH %08xH %pH %08xH\n",
586  __FUNCTION__, (int)aMENU_COMMAND, (int)pItem->iAction,
587  pMenu, (int)wID);
588  }
589  else
590  {
591  XBell(pDisplay, -100); // indicate that the menu is disabled so I know I didn't activate it
592  }
593 
594  WBEndModal(wID, pItem->iAction);
595  }
596 }
597 
598 static int MenuPopupDoExposeEvent(XExposeEvent *pEvent, WBMenu *pMenu,
599  Display *pDisplay, Window wID,
600  WBMenuPopupWindow *pSelf)
601 {
602  int i1, /* i2, */ iHPos, iVPos, iHeight;
603  XWindowAttributes xwa; /* Temp Get Window Attribute struct */
604  XFontStruct *pOldFont, *pFont, *pDefaultMenuFont;
605  XPoint xpt[3];
606  GC gc; // = WBGetWindowDefaultGC(wID);
607  XGCValues xgc;
608  WB_GEOM geomPaint;
609  char tbuf[128];
610 
611  if (XGetWindowAttributes(pDisplay, wID, &xwa) == 0)
612  {
613  WB_WARN_PRINT("%s - * BUG * unable to get window attributes!\n", __FUNCTION__);
614  return 0;
615  }
616 
617  pFont = pOldFont = WBGetWindowFontStruct(wID);
618  pDefaultMenuFont = MBGetDefaultMenuFont();
619 
620  if(!pDefaultMenuFont && !pOldFont)
621  {
622  WB_WARN_PRINT("%s - * BUG * no font!\n", __FUNCTION__);
623  return 0;
624  }
625  else if(pDefaultMenuFont)
626  pFont = pDefaultMenuFont;
627 
628  // get graphics context copy and begin painting
629  gc = WBBeginPaint(wID, pEvent, &geomPaint);
630  if(!gc)
631  {
632  WB_WARN_PRINT("%s - * BUG * no graphics context!\n", __FUNCTION__);
633  return 0;
634  }
635 
636  xgc.font = pFont->fid;
638  XChangeGC(pDisplay, gc, GCFont, &xgc);
640 
642 // XClearArea(pDisplay, wID, geomPaint.x, geomPaint.y, geomPaint.width, geomPaint.height, 0);
643  WBClearWindow(wID, gc);
644 
645  // paint a 3D-looking border
646  XSetForeground(pDisplay, gc, clrMenuBorder2.pixel);
647  xpt[0].x=xwa.border_width;
648  xpt[0].y=xwa.height-1-2*xwa.border_width - 1; // exclude first point
649  xpt[1].x=xwa.border_width;
650  xpt[1].y=xwa.border_width;
651  xpt[2].x=xwa.width-1-2*xwa.border_width - 1; // exclude last point
652  xpt[2].y=xwa.border_width;
653 
654  XDrawLines(pDisplay, wID, gc, xpt, 3, CoordModeOrigin);
655 
656  XSetForeground(pDisplay, gc, clrMenuBorder3.pixel);
657  xpt[0].x=xwa.width-1-2*xwa.border_width;
658  xpt[0].y=xwa.border_width + 1; // exclude first point
659  xpt[1].x=xwa.width-1-2*xwa.border_width;
660  xpt[1].y=xwa.height-1-2*xwa.border_width;
661  xpt[2].x=xwa.border_width + 1; // exclude final point
662  xpt[2].y=xwa.height-1-2*xwa.border_width;
663 
664  XDrawLines(pDisplay, wID, gc, xpt, 3, CoordModeOrigin);
665 
666  // painting the menu items
667 
668  iHeight = pFont->max_bounds.ascent + pFont->max_bounds.descent;
669 
670  iVPos = 4; // 2 pixels for border + 2 pixels
671  iHPos = XTextWidth(pFont, " ", 2); // width of 2 spaces
672 
673  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
674 
675  for(i1=0; pMenu && pMenu->ppItems && i1 < pMenu->nItems; i1++)
676  {
677  WBMenuItem *pItem = pMenu->ppItems[i1];
678  const char *szText;
679  int iU1=0, iU2=0;
680 
681  int iUIState = 0;
682 
683  if(!pItem)
684  {
685  continue;
686  }
687 
688  if(pItem->iPosition < 0)
689  {
690  pItem->iPosition = iVPos; // also needed for mousie/clickie
691  }
692 
693  if(pItem->iAction == WBMENU_SEPARATOR) // separator
694  {
695  xpt[0].x=xwa.border_width + 1;
696  xpt[0].y=pItem->iPosition + SEPARATOR_POS - 1;
697  xpt[1].x=xwa.width-1-2*xwa.border_width;
698  xpt[1].y=xpt[0].y;
699 
700  XDrawLines(pDisplay, wID, gc, xpt, 2, CoordModeOrigin);
701 
702  XSetForeground(pDisplay, gc, clrMenuBorder2.pixel);
703  xpt[1].y=++(xpt[0].y);
704  XDrawLines(pDisplay, wID, gc, xpt, 2, CoordModeOrigin);
705 
706  iVPos += SEPARATOR_HEIGHT;
707  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
708  continue;
709  }
710  else if(pItem->iAction & WBMENU_DYNAMIC_HIGH_BIT)
711  {
713  // TODO: HANDLE DYNAMIC MENU
715 
716  WB_ERROR_PRINT("TODO: %s - handle dynamic menu\n", __FUNCTION__);
717  }
718 
719  iUIState = MBMenuPopupHandleMenuItemUI(pDisplay, pSelf, pMenu, pItem);
720  // iUIState is 0 if menu NOT handled (default action, disable it)
721  // iUIState is < 0 to disable, > 0 to enable. See aMENU_UI_ITEM docs for more info
722 
723  // TODO: do I cache the state so I don't allow activation?
724 
725  if(i1 == pSelf->iSelected) // selected item
726  {
727  int iItemHeight = pFont->max_bounds.ascent + pFont->max_bounds.descent + HEIGHT_SPACING;
728 
729  XSetForeground(pDisplay, gc, clrMenuActiveBG.pixel);
730  XSetBackground(pDisplay, gc, clrMenuActiveBG.pixel);
731 
732  XFillRectangle(pDisplay, wID, gc,
733  xwa.border_width + 2, pItem->iPosition - 1,
734  xwa.width-4-2*xwa.border_width, iItemHeight - 1);
735 
736  if(iUIState > 0)
737  {
738  // TODO: 'checked' state
739  XSetForeground(pDisplay, gc, clrMenuActiveFG.pixel);
740  }
741  else
742  {
743  XSetForeground(pDisplay, gc, clrMenuActiveDisabledFG.pixel);
744  }
745  }
746  else
747  {
748  if(iUIState > 0)
749  {
750  // TODO: 'checked' state
751  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
752  }
753  else
754  {
755  XSetForeground(pDisplay, gc, clrMenuDisabledFG.pixel);
756  }
757  }
758 
759  szText = pItem->data + pItem->iMenuItemText;
760 
761  if(pItem->iUnderscore >= 0) //strchr(szText, '_'))
762  {
763  // locate the first (only the first) underscore
764  char *p1;
765 
766  strcpy(tbuf, szText);
767  p1 = tbuf + pItem->iUnderscore - pItem->iMenuItemText; // position of underscore
768 
769  if(*p1 == '_') // TODO: allow multiple underscores? Not much value in it (could loop)
770  {
771  *p1 = 0;
772 
773  if(p1 == tbuf)
774  iU1 = 0;
775  else
776  iU1 = XTextWidth(pFont, tbuf, p1 - tbuf);
777 
778  if(p1[1]) // character just past underscore
779  {
780  iU2 = XTextWidth(pFont, p1 + 1, 1);
781  strcpy(p1, p1 + 1); // adjust actual text so there is no underscore
782  }
783  else
784  {
785  iU2 = iU1; // shouldn't happen
786  }
787  }
788  else
789  {
790  WB_ERROR_PRINT("%s - ERROR: cannot locate underscore\n", __FUNCTION__);
791  }
792 
793  szText = tbuf; // modified text without '_' in it
794  }
795 
796  if(pItem->iTextWidth < 0)
797  {
798  pItem->iTextWidth = XTextWidth(pFont, szText, strlen(szText));
799  }
800 
801  //***************************************************************//
802  // TODO: handle 'checked' menu items, both enabled AND disabled //
803  //***************************************************************//
804 
805  // TODO: change string into a series of XTextItem structures and
806  // then call XDrawText to draw the array of 'XTextItem's
807  if(*szText)
808  {
809  XDrawString(pDisplay, wID, gc, iHPos,
810  pItem->iPosition + pFont->max_bounds.ascent,
811  szText, strlen(szText));
812  }
813 
814  // next I want to indicate what the hotkey is. This text must be right-justified
815  if(pItem->iHotKey >= 0)
816  {
817  const char *p2 = pItem->data + pItem->iHotKey;
818  if(*p2)
819  {
820  int iLen = strlen(p2);
821  int iWidth = XTextWidth(pFont, p2, iLen)
822  + XTextWidth(pFont, " ", 1) * 2; // white space on right side
823 
824  XDrawString(pDisplay, wID, gc,
825  xwa.width + xwa.border_width - 2 - iWidth,
826  pItem->iPosition + pFont->max_bounds.ascent,
827  p2, iLen);
828  }
829  }
830 
831  if(pItem->iUnderscore >= 0)//strlen(szText) < strlen(pItem->data + pItem->iMenuItemText))
832  {
833  xpt[0].x=iHPos + iU1 - 1;
834  xpt[0].y=pItem->iPosition + iHeight - 1;
835  xpt[1].x=iHPos + iU1 + iU2;
836  xpt[1].y=xpt[0].y;
837 
839  "%s - drawing underscore at %d,%d to %d,%d\n",
840  __FUNCTION__, xpt[0].x, xpt[0].y, xpt[1].x, xpt[1].y);
841 
842  XDrawLines(pDisplay, wID, gc, xpt, 2, CoordModeOrigin);
843  }
844 
845  if(i1 == pSelf->iSelected) // selected item
846  {
847  XSetForeground(pDisplay, gc, clrMenuFG.pixel);
848  XSetBackground(pDisplay, gc, clrMenuBG.pixel);
849  }
850 
851  iVPos += iHeight + HEIGHT_SPACING;
852  }
853 
854  // by convention, restore original objects/state
855 
856  xgc.font = pOldFont->fid;
858  XChangeGC(pDisplay, gc, GCFont, &xgc);
859 
860  XSetForeground(pDisplay, gc, WBGetWindowFGColor(wID)); // restore it at the end
862 
863  WBEndPaint(wID, gc);
864 
865  return 1;
866 }
867 
868 static WBMenuItem * __SelectMenuItemFromMousePos(Window wID, WBMenuPopupWindow *pSelf, WBMenu *pMenu, int iX, int iY)
869 {
870 int i1;
871 int iMaxY;
872 WB_GEOM geom;
873 
874 
875  // first I must invalidate the currently selected item
876 
877  for(i1=0; pMenu->ppItems && i1 < pMenu->nItems; i1++)
878  {
879  WBMenuItem *pItem = pMenu->ppItems[i1];
880 
881  if(!pItem)
882  {
883  continue;
884  }
885 
886  if((i1 + 1) < pMenu->nItems)
887  {
888  iMaxY = pMenu->ppItems[i1 + 1]->iPosition - 1;
889  }
890  else
891  {
892  iMaxY = pSelf->iY + pSelf->iHeight;
893  }
894 
895  if(pItem->iPosition <= iY && iMaxY >= iY) // between them
896  {
897 // XFontStruct *pFont, *pOldFont, *pDefaultMenuFont;
898  int iHPos;
899 
900  if(pSelf->iSelected == i1) // already selected?
901  {
902  return pItem; // just return (nothing else to do)
903  }
904 
905  // WBMenuPopupHandleMenuItemiUI returns 0 if menu NOT handled (default action, disable it)
906  // it returns < 0 to disable, > 0 to enable. See aMENU_UI_ITEM docs for more info
907 
908  if(pItem->iAction == WBMENU_SEPARATOR)// ||
909 // 0 >= MBMenuPopupHandleMenuItemUI(WBGetWindowDisplay(wID), pSelf, pMenu, pItem)) // not handled or 'disable'
910  {
911  if(pSelf->iSelected >= 0 && pSelf->iSelected < pMenu->nItems)
912  {
913  return pMenu->ppItems[pSelf->iSelected];
914  }
915 
916  return NULL;
917  }
918 
919 // TODO: if I need the font, uncomment this later. otherwise, it causes a warning in linux gcc
920 // pFont = pOldFont = WBGetWindowFontStruct(wID);
921 // pDefaultMenuFont = MBGetDefaultMenuFont();
922 //
923 // if(!pDefaultMenuFont && !pOldFont)
924 // {
925 // WB_WARN_PRINT("%s - * BUG * no font!\n", __FUNCTION__);
926 // return 0;
927 // }
928 // else if(pDefaultMenuFont)
929 // {
930 // pFont = pDefaultMenuFont;
931 // }
932 
933  iHPos = 1; // XTextWidth(pFont, " ", 2); // width of 2 spaces
934 
935  // if something is selected it will NOT be the same one I'm trying
936  // to select now, so invalidate it so that it's re-painted
937  if(pSelf->iSelected >= 0 && pSelf->iSelected < pMenu->nItems)
938  {
939  WBMenuItem *pItem0 = pMenu->ppItems[pSelf->iSelected];
940 
941  if(pItem0) // the old selected item
942  {
943  geom.x = iHPos;
944  geom.y = pItem0->iPosition - 1; // actual practice suggests going outside the box a bit
945  geom.width = pSelf->iWidth + 1;
946  geom.height = iMaxY + 2;
947  geom.border = 0;
948  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
949  }
950  }
951 
952  pSelf->iSelected = i1; // indicate the item that's NOW selected
953 
954  // rectangle for new selected item
955  geom.x = iHPos;
956  geom.y = pItem->iPosition - 1; // actual practice suggests going outside the box a bit
957  geom.width = pSelf->iWidth + 1; //pItem->iTextWidth;
958  geom.height = iMaxY + 2;
959  geom.border = 0;
960 
961  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
962 
963  return pItem;
964  }
965  }
966 
967  return NULL;
968 }
969 
970 static void __PostActivatePrevNextEvent(WBMenuPopupWindow *pSelf, int iPrevNext)
971 {
972 XClientMessageEvent evt;
973 
974  bzero(&evt, sizeof(evt));
975  evt.type = ClientMessage;
976  evt.display = WBGetWindowDisplay(pSelf->wBar);
977  evt.window = pSelf->wBar;
978  evt.message_type = aMENU_ACTIVATE;
979  evt.format = 32;
980  evt.data.l[0] = 0;
981  evt.data.l[1] = iPrevNext; // previous/next indicator
982 
983  WBPostPriorityEvent(pSelf->wBar, (XEvent *)&evt);
984 
986  "%s - posting Prev/Next %d to menu bar %d (%08xH)\n",
987  __FUNCTION__, iPrevNext, (int)pSelf->wBar, (int)pSelf->wBar);
988 }
989 
990 // this next callback is assigned via WBRegisterWindowCallback
991 static int MBMenuPopupEvent(Window wID, XEvent *pEvent)
992 {
993  Display *pDisplay = WBGetWindowDisplay(wID);
995  WBMenuItem *pItem;
996  WBMenu *pMenu = pSelf ? pSelf->pMenu : NULL;
997  int i1, iPrevSel;
998  WB_GEOM geom;
999 
1000 
1001 
1002  if(!pSelf)
1003  {
1004  WB_WARN_PRINT("%s - pSelf is NULL\n", __FUNCTION__);
1005  }
1006  else
1007  {
1008  // process 'destroy' events
1009  if(pEvent->type == DestroyNotify)
1010  {
1011  if(pEvent->xdestroywindow.window == wID) // destroying myself?
1012  {
1014  "%s - DestroyNotify\n", __FUNCTION__);
1015 
1016  WBSetWindowData(pSelf->wSelf, 0, 0); // clear the 'back pointer' now
1017 
1018  WBFree(pSelf); // on destroy I always do this (no owned objects so it's easy)
1019 
1020  return 1; // processed
1021  }
1022  }
1023 
1024  // process 'FocusOut' events (this will destroy the window)
1025 
1026  if(pEvent->type == FocusOut)
1027  {
1029  "%s - FocusOut\n", __FUNCTION__);
1030 
1031  WBEndModal(wID, -1); // return '-1' meaning "canceled"
1032  return 1; // processed
1033  }
1034 
1035  // process 'expose' events
1036 
1037  if(pEvent->type == Expose)
1038  {
1039  return MenuPopupDoExposeEvent((XExposeEvent *)pEvent, pMenu, pDisplay, wID, pSelf);
1040  }
1041 
1042  // process keyboard events
1043 
1044  if((pEvent->type == KeyPress ||
1045  pEvent->type == KeyRelease))
1046  {
1047  int iACS=0;
1048  int iKey = WBKeyEventProcessKey((XKeyEvent *)pEvent, NULL, NULL, &iACS);
1049 
1051  "%s - key press/release %x (%d) iACS=%x\n", __FUNCTION__, iKey, iKey, iACS);
1052 
1053  if(pEvent->type == KeyPress)
1054  {
1055  if(iACS & WB_KEYEVENT_KEYSYM)
1056  {
1057  if((iACS & WB_KEYEVENT_ALT)
1058  && (iKey >= XK_F1 && iKey <= XK_F35))
1059  {
1060  XKeyEvent kevt;
1061 
1062  // re-post message to owner as soon as the popup menu goes away
1063 
1064  memcpy(&kevt, &(pEvent->xkey), sizeof(kevt));
1065  kevt.window = kevt.subwindow = pSelf->wOwner;
1066 
1067  WBEndModal(wID, -1); // canceled menu
1068 
1069  WBSetInputFocus(pSelf->wOwner);
1070  XPutBackEvent(pDisplay, (XEvent *)&kevt); // I want this processed correctly
1071 
1072  return 1; // processed
1073  }
1074  else if(iKey == XK_Left)
1075  {
1076  // post an activate message to the menu bar to move to next left and open popup
1077  // and then close this one.
1078 
1079  __PostActivatePrevNextEvent(pSelf, -1);
1080 
1081  WBEndModal(wID, -1); // canceled menu
1082  return 1; // processed
1083  }
1084  else if(iKey == XK_Right)
1085  {
1086  // post an activate message to the menu bar to move to next left and open popup
1087  // and then close this one.
1088 
1089  __PostActivatePrevNextEvent(pSelf, 1);
1090 
1091  WBEndModal(wID, -1); // canceled menu
1092  return 1; // processed
1093  }
1094  else if(iKey == XK_Up)
1095  {
1096  i1 = pSelf->iSelected;
1097 
1098  __SetPrevSelection(pSelf, pMenu);
1099 
1100  if(i1 != pSelf->iSelected)
1101  {
1102  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1103  }
1104  return 1; // handled
1105  }
1106  else if(iKey == XK_Down)
1107  {
1108  i1 = pSelf->iSelected;
1109 
1110  __SetNextSelection(pSelf, pMenu);
1111 
1112  if(i1 != pSelf->iSelected)
1113  {
1114  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1115  }
1116  return 1; // handled
1117  }
1118  else if(iKey == XK_Prior)
1119  {
1120  i1 = pSelf->iSelected;
1121 
1122  __SetFirstSelection(pSelf, pMenu);
1123 
1124  if(i1 != pSelf->iSelected)
1125  {
1126  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1127  }
1128  return 1; // handled
1129  }
1130  else if(iKey == XK_Next)
1131  {
1132  i1 = pSelf->iSelected;
1133 
1134  __SetLastSelection(pSelf, pMenu);
1135 
1136  if(i1 != pSelf->iSelected)
1137  {
1138  WBInvalidateGeom(wID, NULL, TRUE); // TODO: optimize
1139  }
1140  return 1; // handled
1141  }
1142  }
1143  else if(iKey == '\x1b' ||
1144  (((iACS & WB_KEYEVENT_ALT) || (iACS & WB_KEYEVENT_CTRL))// checking for ctrl+TAB or ALT+TAB
1145  && iKey=='\t'))
1146  {
1147  XKeyEvent kevt;
1148 
1149  memcpy(&kevt, &(pEvent->xkey), sizeof(kevt));
1150  kevt.window = kevt.subwindow = pSelf->wOwner;
1151 
1152  WBEndModal(wID, -1); // canceled menu
1153 
1154  if(iKey == '\t') // for the ALT+TAB or CTRL+TAB
1155  {
1156  WBSetInputFocus(pSelf->wOwner);
1157  XPutBackEvent(pDisplay, (XEvent *)&kevt); // I want this processed correctly
1158  }
1159 
1160  return 1; // processed
1161  }
1162 
1163  if(MBMenuProcessHotKey(pMenu, (XKeyEvent *)pEvent) > 0)
1164  {
1165  return 1; // activated
1166  }
1167  }
1168  else if(pEvent->type == KeyRelease)
1169  {
1170  // up/down arrow keys, home, end, pgup, pgdown - choose active item
1171 
1172  // <ENTER>, space bar - activate current item
1173 
1174  if(!iACS && (iKey == '\n' || iKey == '\r' || iKey == ' '))
1175  {
1176  pItem = __GetCurrentSelection(pSelf, pMenu);
1177 
1178  MBMenuPopupHandleMenuItem(pDisplay, wID, pSelf, pMenu, pItem);
1179  return 1; // handled
1180  }
1181  }
1182  }
1183 
1184  // process mouse events
1185 
1186  if((pEvent->type == ButtonPress ||
1187  pEvent->type == ButtonRelease ||
1188  pEvent->type == MotionNotify))
1189  {
1190  int iX, iY; // mousie coordinates (when needed)
1191 
1192  // mousie clickie - yay!
1193 
1194  if(pEvent->type == MotionNotify)
1195  {
1196  WBXlatCoordPoint(pEvent->xmotion.window, pEvent->xmotion.x, pEvent->xmotion.y,
1197  wID, &iX, &iY);
1198  if(pEvent->xmotion.state & Button1Mask) // left drag?
1199  {
1200  // todo - highlight selections by simulating button press, drop into a popup, etc.
1201  }
1202  else if(!(pEvent->xmotion.state & (Button1Mask | Button2Mask | Button3Mask)))
1203  {
1204  // "hover select" - hover far left or far far right, close popup
1205  // vertical hover over, select (immediate)
1206  // hover over far right, open popup (as needed)
1207  // hover below, scroll (as needed)
1208  // hover above, scroll (as needed)
1209 
1210  // hover far left or far far right
1211 
1212  WB_WARN_PRINT("TEMPORARY: %d %d %d\n",
1213  iX, pSelf->iX, pSelf->iX + pSelf->iWidth);
1214  if(iX < pSelf->iX - WB_MOUSE_FAR || iX > pSelf->iX + pSelf->iWidth + 2 * WB_MOUSE_FAR)
1215  {
1216  WBEndModal(wID, -1); // canceled menu
1217 
1218  WBSetInputFocus(pSelf->wOwner);
1219  return 1; // no further processing on this one
1220  }
1221 
1222  // vertical 'hover over'
1223  iPrevSel = pSelf->iSelected;
1224  __SelectMenuItemFromMousePos(wID, pSelf, pMenu, iX, iY);
1225 
1226  if(iPrevSel != pSelf->iSelected)
1227  {
1228  // re-set timers for hover
1229  }
1230 
1231  return 0; // this is just advisory
1232  }
1233  }
1234  else if(pEvent->type == ButtonPress && pMenu)
1235  {
1236  WBXlatCoordPoint(pEvent->xbutton.window, pEvent->xbutton.x, pEvent->xbutton.y,
1237  wID, &iX, &iY);
1238 
1239  if(pEvent->xbutton.state & (Button2Mask | Button3Mask | Button4Mask | Button5Mask
1240  | ShiftMask | LockMask | ControlMask))
1241  {
1242  // this click I ignore (for now)
1243  }
1244  else if(iY >= pSelf->iY && iY <= (pSelf->iY + pSelf->iHeight))
1245  {
1246  // I'm within the dimensions of the 'menu area' so I must now
1247  // which menu item I'm pointing at
1248 
1249  if(__SelectMenuItemFromMousePos(wID, pSelf, pMenu, iX, iY))
1250  {
1251  return 1; // item was found
1252  }
1253 
1254  WB_WARN_PRINT("%s - couldn't find the menu - %d, %d\n", __FUNCTION__, iX, iY);
1255  }
1256  else
1257  {
1259  "%s - Mouse is out of range - %d, %d, %d, %d, %d, %d\n",
1260  __FUNCTION__, iX, iY, pSelf->iX, pSelf->iY, pSelf->iWidth, pSelf->iHeight);
1261  }
1262  }
1263  else // if(pEvent->type == ButtonRelease)
1264  {
1265  i1 = pSelf->iSelected;
1266  pSelf->iSelected = -1;
1267 
1268  if(i1 < 0 || i1 >= pMenu->nItems)
1269  return 0; // nothing selected
1270 
1271  pItem = pMenu->ppItems[i1];
1272  if(!pItem)
1273  return 0;
1274 
1275  geom.x = pItem->iPosition;
1276  geom.y = pSelf->iY;
1277  geom.width = pItem->iTextWidth;
1278  geom.height = pSelf->iHeight;
1279  geom.border = 0;
1280 
1281  WBInvalidateGeom(wID, &geom, TRUE); // force a re-paint (at some point in time)
1282 
1283  // determine what the correct action for this menu item is...
1284  if(pItem->iAction == -1)
1285  return 0; // "not handled"
1286 
1287  MBMenuPopupHandleMenuItem(pDisplay, wID, pSelf, pMenu, pItem);
1288 
1289  return 1;
1290  }
1291  }
1292  }
1293 
1294  if(pEvent && pEvent->type == ClientMessage)
1295  {
1296  if(pMenu &&
1297  ((XClientMessageEvent *)pEvent)->message_type == aMENU_ACTIVATE)
1298  {
1299  int iMenuItemIndex = ((XClientMessageEvent *)pEvent)->data.l[1];
1300  WBMenuItem *pItem = NULL;
1301 
1302  if(iMenuItemIndex >= 0 && iMenuItemIndex < pMenu->nItems)
1303  {
1304  pItem = pMenu->ppItems[iMenuItemIndex];
1305  if((unsigned long)pItem == (unsigned long)(((XClientMessageEvent *)pEvent)->data.l[0]))
1306  {
1307  // NOTE: this will NOT invoke the handler because it sends a MENU_UI_COMMAND before
1308  // posting the handler event
1309  MBMenuPopupHandleMenuItem(pDisplay, wID, pSelf, pMenu, pItem);
1310 
1311  return 1; // handled
1312  }
1313  }
1314 
1315  WB_WARN_PRINT("%s - MENU_ACTIVATE event, invalid menu information, %d %d %p %p\n",
1316  __FUNCTION__, iMenuItemIndex, pMenu->nItems,
1317  (void *)pItem, (void *)(((XClientMessageEvent *)pEvent)->data.l[0]));
1318  }
1319  else if(((XClientMessageEvent *)pEvent)->message_type == aMENU_DISPLAY_POPUP)
1320  {
1321  WBMenuPopupWindow *pPopup;
1322  int iRval = -1;
1323  int iMenuItem = ((XClientMessageEvent *)pEvent)->data.l[0];
1324  int iPosition = ((XClientMessageEvent *)pEvent)->data.l[1];
1325 
1326  for(i1=0; i1 < pMenu->nPopups; i1++)
1327  {
1329  "%s - popup menu id = %d\n",
1330  __FUNCTION__, (pMenu->ppPopups[i1]->iMenuID & WBMENU_POPUP_MASK));
1331 
1332  if(pMenu->ppPopups[i1] &&
1333  (pMenu->ppPopups[i1]->iMenuID & WBMENU_POPUP_MASK) == iMenuItem)
1334  {
1335  pPopup = MBCreateMenuPopupWindow(pSelf->wSelf, pSelf->wOwner, pMenu->ppPopups[i1],
1336  iPosition, pSelf->iY + pSelf->iHeight, 0);
1337 
1338  if(pPopup)
1339  {
1341  "%s - Displaying popup menu %d\n", __FUNCTION__, iMenuItem);
1342 
1343  iRval = MBMenuDoModal(pPopup);
1344 
1346  "%s - done with popup menu %d, return value %d\n", __FUNCTION__, iMenuItem, iRval);
1347  }
1348  else
1349  {
1350  WB_ERROR_PRINT("%s - Unable to create popup menu %d\n", __FUNCTION__, iMenuItem);
1351  }
1352 
1353  WBInvalidateGeom(pSelf->wSelf, NULL, 1); // re-paint (sort of a bug fix)
1354  WBEndModal(wID, iRval);
1355  }
1356  }
1357  if(i1 >= pMenu->nPopups)
1358  {
1359  WB_ERROR_PRINT("%s - Unable to locate popup menu %d\n", __FUNCTION__, iMenuItem);
1360  }
1361 
1362  // in all cases, exit anyway
1363  WBEndModal(wID, -1);
1364  }
1365  }
1366 
1367  return 0; // temporary
1368 }
1369