#if 1

    #include "project.h"

    // ----------------------------------------------------------------
    // every function in this source file can be marked as   static
    // except for the "constructor" function
    // ----------------------------------------------------------------

    static MAINTYPE EDITId = SOB_TYPE_NO;

// to allow a widget to change the default handling of a SOB
//  1) create a COPY of the default SOB METHODS TABLE in the widget's code scope
//  2) overwrite the functional pointer, in that COPY, to the address of an
//     appropriate custom function.
//     We do that when the first instance of this widget class is instantiated.
//  3) and then the pointer, in the SOB data, to the default  SOB METHODS TABLE
//     with a pointer to our COPY
//     We do that, after step 2, when the first instance of this widget class is instantiated.

//  here we make SPACE for the COPY of the SOB METHODS TABLE

SOBMETHODSTABLE  EDITSOBMETHODSTABLE;

WNDPROC    wpOrigEDITProc ;

EDITOBJECT* EDITLastCreated;

// ---------------------------------

HHOOK    editHook = NULL;

static LRESULT CALLBACK cb_editHook (int nCode, WPARAM wParam, LPARAM lParam)
{
    //   myInt nCode,    // hook code
    //   WPARAM wParam,  // virtual-key code
    //   LPARAM lParam   // keystroke-message information

    if (nCode < 0 || nCode != HC_ACTION)
    return CallNextHookEx(editHook, nCode, wParam, lParam);

    // If this is a repeat or the key is being released, ignore it.
    if (lParam & 0x80000000) // || lParam & 0x40000000)
    return CallNextHookEx(editHook, nCode, wParam, lParam);

    SOBDATA* thisSob = sob_find_hwnd(GetFocus());

    if ( thisSob NEQU NULL )
    {
        PostMessage
        ( appHWND
        , WM_APP + 1        // message
        , wParam            // wParam
        , (LPARAM)thisSob   // lParam
        );
    }
    return CallNextHookEx(editHook, nCode, wParam, lParam);
}

// ---------------------------------

static LRESULT cb_intercept_edit_input_generic (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    // as the result of a WM_KEYBOARD hook
    // this function will be called for ALL   edit  widgets (not RichTextEdit widgets)

    // The HOOK function will have identified the HWND (widget) to which the keystroke belongs.
    // Then it POSTs a message to our application, the message contains the keycode and a  SOBDATA * (based
    // on the HWND) .
    // as a consequence of the POST, the original keystroke, if it generates a message, will end up BEHIND
    // i.e. AFTER  our POSTed message in the applications message queue.
    // So we could, if needed, extract the original keystroke message and delete/replace it.

    TCHAR inChar = (TCHAR)wParam;
    EDITOBJECT* thisObject = (EDITOBJECT*) lParam;

    // now see if this specific EDIT widget has a EVENTHANDLER for character input

    debugn((TCHAR*)_TEXT("cb_intercept_edit_input_generic "));  debugIntn((myUInt)message);

    debugSpaces(2);  debugIntn((myUInt)wParam); debugSpaces(2); debugInt((myUInt)lParam);
    EDIT.GetTitle( thisObject, txtBuffer, 256);

    debug(txtBuffer);
    return msg_handled_by_application;
}

#if EDIT_HAS_SUBCLASS

    // ----------------------------------------------------------------
    //  example of a custom  interception function     for a  subclassed   widget
    // ----------------------------------------------------------------

    static LRESULT CALLBACK EDIT_SUBCLASS_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    union
    {
        SOBDATA* thisSob;
        EDITOBJECT* thisObject;
    } promote = {};

    TCHAR ch = (TCHAR)wParam;

    promote.thisSob = sob_find_hwnd(hwnd);
    if (promote.thisSob)
    {
        switch (msg)
        {
            case WM_CHAR:

                if (   ( (ch < 0x30) LAND  ( ch EQU 0x20) )  LOR (ch > 0x39)) return 0;
                debugInt((myUInt)ch);
                wParam = 0x41;
                break;

            default:
                break;
        }
    }
    return CallWindowProc(wpOrigEDITProc, hwnd, msg, wParam, lParam);
}

