
/*

     OOPExample 1.0 - Copyright © 07-Feb-1997 by Maik "BLiZZeR" Schreiber
     ====================================================================

Hello, this is OOPExample. As stated in OOPExample.readme, it should show you
how to write an object-oriented program with MUI. However, OOP with MUI does
not mean C++ here. We are programming in normal C, but using MUI's OOP
features, ok? ;)

First of all you should have little knowledge of OOP, because it's quite
complicated to explain it all here. I think it's the best if you get a good
C++ book from your library or something. Second, you should know how to write
MUI programs. You should know about MUI's classes, their attributes, methods
and stuff.

Ok, now let's go on. Before I got to know how MUI's OOP features work,
I didn't have a clue about it. I didn't know about dispatchers, method
handlers and all that stuff. I looked at psi.c, but that was so complex
that I really didn't get the point!

All I wanted was a small example. It should open a window with a button
inside it. Clicking on this button should open a second window, clicking
it again should close the second window. Nothing more, nothing less.
(Maybe a few guys on the MUI mailing remember ;)

Fine, I knew how to do it with return IDs: Clicking the button returns an
ID to the main input loop, and the loop decides to open or close the second
window. But damn, that was no OOP!

Then, two guys (Kai Hofmann and Jan Hendrik Schulz, hello there :)) told me
to implement my example using an attribute. But how?! How to add a new
attribute to what?

Anyway, I finally had a look at Klaus Melchior's Tron.mcc example and - click!
Of course! That's how it works!


You're still reading? Fine :-))) Let's go on then:

Of course there are may ways to implement a new custom class which satisfies
my wish for that example above. I want to show you two of them:

1. Implement a new custom class which is derived from Window.mui. Inside it,
   it shall create a button itself. Clicking on this button should read an
   attribute specifying the other window to open/close and open or close
   that window.

2. Implement a new custom class which is derived from Text.mui (adding an
   input mode, of course, so that we have a button). Clicking on this button
   again reads out the attribute and opens or closes the other window.

In this example we implement the first way, because it shows general data
hiding techniques of OOP: The first window object we create from our custom
class does not know about the button inside the other window and vice versa.
Furthermore, we have less global identifiers and stuff.

Now let me explain how we're going to implement the new custom class:

First, when creating an object from that class, we create the window itself
(of course). Next, we create the button inside it. Third, we setup a
notification on that button to send the object (our window) a method.

"But how does that method know about the other window, which should be
opened or closed depending on its state?" you may ask.
This is done via a new attribute called MUIA_OOPWindow_WindowToOpenOrClose.

Look, after the complete application object tree is created within the
main program, we know about our two window objects, which are objects of
our new OOPWindow class.

All we have to do now, is to "say" window 1, that its button has to open or
close window 2. And window 2 has to know that its button should open or close
window 1. This is done via a more than simple set() call.

THAT'S IT!

Now, when you click the button in window 1, its notification sends the
specified method (called MUIM_OOPWindow_OpenOrCloseWindow) to the object,
which is again window 1. Using the attribute window 1 now knows about
window 2 and is able to open/close it! (Of course, this works the other
way round in this example.)


Ok, girls and guys, now you have the knowledge of how to write object-oriented
progams with MUI! Isn't that simple? :-)))
The following source code has loads of comments inside, so just have a look.

Happy programming,

Maik "BLiZZeR" Schreiber
(why don't you call me "MUI genius", huh? ;)



******  IMPORTANT NOTE  ******  IMPORTANT NOTE  ******  IMPORTANT NOTE  ******


This source code uses the tag base TAGBASE_BLIZZY, which is defined as
(TAG_USER | (1465UL << 16)).

PLEASE *DO* *NOT* *USE* THIS TAG BASE IN YOUR OWN APPLICATIONS!!!!!!!!!!!!
It is a personalized tag base!!!!!!!!!!

If you need such a tag base for creating attributes and methods identifiers,
you *MUST* write to mccreg@sasg.com and ask for a new tag base for you!!!!!

This is really important!!!


******  IMPORTANT NOTE  ******  IMPORTANT NOTE  ******  IMPORTANT NOTE  *****/



/*** include files ***/
#include <exec/types.h>
#if defined __MAXON__ || defined __STORM__
#include <pragma/exec_lib.h>
#else
#include <proto/exec.h>
#endif
#include <exec/libraries.h>
#if defined __MAXON__ || defined __STORM__
#include <pragma/muimaster_lib.h>
#else
#include <proto/muimaster.h>
#endif
#include <libraries/mui.h>
#include <intuition/classusr.h>
#if defined __MAXON__ || defined __STORM__
#include <pragma/utility_lib.h>
#else
#include <proto/utility.h>
#endif
#include <utility/utility.h>
#include <clib/alib_protos.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#if defined __MAXON__ || defined __STORM__
#include <wbstartup.h>
#endif

