[comp.sources.amiga] twm - Tiny Window Manager

ahh@j.cc.purdue.edu (Brent L. Woods) (01/05/88)

Program Name:  twm - a tiny window manager.
Submitted By:  Rico Mariani <rico%oscvax.uucp@relay.cs.net>
Summary:  A managing program for converting program windows into gadgets.
Poster Boy:  Brent Woods  (ahh@j.cc.purdue.edu)
Not extensively tested.

NOTES:



Brent Woods, Co-Moderator, comp.{sources,binaries}.amiga

USENET:  ...!j.cc.purdue.edu!ahh     ARPANET:  ahh@j.cc.purdue.edu
BITNET:  PODUM@PURCCVM               PHONE:  +1 (317) 743-8421
USNAIL:  320 Brown St., #406  /  West Lafayette, IN  47906

================================================================

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	ReadMeFirst
#	Test1.c
#	twm.c
#	twmClient.c
#	twm.h
#	PopCol.doc
#	XE.doc
#	TWM.article
# This archive created: Mon Jan  4 18:20:21 1988
# By:	Brent L. Woods (Co-Moderators Unlimited.)
cat << \SHAR_EOF > ReadMeFirst
November 10, 1987


These programs, source code and text files are both a demo of and complete
documentation for TWM (Tiny Window Manager), a method of handling multiple
tiny windows on the Amiga. For a quick test of TWM, try the following:

   After executing the ExecMe file in this ARC package, you will have a
   directory called "pgms". Add this directory to your CLI command path, 
   then:

   run twm
   run popcolours
   run test1      ... and press a key
   run xe         ... and select "Tiny Window" from the menu

   Now click anywhere on the TWM tiny window (except the title bar) and
   then... well, you can figure it out.

The text file TWM.article along with the comments in the C source files
twm.c, twmClient.c, test1.c and header/twm.h provide all the necessary
information for using TWM and for adding a TWM-compatible tiny window mode
to your own programs. Other text files provide instructions on the use of
the XE and PopColours programs.

ExecMe is an execute file that will create subdirectories for the files in
this ARC and copy the files into them. The subdirectory structure is built in
the root directory of logical device twm:, which you must assign beforehand.


Nick Sullivan



Transactor Magazine
Transactor for the Amiga Magazine


Transactor Publishing Inc.
85 West Wilmot Street, Unit #10,
Richmond Hill, Ontario L4B 1K7
Telephone: 416-764-5273


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


November 12, 1987

Modification to twm.c and twmClient.c (TWM release 1.1)

A few lines in twm.c and twmClient.c have been altered to correct an unlikely
but theoretically possible bug in the first release. The changes are:

   1) in the PostMe() function of twmClient.c, the FindPort() and PutMsg()
      sequence is now bracketed by a Forbid()/Permit() pair, and two message
      initialization lines have been moved to minimize the Forbidden zone.

   2) in the UnPostMe() function of twmClient.c, the FindPort() and PutMsg()
      sequence has been enclosed by Forbid()/Permit() as in 1).

   3) in the main() function of twm.c, the public message port is no longer
      examined if a CLOSEWINDOW event has been detected in the preceding IDCMP
      loop. Instead the public port is examined/drained in the CloseStuff()
      function, and any post requests are returned to the would-be client
      with an E_ABANDON_SHIP message code.

   4) the CloseStuff() function of twm.c has been modified as described in 3).

Thanks to Rico Mariani of Toronto for suggesting these changes.

