Function Set_Record(Data, DictColPos, VirtualOnly, SkipSymbolics, Window)

/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Set_Record

    Description :   Updates an OpenInsight window's controls with the data being passed in.

    Notes       :   The primary purpose of this routine is to make it easier to update the contents of an OpenInsight
                    window without having to name the controls. This allows the developer to simulate the AREV method
                    which would automatically update the window's fields whenever @RECORD was update. OpenInsight
                    makes this more cumbersome by requiring each control to be updated individually using a property
                    setting.
                    
                    Set_Record provides a level of automation that simulate AREV's @RECORD support. The developer can
                    pass in a specific field position and the data that needs to be updated in that field or the
                    developer can pass in the entire record array. Set_Record will automatically query the window's
                    controls and update them as needed. Even symbolic fields will be updated accordingly.
                    
                    (NOTE: OpenInsight 7.2 introduced a new property - ATRECORD. This works the same as the RECORD
                    property when used in the Get_Property function. However, unlike the RECORD property it will update
                    the window's databound controls, including symbolic fields, when used in the Set_Property function.
                    This also simulates AREV's @RECORD support. However, it cannot be used to update specific fields at
                    a time or to update non-databound controls that need to be associated with database columns such as
                    OLE controls.)

                    Simulating @RECORD = RecordVar
                    ==============================
                    
                    In AREV, the entire data record could be updated at once by setting @RECORD to the new value. As
                    an example, if the developer needed to restore the content of the AREV window to the value of
                    record as it was read from the disk, the following line of code could be called:

                        @RECORD = WC_OREC% ; * Restore the contents of the original record.

                    To do the equivelent using Set_Record:

                        Set_Record(OrigRecord) ; // Restore the contents of the original record.

                    Simulating @RECORD<FieldVal> = FieldData
                    ========================================
                    
                    Individual database columns (aka fields) could also be updated by specifing the column number. In
                    AREV the code might look like this:

                        @RECORD<5> = 1000 ; * Put the customer number in place.

                    To do the equivelent using Set_Record:

                        Set_Record(1000, 5) ; // Put the customer number in place.

                    Value, subvalue, text, and subtext positions can also be updated by separating each delimiter
                    position with a comma. In AREV the code might look like this:

                        @RECORD<10, 2> = "8005552222" ; * Put the contact phone number in the
                                                        * 2nd value position of field 10.

                    To do the equivalent with this routine:

                        Set_Record("8005552222", "10,2") ; // Put the contact phone number in the
                                                           // 2nd value position of field 10.


                    Virtual Databound Controls
                    ==========================
                    
                    Sometimes controls that need to display live data are not truly databound to the database column.
                    A good example of this is if an ActiveX control is used to display and manage data. The internal
                    method OpenInsight uses to populate the window has no knowledge that these controls are connected
                    to the database in any way so they just get ignored.
                    
                    Set_Record provides support for this through the use of the user-defined property @POS. This will
                    virtually bind the control to a database column (or columns, see below). Use the CREATE event of
                    the window to set @POS. For single field controls you would do something like:

                        Set_Property(@Window:".COMBO_1", "@POS", 5)

                    For multi-field controls (e.g. AMV fields) like the SRP OLE EditTable, just separate each database
                    column number with @VM. For instance:

                        Set_Property(@Window:".OLE_EDITTABLE1", "@POS", 2:@VM:5:@VM:6:@VM:"":@VM:7)

                        Set_Property(@Window:".OLE_EDITTABLE2", "@POS", 2:@VM:"DESCRIPTION":@VM:6)

                    In the first example, the first command has a null embedded between database columns 6 and 7. This
                    is to illusrate how one would handle an EditTable column that is not associated with a database
                    column. In the case of the SRP OLE EditTable, this could be a column of buttons or hyperlinks which
                    are there for functionality and not data storage.

                    The second example has a word embedded between database columns 2 and 6. This is to illustrate how
                    one would handle an EditTable column that is virtually bound to a symbolic (calculated) column. Just
                    enter then name of the symbolic column and this routine will recalculate the value and put the data
                    into the the appropriate EditTable column.

                    Handling Symbolic (aka Calculated) Columns
                    ==========================================

                    As noted above in the Virtual Databound Controls section, non-databound controls can be virtually
                    bound to a symbolic column by using the @POS user-defined property. Set_Record will compute it using
                    the latest content from the data record.
                    
                    By default, controls that are truly databound to symbolic columns will be recalculated after all the
                    data fields have been updated. However, sometimes this is not necessary or desirable. Once instance
                    would be during the window's WRITE event. Since OpenInsight windows clear all controls during a
                    successful database write, there is no need to go through the additional effort to recalculate
                    symbolic columns. In these situations set the SkipSymbolic parameter to 1:
                    
                        Set_Record(RecordVar, "", Yes$)
                    
                    Another option for handling symbolics using Set_Record is to update them while leaving all data
                    fields alone. This is useful when changes have been made to a virtually databound control and there
                    is a need to update the dependent symbolic controls. This is done by passing "SYM" into the
                    DictColPos parameter:
                    
                        Set_Record("", "SYM")
                    
                    A Word About Data Conversions
                    =============================
                    
                    Native OpenInsight controls have validation and conversion settings which respond to the INVALUE
                    property. OLE controls, however, do not have validation and conversion settings that can be
                    configured in the Form Designer. Therefore, OLE controls must provide their own way of reporting
                    what validation and/or conversion patterns needed to be used. For the SRP OLE EditTable this is done
                    through the CellConv property. If a CellConv property has been set, the raw data will be converted
                    to this format first before the EditTable is visually updated. Otherwise, only internal data will be
                    displayed in the OLE control.

    Parameters  :
        Data            [in] -- The data that needs to updated into the controls. If DictColPos is not populated then
                                this should be an entire data record. Otherwise, it should only be the field data for
                                the indicated data column position.
        DictColPos      [in] -- The database column position being updated. If unassigned or null then Data is assumed
                                to be the entire data record. Value, subvalue, text, and subtext positions can also be
                                updated using commas to separate each delimiter position. See Notes for details.
        VirtualOnly     [in] -- Flag to determine if only virtually data databound controls should be updated. This
                                would be ideal during a READ event since has already updated the true databound
                                controls.
        SkipSymbolics   [in] -- Flag to determine if symbolic (calculated) columns should be updated. If unassigned or
                                null it will be Yes. This should be set to no if data is being updated during the WRITE
                                event. This will speed up the execution of this routine.
        Window          [in] -- The window being updated. If unassigned or null it will be @Window.
        Record         [out] -- The data record with updated values.

    History     :   (Date, Initials, Notes)
        01/27/06    dmb     Original programmer
        02/02/06    dmb     Add new parameter, VirtualOnly, to indicate that only virtually databound controls (e.g. OLE
                            controls) should be updated. Modified the logic so that virtually databound controls will be
                            updated even if there are no true databound controls bound to the same data column.
        02/03/06    dmb     Add support for value, subvalue, text, and subtext updates to a single field. Fix problem
                            where symbolic columns weren't being updated if only a specific field is being updated.
                            Improved support for virtually bound controls so virtually bound symbolic columns will be
                            updated even if there are no controls that are actually bound to the symbolic column.
        03/24/06    dmb     Change EQ to _EQC when checking for SRP OLE EditTable ProgIDs.
        03/24/06    dmb     If the entire record is being set then clear SRP OLE EditTables to remove accidental data
                            from a previous record.
        02/14/08    dmb     New feature: Rows As Columns. This checks the @ROWSASCOLUMNS UDP which populates SRP
                            EditTable rows with database column data. Thus, the format is inverted.
        02/15/08    dmb     New feature: Cells as Fields. This checks the @CELLSASFIELDS UDP which populates SRP
                            EditTable cells with specific single-valued database columns.
        02/17/08    dmb     New feature: If DictColPos is set to "SYM" then only symbolic columns will be recalculated
                            and displayed.
        02/17/08    dmb     Remove Utility("CURSOR") calls. This was causing to many visual distractions.
        03/04/08    dmb     Don't use Clear method on SRP EditTables if they are also the control with focus.
        03/18/08    rch     Use @CLEARFILL setting if provided to Clear SRP EditTables.
        12/09/10    rch     Move 'While Flag' statements after Remove statements to just before Repeat statements so
                            the last value in each array gets processed by loop's logic.
        12/01/12    krf     Implement SRP FastArray processes to improve performance. Also built reliance upon the
                            @EDITTABLES UDP for the current window to store all SRP EditTable controls. Thus, this needs
                            to be populated from within the form's CREATE event or from the promoted CREATE event.
        02/26/13    dmb     UDPCtrl was not being populated using the SRP_FastArray_Extract so it was getting the
                            handle to UDPCtrls rather than the value stored in the hash table. - [SRPFW-8]
        02/28/13    dmb     UDP column positions and names were not being properly extracted since the Remove statement
                            is unable to handle the two-dimensional array that tracks these. - [SRPFW-10]
        03/02/13    dmb     Fix a reference to CellConv by extracting array <2> rather than the whole value. This was
                            left out during the last major enhancement conversion. - [SRPFW-10]
        04/01/13    dmb     Fix a VNAV issue in Get_Column_Positions gosub. Original code created a For/Next loop setting
                            the variable i. This For/Next loop was replaced with a Remove loop, but the variable i was
                            not being set. - [SRPFW-10]