#include "OOPExample.h"


/*** constants ***/
/* attributes */
#define MUIA_OOPWindow_ButtonTitle         (TAGBASE_BLIZZY + 1) /* [I.G]  STRPTR   */
#define MUIA_OOPWindow_ButtonShortHelp     (TAGBASE_BLIZZY + 2) /* [I.G]  STRPTR   */
#define MUIA_OOPWindow_WindowToOpenOrClose (TAGBASE_BLIZZY + 3) /* [ISG]  Object * */


/* methods */
#define MUIM_OOPWindow_OpenOrCloseWindow   (TAGBASE_BLIZZY + 1)

/* miscellaneous */
#define STR_VERSI_OOPEXAMPLE "1.0"
#define STR_DATE_OOPEXAMPLE  "07-Feb-1997"
#define STR_VDATE_OOPEXAMPLE "7.2.97"


/*** structures ***/
/* class instance data structure */
struct OOPWindowData
{
  Object *wiWindowToOpenOrClose;
  char   *button_title;
  char   *button_shorthelp;
};


/*** macros ***/
/* this macro simplifies creation of OOPWindow objects */
#define OOPWindowObject NewObject(clOOPWindow->mcc_Class, NULL

/* copied from <blizzy/macros.h> */
#ifndef MAKE_ID
#define MAKE_ID(a,b,c,d) ((((ULONG) (a)) << 24) | (((ULONG) (b)) << 16) | (((ULONG) (c)) << 8) | ((ULONG) (d)))
#endif


/*** prototypes ***/
BOOL  gui_init(void);
void  gui_exit(void);
BOOL  classes_init(void);
void  classes_exit(void);

ULONG ASM SAVEDS OOPWindow_Dispatcher(REG(a0) struct IClass *, REG(a2) Object *, REG(a1) Msg);

ULONG DoSuperNew(struct IClass *, Object *, ULONG, ...);
ULONG xget(Object *, ULONG);


/*** global identifiers ***/
Object                 *apMain        = NULL;
Object                 *  owWindow1;
Object                 *  owWindow2;

struct MUI_CustomClass *clOOPWindow   = NULL;

struct Library         *MUIMasterBase = NULL;
struct Library         *UtilityBase   = NULL;


/*** program ***/
int main(void)
{
  int ret = RETURN_FAIL;

  /* open all necessary libraries */
  if (!(UtilityBase = OpenLibrary(UTILITYNAME, 37UL)))
  {
    printf("Couldn't open %s 37!\n", UTILITYNAME);
    goto bye;
  }
  if (!(MUIMasterBase = OpenLibrary(MUIMASTER_NAME, MUIMASTER_VMIN)))
  {
    printf("Couldn't open %s %lu!\n", MUIMASTER_NAME, (ULONG) MUIMASTER_VMIN);
    goto bye;
  }

  /* create custom classes */
  if (!classes_init())
  {
    puts("Couldn't create custom classes!");
    goto bye;
  }

  /* create application */
  if (!gui_init())
  {
    puts("Couldn't create application!");
    goto bye;
  }

  /* This is the ideal input loop for an object oriented MUI
  ** application. Everything is encapsulated in classes, no
  ** return IDs need to be used, we just check if the program
  ** shall terminate.
  **
  ** (Quote and source part copied from the original MUI distribution.)
  */
  {
    ULONG sigs = 0UL;

    while (DoMethod(apMain, MUIM_Application_NewInput, &sigs) != MUIV_Application_ReturnID_Quit)
    {
      if (sigs)
      {
        sigs = Wait(sigs | SIGBREAKF_CTRL_C);
        if (sigs & SIGBREAKF_CTRL_C)
          break;
      }
    }
  }

  ret = RETURN_OK;

  bye:
  /* free all resources */
  gui_exit();
  classes_exit();
  if (MUIMasterBase) CloseLibrary(MUIMasterBase);
  if (UtilityBase)   CloseLibrary(UtilityBase);
  exit(ret);
}

