4 * This file implements the X specific portion of the scrollbar widget.
6 * Copyright (c) 1996 by Sun Microsystems, Inc.
7 * Copyright (c) 1998-2000 by Scriptics Corporation.
9 * See the file "license.terms" for information on usage and redistribution of
10 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
17 #define snprintf _snprintf
21 * Forward declarations for functions defined later in this file:
24 static void DisplayHorizontalScale(TkScale *scalePtr,
25 Drawable drawable, XRectangle *drawnAreaPtr);
26 static void DisplayHorizontalValue(TkScale *scalePtr,
27 Drawable drawable, double value, int top,
29 static void DisplayVerticalScale(TkScale *scalePtr,
30 Drawable drawable, XRectangle *drawnAreaPtr);
31 static void DisplayVerticalValue(TkScale *scalePtr,
32 Drawable drawable, double value, int rightEdge,
36 *----------------------------------------------------------------------
40 * Allocate a new TkScale structure.
43 * Returns a newly allocated TkScale structure.
48 *----------------------------------------------------------------------
53 TCL_UNUSED(Tk_Window))
55 return (TkScale *)ckalloc(sizeof(TkScale));
59 *----------------------------------------------------------------------
63 * Destroy a TkScale structure. It's necessary to do this with
64 * Tcl_EventuallyFree to allow the Tcl_Preserve(scalePtr) to work as
65 * expected in TkpDisplayScale. (hobbs)
73 *----------------------------------------------------------------------
80 Tcl_EventuallyFree(scalePtr, TCL_DYNAMIC);
84 *--------------------------------------------------------------
86 * DisplayVerticalScale --
88 * This function redraws the contents of a vertical scale window. It is
89 * invoked as a do-when-idle handler, so it only runs when there's
90 * nothing else for the application to do.
93 * There is no return value. If only a part of the scale needs to be
94 * redrawn, then drawnAreaPtr is modified to reflect the area that was
98 * Information appears on the screen.
100 *--------------------------------------------------------------
104 DisplayVerticalScale(
105 TkScale *scalePtr, /* Widget record for scale. */
106 Drawable drawable, /* Where to display scale (window or
108 XRectangle *drawnAreaPtr) /* Initally contains area of window; if only a
109 * part of the scale is redrawn, gets modified
110 * to reflect the part of the window that was
113 Tk_Window tkwin = scalePtr->tkwin;
114 int x, y, width, height, shadowWidth;
115 double tickValue, tickInterval = scalePtr->tickInterval;
116 Tk_3DBorder sliderBorder;
119 * Display the information from left to right across the window.
122 if (!(scalePtr->flags & REDRAW_OTHER)) {
123 drawnAreaPtr->x = scalePtr->vertTickRightX;
124 drawnAreaPtr->y = scalePtr->inset;
125 drawnAreaPtr->width = scalePtr->vertTroughX + scalePtr->width
126 + 2*scalePtr->borderWidth - scalePtr->vertTickRightX;
127 drawnAreaPtr->height -= 2*scalePtr->inset;
129 Tk_Fill3DRectangle(tkwin, drawable, scalePtr->bgBorder,
130 drawnAreaPtr->x, drawnAreaPtr->y, drawnAreaPtr->width,
131 drawnAreaPtr->height, 0, TK_RELIEF_FLAT);
132 if (scalePtr->flags & REDRAW_OTHER) {
134 * Display the tick marks.
137 if (tickInterval != 0) {
138 double ticks, maxTicks;
141 * Ensure that we will only draw enough of the tick values such
142 * that they don't overlap
145 ticks = fabs((scalePtr->toValue - scalePtr->fromValue)
147 maxTicks = (double) Tk_Height(tkwin)
148 / (double) scalePtr->fontHeight;
149 if (ticks > maxTicks) {
150 tickInterval *= (ticks / maxTicks);
152 for (tickValue = scalePtr->fromValue; ;
153 tickValue += tickInterval) {
155 * The TkRoundValueToResolution call gets rid of accumulated
156 * round-off errors, if any.
159 tickValue = TkRoundValueToResolution(scalePtr, tickValue);
160 if (scalePtr->toValue >= scalePtr->fromValue) {
161 if (tickValue > scalePtr->toValue) {
165 if (tickValue < scalePtr->toValue) {
169 DisplayVerticalValue(scalePtr, drawable, tickValue,
170 scalePtr->vertTickRightX, scalePtr->tickFormat);
176 * Display the value, if it is desired.
179 if (scalePtr->showValue) {
180 DisplayVerticalValue(scalePtr, drawable, scalePtr->value,
181 scalePtr->vertValueRightX, scalePtr->valueFormat);
185 * Display the trough and the slider.
188 Tk_Draw3DRectangle(tkwin, drawable,
189 scalePtr->bgBorder, scalePtr->vertTroughX, scalePtr->inset,
190 scalePtr->width + 2*scalePtr->borderWidth,
191 Tk_Height(tkwin) - 2*scalePtr->inset, scalePtr->borderWidth,
193 XFillRectangle(scalePtr->display, drawable, scalePtr->troughGC,
194 scalePtr->vertTroughX + scalePtr->borderWidth,
195 scalePtr->inset + scalePtr->borderWidth,
196 (unsigned) scalePtr->width,
197 (unsigned) (Tk_Height(tkwin) - 2*scalePtr->inset
198 - 2*scalePtr->borderWidth));
199 if (scalePtr->state == STATE_ACTIVE) {
200 sliderBorder = scalePtr->activeBorder;
202 sliderBorder = scalePtr->bgBorder;
204 width = scalePtr->width;
205 height = scalePtr->sliderLength/2;
206 x = scalePtr->vertTroughX + scalePtr->borderWidth;
207 y = TkScaleValueToPixel(scalePtr, scalePtr->value) - height;
208 shadowWidth = scalePtr->borderWidth/2;
209 if (shadowWidth == 0) {
212 Tk_Draw3DRectangle(tkwin, drawable, sliderBorder, x, y, width,
213 2*height, shadowWidth, scalePtr->sliderRelief);
216 width -= 2*shadowWidth;
217 height -= shadowWidth;
218 Tk_Fill3DRectangle(tkwin, drawable, sliderBorder, x, y, width,
219 height, shadowWidth, scalePtr->sliderRelief);
220 Tk_Fill3DRectangle(tkwin, drawable, sliderBorder, x, y+height,
221 width, height, shadowWidth, scalePtr->sliderRelief);
224 * Draw the label to the right of the scale.
227 if ((scalePtr->flags & REDRAW_OTHER) && (scalePtr->labelLength != 0)) {
230 Tk_GetFontMetrics(scalePtr->tkfont, &fm);
231 Tk_DrawChars(scalePtr->display, drawable, scalePtr->textGC,
232 scalePtr->tkfont, scalePtr->label,
233 scalePtr->labelLength, scalePtr->vertLabelX,
234 scalePtr->inset + (3 * fm.ascent) / 2);
239 *----------------------------------------------------------------------
241 * DisplayVerticalValue --
243 * This function is called to display values (scale readings) for
244 * vertically-oriented scales.
250 * The numerical value corresponding to value is displayed with its right
251 * edge at "rightEdge", and at a vertical position in the scale that
252 * corresponds to "value".
254 *----------------------------------------------------------------------
258 DisplayVerticalValue(
259 TkScale *scalePtr, /* Information about widget in which to
261 Drawable drawable, /* Pixmap or window in which to draw the
263 double value, /* Y-coordinate of number to display,
264 * specified in application coords, not in
265 * pixels (we'll compute pixels). */
266 int rightEdge, /* X-coordinate of right edge of text,
267 * specified in pixels. */
268 const char *format) /* Format string to use for the value */
270 Tk_Window tkwin = scalePtr->tkwin;
271 int y, width, length;
272 char valueString[TCL_DOUBLE_SPACE];
275 Tk_GetFontMetrics(scalePtr->tkfont, &fm);
276 y = TkScaleValueToPixel(scalePtr, value) + fm.ascent/2;
277 if (snprintf(valueString, TCL_DOUBLE_SPACE, format, value) < 0) {
278 valueString[TCL_DOUBLE_SPACE - 1] = '\0';
280 length = (int) strlen(valueString);
281 width = Tk_TextWidth(scalePtr->tkfont, valueString, length);
284 * Adjust the y-coordinate if necessary to keep the text entirely inside
288 if (y - fm.ascent < scalePtr->inset + SPACING) {
289 y = scalePtr->inset + SPACING + fm.ascent;
291 if (y + fm.descent > Tk_Height(tkwin) - scalePtr->inset - SPACING) {
292 y = Tk_Height(tkwin) - scalePtr->inset - SPACING - fm.descent;
294 Tk_DrawChars(scalePtr->display, drawable, scalePtr->textGC,
295 scalePtr->tkfont, valueString, length, rightEdge - width, y);
299 *--------------------------------------------------------------
301 * DisplayHorizontalScale --
303 * This function redraws the contents of a horizontal scale window. It is
304 * invoked as a do-when-idle handler, so it only runs when there's
305 * nothing else for the application to do.
308 * There is no return value. If only a part of the scale needs to be
309 * redrawn, then drawnAreaPtr is modified to reflect the area that was
313 * Information appears on the screen.
315 *--------------------------------------------------------------
319 DisplayHorizontalScale(
320 TkScale *scalePtr, /* Widget record for scale. */
321 Drawable drawable, /* Where to display scale (window or
323 XRectangle *drawnAreaPtr) /* Initally contains area of window; if only a
324 * part of the scale is redrawn, gets modified
325 * to reflect the part of the window that was
328 Tk_Window tkwin = scalePtr->tkwin;
329 int x, y, width, height, shadowWidth;
330 double tickInterval = scalePtr->tickInterval;
331 Tk_3DBorder sliderBorder;
334 * Display the information from bottom to top across the window.
337 if (!(scalePtr->flags & REDRAW_OTHER)) {
338 drawnAreaPtr->x = scalePtr->inset;
339 drawnAreaPtr->y = scalePtr->horizValueY;
340 drawnAreaPtr->width -= 2*scalePtr->inset;
341 drawnAreaPtr->height = scalePtr->horizTroughY + scalePtr->width
342 + 2*scalePtr->borderWidth - scalePtr->horizValueY;
344 Tk_Fill3DRectangle(tkwin, drawable, scalePtr->bgBorder,
345 drawnAreaPtr->x, drawnAreaPtr->y, drawnAreaPtr->width,
346 drawnAreaPtr->height, 0, TK_RELIEF_FLAT);
347 if (scalePtr->flags & REDRAW_OTHER) {
349 * Display the tick marks.
352 if (tickInterval != 0) {
353 char valueString[TCL_DOUBLE_SPACE];
354 double ticks, maxTicks, tickValue;
357 * Ensure that we will only draw enough of the tick values such
358 * that they don't overlap. We base this off the width that
359 * fromValue would take. Not exact, but better than no constraint.
362 ticks = fabs((scalePtr->toValue - scalePtr->fromValue)
364 if (snprintf(valueString, TCL_DOUBLE_SPACE, scalePtr->tickFormat,
365 scalePtr->fromValue) < 0) {
366 valueString[TCL_DOUBLE_SPACE - 1] = '\0';
368 maxTicks = (double) Tk_Width(tkwin)
369 / (double) Tk_TextWidth(scalePtr->tkfont, valueString, -1);
370 if (ticks > maxTicks) {
371 tickInterval *= ticks / maxTicks;
373 tickValue = scalePtr->fromValue;
376 * The TkRoundValueToResolution call gets rid of accumulated
377 * round-off errors, if any.
380 tickValue = TkRoundValueToResolution(scalePtr, tickValue);
381 if (scalePtr->toValue >= scalePtr->fromValue) {
382 if (tickValue > scalePtr->toValue) {
386 if (tickValue < scalePtr->toValue) {
390 DisplayHorizontalValue(scalePtr, drawable, tickValue,
391 scalePtr->horizTickY, scalePtr->tickFormat);
392 tickValue += tickInterval;
398 * Display the value, if it is desired.
401 if (scalePtr->showValue) {
402 DisplayHorizontalValue(scalePtr, drawable, scalePtr->value,
403 scalePtr->horizValueY, scalePtr->valueFormat);
407 * Display the trough and the slider.
410 y = scalePtr->horizTroughY;
411 Tk_Draw3DRectangle(tkwin, drawable,
412 scalePtr->bgBorder, scalePtr->inset, y,
413 Tk_Width(tkwin) - 2*scalePtr->inset,
414 scalePtr->width + 2*scalePtr->borderWidth,
415 scalePtr->borderWidth, TK_RELIEF_SUNKEN);
416 XFillRectangle(scalePtr->display, drawable, scalePtr->troughGC,
417 scalePtr->inset + scalePtr->borderWidth,
418 y + scalePtr->borderWidth,
419 (unsigned) (Tk_Width(tkwin) - 2*scalePtr->inset
420 - 2*scalePtr->borderWidth),
421 (unsigned) scalePtr->width);
422 if (scalePtr->state == STATE_ACTIVE) {
423 sliderBorder = scalePtr->activeBorder;
425 sliderBorder = scalePtr->bgBorder;
427 width = scalePtr->sliderLength/2;
428 height = scalePtr->width;
429 x = TkScaleValueToPixel(scalePtr, scalePtr->value) - width;
430 y += scalePtr->borderWidth;
431 shadowWidth = scalePtr->borderWidth/2;
432 if (shadowWidth == 0) {
435 Tk_Draw3DRectangle(tkwin, drawable, sliderBorder,
436 x, y, 2*width, height, shadowWidth, scalePtr->sliderRelief);
439 width -= shadowWidth;
440 height -= 2*shadowWidth;
441 Tk_Fill3DRectangle(tkwin, drawable, sliderBorder, x, y, width, height,
442 shadowWidth, scalePtr->sliderRelief);
443 Tk_Fill3DRectangle(tkwin, drawable, sliderBorder, x+width, y,
444 width, height, shadowWidth, scalePtr->sliderRelief);
447 * Draw the label at the top of the scale.
450 if ((scalePtr->flags & REDRAW_OTHER) && (scalePtr->labelLength != 0)) {
453 Tk_GetFontMetrics(scalePtr->tkfont, &fm);
454 Tk_DrawChars(scalePtr->display, drawable, scalePtr->textGC,
455 scalePtr->tkfont, scalePtr->label,
456 scalePtr->labelLength, scalePtr->inset + fm.ascent/2,
457 scalePtr->horizLabelY + fm.ascent);
462 *----------------------------------------------------------------------
464 * DisplayHorizontalValue --
466 * This function is called to display values (scale readings) for
467 * horizontally-oriented scales.
473 * The numerical value corresponding to value is displayed with its
474 * bottom edge at "bottom", and at a horizontal position in the scale
475 * that corresponds to "value".
477 *----------------------------------------------------------------------
481 DisplayHorizontalValue(
482 TkScale *scalePtr, /* Information about widget in which to
484 Drawable drawable, /* Pixmap or window in which to draw the
486 double value, /* X-coordinate of number to display,
487 * specified in application coords, not in
488 * pixels (we'll compute pixels). */
489 int top, /* Y-coordinate of top edge of text, specified
491 const char *format) /* Format string to use for the value */
493 Tk_Window tkwin = scalePtr->tkwin;
494 int x, y, length, width;
495 char valueString[TCL_DOUBLE_SPACE];
498 x = TkScaleValueToPixel(scalePtr, value);
499 Tk_GetFontMetrics(scalePtr->tkfont, &fm);
501 if (snprintf(valueString, TCL_DOUBLE_SPACE, format, value) < 0) {
502 valueString[TCL_DOUBLE_SPACE - 1] = '\0';
504 length = (int) strlen(valueString);
505 width = Tk_TextWidth(scalePtr->tkfont, valueString, length);
508 * Adjust the x-coordinate if necessary to keep the text entirely inside
513 if (x < scalePtr->inset + SPACING) {
514 x = scalePtr->inset + SPACING;
518 * Check the right border so use starting point +text width for the check.
521 if (x + width >= (Tk_Width(tkwin) - scalePtr->inset)) {
522 x = Tk_Width(tkwin) - scalePtr->inset - SPACING - width;
524 Tk_DrawChars(scalePtr->display, drawable, scalePtr->textGC,
525 scalePtr->tkfont, valueString, length, x, y);
529 *----------------------------------------------------------------------
533 * This function is invoked as an idle handler to redisplay the contents
540 * The scale gets redisplayed.
542 *----------------------------------------------------------------------
547 ClientData clientData) /* Widget record for scale. */
549 TkScale *scalePtr = (TkScale *)clientData;
550 Tk_Window tkwin = scalePtr->tkwin;
551 Tcl_Interp *interp = scalePtr->interp;
554 char string[TCL_DOUBLE_SPACE];
555 XRectangle drawnArea;
558 scalePtr->flags &= ~REDRAW_PENDING;
559 if ((scalePtr->tkwin == NULL) || !Tk_IsMapped(scalePtr->tkwin)) {
564 * Invoke the scale's command if needed.
567 Tcl_Preserve(scalePtr);
568 if ((scalePtr->flags & INVOKE_COMMAND) && (scalePtr->command != NULL)) {
569 Tcl_Preserve(interp);
570 if (snprintf(string, TCL_DOUBLE_SPACE, scalePtr->valueFormat,
571 scalePtr->value) < 0) {
572 string[TCL_DOUBLE_SPACE - 1] = '\0';
574 Tcl_DStringInit(&buf);
575 Tcl_DStringAppend(&buf, scalePtr->command, -1);
576 Tcl_DStringAppend(&buf, " ", -1);
577 Tcl_DStringAppend(&buf, string, -1);
578 result = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, TCL_EVAL_GLOBAL);
579 Tcl_DStringFree(&buf);
580 if (result != TCL_OK) {
581 Tcl_AddErrorInfo(interp, "\n (command executed by scale)");
582 Tcl_BackgroundException(interp, result);
586 scalePtr->flags &= ~INVOKE_COMMAND;
587 if (scalePtr->flags & SCALE_DELETED) {
588 Tcl_Release(scalePtr);
591 Tcl_Release(scalePtr);
593 #ifndef TK_NO_DOUBLE_BUFFERING
595 * In order to avoid screen flashes, this function redraws the scale in a
596 * pixmap, then copies the pixmap to the screen in a single operation.
597 * This means that there's no point in time where the on-sreen image has
601 pixmap = Tk_GetPixmap(scalePtr->display, Tk_WindowId(tkwin),
602 Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
604 pixmap = Tk_WindowId(tkwin);
605 #endif /* TK_NO_DOUBLE_BUFFERING */
608 drawnArea.width = Tk_Width(tkwin);
609 drawnArea.height = Tk_Height(tkwin);
612 * Much of the redisplay is done totally differently for horizontal and
613 * vertical scales. Handle the part that's different.
616 if (scalePtr->orient == ORIENT_VERTICAL) {
617 DisplayVerticalScale(scalePtr, pixmap, &drawnArea);
619 DisplayHorizontalScale(scalePtr, pixmap, &drawnArea);
623 * Now handle the part of redisplay that is the same for horizontal and
624 * vertical scales: border and traversal highlight.
627 if (scalePtr->flags & REDRAW_OTHER) {
628 if (scalePtr->relief != TK_RELIEF_FLAT) {
629 Tk_Draw3DRectangle(tkwin, pixmap, scalePtr->bgBorder,
630 scalePtr->highlightWidth, scalePtr->highlightWidth,
631 Tk_Width(tkwin) - 2*scalePtr->highlightWidth,
632 Tk_Height(tkwin) - 2*scalePtr->highlightWidth,
633 scalePtr->borderWidth, scalePtr->relief);
635 if (scalePtr->highlightWidth != 0) {
638 if (scalePtr->flags & GOT_FOCUS) {
639 gc = Tk_GCForColor(scalePtr->highlightColorPtr, pixmap);
642 Tk_3DBorderColor(scalePtr->highlightBorder), pixmap);
644 Tk_DrawFocusHighlight(tkwin, gc, scalePtr->highlightWidth, pixmap);
648 #ifndef TK_NO_DOUBLE_BUFFERING
650 * Copy the information from the off-screen pixmap onto the screen, then
654 XCopyArea(scalePtr->display, pixmap, Tk_WindowId(tkwin),
655 scalePtr->copyGC, drawnArea.x, drawnArea.y, drawnArea.width,
656 drawnArea.height, drawnArea.x, drawnArea.y);
657 Tk_FreePixmap(scalePtr->display, pixmap);
658 #endif /* TK_NO_DOUBLE_BUFFERING */
661 scalePtr->flags &= ~REDRAW_ALL;
665 *----------------------------------------------------------------------
669 * Determine which part of a scale widget lies under a given point.
672 * The return value is either TROUGH1, SLIDER, TROUGH2, or OTHER,
673 * depending on which of the scale's active elements (if any) is under
674 * the point at (x,y).
679 *----------------------------------------------------------------------
684 TkScale *scalePtr, /* Widget record for scale. */
685 int x, int y) /* Coordinates within scalePtr's window. */
689 if (scalePtr->orient == ORIENT_VERTICAL) {
690 if ((x < scalePtr->vertTroughX)
691 || (x >= (scalePtr->vertTroughX + 2*scalePtr->borderWidth +
695 if ((y < scalePtr->inset)
696 || (y >= (Tk_Height(scalePtr->tkwin) - scalePtr->inset))) {
699 sliderFirst = TkScaleValueToPixel(scalePtr, scalePtr->value)
700 - scalePtr->sliderLength/2;
701 if (y < sliderFirst) {
704 if (y < sliderFirst + scalePtr->sliderLength) {
710 if ((y < scalePtr->horizTroughY)
711 || (y >= (scalePtr->horizTroughY + 2*scalePtr->borderWidth +
715 if ((x < scalePtr->inset)
716 || (x >= (Tk_Width(scalePtr->tkwin) - scalePtr->inset))) {
719 sliderFirst = TkScaleValueToPixel(scalePtr, scalePtr->value)
720 - scalePtr->sliderLength/2;
721 if (x < sliderFirst) {
724 if (x < sliderFirst + scalePtr->sliderLength) {