/* twm.c

   Tiny-Window Manager v1.0         (c) 1987 Transactor Publishing Inc.
   by Nick Sullivan

   This program is freely redistributable provided that no charge is made
   for the redistribution beyond reasonable reproduction costs, and that no
   changes are made except with the prior written approval of Transactor
   Publishing Inc., 85 West Wilmot St. #10, Richmond Hill, Ontario L4B 1K7.

   ------------------------------------------------------------------------

   TWM provides a storage area in which applications that are inactive, but
   running, can wait to be re-activated without using any chip RAM. It thus
   provides an alternative to the "tiny window" approach to minimizing chip
   RAM use, as exemplified by such programs as Uedit and PopColours.

   When TWM is run, it puts up its own tiny window, and creates a public
   message port. Client applications should check for the existence of this
   port and, if it is present, send a "twMessage" (as defined in twm.h)
   with the tmAction field set to TWM_ACTION_ADD when they wish to go to
   sleep. When the TWM window is clicked in thereafter, a larger window will
   be put up containing gadgets bearing the names of each client application
   that has been added. Clicking on one of these gadgets will cause the
   twMessage to be replied to, which is the signal for them to reawaken.
   At the same time as the reply is sent, the large TWM window will be taken
   down, and the gadget for that client removed.

   If a client wishes to reactivate itself before its TWM gadget is clicked,
   or if it wishes to exit altogether, it should first send a twMessage with
   the twmAction field set to TWM_ACTION_DELETE.

   The twmAction field is used by TWM to return a code to the client that
   indicates whether the requested operation was successful. The code for
   success is E_OK. Other possibilities are:

   E_NO_MEM
      A client has asked TWM to add a gadget for him, but TWM was unable to
      allocate memory for the gadget structure.
   E_ABANDON_SHIP
      1) A client has asked TWM to add a gadget for him while TWM had its
      large window up. In this case, TWM closes the large window, and
      re-opens it after rethinking the gadget positions and the window size.
      2) The user has clicked on TWM's tiny window, or closed its large
      window, causing TWM to close the current window and attempt to open the
      other one.
         If the window open fails in either of these cases, TWM sends all
      current clients this error message, then exits, since it has no means
      of recovery.
   E_TASK_UNKNOWN
      A client has sent a TWM_ACTION_DELETE, but TWM does not currently have
      a gadget for that client.
   E_ACTION_UNKNOWN
      A message has been received with the twmAction field set to an unknown
      code... currently the only possibilities are TWM_ACTION_DELETE and
      TWM_ACTION_ADD.


   Sample C code for applications wishing to interface correctly with TWM
   is contained in the file twmClient.c. The header file twm.h is also
   required. Before using other functions in this file, the client should
   call twmInit().
      The function PostMe() in twmClient.c should be called when the
   client wishes to deactivate. If this function returns TRUE, the client
   should resume its life as an active application. If it returns FALSE,
   either TWM is not present in the system or else the attempt to post failed
   for some reason. In this case, the client should take an alternative
   approach to deactivated living (like making its own tiny window), or else
   not allow itself to be deactivated.
      Programs that wish to be able to receive messages even when deactivated
   (time-outs, for example), will need to use a modified version of PostMe().
   If such a program wishes to reactivate before its gadget has been clicked
   in the TWM window, it should first call UnPostMe() (no arguments, no
   return) to inform TWM that the gadget should be taken down. Programs that
   will NOT need to call UnPostMe() (i.e. most programs) can use a version
   of twmClient.c from which the UnPostMe() function and all references to
   the global variable Delmsg have been removed.
      Before the first call to PostMe(), the function twmInit() must be
   invoked to set up the required messages and ports that PostMe() will need.
      Before the client exits, it should call the function UnPostMe() to
   deallocate resources twmInit() has allocated.
   
   Update history:
   
      Nov 12/87: Now making absolutely sure message port is cleared before
      exiting.
*/

#include "header/twm.h"


