/*
**  VLab plug-in for ArtEffect
**  © 2002 Kakace Productions
**
*/


#define PLUGIN_VERSION  "VLab.pss 1.8 (27.10.2002)"


#include <stdlib.h>

#ifndef  INTUITION_INTUITION_H
#include <intuition/intuition.h>
#endif

#ifndef  UTILITY_TAGITEM_H
#include <utility/tagitem.h>
#endif

#ifndef  ARTEFFECT_AE_PLUGIN_H
#include <libraries/ae_plugin.h>
#endif

#ifndef  MONITOR_H
#include "Monitor.h"
#endif

#ifndef  VLAB_CONFIG_H
#include "VLab_Config.h"
#endif

#include <proto/exec.h>
#include <proto/ae_plugin.h>


#define COPYRIGHT  PLUGIN_VERSION" © 2002, Kakace Productions.\0$VER: "PLUGIN_VERSION"\r\n"


/*------------------------------------------------------------------------------------------------*/

AE_DialogPtr PlugIn_OpenWindow(AE_AppObjPtr app);
void         PlugIn_CloseWindow(AE_AppObjPtr app, AE_DialogPtr window);
void         PlugIn_Apply(AE_AppObjPtr app, AE_EventPtr event);

struct VLabMonitor *PlugIn_HandleGadget(AE_AppObjPtr app, AE_DialogPtr window, AE_EventPtr event,
                                        struct VLabMonitor *monitoring);

static void showError(AE_AppObjPtr app, ULONG errorID);
static void closeMonitor(struct VLabSettings *settings, struct VLabMonitor *monitor);
static void updateGadgets(AE_AppObjPtr app, AE_DialogPtr window, struct InputSettings *inputCfg);


/*
** Error codes and error strings for showError()
*/

enum PlugIn_ErrorID {
    ERROR_NONE,
    ERROR_NO_MEMORY,
    ERROR_NO_WINDOW,
    ERROR_VLAB_BUSY
};

static const char *PlugIn_Errors[] = {
    "Not enough memory.",
    "Unable to open the window.",
    "The VLab is currently busy."
};


/*------------------------------------------------------------------------------------------------*/

/*
** Default settings
*/

static const struct VLabSettings DefSettings = {DEFAULT_CONF};


/*
** Global datas
*/

static struct VLabSettings SavedSettings;   /* Loaded upon startup by ArtEffect */
static struct VLabSettings CurSettings;     /* Current settings */
static ULONG  MaxInputs;                    /* Max number of inputs. 2 = VLab Classic, 3 = VLab Y/C */
static UBYTE *RGB_Buffer;                   /* Allocated at startup time */

struct Library *AEPlugInBase;


/*
** Private functions
*/

static void                handleEvents(AE_AppObjPtr application, AE_DialogPtr window);
static void                set_monitoring_flags(struct VLabSettings *settings);
static struct VLabMonitor *open_monitor(AE_AppObjPtr app, AE_EventPtr event, struct VLabSettings *settings);


/*
** We cannot change all flags when the monitor is running.
** The mask below must be applied to filter flags changes in this case.
*/

#define MONITORING_MASK ~(VLABF_LACE | VLABF_HIRES)


/*------------------------------------------------------------------------------------------------*/


int main(int argc, char *argv[])
{
    static struct TagItem tags[] = {
        {PLUGIN_INFO_NAME, (ULONG) "VLab"},
        {PLUGIN_INFO_COPYRIGHT, (ULONG) COPYRIGHT},
        {PLUGIN_INFO_MENUTITLE, (ULONG) "VLab..."},
        {PLUGIN_INFO_CONFIG, (ULONG) &SavedSettings},
        {TAG_DONE}
    };

    int  ret = EXIT_FAILURE;

    if ( (AEPlugInBase = OpenLibrary("ArtEffect:libs/ae_plugin.library", 2)) ) {

        struct TagItem *tagList;            /* Initialization tag list  */
        AE_AppObjPtr    app;                /* Application object       */
        LONG            plugInType;         /* Plug-in type             */

        /*
        ** Initialize our plug-in
        */

        SavedSettings = DefSettings;
        plugInType = PLUGIN_TYPE_ERROR;
        tagList = NULL;

        if ( (MaxInputs = VLab_IdentifyHardware()) ) {
            plugInType = PLUGIN_TYPE_SCANNER;
            tagList = tags;
        }

        /*
        ** Event handling
        */

        if ( (app = AE_SetupPlugInTagList(argc, argv, plugInType, tagList)) ) {
            struct Task *task;
            AE_DialogPtr window;
            LONG         oldPri;
            enum PlugIn_ErrorID errcode = ERROR_NO_MEMORY;

            task   = FindTask(NULL);
            oldPri = SetTaskPri(task, 0);

            /*
            ** Allocate the YUV and RGB buffers now so that we won't have to deal with
            ** this if the user decide to digitize several picture before closing the
            ** window.
            */

            if (VLab_AllocateYUV()) {
                if ( (RGB_Buffer = (UBYTE *) AllocVec(VLAB_MAXWIDTH * VLAB_MAXHEIGHT_PAL * 3, MEMF_ANY)) ) {

                    errcode = ERROR_VLAB_BUSY;

                    if (VLab_OwnHardware()) {

                        errcode = ERROR_NO_WINDOW;
                        InitMonitoring();               /* Must be called before opening the main window */
                        CurSettings = SavedSettings;

                        if ( (window = PlugIn_OpenWindow(app)) ) {
                            handleEvents(app, window);
                            SavedSettings = CurSettings;
                            PlugIn_CloseWindow(app, window);
                            errcode = ERROR_NONE;
                        }

                        CleanupMonitoring();
                        VLab_FreeHardware();
                    }
                    FreeVec(RGB_Buffer);
                }
                VLab_FreeYUV();
            }

            showError(app, errcode);
            AE_ExitPlugIn(app);

            SetTaskPri(task, oldPri);
        }

        CloseLibrary(AEPlugInBase);
        ret = EXIT_SUCCESS;
    }

    return ret;
}


