CSliderControl.cp

/*
** File:        CSliderControl.cp
** Written by:  Tim Nufire
**
** Copyright © 1990-1995 Apple Computer, Inc.
** All rights reserved. */
 
/*****************************************************************************/
/* This is a custom slider ported by me from Kibitz to PowerPlant.           */
/*****************************************************************************/
 
 
enum {
    kCapHeight = 11,
    kThumbHeight = 9,
    kThumbOffset = 11,
    kSliderWidth = 20
};
 
#include "CSliderControl.h"
 
#ifndef __RESOURCES__
#include <Resources.h>
#endif
 
#ifndef __TOOLUTILS__
#include <ToolUtils.h>
#endif
 
#ifndef __MEMORY__
#include <Memory.h>
#endif
 
#ifndef __OSUTILS__
#include <OSUtils.h>
#endif
 
#ifndef __CONTROLS__
#include <Controls.h>
#endif
 
#ifndef __GWLAYERS__
#include "GWLayers.h"
#endif
 
/*****************************************************************************/
 
#pragma options align=mac68k
 
typedef struct cdefRsrcJMP {
    long    jsrInst;
    long    moveInst;
    short   jmpInst;
    long    jmpAddress;
} cdefRsrcJMP;
typedef cdefRsrcJMP *cdefRsrcJMPPtr, **cdefRsrcJMPHndl;
 
typedef struct thumbCntlParams {
    Rect    limitRect;
    Rect    slopRect;
    short   axis;
} thumbCntlParams;
 
#pragma options align=reset
 
 
/*****************************************************************************/
/* SetSliderJmpAddress()                                                     */
/*                                                                           */
/* This routine patches a dummy slider CDEF so that the slider code can      */
/* reside inside our object. This be called once during application startup  */
/* BEFORE any slider objects are created.                                    */ 
/*****************************************************************************/
 
void 
CSliderControl::SetSliderJmpAddress()
{
    cdefRsrcJMPHndl cdefRsrc;
    static  ControlDefUPP   sliderCtlUPP = nil;
 
    cdefRsrc = (cdefRsrcJMPHndl)GetResource('CDEF', (sliderCntlProc / 16));
    if (!sliderCtlUPP)
        sliderCtlUPP = NewControlDefProc(CSliderControl::SliderCtl);
    (*cdefRsrc)->jmpAddress = (long)sliderCtlUPP;
    
    /* Make sure that instruction caches don't kill us. */
    #ifndef powerc
        FlushInstructionCache(); 
    #endif
}
 
/*****************************************************************************
** SliderCtl(short varCode, ControlHandle ctl, short msg, long parm)         
**                                                                           
** This is a custom slider originally from Kibitz. It expects contrlRfCon to
** contain a pointer to an CSliderControl object and will call that objects
** member functions UpdateSlider() and TrackSlider() to handle drawing and 
** tracking respectively.
**
** There is a custom cdef for this code.  All it does is jump to this code.
** In addition to being a nice way to objectize the slider, there is another
** really good reason for this which is that it is possible for the cdef to 
** be re-entered! When the user clicks on a slider, the control manager locks 
** down the cdef, and then calls it. When the custom cdef returns to the control 
** manager, the cdef is unlocked. This all seems very reasonable. However, if 
** the code that tracks the slider updates another slider you have a problem.
** This is because the control manager handles the update by calling the cdef.  
** Of course, it locks it down, and then when the control manager is returned to, 
** it unlocks it.  BUT WAIT!!  We are still tracking the slider which caused the 
** update in the first place.  This is true.  It is also true that the cdef is now 
** UNLOCKED!  It isn't a good idea to rts to code that has moved.  This is why 
** the cdef jumps to the code in the application.  We never return to the 
** (potentially unlocked) cdef.  We  return straight to the control manager.  
** Ugly problem, huh?
*****************************************************************************/
 
