Unnamed Fossil Project

Artifact [c999227d5f]
Login

Artifact [c999227d5f]

Artifact c999227d5f3b1fc35d7fd1b4edd5db9825d5096f4b160eafe1e1c263998c7a91:


/* $Id: scroll.c,v 1.2 2005/03/08 20:54:13 jenglish Exp $
 *
 * Copyright 2004, Joe English
 *
 * Support routines for scrollable widgets.
 *
 * (This is sort of half-baked; needs some work)
 *
 * Scrollable interface:
 *
 * 	+ 'first' is controlled by [xy]view widget command 
 * 	  and other scrolling commands like 'see';
 *      + 'total' depends on widget contents;
 *      + 'last' depends on first, total, and widget size.
 *
 * Once the widget knows 'total' and 'last' (typically in the LayoutProc),
 * call Scrolled(h,first,last,total) to synchronize the scrollbar.
 */

#include <tk.h>
#include "tkTheme.h"
#include "widget.h"

/* Private data:
 */
#define SCROLL_UPDATE_PENDING  (0x1)
#define SCROLL_UPDATE_REQUIRED (0x2)

struct ScrollHandleRec
{
    unsigned 	flags;
    WidgetCore	*corePtr;
    Scrollable	*scrollPtr;
};

/* CreateScrollHandle --
 * 	Initialize scroll handle.
 */
ScrollHandle CreateScrollHandle(WidgetCore *corePtr, Scrollable *scrollPtr)
{
    ScrollHandle h = (ScrollHandle)ckalloc(sizeof(*h));

    h->flags = 0;
    h->corePtr = corePtr;
    h->scrollPtr = scrollPtr;

    scrollPtr->first = 0;
    scrollPtr->last = 1;
    scrollPtr->total = 1;
    return h;
}

void FreeScrollHandle(ScrollHandle h)
{
    Tcl_EventuallyFree((ClientData)h, TCL_DYNAMIC);
}

/* UpdateScrollbar --
 *	Call the -scrollcommand callback to sync the scrollbar.
 * 	Returns: Whatever the -scrollcommand does.
 */
static int UpdateScrollbar(Tcl_Interp *interp, ScrollHandle h)
{
    Scrollable *s = h->scrollPtr;
    char args[TCL_DOUBLE_SPACE * 2];
    int code;

    h->flags &= ~(SCROLL_UPDATE_PENDING | SCROLL_UPDATE_REQUIRED);

    if (s->scrollCmd == NULL)
	return TCL_OK;

    sprintf(args, " %g %g",
		(double)s->first / s->total,
		(double)s->last / s->total);
    code = Tcl_VarEval(interp, s->scrollCmd, args, NULL);

    if (WidgetDestroyed(h->corePtr))
	return TCL_ERROR;

    if (code != TCL_OK) {
	/* Disable the -scrollcommand, add to stack trace:
	 */
	ckfree(s->scrollCmd);
	s->scrollCmd = 0;

	Tcl_AddErrorInfo(interp, /* @@@ "horizontal" / "vertical" */
		"\n    (scrolling command executed by ");
	Tcl_AddErrorInfo(interp, Tk_PathName(h->corePtr->tkwin));
	Tcl_AddErrorInfo(interp, ")");
    }
    return code;
}

/* UpdateScrollbarBG --
 * 	Idle handler to update the scrollbar.
 */
static void UpdateScrollbarBG(ClientData clientData)
{
    ScrollHandle h = (ScrollHandle)clientData;
    Tcl_Interp *interp = h->corePtr->interp;
    int code;

    if (WidgetDestroyed(h->corePtr)) {
	Tcl_Release(clientData);
	return;
    }

    Tcl_Preserve((ClientData) interp);
    code = UpdateScrollbar(interp, h);
    if (code == TCL_ERROR && !Tcl_InterpDeleted(interp)) {
	Tcl_BackgroundError(interp);
    }
    Tcl_Release((ClientData) interp);
    Tcl_Release(clientData);
}


/* Scrolled --
 * 	Update scroll info, schedule scrollbar update.
 */
void Scrolled(ScrollHandle h, int first, int last, int total)
{
    Scrollable *s = h->scrollPtr;
    
    /* Sanity-check inputs:
     */
    if (total <= 0) {
	first = 0;
	last = 1;
	total = 1;
    }

    if (s->first != first || s->last != last || s->total != total
	    || (h->flags & SCROLL_UPDATE_REQUIRED))
    {
	s->first = first;
	s->last = last;
	s->total = total;

	if (!(h->flags & SCROLL_UPDATE_PENDING)) {
	    Tcl_Preserve((ClientData)h);
	    Tcl_DoWhenIdle(UpdateScrollbarBG, (ClientData)h);
	    h->flags |= SCROLL_UPDATE_PENDING;
	}
    }
}

/* ScrollbarUpdateRequired --
 * 	Force a scrollbar update at the next call to Scrolled(),
 * 	even if scroll parameters haven't changed (e.g., if
 * 	-yscrollcommand has changed).
 */

void ScrollbarUpdateRequired(ScrollHandle h)
{
    h->flags |= SCROLL_UPDATE_REQUIRED;
}

/* ScrollviewCommand --
 * 	Widget [xy]view command implementation.
 *
 *  $w [xy]view -- return current view region
 *  $w [xy]view $index -- set topmost item
 *  $w [xy]view moveto $fraction
 *  $w [xy]view scroll $number $what -- scrollbar interface
 */
int ScrollviewCommand(
    Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], ScrollHandle h)
{
    Scrollable *s = h->scrollPtr;
    int newFirst = s->first;

    if (objc == 2) {
	char buf[TCL_DOUBLE_SPACE * 2];
	sprintf(buf, "%g %g",
		(double)s->first / s->total,
		(double)s->last / s->total);
	Tcl_SetResult(interp, buf, TCL_VOLATILE);
	return TCL_OK;
    } else if (objc == 3) {
	if (Tcl_GetIntFromObj(interp, objv[2], &newFirst) != TCL_OK) {
	    return TCL_ERROR;
	}
    } else {
	double fraction;
	int count;

	switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count)) {
	    case TK_SCROLL_ERROR:
		return TCL_ERROR;
	    case TK_SCROLL_MOVETO:
		newFirst = (int) ((fraction * s->total) + 0.5);
		break;
	    case TK_SCROLL_UNITS:
		newFirst = s->first + count;
		break;
	    case TK_SCROLL_PAGES: {
		int perPage = s->last - s->first;	/* @@@ */
		newFirst = s->first + count * perPage;
		break;
	    }
	}
    }
    if (newFirst >= s->total)
	newFirst = s->total - 1;
    if (newFirst > s->first && s->last >= s->total) /* don't scroll past end */
	newFirst = s->first;
    if (newFirst < 0)
	newFirst = 0;

    if (newFirst != s->first) {
	s->first = newFirst;
	TtkRedisplayWidget(h->corePtr);
    }

    return TCL_OK;
}