static void handleEvents(AE_AppObjPtr app, AE_DialogPtr window)
{
    struct VLabSettings *settings;
    struct VLabMonitor *monitoring;
    ULONG  monitorBitMask = 0;      /* Signal mask (monitoring)  */
    ULONG  appBitMask;              /* Signal mask (main window) */
    int    running = TRUE;
    AE_EventPtr event;

    settings = &CurSettings;
    monitoring = NULL;

    appBitMask = 1 << app->UserPort->mp_SigBit;

    do {
        int signals = 0;

        /*
        ** If the monitoring is active, we poll the signals
        ** continuously. Otherwise, we wait for an event
        ** from ArtEffect.
        */

        if (monitoring) {
            UpdateMonitorWindow(monitoring);
            monitorBitMask = 1 << MonitoringWindow(monitoring)->UserPort->mp_SigBit;
            signals = SetSignal(0, 0);
        }
        else {
            monitorBitMask = 0;
            WaitPort(app->UserPort);
            signals = appBitMask;
        }

        /*
        ** Handle monitoring window's IDCMP
        */

        if (signals & monitorBitMask) {
            struct IntuiMessage *imsg;
            int close = FALSE;

            SetSignal(0, signals & monitorBitMask);

            while( (imsg = (struct IntuiMessage *) GetMsg(MonitoringWindow(monitoring)->UserPort)) ){

                if (imsg->Class == IDCMP_CLOSEWINDOW)
                    close = TRUE;

                ReplyMsg((struct Message *) imsg);
            }

            if (close) {
                closeMonitor(settings, monitoring);
                monitoring = NULL;
            }
        }

        /*
        ** Handle plug-in window's messages.
        */

        if (signals & appBitMask) {
            SetSignal(0, signals & appBitMask);

            while ( (event = AE_GetEvent(app)) ) {

                switch (event->Type) {

                    case PLUGIN_EVENT_GADGET:
                        monitoring = PlugIn_HandleGadget(app, window, event, monitoring);
                        break;


                    case PLUGIN_EVENT_EXECUTE:
                        PlugIn_Apply(app, event);
                        /*
                        ** Reset monitoring flags.
                        */
                        if (monitoring)
                            set_monitoring_flags(settings);

                        break;


                    case PLUGIN_EVENT_QUIT:
                        running = FALSE;

                        if (monitoring) {
                            closeMonitor(settings, monitoring);
                            monitoring = NULL;
                        }
                        break;

                }
                AE_ReplyEvent(app, event);
            }
        }
    } while (running);
}


static void closeMonitor(struct VLabSettings *settings, struct VLabMonitor *monitor)
{
    settings->vs_WLeft = MonitoringWindow(monitor)->LeftEdge;
    settings->vs_WTop  = MonitoringWindow(monitor)->TopEdge;
    CloseMonitorWindow(monitor);
}


static void showError(AE_AppObjPtr app, ULONG errorID)
{
    if (errorID--) {
        AE_DoRequestTags(app, AER_TYPE_INFO, AER_BodyText, (ULONG) PlugIn_Errors[errorID],
                AER_TitleText, (ULONG) "VLab error", TAG_DONE);
    }
}


/*------------------------------------------------------------------------------------------------*/

/****** VLab/PlugIn_OpenWindow **********************************************
*
*   NAME
*   PlugIn_OpenWindow -- Open the dialog window
*
*   SYNOPSIS
*   window = PlugIn_OpenWindow(app)
*
*   AE_DialogPtr PlugIn_OpenWindow(AE_AppObjPtr);
*
*   FUNCTION
*   Open the dialog window. The window shall be closed by a call to the
*   PlugIn_CloseWindow() function.
*
*   INPUTS
*   app - Object pointer returned by AE_SetupPlugInTagList().
*
*   RESULT
*   window - Pointer to the newly opened window, or NULL if an error occured
*            or if no window is needed.
*
*   NOTES
*   Gadgets related to the monitoring feature might be disabled if the
*   monitoring is not available (depending on the screen mode and the
*   availability of the PiP feature).
*
*   BUGS
*
*   SEE ALSO
*   PlugIn_CloseWindow(), AE_OpenWindowTagList()
*
*****************************************************************************
*
*/

/*
** Gadgets ID.
** Those ID depend upon the order in which each gadget appears in the tag list
** passed to AE_OpenWindowTags().
** We also use cycle gadgets instead of HRADIO because the latter are no longer
** supported by ArtEffect (all radio buttons are rendered verticaly).
*/

enum gadgetsID {
        /* Video */
        GADID_INPUT = 1,            /* Input selector (cycle gadget). 2 or 3 values. */
        GADID_VIDEO,                /* Video mode (cycle gadget, 5 values)      */
        GADID_HUE,                  /* Hue value (integer)                      */

        /* Filters */
        GADID_VTR,                  /* VTR flag (checkbox)                      */
        GADID_PREFILTER,            /* Prefilter flag (checkbox)                */
        GADID_BW_SOURCE,            /* B&W source flag (checkbox)               */
        GADID_BANDPASS,             /* Bandpass filter (cycle gadget), 4 values */
        GADID_CORING,               /* Coring point (cycle gadget), 4 values    */
        GADID_WEIGHT,               /* Filter weight (cycle gadget), 4 values   */

        /* Image */
        GADID_HIRES,                /* HiRes flag (checkbox)                    */
        GADID_LACE,                 /* Lace flag (checkbox)                     */
        GADID_COLOR,                /* Greyscale flag (checkbox)                */

        GADID_GAMMA,                /* Gamma correction (luma)                  */
        GADID_BRIGHT,               /* Brightness correction                    */
        GADID_CONTRAST,             /* Contrast correction                      */
        /* Clipping */
        GADID_XPOS,                 /* X position (integer)                     */
        GADID_YPOS,                 /* Y position (integer)                     */
        GADID_WIDTH,                /* Width (integer)                          */
        GADID_HEIGHT,               /* Height (integer)                         */
        /* Monitoring */
        GADID_DISABLE_PIP,          /* 1 = Disable PiP monitoring               */
        GADID_COLORMONITOR,         /* 1 = Color monitoring                     */
        GADID_RESOLUTION,           /* Resolution ID (cycle gadget, 4 values)   */
        GADID_OPENMONITOR,          /* Open monitor window (button)             */