pascal long 
CSliderControl::SliderCtl(short /*varCode*/, ControlHandle ctl, short msg, long parm)
{
    CSliderControl  *thisSlider;
    Rect            viewRect;
    thumbCntlParams *tcp;
    
    thisSlider = (CSliderControl *)(*ctl)->contrlRfCon;
    viewRect = (*ctl)->contrlRect;
 
    if (thisSlider) switch (msg) {
        case initCntl:
        case dispCntl:
        case posCntl:
            break;
 
        case drawCntl:
            thisSlider->UpdateSlider();
            break;
 
        case testCntl:
            if ((*ctl)->contrlHilite != 255
             && (*ctl)->contrlMax
             && PtInRect(*(Point *)&parm, &viewRect)) {
                thisSlider->mLastClick = *((Point *)&parm);
                
                /* Everything is the thumb.  The "thumb" routine will figure
                ** out what part it is.  Since this is a very specific control,
                ** we can get away with this simplification. */
                return(kControlIndicatorPart);
            }
            break;
 
        case calcCRgns:
            parm &= 0x00FFFFFF; /* 24-bit memory manager is in use */
            /* fall through to calcCntlRgn */
        case calcCntlRgn:
        case calcThumbRgn:
            RectRgn((RgnHandle)parm, &viewRect);
            break;
 
        case thumbCntl:
            tcp = (thumbCntlParams *)parm;
            tcp->limitRect = viewRect;
            InsetRect(&viewRect, -64, -64);
            tcp->slopRect = viewRect;
            tcp->axis = 2;
            break;
 
        case dragCntl:
            thisSlider->TrackSlider(thisSlider->mLastClick);
            return(true);
 
        case autoTrack:
            break;
 
        default:
            break;
    }
 
    return(0);
}
 
 
// ---------------------------------------------------------------------------
//      ¥ CreateFromCNTL [static]
// ---------------------------------------------------------------------------
//  Create a StdControl or SliderControl from a CNTL resource
/*
LStdControl*
LStdControl::CreateFromCNTL(
    ResIDT      inCNTLid,
    MessageT    inValueMessage,
    ResIDT      inTextTraitsID,
    LView       *inSuperView)
{
    LStdControl *theStdControl = nil;
 
    ::HidePen();
    inSuperView->FocusDraw();
    
    if (inTextTraitsID != 0) {      // Control does not use System font
        UTextTraits::SetPortTextTraits(inTextTraitsID);
    }
    
    ControlHandle   macControlH = ::GetNewControl(inCNTLid,
                                                inSuperView->GetMacPort());
    ::ShowPen();
    ThrowIfNil_(macControlH);
    
    SCNTLResource   *resP = *(SCNTLResourceH) ::GetResource('CNTL', inCNTLid);
    
    SPaneInfo   thePaneInfo;
    thePaneInfo.paneID = inCNTLid;
    thePaneInfo.left = resP->bounds.left;
    thePaneInfo.top = resP->bounds.top;
    thePaneInfo.width = resP->bounds.right - thePaneInfo.left;
    thePaneInfo.height = resP->bounds.bottom - thePaneInfo.top;
    thePaneInfo.visible = (resP->visible != 0);
    thePaneInfo.enabled = true;
    thePaneInfo.bindings.left =
        thePaneInfo.bindings.top =
        thePaneInfo.bindings.right =
        thePaneInfo.bindings.bottom = false;
    thePaneInfo.userCon = 0;
    thePaneInfo.superView = inSuperView;
    
                                    // Mask off useWFont variation code
    Int16   controlKind =
            (resP->procID & ~((Uint16) kControlUsesOwningWindowsFontVariant));
    switch (controlKind) {
    
        case pushButProc:
            theStdControl = new LStdButton(thePaneInfo, inValueMessage,
                                    inTextTraitsID, macControlH);
            break;
            
        case checkBoxProc:
            theStdControl = new LStdCheckBox(thePaneInfo, inValueMessage,
                                    resP->value, inTextTraitsID, macControlH);
            break;
            
        case radioButProc:
            theStdControl = new LStdRadioButton(thePaneInfo, inValueMessage,
                                    resP->value, inTextTraitsID, macControlH);
            break;
            
        case popupMenuProc:
            theStdControl = new LStdPopupMenu(thePaneInfo, inValueMessage,
                                    ::GetControlMaximum(macControlH),
                                    inTextTraitsID, macControlH);
            break;
            
        case sliderCntlProc:
            theStdControl = new CSliderControl(thePaneInfo, inValueMessage,
                                    ::GetControlValue(macControlH),
                                    ::GetControlMinimum(macControlH),
                                    ::GetControlMaximum(macControlH),
                                    controlKind, inTextTraitsID,
                                    macControlH);
            break;
            
        default:
            theStdControl = new LStdControl(thePaneInfo, inValueMessage,
                                    ::GetControlValue(macControlH),
                                    ::GetControlMinimum(macControlH),
                                    ::GetControlMaximum(macControlH),
                                    controlKind, inTextTraitsID,
                                    macControlH);
            break;
    }
 
    return theStdControl;
}
*/
 
