/** ** *** MakeRev Header *** **
**
**  ** PopUpMenu Gadget Class - BOOPSI gadget class. **
**
**  Copyright © 1993 Markus Aalto
**
**  Creation date: 09-Dec-93
**
**  ------------------------------------------------------------------
**  $Filename: PopUpMenuClass.c $
**  $Revision: 1.0 $
**  $Date: 23-Dec-93 $
**
**  $Author: Markus Aalto $
**  $Comment: Distributed under the GNU General Public Licence. $
**
*/

/* Make sure everything is added from PopUpMenuClass.h */
#define POPUPMENUCLASS_PRIVATE  1

#include    "PopUpMenuClass.h"
#include    <string.h>
#include    <math.h>

/* Version. */
static UBYTE popupmenuclass_version[] = "$VER: PopUpMenuClass 1.0 (23.12.93)";

/* STATIC Protos for real Method functions and for class dispatcher. */
static ULONG    __saveds __asm PUMG_Dispatcher( register __a0 Class *cl,
                                                register __a2 Object *o,
                                                register __a1 Msg msg);

static ULONG    PUMG_NEW( Class *cl,
                          Object *o,
                          struct opSet *ops );

static ULONG    PUMG_DISPOSE( Class *cl,
                              Object *o,
                              Msg msg );

static ULONG    PUMG_SET( Class *cl,
                          Object *o,
                          struct opSet *ops );

static ULONG    PUMG_GET( Class *cl,
                          Object *o,
                          struct opGet *opg );

static ULONG    PUMG_UPDATE( Class *cl,
                             Object *o,
                             struct opUpdate *opu );

static ULONG    PUMG_NOTIFY( Class *cl,
                             Object *o,
                             struct opUpdate *opu );

static ULONG    PUMG_RENDER( Class *cl,
                             Object *o,
                             struct gpRender *gpr );

static ULONG    PUMG_GOACTIVE( Class *cl,
                               Object *o,
                               struct gpInput *gpi );

static ULONG    PUMG_HANDLEINPUT( Class *cl,
                                  Object *o,
                                  struct gpInput *gpi );

static ULONG    PUMG_GOINACTIVE( Class *cl,
                                 Object *o,
                                 struct gpGoInactive *gpgi );

/* Protos for static help functions. */

static void     PUMG_MakeCheckings( PUMGData *PD );

static void     PUMG_GetGadgetRect( Object *o,
                                    struct GadgetInfo *gi,
                                    struct Rectangle *rect );

static void     PUMG_DrawPopupWindow( PUMGData *PD,
                                      struct DrawInfo *dri,
                                      ULONG From,
                                      LONG Count );

static void     PUMG_DrawFrame( struct Window *win,
                                int order,
                                struct DrawInfo *dri,
                                UBYTE *name,
                                struct TextFont *tf,
                                BOOL Active );

static void     PUMG_DrawPopupMenuImage( struct RastPort *RP,
                                         ULONG Pen1,
                                         ULONG Pen2,
                                         ULONG Left,
                                         ULONG Top,
                                         LONG Height );

/*******************************************************************/
/*******************************************************************/
/* The real code starts here.                                      */
/*******************************************************************/
/*******************************************************************/

/* Global Create PopUpMenuClass function. */

Class *CreatePopUpMenuClass()
{
    Class *cl;

    cl = MakeClass( NULL, GADGETCLASS, NULL, sizeof(PUMGData), 0);
    if( cl ) {
        cl->cl_Dispatcher.h_Entry = (HookFunction)PUMG_Dispatcher;
    }

    return( cl );
}

/* Global Free PopUpMenuClass function. */
BOOL DisposePopUpMenuClass( Class * cl )
{
    return( FreeClass( cl ) ) ;
}


/*******************************************************************/
/*******************************************************************/
/* PopUpMenu specific class code.                                  */
/*******************************************************************/
/*******************************************************************/

