[net.micro.amiga] Fun with gadgets

crunch@well.UUCP (John Draper) (12/12/85)

                    PHUN WITH GADGETS - John T. Draper
                    ----------------------------------
     
  Feel free to distribute this information onto any network.   Please
check first to avoid duplication.   Would at least like credit for the
20 hours or so I took in preparing this section.

  Gadgets are multi function input "devices" which run under Intuition.  There
are four kinds,  and two flavers.

The flavers are:

1) System gadgets - Those created for you when you create a window or screen

2) User gadgets - Those gadgets YOU create and use in your program.   There
   are four kinds of these.


The four kinds are:

1) Boolean Gadgets - Usually "Yes/no" desisions,  "On/Off" buttons,  and
   other related uses.    They look like rectangles,  with text displayed
   (Or rendered), within those rectangles.

2) Proportional Gadgets - Those which look like "Sliders" as displayed in
   the "R G B" Controls in the "Preferences" program.   I think Amiga should
   release the source to Preferences,  because It would be a very good example
   program.

3) String gadgets - These allow text entry through the Keyboard.  Text can be
   automatically centered.

4) Integer Gadget - A special form of string gadget that converts numeric text
   input into integers.


USING GADGETS
-------------

  Each gadget has a data structure "Gadget",  which must first be declared
and initialized.   There are several ways to do this.    The easiest way is
to declare and initialize them like this:

     
#define BLUE_GADGET 0         /* My own Gadget ID to mean this gadget */
struct Gadget blue_gad = {
   NL, 17,112, 150,11, GADGHCOMP, GADGIMMEDIATE | RELVERIFY,
   PROPGADGET,(APTR)&b_img, NL,
   &btxt, NL,(APTR)&b_prop, BLUE_GADGET, NL
};
     
     
#define GREEN_GADGET 1        /* Another personal ID */
struct Gadget green_gad = {
   &blue_gad, 17,97, 120,11, GADGHCOMP, GADGIMMEDIATE | RELVERIFY,
   PROPGADGET,(APTR)&g_img, NL,
   &gtxt, NL,(APTR)&g_prop, GREEN_GADGET, NL
};
     
     
#define RED_GADGET 2          /* And yet another gadget */
struct Gadget red_gad = {
   &green_gad, 17,82, 90,11, GADGHCOMP, GADGIMMEDIATE | RELVERIFY,
   PROPGADGET,(APTR)&r_img, NL,
   &rtxt, NL,(APTR)&r_prop, RED_GADGET, NL
};
     
     
#define TEX_GAD 3
struct Gadget tex_gad = {
   &red_gad, 10,10, 150,11, GADGHCOMP, STRINGCENTER | LONGINT,
   STRGADGET, NL, NL,
   NL, NL, (APTR)&TexString, TEX_GAD, NL
};
     
   The #define BLUE_GADGET 0,  etc,  are my own personal numbers that I
want to use to identify these gadgets in my very own personal way.   I am
using them for identification and to take action when the gadget is chosen
via a C "Switch" statement.

   Each "Element" in the structures declared above are separated by commas
(For those not familiar with C),  and will be explained in detail.   In the
above examples are Three Proportional Gadgets,  and a Text Gadget.  A more
detailed description of each of the gadget fields are listed below.   It is
pretty well explained in the Intuition manual.    Just a lack of examples
showing how to build and detect them.

-- (NextGadget) First element is the "link" to the next gadget.  Last gadget
   has a Null (NL) for a link.   Note,  the last gadget is on the top.  The
   LAST Gadget should be set to NULL (NL).

-- (LeftEdge, TopEdge) - Specifies the LOCATION of the gadget relative to the
   window or screen,  in the above example,  "tex_gad.LeftEdge" has the
   value of 10.  The last gadget has LeftEdge = 17, TopEdge = 112 as shown
   above.

-- (Width, Height) - Specifies the Width and the height of the gadget select
   box.  In the above example,  the height remained the same,  and the Width
   progressively got smaller.  Width = 150, Height = 11 on the last one,
   then Width = 120, then eventually 90, See above.

-- (Flags) - Flags that you share with Intuition.to describe the appearance
   and behavior of the gadget.   GADGHCOMP is a flag that complements all
   of the bits contained within the gadget's select box.

-- (Activation) - Other flags that are used to activate certain features of
   the gadget.  GADGIMMEDIATE | RELVERIFY mean that we set BOTH those "Bits".

-- (GadgetType) - Describes the type of gadget.   In the example above,
   STRGADGET is one of the four types of gadgets that describe "string" or
   "Text".

