/* * @OPENGROUP_COPYRIGHT@ * COPYRIGHT NOTICE * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc. * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group * ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for * the full copyright text. * * This software is subject to an open license. It may only be * used on, with or for operating systems which are themselves open * source systems. You must contact The Open Group for a license * allowing distribution and sublicensing of this software on, with, * or for operating systems which are not Open Source programs. * * See http://www.opengroup.org/openmotif/license for full * details of the license agreement. Any use, reproduction, or * distribution of the program constitutes recipient's acceptance of * this agreement. * * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY * OR FITNESS FOR A PARTICULAR PURPOSE * * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. */ /* * HISTORY */ #ifdef REV_INFO #ifndef lint static char *rcsid = "$XConsortium: RCPopup.c /main/7 1996/03/28 15:15:24 daniel $"; #endif #endif #include #include #include "XmI.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GadgetUtiI.h" #include "HashI.h" #include "MapEventsI.h" #include "MenuStateI.h" #include "MessagesI.h" #include "RCMenuI.h" #include "RowColumnI.h" #include "ScreenI.h" #include "TearOffI.h" #include "UniqueEvnI.h" static void ButtonEventHandler( Widget w, XtPointer data, XEvent *event, Boolean *cont) ; static void PopupMenuEventHandler(Widget wid, XtPointer ignored, XEvent *e, Boolean *cont) ; static void AddHandlersToPostFromWidget( Widget popup, Widget widget) ; static Widget FindBestMatchWidget(Widget, XEvent*); static Widget FindPopupMenu(Widget toplevel, Widget target, XEvent* event, int level); static void EventNoop( Widget reportingWidget, XtPointer data, XEvent *event, Boolean *cont) ; static Boolean ProcessKey( XmRowColumnWidget rowcol, XEvent *event) ; static Boolean CheckKey( XmRowColumnWidget rowcol, XEvent *event) ; static int MatchInKeyboardList( XmRowColumnWidget rowcol, XKeyEvent *event, int startIndex) ; static Boolean AllWidgetsAccessible(Widget w) ; static int OnPostFromList(XmRowColumnWidget menu, Widget widget ) ; static void RemoveTable(Widget, XtPointer, XtPointer) ; static Widget MenuMatches(Widget possible, int level, XEvent*); /* Used to store popup information in hash table for non-popup-list menus added by XmAddToPopupFromList */ typedef struct _PopupListRec { WidgetList popups; Cardinal num_popups; } PopupListRec, *PopupList; static XmHashTable popup_table = (XmHashTable) NULL; /**************************************************************************/ /* From the Specification: * * The API view changes with the addition of new values for existing * resources and the addition of a specialized callback for the classes * Primitive and Manager. The combination of these provides the * necessary means to inform Motif of automatic popup menu behavior and * allows user to customize of the automatic behavior. The current delay * for unhandled menu events (a three second hang) will be removed. * * The behavior of the popup menu system will only change with * automatic menus. When the user provides a triggering button event, * the menu system will choose the correct menu to present. **************************************************************************/ /* * The design: * * When a user creates a new popup menu then we will install a particular * event handler on the menu's widget parent. Along with this we install * a grab on the button specified in XmNmenuPost or XmNwhichButton. The * nature of grabs is such that all events will go to the topmost grab * window, therefore the event handler must do all the child list * processing itself (versus the Intrinsics performing the search). * * For keyboard events, the menu system has done half the job in the * past. Here we use the new event handler for just the posting * of the popup menu. We continue to use the old handler for keyboard * input interest for accelerators and mnemonics. * * The posting algorithm is as follows: * * 1. On receipt of a posting event, the handler will search the child * list for a candidate widget or gadget, and track the most specific * popup menu available (these can be found in the popup list). The * criteria for a match includes matching the XmNmenuPost information. * * 2. Matching criteria include: * * * The menu must have XmNpopupEnabled set to either * XmPOPUP_AUTOMATIC or XmPOPUP_AUTOMATIC_RECURSIVE. * * * The popup menu is chosen according to creation order. If there is * more than one, the first correct match is chosen. * * * If the popup menu is found in a parent of the target widget, and * the popup menu must also have XmNpopupEnabled set to * XmPOPUP_AUTOMATIC_RECURSIVE to match. * * 3. Once a selection is made, if the menu's parent widget has a * popupHandlerCallback, it is invoked. The callback allows the user to * determine if a more specific menu is necessary, such as would be the * case in a graphical manipulation environment, and includes all the * necessary information. * * If the user has posted the menu (by clicking the appropriate button) * rather then just a button down, select, button up, then we need to * deal with the unpost and replay semantics of the menu. * * For keyboard events we use the current focus widget instead of the * pointer position and we always post the menu. **************************************************************************/ /*****************************************************************/ /* */ /* New Button Press handler for the Popup menu system and the */ /* associated routines. This code is installed as the event */ /* handler when the type of the Popup menu is */ /* XmPOPUP_AUTOMATIC or XmPOPUP_AUTOMATIC_RECURSIVE. */ /* */ /*****************************************************************/ static Widget lasttarget = (Widget) 0; static void PopupMenuEventHandler(Widget wid, XtPointer poster, XEvent *e, Boolean *cont) { Boolean seen; Widget postWidget; Widget popup; XmPopupHandlerCallbackStruct cb; XmMenuState mst = _XmGetMenuState((Widget) wid); XmRowColumnWidget popupW = (XmRowColumnWidget) poster ; Time _time = _XmGetDefaultTime(wid, e); /* Now look at the event type. It better be a button or key event. Otherwise abort */ if (e -> type != ButtonPress && e -> type != ButtonRelease && e -> type != KeyPress && e -> type != KeyRelease) return; /* First determine if this might have been a replay. This variable gets passed to the callback as the repost field */ seen = (mst->RC_ReplayInfo.time == _time); /* Decide whether this event is a posting event. If it is then we turn cont off to prevent multiple PopupMenuEventHandlers from being invoked on a single event. */ mst->RC_ButtonEventStatus.time = _time; if (e -> type == KeyPress || e -> type == KeyRelease) { mst->RC_ButtonEventStatus.verified = True; } else { mst->RC_ButtonEventStatus.verified = _XmMatchBtnEvent( e, RC_PostEventType(popupW), RC_PostButton(popupW), RC_PostModifiers(popupW)); } /* Add the handler to remove the grab if an error occurs */ if (mst -> RC_ButtonEventStatus.verified) { if (!popupW->core.being_destroyed && !popupW->row_column.popup_workproc) popupW->row_column.popup_workproc = XtAppAddWorkProc(XtWidgetToApplicationContext((Widget) popupW), _XmRC_PostTimeOut, (XtPointer) popupW); mst->RC_ButtonEventStatus.waiting_to_be_managed = TRUE; mst->RC_ButtonEventStatus.event = e -> xbutton; *cont = False; } else /* If this is not a verified event then return */ return; if (e -> type == ButtonPress || e -> type == ButtonRelease) { /* OK. We have a button event on some widget in our hierarchy. Go find the most specific widget */ postWidget = FindBestMatchWidget(wid, e); } else { /* If this is a key event then use the focus data for the most specific widget */ postWidget = XmGetFocusWidget(wid); if (postWidget == (Widget) NULL) postWidget = wid; } /* Now find the right popup */ popup = FindPopupMenu(wid, postWidget, e, 0); /* Construct the callback structure */ _XmProcessLock(); if (seen && (postWidget == lasttarget)) { cb.reason = XmCR_REPOST; cb.postIt = False; } else { cb.reason = XmCR_POST; cb.postIt = True; } lasttarget = postWidget; _XmProcessUnlock(); cb.event = e; cb.menuToPost = popup; cb.target = postWidget; /* Call the callback(s) */ if (XtHasCallbacks(postWidget, XmNpopupHandlerCallback) == XtCallbackHasSome) XtCallCallbacks(postWidget, XmNpopupHandlerCallback, (XtPointer) &cb); popup = cb.menuToPost; if (popup != 0 && cb.postIt) { if (RC_TornOff(popup) && !XmIsMenuShell(XtParent(popup))) _XmRestoreTearOffToMenuShell((Widget)popup, e); /* * popups keep active widget in postFromList in cascadeBtn field - * moved here from PositionMenu so XmGetPostedFromWidget set up * BEFORE application's event handler. */ RC_CascadeBtn(popup) = XtParent(XtParent(popup)); if (e -> type == KeyPress || e -> type == KeyRelease) { XmRowColumnClassRec * rc; rc = (XmRowColumnClassRec *)XtClass(popup); (*(rc->row_column_class.armAndActivate)) (popup, e, NULL, NULL); } else { XmMenuPosition(popup, (XButtonPressedEvent *) e); XtManageChild(popup); } } else { /* If we've gotten to here, we decided not to post the menu, so continue to process the event */ *cont = True; } } /*************************************************************** * FindBestMatchWidget walks down to the most specific widget * that matches the event. For each apparent range match, we * check if this matching widget is a composite. Each composite * then pushes a level and determines if a subwidget of the * composite might match the event ***************************************************************/ static Widget FindBestMatchWidget(Widget wid, XEvent* event) { Widget target; Widget possible; CompositeRec *ctarget; int i; int appx, appy; int delx, dely; int done; int found; target = wid; done = 0; /* appx,appy always contain the relative event (x,y) in the current widget being examined */ appx = event -> xbutton.x; appy = event -> xbutton.y; while(done == 0) { if (XtIsComposite(target)) { ctarget = (CompositeRec *) target; found = 0; for(i = 0; i < ctarget -> composite.num_children; i++) { possible = ctarget -> composite.children[i]; if (XtIsManaged(possible)) { delx = appx - XtX(possible); dely = appy - XtY(possible); if (delx >= 0 && delx <= XtWidth(possible) && dely >= 0 && dely <= XtHeight(possible)) { target = possible; appx = delx; appy = dely; found = 1; break; } } } /* Composite with no children or event outside children */ if (found == 0) done = 1; } else done = 1; } return(target); } /*********************************************************** * FindPopupMenu starts at the target widget (at level == 0) * and starts to walk up the widget hierarchy up to the * toplevel widget. * * At each level, the popup list is searched for a popup * menu with the correct menuPost flags and the correct * value of popupEnabled. **********************************************************/ static Widget FindPopupMenu(Widget toplevel, Widget target, XEvent *event, int level) { int i; WidgetRec *thiswid = (WidgetRec *) target; Widget possible = NULL; if (target == NULL) return(NULL); if (! XmIsGadget(target)) { for(i = 0; i < thiswid -> core.num_popups; i++) { possible = thiswid -> core.popup_list[i]; if ((possible = MenuMatches(possible, level, event)) != NULL) break; } if (! possible) { PopupList list = NULL; _XmProcessLock(); if (popup_table) { /* How we could get here without the table being inited is beyond me. But just in case, avoid crashing */ list = (PopupList) _XmGetHashEntry(popup_table, (XmHashKey) target); } _XmProcessUnlock(); if (list) { for(i = 0; i < list -> num_popups; i++) { possible = list -> popups[i]; if ((possible = MenuMatches(possible, level, event)) != NULL) break; } } } } if (possible) return(possible); else if (toplevel != target) return(FindPopupMenu(toplevel, XtParent(target), event, level + 1)); else return((Widget) NULL); } static Widget MenuMatches(Widget menu, int level, XEvent *event) { Boolean found = False; if (XtIsShell(menu) && ((CompositeRec *) menu) -> composite.num_children == 1) { menu = ((CompositeRec *) menu) -> composite.children[0]; if (XmIsRowColumn(menu) && IsPopup(menu) && (((level == 0) && (RC_PopupEnabled(menu) == XmPOPUP_AUTOMATIC)) || (RC_PopupEnabled(menu) == XmPOPUP_AUTOMATIC_RECURSIVE))) /* We have a candidate. It is a popup menu with an appropriate popup enabled value */ { if (event -> type == KeyPress || event -> type == KeyRelease) { found = MatchInKeyboardList((XmRowColumnWidget) menu, (XKeyEvent *) event, 0) != -1; } else { found = _XmMatchBtnEvent(event, RC_PostEventType(menu), RC_PostButton(menu), RC_PostModifiers(menu)); } } } if (found) return(menu); else return((Widget) NULL); } /*Argsused*/ Boolean _XmRC_PostTimeOut( XtPointer wid ) { XmRowColumnWidget popup = (XmRowColumnWidget) wid; XmMenuState mst = _XmGetMenuState((Widget) wid); Time _time = XtLastTimestampProcessed(XtDisplay((Widget) wid)); popup->row_column.popup_workproc = 0; if (mst->RC_ButtonEventStatus.waiting_to_be_managed) { XtUngrabPointer((Widget) popup, _time); mst->RC_ButtonEventStatus.waiting_to_be_managed = FALSE; mst->RC_ButtonEventStatus.verified = FALSE; } return TRUE; } /* * ButtonEventHandler is inserted at the head of the event handlers. We must * pre-verify the events that popup a menupane. When the application manages * the popup menupane, MenuShell's managed_set_changed(), checks the * verification. */ /* ARGSUSED */ static void ButtonEventHandler( Widget w, XtPointer data, XEvent *event, Boolean *cont ) { XmRowColumnWidget popup = (XmRowColumnWidget) data ; XButtonEvent *xbutton_event = (XButtonEvent *)event; XmMenuState mst = _XmGetMenuState((Widget) w); /* * if this event has been seen before, and some other popup has * already marked it as verified, then we don't need to bother with it. */ if ((mst->RC_ButtonEventStatus.time == xbutton_event->time) && (mst->RC_ButtonEventStatus.verified == True)) /* * If this is a torn-off menu, then we should really make sure * that THIS menu, not the tear-off was verified. Otherwise, * there is no way for the menu's client (ie. mwm) to map the * window since it may be currently reparented. */ if ( !RC_TornOff(popup) || !_XmMatchBtnEvent(event, RC_PostEventType(popup), RC_PostButton(popup), RC_PostModifiers(popup)) ) return; mst->RC_ButtonEventStatus.time = xbutton_event->time; mst->RC_ButtonEventStatus.verified = _XmMatchBtnEvent( event, RC_PostEventType(popup), RC_PostButton(popup), RC_PostModifiers(popup)); if (mst->RC_ButtonEventStatus.verified) { XtUngrabPointer((Widget) popup,CurrentTime); mst->RC_ButtonEventStatus.waiting_to_be_managed = TRUE; if (!popup->core.being_destroyed && !popup->row_column.popup_workproc) popup->row_column.popup_workproc = XtAppAddWorkProc(XtWidgetToApplicationContext((Widget) popup), _XmRC_PostTimeOut, (XtPointer) popup); mst->RC_ButtonEventStatus.event = *xbutton_event; if (RC_TornOff(popup) && !XmIsMenuShell(XtParent(popup))) _XmRestoreTearOffToMenuShell((Widget)popup, event); /* * popups keep active widget in postFromList in cascadeBtn field - * moved here from PositionMenu so XmGetPostedFromWidget set up * BEFORE application's event handler. */ RC_CascadeBtn(popup) = XtWindowToWidget(XtDisplay(popup), xbutton_event->window); } } static void AddHandlersToPostFromWidget( Widget popup, Widget widget ) { Cursor cursor; cursor = _XmGetMenuCursorByScreen(XtScreen(popup)); if (RC_PopupEnabled(popup) == XmPOPUP_AUTOMATIC || RC_PopupEnabled(popup) == XmPOPUP_AUTOMATIC_RECURSIVE) XtInsertEventHandler(widget, ButtonPressMask|ButtonReleaseMask, False, PopupMenuEventHandler, (XtPointer) popup, XtListHead); else XtInsertEventHandler(widget, ButtonPressMask|ButtonReleaseMask, False, ButtonEventHandler, (XtPointer) popup, XtListHead); if ((RC_PopupEnabled(popup) == XmPOPUP_AUTOMATIC || RC_PopupEnabled(popup) == XmPOPUP_AUTOMATIC_RECURSIVE)) XtAddEventHandler(widget, KeyPressMask|KeyReleaseMask, False, PopupMenuEventHandler, (XtPointer) popup); else XtAddEventHandler(widget, KeyPressMask|KeyReleaseMask, False, _XmRC_KeyboardInputHandler, (XtPointer) popup); /* * Add an event handler on the associated widget for ButtonRelease * events. This is so that a quick press/release pair does not get * lost if the release is processed before our pointer grab is made. * This will guarantee that the associated widget gets the button * release event; it would be discarded if the widget was not selecting * for button release events. */ XtAddEventHandler(widget, ButtonReleaseMask, False, EventNoop, NULL); /* * Must add a passive grab, so that owner_events is set to True * when the button grab is activated; this is so that enter/leave * events get dispatched by the server to the client. */ XtGrabButton (widget, RC_PostButton(popup), RC_PostModifiers(popup), TRUE, (unsigned int)ButtonReleaseMask, GrabModeSync, GrabModeSync, None, cursor); } void _XmRC_RemoveHandlersFromPostFromWidget( Widget popup, Widget widget ) { if (RC_PopupEnabled(popup) == XmPOPUP_AUTOMATIC || RC_PopupEnabled(popup) == XmPOPUP_AUTOMATIC_RECURSIVE) XtRemoveEventHandler(widget, ButtonPressMask|ButtonReleaseMask, False, PopupMenuEventHandler, (XtPointer) popup); else XtRemoveEventHandler(widget, ButtonPressMask|ButtonReleaseMask, False, ButtonEventHandler, (XtPointer) popup); XtRemoveEventHandler(widget, KeyPressMask|KeyReleaseMask, False, _XmRC_KeyboardInputHandler, (XtPointer) popup); XtRemoveEventHandler(widget, ButtonReleaseMask, False, EventNoop, NULL); /* Remove our passive grab */ if (!widget->core.being_destroyed) XtUngrabButton (widget, RC_PostButton(popup), AnyModifier); } /* * Add the Popup Menu Event Handlers needed for posting and accelerators */ void _XmRC_AddPopupEventHandlers( XmRowColumnWidget pane ) { int i; /* to myself for gadgets */ XtAddEventHandler( (Widget) pane, KeyPressMask|KeyReleaseMask, False, _XmRC_KeyboardInputHandler, (XtPointer) pane); /* Add to Our shell parent */ XtAddEventHandler(XtParent(pane), KeyPressMask|KeyReleaseMask, False, _XmRC_KeyboardInputHandler, pane); /* add to all of the widgets in the postFromList*/ for (i=0; i < pane->row_column.postFromCount; i++) { AddHandlersToPostFromWidget ((Widget) pane, pane->row_column.postFromList[i]); } } /* * Remove the Popup Menu Event Handlers needed for posting and accelerators */ void _XmRC_RemovePopupEventHandlers( XmRowColumnWidget pane ) { int i; /* Remove it from us */ XtRemoveEventHandler((Widget) pane, KeyPressMask|KeyReleaseMask, False, _XmRC_KeyboardInputHandler, (XtPointer) pane); /* Remove it from our shell parent */ XtRemoveEventHandler(XtParent(pane), KeyPressMask|KeyReleaseMask, False, _XmRC_KeyboardInputHandler, (XtPointer) pane); /* Remove it from the postFrom widgets */ for (i=0; i < pane->row_column.postFromCount; i++) { _XmRC_RemoveHandlersFromPostFromWidget((Widget) pane, pane->row_column.postFromList[i]); } } /* ************************************************************************* * * Semi-public Routines * ************************************************************************* */ /* * _XmPostPopupMenu is intended for use by applications that do their own * event processing/dispatching. This convenience routine makes sure that * the menu system has a crack at verifying the event before MenuShell's * managed_set_changed() routine tries to post the popup. */ void _XmPostPopupMenu( Widget wid, XEvent *event ) { Window saveWindow; XmMenuState mst = _XmGetMenuState((Widget)wid); if (!(wid && XmIsRowColumn(wid) && IsPopup(wid))) return; /* We'll still verify the incoming button event. But for all other cases * we'll just take for granted that the application knows what it's doing * and force the menu to post. */ if (event->type == ButtonPress || event->type == ButtonRelease) { ButtonEventHandler( wid, (XtPointer) wid, event, NULL); /* drand #4973 */ } else { mst->RC_ButtonEventStatus.verified = True; /* This could be trouble if the event type passed in does not have * a time stamp! */ mst->RC_ButtonEventStatus.time = event->xkey.time; mst->RC_ButtonEventStatus.waiting_to_be_managed = True; mst->RC_ButtonEventStatus.event = *((XButtonEvent *)event); } if (mst->RC_ButtonEventStatus.verified) /* sync up Xt timestamp */ { saveWindow = event->xany.window; event->xany.window = 0; XtDispatchEvent(event); event->xany.window = saveWindow; } XtManageChild(wid); } /* When an application uses shared menupanes, enabling accelerators are a * tricky situation. Before now, the application was required to set all * menu-items sensitive when the menu hierarchy unposted. Then when the * activate event arrived, if the event was via an accelerator, internal * verification was done. NOW, with tear offs, this may not be possible. * The application may need to leave menu-items insensitive so that the * tear off acts and looks as desired. In this case, the application * calls this function to pass the events to accelerated insensitive menu * items for its own internal validation. */ void _XmAllowAcceleratedInsensitiveUnmanagedMenuItems( Widget wid, #if NeedWidePrototypes int allowed ) #else Boolean allowed ) #endif /* NeedWidePrototypes */ { _XmGetMenuState((Widget)wid)-> RC_allowAcceleratedInsensitiveUnmanagedMenuItems = (Boolean)allowed; } /*ARGSUSED*/ static void EventNoop( Widget reportingWidget, /* unused */ XtPointer data, /* unused */ XEvent *event, /* unused */ Boolean *cont ) /* unused */ { /* * Do nothing; the purpose is to override the actions taken by the * Primitive translations. */ } /* * This is the event handler which catches, verifies and dispatches all * accelerators and mnemonics defined for a given menu hierarchy. It * is attached to the menu's associated widget, along with an assortment * of other widgets. */ /*ARGSUSED*/ void _XmRC_KeyboardInputHandler( Widget reportingWidget, XtPointer data, XEvent *event, Boolean *cont ) /* unused */ { XmRowColumnWidget topLevel = (XmRowColumnWidget) data; ShellWidget topLevelShell = (ShellWidget)XtParent(topLevel); XmMenuState mst = _XmGetMenuState((Widget)topLevel); /* Process the event only if not already processed */ if (!_XmIsEventUnique(event)) return; if (IsBar(topLevel) || IsOption(topLevel)) if (! AllWidgetsAccessible((Widget) topLevel)) return; /* * XmGetPostFromWidget() requires help to identify the topLevel widget * when a menupane is posted via accelerators. */ if (IsBar(topLevel) || IsOption(topLevel)) mst->RC_LastSelectToplevel = (Widget) topLevel; else if ((IsPopup(topLevel) || IsPulldown(topLevel)) && !XmIsMenuShell(topLevelShell) && XmeFocusIsInShell((Widget)topLevel)) mst->RC_LastSelectToplevel = topLevel->row_column.tear_off_lastSelectToplevel; else if (IsPopup(topLevel)) { /* If the popup is already posted, lastSelectToplevel already set! */ if (!(XmIsMenuShell(topLevelShell) && topLevelShell->shell.popped_up)) mst->RC_LastSelectToplevel = reportingWidget; /* popup */ } else mst->RC_LastSelectToplevel = NULL; ProcessKey (topLevel, event); /* reset toplevel "accelerator" state to NULL */ mst->RC_LastSelectToplevel = NULL; } /* * try to find a match in the menu for the key event. Cascade down the * submenus if necessary */ static Boolean ProcessKey( XmRowColumnWidget rowcol, XEvent *event ) { Boolean found = FALSE; int i; Widget child; Widget SaveCascadeButton; /* Try to use it on the current rowcol */ if (! CheckKey (rowcol, event)) { /* not used, try moving down the cascade */ for (i=0; (i < rowcol->composite.num_children) && (! found); i++) { child = rowcol->composite.children[i]; /* only check sensitive and managed cascade buttons */ if (XtIsSensitive(child) && XtIsManaged(child)) { if (XmIsCascadeButtonGadget(child)) { if (CBG_Submenu(child)) { SaveCascadeButton = RC_CascadeBtn(CBG_Submenu(child)); /* Build the menu cascade for menuHistory */ RC_CascadeBtn(CBG_Submenu(child)) = child; found = ProcessKey((XmRowColumnWidget) ((XmCascadeButtonGadget)child)->cascade_button.submenu, event); /* Restore the cascade button / submenu link in case of * shared menupanes. */ if (!found) RC_CascadeBtn(CBG_Submenu(child)) = SaveCascadeButton; } } else if (XmIsCascadeButton(child)) { if (CB_Submenu(child)) { SaveCascadeButton = RC_CascadeBtn(CB_Submenu(child)); RC_CascadeBtn(CB_Submenu(child)) = child; found = ProcessKey((XmRowColumnWidget) ((XmCascadeButtonWidget)child)->cascade_button.submenu, event); if (!found) RC_CascadeBtn(CB_Submenu(child)) = SaveCascadeButton; } } } } return (found); } else return (True); } /* * Check if the key event is used in the rowcol */ static Boolean CheckKey( XmRowColumnWidget rowcol, XEvent *event ) { int menu_index = 0; XmKeyboardData * entry; ShellWidget shell; /* Process all matching key events */ while ((menu_index = MatchInKeyboardList(rowcol, (XKeyEvent *) event, menu_index)) != -1) { entry = MGR_KeyboardList(rowcol) + menu_index; /* Ignore this entry if it is not accessible to the user */ if (XmIsRowColumn(entry->component)) { /* * Rowcols are not accessible if they are insensitive or * if menubars or optionmenus are unmanaged. */ if (! XtIsSensitive(entry->component) || ((RC_Type(entry->component) != XmMENU_POPUP) && (RC_Type(entry->component) != XmMENU_PULLDOWN) && (! XtIsManaged(entry->component)))) { menu_index++; continue; } } else if (((XmIsMenuShell(XtParent(rowcol)) && ((XmMenuShellWidget)XtParent(rowcol))->shell.popped_up) || !_XmGetMenuState((Widget)rowcol)-> RC_allowAcceleratedInsensitiveUnmanagedMenuItems) && (!XtIsSensitive(entry->component) || !XtIsManaged(entry->component))) { /* In general, insensitive or unmanaged buttons are not accessible. * However, to support shared menupanes, we will allow applications to * pass all accelerated items through, regardless of senstivity/managed. * EXCEPT when the pane is posted, and then sensitivity is presumed * valid. * (The individual menu items also checks if the tear off has the focus.) */ menu_index++; continue; } /* * For a mnemonic, the associated component must be visible, and * it must be in the last menupane posted. * This only needs to be checked for a popup or pulldown menu pane. */ if (entry->isMnemonic) { if (XmeTraitGet((XtPointer) XtClass(entry->component), XmQTmenuSavvy) != NULL) { if (IsBar(XtParent(entry->component)) && ! RC_PopupPosted(XtParent(entry->component)) && ((XmManagerWidget) XtParent(entry->component))-> manager.active_child == NULL) { menu_index++; continue; } else if (IsPopup(XtParent(entry->component)) || IsPulldown(XtParent(entry->component))) { /* See if the associated shell is visible */ shell = (ShellWidget)XtParent(XtParent(entry->component)); /* * Verify the pane is popped up, and the active pane is our * parent (this is necessary because of shared menupanes. */ if ((!shell->shell.popped_up) || (shell->composite.children[0] != XtParent(entry->component))) { menu_index++; continue; } /* Verify we are the last pane */ if (RC_PopupPosted(shell->composite.children[0])) { menu_index++; continue; } } } else if (XmIsRowColumn(entry->component)) { /* * Ignore the posting mnemonic for an option menu, if its * menupane is already posted. */ if (RC_PopupPosted(entry->component)) { menu_index++; continue; } } } /* Key event - make sure we're not in drag mode */ _XmSetInDragMode(entry->component, False); /* Fix 7766, Ungrab the keyboard. This seems to be necessary to avoid focus problems in some cases where the activate callback does "fancy" things like tracking locate. Strictly speaking the ungrab shouldn't be necessary */ if (XmIsGadget(entry->component)) XtUngrabKeyboard(XtParent(entry->component), CurrentTime); else XtUngrabKeyboard(entry->component, CurrentTime); /* Perform the action associated with the keyboard event */ if (XmIsPrimitive(entry->component)) { XmPrimitiveClassRec * prim; prim = (XmPrimitiveClassRec *)XtClass(entry->component); (*(prim->primitive_class.arm_and_activate)) (entry->component, event, NULL, NULL); } else if (XmIsGadget(entry->component)) { XmGadgetClassRec * gadget; gadget = (XmGadgetClassRec *)XtClass(entry->component); (*(gadget->gadget_class.arm_and_activate)) (entry->component, event, NULL, NULL); } else if (XmIsRowColumn(entry->component)) { XmRowColumnClassRec * rc; rc = (XmRowColumnClassRec *)XtClass(entry->component); (*(rc->row_column_class.armAndActivate)) (entry->component, event, NULL, NULL); } /* used the key */ _XmRecordEvent(event); return (True); } /* did not use the key */ return (False); } /* * This function searches the list of keyboard events associated with the * specified row column widget to see if any of them match the * passed in X event. This function can be called multiple times, to get * all entries which match. */ static int MatchInKeyboardList( XmRowColumnWidget rowcol, XKeyEvent *event, int startIndex ) { XmKeyboardData * klist = MGR_KeyboardList(rowcol); int count = MGR_NumKeyboardEntries(rowcol); int i; if (klist == NULL) return(-1); for (i = startIndex; i < count; i++) { /* * We want to ignore shift and shift-lock for mnemonics. So, OR the * event's two bits with the (previously two bits initialized to zero) * klist.modifier * * If the .key field is 1, then we have delayed calling XKeysymToKeycode * until now. */ if (klist[i].key == 1) klist[i].key = XKeysymToKeycode(XtDisplay(rowcol), klist[i].keysym); if (klist[i].key != NoSymbol) { if (_XmMatchKeyEvent((XEvent *) event, klist[i].eventType, klist[i].key, klist[i].isMnemonic ? klist[i].modifiers | (event->state & (ShiftMask | LockMask)) : klist[i].modifiers)) { return(i); } } } /* No match */ return (-1); } /* * This function determines if the widget to which a menu is * attached is accessible to the user. The widget is considered * accessible if it, and its ancestors, are both sensitive and * managed. This is useful for MenuBars and Option Menus only. */ static Boolean AllWidgetsAccessible( Widget w ) { while (w && XtParent(w) && !XtIsShell(w)) { if (!XtIsSensitive(w) || !XtIsManaged(w) || !w->core.mapped_when_managed) return (False); w = XtParent(w); } return (True); } /* * search the postFromList and return the index of the found widget. If it * is not found, return -1 * * This function is duplicated from RCMenu.c. */ static int OnPostFromList( XmRowColumnWidget menu, Widget widget ) { int i; for (i = 0; i < menu->row_column.postFromCount; i++) { if (menu->row_column.postFromList[i] == widget) return (i); } return (-1); } /************************************************************************ * * XmAddToPostFromList and XmRemoveFromPostFromList * ************************************************************************/ void XmAddToPostFromList( Widget m, Widget widget ) { XmRowColumnWidget menu = (XmRowColumnWidget) m ; Arg args[1]; _XmWidgetToAppContext(m); _XmAppLock(app); /* only continue if its a valid widget and a popup or pulldown menu */ if (! XmIsRowColumn(menu) || ! (IsPopup(menu) || IsPulldown(menu)) || ! widget) { _XmAppUnlock(app); return; } if (OnPostFromList(menu, widget) == -1) { /* The first section of this code, new in 2.0, places the popup information in a secondary list so we can find it when doing automatic popup behavior */ PopupList list; /* Add to associated popup data for widget */ _XmProcessLock(); if (! popup_table) { popup_table = _XmAllocHashTable(100, NULL, NULL); } /* See if we have an associated table. If not, create one. */ if ((list = (PopupList) _XmGetHashEntry(popup_table, (XmHashKey) widget)) == NULL) { /* Resize if the table has many more entries than slots */ if (_XmHashTableCount(popup_table) > (2 * _XmHashTableSize(popup_table))) _XmResizeHashTable(popup_table, 2 * _XmHashTableSize(popup_table)); list = (PopupList) XtMalloc(sizeof(PopupListRec)); list -> popups = (WidgetList) NULL; list -> num_popups = 0; _XmAddHashEntry(popup_table, (XmHashKey) widget, (XtPointer) list); XtAddCallback(widget, XtNdestroyCallback, RemoveTable, NULL); } _XmProcessUnlock(); /* Add to list, we add the menu's shell parent */ list -> popups = (WidgetList) XtRealloc((char*) list -> popups, sizeof(Widget) * (list -> num_popups + 1)); list -> popups[list -> num_popups] = XtParent(m); list -> num_popups++; if (IsPulldown(menu)) { XtSetArg (args[0], XmNsubMenuId, menu); XtSetValues (widget, args, 1); } else { _XmRC_AddToPostFromList (menu, widget); AddHandlersToPostFromWidget ((Widget) menu, widget); _XmRC_DoProcessMenuTree ((Widget)menu, XmADD); } } _XmAppUnlock(app); } void XmRemoveFromPostFromList( Widget m, Widget widget ) { XmRowColumnWidget menu = (XmRowColumnWidget) m ; Arg args[1]; _XmWidgetToAppContext(m); _XmAppLock(app); /* only continue if its a valid widget and a popup or pulldown menu */ if (! XmIsRowColumn(menu) || ! (IsPopup(menu) || IsPulldown(menu)) || ! widget) { _XmAppUnlock(app); return; } if ((OnPostFromList(menu, widget)) != -1) { PopupList list; _XmProcessLock(); if (popup_table) { int i, j; /* How we could get here without the table being inited is beyond me. But just in case, avoid crashing */ list = (PopupList) _XmGetHashEntry(popup_table, (XmHashKey) widget); /* Remove from associated list */ for(i = 0; i < list -> num_popups;) { if (list -> popups[i] == XtParent(m)) { /* First shift all the remaining elements */ for(j = i; j < list -> num_popups - 1; j++) { list -> popups[j] = list -> popups[j + 1]; } list -> num_popups--; } else { i++; } } } _XmProcessUnlock(); if (IsPulldown(menu)) { XtSetArg (args[0], XmNsubMenuId, NULL); XtSetValues (widget, args, 1); } else { _XmRC_RemoveFromPostFromList (menu, widget); _XmRC_RemoveHandlersFromPostFromWidget ((Widget) menu, widget); _XmRC_DoProcessMenuTree ((Widget)menu, XmDELETE); } } _XmAppUnlock(app); } /*ARGSUSED*/ static void RemoveTable(Widget w, XtPointer ig1, /* unused */ XtPointer ig2) /* unused */ { PopupList list; _XmProcessLock(); list = (PopupList) _XmGetHashEntry(popup_table, (XmHashKey) w); _XmRemoveHashEntry(popup_table, (XmHashKey) w); XtFree((char*) list -> popups); XtFree((char*) list); _XmProcessUnlock(); }