1/* $Id$ 2 * 3 * Copyright 2004, Joe English 4 * 5 * Support routines for scrollable widgets. 6 * 7 * (This is sort of half-baked; needs some work) 8 * 9 * Scrollable interface: 10 * 11 * + 'first' is controlled by [xy]view widget command 12 * and other scrolling commands like 'see'; 13 * + 'total' depends on widget contents; 14 * + 'last' depends on first, total, and widget size. 15 * 16 * Choreography (typical usage): 17 * 18 * 1. User adjusts scrollbar, scrollbar widget calls its -command 19 * 2. Scrollbar -command invokes the scrollee [xy]view widget method 20 * 3. TtkScrollviewCommand calls TtkScrollTo(), which updates 21 * 'first' and schedules a redisplay. 22 * 4. Once the scrollee knows 'total' and 'last' (typically in 23 * the LayoutProc), call TtkScrolled(h,first,last,total) to 24 * synchronize the scrollbar. 25 * 5. The scrollee -[xy]scrollcommand is called (in an idle callback) 26 * 6. Which calls the scrollbar 'set' method and redisplays the scrollbar. 27 * 28 * If the scrollee has internal scrolling (e.g., a 'see' method), 29 * it should TtkScrollTo() directly (step 2). 30 * 31 * If the widget value changes, it should call TtkScrolled() (step 4). 32 * (This usually happens automatically when the widget is redisplayed). 33 * 34 * If the scrollee's -[xy]scrollcommand changes, it should call 35 * TtkScrollbarUpdateRequired, which will invoke step (5) (@@@ Fix this) 36 */ 37 38#include <tk.h> 39#include "ttkTheme.h" 40#include "ttkWidget.h" 41 42/* Private data: 43 */ 44#define SCROLL_UPDATE_PENDING (0x1) 45#define SCROLL_UPDATE_REQUIRED (0x2) 46 47struct ScrollHandleRec 48{ 49 unsigned flags; 50 WidgetCore *corePtr; 51 Scrollable *scrollPtr; 52}; 53 54/* TtkCreateScrollHandle -- 55 * Initialize scroll handle. 56 */ 57ScrollHandle TtkCreateScrollHandle(WidgetCore *corePtr, Scrollable *scrollPtr) 58{ 59 ScrollHandle h = (ScrollHandle)ckalloc(sizeof(*h)); 60 61 h->flags = 0; 62 h->corePtr = corePtr; 63 h->scrollPtr = scrollPtr; 64 65 scrollPtr->first = 0; 66 scrollPtr->last = 1; 67 scrollPtr->total = 1; 68 return h; 69} 70 71/* UpdateScrollbar -- 72 * Call the -scrollcommand callback to sync the scrollbar. 73 * Returns: Whatever the -scrollcommand does. 74 */ 75static int UpdateScrollbar(Tcl_Interp *interp, ScrollHandle h) 76{ 77 Scrollable *s = h->scrollPtr; 78 WidgetCore *corePtr = h->corePtr; 79 char arg1[TCL_DOUBLE_SPACE + 2]; 80 char arg2[TCL_DOUBLE_SPACE + 2]; 81 int code; 82 83 h->flags &= ~SCROLL_UPDATE_REQUIRED; 84 85 if (s->scrollCmd == NULL) { 86 return TCL_OK; 87 } 88 89 arg1[0] = arg2[0] = ' '; 90 Tcl_PrintDouble(interp, (double)s->first / s->total, arg1+1); 91 Tcl_PrintDouble(interp, (double)s->last / s->total, arg2+1); 92 93 Tcl_Preserve(corePtr); 94 code = Tcl_VarEval(interp, s->scrollCmd, arg1, arg2, NULL); 95 if (WidgetDestroyed(corePtr)) { 96 Tcl_Release(corePtr); 97 return TCL_ERROR; 98 } 99 Tcl_Release(corePtr); 100 101 if (code != TCL_OK && !Tcl_InterpDeleted(interp)) { 102 /* Disable the -scrollcommand, add to stack trace: 103 */ 104 ckfree(s->scrollCmd); 105 s->scrollCmd = 0; 106 107 Tcl_AddErrorInfo(interp, /* @@@ "horizontal" / "vertical" */ 108 "\n (scrolling command executed by "); 109 Tcl_AddErrorInfo(interp, Tk_PathName(h->corePtr->tkwin)); 110 Tcl_AddErrorInfo(interp, ")"); 111 } 112 return code; 113} 114 115/* UpdateScrollbarBG -- 116 * Idle handler to update the scrollbar. 117 */ 118static void UpdateScrollbarBG(ClientData clientData) 119{ 120 ScrollHandle h = (ScrollHandle)clientData; 121 Tcl_Interp *interp = h->corePtr->interp; 122 int code; 123 124 h->flags &= ~SCROLL_UPDATE_PENDING; 125 Tcl_Preserve((ClientData) interp); 126 code = UpdateScrollbar(interp, h); 127 if (code == TCL_ERROR && !Tcl_InterpDeleted(interp)) { 128 Tcl_BackgroundError(interp); 129 } 130 Tcl_Release((ClientData) interp); 131} 132 133/* TtkScrolled -- 134 * Update scroll info, schedule scrollbar update. 135 */ 136void TtkScrolled(ScrollHandle h, int first, int last, int total) 137{ 138 Scrollable *s = h->scrollPtr; 139 140 /* Sanity-check inputs: 141 */ 142 if (total <= 0) { 143 first = 0; 144 last = 1; 145 total = 1; 146 } 147 148 if (last > total) { 149 first -= (last - total); 150 if (first < 0) first = 0; 151 last = total; 152 } 153 154 if (s->first != first || s->last != last || s->total != total 155 || (h->flags & SCROLL_UPDATE_REQUIRED)) 156 { 157 s->first = first; 158 s->last = last; 159 s->total = total; 160 161 if (!(h->flags & SCROLL_UPDATE_PENDING)) { 162 Tcl_DoWhenIdle(UpdateScrollbarBG, (ClientData)h); 163 h->flags |= SCROLL_UPDATE_PENDING; 164 } 165 } 166} 167 168/* TtkScrollbarUpdateRequired -- 169 * Force a scrollbar update at the next call to TtkScrolled(), 170 * even if scroll parameters haven't changed (e.g., if 171 * -yscrollcommand has changed). 172 */ 173 174void TtkScrollbarUpdateRequired(ScrollHandle h) 175{ 176 h->flags |= SCROLL_UPDATE_REQUIRED; 177} 178 179/* TtkScrollviewCommand -- 180 * Widget [xy]view command implementation. 181 * 182 * $w [xy]view -- return current view region 183 * $w [xy]view $index -- set topmost item 184 * $w [xy]view moveto $fraction 185 * $w [xy]view scroll $number $what -- scrollbar interface 186 */ 187int TtkScrollviewCommand( 188 Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle h) 189{ 190 Scrollable *s = h->scrollPtr; 191 int newFirst = s->first; 192 193 if (objc == 2) { 194 Tcl_Obj *result[2]; 195 result[0] = Tcl_NewDoubleObj((double)s->first / s->total); 196 result[1] = Tcl_NewDoubleObj((double)s->last / s->total); 197 Tcl_SetObjResult(interp, Tcl_NewListObj(2, result)); 198 return TCL_OK; 199 } else if (objc == 3) { 200 if (Tcl_GetIntFromObj(interp, objv[2], &newFirst) != TCL_OK) { 201 return TCL_ERROR; 202 } 203 } else { 204 double fraction; 205 int count; 206 207 switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count)) { 208 case TK_SCROLL_ERROR: 209 return TCL_ERROR; 210 case TK_SCROLL_MOVETO: 211 newFirst = (int) ((fraction * s->total) + 0.5); 212 break; 213 case TK_SCROLL_UNITS: 214 newFirst = s->first + count; 215 break; 216 case TK_SCROLL_PAGES: { 217 int perPage = s->last - s->first; /* @@@ */ 218 newFirst = s->first + count * perPage; 219 break; 220 } 221 } 222 } 223 224 TtkScrollTo(h, newFirst); 225 226 return TCL_OK; 227} 228 229void TtkScrollTo(ScrollHandle h, int newFirst) 230{ 231 Scrollable *s = h->scrollPtr; 232 233 if (newFirst >= s->total) 234 newFirst = s->total - 1; 235 if (newFirst > s->first && s->last >= s->total) /* don't scroll past end */ 236 newFirst = s->first; 237 if (newFirst < 0) 238 newFirst = 0; 239 240 if (newFirst != s->first) { 241 s->first = newFirst; 242 TtkRedisplayWidget(h->corePtr); 243 } 244} 245 246void TtkFreeScrollHandle(ScrollHandle h) 247{ 248 if (h->flags & SCROLL_UPDATE_PENDING) { 249 Tcl_CancelIdleCall(UpdateScrollbarBG, (ClientData)h); 250 } 251 ckfree((ClientData)h); 252} 253 254