1/*
2 * $Id$
3 *
4 * Copyright 2004, Joe English.
5 *
6 * Usage:
7 * 	TtkBlinkCursor(corePtr), usually called in a widget's Init hook,
8 * 	arranges to periodically toggle the corePtr->flags CURSOR_ON bit
9 * 	on and off (and schedule a redisplay) whenever the widget has focus.
10 *
11 * 	Note: Widgets may have additional logic to decide whether
12 * 	to display the cursor or not (e.g., readonly or disabled states);
13 * 	TtkBlinkCursor() does not account for this.
14 *
15 * TODO:
16 * 	Add script-level access to configure application-wide blink rate.
17 */
18
19#include <tk.h>
20#include "ttkTheme.h"
21#include "ttkWidget.h"
22
23#define DEF_CURSOR_ON_TIME	600		/* milliseconds */
24#define DEF_CURSOR_OFF_TIME	300		/* milliseconds */
25
26/* Interp-specific data for tracking cursors:
27 */
28typedef struct
29{
30    WidgetCore		*owner; 	/* Widget that currently has cursor */
31    Tcl_TimerToken	timer;		/* Blink timer */
32    int 		onTime;		/* #milliseconds to blink cursor on */
33    int 		offTime;	/* #milliseconds to blink cursor off */
34} CursorManager;
35
36/* CursorManagerDeleteProc --
37 * 	InterpDeleteProc for cursor manager.
38 */
39static void CursorManagerDeleteProc(ClientData clientData, Tcl_Interp *interp)
40{
41    CursorManager *cm = (CursorManager*)clientData;
42    if (cm->timer) {
43	Tcl_DeleteTimerHandler(cm->timer);
44    }
45    ckfree(clientData);
46}
47
48/* GetCursorManager --
49 * 	Look up and create if necessary the interp's cursor manager.
50 */
51static CursorManager *GetCursorManager(Tcl_Interp *interp)
52{
53    static const char *cm_key = "ttk::CursorManager";
54    CursorManager *cm = (CursorManager *) Tcl_GetAssocData(interp, cm_key,0);
55
56    if (!cm) {
57	cm = (CursorManager*)ckalloc(sizeof(*cm));
58	cm->timer = 0;
59	cm->owner = 0;
60	cm->onTime = DEF_CURSOR_ON_TIME;
61	cm->offTime = DEF_CURSOR_OFF_TIME;
62	Tcl_SetAssocData(interp,cm_key,CursorManagerDeleteProc,(ClientData)cm);
63    }
64    return cm;
65}
66
67/* CursorBlinkProc --
68 *	Timer handler to blink the insert cursor on and off.
69 */
70static void
71CursorBlinkProc(ClientData clientData)
72{
73    CursorManager *cm = (CursorManager*)clientData;
74    int blinkTime;
75
76    if (cm->owner->flags & CURSOR_ON) {
77	cm->owner->flags &= ~CURSOR_ON;
78	blinkTime = cm->offTime;
79    } else {
80	cm->owner->flags |= CURSOR_ON;
81	blinkTime = cm->onTime;
82    }
83    cm->timer = Tcl_CreateTimerHandler(blinkTime, CursorBlinkProc, clientData);
84    TtkRedisplayWidget(cm->owner);
85}
86
87/* LoseCursor --
88 * 	Turn cursor off, disable blink timer.
89 */
90static void LoseCursor(CursorManager *cm, WidgetCore *corePtr)
91{
92    if (corePtr->flags & CURSOR_ON) {
93	corePtr->flags &= ~CURSOR_ON;
94	TtkRedisplayWidget(corePtr);
95    }
96    if (cm->owner == corePtr) {
97	cm->owner = NULL;
98    }
99    if (cm->timer) {
100	Tcl_DeleteTimerHandler(cm->timer);
101	cm->timer = 0;
102    }
103}
104
105/* ClaimCursor --
106 * 	Claim ownership of the insert cursor and blink on.
107 */
108static void ClaimCursor(CursorManager *cm, WidgetCore *corePtr)
109{
110    if (cm->owner == corePtr)
111	return;
112    if (cm->owner)
113	LoseCursor(cm, cm->owner);
114
115    corePtr->flags |= CURSOR_ON;
116    TtkRedisplayWidget(corePtr);
117
118    cm->owner = corePtr;
119    cm->timer = Tcl_CreateTimerHandler(cm->onTime, CursorBlinkProc, cm);
120}
121
122/*
123 * CursorEventProc --
124 * 	Event handler for FocusIn and FocusOut events;
125 * 	claim/lose ownership of the insert cursor when the widget
126 * 	acquires/loses keyboard focus.
127 */
128
129#define CursorEventMask (FocusChangeMask|StructureNotifyMask)
130#define RealFocusEvent(d) \
131    (d == NotifyInferior || d == NotifyAncestor || d == NotifyNonlinear)
132
133static void
134CursorEventProc(ClientData clientData, XEvent *eventPtr)
135{
136    WidgetCore *corePtr = (WidgetCore *)clientData;
137    CursorManager *cm = GetCursorManager(corePtr->interp);
138
139    switch (eventPtr->type) {
140	case DestroyNotify:
141	    if (cm->owner == corePtr)
142		LoseCursor(cm, corePtr);
143	    Tk_DeleteEventHandler(
144		corePtr->tkwin, CursorEventMask, CursorEventProc, clientData);
145	    break;
146	case FocusIn:
147	    if (RealFocusEvent(eventPtr->xfocus.detail))
148		ClaimCursor(cm, corePtr);
149	    break;
150	case FocusOut:
151	    if (RealFocusEvent(eventPtr->xfocus.detail))
152		LoseCursor(cm, corePtr);
153	    break;
154    }
155}
156
157/*
158 * TtkBlinkCursor (main routine) --
159 * 	Arrange to blink the cursor on and off whenever the
160 * 	widget has focus.
161 */
162void TtkBlinkCursor(WidgetCore *corePtr)
163{
164    Tk_CreateEventHandler(
165	corePtr->tkwin, CursorEventMask, CursorEventProc, corePtr);
166}
167
168/*EOF*/
169