1/*
2 * tkUnixScrollbar.c --
3 *
4 *	This file implements the Unix specific portion of the scrollbar
5 *	widget.
6 *
7 * Copyright (c) 1996 by Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tkUnixScrlbr.c,v 1.3 2000/11/22 01:49:38 ericm Exp $
13 */
14
15#include "tkScrollbar.h"
16
17/*
18 * Minimum slider length, in pixels (designed to make sure that the slider
19 * is always easy to grab with the mouse).
20 */
21
22#define MIN_SLIDER_LENGTH	5
23
24/*
25 * Declaration of Unix specific scrollbar structure.
26 */
27
28typedef struct UnixScrollbar {
29    TkScrollbar info;		/* Generic scrollbar info. */
30    GC troughGC;		/* For drawing trough. */
31    GC copyGC;			/* Used for copying from pixmap onto screen. */
32} UnixScrollbar;
33
34/*
35 * The class procedure table for the scrollbar widget.  All fields except
36 * size are left initialized to NULL, which should happen automatically
37 * since the variable is declared at this scope.
38 */
39
40Tk_ClassProcs tkpScrollbarProcs = {
41    sizeof(Tk_ClassProcs)	/* size */
42};
43
44
45/*
46 *----------------------------------------------------------------------
47 *
48 * TkpCreateScrollbar --
49 *
50 *	Allocate a new TkScrollbar structure.
51 *
52 * Results:
53 *	Returns a newly allocated TkScrollbar structure.
54 *
55 * Side effects:
56 *	Registers an event handler for the widget.
57 *
58 *----------------------------------------------------------------------
59 */
60
61TkScrollbar *
62TkpCreateScrollbar(tkwin)
63    Tk_Window tkwin;
64{
65    UnixScrollbar *scrollPtr = (UnixScrollbar *)ckalloc(sizeof(UnixScrollbar));
66    scrollPtr->troughGC = None;
67    scrollPtr->copyGC = None;
68
69    Tk_CreateEventHandler(tkwin,
70	    ExposureMask|StructureNotifyMask|FocusChangeMask,
71	    TkScrollbarEventProc, (ClientData) scrollPtr);
72
73    return (TkScrollbar *) scrollPtr;
74}
75
76/*
77 *--------------------------------------------------------------
78 *
79 * TkpDisplayScrollbar --
80 *
81 *	This procedure redraws the contents of a scrollbar window.
82 *	It is invoked as a do-when-idle handler, so it only runs
83 *	when there's nothing else for the application to do.
84 *
85 * Results:
86 *	None.
87 *
88 * Side effects:
89 *	Information appears on the screen.
90 *
91 *--------------------------------------------------------------
92 */
93
94void
95TkpDisplayScrollbar(clientData)
96    ClientData clientData;	/* Information about window. */
97{
98    register TkScrollbar *scrollPtr = (TkScrollbar *) clientData;
99    register Tk_Window tkwin = scrollPtr->tkwin;
100    XPoint points[7];
101    Tk_3DBorder border;
102    int relief, width, elementBorderWidth;
103    Pixmap pixmap;
104
105    if ((scrollPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
106	goto done;
107    }
108
109    if (scrollPtr->vertical) {
110	width = Tk_Width(tkwin) - 2*scrollPtr->inset;
111    } else {
112	width = Tk_Height(tkwin) - 2*scrollPtr->inset;
113    }
114    elementBorderWidth = scrollPtr->elementBorderWidth;
115    if (elementBorderWidth < 0) {
116	elementBorderWidth = scrollPtr->borderWidth;
117    }
118
119    /*
120     * In order to avoid screen flashes, this procedure redraws
121     * the scrollbar in a pixmap, then copies the pixmap to the
122     * screen in a single operation.  This means that there's no
123     * point in time where the on-sreen image has been cleared.
124     */
125
126    pixmap = Tk_GetPixmap(scrollPtr->display, Tk_WindowId(tkwin),
127	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
128
129    if (scrollPtr->highlightWidth != 0) {
130	GC gc;
131
132	if (scrollPtr->flags & GOT_FOCUS) {
133	    gc = Tk_GCForColor(scrollPtr->highlightColorPtr, pixmap);
134	} else {
135	    gc = Tk_GCForColor(scrollPtr->highlightBgColorPtr, pixmap);
136	}
137	Tk_DrawFocusHighlight(tkwin, gc, scrollPtr->highlightWidth, pixmap);
138    }
139    Tk_Draw3DRectangle(tkwin, pixmap, scrollPtr->bgBorder,
140	    scrollPtr->highlightWidth, scrollPtr->highlightWidth,
141	    Tk_Width(tkwin) - 2*scrollPtr->highlightWidth,
142	    Tk_Height(tkwin) - 2*scrollPtr->highlightWidth,
143	    scrollPtr->borderWidth, scrollPtr->relief);
144    XFillRectangle(scrollPtr->display, pixmap,
145	    ((UnixScrollbar*)scrollPtr)->troughGC,
146	    scrollPtr->inset, scrollPtr->inset,
147	    (unsigned) (Tk_Width(tkwin) - 2*scrollPtr->inset),
148	    (unsigned) (Tk_Height(tkwin) - 2*scrollPtr->inset));
149
150    /*
151     * Draw the top or left arrow.  The coordinates of the polygon
152     * points probably seem odd, but they were carefully chosen with
153     * respect to X's rules for filling polygons.  These point choices
154     * cause the arrows to just fill the narrow dimension of the
155     * scrollbar and be properly centered.
156     */
157
158    if (scrollPtr->activeField == TOP_ARROW) {
159	border = scrollPtr->activeBorder;
160	relief = scrollPtr->activeField == TOP_ARROW ? scrollPtr->activeRelief
161		: TK_RELIEF_RAISED;
162    } else {
163	border = scrollPtr->bgBorder;
164	relief = TK_RELIEF_RAISED;
165    }
166    if (scrollPtr->vertical) {
167	points[0].x = scrollPtr->inset - 1;
168	points[0].y = scrollPtr->arrowLength + scrollPtr->inset - 1;
169	points[1].x = width + scrollPtr->inset;
170	points[1].y = points[0].y;
171	points[2].x = width/2 + scrollPtr->inset;
172	points[2].y = scrollPtr->inset - 1;
173	Tk_Fill3DPolygon(tkwin, pixmap, border, points, 3,
174		elementBorderWidth, relief);
175    } else {
176	points[0].x = scrollPtr->arrowLength + scrollPtr->inset - 1;
177	points[0].y = scrollPtr->inset - 1;
178	points[1].x = scrollPtr->inset;
179	points[1].y = width/2 + scrollPtr->inset;
180	points[2].x = points[0].x;
181	points[2].y = width + scrollPtr->inset;
182	Tk_Fill3DPolygon(tkwin, pixmap, border, points, 3,
183		elementBorderWidth, relief);
184    }
185
186    /*
187     * Display the bottom or right arrow.
188     */
189
190    if (scrollPtr->activeField == BOTTOM_ARROW) {
191	border = scrollPtr->activeBorder;
192	relief = scrollPtr->activeField == BOTTOM_ARROW
193		? scrollPtr->activeRelief : TK_RELIEF_RAISED;
194    } else {
195	border = scrollPtr->bgBorder;
196	relief = TK_RELIEF_RAISED;
197    }
198    if (scrollPtr->vertical) {
199	points[0].x = scrollPtr->inset;
200	points[0].y = Tk_Height(tkwin) - scrollPtr->arrowLength
201		- scrollPtr->inset + 1;
202	points[1].x = width/2 + scrollPtr->inset;
203	points[1].y = Tk_Height(tkwin) - scrollPtr->inset;
204	points[2].x = width + scrollPtr->inset;
205	points[2].y = points[0].y;
206	Tk_Fill3DPolygon(tkwin, pixmap, border,
207		points, 3, elementBorderWidth, relief);
208    } else {
209	points[0].x = Tk_Width(tkwin) - scrollPtr->arrowLength
210		- scrollPtr->inset + 1;
211	points[0].y = scrollPtr->inset - 1;
212	points[1].x = points[0].x;
213	points[1].y = width + scrollPtr->inset;
214	points[2].x = Tk_Width(tkwin) - scrollPtr->inset;
215	points[2].y = width/2 + scrollPtr->inset;
216	Tk_Fill3DPolygon(tkwin, pixmap, border,
217		points, 3, elementBorderWidth, relief);
218    }
219
220    /*
221     * Display the slider.
222     */
223
224    if (scrollPtr->activeField == SLIDER) {
225	border = scrollPtr->activeBorder;
226	relief = scrollPtr->activeField == SLIDER ? scrollPtr->activeRelief
227		: TK_RELIEF_RAISED;
228    } else {
229	border = scrollPtr->bgBorder;
230	relief = TK_RELIEF_RAISED;
231    }
232    if (scrollPtr->vertical) {
233	Tk_Fill3DRectangle(tkwin, pixmap, border,
234		scrollPtr->inset, scrollPtr->sliderFirst,
235		width, scrollPtr->sliderLast - scrollPtr->sliderFirst,
236		elementBorderWidth, relief);
237    } else {
238	Tk_Fill3DRectangle(tkwin, pixmap, border,
239		scrollPtr->sliderFirst, scrollPtr->inset,
240		scrollPtr->sliderLast - scrollPtr->sliderFirst, width,
241		elementBorderWidth, relief);
242    }
243
244    /*
245     * Copy the information from the off-screen pixmap onto the screen,
246     * then delete the pixmap.
247     */
248
249    XCopyArea(scrollPtr->display, pixmap, Tk_WindowId(tkwin),
250	    ((UnixScrollbar*)scrollPtr)->copyGC, 0, 0,
251	    (unsigned) Tk_Width(tkwin), (unsigned) Tk_Height(tkwin), 0, 0);
252    Tk_FreePixmap(scrollPtr->display, pixmap);
253
254    done:
255    scrollPtr->flags &= ~REDRAW_PENDING;
256}
257
258/*
259 *----------------------------------------------------------------------
260 *
261 * TkpComputeScrollbarGeometry --
262 *
263 *	After changes in a scrollbar's size or configuration, this
264 *	procedure recomputes various geometry information used in
265 *	displaying the scrollbar.
266 *
267 * Results:
268 *	None.
269 *
270 * Side effects:
271 *	The scrollbar will be displayed differently.
272 *
273 *----------------------------------------------------------------------
274 */
275
276extern void
277TkpComputeScrollbarGeometry(scrollPtr)
278    register TkScrollbar *scrollPtr;	/* Scrollbar whose geometry may
279					 * have changed. */
280{
281    int width, fieldLength;
282
283    if (scrollPtr->highlightWidth < 0) {
284	scrollPtr->highlightWidth = 0;
285    }
286    scrollPtr->inset = scrollPtr->highlightWidth + scrollPtr->borderWidth;
287    width = (scrollPtr->vertical) ? Tk_Width(scrollPtr->tkwin)
288	    : Tk_Height(scrollPtr->tkwin);
289    scrollPtr->arrowLength = width - 2*scrollPtr->inset + 1;
290    fieldLength = (scrollPtr->vertical ? Tk_Height(scrollPtr->tkwin)
291	    : Tk_Width(scrollPtr->tkwin))
292	    - 2*(scrollPtr->arrowLength + scrollPtr->inset);
293    if (fieldLength < 0) {
294	fieldLength = 0;
295    }
296    scrollPtr->sliderFirst = fieldLength*scrollPtr->firstFraction;
297    scrollPtr->sliderLast = fieldLength*scrollPtr->lastFraction;
298
299    /*
300     * Adjust the slider so that some piece of it is always
301     * displayed in the scrollbar and so that it has at least
302     * a minimal width (so it can be grabbed with the mouse).
303     */
304
305    if (scrollPtr->sliderFirst > (fieldLength - MIN_SLIDER_LENGTH)) {
306	scrollPtr->sliderFirst = fieldLength - MIN_SLIDER_LENGTH;
307    }
308    if (scrollPtr->sliderFirst < 0) {
309	scrollPtr->sliderFirst = 0;
310    }
311    if (scrollPtr->sliderLast < (scrollPtr->sliderFirst
312	    + MIN_SLIDER_LENGTH)) {
313	scrollPtr->sliderLast = scrollPtr->sliderFirst + MIN_SLIDER_LENGTH;
314    }
315    if (scrollPtr->sliderLast > fieldLength) {
316	scrollPtr->sliderLast = fieldLength;
317    }
318    scrollPtr->sliderFirst += scrollPtr->arrowLength + scrollPtr->inset;
319    scrollPtr->sliderLast += scrollPtr->arrowLength + scrollPtr->inset;
320
321    /*
322     * Register the desired geometry for the window (leave enough space
323     * for the two arrows plus a minimum-size slider, plus border around
324     * the whole window, if any).  Then arrange for the window to be
325     * redisplayed.
326     */
327
328    if (scrollPtr->vertical) {
329	Tk_GeometryRequest(scrollPtr->tkwin,
330		scrollPtr->width + 2*scrollPtr->inset,
331		2*(scrollPtr->arrowLength + scrollPtr->borderWidth
332		+ scrollPtr->inset));
333    } else {
334	Tk_GeometryRequest(scrollPtr->tkwin,
335		2*(scrollPtr->arrowLength + scrollPtr->borderWidth
336		+ scrollPtr->inset), scrollPtr->width + 2*scrollPtr->inset);
337    }
338    Tk_SetInternalBorder(scrollPtr->tkwin, scrollPtr->inset);
339}
340
341/*
342 *----------------------------------------------------------------------
343 *
344 * TkpDestroyScrollbar --
345 *
346 *	Free data structures associated with the scrollbar control.
347 *
348 * Results:
349 *	None.
350 *
351 * Side effects:
352 *	Frees the GCs associated with the scrollbar.
353 *
354 *----------------------------------------------------------------------
355 */
356
357void
358TkpDestroyScrollbar(scrollPtr)
359    TkScrollbar *scrollPtr;
360{
361    UnixScrollbar *unixScrollPtr = (UnixScrollbar *)scrollPtr;
362
363    if (unixScrollPtr->troughGC != None) {
364	Tk_FreeGC(scrollPtr->display, unixScrollPtr->troughGC);
365    }
366    if (unixScrollPtr->copyGC != None) {
367	Tk_FreeGC(scrollPtr->display, unixScrollPtr->copyGC);
368    }
369}
370
371/*
372 *----------------------------------------------------------------------
373 *
374 * TkpConfigureScrollbar --
375 *
376 *	This procedure is called after the generic code has finished
377 *	processing configuration options, in order to configure
378 *	platform specific options.
379 *
380 * Results:
381 *	None.
382 *
383 * Side effects:
384 *	Configuration info may get changed.
385 *
386 *----------------------------------------------------------------------
387 */
388
389void
390TkpConfigureScrollbar(scrollPtr)
391    register TkScrollbar *scrollPtr;	/* Information about widget;  may or
392					 * may not already have values for
393					 * some fields. */
394{
395    XGCValues gcValues;
396    GC new;
397    UnixScrollbar *unixScrollPtr = (UnixScrollbar *) scrollPtr;
398
399    Tk_SetBackgroundFromBorder(scrollPtr->tkwin, scrollPtr->bgBorder);
400
401    gcValues.foreground = scrollPtr->troughColorPtr->pixel;
402    new = Tk_GetGC(scrollPtr->tkwin, GCForeground, &gcValues);
403    if (unixScrollPtr->troughGC != None) {
404	Tk_FreeGC(scrollPtr->display, unixScrollPtr->troughGC);
405    }
406    unixScrollPtr->troughGC = new;
407    if (unixScrollPtr->copyGC == None) {
408	gcValues.graphics_exposures = False;
409	unixScrollPtr->copyGC = Tk_GetGC(scrollPtr->tkwin, GCGraphicsExposures,
410	    &gcValues);
411    }
412}
413
414/*
415 *--------------------------------------------------------------
416 *
417 * TkpScrollbarPosition --
418 *
419 *	Determine the scrollbar element corresponding to a
420 *	given position.
421 *
422 * Results:
423 *	One of TOP_ARROW, TOP_GAP, etc., indicating which element
424 *	of the scrollbar covers the position given by (x, y).  If
425 *	(x,y) is outside the scrollbar entirely, then OUTSIDE is
426 *	returned.
427 *
428 * Side effects:
429 *	None.
430 *
431 *--------------------------------------------------------------
432 */
433
434int
435TkpScrollbarPosition(scrollPtr, x, y)
436    register TkScrollbar *scrollPtr;	/* Scrollbar widget record. */
437    int x, y;				/* Coordinates within scrollPtr's
438					 * window. */
439{
440    int length, width, tmp;
441
442    if (scrollPtr->vertical) {
443	length = Tk_Height(scrollPtr->tkwin);
444	width = Tk_Width(scrollPtr->tkwin);
445    } else {
446	tmp = x;
447	x = y;
448	y = tmp;
449	length = Tk_Width(scrollPtr->tkwin);
450	width = Tk_Height(scrollPtr->tkwin);
451    }
452
453    if ((x < scrollPtr->inset) || (x >= (width - scrollPtr->inset))
454	    || (y < scrollPtr->inset) || (y >= (length - scrollPtr->inset))) {
455	return OUTSIDE;
456    }
457
458    /*
459     * All of the calculations in this procedure mirror those in
460     * TkpDisplayScrollbar.  Be sure to keep the two consistent.
461     */
462
463    if (y < (scrollPtr->inset + scrollPtr->arrowLength)) {
464	return TOP_ARROW;
465    }
466    if (y < scrollPtr->sliderFirst) {
467	return TOP_GAP;
468    }
469    if (y < scrollPtr->sliderLast) {
470	return SLIDER;
471    }
472    if (y >= (length - (scrollPtr->arrowLength + scrollPtr->inset))) {
473	return BOTTOM_ARROW;
474    }
475    return BOTTOM_GAP;
476}
477