struct Gadget WakeUpGadget =
{
   NULL,                         /* address of next gadget        */
   2, 10, 116, 10,               /* left, top, width, height      */
   GADGHNONE,                    /* flags - no highlighting       */
   RELVERIFY,                    /* activation flags              */
   BOOLGADGET,                   /* gadget type                   */
   NULL,                         /* no imagery                    */
   NULL,                         /* no alternate imagery          */
   NULL,                         /* no text                       */
   0,                            /* mutual exclude                */
   NULL,                         /* SpecialInfo                   */
   0,                            /* gadget ID                     */
   NULL,                         /* user data                     */
};

struct NewWindow wtiny =
{
   480, 60, 120, 20,    /* left, top, width, height   */
     0,  1,             /* detail pen, block pen      */
   GADGETUP             /* IDCMP flags                */
   | CLOSEWINDOW,
   WINDOWDRAG           /* Window flags               */
   | WINDOWCLOSE
   | WINDOWDEPTH,
   &WakeUpGadget,       /* application gadget list    */
   NULL,                /* special checkmark imagery  */
   (UBYTE *)"TWM",      /* window title               */
   NULL,                /* custom screen pointer      */
   NULL,                /* super bitmap pointer       */
   0, 0, 0, 0,          /* min/max width and height   */
   WBENCHSCREEN         /* screen type                */
};

struct NewWindow whuge =
{
   480, 60, 0, 0,       /* left, top, width, height   */
     0,  1,             /* detail pen, block pen      */
   GADGETUP
   | CLOSEWINDOW,       /* IDCMP flags                */
   WINDOWDRAG           /* Window flags               */
   | WINDOWCLOSE
   | WINDOWDEPTH
   | SMART_REFRESH,
   NULL,                /* application gadget list    */
   NULL,                /* special checkmark imagery  */
   (UBYTE *)"TWM",      /* window title               */
   NULL,                /* custom screen pointer      */
   NULL,                /* super bitmap pointer       */
   0, 0, 0, 0,          /* min/max width and height   */
   WBENCHSCREEN         /* screen type                */
};

struct TextAttr twmFont =    /* 80 column topaz font          */
{
   (UBYTE *)"topaz.font",
   TOPAZ_EIGHTY,
   FS_NORMAL,
   FPF_ROMFONT
};

SHORT borderlines[5][2] =    /* simple box around gadgets     */
{
   {-3,              -3            },
   {GADGWIDTH + 3,   -3            },
   {GADGWIDTH + 3,   GADGHEIGHT + 2},
   {-3,              GADGHEIGHT + 2},
   {-3,              -3            }
};

struct twmGadget gadgTemplate =
{
   /* intuition gadget structure */
   NULL,                         /* address of next gadget        */
   0, 0, GADGWIDTH, GADGHEIGHT,  /* left, top, width, height      */
   GADGHCOMP,                    /* flags - invert to highlightg  */
   RELVERIFY,                    /* activation flags              */
   BOOLGADGET,                   /* gadget type                   */
   NULL,                         /* address of border struct      */
   NULL,                         /* SelectRender                  */
   NULL,                         /* address of intuitext struct   */
   0,                            /* mutual exclude                */
   NULL,                         /* SpecialInfo                   */
   0,                            /* gadget ID                     */
   NULL,                         /* user data - client replyport  */

   /* intuition border structure */
   0, 0,                         /* left edge, top edge           */
   2, 0, JAM1,                   /* front, back pens, draw mode   */
   5,                            /* number of points in border    */
   (SHORT *)borderlines,         /* address of coordinate array   */
   NULL,                         /* address of next border        */

   /* intuitext structure */
   1, 0, JAM1,                   /* front, back pens, draw mode   */
   0, 1,                         /* left edge, top edge           */
   &twmFont,                     /* address of TextAttr struct    */
   NULL,                         /* pointer to text               */
   NULL,                         /* address of next IntuiText     */

   /* name of gadget as supplied by client */
   "",