static ULONG __saveds __asm PUMG_Dispatcher( register __a0 Class *cl,
                                             register __a2 Object *o,
                                             register __a1 Msg msg)
{
    ULONG retval;

    switch( msg->MethodID )
    {
    case OM_NEW:
        retval = PUMG_NEW(cl, o, (struct opSet *)msg );
        break;
    case OM_DISPOSE:
        retval = PUMG_DISPOSE( cl, o, msg );
        break;
    case OM_SET:
        retval = PUMG_SET(cl, o, (struct opSet *)msg );
        break;
    case OM_GET:
        retval = PUMG_GET( cl, o, (struct opGet *)msg );
        break;
    case OM_UPDATE:
        retval = PUMG_UPDATE( cl, o, (struct opUpdate *)msg );
        break;
    case OM_NOTIFY:
        retval = PUMG_NOTIFY( cl, o, (struct opUpdate *)msg );
        break;
    case GM_RENDER:
        retval = PUMG_RENDER( cl, o, (struct gpRender *)msg );
        break;
    case GM_GOACTIVE:
        retval = PUMG_GOACTIVE( cl, o, (struct gpInput *)msg );
        break;
    case GM_HANDLEINPUT:
        retval = PUMG_HANDLEINPUT( cl, o, (struct gpInput *)msg );
        break;
    case GM_GOINACTIVE:
        retval = PUMG_GOINACTIVE( cl, o, (struct gpGoInactive *)msg );
        break;
    default:
        retval = DoSuperMethodA(cl, o, msg);
        break;
    }


    return(retval);
}

static ULONG PUMG_NEW( Class *cl,
                       Object *o,
                       struct opSet *ops )
{
    Object *object;
    PUMGData *PD;

    object = (Object *)DoSuperMethodA( cl, o, (Msg)ops );
    if( object ) {
        PD = INST_DATA( cl, object );

        PD->Labels = (struct List *)GetTagData( PUMG_Labels, NULL, ops->ops_AttrList );
        PD->Active = GetTagData( PUMG_Active, 0, ops->ops_AttrList );

        PUMG_MakeCheckings( PD );

        PD->Font = (struct TextFont *)GetTagData( PUMG_TextFont, NULL,
            ops->ops_AttrList);

        PD->FrameImage = (struct Image *)NewObject(NULL,"frameiclass",
                IA_Recessed,    FALSE,
                IA_EdgesOnly,   FALSE,
                IA_FrameType,   FRAME_BUTTON,
                TAG_END);
        if( PD->FrameImage == NULL ) {
            CoerceMethod(cl, o, OM_DISPOSE);
            object = NULL;
        }
    }

    return( (ULONG)object );
}


static ULONG PUMG_DISPOSE( Class *cl,
                           Object *o,
                           Msg msg )
{
    PUMGData *PD = INST_DATA( cl, o );

    if( PD->popup_window ) {
        CloseWindow( PD->popup_window );
    }

    if( PD->FrameImage ) DisposeObject( PD->FrameImage );

    return( DoSuperMethodA(cl, o, msg) );
}


static ULONG PUMG_SET( Class *cl,
                       Object *o,
                       struct opSet *ops )
{
    ULONG retval;
    PUMGData *PD = INST_DATA( cl, o );
    struct TagItem *tag, notify;
    UWORD old_active;

    retval = DoSuperMethodA( cl, o, (Msg)ops );

