#include "WINGsP.h"

#include <math.h>

/* undefine will disable the autoadjusting of the knob dimple to be
 * directly below the cursor
 * DOES NOT WORK */
#undef STRICT_NEXT_BEHAVIOUR

#define AUTOSCROLL_INITIAL_DELAY 	200

#define AUTOSCROLL_DELAY 		40


char *WMScrollerDidScrollNotification = "WMScrollerDidScrollNotification";



typedef struct W_Scroller {
    W_Class widgetClass;
    W_View *view;

    void *clientData;
    WMAction *action;

    float knobProportion;
    float floatValue;

    WMHandlerID timerID;	       /* for continuous scrolling mode */

#ifndef STRICT_NEXT_BEHAVIOUR
    int dragPoint;		       /* point where the knob is being
    * dragged */
#endif
    struct {
        WMScrollArrowPosition arrowsPosition:4;

        unsigned int horizontal:1;

        WMScrollerPart hitPart:4;

        /* */
        unsigned int documentFullyVisible:1;   /* document is fully visible */

        unsigned int prevSelected:1;

        unsigned int pushed:1;

        unsigned int incrDown:1;      /* whether increment button is down */

        unsigned int decrDown:1;

        unsigned int draggingKnob:1;

        unsigned int configured:1;

        unsigned int redrawPending:1;
    } flags;
} Scroller;



#define DEFAULT_HEIGHT		60
#define DEFAULT_WIDTH		SCROLLER_WIDTH
#define DEFAULT_ARROWS_POSITION	WSAMinEnd


#define BUTTON_SIZE             ((SCROLLER_WIDTH) - 4)


static void destroyScroller(Scroller *sPtr);
static void paintScroller(Scroller *sPtr);

static void willResizeScroller();
static void handleEvents(XEvent *event, void *data);
static void handleActionEvents(XEvent *event, void *data);

static void handleMotion(Scroller *sPtr, int mouseX, int mouseY);


W_ViewDelegate _ScrollerViewDelegate = {
    NULL,
    NULL,
    NULL,
    NULL,
    willResizeScroller
};




WMScroller*
WMCreateScroller(WMWidget *parent)
{
    Scroller *sPtr;

    sPtr = wmalloc(sizeof(Scroller));
    memset(sPtr, 0, sizeof(Scroller));

    sPtr->widgetClass = WC_Scroller;

    sPtr->view = W_CreateView(W_VIEW(parent));
    if (!sPtr->view) {
        wfree(sPtr);
        return NULL;
    }
    sPtr->view->self = sPtr;

    sPtr->view->delegate = &_ScrollerViewDelegate;

    sPtr->flags.documentFullyVisible = 1;

    WMCreateEventHandler(sPtr->view, ExposureMask|StructureNotifyMask
                         |ClientMessageMask, handleEvents, sPtr);

    W_ResizeView(sPtr->view, DEFAULT_WIDTH, DEFAULT_WIDTH);
    sPtr->flags.arrowsPosition = DEFAULT_ARROWS_POSITION;

    WMCreateEventHandler(sPtr->view, ButtonPressMask|ButtonReleaseMask
                         |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
                         handleActionEvents, sPtr);

    sPtr->flags.hitPart = WSNoPart;

    sPtr->floatValue = 0.0;
    sPtr->knobProportion = 1.0;

    return sPtr;
}



void
WMSetScrollerArrowsPosition(WMScroller *sPtr, WMScrollArrowPosition position)
{
    sPtr->flags.arrowsPosition = position;
    if (sPtr->view->flags.realized) {
        paintScroller(sPtr);
    }
}


static void
willResizeScroller(W_ViewDelegate *self, WMView *view,
                   unsigned int *width, unsigned int *height)
{
    WMScroller *sPtr = (WMScroller*)view->self;

    if (*width > *height) {
        sPtr->flags.horizontal = 1;
        *height = SCROLLER_WIDTH;
    } else {
        sPtr->flags.horizontal = 0;
        *width = SCROLLER_WIDTH;
    }
}