***********************************************************************************************************************/

$insert Logical
$insert OIWin_Equates
If Assigned(Window) else Window = @Window
If Window EQ "" then Window = @Window
WinId = Window
$insert OIWin_Comm_Init

Declare function    Get_property, Utility, OIWin_Compile_Impacts, Calculate, GetTickCount, SRP_FastArray_Create, SRP_FastArray_Extract, SRP_FastArray_GetVariable
Declare subroutine  Set_Property, Utility, Send_Message, Send_Event, SRP_FastArray_Release, SRP_FastArray_Insert

Benchmark   = ''
Total       = ''
ProcessCnt  = 0
ProcessDone = No$
Loop
    ProcessCnt += 1
Until ProcessCnt GT 7 OR ProcessDone = Yes$
    StartTime = GetTickCount()
    On ProcessCnt GoSub Initialize_Vars, Set_At_Variables, Get_Column_Positions, Update_Window, Update_Record, Update_Symbolics, Clean_Up
    EndTime = GetTickCount()
    Benchmark := "Process ":ProcessCnt:": ":(EndTime - StartTime):"ms|"
    Total += (EndTime - StartTime)
Repeat
Benchmark := "|Total ":Total:"ms"
* Call Msg(@Window, Benchmark)

GoSub Restore_At_Variables

Return Record

Initialize_Vars:
    If Assigned(Data)       else Data       = ""
    If Assigned(DictColPos) else DictColPos = ""
    Convert " " to "" in DictColPos
    
    Begin Case
        Case DictColPos EQ ""
            // DictColPos is null so the whole data record is being updated.
            WholeRecord = Yes$
            
        Case DictColPos _EQC "SYM"
            // DictColPos is set to update only symbolics. To do this the WholeRecord flag needs to be set.
            WholeRecord = Yes$

        Case Num(DictColPos[1, 1])
            // DictColPos is numerical so only a specific column is being updated.
            WholeRecord = No$
            
        Case Otherwise$
            // An unrecognized value was sent in. Just assume the whole data record is being updated.
            WholeRecord = Yes$
    
    End Case
    
    If Assigned(VirtualOnly)    else VirtualOnly    = No$
    If VirtualOnly EQ ""        then VirtualOnly    = No$
    If Assigned(SkipSymbolics)  else SkipSymbolics  = No$
    If SkipSymbolics EQ ""      then SkipSymbolics  = No$

    Controls    = ""        ; // List of OpenInsight controls that will be updated.
    Properties  = ""        ; // List of OpenInsight properties that will be set. For native OI controls this will be INVALUE. OLE controls will use appropriate properties.
    Values      = ""        ; // List of values that will be updated to the controls.
    EdtCols     = ""        ; // EditTable column positions. AMV data is mapped to specific column positions within the EditTable control.
    
    RowMaps            = RowMaps@        ; // @VM/@SVM common array from OIWin_Comm_Init containing a list of all dictionary column names, their column numbers, and formatting information.
    If RowMaps[-1, 1] = @VM then RowMaps[-1, 1] = ""
    RMCount = Count(RowMaps, @VM) + (RowMaps NE "")
    
    // @FM/@VM Common array of control, control type, and other relevant information. Subvalue 4 in each RowMaps entry has an index to this array.
    MasterRowMap = SRP_FastArray_Create(MasterRowMap@)
    MRCount = Count(MasterRowMap@, @FM) + (MasterRowMap@ NE "")

    FocusControl = Get_Property(Window, "FOCUS")    ; // Get the name of the control with focus will need this later.

    UpdatedOLEEditTables = ""        ; // Store the list of OLE EditTables already updated. Refer to this before executing a Clear method.
