//------------------------------------------------------------------------------
// icmdtgt.cpp
// Copyright (c)1997-1999 Microsoft Corporation, All Rights Reserved
//
// Author
//     bash
//
// History
//      7-19-97     created     (bash)
//
// Implementation of IOleCommandTarget
//
//------------------------------------------------------------------------------

#include "stdafx.h"

#include <mshtmcid.h>
#include <designer.h>

//#include "mfcincl.h"
#include "triedit.h"
#include "document.h"
#include "triedcid.h"       //TriEdit Command IDs here.
#include "dispatch.h"
#include "undo.h"

#define CMDSTATE_NOTSUPPORTED  0
#define CMDSTATE_DISABLED      OLECMDF_SUPPORTED
#define CMDSTATE_UP           (OLECMDF_SUPPORTED | OLECMDF_ENABLED)
#define CMDSTATE_DOWN         (OLECMDF_SUPPORTED | OLECMDF_ENABLED | OLECMDF_LATCHED)
#define CMDSTATE_NINCHED      (OLECMDF_SUPPORTED | OLECMDF_ENABLED | OLECMDF_NINCHED)

// Mapping from TriEdit to Trident commands
typedef struct {
ULONG cmdTriEdit;
ULONG cmdTrident;    
} CMDMAP;

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::MapTriEditCommand 
//
// Map the given TriEdit IDM to the equivalent Trident IDM. 
//
// Return:
//   Mapped command under *pCmdTrident and S_OK for a valid command.
//   E_FAIL for an invalid command.
//

HRESULT CTriEditDocument::MapTriEditCommand(ULONG cmdTriEdit, ULONG *pCmdTrident)
{
    static CMDMAP rgCmdMap[] = {
        { IDM_TRIED_ACTIVATEACTIVEXCONTROLS, IDM_NOACTIVATENORMALOLECONTROLS }, 
        { IDM_TRIED_ACTIVATEAPPLETS, IDM_NOACTIVATEJAVAAPPLETS },
        { IDM_TRIED_ACTIVATEDTCS, IDM_NOACTIVATEDESIGNTIMECONTROLS },
        { IDM_TRIED_BACKCOLOR, IDM_BACKCOLOR },
        { IDM_TRIED_BLOCKFMT, IDM_BLOCKFMT },
        { IDM_TRIED_BOLD, IDM_BOLD },
        { IDM_TRIED_BROWSEMODE, IDM_BROWSEMODE },
        { IDM_TRIED_COPY, IDM_COPY },
        { IDM_TRIED_CUT, IDM_CUT },
        { IDM_TRIED_DELETE, IDM_DELETE },
        { IDM_TRIED_EDITMODE, IDM_EDITMODE },
        { IDM_TRIED_FIND, IDM_FIND },
        { IDM_TRIED_FONT, IDM_FONT },
        { IDM_TRIED_FONTNAME, IDM_FONTNAME },
        { IDM_TRIED_FONTSIZE, IDM_FONTSIZE },
        { IDM_TRIED_FORECOLOR, IDM_FORECOLOR },
        { IDM_TRIED_GETBLOCKFMTS, IDM_GETBLOCKFMTS },
        { IDM_TRIED_HYPERLINK, IDM_HYPERLINK },
        { IDM_TRIED_IMAGE, IDM_IMAGE },
        { IDM_TRIED_INDENT, IDM_INDENT },
        { IDM_TRIED_ITALIC, IDM_ITALIC },
        { IDM_TRIED_JUSTIFYCENTER, IDM_JUSTIFYCENTER },
        { IDM_TRIED_JUSTIFYLEFT, IDM_JUSTIFYLEFT },
        { IDM_TRIED_JUSTIFYRIGHT, IDM_JUSTIFYRIGHT },
        { IDM_TRIED_ORDERLIST, IDM_ORDERLIST },
        { IDM_TRIED_OUTDENT, IDM_OUTDENT },
        { IDM_TRIED_PASTE, IDM_PASTE },
        { IDM_TRIED_PRINT, IDM_PRINT },
        { IDM_TRIED_REDO, IDM_REDO },
        { IDM_TRIED_REMOVEFORMAT, IDM_REMOVEFORMAT },
        { IDM_TRIED_SELECTALL, IDM_SELECTALL },
        { IDM_TRIED_SHOWBORDERS, IDM_SHOWZEROBORDERATDESIGNTIME },
        { IDM_TRIED_SHOWDETAILS, IDM_SHOWALLTAGS },
        { IDM_TRIED_UNDERLINE, IDM_UNDERLINE },
        { IDM_TRIED_UNDO, IDM_UNDO },
        { IDM_TRIED_UNLINK, IDM_UNLINK },
        { IDM_TRIED_UNORDERLIST, IDM_UNORDERLIST }
    };

    if (NULL == pCmdTrident)
        return E_POINTER;

    for (int i=0; i < sizeof(rgCmdMap)/sizeof(CMDMAP); ++i)
    {
        if (cmdTriEdit == rgCmdMap[i].cmdTriEdit)
        {
            *pCmdTrident = rgCmdMap[i].cmdTrident;
            return S_OK;
        }
    }

    return E_FAIL;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::SetUpDefaults
//
// Set Trident flags to the TriEdit default values:
//
//      IDM_PRESERVEUNDOALWAYS                  On
//      IDM_NOFIXUPURLSONPASTE                  On
//      IDM_NOACTIVATEDESIGNTIMECONTROLS        Off
//      IDM_NOACTIVATEJAVAAPPLETS               On
//      IDM_NOACTIVATENORMALOLECONTROLS         On
//
//
// No return value.

void CTriEditDocument::SetUpDefaults()
{
    VARIANT var;

    // Turn on Trident's preserve undo flag for setting properties
    V_VT(&var) = VT_BOOL;
    V_BOOL(&var) = TRUE;

    m_pCmdTgtTrident->Exec(&CMDSETID_Forms3,
             6049, // IDM_PRESERVEUNDOALWAYS
             OLECMDEXECOPT_DONTPROMPTUSER,
             &var,
             NULL);

    // Turn on Trident's url fixup flag for paste and drag-drop
    V_VT(&var) = VT_BOOL;
    V_BOOL(&var) = TRUE;

    m_pCmdTgtTrident->Exec(&CMDSETID_Forms3,
             2335, // IDM_NOFIXUPURLSONPASTE
             OLECMDEXECOPT_DONTPROMPTUSER,
             &var,
             NULL);

    // Set up defaults for Activating DTCs but not Applets or other ActiveX Controls
    V_VT(&var) = VT_BOOL;
    V_BOOL(&var) = FALSE;

    m_pCmdTgtTrident->Exec(&CMDSETID_Forms3,
             IDM_NOACTIVATEDESIGNTIMECONTROLS,
             OLECMDEXECOPT_DONTPROMPTUSER,
             &var,
             NULL);

    V_VT(&var) = VT_BOOL;
    V_BOOL(&var) = TRUE;

    m_pCmdTgtTrident->Exec(&CMDSETID_Forms3,
             IDM_NOACTIVATEJAVAAPPLETS,
             OLECMDEXECOPT_DONTPROMPTUSER,
             &var,
             NULL);

    V_VT(&var) = VT_BOOL;
    V_BOOL(&var) = TRUE;

    m_pCmdTgtTrident->Exec(&CMDSETID_Forms3,
             IDM_NOACTIVATENORMALOLECONTROLS,
             OLECMDEXECOPT_DONTPROMPTUSER,
             &var,
             NULL);
}

///////////////////////////////////////////////////////////////////////////////
//
//
// CTriEditDocument::SetUpGlyphTable
//
// Load the glyphs from the DLL and install them in Trident's table. No return
// value.
//

void CTriEditDocument::SetUpGlyphTable(BOOL fDetails)
{
    VARIANT var;
    const int RuleMax = 100; // This needs to be updated if we ever have a long rule
    const int PathMax = 256; // For %program files%\common files\microsoft shared\triedit\triedit.dll
    int iGlyphTableStart = IDS_GLYPHTABLESTART;
    int iGlyphTableEnd = fDetails ? IDS_GLYPHTABLEEND : IDS_GLYPHTABLEFORMEND;
    TCHAR szPathName[PathMax];
    TCHAR szRule[RuleMax + PathMax];
    TCHAR szGlyphTable[(RuleMax + PathMax) * (IDS_GLYPHTABLEEND - IDS_GLYPHTABLESTART + 1)];
    TCHAR *pchGlyphTable, *pchTemp;

    // Get full path name for triedit.dll
    ::GetModuleFileName(_Module.GetModuleInstance(),
            szPathName,
            sizeof(szPathName)
            );

    // Load glyph table
    pchGlyphTable = szGlyphTable;
    for (int i = iGlyphTableStart; i <= iGlyphTableEnd; i++)
    {
        ::LoadString(_Module.GetModuleInstance(), i, szRule, RuleMax);
        pchTemp = wcsstr(szRule, _T("!"));
        if (pchTemp) // else bad rule, ignore
        {
            *pchTemp = 0;
            // Copy upto the "!"
            wcscpy(pchGlyphTable, szRule);
            pchGlyphTable += wcslen(szRule);
            // Append pathname
            wcscpy(pchGlyphTable, szPathName);
            pchGlyphTable += wcslen(szPathName);
            // Skip past "!"
            pchTemp = pchTemp + 1;
            // Copy remaining characters
            wcscpy(pchGlyphTable, pchTemp);
            pchGlyphTable += wcslen(pchTemp);
        }
    }
     
    // First empty the glyph table
    m_pCmdTgtTrident->Exec(&CMDSETID_Forms3,
             IDM_EMPTYGLYPHTABLE,
             OLECMDEXECOPT_DONTPROMPTUSER,
             NULL,
             NULL);
    
    V_VT(&var) = VT_BSTR;
    V_BSTR(&var) = SysAllocString(szGlyphTable);
    m_pCmdTgtTrident->Exec(&CMDSETID_Forms3,
             IDM_ADDTOGLYPHTABLE,
             OLECMDEXECOPT_DONTPROMPTUSER,
             &var,
             NULL);
    VariantInit(&var);

}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::QueryStatus
//
// Report on the status of the given array of TriEdit and Trident commands. 
// Pass Trident commands on to Trident. Fix the Trident return value to
// compensate for some inconsistencies. Return S_OK if all goes well, or
// E_FAIL if not.
//


STDMETHODIMP CTriEditDocument::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds,
                                           OLECMD prgCmds[], OLECMDTEXT *pCmdText)