void
WMSetScrollerAction(WMScroller *sPtr, WMAction *action, void *clientData)
{
    CHECK_CLASS(sPtr, WC_Scroller);

    sPtr->action = action;

    sPtr->clientData = clientData;
}


void
WMSetScrollerParameters(WMScroller *sPtr, float floatValue,
                        float knobProportion)
{
    CHECK_CLASS(sPtr, WC_Scroller);

    assert(!isnan(floatValue));

    if (floatValue < 0.0)
        sPtr->floatValue = 0.0;
    else if (floatValue > 1.0)
        sPtr->floatValue = 1.0;
    else
        sPtr->floatValue = floatValue;

    if (knobProportion <= 0.0) {

        sPtr->knobProportion = 0.0;
        sPtr->flags.documentFullyVisible = 0;

    } else if (knobProportion >= 1.0) {

        sPtr->knobProportion = 1.0;
        sPtr->flags.documentFullyVisible = 1;

    } else {
        sPtr->knobProportion = knobProportion;
        sPtr->flags.documentFullyVisible = 0;
    }

    if (sPtr->view->flags.realized)
        paintScroller(sPtr);

    /*   WMPostNotificationName(WMScrollerDidScrollNotification, sPtr, NULL);*/
}


float
WMGetScrollerKnobProportion(WMScroller *sPtr)
{
    CHECK_CLASS(sPtr, WC_Scroller);

    return sPtr->knobProportion;
}


float
WMGetScrollerValue(WMScroller *sPtr)
{
    CHECK_CLASS(sPtr, WC_Scroller);

    return sPtr->floatValue;
}


WMScrollerPart
WMGetScrollerHitPart(WMScroller *sPtr)
{
    CHECK_CLASS(sPtr, WC_Scroller);

    return sPtr->flags.hitPart;
}