return

Set_At_Variables:
    If Window NE @Window then
        // Preserve the original values of @ variables in case a different window other than @Window is being updated.
        Transfer @ID        to OrigID
        Transfer @RECORD    to OrigRecord
        Transfer @DICT      to OrigDict
    end
    
    TableName = JoinMap@<1, 1>
    If TableName NE "" then
        // Set up @ variables so that symbolic (calculated) columns can be computed after the data record has been
        // updated.
        Dictionary = "DICT.":TableName
        Open Dictionary to @DICT then
            NumKeyCtrls = Count(KeyMap@, @FM) + (KeyMap@ NE "")
            @ID = ""
            For i = 1 to NumKeyCtrls
                @ID := Get_Property(KeyMap@<i, 1>, "INVALUE"):"*"
            Next i
            @ID[-1, 1] = ""
            
            Begin Case
                Case WholeRecord EQ Yes$ AND DictColPos _NEC "SYM"
                    DelimDepth = 1
                    @RECORD = Data
                 
                Case WholeRecord EQ No$
                    GoSub ParseDictColPos
                    If DelimDepth GT 1 then GoSub Update_Data_Array
                    @RECORD = Get_Property(Window, "RECORD")
                    @RECORD<DictColPos> = Data

                Case Otherwise$
                    // Most likely only updating symbolic columns. Make sure @RECORD is current.
                    @RECORD = Get_Property(Window, "RECORD")
                    
            End Case
                                
        end
    end else
        @RECORD = ""
    end
return