{
    OLECMD *pCmd;
    INT c;
    HRESULT hr;

    if (pguidCmdGroup && IsEqualGUID((const GUID&)*pguidCmdGroup, GUID_TriEditCommandGroup))
    {
        // Loop through each command in the ary, setting the status of each.
        for (pCmd = prgCmds, c = cCmds; --c >= 0; pCmd++)
        {
            // Assume this is a valid command and set default command status to DISABLED.
            // The state will get reset to UP, DOWN or NOTSUPPORTED in the switch statement below.
            pCmd->cmdf = CMDSTATE_DISABLED;
        
            switch(pCmd->cmdID)
            {
                case IDM_TRIED_IS_1D_ELEMENT:   
                case IDM_TRIED_IS_2D_ELEMENT:
                    {
                        if (SUCCEEDED(GetElement()) && m_pihtmlElement)
                        {
                            pCmd->cmdf = CMDSTATE_UP;
                        }
                        break;
                    }

                case IDM_TRIED_SET_ALIGNMENT:
                    {
                        pCmd->cmdf = CMDSTATE_UP;
                        break;
                    }

                case IDM_TRIED_LOCK_ELEMENT:
                    {
                        if (SUCCEEDED(GetElement()) && m_pihtmlElement)
                        {
                            BOOL f2d=FALSE;
                            if (SUCCEEDED(Is2DElement(m_pihtmlElement, &f2d)) && f2d)
                            {
                                BOOL fLocked=FALSE;
                                pCmd->cmdf =
                                        (SUCCEEDED(IsLocked(m_pihtmlElement, &fLocked)) && fLocked)
                                        ? CMDSTATE_DOWN : CMDSTATE_UP;
                            }
                        }
                        break;
                    }
                case IDM_TRIED_CONSTRAIN:
                    {
                        pCmd->cmdf = (m_fConstrain) ? CMDSTATE_DOWN : CMDSTATE_UP;
                        break;
                    }

                case IDM_TRIED_SEND_TO_BACK:
                case IDM_TRIED_SEND_TO_FRONT:
                case IDM_TRIED_SEND_BACKWARD:
                case IDM_TRIED_SEND_FORWARD:
                case IDM_TRIED_SEND_BEHIND_1D:
                case IDM_TRIED_SEND_FRONT_1D:
                    {
                        if (SUCCEEDED(GetElement()) && m_pihtmlElement)
                        {
                            BOOL f2d=FALSE;

                            if (SUCCEEDED(Is2DElement(m_pihtmlElement, &f2d)) && f2d)
                            {
                                pCmd->cmdf = CMDSTATE_UP;
                            }
                        }
                        break;
                    }

                case IDM_TRIED_NUDGE_ELEMENT:
                    {
                        BOOL f2d = FALSE;

                        if (SUCCEEDED(GetElement()) && m_pihtmlElement
                            && SUCCEEDED(Is2DElement(m_pihtmlElement, &f2d)) && f2d)
                        {
                            BOOL fLock = FALSE;

                            if (!(SUCCEEDED(IsLocked(m_pihtmlElement, &fLock)) && fLock))
                                pCmd->cmdf = CMDSTATE_UP;
                        }
                        break;
                    }

                case IDM_TRIED_MAKE_ABSOLUTE:
                    {
                        if (SUCCEEDED(GetElement()) && m_pihtmlElement)
                        {
                            BOOL f2d = FALSE;

                            if (SUCCEEDED(IsElementDTC(m_pihtmlElement)))
                                break;

                            if (SUCCEEDED(Is2DElement(m_pihtmlElement, &f2d)))
                            {
                                BOOL f2dCapable=FALSE;
                                if ( f2d )
                                {
                                    pCmd->cmdf = CMDSTATE_DOWN;
                                }
                                else if (SUCCEEDED(Is2DCapable(m_pihtmlElement, &f2dCapable)) && f2dCapable)
                                {
                                    pCmd->cmdf = CMDSTATE_UP;
                                }
                            }
                        }
                        break;
                    }

                case IDM_TRIED_SET_2D_DROP_MODE:
                    {
                        pCmd->cmdf = (m_f2dDropMode) ? CMDSTATE_DOWN : CMDSTATE_UP;
                        break;
                    }

                case IDM_TRIED_INSERTROW:
                case IDM_TRIED_DELETEROWS:
                case IDM_TRIED_INSERTCELL:
                case IDM_TRIED_DELETECELLS:
                case IDM_TRIED_INSERTCOL:
                    {
                        pCmd->cmdf = (IsSelectionInTable() == S_OK && GetSelectionTypeInTable() != -1)? CMDSTATE_UP : CMDSTATE_DISABLED;
                        break;
                    }

                case IDM_TRIED_MERGECELLS:
                    {
                        ULONG grf = IsSelectionInTable() == S_OK ? GetSelectionTypeInTable() : 0;
                        pCmd->cmdf =  ( (grf != -1) && (!(grf & grfSelectOneCell) && (grf & (grfInSingleRow|grpSelectEntireRow))))  ? CMDSTATE_UP : CMDSTATE_DISABLED;
                        break;
                    }

                case IDM_TRIED_SPLITCELL:
                    {
                        ULONG grf = IsSelectionInTable() == S_OK ? GetSelectionTypeInTable() : 0;
                        pCmd->cmdf = ((grf != -1) && (grf & grfSelectOneCell)) ? CMDSTATE_UP : CMDSTATE_DISABLED;
                        break;
                    }

                case IDM_TRIED_DELETECOLS:
                    {
                        ULONG grf = IsSelectionInTable() == S_OK ? GetSelectionTypeInTable() : 0;
                        pCmd->cmdf = ((grf != -1) && (grf & grfInSingleRow)) ? CMDSTATE_UP : CMDSTATE_DISABLED;
                        break;
                    }

                case IDM_TRIED_INSERTTABLE:
                    {
                        pCmd->cmdf = FEnableInsertTable() ? CMDSTATE_UP : CMDSTATE_DISABLED;
                        break;
                    }

                case IDM_TRIED_DOVERB:
                    {
                        if (SUCCEEDED(GetElement()) && m_pihtmlElement && SUCCEEDED(DoVerb(NULL, TRUE)))
                            pCmd->cmdf = CMDSTATE_UP;

                        break;
                    }

                case IDM_TRIED_ACTIVATEACTIVEXCONTROLS:
                case IDM_TRIED_ACTIVATEAPPLETS:
                case IDM_TRIED_ACTIVATEDTCS:
                case IDM_TRIED_BACKCOLOR:
                case IDM_TRIED_BLOCKFMT:
                case IDM_TRIED_BOLD:
                case IDM_TRIED_BROWSEMODE:
                case IDM_TRIED_COPY:
                case IDM_TRIED_CUT:
                case IDM_TRIED_DELETE:
                case IDM_TRIED_EDITMODE:
                case IDM_TRIED_FIND:
                case IDM_TRIED_FONT:
                case IDM_TRIED_FONTNAME:
                case IDM_TRIED_FONTSIZE:
                case IDM_TRIED_FORECOLOR:
                case IDM_TRIED_GETBLOCKFMTS:
                case IDM_TRIED_HYPERLINK:
                case IDM_TRIED_IMAGE:
                case IDM_TRIED_INDENT:
                case IDM_TRIED_ITALIC:
                case IDM_TRIED_JUSTIFYCENTER:
                case IDM_TRIED_JUSTIFYLEFT:
                case IDM_TRIED_JUSTIFYRIGHT:
                case IDM_TRIED_ORDERLIST:
                case IDM_TRIED_OUTDENT:
                case IDM_TRIED_PASTE:
                case IDM_TRIED_PRINT:
                case IDM_TRIED_REDO:
                case IDM_TRIED_REMOVEFORMAT:
                case IDM_TRIED_SELECTALL:
                case IDM_TRIED_SHOWBORDERS:
                case IDM_TRIED_SHOWDETAILS:
                case IDM_TRIED_UNDERLINE:
                case IDM_TRIED_UNDO:
                case IDM_TRIED_UNLINK:
                case IDM_TRIED_UNORDERLIST:
                    {
                        // We will return E_UNEXPECTED if Trident's command target is not available
                        hr = E_UNEXPECTED;

                        _ASSERTE(m_pCmdTgtTrident);
                        if (m_pCmdTgtTrident)
                        {
                            OLECMD olecmd;
                            
                            olecmd.cmdf = pCmd->cmdf;
                            if (SUCCEEDED(MapTriEditCommand(pCmd->cmdID, &olecmd.cmdID)))
                            {
                                hr = m_pCmdTgtTrident->QueryStatus(&CMDSETID_Forms3, 1, &olecmd, pCmdText);
                            }
                            pCmd->cmdf = olecmd.cmdf;
                        }
                        
                        if (FAILED(hr))
                            return hr;

                        // Trident returns NOTSUPPORTED sometimes when they really mean DISABLED, so we fix this up here.
                        if (pCmd->cmdf == CMDSTATE_NOTSUPPORTED)
                            pCmd->cmdf = CMDSTATE_DISABLED;

                        // Trident returns CMDSTATE_DISABLED for IDM_TRIED_GETBLOCKFMTS but this command should never be disabled
                        if (pCmd->cmdID == IDM_TRIED_GETBLOCKFMTS)
                            pCmd->cmdf = CMDSTATE_UP;

                        // Trident bug: Trident returns the wrong value for IDM_TRIED_SHOWBORDERS,
                        // IDM_TRIED_SHOWDETAILS and the IDM_TRIED_ACTIVATE* commands, so we fix
                        // them up here.  We don't have code for IDM_TRIED_ACTIVATE* since the logic
                        // of the Trident commands is actually reverse in these cases.

                        if (pCmd->cmdID == IDM_TRIED_SHOWBORDERS ||
                            pCmd->cmdID == IDM_TRIED_SHOWDETAILS)
                        {
                            if (pCmd->cmdf == CMDSTATE_UP)
                                pCmd->cmdf = CMDSTATE_DOWN;
                            else if (pCmd->cmdf == CMDSTATE_DOWN)
                                pCmd->cmdf = CMDSTATE_UP;
                        }

                        break;
                    }

                default:
                    {
                        pCmd->cmdf = CMDSTATE_NOTSUPPORTED;
                        break;
                    }
            } // switch
        } // for

        return S_OK;
    }
    else if (m_pCmdTgtTrident)
    {
        hr = m_pCmdTgtTrident->QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText);
        if (hr != S_OK)
            return hr;

        // Loop through each command in the ary, fixing up the status of each.
        for (pCmd = prgCmds, c = cCmds; --c >= 0; pCmd++)
        {
            // Trident returns NOTSUPPORTED sometimes when they really mean DISABLED.
            if (pCmd->cmdf == CMDSTATE_NOTSUPPORTED)
                pCmd->cmdf = CMDSTATE_DISABLED;

            if (pguidCmdGroup && IsEqualGUID((const GUID&)*pguidCmdGroup, CMDSETID_Forms3))
            {
                // Trident returns CMDSTATE_DISABLED for IDM_GETBLOCKFMTS but this command should never be disabled
                if (pCmd->cmdID == IDM_GETBLOCKFMTS)
                    pCmd->cmdf = CMDSTATE_UP;

                // Trident bug: Trident returns the wrong value for IDM_SHOWZEROBORDER*,
                // IDM_SHOWALLTAGS and the IDM_NOACTIVATE* commands, so we fix
                // them up here.

                if (pCmd->cmdID == IDM_NOACTIVATENORMALOLECONTROLS ||
                    pCmd->cmdID == IDM_NOACTIVATEJAVAAPPLETS ||
                    pCmd->cmdID == IDM_NOACTIVATEDESIGNTIMECONTROLS ||
                    pCmd->cmdID == IDM_SHOWZEROBORDERATDESIGNTIME ||
                    pCmd->cmdID == IDM_SHOWALLTAGS)
                {
                    if (pCmd->cmdf == CMDSTATE_UP)
                        pCmd->cmdf = CMDSTATE_DOWN;
                    else if (pCmd->cmdf == CMDSTATE_DOWN)
                        pCmd->cmdf = CMDSTATE_UP;
                }
            }
        }

        return S_OK;
    }

    return E_UNEXPECTED;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::Exec
//
// Perform the given TriEdit or Trident command. Pass Trident commands on to
// Trident for execution. Return S_OK if all goes well or E_FAIL if not.
//