static void
paintArrow(WMScroller *sPtr, Drawable d, int part)
/*
 * part- 0 paints the decrement arrow, 1 the increment arrow
 */
{
    WMView *view = sPtr->view;
    WMScreen *scr = view->screen;
    int ofs;
    W_Pixmap *arrow;

#ifndef DOUBLE_BUFFER
    GC gc = scr->lightGC;
#endif

    if (part == 0) { /* decrement button */
        if (sPtr->flags.horizontal) {
            if (sPtr->flags.arrowsPosition == WSAMaxEnd) {
                ofs = view->size.width - 2*(BUTTON_SIZE+1) - 1;
            } else {
                ofs = 2;
            }
            if (sPtr->flags.decrDown)
                arrow = scr->hiLeftArrow;
            else
                arrow = scr->leftArrow;

        } else {
            if (sPtr->flags.arrowsPosition == WSAMaxEnd) {
                ofs = view->size.height - 2*(BUTTON_SIZE+1) - 1;
            } else {
                ofs = 2;
            }
            if (sPtr->flags.decrDown)
                arrow = scr->hiUpArrow;
            else
                arrow = scr->upArrow;
        }

#ifndef DOUBLE_BUFFER
        if (sPtr->flags.decrDown)
            gc = WMColorGC(scr->white);
#endif
    } else { /* increment button */
        if (sPtr->flags.horizontal) {
            if (sPtr->flags.arrowsPosition == WSAMaxEnd) {
                ofs = view->size.width - BUTTON_SIZE+1 - 3;
            } else {
                ofs = 2 + BUTTON_SIZE+1;
            }
            if (sPtr->flags.incrDown)
                arrow = scr->hiRightArrow;
            else
                arrow = scr->rightArrow;
        } else {
            if (sPtr->flags.arrowsPosition == WSAMaxEnd) {
                ofs = view->size.height - BUTTON_SIZE+1 - 3;
            } else {
                ofs = 2 + BUTTON_SIZE+1;
            }
            if (sPtr->flags.incrDown)
                arrow = scr->hiDownArrow;
            else
                arrow = scr->downArrow;
        }

#ifndef DOUBLE_BUFFER
        if (sPtr->flags.incrDown)
            gc = scr->whiteGC;
#endif
    }


    if (sPtr->flags.horizontal) {
        /* paint button */
#ifndef DOUBLE_BUFFER
        XFillRectangle(scr->display, d, gc,
                       ofs+1, 2+1, BUTTON_SIZE+1-3, BUTTON_SIZE-3);
#else
        if ((!part&&sPtr->flags.decrDown) || (part&&sPtr->flags.incrDown))
            XFillRectangle(scr->display, d, WMColorGC(scr->white),
                           ofs+1, 2+1, BUTTON_SIZE+1-3, BUTTON_SIZE-3);
#endif /* DOUBLE_BUFFER */
        W_DrawRelief(scr, d, ofs, 2, BUTTON_SIZE, BUTTON_SIZE, WRRaised);

        /* paint arrow */
        XSetClipMask(scr->display, scr->clipGC, arrow->mask);
        XSetClipOrigin(scr->display, scr->clipGC,
                       ofs + (BUTTON_SIZE - arrow->width) / 2,
                       2 + (BUTTON_SIZE - arrow->height) / 2);

        XCopyArea(scr->display, arrow->pixmap, d, scr->clipGC,
                  0, 0, arrow->width, arrow->height,
                  ofs + (BUTTON_SIZE - arrow->width) / 2,
                  2 + (BUTTON_SIZE - arrow->height) / 2);

    } else { /* vertical */

        /* paint button */
#ifndef DOUBLE_BUFFER
        XFillRectangle(scr->display, d, gc,
                       2+1, ofs+1, BUTTON_SIZE-3, BUTTON_SIZE+1-3);
#else
        if ((!part&&sPtr->flags.decrDown) || (part&&sPtr->flags.incrDown))
            XFillRectangle(scr->display, d, WMColorGC(scr->white),
                           2+1, ofs+1, BUTTON_SIZE-3, BUTTON_SIZE+1-3);
#endif /* DOUBLE_BUFFER */
        W_DrawRelief(scr, d, 2, ofs, BUTTON_SIZE, BUTTON_SIZE, WRRaised);

        /* paint arrow */

        XSetClipMask(scr->display, scr->clipGC, arrow->mask);
        XSetClipOrigin(scr->display, scr->clipGC,
                       2 + (BUTTON_SIZE - arrow->width) / 2,
                       ofs + (BUTTON_SIZE - arrow->height) / 2);
        XCopyArea(scr->display, arrow->pixmap, d, scr->clipGC,
                  0, 0, arrow->width, arrow->height,
                  2 + (BUTTON_SIZE - arrow->width) / 2,
                  ofs + (BUTTON_SIZE - arrow->height) / 2);
    }
}


static int
knobLength(Scroller *sPtr)
{
    int tmp, length;


    if (sPtr->flags.horizontal)
        length = sPtr->view->size.width - 4;
    else
        length = sPtr->view->size.height - 4;

    if (sPtr->flags.arrowsPosition != WSANone) {
        length -= 2*(BUTTON_SIZE+1);
    }

    tmp = (int)((float)length * sPtr->knobProportion + 0.5);
    /* keep minimum size */
    if (tmp < BUTTON_SIZE)
        tmp = BUTTON_SIZE;

    return tmp;
}