Get_Column_Positions:
    // Get the actual data column positions and the virtual (@POS) data column positions of the current window
    // to set up in look up arrays

    // Start with the actual data column positions. OpenInsight EditTable controls store data column positions
    // as an @SVM delimited array. This code will flatten this out so each each EditTable column will have its
    // own lookup reference.
    OICtrls                 = SRP_FastArray_Create()
    OICtrlColPos            = SRP_FastArray_Create()
    OICtrlEdtCol            = SRP_FastArray_Create()
    OICtrlTypes             = SRP_FastArray_Create()

    // These variables will track symbolic (calculated) column information so that the CALCULATE event or CALCULATE
    // function can be executed as needed.
    NumSymCtrls             = 0
    OISymCtrls              = SRP_FastArray_Create()
    OISymDepColPos          = SRP_FastArray_Create()
    OISymCtrlEdtCol         = SRP_FastArray_Create()
    OISymCtrlRowsAsColumns  = SRP_FastArray_Create()
    OISymCtrlCellsAsFields  = SRP_FastArray_Create()
    OISymCtrlSelPos         = SRP_FastArray_Create()
    OISymCtrlTypes          = SRP_FastArray_Create()
    OISymCtrlOrigText       = SRP_FastArray_Create()
    OISymCtrlColName        = SRP_FastArray_Create()

    OICtrlPos               = 1
    ControlSemanticPos      = 1
    ControlListItemPos      = 1
    Flag                    = ""
    Unused                  = ""

    Loop
        Remove OICtrl from ControlMap@ at OICtrlPos Setting Flag
        ControlSemantic = ControlSemantics@[ControlSemanticPos, @FM] ; ControlSemanticPos = Col2() + 1
        ControlListItem = ControlList@[ControlListItemPos, @FM] ; ControlListItemPos = Col2() + 1
        Unused      = ControlSemantic[1, @VM]
        ColName     = ControlSemantic[Col2() + 1, @VM]
        ColPos      = ControlSemantic[Col2() + 1, @VM]
        OICtrlType  = ControlListItem<1, 3>
        NumEdtCols  = Count(ColPos, @SVM) + 1   ; // There is always one even if it is null
        EdtColPos   = 0                         ; // Start the column counter at 0. Increase as each column is retrieved.
        
        ColPosFlag  = ""
        ColPosPos   = 1
        Loop
            Remove CurrColPos from ColPos at ColPosPos setting ColPosFlag
            EdtColPos += 1
            Begin Case
                Case CurrColPos GT 0
                    // Regular databound control.
                    SRP_FastArray_Insert(OICtrlColPos, -1, 0, 0, CurrColPos)
                    SRP_FastArray_Insert(OICtrls,      -1, 0, 0, OICtrl)
                    SRP_FastArray_Insert(OICtrlEdtCol, -1, 0, 0, EdtColPos)
                    SRP_FastArray_Insert(OICtrlTypes,  -1, 0, 0, OICtrlType)

                Case ColName<0, 0, EdtColPos> NE "" AND CurrColPos EQ "" AND OICtrlType NE "WINDOW"
                    // Must be a symbolic (calculated) control. If symbolics are not being skipped then collect
                    // necessary information for them to be updated later on.
                    If Not(SkipSymbolics) then
                        DepColPos   = ""
                        ColNames    = ColName<0, 0, EdtColPos>           
                        ColNameFlag = ""
                        ColNamePos  = 1
                        Loop
                            Remove ColName from ColNames at ColNamePos setting ColNameFlag
                            If Len(ColName) then
                                DepCols = OIWin_Compile_Impacts(TableName, ColName)

                                DepColsFlag = ""
                                DepColsPos  = 1
                                Loop
                                    Remove DepCol from DepCols at DepColsPos Setting DepColsFlag
                                    DepCol = Field(DepCol, "*", 2)
                                    If Num(DepCol) then
                                        
                                        // This is a database column position. If it hasn't already been identified then
                                        // append the tracking variables.
                                        Locate DepCol in DepColPos using @FM setting fPos else
                                            DepColPos<-1> = DepCol        ; // Track columns already found to avoid duplication

                                            // Information is being inserted rather than appended so the most independent
                                            // columns appear first in the list. This way the CALCULATE event is sent to
                                            // those controls and more dependent columsn will have the most current
                                            // information available.
                                            NumSymCtrls += 1
                                            SRP_FastArray_Insert(OISymCtrls, 1, 0, 0, OICtrl)
                                            SRP_FastArray_Insert(OISymDepColPos, 1, 0, 0, DepCol)
                                            SRP_FastArray_Insert(OISymCtrlEdtCol, 1, 0, 0, EdtColPos)
                                            SRP_FastArray_Insert(OISymCtrlRowsAsColumns, 1, 0, 0, "")   ; // Not needed here, but need to keep values synched for the UDP controls.
                                            SRP_FastArray_Insert(OISymCtrlCellsAsFields, 1, 0, 0, "")   ; // Not needed here, but need to keep values synched for the UDP controls.
                                            SRP_FastArray_Insert(OISymCtrlSelPos, 1, 0, 0, "")          ; // Not needed here, but need to keep values synched for the UDP controls.
                                            SRP_FastArray_Insert(OISymCtrlTypes, 1, 0, 0, OICtrlType)
                                            SRP_FastArray_Insert(OISymCtrlOrigText, 1, 0, 0, "")        ; // Not needed here, but need to keep values synched for the UDP controls.
                                            SRP_FastArray_Insert(OISymCtrlColName, 1, 0, 0, "")         ; // Not needed here, but need to keep values synched for the UDP controls.
                                        end

                                    end else
                                        // This is another symbolic column. Add it to the list so all dependent database
                                        // columns are collected.
                                        ColNames<-1> = DepCol
                                    end
                                While DepColsFlag
                                Repeat
                            end
                        While ColNameFlag
                        Repeat                        
                    end

            End Case
        While ColPosFlag
        Repeat
    While Flag
    Repeat
    
    // Now get the virtual (@POS) data column positions. SRP OLE EditTable controls store data column positions
    // as an @VM delimited array. This code will flatten this out so each each SRP OLE EditTable column will have its
    // own lookup reference.

    ControlMap = Get_Property(Window, "@EDITTABLES")
    Convert @FM to @RM in ControlMap
    UDPColPos = Get_Property(ControlMap, "@POS")
    UDPRowsAsColumns = Get_Property(ControlMap, "@ROWSASCOLUMNS")
    UDPCellsAsFields = Get_Property(ControlMap, "@CELLSASFIELDS")
    UDPTypes = Get_Property(ControlMap, "TYPE")
    UDPOrigTexts = Get_Property(ControlMap, "ORIG_TEXT")

    UDPCtrls                    = SRP_FastArray_Create()
    UDPCtrlColPos               = SRP_FastArray_Create()
    UDPCtrlRowsAsColumnsList    = SRP_FastArray_Create()
    UDPCtrlCellsAsFieldsList    = SRP_FastArray_Create()
    UDPCtrlSelPosList           = SRP_FastArray_Create()
    *     UDPCtrlTypes               = SRP_FastArray_Create()
    UDPCtrlEdtCol               = SRP_FastArray_Create()
    UDPCtrlOrigText             = SRP_FastArray_Create()

    ControlMapPos               = 1
    UDPColPosPos                = 1
    UDPRowsAsColumnsPos         = 1
    UDPCellsAsFieldsPos         = 1
    UDPTypesPos                 = 1
    UDPOrigTextsPos             = 1
    Flag                        = ""
    Unused                      = ""

    Loop
        ColPos = UDPColPos[UDPColPosPos, @RM]
        UDPColPosPos = Col2() + 1

        Remove UDPCtrl from ControlMap at ControlMapPos Setting Flag
        Remove UDPCtrlRowsAsColumns from UDPRowsAsColumns at UDPRowsAsColumnsPos Setting Unused
        Remove UDPCtrlCellsAsFields from UDPCellsAsFields at UDPCellsAsFieldsPos Setting Unused
        Remove UDPCtrlType from UDPTypes at UDPTypesPos Setting Unused
        Remove UDPOrigText from UDPOrigTexts at UDPOrigTextsPos Setting Unused
        
        If ColPos NE "" then
        
            If UDPCtrlRowsAsColumns EQ "" then UDPCtrlRowsAsColumns = No$   ; // Make sure something is in place so the <-1> append logic will work.
            If UDPCtrlCellsAsFields EQ "" then UDPCtrlCellsAsFields = No$   ; // Make sure something is in place so the <-1> append logic will work.
            If UDPOrigText EQ "" then UDPOrigText = UDPCtrlType             ; // Make sure something is in place so the <-1> append logic will work.

            NumEdtCols = Count(ColPos, @VM) + (ColPos NE "")
            If UDPCtrlCellsAsFields EQ Yes$ then
                NumEdtRows = Count(ColPos<0, 1>, @SVM) + (ColPos<0, 1> NE "")
                If NumEdtRows GT 1 then NumEdtCols = NumEdtCols * NumEdtRows
            end else
                NumEdtRows = 1
            end
            
            ColNameFlag = ""
            ColNamePos  = 1

            For i = 1 to NumEdtCols
                For j = 1 to NumEdtRows
                    ColName				            = ColPos<0, i, j>
                
                    If ColName NE "" then
                         
                        UDPCtrlSelPos = i:";":j
                        
                        SRP_FastArray_Insert(UDPCtrlColPos,            -1, 0, 0, ColName)
                        SRP_FastArray_Insert(UDPCtrls,                 -1, 0, 0, UDPCtrl)
                        SRP_FastArray_Insert(UDPCtrlEdtCol,            -1, 0, 0, i)
                        SRP_FastArray_Insert(UDPCtrlRowsAsColumnsList, -1, 0, 0, UDPCtrlRowsAsColumns)
                        SRP_FastArray_Insert(UDPCtrlCellsAsFieldsList, -1, 0, 0, UDPCtrlCellsAsFields)
                        SRP_FastArray_Insert(UDPCtrlSelPosList,        -1, 0, 0, UDPCtrlSelPos)
    *                    SRP_FastArray_Insert(UDPCtrlTypes,             -1, 0, 0, UDPCtrlType)
                        SRP_FastArray_Insert(UDPCtrlOrigText,          -1, 0, 0, UDPOrigText)
                        
                        // In case there are no true databound controls on the form, the data column positions that are
                        // virtually bound will be added to the RowMaps variable. This way the logic that updates the
                        // entire window will catch these virtually bound column positions and update accordingly.
                        If Num(ColName) then
                            
                            // This is a data column so put the column position in the dictionary column name and number
                            // areas.                            
                            MRIndex = MRCount + 1
                            RowMaps := @VM:ColName:@SVM:ColName:@SVM:@SVM:MRIndex
                            RMCount += 1
                            SRP_FastArray_Insert(MasterRowMap, -1, 0, 0, UDPCtrl:@VM:i:@VM:UDPCtrlType)
                            MRCount += 1
                            
                        end else
                            
                            // This is a symbolic column so only put the column name in the dictionary column name area.
                            RowMaps   := @VM:ColName:@SVM
                            RMCount   += 1
                            DepColPos  = ""
                            
                            DepCols = OIWin_Compile_Impacts(TableName, ColName)
                            
                            DepColsFlag = ""
                            DepColsPos  = 1
                            Loop
                                Remove DepCol from DepCols at DepColsPos Setting DepColsFlag
                                DepCol = Field(DepCol, "*", 2)
                                If Num(DepCol) then
                                    
                                    // This is a database column position. If it hasn't already been identified then
                                    // append the tracking variables.
                                    Locate DepCol in DepColPos using @FM setting fPos else
                                        DepColPos<-1> = DepCol        ; // Track columns already found to avoid duplication
                                        
                                        // Information is being inserted rather than appended so the most independent
                                        // columns appear first in the list. This way the CALCULATE event is sent to
                                        // those controls and more dependent columsn will have the most current
                                        // information available.
                                        SRP_FastArray_Insert(OISymCtrls, 1, 0, 0, UDPCtrl) ; NumSymCtrls += 1
                                        SRP_FastArray_Insert(OISymDepColPos, 1, 0, 0, DepCol)
                                        SRP_FastArray_Insert(OISymCtrlEdtCol, 1, 0, 0, i)
                                        SRP_FastArray_Insert(OISymCtrlRowsAsColumns, 1, 0, 0, UDPCtrlRowsAsColumns)
                                        SRP_FastArray_Insert(OISymCtrlCellsAsFields, 1, 0, 0, UDPCtrlCellsAsFields)
                                        SRP_FastArray_Insert(OISymCtrlSelPos, 1, 0, 0, UDPCtrlSelPos)
                                        SRP_FastArray_Insert(OISymCtrlTypes, 1, 0, 0, UDPCtrlType)
                                        SRP_FastArray_Insert(OISymCtrlOrigText, 1, 0, 0, UDPOrigText)
                                        SRP_FastArray_Insert(OISymCtrlColName, 1, 0, 0, ColName)    ; // The name of the symbolic column so it can be calculated later on.
                                    end
                                    
                                end
                            While DepColsFlag
                            Repeat                    
                        end
                    end
                Next j
            Next i
        end
    While Flag
    Repeat
    
    // Check the DictColPos for the special keyword "SYM". This means only the symbolic columns should be updated.
    // Reset the ProcessCnt counter accordingly.
    If DictColPos _EQC "SYM" then ProcessCnt += 2
