source: trunk/kitgen/8.x/blt/generic/bltBind.c@ 196

Last change on this file since 196 was 175, checked in by demin, 12 years ago

initial commit

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