        GADID_RESET,                /* Reset configuration to last saved values */
};


AE_DialogPtr PlugIn_OpenWindow(AE_AppObjPtr app)
{
    static const char *VLAB_YC_InputNames[] = {"Input", "CVBS-1", "CVBS-2", "S-VHS (Y/C)", NULL};
    static const char *VLAB_InputNames[]    = {"Input", "CVBS-1", "CVBS-2", NULL};
    static const char *VLAB_Video[]         = {"Source", "PAL B/G/H/I", "PAL N", "PAL M", "NTSC 4.43", "NTSC M", NULL};
    static const char *VLAB_Resolutions[]   = {"Preview", "Small", "Medium", "Large", NULL};
    static const char *VLAB_Bandpass[]      = {"Bandpass", "4.1 MHz", "3.8 MHz", "2.6 MHz", "2.9 MHz", NULL};
    static const char *VLAB_Coring[]        = {"Noise", "OFF", "Weak", "Medium", "Strong", NULL};
    static const char *VLAB_Weight[]        = {"K factor", "OFF", "Weak", "Medium", "Strong", NULL};

    /*
    ** The GUI has to be modified depending upon the availability of the monitoring
    ** feature. The two tag lists below implement this.
    */

    static const struct TagItem finalTags[] = {
        {AE_Bar},
        {AE_HGroup},
        {PLUGIN_INFO_ID, GADID_RESET},
        {PLUGIN_INFO_BUTTON, (ULONG) "Reset settings"},         /* GADGET */
        {AE_Bar},
        {PLUGIN_INFO_ACTION, (ULONG) "<< GRAB >>"},             /* GADGET */
        {AE_EndGroup},
        {PLUGIN_INFO_TITLE, (ULONG) "VLab settings..."},
        {TAG_DONE}
    };

    static struct TagItem monitoringTags[] = {                  /* INDEX    */
        {AE_Title("Monitoring")},                               /*  0       */
        {AE_HGroup},                                            /*  1       */
        {AE_VGroup},                                            /*  2       */
        {PLUGIN_INFO_ACTIVE, 0},                                /*  3 <==   */
        {PLUGIN_INFO_CHECKBOX, (ULONG) "Disable PiP"},          /*  4 GDGT  */
        {PLUGIN_INFO_ACTIVE, 0},                                /*  5 <==   */
        {PLUGIN_INFO_CHECKBOX, (ULONG) "Color preview"},        /*  6 GDGT  */
        {AE_EndGroup},                                          /*  7       */
        {AE_VGroup},                                            /*  8       */
        {PLUGIN_INFO_ACTIVE, 1},                                /*  9 <==   */
        {PLUGIN_INFO_CYCLE, (ULONG) VLAB_Resolutions},          /* GADGET   */
        {PLUGIN_INFO_BUTTON, (ULONG) "Start monitor"},          /* GADGET   */
        {AE_EndGroup},
        {AE_EndGroup},
        {TAG_MORE, (ULONG) finalTags}
    };

    struct VLabSettings *settings = &CurSettings;
    struct InputSettings *inputCfg = &settings->vs_Settings[settings->vs_Input];
    AE_DialogPtr window = NULL;
    ULONG maxH, maxW, flags;

    flags = inputCfg->is_Flags;

    /*
    ** Verify the settings. ArtEffect automaticaly loads the settings from a
    ** configuration file, so we shall check the settings version and reload
    ** the default settings when a mismatch occurs.
    */

    if (settings->vs_CfgVer != CONFIG_VERSION)
        CurSettings = SavedSettings = DefSettings;

    /*
    ** Setup default monitoring values in the related taglist.
    */

    monitoringTags[3].ti_Data = (settings->vs_MonFlags & MONITOR_NO_PIP) ? 1 : 0;
    monitoringTags[5].ti_Data = (settings->vs_MonFlags & MONITOR_COLOR) ? 1 : 0;
    monitoringTags[9].ti_Data = settings->vs_Resolution;

    /*
    ** Inform the vlab about the default settings.
    */

    VLab_ChangeInput(settings->vs_Input);
    VLab_ChangeVideo(inputCfg->is_VideoStd);
    VLab_ChangeHue(inputCfg->is_Hue);
    VLab_ChangeFlags(flags);
    VLab_LumaCorrection(inputCfg->is_Gamma, inputCfg->is_Brightness, inputCfg->is_Contrast);
    VLab_ChangeFilters(inputCfg->is_Filters);

    maxH = VLab_FrameHeight(inputCfg->is_VideoStd, flags);
    maxW = VLab_FrameWidth(flags);

    /*
    ** Open the window.
    ** We use AE_OpenWindowTags() because most tags shall be initialized properly and
    ** because this allows us to modify the TagList without too much troubles.
    **
    ** The default "Execute" button is replaced by an ACTION button. I don't know
    ** whether this is the right thing to do. Looks like it works though...
    **
    ** ArtEffect forget the window title when the tag appear near the top of the
    ** tag list. I tried several things to figure out what happens, and I think it
    ** has something to do with Groups.
    */

    if (!app->Quiet) {
        window = AE_OpenWindowTags(app,
            PLUGIN_INFO_ACTIVE, settings->vs_Input,                 /* GADGET */
            PLUGIN_INFO_CYCLE, (ULONG) (MaxInputs == 2 ? VLAB_InputNames : VLAB_YC_InputNames),
            PLUGIN_INFO_ACTIVE, inputCfg->is_VideoStd,
            PLUGIN_INFO_CYCLE, (ULONG) VLAB_Video,                  /* GADGET */
            PLUGIN_INFO_VALUE_MIN, -180,
            PLUGIN_INFO_VALUE_MAX, 180,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_Hue,
            PLUGIN_INFO_VALUE, (ULONG) "Hue (NTSC)",                /* GADGET */

            AE_Title("Luminance filters"),
            AE_HGroup,
            AE_VGroup,
            PLUGIN_INFO_ACTIVE, (flags & VLABF_VTR ? 1 : 0),
            PLUGIN_INFO_CHECKBOX, (ULONG) "VTR",                    /* GADGET */
            PLUGIN_INFO_ACTIVE, (flags & VLABF_PREFILTER ? 0 : 1),
            PLUGIN_INFO_CHECKBOX, (ULONG) "Pre-filter",             /* GADGET */
            PLUGIN_INFO_ACTIVE, (flags & VLABF_BW_SOURCE ? 1 : 0),
            PLUGIN_INFO_CHECKBOX, (ULONG) "B&W source",             /* GADGET */
            AE_EndGroup,
            AE_VGroup,
            PLUGIN_INFO_ACTIVE, FILTER_BANDPASS_R(inputCfg->is_Filters),
            PLUGIN_INFO_CYCLE, (ULONG) VLAB_Bandpass,               /* GADGET */
            PLUGIN_INFO_ACTIVE, FILTER_CORING_R(inputCfg->is_Filters),
            PLUGIN_INFO_CYCLE, (ULONG) VLAB_Coring,                 /* GADGET */
            PLUGIN_INFO_ACTIVE, FILTER_FACTOR_R(inputCfg->is_Filters),
            PLUGIN_INFO_CYCLE, (ULONG) VLAB_Weight,                 /* GADGET */
            AE_EndGroup,
            AE_EndGroup,

            AE_Title("Image"),
            AE_HGroup,
            AE_VGroup,
            PLUGIN_INFO_ACTIVE, (flags & VLABF_HIRES ? 1 : 0),
            PLUGIN_INFO_CHECKBOX, (ULONG) "HiRes",                  /* GADGET */
            PLUGIN_INFO_ACTIVE, (flags & VLABF_LACE ? 1 : 0),
            PLUGIN_INFO_CHECKBOX, (ULONG) "Lace",                   /* GADGET */
            PLUGIN_INFO_ACTIVE, (flags & VLABF_COLOR ? 1 : 0),
            PLUGIN_INFO_CHECKBOX, (ULONG) "Color",                  /* GADGET */
            AE_EndGroup,
            AE_VGroup,
            PLUGIN_INFO_VALUE_MIN, VLAB_GAMMA_MIN,
            PLUGIN_INFO_VALUE_MAX, VLAB_GAMMA_MAX,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_Gamma,
            PLUGIN_INFO_VALUE, (ULONG) "Gamma",                     /* GADGET */
            PLUGIN_INFO_VALUE_MIN, VLAB_BRIGHT_MIN,
            PLUGIN_INFO_VALUE_MAX, VLAB_BRIGHT_MAX,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_Brightness,
            PLUGIN_INFO_VALUE, (ULONG) "Brightness",                /* GADGET */
            PLUGIN_INFO_VALUE_MIN, VLAB_CONTRAST_MIN,
            PLUGIN_INFO_VALUE_MAX, VLAB_CONTRAST_MAX,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_Contrast,
            PLUGIN_INFO_VALUE, (ULONG) "Contrast",                  /* GADGET */
            AE_EndGroup,
            AE_EndGroup,

            AE_Title("Clipping"),
            PLUGIN_INFO_VALUE_MIN, 0,
            PLUGIN_INFO_VALUE_MAX, maxW - inputCfg->is_Width,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_XPos,
            PLUGIN_INFO_VALUE, (ULONG) "X pos",                     /* GADGET */
            PLUGIN_INFO_VALUE_MIN, 0,
            PLUGIN_INFO_VALUE_MAX, maxH - inputCfg->is_Height,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_YPos,
            PLUGIN_INFO_VALUE, (ULONG) "Y pos",                     /* GADGET */
            PLUGIN_INFO_VALUE_MIN, 4,
            PLUGIN_INFO_VALUE_MAX, maxW - inputCfg->is_XPos,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_Width,
            PLUGIN_INFO_VALUE, (ULONG) "Width",                     /* GADGET */
            PLUGIN_INFO_VALUE_MIN, 2,
            PLUGIN_INFO_VALUE_MAX, maxH - inputCfg->is_YPos,
            PLUGIN_INFO_VALUE_DEF, inputCfg->is_Height,
            PLUGIN_INFO_VALUE, (ULONG) "Height",                    /* GADGET */

            TAG_MORE, (ULONG) (IsMonitoringAvailable(app->PubScreenName) ? monitoringTags : finalTags));
    }

    return window;
}