return

Update_Window:
    // Update the control(s) on the window with the data.

    If WholeRecord EQ No$ then
        // Only one database column is being updated. Add all the controls that are bound to this database column and
        // update them.
        FieldData = Data

        // Add all the controls that are actually data bound to this database column.
        If Not(VirtualOnly) then GoSub Add_OI_Control_Information

        // Add all the controls that are virtually data bound to this database column.
        GoSub Add_UDP_Control_Information
    end else
        // The entire data record is being updated. Use the common variables already set up in the OIWin_Comm_Init
        // insert to loop through each control, get the database column bound to it, then update it with the data record
        // column.

        Loop
            
            RowMap = RowMaps[1, @VM]
            RowMaps[1, Col2()] = ""
        
        While RowMap GT ""

            DictColName = RowMap<1, 1, 1>
            DictColPos  = RowMap<1, 1, 2>

            Begin Case
                Case DictColPos GT 0            
                    // This is a data column.
                    FieldData = Data<DictColPos>
                    // Add the control indexed to this RowMap.
                    If Not(VirtualOnly) then GoSub Add_Native_Control_Information
                    // Add all the controls that are virtually data bound to this database column.
                    GoSub Add_UDP_Control_Information

                Case DictColPos EQ ""
                    // This is a symbolic (calculated) column but a non-databound control might be virtually bound to
                    // it. The symbolic will need to be calculated and the value put into FieldData. However, in order
                    // to avoid unnecessary symbolic calculations check first to see if any controls are virtually bound
                    Transfer DictColName to DictColPos
                    GoSub Add_UDP_Control_Information
        
            End Case
        Repeat
    end
    
    // Strip off the preceding @RM
    Controls[1, 1]      = ""
    Properties[1, 1]    = ""
    Values[1, 1]        = ""
    EdtCols[1, 1]       = ""
    
    Set_Property(Controls, Properties, Values, EdtCols)

    ProgID = Get_Property(FocusControl, "OLE.ProgID")   ; // Get the ProgID of the focus control
    If ProgID _EQC "SRP.EditTable.1" then               ; // Is the focus control an SRP EditTable?
        Send_Message(FocusControl, "OLE.AbortCellEdit") ; // Because the SRP EditTable can already be in edit mode before the data
        Send_Message(FocusControl, "OLE.EditCell", "")  ; // is updated, reset the edit mode so the data is visible in the cell
    end
return

Update_Record:
    If TableName NE "" then
        // Clear out and then update the RECORD property of the window. This will make sure that all related components
        // to the data changes are in synch so symbolic (calculated) columns and the database write work correctly.
        Set_Property(Window, "RECORD", "")
        Set_Property(Window, "RECORD", @RECORD)
    end