    /* I decided that it would be best that the values which are
    ** specific to this class, could bot be changed when we have
    ** our PopUpMenu window opened. */
    if( (ops->ops_AttrList != NULL) && (PD->popup_window == NULL) ) {
        tag = FindTagItem( PUMG_Labels, ops->ops_AttrList );
        if( tag ) {
            PD->Labels = (struct List *)tag->ti_Data;
            retval = TRUE;
        }

        old_active = PD->Active;

        tag = FindTagItem( PUMG_Active, ops->ops_AttrList );
        if( tag ) {
            PD->Active = tag->ti_Data;
            retval = TRUE;
        }

        PUMG_MakeCheckings( PD );

        tag = FindTagItem( PUMG_TextFont, ops->ops_AttrList );
        if( tag ) {
            PD->Font = (struct TextFont *)tag->ti_Data;
            retval = TRUE;
        }

        if( old_active != PD->Active ) {
            /* We send ourselves a OM_NOTIFY message, which will
            ** eventually be broadcasted as OM_UPDATE message
            ** to the target object. Note that we don't send it
            ** simply to our parent, but to ourselves, so if
            ** we have a children which needs to add it's own
            ** data it will be added. */
            PUMG_SetTagArg( notify, TAG_END, NULL );
            (VOID)DoMethod( o, OM_NOTIFY, &notify, ops->ops_GInfo, 0 );
        }
    }

    return( retval );
}

static ULONG PUMG_GET( Class *cl,
                       Object *o,
                       struct opGet *opg )
{
    ULONG retval;
    PUMGData *PD = INST_DATA( cl, o );

    switch( opg->opg_AttrID )
    {
    case PUMG_Labels:
        *opg->opg_Storage = (ULONG)PD->Labels;
        retval = TRUE;
        break;
    case PUMG_Active:
        *opg->opg_Storage = (ULONG)PD->Active;
        retval = TRUE;
        break;
    case PUMG_TextFont:
        *opg->opg_Storage = (ULONG)PD->Font;
        retval = TRUE;
        break;
    default:
        retval = DoSuperMethodA(cl, o, (Msg)opg);
        break;
    }

    return( retval );
}


static ULONG PUMG_UPDATE( Class *cl,
                          Object *o,
                          struct opUpdate *opu )
{
    ULONG retval;
    PUMGData *PD = INST_DATA( cl, o );
    struct TagItem *tag, notify;
    struct RastPort *rp;

    retval = DoSuperMethodA(cl, o, opu);

    /* Update only if gadget isn't currently manipulated. */
    if( PD->popup_window == NULL ) {
        if( opu->opu_AttrList ) {
            tag = FindTagItem( PUMG_Active, opu->opu_AttrList );
            if( tag ) {
                if( tag->ti_Data != PD->Active ) {
                    PD->Active = tag->ti_Data;

                    PUMG_MakeCheckings( PD );

                    rp = ObtainGIRPort( opu->opu_GInfo );
                    if( rp ) {
                        DoMethod( o, GM_RENDER, opu->opu_GInfo, rp, GREDRAW_UPDATE );
                        ReleaseGIRPort( rp );
                    }

                    /* Notify the change. */
                    PUMG_SetTagArg( notify, TAG_END, NULL );
                    (void)DoMethod( o, OM_NOTIFY, &notify, opu->opu_GInfo, 0 );
                }
            }
        }
    }

    return( retval );
}


static ULONG PUMG_NOTIFY( Class *cl,
                          Object *o,
                          struct opUpdate *opu )
{
    struct TagItem tags[3];
    PUMGData *PD = INST_DATA( cl, o );

    PUMG_SetTagArg(tags[0], GA_ID, ((struct Gadget *)o)->GadgetID);
    PUMG_SetTagArg(tags[1], PUMG_Active, PD->Active );

    /* If there are no previous tags in OM_NOTIFY message, we
    ** add them there as only ones. Otherwise we tag previous
    ** tags to the end of our tags. Got it? :')
    */
    if( opu->opu_AttrList == NULL ) {
        PUMG_SetTagArg(tags[2], TAG_END, NULL);
    }
    else PUMG_SetTagArg(tags[2], TAG_MORE, opu->opu_AttrList );

    return( DoSuperMethod(cl, o, OM_NOTIFY, tags, opu->opu_GInfo, opu->opu_Flags) );
}

