/*
 * af_scroller.c
 *
 * Modified from ClassFree scoller.gadget
 * (Needs some tidying up, yet. . .)
 *
 * But it now supports relativity and can be put in borders :)
 *
 * $Id :$
 * $Log:$
 *
 */
#include "defs.h"

#include <intuition/intuition.h>
#include <intuition/classes.h>
#include <intuition/icclass.h>
#include <intuition/gadgetclass.h>
#include <intuition/imageclass.h>

#include <proto/dos.h>
#include <proto/intuition.h>
#include <clib/alib_protos.h>

#include "boopsi_misc.h"
#include "af_scroller.h"
#include "af_button.h"


#define     THISCLASS_ID            NULL
#define     SUPERCLASS_ID           GADGETCLASS
#define     SUPERCLASS_PTR          NULL


#define     GADGET_BORDER_FLAGS     ( GACT_RIGHTBORDER | GACT_LEFTBORDER | GACT_TOPBORDER | GACT_BOTTOMBORDER )

#define     PROP_ID      0L
#define     DECBTN_ID    1L
#define     INCBTN_ID    2L


/*
 * Private class types
 */

struct InstanceData
{
    Object *act;
    struct Gadget *prop,*decbtn,*incbtn;
    struct Image *dec_image;
    struct Image *inc_image;
};

/*
 * Private data
 */

static Class *class_base;

static ULONG SAVEDS dispatcher( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) Msg msg );

static ULONG new_method( Class *cl, Object *o, struct opSet *msg );
static ULONG dispose_method( Class *cl, Object *o );
static ULONG set_method( Class *cl, Object *o, struct opSet *msg );
static ULONG update_method( Class *cl, Object *o, Msg msg );
static ULONG hittest_method( Class *cl, Object *o, Msg msg );
static ULONG goactive_method( Class *cl, Object *o, Msg msg );
static ULONG handleinput_method( Class *cl, Object *o, Msg msg );
static ULONG goinactive_method( Class *cl, Object *o, Msg msg );
static ULONG render_method( Class *cl, Object *o, Msg msg );
static ULONG layout_method( Class *cl,Object *o, Msg msg );

static VOID position_gadgets( struct Gadget *scr, struct InstanceData *data, ULONG freedom );
static VOID set_gadget_position( struct Gadget *source, struct Gadget *dest, UWORD x_offset, UWORD y_offset );
static VOID set_gadget_size( struct Gadget *source, struct Gadget *dest, UWORD x_offset, UWORD y_offset );
static BOOL hit_test_gadget( struct Gadget *gad, struct Gadget *rel_gad, struct GadgetInfo *gi,  WORD x, WORD y );

/*
 * Public functions
 */

Class
*AFSCROLLER_GetClass(VOID)
{
    return class_base;
}



VOID
_INIT_7_AFSCROLLER_InitClass( VOID )
{
    if( class_base = MakeClass( THISCLASS_ID, SUPERCLASS_ID, SUPERCLASS_PTR, sizeof(struct InstanceData), 0L ) )
    {
        class_base->cl_Dispatcher.h_Entry = (ULONG (*)()) dispatcher;
        return;
    }
    else
        exit (20L);
}



VOID
_EXIT_7_AFSCROLLER_RemoveClass( VOID )
{
    if( class_base )
        FreeClass( class_base );
}



static ULONG SAVEDS
dispatcher( REG(a0) Class *cl, REG(a2) Object *o, REG(a1) Msg msg )
{
    switch(msg->MethodID)
    {
        case OM_NEW:         return( new_method( cl, o, (struct opSet *) msg ) );
        case OM_DISPOSE:     return( dispose_method( cl, o ) );
        case OM_SET:         return( set_method( cl, o, (struct opSet *) msg ) );
        case OM_UPDATE:      return( update_method( cl, o, msg ) );
        case GM_HITTEST:     return( hittest_method( cl, o, msg ) );
        case GM_GOACTIVE:
        case GM_HANDLEINPUT: return( handleinput_method( cl, o, msg ) );
        case GM_GOINACTIVE:  return( goinactive_method( cl, o, msg ) );
        case GM_RENDER:      return( render_method( cl, o, msg ) );
        case GM_LAYOUT:      return( layout_method( cl, o, msg ) );

        default:             return( DoSuperMethodA(cl,o,msg) );
    }
}


