MODULE Class2 ;

(* IMPORTANT: RangeChk mußt be switched off, otherwise you'll get an error
** when entering the Colorwheel-Page!
*)

(*$ RangeChk := FALSE *)

FROM SYSTEM     IMPORT  TAG, ADR, ADDRESS, LONGSET, CAST, SETREG, REG ;
FROM AmigaLib   IMPORT  DoSuperMethodA ;
FROM ExecL      IMPORT  Wait ;

IMPORT
        R,
        gd  : GraphicsD,
        gl  : GraphicsL,
        id  : IntuitionD,
        il  : IntuitionL,
        m   : MuiD,
        mc  : MuiClasses,
        ml  : MuiL,
        mm  : MuiMacros,
        ms  : MuiSupport,
        ud  : UtilityD,
        ul  : UtilityL ;

(***************************************************************************)
(* Here is the beginning of our simple new class...                        *)
(***************************************************************************)

(*
** This class is the same as within Class1.c except that it features
** a pen attribute.
*)

TYPE
  LongcardPtr = POINTER TO LONGCARD ;

  Data = RECORD
           penspec   : m.mPenSpec ;
           pen       : ADDRESS;
           penchange : BOOLEAN ;
         END (* RECORD *) ;

CONST
  MyAttrPen = LONGCARD(8022H) ; (* tag value for the new attribute.            *)

