/* * bltBind.c -- * * This module implements object binding procedures for the BLT * toolkit. * * Copyright 1998 Lucent Technologies, Inc. * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that the copyright notice and warranty * disclaimer appear in supporting documentation, and that the names * of Lucent Technologies any of their entities not be used in * advertising or publicity pertaining to distribution of the software * without specific, written prior permission. * * Lucent Technologies disclaims all warranties with regard to this * software, including all implied warranties of merchantability and * fitness. In no event shall Lucent Technologies be liable for any * special, indirect or consequential damages or any damages * whatsoever resulting from loss of use, data or profits, whether in * an action of contract, negligence or other tortuous action, arising * out of or in connection with the use or performance of this * software. */ #include "bltInt.h" #include "bltBind.h" static Tk_EventProc BindProc; /* * Binding table procedures. */ #define REPICK_IN_PROGRESS (1<<0) #define LEFT_GRABBED_ITEM (1<<1) #define ALL_BUTTONS_MASK \ (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask) #ifndef VirtualEventMask #define VirtualEventMask (1L << 30) #endif #define ALL_VALID_EVENTS_MASK \ (ButtonMotionMask | Button1MotionMask | Button2MotionMask | \ Button3MotionMask | Button4MotionMask | Button5MotionMask | \ ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \ LeaveWindowMask | KeyPressMask | KeyReleaseMask | \ PointerMotionMask | VirtualEventMask) static int buttonMasks[] = { 0, /* No buttons pressed */ Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask, }; /* * How to make drag&drop work? * * Right now we generate pseudo events within * button grab on an object. They're marked NotifyVirtual instead * of NotifyAncestor. A better solution: generate new-style * virtual <> <> <> events. * These virtual events don't have to exist as "real" event * sequences, like virtual events do now. */ /* *-------------------------------------------------------------- * * DoEvent -- * * This procedure is called to invoke binding processing * for a new event that is associated with the current item * for a legend. * * Results: * None. * * Side effects: * Depends on the bindings for the legend. A binding script * could delete an entry, so callers should protect themselves * with Tcl_Preserve and Tcl_Release. * *-------------------------------------------------------------- */ static void DoEvent(bindPtr, eventPtr, item, context) struct Blt_BindTableStruct *bindPtr; /* Binding information for widget in * which event occurred. */ XEvent *eventPtr; /* Real or simulated X event that * is to be processed. */ ClientData item; /* Item picked. */ ClientData context; /* Context of item. */ { Blt_List bindIds; int nIds; if ((bindPtr->tkwin == NULL) || (bindPtr->bindingTable == NULL)) { return; } if ((eventPtr->type == KeyPress) || (eventPtr->type == KeyRelease)) { item = bindPtr->focusItem; context = bindPtr->focusContext; } if (item == NULL) { return; } /* * Invoke the binding system. */ bindIds = Blt_ListCreate(BLT_ONE_WORD_KEYS); if (bindPtr->tagProc == NULL) { Blt_ListAppend(bindIds, (char *)Tk_GetUid("all"), 0); Blt_ListAppend(bindIds, (char *)item, 0); } else { (*bindPtr->tagProc) (bindPtr, item, context, bindIds); } nIds = Blt_ListGetLength(bindIds); if (nIds > 0) { ClientData *idArray; ClientData tags[32]; Blt_ListNode node; idArray = tags; if (nIds >= 32) { idArray = Blt_Malloc(sizeof(ClientData) * nIds); } nIds = 0; for (node = Blt_ListFirstNode(bindIds); node != NULL; node = Blt_ListNextNode(node)) { idArray[nIds++] = (ClientData)Blt_ListGetKey(node); } Tk_BindEvent(bindPtr->bindingTable, eventPtr, bindPtr->tkwin, nIds, idArray); if (nIds >= 32) { Blt_Free(idArray); } } Blt_ListDestroy(bindIds); } /* *-------------------------------------------------------------- * * PickCurrentItem -- * * Find the topmost item in a legend that contains a given * location and mark the the current item. If the current * item has changed, generate a fake exit event on the old * current item and a fake enter event on the new current * item. * * Results: * None. * * Side effects: * The current item may change. If it does, then the commands * associated with item entry and exit could do just about * anything. A binding script could delete the legend, so * callers should protect themselves with Tcl_Preserve and * Tcl_Release. * *-------------------------------------------------------------- */ static void PickCurrentItem(bindPtr, eventPtr) struct Blt_BindTableStruct *bindPtr; /* Binding table information. */ XEvent *eventPtr; /* Event describing location of * mouse cursor. Must be EnterWindow, * LeaveWindow, ButtonRelease, or * MotionNotify. */ { int buttonDown; ClientData newItem; ClientData newContext; /* * Check whether or not a button is down. If so, we'll log entry * and exit into and out of the current item, but not entry into * any other item. This implements a form of grabbing equivalent * to what the X server does for windows. */ buttonDown = (bindPtr->state & ALL_BUTTONS_MASK); if (!buttonDown) { bindPtr->flags &= ~LEFT_GRABBED_ITEM; } /* * Save information about this event in the widget. The event in * the widget is used for two purposes: * * 1. Event bindings: if the current item changes, fake events are * generated to allow item-enter and item-leave bindings to trigger. * 2. Reselection: if the current item gets deleted, can use the * saved event to find a new current item. * Translate MotionNotify events into EnterNotify events, since that's * what gets reported to item handlers. */ if (eventPtr != &bindPtr->pickEvent) { if ((eventPtr->type == MotionNotify) || (eventPtr->type == ButtonRelease)) { bindPtr->pickEvent.xcrossing.type = EnterNotify; bindPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial; bindPtr->pickEvent.xcrossing.send_event = eventPtr->xmotion.send_event; bindPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display; bindPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window; bindPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root; bindPtr->pickEvent.xcrossing.subwindow = None; bindPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time; bindPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x; bindPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y; bindPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root; bindPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root; bindPtr->pickEvent.xcrossing.mode = NotifyNormal; bindPtr->pickEvent.xcrossing.detail = NotifyNonlinear; bindPtr->pickEvent.xcrossing.same_screen = eventPtr->xmotion.same_screen; bindPtr->pickEvent.xcrossing.focus = False; bindPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state; } else { bindPtr->pickEvent = *eventPtr; } } bindPtr->activePick = TRUE; /* * If this is a recursive call (there's already a partially completed * call pending on the stack; it's in the middle of processing a * Leave event handler for the old current item) then just return; * the pending call will do everything that's needed. */ if (bindPtr->flags & REPICK_IN_PROGRESS) { return; } /* * A LeaveNotify event automatically means that there's no current * item, so the check for closest item can be skipped. */ newContext = NULL; if (bindPtr->pickEvent.type != LeaveNotify) { int x, y; x = bindPtr->pickEvent.xcrossing.x; y = bindPtr->pickEvent.xcrossing.y; newItem = (*bindPtr->pickProc) (bindPtr->clientData, x, y, &newContext); } else { newItem = NULL; } if (((newItem == bindPtr->currentItem) && (newContext == bindPtr->currentContext)) && (!(bindPtr->flags & LEFT_GRABBED_ITEM))) { /* * Nothing to do: the current item hasn't changed. */ return; } #ifndef FULLY_SIMULATE_GRAB if (((newItem != bindPtr->currentItem) || (newContext != bindPtr->currentContext)) && (buttonDown)) { bindPtr->flags |= LEFT_GRABBED_ITEM; return; } #endif /* * Simulate a LeaveNotify event on the previous current item and * an EnterNotify event on the new current item. Remove the "current" * tag from the previous current item and place it on the new current * item. */ if ((bindPtr->currentItem != NULL) && ((newItem != bindPtr->currentItem) || (newContext != bindPtr->currentContext)) && !(bindPtr->flags & LEFT_GRABBED_ITEM)) { XEvent event; event = bindPtr->pickEvent; event.type = LeaveNotify; /* * If the event's detail happens to be NotifyInferior the * binding mechanism will discard the event. To be consistent, * always use NotifyAncestor. */ event.xcrossing.detail = NotifyAncestor; bindPtr->flags |= REPICK_IN_PROGRESS; DoEvent(bindPtr, &event, bindPtr->currentItem, bindPtr->currentContext); bindPtr->flags &= ~REPICK_IN_PROGRESS; /* * Note: during DoEvent above, it's possible that * bindPtr->newItem got reset to NULL because the * item was deleted. */ } if (((newItem != bindPtr->currentItem) || (newContext != bindPtr->currentContext)) && (buttonDown)) { XEvent event; bindPtr->flags |= LEFT_GRABBED_ITEM; event = bindPtr->pickEvent; if ((newItem != bindPtr->newItem) || (newContext != bindPtr->newContext)) { ClientData savedItem; ClientData savedContext; /* * Generate and events for objects during * button grabs. This isn't standard. But for example, it * allows one to provide balloon help on the individual * entries of the Hierbox widget. */ savedItem = bindPtr->currentItem; savedContext = bindPtr->currentContext; if (bindPtr->newItem != NULL) { event.type = LeaveNotify; event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; bindPtr->currentItem = bindPtr->newItem; DoEvent(bindPtr, &event, bindPtr->newItem, bindPtr->newContext); } bindPtr->newItem = newItem; bindPtr->newContext = newContext; if (newItem != NULL) { event.type = EnterNotify; event.xcrossing.detail = NotifyVirtual /* Ancestor */ ; bindPtr->currentItem = newItem; DoEvent(bindPtr, &event, newItem, newContext); } bindPtr->currentItem = savedItem; bindPtr->currentContext = savedContext; } return; } /* * Special note: it's possible that * bindPtr->newItem == bindPtr->currentItem * here. This can happen, for example, if LEFT_GRABBED_ITEM was set. */ bindPtr->flags &= ~LEFT_GRABBED_ITEM; bindPtr->currentItem = bindPtr->newItem = newItem; bindPtr->currentContext = bindPtr->newContext = newContext; if (bindPtr->currentItem != NULL) { XEvent event; event = bindPtr->pickEvent; event.type = EnterNotify; event.xcrossing.detail = NotifyAncestor; DoEvent(bindPtr, &event, newItem, newContext); } } /* *-------------------------------------------------------------- * * BindProc -- * * This procedure is invoked by the Tk dispatcher to handle * events associated with bindings on items. * * Results: * None. * * Side effects: * Depends on the command invoked as part of the binding * (if there was any). * *-------------------------------------------------------------- */ static void BindProc(clientData, eventPtr) ClientData clientData; /* Pointer to widget structure. */ XEvent *eventPtr; /* Pointer to X event that just * happened. */ { struct Blt_BindTableStruct *bindPtr = clientData; int mask; Tcl_Preserve(bindPtr->clientData); /* * This code below keeps track of the current modifier state in * bindPtr->state. This information is used to defer repicks of * the current item while buttons are down. */ switch (eventPtr->type) { case ButtonPress: case ButtonRelease: mask = 0; if ((eventPtr->xbutton.button >= Button1) && (eventPtr->xbutton.button <= Button5)) { mask = buttonMasks[eventPtr->xbutton.button]; } /* * For button press events, repick the current item using the * button state before the event, then process the event. For * button release events, first process the event, then repick * the current item using the button state *after* the event * (the button has logically gone up before we change the * current item). */ if (eventPtr->type == ButtonPress) { /* * On a button press, first repick the current item using * the button state before the event, the process the event. */ bindPtr->state = eventPtr->xbutton.state; PickCurrentItem(bindPtr, eventPtr); bindPtr->state ^= mask; DoEvent(bindPtr, eventPtr, bindPtr->currentItem, bindPtr->currentContext); } else { /* * Button release: first process the event, with the button * still considered to be down. Then repick the current * item under the assumption that the button is no longer down. */ bindPtr->state = eventPtr->xbutton.state; DoEvent(bindPtr, eventPtr, bindPtr->currentItem, bindPtr->currentContext); eventPtr->xbutton.state ^= mask; bindPtr->state = eventPtr->xbutton.state; PickCurrentItem(bindPtr, eventPtr); eventPtr->xbutton.state ^= mask; } break; case EnterNotify: case LeaveNotify: bindPtr->state = eventPtr->xcrossing.state; PickCurrentItem(bindPtr, eventPtr); break; case MotionNotify: bindPtr->state = eventPtr->xmotion.state; PickCurrentItem(bindPtr, eventPtr); DoEvent(bindPtr, eventPtr, bindPtr->currentItem, bindPtr->currentContext); break; case KeyPress: case KeyRelease: bindPtr->state = eventPtr->xkey.state; PickCurrentItem(bindPtr, eventPtr); DoEvent(bindPtr, eventPtr, bindPtr->currentItem, bindPtr->currentContext); break; } Tcl_Release(bindPtr->clientData); } int Blt_ConfigureBindings(interp, bindPtr, item, argc, argv) Tcl_Interp *interp; struct Blt_BindTableStruct *bindPtr; ClientData item; int argc; CONST char **argv; { CONST char *command; unsigned long mask; CONST char *seq; if (argc == 0) { Tk_GetAllBindings(interp, bindPtr->bindingTable, item); return TCL_OK; } if (argc == 1) { command = Tk_GetBinding(interp, bindPtr->bindingTable, item, argv[0]); if (command == NULL) { return TCL_ERROR; } Tcl_SetResult(interp, (char *) command, TCL_VOLATILE); return TCL_OK; } seq = argv[0]; command = argv[1]; if (command[0] == '\0') { return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); } if (command[0] == '+') { mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, command + 1, TRUE); } else { mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, command, FALSE); } if (mask == 0) { return TCL_ERROR; } if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); Tcl_ResetResult(interp); Tcl_AppendResult(interp, "requested illegal events; ", "only key, button, motion, enter, leave, and virtual ", "events may be used", (char *)NULL); return TCL_ERROR; } return TCL_OK; } int Blt_ConfigureBindingsFromObj(interp, bindPtr, item, objc, objv) Tcl_Interp *interp; struct Blt_BindTableStruct *bindPtr; ClientData item; int objc; Tcl_Obj *CONST *objv; { char *command; unsigned long mask; char *seq; char *string; if (objc == 0) { Tk_GetAllBindings(interp, bindPtr->bindingTable, item); return TCL_OK; } string = Tcl_GetString(objv[0]); if (objc == 1) { command = (char *) Tk_GetBinding(interp, bindPtr->bindingTable, item, string); if (command == NULL) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, "invalid binding event \"", string, "\"", (char *)NULL); return TCL_ERROR; } Tcl_SetResult(interp, command, TCL_VOLATILE); return TCL_OK; } seq = string; command = Tcl_GetString(objv[1]); if (command[0] == '\0') { return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); } if (command[0] == '+') { mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, command + 1, TRUE); } else { mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq, command, FALSE); } if (mask == 0) { return TCL_ERROR; } if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) { Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq); Tcl_ResetResult(interp); Tcl_AppendResult(interp, "requested illegal events; ", "only key, button, motion, enter, leave, and virtual ", "events may be used", (char *)NULL); return TCL_ERROR; } return TCL_OK; } Blt_BindTable Blt_CreateBindingTable(interp, tkwin, clientData, pickProc, tagProc) Tcl_Interp *interp; Tk_Window tkwin; ClientData clientData; Blt_BindPickProc *pickProc; Blt_BindTagProc *tagProc; { unsigned int mask; struct Blt_BindTableStruct *bindPtr; bindPtr = Blt_Calloc(1, sizeof(struct Blt_BindTableStruct)); assert(bindPtr); bindPtr->clientData = clientData; bindPtr->pickProc = pickProc; bindPtr->tagProc = tagProc; bindPtr->tkwin = tkwin; bindPtr->bindingTable = Tk_CreateBindingTable(interp); mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask); Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); return bindPtr; } void Blt_DestroyBindingTable(bindPtr) struct Blt_BindTableStruct *bindPtr; { unsigned int mask; Tk_DeleteBindingTable(bindPtr->bindingTable); mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask); Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); Blt_Free(bindPtr); } void Blt_PickCurrentItem(bindPtr) struct Blt_BindTableStruct *bindPtr; { if (bindPtr->activePick) { PickCurrentItem(bindPtr, &(bindPtr->pickEvent)); } } void Blt_DeleteBindings(bindPtr, object) struct Blt_BindTableStruct *bindPtr; ClientData object; { Tk_DeleteAllBindings(bindPtr->bindingTable, object); /* * If this is the object currently picked, we need to repick one. */ if (bindPtr->currentItem == object) { bindPtr->currentItem = NULL; bindPtr->currentContext = NULL; } if (bindPtr->newItem == object) { bindPtr->newItem = NULL; bindPtr->newContext = NULL; } if (bindPtr->focusItem == object) { bindPtr->focusItem = NULL; bindPtr->focusContext = NULL; } } void Blt_MoveBindingTable(bindPtr, tkwin) struct Blt_BindTableStruct *bindPtr; Tk_Window tkwin; { unsigned int mask; mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask); if (bindPtr->tkwin != NULL) { Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr); } Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr); bindPtr->tkwin = tkwin; }