#endif

// ---------------------------------

#if EDIT_HAS_OWNERDRAWN
    // ----------------------------------------------------------------
    //  example of a custom  ownerdrawn handler
    // ----------------------------------------------------------------

    OWNDERDRAWNHANDLER EDIT_OD_HANDLER;

static void  EDIT_OD_HANDLER(SOBDATA* sData, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    // when the WINDOW message handler is sent a  WM_DRAWITEM message, it will scan
    // the SOB DATA to find the SOB with the HWND referenced by the WM_DRAWITEM message's
    // (LPDRAWITEMSTRUCT)lParam structure.
    // If the  HWND  is found then that SOB's ownerDrawnHandler field is checked for an
    // entry (i.e.  NEQU NULL). If NEQU NULL then the value is treated as a functional pointer
    // a EDIT_OD_HANDLER function!

    // the code, bDrawButtonRGB,  is common to BUTTONs and STATIC widgets and is fairly involved.
    // So it has been made a common function located in the  ownderdraw_support files.

    bDrawButtonRGB(sData, lpDrawItemStruct);
}

#endif

// ---------------------------------

#if EDIT_HAS_RGB

    EDITFNC_SETVALUE EDIT_SetRGB;
static  myUInt EDIT_SetRGB(EDITOBJECT* thisObject, myUInt rgbValue)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetRGB given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }
    return sob_SetRGB(thisObject->psSobData, (COLORREF)rgbValue);
}
#endif

// ---------------------------------

#if EDIT_HAS_STATE
    // some widget types have a  STATE
    // e.g. menu item ticked, radio button, check button

    EDITFNC_GETVALUE EDIT_GetState;
static  myUInt EDIT_GetState(EDITOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.GetState given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    return
    (  (myInt)SendMessage(thisObject->hWnd, BM_GETCHECK, 0, 0)
    EQU BST_CHECKED
    );
}

// ---------------------------------

EDITFNC_SETVALUE  EDIT_SetState;
static  myUInt EDIT_SetState(EDITOBJECT* thisObject, myUInt check)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetState given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }
    SOBDATA* pData = thisObject->ppSobData;

    SOBDATA* cData = sob_get_first_child(pData);
    while (cData)
    {
        if (cData->type EQU  EDITId)
        {
            SendMessage
            (cData->hWnd
            , BM_SETCHECK
            , (WPARAM)BST_UNCHECKED
            , 0
            );
        }
        cData = sob_get_next_child(cData);
    }

    if (check) check = BST_CHECKED;
    else  check = BST_UNCHECKED;

    SendMessage
    (thisObject->hWnd
    , BM_SETCHECK
    , (WPARAM)check
    , 0
    );
    return (check EQU BST_CHECKED);
}

// ---------------------------------

EDITFNC_SHAKE EDIT_ToggleState;
static  myUInt EDIT_ToggleState(EDITOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.ToggleState given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    return  EDIT.SetState(thisObject, (EDIT.GetState(thisObject)  EQU 0));
}

#endif

// ---------------------------------

#if  EDIT_HAS_EVENTHANDLER

    // example of a EVENTHANDLER: in this case when the widget is clicked on with the mouse
    // Careful this  function is only valid for widgets that receive
    //   WM_COMMAND messages   carrying a  BN_CLICKED notification

    static void EDIT_object_clicked(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam, SOBDATA * sData)
{
    EDITOBJECT* thisObject = (EDITOBJECT*)lParam;
    WORD NotificationCode = HIWORD(wParam);
    WORD identifier = LOWORD(wParam);

    switch (NotificationCode)
    {
        case EN_CHANGE: // BN_CLICKED :
            swprintf_s
            (txtBuffer, szTxtBuffer, L" EDIT clicked 0x%04x  0x%04x  0x%04x\n"
            , thisObject->sIndex
            , NotificationCode
            , identifier);    addText(txtBuffer);

            //          EDIT.ToggleState(thisObject);

            break;

        default:
            //           #if DebuggingAid
            //         if ((NotificationCode  EQU EN_UPDATE)   LOR(  NotificationCode EQU EN_CHANGE) ) ;
            //   EN_UPDATE  = before
            //   EN_CHANGE  = after

            swprintf_s
            (txtBuffer, szTxtBuffer, L" EDIT unhandled notification 0x%04x  0x%04x  0x%04x\n"
            , thisObject->sIndex
            , NotificationCode
            , identifier);
            // addText(txtBuffer);
            //            #endif

            break;
    }
}