STDMETHODIMP CTriEditDocument::Exec(const GUID *pguidCmdGroup, DWORD nCmdID,
                                DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut)
{
    if (pguidCmdGroup && IsEqualGUID((const GUID&)*pguidCmdGroup, GUID_TriEditCommandGroup) &&
        m_pUnkTrident)
    {
        HRESULT hr = GetElement();

        switch(nCmdID)
        {
            case IDM_TRIED_IS_1D_ELEMENT:   //[out,VT_BOOL]
                if (pvaOut && m_pihtmlElement &&
                    SUCCEEDED(VariantChangeType(pvaOut, pvaOut, 0, VT_BOOL)))
                {
                    hr = Is2DElement(m_pihtmlElement, (BOOL*)&pvaOut->boolVal);
                    _ASSERTE(SUCCEEDED(hr));
                    if (SUCCEEDED(hr))
                    {
                        pvaOut->boolVal = !pvaOut->boolVal;
                    }
                }
                break;
           case IDM_TRIED_IS_2D_ELEMENT:   //[out,VT_BOOL]
                if (pvaOut && m_pihtmlElement &&
                    SUCCEEDED(VariantChangeType(pvaOut, pvaOut, 0, VT_BOOL)))
                {
                    hr = Is2DElement(m_pihtmlElement, (BOOL*)&pvaOut->boolVal);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_NUDGE_ELEMENT:   //[in,VT_BYREF (VARIANT.byref=LPPOINT)]
                {
                    BOOL fLock = FALSE;
                    IsLocked(m_pihtmlElement, &fLock);
                    if (!pvaIn)
                        hr = E_FAIL;
                    else if (!fLock && VT_BYREF == pvaIn->vt && pvaIn->byref)
                    {
                        hr = NudgeElement(m_pihtmlElement, (LPPOINT)pvaIn->byref);
                        _ASSERTE(SUCCEEDED(hr));
                    }
                }
                break;
            case IDM_TRIED_SET_ALIGNMENT:   //[in,VT_BYREF (VARIANT.byref=LPPOINT)]
                if (!pvaIn)
                    hr = E_FAIL;
                else if (VT_BYREF == pvaIn->vt && pvaIn->byref)
                {
                    hr = SetAlignment((LPPOINT)pvaIn->byref);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_LOCK_ELEMENT:
                if (m_pihtmlElement)
                {
                    BOOL f2d=FALSE;
                    BOOL fLocked=TRUE;
                    if (SUCCEEDED(Is2DElement(m_pihtmlElement, &f2d)) && f2d &&
                            SUCCEEDED(IsLocked(m_pihtmlElement, &fLocked)))
                    {
                        hr = LockElement(m_pihtmlElement, !fLocked);
                        _ASSERTE(SUCCEEDED(hr));
                    }
                }
                break;
            case IDM_TRIED_SEND_TO_BACK:
                if (m_pihtmlElement)
                {
                    hr = AssignZIndex(m_pihtmlElement, SEND_TO_BACK);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_SEND_TO_FRONT:
                if (m_pihtmlElement)
                {
                    hr = AssignZIndex(m_pihtmlElement, SEND_TO_FRONT);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_SEND_BACKWARD:
                if (m_pihtmlElement)
                {
                    hr = AssignZIndex(m_pihtmlElement, SEND_BACKWARD);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_SEND_FORWARD:
                if (m_pihtmlElement)
                {
                    hr = AssignZIndex(m_pihtmlElement, SEND_FORWARD);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_SEND_BEHIND_1D:
                if (m_pihtmlElement)
                {
                    hr = AssignZIndex(m_pihtmlElement, SEND_BEHIND_1D);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_SEND_FRONT_1D:
                if (m_pihtmlElement)
                {
                    hr = AssignZIndex(m_pihtmlElement, SEND_FRONT_1D);
                    _ASSERTE(SUCCEEDED(hr));
                }
                break;
            case IDM_TRIED_CONSTRAIN:
                if (!pvaIn)
                    hr = E_FAIL;
                else if (SUCCEEDED(hr = VariantChangeType(pvaIn, pvaIn, 0, VT_BOOL)))
                {
                    hr = Constrain((BOOL)pvaIn->boolVal);
                }
                break;
            case IDM_TRIED_SET_2D_DROP_MODE:
                if (!pvaIn)
                    hr = E_FAIL;
                else if (SUCCEEDED(hr = VariantChangeType(pvaIn, pvaIn, 0, VT_BOOL)))
                {
                    m_f2dDropMode = pvaIn->boolVal;
                }
                break;
            case IDM_TRIED_INSERTROW:
                hr = InsertTableRow();
                break;
            case IDM_TRIED_INSERTCOL:
                hr = InsertTableCol();
                break;
            case IDM_TRIED_INSERTCELL:
                hr = InsertTableCell();
                break;
            case IDM_TRIED_DELETEROWS:
                hr = DeleteTableRows();
                break;
            case IDM_TRIED_DELETECOLS:
                hr = DeleteTableCols();
                break;
            case IDM_TRIED_DELETECELLS:
                hr = DeleteTableCells();
                break;
            case IDM_TRIED_MERGECELLS:
                hr = MergeTableCells();
                break;
            case IDM_TRIED_SPLITCELL:
                hr = SplitTableCell();
                break;
            case IDM_TRIED_INSERTTABLE:
                hr = InsertTable(pvaIn);
                break;
            case IDM_TRIED_DOVERB:
                if (m_pihtmlElement)
                    hr = DoVerb(pvaIn, FALSE);
                else
                    hr = E_FAIL;
                break;
            case IDM_TRIED_MAKE_ABSOLUTE:
                if (m_pihtmlElement)
                {
                    BOOL f2d = FALSE;
                    hr = Is2DElement(m_pihtmlElement, &f2d);

                    if (SUCCEEDED(hr))
                    {
                        BOOL f2dCapable=FALSE;
                        if ( f2d )
                        {
                            hr = Make1DElement(m_pihtmlElement);
                            _ASSERTE(SUCCEEDED(hr));
                        }
                        else if (SUCCEEDED(Is2DCapable(m_pihtmlElement, &f2dCapable)) && f2dCapable)
                        {
                            hr = Make2DElement(m_pihtmlElement);
                            _ASSERTE(SUCCEEDED(hr));
                        }

                    }

                }
                break;

            case IDM_TRIED_ACTIVATEACTIVEXCONTROLS:
            case IDM_TRIED_ACTIVATEAPPLETS:
            case IDM_TRIED_ACTIVATEDTCS:
            case IDM_TRIED_BACKCOLOR:
            case IDM_TRIED_BLOCKFMT:
            case IDM_TRIED_BOLD:
            case IDM_TRIED_BROWSEMODE:
            case IDM_TRIED_COPY:
            case IDM_TRIED_CUT:
            case IDM_TRIED_DELETE:
            case IDM_TRIED_EDITMODE:
            case IDM_TRIED_FIND:
            case IDM_TRIED_FONT:
            case IDM_TRIED_FONTNAME:
            case IDM_TRIED_FONTSIZE:
            case IDM_TRIED_FORECOLOR:
            case IDM_TRIED_GETBLOCKFMTS:
            case IDM_TRIED_HYPERLINK:
            case IDM_TRIED_IMAGE:
            case IDM_TRIED_INDENT:
            case IDM_TRIED_ITALIC:
            case IDM_TRIED_JUSTIFYCENTER:
            case IDM_TRIED_JUSTIFYLEFT:
            case IDM_TRIED_JUSTIFYRIGHT:
            case IDM_TRIED_ORDERLIST:
            case IDM_TRIED_OUTDENT:
            case IDM_TRIED_PASTE:
            case IDM_TRIED_PRINT:
            case IDM_TRIED_REDO:
            case IDM_TRIED_REMOVEFORMAT:
            case IDM_TRIED_SELECTALL:
            case IDM_TRIED_SHOWBORDERS:
            case IDM_TRIED_SHOWDETAILS:
            case IDM_TRIED_UNDERLINE:
            case IDM_TRIED_UNDO:
            case IDM_TRIED_UNLINK:
            case IDM_TRIED_UNORDERLIST:
                {
                    ULONG cmdTrident;
                    VARIANT varColor;

                    // We will return E_FAIL if Trident's command target is not available
                    hr = E_FAIL;

                    _ASSERTE(m_pCmdTgtTrident);
                    if (m_pCmdTgtTrident && (SUCCEEDED(MapTriEditCommand(nCmdID, &cmdTrident))))
                    {
                        if (nCmdID == IDM_TRIED_ACTIVATEACTIVEXCONTROLS ||
                            nCmdID == IDM_TRIED_ACTIVATEAPPLETS ||
                            nCmdID == IDM_TRIED_ACTIVATEDTCS)
                        {
                            if (pvaIn && pvaIn->vt == VT_BOOL)
                                pvaIn->boolVal = !pvaIn->boolVal;
                        }
                       
                        // Trident bug: When you exec the forecolor, fontname or fontsize command, they also change the backcolor,
                        // so we apply a workaround here.  The workaround is to save the old backcolor and exec it later.
                        if (pvaIn && (nCmdID == IDM_TRIED_FORECOLOR || nCmdID == IDM_TRIED_FONTNAME || nCmdID == IDM_TRIED_FONTSIZE))
                        {
                            HRESULT hrT;
 
                            VariantInit(&varColor);
                            V_VT(&varColor) = VT_I4;

                            hrT = m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, IDM_BACKCOLOR, OLECMDEXECOPT_DONTPROMPTUSER, NULL, &varColor);
                            _ASSERTE(SUCCEEDED(hrT));
                        }

                        // Trident bug: When you exec the block format command with "Normal", they don't remove OL and UL tags
                        if (pvaIn && nCmdID == IDM_TRIED_BLOCKFMT && pvaIn->vt == VT_BSTR && (_wcsicmp(pvaIn->bstrVal, L"Normal") == 0))
                        {
                            OLECMD olecmd;

                            olecmd.cmdID = IDM_ORDERLIST;
                            olecmd.cmdf = CMDSTATE_NOTSUPPORTED;
                            if (S_OK == m_pCmdTgtTrident->QueryStatus(&CMDSETID_Forms3, 1, &olecmd, NULL) && olecmd.cmdf == CMDSTATE_DOWN)
                                m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, IDM_ORDERLIST, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
                            
                            olecmd.cmdID = IDM_UNORDERLIST;
                            olecmd.cmdf = CMDSTATE_NOTSUPPORTED;
                            if (S_OK == m_pCmdTgtTrident->QueryStatus(&CMDSETID_Forms3, 1, &olecmd, NULL) && olecmd.cmdf == CMDSTATE_DOWN)
                                m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, IDM_UNORDERLIST, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
                        }

                        hr = m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, cmdTrident, nCmdExecOpt, pvaIn, pvaOut);

                        if (pvaIn && (nCmdID == IDM_TRIED_FORECOLOR || nCmdID == IDM_TRIED_FONTNAME || nCmdID == IDM_TRIED_FONTSIZE))
                        {
                            HRESULT hrT;

                            hrT = m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, IDM_BACKCOLOR, OLECMDEXECOPT_DONTPROMPTUSER, &varColor, NULL);
                            _ASSERTE(SUCCEEDED(hrT));
                        }
                        else if (nCmdID == IDM_TRIED_SHOWDETAILS && pvaIn && pvaIn->vt == VT_BOOL)
                        {
                            SetUpGlyphTable(pvaIn->boolVal);
                        }

                        // Trident bug: They enable the justify commands but not actually support them.
                        // We workaround this by returning S_OK for these no matter what Trident returns.
                        if (nCmdID == IDM_TRIED_JUSTIFYLEFT || nCmdID == IDM_TRIED_JUSTIFYCENTER || nCmdID == IDM_TRIED_JUSTIFYRIGHT)
                            hr = S_OK;
                    }

                    break;
                }

            default:
                hr = E_FAIL;
                break;
        }

        if (pvaIn)
            VariantClear(pvaIn);

        // We shouldn't return any unexpected error codes here, so return E_FAIL
        if (FAILED(hr))
            hr = E_FAIL;

        return hr;
    }
    else if (m_pCmdTgtTrident)
    {
        HRESULT hr;
        BOOL fTridentCmdSet;
        VARIANT varColor;

        fTridentCmdSet = pguidCmdGroup && IsEqualGUID((const GUID&)*pguidCmdGroup, CMDSETID_Forms3);

#ifdef NEEDED
        if (fTridentCmdSet)
        {
            if (nCmdID == IDM_PARSECOMPLETE)
                OnObjectModelComplete();
            return S_OK;
        }
#endif //NEEDED

        // Trident bug: When you exec the forecolor, fontname or fontsize command, they also change the backcolor,
        // so we apply a workaround here.  The workaround is to save the old backcolor and exec it later.
        if (pvaIn && fTridentCmdSet && (nCmdID == IDM_FORECOLOR || nCmdID == IDM_FONTNAME || nCmdID == IDM_FONTSIZE))
        {
            HRESULT hrT;

            VariantInit(&varColor);
            V_VT(&varColor) = VT_I4;

            hrT = m_pCmdTgtTrident->Exec(pguidCmdGroup, IDM_BACKCOLOR, OLECMDEXECOPT_DONTPROMPTUSER, NULL, &varColor);
            _ASSERTE(SUCCEEDED(hrT));
        }

        // Trident bug: When you exec the block format command with "Normal", they don't remove OL and UL tags
        if (pvaIn && fTridentCmdSet && nCmdID == IDM_BLOCKFMT && pvaIn->vt == VT_BSTR && (_wcsicmp(pvaIn->bstrVal, L"Normal") == 0))
        {
            OLECMD olecmd;

            olecmd.cmdID = IDM_ORDERLIST;
            olecmd.cmdf = CMDSTATE_NOTSUPPORTED;
            if (S_OK == m_pCmdTgtTrident->QueryStatus(&CMDSETID_Forms3, 1, &olecmd, NULL) && olecmd.cmdf == CMDSTATE_DOWN)
                m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, IDM_ORDERLIST, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
            
            olecmd.cmdID = IDM_UNORDERLIST;
            olecmd.cmdf = CMDSTATE_NOTSUPPORTED;
            if (S_OK == m_pCmdTgtTrident->QueryStatus(&CMDSETID_Forms3, 1, &olecmd, NULL) && olecmd.cmdf == CMDSTATE_DOWN)
                m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, IDM_UNORDERLIST, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
        }

        hr = m_pCmdTgtTrident->Exec(pguidCmdGroup, nCmdID, nCmdExecOpt, pvaIn, pvaOut);

        if (pvaIn && fTridentCmdSet && (nCmdID == IDM_FORECOLOR || nCmdID == IDM_FONTNAME || nCmdID == IDM_FONTSIZE))
        {
            HRESULT hrT;

            hrT = m_pCmdTgtTrident->Exec(pguidCmdGroup, IDM_BACKCOLOR, OLECMDEXECOPT_DONTPROMPTUSER, &varColor, NULL);
            _ASSERTE(SUCCEEDED(hrT));
        }
        else if ((nCmdID == IDM_SHOWALLTAGS || nCmdID == IDM_SHOWMISCTAGS) && pvaIn && pvaIn->vt == VT_BOOL)
        {
            SetUpGlyphTable(pvaIn->boolVal);
        }

        // Trident bug: They enable the justify commands but not actually support them.
        // We workaround this by returning S_OK for these no matter what Trident returns.
        if (fTridentCmdSet && (nCmdID == IDM_JUSTIFYLEFT || nCmdID == IDM_JUSTIFYCENTER || nCmdID == IDM_JUSTIFYRIGHT))
            hr = S_OK;

        return hr;
    }

    return E_UNEXPECTED;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::Is2DElement
//
// Test the given HTML element to ascertain if it is 2D positioned or not.
// Return:
//      S_OK and *pf2D = TRUE if the element is 2D positioned.
//      S_OK and *pf2D = FALSE if the element is not 2D positioned.
//

HRESULT CTriEditDocument::Is2DElement(IHTMLElement* pihtmlElement, BOOL* pf2D)
{
    IHTMLStyle* pihtmlStyle = NULL;
    BSTR bstrPosition = NULL;
    BOOL f2DCapable;
    _ASSERTE(pihtmlElement);
    _ASSERTE(pf2D);

    *pf2D = FALSE;

    if (SUCCEEDED(Is2DCapable(pihtmlElement, &f2DCapable)))
    {
        if (f2DCapable && SUCCEEDED(pihtmlElement->get_style(&pihtmlStyle)))
        {
            _ASSERTE(pihtmlStyle);
            if (SUCCEEDED(pihtmlStyle->get_position(&bstrPosition)))
            {
                if (bstrPosition)
                {
                    *pf2D = (_wcsicmp(bstrPosition, L"absolute") == 0);
                    SysFreeString(bstrPosition);
                }
            SAFERELEASE(pihtmlStyle);
            }
        }
    }

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEDitDocument::NudgeElement
//
// Move the given HTML element (which must be 2D positioned) as indicated
// by pptNudge, further adjusted by the grid spacing in m_ptAlign. Returns
// S_OK if all goes well; E_UNEXPECTED otherwise.
//

HRESULT CTriEditDocument::NudgeElement(IHTMLElement* pihtmlElement, LPPOINT pptNudge)
{
    HRESULT hr = E_UNEXPECTED;
    IHTMLStyle* pihtmlStyle = NULL;
    long x, y;

    _ASSERTE(pihtmlElement);
    _ASSERTE(pptNudge);
    if (pihtmlElement)
    {
        if (SUCCEEDED(pihtmlElement->get_style(&pihtmlStyle)) &&
            pihtmlStyle &&
            SUCCEEDED(pihtmlStyle->get_pixelTop(&y)) &&
            SUCCEEDED(pihtmlStyle->get_pixelLeft(&x)))
        {
            if (x == 0 || y == 0)
            {
                IHTMLElement *pihtmlElementParent = NULL;
                RECT rcElement, rcParent;

                if (SUCCEEDED(pihtmlElement->get_offsetParent(&pihtmlElementParent))
                    && pihtmlElementParent)
                {
                    if (SUCCEEDED(GetElementPosition(pihtmlElement, &rcElement)))
                    {
                        ::SetRect(&rcParent, 0, 0, 0, 0);

                        if (SUCCEEDED(GetElementPosition(pihtmlElementParent, &rcParent)))
                        {
                            x = rcElement.left - rcParent.left;
                            y = rcElement.top - rcParent.top;
                        }
                    }
                    pihtmlElementParent->Release();
                }
            }

            x += pptNudge->x;
            y += pptNudge->y;
            if (pptNudge->x != 0)
            {
                if (x >= 0)
                    x -= (x % m_ptAlign.x);
                else
                    x -= (((x % m_ptAlign.x) ? m_ptAlign.x : 0) + (x % m_ptAlign.x));
            }
            if (pptNudge->y != 0)
            {
                if (y >= 0)
                    y -= (y % m_ptAlign.y);
                else
                    y -= (((y % m_ptAlign.y) ? m_ptAlign.y : 0) + (y % m_ptAlign.y));
            }
            pihtmlStyle->put_pixelTop(y);
            pihtmlStyle->put_pixelLeft(x);
            return S_OK;
        }
    }
    SAFERELEASE(pihtmlStyle);
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::SetAlignment
//
// Set the TriEdit alignment values as indicated. Return S_OK if all goes
// well; or E_POINTER if a bad pointer is supplied.
//

HRESULT CTriEditDocument::SetAlignment(LPPOINT pptAlign)
{
    _ASSERTE(pptAlign);
    if (pptAlign)
    {
        m_ptAlign.x = max(pptAlign->x, 1);
        m_ptAlign.y = max(pptAlign->y, 1);
        return S_OK;
    }
    return E_POINTER;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::LockElement
//
// Set or clear the TriEdit design-time locking flag (an expando attribute) as
// indicated by fLock. Return S_OK if all goes well; E_FAIL if not. Note that
// setting the locking flag also sets the top and left attributes if they
// were not already set.
//

HRESULT CTriEditDocument::LockElement(IHTMLElement* pihtmlElement, BOOL fLock)
{
    IHTMLStyle* pihtmlStyle=NULL;
    HRESULT hr = E_FAIL;
    VARIANT var;
    VARIANT_BOOL fSuccess = FALSE;

    if (pihtmlElement)
    {
        hr = pihtmlElement->get_style(&pihtmlStyle);
        _ASSERTE(SUCCEEDED(hr));
        if (SUCCEEDED(hr))
        {
            _ASSERTE(pihtmlStyle);
            if (pihtmlStyle)
            {
                if(!fLock)
                {
                    hr = pihtmlStyle->removeAttribute(DESIGN_TIME_LOCK, 0, &fSuccess);
                    _ASSERTE(fSuccess);
                }
                else
                {
                    // Trident doesn't persist the Design_Time_Lock attribute
                    // if left, top, width and height properties are not present as part of
                    // the elements style attribute. Hence as a part of locking the element 
                    // we also assign the top and left styles only if they don't exist.

                    LONG lTop, lLeft;

                    pihtmlStyle->get_pixelTop(&lTop);
                    pihtmlStyle->get_pixelLeft(&lLeft);

                    if (lTop == 0 || lLeft == 0)
                    {
                        IHTMLElement *pihtmlElementParent = NULL;

                        if (SUCCEEDED(pihtmlElement->get_offsetParent(&pihtmlElementParent))
                            && pihtmlElementParent)
                        {
                            if (SUCCEEDED(GetElementPosition(pihtmlElement, &m_rcElement)))
                            {
                                RECT rcParent;
                                ::SetRect(&rcParent, 0, 0, 0, 0);
    
                                if (SUCCEEDED(GetElementPosition(pihtmlElementParent, &rcParent)))
                                {
                                    m_rcElement.left   = m_rcElement.left - rcParent.left;
                                    m_rcElement.top    = m_rcElement.top  - rcParent.top;
                                    pihtmlStyle->put_pixelTop(m_rcElement.top);
                                    pihtmlStyle->put_pixelLeft(m_rcElement.left);
                                }
                            }
                            pihtmlElementParent->Release();
                        }
                    }

                    VariantInit(&var);
                    var.vt = VT_BSTR;
                    var.bstrVal = SysAllocString(L"True");
                    hr = pihtmlStyle->setAttribute(DESIGN_TIME_LOCK, var, 0);
                    hr = SUCCEEDED(hr) ? S_OK:E_FAIL;
                }
                pihtmlStyle->Release();
            }
        }

        if (SUCCEEDED(hr))
        {
            RECT rcElement;

            hr = GetElementPosition(pihtmlElement, &rcElement);
            _ASSERTE(SUCCEEDED(hr));
            if (SUCCEEDED(hr))
            {
                 InflateRect(&rcElement, ELEMENT_GRAB_SIZE, ELEMENT_GRAB_SIZE);
                 if( SUCCEEDED(hr = GetTridentWindow()))
                 {
                     _ASSERTE(m_hwndTrident);
                     InvalidateRect(m_hwndTrident,&rcElement, FALSE);
                 }
            }

            // Trident doesn't set itself to be dirty, so force the dirty state.
            VariantInit(&var);
            var.vt = VT_BOOL;
            var.boolVal = TRUE; 
            if (m_pCmdTgtTrident)           
                m_pCmdTgtTrident->Exec(&CMDSETID_Forms3, IDM_SETDIRTY, 0, &var, NULL);
        }
    }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::IsLocked
//
// Test the given HTML element to ascertain if it is design-time locked or not.
// Return:
//      S_OK and *pfLocked = TRUE if the element is design-time locked.
//      S_OK and *pfLocked = FALSE if the element is not design-time locked.
//

HRESULT CTriEditDocument::IsLocked(IHTMLElement* pihtmlElement, BOOL* pfLocked)
{
    IHTMLStyle* pihtmlStyle=NULL;
    BSTR bstrAttributeName = NULL;
    HRESULT hr = E_FAIL;
    VARIANT var;

    VariantInit(&var);
    var.vt = VT_BSTR;
    var.bstrVal = NULL;

    if (pihtmlElement)
    {
        hr = pihtmlElement->get_style(&pihtmlStyle);
        _ASSERTE(SUCCEEDED(hr));
        if (SUCCEEDED(hr))
        {
            _ASSERTE(pihtmlStyle);
            if (pihtmlStyle)
            {
                bstrAttributeName = SysAllocString(DESIGN_TIME_LOCK);

                if (bstrAttributeName)
                {
                    hr = pihtmlStyle->getAttribute(bstrAttributeName, 0, &var);
                    _ASSERTE(SUCCEEDED(hr));
                    if (var.bstrVal == NULL)
                        *pfLocked = FALSE;
                    else
                        *pfLocked = TRUE;
                    SysFreeString(bstrAttributeName);
                }
                pihtmlStyle->Release();
            }
        }
    }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
//
// CTriEditDocument::Make1DElement
//
// Set the given HTML element to layout in the flow. As a side effect this
// also removes any design-time lock on the element. Return S_OK if all goes
// well; E_UNEXPECTED otherwise.
//

HRESULT CTriEditDocument::Make1DElement(IHTMLElement* pihtmlElement)
{
    IHTMLStyle* pihtmlStyle=NULL;
    VARIANT_BOOL fSuccess = FALSE;
    VARIANT var;
    HRESULT hr;

    if (pihtmlElement)
    {
        pihtmlElement->get_style(&pihtmlStyle);
        _ASSERTE(pihtmlStyle);
        if (pihtmlStyle)
        {
            VariantInit(&var);
            var.vt = VT_I4;
            var.lVal = 0; 
            hr = pihtmlStyle->put_zIndex(var);
            _ASSERTE(SUCCEEDED(hr));

            pihtmlStyle->removeAttribute(DESIGN_TIME_LOCK, 0, &fSuccess);
            pihtmlStyle->removeAttribute(L"position", 0, &fSuccess);
            pihtmlStyle->Release();
        }
    }
    
    return (fSuccess? S_OK: E_UNEXPECTED);
}

///////////////////////////////////////////////////////////////////////////////
//
//
// CTriEditDocument::Make2DElement
//
// Set the given HTML element to be positioned. Return S_OK if all goes
// well; E_FAIL otherwise.
//

HRESULT CTriEditDocument::Make2DElement(IHTMLElement* pihtmlElement, POINT *ppt)
{

    IHTMLElement* pihtmlElementParent = NULL;
    IHTMLElementCollection* pihtmlCollection = NULL;
    IHTMLElement* pihtmlElementNew = NULL;
    IHTMLStyle* pihtmlElementStyle = NULL;
    VARIANT var;
    LONG lSourceIndex;
    HRESULT hr = E_FAIL;
    BSTR bstrOuterHtml = NULL;

    _ASSERTE(pihtmlElement);

    if(!pihtmlElement)
    {
        return E_FAIL;
    }
    
    hr = pihtmlElement->get_style(&pihtmlElementStyle);
    _ASSERTE(SUCCEEDED(hr) && pihtmlElementStyle);

    if (FAILED(hr) || !pihtmlElementStyle)
    {
        return E_FAIL;
    }

    // The reason to save the source index here is that once we call put_outerHTML
    // the element is lost, we later use the source index to get back the element from the collection.  
    // Note that the source index remains the same after put_outerHTML.
    hr = pihtmlElement->get_sourceIndex(&lSourceIndex); 
    _ASSERTE(SUCCEEDED(hr) && (lSourceIndex != -1));
    
    if (lSourceIndex == -1 || FAILED(hr))
    {
        return E_FAIL;
    }

    hr = pihtmlElement->get_offsetParent(&pihtmlElementParent);
    _ASSERTE(SUCCEEDED(hr) && pihtmlElementParent);

    if (SUCCEEDED(hr) && pihtmlElementParent)
    {
        VariantInit(&var);
        var.vt = VT_BSTR;
        var.bstrVal = SysAllocString(L"absolute");
        hr = pihtmlElementStyle->setAttribute(L"position", var, 1);

        if (var.bstrVal)
            SysFreeString(var.bstrVal);

        _ASSERTE(SUCCEEDED(hr));

        if (SUCCEEDED(hr))
        {
            if (SUCCEEDED(hr = GetElementPosition(pihtmlElement, &m_rcElement)))
            {
                IHTMLTable* pihtmlTable = NULL;
                IHTMLElement* pihtmlElementTemp = NULL, *pihtmlElementPrev = NULL;
                RECT rcParent;
                BOOL f2d = FALSE;
                BOOL fIsIE5AndBeyond = IsIE5OrBetterInstalled();

                ::SetRect(&rcParent, 0, 0, 0, 0);

                pihtmlElementTemp = pihtmlElementParent;
                pihtmlElementTemp->AddRef();

                // Handle tables specially since the offset parent may have been the TD or the TR
                while (pihtmlElementTemp)
                {
                    if (SUCCEEDED(pihtmlElementTemp->QueryInterface(IID_IHTMLTable, (void **)&pihtmlTable)) && pihtmlTable)
                        break;

                    pihtmlElementPrev = pihtmlElementTemp;
                    pihtmlElementPrev->get_offsetParent(&pihtmlElementTemp);
                    SAFERELEASE(pihtmlElementPrev);
                }

                // If parent is a 2d element, we need to offset its top and left
                if (pihtmlElementTemp && SUCCEEDED(Is2DElement(pihtmlElementTemp, &f2d)) && f2d)
                {
                    GetElementPosition(pihtmlElementTemp, &rcParent);
                }
                else if (SUCCEEDED(Is2DElement(pihtmlElementParent, &f2d)) && f2d)
                {
                    GetElementPosition(pihtmlElementParent, &rcParent);
                }

                SAFERELEASE(pihtmlTable);
                SAFERELEASE(pihtmlElementTemp);
                SAFERELEASE(pihtmlElementPrev);

                m_rcElement.left   = (ppt ? ppt->x : m_rcElement.left) - rcParent.left;
                m_rcElement.top    = (ppt ? ppt->y : m_rcElement.top) - rcParent.top;

                // We need to call get_outerHTML and put_outerHTML to work around a Trident bug
                // We should not really have to call these here, but the element doesn't get
                // updated unless we do this.
                if (fIsIE5AndBeyond || SUCCEEDED(hr = pihtmlElement->get_outerHTML(&bstrOuterHtml)))
                {
                    if (fIsIE5AndBeyond || SUCCEEDED(hr = pihtmlElement->put_outerHTML(bstrOuterHtml)))
                    {
                        hr = GetAllCollection(&pihtmlCollection);
                        _ASSERTE(SUCCEEDED(hr));
                        _ASSERTE(pihtmlCollection);

                        if (SUCCEEDED(hr) && pihtmlCollection)
                        {
                            hr = GetCollectionElement(pihtmlCollection, lSourceIndex, &pihtmlElementNew);
                            _ASSERTE(SUCCEEDED(hr));
                            _ASSERTE(pihtmlElementNew);

                            if (SUCCEEDED(hr) && pihtmlElementNew)
                            {
                                hr = SelectElement(pihtmlElementNew, pihtmlElementParent);

                                GetElement(); // to update m_pihtmlElement and friends after the above SelectElement

                                if (SUCCEEDED(hr))
                                {
                                    hr = AssignZIndex(pihtmlElementNew,  MADE_ABSOLUTE);
                                    _ASSERTE(SUCCEEDED(hr));

                                    if (SUCCEEDED(hr))
                                    {
                                        SAFERELEASE(pihtmlElementStyle);
                                        if (SUCCEEDED(hr = pihtmlElementNew->get_style(&pihtmlElementStyle)))
                                        {
                                            pihtmlElementStyle->put_pixelLeft(m_rcElement.left);
                                            pihtmlElementStyle->put_pixelTop(m_rcElement.top);
                                            VariantInit(&var);
                                            var.vt = VT_BOOL;
                                            var.boolVal = FALSE;
                                            pihtmlElementNew->scrollIntoView(var);
                                         }

                                    }

                                }

                            }

                        }

                    }

                }

            }

        }

    }
               
    if (bstrOuterHtml)                  
        SysFreeString(bstrOuterHtml);

    SAFERELEASE(pihtmlElementParent);
    SAFERELEASE(pihtmlElementStyle);
    SAFERELEASE(pihtmlElementNew);
    SAFERELEASE(pihtmlCollection);
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::Constrain
// 
// Set the TriEdit constraint flag as indicated by fConstrain. Also, reset
// the constraint direction to CONSTRAIN_NONE. Return S_OK.

HRESULT CTriEditDocument::Constrain(BOOL fConstrain)
{
    m_fConstrain = (fConstrain) ? TRUE:FALSE;
    m_eDirection = CONSTRAIN_NONE;
    return S_OK;
}

typedef struct SELCELLINFO
   {
       LONG cCellIndex; // cell index in a row
       LONG cRowIndex; // which row is this cell in
       CComPtr<IDispatch> srpCell; // cell element
       CComPtr<IDispatch> srpRow; // row element
       CComPtr<IDispatch> srpTable;
   } SELCELLINFO;


///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::GetTableRowElementAndTableFromCell
//
// Given the IDispatch pointer to an element within a table, return the
// row index in *pindexRow (if pindexRow is not NULL) and/or the 
// actual row element in *psrpRow (if psrpRow is not NULL) of the
// element within the table. If psrpTable is not NULL, return the
// table containing the element therein. Return S_OK if all goes well,
// or E_FAIL if something goes wrong.
//

HRESULT CTriEditDocument::GetTableRowElementAndTableFromCell(IDispatch *srpCell, LONG *pindexRow , IDispatch **psrpRow, IDispatch **psrpTable)
{
   CComPtr<IDispatch>    srpParent,srpElement;
   HRESULT hr = E_FAIL;
   CComBSTR bstrTag;

    _ASSERTE(srpCell != NULL);

    if (pindexRow == NULL && psrpRow == NULL)
        goto Fail;

    srpParent = srpCell;

    while (srpParent != NULL)
    {
        srpElement.Release();
        if (FAILED(hr = GetDispatchProperty(srpParent, L"parentElement", VT_DISPATCH, (void**)&srpElement)))
            goto Fail;

        if (srpElement == NULL)
            {
            hr = E_FAIL;
            goto Fail;
            }

        bstrTag.Empty();
        if (FAILED(hr = GetDispatchProperty(srpElement, L"tagName", VT_BSTR, &bstrTag)))
            goto Fail;

        if (lstrcmpi(_T("TR"), OLE2T(bstrTag)) == 0)
        {
            if (psrpRow != NULL)
            {
                *psrpRow = srpElement;
                (*psrpRow)->AddRef();
            }

            if (pindexRow != NULL)
            {
                if (FAILED(hr = GetDispatchProperty(srpElement, L"rowIndex", VT_I4, pindexRow)))
                    goto Fail;
            }
            break;
        }
        srpParent = srpElement;
    }

   if (psrpTable != NULL)
   {
       srpParent = srpElement;
       while (srpParent != NULL)
       {
            srpElement.Release();
            if (FAILED(hr = GetDispatchProperty(srpParent, L"parentElement", VT_DISPATCH, (void**)&srpElement)))
                goto Fail;

            if (srpElement == NULL)
                {
                hr = E_FAIL;
                goto Fail;
                }

            bstrTag.Empty();
            if (FAILED(hr = GetDispatchProperty(srpElement, L"tagName", VT_BSTR, &bstrTag)))
                goto Fail;

            if (lstrcmpi(_T("TABLE"), OLE2T(bstrTag)) == 0)
            {
                if (psrpTable != NULL)
                {
                    *psrpTable = srpElement;
                    (*psrpTable)->AddRef();
                }
                break;
            }
            srpParent = srpElement;
        }
   }

Fail:

    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::FEnableInsertTable
//
// Return TRUE if the Trident selection is within a table and if the selection
// type and location will allow elements to be inserted within the table. 
// Return FALSE otherwise.
//

BOOL CTriEditDocument::FEnableInsertTable(void)
{
    BOOL fRet = FALSE;
    CComPtr<IDispatch>    srpRange,srpParent,srpElement;
    CComPtr<IHTMLSelectionObject>    srpSel;
    CComPtr<IHTMLDocument2>    srpiHTMLDoc;
    CComBSTR    bstr;
    CComBSTR    bstrTag;

    if (FAILED(m_pUnkTrident->QueryInterface(IID_IHTMLDocument2, (void**)&srpiHTMLDoc)))
        goto Fail;

    if (FAILED(srpiHTMLDoc->get_selection(&srpSel)))
        goto Fail;

    if (FAILED(GetDispatchProperty(srpSel, L"type", VT_BSTR, &bstr)))
        goto Fail;

    if (lstrcmpi(_T("CONTROL"), OLE2T(bstr)) == 0)
    {
        return FALSE;
    }

    if (FAILED(CallDispatchMethod(srpSel, L"createRange", VTS_DISPATCH_RETURN, (void**)&srpRange)))
        goto Fail;

    if (srpRange == NULL)
        goto Fail;
        
    srpParent = srpRange;

    while (srpParent != NULL)
    {
        srpElement.Release();
        if (FAILED(GetDispatchProperty(srpParent, L"parentElement", VT_DISPATCH, (void**)&srpElement)))
            goto Fail;

        if (srpElement == NULL)
            break;

        bstrTag.Empty();
        if (FAILED(GetDispatchProperty(srpElement, L"tagName", VT_BSTR, &bstrTag)))
            goto Fail;

        if (lstrcmpi(_T("INPUT"), OLE2T(bstrTag)) == 0)
        {
           return FALSE;
        }
        srpParent = srpElement;
    }

    // if the selection is inside a table, make sure only one  cell is selected
    if (IsSelectionInTable() == S_OK)
    {
        UINT grf = GetSelectionTypeInTable();
        if (grf != -1 && !(grf & grfSelectOneCell)) 
            return FALSE;
    }
    
    fRet = TRUE;

Fail:
    return fRet;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::IsSelectionInTable
//
// Return S_OK if the Trident selection is within a table. Return
// E_FAIL otherwise.
//

HRESULT CTriEditDocument::IsSelectionInTable(IDispatch **ppTable)
{
    HRESULT    hr=0;
    CComPtr<IHTMLSelectionObject>    srpSel;
    CComPtr<IDispatch>    srpRange,srpParent,srpElement;
    CComPtr<IHTMLDocument2>    srpiHTMLDoc;
    CComBSTR    bstrTag;
    BOOL  fTable= FALSE;

    if (FAILED(hr = m_pUnkTrident->QueryInterface(IID_IHTMLDocument2, (void**)&srpiHTMLDoc)))
        goto Fail;

    if (FAILED(hr = srpiHTMLDoc->get_selection(&srpSel)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpSel, L"createRange", VTS_DISPATCH_RETURN, (void**)&srpRange)))
        goto Fail;

    srpParent = srpRange;
    
    while (srpParent != NULL)
    {
        srpElement.Release();
        if (FAILED(hr = GetDispatchProperty(srpParent, L"parentElement", VT_DISPATCH, (void**)&srpElement)))
            goto Fail;

        if (srpElement == NULL)
            break;

        bstrTag.Empty();
        if (FAILED(hr = GetDispatchProperty(srpElement, L"tagName", VT_BSTR, &bstrTag)))
            goto Fail;

        if (lstrcmpi(_T("TABLE"), OLE2T(bstrTag)) == 0)
        {
            if (ppTable != NULL)
            {
                *ppTable = srpElement;
                (*ppTable)->AddRef();
            }
            fTable = TRUE;
            break;
        }
        else if (lstrcmpi(_T("CAPTION"), OLE2T(bstrTag)) == 0)
        {
            fTable = FALSE;
            break;
        }

        srpParent = srpElement;
    }

Fail:

    return fTable ? S_OK : E_FAIL;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::FillInSelectionCellsInfo
//
// Fill *pSelStart with the information concerning the table cell containing
// the beginning of the Trident selection and *pSelSle with the information
// on the table cell at the end of the selection. Return S_OK if all goes well,
// or E_FAIL otherwise.

HRESULT   CTriEditDocument::FillInSelectionCellsInfo(struct SELCELLINFO * pselStart, struct SELCELLINFO *pselEnd)
{
    CComPtr<IHTMLDocument2>  srpiHTMLDoc;
    CComPtr<IHTMLSelectionObject>   srpSel;
    CComPtr<IHTMLTxtRange>      srpRange[2];
    CComPtr<IDispatch>    srpParent;
    CComBSTR       bstrText, bstrTag;;
    LONG cReturn=0;
    HRESULT i=0, hr=0;
    LONG cCharSelected=0;
    WCHAR *pData = NULL;
    BOOL fContain = FALSE;

    if (FAILED(hr = IsSelectionInTable()))
        goto Fail;

    if (FAILED(hr = m_pUnkTrident->QueryInterface(IID_IHTMLDocument2, (void**)&srpiHTMLDoc)))
        goto Fail;

    if (FAILED(hr = srpiHTMLDoc->get_selection(&srpSel)))
        goto Fail;

    for (i=0; i<2 ; i++)
    {
		// BUG 568250.  We HAD treated the dispatch like a text range, this now crashes.
		CComPtr<IDispatch> srpDisp;
        if (FAILED(hr = CallDispatchMethod(srpSel, L"createRange", VTS_DISPATCH_RETURN, (void**)&srpDisp)))
		{
             goto Fail;
		}
		else
		{
			if (FAILED(hr = srpDisp->QueryInterface(&srpRange[i])))
				goto Fail;
		}
    }

    bstrText.Empty();
    hr = srpRange[0]->get_text(&bstrText);
    if (FAILED(hr))
    goto Fail;

    cCharSelected = bstrText ? ocslen(bstrText) : 0;
    pData = (WCHAR *) bstrText;

    // VID98 bug 3117: trident use '0x0D' to mark column/row and this char is ignored when
    // move range so we need to deduct these characters
    while (pData != NULL && *pData !='\0')
    {
        if (*pData == 0x0D)
            cCharSelected--;
        pData++;
    }

    if (pselStart != NULL)
    {
        hr = srpRange[0]->collapse(TRUE);
        if (FAILED(hr))
        goto Fail;

        srpParent = srpRange[0];
        while (srpParent != NULL)
        {
            pselStart->srpCell.Release();
            if (FAILED(hr = GetDispatchProperty(srpParent, L"parentElement", VT_DISPATCH, (void**)&pselStart->srpCell)))
                goto Fail;

            if (pselStart->srpCell == NULL)
                {
                hr = E_FAIL;
                goto Fail;
                }

            bstrTag.Empty();
            if (FAILED(hr = GetDispatchProperty(pselStart->srpCell, L"tagName", VT_BSTR, &bstrTag)))
                goto Fail;

            if (lstrcmpi(_T("TD"), OLE2T(bstrTag)) == 0 || lstrcmpi(_T("TH"), OLE2T(bstrTag)) == 0)
            {
                break;
            }
          
            srpParent = pselStart->srpCell;
        }

        _ASSERTE(pselStart->srpCell != NULL);
        if (FAILED(hr = GetDispatchProperty(pselStart->srpCell, L"cellIndex", VT_I4, &pselStart->cCellIndex)))
            goto Fail;

        pselStart->srpRow.Release();
        if (FAILED(hr = GetTableRowElementAndTableFromCell(pselStart->srpCell, &pselStart->cRowIndex, &pselStart->srpRow, &pselStart->srpTable)))
            goto Fail;
    }

    if (pselEnd != NULL)
    {
        hr = srpRange[1]->collapse(FALSE);
        if (FAILED(hr))
            goto Fail;

        if (cCharSelected != 0)
        {
            hr = srpRange[1]->moveStart(L"Character", -1, &cReturn);
            if (FAILED(hr))
                goto Fail;
    
            hr = srpRange[1]->moveEnd(L"Character", -1, &cReturn);
            if (FAILED(hr))
                goto Fail;
        }

        srpParent = srpRange[1];
        while (srpParent != NULL)
        {
            pselEnd->srpCell.Release();
            if (FAILED(hr = GetDispatchProperty(srpParent, L"parentElement", VT_DISPATCH, (void**)&pselEnd->srpCell)))
                goto Fail;

            if (pselEnd->srpCell == NULL)
                {
                hr = E_FAIL;
                goto Fail;
                }

            bstrTag.Empty();
            if (FAILED(hr = GetDispatchProperty(pselEnd->srpCell, L"tagName", VT_BSTR, &bstrTag)))
                goto Fail;
            
            if (lstrcmpi(_T("TD"), OLE2T(bstrTag)) == 0 || lstrcmpi(_T("TH"), OLE2T(bstrTag)) == 0)
            {
                break;
            }
            srpParent = pselEnd->srpCell;
        }

        _ASSERTE(pselEnd->srpCell != NULL);
        if (FAILED(hr = GetDispatchProperty(pselEnd->srpCell, L"cellIndex", VT_I4, &pselEnd->cCellIndex)))
            goto Fail;

        pselEnd->srpRow.Release();
        if (FAILED(hr =  GetTableRowElementAndTableFromCell(pselEnd->srpCell, &pselEnd->cRowIndex, &pselEnd->srpRow, &pselEnd->srpTable)))
            goto Fail;
    }

    if (pselEnd != NULL && pselStart != NULL)
    {
    // VID 98 bug 3116: we need to check if first cell and last cell are in the same table. If they are not
    // the row index and cell index we just got do not make sense
        if (FAILED(hr = CallDispatchMethod(pselEnd->srpTable, L"contains", VTS_DISPATCH VTS_BOOL_RETURN, pselStart->srpRow, &fContain)))
            goto Fail;

        if (!fContain)
           return E_FAIL;

        fContain = FALSE;
        if (FAILED(hr = CallDispatchMethod(pselStart->srpTable, L"contains", VTS_DISPATCH VTS_BOOL_RETURN, pselEnd->srpRow, &fContain)))
            goto Fail;

        if (!fContain)
           return E_FAIL;
    }


Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::GetSelectionTypeInTable
//
// Return a set of flags that characterize the current selection. Return
// -1 if something goes wrong. The flags are as follows:
//
//      grfInSingleRow          Selection is comprised of one or more cells 
//                              within a single row.
//
//      grfSelectOneCell        Selection is comprised of a single cell.
//
//      grpSelectEntireRow      Selection is comprised of one or more 
//                              complete rows.

ULONG    CTriEditDocument::GetSelectionTypeInTable(void)
{
    CComPtr<IDispatch>    srpCells;
    struct SELCELLINFO    selinfo[2]; // 0 is start cell, 1 is end cell
    LONG cCells=0;
    HRESULT hr=0;
    ULONG grf=0;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo[0], &selinfo[1])))
        goto Fail;

    if (selinfo[0].cRowIndex == selinfo[1].cRowIndex)
    {
        grf |= grfInSingleRow;
        if (selinfo[0].cCellIndex == selinfo[1].cCellIndex)
            grf |= grfSelectOneCell;
    }
    else
    {
        grf &= ~grfInSingleRow;
    }

    if (selinfo[0].cCellIndex != 0)
        grf &= ~grpSelectEntireRow;
    else
    {
        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(selinfo[1].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
            goto Fail;

        if (selinfo[1].cCellIndex != cCells-1)
            grf &= ~grpSelectEntireRow;
        else
            grf |= grpSelectEntireRow;
    }



Fail:
    return FAILED(hr) ? -1 : grf;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::CopyProperty
//
// Copy properties from the pFrom element on to the pTo element. Return S_OK.
//

HRESULT CTriEditDocument::CopyProperty(IDispatch *pFrom, IDispatch *pTo)
{
    CComVariant varProp;
    CComBSTR bstrProp;
    VARIANT_BOOL bProp;

    bstrProp.Empty();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"align", VT_BSTR, (void **)&bstrProp)))
    {
        if (lstrcmpW(bstrProp, L""))
            PutDispatchProperty(pTo, L"align", VT_BSTR, bstrProp);
    }
    
    bstrProp.Empty();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"vAlign", VT_BSTR, (void **)&bstrProp)))
    {
        if (lstrcmpW(bstrProp, L""))
            PutDispatchProperty(pTo, L"vAlign", VT_BSTR, bstrProp);
    }

    bstrProp.Empty();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"background", VT_BSTR, (void **)&bstrProp)))
    {
        if (lstrcmpW(bstrProp, L""))
            PutDispatchProperty(pTo, L"background", VT_BSTR, bstrProp);
    }

    bstrProp.Empty();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"lang", VT_BSTR, (void **)&bstrProp)))
    {
        if (lstrcmpW(bstrProp, L""))
            PutDispatchProperty(pTo, L"lang", VT_BSTR, bstrProp);
    }   

    bstrProp.Empty();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"className", VT_BSTR, (void **)&bstrProp)))
    {
        if (lstrcmpW(bstrProp, L""))
            PutDispatchProperty(pTo, L"className", VT_BSTR, bstrProp);
    }   
    
    varProp.Clear();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"bgColor", VT_VARIANT, (void **)&varProp)))
        PutDispatchProperty(pTo, L"bgColor", VT_VARIANT, varProp);

    varProp.Clear();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"borderColor", VT_VARIANT, (void **)&varProp)))
        PutDispatchProperty(pTo, L"borderColor", VT_VARIANT, varProp);
    
    varProp.Clear();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"borderColorLight", VT_VARIANT, (void **)&varProp)))
        PutDispatchProperty(pTo, L"borderColorLight", VT_VARIANT, varProp);

    varProp.Clear();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"borderColorDark", VT_VARIANT, (void **)&varProp)))
        PutDispatchProperty(pTo, L"borderColorDark", VT_VARIANT, varProp);

    varProp.Clear();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"height", VT_VARIANT, (void **)&varProp)))
        PutDispatchProperty(pTo, L"height", VT_VARIANT, varProp);

    varProp.Clear();
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"width", VT_VARIANT, (void **)&varProp)))
        PutDispatchProperty(pTo, L"width", VT_VARIANT, varProp);

    
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"noWrap", VT_BOOL, (void **)&bProp)))
    {
#pragma warning(disable: 4310) // cast truncates constant value
        if (bProp == VARIANT_TRUE) 
#pragma warning(default: 4310) // cast truncates constant value
            PutDispatchProperty(pTo, L"noWrap", VT_BOOL, bProp);
    }

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::CopyStyle
//
// Copy style properties from style element pFrom on to style element pTo.
// Return S_OK.
//      