/****** VLab/PlugIn_CloseWindow *********************************************
*
*   NAME
*   PlugIn_CloseWindow -- Close the dialog window.
*
*   SYNOPSIS
*   PlugIn_CloseWindow(app, window)
*
*   void PlugIn_CloseWindow(AE_AppObjPtr, AE_DialogPtr);
*
*   FUNCTION
*   Close the dialog window opened by PlugIn_OpenWindow().
*
*   INPUTS
*   app    - Application object obtained by AE_SetupPlugInTagList().
*   window - Window pointer obtained by PlugIn_OpenWindow(). It's safe to
*            pass a NULL pointer.
*
*   SEE ALSO
*   PlugIn_OpenWindow(), AE_CloseWindow()
*
*****************************************************************************
*
*/


void PlugIn_CloseWindow(AE_AppObjPtr app, AE_DialogPtr window)
{
    if (window)
        AE_CloseWindow(app, window);
}


/****** VLab/PlugIn_HandleGadget ********************************************
*
*   NAME
*   PlugIn_HandleGadget -- Handle a gadget event.
*
*   SYNOPSIS
*   monitoring = PlugIn_HandleGadget(app, window, event, monitoring)
*
*   struct VLabMonitor *PlugIn_HandleGadget(AE_AppObjPtr, AE_DialogPtr,
*               AE_EventPtr, struct VLabMonitor *);
*
*   FUNCTION
*   Handle a gadget event.
*
*   INPUTS
*   app    - Application object obtained by AE_SetupPlugInTagList().
*   window - Window pointer obtained by PlugIn_OpenWindow().
*   event  - Event pointer :
*       event->ID    : Gadget ID.
*       event->Value : New gadget value.
*
*   RESULT
*   monitoring - Pointer to a monitoring object.
*   event->Error : Optional error code.
*
*   NOTES
*   The HiRes and Lace flags are not sent to the vlab hardware, but the
*   settings always reflect any changes made by the user. This means that
*   the full set of flags must be sent to the hardware before grabbing a
*   new picture.
*   The "B&W source" gadget, when set, force any grab (as well as the
*   monitoring) in greyscale mode.
*
*   SEE ALSO
*
*****************************************************************************
*
*   The "hires" and "lace" flags must not be sent to the vlab to avoid
*   conflicts with the monitoring feature.
*   Therefore, all flags sent to the VLab hardware by a call to the
*   VLab_ChangeFlags function must be masked by MONITORING_MASK to avoid
*   side effects. This allows the user to see the effect of the other flags
*   while monitoring the current input.
*/