(*/// "mNew(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRESS" *)

PROCEDURE mNew(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRESS ;

VAR
  data  : POINTER TO Data ;
  tag,
  tags  : ud.TagItemPtr ;

BEGIN
  obj := DoSuperMethodA(cl, obj, msg) ;
  IF obj = NIL THEN RETURN NIL END ;

  data := mc.InstData(cl, obj) ;

  (* parse initial taglist *)

  tags := msg^.attrList ;
  tag  := ul.NextTagItem(tags) ;
  WHILE tag # NIL DO
    CASE tag^.tag OF
    | MyAttrPen : IF tag^.data # 0 THEN
                    data^.penspec := CAST(m.mPenSpecPtr, tag^.data)^ ;
                  END (* IF *) ;
    ELSE
    END (* CASE *) ;
    tag := ul.NextTagItem(tags) ;
  END (* WHILE *) ;

  RETURN obj ;
END mNew ;

(*\\\*)
(*/// "mDispose(cl : id.IClassPtr; obj : id.ObjectPtr; msg : ADDRESS) : ADDRESS" *)

PROCEDURE mDispose(cl : id.IClassPtr; obj : id.ObjectPtr; msg : ADDRESS) : ADDRESS ;

BEGIN
  (* OM_NEW didnt allocates something, just do nothing here... *)
  RETURN DoSuperMethodA(cl, obj, msg) ;
END mDispose ;

(*\\\*)

(*
** OM_SET method, we need to see if someone changed the penspec attribute.
*)

(*/// "mSet(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRESS" *)

PROCEDURE mSet(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRESS ;

VAR
  data : POINTER TO Data ;
  tag,
  tags : ud.TagItemPtr ;

BEGIN
  data := mc.InstData(cl, obj) ;

  tags := msg^.attrList ;
  tag  := ul.NextTagItem(tags) ;
  WHILE tag # NIL DO
    CASE tag^.tag OF
    | MyAttrPen : IF tag^.data # 0 THEN
                    data^.penspec   := CAST(m.mPenSpecPtr, tag^.data)^ ;
                    data^.penchange := TRUE ;
                    IF ml.moRedraw(obj, CAST(LONGCARD, mc.MADFlagSet{mc.drawObject})) # NIL THEN END ;
                  END (* IF *) ;
    ELSE
    END (* CASE *) ;
    tag := ul.NextTagItem(tags) ;
  END (* WHILE *) ;

  RETURN DoSuperMethodA(cl, obj, msg) ;
END mSet ;

(*\\\*)

(*
** OM_GET method, see if someone wants to read the color.
*)

(*/// "mGet(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpGetPtr) : ADDRES" *)

PROCEDURE mGet(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpGetPtr) : ADDRESS;

VAR
  data  : POINTER TO Data ;
  store : LongcardPtr ;

BEGIN
  data := mc.InstData(cl, obj) ;
  store := CAST(LongcardPtr, msg^.storage) ;

  CASE msg^.attrID OF
  | MyAttrPen : store^ := ADR(data^.penspec) ;
                RETURN LONGCARD(TRUE) ;
  ELSE
    RETURN DoSuperMethodA(cl, obj, msg) ;
  END (* CASE *) ;
END mGet ;

(*\\\*)
(*/// "mSetup(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRES" *)

PROCEDURE mSetup(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRESS;

VAR
  data : POINTER TO Data ;
  test : ADDRESS ;

BEGIN
  data := mc.InstData(cl, obj) ;

  IF DoSuperMethodA(cl, obj, msg) = NIL THEN
    RETURN LONGCARD(FALSE) ;
  END (* IF *) ;

  test := mc.muiRenderInfo(obj) ;
  data^.pen := ml.moObtainPen(mc.muiRenderInfo(obj), ADR(data^.penspec)) ;

  RETURN LONGCARD(TRUE) ;
END mSetup ;     

(*\\\*)
(*/// "mCleanup(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRES" *)

PROCEDURE mCleanup(cl : id.IClassPtr; obj : id.ObjectPtr; msg : id.OpSetPtr) : ADDRESS;

VAR
  data :POINTER TO Data ;

BEGIN
  data := mc.InstData(cl, obj) ;
  ml.moReleasePen(mc.muiRenderInfo(obj), data^.pen) ;
  RETURN DoSuperMethodA(cl, obj, msg) ;
END mCleanup ;

(*\\\*)
(*/// "mAskMinMax(cl : id.IClassPtr; obj : id.ObjectPtr; msg : mc.mpAskMinMaxPtr) : ADDRES" *)

PROCEDURE mAskMinMax(cl : id.IClassPtr; obj : id.ObjectPtr; msg : mc.mpAskMinMaxPtr) : ADDRESS;

BEGIN
  (*
  ** let our superclass first fill in what it thinks about sizes.
  ** this will e.g. add the size of frame and inner spacing.
  *)

  IF DoSuperMethodA(cl, obj, msg) # NIL THEN END ;

  (*
  ** now add the values specific to our object. note that we
  ** indeed need to *add* these values, not just set them!
  *)

  INC(msg^.MinMaxInfo^.MinWidth, 100) ;
  INC(msg^.MinMaxInfo^.DefWidth, 120) ;
  INC(msg^.MinMaxInfo^.MaxWidth, 500) ;

  INC(msg^.MinMaxInfo^.MinHeight, 40) ;
  INC(msg^.MinMaxInfo^.DefHeight, 90) ;
  INC(msg^.MinMaxInfo^.MaxHeight, 300) ;

  RETURN NIL ;
END mAskMinMax ;

(*\\\*)

(*
** Draw method is called whenever MUI feels we should render
** our object. This usually happens after layout is finished
** or when we need to refresh in a simplerefresh window.
** Note: You may only render within the rectangle
**       _mleft(obj), _mtop(obj), _mwidth(obj), _mheight(obj).
*)

(*/// "mDraw(cl : id.IClassPtr; obj : id.ObjectPtr; msg : mc.mpDraw) : ADDRES" *)

PROCEDURE mDraw(cl : id.IClassPtr; obj : id.ObjectPtr; msg : mc.mpDrawPtr) : ADDRESS;

VAR
  data : POINTER TO Data ;
  i    : INTEGER ;

BEGIN
  data := mc.InstData(cl, obj) ;

  (*
  ** let our superclass draw itself first, area class would
  ** e.g. draw the frame and clear the whole region. What
  ** it does exactly depends on msg->flags.
  *)

  IF DoSuperMethodA(cl, obj, msg) # NIL THEN END ;

  (*
  ** if MADF_DRAWOBJECT isn't set, we shouldn't draw anything.
  ** MUI just wanted to update the frame or something like that.
  *)

  IF NOT (mc.drawObject IN msg^.flags) THEN RETURN NIL END ;

  (*
  ** test if someone changed our pen
  *)

  IF data^.penchange THEN
    data^.penchange := FALSE ;
    ml.moReleasePen(mc.muiRenderInfo(obj), data^.pen) ;
    data^.pen := ml.moObtainPen(mc.muiRenderInfo(obj), ADR(data^.penspec)) ;
  END (* IF *) ;

  (*
  ** ok, everything ready to render...
  ** Note that we *must* use the MUIPEN() macro before actually
  ** using pens from MUI_ObtainPen() in rendering calls.
  *)

  gl.SetAPen(mc.OBJ_rp(obj),mc.muiPen(data^.pen));

  FOR i := mc.OBJ_mleft(obj) TO mc.OBJ_mright(obj) BY 5 DO
    gl.Move(mc.OBJ_rp(obj),mc.OBJ_mleft(obj),mc.OBJ_mtop(obj));
    gl.Draw(mc.OBJ_rp(obj),i,mc.OBJ_mbottom(obj));
    gl.Move(mc.OBJ_rp(obj),mc.OBJ_mright(obj),mc.OBJ_mtop(obj));
    gl.Draw(mc.OBJ_rp(obj),i,mc.OBJ_mbottom(obj));
  END (* FOR *) ;

  RETURN NIL ;
END mDraw ;

(*\\\*)

(*
** Here comes the dispatcher for our custom class. We only need to
** care about MUIM_AskMinMax and MUIM_Draw in this simple case.
** Unknown/unused methods are passed to the superclass immediately.
*)

(*/// "MyDispatcher(cl : id.IClassPtr; obj : ADDRESS; msg : ADDRESS) : ADDRESS" *)

PROCEDURE MyDispatcher(cl : id.IClassPtr; obj : ADDRESS; msg : ADDRESS) : ADDRESS ;

VAR
  mid : LONGCARD ;

BEGIN
  mid := CAST(id.Msg, msg)^.methodID ;

     IF mid = id.omNEW      THEN RETURN mNew(cl, obj, msg)
  ELSIF mid = id.omDISPOSE  THEN RETURN mDispose(cl, obj, msg)
  ELSIF mid = id.omSET      THEN RETURN mSet(cl, obj, msg)
  ELSIF mid = id.omGET      THEN RETURN mGet(cl, obj, msg)
  ELSIF mid = m.mmAskMinMax THEN RETURN mAskMinMax(cl, obj, msg)
  ELSIF mid = m.mmSetup     THEN RETURN mSetup(cl, obj, msg)
  ELSIF mid = m.mmCleanup   THEN RETURN mCleanup(cl, obj, msg)
  ELSIF mid = m.mmDraw      THEN RETURN mDraw(cl, obj, msg)
  ELSE
    RETURN DoSuperMethodA(cl, obj, msg)
  END (* CASE *) ;
END MyDispatcher ;

(*\\\*)

(***************************************************************************)
(* Thats all there is about it. Now lets see how things are used...        *)
(***************************************************************************)

VAR
  app,
  window,
  grp,
  myObj,
  pen      : id.ObjectPtr ;
  mcc      : mc.mCustomClassPtr ;
  signals  : LONGSET ;
  running  := BOOLEAN{TRUE} ;
  startpen : m.mPenSpecPtr ;
  NULL     :=ADDRESS{NIL};

  tags     : ARRAY [0..31] OF LONGINT ;

BEGIN
  (* Create the new custom class with a call to MUI_CreateCustomClass(). *)
  (* Caution: This function returns not a struct IClass, but a           *)
  (* struct MUI_CustomClass which contains a struct IClass to be         *)
  (* used with NewObject() calls.                                        *)
  (* Note well: MUI creates the dispatcher hook for you, you may         *)
  (* *not* use its h_Data field! If you need custom data, use the        *)
  (* cl_UserData of the IClass structure!                                *)

  IF ml.muiMasterVersion < 11 THEN ms.fail(NULL, "You need MUI 3 to run this demo.") END;

  mcc := ml.moCreateCustomClass(NIL, ADR(m.mcArea), NIL, SIZE(Data), ADR(MyDispatcher)) ;
  IF mcc = NIL THEN ms.fail(NULL, "Could not create custom class.") END ;

  mc.MakeDispatcher(MyDispatcher, mcc^.class) ;

  pen := mm.PenadjustObject(TAG(tags, m.maPenadjustDoSyspens, FALSE, ud.tagDone));
  myObj := il.NewObjectA(mcc^.class, NIL, TAG(tags, m.maFrame,      m.mvFrameText,
                                                    m.maBackground, m.miBACKGROUND,
                                              ud.tagDone)) ;

  grp := mm.GroupObject(TAG(tags, m.maGroupHoriz,  TRUE,
                                  mm.Child,        myObj,
                                  mm.Child,        pen,
                            ud.tagDone)) ;

  window := mm.WindowObject(TAG(tags, m.maWindowTitle, ADR("Another Custom Class"),
                                      m.maWindowID,    mm.MakeID("CLS2"),
                                      mm.WindowContents, grp,
                                ud.tagDone)) ;

  app := mm.ApplicationObject(TAG(tags, m.maApplicationTitle,       ADR("Class2-M2"),
                                        m.maApplicationVersion,     ADR("$VER: Class2-M2 11.1 (21.9.95)"),
                                        m.maApplicationCopyright,   ADR("©1995, Olaf Peters, Stefan Stuntz"),
                                        m.maApplicationAuthor,      ADR("Olaf Peters, Stefan Stuntz"),
                                        m.maApplicationDescription, ADR("Demonstrate the use of custom classes."),
                                        m.maApplicationBase,        ADR("CLASS2M2"),
                                        mm.SubWindow,               window,
                                  ud.tagDone)) ;

  IF app = NIL THEN ms.fail(NULL, "Failed to create Application.") END ;

  mm.NoteClose(app, window, m.mvApplicationReturnIDQuit) ;

  ms.DoMethod(pen,TAG(tags, m.mmNotify, m.maPenadjustSpec, m.mvEveryTime,
                         myObj, 3, m.mmSet, MyAttrPen, m.mvTriggerValue,
                   ud.tagDone));

  mm.get(pen, m.maPenadjustSpec, ADR(startpen)) ;
  mm.set(myObj, MyAttrPen, LONGCARD(startpen)) ;

(*
** Input loop...
*)

  mm.set(window, m.maWindowOpen, LONGCARD(TRUE)) ;

  WHILE running DO
    CASE ms.DOMethod(app, TAG(tags, m.mmApplicationInput, ADR(signals), ud.tagDone)) OF
    | m.mvApplicationReturnIDQuit : running := FALSE ;
    ELSE
    END (* CASE *) ;
    IF running AND (signals # LONGSET{}) THEN
      signals := Wait(signals) ;
    END (* IF *) ;
  END (* WHILE *) ;

  mm.set(window, m.maWindowOpen, LONGCARD(FALSE)) ;

(*
** Shut down...
*)

CLOSE
  IF app # NIL THEN
    ml.mDisposeObject(app)
    app := NIL ;
  END (* IF *) ;

  IF mcc # NIL THEN
    IF ml.moDeleteCustomClass(mcc) THEN END ;
    mcc := NIL ;
  END (* IF *) ;
END Class2.
