|
|
/*
* @doc INTERNAL * * @module objmgr.cpp. Object manager implementation | manages a * collection of OLE embedded objects * * Author: alexgo 11/5/95 * * Copyright (c) 1995-1997, Microsoft Corporation. All rights reserved. */
#include "_common.h"
#include "_objmgr.h"
#include "_edit.h"
#include "_disp.h"
#include "_select.h"
ASSERTDATA
//
// PUBLIC methods
//
/*
* CObjectMgr::GetObjectCount * * @mfunc returns the number of embedded objects currently in * the document. * * @rdesc LONG, the count */ LONG CObjectMgr::GetObjectCount() { TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::GetObjectCount");
return _objarray.Count(); }
/*
* CObjectMgr::GetLinkCount() * * @mfunc returns the number of embedded objects which are links * * @rdesc LONG, the count */ LONG CObjectMgr::GetLinkCount() { LONG count = 0; COleObject *pobj; LONG i;
TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::GetLinkCount"); for(i = 0; i < _objarray.Count(); i++) { pobj = *_objarray.Elem(i); if(pobj && pobj->IsLink()) count++; } return count; }
/*
* CObjectMgr::GetObjectFromCp() * * @mfunc fetches an object corresponding to the given cp * * @rdesc the object @ a cp; NULL if nothing found * * @comm the algorithm is a modified binary search. Since the * "typical" access pattern will be to linearly access the * objects, we used the cached index to guess first. If * that doesn't work, we resort to a binary search. */ COleObject *CObjectMgr::GetObjectFromCp( LONG cp) //@parm the cp for the object
{ COleObject *pobj = NULL; LONG i = 0; // No tracing on this method as it's too noisy.
if(_objarray.Count() > 0) { if(_lastindex < _objarray.Count()) { pobj = *_objarray.Elem(_lastindex); if(pobj && pobj->GetCp() == cp) return pobj; } // The quick lookup failed; try a binary search.
i = FindIndexForCp(cp);
// Because of the insert at end case, i may be equal
// to the count of objects().
pobj = NULL; if(i < _objarray.Count()) pobj = *_objarray.Elem(i); }
// FindIndex will return a matching or _near_ index.
// In this case, we only want a matching index
if(pobj) { if(pobj->GetCp() != cp) pobj = NULL; else { // Set the cached index to be the next one,
// so that somebody walking through objects in
// cp order will always get immediate hits.
_lastindex = i + 1; } } #ifdef DEBUG
// Make sure the binary search found the right thing
for( i = 0 ; i < _objarray.Count(); i++ ) { COleObject *pobj2 = *_objarray.Elem(i); if( pobj2 ) { if(*_objarray.Elem(i) == pobj) { Assert((*_objarray.Elem(i))->GetCp() == cp); } else Assert((*_objarray.Elem(i))->GetCp() != cp); } } #endif //DEBUG
return pobj; }
/*
* CObjectMgr::CountObjects (cObjects, cp) * * @mfunc Count char counts upto <p cObjects> objects away The direction of * counting is determined by the sign of <p cObjects>. * * @rdesc Return the signed cch counted and set <p cObjects> to count of * objects actually counted. If <p cobject> <gt> 0 and cp is at * the last object, no change is made and 0 is returned. * * @devnote This is called from TOM, which uses LONGs for cp's (because VB * can't use unsigned quantities) */ LONG CObjectMgr::CountObjects ( LONG& cObjects, //@parm Count of objects to get cch for
LONG cp) //@parm cp to start counting from
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::CountObjects");
LONG iStart, iEnd; LONG iMaxEnd = (LONG)_objarray.Count() - 1;
if(!cObjects || !_objarray.Count()) { cObjects = 0; return 0; }
iStart = (LONG)FindIndexForCp(cp);
// if we are looking past either end, return 0
if (iStart > iMaxEnd && cObjects > 0 || iStart == 0 && cObjects < 0 ) { cObjects = 0; return 0; }
// If the index that we found is on an object and
// we are looking forward, it should be skipped.
if( iStart < (LONG)_objarray.Count() && (LONG)(*_objarray.Elem(iStart))->GetCp() == cp && cObjects > 0) { iStart++; }
if(cObjects < 0) { if(-cObjects > iStart) // Going past the beginning
{ iEnd = 0; cObjects = -iStart; } else iEnd = iStart + cObjects; } else { if(cObjects > iMaxEnd - iStart) //Going past the end
{ iEnd = iMaxEnd; cObjects = iMaxEnd - iStart + 1; } else iEnd = iStart + cObjects - 1; }
Assert(iEnd >= 0 && iEnd < (LONG)_objarray.Count() );
return (*_objarray.Elem(iEnd))->GetCp() - cp; }
/*
* CObjectMgr::CountObjectsInRange (cpMin, cpMost) * * @mfunc Count the number of objects in the given range. * * @rdesc Return the number of objects. */ LONG CObjectMgr::CountObjectsInRange ( LONG cpMin, //@parm Beginning of range
LONG cpMost) //@parm End of range
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::CountObjectsInRange");
//Get the indexes for the objects at or after cpMin and cpMost
//respectively.
return FindIndexForCp(cpMost) - FindIndexForCp(cpMin); }
/*
* CObjectMgr::GetFirstObjectInRange (cpMin, cpMost) * * @mfunc Get the first object in the given range. * * @rdesc Pointer to first object in range, or NULL if none. */ COleObject * CObjectMgr::GetFirstObjectInRange ( LONG cpMin, //@parm Beginning of range
LONG cpMost) //@parm End of range
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::GetFirstObjectInRange");
if (cpMin == cpMost) // degenerate range no object selected
return NULL;
LONG iLast = (LONG)_objarray.Count() - 1; // Index for next object
LONG iObj = FindIndexForCp(cpMin); // at or after cpMin
//Make sure this is an existing object.
if(iObj <= iLast) { //Make sure it is within the range
COleObject * pObj = *_objarray.Elem(iObj);
if(pObj && pObj->GetCp() <= cpMost) return pObj; } return NULL; }
/*
* CObjectMgr::GetObjectFromIndex(index) * * @mfunc retrieves the object at the indicated index * * @rdesc a pointer to the object, if found, NULL otherwise */ COleObject *CObjectMgr::GetObjectFromIndex( LONG index) //@parm Index to use
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::GetObjectFromIndex");
if( index < _objarray.Count() ) return *_objarray.Elem(index);
return NULL; }
/*
* CObjectMgr::InsertObject(cp, preobj, publdr) * * @mfunc inserts an object at the indicated index. It is the * caller's responsibility to handle inserting any data * (such as WCH_EMBEDDING) into the text stream. * * @rdesc HRESULT */ HRESULT CObjectMgr::InsertObject( LONG cp, //@parm cp to use
REOBJECT * preobj, //@parm Object to insert
IUndoBuilder *publdr) //@parm Undo context
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::InsertObject");
HRESULT hr; COleObject *pobj = (COleObject *)(preobj->polesite);
// Let the client know what we're up to
if (_precall) { hr = _precall->QueryInsertObject(&preobj->clsid, preobj->pstg, REO_CP_SELECTION);
if( hr != NOERROR ) return hr; }
// Set some stuff up first; since we may make outgoing calls, don't
// change our internal state yet.
hr = pobj->InitFromREOBJECT(cp, preobj); if( hr != NOERROR ) return hr;
return RestoreObject(pobj); }
/*
* CObjectMgr::RestoreObject(pobj) * * @mfunc [re-]inserts the given object into the list of objects * in the backing store * * @rdesc HRESULT */ HRESULT CObjectMgr::RestoreObject( COleObject *pobj) //@parm Object to insert
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::RestoreObject");
COleObject **ppobj = _objarray.Insert(FindIndexForCp(pobj->GetCp()), 1);
if( ppobj == NULL ) return E_OUTOFMEMORY;
*ppobj = pobj; pobj->AddRef();
return NOERROR; }
/*
* CObjectMgr::SetRECallback(precall) * * @mfunc sets the callback interface * * @rdesc void */ void CObjectMgr::SetRECallback( IRichEditOleCallback *precall) //@parm Callback interface pointer
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::SetRECallback");
if( _precall ) SafeReleaseAndNULL((IUnknown**)&_precall);
_precall = precall;
if( _precall ) _precall->AddRef(); }
/*
* CObjectMgr::SetHostNames(pszApp, pszDoc) * * @mfunc set host names for this edit instance * * @rdesc NOERROR or E_OUTOFMEMORY */ HRESULT CObjectMgr::SetHostNames( LPWSTR pszApp, //@parm app name
LPWSTR pszDoc) //@parm doc name
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::SetHostNames"); HRESULT hr = NOERROR;
if( _pszApp ) { delete _pszApp; _pszApp = NULL; } if( _pszDoc ) { delete _pszDoc; _pszDoc = NULL; } if( pszApp ) { _pszApp = new WCHAR[wcslen(pszApp) + 1]; if( _pszApp ) wcscpy(_pszApp, pszApp); else hr = E_OUTOFMEMORY; } if( pszDoc ) { _pszDoc = new WCHAR[wcslen(pszDoc) + 1]; if( _pszDoc ) wcscpy(_pszDoc, pszDoc); else hr = E_OUTOFMEMORY; } return hr; }
/*
* CObjectMgr::CObjectMgr * * @mfunc constructor */ CObjectMgr::CObjectMgr() { _pobjselect = NULL; _pobjactive = NULL; }
/*
* CObjectMgr::~CObjectMgr * * @mfunc destructor */ CObjectMgr::~CObjectMgr() { LONG i, count; COleObject *pobj;
TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::~CObjectMgr");
count = _objarray.Count();
for( i = 0; i < count; i++ ) { pobj = *_objarray.Elem(i); // We NULL stuff here to try to protect ourselves
// better in re-entrancy cases.
*_objarray.Elem(i) = NULL; if( pobj ) { pobj->Close(OLECLOSE_NOSAVE); pobj->MakeZombie(); SafeReleaseAndNULL((IUnknown**)&pobj); } }
if( _precall ) SafeReleaseAndNULL((IUnknown**)&_precall); if( _pszApp ) delete _pszApp;
if( _pszDoc ) delete _pszDoc; }
/*
* CObjectMgr::ReplaceRange (cp, cchDel, publdr) * * @mfunc handles the deletion of objects from a given range. This * method _must_ be called before any floating range notifications * are sent. * * @rdesc void */ void CObjectMgr::ReplaceRange( LONG cp, //@parm cp starting the deletion
LONG cchDel, //@parm Count of characters deleted
IUndoBuilder *publdr) //@parm Undo builder for this actions
{ LONG i; LONG iDel = -1, cDel = 0; // index at which to delete && number of objects
// to delete.
COleObject *pobj;
// nothing deleted, don't bother doing anything.
if( !cchDel ) return;
// Basically, we loop through all of the objects within the
// range of deleted text and ask them to delete themselves.
// We remember the range of objects deleted (the starting index
// and # of objects deleted) so that we can remove them from
// the array all at once.
i = FindIndexForCp(cp);
while( i < _objarray.Count() ) { pobj = *_objarray.Elem(i); if( pobj && pobj->GetCp() >= cp) { if( pobj->GetCp() < (cp + cchDel) ) { if( _pobjactive == pobj ) { // Deactivate the object just to be on the safe side.
_pobjactive->DeActivateObj(); _pobjactive = NULL; }
if(iDel == -1) iDel = i;
cDel++; if (_precall) { IOleObject *poo; if (pobj->GetIUnknown()->QueryInterface(IID_IOleObject, (void **)&poo) == NOERROR) { _precall->DeleteObject(poo); poo->Release(); } }
// if the object was selected, then it obviously
// can't be anymore!
if( _pobjselect == pobj ) { _pobjselect = NULL; }
pobj->Delete(publdr); *_objarray.Elem(i) = NULL; pobj->Release(); } else break; } i++; } if(cDel) _objarray.Remove(iDel, cDel); return; }
/*
* CObjectMgr::ScrollObjects(dx, dy, prcScroll) * * @mfunc informs all objects that scrolling has occured so they can * update if necessary * * @rdesc void */ void CObjectMgr::ScrollObjects( LONG dx, //@parm change in the x direction
LONG dy, //@parm change in the y direction
LPCRECT prcScroll) //@parm rect that is being scrolled
{ LONG count = _objarray.Count(); for(LONG i = 0; i < count; i++ ) { COleObject *pobj = *_objarray.Elem(i); if(pobj) pobj->ScrollObject(dx, dy, prcScroll); } }
//
// PRIVATE methods
//
/*
* CObjectMgr::FindIndexForCp(cp) * * @mfunc does a binary search to find the index at which an object * at the given cp exists or should be inserted. * * @rdesc LONG, an index */ LONG CObjectMgr::FindIndexForCp( LONG cp) { LONG l, r; COleObject *pobj = NULL; LONG i = 0; l = 0; r = _objarray.Count() - 1; while( r >= l ) { i = (l + r)/2; pobj = *_objarray.Elem(i); if( !pobj ) { TRACEWARNSZ("null entry in object table. Recovering..."); for( i = 0 ; i < _objarray.Count() -1; i++ ) { pobj = *_objarray.Elem(i); if( pobj && pobj->GetCp() >= cp ) return i; } return i; } if( pobj->GetCp() == cp ) return i;
else if( pobj->GetCp() < cp ) l = i + 1;
else r = i - 1; }
// Yikes! nothing was found. Fixup i so that
// it points to the correct index for insertion.
Assert(pobj || (!pobj && i == 0));
if(pobj) { Assert(pobj->GetCp() != cp); if( pobj->GetCp() < cp ) i++; } return i; } /*
* CObjectMgr::HandleDoubleClick(ped, &pt, flags) * * @mfunc Handles a double click message, potentially activating * an object. * * @rdesc BOOL-- TRUE if double click-processing is completely * finished. */ BOOL CObjectMgr::HandleDoubleClick( CTxtEdit *ped, //@parm edit context
const POINT &pt,//@parm point of click (WM_LBUTTONDBLCLK wparam)
DWORD flags) //@parm flags (lparam)
{ LONG cp; COleObject *pobj;
TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::HandleDoubleClick");
ped->_pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, NULL, NULL, &cp); pobj = GetObjectFromCp(cp);
if (!pobj) return FALSE;
if (_pobjactive != pobj) { //Deactivate currently active object if any.
if (_pobjactive) _pobjactive->DeActivateObj();
return pobj->ActivateObj(WM_LBUTTONDBLCLK, flags, MAKELONG(pt.x, pt.y)); } return TRUE; }
/*
* CObjectMgr::HandleClick(ped, &pt) * * @mfunc * The position of the caret is changing. We need to * Deactivate the active object, if any. If the change is * because of a mouse click and there is an object at this * cp, we set a new individually selected object. Otherwise * we set the individually selected object to NULL. * * @rdesc returns TRUE if this method set the selection. Otherwise, * returns FALSE; */ ClickStatus CObjectMgr::HandleClick( CTxtEdit *ped, //@parm the edit context
const POINT &pt)//@parm the point of the mouse click
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::HandleClick");
COleObject * pobjnew;//, * pobjold;
CTxtSelection * psel; LONG cp; LONG cpMin, cpMost; if( _pobjactive ) { _pobjactive->DeActivateObj(); return CLICK_OBJDEACTIVATED; }
ped->_pdp->CpFromPoint(pt, NULL, NULL, NULL, FALSE, NULL, NULL, &cp); pobjnew = GetObjectFromCp(cp);
//If we clicked on an object, set the selection to this object.
//CTxtSelection::UpdateSelection will be called as a result of this
//and will determine the highlighting.
if( pobjnew ) { cp = pobjnew->GetCp(); psel = ped->GetSel(); if (psel->GetRange(cpMin, cpMost) > 1 && cpMin <= (LONG) cp && (LONG) cp <= cpMost) { // There is more than one character in the selection
// And the object is part of the selection.
// Do not change the selection
return CLICK_SHOULDDRAG; } // don't reset the selection if the object is already selected
if( pobjnew != _pobjselect ) { // Freeze the Display while we handle this click
CFreezeDisplay fd(ped->_pdp);
psel->SetSelection(cp, cp+1); if (GetSingleSelect()) { // Note thate the call to SetSelection may have set selected object to NULL !!!!
// This can happen in some strange scenarios where our state is out of whack
AssertSz(GetSingleSelect() == pobjnew, "Object NOT Selected!!"); return CLICK_OBJSELECTED; } return CLICK_IGNORED; } return CLICK_OBJSELECTED; } return CLICK_IGNORED; }
/*
* CObjectMgr::HandleSingleSelect(ped, cp, fHiLite) * * @mfunc * When an object is selected and it is the only thing selected, we do * not highlight it by inverting it. We Draw a frame and handles around * it. This function is called either because an object has been * selected and it is the only thing selected, or because we need to * check for an object that used to be in this state but may no longer be. */ void CObjectMgr::HandleSingleSelect( CTxtEdit *ped, //@parm edit context
LONG cp, //@parm cp of object
BOOL fHiLite) //@parm is this a call for hding the selection
{ TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEINTERN, "CObjectMgr::HandleSingleSelect");
COleObject* pobjnew = GetObjectFromCp(cp);
//This should only be called when we know we have a singley selected
//object. However, there are boundary cases (such as inserting an object)
//where WCH_EMBEDDING is the backing store yet no object exists. These
//cases are OK; thus, we check for NULL on pobjnew.
if(pobjnew) { //The object is the same as the currently selected object (if any)
//we are deselecting it. This works like a toggle unless state is messed up.
//If the object is different, we are replacing the current selected
//object (if any).
if(!fHiLite && _pobjselect) { // This covers _pobjselct == pobjnew Normal case
// and _pobjselect != pobjnew Degenerate case.
_pobjselect->SetREOSELECTED(FALSE); _pobjselect = NULL;
//Remove frame/handles from currently selected object.
ped->_pdp->OnPostReplaceRange(CP_INFINITE, 0, 0, cp, cp + 1); } else if(fHiLite && pobjnew != _pobjselect) { // Only do this if we are setting a new selection.
_pobjselect = pobjnew; _pobjselect->SetREOSELECTED(TRUE);
//Draw frame/handles on newly selected object.
ped->_pdp->OnPostReplaceRange(CP_INFINITE, 0, 0, cp, cp + 1); } else { // We want to hilite the selection but the object is already selected.
// Or we want to undo hilite on the selection but the selected object is NULL.
// Do nothing.
} } }
/*
* CObjectMgr::ActivateObjectsAs (rclsid, rclsidAs) * * @mfunc Handles a request by the user to activate all objects of a particular * class as objects of another class. * * @rdesc * HRESULT Success code. */ HRESULT CObjectMgr::ActivateObjectsAs( REFCLSID rclsid, REFCLSID rclsidAs) { TRACEBEGIN(TRCSUBSYSOLE, TRCSCOPEEXTERN, "CObjectMgr::ActivateObjectsAs");
COleObject * pobj; HRESULT hr, hrLatest;
// Tell the system to treat all rclsid objects as rclsidAs
hr = CoTreatAsClass(rclsid, rclsidAs); if( hr != NOERROR ) return hr;
LONG cobj = GetObjectCount();
// Go through objects, letting them decide if
// they have anything to do for this.
for (LONG iobj = 0; iobj < cobj; iobj++) { pobj = GetObjectFromIndex(iobj); hrLatest = pobj->ActivateAs(rclsid, rclsidAs); // Make hr the latest hresult unless we have previously had an error.
if(hr == NOERROR) hr = hrLatest; } return hr; }
#ifdef DEBUG
void CObjectMgr::DbgDump(void) { Tracef(TRCSEVNONE, "Object Manager %d objects", _objarray.Count());
for(LONG i = 0 ; i < _objarray.Count(); i++) { COleObject *pobj = *_objarray.Elem(i); if(pobj) pobj->DbgDump(i); } } #endif
|