struct VLabMonitor *PlugIn_HandleGadget(AE_AppObjPtr app, AE_DialogPtr window,
                AE_EventPtr event, struct VLabMonitor *monitoring)
{
    ULONG height, width, xScale, yScale;

    struct VLabSettings *settings = &CurSettings;
    struct InputSettings *inputCfg = &settings->vs_Settings[settings->vs_Input];
    LONG  value = event->Value;
    ULONG flags = inputCfg->is_Flags;

    height = VLab_FrameHeight(inputCfg->is_VideoStd, flags);
    width  = VLab_FrameWidth(flags);

    xScale = (flags & VLABF_HIRES) ? 4 : 2;
    yScale = (flags & VLABF_LACE)  ? 2 : 1;

    /*
    ** Handle all gadgets.
    ** When a value concerning the image's dimensions is changed, the code takes care
    ** to modify other related settings (xpos<->width and ypos<->height)
    ** When the monitoring is active, some values need to be changed in real time so
    ** that their effect applies to the monitoring as well.
    */

    switch(event->ID) {

        /*
        **              ########## Video ##########
        */

        case GADID_INPUT:                       /* Input : CVBS1 / CVBS2 / SVHS */
            settings->vs_Input = value;         /* Value : [0; 2]               */
            inputCfg = &settings->vs_Settings[value];
            updateGadgets(app, window, inputCfg);
            VLab_ChangeInput(value);
            break;


        case GADID_VIDEO:                       /* PAL B/G/H/I, N, M, NTSC 4.43, M */
            inputCfg->is_VideoStd = value;
            VLab_ChangeVideo(value);
            height = VLab_FrameHeight(value, flags);

            /*
            ** Update YPos/Height gadgets.
            ** When switching from PAL to NTSC, the current frame size
            ** is scaled down to fit into the new limits.
            */

            if (inputCfg->is_Height + inputCfg->is_YPos > height) {
                inputCfg->is_Height = (inputCfg->is_Height * 5) / 6;
                inputCfg->is_YPos   = (inputCfg->is_YPos   * 5) / 6;

                if (flags & VLABF_LACE) {
                    inputCfg->is_Height &= ~1;
                    inputCfg->is_YPos   &= ~1;
                }
            }

            AE_SetSlider(app, window, GADID_YPOS, inputCfg->is_YPos, 0, height - inputCfg->is_Height);
            AE_SetSlider(app, window, GADID_HEIGHT, inputCfg->is_Height, yScale, height - inputCfg->is_YPos);

            /*
            ** Resize the monitoring window. If this fails, the monitoring
            ** is no longer active.
            */

            if (monitoring) {
                value = settings->vs_Resolution;
                if (inputCfg->is_VideoStd >= VIDEO_525)
                    value += MONITOR_SIZES;

                monitoring = ResizeMonitor(monitoring, app->PubScreenName, value);
            }
            break;


        case GADID_HUE:                         /* Hue (NTSC only) */
            inputCfg->is_Hue = value;
            VLab_ChangeHue(value);
            break;


        /*
        **              ########## Filters ##########
        */

        case GADID_VTR:                         /* Video Tape Recorder flag */
            flags &= ~VLABF_VTR;
            if (value)
                flags |= VLABF_VTR;
            inputCfg->is_Flags = flags;
            VLab_ChangeFlags(flags & MONITORING_MASK);
            break;


        case GADID_PREFILTER:                   /* Not checked : disabled */
            flags &= ~VLABF_PREFILTER;
            if (!value)
                flags |= VLABF_PREFILTER;
            inputCfg->is_Flags = flags;
            VLab_ChangeFlags(flags & MONITORING_MASK);
            break;


        case GADID_BW_SOURCE:                   /* Disable chroma filter */
            flags &= ~VLABF_BW_SOURCE;
            if (value)
                flags |= VLABF_BW_SOURCE;
            inputCfg->is_Flags = flags;
            VLab_ChangeFlags(flags & MONITORING_MASK);

            if (monitoring)
                MonitorChangeMode(monitoring, ((settings->vs_MonFlags & MONITOR_COLOR) && !value ? TRUE : FALSE));
            break;


        case GADID_BANDPASS:
            inputCfg->is_Filters = FILTER_BANDPASS_W(inputCfg->is_Filters, value);
            VLab_ChangeFilters(inputCfg->is_Filters);
            break;


        case GADID_CORING:
            inputCfg->is_Filters = FILTER_CORING_W(inputCfg->is_Filters, value);
            VLab_ChangeFilters(inputCfg->is_Filters);
            break;


        case GADID_WEIGHT:
            inputCfg->is_Filters = FILTER_FACTOR_W(inputCfg->is_Filters, value);
            VLab_ChangeFilters(inputCfg->is_Filters);
            break;


        /*
        **              ########## Image ##########
        */

        case GADID_GAMMA:                       /* Gamma correction */
            inputCfg->is_Gamma = value;
            VLab_LumaCorrection(inputCfg->is_Gamma, inputCfg->is_Brightness, inputCfg->is_Contrast);
            break;


        case GADID_BRIGHT:                      /* Brightness */
            inputCfg->is_Brightness = value;
            VLab_LumaCorrection(inputCfg->is_Gamma, inputCfg->is_Brightness, inputCfg->is_Contrast);
            break;


        case GADID_CONTRAST:                    /* Contrast */
            inputCfg->is_Contrast = value;
            VLab_LumaCorrection(inputCfg->is_Gamma, inputCfg->is_Brightness, inputCfg->is_Contrast);
            break;


        case GADID_LACE:                        /* Lace / No lace */
            flags &= ~VLABF_LACE;
            yScale = 1;
            if (value) {
                flags |= VLABF_LACE;
                yScale = 2;
            }

            inputCfg->is_Flags = flags;
            height = VLab_FrameHeight(inputCfg->is_VideoStd, flags);

            /*
            ** Update YPos/Height gadgets.
            ** Both YPos/Height are scaled when they overflow the current
            ** frame height or when the user switches to interlaced mode.
            ** Otherwise, only the limits are changed.
            */

            if (value) {
                inputCfg->is_YPos   *= 2;
                inputCfg->is_Height *= 2;
            }
            else {
                if (inputCfg->is_YPos + inputCfg->is_Height > height) {
                    inputCfg->is_YPos   = inputCfg->is_YPos / 2;
                    inputCfg->is_Height = inputCfg->is_Height / 2;
                }
            }

            AE_SetSlider(app, window, GADID_YPOS, inputCfg->is_YPos, 0, height - inputCfg->is_Height);
            AE_SetSlider(app, window, GADID_HEIGHT, inputCfg->is_Height, yScale, height - inputCfg->is_YPos);
            /* AE bug ? In some case, the gadget value is not updated properly. */
            /* This is the case when the new value exceeds the current limit.   */
            AE_SetGadget(app, window, GADID_YPOS, inputCfg->is_YPos);
            AE_SetGadget(app, window, GADID_HEIGHT, inputCfg->is_Height);
            break;


        case GADID_HIRES:
            flags &= ~VLABF_HIRES;
            xScale = 1;
            if (value) {
                flags |= VLABF_HIRES;
                xScale = 2;
            }

            inputCfg->is_Flags = flags;
            width = VLab_FrameWidth(flags);

            /*
            ** Update XPos/Width gadgets.
            ** Both XPos/Width are scaled when they overflow the current
            ** frame width or when the user switches to HiRes mode.
            ** Otherwise, only the limits are changed.
            */

            if (value) {
                inputCfg->is_XPos  *= 2;
                inputCfg->is_Width *= 2;
            }
            else {
                if (inputCfg->is_XPos + inputCfg->is_Width > width) {
                    inputCfg->is_XPos  = (inputCfg->is_XPos / 2) & ~1;
                    inputCfg->is_Width = (inputCfg->is_Width / 2) & ~1;
                }
            }

            AE_SetSlider(app, window, GADID_XPOS, inputCfg->is_XPos, 0, width - inputCfg->is_Width);
            AE_SetSlider(app, window, GADID_WIDTH, inputCfg->is_Width, xScale, width - inputCfg->is_XPos);
            /* AE bug ? In some case, the gadget value is not updated properly. */
            /* This is the case when the new value exceeds the current limit.   */
            AE_SetGadget(app, window, GADID_XPOS, inputCfg->is_XPos);
            AE_SetGadget(app, window, GADID_WIDTH, inputCfg->is_Width);
            break;


        case GADID_COLOR:
            flags &= ~VLABF_COLOR;
            if (value)
                flags |= VLABF_COLOR;
            inputCfg->is_Flags = flags;
            break;


        /*
        **              ########## Clipping ##########
        **
        **
        ** The following values shall be rounded properly.
        ** X, Width  : granule = 4 (HiRes) or 2 (LoRes)
        ** Y, Height : granule = 1 (single frame) or 2 (interlaced mode)
        ** Each time the user changes one setting, the maximum limit of
        ** this setting's counter-part is modified (xpos<->width and
        ** ypos<->height)
        */

        case GADID_XPOS:
            if (value > inputCfg->is_XPos)
                value += xScale - 1;

            value &= -xScale;
            inputCfg->is_XPos = value;
            AE_SetGadget(app, window, GADID_XPOS, value);
            AE_SetSlider(app, window, GADID_WIDTH, inputCfg->is_Width, xScale, width - value);
            break;


        case GADID_YPOS:
            if (value > inputCfg->is_YPos)
                value++;

            if (flags & VLABF_LACE)
                value &= -yScale;

            inputCfg->is_YPos = value;
            AE_SetGadget(app, window, GADID_YPOS, value);
            AE_SetSlider(app, window, GADID_HEIGHT, inputCfg->is_Height, yScale, height - value);
            break;


        case GADID_WIDTH:
            if (value > inputCfg->is_Width)
                value += xScale - 1;

            value &= -xScale;
            inputCfg->is_Width = value;
            AE_SetGadget(app, window, GADID_WIDTH, value);
            AE_SetSlider(app, window, GADID_XPOS, inputCfg->is_XPos, 0, width - value);
            break;


        case GADID_HEIGHT:
            if (value > inputCfg->is_Height)
                value++;

            if (flags & VLABF_LACE)
                value &= -yScale;

            inputCfg->is_Height = value;
            AE_SetGadget(app, window, GADID_HEIGHT, value);
            AE_SetSlider(app, window, GADID_YPOS, inputCfg->is_YPos, 0, height - value);
            break;


        /*
        **              ########## Monitoring ##########
        */

        case GADID_COLORMONITOR:
            flags = settings->vs_MonFlags & ~MONITOR_COLOR;
            if (value)
                flags |= MONITOR_COLOR;

            settings->vs_MonFlags = flags;
            set_monitoring_flags(settings);

            if (monitoring && !(inputCfg->is_Flags & VLABF_BW_SOURCE))
                MonitorChangeMode(monitoring, value);
            break;


        case GADID_DISABLE_PIP:
            flags = settings->vs_MonFlags & ~MONITOR_NO_PIP;
            if (value)
                flags |= MONITOR_NO_PIP;

            settings->vs_MonFlags = flags;
            if (monitoring) {
                CloseMonitorWindow(monitoring);
                monitoring = open_monitor(app, event, settings);
            }
            break;


        case GADID_RESOLUTION:
            settings->vs_Resolution = value;

            if (inputCfg->is_VideoStd >= VIDEO_525)
                value += MONITOR_SIZES;

            /*
            ** Resize the monitoring window. If this fails, the monitoring
            ** is no longer active.
            */

            if (monitoring)
                monitoring = ResizeMonitor(monitoring, app->PubScreenName, value);

            break;


        case GADID_OPENMONITOR:
            if (!monitoring)
                monitoring = open_monitor(app, event, settings);
            break;


        /*
        **              ########## RESET ##########
        */

        case GADID_RESET:
            /*
            ** Reset input specific settings to their last saved values.
            */
            settings->vs_Settings[settings->vs_Input] = SavedSettings.vs_Settings[settings->vs_Input];
            updateGadgets(app, window, inputCfg);

            /*
            ** Update vlab's internal state.
            */

            VLab_ChangeFilters(inputCfg->is_Filters);
            VLab_ChangeHue(inputCfg->is_Hue);
            VLab_ChangeFlags(inputCfg->is_Flags & MONITORING_MASK);
            VLab_ChangeVideo(value);
            VLab_LumaCorrection(inputCfg->is_Gamma, inputCfg->is_Brightness, inputCfg->is_Contrast);

            if (monitoring) {
                MonitorChangeMode(monitoring, ((settings->vs_MonFlags & MONITOR_COLOR) &&
                                                    !(inputCfg->is_Flags & VLABF_BW_SOURCE) ? TRUE : FALSE));
            }
            break;
    }

    return monitoring;
}