static ULONG PUMG_RENDER( Class *cl,
                          Object *o,
                          struct gpRender *gpr )
{
    ULONG retval, State;
    struct Gadget *gad = (struct Gadget *)o;
    struct Rectangle rect;
    struct DrawInfo *dri;
    struct IBox container;
    struct Node *node;
    struct TextExtent temp_te;
    struct RastPort *RP = gpr->gpr_RPort;
    UWORD BorderWidth, BorderHeight, TextWidth;
    UWORD patterndata[2] = { 0x2222, 0x8888 };
    ULONG TextPen, ImagePen1, ImagePen2;
    PUMGData *PD = INST_DATA( cl, o );

    retval = DoSuperMethodA(cl, o, gpr);

    /* Get real Min and Max positions. */
    PUMG_GetGadgetRect( o, gpr->gpr_GInfo, &rect );

    /* Calculate real dimensions. */
    container.Left = rect.MinX; container.Top = rect.MinY;
    container.Width = 1 + rect.MaxX - rect.MinX;
    container.Height = 1 + rect.MaxY - rect.MinY;

    dri = gpr->gpr_GInfo->gi_DrInfo;

    if( gad->Flags & GFLG_DISABLED ) {
        State = IDS_DISABLED;
    }
    else if( gad->Flags & GFLG_SELECTED ) {
        State = IDS_SELECTED;
    }
    else State = IDS_NORMAL;

    /* Frame rendering goes here. */
    SetAttrs( PD->FrameImage,
            IA_Left,    container.Left,
            IA_Top,     container.Top,
            IA_Width,   container.Width,
            IA_Height,  container.Height,
            TAG_END);

    DrawImageState( RP, PD->FrameImage, 0, 0, State, dri);

    if( dri ) {
        TextPen = dri->dri_Pens[TEXTPEN];
        ImagePen1 = dri->dri_Pens[SHINEPEN];
        ImagePen2 = dri->dri_Pens[SHADOWPEN];
    }
    else { /* If for some unknown reason Drawinfo is NULL then we
           ** Use these predefined values, which should work atleast
           ** for current OS releases. */
        TextPen = ImagePen2 = 1;
        ImagePen1 = 2;
    }

    /* Draw the PopupMenu Image. */
    PUMG_DrawPopupMenuImage( RP, ImagePen1, ImagePen2,
        5LU + container.Left, 2LU + container.Top, -5L + container.Height);

    /*******************************/
    /* Text rendering starts here. */
    /*******************************/

    /* Do we have a proper font. */
    if( PD->Font == NULL ) {
        /* If not we use the font we have in RastPort. */
        PD->Font = RP->Font;
    }
    else SetFont( RP, PD->Font );

    /* Check if we have nothing to print. */
    if( PD->Count > 0 ) {
        ULONG len, i = 0;
        char *label_name;

        node = PD->Labels->lh_Head;
        while( node->ln_Succ ) {
            if( i == PD->Active ) {
                label_name = node->ln_Name;
                if( label_name ) {
                    len = TextFit( RP, label_name, (ULONG)strlen(label_name),
                        &temp_te, NULL, 1, (ULONG)container.Width - 28,
                        (ULONG)( 1 + PD->Font->tf_YSize ) );

                    TextWidth = 1 + temp_te.te_Extent.MaxX - temp_te.te_Extent.MinX;

                    SetAPen(RP, TextPen);
                    Move( RP, (LONG)( 10 + container.Left + (container.Width - TextWidth)/2
                        - temp_te.te_Extent.MinX ), (LONG)
                        PD->Font->tf_Baseline + (1 + container.Top + rect.MaxY
                        - PD->Font->tf_YSize)/2 );

                    Text( RP, label_name, len );
                }

                /* End the drawing. */
                break;
            }
            else {
                i++;
                node = node->ln_Succ;
            }
        }
    }

    /* Disabled pattern rendering is here. */
    if( State == IDS_DISABLED ) {
        BorderHeight = 1;
        BorderWidth = (IntuitionBase->LibNode.lib_Version < 39) ? 1 : 2;

        container.Left += BorderWidth;
        container.Top += BorderHeight;
        container.Width = max( 1, container.Width - 2*BorderWidth );
        container.Height = max( 1, container.Height - 2*BorderHeight );

        SetDrMd(RP,JAM1);
        SetAfPt(RP, patterndata, 1);

        RectFill(RP,  (LONG)container.Left, (LONG)container.Top,
                -1L + container.Left + container.Width,
                -1L + container.Top + container.Height );

        SetAfPt(RP, NULL, 0 );
    }