EDITFNC_SETEVENTHANDLER EDIT_SetEventHandler;
static myUInt EDIT_SetEventHandler(EDITOBJECT* thisObject, EVENTHANDLER value)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetEventHandler given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }
    return SOB.SetEventHandler(SOBOBJ thisObject, value);
}

#endif

// ---------------------------------

#if  EDIT_HAS_EMPTY
    // some widget types can be emptied e.g. combo boxes, RichTextEdit ..

    EDITFNC_SHAKE EDIT_Empty;
static  myUInt EDIT_Empty(EDITOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.Empty given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    SendMessage(thisObject->hWnd,  WM_SETTEXT, 0, (LPARAM)_TEXT('\0'));

    return  SOB.Empty(SOBOBJ thisObject);
}

#endif

// ---------------------------------

#if  EDIT_HAS_GREY
    // some widget types can be greyed e.g. menu items, some button types (rare but possible)

    EDITFNC_SETVALUE EDIT_GetGrey;
static  myUInt EDIT_GetGrey(EDITOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.GetGrey given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }
    return  SOB_OK;
}

EDITFNC_SETVALUE EDIT_SetGrey;
static  myUInt EDIT_SetGrey(EDITOBJECT* thisObject, myUInt value)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetGrey given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    return  SOB_OK;
}

#endif

// ---------------------------------

#if  EDIT_HAS_SELECTION
    // some widget types have selectable items
    // e.g. COMBO boxes, edit fields

    EDITFNC_GETVALUE EDIT_GetSelectionIndex;
static  myUInt EDIT_GetSelectionIndex(EDITOBJECT* thisObject)
{

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.GetSelectionIndex given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    #if EDIT_IS_2D
        return (myInt)SendMessage(thisObject->hWnd, CB_GETCURSEL, 0, 0);
    #endif

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

EDITFNC_SETVALUE EDIT_SetSelectionIndex;
static  myUInt EDIT_SetSelectionIndex(EDITOBJECT* thisObject, myUInt value)
{
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetSelectionIndex given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    #if EDIT_IS_2D
        // Sets the ZERO based index to be  the currently selected item of the combo
        return (int)SendMessage(thisObject->hWnd, CB_SETCURSEL, value, 0);
    #endif

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

EDITFNC_GETVALUE EDIT_GetItemCount;
static  myUInt EDIT_GetItemCount(EDITOBJECT* thisObject)
{

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.GetItemCount given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    #if EDIT_IS_2D

        return (myUInt)SendMessage(thisObject->hWnd, CB_GETCOUNT, 0, 0);
    #endif
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

EDITFNC_NEWITEMTEXT EDIT_AddItem;
static myUInt EDIT_AddItem(EDITOBJECT* thisObject, TCHAR* pText)
{
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.AddItem given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    #if EDIT_IS_2D
        int index;

        index = (int)SendMessage(thisObject->hWnd, CB_ADDSTRING, 0, (LPARAM)pText);
        SendMessage(thisObject->hWnd, CB_SETITEMDATA, (WPARAM)index, (LPARAM)index);
        return (myUInt)index;
    #endif

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

EDITFNC_GETITEMTEXT EDIT_GetItemText;
static TCHAR* EDIT_GetItemText(EDITOBJECT* thisObject, myUInt index)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULL;

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.GetItemText given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return NULL;
    }

    #define  TitleMax 255
    static TCHAR title[TitleMax + 1] = { 0 };

    myUInt len = (myUInt) SendMessage(thisObject->hWnd, CB_GETLBTEXTLEN, index, 0); // get character count

    if (len < TitleMax)   SendMessage(thisObject->hWnd, CB_GETLBTEXT, index, (LPARAM)title) ;

    return & title[0];
}

EDITFNC_SETITEMTEXT EDIT_SetItemText;
static myUInt EDIT_SetItemText(EDITOBJECT* thisObject, myUInt index, TCHAR* title)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetItemText given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    return (myUInt)  SendMessage(thisObject->hWnd, CB_INSERTSTRING, index, (LPARAM)title);

}

EDITFNC_SETVALUE EDIT_DeleteIndex;
static  myUInt EDIT_DeleteIndex(EDITOBJECT* thisObject, myUInt value)
{
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.DeleteIndex given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    #if EDIT_IS_2D
        // Sets the ZERO based index to be  the currently selected item of the combo
        return (int)SendMessage(thisObject->hWnd, CB_DELETESTRING, value, 0);
    #endif
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return  SOB_OK;
}

EDITFNC_GETITEMDATA EDIT_GetItemData;
static myUInt EDIT_GetItemData(EDITOBJECT* thisObject, myUInt itemId)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULL;

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.GetItemData given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }

    return   SendMessage(thisObject->hWnd, CB_GETITEMDATA, itemId, 0);
}