/****i* VLab/updateGadgets **************************************************
*
*   NAME
*   updateGadgets -- Update gadgets related to input specific settings.
*
*   SYNOPSIS
*   updateGadgets(app, window, inputCfg)
*
*   static void updateGadgets(AE_AppObjPtr, AE_DialogPtr, struct InputSettings *);
*
*   FUNCTION
*   Update the gadgets that reflect input specific settings (image size and
*   dimension, filters, video mode, etc).
*
*   INPUTS
*   app      - Application object obtained by AE_SetupPlugInTagList().
*   window   - Window pointer obtained by PlugIn_OpenWindow().
*   inputCfg - Input specific settings.
*
*   SEE ALSO
*
*****************************************************************************
*
*/


static void updateGadgets(AE_AppObjPtr app, AE_DialogPtr window, struct InputSettings *inputCfg)
{
    ULONG xScale, yScale, max_width, max_height;
    ULONG flags = inputCfg->is_Flags;

    AE_SetGadget(app, window, GADID_BANDPASS, FILTER_BANDPASS_R(inputCfg->is_Filters));
    AE_SetGadget(app, window, GADID_CORING, FILTER_CORING_R(inputCfg->is_Filters));
    AE_SetGadget(app, window, GADID_WEIGHT, FILTER_FACTOR_R(inputCfg->is_Filters));
    AE_SetGadget(app, window, GADID_VTR, ((flags & VLABF_VTR) ? 1 : 0));
    AE_SetGadget(app, window, GADID_PREFILTER, ((flags & VLABF_PREFILTER) ? 0 : 1));
    AE_SetGadget(app, window, GADID_BW_SOURCE, ((flags & VLABF_BW_SOURCE) ? 1 : 0));

    AE_SetGadget(app, window, GADID_HIRES, ((flags & VLABF_HIRES) ? 1 : 0));
    AE_SetGadget(app, window, GADID_LACE, ((flags & VLABF_LACE) ? 1 : 0));
    AE_SetGadget(app, window, GADID_COLOR, ((flags & VLABF_COLOR) ? 1 : 0));
    AE_SetGadget(app, window, GADID_GAMMA, inputCfg->is_Gamma);
    AE_SetGadget(app, window, GADID_BRIGHT, inputCfg->is_Brightness);
    AE_SetGadget(app, window, GADID_CONTRAST, inputCfg->is_Contrast);
    AE_SetGadget(app, window, GADID_HUE, inputCfg->is_Hue);
    AE_SetGadget(app, window, GADID_VIDEO, inputCfg->is_VideoStd);

    /*
    ** Update image dimension/position gadgets.
    */

    max_height = VLab_FrameHeight(inputCfg->is_VideoStd, flags);
    max_width  = VLab_FrameWidth(flags);
    xScale = (flags & VLABF_HIRES) ? 4 : 2;
    yScale = (flags & VLABF_LACE)  ? 2 : 1;

    AE_SetSlider(app, window, GADID_XPOS, inputCfg->is_XPos, 0, max_width - inputCfg->is_Width);
    AE_SetSlider(app, window, GADID_WIDTH, inputCfg->is_Width, xScale, max_width - inputCfg->is_XPos);
    AE_SetSlider(app, window, GADID_YPOS, inputCfg->is_YPos, 0, max_height - inputCfg->is_Height);
    AE_SetSlider(app, window, GADID_HEIGHT, inputCfg->is_Height, yScale, max_height - inputCfg->is_YPos);

    /* AE bug ? In some case, the gadget value is not updated properly. */
    /* This is the case when the new value exceeds the current limit.   */

    AE_SetGadget(app, window, GADID_XPOS, inputCfg->is_XPos);
    AE_SetGadget(app, window, GADID_WIDTH, inputCfg->is_Width);
    AE_SetGadget(app, window, GADID_YPOS, inputCfg->is_YPos);
    AE_SetGadget(app, window, GADID_HEIGHT, inputCfg->is_Height);
}