static void
paintScroller(Scroller *sPtr)
{
    WMView *view = sPtr->view;
    WMScreen *scr = view->screen;
#ifdef DOUBLE_BUFFER
    Pixmap d;
#else
    Drawable d = view->window;
#endif
    int length, ofs;
    float knobP, knobL;


#ifdef DOUBLE_BUFFER
    d = XCreatePixmap(scr->display, view->window, view->size.width,
                      view->size.height, scr->depth);
    XFillRectangle(scr->display, d, WMColorGC(scr->gray), 0, 0,
                   view->size.width, view->size.height);
#endif

    XDrawRectangle(scr->display, d, WMColorGC(scr->black), 0, 0,
                   view->size.width-1, view->size.height-1);
#ifndef DOUBLE_BUFFER
    XDrawRectangle(scr->display, d, WMColorGC(scr->gray), 1, 1,
                   view->size.width-3, view->size.height-3);
#endif

    if (sPtr->flags.horizontal)
        length = view->size.width - 4;
    else
        length = view->size.height - 4;

    if (sPtr->flags.documentFullyVisible) {
        XFillRectangle(scr->display, d, scr->stippleGC, 2, 2,
                       view->size.width-4, view->size.height-4);
    } else {
        ofs = 2;
        if (sPtr->flags.arrowsPosition==WSAMaxEnd) {
            length -= (BUTTON_SIZE+1)*2;
        } else if (sPtr->flags.arrowsPosition==WSAMinEnd) {
            ofs += (BUTTON_SIZE+1)*2;
            length -= (BUTTON_SIZE+1)*2;
        }

        knobL = (float)knobLength(sPtr);

        knobP = sPtr->floatValue * ((float)length - knobL);

        if (sPtr->flags.horizontal) {
            /* before */
            XFillRectangle(scr->display, d, scr->stippleGC,
                           ofs, 2, (int)knobP, view->size.height-4);

            /* knob */
#ifndef DOUBLE_BUFFER
            XFillRectangle(scr->display, d, scr->lightGC,
                           ofs+(int)knobP+2, 2+2, (int)knobL-4,
                           view->size.height-4-4);
#endif
            W_DrawRelief(scr, d, ofs+(int)knobP, 2, (int)knobL,
                         view->size.height-4, WRRaised);

            XCopyArea(scr->display, scr->scrollerDimple->pixmap, d,
                      scr->copyGC, 0, 0,
                      scr->scrollerDimple->width, scr->scrollerDimple->height,
                      ofs+(int)knobP+((int)knobL-scr->scrollerDimple->width-1)/2,
                      (view->size.height-scr->scrollerDimple->height-1)/2);

            /* after */
            if ((int)(knobP+knobL) < length)
                XFillRectangle(scr->display, d, scr->stippleGC,
                               ofs+(int)(knobP+knobL), 2,
                               length-(int)(knobP+knobL),
                               view->size.height-4);
        } else {
            /* before */
            if (knobP>0.0)
                XFillRectangle(scr->display, d, scr->stippleGC,
                               2, ofs, view->size.width-4, (int)knobP);

            /* knob */
#ifndef DOUBLE_BUFFER
            XFillRectangle(scr->display, d, scr->lightGC,
                           2+2, ofs+(int)knobP+2,
                           view->size.width-4-4, (int)knobL-4);
#endif
            XCopyArea(scr->display, scr->scrollerDimple->pixmap, d,
                      scr->copyGC, 0, 0,
                      scr->scrollerDimple->width, scr->scrollerDimple->height,
                      (view->size.width-scr->scrollerDimple->width-1)/2,
                      ofs+(int)knobP+((int)knobL-scr->scrollerDimple->height-1)/2);

            W_DrawRelief(scr, d, 2, ofs+(int)knobP,
                         view->size.width-4, (int)knobL, WRRaised);

            /* after */
            if ((int)(knobP+knobL) < length)
                XFillRectangle(scr->display, d, scr->stippleGC,
                               2, ofs+(int)(knobP+knobL),
                               view->size.width-4,
                               length-(int)(knobP+knobL));
        }

        if (sPtr->flags.arrowsPosition != WSANone) {
            paintArrow(sPtr, d, 0);
            paintArrow(sPtr, d, 1);
        }
    }

#ifdef DOUBLE_BUFFER
    XCopyArea(scr->display, d, view->window, scr->copyGC, 0, 0,
              view->size.width, view->size.height, 0, 0);
    XFreePixmap(scr->display, d);
#endif
}



static void
handleEvents(XEvent *event, void *data)
{
    Scroller *sPtr = (Scroller*)data;

    CHECK_CLASS(data, WC_Scroller);


    switch (event->type) {
    case Expose:
        if (event->xexpose.count==0)
            paintScroller(sPtr);
        break;

    case DestroyNotify:
        destroyScroller(sPtr);
        break;
    }
}



/*
 * locatePointInScroller-
 *     Return the part of the scroller where the point is located.
 */