HRESULT CTriEditDocument::CopyStyle(IDispatch *pFrom, IDispatch *pTo)
{
    CComPtr<IDispatch>  srpStyleTo, srpStyleFrom;
  
    if (SUCCEEDED(GetDispatchProperty(pFrom, L"style", VT_DISPATCH, (void **)&srpStyleFrom)))
    {
        if (SUCCEEDED(GetDispatchProperty(pTo, L"style", VT_DISPATCH, (void **)&srpStyleTo)))
        {
            CComVariant varProp;
            CComBSTR bstrProp;

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"backgroundAttachment", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"backgroundAttachment", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"backgroundImage", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"backgroundImage", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"backgroundRepeat", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"backgroundRepeat", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"borderBottom", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"borderBottom", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"borderLeft", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"borderLeft", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"borderTop", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"borderTop", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"borderRight", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"borderRight", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"fontFamily", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"fontFamily", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"fontStyle", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"fontStyle", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"fontVariant", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"fontVariant", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"fontWeight", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"fontWeight", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"textAlign", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"textAlign", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"textTransform", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"textTransform", VT_BSTR, bstrProp);
            }   

            bstrProp.Empty();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"textDecoration", VT_BSTR, (void **)&bstrProp)))
            {
                if (lstrcmpW(bstrProp, L""))
                    PutDispatchProperty(srpStyleTo, L"textDecoration", VT_BSTR, bstrProp);
            }   
            
            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"backgroundcolor", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"backgroundcolor", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"color", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"color", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"fontSize", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"fontSize", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"height", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"height", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"letterSpacing", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"letterSpacing", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"lineHeight", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"lineHeight", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"paddingRight", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"paddingRight", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"paddingBottom", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"paddingBottom", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"paddingLeft", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"paddingLeft", VT_VARIANT, varProp);

            varProp.Clear();
            if (SUCCEEDED(GetDispatchProperty(srpStyleFrom, L"paddingTop", VT_VARIANT, (void **)&varProp)))
                PutDispatchProperty(srpStyleTo, L"paddingTop", VT_VARIANT, varProp);
        }
    }

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::DeleteTableRows
//
// Delete the table row(s) contained within the Trident selection. The 
// entire operation is a single undo unit. Return S_OK or a Trident error.
//