/****** VLab/PlugIn_Apply ***************************************************
*
*   NAME
*   PlugIn_Apply -- Apply the plug-in's effect
*
*   SYNOPSIS
*   PlugIn_Apply(app, event)
*
*   void PlugIn_Apply(AE_AppObjPtr, AE_EventPtr);
*
*   FUNCTION
*   Digitize a picture based on the current settings. A new paint window is
*   opened only if the operation succeeds.
*
*   INPUTS
*   app    - Application object obtained by AE_SetupPlugInTagList().
*   event  - Event pointer. event->Object points to the object the effect
*            shall be applied to.
*
*   RESULT
*   event->Error : Optional error code.
*
*   SEE ALSO
*
*****************************************************************************
*
*   The YUV buffer required by VLab_Grab() has been allocated when we opened
*   the window. Thus, the pointer remains valid even if the user digitizes
*   several pictures without closing the window.
*
*   The function reset all "flags" settings before processing. Some of those
*   flags cannot be changed when the user modify them because this collides
*   with the monitoring feature.
*/


void PlugIn_Apply(AE_AppObjPtr app, AE_EventPtr event)
{
    struct InputSettings *inputCfg = &CurSettings.vs_Settings[CurSettings.vs_Input];
    long errCode = PLUGIN_ERROR_MEMORY;     /* Default : No memory error */
    AE_ObjPtr Picture;                      /* Current Picture object to grab to. */

    /*
    ** Allocate a new picture object (we'll display the new image there)
    ** then create an access cache for this object.
    */

    if ( (Picture = AE_AllocPicture(app, inputCfg->is_Width, inputCfg->is_Height,
                ((inputCfg->is_Flags & VLABF_COLOR) ? PIC_RGB : PIC_GRAYSCALE))) ) {

        int  vlabErr, colorFlag;
        ULONG mode, line;

        /*
        ** The monitoring activity uses different settings. Make sure we use
        ** the right ones for the final shot.
        */
        VLab_ChangeFlags(inputCfg->is_Flags);
        mode = (inputCfg->is_Flags & VLABF_HIRES) ? CAPTURE_HIRES : CAPTURE_LORES;
        colorFlag = ((inputCfg->is_Flags & (VLABF_COLOR | VLABF_BW_SOURCE)) == VLABF_COLOR);

        AE_BeginOperation(app, Picture, "Digitizing...");
        if (VLab_InitHardware())
            vlabErr = VLABERR_I2C;
        else
            vlabErr = VLab_Grab(inputCfg->is_XPos, inputCfg->is_YPos, inputCfg->is_Width, inputCfg->is_Height,
                            mode, colorFlag);

        /*
        ** Fill-in the picture object. In greyscale mode, the Luma component is
        ** used as is.
        */

        if (vlabErr == VLABERR_OK) {
            UBYTE *buffer;
            ULONG step;
            ULONG colorMode;

            if (colorFlag) {
                buffer = RGB_Buffer;
                step = inputCfg->is_Width * 3;
                colorMode = BITMAP_TYPE_RGB;
                VLab_YUV_to_RGB(buffer, inputCfg->is_Width * inputCfg->is_Height,
                        ((inputCfg->is_Flags & VLABF_HIRES) ? CAPTURE_HIRES : CAPTURE_LORES));
            }
            else {
                buffer = VLab_GetYBuffer();
                step = inputCfg->is_Width;
                colorMode = BITMAP_TYPE_GRAY8;
            }

            /*
            ** ArtEffect bitmap is filled line by line (there's no way to copy
            ** the image in one go).
            */

            for (line = 0; line < inputCfg->is_Height; line++, buffer += step)
                AE_WriteHLine(app, Picture, buffer, 0, line, inputCfg->is_Width, colorMode);
        }

        AE_EndOperation(app, Picture);

        if (vlabErr == VLABERR_OK) {
            AE_SetPicture(app, Picture);
            errCode = PLUGIN_ERROR_NONE;
        }
        else {
            const char *text;

            AE_FreePicture(app, Picture);
            errCode = PLUGIN_ERROR_CUSTOM;
            text = VLab_GetErrorString(vlabErr);
            AE_DoRequestTags(app, AER_TYPE_INFO, AER_BodyText, (ULONG) text, AER_TitleText, (ULONG) "VLab error", TAG_DONE);
        }
    }

    event->Error = errCode;
}