static WMScrollerPart
locatePointInScroller(Scroller *sPtr, int x, int y, int alternate)
{
    int width = sPtr->view->size.width;
    int height = sPtr->view->size.height;
    int c, p1, p2, p3, p4, p5, p6;
    int knobL, slotL;


    /* if there is no knob... */
    if (sPtr->flags.documentFullyVisible)
        return WSKnobSlot;

    if (sPtr->flags.horizontal)
        c = x;
    else
        c = y;

    /*     p1  p2           p3           p4   p5   p6
     * |   |   |###########|             |#####|   |   |
     * | < | > |###########|      O      |#####| < | > |
     * |   |   |###########|             |#####|   |   |
     */

    if (sPtr->flags.arrowsPosition == WSAMinEnd) {
        p1 = 18;
        p2 = 36;

        if (sPtr->flags.horizontal) {
            slotL = width - 36;
            p5 = width;
        } else {
            slotL = height - 36;
            p5 = height;
        }
        p6 = p5;
    } else if (sPtr->flags.arrowsPosition == WSAMaxEnd) {
        if (sPtr->flags.horizontal) {
            slotL = width - 36;
            p6 = width - 18;
        } else {
            slotL = height - 36;
            p6 = height - 18;
        }
        p5 = p6 - 18;

        p1 = p2 = 0;
    } else {
        /* no arrows */
        p1 = p2 = 0;

        if (sPtr->flags.horizontal) {
            slotL = p5 = p6 = width;
        } else {
            slotL = p5 = p6 = height;
        }
    }

    knobL = knobLength(sPtr);
    p3 = p2 + (int)((float)(slotL-knobL) * sPtr->floatValue);
    p4 = p3 + knobL;

    /* uses a mix of the NS and Win ways of doing scroll page */
    if (c <= p1)
        return alternate ? WSDecrementPage : WSDecrementLine;
    else if (c <= p2)
        return alternate ? WSIncrementPage : WSIncrementLine;
    else if (c <= p3)
        return WSDecrementPage;
    else if (c <= p4)
        return WSKnob;
    else if (c <= p5)
        return WSIncrementPage;
    else if (c <= p6)
        return alternate ? WSDecrementPage : WSDecrementLine;
    else
        return alternate ? WSIncrementPage : WSIncrementLine;
}



static void
handlePush(Scroller *sPtr, int pushX, int pushY, int alternate)
{
    WMScrollerPart part;
    int doAction = 0;

    part = locatePointInScroller(sPtr, pushX, pushY, alternate);

    sPtr->flags.hitPart = part;

    switch (part) {
    case WSIncrementLine:
        sPtr->flags.incrDown = 1;
        doAction = 1;
        break;

    case WSIncrementPage:
        doAction = 1;
        break;

    case WSDecrementLine:
        sPtr->flags.decrDown = 1;
        doAction = 1;
        break;

    case WSDecrementPage:
        doAction = 1;
        break;

    case WSKnob:
        sPtr->flags.draggingKnob = 1;
#ifndef STRICT_NEXT_BEHAVIOUR
        if (sPtr->flags.horizontal)
            sPtr->dragPoint = pushX;
        else
            sPtr->dragPoint = pushY;

        {
            int length, knobP;
            int buttonsLen;

            if (sPtr->flags.arrowsPosition != WSANone)
                buttonsLen = 2*(BUTTON_SIZE+1);
            else
                buttonsLen = 0;

            if (sPtr->flags.horizontal)
                length = sPtr->view->size.width - 4 - buttonsLen;
            else
                length = sPtr->view->size.height - 4 - buttonsLen;

            knobP = (int)(sPtr->floatValue * (float)(length-knobLength(sPtr)));

            if (sPtr->flags.arrowsPosition == WSAMinEnd)
                sPtr->dragPoint -= 2 + buttonsLen + knobP;
            else
                sPtr->dragPoint -= 2 + knobP;
        }
#endif /* STRICT_NEXT_BEHAVIOUR */
        /* This does not seem necesary here since we don't know yet if the
         * knob will be dragged later. -Dan
         handleMotion(sPtr, pushX, pushY); */
        break;

    case WSDecrementWheel:
    case WSIncrementWheel:
    case WSKnobSlot:
    case WSNoPart:
        /* dummy */
        break;
    }

    if (doAction && sPtr->action) {
        (*sPtr->action)(sPtr, sPtr->clientData);

        WMPostNotificationName(WMScrollerDidScrollNotification, sPtr, NULL);
    }
}