HRESULT CTriEditDocument::DeleteTableRows(void)
{
    HRESULT    hr = S_OK;
    CComPtr<IHTMLElement>       srpTable;
    struct SELCELLINFO    selinfo[2]; // 0 is start cell, 1 is end cell
    INT i=0;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    if (FAILED(hr = IsSelectionInTable((IDispatch**)&srpTable)))
        goto Fail;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo[0], &selinfo[1])))
        goto Fail;
            
    undoPackMgr.Start();
    
    for(i= selinfo[0].cRowIndex; i <= selinfo[1].cRowIndex; i++)
    {
        if (FAILED(hr = DeleteRowEx(srpTable, selinfo[0].cRowIndex)))
            goto Fail;
    }
    
Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::DeleteRowEx
//
// Delete the indicated table row. If the row is the only row in the table,
// delete the whole table. Return S_OK or a Trident error.
//

inline HRESULT CTriEditDocument::DeleteRowEx(IHTMLElement *pTable, LONG index)
{
    HRESULT    hr = S_OK;
    CComPtr<IDispatch> srpRows;
    INT cRows = 0;

    if (FAILED(hr = GetDispatchProperty(pTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
        goto Fail;

    if (FAILED(hr = GetDispatchProperty(srpRows, L"length", VT_I4, &cRows)))
        goto Fail;

    // if this is the only row in the table, delete the whole table
    if (cRows == 1)
    {
        _ASSERT(index == 0);
        hr = DeleteTable(pTable);
    }
    else
    {
        if (FAILED(hr = CallDispatchMethod(pTable, L"deleteRow", VTS_I4, index)))
            goto Fail;
    }

Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::DeleteCellEx
//
// Delete the indicated cell from the indicated row of the given table. If
// the cell is the only row in the table, delete the whole table. Return
// S_OK or a Trident error.
//

inline HRESULT CTriEditDocument::DeleteCellEx(IHTMLElement *pTable, IDispatch *pRow, LONG indexRow, LONG indexCell)
{
    HRESULT    hr = S_OK;
    CComPtr<IDispatch> srpCells;
    INT cCells = 0;

    if (FAILED(hr = GetDispatchProperty(pRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
        goto Fail;

    // if this is the only cell in the table, delete the whole row
    if (cCells == 1)
    {
        _ASSERT(indexCell == 0);
        hr = DeleteRowEx(pTable, indexRow);
    }
    else
    {
        if (FAILED(hr = CallDispatchMethod(pRow, L"deleteCell", VTS_I4, indexCell)))
            goto Fail;
    }

Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::DeleteTable
//
// Delete the given table. Return S_OK if all goes well; E_FAIL if something
// goes wrong.
//

HRESULT CTriEditDocument::DeleteTable(IHTMLElement *pTable)
{
    CComPtr<IHTMLElement>   srpParent;
    HRESULT hr = E_FAIL;

    _ASSERTE(pTable != NULL);

    if (pTable  == NULL)
        goto Fail;
        
    if (FAILED(hr=pTable->get_offsetParent(&srpParent)))
        goto Fail;

    _ASSERTE(srpParent != NULL);
    if (FAILED(hr = SelectElement(pTable, srpParent)))
        goto Fail;
        
    hr = Exec(&CMDSETID_Forms3, IDM_DELETE, OLECMDEXECOPT_DONTPROMPTUSER, NULL, NULL);
    
Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::InsertTableRow
//
// Insert a new table row in to the table which contains the Trident selection,
// in the row preceding the selection. The new row will have the same number of
// cells as the row containing the selection. The colSpan of each new cell
// will be copied from the row containing the selection. The entire operation
// is a single undo unit. Returns S_OK or a Trident error.
//

HRESULT CTriEditDocument::InsertTableRow(void)
{
    HRESULT    hr = S_OK;
    CComPtr<IDispatch> srpCell,srpCellNew, srpTable,srpCells,srpRows,srpNewRow,srpCellsNew;
    LONG ccolSpan=0;
    LONG cCells=0,i=0;
    struct SELCELLINFO    selinfo;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();

    if (FAILED(hr = IsSelectionInTable(&srpTable)))
        goto Fail;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo, NULL)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpTable, L"insertRow", VTS_I4, selinfo.cRowIndex)))
        goto Fail;

    if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, selinfo.cRowIndex, &srpNewRow)))
        goto Fail;

    CopyStyle(selinfo.srpRow, srpNewRow);
    
    // get the number of cells contains in the selected row
    if (FAILED(hr = GetDispatchProperty(selinfo.srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
        goto Fail;

    // now insert cells
    for (i=cCells-1; i >=0; i--)
    {
         if (FAILED(hr = CallDispatchMethod(srpNewRow, L"insertCell", VTS_I4, 0)))
             goto Fail;

         srpCell.Release();
         if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpCell)))
             goto Fail;

         srpCellsNew.Release();
         if (FAILED(hr = GetDispatchProperty(srpNewRow, L"cells", VT_DISPATCH, (void**)&srpCellsNew)))
             goto Fail;
         srpCellNew.Release();
         if (FAILED(hr = CallDispatchMethod(srpCellsNew, L"Item", VTS_I4 VTS_DISPATCH_RETURN, 0, &srpCellNew)))
             goto Fail;

         CopyStyle(srpCell, srpCellNew);
         CopyProperty(srpCell, srpCellNew);
         
         {
         VARIANT width;
         VariantInit(&width);
         if (SUCCEEDED(hr = GetDispatchProperty(srpCell, L"width", VT_VARIANT, &width)))
             PutDispatchProperty(srpCellNew, L"width", VT_VARIANT, width);
         }

         if (SUCCEEDED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &ccolSpan)))
             PutDispatchProperty(srpCellNew, L"colSpan", VT_I4, ccolSpan);
    }

Fail:
       return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::MapCellToFirstRowCell
//
// Given a table cell in pselInfo, return (by modifying pselInfo) the cell in
// the first row with the same column position, accounting for colSpans. Return
// S_OK or a Trident error.
//

HRESULT CTriEditDocument::MapCellToFirstRowCell(IDispatch *srpTable, struct SELCELLINFO *pselinfo)
{
    HRESULT hr = 0;
    CComPtr<IDispatch> srpCell, srpCells,srpRow,srpRows;
    INT i=0,iCellIndex=0,iColSpanCurRow=0,cSpan=0,iColSpanFirstRow=0,crowSpan=0;

    _ASSERTE(pselinfo != NULL);
    // if current selection is not first row, find the corresponding first row cell index
    if (pselinfo->cRowIndex == 0)
        return S_OK;

    srpCells.Release();
    _ASSERTE(pselinfo->srpRow != NULL);
    if (FAILED(hr = GetDispatchProperty(pselinfo->srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    for (i=0; i < pselinfo->cCellIndex ; i++)
    {
        srpCell.Release();
        _ASSERTE(srpCells != NULL);
        if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpCell)))
            goto Fail;

        _ASSERTE(srpCell != NULL);
        if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &cSpan)))
            goto Fail;

        iColSpanCurRow += cSpan;
    }

    srpRows.Release();
     _ASSERTE(srpTable != NULL);

    if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
        goto Fail;

    _ASSERTE(srpRows != NULL);
    srpRow.Release();
    if (FAILED(hr = CallDispatchMethod(srpRows, L"Item",VTS_I4 VTS_DISPATCH_RETURN, 0, &srpRow)))
        goto Fail;

    srpCells.Release();
    if (FAILED(hr = GetDispatchProperty(srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    iCellIndex=-1;
    while(iColSpanCurRow >= iColSpanFirstRow)
    {
        iCellIndex++;
        srpCell.Release();
        _ASSERTE(srpCells != NULL);
        if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, iCellIndex, &srpCell)))
            goto Fail;

        // we might hit the end. If so, first row is shorter than curret row and there's no mapping first row, bail out...
        if (srpCell == NULL)
        {
        hr = E_FAIL;
        goto Fail;
        }

        _ASSERTE(srpCell != NULL);
        if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &cSpan)))
            goto Fail;

        iColSpanFirstRow += cSpan;

        if (FAILED(hr = GetDispatchProperty(srpCell, L"rowSpan", VT_I4, &crowSpan)))
            goto Fail;

        if (crowSpan > pselinfo->cRowIndex)
        {
            iColSpanCurRow += cSpan;
        }

    }

    pselinfo->srpCell = srpCell;
    pselinfo->srpRow.Release();
    if (FAILED(hr = GetTableRowElementAndTableFromCell(pselinfo->srpCell, NULL, &pselinfo->srpRow)))
        goto Fail;

    pselinfo->cRowIndex = 0;
    _ASSERTE(iCellIndex >= 0);
    pselinfo->cCellIndex = iCellIndex;

Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::InsertTableCol
//
// Insert a new column in to the table containing the selection, at the column
// of the selection. The entire operation is a single undo unit. Return S_OK
// or a Trident error.
//