EDITFNC_SETITEMDATA EDIT_SetItemData;
static myUInt EDIT_SetItemData(EDITOBJECT* thisObject, myUInt itemId, myUInt uData)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetItemData given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return SOB_NOK;
    }
    return  SendMessage(thisObject->hWnd, CB_SETITEMDATA, itemId, uData);;
}

#endif

EDITFNC_GETVALUE  EDIT_GetUserData;
static myUInt EDIT_GetUserData(EDITOBJECT* thisObject )
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULLV;

    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.GetUserData given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return NULLV;
    }

    return fnGetUserDataGeneric (SOBOBJ thisObject);
}

EDITFNC_SETVALUE EDIT_SetUserData;
static myUInt EDIT_SetUserData(EDITOBJECT* thisObject, myUInt value )
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;
    if (thisObject->type NEQU EDITId)
    {
        swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT.SetUserData given a non EDIT object\n"));  ReportError(txtBuffer);
        SOB.Report(SOBOBJ thisObject);
        return NULLV;
    }      return  fnSetUserDataGeneric(SOBOBJ thisObject, value);

}

// ---------------------------------
#if EDIT_HAS_TITLE

    EDITFNC_GETTEXTBUFFER EDIT_GetTitle;
static myUInt EDIT_GetTitle (EDITOBJECT* thisObject, TCHAR* txtBuffer, myUInt length)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULLV;

    myUInt len = SendMessage(thisObject->hWnd, WM_GETTEXTLENGTH, 0, 0);
    if (len < length)
    {
        return SendMessage(thisObject->hWnd, WM_GETTEXT, length-1, (LPARAM)txtBuffer);
    }

    return   -1 ;
}

EDITFNC_GETVALUE EDIT_GetTitleLength;
static myUInt EDIT_GetTitleLength(EDITOBJECT* thisObject )
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULLV;
    return  SendMessage(thisObject->hWnd, WM_GETTEXTLENGTH, 0, 0);
}

// ---------------------------------

EDITFNC_SETTEXT  EDIT_SetTitle;
static myUInt EDIT_SetTitle(EDITOBJECT* thisObject, TCHAR* txt)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    SendMessage(thisObject->hWnd, EM_SETSEL, 0, -1);

    SendMessage(thisObject->hWnd, WM_SETTEXT, 0, (LPARAM)txt) ;

    return SOB_OK;
}
#endif

// ---------------------------------

SOBFNC_GETTEXT EDIT_SOB_GetType;
static TCHAR* EDIT_SOB_GetType(SOBDATA* thisSob)
{
    static TCHAR objText[] = TEXT("EDIT");
    return  objText;
}

