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 of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id$
13 */
14
15#include "tkInt.h"
16#include "tkScrollbar.h"
17
18/*
19 * Minimum slider length, in pixels (designed to make sure that the slider is
20 * always easy to grab with the mouse).
21 */
22
23#define MIN_SLIDER_LENGTH	5
24
25/*
26 * Declaration of Unix specific scrollbar structure.
27 */
28
29typedef struct UnixScrollbar {
30    TkScrollbar info;		/* Generic scrollbar info. */
31    GC troughGC;		/* For drawing trough. */
32    GC copyGC;			/* Used for copying from pixmap onto screen. */
33} UnixScrollbar;
34
35/*
36 * The class procedure table for the scrollbar widget. All fields except size
37 * are left initialized to NULL, which should happen automatically since the
38 * variable is declared at this scope.
39 */
40
41Tk_ClassProcs tkpScrollbarProcs = {
42    sizeof(Tk_ClassProcs)	/* size */
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(
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. It is
82 *	invoked as a do-when-idle handler, so it only runs when there's
83 *	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(
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 the scrollbar
121     * in a pixmap, then copies the pixmap to the screen in a single
122     * operation. This means that there's no point in time where the on-sreen
123     * 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 points
152     * probably seem odd, but they were carefully chosen with respect to X's
153     * rules for filling polygons. These point choices cause the arrows to
154     * just fill the narrow dimension of the scrollbar and be properly
155     * 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, then
246     * 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 procedure
264 *	recomputes various geometry information used in displaying the
265 *	scrollbar.
266 *
267 * Results:
268 *	None.
269 *
270 * Side effects:
271 *	The scrollbar will be displayed differently.
272 *
273 *----------------------------------------------------------------------
274 */
275
276extern void
277TkpComputeScrollbarGeometry(
278    register TkScrollbar *scrollPtr)
279				/* Scrollbar whose geometry may have
280				 * changed. */
281{
282    int width, fieldLength;
283
284    if (scrollPtr->highlightWidth < 0) {
285	scrollPtr->highlightWidth = 0;
286    }
287    scrollPtr->inset = scrollPtr->highlightWidth + scrollPtr->borderWidth;
288    width = (scrollPtr->vertical) ? Tk_Width(scrollPtr->tkwin)
289	    : Tk_Height(scrollPtr->tkwin);
290    scrollPtr->arrowLength = width - 2*scrollPtr->inset + 1;
291    fieldLength = (scrollPtr->vertical ? Tk_Height(scrollPtr->tkwin)
292	    : Tk_Width(scrollPtr->tkwin))
293	    - 2*(scrollPtr->arrowLength + scrollPtr->inset);
294    if (fieldLength < 0) {
295	fieldLength = 0;
296    }
297    scrollPtr->sliderFirst = fieldLength*scrollPtr->firstFraction;
298    scrollPtr->sliderLast = fieldLength*scrollPtr->lastFraction;
299
300    /*
301     * Adjust the slider so that some piece of it is always displayed in the
302     * scrollbar and so that it has at least a minimal width (so it can be
303     * grabbed with the mouse).
304     */
305
306    if (scrollPtr->sliderFirst > (fieldLength - MIN_SLIDER_LENGTH)) {
307	scrollPtr->sliderFirst = fieldLength - MIN_SLIDER_LENGTH;
308    }
309    if (scrollPtr->sliderFirst < 0) {
310	scrollPtr->sliderFirst = 0;
311    }
312    if (scrollPtr->sliderLast < (scrollPtr->sliderFirst
313	    + MIN_SLIDER_LENGTH)) {
314	scrollPtr->sliderLast = scrollPtr->sliderFirst + MIN_SLIDER_LENGTH;
315    }
316    if (scrollPtr->sliderLast > fieldLength) {
317	scrollPtr->sliderLast = fieldLength;
318    }
319    scrollPtr->sliderFirst += scrollPtr->arrowLength + scrollPtr->inset;
320    scrollPtr->sliderLast += scrollPtr->arrowLength + scrollPtr->inset;
321
322    /*
323     * Register the desired geometry for the window (leave enough space for
324     * the two arrows plus a minimum-size slider, plus border around the whole
325     * window, if any). Then arrange for the window to be 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(
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 platform
378 *	specific options.
379 *
380 * Results:
381 *	None.
382 *
383 * Side effects:
384 *	Configuration info may get changed.
385 *
386 *----------------------------------------------------------------------
387 */
388
389void
390TkpConfigureScrollbar(
391    register TkScrollbar *scrollPtr)
392				/* Information about widget; may or may not
393				 * already have values for 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 given position.
420 *
421 * Results:
422 *	One of TOP_ARROW, TOP_GAP, etc., indicating which element of the
423 *	scrollbar covers the position given by (x, y). If (x,y) is outside the
424 *	scrollbar entirely, then OUTSIDE is returned.
425 *
426 * Side effects:
427 *	None.
428 *
429 *--------------------------------------------------------------
430 */
431
432int
433TkpScrollbarPosition(
434    register TkScrollbar *scrollPtr,
435				/* Scrollbar widget record. */
436    int x, int y)		/* Coordinates within scrollPtr's window. */
437{
438    int length, width, tmp;
439
440    if (scrollPtr->vertical) {
441	length = Tk_Height(scrollPtr->tkwin);
442	width = Tk_Width(scrollPtr->tkwin);
443    } else {
444	tmp = x;
445	x = y;
446	y = tmp;
447	length = Tk_Width(scrollPtr->tkwin);
448	width = Tk_Height(scrollPtr->tkwin);
449    }
450
451    if ((x < scrollPtr->inset) || (x >= (width - scrollPtr->inset))
452	    || (y < scrollPtr->inset) || (y >= (length - scrollPtr->inset))) {
453	return OUTSIDE;
454    }
455
456    /*
457     * All of the calculations in this procedure mirror those in
458     * TkpDisplayScrollbar. Be sure to keep the two consistent.
459     */
460
461    if (y < (scrollPtr->inset + scrollPtr->arrowLength)) {
462	return TOP_ARROW;
463    }
464    if (y < scrollPtr->sliderFirst) {
465	return TOP_GAP;
466    }
467    if (y < scrollPtr->sliderLast) {
468	return SLIDER;
469    }
470    if (y >= (length - (scrollPtr->arrowLength + scrollPtr->inset))) {
471	return BOTTOM_ARROW;
472    }
473    return BOTTOM_GAP;
474}
475
476/*
477 * Local Variables:
478 * mode: c
479 * c-basic-offset: 4
480 * fill-column: 78
481 * End:
482 */
483