-- (GadgetRender) - Points to an intuiImage.   This is what gets written onto
   screen or window as the gadget.  In the case where we use the proportional
   gadget, we supply the address of the Image Structure without initializing
   it.   It then gets rendered as a rectangle.

-- (SelectRender) - In this example, we put a NULL (NL) in this field.   If
   we wanted, we could put in another image.  This image would be displayed
   during highlighting (When mouse button is pressed in the gadget's select
   box).

-- (GadgetText) - A pointer to an "Intuitext" structure that describes any
   text that might be rendered when the gadget is rendered.

-- (MutualExclude) - In this example, we place a NULL (NL).  There are 32
   bits that can be used to "Turn off" other gadgets if this gadget gets
   selected.

-- (SpecialInfo) - A pointer to more information about proportional (PropInfo)
   and text (StringInfo).
   gadgets.

-- (GadgetID) - User definable ID Field.   It can be used to identify the
   gadget from the gadget pointer.   I use it as an argument to the
   "switch" statement,  because using an address (Or any large number) as
   arguments to the "switch" statement will cause the system to crash.
   It's a bug in the Lattice C compiler.

-- (UserData) - A pointer to data that can be of any user definable
   structure or data.

   There are other structures needed for gadgets which are "SpecialInfo"
types of structures.   They are: PropInfo, and StringInfo structures.
These describe in detail more information on Proportional structures
and Text structures.   Addresses of these structures will be put in the
"SpecialInfo" field of the gadget structure.   For instance,  in the
example above where the gadgets are declared and initialized, (APTR)&r_img
is used in the "red_gad" declaration.   Where "r_img" is declared as follows:

struct Image    r_img, g_img, b_img;

   In this particular example,  these are Images used by Intuition.  Sometimes
YOU have to declare your own image.   Other times,  such as in the case above,
Intuition initializes these images.   On first reading of the Intuition manual,
it is not evident that These are initialized.


     

PropInfo Structure:
-------------------

   A PropInfo structure contains specific information about a proportional
gadget.   Such as a slider arm,  or a volume control.

-- (flags) - A set of flags like:

   AUTOKNOB - If you want to use a generic default knob.   It's essentually
   a rectangle that fills the body of the proportional gadget.   If you set
   this flag,  Intuition will initialize the rest of this structure.
   A piece of code in the Example GAD.C shows where Three proportional
   structures are initialized.   These structures are named:
   "r_prop", "g_prop", and "b_prop".

/***************************************************************************
           Must Initialize the Proportional "Specialinfo" before
      opening up the window.   Init Flags,  and position.
***************************************************************************/
     
   r_prop.Flags = g_prop.Flags = b_prop.Flags = FREEHORIZ | AUTOKNOB;
   r_prop.HorizBody = g_prop.HorizBody = b_prop.HorizBody = 0x1000;
   r_prop.HorizPot = g_prop.HorizPot = b_prop.HorizPot = 0x8000;
     

   FREEHORIZ - Set this flag if you only want the "slider" or "Pot" to
   slide horizontally.

   FREEVERT - Set this flag if you want to slide vertically only.

   PROPBORDERLESS - Set this flag if you don't want borders.

   KNOBHIT - Check this flag if you want to know if the mouse button is
   on the knob.

   For example,  if you want a standard slider which is horizontally placed
in the window,  set the flags:  AUTOKNOB | FREEHORIZ

   If you are using a custom gadget, for instance a "joystick" that can move
BOTH horizontally and vertically like the source code example in this lesson.
then set the flags:  FREEHORIZ | FREEVERT.

-- (HorizPot) - Horizontal Position value for the gadget.

-- (VertPot)  - Vert Position value for the gadget.

-- (horizBody) - The incremental width of the gadget.   If set to 0x1000,
   means that the pot will move 1/16th of the full position if the mouse
   was pressed between the slider and the side of the gadget.   it always
   moves "towards" the point where the mouse was pressed.  The "joystick"
   custom gadget in the example sort of "Follows" the mouse.

-- (VertBody) - Vertical incremental width of the gadget.

   The following fields are set by Intuition and are accessable to the
user if needed.

-- (cWidth) - Container width.

-- (cHeight) - Container height.

-- (HPotRes, VPotRes) - Horiz and Vert increments.

-- (LeftBorder, TopBorder) - Container borders.

   Here is a PropInfo structure for a custom designed joystick.

struct PropInfo cust_prop = {
   FREEHORIZ | FREEVERT,            /* Want knob to go both vert and horiz */
   0x8000,  0x8000,                 /* Want knob to be centered initially  */
   0x800,   0x800,                  /* Smallest increment the knob can move */
   150, 50,                         /* cWidth, cHeight - Container w & h */
   1, 1,                            /* HPosres, VPotres - Pot increments */
   0, 0                             /* Container borders  */
};
     
   You can stick this anywhere in the global declarations section of your
