;/* boopsi.c - Test for the gtdrag.library, 7.6.1999
;**
;** Copyright ©1999 pinc Software.
;** All rights reserved.
;**
sc boopsi.c lib:debug.lib nodbg nostkchk link ign=73 to=ram:boopsi
run ram:boopsi
quit
*/


/* This example shows how to create a custom (BOOPSI) gadget
** with support for drag&drop.
**
** It's a very simple example but you may use this code to produce
** one at your own.
**
** Although I tried hardly to not produce any errors, I cannot give
** any guaranties. Neither I nor pinc Software is liable for any
** problems you might have with it.
** The usage of this example is on your own risk.
*/


#define INTUI_V36_NAMES_ONLY

#include <exec/memory.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <intuition/icclass.h>
#include <intuition/gadgetclass.h>
#include <intuition/imageclass.h>
#include <intuition/sghooks.h>
#include <graphics/gfx.h>
#include <libraries/gadtools.h>
#include <libraries/gtdrag.h>

#include <clib/console_protos.h>
#include <pragmas/console_pragmas.h>
#include <clib/gtdrag_protos.h>
#include <pragmas/gtdrag_pragmas.h>
#include <proto/exec.h>
#include <proto/gadtools.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/utility.h>

#include <strings.h>
#include <math.h>

#define reg(x) register __ ## x
#define PUBLIC __saveds __asm
#define bug kprintf
#define foreach(l,v) for(v = (APTR)((struct List *)l)->lh_Head;((struct Node *)v)->ln_Succ;v = (APTR)((struct Node *)v)->ln_Succ)
#define IsBoopsiGadget(gad) ((gad->GadgetType & GTYP_GTYPEMASK) == GTYP_CUSTOMGADGET)

void kprintf(STRPTR,...);

void processMsg(void);

struct Library *GTDragBase;
struct Window *win;
Class  *simplegclass;
long   fontheight;
struct List list;


/********************************** Simple-Gadget **********************************/

struct SimpleGData
{
  struct MinList *sd_List;  /* the list to display */
  ULONG  sd_Items;          /* number of items to be displayed */
  ULONG  sd_Selected;       /* currently selected item */
  ULONG  sd_Range;          /* width/height of an item */
  ULONG  sd_CurrentDrag;    /* currently dragged item */
  BOOL   sd_Direction;      /* horizontal or vertical direction? */
  struct Hook *sd_Hook;     /* hook to render the items */
};

#define SDD_VERTICAL 0
#define SDD_HORIZONTAL 1

#define SDA_List  (TAG_USER | 0x10000)
#define SDA_Items (TAG_USER | 0x10001)
#define SDA_Direction (TAG_USER | 0x10002)   /* one of the SDD_### */


/** draws the simple gadget */

void DrawSimpleGadget(struct RastPort *rp,struct Gadget *gad,struct GadgetInfo *gi,struct SimpleGData *sd,BOOL all)
{
  struct Node *ln;
  struct LVDrawMsg lvdm;
  UWORD  *pens = gi->gi_DrInfo->dri_Pens;
  long   i,width,height;

  /*** draw gadget frame ***/

  if (all)
  {
    struct Image *im;

    if (im = NewObject(NULL,"frameiclass",IA_Width,     gad->Width,
                                          IA_Height,    gad->Height,
                                          IA_FrameType, FRAME_BUTTON,
                                          TAG_END))
    {
      DrawImage(rp,im,gad->LeftEdge,gad->TopEdge);
      DisposeObject(im);
    }
  }
  width = 0;  height = 0;

  /*** render gadet contents, without any optimisations ***/

  if (sd->sd_Direction == SDD_HORIZONTAL)
    width = sd->sd_Range;
  else
    height = sd->sd_Range;

  /* initialize listview callback render hook message */

  lvdm.lvdm_MethodID = LV_DRAW;
  lvdm.lvdm_RastPort = rp;
  lvdm.lvdm_DrawInfo = gi->gi_DrInfo;
  lvdm.lvdm_Bounds.MinX = gad->LeftEdge+4;
  lvdm.lvdm_Bounds.MinY = gad->TopEdge+4;
  lvdm.lvdm_Bounds.MaxX = lvdm.lvdm_Bounds.MinX+(width ? width-1 : gad->Width-9);
  lvdm.lvdm_Bounds.MaxY = lvdm.lvdm_Bounds.MinY+(height ? height-1 : gad->Height-9);
  lvdm.lvdm_State = LVR_NORMAL;

  if (!sd->sd_List)
    return;

  /* display the whole list */

  SetABPenDrMd(rp,pens[TEXTPEN],pens[FILLPEN],JAM2);
  for(i = 0,ln = (APTR)sd->sd_List->mlh_Head;ln->ln_Succ;ln = ln->ln_Succ,i++)
  {
    if (i >= sd->sd_Items)
      break;

    if (sd->sd_Selected == i)
      lvdm.lvdm_State = LVR_SELECTED;

    CallHookPkt(sd->sd_Hook,ln,&lvdm);

    if (sd->sd_Direction == SDD_HORIZONTAL)
    {
      lvdm.lvdm_Bounds.MinX += width;
      lvdm.lvdm_Bounds.MaxX += width;
    }
    else
    {
      lvdm.lvdm_Bounds.MinY += height;
      lvdm.lvdm_Bounds.MaxY += height;
    }
    lvdm.lvdm_State = LVR_NORMAL;
  }
}