/* setup application and do all necessary initializations */
BOOL gui_init(void)
{
  if (apMain = ApplicationObject,
    MUIA_Application_Title,       "OOPExample",
    MUIA_Application_Version,     "$VER: OOPExample " STR_VERSI_OOPEXAMPLE " (" STR_VDATE_OOPEXAMPLE ") © by Maik \"BLiZZeR\" Schreiber, "
#ifdef __SASC
                                  "SAS/C"
#else
  #ifdef __MAXON__
                                  "MaxonC++"
  #else
    #ifdef __STORM__
                                  "StormC"
    #else
      #ifdef _DCC
                                  "Dice C"
      #else
        #ifdef _GCC
                                  "GNU C"
        #else
                                  "unknown compiler"
        #endif
      #endif
    #endif
  #endif
#endif
                                  " version",
    MUIA_Application_Copyright,   "Copyright © " STR_DATE_OOPEXAMPLE " by Maik Schreiber <BLiZZeR@dame.de>",
    MUIA_Application_Author,      "Maik Schreiber <BLiZZeR@dame.de>",
    MUIA_Application_Description, "Learn OOP with MUI!",
    MUIA_Application_Base,        "OOPEXAMPLE",

    SubWindow, owWindow1 = OOPWindowObject,
      MUIA_Window_Title,              "OOPExample · Window 1",
      MUIA_Window_ID,                 MAKE_ID('W', 'I', 'N', '1'),
      MUIA_OOPWindow_ButtonTitle,     "Open/close window _2",
      MUIA_OOPWindow_ButtonShortHelp, "This button opens or closes window 2.",
    End,

    SubWindow, owWindow2 = OOPWindowObject,
      MUIA_Window_Title,              "OOPExample · Window 2",
      MUIA_Window_ID,                 MAKE_ID('W', 'I', 'N', '2'),
      MUIA_OOPWindow_ButtonTitle,     "Open/close window _1",
      MUIA_OOPWindow_ButtonShortHelp, "This button opens or closes window 1.",
    End,

  End)
  {
    /* specify which window's button should open/close which window */
    set(owWindow1, MUIA_OOPWindow_WindowToOpenOrClose, owWindow2);
    set(owWindow2, MUIA_OOPWindow_WindowToOpenOrClose, owWindow1);

    /* this lets us quit the program by closing one of the windows directly */
    DoMethod(owWindow1, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, apMain, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
    DoMethod(owWindow2, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, apMain, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);

    /* open window 1 and let MUI do the work :-) */
    set(owWindow1, MUIA_Window_Open, TRUE);

    return(TRUE);
  }
  return(FALSE);
}
/* dispose application */
void gui_exit(void)
{
  if (apMain) MUI_DisposeObject(apMain);
}
/* create custom classes */
BOOL classes_init(void)
{
  /* This is one of the essential parts when fiddling with custom classes.
  ** Here we create our own custom classes, which can be derived from
  ** any MUI class. Our new classes can also be derived from themselves,
  ** e.g. a new window class derived from Window.mui and a second new
  ** window class which is derived from that first window class.
  **
  ** In this OOPExample program, we create a new window class which is
  ** derived from Window.mui.
  **
  ** A note about the datasize parameter of MUI_CreateCustomClass():
  ** This parameter is used to tell MUI, how much memory a class instance
  ** of this class needs for its data. MUI allocates a memory block of
  ** that given size, which you may use for anything you want.
  **
  ** You can always get a pointer to an object's instance data using
  **
  **   INST_DATA(class, obj);   // see <intuition/classes.h>
  */
  clOOPWindow = MUI_CreateCustomClass(NULL, MUIC_Window, NULL, sizeof(struct OOPWindowData), OOPWindow_Dispatcher);
  if (clOOPWindow)
    return(TRUE);
  return(FALSE);
}
/* dispose custom classes */
void classes_exit(void)
{
  if (clOOPWindow) MUI_DeleteCustomClass(clOOPWindow);
}

/* This is the constructor of our newly created OOPWindow custom class. */
ULONG OOPWindow_NEW(struct IClass *cl, Object *obj, struct opSet *msg)
{
  Object         *button;
  struct TagItem *tag;
  char           *button_title;
  char           *button_shorthelp;

  /* The MUIA_OOPWindow_ButtonTitle attribute needs a bit more overhead... */
  if (tag = FindTagItem(MUIA_OOPWindow_ButtonTitle, msg->ops_AttrList))
    button_title = (char *) tag->ti_Data;
  else
    button_title = "";

  /* ... as does the MUIA_OOPWindow_ButtonShortHelp attribute... */
  if (tag = FindTagItem(MUIA_OOPWindow_ButtonShortHelp, msg->ops_AttrList))
    button_shorthelp = (char *) tag->ti_Data;
  else
    button_shorthelp = "";


  /* This is the point where we create the new object from our class.
  ** There exist two ways for doing this:
  **
  ** 1. Just creating the new object.
  **    This is done via DoSuperMethodA():
  **
  **    if (obj = (Object *) DoSuperMethodA(cl, obj, (Msg)))
  **    {
  **      ...
  **    }
  **
  ** 2. Creating the object plus adding some new tags to its initial
  **    taglist.
  **    This is done via DoSuperNew(), which is an extra function calling
  **    DoSuperMethodA() again:
  **
  **
  **    if (obj = (Object *) DoSuperNew(cl, obj,
  **      ... add new ("hardcoded") tags here ...
  **      TAG_MORE, msg->ops_AttrList))
  **    {
  **      ...
  **    }
  **
  **
  ** In OOPExample, we want to create a button inside the window object.
  ** Since the main program should not create the button but we want to
  ** create it ourselves, we have to do a call to DoSuperNew().
  */
  if (obj = (Object *) DoSuperNew(cl, obj,
    WindowContents, VGroup,
      Child, TextObject,
        MUIA_Text_Contents, "\033c\033b\033uOOPExample " STR_VERSI_OOPEXAMPLE,
        MUIA_Font, MUIV_Font_Big,
      End,
      Child, TextObject,
        MUIA_Text_Contents, "\033cCopyright © " STR_DATE_OOPEXAMPLE " by Maik \"BLiZZeR\" Schreiber/IQ Computing",
      End,
      Child, TextObject,
        MUIA_Text_Contents, "\033c(This version was compiled using "
#ifdef __SASC
                            "SAS/C."
#else
  #ifdef __MAXON__
                            "MaxonC++."
  #else
    #ifdef __STORM__
                            "StormC."
    #else
      #ifdef _DCC
                            "Dice C."
      #else
        #ifdef _GCC
                            "GNU C."
        #else
                            "an unknown C compiler?!"
        #endif
      #endif
    #endif
  #endif
#endif
                            ")",
        MUIA_Font, MUIV_Font_Tiny,
      End,
      Child, button = SimpleButton(button_title),
    End,
    TAG_MORE, msg->ops_AttrList))
  {
    /* get instance data of the newly created object */
    struct OOPWindowData *data = INST_DATA(cl, obj);

    /* set default values */
    memset(data, 0, sizeof(struct OOPWindowData));
    data->button_title     = button_title;
    data->button_shorthelp = button_shorthelp;

    /* use a trick to set the initial values */
    msg->MethodID = OM_SET;
    DoMethodA(obj, (Msg) msg);
    msg->MethodID = OM_NEW;

    /* set cycle chain */
    set(button, MUIA_CycleChain, TRUE);

    /* bubble help */
    set(button, MUIA_ShortHelp, button_shorthelp);

    /* Setup notification for the button to open/close a window.
    ** We simply send ourselves the MUIM_OOPWindow_OpenOrCloseWindow
    ** method.
    */
    DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, obj, 1, MUIM_OOPWindow_OpenOrCloseWindow);

    /* return address of the new object */
    return((ULONG) obj);
  }
  /* return failure indicator */
  return(0);
}
/* This is the OM_SET method handler of our class. */
ULONG OOPWindow_SET(struct IClass *cl, Object *obj, struct opSet *msg)
{
  /* get instance data of our object */
  struct OOPWindowData *data      = INST_DATA(cl, obj);
  struct TagItem       *tags      = msg->ops_AttrList;
  struct TagItem       *tag;

  /* The identifier no_notify gives us the possibility to handle
  ** the MUIA_NoNotify attribute in our class. If you need this feature
  ** in your class, simple remove the comment markers here and a few
  ** lines below. Then, you have to write
  **
  **   SetAttrs(obj, MUIA_NoNotify, no_notify, ...);
  **
  ** to take care of MUIA_NoNotify.
  */
/*
  BOOL                  no_notify = FALSE;
*/

  /* There are many ways to find out what tag items provided by set()
  ** we do know. The best way should be using NextTagItem() and simply
  ** browsing through the list.
  */
  while (tag = NextTagItem(&tags))
  {
    switch (tag->ti_Tag)
    {
      /* handle MUIA_NoNotify in our class */
/*
      case MUIA_NoNotify:
        no_notify = (BOOL) tag->ti_Data;
        break;
*/

      /* specify the window, which should be opened/closed on pressing
      ** the button
      */
      case MUIA_OOPWindow_WindowToOpenOrClose:
        data->wiWindowToOpenOrClose = (Object *) tag->ti_Data;
        break;
    }
  }
  return(DoSuperMethodA(cl, obj, (Msg) msg));
}
/* Here comes the OM_GET method handler. */
ULONG OOPWindow_GET(struct IClass *cl, Object *obj, struct opGet *msg)
{
  /* The OM_GET handler also has a very special behaviour:
  ** Since you can only get() one attribute value at a time,
  ** it has to return() TRUE if it does "understand" your
  ** attribute identifier.
  ** If not, the handler must pass the attribute identifier
  ** to the superclass via DoSuperMethodA().
  */

/* small macro to simplify return value storage */
#define STORE *(msg->opg_Storage)

  /* get instance data of our object */
  struct OOPWindowData *data = INST_DATA(cl, obj);

  switch(msg->opg_AttrID)
  {
    /* Return button title of our button.
    ** This attribute isn't used in this example program,
    ** but it shows another time how the technique is done.
    **
    ** NOTE: In this example, the returned string must not be
    ** modified!
    */
    case MUIA_OOPWindow_ButtonTitle:
      STORE = (ULONG) data->button_title;
      return(TRUE);

    /* return bubble help string of our button */
    case MUIA_OOPWindow_ButtonShortHelp:
      STORE = (ULONG) data->button_shorthelp;
      return(TRUE);

    /* return the specified window, which should be opened/closed
    ** on pressing the button
    */
    case MUIA_OOPWindow_WindowToOpenOrClose:
      STORE = (ULONG) data->wiWindowToOpenOrClose;
      return(TRUE);
  }

  /* our handler didn't understand the attribute, we simply pass
  ** it to our superclass now
  */
  return(DoSuperMethodA(cl, obj, (Msg) msg));
#undef STORE
}
/* MUIM_OOPWindow_OpenOrCloseWindow method handler */
ULONG OOPWindow_OpenOrCloseWindow(Object *obj)
{
  /* This is the "main" method of our OOPWindow class.
  ** Here's what it does:
  **
  ** 1. Get the window, which should be opened or closed.
  ** 2. Open/close the window depending on its current state
  **    (ie. if it's open, close it, otherwise open).
  **
  ** Since this is a new method, we don't have anything to pass to
  ** our superclass here. Instead, we return() TRUE to indicate that
  ** all went ok.
  **
  ** NOTE: In this example, the return value of this method isn't used,
  ** so we could also return() FALSE here. When writing custom classes,
  ** it can sometimes be very handy to have return values. You can get
  ** them from your DoMethod() call to this method.
  */

  Object *window;

  if (window = (Object *) xget(obj, MUIA_OOPWindow_WindowToOpenOrClose))
    set(window, MUIA_Window_Open, xget(window, MUIA_Window_Open) ? FALSE : TRUE);
  return(TRUE);
}
/* Here you see the dispatcher of our class. */
ULONG ASM SAVEDS OOPWindow_Dispatcher(REG(a0) struct IClass *cl, REG(a2) Object *obj, REG(a1) Msg msg)
{
  /* The class dispatcher is the function, that will be called when a
  ** method is sent to your class. The dispatcher has to determine
  ** if it understands the method. If yes, it calls the according
  ** method handler (you can also write the handler here instead of
  ** return()ing a function return value - that's a style question).
  ** If not, it has to pass the method to the superclass.
  */

  /* watch out for methods we do understand */
  switch (msg->MethodID)
  {
    /* Whenever an object shall be created using NewObject(), it will be
    ** sent a OM_NEW method.
    */
    case OM_NEW:                           return(OOPWindow_NEW(cl, obj, (struct opSet *) msg));

    /* SetAttrs() sends a OM_SET method */
    case OM_SET:                           return(OOPWindow_SET(cl, obj, (struct opSet *) msg));

    /* GetAttr() sends a OM_GET method */
    case OM_GET:                           return(OOPWindow_GET(cl, obj, (struct opGet *) msg));

    /* one of our own methods */
    case MUIM_OOPWindow_OpenOrCloseWindow: return(OOPWindow_OpenOrCloseWindow(obj));
  }

  /* we didn't understand the last method, so call our superclass */
  return(DoSuperMethodA(cl, obj, msg));
}

/* This is a small function to add some new capabilities to
** DoSuperMethodA().
*/
ULONG DoSuperNew(struct IClass *cl, Object *obj, ULONG tag1, ...)
{
  return(DoSuperMethod(cl, obj, OM_NEW, &tag1, NULL));
}
/* This simplifies get()ting values of attributes. */
ULONG xget(Object *obj, ULONG attr)
{
  ULONG ret;

  get(obj, attr, &ret);
  return(ret);
}