static float
floatValueForPoint(Scroller *sPtr, int point)
{
    float floatValue = 0;
    float position;
    int slotOfs, slotLength, knobL;

    if (sPtr->flags.horizontal)
        slotLength = sPtr->view->size.width - 4;
    else
        slotLength = sPtr->view->size.height - 4;

    slotOfs = 2;
    if (sPtr->flags.arrowsPosition==WSAMaxEnd) {
        slotLength -= (BUTTON_SIZE+1)*2;
    } else if (sPtr->flags.arrowsPosition==WSAMinEnd) {
        slotOfs += (BUTTON_SIZE+1)*2;
        slotLength -= (BUTTON_SIZE+1)*2;
    }

    knobL = (float)knobLength(sPtr);
#ifdef STRICT_NEXT_BEHAVIOUR
    if (point < slotOfs + knobL/2)
        position = (float)(slotOfs + knobL/2);
    else if (point > slotOfs + slotLength - knobL/2)
        position = (float)(slotOfs + slotLength - knobL/2);
    else
        position = (float)point;

    floatValue = (position-(float)(slotOfs+slotLength/2))
        /(float)(slotLength-knobL);
#else
    /* Adjust the last point to lie inside the knob slot */
    if (point < slotOfs)
        position = (float)slotOfs;
    else if (point > slotOfs + slotLength)
        position = (float)(slotOfs + slotLength);
    else
        position = (float)point;

    /* Compute the float value */
    floatValue = (position-(float)slotOfs) / (float)(slotLength-knobL);
#endif

    assert(!isnan(floatValue));
    return floatValue;
}


static void
handleMotion(Scroller *sPtr, int mouseX, int mouseY)
{
    if (sPtr->flags.draggingKnob) {
        float newFloatValue;
        int point;

        if (sPtr->flags.horizontal) {
            point = mouseX;
        } else {
            point = mouseY;
        }

#ifndef STRICT_NEXT_BEHAVIOUR
        point -= sPtr->dragPoint;
#endif

        newFloatValue = floatValueForPoint(sPtr, point);
        WMSetScrollerParameters(sPtr, newFloatValue, sPtr->knobProportion);
        if (sPtr->action) {
            (*sPtr->action)(sPtr, sPtr->clientData);
            WMPostNotificationName(WMScrollerDidScrollNotification, sPtr,
                                   NULL);
        }
    } else {
        int part;

        part = locatePointInScroller(sPtr, mouseX, mouseY, False);

        sPtr->flags.hitPart = part;

        if (part == WSIncrementLine && sPtr->flags.decrDown) {
            sPtr->flags.decrDown = 0;
            sPtr->flags.incrDown = 1;
        } else if (part == WSDecrementLine && sPtr->flags.incrDown) {
            sPtr->flags.incrDown = 0;
            sPtr->flags.decrDown = 1;
        } else if (part != WSIncrementLine && part != WSDecrementLine) {
            sPtr->flags.incrDown = 0;
            sPtr->flags.decrDown = 0;
        }
    }
}


static void
autoScroll(void *clientData)
{
    Scroller *sPtr = (Scroller*)clientData;

    if (sPtr->action) {
        (*sPtr->action)(sPtr, sPtr->clientData);
        WMPostNotificationName(WMScrollerDidScrollNotification, sPtr, NULL);
    }
    sPtr->timerID= WMAddTimerHandler(AUTOSCROLL_DELAY, autoScroll, clientData);
}