EDITFNC_GETTEXT EDIT_GetType;
static  TCHAR* EDIT_GetType(EDITOBJECT* thisObject)
{
    if (fnInvalidSobGeneric(SOBOBJ thisObject))return NULL;
    return   EDIT_SOB_GetType(SOBOBJ thisObject);
}

// ---------------------------------

EDITFNC_SETVALUE EDIT_SetShow;
static  myUInt EDIT_SetShow(EDITOBJECT* thisObject, myUInt value)
{
    // this will need customising if the widget is complex
    // i.e. is really a member of a set of  widgets that can not all appear at the same time
    // e.g. a radio button     or an  overlay set

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return SOB.SetShow(SOBOBJ thisObject, value);
}

// ---------------------------------
#if EDIT_HAS_TEXT

    SOBFNC_GETTEXT EDIT_SOB_GetText;
static TCHAR* EDIT_SOB_GetText(SOBDATA* thisSob)
{
    static TCHAR objText[] = TEXT("EDIT");
    return  objText;
}

#endif
// ---------------------------------

#if EDIT_HAS_STRETCH

    EDITFNC_SETVALUE EDIT_SetStretch;
static  myUInt EDIT_SetStretch(EDITOBJECT* thisObject, myUInt value)
{

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    SOB.SetStretch(SOBOBJ thisObject, value);

    return SOB.SetShow(SOBOBJ thisObject, value);
}

#endif

// ---------------------------------
#if EDIT_HAS_LAST

    EDITFNC_LASTOBJECT EDIT_LastWidget;
static  EDITOBJECT*  EDIT_LastWidget( )
{
    return EDITLastCreated;
}
#endif

// ----------------------------------------------------------------
// the "destructor" function
// ----------------------------------------------------------------
SOBFNC_SHAKE EDIT_SOB_Delete;
static myUInt EDIT_SOB_Delete(SOBDATA* thisSob)
{
    // we need this function so that when a CONTAINER is deleted
    // it can trigger a delete of any widget related things of it's child
    // SOB OBJECTs here:-

    if (fnInvalidSobGeneric(thisSob)) return SOB_NOK;

    SOB.Delete(thisSob); // trigger the generic Delete

    return SOB_OK;
}

EDITFNC_SHAKE EDIT_Delete;
static  myUInt EDIT_Delete(EDITOBJECT* thisObject)
{
    // this will need customising if the widget is complex
    // i.e. is really a set of widgets
    // e.g. an  overlay set

    if (fnInvalidSobGeneric(SOBOBJ thisObject))return SOB_NOK;

    return   EDIT_SOB_Delete(SOBOBJ thisObject  );
}

// ----------------------------------------------------------------
//  inheritance from the underlying SOB
// ----------------------------------------------------------------

// if an inherited function is to be customised/overwritten
// then do it HERE:

EDITFNC_GETTEXT  fnGetTypeTextCustom;
static TCHAR* fnGetTypeTextCustom(EDITOBJECT* sData)
{
    return (TCHAR*)_TEXT("EDIT");
}

// ----------------------------------------------------------------
// the "constructor" function
// ----------------------------------------------------------------

