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