program.  Because of the limitations of the Lattice C compiler,  It should
be declared "Before" the Gadget structure that uses it.

   An important thing to remember about constructing gadgets is that the
Coordinates in the Gadget structure are relative to the coordinates in the
Window or Screen.

   The coordinates in the border, or intuitext structure used by gadgets
are RELATIVE to the Gadgets and NOT the window.   For instance,  if you
want text to be displayed OVER the gadget rectangle,  your Y coords will
be about -8 to -10,  while your x coords will be positive.   The point where
text is written is at the UPPER LEFT section of the letters and NOT the
lower left portion of the letters like in the Macintosh.

   If you use borders around your gadgets, make sure you make room for
at least one pixel around the "Selection box".   Because, when the user
presses in the box,  it is inversed, and if the border coincides with
a pixel in the selection box,   that pixel overlaps and turns into
another color, thus making it look a bit cludgey.


     
HOW TO CREATE GADGETS IN YOUR PROGRAM
-------------------------------------

   FIRST OFF - Sketch out an approximate picture showing approximately
what the overall layout of the gadgets should look like.    Eventually,
we will write a Gadget Editor,   but for now,   lets make one up the
old fashion way.

   Define gadget structures for each gadget.   Set the gadget flags
appropriately,  specify pointers to Special structures, like "PropInfo"
or "StringInfo" structure in the "SpecialInfo" field.   If you have
custom images that you have created,   you need to declare a pointer
to the image.

   When you declare the gadgets,  you create the LAST gadget first in
your Source code.   Each sucessive gadget then "Links" to the one in
front of it.   The last gadget pointer is a NULL.   After each of the
gadgets are linked properly,  then:

   Declare any custom images,  borders,  or Text associated with the
gadget.   This section of code shows an example of this.   Remember,  this
portion of code should come BEFORE any of the gadget declarations because
the C compiler doesn't seem to be able to forward reference.

/*  Image for a custom proportional gadget */
     
UWORD custimage[] = {
0x0000, 0x0000, 0x0180, 0x0660, 0x1818, 0x2004, 0x4002, 0x4002,
0x4002, 0x4002, 0x2004, 0x1818, 0x0660, 0x0180, 0x0000, 0x0000
};
     
struct Image cus_image = {
  0, 0,                    /* LeftEdge, TopEdge */
  16, 16, 1,               /* Width, Height, Depth */
  &custimage[0],           /* Pointer to bit image */
  1,  0,                   /* PlanePick, Planeonoff */
  NULL                     /* No other images */
};
     
One of the gadgets that use the above image:

#define CUST_KNOB 4
struct Gadget cust_knob = {
   &tex_gad, 17, 140, 150, 50, GADGHCOMP, GADGIMMEDIATE | RELVERIFY,
   PROPGADGET, (APTR)&cus_image, NL,
   &cus2_txt, NL, (APTR)&cust_prop, CUST_KNOB, NL
};

   Now,  we have to make sure the very first gadget is linked to our window
OR SCREEN.   In this particular example,  we are only using a window,  and
are using the Intuition WorkBench Screen.   The NewWindow structure is declared
further down in the Code as shown below.   Making the assumption that the
gadget shown above is the FIRST gadget (last in listing),  then the New
Window structure is shown as below.

/***************************************************************************
*                  N E W     W I N D O W     S T R U C T U R E
***************************************************************************/
     
struct NewWindow nw = {
  0, 0,                  /*  Start position                               */
  320, 200,              /*  width, height,                               */
  0, 1,                  /*  detail, block pens                           */
  CLOSEWINDOW            /*  IDCMP flags                             */
| REFRESHWINDOW
| MOUSEBUTTONS
| MOUSEMOVE
| GADGETDOWN     <--  MUST SET THIS ONE
| GADGETUP,      <--  AND THIS ONE
                         /*  Regular flags for gadgets and such           */
  WINDOWDEPTH
| WINDOWSIZING
| WINDOWDRAG
| REPORTMOUSE    <-- AND THIS ONE
| WINDOWCLOSE
| SMART_REFRESH,
     
  &cust_knob,            /* First gadget in list  <-- WE DO IT HERE       */
  NULL,                  /* User checkmark                                */
  "Fun with Gadgets",    /* Window Title                                  */
  NULL,                  /* Pointer to screen (Set later)                 */
  NULL,                  /* Pointer to superbitmap                        */
  0, 0, 320, 186,        /* Ignored because not sizeable                  */
  WBENCHSCREEN,          /* Using the Workbench screen                    */
  };
     
   The example program given uses a different First Gadget.

   The last thing you should do is set the appropriate flags.   That is