   /* pointer to message that requested this gadget */
   NULL
};


extern VOID *OpenWindow(), *OpenLibrary();
extern VOID *CreatePort(), *FindPort();
extern VOID *GetMsg(),     *AllocMem();

struct IntuitionBase *IntuitionBase;

struct twmGadget *NewGadget();

struct MsgPort      *mp;     /* public port (called PORTNAME) */
struct NewWindow    *nw;     /* describes current window      */
struct Window       *w;      /* pointer to current window     */
struct twmMessage   *Tmsg;   /* message arrived at mp         */
struct IntuiMessage *Imsg;   /* message arrived at IDCMP      */
struct twmGadget    *twmg;   /* first gadget in my list       */


/* main
*
*  Update history:
*     Nov 12/87: Public port no longer checked here if a CLOSEWINDOW event
*     has been detected in the IDCMP loop above.
*/

main ()
{
register int exitflag;              /* quit input loop if set        */
register int swapflag;              /* use other window (tiny/huge)  */
register int tinyflag;              /* currently using tiny window   */
register UWORD class;               /* IDCMP message class           */
UWORD code;                         /* IDCMP message code            */
int gadgCount;                      /* # of gadgets in my list       */
register struct twmGadget *gadget;  /* gadget clicked in huge window */

   exitflag = FALSE;
   swapflag = FALSE;
   tinyflag = TRUE;                 /* start out with tiny window    */

   gadgCount = 0;

   if ((IntuitionBase = OpenLibrary("intuition.library", 33L)) == NULL)
      CloseStuff(E_OPEN_INTUI);

   /* if we already exist, quit  */
   if (FindPort(PORTNAME) != NULL)
      CloseStuff(E_ALREADY_UP);

   if ((mp = CreatePort(PORTNAME, 0L)) == NULL)
      CloseStuff(E_OPEN_PORT);

   if ((w = OpenWindow(nw = &wtiny)) == NULL)
      CloseStuff(E_OPEN_WINDOW);

   /* exitflag set by close gadget on tiny window if no current clients */
   while (!exitflag)
   {
      /* waiting for message at IDCMP or our own port */
      Wait(1L << w->UserPort->mp_SigBit | 1L << mp->mp_SigBit);

      /* check IDCMP messages first */
      while (Imsg = GetMsg(w->UserPort))
      {
         class  = Imsg->Class;
         code   = Imsg->Code;
         gadget = (struct twmGadget *)Imsg->IAddress;
         ReplyMsg(Imsg);

         if (class == CLOSEWINDOW)
            /* exit from tiny window only if we have no clients, else beep */
            if (tinyflag)
               if (gadgCount == 0)
                  exitflag = TRUE;
               else
                  DisplayBeep(w->WScreen);
            /* close gadget on huge window means switch back to tiny */
            else
               swapflag = TRUE;

         /* this message means gadget pressed in huge window   */
         else if (class == GADGETUP)
            if (tinyflag)
               swapflag = TRUE;
            else
            {
               gadget->tgMessage->tmAction = E_OK;  /* return code E_OK  */
               KillGadget(gadget->tgMessage, TRUE); /* get rid of gadget */
               gadgCount--;
               swapflag = TRUE;                     /* switch to tiny    */
               ReplyMsg(gadget->tgMessage);         /* inform client     */
            }
      }

      /* now check messages at our public port  */
      while (!exitflag && (Tmsg = GetMsg(mp)))
      {
         /* client going on vacation, create a gadget for him  */
         if (Tmsg->tmAction == TWM_ACTION_ADD)
         {
            if ((gadget = NewGadget(Tmsg)) == NULL)
            {
               Tmsg->tmAction = E_NO_MEM;       /* send regrets */
               ReplyMsg(Tmsg);
            }
            else
               gadgCount++;

            /* if the huge window is up right now, close and re-open so that
               we can be sure the new gadget will fit
            */
            if (!tinyflag)
            {
               SavePosCloseW(nw, w);
               CalcGadgPos(nw);
               if ((w = OpenWindow(nw)) == NULL)
                  CloseStuff(E_ABANDON_SHIP);
            }
         }

         /* client going right out of business, cancel his gadget */
         else if (Tmsg->tmAction == TWM_ACTION_DELETE)
         {
            /* kill the gadget, and ghost it if huge window is up */
            if (KillGadget(Tmsg, !tinyflag))
            {
               Tmsg->tmAction = E_OK;
               gadgCount--;
            }
            else
               Tmsg->tmAction = E_TASK_UNKNOWN;    /* unrecognized client */

            ReplyMsg(Tmsg);
         }

         /* some message type we don't know */
         else
         {
            Tmsg->tmAction = E_ACTION_UNKNOWN;
            ReplyMsg(Tmsg);
         }
      }

      /* switch between huge and tiny windows */
      if (swapflag)
      {
         swapflag = FALSE;
         SavePosCloseW(nw, w);

         nw = tinyflag ? &whuge : &wtiny;

         tinyflag = !tinyflag;

         /* if we're going to open huge window, reformat gadgets and
            recalculate the window size
         */
         if (!tinyflag)
            CalcGadgPos(nw);

         if ((w = OpenWindow(nw)) == NULL)
            CloseStuff(E_ABANDON_SHIP);
      }
   }

   CloseStuff(E_OK);
}