    /* Copy current Rectangle. */
    PD->rect = rect;

    return( retval );
}


static ULONG PUMG_GOACTIVE( Class *cl,
                            Object *o,
                            struct gpInput *gpi )
{
    ULONG retval = GMR_MEACTIVE, Left, Top;
    struct RastPort *rp;
    PUMGData *PD = INST_DATA( cl, o );
    struct GadgetInfo *gi = gpi->gpi_GInfo;
    struct Gadget *gad = (struct Gadget *)o;

    /* Test if we are disabled. */
    if( gad->Flags & GFLG_DISABLED ) return( GMR_NOREUSE );

    /* Call first our parent class. */
    (void)DoSuperMethodA(cl, o, gpi);

    /* Chech whether we were activated from mouse or keyboard. */
    PD->ActiveFromMouse = (gpi->gpi_IEvent != NULL);

    /* Select this gadget. */
    gad->Flags |= GFLG_SELECTED;

    rp = ObtainGIRPort( gi );
    if( rp ) {
        /* Render ourselves as selected gadget. */
        DoMethod( o, GM_RENDER, gi, rp, GREDRAW_UPDATE );
        ReleaseGIRPort( rp );

        /* Get the domain top/left position. */
        Left = gi->gi_Domain.Left;
        Top = gi->gi_Domain.Top;

        /* If this is window, we have to add window Left/Top values too. */
        if( gi->gi_Window ) {
            Left += gi->gi_Window->LeftEdge;
            Top += gi->gi_Window->TopEdge;
        }

        /* Count how many items fits to menu. */
        PD->FitsItems = (gi->gi_Screen->Height - 4) / (5 + PD->Font->tf_YSize);
        if( PD->FitsItems > PD->Count ) {
            PD->FitsItems = PD->Count;
        }

        PD->popup_window = OpenWindowTags(NULL,
            WA_Left,            Left + PD->rect.MinX,
            WA_Top,             1 + Top + PD->rect.MaxY,
            WA_Width,           1 + PD->rect.MaxX - PD->rect.MinX,
            WA_Height,          4 + PD->FitsItems*(5 + PD->Font->tf_YSize),
            WA_Activate,        FALSE,
            WA_CustomScreen,    gi->gi_Screen,
            WA_SizeGadget,      FALSE,
            WA_DragBar,         FALSE,
            WA_DepthGadget,     FALSE,
            WA_CloseGadget,     FALSE,
            WA_Borderless,      TRUE,
            WA_Flags,           0,
            WA_AutoAdjust,      TRUE,
            WA_RMBTrap,         TRUE,
            WA_SimpleRefresh,   TRUE,
            WA_NoCareRefresh,   TRUE,
            TAG_END );

        if( PD->popup_window == NULL ) {
            retval = GMR_NOREUSE;
        }
        else {
            /* We make sure Active item isn't too large to display. */
            if( PD->FitsItems < PD->Active ) PD->Active = PD->FitsItems-1;

            /* If activated from keyboard we can set temporary value
            ** to currently activated item. Otherwise we set it
            ** to -1 which means that there is no active item. */
            PD->Temp_Active = PD->ActiveFromMouse ? (ULONG)~0 : PD->Active;

            /* Render all items. */
            PUMG_DrawPopupWindow( PD, gi->gi_DrInfo, 0, -1);
        }
    }
    else retval = GMR_NOREUSE;

    return(retval);
}

