//------------------------------------------------------------------------------ // undo.cpp // Copyright (c)1997-1999 Microsoft Corporation, All Rights Reserved // // Undo support routines for TriEdit // //------------------------------------------------------------------------------ #include "stdafx.h" #include #include "undo.h" #include "triedit.h" #include "document.h" /////////////////////////////////////////////////////////////////////////////// // // AddUndoUnit // // Add the given undo unit to the given Trident instance. Return S_OK // or a Trident error code. // HRESULT AddUndoUnit(IUnknown* punkTrident, IOleUndoUnit* pioleUndoUnit) { HRESULT hr = E_FAIL; IServiceProvider* piservProv; IOleUndoManager* pioleUndoManager; if (punkTrident && pioleUndoUnit) { hr = punkTrident->QueryInterface(IID_IServiceProvider, (LPVOID*)&piservProv); if (SUCCEEDED(hr)) { _ASSERTE(piservProv); hr = piservProv->QueryService(IID_IOleUndoManager, IID_IOleUndoManager, (LPVOID*)&pioleUndoManager); if (SUCCEEDED(hr)) { _ASSERTE(pioleUndoManager); hr = pioleUndoManager->Add(pioleUndoUnit); _ASSERTE(SUCCEEDED(hr)); pioleUndoManager->Release(); } piservProv->Release(); } } return hr; } /////////////////////////////////////////////////////////////////////////////// // // EmptyUndoRedoStack // // If fUndo is TRUE, discard all undo items from the given undo manager. // If fUndo is FALSE, discard all redo items from the given undo manager // Return S_OK if all goes well, or a Trident error code otherwise. // HRESULT EmptyUndoRedoStack(BOOL fUndo, IOleUndoManager *pUndoManager) { CComPtr srpEnum; CComPtr srpcd; ULONG cFetched=0, cTotal=0; HRESULT hr = E_FAIL; if (fUndo) { if (FAILED(hr = pUndoManager->EnumUndoable(&srpEnum))) goto Fail; } else { if (FAILED(hr = pUndoManager->EnumRedoable(&srpEnum))) goto Fail; } while (SUCCEEDED(srpEnum->Next(1, &srpcd, &cFetched))) { _ASSERTE(cFetched <=1); if (srpcd == NULL) break; cTotal++; srpcd.Release(); } // get the one on top of the stack and discard from that if (cTotal > 0) { if (FAILED(hr = srpEnum->Reset())) goto Fail; if (FAILED(hr = srpEnum->Skip(cTotal-1))) goto Fail; srpcd.Release(); if (FAILED(hr = srpEnum->Next(1, &srpcd, &cFetched))) goto Fail; _ASSERTE(cFetched ==1); if (FAILED(hr = pUndoManager->DiscardFrom(srpcd))) goto Fail; } Fail: return hr; } /////////////////////////////////////////////////////////////////////////////// // // GetUndoManager // // Obtain and return (under *ppOleUndoManager) the IOleUndoManager // associated with the given Trident instance. Return S_OK if a // manager was returned; E_FAIL otherwise. // HRESULT GetUndoManager(IUnknown* punkTrident, IOleUndoManager **ppOleUndoManager) { HRESULT hr = E_FAIL; CComPtr srpiservProv; CComPtr srpioleUndoManager; _ASSERTE(ppOleUndoManager != NULL); _ASSERTE(punkTrident != NULL); if (punkTrident) { hr = punkTrident->QueryInterface(IID_IServiceProvider, (LPVOID*)&srpiservProv); if (SUCCEEDED(hr)) { _ASSERTE(srpiservProv); if (SUCCEEDED(hr = srpiservProv->QueryService(IID_IOleUndoManager, IID_IOleUndoManager, (LPVOID*)&srpioleUndoManager))) { *ppOleUndoManager = srpioleUndoManager; (*ppOleUndoManager)->AddRef(); } } } return hr; } /////////////////////////////////////////////////////////////////////////////// // // CUndo::CUndo // CUndo::~Undo // // Simple constructor and destructor for the CUndo class. // CUndo::CUndo() { m_cRef = 1; m_fUndo = TRUE; } CUndo::~CUndo() { } /////////////////////////////////////////////////////////////////////////////// // // CUndo::QueryInterface (IUnknown method) // CUndo::AddRef (IUnknown method) // CUndo::Release (IUnknown method) // // Implementations of the three IUnknown methods. // STDMETHODIMP CUndo::QueryInterface(REFIID riid, LPVOID* ppvObject) { if (!ppvObject) return E_POINTER; if (IsEqualGUID(riid, IID_IUnknown)) *ppvObject = (IUnknown*)this; else if (IsEqualGUID(riid, IID_IOleUndoUnit)) *ppvObject = (IOleUndoUnit*)this; else return E_NOINTERFACE; AddRef(); return S_OK; } STDMETHODIMP_(ULONG) CUndo::AddRef(void) { return InterlockedIncrement((LONG*)&m_cRef); } STDMETHODIMP_(ULONG) CUndo::Release(void) { ULONG cRef = InterlockedDecrement((LONG*)&m_cRef); if (!cRef) delete this; return cRef; } /////////////////////////////////////////////////////////////////////////////// // // CUndoDrag::CUndoDrag // CUndoDrag::~CUndoDrag // // Constructor for an object which can undo the drag of an HTML element. // CUndoDrag::CUndoDrag(IHTMLStyle* pihtmlStyle, POINT ptOrig, POINT ptMove) { m_pihtmlStyle = pihtmlStyle; if (m_pihtmlStyle) m_pihtmlStyle->AddRef(); m_ptOrig = ptOrig; m_ptMove = ptMove; } CUndoDrag::~CUndoDrag() { SAFERELEASE(m_pihtmlStyle); } /////////////////////////////////////////////////////////////////////////////// // // CUndoDrag::Do (IOleUndoUnit method) // // Do or undo dragging of an HTML element from place to place. Set or // restore the item's position. Return S_OK. // STDMETHODIMP CUndoDrag::Do(IOleUndoManager *pUndoManager) { HRESULT hr = S_OK; if (pUndoManager) { hr = pUndoManager->Add(this); } if (m_pihtmlStyle) { // We do a put_pixelLeft(-1) and put_pixelTop(-1) below in order // to work around a Trident problem. Sometimes they don't think // that anything has changed - these calls below fool them into // thinking that the values have changed. if (m_fUndo) { m_pihtmlStyle->put_pixelLeft(-1); m_pihtmlStyle->put_pixelLeft(m_ptOrig.x); m_pihtmlStyle->put_pixelTop(-1); m_pihtmlStyle->put_pixelTop(m_ptOrig.y); } else { m_pihtmlStyle->put_pixelLeft(-1); m_pihtmlStyle->put_pixelLeft(m_ptMove.x); m_pihtmlStyle->put_pixelTop(-1); m_pihtmlStyle->put_pixelTop(m_ptMove.y); } m_fUndo = !m_fUndo; } return hr; } /////////////////////////////////////////////////////////////////////////////// // // CUndoDrag::GetDescription (IOleUndoUnit method) // // Return the description of the undo item. Note that this function // returns an empty string since this is the only would-be localizable // content in TriEdit. // STDMETHODIMP CUndoDrag::GetDescription(BSTR *pBstr) { if (pBstr) { *pBstr = SysAllocString(_T(" ")); return S_OK; } return E_FAIL; } /////////////////////////////////////////////////////////////////////////////// // // CUndoDrag::GetUnitType (IOleUndoUnit method) // // Return the CLSID and an identifier for the undo item. // STDMETHODIMP CUndoDrag::GetUnitType(CLSID *pClsid, LONG *plID) { if (pClsid) *pClsid = UID_TRIEDIT_UNDO; if (plID) *plID = TRIEDIT_UNDO_DRAG; return S_OK; } /////////////////////////////////////////////////////////////////////////////// // // CUndoDrag::OnNextAdd (IOleUndoUnit method) // // Do nothing, but do it extremely well. // STDMETHODIMP CUndoDrag::OnNextAdd(void) { return S_OK; } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackManager::~CUndoPackManager // // Destructor for a CUndoPackManager object. If currently packing undo // items, end the packing before destroying the object. // CUndoPackManager::~CUndoPackManager(void) { if (m_fPacking) End(); } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackManager::Start // // Called to tell the pack manager to begin accumulating subsequent // undo units into a unit that can be undone in one fell swoop. Turn // on the packing flag. Return S_OK if all goes well or E_FAIL if // something goes wrong. // HRESULT CUndoPackManager::Start(void) { HRESULT hr = E_FAIL; CComPtr srpioleUndoManager; CComPtr srpEnum; CComPtr srpcd; ULONG cFetched=0; _ASSERTE(m_indexStartPacking==0); if (FAILED(hr = GetUndoManager(m_srpUnkTrident, &srpioleUndoManager))) goto Fail; if (FAILED(hr = srpioleUndoManager->EnumUndoable(&srpEnum))) goto Fail; while(SUCCEEDED(srpEnum->Next(1, &srpcd, &cFetched))) { _ASSERTE(cFetched <=1); if (srpcd == NULL) break; m_indexStartPacking++; srpcd.Release(); } m_fPacking = TRUE; Fail: if (!m_fPacking) m_indexStartPacking=0; return hr; } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackManager::End // // Called to tell the pack manager to stop accumulating undo units. Pack // the accumulated undo units in to the parent undo unit and turn off // the packing flag. Return S_OK if all goes well or E_FAIL if something // goes wrong. // HRESULT CUndoPackManager::End(void) { HRESULT hr = E_FAIL; CUndoPackUnit *pUndoParentUnit; _ASSERTE(m_srpUnkTrident != NULL); pUndoParentUnit = new CUndoPackUnit(); _ASSERTE(pUndoParentUnit != NULL); if (FAILED(hr = pUndoParentUnit->PackUndo(m_indexStartPacking, m_srpUnkTrident))) goto Fail; m_fPacking = FALSE; Fail: pUndoParentUnit->Release(); return hr; } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackUnit::Do (IOleUndoUnit method) // // Invoke the Do method of each undo unit referenced by the object. Return // S_OK. // STDMETHODIMP CUndoPackUnit::Do(IOleUndoManager *pUndoManager) { HRESULT hr = S_OK; for (INT i=sizeof(m_rgUndo)/sizeof(IOleUndoUnit*)-1; i >= 0; i--) { if (m_rgUndo[i] == NULL) continue; if (FAILED(hr = m_rgUndo[i]->Do(pUndoManager))) goto Fail; } ::EmptyUndoRedoStack(FALSE, pUndoManager); Fail: return hr; } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackUnit::GetDescription (IOleUndoUnit method) // // Return the description of the undo item. Note that this function // returns an empty string since this string would be one of only // two localizable strings in TriEdit. // STDMETHODIMP CUndoPackUnit::GetDescription(BSTR *pBstr) { if (pBstr) { // In order to save localization work for the two TriEdit strings, // it was decided that we would return a blank string here *pBstr = SysAllocString(_T(" ")); return S_OK; } return E_FAIL; } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackUnit::GetUnitType (IOleUndoUnit method) // // Return the CLSID and an identifier for the undo item. // STDMETHODIMP CUndoPackUnit::GetUnitType(CLSID *pClsid, LONG *plID) { if (pClsid) *pClsid = UID_TRIEDIT_UNDO; if (plID) *plID = TRIEDIT_UNDO_PACK; return S_OK; } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackUnit::OnNextAdd (IOleUndoUnit method) // // Do nothing, but do it extremely well. // STDMETHODIMP CUndoPackUnit::OnNextAdd(void) { return S_OK; } /////////////////////////////////////////////////////////////////////////////// // // CUndoPackUnit::PackUndo // // Pack all of the undo units starting at the given index in to // the parent undo manager. Return S_OK if all goes well, or // E_FAIL if something goes wrong. // HRESULT CUndoPackUnit::PackUndo(ULONG indexStartPacking, IUnknown *pUnkTrident) { HRESULT hr = E_FAIL; CComPtr srpioleUndoManager; CComPtr srpEnumUndo; CComPtr rgUndo[cUndoPackMax]; // CONSIDER: allocate dynamically CComPtr srpcd; ULONG cFetched=0, cUndo=0, i=0; if (FAILED(hr = GetUndoManager(pUnkTrident, &srpioleUndoManager))) goto Fail; if (FAILED(hr = srpioleUndoManager->EnumUndoable(&srpEnumUndo))) goto Fail; _ASSERTE(srpEnumUndo != NULL); while(SUCCEEDED(srpEnumUndo->Next(1, &srpcd, &cFetched))) { _ASSERTE(cFetched <= 1); if (srpcd == NULL) break; cUndo++; srpcd.Release(); } // if there's nothing to pack if ((cUndo-indexStartPacking) == 0) return S_OK; if ((cUndo-indexStartPacking) > cUndoPackMax) return E_OUTOFMEMORY; // get the undo units that we want to pack if (FAILED(hr = srpEnumUndo->Reset())) goto Fail; if (FAILED(hr =srpEnumUndo->Skip(indexStartPacking))) goto Fail; if (FAILED(hr = srpEnumUndo->Next(cUndo-indexStartPacking, (IOleUndoUnit **) &m_rgUndo, &cFetched))) goto Fail; _ASSERTE(cFetched == (cUndo-indexStartPacking)); // now clear the undo/redo stack and then adds back the undo unit except that one that we just packed if (FAILED(hr = srpEnumUndo->Reset())) goto Fail; if (FAILED(hr = srpEnumUndo->Next(cUndo, (IOleUndoUnit **) &rgUndo, &cFetched))) goto Fail; _ASSERTE(cFetched == cUndo); if (FAILED(hr = srpioleUndoManager->DiscardFrom(NULL))) goto Fail; for (i=0; i < indexStartPacking; i++) { if (FAILED(hr = srpioleUndoManager->Add(rgUndo[i]))) goto Fail; } if (FAILED(hr = ::AddUndoUnit(pUnkTrident, this))) goto Fail; Fail: return hr; }