static ULONG
new_method( Class *cl, Object *o, struct opSet *msg )
{
    struct TagItem  *attrs = msg->ops_AttrList;
    struct Gadget   *scr;
    struct DrawInfo *dri;
    WORD   oldfill;


    if( scr = (struct Gadget *) DoSuperMethodA( cl, o, (Msg) msg ) )
    {
        if( !( dri = (struct DrawInfo *) GetTagData( GA_DrawInfo,NULL, attrs ) ) )
        {
            DoSuperMethod( (Class *)o, (Object *) scr, OM_DISPOSE );
        }
        else
        {
            ULONG size    = GetTagData( SYSIA_Size, SYSISIZE_MEDRES, attrs );
            ULONG fdm     = GetTagData( PGA_Freedom, FREEVERT, attrs );
            ULONG total   = GetTagData( PGA_Total, 1, attrs );
            ULONG visible = GetTagData( PGA_Visible, 1, attrs );
            ULONG top     = GetTagData( PGA_Top, 0, attrs );

            struct InstanceData *data = INST_DATA( cl ,scr );

            /* Change fill color */
            if( !( scr->Activation & GADGET_BORDER_FLAGS ) )
            {
                oldfill = dri->dri_Pens[ FILLPEN ];
                dri->dri_Pens[ FILLPEN ] = 0;
            }

            data->dec_image = (struct Image *) NewObject( NULL, SYSICLASS,
                                                    SYSIA_Which,    (fdm == FREEVERT) ? UPIMAGE : LEFTIMAGE,
                                                    SYSIA_DrawInfo, dri,
                                                    SYSIA_Size,     size,
                                                TAG_DONE );

            data->inc_image = (struct Image *) NewObject( NULL, SYSICLASS,
                                                    SYSIA_Which,    (fdm == FREEVERT) ? DOWNIMAGE : RIGHTIMAGE,
                                                    SYSIA_DrawInfo, dri,
                                                    SYSIA_Size,     size,
                                                TAG_DONE );

            if( !( scr->Activation & GADGET_BORDER_FLAGS ) )
                dri->dri_Pens[FILLPEN] = oldfill;

            if( data->dec_image && data->inc_image )
            {
                data->prop = NewObject( NULL,PROPGCLASS,
                                                   PGA_Freedom,    fdm,
                                                   PGA_NewLook,    TRUE,
                                                   PGA_Borderless, TRUE,
                                                   PGA_Total,      total,
                                                   PGA_Visible,    visible,
                                                   PGA_Top,        top,
                                                   ICA_TARGET,     scr,
                                                   GA_ID,          PROP_ID,
                                               TAG_DONE );

                data->decbtn = NewObject( AFBUTTON_GetClass(), NULL,
                                                   GA_Image,   data->dec_image,
                                                   GA_ID,      DECBTN_ID,
                                                   ICA_TARGET, scr,
                                               TAG_DONE );

                data->incbtn = NewObject( AFBUTTON_GetClass(), NULL,
                                                   GA_Image,   data->inc_image,
                                                   GA_ID,      INCBTN_ID,
                                                   ICA_TARGET, scr,
                                               TAG_DONE );


                if( data->prop && data->decbtn && data->incbtn )
                {

                    position_gadgets( scr, data, fdm );

                    return (ULONG)scr;
                }
            }
            DoMethod( (Object *) scr, OM_DISPOSE );
        }
    }
    return 0L;
}


static ULONG
dispose_method( Class *cl, Object *o )
{
    struct InstanceData *data = INST_DATA( cl, o );

    if( data->prop )      { DisposeObject( (Object *) data->prop   );    data->prop   =    NULL; }
    if( data->incbtn )    { DisposeObject( (Object *) data->incbtn );    data->incbtn =    NULL; }
    if( data->decbtn )    { DisposeObject( (Object *) data->decbtn );    data->decbtn =    NULL; }
    if( data->inc_image ) { DisposeObject( (Object *) data->inc_image ); data->inc_image = NULL; }
    if( data->dec_image ) { DisposeObject( (Object *) data->dec_image ); data->dec_image = NULL; }

    DoSuperMethod( cl, o, OM_DISPOSE );

    return 0L;
}

static ULONG
set_method( Class *cl, Object *o, struct opSet *msg )
{
  struct Gadget       *scr   = (struct Gadget *) o;
  struct InstanceData *data  = (struct InstanceData *)INST_DATA(cl,o);
  struct TagItem      *attrs = msg->ops_AttrList;
  Tag filter[] = { PGA_Top, PGA_Visible, PGA_Total, TAG_DONE };
  ULONG freedom;

  DoSuperMethodA( cl, o, (Msg) msg );

  FilterTagItems( attrs, filter, TAGFILTER_AND );
  DoMethodA( (Object *) data->prop, (Msg) msg );

  freedom = (data->decbtn->LeftEdge == data->incbtn->LeftEdge) ? FREEVERT : FREEHORIZ;

  position_gadgets( scr, data, freedom );

  return 0L;
}