static ULONG PUMG_HANDLEINPUT( Class *cl,
                               Object *o,
                               struct gpInput *gpi )
{
    ULONG retval = GMR_MEACTIVE;
    struct InputEvent *ie = gpi->gpi_IEvent;
    PUMGData *PD = INST_DATA(cl, o);
    WORD X, Y;
    WORD count, old_active;
    struct GadgetInfo *gi = gpi->gpi_GInfo;
    struct TagItem tags;    /* If our possible child class, doesn't know
                            ** how to handle NULL AttrList ptr, then
                            ** this can save lots of crashes. */

    /* If there is anykind of AutoPoint program then our main window
    ** might get inactive and we wouldn't get any more messages.
    ** So we check out that we are active and deactivate ourselves
    ** if our window isn't active anymore. */
    if( gi->gi_Window ) {
        if( (gi->gi_Window->Flags & WFLG_WINDOWACTIVE) == 0 ) {
            return( GMR_NOREUSE );
        }
    }

    if( PD->ActiveFromMouse ) {
        X = PD->popup_window->MouseX;
        Y = PD->popup_window->MouseY;

        count = ( (Y - 2) >= 0 ) ? (Y - 2) / (5 + PD->Font->tf_YSize) : ~0;

        old_active = PD->Temp_Active;

        if( (X > 2) && (X < (PD->popup_window->Width - 2))
                && (count >= 0) && (count < PD->FitsItems) ) {
            PD->Temp_Active = (UWORD)count;
        }
        else PD->Temp_Active = (UWORD)~0;

        if( old_active != (WORD)PD->Temp_Active ) {
            PUMG_DrawPopupWindow( PD, gi->gi_DrInfo,(ULONG)PD->Temp_Active, 1 );
            PUMG_DrawPopupWindow( PD, gi->gi_DrInfo,(ULONG)old_active, 1 );
        }

        while( ie && (retval == GMR_MEACTIVE) ) {
            if( ie->ie_Class == IECLASS_RAWMOUSE ) {
                if( ie->ie_Code == SELECTUP ) {
                    retval = GMR_NOREUSE;

                    if( (PD->Temp_Active != (UWORD)~0) ) {
                        PD->Active = PD->Temp_Active;
                        PUMG_MakeCheckings( PD );

                        PUMG_SetTagArg(tags, TAG_END, NULL);
                        (VOID)DoMethod( o, OM_NOTIFY, &tags, gi, 0);

                        retval |= GMR_VERIFY;
                        *gpi->gpi_Termination = (ULONG)PD->Active;
                    }
                }
            }

            ie = ie->ie_NextEvent;
        }
    }
    else {
        while( ie && (retval == GMR_MEACTIVE) ) {
            switch( ie->ie_Class )
            {
            case IECLASS_RAWMOUSE:
                if( ie->ie_Code != IECODE_NOBUTTON ) {
                    retval = GMR_REUSE; /* Reuse the InputEvent. */
                }
                break;
            case IECLASS_RAWKEY:
                old_active = PD->Temp_Active;
                switch( ie->ie_Code )
                {
                case CURSORDOWN:
                    if( ie->ie_Qualifier & (ALTLEFT|ALTRIGHT) ) {
                        PD->Temp_Active = PD->FitsItems-1;  /* Jump to end. */
                    }
                    else if( PD->Temp_Active < (PD->FitsItems-1) ) {
                        PD->Temp_Active += 1;
                    }
                    break;
                case CURSORUP:
                    if( ie->ie_Qualifier & (ALTLEFT|ALTRIGHT) ) {
                        PD->Temp_Active = 0;    /* Jump to start. */
                    }
                    else if( PD->Temp_Active > 0 ) {
                        PD->Temp_Active -= 1;
                    }
                    break;
                case 0x45:  /* ESC key. */
                    retval = GMR_NOREUSE;
                    break;
                case 0x44:  /* RETURN key. */
                    PD->Active = PD->Temp_Active;
                    PUMG_MakeCheckings( PD );

                    PUMG_SetTagArg(tags, TAG_END, NULL);
                    (VOID)DoMethod( o, OM_NOTIFY, &tags, gi, 0);

                    retval = GMR_NOREUSE|GMR_VERIFY;
                    *gpi->gpi_Termination = (ULONG)PD->Active;
                    break;
                }

                /* Update the popupwindow items, if changes were made. */
                if( old_active != PD->Temp_Active ) {
                    PUMG_DrawPopupWindow( PD, gi->gi_DrInfo,
                        (ULONG)PD->Temp_Active, 1 );
                    PUMG_DrawPopupWindow( PD, gi->gi_DrInfo,
                        (ULONG)old_active, 1 );
                }
            }

            ie = ie->ie_NextEvent;
        }
    }