// ---------------------------------------------------------------------------
//      ¥ CreateSliderControlStream [static]
// ---------------------------------------------------------------------------
//  Create a new SliderControl from the data in a Stream
//
//  Current port must be the Window into which to install the control
 
CSliderControl*
CSliderControl::CreateSliderControlStream(
    LStream *inStream)
{
    return (new CSliderControl(inStream));
}
 
// ---------------------------------------------------------------------------
//      ¥ InitSlider
// ---------------------------------------------------------------------------
//  Initialization routine.
 
void
CSliderControl::InitSlider(void)
{
    if (mMacControlH) {
        SetSliderRefCon(GetControlReference(mMacControlH));
        SetControlReference(mMacControlH, (long)this);
    }
}
 
// ---------------------------------------------------------------------------
//      ¥ CSliderControl(const CSliderControl&)
// ---------------------------------------------------------------------------
//  Copy Constructor
 
CSliderControl::CSliderControl(
    const CSliderControl &inOriginal)
        : LStdControl(inOriginal)
{
    InitSlider();
}
 
 
// ---------------------------------------------------------------------------
//      ¥ CSliderControl(Int16)
// ---------------------------------------------------------------------------
//  Construct a SliderControl for a particular kind of Toolbox Control
//
//  NOTE: On entry, the current Port must be Window into which to
//  install the Control.
 
CSliderControl::CSliderControl( Int16 inControlKind) : LStdControl(inControlKind)
{
    InitSlider();
}
 
 
// ---------------------------------------------------------------------------
//      ¥ CSliderControl
// ---------------------------------------------------------------------------
//  Construct SliderControl from input parameters
 
CSliderControl::CSliderControl(
    const SPaneInfo &inPaneInfo,
    MessageT        inValueMessage,
    Int32           inValue,
    Int32           inMinValue,
    Int32           inMaxValue,
    Int16           inControlKind,
    ResIDT          inTextTraitsID,
    Str255          inTitle,
    Int32           inMacRefCon)
        : LStdControl(inPaneInfo, inValueMessage, inValue,
                        inMinValue, inMaxValue, inControlKind, 
                        inTextTraitsID, inTitle, inMacRefCon)
{
    InitSlider();
}
 
 
// ---------------------------------------------------------------------------
//      ¥ CSliderControl
// ---------------------------------------------------------------------------
//  Construct from input parameters and an existing ControlHandle
 
CSliderControl::CSliderControl(
    const SPaneInfo &inPaneInfo,
    MessageT        inValueMessage,
    Int32           inValue,
    Int32           inMinValue,
    Int32           inMaxValue,
    Int16           inControlKind,
    ResIDT          inTextTraitsID,
    ControlHandle   inMacControlH)
        : LStdControl(inPaneInfo, inValueMessage, inValue,
                        inMinValue, inMaxValue, inControlKind,
                        inTextTraitsID, inMacControlH)
{
    InitSlider();
}
 
 
// ---------------------------------------------------------------------------
//      ¥ CSliderControl(LStream*)
// ---------------------------------------------------------------------------
//  Construct SliderControl from a data stream
 
CSliderControl::CSliderControl(
    LStream *inStream)
        : LStdControl(inStream)
{
    InitSlider();
}
 
 
 
// ---------------------------------------------------------------------------
//      ¥ ~CSliderControl
// ---------------------------------------------------------------------------
//  Destructor
 
CSliderControl::~CSliderControl()
{
}
 
 
/*****************************************************************************/
 
 
 
void    
CSliderControl::AdjustSlider(Int32 val, Int32 max)
{
    GrafPtr oldPort = UQDGlobals::GetCurrentPort();
 
    SetPort((**mMacControlH).contrlOwner);
 
    /* Change the slider value and show the result. */
    SetMaxValue(max);
    SetValue(val);
    UpdateSlider();
 
    SetPort(oldPort);
}
 
 
 
 
 