static ULONG
update_method( Class *cl, Object *o, Msg msg )
{
  struct opUpdate *updin = (struct opUpdate *)msg;
  struct InstanceData *data = (struct InstanceData *)INST_DATA(cl,o);
  struct Gadget *scr = (struct Gadget *)o;
  struct opUpdate upd;
  struct TagItem tag[5];
  ULONG id,top,newtop;

    id = GetTagData(GA_ID,0,updin->opu_AttrList);

    top = QuickGetAttr( data->prop, PGA_Top );
/*  DoMethod((Object *)data->prop,OM_GET,PGA_Top,&top); */
  /* An update message is used for both OM_SET and OM_NOTIFY
     since the messages are so much alike. */
  upd.MethodID = OM_SET;
  upd.opu_AttrList = tag;
  upd.opu_GInfo = updin->opu_GInfo;
  upd.opu_Flags = updin->opu_Flags;
  /*
   Only enter this if its a button update and an interrim message
   Problem: First update message must emulate the gadgetdown message
   */
  if(id>0&&updin->opu_Flags&OPUF_INTERIM)
  {
      if( id==DECBTN_ID ) newtop = top-1;
      if( id==INCBTN_ID ) newtop = top+1;
      tag[0].ti_Tag = PGA_Top; tag[0].ti_Data = newtop;
      tag[1].ti_Tag = TAG_DONE;
      /* Prevent backwards overrun, forwards overrun is prevented
        by propgadget */
      if(newtop!=-1)  DoMethodA((Object *)data->prop,(Msg)&upd);

      /* Initial check to prevent notification messages in case of overrun */
      top = QuickGetAttr( data->prop, PGA_Top );
/*      DoMethod((Object *)data->prop,OM_GET,PGA_Top,&top); */
  }
  /* Only enter if interim message or message from propg */
  if( updin->opu_Flags&OPUF_INTERIM || id == PROP_ID )
  {
    /* Prevent notification in case of overrun */
    if( id != PROP_ID && top != newtop) return(0);
    /* Notify */
/*    upd.MethodID = OM_NOTIFY;
    tag[0].ti_Tag = PGA_Top; tag[0].ti_Data = top;
    tag[1].ti_Tag = GA_ID; tag[1].ti_Data = scr->GadgetID;
    tag[2].ti_Tag = TAG_DONE;
    DoMethodA(o,(Msg)&upd); */
    NotifyAttrChanges( o, updin->opu_GInfo, updin->opu_Flags, PGA_Top, top, GA_ID, scr->GadgetID, TAG_DONE );
  }
  return 0L;
}


static ULONG
hittest_method( Class *cl, Object *o, Msg msg )
{
    struct gpHitTest    *test = (struct gpHitTest *)msg;
    struct Gadget       *scr  = (struct Gadget *)o;
    struct InstanceData *data = (struct InstanceData *)INST_DATA(cl,o);
    WORD x = test->gpht_Mouse.X ,y = test->gpht_Mouse.Y;


    if( hit_test_gadget( data->prop, scr, test->gpht_GInfo, x, y ) )
    {
        data->act = (Object *)data->prop;
        return GMR_GADGETHIT;
    }

    if( hit_test_gadget( data->decbtn, scr, test->gpht_GInfo, x, y ) )
    {
        data->act = (Object *)data->decbtn;
        return GMR_GADGETHIT;
    }

    if( hit_test_gadget( data->incbtn, scr, test->gpht_GInfo, x, y ) )
    {
        data->act = (Object *)data->incbtn;
        return GMR_GADGETHIT;
    }

    return 0L;
}

static ULONG
goactive_method( Class *cl, Object *o, Msg msg )
{
  struct InstanceData *data = (struct InstanceData *)INST_DATA(cl,o);
  struct gpInput *in = (struct gpInput *)msg;
  struct Gadget *act = (struct Gadget *)data->act, *scr = (struct Gadget *)o;
  ULONG result;

  in->gpi_Mouse.X -= act->LeftEdge-scr->LeftEdge;
  in->gpi_Mouse.Y -= act->TopEdge-scr->TopEdge;
  result = DoMethodA(data->act,msg);

  return result;
}

static ULONG
handleinput_method( Class *cl, Object *o, Msg msg )
{
  struct InstanceData *data = (struct InstanceData *)INST_DATA(cl,o);
  struct gpInput *in = (struct gpInput *)msg;
  struct Gadget *act = (struct Gadget *)data->act, *scr = (struct Gadget *)o;
  ULONG result;

  in->gpi_Mouse.X -= act->LeftEdge-scr->LeftEdge;
  in->gpi_Mouse.Y -= act->TopEdge-scr->TopEdge;
  result = DoMethodA(data->act,msg);
  if(result)
  {
    if(scr->Activation&GACT_RELVERIFY)
    {
      result |= GMR_VERIFY;
      *(in->gpi_Termination) = QuickGetAttr( data->prop, PGA_Top );
    }
    else
    {
      result &= ~GMR_VERIFY;
    }
  }

  return result;
}