    return(retval);
}


static ULONG PUMG_GOINACTIVE( Class *cl,
                              Object *o,
                              struct gpGoInactive *gpgi )
{
    ULONG retval;
    struct RastPort *rp;
    PUMGData *PD = INST_DATA(cl, o);

    retval = DoSuperMethodA(cl, o, gpgi);

    ((struct Gadget *)o)->Flags &= ~GFLG_SELECTED;

    rp = ObtainGIRPort( gpgi->gpgi_GInfo );
    if( rp ) {
        DoMethod( o, GM_RENDER, gpgi->gpgi_GInfo, rp, GREDRAW_UPDATE );
        ReleaseGIRPort( rp );
    }

    if( PD->popup_window ) {
        CloseWindow(PD->popup_window);
        PD->popup_window = NULL;
    }

    return(retval);
}

/* Static functions for help with real Method functions. */

static void PUMG_MakeCheckings( PUMGData *PD )
{
    struct Node *node;

    PD->Count = 0;

    if( PD->Labels == NULL ) {
        PD->Active = 0;
    }
    else if( PD->Labels != (struct List *)~0 ) {
        node = (struct Node *)PD->Labels->lh_Head;
        while( node->ln_Succ ) {
            PD->Count += 1;
            node = node->ln_Succ;
        }

        if( PD->Active >= PD->Count ) {
            PD->Active = PD->Count + (PD->Count == 0) - 1;
        }
    }
}

static void PUMG_GetGadgetRect( Object *o,
                                struct GadgetInfo *gi,
                                struct Rectangle *rect )
{
    struct Gadget *gad = (struct Gadget *)o;
    LONG W, H;

    rect->MinX = gad->LeftEdge;
    rect->MinY = gad->TopEdge;
    W = gad->Width;
    H = gad->Height;

    if( gi ) {
        if( gad->Flags & GFLG_RELRIGHT ) rect->MinX += gi->gi_Domain.Width - 1;
        if( gad->Flags & GFLG_RELBOTTOM ) rect->MinY += gi->gi_Domain.Height - 1;
        if( gad->Flags & GFLG_RELWIDTH ) W += gi->gi_Domain.Width;
        if( gad->Flags & GFLG_RELHEIGHT ) H += gi->gi_Domain.Height;
    }

    rect->MaxX = rect->MinX + W - (W > 0);
    rect->MaxY = rect->MinY + H - (H > 0);
}

static void PUMG_DrawPopupWindow( PUMGData *PD,
                                  struct DrawInfo *dri,
                                  ULONG From, LONG Count)
{
    int i, End;
    struct Node *node;
    struct Window *win = PD->popup_window;
    struct RastPort *RP = win->RPort;

    if( PD->Count && dri ) {
        /* If we want to draw all entries then we draw
        ** window borders too. */
        if( Count == -1) {
            Count = PD->FitsItems;
            SetAPen( RP, (ULONG)dri->dri_Pens[SHINEPEN]);
            Move( RP, 0, (LONG)win->Height - 1 );
            Draw( RP, 0, 0 );
            Draw( RP, (LONG)win->Width - 1, 0 );
            SetAPen( RP, (ULONG)dri->dri_Pens[SHADOWPEN]);
            Draw( RP, (LONG)win->Width - 1, (LONG)win->Height - 1 );
            Draw( RP, 1, (LONG)win->Height - 1 );
        }

        SetFont( RP, PD->Font );
        SetDrMd( RP, JAM1);

        node = PD->Labels->lh_Head;

        for( i = 0, End = From + Count; node->ln_Succ ; i++ ) {
            if( i < PD->FitsItems ) {
                if( (i >= From) && ( i < End ) ) {
                    PUMG_DrawFrame( PD->popup_window, i, dri, node->ln_Name,
                        PD->Font, (BOOL)(i == PD->Temp_Active) );
                }
                else if( i >= End ) return;
            }

            node = node->ln_Succ;
        }
    }
}