/** dispatch the OM_NEW method */

BOOL DispatchSimpleNew(struct opSet *ops,Object *o,struct SimpleGData *sd)
{
  SetAttrs(o,GA_Immediate,TRUE,TAG_END);
  sd->sd_List = (APTR)GetTagData(SDA_List,NULL,ops->ops_AttrList);
  sd->sd_Items = GetTagData(SDA_Items,1,ops->ops_AttrList);
  sd->sd_Direction = GetTagData(SDA_Direction,SDD_VERTICAL,ops->ops_AttrList);
  sd->sd_Hook = GTD_GetHook(GTDH_IMAGE);
  sd->sd_Selected = sd->sd_CurrentDrag = ~0L;

  if (!sd->sd_Items)
    return(FALSE);

  sd->sd_Range = (sd->sd_Direction == SDD_HORIZONTAL ? ((struct Gadget *)o)->Width-8 : ((struct Gadget *)o)->Height-8)/sd->sd_Items;
  return(TRUE);
}

/** dispatch all methods.
 *
 *  To mark the drag&drop related methods, these are separated
 *  from the other and, furthermore, they are commented.
 */

ULONG PUBLIC DispatchSimpleGadget(reg (a0) Class *cl,reg (a2) Object *o,reg (a1) Msg msg)
{
  struct SimpleGData *sd;
  ULONG  retval = 0;

  sd = INST_DATA(cl,o);

  switch(msg->MethodID)
  {
    case OM_NEW:
      if (retval = DoSuperMethodA(cl,o,msg))
      {
        sd = INST_DATA(cl,retval);

        if (!DispatchSimpleNew((struct opSet *)msg,(Object *)retval,sd))
        {
          DoSuperMethod(cl,(Object *)retval,OM_DISPOSE);
          retval = NULL;
        }
      }
      break;
    case OM_DISPOSE:
      retval = DoSuperMethodA(cl,o,msg);
      break;
    case OM_SET:
    case OM_UPDATE:
    case OM_NOTIFY:
      DoSuperMethodA(cl,o,msg);
      {
        struct TagItem *tstate,*ti;

        tstate = ((struct opSet *)msg)->ops_AttrList;
        while(ti = NextTagItem(&tstate))
        {
          switch(ti->ti_Tag)
          {
            case SDA_List:
            {
              struct RastPort *rp = ObtainGIRPort(((struct opUpdate *)msg)->opu_GInfo);

              sd->sd_List = (APTR)ti->ti_Data;

              DrawSimpleGadget(rp,(struct Gadget *)o,NULL,sd,FALSE);
              ReleaseGIRPort(rp);
              break;
            }
          }
        }
      }
      break;
    case OM_GET:
      if (((struct opGet *)msg)->opg_AttrID == SDA_List)
      {
        *((struct opGet *)msg)->opg_Storage = (ULONG)sd->sd_List;
        retval = TRUE;
      }
      else
        retval = DoSuperMethodA(cl,o,msg);
      break;
    case GM_RENDER:
      {
        struct gpRender *gpr = (struct gpRender *)msg;

        DrawSimpleGadget(gpr->gpr_RPort,(struct Gadget *)o,gpr->gpr_GInfo,sd,TRUE);
      }
      break;
    case GM_HITTEST:
      retval = GMR_GADGETHIT;
      break;

/*** here come drag&drop related method dispatching ***/

    case GM_OBJECTDRAG:             // do we want the object? is some further rendering needed?
    {
      struct gpObjectDrag *gpod = (struct gpObjectDrag *)msg;

      if ((struct Gadget *)o == gpod->gpod_Source)  // get current drag position
      {
        ULONG current;

        if (sd->sd_Direction == SDD_HORIZONTAL)
          current = (gpod->gpod_Mouse.X-4+sd->sd_Range/2)/sd->sd_Range;
        else
          current = (gpod->gpod_Mouse.Y-4+sd->sd_Range/2)/sd->sd_Range;

        retval = GMR_ACCEPTOBJECT | (current != sd->sd_CurrentDrag ? GMR_UPDATE : 0);
        sd->sd_CurrentDrag = current;
      }
      else
      {
        struct Gadget *gad = gpod->gpod_Source;

        if (gad && IsBoopsiGadget(gad) && OCLASS(gad) == cl) // has the source the same class then we have? (simple, no check for super-classes...)
          retval = GMR_ACCEPTOBJECT;                         // we want it all
        else
          retval = GMR_REJECTOBJECT;

        retval |= GMR_FINAL;                // do not send this message again
      }
      break;
    }
    case GM_OBJECTDROP:
      /* we do not process this method, gtdrag will send
      ** the DropMessage to the window's MsgPort if we
      ** return NULL, otherwise it will be catched by us.
      */
      break;
    case GM_RENDERDRAG:
    {
      struct gpRenderDrag *gprd = (struct gpRenderDrag *)msg;

      /* GRENDER_HIGHLIGHT is the only one implemented here; let's
      ** gtdrag do the dirty stuff.
      */

      if (gprd->gprd_Mode == GRENDER_HIGHLIGHT && sd->sd_CurrentDrag != ~0L)
      {
        struct RastPort *rp = gprd->gprd_RPort;
        struct Gadget *gad = (APTR)o;
        int    current = sd->sd_CurrentDrag * sd->sd_Range;

        rp->linpatcnt = 15;  rp->Flags |= FRST_DOT;
        rp->LinePtrn = 0x0f0f;
        SetABPenDrMd(rp,1,2,JAM2);    // use gprd_GInfo->gi_DrInfo->dri_Pens[] instead

        if (sd->sd_Direction == SDD_HORIZONTAL)
        {
          Move(rp,gad->LeftEdge+current+4,gad->TopEdge+2);
          Draw(rp,gad->LeftEdge+current+4,gad->TopEdge+gad->Height-3);
        }
        else
        {
          Move(rp,gad->LeftEdge+4,gad->TopEdge+3+current);
          Draw(rp,gad->LeftEdge+gad->Width-4,gad->TopEdge+3+current);
        }
        retval = TRUE;
      }
      else if (gprd->gprd_Mode == GRENDER_DELETE)
        sd->sd_CurrentDrag = ~0L;                 // reset temporary drag position
      break;
    }
    case GM_GOACTIVE:
    case GM_HANDLEINPUT:
    {
      struct gpInput *gpi = (struct gpInput *)msg;
      struct InputEvent *ie = gpi->gpi_IEvent;
      long x = gpi->gpi_Mouse.X, y = gpi->gpi_Mouse.Y;

      /* test if input is for us or not */

      if ((retval = GTD_HandleInput((struct Gadget *)o,gpi)) != GMR_HANDLEYOURSELF)
        break;
      retval = GMR_MEACTIVE;

      if (ie->ie_Class == IECLASS_RAWMOUSE)
      {
        if (ie->ie_Code == IECODE_RBUTTON)
          retval = GMR_REUSE;
        else if (ie->ie_Code == (IECODE_LBUTTON | IECODE_UP_PREFIX))
        {
          retval = GMR_REUSE;
        }
        else if (ie->ie_Code == IECODE_LBUTTON)
        {
          struct RastPort *rp = ObtainGIRPort(gpi->gpi_GInfo);
          struct Gadget *gad = (APTR)o;

          if (sd->sd_Direction == SDD_HORIZONTAL)
            sd->sd_Selected = (x-4)/sd->sd_Range;
          else
            sd->sd_Selected = (y-4)/sd->sd_Range;

          DrawSimpleGadget(rp,gad,gpi->gpi_GInfo,sd,FALSE);
          ReleaseGIRPort(rp);
        }
        else if (ie->ie_Code == IECODE_NOBUTTON && GTD_PrepareDrag((struct Gadget *)o,gpi))   // mouse movement
        {
          struct Gadget *gad = (struct Gadget *)o;
          int    i = sd->sd_Selected;
          struct Node *ln;

          foreach(sd->sd_List,ln)  // get the current node
          {
            if (--i)
              break;
          }
          GTD_SetAttrs(o,GTDA_Object,     ln,             // object to be dragged
                         GTDA_RenderHook, sd->sd_Hook,    // use this to render the object
                         GTDA_Width,      sd->sd_Direction == SDD_HORIZONTAL ? sd->sd_Range : gad->Width,
                         GTDA_Height,     sd->sd_Direction == SDD_VERTICAL ? sd->sd_Range : gad->Height,
                         GTDA_Type,       ODT_NODE,       // it's basically a simple node
                         GTDA_Same,       TRUE,           // I want to drag&drop my objects over me
                         TAG_END);
          GTD_BeginDrag(gad,gpi);
        }
      }
      break;
    }
    case GM_GOINACTIVE:
    {
      struct gpInput *gpi = (struct gpInput *)msg;
      struct RastPort *rp = ObtainGIRPort(gpi->gpi_GInfo);

      GTD_StopDrag((struct Gadget *)o); // used to break the drag of an object, could also be used in other circumstances
      sd->sd_Selected = ~0L;
      DrawSimpleGadget(rp,(struct Gadget *)o,gpi->gpi_GInfo,sd,FALSE);
      ReleaseGIRPort(rp);
      break;
    }
    default:
      retval = DoSuperMethodA(cl,o,msg);
  }
  return(retval);
}