shown above.

RESPONDING TO THOSE GADGETS
---------------------------

   Last but not least,  you need to be able to respond, read, or act on
a gadget.   This is done by "Listening" to a gadget port.   A particular
bit comes to you in a Message class field.   You must recieve this "message"
from Intuition telling you that the user "clicked" the mouse in a particular
gadget.   You don't know which one yet.   But you snatch that "message"
by detecting a certain bit in the message.class field.   You tuck this
value away for later use and "Reply" to the message, using the
ReplyMsg function.  We keep doing this over and over until the user
closes the window.   This is called an "Event loop".


/***************************************************************************
                            MAIN EVENT LOOP
***************************************************************************/
     
   for (;;)
   {
     
      if (message = (struct IntuiMessage *)GetMsg(w->UserPort))  {
        MessageClass = message->Class;
        code = message->Code;
        ReplyMsg(message);
        switch (MessageClass) {
     
           case GADGETUP    :
           case GADGETDOWN  : do_gadgets(message, w);
                              break;
     
           case CLOSEWINDOW : close_things();
                              exit(0);
                              break;
           case MOUSEBUTTONS: break;
        }   /* Case */
      }  /* if */
   }  /* for */
} /* main */
     
   In the Message,  contains the Address of the gadget chosen by the user.
You pass the Message and the Window into a function called do_gadgets.
This function identifies the gadget,  then acts on it in the appropriate
way.    This function is listed below.   I basically grab the address of
the particular structure,  then look for that special private code that
I described to identify the gadget.

/***************************************************************************
                           HANDLE THE GADGETS
***************************************************************************/
do_gadgets (mes, win)
     
struct IntuiMessage *mes;        /* Pointer to Message structure */
struct Window *win;              /* And a window structure       */
{
        struct Gadget *igad;     /* Ptr to gadget that Intuition found    */
        int  gadgid;             /* ID Code identifying which gadget      */
        ULONG val;
     
     igad = (struct Gadget *) mes->IAddress;      /* Ptr to a gadget      */
        gadgid = igad->GadgetID;    /* My own personal code for this gad  */
        val = (ULONG)TexString.LongInt;
        switch(gadgid) {
     
            case GREEN_GADGET: break;
            case BLUE_GADGET : break;
            case TEX_GAD     : printf("got here ...\n");
                               printf("val = %ld\n", val);
                               break;
        }
}
     
As each gadget is identified, a CASE statement selects the appropriate
action to be taken depending on the gadget.   In many cases, the SAME
action is usually performed on similar gadgets,   but in the above
example,  I only handle 3 gadgets.   I could have exampled more,  but
Good ol Lattice C has a nasty bug that won't let me use four cases
without crashing.   This is why it takes so long to write programs on the
Amiga.   But thats the price for being a pioneer.

   Feel free to chop it up and hack it to death.   So gotta work on
Menus tommorrow.   Only have the Amiga for 5 more days.   Next on
my aggenda for learning is:

Menus

Requestors

Sound

   This is it for now.   Have fun hacking,  and I will be back with more
later.   Feel free to ask me questions,  and tell me your problems
with gadgets so we can solve them.
     


     


     

dale@amiga.UUCP (Dale Luck) (12/13/85)

In article <338@well.UUCP> crunch@well.UUCP (John Draper) writes:
>/***************************************************************************
>                            MAIN EVENT LOOP
>***************************************************************************/
> 
>   for (;;)
>   {
> 
>      if (message = (struct IntuiMessage *)GetMsg(w->UserPort))  {
>        MessageClass = message->Class;
>        code = message->Code;
>        ReplyMsg(message);
>        switch (MessageClass) {
>        }   /* Case */
>      }  /* if */
>   }  /* for */
>} /* main */
> 
   This is similar to the loop I had in the dot.c program
   If your program does need to do anything while waiting for
   messages though you should use

	for (;;)
	{
		Wait(1<<w->UserPort->mp_SigBit);	/* wake up when message there */
		if (message = (......
			etc.
	}

	This will be much more fair to those other programs and to intuition
	as well.

dale@amiga.UUCP (Dale Luck) (12/13/85)

Sorry for posting this again but I forgot and important NOT

In article <394@amiga.amiga.UUCP> dale@tooter.UUCP (Dale Luck) writes:
>   This is similar to the loop I had in the dot.c program
>   If your program does NOT need to do anything while waiting for
>   messages though you should use

In general busy waiting is not fair in the multitasking amiga.  The
schedular does not change your priority since this was meant to be
a realtime multitasker. This means programs must cooperate a little.