1 /*
2 * bltBind.c --
3 *
4 * This module implements object binding procedures for the BLT
5 * toolkit.
6 *
7 * Copyright 1998 Lucent Technologies, Inc.
8 *
9 * Permission to use, copy, modify, and distribute this software and
10 * its documentation for any purpose and without fee is hereby
11 * granted, provided that the above copyright notice appear in all
12 * copies and that both that the copyright notice and warranty
13 * disclaimer appear in supporting documentation, and that the names
14 * of Lucent Technologies any of their entities not be used in
15 * advertising or publicity pertaining to distribution of the software
16 * without specific, written prior permission.
17 *
18 * Lucent Technologies disclaims all warranties with regard to this
19 * software, including all implied warranties of merchantability and
20 * fitness. In no event shall Lucent Technologies be liable for any
21 * special, indirect or consequential damages or any damages
22 * whatsoever resulting from loss of use, data or profits, whether in
23 * an action of contract, negligence or other tortuous action, arising
24 * out of or in connection with the use or performance of this
25 * software.
26 */
27
28 #include "bltInt.h"
29 #include "bltBind.h"
30
31 static Tk_EventProc BindProc;
32
33 /*
34 * Binding table procedures.
35 */
36 #define REPICK_IN_PROGRESS (1<<0)
37 #define LEFT_GRABBED_ITEM (1<<1)
38
39 #define ALL_BUTTONS_MASK \
40 (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)
41
42 #ifndef VirtualEventMask
43 #define VirtualEventMask (1L << 30)
44 #endif
45
46 #define ALL_VALID_EVENTS_MASK \
47 (ButtonMotionMask | Button1MotionMask | Button2MotionMask | \
48 Button3MotionMask | Button4MotionMask | Button5MotionMask | \
49 ButtonPressMask | ButtonReleaseMask | EnterWindowMask | \
50 LeaveWindowMask | KeyPressMask | KeyReleaseMask | \
51 PointerMotionMask | VirtualEventMask)
52
53 static int buttonMasks[] =
54 {
55 0, /* No buttons pressed */
56 Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask,
57 };
58
59
60 /*
61 * How to make drag&drop work?
62 *
63 * Right now we generate pseudo <Enter> <Leave> events within
64 * button grab on an object. They're marked NotifyVirtual instead
65 * of NotifyAncestor. A better solution: generate new-style
66 * virtual <<DragEnter>> <<DragMotion>> <<DragLeave>> events.
67 * These virtual events don't have to exist as "real" event
68 * sequences, like virtual events do now.
69 */
70
71 /*
72 *--------------------------------------------------------------
73 *
74 * DoEvent --
75 *
76 * This procedure is called to invoke binding processing
77 * for a new event that is associated with the current item
78 * for a legend.
79 *
80 * Results:
81 * None.
82 *
83 * Side effects:
84 * Depends on the bindings for the legend. A binding script
85 * could delete an entry, so callers should protect themselves
86 * with Tcl_Preserve and Tcl_Release.
87 *
88 *--------------------------------------------------------------
89 */
90 static void
DoEvent(bindPtr,eventPtr,item,context)91 DoEvent(bindPtr, eventPtr, item, context)
92 struct Blt_BindTableStruct *bindPtr; /* Binding information for widget in
93 * which event occurred. */
94 XEvent *eventPtr; /* Real or simulated X event that
95 * is to be processed. */
96 ClientData item; /* Item picked. */
97 ClientData context; /* Context of item. */
98 {
99 Blt_List bindIds;
100 int nIds;
101
102 if ((bindPtr->tkwin == NULL) || (bindPtr->bindingTable == NULL)) {
103 return;
104 }
105 if (Tcl_InterpDeleted(bindPtr->interp)) {
106 return;
107 }
108
109 if ((eventPtr->type == KeyPress) || (eventPtr->type == KeyRelease)) {
110 item = bindPtr->focusItem;
111 context = bindPtr->focusContext;
112 }
113 if (item == NULL) {
114 return;
115 }
116 /*
117 * Invoke the binding system.
118 */
119
120 bindIds = Blt_ListCreate(BLT_ONE_WORD_KEYS);
121 if (bindPtr->tagProc == NULL) {
122 Blt_ListAppend(bindIds, (char *)Tk_GetUid("all"), 0);
123 Blt_ListAppend(bindIds, (char *)item, 0);
124 } else {
125 (*bindPtr->tagProc) (bindPtr, item, context, bindIds);
126 }
127 nIds = Blt_ListGetLength(bindIds);
128 if (nIds > 0) {
129 ClientData *idArray;
130 ClientData tags[32];
131 register Blt_ListNode node;
132
133 idArray = tags;
134 if (nIds >= 32) {
135 idArray = Blt_Malloc(sizeof(ClientData) * nIds);
136
137 }
138 nIds = 0;
139 for (node = Blt_ListFirstNode(bindIds); node != NULL;
140 node = Blt_ListNextNode(node)) {
141 idArray[nIds++] = (ClientData)Blt_ListGetKey(node);
142 }
143 Tk_BindEvent(bindPtr->bindingTable, eventPtr, bindPtr->tkwin, nIds,
144 idArray);
145 if (nIds >= 32) {
146 Blt_Free(idArray);
147 }
148 }
149 Blt_ListDestroy(bindIds);
150 }
151
152 /*
153 *--------------------------------------------------------------
154 *
155 * PickCurrentItem --
156 *
157 * Find the topmost item in a legend that contains a given
158 * location and mark the the current item. If the current
159 * item has changed, generate a fake exit event on the old
160 * current item and a fake enter event on the new current
161 * item.
162 *
163 * Results:
164 * None.
165 *
166 * Side effects:
167 * The current item may change. If it does, then the commands
168 * associated with item entry and exit could do just about
169 * anything. A binding script could delete the legend, so
170 * callers should protect themselves with Tcl_Preserve and
171 * Tcl_Release.
172 *
173 *--------------------------------------------------------------
174 */
175 static void
PickCurrentItem(bindPtr,eventPtr)176 PickCurrentItem(bindPtr, eventPtr)
177 struct Blt_BindTableStruct *bindPtr; /* Binding table information. */
178 XEvent *eventPtr; /* Event describing location of
179 * mouse cursor. Must be EnterWindow,
180 * LeaveWindow, ButtonRelease, or
181 * MotionNotify. */
182 {
183 int buttonDown;
184 ClientData newItem;
185 ClientData newContext;
186
187 if (Tcl_InterpDeleted(bindPtr->interp)) {
188 return;
189 }
190 /*
191 * Check whether or not a button is down. If so, we'll log entry
192 * and exit into and out of the current item, but not entry into
193 * any other item. This implements a form of grabbing equivalent
194 * to what the X server does for windows.
195 */
196 buttonDown = (bindPtr->state & ALL_BUTTONS_MASK);
197 if (!buttonDown) {
198 bindPtr->flags &= ~LEFT_GRABBED_ITEM;
199 }
200 /*
201 * Save information about this event in the widget. The event in
202 * the widget is used for two purposes:
203 *
204 * 1. Event bindings: if the current item changes, fake events are
205 * generated to allow item-enter and item-leave bindings to trigger.
206 * 2. Reselection: if the current item gets deleted, can use the
207 * saved event to find a new current item.
208 * Translate MotionNotify events into EnterNotify events, since that's
209 * what gets reported to item handlers.
210 */
211
212 if (eventPtr != &bindPtr->pickEvent) {
213 if ((eventPtr->type == MotionNotify) ||
214 (eventPtr->type == ButtonRelease)) {
215 bindPtr->pickEvent.xcrossing.type = EnterNotify;
216 bindPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial;
217 bindPtr->pickEvent.xcrossing.send_event =
218 eventPtr->xmotion.send_event;
219 bindPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display;
220 bindPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window;
221 bindPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root;
222 bindPtr->pickEvent.xcrossing.subwindow = None;
223 bindPtr->pickEvent.xcrossing.time = eventPtr->xmotion.time;
224 bindPtr->pickEvent.xcrossing.x = eventPtr->xmotion.x;
225 bindPtr->pickEvent.xcrossing.y = eventPtr->xmotion.y;
226 bindPtr->pickEvent.xcrossing.x_root = eventPtr->xmotion.x_root;
227 bindPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root;
228 bindPtr->pickEvent.xcrossing.mode = NotifyNormal;
229 bindPtr->pickEvent.xcrossing.detail = NotifyNonlinear;
230 bindPtr->pickEvent.xcrossing.same_screen
231 = eventPtr->xmotion.same_screen;
232 bindPtr->pickEvent.xcrossing.focus = False;
233 bindPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state;
234 } else {
235 bindPtr->pickEvent = *eventPtr;
236 }
237 }
238 bindPtr->activePick = TRUE;
239
240 /*
241 * If this is a recursive call (there's already a partially completed
242 * call pending on the stack; it's in the middle of processing a
243 * Leave event handler for the old current item) then just return;
244 * the pending call will do everything that's needed.
245 */
246 if (bindPtr->flags & REPICK_IN_PROGRESS) {
247 return;
248 }
249 /*
250 * A LeaveNotify event automatically means that there's no current
251 * item, so the check for closest item can be skipped.
252 */
253 newContext = NULL;
254 if (bindPtr->pickEvent.type != LeaveNotify) {
255 int x, y;
256
257 x = bindPtr->pickEvent.xcrossing.x;
258 y = bindPtr->pickEvent.xcrossing.y;
259 newItem = (*bindPtr->pickProc) (bindPtr->clientData, x, y, &newContext);
260 } else {
261 newItem = NULL;
262 }
263 if (((newItem == bindPtr->currentItem) &&
264 (newContext == bindPtr->currentContext)) &&
265 (!(bindPtr->flags & LEFT_GRABBED_ITEM))) {
266 /*
267 * Nothing to do: the current item hasn't changed.
268 */
269 return;
270 }
271 #ifndef FULLY_SIMULATE_GRAB
272 if (((newItem != bindPtr->currentItem) ||
273 (newContext != bindPtr->currentContext)) &&
274 (buttonDown)) {
275 bindPtr->flags |= LEFT_GRABBED_ITEM;
276 return;
277 }
278 #endif
279 /*
280 * Simulate a LeaveNotify event on the previous current item and
281 * an EnterNotify event on the new current item. Remove the "current"
282 * tag from the previous current item and place it on the new current
283 * item.
284 */
285 if ((bindPtr->currentItem != NULL) &&
286 ((newItem != bindPtr->currentItem) ||
287 (newContext != bindPtr->currentContext)) &&
288 !(bindPtr->flags & LEFT_GRABBED_ITEM)) {
289 XEvent event;
290
291 event = bindPtr->pickEvent;
292 event.type = LeaveNotify;
293 /*
294 * If the event's detail happens to be NotifyInferior the
295 * binding mechanism will discard the event. To be consistent,
296 * always use NotifyAncestor.
297 */
298 event.xcrossing.detail = NotifyAncestor;
299
300 bindPtr->flags |= REPICK_IN_PROGRESS;
301 DoEvent(bindPtr, &event, bindPtr->currentItem, bindPtr->currentContext);
302 bindPtr->flags &= ~REPICK_IN_PROGRESS;
303
304 /*
305 * Note: during DoEvent above, it's possible that
306 * bindPtr->newItem got reset to NULL because the
307 * item was deleted.
308 */
309 }
310 if (((newItem != bindPtr->currentItem) ||
311 (newContext != bindPtr->currentContext)) &&
312 (buttonDown)) {
313 XEvent event;
314
315 bindPtr->flags |= LEFT_GRABBED_ITEM;
316 event = bindPtr->pickEvent;
317 if ((newItem != bindPtr->newItem) ||
318 (newContext != bindPtr->newContext)) {
319 ClientData savedItem;
320 ClientData savedContext;
321
322 /*
323 * Generate <Enter> and <Leave> events for objects during
324 * button grabs. This isn't standard. But for example, it
325 * allows one to provide balloon help on the individual
326 * entries of the Hierbox widget.
327 */
328 savedItem = bindPtr->currentItem;
329 savedContext = bindPtr->currentContext;
330 if (bindPtr->newItem != NULL) {
331 event.type = LeaveNotify;
332 event.xcrossing.detail = NotifyVirtual /* Ancestor */ ;
333 bindPtr->currentItem = bindPtr->newItem;
334 DoEvent(bindPtr, &event, bindPtr->newItem, bindPtr->newContext);
335 }
336 bindPtr->newItem = newItem;
337 bindPtr->newContext = newContext;
338 if (newItem != NULL) {
339 event.type = EnterNotify;
340 event.xcrossing.detail = NotifyVirtual /* Ancestor */ ;
341 bindPtr->currentItem = newItem;
342 DoEvent(bindPtr, &event, newItem, newContext);
343 }
344 bindPtr->currentItem = savedItem;
345 bindPtr->currentContext = savedContext;
346 }
347 return;
348 }
349 /*
350 * Special note: it's possible that
351 * bindPtr->newItem == bindPtr->currentItem
352 * here. This can happen, for example, if LEFT_GRABBED_ITEM was set.
353 */
354
355 bindPtr->flags &= ~LEFT_GRABBED_ITEM;
356 bindPtr->currentItem = bindPtr->newItem = newItem;
357 bindPtr->currentContext = bindPtr->newContext = newContext;
358 if (bindPtr->currentItem != NULL) {
359 XEvent event;
360
361 event = bindPtr->pickEvent;
362 event.type = EnterNotify;
363 event.xcrossing.detail = NotifyAncestor;
364 DoEvent(bindPtr, &event, newItem, newContext);
365 }
366 }
367
368 /*
369 *--------------------------------------------------------------
370 *
371 * BindProc --
372 *
373 * This procedure is invoked by the Tk dispatcher to handle
374 * events associated with bindings on items.
375 *
376 * Results:
377 * None.
378 *
379 * Side effects:
380 * Depends on the command invoked as part of the binding
381 * (if there was any).
382 *
383 *--------------------------------------------------------------
384 */
385 static void
BindProc(clientData,eventPtr)386 BindProc(clientData, eventPtr)
387 ClientData clientData; /* Pointer to widget structure. */
388 XEvent *eventPtr; /* Pointer to X event that just
389 * happened. */
390 {
391 struct Blt_BindTableStruct *bindPtr = clientData;
392 int mask;
393
394 if (Tcl_InterpDeleted(bindPtr->interp)) {
395 return;
396 }
397 Tcl_Preserve(bindPtr->clientData);
398
399 /*
400 * This code below keeps track of the current modifier state in
401 * bindPtr->state. This information is used to defer repicks of
402 * the current item while buttons are down.
403 */
404 switch (eventPtr->type) {
405 case ButtonPress:
406 case ButtonRelease:
407 mask = 0;
408 if ((eventPtr->xbutton.button >= Button1) &&
409 (eventPtr->xbutton.button <= Button5)) {
410 mask = buttonMasks[eventPtr->xbutton.button];
411 }
412 /*
413 * For button press events, repick the current item using the
414 * button state before the event, then process the event. For
415 * button release events, first process the event, then repick
416 * the current item using the button state *after* the event
417 * (the button has logically gone up before we change the
418 * current item).
419 */
420
421 if (eventPtr->type == ButtonPress) {
422
423 /*
424 * On a button press, first repick the current item using
425 * the button state before the event, the process the event.
426 */
427
428 bindPtr->state = eventPtr->xbutton.state;
429 PickCurrentItem(bindPtr, eventPtr);
430 bindPtr->state ^= mask;
431 DoEvent(bindPtr, eventPtr, bindPtr->currentItem,
432 bindPtr->currentContext);
433
434 } else {
435
436 /*
437 * Button release: first process the event, with the button
438 * still considered to be down. Then repick the current
439 * item under the assumption that the button is no longer down.
440 */
441 bindPtr->state = eventPtr->xbutton.state;
442 DoEvent(bindPtr, eventPtr, bindPtr->currentItem,
443 bindPtr->currentContext);
444 eventPtr->xbutton.state ^= mask;
445 bindPtr->state = eventPtr->xbutton.state;
446 PickCurrentItem(bindPtr, eventPtr);
447 eventPtr->xbutton.state ^= mask;
448 }
449 break;
450
451 case EnterNotify:
452 case LeaveNotify:
453 bindPtr->state = eventPtr->xcrossing.state;
454 PickCurrentItem(bindPtr, eventPtr);
455 break;
456
457 case MotionNotify:
458 bindPtr->state = eventPtr->xmotion.state;
459 PickCurrentItem(bindPtr, eventPtr);
460 DoEvent(bindPtr, eventPtr, bindPtr->currentItem,
461 bindPtr->currentContext);
462 break;
463
464 case KeyPress:
465 case KeyRelease:
466 bindPtr->state = eventPtr->xkey.state;
467 PickCurrentItem(bindPtr, eventPtr);
468 DoEvent(bindPtr, eventPtr, bindPtr->currentItem,
469 bindPtr->currentContext);
470 break;
471 }
472 Tcl_Release(bindPtr->clientData);
473 }
474
475 int
Blt_ConfigureBindings(interp,bindPtr,item,argc,argv)476 Blt_ConfigureBindings(interp, bindPtr, item, argc, argv)
477 Tcl_Interp *interp;
478 struct Blt_BindTableStruct *bindPtr;
479 ClientData item;
480 int argc;
481 char **argv;
482 {
483 char *command;
484 unsigned long mask;
485 char *seq;
486
487 if (argc == 0) {
488 Tk_GetAllBindings(interp, bindPtr->bindingTable, item);
489 return TCL_OK;
490 }
491 if (argc == 1) {
492 command = Tk_GetBinding(interp, bindPtr->bindingTable, item, argv[0]);
493 if (command == NULL) {
494 return TCL_ERROR;
495 }
496 Tcl_SetResult(interp, command, TCL_VOLATILE);
497 return TCL_OK;
498 }
499
500 seq = argv[0];
501 command = argv[1];
502
503 if (command[0] == '\0') {
504 return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq);
505 }
506
507 if (command[0] == '+') {
508 mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq,
509 command + 1, TRUE);
510 } else {
511 mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq,
512 command, FALSE);
513 }
514 if (mask == 0) {
515 return TCL_ERROR;
516 }
517 if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) {
518 Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq);
519 Tcl_ResetResult(interp);
520 Tcl_AppendResult(interp, "requested illegal events; ",
521 "only key, button, motion, enter, leave, and virtual ",
522 "events may be used", (char *)NULL);
523 return TCL_ERROR;
524 }
525 return TCL_OK;
526 }
527
528
529 #if (TCL_MAJOR_VERSION >= 8)
530
531 int
Blt_ConfigureBindingsFromObj(interp,bindPtr,item,objc,objv)532 Blt_ConfigureBindingsFromObj(interp, bindPtr, item, objc, objv)
533 Tcl_Interp *interp;
534 struct Blt_BindTableStruct *bindPtr;
535 ClientData item;
536 int objc;
537 Tcl_Obj *CONST *objv;
538 {
539 char *command;
540 unsigned long mask;
541 char *seq;
542 char *string;
543
544 if (objc == 0) {
545 Tk_GetAllBindings(interp, bindPtr->bindingTable, item);
546 return TCL_OK;
547 }
548 string = Tcl_GetString(objv[0]);
549 if (objc == 1) {
550 command = Tk_GetBinding(interp, bindPtr->bindingTable, item, string);
551 if (command == NULL) {
552 Tcl_ResetResult(interp);
553 Tcl_AppendResult(interp, "invalid binding event \"", string, "\"",
554 (char *)NULL);
555 return TCL_ERROR;
556 }
557 Tcl_SetResult(interp, command, TCL_VOLATILE);
558 return TCL_OK;
559 }
560
561 seq = string;
562 command = Tcl_GetString(objv[1]);
563
564 if (command[0] == '\0') {
565 return Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq);
566 }
567
568 if (command[0] == '+') {
569 mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq,
570 command + 1, TRUE);
571 } else {
572 mask = Tk_CreateBinding(interp, bindPtr->bindingTable, item, seq,
573 command, FALSE);
574 }
575 if (mask == 0) {
576 return TCL_ERROR;
577 }
578 if (mask & (unsigned)~ALL_VALID_EVENTS_MASK) {
579 Tk_DeleteBinding(interp, bindPtr->bindingTable, item, seq);
580 Tcl_ResetResult(interp);
581 Tcl_AppendResult(interp, "requested illegal events; ",
582 "only key, button, motion, enter, leave, and virtual ",
583 "events may be used", (char *)NULL);
584 return TCL_ERROR;
585 }
586 return TCL_OK;
587 }
588 #endif
589
590 Blt_BindTable
Blt_CreateBindingTable(interp,tkwin,clientData,pickProc,tagProc)591 Blt_CreateBindingTable(interp, tkwin, clientData, pickProc, tagProc)
592 Tcl_Interp *interp;
593 Tk_Window tkwin;
594 ClientData clientData;
595 Blt_BindPickProc *pickProc;
596 Blt_BindTagProc *tagProc;
597 {
598 unsigned int mask;
599 struct Blt_BindTableStruct *bindPtr;
600
601 bindPtr = Blt_Calloc(1, sizeof(struct Blt_BindTableStruct));
602 assert(bindPtr);
603 bindPtr->interp = interp;
604 bindPtr->clientData = clientData;
605 bindPtr->pickProc = pickProc;
606 bindPtr->tagProc = tagProc;
607 bindPtr->tkwin = tkwin;
608 bindPtr->bindingTable = Tk_CreateBindingTable(interp);
609 mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask |
610 ButtonReleaseMask | EnterWindowMask | LeaveWindowMask |
611 PointerMotionMask);
612 Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr);
613 return bindPtr;
614 }
615
616 void
Blt_DestroyBindingTable(bindPtr)617 Blt_DestroyBindingTable(bindPtr)
618 struct Blt_BindTableStruct *bindPtr;
619 {
620 unsigned int mask;
621
622 Tk_DeleteBindingTable(bindPtr->bindingTable);
623 mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask |
624 ButtonReleaseMask | EnterWindowMask | LeaveWindowMask |
625 PointerMotionMask);
626 Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr);
627 Blt_Free(bindPtr);
628 }
629
630 void
Blt_PickCurrentItem(bindPtr)631 Blt_PickCurrentItem(bindPtr)
632 struct Blt_BindTableStruct *bindPtr;
633 {
634 if (bindPtr->activePick) {
635 PickCurrentItem(bindPtr, &(bindPtr->pickEvent));
636 }
637 }
638
639 void
Blt_DeleteBindings(bindPtr,object)640 Blt_DeleteBindings(bindPtr, object)
641 struct Blt_BindTableStruct *bindPtr;
642 ClientData object;
643 {
644 Tk_DeleteAllBindings(bindPtr->bindingTable, object);
645
646 /*
647 * If this is the object currently picked, we need to repick one.
648 */
649 if (bindPtr->currentItem == object) {
650 bindPtr->currentItem = NULL;
651 bindPtr->currentContext = NULL;
652 }
653 if (bindPtr->newItem == object) {
654 bindPtr->newItem = NULL;
655 bindPtr->newContext = NULL;
656 }
657 if (bindPtr->focusItem == object) {
658 bindPtr->focusItem = NULL;
659 bindPtr->focusContext = NULL;
660 }
661 }
662
663 void
Blt_MoveBindingTable(bindPtr,tkwin)664 Blt_MoveBindingTable(bindPtr, tkwin)
665 struct Blt_BindTableStruct *bindPtr;
666 Tk_Window tkwin;
667 {
668 unsigned int mask;
669
670 mask = (KeyPressMask | KeyReleaseMask | ButtonPressMask |
671 ButtonReleaseMask | EnterWindowMask | LeaveWindowMask |
672 PointerMotionMask);
673 if (bindPtr->tkwin != NULL) {
674 Tk_DeleteEventHandler(bindPtr->tkwin, mask, BindProc, bindPtr);
675 }
676 Tk_CreateEventHandler(tkwin, mask, BindProc, bindPtr);
677 bindPtr->tkwin = tkwin;
678 }
679