/********************************** main programme **********************************/


void processMsg(void)
{
  struct IntuiMessage *imsg;
  ULONG  class;
  UWORD  code;
  BOOL   ende = FALSE;

  while(!ende)                /* simple gtdrag message loop */
  {
    imsg = (struct IntuiMessage *)WaitPort(win->UserPort);
    while (imsg = (struct IntuiMessage *)GTD_GetIMsg(win->UserPort))
    {
      class = imsg->Class;
      code = imsg->Code;

      switch(class)
      {
        case IDCMP_CLOSEWINDOW:
          ende = TRUE;
          break;
      }
      GTD_ReplyIMsg(imsg);
    }
  }
}

/** frees the list */

void clean(void)
{
  struct Node *ln;

  while(ln = RemHead(&list))
    FreeMem(ln,sizeof(struct ImageNode));
}

const STRPTR text[] = {"abc","def","ghi","jkl","mno","pqr",NULL};

/** initializes the list to be used by the SimpleGadget */

void init(void)
{
  struct ImageNode *in;
  int    i;

  NewList(&list);

  for(i = 0;text[i];i++)
  {
    if (in = AllocMem(sizeof(struct ImageNode),MEMF_CLEAR))
    {
      in->in_Name = text[i];
      AddTail(&list,(APTR)in);
    }
  }
}

