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