void    
CSliderControl::UpdateSlider(short hiliteCap)
{
    Rect        ctlRect, workRect, sliderRect;
    Boolean     active;
    short       i, j;
    RgnHandle   origClipRgn, clipRgn, workRgn;
    CIconHandle icons[7];
 
    /* We use color icons here for the various slider parts.  This is so that we
    ** can take advantage of the depth of monitors.  I use icons here so that I
    ** can do a single plot of an icon if the delta of the thumb is -12 to 12.
    ** (The thumb is in the center of an icon, and 12 pixels above and below the
    ** icon, I have slider bar.  Use RedEdit to check it out.)  This technique
    ** gives a very smooth appearance when the thumb slides.  There is no flash.
    ** For deltas greater than +-12, I redraw the slider without the thumb, and
    ** then draw the thumb in the new position.  Since the thumb is moving a lot
    ** anyway, this doesn't show up as a flicker.  There is no overlap in the
    ** old and new positions for a big delta. */
 
    FocusDraw();
 
    ctlRect = (*mMacControlH)->contrlRect;
 
    for (i = 0; i < 7; i++) icons[i] = ReadCIcon(i + rSliderBase);
 
    origClipRgn = NewRgn();
    GetClip(origClipRgn);
 
    clipRgn = NewRgn();
 
    for (i = 0; i < 2; i++) {       /* Draw the arrow parts first. */
        j = i;
        if (hiliteCap == i) j += 5;
        workRect = ctlRect;
        if (!i)
            workRect.bottom = workRect.top + kCapHeight;
        else
            workRect.top = workRect.bottom - kCapHeight;
        RectRgn(clipRgn, &workRect);
        SetClip(clipRgn);
            /* Clip out the area outside the arrow part. */
        SliderDrawCIcon(icons[j], workRect.left, workRect.top);
            /* Draw the arrow part. */
    }
 
    ctlRect.top    += kCapHeight;
    ctlRect.bottom -= kCapHeight;
    RectRgn(clipRgn, &ctlRect);
    SetClip(clipRgn);
        /* Clip out everything except the slider bar area. */
 
    active  = true; /* ((*mMacControlH)->contrlOwner == FrontWindow()); */
    if ((*mMacControlH)->contrlHilite == 255) active = false;
    if (!(*mMacControlH)->contrlMax) active = false;
 
    if (active) {       /* If control active, draw the thumb. */
        sliderRect = CalcSliderRect();
        SliderDrawCIcon(icons[3],
                       sliderRect.left, sliderRect.top - kThumbOffset);
        workRgn = NewRgn();
        RectRgn(workRgn, &sliderRect);
        DiffRgn(clipRgn, workRgn, clipRgn);
        SetClip(clipRgn);
        DisposeRgn(workRgn);
            /* Now that the thumb is drawn, protect it by clipping it out. */
    }
 
    for (i = ctlRect.top; i < ctlRect.bottom; i += 32)
        SliderDrawCIcon(icons[2], ctlRect.left, i);
            /* Draw the slider bar portion. */
 
    /* It is now completely drawn.  Clean up and get out. */
 
    SetClip(origClipRgn);
    DisposeRgn(clipRgn);
    DisposeRgn(origClipRgn);
 
    for (i = 0; i < 7; i++) KillCIcon(icons[i]);
}
 
 
 
/*****************************************************************************/
 
 
 
void    
CSliderControl::SliderDrawCIcon(CIconHandle iconHndl, short hloc, short vloc)
{
    Rect    iconRect;
 
    iconRect.right  = (iconRect.left = hloc) + 32;
    iconRect.bottom = (iconRect.top  = vloc) + 32;
 
    DrawCIcon(iconHndl, iconRect);
}
 
 
 
/*****************************************************************************/
 
 
 