Nick Sullivan
SHAR_EOF
cat << \SHAR_EOF > Test1.c
/* test1.c

   This is a test program for TWM, and provides an example of using twmClient
   in an application. It puts up a window named "Press any key"; if a key is
   pressed, the window is taken down, and is replaced by either a tiny window
   or by a gadget in TWM's window (if PostMe() returns TRUE).  Clicking in
   the main part of the tiny window kills it, and brings the big window back.
   The same applies to the gadget in the TWM window, if it is being used
   instead: clicking on it kills it and bring up this program's big window.
   Clicking close in either the big or the small window exits the program.

   The compiled test1 program may be copied to test2, test3 etc, and all
   copies run simultaneously, to get a better idea of how TWM works.

   Under Aztec, put this program and twmClient.c in the current directory,
   and twm.h in the subdirectory "header", then compile and link thus:

      cc +Htwm.p header/twm.h
      cc +Itwm.p test1
      cc +Itwm.p twmClient

      ln test1.o twmClient.o -lc
*/

#include "header/twm.h"

#define HUGE_WINDOW_NAME   ((UBYTE *)"Press any key")

struct NewWindow wtiny =
{
   280, 120, 120, 20,   /* left, top, width, height   */
     0,  1,             /* detail pen, block pen      */
   MOUSEBUTTONS         /* IDCMP flags                */
   | CLOSEWINDOW,
   WINDOWDRAG           /* Window flags               */
   | WINDOWCLOSE
   | WINDOWDEPTH
   | SMART_REFRESH
   | ACTIVATE,
   NULL,                /* application gadget list    */
   NULL,                /* special checkmark imagery  */
   NULL,                /* 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 =
{
   280, 120, 360, 60,   /* left, top, width, height   */
     0,  1,             /* detail pen, block pen      */
   CLOSEWINDOW          /* IDCMP flags                */
   | RAWKEY,
   WINDOWDRAG           /* Window flags               */
   | WINDOWCLOSE
   | WINDOWDEPTH
   | SMART_REFRESH
   | ACTIVATE,
   NULL,                /* application gadget list    */
   NULL,                /* special checkmark imagery  */
   HUGE_WINDOW_NAME,    /* window title               */
   NULL,                /* custom screen pointer      */
   NULL,                /* super bitmap pointer       */
   0, 0, 0, 0,          /* min/max width and height   */
   WBENCHSCREEN         /* screen type                */
};

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

struct IntuitionBase *IntuitionBase = NULL;

struct NewWindow    *nw    = NULL;
struct Window       *w     = NULL;
struct IntuiMessage *Imsg  = NULL;

main (argc, argv)
int argc;
char **argv;
{
int exitflag;  /* true when close gadget has been pressed         */
int swapflag;  /* true when it's time to switch between windows   */
int tinyflag;  /* true when the tiny window is up                 */
UWORD class;   /* IDCMP message class                             */
UWORD code;    /* IDCMP message code                              */
char filename[31];
register int i, c;

   /* Use program name as title of tiny window */
   for (i = strlen(argv[0]) - 1;
         i > 0 && (c = argv[0][i - 1]) != ':' && c != '/';
         i--)
      ;
   wtiny.Title = (UBYTE *)(&argv[0][i]);

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

   /* Let twmClient do its allocations... we don't care this time, but it
      returns FALSE if the allocations fail
   */
   twmInit();

   /* initialize flags */
   tinyflag = swapflag = exitflag = FALSE;

   /* open the big window, and save pointer to its NewWindow struct */
   if ((w = OpenWindow(nw = &whuge)) == NULL)
      CloseStuff(E_OPEN_WINDOW);

   /* IDCMP loop */
   while (!exitflag)
   {
      Wait(1L << w->UserPort->mp_SigBit);

      while (Imsg = GetMsg(w->UserPort))
      {
         class = Imsg->Class;
         code  = Imsg->Code;
         ReplyMsg(Imsg);

         if (class == CLOSEWINDOW)
            exitflag = TRUE;

         /* swap if tiny window clicked, or key pressed in big window */
         else if ((class == MOUSEBUTTONS && code == SELECTUP && tinyflag)
                  || class == RAWKEY)
            swapflag = TRUE;
      }

      if (swapflag)
      {
         swapflag = FALSE;

         /* remember where this window is now stationed, and close it */
         SavePosCloseW(nw, w);

         /* if tiny window is now up, or PostMe() fails, open other window */
         if (tinyflag || !PostMe(wtiny.Title))
         {
            nw = tinyflag ? &whuge : &wtiny;
            tinyflag = !tinyflag;
         }

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

   CloseStuff(E_OK);
}

/* CloseStuff
*
*  Call twmCleanUp() to deallocate messages and port we've been using,
*  then close our own stuff and exit.
*/

CloseStuff (error)
int error;     /* errors start at 500 (see twm.h) except for E_OK = 0   */
{
   twmCleanUp();  /* twmClient deallocations */

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

   exit(error);
}

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

static 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);
}
SHAR_EOF
cat << \SHAR_EOF > twm.c
/* twm.c

   Tiny-Window Manager v1.1         (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 "twmMessage" (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
   twmMessage 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 twmMessage with
   the tmAction field set to TWM_ACTION_DELETE.

   The tmAction 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 tmAction 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 twmCleanUp() 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 highlight   */
   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