return

Update_Symbolics:
    // Symbolics are updated last (and not with the data controls in the Update_Window gosub) because they need to be
    // recalculated after the data controls have been populated.
    If Not(SkipSymbolics) then
        If WholeRecord EQ Yes$ then
            
            // Send a CALCULATE event to all controls bound to a symbolic column.
            For fPos = 1 to NumSymCtrls
                GoSub Calculate_And_Display
            Next fPos
            // No longer need OISymDepColPos array since all controls are being recalculated.
            SRP_FastArray_Release(OISymDepColPos)

        end else
            
            // Convert from Fast Array back to normal array
            Handle          = OISymDepColPos
            OISymDepColPos  = SRP_FastArray_GetVariable(Handle)
            SRP_FastArray_Release(Handle)
            
            // Send a CALCULATE event to those controls which are dependent upon the changed database column.
            Match = Yes$
            Loop
                Locate DictColPos in OISymDepColPos using @FM setting fPos then
                    OISymDepColPos<fPos> = ""    ; // Clear field position so it won't be found again.
                    GoSub Calculate_And_Display
                end else
                    Match = No$
                end
            Until Not(Match)
            Repeat
            
        end
    end
return

Clean_Up:

    SRP_FastArray_Release(MasterRowMap)

    SRP_FastArray_Release(OICtrls)
    SRP_FastArray_Release(OICtrlColPos)
    SRP_FastArray_Release(OICtrlEdtCol)
    SRP_FastArray_Release(OICtrlTypes)
    
    SRP_FastArray_Release(OISymCtrls)
    SRP_FastArray_Release(OISymCtrlEdtCol)
    SRP_FastArray_Release(OISymCtrlRowsAsColumns)
    SRP_FastArray_Release(OISymCtrlCellsAsFields)
    SRP_FastArray_Release(OISymCtrlSelPos)
    SRP_FastArray_Release(OISymCtrlTypes)
    SRP_FastArray_Release(OISymCtrlOrigText)
    SRP_FastArray_Release(OISymCtrlColName)
    
    SRP_FastArray_Release(UDPCtrls)
    SRP_FastArray_Release(UDPCtrlColPos)
    SRP_FastArray_Release(UDPCtrlRowsAsColumnsList)
    SRP_FastArray_Release(UDPCtrlCellsAsFieldsList)
    SRP_FastArray_Release(UDPCtrlSelPosList)
*    SRP_FastArray_Release(UDPCtrlTypes)
    SRP_FastArray_Release(UDPCtrlEdtCol)
    SRP_FastArray_Release(UDPCtrlOrigText)
    
return

Calculate_And_Display:

    SymCtrl                 = SRP_FastArray_Extract(OISymCtrls, fPos, 0, 0)
    SymCtrlEdtCol           = SRP_FastArray_Extract(OISymCtrlEdtCol, fPos, 0, 0)
    SymCtrlRowsAsColumns    = SRP_FastArray_Extract(OISymCtrlRowsAsColumns, fPos, 0, 0)
    SymCtrlCellsAsFields    = SRP_FastArray_Extract(OISymCtrlCellsAsFields, fPos, 0, 0)
    SymCtrlSelPos           = SRP_FastArray_Extract(OISymCtrlSelPos, fPos, 0, 0)
    SymCtrlOrigText         = SRP_FastArray_Extract(OISymCtrlOrigText, fPos, 0, 0)
    SymCtrlColName          = SRP_FastArray_Extract(OISymCtrlColName, fPos, 0, 0)
    
    If SymCtrlColName EQ "" then
        
        // This control is bound to an actual symbolic (calculated) column. Use the CALCULATE event to update it.
        Send_Event(SymCtrl, "CALCULATE", SymCtrlEdtCol)
    
    end else
        
        // This control is virtually bound to a symboic (calculated) column. Use the CALCULATE function to compute its
        // value and update the control's display directly.
    
        SymResult = Calculate(SymCtrlColName)
    
        If SymCtrlOrigText _EQC "SRP.EditTable.1" then

            // OLE EditTable controls need to have enough empty rows to display the data being added. Compute the
            // number of additional rows and use the Dimension property to create them.
            If WholeRecord EQ Yes$ then
                Locate SymCtrl in UpdatedOLEEditTables using @FM setting Dummy else
                    // This OLE EditTable might have information from a previous record. Therefore, clear the OLE
                    // EditTable just in case but track the control so it won't get cleared if other columns are updated
                    If DictColPos _NEC "SYM" then
                        // If the control with focus is the control being updated then don't call the Clear method.
                        // First, the changes that are being made in this control will be cleared. Second, if the
                        // current cell is a Combobox type this will crash OpenInsight.
                        If SymCtrlRowsAsColumns OR SymCtrlCellsAsFields then
                            // Clear data but maintain the original rows
                            Send_Message(SymCtrl, "OLE.Clear", 2)
                        end else
                            // Clear data and rows
                            Send_Message(SymCtrl, "OLE.Clear", 0)
                        end
                    end
                    UpdatedOLEEditTables<-1> = SymCtrl    ; // Track this OLE EditTable so the Clear method won't be executed again.
                end
            end
    
            Dimension       = Get_Property(SymCtrl, "OLE.Dimension")
            CurrNumColumns  = Dimension[1, @FM]
            CurrNumRows     = Dimension[Col2() + 1, @FM]
    
            Begin Case
                Case SymCtrlRowsAsColumns EQ Yes$
                    // Data in the SRP EditTable is being stored in a LIST format where rows are associated with field
                    // data.
                    NeededColumns = Count(SymResult, @VM) + (SymResult NE "")
                    If NeededColumns GT CurrNumColumns then
                        CurrNumColumns = NeededColumns
                        Set_Property(SymCtrl, "OLE.Dimension", CurrNumColumns)
                    end
                    Conv = Get_Property(SymCtrl, "OLE.CellConv[1;":SymCtrlEdtCol:"]")<2>
                    If Conv NE "" then SymResult = Oconv(SymResult, Conv)
                    Set_Property(SymCtrl, "OLE.RecordData[":SymCtrlEdtCol:"]", SymResult)
                    
                Case SymCtrlCellsAsFields EQ Yes$
                    // Each cell within he SRP EditTable is associated with a specific field data.
                    NeededColumns = Trim(SymCtrlSelPos[1, ";"])
                    NeededRows    = Trim(SymCtrlSelPos[Col2() + 1, ";"])
                    If (NeededColumns GT CurrNumColumns) OR (NeededRows GT CurrNumRows) then
                        If NeededColumns GT CurrNumColumns  then CurrNumColumns = NeededColumns
                        If NeededRows GT CurrNumRows        then CurrNumRows = NeededRows
                        Set_Property(SymCtrl, "OLE.Dimension", CurrNumColumns:@FM:CurrNumRows)
                    end
                    Conv = Get_Property(SymCtrl, "OLE.CellConv[":SymCtrlSelPos:"]")<2>
                    If Conv NE "" then SymResult = Oconv(SymResult, Conv)
                    Set_Property(SymCtrl, "OLE.CellText[":SymCtrlSelPos:"]", SymResult)
    
                Case Otherwise$
                    // Data in the SRP EditTable is being stored in the traditional ARRAY format where columns are
                    // associated with field data.
                    NeededRows    = Count(SymResult, @VM) + (SymResult NE "")
                    If NeededRows GT CurrNumRows then
                        DCurrNumRows = NeededRows
                        Set_Property(SymCtrl, "OLE.Dimension", @FM:CurrNumRows)
                    end
                    Conv = Get_Property(SymCtrl, "OLE.CellConv[":SymCtrlEdtCol:";1]")<2>
                    If Conv NE "" then SymResult = Oconv(SymResult, Conv)
                    Set_Property(SymCtrl, "OLE.ColumnData[":SymCtrlEdtCol:"]", SymResult)
    
             End Case
    
        end else
            // Data is being updated in a standard OpenInsight control. Use the INVALUE property to make sure the data
            // is being converted correctly.
            Set_Property(SymCtrl, "INVALUE", SymResult, SymCtrlEdtCol)
        end

    end
    