HRESULT CTriEditDocument::InsertTableCol(void)
{
    HRESULT    hr = S_OK;
    CComPtr<IDispatch>               srpCellNew, srpTable,srpRows,srpRow,srpCells,srpCell;
    LONG    cRows=0,i=0, j=0, iColSpanInsert=0, iColSpanCur=0, cSpan=0,crowSpan = 0, cCells=0;
    struct SELCELLINFO    selinfo;
    INT *pccolFix = NULL;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();

    if (FAILED(hr = IsSelectionInTable(&srpTable)))
        goto Fail;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo, NULL)))
        goto Fail;

    MapCellToFirstRowCell(srpTable, &selinfo);

    srpCells.Release();
    _ASSERTE(selinfo.srpRow != NULL);
    if (FAILED(hr = GetDispatchProperty(selinfo.srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    _ASSERTE(srpTable != NULL);
    if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
       goto Fail;

    _ASSERTE(srpRows != NULL);
    if (FAILED(hr = GetDispatchProperty(srpRows, L"length", VT_I4, &cRows)))
       goto Fail;

    pccolFix = new INT[cRows];
    _ASSERTE(pccolFix != NULL);
    for (i=0; i< cRows; i++)
        *(pccolFix+i) = 0;

    for (i=0; i < selinfo.cCellIndex; i++)
    {
        srpCell.Release();
        _ASSERTE(srpCells != NULL);
        if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpCell)))             goto Fail;

        _ASSERTE(srpCell != NULL);
        if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &cSpan)))
            goto Fail;

        iColSpanInsert += cSpan;

        if (FAILED(hr = GetDispatchProperty(srpCell, L"rowSpan", VT_I4, &crowSpan)))
            goto Fail;

    // if someone before the current cell has row span, this needs to propogate to
    // the next spanned rows
        if (crowSpan > 1)
            {
            for (j= selinfo.cRowIndex+1; j < (selinfo.cRowIndex+crowSpan); j++)
                *(pccolFix+j) += cSpan;
            }
    }

    for (i=0; i < cRows;)
    {
        srpRow.Release();
         _ASSERTE(srpRows != NULL);
        if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpRow)))
            goto Fail;

        srpCells.Release();
        _ASSERTE(srpRow != NULL);
        if (FAILED(hr = GetDispatchProperty(srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        _ASSERTE(srpCells != NULL);
        if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
            goto Fail;

        iColSpanCur =  *(pccolFix+i);
        for (j=0; j < cCells; j++)
        {
            srpCell.Release();
            if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, j, &srpCell)))             goto Fail;

             _ASSERTE(srpCell != NULL);
            if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &cSpan)))
                goto Fail;

            if (iColSpanCur >= iColSpanInsert)
                break;

            iColSpanCur += cSpan;
        }

        _ASSERTE(srpRow != NULL);
        if (FAILED(hr = CallDispatchMethod(srpRow, L"insertCell", VTS_I4, j)))
            goto Fail;

        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        srpCellNew.Release();
        if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, j, &srpCellNew)))
            goto Fail;

                
        if (!(!srpCell))
        {
            CopyStyle(srpCell, srpCellNew);
            CopyProperty(srpCell, srpCellNew);
            
            {
            VARIANT height;
            VariantInit(&height);
            if (SUCCEEDED(hr = GetDispatchProperty(srpCell, L"height", VT_VARIANT, &height)))
                PutDispatchProperty(srpCellNew, L"height", VT_VARIANT, height);
             }

            if (SUCCEEDED(GetDispatchProperty(srpCell, L"rowSpan", VT_I4, &cSpan)))
                PutDispatchProperty(srpCellNew, L"rowSpan", VT_I4, cSpan);
        }

        // cSpan might be 0 if we are inserting a cell into an empty row
        i += max(1, cSpan);
    }

Fail:
    if (pccolFix != NULL)
        delete [] pccolFix;
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::DeleteTableCols
//
// Delete the table columns that are contained within the Trident selection.
// The entire operation is a single undo unit. Return S_OK or a Trident error. 
//

HRESULT CTriEditDocument::DeleteTableCols(void)
{
    CComPtr<IDispatch>       srpRows,srpRow,srpCells,srpCell;
    CComPtr<IHTMLElement>   srpTable;
    struct SELCELLINFO          selinfo[2]; // 0 is start cell, 1 is end cell
    LONG cRows=0, i=0, j=0, k=0, cCells=0;
    HRESULT      hr=0;
    LONG iColSpanStart=0, iColSpanEnd=0,cColSpan=0,iColSpanCur=0, crowSpan=0;
    INT *  pccolFixStart=NULL, *pccolFixEnd = NULL;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();

    if (FAILED(hr = IsSelectionInTable((IDispatch**)&srpTable)))
        goto Fail;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo[0], &selinfo[1])))
        goto Fail;

    if (!FAILED(MapCellToFirstRowCell(srpTable, &selinfo[1])))
        MapCellToFirstRowCell(srpTable, &selinfo[0]);

    _ASSERTE(selinfo[0].srpRow != NULL);
    if (FAILED(hr = GetDispatchProperty(selinfo[0].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    _ASSERTE(srpCells != NULL);
    if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
        goto Fail;

    _ASSERTE(selinfo[1].cRowIndex == selinfo[0].cRowIndex);
    _ASSERTE(selinfo[1].cCellIndex >= selinfo[0].cCellIndex);

    srpRows.Release();
    if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
       goto Fail;

    _ASSERTE(srpRows != NULL);
    if (FAILED(hr = GetDispatchProperty(srpRows, L"length", VT_I4, &cRows)))
       goto Fail;

    pccolFixEnd = new INT[cRows];
    pccolFixStart = new INT[cRows];
    for (i=0; i< cRows; i++)
        {
        *(pccolFixStart+i) = 0;
        *(pccolFixEnd+i) = 0;
        }

    for (i=0; i<= selinfo[1].cCellIndex; i++)
    {
        srpCell.Release();
        if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpCell)))             goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &cColSpan)))
            goto Fail;
        if (i < selinfo[0].cCellIndex)
           iColSpanStart += cColSpan;

        if (i <= selinfo[1].cCellIndex)
           iColSpanEnd += cColSpan;

        if (FAILED(hr = GetDispatchProperty(srpCell, L"rowSpan", VT_I4, &crowSpan)))
            goto Fail;

        if (crowSpan > 1)
        {
            if (i < selinfo[0].cCellIndex)
            {
                for (j= selinfo[0].cRowIndex+1; j < selinfo[0].cRowIndex+crowSpan; j++)
                    *(pccolFixStart+j) += cColSpan;
            }

            if (i <= selinfo[1].cCellIndex)
            {
                for (j= selinfo[0].cRowIndex+1; j < selinfo[0].cRowIndex+crowSpan; j++)
                    *(pccolFixEnd+j) += cColSpan;
            }
        }
    }

    for (j=cRows-1; j >= 0; j--)
    {
        srpRow.Release();
        if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, j, &srpRow)))
            goto Fail;

        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
            goto Fail;

        iColSpanCur = 0;
        _ASSERTE(iColSpanEnd-*(pccolFixEnd+j) >= 0);
        _ASSERTE(iColSpanStart-*(pccolFixStart+j) >= 0);

        for (i=0, k=0; iColSpanCur <= (iColSpanEnd-*(pccolFixEnd+j)) && k < cCells ; i++, k++)
        {
            srpCell.Release();
            if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpCell)))
                goto Fail;
            if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &cColSpan)))
                goto Fail;

            if (iColSpanCur >= (iColSpanStart-*(pccolFixStart+j)) && iColSpanCur < (iColSpanEnd-*(pccolFixEnd+j)))
            {
                if (FAILED(hr = DeleteCellEx(srpTable, srpRow, j, i)))
                    goto Fail;
                i--; // we've deleted one cell, need to decrement cell index
            }

            iColSpanCur += cColSpan;
        }
    }