/****i* VLab/set_monitoring_flags *******************************************
*
*   NAME
*   set_monitoring_flags -- Set monitoring specific flags
*
*   SYNOPSIS
*   set_monitoring_flags(settings)
*
*   void set_monitoring_flags(struct VLabSettings *);
*
*   FUNCTION
*   Initialize the VLab hardware for LoRes non-lace scanning.
*
*   INPUTS
*   settings - Pointer to the actual settings.
*
*   SEE ALSO
*
*****************************************************************************
*
*/


static void set_monitoring_flags(struct VLabSettings *settings)
{
    ULONG flags = settings->vs_Settings[settings->vs_Input].is_Flags & MONITORING_MASK;

    VLab_ChangeFlags(flags);
}


/****i* VLab/open_monitor ***************************************************
*
*   NAME
*   open_monitor -- Open the monitoring window
*
*   SYNOPSIS
*   monitorHandle = open_monitor(app, event, settings)
*
*   struct VLabMonitor *open_monitor(AE_AppObjPtr, AE_EventPtr,
*                               struct VLabSettings *);
*
*   FUNCTION
*   Open the monitoring window, which is either a standard window or a PiP
*   layer.
*
*   INPUTS
*   app      - Application object pointer.
*   event    - Event pointer.
*   settings - Pointer to the current settings.
*
*   RESULT
*   monitorHandle - Pointer to a private Monitor structure, or NULL if the
*           monitoring is not available for some reason. If the monitoring
*           fails, the function displays an error requester and set the
*           event's error code to PLUGIN_ERROR_CUSTOM.
*
*   SEE ALSO
*
*****************************************************************************
*
*/


static struct VLabMonitor *open_monitor(AE_AppObjPtr app, AE_EventPtr event, struct VLabSettings *settings)
{
    struct VLabMonitor *monitor;
    ULONG resolution = settings->vs_Resolution;
    ULONG monFlags = settings->vs_MonFlags;

    if (settings->vs_Settings[settings->vs_Input].is_VideoStd >= VIDEO_525)
        resolution += MONITOR_SIZES;

    /*
    ** Setup monitoring settings.
    */

    if (settings->vs_Settings[settings->vs_Input].is_Flags & VLABF_BW_SOURCE)
        monFlags &= ~MONITOR_COLOR;

    if ( (monitor = OpenMonitorWindow(app->PubScreenName, settings->vs_WLeft, settings->vs_WTop,
                        resolution, monFlags)) ) {
        set_monitoring_flags(settings);
    }
    else if (event) {
        event->Error = PLUGIN_ERROR_CUSTOM;
        AE_DoRequestTags(app, AER_TYPE_INFO, AER_BodyText, (ULONG) "Monitoring not available.", (ULONG) "GUI error", TAG_DONE);
    }

    return monitor;
}