EDITFNC_GETOBJECT EDIT_Create;
static EDITOBJECT* EDIT_Create(EDITCREATEPARAMETERS)
{
    union
    {
        SOBDATA* thisSob;
        EDITOBJECT* thisObject;
    } promote = {0};

    // ---------------------------------
    // in some cases a given widget can only be assigned to certain types of container
    // this is probably the best case to catch such exceptions and
    // permit / block them as appropriate

    // ---------------------------------
    // use the FONT assigned to the PARENT as guidelines for text size

    SOBDATA* pData = sob_get_base(gpSobIndex);
    int nbrLines = 1;  // some CONSTRUCTOR functions want 2 input parameters,
    // typicaly the 2nd parameter is the height of the widget
    // give as the nbr of text lines

    #if EDIT_IS_2D
        nbrLines= nbrLines* lines;
    #endif

    int height = 0;
    int width = 0;

    int_sob_text_size(pData, txt, &width, &height);

    // But the HEIGHT only covers text, we need to increase it to allow multiple text
    // lines   AND for any SOB surround
    int objHeight = height * nbrLines + 2; // 8;
    int objWidth = width + 20;

    // ---------------------------------
    // A  Screen OBject ( SOB )  is really a structure
    //    the GENERIC structure
    //    and a ptr to the VTABLE
    // ---------------------------------
    // An OBJECT instance is really a structure
    //    the GENERIC structure
    //    and a  ptr to the OBJECT's private functions
    // ---------------------------------
    // So a   SOB   and an    OBJECT     have identical structures
    // WHY:
    // This way we can create a SOB as a sort of generic OBJECT.
    // Some functions, predominantly concerned with
    //    screen size management
    //    screen position management
    //   are OBJECT independent.
    //
    // Then we can have an OBJECT specific "view" were each OBJECT type
    // can have its own strongly typed structure.
    // ---------------------------------

    if (!EDITId)
    {
        // ---------------------------------
        #if DeveloperMode

            union
            {
                myUInt* pEntry;
                void * pVoid;
            } reRef = {0};

            reRef.pVoid = & EDIT;
            myUInt* pEntry = reRef.pEntry;

            int entryCount = sizeof(EDITMETHODTABLE) / sizeof(void*);
            int i;
            for (i = 0; i < entryCount; i++)
            {
                if ((!*pEntry)   LOR(*pEntry BAND 0x07))
                { 
                    swprintf_s(txtBuffer, szTxtBuffer, _TEXT("EDIT methods table, entry: %02i,  not fully/correctly populated. 0x%llx\n"), i, *pEntry);
                    ReportError(txtBuffer);
                }
                pEntry++;
            }

        #endif

        // ---------------------------------

        EDITId = int_next_maintype();

        // to allow a widget to change the default handling of a SOB
        //  1) create a COPY of the default SOB METHODS TABLE in the widget's code scope
        //  2) overwrite the functional pointer, in that COPY, to the address of an
        //     appropriate custom function.
        //     We do that when the first instance of this widget class is instantiated.
        //  3) and then the pointer, in the SOB data, to the default  SOB METHODS TABLE
        //     with a pointer to our COPY
        //     We do that, after step 2, when the first instance of this widget class is instantiated.

        // ---------------------------------
        //    SPACE for the COPY of the SOB METHODS TABLE  was made at the TOP of this source file
        //    SOBMETHODSTABLE  EDITSOBMETHODSTABLE;
        // ---------------------------------
        // populate the COPY with the default SOB METHID TABLE

        fnInheritSOBmethods( &  EDITSOBMETHODSTABLE);

        // ---------------------------------
        // now populate those entries, in the COPY of the  SOB METHODS TABLE , that are
        // to be widget specific

        EDITSOBMETHODSTABLE.GetType = & EDIT_SOB_GetType;

        EDITSOBMETHODSTABLE.Delete = & EDIT_SOB_Delete;

        /*

        editHook = SetWindowsHookEx
        ( WH_KEYBOARD           // myInt idHook,       type of hook to install
        ,  cb_editHook           // HOOKPROC lpfn,    address of hook procedure
        , 0                     // HINSTANCE hMod,   handle to application instance
        , GetCurrentThreadId()  // DWORD dwThreadId  identity of thread to install hook for
        );
        */ 
    }

    // ---------------------------------

    promote.thisSob = sob_new_new(EDITId, SOB_CONTAINER_NO, objWidth, objHeight);
    if (promote.thisSob EQU NULL)
    {
        int_return_maintype();  //  give the custom EDITId back
        return NULL;
    }

    // ---------------------------------
    // replace the default SOB METHOD TABLE with our widget's custom table

    //  promote.thisSob->sVtbl = &EDITSOBMETHODSTABLE;

    // promote.thisSob->fncCustomDelete;    
    promote.thisSob->fncCustomGetTypeText = EDIT_SOB_GetType ;
    // promote.thisSob->fncCustomGetSubTypeText = EDIT_SOB_GetType;
    // promote.thisSob->fncCustomShow;

    promote.thisSob->txtRGB = -1; // use system default
    promote.thisSob->bgRGB  = -1; // use system default

    // ---------------------------------
    // create an instance of the  EDIT widget on the display

    PCWSTR objClassName = _TEXT(  "EDIT");
    // DWORD objStyle = WS_VISIBLE | WS_CHILD |   WS_BORDER |  WS_VSCROLL | ES_LEFT | ES_AUTOVSCROLL ;
    DWORD objStyle = WS_VISIBLE | WS_CHILD | WS_BORDER  | ES_LEFT    ;

    (void)sob_CreateWindowExNew(promote.thisSob, 0, objClassName, txt, objStyle);

    //    add_AppMessage(WM_APP + 1, &cb_intercept_edit_input_generic);

    #if EDIT_HAS_SUBCLASS
        // ---------------------------------
        // if you need to subclass the widget (i.e. intercept its internal message handling)
        // then insert the interception function here.

        wpOrigEDITProc = (WNDPROC)SetWindowLongPtr(promote.thisObject->hWnd, GWLP_WNDPROC, (LONG_PTR)EDIT_SUBCLASS_proc);
    #endif

    #if EDIT_HAS_OWNERDRAWN
        // ---------------------------------
        // if this widget is ownderdraw then link in the handler here

        promote.thisSob->ownerDrawnHandler = EDIT_OD_HANDLER;
    #endif

    #if EDIT_HAS_EVENTHANDLER
        // ---------------------------------
        // All OBJECTS are created with a prime/main  EVENTHANDLER
        // For this particular OBJECT the prime/main  EVENTHANDLER
        // is a left mouse click

        // Normally the  "user"  sets a, probably different, EVENTHANDLER for
        // each instantiated widget.

        // If you want you can set a default EVENTHANDLER which the
        // "user" can overwrite for a specific OBJECT
        //     promote.thisSob->eventHandler = (EVENTHANDLER)EDIT_object_clicked;
    #endif
    EDITLastCreated = promote.thisObject;
    return   promote.thisObject;
}