Rect    
CSliderControl::CalcSliderRect()
{
    Rect    ctlRect, sliderRect;
    short   max, val;
    long    calc;
 
    ctlRect = (*mMacControlH)->contrlRect;
    ctlRect.top    += kCapHeight;
    ctlRect.bottom -= kCapHeight;
    max = (*mMacControlH)->contrlMax;
    val = (*mMacControlH)->contrlValue;
 
    calc = ctlRect.bottom - ctlRect.top - kThumbHeight;
    calc *= val;
    if (max) calc /= max;
    sliderRect.top    = ctlRect.top + calc,
    sliderRect.left   = ctlRect.left;
    sliderRect.bottom = sliderRect.top + kThumbHeight;
    sliderRect.right  = ctlRect.right;
 
    return(sliderRect);
}
 
 
/*****************************************************************************/
 
 
 
short   
CSliderControl::CalcSliderValue(Point mouseLoc)
{
    Rect    ctlRect;
    long    max, val;
 
    ctlRect = (*mMacControlH)->contrlRect;
    ctlRect.top    += kCapHeight;
    ctlRect.bottom -= kCapHeight;
    max = (*mMacControlH)->contrlMax;
    
    val = (mouseLoc.v - ctlRect.top - kThumbHeight/2) * max;
    val /= ctlRect.bottom - ctlRect.top - kThumbHeight;
 
    if (val < 0)    val = 0;
    if (val > max)  val = max;
    
    return(val);
}
 
 
 
/*****************************************************************************/
 
void    
CSliderControl::TrackAndRun(Point *mouseLoc)
{
    EventRecord macEvent;
 
    // Give the movies some time (both in our app and outside)
    ::WaitNextEvent (0, &macEvent, 0, 0);
    
    // Repeaters get time after every event
    LPeriodical::DevoteTimeToRepeaters(macEvent);
 
//  SystemTask();
//  MoviesTask(0, 0);
 
    FocusDraw();
    *mouseLoc = macEvent.where;
    GlobalToLocal(mouseLoc);
}
 
/*****************************************************************************/
 