/* CloseStuff
*
*  Close and deallocate everything. If there are any active clients, that
*  means something has gone wrong, so we send them an E_ABANDON_SHIP. The
*  return error codes start at 500 as defined in twm/header.h.
*
*  Update history:
*     Nov 12/87: Clients who have messages pending at our port when we're
*   about to shut down are also sent an E_ABANDON_SHIP.
*/

CloseStuff (error)
int error;
{
register struct twmGadget *g;

   g = twmg;

   if (w)               CloseWindow(w);
   if (IntuitionBase)   CloseLibrary(IntuitionBase);

   while (g != NULL)
   {
      g->tgMessage->tmAction = E_ABANDON_SHIP;
      ReplyMsg(g->tgMessage);
      KillGadget(g->tgMessage);
   }

   if (mp)
   {
      Forbid();

      while ((Tmsg = GetMsg(mp)) != NULL)
      {
         Tmsg->tmAction = E_ABANDON_SHIP;
         ReplyMsg(Tmsg);
      }

      DeletePort(mp);

      Permit();
   }

   exit(error);
}


/* NewGadget
*
*  We have a new client to create a gadget for. We link him to the
*  NextGadget field of the last gadget on the list, set up the new gadget
*  and return its address.
*/

struct twmGadget *
NewGadget (msg)
struct twmMessage *msg;
{
register struct twmGadget *g, *gprev;
register char *clientname;
register char c;

   gprev = NULL;
   g     = twmg;

   while (g != NULL)
   {
      gprev = g;
      g = g->tgMynext;
   }

   if ((g = AllocMem((long)sizeof(struct twmGadget), 0L)) == NULL)
      return FALSE;

   if (gprev != NULL)
   {
      gprev->tgMynext = g;
      gprev->tgGadget.NextGadget = &g->tgGadget;
   }

   *g = gadgTemplate;

   clientname = msg->tmName + strlen(msg->tmName);

   while (clientname > msg->tmName
         && (c = *(clientname - 1)) != ':'
         && c != '/')
      clientname--;

   strncpy(g->tgName, clientname, GADGNAMESIZE - 1);

   g->tgGadget.GadgetRender   = (APTR)&g->tgBorder;
   g->tgGadget.GadgetText     = &g->tgIText;

   g->tgIText.LeftEdge        = (GADGNAMESIZE - strlen(g->tgName)) << 2;
   g->tgIText.IText           = (UBYTE *)g->tgName;

   g->tgMessage               = msg;

   if (twmg == NULL)
      twmg = g;

   return g;
}