SHAR_EOF
cat << \SHAR_EOF > twmClient.c
/* twmClient.c

   Tiny-Window Manager v1.1         (c) 1987 Transactor Publishing Inc.
   Client Interface Module
   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.
   Programs that incorporate this code should include in their documentation
   the line: "This program supports TWM ((c) 1987 Transactor Publishing Inc.)

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

   This module should be compiled and linked with applications that wish to
   be clients of TWM when it is present in the system. Briefly, the client
   calls the function twmInit() to set up, afterwards calls PostMe() whenever
   he wishes to go to sleep, then finally calls twmCleanUp() just before
   exiting. Full details are in the prefatory comments to TWM.c. Note that
   the size of this module can be further reduced, especially in programs
   that do not require UnPostMe(); for details see comments in the code.


   Update history:
      Nov 12/87:  Forbid()/Permit() added to PostMe() and UnPostMe()
*/

#include "header/twm.h"

#define TWM_MSGSIZE ((long)sizeof(struct twmMessage))

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

struct MsgPort      *mp       = NULL;  /* reply port for our msgs    */
struct MsgPort      *twmport  = NULL;  /* points to twm's port       */
struct twmMessage   *Addmsg   = NULL;  /* TWM_ACTION_ADD message     */

/* The following line can be deleted if UnPostMe() is not required.  */
struct twmMessage   *Delmsg   = NULL;  /* TWM_ACTION_DELETE message  */

int twmReady = FALSE;   /* TRUE means ports are allocated & initialized */

PostMe (clientName)
register char *clientName;
{
   /* trying not to pass junk to TWM... this if-statement can be deleted
      after the client application has been debugged.
   */
   if (clientName == NULL || *clientName == '\0')
      return FALSE;

   /* set up our message telling TWM to add its gadget   */
   Addmsg->tmName   = clientName;
   Addmsg->tmAction = TWM_ACTION_ADD;

   /* check we're initialized and that twm exists in system and send message
   */

   Forbid();                  /* to be on the safe side */

   if (!twmReady || (twmport = FindPort(PORTNAME)) == NULL)
      return FALSE;

   PutMsg(twmport, Addmsg);

   Permit();

   WaitPort(mp);

   Addmsg = GetMsg(mp);

   /* anything other than E_OK return code is bad news... forget about TWM */
   return (Addmsg->tmAction == E_OK);
}

/* The following function can be deleted in normal use */
UnPostMe ()
{
   Delmsg->tmAction = TWM_ACTION_DELETE;

   if (twmReady)
   {
      Forbid();

      if ((twmport = FindPort(PORTNAME)) != NULL)
         PutMsg(twmport, Delmsg);

      Permit();

      /* twm will reply the original (ADD) message before replying this
         one if it's going to reply it at all... hence loop exit condition
      */
      if (twmport != NULL)
         do
         {
            WaitPort(mp);
         } while (GetMsg(mp) != Delmsg);
   }
}