static void PUMG_DrawFrame( struct Window *win,
                            int order,
                            struct DrawInfo *dri,
                            UBYTE *name,
                            struct TextFont *tf,
                            BOOL Active )
{
    ULONG   Pen1, Pen2, TextPen, BPen;
    ULONG   Top, Width, Bottom, MaxX,
            Len, TextWidth,
            font_height = tf->tf_YSize;
    struct  RastPort *RP = win->RPort;
    struct  TextExtent temp_te;

    if( Active ) {
        Pen1 = dri->dri_Pens[SHADOWPEN];
        Pen2 = dri->dri_Pens[SHINEPEN];
        BPen = dri->dri_Pens[FILLPEN];
    }
    else {
        Pen2 = dri->dri_Pens[SHADOWPEN];
        Pen1 = dri->dri_Pens[SHINEPEN];
        BPen = dri->dri_Pens[BACKGROUNDPEN];
    }

    TextPen = dri->dri_Pens[TEXTPEN];

    Top = 2 + order * (5 + font_height);
    Bottom = 4 + Top + font_height;
    MaxX = win->Width - 4;

    SetAPen( RP, BPen );
    RectFill( RP, 3, Top, MaxX, Bottom );

    SetAPen( RP, Pen1);
    Move( RP, 3, Bottom);
    Draw( RP, 3, Top );
    Draw( RP, MaxX, Top );
    SetAPen( RP, Pen2);
    Draw( RP, MaxX, Bottom );
    Draw( RP, 4, Bottom );

    SetAPen( RP, TextPen);

    Width = win->Width - 10;

    Len = TextFit( RP, name, (ULONG)strlen(name), &temp_te, NULL, 1,
        Width, 1 + font_height);

    TextWidth = temp_te.te_Extent.MaxX - temp_te.te_Extent.MinX;

    Move( RP, 5 + (Width - TextWidth)/2 - temp_te.te_Extent.MinX,
        3 + Top + tf->tf_Baseline );
    Text( RP, name, Len );
}

static void PUMG_DrawPopupMenuImage( struct RastPort *RP,
                                     ULONG Pen1,
                                     ULONG Pen2,
                                     ULONG Left,
                                     ULONG Top,
                                     LONG Height )
{
    int count, i;

    if( Height > 0 ) {
        SetAPen( RP, Pen1 );
        Move(RP, Left, Top + Height - 1 );
        Draw(RP, Left, Top );

        count = (Height-1) / 4;
        for( i = 0; i <= count; i++ ) {
            Move(RP, Left, Top + i*4);
            Draw(RP, 10 + Left, Top + i*4);
        }

        /* Draw other horizontal bar. */
        Move( RP, 16 + Left, Top);
        Draw( RP, 16 + Left, Top + Height);


        SetAPen( RP, Pen2 );
        Move(RP, 1 + Left, Top + Height - 1);
        Draw(RP, 11 + Left, Top + Height - 1);
        Draw(RP, 11 + Left, Top);

        if( Height >= 4 ) {
            count = (Height-4) / 4;
            for( i = 0; i <= count; i++ ) {
                Move(RP, 1 + Left, 3 + Top + i*4);
                Draw(RP, 11 + Left, 3 + Top + i*4);
            }
        }

        /* Draw other horizontal bar. */
        Move( RP, 15 + Left, Top);
        Draw( RP, 15 + Left, Top + Height );
    }
}