static ULONG
goinactive_method( Class *cl, Object *o, Msg msg )
{
  struct InstanceData *data = (struct InstanceData *) INST_DATA( cl, o );
  ULONG result;

  result = DoMethodA(data->act,msg);
  data->act = NULL;

  return result;
}


static ULONG
render_method( Class *cl, Object *o, Msg msg )
{
  struct gpRender *rend = (struct gpRender *)msg;
  struct InstanceData *data = INST_DATA( cl, o );

  DoMethodA( (Object *) data->prop,msg);
  DoMethodA( (Object *) data->decbtn,msg);
  DoMethodA( (Object *) data->incbtn,msg);

  return 1L;
}


static ULONG
layout_method( Class *cl,Object *o, Msg msg )
{
    struct gpLayout     *lay  = (struct gpLayout *)msg;
    struct GadgetInfo   *gi   = lay->gpl_GInfo;
    struct Gadget       *scr  = (struct Gadget *)o;
    struct InstanceData *data = (struct InstanceData *)INST_DATA(cl,o);

    if( scr->Flags & GFLG_RELWIDTH )
    {
        data->incbtn->LeftEdge = scr->LeftEdge + gi->gi_Domain.Width + scr->Width - data->incbtn->Width;
        data->decbtn->LeftEdge = data->incbtn->LeftEdge - data->decbtn->Width;
    }

    if( scr->Flags & GFLG_RELHEIGHT )
    {
        data->incbtn->TopEdge = scr->TopEdge + gi->gi_Domain.Height + scr->Height - data->incbtn->Height;
        data->decbtn->TopEdge = data->incbtn->TopEdge - data->decbtn->Height;
    }

    return 1L;
}



static VOID
position_gadgets( struct Gadget *scr, struct InstanceData *data, ULONG freedom )
{
    UWORD x, y;

    if( freedom == FREEVERT)
    {
        scr->Width = data->incbtn->Width;
        x = 0;
        y = data->decbtn->Height*2;
    }
    else
    {
        x = data->decbtn->Width*2;
        scr->Height = data->decbtn->Height;
        y = 0;
    }

    set_gadget_position( scr, data->prop, 4, 2 );
    set_gadget_size( scr, data->prop, x+8, y+4  );

    if(freedom == FREEVERT)
        y = scr->Height - data->incbtn->Height;
    else
        x = scr->Width  - data->incbtn->Width;

    set_gadget_position( scr, data->incbtn, x, y );

    if(freedom == FREEVERT)
        y -= data->decbtn->Height;
    else
        x -= data->decbtn->Width;

    set_gadget_position( scr, data->decbtn, x, y );

    return;
}

static VOID
set_gadget_position( struct Gadget *source, struct Gadget *dest, UWORD x_offset, UWORD y_offset )
{
    dest->LeftEdge    = source->LeftEdge + x_offset;
    dest->TopEdge     = source->TopEdge  + y_offset;
    dest->Flags      |= source->Flags & ( GFLG_RELRIGHT | GFLG_RELBOTTOM );
    dest->Activation |= source->Activation & GADGET_BORDER_FLAGS;

    return;
}

static VOID
set_gadget_size( struct Gadget *source, struct Gadget *dest, UWORD x_offset, UWORD y_offset )
{
    dest->Width  =  source->Width  - x_offset;
    dest->Height =  source->Height - y_offset;
    dest->Flags |= ( source->Flags & ( GFLG_RELWIDTH | GFLG_RELHEIGHT ));

    return;
}

static BOOL
hit_test_gadget( struct Gadget *gad, struct Gadget *rel_gad, struct GadgetInfo *gi,  WORD x, WORD y )
{
    UWORD gx = gad->LeftEdge - rel_gad->LeftEdge;
    UWORD gy = gad->TopEdge  - rel_gad->TopEdge;
    UWORD gw = (gad->Flags & GFLG_RELWIDTH)  ? gx + gi->gi_Domain.Width  + gad->Width  : gx + gad->Width;
    UWORD gh = (gad->Flags & GFLG_RELHEIGHT) ? gy + gi->gi_Domain.Height + gad->Height : gy + gad->Height;

    return ( x >= gx && x <= gw && y >= gy && y <= gh );
}