/* This function must be called by the client before calling PostMe() */
twmInit ()
{
   /* don't re-initialize */
   if (twmReady)
      return TRUE;

   /* set up our messages, allocate a port. The allocation of Delmsg can
      be deleted if the UnPostMe() function is not required
   */
   if ((mp = CreatePort(NULL, 0L)) == NULL
       || (Delmsg = AllocMem(TWM_MSGSIZE, MEMF_CLEAR)) == NULL
       || (Addmsg = AllocMem(TWM_MSGSIZE, MEMF_CLEAR)) == NULL
      )
   {
      twmCleanUp();
      return FALSE;
   }
   else
   {
      /* the next two lines can be deleted if UnPostMe() is not required. */
      Delmsg->tmMessage.mn_ReplyPort = mp;
      Delmsg->tmMessage.mn_Node.ln_Type = NT_MESSAGE;

      Addmsg->tmMessage.mn_ReplyPort = mp;
      Addmsg->tmMessage.mn_Node.ln_Type = NT_MESSAGE;

      return (twmReady = TRUE);
   }
}

/* This function must be called by the client before it exits. */
twmCleanUp ()
{
   twmReady = FALSE;

   if (mp)        DeletePort(mp);
   if (Addmsg)    FreeMem(Addmsg, TWM_MSGSIZE);

   /* the next line can be deleted if UnPostMe() is not required. */
   if (Delmsg)    FreeMem(Delmsg, TWM_MSGSIZE);
}



SHAR_EOF
cat << \SHAR_EOF > twm.h
/* header/twm.h

   This is the same header file for both twm.c and twmClient.c. However, the
   intuition include is not needed for twmClient, and can be omitted to
   reduce compilation time.
*/

#include <intuition/intuitionbase.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/lists.h>

#define PORTNAME ("TinyWindowManager")

#define GADGNAMESIZE   17

#define GADGHGUTTER        18
#define GADGVGUTTER        10
#define GADGWIDTH         (GADGNAMESIZE << 3)
#define GADGHEIGHT         10

/* commands passed in twm_action field of a twmMessage */
#define TWM_ACTION_ADD      0
#define TWM_ACTION_DELETE   1

/* return codes passed back in same field */
#define E_OK                0
#define E_OPEN_INTUI      501
#define E_ALREADY_UP      502
#define E_OPEN_PORT       503
#define E_OPEN_WINDOW     504
#define E_ACTION_UNKNOWN  505
#define E_TASK_UNKNOWN    506
#define E_NO_MEM          507
#define E_ABANDON_SHIP    508


struct twmMessage {
   struct Message tmMessage;     /* Exec message structure     */
   char *tmName;                 /* the client's gadget name   */
   int tmAction;                 /* add or delete gadget       */
};

struct twmGadget {
   struct Gadget      tgGadget;                 /* the gadget for a client */
   struct Border      tgBorder;                 /* box around gadget       */
   struct IntuiText   tgIText;                  /* text in gadget          */
   char               tgName[GADGNAMESIZE];     /* string for Intuitext    */
   struct twmMessage *tgMessage;                /* msg to reply on click   */
   struct twmGadget  *tgMynext;                 /* my link to next gadget  */
};
SHAR_EOF
cat << \SHAR_EOF > PopCol.doc
PopColours 1.3 - September 1987

PopColours lets you change the Red/Green/Blue components of any colour
register, on any screen currently in the system.

PopColours has two modes - a large working window and a tiny window for when
the program is not in use. Switch between the two modes with the toggle
switch gadget. PopColours also supports Transactor's TWM standard for tiny
windows, so the tiny window rendering may not necessarily be used.

The PopColours working window has three proportional colour gadgets on its
instrument panel. These affect the colour of the indicated colour register.
To modify a colour register other than the one currently selected, use the up
and down arrows on the right of the control panel. When the colour register
value gets to the highest colour register available in the screen, it next
goes back to zero. You can hold the arrows down and they will auto-repeat.
when modifying the colour of the WorkBench screen, a square is drawn next to
the colour register indicator in the colour that's being changed (the current
colour register).