/* KillGadget
*
*  Get rid of a gadget currently on our list. If off_flag is true, the
*  gadget is currently being displayed, so we'll ghost it. Return FALSE
*  if the gadget is not on the list.
*/

KillGadget (msg, off_flag)
struct twmMessage *msg;
int off_flag;
{
register struct twmGadget *g, *gprev;
int flag;

   flag  = FALSE;
   gprev = NULL;
   g     = twmg;

   while (g != NULL && !flag)
      if (g->tgMessage->tmMessage.mn_ReplyPort == msg->tmMessage.mn_ReplyPort)
         flag = TRUE;
      else
      {
         gprev = g;
         g = g->tgMynext;
      }

   if (flag)
   {
      if (off_flag)
         OffGadget(&g->tgGadget, w, 0L);

      RemoveGadget(w, &g->tgGadget);

      if (gprev != NULL)
         gprev->tgMynext = g->tgMynext;
      else
         twmg = g->tgMynext;

      FreeMem(g, (long)sizeof(struct twmGadget));
   }

   return flag;
}


/* CalcGadgPos
*
*  Position the gadgets in the huge window, and set the window size to
*  accommodate them. The gadgets are displayed four across, to the maximum
*  depth of the screen.
*/

CalcGadgPos (nw)
register struct NewWindow *nw;
{
register int i, x, y;
register struct twmGadget *g;

   i = 0;                  /* gadget counter          */
   x = GADGHGUTTER +  2;   /* starting x position     */
   y = GADGVGUTTER + 10;   /* starting y position     */

   g = twmg;               /* address of 1st gadget   */

   /* chain through gadget list, writing in new left and top   */
   while (g)
   {
      g->tgGadget.LeftEdge  = x;
      g->tgGadget.TopEdge   = y;

      /* if this gadget is in R.H. column, reposition to left of next line */
      if ((i++ & 3) == 3)
      {
         x = GADGHGUTTER + 2;
         y += GADGHEIGHT + GADGVGUTTER;
      }
      else
         x += GADGWIDTH + GADGHGUTTER;

      /* chain to next gadget */
      g = g->tgMynext;
   }

   /* if there are no gadgets, make the window big enough to hold 1 */
   if (i == 0)
      x = GADGHGUTTER * 2 + GADGWIDTH + 2;

   /* if less than 4 gadgets, make window just big enough to hold them */
   if (i < 4)
      nw->Width = x;
   /* otherwise make it full width */
   else
      nw->Width = GADGHGUTTER + 2 + (GADGHGUTTER + GADGWIDTH) * 4;

   /* if the last gadget on the list falls at the R.H. edge of the window,
      y is already big enough; otherwise, make it so
   */
   if ((i & 3) == 0)
      nw->Height = y;
   else
      nw->Height = y + GADGVGUTTER + GADGHEIGHT;

   /* make sure that new dimensions of window will still fit on the screen...
      if necessary reposition the window
   */
   if (nw->LeftEdge + nw->Width > w->WScreen->Width)
      nw->LeftEdge = w->WScreen->Width - nw->Width;

   if (nw->TopEdge + nw->Height > w->WScreen->Height)
      nw->TopEdge = w->WScreen->Height - nw->Height;

   /* install our first gadget as the new window's first gadget */
   nw->FirstGadget = &twmg->tgGadget;
}


/* SavePosCloseW
*
*  Save the current window position and size in the NewWindow structure for
*  that window, then close the window.
*/

SavePosCloseW (nw, w)
register struct NewWindow *nw;
register struct Window *w;
{
   nw->LeftEdge = w->LeftEdge;
   nw->TopEdge  = w->TopEdge;
   nw->Width    = w->Width;
   nw->Height   = w->Height;

   CloseWindow(w);
}


/* When compiling with Aztec, the following two stubs replace the Aztec
   code for parsing the command line, thus reducing code size a bit
*/

#ifdef AZTEC_C

_wb_parse ()
{
}


_cli_parse ()
{
}

#endif !AZTEC_C