// ----------------------------------------------------------------
// the external view
// ----------------------------------------------------------------

// BE WARNED: must be in the same sequence as the struct defn in the  header file
// if you create custom functions / methods for this widget then they
// MUST be in the struct defn and initialised into this struct instance.

EDITMETHODTABLE  EDIT =
{
    EDIT_Create
    , EDIT_Delete

    #if EDIT_HAS_RGB
        , EDIT_SetRGB
    #endif

    #if EDIT_HAS_EMPTY
        , EDIT_Empty
    #endif

    #if EDIT_HAS_STATE
        , EDIT_GetState
        , EDIT_SetState
        , EDIT_ToggleState

    #endif

    #if EDIT_HAS_EVENTHANDLER
        , EDIT_SetEventHandler
    #endif

    #if  EDIT_HAS_GREY
        , EDIT_GetGrey
        , EDIT_SetGrey
    #endif

    #if  EDIT_HAS_SELECTION
        , EDIT_GetSelectionIndex
        , EDIT_SetSelectionIndex

        , EDIT_AddItem

        , EDIT_GetItemCount

        , EDIT_GetItemText
        , EDIT_SetItemText

        , EDIT_GetItemData
        , EDIT_SetItemData

        , EDIT_DeleteIndex

    #endif

    #if EDIT_HAS_TITLE

        , EDIT_GetTitle
        , EDIT_GetTitleLength
        , EDIT_SetTitle
    #endif

    #if EDIT_HAS_STRETCH
        , EDIT_SetStretch
    #endif

    #if EDIT_HAS_LAST
        , EDIT_LastWidget
    #endif

    , EDIT_GetUserData
    , EDIT_SetUserData

};

#endif