The "TOP SCREEN" message is both an indicator and a gadget. It indicates what
screen you are changing the colours of, and lets you select either the
topmost or the second screen. For example, if you started a task that opened
a new screen, then slid the screen down partially, revealing the WorkBench
screen, playing with PopColours would change the colours of the new, top
screen, not the WorkBench screen. This way you can modify the colours of just
about ANY program. With your new screen in place, you can click on the "TOP
SCREEN" message; it will switch to "SECOND SCREEN". This choice allows you to
change the colours of a non-slidable screen (like TextCraft): use left-Amiga
'N' to bring the WorkBench screen to the front, then slide it partially down
to see the other screen, and set PopColours to "SECOND SCREEN" mode. You can
then muck about with the colours of a program that doesn't give you the
option to do so. The poor program doesn't know what's going on! If you click
on "TOP SCREEN" when there isn't another screen, the message "(no 2nd
screen)" will appear for an instant, then "TOP SCREEN" will re-appear.

PopColours was written by Chris Zamara and Nick Sullivan.
(c) 1986, 1987 Transactor Publishing Inc. 
and AHA! (Acme Heuristic Applications!).
Freely distributable.
SHAR_EOF
cat << \SHAR_EOF > XE.doc
Documentation for XE - A mini-expression evaluator from Transactor Magazine

Code and docs freely distributable but
copyright (c) 1987 Transactor Publishing Inc.

XE is an expression evaluator - a handy little calculator to have around for
the odd bit of number-crunching you may need. It comes up in a window that
can be sized, dragged, re-ordered and closed.

Features of XE:
  * allows nested parentheses
  * has 26 variables that can be used in expressions
  * prints results in any number base
  * accepts numerical constants in decimal, hex, binary or any other base
  * can evaluate multiple expressions with a single command line
  * assignments to variables can be made within expressions

Limitations:
  * 32-bit integers only.
  * no checking for overflow.
  * the only operators supported are the four basic operations (+ - * /)
      plus the modulo operation (%) and assignment (=).

How to use XE:

XE doesn't work with a calculator keyboard, but allows you to enter
expressions in their normal algebraic form, for example:

  >2*(3+4)-2*4      (The '>' is XE's prompt)
  6                 (XE's answer)

Except for the assignment operation, which binds from right to left,
expressions are evaluated from left to right, with multiplication, division
and the modulo operation (*, / and %) taking precedence over addition and
subtraction (+ and -). 

XE allows single-letter variables, which can be assigned a constant or an
expression, and used in expressions. For example:

  >a=5
  5
  >2*a
  10
  >b=a+1
  6

Notice that a result is printed when a variable assignment is made. This is
because an assignment returns a value in an expression (as in C). So you
could do this:

  >25+3*(b=4*3)
  61
  >b
  12

The variable 'b' was assigned the value 3*4, and that value was used in
the expression. This allows you to do multiple-variable assignments:

  >a=b=c=d=e=x=0

XE will evaluate more than one expression at a time if you separate
the expressions by commas. This can be useful to print out the values
of several variables or results, e.g.:

  >a,b,c,a+b

XE can speak not only in decimal (base 10), but in any arbitrary base
up to base 36. After you select a new base using the syntax Bn, XE
will print all results in that base. For example, to work in hex:

  >B16
  New base: 16 (decimal)
  >23*10
  $E6
  >B24,21*10
  New base: 21 (decimal)
  21: AK

Notice that the notation for number bases higher than 16 extends hexadecimal
notation by using letters of the alphabet higher than F. In this case, the
'A' in AK means '10 * 21^1', and the K means '20 * 21^0'.

To enter your numbers in a different base, use the following prefixes:
$ - hexadecimal (base 16)
% - binary (base 2)
# - current output base

Example: add binary 100101110 to decimal 152 and print the result
in hexadecimal. Solution:

  >B16
  New base: 16 (decimal)
  >%100101110+152
  $1C6

When you're not actually calculating with XE, you can put it away temporarily
without actually closing it down by selecting "Tiny Window" from the menu.
The main XE window will close, and a conveniently small and inconspicuous
window will open in its place. Clicking anywhere in the tiny window (except
the drag bar or depth gadgets, of course) will close the tiny window and
bring the main XE window up instead.

XE also supports Transactor's TWM (Tiny Window Manager) program, so if you
have TWM running in your system when you select "Tiny Window" from the menu,
XE will be allotted a gadget (named "TransCalc") in the TWM window, and will
not bother to put up a tiny window of its own.






SHAR_EOF
cat << \SHAR_EOF > TWM.article
TWM - A Paneless Approach to Tiny Window Management

by Nick Sullivan

| This text is a slightly adapted version of an article from Volume 8, Issue 4
| of Transactor Magazine. This article and the TWM source code are freely
| distributable, but are copyright (c) 1987, Transactor Publishing Inc.

Most programs on the Amiga can be divided into three fairly tidy classes.
The commonest class consists of programs like DIR and LIST, that you invoke
as commands, that do their work then exit. Another class consists of
handlers, like the console handler ConMan, or PopToFront, from a few
Transactors ago. These programs, or their offspring, live in the system
usually until next reboot, but because they require no user interaction, they
are invisible.

Programs in the third class are the ones you interact with for an extended
period of time, such as text editors, terminal emulators and paint programs,
or that you might keep around for sporadic interaction, such as PopColours
and Structure Browser. One benefit of the Amiga's multitasking environment is
that you don't have to take such programs down in order to do something else.
You can switch readily from your editor to your terminal, for instance, and
keep your text in memory; you can switch from the terminal back to the editor
and stay on-line.

The extent to which you can take advantage of this capability depends, of
course, on how much RAM you have in your system in relation to the size of
the programs you're running. Even with a lot of expansion RAM, though, you
are still limited by the amount of available "chip RAM" - the special area
of memory that the Amiga's custom chips can use. On current Amigas, chip
RAM is limited to 512K and, while that sounds like a lot, it can quickly
get eaten up by programs that use lots of windows, colourful screens,
gadgets, and other display elements that need chip RAM to survive.

The other problem with running a lot of interactive programs simultaneously
is that they tend to crowd your monitor screen. That makes for a lot of
depth-arranging and resizing as you flit from one task to another - the
infamous "electronic shell-game" - and can get pretty tiresome if you have
to do a lot of it. A few programs even put up a full-size window and won't
allow you to get at the Workbench screen behind.

One approach that some programs have taken to relieve the on-screen
congestion has been to supply a "tiny window mode", which can be invoked when
the program is not in active use. This idea was arrived at independently
quite a while ago in at least two programs I know of - Rick Stiles' shareware
text editor Uedit, and Chris Zamara's PopColours. In Uedit particularly, use
of the tiny window (invoked by clicking on the editor's title bar) achieves a
significant savings in chip RAM. Using a normal 640 by 200 window on the
Workbench screen, which has two bit-planes, Uedit needs 32K for its bit-map,
plus a bit more for gadgets. Its tiny window, however, is a mere 100 by 20
pixels in size, and so consumes less than 600 bytes. Clearly, the chip RAM
penalty for running concurrent applications would be considerably eased if
the use of a tiny window mode was more widespread.

A tiny window consists of no more than an inch or two of title bar with an
equivalent thickness of empty window beneath. It is draggable, and may be
depth arranged (since part of its purpose is to keep the application that
owns it out of your hair), but is not resizable. Clicking in the empty part
reactivates the parent program, prompting it to take the tiny window down,
put its working window (or screen) back up, and carry on with business as
usual.

One reason for this article is to advocate the use of tiny windows in
programs - including commercial programs - in which their use is appropriate
(for one approach to implementing a tiny window mode see the listings for
"TWM" and "Test1"). Suppose this idea *were* generally adopted,
though, making it easier to run several such programs concurrently. Now the
user has another problem: the new disorder of TWL (Tiny Window Litter), in
which one's visible workspace is obscured by annoying swarms of tiny windows
that continually seem to be getting in your way as you work, no matter how
much you try to shuffle things around.

So the other reason for this article is to present TWM, for 'Tiny Window
Manager', a small and easily implemented piece of code that enables programs
to support a tiny window mode while giving users a method of avoiding the
anguish of TWL, and the consequent disruption of their lives.

From the user's point of view, TWM is a kind of central storage compartment
in which sleeping programs are housed, and from which they can be activated.
The programs do not have to maintain any display of their own - not even a
tiny window - so the user's screen is free from clutter. TWM's own working
window contains gadgets bearing the names of its "client programs". When the
user clicks on one of these gadgets, the corresponding client program is
awoken and resumes operation. TWM also has its own tiny window mode; when
that is in use, the amount of chip RAM jointly consumed for windows by the
client programs and TWM itself is very small. When the system is hosting two
or more applications that support TWM, there is a significant savings in both
resources and convenience. Of course, even if TWM is not running,
applications that support it will run normally - but instead of disappearing
entirely when they go to sleep, they will put up a tiny window in the usual
way.

From the programmer's point of view, TWM comes in two parts - the program TWM
itself, and a short C-language module called twmClient.c. The twmClient code
can be compiled and linked with any application that supports a tiny window
mode. Let us suppose that this client application has been running in its
active mode but now, as a result of some action of the user's (perhaps a menu
selection, perhaps clicking on a gadget) it has taken down its working
display and is about to put up its tiny window and go to sleep.

Before taking that step, the application now calls the function PostMe() in
the twmClient module, passing as an argument the name by which it would like
to be known, as in:

   PostMe("PopColours");

PostMe(), in its turn, searches the system for a public message port with
the name "TinyWindowManager". If the search succeeds, PostMe() sends a
message to that port with the name of the client, and waits at its own
message port for a reply. Effectively, the client application has now let
itself go to sleep and, because it has closed its working window, there are
no visible signs of its existence.

The message sent by the client is now picked up by the TWM program, which the
user has earlier run, and which is now displaying one of its own windows
(either the tiny window or the larger working window) on the user's
screen. On receipt of the message TWM creates a gadget bearing the client
application's name. The gadget will be displayed in TWM's working window
(immediately, if that window is up). There may be other gadgets in the window
also - one for each client application. This is the only indication that the
clients still exist and, when TWM is in its tiny window mode, there is no
sign of them at all. Chip RAM is conserved, and the user's window is
uncluttered. When the user later clicks on the gadget, TWM replies to the
message the client sent, deletes the gadget, then forgets about the client
altogether.

Back now to PostMe(), waiting asleep at its message port for a reply to its
message. The reply has finally come, signifying that the user has selected
the client's gadget in the TWM working window, and wants the client to put
up its own working window again. PostMe() now returns to the client, with
the value TRUE, and the client goes back to work.

Several things might have gone wrong along the way. The most probable of
these is that the user may not currently have TWM running. A remoter
possibility is that TWM might have failed to allocate memory for the client's
gadget, or could not open a window. In all these cases, PostMe() returns
FALSE to the client, who then knows that it is necessary to put up a tiny
window of its own after all.

As you will see in the code, there are other details. In case the client
application wishes to wake itself up (in response to a time-out or some other
kind of message) while it is in TWM's care, an UnPostMe() function is also
provided. Most clients won't need UnPostMe(); in that case, the programmer
can remove UnPostMe() from twmClient.c to shrink the code even further.
Another detail is that TWM remembers where the user last placed its windows,
and restores them to the chosen position each time they are re-opened. Uedit
and PopColours also have this feature, and it is recommended that other tiny
window programs include it.

The intent of TWM is to institute a standard of which all tiny window
programs can take advantage. Therefore all the code is freely
redistributable, and may be used in any program - PD, shareware, or
commercial.


SHAR_EOF
#	End of shell archive
exit 0