void    
CSliderControl::TrackSlider(Point origMouseLoc)
{
    CIconHandle icons[7];
    GrafPtr     oldPort = UQDGlobals::GetCurrentPort();
    Rect        ctlRect, sliderRange, slopRect, sliderRect, capRect, pgRect;
    RgnHandle   origClipRgn, clipRgn, workRgn;
    short       i, max, val, ovloc, voffset, vloc, delta, hiliteCap;
    Boolean     hiliteOn;
    long        origTick, calc;
    Point       lastMouseLoc, mouseLoc;
 
    /* Get everything we need set up. */
 
    FocusDraw();
    origTick = TickCount();
 
    for (i = 2; i < 5; i++) icons[i] = ReadCIcon(i + rSliderBase);
 
    SetPort((**mMacControlH).contrlOwner);
    ctlRect = (*mMacControlH)->contrlRect;
 
    origClipRgn = NewRgn();
    GetClip(origClipRgn);
 
    clipRgn = NewRgn();
    workRgn = NewRgn();
 
    max = (*mMacControlH)->contrlMax;
 
    pgRect = ctlRect;
    pgRect.top    += kCapHeight;
    pgRect.bottom -= kCapHeight - 1;
 
    /* That ought to be enough setup. */
 
    if (PtInRect(origMouseLoc, &pgRect)) {      /* If in the slide area... */
 
        sliderRect = CalcSliderRect();
        
        if (!PtInRect(origMouseLoc, &sliderRect)) { /* If they are not on the thumb... */
        
            /* Move the thumb to them... */
            (*mMacControlH)->contrlValue = val = CalcSliderValue(origMouseLoc);
            UpdateSlider();
            SliderAction(val);
            
            /* Update starting slider rect... */
            sliderRect = CalcSliderRect();
        }
    
        RectRgn(clipRgn, &pgRect);
        SetClip(clipRgn);
 
        sliderRange = pgRect;                   /* Calc area thumb can move. */
        sliderRange.bottom -= kThumbHeight + 1; /* Count height of thumb against range. */
 
        slopRect = sliderRange;                 /* Give the user some slop. */
        InsetRect(&slopRect, -20, -20);
 
        lastMouseLoc = origMouseLoc;
 
        voffset = lastMouseLoc.v - sliderRect.top;
        ovloc   = lastMouseLoc.v - voffset;
    
        while (StillDown()) {
 
            TrackAndRun(&mouseLoc);
            SetClip(clipRgn);
            if (!EqualPt(mouseLoc, lastMouseLoc)) {     /* The mouse has moved. */
 
                if (!PtInRect(mouseLoc, &slopRect)) mouseLoc = lastMouseLoc;
                    /* Outside slopRect, so snap back to the last good position. */
 
                vloc = mouseLoc.v - voffset;
                if (vloc < sliderRange.top)    vloc = sliderRange.top;
                if (vloc > sliderRange.bottom) vloc = sliderRange.bottom;
                delta = vloc - ovloc;
                    /* The delta tells us how much the thumb moved. */
 
                if (
                    (delta < -((32 - kThumbHeight) / 2)) ||
                    (delta >  ((32 - kThumbHeight) / 2))
                ) {
                    for (i = pgRect.top; i < pgRect.bottom; i += 32)
                        SliderDrawCIcon(icons[2], pgRect.left, i);
                            /* The thumb moved too far for a single plot to cover
                            ** up the old position, so clear the old thumb. */
                }
 
                calc  = max + 1;        /* Force below math to be with longs. */
                calc *= (vloc - sliderRange.top);
                    /* We use max + 1 because there is one more game
                    ** move position than moves in the game.  This is
                    ** because we can position in front of the first move,
                    ** as well as after the last move. */
                calc /= (sliderRange.bottom - sliderRange.top);
 
                val = calc;
                if (val > max) val = max;
                if (val < 0)   val = 0;
                if (delta)
                    SliderDrawCIcon(icons[3], pgRect.left, vloc - kThumbOffset);
                                        /* The thumb is now updated. */
 
                SetClip(origClipRgn);
                SliderAction(val);
                    /* We set the clipRgn back to the original so the board
                    ** can update.  (Pretty boring if it doesn't). */
 
                SetClip(clipRgn);
                FocusDraw();
                    /* Back to our normally scheduled program... */
 
                lastMouseLoc = mouseLoc;
                (*mMacControlH)->contrlValue = val;
                ovloc = vloc;
            }
        }
    }
    else {      /* We missed the thumb.  See if we hit an arrow part... */
        delta = hiliteOn = 0;
        capRect = ctlRect;
        capRect.bottom = ctlRect.top + kCapHeight;
 
        if (max > 1000)
            delta = max/1000;
        else delta = 1;
 
        if (PtInRect(origMouseLoc, &capRect)) {
            delta = -delta;
            hiliteCap = 0;
        }
        else {
            capRect = ctlRect;
            capRect.top = ctlRect.bottom - kCapHeight + 1;
            if (PtInRect(origMouseLoc, &capRect)) {
                hiliteCap = 1;
            }
        }
 
        if (delta) {    /* We hit an arrow, and there is a change to do... */
            do {
 
                TrackAndRun(&mouseLoc);
                SetClip(clipRgn);
                if (PtInRect(mouseLoc, &capRect)) {     /* Still in arrow... */
                    val = (*mMacControlH)->contrlValue + delta;
                    if (val < 0) val = 0;
                    if (val > max) val = max;
                    
                    if (val != (*mMacControlH)->contrlValue) {  /* Still scrolling... */
                        hiliteOn = true;
                        (*mMacControlH)->contrlValue = val;
                        UpdateSlider(hiliteCap);
                        SetClip(origClipRgn);
                        SliderAction(val);
                    }
                    else {      /* Scrolled as far as we can go, so unhilite arrow. */
                        if (hiliteOn) {
                            UpdateSlider();
                            hiliteOn = false;
                        }
                    }
                }
                else {      /* Outside arrow, so unhilite it. */
                    if (hiliteOn) {
                        UpdateSlider();
                        hiliteOn = false;
                    }
                }
 
                while ((StillDown()) && (origTick + 20 > TickCount())) {};
                    /* Don't go too fast. */
 
            } while (StillDown());
        }
    }
 
    SetClip(origClipRgn);
    DisposeRgn(workRgn);
    DisposeRgn(clipRgn);
    DisposeRgn(origClipRgn);
 
    UpdateSlider();
        /* Snap the slider to a move position.  The user may have let go of the
        ** slider at a position that doesn't map exactly to the game position. */
 
    for (i = 2; i < 5; i++) KillCIcon(icons[i]);
 
    SetPort(oldPort);
}
 
 
 
/*****************************************************************************/
 
 
 
void    
CSliderControl::SliderAction(short /*newPos*/)
{
}