/* * @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

objects away The direction of * counting is determined by the sign of

. * * @rdesc Return the signed cch counted and set

to count of * objects actually counted. If

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