return

Add_Native_Control_Information:
    MRIndex     = RowMap<1, 1, 4>
    Control     = SRP_FastArray_Extract(MasterRowMap, MRIndex, 1, 0)
    EdtCol      = SRP_FastArray_Extract(MasterRowMap, MRIndex, 2, 0)
    CtrlType    = SRP_FastArray_Extract(MasterRowMap, MRIndex, 3, 0)
    If CtrlType NE "OLECONTROL" then
        // Only native OpenInsight controls apply here.

        If CtrlType EQ "EDITTABLE" then
            // EditTable controls need to have enough empty rows to display the data being added. Compute the number of
            // additional rows and use the INSERT method to create them.
            Invalue = Get_Property(Control, "INVALUE")
            CurrNumRows    = Count(Invalue<1>, @VM) + (Invalue<1> NE "")
            NeededRows    = Count(FieldData, @VM) + (FieldData NE "")
            For i = CurrNumRows to NeededRows
                Send_Message(Control, "INSERT", -1, "")
            Next i
        end
        
        Controls    := @RM : Control
        Properties  := @RM : "INVALUE" 
        Values      := @RM : FieldData
        EdtCols     := @RM : EdtCol
    end
return

Add_OI_Control_Information:
    TempOICtrlColPos    = SRP_FastArray_GetVariable(OICtrlColPos)
    Match               = Yes$

    Loop
        Locate DictColPos in TempOICtrlColPos using @FM setting fPos then
            Control  = SRP_FastArray_Extract(OICtrls, fPos, 0, 0)
            CtrlType = SRP_FastArray_Extract(OICtrlTypes, fPos, 0, 0)

            If CtrlType EQ "EDITTABLE" then
                // EditTable controls need to have enough empty rows to display the data being added. Compute the number
                // of additional rows and use the INSERT method to create them.
                Invalue = Get_Property(Control, "INVALUE")
                CurrNumRows    = Count(Invalue<1>, @VM) + (Invalue<1> NE "")
                NeededRows    = Count(FieldData, @VM) + (FieldData NE "")
                For i = CurrNumRows to NeededRows
                    Send_Message(Control, "INSERT", -1, "")
                Next i
            end
            
            Controls    := @RM : Control
            Properties  := @RM : "INVALUE"
            Values      := @RM : FieldData
            EdtCol       = SRP_FastArray_Extract(OICtrlEdtCol, fPos, 0, 0)
            EdtCols     := @RM : EdtCol

            TempOICtrlColPos<fPos> = ""
            
        end else
            Match = No$
        end
    Until Not(Match)
    Repeat
return