Fail:
    if (pccolFixStart != NULL)
     {
         delete [] pccolFixStart;
     }

    if (pccolFixEnd != NULL)
     {
         delete [] pccolFixEnd;
     }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::InsertTableCell
//
// Insert a table cell before the cell containing the Trident selection; copy
// the properties and style of the cell containing the selection to the new
// cell. The entire operation is a single undo unit. Returns S_OK or a Trident
// error.
//

HRESULT CTriEditDocument::InsertTableCell(void)
{
    HRESULT    hr = S_OK;
    struct SELCELLINFO    selinfo;
    CComPtr<IDispatch>    srpCellNew, srpCells;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();
    
    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo, NULL)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(selinfo.srpRow, L"insertCell", VTS_I4, selinfo.cCellIndex)))
       goto Fail;

    srpCells.Release();
    if (FAILED(hr = GetDispatchProperty(selinfo.srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    srpCellNew.Release();
    if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, selinfo.cCellIndex, &srpCellNew)))
        goto Fail;

    CopyStyle(selinfo.srpCell, srpCellNew);
    CopyProperty(selinfo.srpCell, srpCellNew);

Fail:

    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::DeleteTableCells
//
// Delete the table cells contained within the Trident selection. Delete entire
// rows as indicated. The entire operation is a single undo unit. Return
// S_OK or a Trident error.
//

HRESULT CTriEditDocument::DeleteTableCells(void)
{
    CComPtr<IHTMLElement>       srpTable,srpCells;
    struct SELCELLINFO          selinfo[2]; // 0 is start cell, 1 is end cell
    LONG i=0, cCells=0;
    HRESULT      hr=0;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();

    if (FAILED(hr = IsSelectionInTable((IDispatch**)&srpTable)))
        goto Fail;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo[0], &selinfo[1])))
        goto Fail;

    if (selinfo[0].cRowIndex == selinfo[1].cRowIndex) // same row
    {
        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(selinfo[0].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
            goto Fail;

        // if the selection is select all the cells in this row, delete the whole row instead
        if ( cCells == selinfo[1].cCellIndex+1 && selinfo[0].cCellIndex == 0)
        {
            if (FAILED(hr = DeleteRowEx(srpTable, selinfo[0].cRowIndex)))
                goto Fail;
        }
        else // delete cell by cell
        {
            for (i = selinfo[1].cCellIndex; i >= selinfo[0].cCellIndex; i--)
            {
                if (FAILED(hr = DeleteCellEx(srpTable, selinfo[0].srpRow, selinfo[0].cRowIndex, i)))
                    goto Fail;
            }
         }
    }
    else
    {
        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(selinfo[1].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
                goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
                goto Fail;

        // if the selection ends at the last cell of the row, delete the whole row instead
        if ( cCells == selinfo[1].cCellIndex+1)
        {
            if (FAILED(hr = DeleteRowEx(srpTable, selinfo[1].cRowIndex)))
                goto Fail;
        }
        else // delete cell by cell
        {
            for (i = selinfo[1].cCellIndex; i >= 0; i--)
            {
                if (FAILED(hr = DeleteCellEx(srpTable, selinfo[1].srpRow, selinfo[1].cRowIndex, i)))
                    goto Fail;
            }
        }
        
        for (i = selinfo[1].cRowIndex-1; i > selinfo[0].cRowIndex; i--)
        {
            if (FAILED(hr = DeleteRowEx(srpTable, i)))
                goto Fail;
        }

       
        if (selinfo[0].cCellIndex == 0) // if the selection is from first cell of a row across other rows, delete the whole row instead
        {
            if (FAILED(hr = DeleteRowEx(srpTable, selinfo[0].cRowIndex)))
                goto Fail;
        }
        else // delete cell by cell
        {
            srpCells.Release();
            if (FAILED(hr = GetDispatchProperty(selinfo[0].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
                goto Fail;

            if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
                goto Fail;

            for (i = cCells-1; i >= selinfo[0].cCellIndex; i--)
            {
                if (FAILED(hr = DeleteCellEx(srpTable, selinfo[0].srpRow, selinfo[0].cRowIndex, i)))
                    goto Fail;
            }
        }
    }

Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::MergeTableCells
//
// Merge the indicated cells in to a single cell, and adjust its colSpan.
// The cells must be within a single table row. The innerHTML of all merged cells
// is concatenated and placed in the remaining cell. Return S_OK or a Trident error.
//

HRESULT CTriEditDocument::MergeTableCells(IDispatch* srpTable, INT iRow, INT iIndexStart, INT iIndexEnd)
{
    CComPtr<IDispatch>    srpCells,srpRows,srpCurRow,srpCell;
    INT ccolSpanTotal=0, i=0, ccolSpan=0;
    HRESULT      hr=0;
    CComBSTR    bstrText;
    CComBSTR    bstrMergedText;

    if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, iRow, &srpCurRow)))
        goto Fail;

    srpCells.Release();
    if (FAILED(hr = GetDispatchProperty(srpCurRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    bstrMergedText.Empty();
    ccolSpanTotal = 0;

    for (i = iIndexEnd; i >= iIndexStart; i--)
    {
        srpCell.Release();
        if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpCell)))
            goto Fail;

        bstrText.Empty();
        if (FAILED(hr = GetDispatchProperty(srpCell, L"innerHTML", VT_BSTR, &bstrText)))
            goto Fail;
        bstrText += bstrMergedText;
        bstrMergedText = bstrText;

        if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &ccolSpan)))
            goto Fail;
        ccolSpanTotal += ccolSpan;

        if (i != iIndexStart)
        {
              if (FAILED(hr = DeleteCellEx((IHTMLElement*)srpTable, srpCurRow, iRow, i)))
                  goto Fail;
        }
        else
        {
            if (FAILED(hr = PutDispatchProperty(srpCell, L"colSpan", VT_I4, ccolSpanTotal)))
                goto Fail;
            if (FAILED(hr = PutDispatchProperty(srpCell, L"innerHTML", VT_BSTR, bstrMergedText)))
                goto Fail;
        }
    }
Fail:
    return hr;
}


///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::MergeTableCells
//
// Merge the cells in the Trident selection in to a single cell, and adjust that
// cell's colSpan. The cells must be within a single table row. The innerHTML of
// all merged cells is concatenated and placed in the remaining cell. Return S_OK
// or a Trident error.
//

HRESULT CTriEditDocument::MergeTableCells(void)
{
    CComPtr<IDispatch>       srpCell, srpCells,srpElement,srpRows,srpRow;
    CComPtr<IHTMLElement>   srpTable;
    struct SELCELLINFO          selinfo[2]; // 0 is start cell, 1 is end cell
    LONG i=0, cCells=0;
    HRESULT      hr=0;
    CComBSTR    bstrText;
    CComBSTR    bstrMergedText;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();

    if (FAILED(hr = IsSelectionInTable((IDispatch**)&srpTable)))
        goto Fail;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo[0], &selinfo[1])))
        goto Fail;

    if (selinfo[0].cRowIndex == selinfo[1].cRowIndex)
    {
        if (selinfo[1].cCellIndex == selinfo[0].cCellIndex)
            {
                hr = S_OK;
                goto Fail;
            }

        if (FAILED(hr = MergeTableCells(srpTable, selinfo[0].cRowIndex, selinfo[0].cCellIndex, selinfo[1].cCellIndex)))
            goto Fail;
    }
    else
    {
        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(selinfo[1].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        if (FAILED(hr = MergeTableCells(srpTable, selinfo[1].cRowIndex, 0, selinfo[1].cCellIndex)))
            goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
            goto Fail;

        for (i = selinfo[1].cRowIndex-1; i > selinfo[0].cRowIndex; i--)
        {
            srpElement.Release();
            if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpElement)))
                goto Fail;

            srpCells.Release();
            if (FAILED(hr = GetDispatchProperty(srpElement, L"cells", VT_DISPATCH, (void**)&srpCells)))
                goto Fail;

            if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
                goto Fail;

            if (FAILED(hr = MergeTableCells(srpTable, i, 0, cCells-1)))
                goto Fail;
        }

        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(selinfo[0].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
            goto Fail;

        if (FAILED(hr = MergeTableCells(srpTable, selinfo[0].cRowIndex, selinfo[0].cCellIndex, cCells-1)))
            goto Fail;

        bstrMergedText.Empty();
        for (i = selinfo[0].cRowIndex; i <= selinfo[1].cRowIndex; i++)
        {
            srpRows.Release();
            if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
                goto Fail;

            srpRow.Release();
            if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, selinfo[0].cRowIndex, &srpRow)))
                goto Fail;

            srpCells.Release();
            if (FAILED(hr = GetDispatchProperty(srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
                goto Fail;

            srpCell.Release();
            if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, 0, &srpCell)))
                goto Fail;

            bstrText.Empty();
            if (FAILED(hr = GetDispatchProperty(srpCell, L"innerHTML", VT_BSTR, &bstrText)))
                goto Fail;
            bstrMergedText += L"<P>";
            bstrMergedText += bstrText;
            bstrMergedText += L"</P>";

            if (i != selinfo[1].cRowIndex)
            {
                if (FAILED(hr = DeleteRowEx(srpTable, selinfo[0].cRowIndex)))
                    goto Fail;
            }
        }
        if (FAILED(hr = PutDispatchProperty(srpCell, L"innerHTML", VT_BSTR, bstrMergedText)))
            goto Fail;
    }

Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::SplitTableCell 
//
// Split the indicated table cell in to two cells and adjust the colSpan
// of the relevant cells in the other rows as needed. The entire operation is
// a single undo unit. Return S_OK or a Trident error.
//

