1/* $Id$
2 * Copyright (c) 2004, Joe English
3 *
4 * TtkTrackElementState() -- helper routine for widgets
5 * like scrollbars in which individual elements may
6 * be active or pressed instead of the widget as a whole.
7 *
8 * Usage:
9 * 	TtkTrackElementState(&recordPtr->core);
10 *
11 * Registers an event handler on the widget that tracks pointer
12 * events and updates the state of the element under the
13 * mouse cursor.
14 *
15 * The "active" element is the one under the mouse cursor,
16 * and is normally set to the ACTIVE state unless another element
17 * is currently being pressed.
18 *
19 * The active element becomes "pressed" on <ButtonPress> events,
20 * and remains "active" and "pressed" until the corresponding
21 * <ButtonRelease> event.
22 *
23 * TODO: Handle "chords" properly (e.g., <B1-ButtonPress-2>)
24 */
25
26#include <tk.h>
27#include "ttkTheme.h"
28#include "ttkWidget.h"
29
30typedef struct {
31    WidgetCore		*corePtr;	/* widget to track */
32    Ttk_Layout		tracking;	/* current layout being tracked */
33    Ttk_Element 	activeElement;	/* element under the mouse cursor */
34    Ttk_Element 	pressedElement; /* currently pressed element */
35} ElementStateTracker;
36
37/*
38 * ActivateElement(es, node) --
39 * 	Make 'node' the active element if non-NULL.
40 * 	Deactivates the currently active element if different.
41 *
42 * 	The active element has TTK_STATE_ACTIVE set _unless_
43 * 	another element is 'pressed'
44 */
45static void ActivateElement(ElementStateTracker *es, Ttk_Element element)
46{
47    if (es->activeElement == element) {
48	/* No change */
49	return;
50    }
51
52    if (!es->pressedElement) {
53	if (es->activeElement) {
54	    /* Deactivate old element */
55	    Ttk_ChangeElementState(es->activeElement, 0,TTK_STATE_ACTIVE);
56	}
57	if (element) {
58	    /* Activate new element */
59	    Ttk_ChangeElementState(element, TTK_STATE_ACTIVE,0);
60	}
61	TtkRedisplayWidget(es->corePtr);
62    }
63
64    es->activeElement = element;
65}
66
67/* ReleaseElement --
68 * 	Releases the currently pressed element, if any.
69 */
70static void ReleaseElement(ElementStateTracker *es)
71{
72    if (!es->pressedElement)
73	return;
74
75    Ttk_ChangeElementState(
76	es->pressedElement, 0,TTK_STATE_PRESSED|TTK_STATE_ACTIVE);
77    es->pressedElement = 0;
78
79    /* Reactivate element under the mouse cursor:
80     */
81    if (es->activeElement)
82	Ttk_ChangeElementState(es->activeElement, TTK_STATE_ACTIVE,0);
83
84    TtkRedisplayWidget(es->corePtr);
85}
86
87/* PressElement --
88 * 	Presses the specified element.
89 */
90static void PressElement(ElementStateTracker *es, Ttk_Element element)
91{
92    if (es->pressedElement) {
93	ReleaseElement(es);
94    }
95
96    if (element) {
97	Ttk_ChangeElementState(
98	    element, TTK_STATE_PRESSED|TTK_STATE_ACTIVE, 0);
99    }
100
101    es->pressedElement = element;
102    TtkRedisplayWidget(es->corePtr);
103}
104
105/* ElementStateEventProc --
106 * 	Event handler for tracking element states.
107 */
108
109static const unsigned ElementStateMask =
110      ButtonPressMask
111    | ButtonReleaseMask
112    | PointerMotionMask
113    | LeaveWindowMask
114    | EnterWindowMask
115    | StructureNotifyMask
116    ;
117
118static void
119ElementStateEventProc(ClientData clientData, XEvent *ev)
120{
121    ElementStateTracker *es = clientData;
122    Ttk_Layout layout = es->corePtr->layout;
123    Ttk_Element element;
124
125    /* Guard against dangling pointers [#2431428]
126     */
127    if (es->tracking != layout) {
128	es->pressedElement = es->activeElement = 0;
129	es->tracking = layout;
130    }
131
132    switch (ev->type)
133    {
134	case MotionNotify :
135	    element = Ttk_IdentifyElement(
136		layout, ev->xmotion.x, ev->xmotion.y);
137	    ActivateElement(es, element);
138	    break;
139	case LeaveNotify:
140	    ActivateElement(es, 0);
141	    if (ev->xcrossing.mode == NotifyGrab)
142		PressElement(es, 0);
143	    break;
144	case EnterNotify:
145	    element = Ttk_IdentifyElement(
146		layout, ev->xcrossing.x, ev->xcrossing.y);
147	    ActivateElement(es, element);
148	    break;
149	case ButtonPress:
150	    element = Ttk_IdentifyElement(
151		layout, ev->xbutton.x, ev->xbutton.y);
152	    if (element)
153		PressElement(es, element);
154	    break;
155	case ButtonRelease:
156	    ReleaseElement(es);
157	    break;
158	case DestroyNotify:
159	    /* Unregister this event handler and free client data.
160	     */
161	    Tk_DeleteEventHandler(es->corePtr->tkwin,
162		    ElementStateMask, ElementStateEventProc, es);
163	    ckfree(clientData);
164	    break;
165    }
166}
167
168/*
169 * TtkTrackElementState --
170 * 	Register an event handler to manage the 'pressed'
171 * 	and 'active' states of individual widget elements.
172 */
173
174void TtkTrackElementState(WidgetCore *corePtr)
175{
176    ElementStateTracker *es = (ElementStateTracker*)ckalloc(sizeof(*es));
177    es->corePtr = corePtr;
178    es->tracking = 0;
179    es->activeElement = es->pressedElement = 0;
180    Tk_CreateEventHandler(corePtr->tkwin,
181	    ElementStateMask,ElementStateEventProc,es);
182}
183
184