Add_UDP_Control_Information:
    TempUDPCtrlColPos    = SRP_FastArray_GetVariable(UDPCtrlColPos)
    Match                = Yes$

    Loop
        Locate DictColPos in TempUDPCtrlColPos using @FM setting fPos then
            Control         = SRP_FastArray_Extract(UDPCtrls, fPos, 0, 0)
            RowsAsColumns   = SRP_FastArray_Extract(UDPCtrlRowsAsColumnsList, fPos, 0, 0)
            CellsAsFields   = SRP_FastArray_Extract(UDPCtrlCellsAsFieldsList, fPos, 0, 0)
            SelPos          = SRP_FastArray_Extract(UDPCtrlSelPosList, fPos, 0, 0)
            OrigText        = SRP_FastArray_Extract(UDPCtrlOrigText, fPos, 0, 0)
            // If DictColPos is not a number then this is a symbolic that needs to be calculated
            If Not(Num(DictColPos)) then FieldData = Calculate(DictColPos)

            Controls    := @RM : Control
            
            Begin Case
                Case OrigText _EQC "SRP.EditTable.1"
                    // OLE EditTable controls need to have enough empty rows to display the data being added. Compute
                    // the number of additional rows and use the Dimension property to create them.
                    If WholeRecord EQ Yes$ then
                        Locate Control in UpdatedOLEEditTables using @FM setting Dummy else
                            // This OLE EditTable might have information from a previous record. Therefore, clear the
                            // OLE EditTable just in case but track the control so it won't get cleared if other columns
                            // are updated.
                            If Control NE FocusControl then            
                                // If the control with focus is the control being updated then don't call the Clear
                                // method. First, the changes that are being made in this control will be cleared.
                                // Second, if the current cell is a Combobox type this will crash OpenInsight.
                                ClearFill = Get_Property(Control, "@CLEARFILL")
                                *If RowsAsColumns OR CellsAsFields then
                                Begin Case
                                    Case ClearFill    
                                        // Clear using fill value defined for EditTable
                                        Send_Message(Control, "OLE.Clear", ClearFill)
                                    Case RowsAsColumns OR CellsAsFields
                                        // Clear data but maintain the original rows
                                        Send_Message(Control, "OLE.Clear", 2)
                                    Case Otherwise$    
                                        // Clear data and rows
                                        Send_Message(Control, "OLE.Clear", 0)
                                End Case
                            end
                            UpdatedOLEEditTables<-1> = Control    ; // Track this OLE EditTable so the Clear method won't be executed again.
                        end
                    end
                    
                    Dimension       = Get_Property(Control, "OLE.Dimension")
                    CurrNumColumns  = Dimension<1>
                    CurrNumRows     = Dimension<2>
                    
                    Begin Case
                        Case RowsAsColumns EQ Yes$
                            // Data in the SRP EditTable is being stored in a LIST format where rows are associated with
                            // field data.
                            NeededColumns = Count(FieldData, @VM) + (FieldData NE "")
                            If NeededColumns GT CurrNumColumns then
                                Dimension<1> = NeededColumns
                                Set_Property(Control, "OLE.Dimension", Dimension)
                            end
                            EdtCol      = SRP_FastArray_Extract(UDPCtrlEdtCol, fPos, 0, 0)
                            Properties := @RM : "OLE.RecordData[":EdtCol:"]"
                            CellConv    = Get_Property(Control, "OLE.CellConv[1;":EdtCol:"]")
                            
                        Case CellsAsFields EQ Yes$
                            // Each cell within he SRP EditTable is associated with a specific field data.
                            NeededColumns   = Trim(Field(SelPos, ";", 1))
                            NeededRows      = Trim(Field(SelPos, ";", 2))
                            If (NeededColumns GT CurrNumColumns) OR (NeededRows GT CurrNumRows) then
                                If NeededColumns GT CurrNumColumns  then Dimension<1> = NeededColumns
                                If NeededRows GT CurrNumRows        then Dimension<2> = NeededRows
                                Set_Property(Control, "OLE.Dimension", Dimension)
                            end
                            Properties     := @RM : "OLE.CellText[":SelPos:"]"
                            CellConv        = Get_Property(Control, "OLE.CellConv[":SelPos:"]")
                            
                        Case Otherwise$
                            // Data in the SRP EditTable is being stored in the traditional ARRAY format where columns
                            // are associated with field data.
                            NeededRows = Count(FieldData, @VM) + (FieldData NE "")
                            If NeededRows GT CurrNumRows then
                                Dimension<2> = NeededRows
                                Set_Property(Control, "OLE.Dimension", Dimension)
                            end
                            EdtCol      = SRP_FastArray_Extract(UDPCtrlEdtCol, fPos, 0, 0)
                            Properties := @RM : "OLE.ColumnData[":EdtCol:"]"
                            CellConv    = Get_Property(Control, "OLE.CellConv[":EdtCol:";1]")
                            
                    End Case        
                            
                    Conv = CellConv<2>
                    If Conv NE "" then FieldData = Oconv(FieldData, Conv)
                    Values      := @RM : FieldData
                    EdtCols     := @RM : ""

                Case Otherwise$
                    Properties  := @RM : "INVALUE" 
                    Values      := @RM : FieldData
                    EdtCols     := @RM : ""

            End Case
            
            TempUDPCtrlColPos<fPos> = ""
            
        end else
            Match = No$
        end
    Until Not(Match)
    Repeat
return

Restore_At_Variables:
    Record = @RECORD    ; // Copy @RECORD before it gets restored (if necessary) so it can be returned.

    If Window NE @Window then
        // Restore the original values of @ variables in case a different window other than @Window is being updated.
        Transfer OrigID     to @ID
        Transfer OrigRecord to @RECORD
        Transfer OrigDict   to @DICT
    end
return

ParseDictColPos:
    // Determine if the data needs to update a value, subvalue, text, or subtext part of a field.
    DelimDepth = 0

    Loop
        DelimDepth += 1
        Pos = Field(DictColPos, ",", DelimDepth)
    Until Not(Num(Pos)) OR Pos EQ "" OR DelimDepth EQ 6
        Begin Case
            Case DelimDepth EQ 1    ; Transfer Pos to FieldPos
            Case DelimDepth EQ 2    ; Transfer Pos to ValPos
            Case DelimDepth EQ 3    ; Transfer Pos to SubValPos
            Case DelimDepth EQ 4    ; Transfer Pos to TextPos
            Case DelimDepth EQ 5    ; Transfer Pos to SubTextPos
        End Case
    Repeat
    
    DictColPos  = FieldPos
    DelimDepth -= 1
return

Update_Data_Array:
    // Drill into the data field as much as necessary and update with the data being passed in. The entire data field
    // will then be put into each control that is bound to this database column.
    DataArray = ""
    DataArray<1> = @RECORD<FieldPos>
    DataArray<2> = Field(DataArray<1>, @VM, ValPos)
    If DelimDepth GT 2 then
        DataArray<3> = Field(DataArray<2>, @SVM, SubValPos)
        If DelimDepth GT 3 then
            DataArray<4> = Field(DataArray<3>, @TM, TextPos)
            
            If DelimDepth GT 4 then
                Data = FieldStore(DataArray<4>, @STM, SubTextPos, 1, Data)
            end
            
            Data = FieldStore(DataArray<3>, @TM, TextPos, 1, Data)
        end
        
        Data = FieldStore(DataArray<2>, @SVM, SubValPos, 1, Data)
    end
    
    Data = FieldStore(DataArray<1>, @VM, ValPos, 1, Data)
return