/** initializes all stuff is needed */

void main(void)
{
  struct Screen *scr;
  struct DrawInfo *dri;
  void   *vi;
  struct Gadget *hgad,*vgad;

  if (!(simplegclass = MakeClass(NULL,"gadgetclass",NULL,sizeof(struct SimpleGData),0)))
    return;

  simplegclass->cl_Dispatcher.h_Entry = (ULONG (*)())DispatchSimpleGadget;

  if (GTDragBase = OpenLibrary("gtdrag.library",3))
  {
    if (GTD_AddApp("boopsitest",GTDA_NewStyle,TRUE,TAG_END))
    {
      if (scr = LockPubScreen(NULL))
      {
        fontheight = scr->Font->ta_YSize;
        dri = GetScreenDrawInfo(scr);
        init();
        if (vi = GetVisualInfo(scr,TAG_END))
        {
          if (hgad = NewObject(simplegclass,NULL,GA_Left,       scr->WBorLeft,
                                                 GA_Top,        fontheight+scr->WBorTop+1,
                                                 GA_Width,      300-scr->WBorRight-scr->WBorLeft,
                                                 GA_Height,     fontheight+9,
                                                 SDA_List,      &list,
                                                 SDA_Items,     5,
                                                 SDA_Direction, SDD_HORIZONTAL,
                                                 TAG_END))
          {
            int y;     /* fill the stack with some useless stuff */

            if (vgad = NewObject(simplegclass,NULL,GA_Left,       scr->WBorLeft,
                                                   GA_Top,        y = 2*fontheight+scr->WBorTop+10,
                                                   GA_Width,      40,
                                                   GA_Height,     200-scr->WBorBottom-y,
                                                   GA_Previous,   hgad,
                                                   SDA_List,      &list,
                                                   SDA_Items,     5,
                                                   SDA_Direction, SDD_VERTICAL,
                                                   TAG_END))
            {
              if (win = OpenWindowTags(NULL,WA_Title,     "gtdrag - Boopsi-Example",
                                            WA_Width,     300,
                                            WA_Height,    200,
                                            WA_Flags,     WFLG_CLOSEGADGET | WFLG_DRAGBAR | WFLG_DEPTHGADGET | WFLG_ACTIVATE,
                                            WA_IDCMP,     IDCMP_CLOSEWINDOW | IDCMP_GADGETUP | IDCMP_IDCMPUPDATE | DRAGIDCMP,
                                            WA_Gadgets,   hgad,
                                            WA_PubScreen, scr,
                                            TAG_END))
              {
                processMsg();
                RemoveGList(win,hgad,-1);
                CloseWindow(win);
              }
              DisposeObject(vgad);
            }
            DisposeObject(hgad);
          }
          FreeVisualInfo(vi);
        }
        clean();
        FreeScreenDrawInfo(scr,dri);
        UnlockPubScreen(NULL,scr);
      }
      GTD_RemoveApp();
    }
    CloseLibrary(GTDragBase);
  }
  FreeClass(simplegclass);
}