HRESULT CTriEditDocument::SplitTableCell(IDispatch *srpTable, INT iRow, INT index)
{
    CComPtr<IDispatch>       srpCellSplit, srpCells,srpCell,srpElement,srpRows,srpRow,srpCurRow,srpCellNew;
    INT cRows=0,i=0,j=0,ccolSpan=0,ccolSpanCur=0,crowSpan=0, cCells=0;
    HRESULT      hr=0;
    CComBSTR    bstrText;
    INT *pccolFix = NULL;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();
    
    if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
        goto Fail;

    if (FAILED(hr = GetDispatchProperty(srpRows, L"length", VT_I4, &cRows)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, iRow, &srpCurRow)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpCurRow, L"insertCell", VTS_I4, index+1)))
        goto Fail;

    srpCells.Release();
    if (FAILED(hr = GetDispatchProperty(srpCurRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
        goto Fail;

    srpCellNew.Release();
    if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, index+1, &srpCellNew)))
        goto Fail;
                
    srpCellSplit.Release();
    if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, index, (void**)&srpCellSplit)))
        goto Fail;

    ccolSpan=0;
    if (FAILED(hr = GetDispatchProperty(srpCellSplit, L"colSpan", VT_I4, &ccolSpan)))
        goto Fail;

    CopyStyle(srpCellSplit, srpCellNew);
    CopyProperty(srpCellSplit, srpCellNew);
    
    if (ccolSpan == 1)
    {
        INT ccolSpanStart = 0,ccolSpanEnd=0;
        INT ccolSpanTmp = 0, cRowSpan = 0;

        pccolFix = new INT[cRows];
        for (j=0; j < cRows; j++)
            *(pccolFix+j) = 0;

        for (j=0; j<index;j++)
        {
            srpCell.Release();
            if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, j, (void**)&srpCell)))
                goto Fail;

            ccolSpanTmp = 0;
            if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &ccolSpanTmp)))
                goto Fail;
            ccolSpanStart += ccolSpanTmp;

            if (FAILED(hr = GetDispatchProperty(srpCell, L"rowSpan", VT_I4, &cRowSpan)))
                goto Fail;

            if (cRowSpan > 1)
                for (i = index+1; i < index+cRowSpan; i++)
                    *(pccolFix+i) += ccolSpanTmp;
        }

        ccolSpanEnd = ccolSpanStart + ccolSpan;

        for (j=0; j < cRows; j++)
        {
            if (j == iRow)
                continue;

            srpRow.Release();
            if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, j, &srpRow)))
                goto Fail;

            srpCells.Release();
            if (FAILED(hr = GetDispatchProperty(srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
                goto Fail;

            if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
                goto Fail;

            ccolSpanCur = *(pccolFix+j);
            for(i=0 ; i < cCells; i++)
            {
                srpCell.Release();
                if (FAILED(hr = CallDispatchMethod(srpCells, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpCell)))
                    goto Fail;

                ccolSpan=0;
                if (FAILED(hr = GetDispatchProperty(srpCell, L"colSpan", VT_I4, &ccolSpan)))
                    goto Fail;

                if (ccolSpanStart <= ccolSpanCur && ccolSpanCur < ccolSpanEnd)
                {
                    if (FAILED(hr = PutDispatchProperty(srpCell, L"colSpan", VT_I4, ccolSpan+1)))
                        goto Fail;
                }

                if (ccolSpanCur >= ccolSpanEnd)
                    break;

                ccolSpanCur += ccolSpan;
            }
         }
     }
     else
     {
         if (FAILED(hr = PutDispatchProperty(srpCellNew, L"colSpan", VT_I4, ccolSpan/2)))
             goto Fail;

         if (FAILED(hr = PutDispatchProperty(srpCellSplit, L"colSpan", VT_I4, ccolSpan-ccolSpan/2)))
             goto Fail;
     }

    
      // now copy row span
     if (FAILED(hr = GetDispatchProperty(srpCellSplit, L"rowSpan", VT_I4, &crowSpan)))
         goto Fail;

     if (FAILED(hr = PutDispatchProperty(srpCellNew, L"rowSpan", VT_I4, crowSpan)))
         goto Fail;

Fail:
     if (pccolFix != NULL)
     {
         delete [] pccolFix;
      }

    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::SplitTableCell 
//
// Split the table cell in the Trident selection in to two cells and adjust the
// colSpan of the relevant cells in the other rows as needed. The entire operation
// is a single undo unit. Return S_OK or a Trident error.
//

HRESULT CTriEditDocument::SplitTableCell(void)
{
    CComPtr<IDispatch>       srpCell, srpTable,srpCells,srpElement,srpRows,srpRow;
    struct SELCELLINFO          selinfo[2]; // 0 is start cell, 1 is end cell
    LONG i=0, j=0,cCells=0;
    HRESULT      hr=0;
    CUndoPackManager undoPackMgr(m_pUnkTrident);

    undoPackMgr.Start();

    if (FAILED(hr = IsSelectionInTable(&srpTable)))
        goto Fail;

    if (FAILED(hr = FillInSelectionCellsInfo(&selinfo[0], &selinfo[1])))
        goto Fail;

    if (FAILED(hr = GetDispatchProperty(srpTable, L"rows", VT_DISPATCH, (void**)&srpRows)))
        goto Fail;

    if (selinfo[0].cRowIndex == selinfo[1].cRowIndex)
    {
        for (i = selinfo[1].cCellIndex; i >= selinfo[0].cCellIndex; i--)
        {
           if (FAILED(hr = SplitTableCell(srpTable, selinfo[0].cRowIndex, i)))
               goto Fail;
        }
    }
    else
    {
        for (i = selinfo[1].cCellIndex; i >= 0; i--)
        {
            if (FAILED(hr = SplitTableCell(srpTable, selinfo[1].cRowIndex, i)))
               goto Fail;
        }

        for (i = selinfo[1].cRowIndex-1; i > selinfo[0].cRowIndex; i--)
        {
            srpElement.Release();
            if (FAILED(hr = CallDispatchMethod(srpRows, L"Item", VTS_I4 VTS_DISPATCH_RETURN, i, &srpElement)))
                goto Fail;

            srpCells.Release();
            if (FAILED(hr = GetDispatchProperty(srpElement, L"cells", VT_DISPATCH, (void**)&srpCells)))
                goto Fail;

            if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
                goto Fail;

            for (j = cCells-1; j >= 0; j--)
            {
                if (FAILED(hr = SplitTableCell(srpTable, i, j)))
                    goto Fail;
            }
        }

        srpCells.Release();
        if (FAILED(hr = GetDispatchProperty(selinfo[0].srpRow, L"cells", VT_DISPATCH, (void**)&srpCells)))
            goto Fail;

        if (FAILED(hr = GetDispatchProperty(srpCells, L"length", VT_I4, &cCells)))
            goto Fail;

        for (i = cCells-1; i >= selinfo[0].cCellIndex; i--)
        {
            if (FAILED(hr = SplitTableCell(srpTable, selinfo[0].cRowIndex, i)))
               goto Fail;
        }
    }

Fail:
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::InsertTable
//
// Insert a table in to the document at the selection point. All parameters
// are optional and taken from members of pvarargIn as follows:
//
//      pvarargIn[0]    I4   - Number of rows; default 0.
//      pvarargIn[1]    I4   - Number of columns; default 0.
//      pvarargIn[2]    BSTR - Table tag attributes; default "".
//      pvarargIn[3]    BSTR - Table cell attributes; default "".
//      pvarargIn[4]    BSTR - Table caption; default "".
//
// pvarArgIn must be sipplied even if the default values are to be used for
// all parameters. The entire operation is a single undo unit. The wait cursor
// is displayed since this can be a fairly time-consuming operation. Returns S_OK
// or a Trident error.
//

HRESULT CTriEditDocument::InsertTable(VARIANTARG *pvarargIn)
{
    HRESULT    hr=0;
    CComPtr<IHTMLSelectionObject>    srpSel;
    CComPtr<IDispatch> srpRange;
    CComPtr<IDispatch>    srpCell;
    CComPtr<IHTMLDocument2>    srpiHTMLDoc;
    CComBSTR    bstrHtml;
    CComBSTR    bstrTblAttr;
    CComBSTR    bstrTCellAttr;
    CComBSTR    bstrCaption;
    int i=0, j=0, iRow=0, iCol=0;
    VARIANT rgvar[5];
    HCURSOR hOldCursor;

    if (pvarargIn == NULL)
        return E_FAIL;

    hOldCursor = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));

    for(i = 0; i < sizeof(rgvar)/sizeof(VARIANT); i++)
        VariantInit(&rgvar[i]);

    // default
    iRow=1;
    iCol=1;
    bstrTCellAttr.Empty();
    bstrTblAttr.Empty();

    if (pvarargIn != NULL)
    {
        LONG lLBound=0, lUBound=0,lIndex=0;
        SAFEARRAY *psa;
        LONG cParam; // number of parameters host passes in

        psa = V_ARRAY(pvarargIn);
        SafeArrayGetLBound(psa, 1, &lLBound);
        SafeArrayGetUBound(psa, 1, &lUBound);
        cParam = 0;
        _ASSERTE(lLBound == 0);
        _ASSERTE(lUBound -  lLBound < 5);
        for (lIndex = lLBound; lIndex <= lUBound && cParam < sizeof(rgvar)/sizeof(VARIANT); lIndex++)
        {
             SafeArrayGetElement(psa, &lIndex, &rgvar[cParam++]);
        }

        // first element: number of rows
        if (cParam >= 1)
            iRow = V_I4(&rgvar[0]);
        // 2'rd element: number of columns
        if (cParam >= 2)
            iCol = V_I4(&rgvar[1]);
        // 3'rd element: table tag attributes
        if (cParam >= 3)
            bstrTblAttr = V_BSTR(&rgvar[2]);
        // 4'th element: table cell tag attributes
        if (cParam >= 4)
            bstrTCellAttr = V_BSTR(&rgvar[3]);
        if (cParam >= 5)
            bstrCaption = V_BSTR(&rgvar[4]);
    }

    if (iRow < 0 || iCol < 0)
        goto Fail;

    bstrHtml.Empty();
    bstrHtml += "<TABLE ";
    if (bstrTblAttr != NULL)
        bstrHtml += bstrTblAttr;
    bstrHtml += ">";

    if (bstrCaption != NULL)
    {
         bstrHtml += "<CAPTION>";
         bstrHtml += bstrCaption;
         bstrHtml += "</CAPTION>";
    }

    bstrHtml +="<TBODY>";

    for (i=0; i<iRow; i++)
    {
        bstrHtml += "<TR>";
        for (j=0; j<iCol; j++)
        {
            bstrHtml += "<TD ";
            if (bstrTCellAttr != NULL)
                bstrHtml += bstrTCellAttr;
            bstrHtml +="></TD>";
        }
        bstrHtml += "</TR>";
    }
    bstrHtml += "</TBODY></TABLE>";

    if (FAILED(hr = m_pUnkTrident->QueryInterface(IID_IHTMLDocument2, (void**)&srpiHTMLDoc)))
        goto Fail;

    if (FAILED(hr = srpiHTMLDoc->get_selection(&srpSel)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpSel, L"createRange", VTS_DISPATCH_RETURN, (void**)&srpRange)))
        goto Fail;

    if (FAILED(hr = CallDispatchMethod(srpRange, L"pasteHTML", VTS_BSTR, bstrHtml)))
        goto Fail;

Fail:

    for(i = 0; i < sizeof(rgvar)/sizeof(VARIANT); i++)
        VariantClear(&rgvar[i]);

    ::SetCursor(hOldCursor);
    return hr;

}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::DoVerb
//
// Execute the verb in pvarargIn (or OLEIVERB_PRIMARY if pvarargIn is NULL)
// on the current object (which must QI for IHTMLObjectElement). Return E_FAIL
// or the code returned as a result of executing the verb,
//

HRESULT CTriEditDocument::DoVerb(VARIANTARG *pvarargIn, BOOL fQueryStatus)
{
    LONG iVerb;
    IHTMLObjectElement *piHTMLObjectElement = NULL;
    IDispatch *pDisp = NULL;
    IOleObject *pOleObj = NULL;
    HRESULT hr = E_FAIL;

    _ASSERTE(m_pihtmlElement != NULL);

    if (SUCCEEDED(m_pihtmlElement->QueryInterface(IID_IHTMLObjectElement, (void **)&piHTMLObjectElement)) && piHTMLObjectElement)
    {
        if (SUCCEEDED(piHTMLObjectElement->get_object(&pDisp)) && pDisp)
        {
            if (SUCCEEDED(pDisp->QueryInterface(IID_IOleObject, (void **)&pOleObj)) && pOleObj)
            {
                if (fQueryStatus) // In the query status case, we're done
                    hr = S_OK;
                else
                {
                    if (pvarargIn == NULL)
                        iVerb = OLEIVERB_PRIMARY;
                    else if (pvarargIn->vt == VT_I4)
                        iVerb = V_I4(pvarargIn);    
                    else
                    {
                        hr = E_INVALIDARG;
                        goto LSkipDoVerb;
                    }

                    GetTridentWindow();
                    _ASSERTE(m_hwndTrident != NULL);

                    hr = pOleObj->DoVerb(iVerb, NULL, NULL, 0, m_hwndTrident, NULL);
                }
LSkipDoVerb:
                pOleObj->Release();
            }
            pDisp->Release();
        }
        piHTMLObjectElement->Release();
    }

    return hr;

}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::GetDocument
//
// Return the IHTMLDocument pointer (under *ppihtmlDocument) and S_OK, or
// E_FAIL/E_POINTER.
//

STDMETHODIMP CTriEditDocument::GetDocument(IHTMLDocument2** ppihtmlDocument)
{
    _ASSERTE(ppihtmlDocument);
    if (ppihtmlDocument)
    {
        if (m_pUnkTrident)
        {
            return m_pUnkTrident->QueryInterface(IID_IHTMLDocument2,
                        (LPVOID*)ppihtmlDocument);
        }
        return E_FAIL;
    }
    return E_POINTER;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::GetAllColllection
//
// Return the all collection of the HTML document (under *ppihtmlCollection),
// or E_FAIL.
//

STDMETHODIMP CTriEditDocument::GetAllCollection(IHTMLElementCollection** ppihtmlCollection)
{
    IHTMLDocument2* pihtmlDoc2;
    HRESULT hr=E_FAIL;

    _ASSERTE(ppihtmlCollection);
    if (ppihtmlCollection && SUCCEEDED(GetDocument(&pihtmlDoc2)))
    {
        _ASSERTE(pihtmlDoc2);
        hr = pihtmlDoc2->get_all(ppihtmlCollection);
        pihtmlDoc2->Release();
    }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::GetCollectionElement
//
// Return the indicated element from the given collection under *ppihtmlElement.
// Return S_OK if all goes well,or E_FAIL or a Triedent error on error.
//

STDMETHODIMP CTriEditDocument::GetCollectionElement(
    IHTMLElementCollection* pihtmlCollection,
    LONG iElem, IHTMLElement** ppihtmlElement)
{
    VARIANT var;
    VARIANT varEmpty;
    IDispatch* pidispElement=NULL;
    HRESULT hr = E_FAIL;

    _ASSERTE(pihtmlCollection && iElem >= 0 && ppihtmlElement);
    if (!pihtmlCollection || iElem < 0 || !ppihtmlElement)
        return E_POINTER;

    *ppihtmlElement = NULL;     //initialize [out] parameter

    VariantInit(&var);
    var.vt = VT_I4;
    var.lVal = iElem;

    VariantInit(&varEmpty);
    varEmpty.vt = VT_EMPTY;

    hr = pihtmlCollection->item(var, varEmpty, &pidispElement);
    if (SUCCEEDED(hr))
    {
        if (pidispElement)
        {
            hr = pidispElement->QueryInterface(IID_IHTMLElement, (LPVOID*)ppihtmlElement);
            pidispElement->Release();
        }
        else
        {
            hr = E_FAIL;
        }
    }
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
//
// CTriEditDocument::Is2DCapable
//
// Return (under *pfBool) TRUE if the given HTML element can be positioned
// out of the flow as a 2D element, or FALSE if not. Return S_OK in either
// case. Return E_FAIL or a Trident error if something goes wrong.
// 

STDMETHODIMP CTriEditDocument::Is2DCapable(IHTMLElement* pihtmlElement, BOOL* pfBool)
{
    HRESULT hr= E_FAIL;
    CComBSTR bstrTag;

    _ASSERTE(pihtmlElement);

    if (!pihtmlElement || !pfBool)
        return E_POINTER;

    *pfBool = FALSE;

     bstrTag.Empty();
     if (FAILED(hr = GetDispatchProperty(pihtmlElement, L"tagName", VT_BSTR, &bstrTag)))
            return E_FAIL;

     if (lstrcmpi(_T("APPLET"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("BUTTON"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("DIV"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("EMBED"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("FIELDSET"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("HR"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("IFRAME"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("IMG"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("INPUT"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("MARQUEE"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("OBJECT"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("SELECT"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("SPAN"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("TABLE"), OLE2T(bstrTag)) == 0 ||
         lstrcmpi(_T("TEXTAREA"), OLE2T(bstrTag)) == 0 )
    {
        *pfBool = TRUE;
        return S_OK;
    }

    return S_OK;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::SelectElement
//
// Select the given element within Trident as a site selection. Return S_OK or
// a Trident error.
//

STDMETHODIMP CTriEditDocument::SelectElement(IHTMLElement* pihtmlElement, IHTMLElement* pihtmlElementParent)
{
    IHTMLControlElement* picont=NULL;
    IHTMLElement* piParent=NULL;
    IDispatch* pidisp=NULL;
    IHTMLTextContainer* pitext=NULL;
    IHTMLControlRange* pirange=NULL;
    HRESULT hr;
    CComBSTR bstrTag;

    if ( !pihtmlElement || !pihtmlElementParent )
        return E_FAIL;
    
    hr = pihtmlElement->QueryInterface(IID_IHTMLControlElement, (LPVOID*)&picont);

    if ( FAILED(hr) )
        goto CleanUp;

    _ASSERTE(picont);

    hr = pihtmlElementParent->QueryInterface(IID_IHTMLTextContainer, (LPVOID*)&pitext);

    if ( FAILED(hr) )
        goto CleanUp;

    _ASSERTE(pitext);

    hr = pitext->createControlRange(&pidisp);

    if ( FAILED(hr) )
        goto CleanUp;

    _ASSERTE(pitext);

    hr = pidisp->QueryInterface(IID_IHTMLControlRange, (LPVOID*)&pirange);

    if ( FAILED(hr) )
        goto CleanUp;

    _ASSERTE(pirange);

    hr = pirange->add(picont);

    if ( FAILED(hr) )
        goto CleanUp;

    hr = pirange->select();

CleanUp:
    SAFERELEASE(picont);
    SAFERELEASE(piParent);
    SAFERELEASE(pidisp);
    SAFERELEASE(pitext);
    SAFERELEASE(pirange);
    return hr;
}

///////////////////////////////////////////////////////////////////////////////
//
// CTriEditDocument::IsElementDTC
//
// Return S_OK if the given element is a DTC (Design-Time Control) or E_FAIL 
// if not.
//

HRESULT CTriEditDocument::IsElementDTC(IHTMLElement *pihtmlElement)
{
    IHTMLObjectElement *piHTMLObjectElement = NULL;
    IDispatch *pDisp = NULL;
    IActiveDesigner *piActiveDesigner = NULL;
    IUnknown *piUnk = NULL;

    if (SUCCEEDED(pihtmlElement->QueryInterface(IID_IHTMLObjectElement, (void **)&piHTMLObjectElement)) && piHTMLObjectElement)
    {
        if (SUCCEEDED(piHTMLObjectElement->get_object(&pDisp)) && pDisp)
        {
            if (SUCCEEDED(pDisp->QueryInterface(IID_IUnknown, (void **)&piUnk)) && piUnk)
            {
                if (SUCCEEDED(piUnk->QueryInterface(IID_IActiveDesigner, (void **)&piActiveDesigner)) && piActiveDesigner)
                {
                    piHTMLObjectElement->Release();
                    pDisp->Release();
                    piUnk->Release();
                    piActiveDesigner->Release();
                    return S_OK;
                }
                piUnk->Release();
            }
            pDisp->Release();
        }
        piHTMLObjectElement->Release();
    }

    return E_FAIL;
}