static void
handleActionEvents(XEvent *event, void *data)
{
    Scroller *sPtr = (Scroller*)data;
    int wheelDecrement, wheelIncrement;
    int id, dd;


    /* check if we're really dealing with a scroller, as something
     * might have gone wrong in the event dispatching stuff */
    CHECK_CLASS(sPtr, WC_Scroller);

    id = sPtr->flags.incrDown;
    dd = sPtr->flags.decrDown;

    switch (event->type) {
    case EnterNotify:
        break;

    case LeaveNotify:
        if (sPtr->timerID) {
            WMDeleteTimerHandler(sPtr->timerID);
            sPtr->timerID = NULL;
        }
        sPtr->flags.incrDown = 0;
        sPtr->flags.decrDown = 0;
        break;

    case ButtonPress:
        /* FIXME: change Mod1Mask with something else */
        if (sPtr->flags.documentFullyVisible)
            break;

        if (sPtr->flags.horizontal) {
            wheelDecrement = WINGsConfiguration.mouseWheelDown;
            wheelIncrement = WINGsConfiguration.mouseWheelUp;
        } else {
            wheelDecrement = WINGsConfiguration.mouseWheelUp;
            wheelIncrement = WINGsConfiguration.mouseWheelDown;
        }

        if (event->xbutton.button == wheelDecrement) {
            if (event->xbutton.state & ControlMask) {
                sPtr->flags.hitPart = WSDecrementPage;
            } else if (event->xbutton.state & ShiftMask) {
                sPtr->flags.hitPart = WSDecrementLine;
            } else {
                sPtr->flags.hitPart = WSDecrementWheel;
            }
            if (sPtr->action) {
                (*sPtr->action)(sPtr, sPtr->clientData);
                WMPostNotificationName(WMScrollerDidScrollNotification, sPtr,
                                       NULL);
            }
        } else if (event->xbutton.button == wheelIncrement) {
            if (event->xbutton.state & ControlMask) {
                sPtr->flags.hitPart = WSIncrementPage;
            } else if (event->xbutton.state & ShiftMask) {
                sPtr->flags.hitPart = WSIncrementLine;
            } else {
                sPtr->flags.hitPart = WSIncrementWheel;
            }
            if (sPtr->action) {
                (*sPtr->action)(sPtr, sPtr->clientData);
                WMPostNotificationName(WMScrollerDidScrollNotification, sPtr,
                                       NULL);
            }
        } else {
            handlePush(sPtr, event->xbutton.x, event->xbutton.y,
                       (event->xbutton.state & Mod1Mask)
                       ||event->xbutton.button==Button2);
            /* continue scrolling if pushed on the buttons */
            if (sPtr->flags.hitPart == WSIncrementLine
                || sPtr->flags.hitPart == WSDecrementLine) {
                sPtr->timerID = WMAddTimerHandler(AUTOSCROLL_INITIAL_DELAY,
                                                  autoScroll, sPtr);
            }
        }
        break;

    case ButtonRelease:
        if (sPtr->flags.draggingKnob) {
            if (sPtr->action) {
                (*sPtr->action)(sPtr, sPtr->clientData);
                WMPostNotificationName(WMScrollerDidScrollNotification, sPtr,
                                       NULL);
            }
        }
        if (sPtr->timerID) {
            WMDeleteTimerHandler(sPtr->timerID);
            sPtr->timerID = NULL;
        }
        sPtr->flags.incrDown = 0;
        sPtr->flags.decrDown = 0;
        sPtr->flags.draggingKnob = 0;
        break;

    case MotionNotify:
        handleMotion(sPtr, event->xbutton.x, event->xbutton.y);
        if (sPtr->timerID && sPtr->flags.hitPart != WSIncrementLine
            && sPtr->flags.hitPart != WSDecrementLine) {
            WMDeleteTimerHandler(sPtr->timerID);
            sPtr->timerID = NULL;
        }
        break;
    }
    if (id != sPtr->flags.incrDown || dd != sPtr->flags.decrDown)
        paintScroller(sPtr);
}



static void
destroyScroller(Scroller *sPtr)
{
    /* we don't want autoscroll try to scroll a freed widget */
    if (sPtr->timerID) {
        WMDeleteTimerHandler(sPtr->timerID);
    }

    wfree(sPtr);
}



syntax highlighted by Code2HTML, v. 0.9.1