CNTRBASE.C

/************************************************************************* 
**
** OLE 2 Container Sample Code
**
** cntrbase.c
**
** This file contains all interfaces, methods and related support
** functions for the basic OLE Container application. The
** basic OLE Container application supports being a container for
** embedded and linked objects.
** The basic Container application includes the following
** implementation objects:
**
** ContainerDoc Object
** no required interfaces for basic functionality
** (see linking.c for linking related support)
** (see clipbrd.c for clipboard related support)
** (see dragdrop.c for drag/drop related support)
**
** ContainerLine Object
** (see cntrline.c for all ContainerLine functions and interfaces)
** exposed interfaces:
** IOleClientSite
** IAdviseSink
**
** (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"


OLEDBGDATA


extern LPOUTLINEAPP g_lpApp;
extern IOleUILinkContainerVtbl g_CntrDoc_OleUILinkContainerVtbl;

#if defined( INPLACE_CNTR )
extern BOOL g_fInsideOutContainer;
#endif // INPLACE_CNTR

// REVIEW: should use string resource for messages
OLECHAR ErrMsgShowObj[] = OLESTR("Could not show object server!");
OLECHAR ErrMsgInsertObj[] = OLESTR("Insert Object failed!");
OLECHAR ErrMsgConvertObj[] = OLESTR("Convert Object failed!");
OLECHAR ErrMsgCantConvert[] = OLESTR("Unable to convert the selection!");
OLECHAR ErrMsgActivateAsObj[] = OLESTR("Activate As Object failed!");

extern OLECHAR ErrMsgSaving[];
extern OLECHAR ErrMsgOpening[];


/* ContainerDoc_Init
* -----------------
*
* Initialize the fields of a new ContainerDoc object. The doc is initially
* not associated with a file or an (Untitled) document. This function sets
* the docInitType to DOCTYPE_UNKNOWN. After calling this function the
* caller should call:
* 1.) Doc_InitNewFile to set the ContainerDoc to (Untitled)
* 2.) Doc_LoadFromFile to associate the ContainerDoc with a file.
* This function creates a new window for the document.
*
* NOTE: the window is initially created with a NIL size. it must be
* sized and positioned by the caller. also the document is initially
* created invisible. the caller must call Doc_ShowWindow
* after sizing it to make the document window visible.
*/
BOOL ContainerDoc_Init(LPCONTAINERDOC lpContainerDoc, BOOL fDataTransferDoc)
{
LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;

lpOutlineDoc->m_cfSaveFormat = lpContainerApp->m_cfCntrOutl;
lpContainerDoc->m_nNextObjNo = 0L;
lpContainerDoc->m_lpNewStg = NULL;
lpContainerDoc->m_fEmbeddedObjectAvail = FALSE;
lpContainerDoc->m_clsidOleObjCopied = CLSID_NULL;
lpContainerDoc->m_dwAspectOleObjCopied = DVASPECT_CONTENT;
lpContainerDoc->m_lpSrcContainerLine = NULL;
lpContainerDoc->m_fShowObject = TRUE;

#if defined( INPLACE_CNTR )
lpContainerDoc->m_lpLastIpActiveLine = NULL;
lpContainerDoc->m_lpLastUIActiveLine = NULL;
lpContainerDoc->m_hWndUIActiveObj = NULL;
lpContainerDoc->m_fAddMyUI = TRUE; // UI needs to be added
lpContainerDoc->m_cIPActiveObjects = 0;
lpContainerApp->m_fMenuHelpMode = FALSE; // F1 pressed in menu

#if defined( INPLACE_CNTRSVR )
lpContainerDoc->m_lpTopIPFrame =
(LPOLEINPLACEUIWINDOW)&lpContainerDoc->m_OleInPlaceFrame;
lpContainerDoc->m_lpTopIPDoc =
(LPOLEINPLACEUIWINDOW)&lpContainerDoc->m_OleInPlaceDoc;
lpContainerDoc->m_hSharedMenu = NULL;
lpContainerDoc->m_hOleMenu = NULL;

#endif // INPLACE_CNTRSVR
#endif // INPLACE_CNTR

INIT_INTERFACEIMPL(
&lpContainerDoc->m_OleUILinkContainer,
&g_CntrDoc_OleUILinkContainerVtbl,
lpContainerDoc
);

return TRUE;
}


/* ContainerDoc_GetNextLink
* ------------------------
*
* Update all links in the document. A dialog box will be popped up showing
* the progress of the update and allow the user to quit by pushing the
* stop button
*/
LPCONTAINERLINE ContainerDoc_GetNextLink(
LPCONTAINERDOC lpContainerDoc,
LPCONTAINERLINE lpContainerLine
)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
DWORD dwNextLink = 0;
LPLINE lpLine;
static int nIndex = 0;

if (lpContainerLine==NULL)
nIndex = 0;

for ( ; nIndex < lpLL->m_nNumLines; nIndex++) {
lpLine = LineList_GetLine(lpLL, nIndex);

if (lpLine
&& (Line_GetLineType(lpLine) == CONTAINERLINETYPE)
&& ContainerLine_IsOleLink((LPCONTAINERLINE)lpLine)) {

nIndex++;
ContainerLine_LoadOleObject((LPCONTAINERLINE)lpLine);
return (LPCONTAINERLINE)lpLine;
}
}

return NULL;
}



/* ContainerDoc_UpdateLinks
* ------------------------
*
* Update all links in the document. A dialog box will be popped up showing
* the progress of the update and allow the user to quit by pushing the
* stop button
*/
void ContainerDoc_UpdateLinks(LPCONTAINERDOC lpContainerDoc)
{
int cLinks;
BOOL fAllLinksUpToDate = TRUE;
HWND hwndDoc = ((LPOUTLINEDOC)lpContainerDoc)->m_hWndDoc;
HCURSOR hCursor;
LPCONTAINERLINE lpContainerLine = NULL;
HRESULT hrErr;
DWORD dwUpdateOpt;
LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
BOOL fPrevEnable1;
BOOL fPrevEnable2;

hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

/* NOTE: we do not want to ever give the Busy/NotResponding
** dialogs when we are updating automatic links as part of
** opening a document. even if the link source of data is busy,
** we do not want put up the busy dialog. thus we will disable
** the dialog and later re-enable them
*/
OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

/* get total number of automatic links */
cLinks = 0;
while (lpContainerLine = ContainerDoc_GetNextLink(
lpContainerDoc,
lpContainerLine)) {
hrErr = CntrDoc_LinkCont_GetLinkUpdateOptions(
(LPOLEUILINKCONTAINER)&lpContainerDoc->m_OleUILinkContainer,
(DWORD)lpContainerLine,
(LPDWORD)&dwUpdateOpt
);
if (hrErr == NOERROR) {
if (dwUpdateOpt==OLEUPDATE_ALWAYS) {
cLinks++;
if (fAllLinksUpToDate) {
OLEDBG_BEGIN2("IOleObject::IsUpToDate called\r\n")
hrErr = lpContainerLine->m_lpOleObj->lpVtbl->IsUpToDate(
lpContainerLine->m_lpOleObj);
OLEDBG_END2
if (hrErr != NOERROR)
fAllLinksUpToDate = FALSE;
}
}
}
#if defined( _DEBUG )
else
OleDbgOutHResult("IOleUILinkContainer::GetLinkUpdateOptions returned",hrErr);
#endif

}

if (fAllLinksUpToDate)
goto done; // don't bother user if all links are up-to-date

SetCursor(hCursor);

if ((cLinks > 0) && !OleUIUpdateLinks(
(LPOLEUILINKCONTAINER)&lpContainerDoc->m_OleUILinkContainer,
hwndDoc,
APPNAMEA,
cLinks))
{
char szTemp[256];
wcstombs(szTemp, APPNAME, 255);
if (ID_PU_LINKS == OleUIPromptUser(
(WORD)IDD_CANNOTUPDATELINK,
hwndDoc,
szTemp)) {
ContainerDoc_EditLinksCommand(lpContainerDoc);
}
}

done:
// re-enable the Busy/NotResponding dialogs
OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);
}



/* ContainerDoc_SetShowObjectFlag
* ------------------------------
*
* Set/Clear the ShowObject flag of ContainerDoc
*/
void ContainerDoc_SetShowObjectFlag(LPCONTAINERDOC lpContainerDoc, BOOL fShow)
{
if (!lpContainerDoc)
return;

lpContainerDoc->m_fShowObject = fShow;
}


/* ContainerDoc_GetShowObjectFlag
* ------------------------------
*
* Get the ShowObject flag of ContainerDoc
*/
BOOL ContainerDoc_GetShowObjectFlag(LPCONTAINERDOC lpContainerDoc)
{
if (!lpContainerDoc)
return FALSE;

return lpContainerDoc->m_fShowObject;
}


/* ContainerDoc_InsertOleObjectCommand
* -----------------------------------
*
* Insert a new OLE object in the ContainerDoc.
*/
void ContainerDoc_InsertOleObjectCommand(LPCONTAINERDOC lpContainerDoc)
{
LPLINELIST lpLL =&((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
LPLINE lpLine = NULL;
HDC hDC;
int nTab = 0;
int nIndex = LineList_GetFocusLineIndex(lpLL);
LPCONTAINERLINE lpContainerLine=NULL;
OLECHAR szStgName[CWCSTORAGENAME];
UINT uRet;
OLEUIINSERTOBJECT io;
char szFile[OLEUI_CCHPATHMAX];
OLECHAR szFileW[OLEUI_CCHPATHMAX];
DWORD dwOleCreateType;
BOOL fDisplayAsIcon;
HCURSOR hPrevCursor;

_fmemset((LPOLEUIINSERTOBJECT)&io, 0, sizeof(OLEUIINSERTOBJECT));
io.cbStruct=sizeof(OLEUIINSERTOBJECT);
io.dwFlags=IOF_SELECTCREATENEW | IOF_SHOWHELP;
io.hWndOwner=((LPOUTLINEDOC)lpContainerDoc)->m_hWndDoc;
io.lpszFile=szFile;
io.cchFile=OLEUI_CCHPATHMAX;
// _fmemset(/*(LPOLESTR)*/szFile, 0, OLEUI_CCHPATHMAX);
_fmemset(/*(LPOLESTR)*/szFile, 0, sizeof(szFile));

#if defined( OLE_VERSION )
OleApp_PreModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

OLEDBG_BEGIN3("OleUIInsertObject called\r\n")
uRet=OleUIInsertObject((LPOLEUIINSERTOBJECT)&io);
OLEDBG_END3

#if defined( OLE_VERSION )
OleApp_PostModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

if (OLEUI_OK != uRet)
return; // user canceled dialog

// this may take a while, put up hourglass cursor
hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

fDisplayAsIcon = (io.dwFlags & IOF_CHECKDISPLAYASICON ? TRUE : FALSE);

// make up a storage name for the OLE object
ContainerDoc_GetNextStgName(lpContainerDoc, szStgName, CWCSTORAGENAME);

/* default the new line to have the same indent as previous line */
lpLine = LineList_GetLine(lpLL, nIndex);
if (lpLine)
nTab = Line_GetTabLevel(lpLine);

hDC = LineList_GetDC(lpLL);

if ((io.dwFlags & IOF_SELECTCREATENEW))
dwOleCreateType = IOF_SELECTCREATENEW;
else if ((io.dwFlags & IOF_CHECKLINK))
dwOleCreateType = IOF_CHECKLINK;
else
dwOleCreateType = IOF_SELECTCREATEFROMFILE;

A2W(szFile, szFileW, OLEUI_CCHPATHMAX);
lpContainerLine = ContainerLine_Create(
dwOleCreateType,
hDC,
nTab,
lpContainerDoc,
&io.clsid,
szFileW,
fDisplayAsIcon,
io.hMetaPict,
szStgName
);

if (!lpContainerLine)
goto error; // creation of OLE object FAILED

if (io.hMetaPict) {
OleUIMetafilePictIconFree(io.hMetaPict); // clean up metafile
}

/* add a ContainerLine object to the document's LineList. The
** ContainerLine manages the rectangle on the screen occupied by
** the OLE object.
*/

LineList_AddLine(lpLL, (LPLINE)lpContainerLine, nIndex);

/* before calling DoVerb(OLEIVERB_SHOW), check to see if the object
** has any initial extents.
*/
ContainerLine_UpdateExtent(lpContainerLine, NULL);

/* If a new embedded object was created, tell the object server to
** make itself visible (show itself).
** NOTE: the standard OLE 2 User Model is to only call
** IOleObject::DoVerb(OLEIVERB_SHOW...) if a new object is
** created. specifically, it should NOT be calld if the object
** is created from file or link to file.
*/
if (dwOleCreateType == IOF_SELECTCREATENEW) {
if (! ContainerLine_DoVerb(
lpContainerLine, OLEIVERB_SHOW, NULL, TRUE, TRUE)) {
OutlineApp_ErrorMessage(g_lpApp, ErrMsgShowObj);
}

/* NOTE: we will immediately force a save of the object
** to guarantee that a valid initial object is saved
** with our document. if the object is a OLE 1.0 object,
** then it may exit without update. by forcing this
** initial save we consistently always have a valid
** object even if it is a OLE 1.0 object that exited
** without saving. if we did NOT do this save here, then
** we would have to worry about deleting the object if
** it was a OLE 1.0 object that closed without saving.
** the OLE 2.0 User Model dictates that the object
** should always be valid after CreateNew performed. the
** user must explicitly delete it.
*/
ContainerLine_SaveOleObjectToStg(
lpContainerLine,
lpContainerLine->m_lpStg,
lpContainerLine->m_lpStg,
TRUE /* fRemember */
);
}
#if defined( INPLACE_CNTR )
else if (dwOleCreateType == IOF_SELECTCREATEFROMFILE) {
/* NOTE: an inside-out container should check if the object
** created from file is an inside-out and prefers to be
** activated when visible type of object. if so, the object
** should be immediately activated in-place, BUT NOT UIActived.
*/
if (g_fInsideOutContainer &&
lpContainerLine->m_dwDrawAspect == DVASPECT_CONTENT &&
lpContainerLine->m_fInsideOutObj ) {
HWND hWndDoc = OutlineDoc_GetWindow((LPOUTLINEDOC)lpContainerDoc);

ContainerLine_DoVerb(
lpContainerLine,OLEIVERB_INPLACEACTIVATE,NULL,FALSE,FALSE);

/* NOTE: following this DoVerb(INPLACEACTIVATE) the
** object may have taken focus. but because the
** object is NOT UIActive it should NOT have focus.
** we will make sure our document has focus.
*/
SetFocus(hWndDoc);
}
}
#endif // INPLACE_CNTR

OutlineDoc_SetModified((LPOUTLINEDOC)lpContainerDoc, TRUE, TRUE, TRUE);

LineList_ReleaseDC(lpLL, hDC);

SetCursor(hPrevCursor); // restore original cursor

return;

error:
// NOTE: if ContainerLine_Create failed
LineList_ReleaseDC(lpLL, hDC);

if (OLEUI_OK == uRet && io.hMetaPict)
OleUIMetafilePictIconFree(io.hMetaPict); // clean up metafile

SetCursor(hPrevCursor); // restore original cursor
OutlineApp_ErrorMessage(g_lpApp, ErrMsgInsertObj);
}



void ContainerDoc_EditLinksCommand(LPCONTAINERDOC lpContainerDoc)
{
UINT uRet;
OLEUIEDITLINKS el;
LPCONTAINERLINE lpContainerLine = NULL;
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;

_fmemset((LPOLEUIEDITLINKS)&el,0,sizeof(OLEUIEDITLINKS));
el.cbStruct=sizeof(OLEUIEDITLINKS);
el.dwFlags=ELF_SHOWHELP;
el.hWndOwner=((LPOUTLINEDOC)lpContainerDoc)->m_hWndDoc;
el.lpOleUILinkContainer =
(LPOLEUILINKCONTAINER)&lpContainerDoc->m_OleUILinkContainer;

#if defined( OLE_VERSION )
OleApp_PreModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

OLEDBG_BEGIN3("OleUIEditLinks called\r\n")
uRet=OleUIEditLinks((LPOLEUIEDITLINKS)&el);
OLEDBG_END3

#if defined( OLE_VERSION )
OleApp_PostModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

OleDbgAssert((uRet==1) || (uRet==OLEUI_CANCEL));

}


/* Convert command - brings up the "Convert" dialog
*/
void ContainerDoc_ConvertCommand(
LPCONTAINERDOC lpContainerDoc,
BOOL fServerNotRegistered
)
{
LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
OLEUICONVERT ct;
UINT uRet;
LPDATAOBJECT lpDataObj;
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
LPCONTAINERLINE lpContainerLine = NULL;
BOOL fSelIsOleObject;
int nIndex;
STGMEDIUM medium;
LPOLESTR lpErrMsg = NULL;
HRESULT hrErr;
HCURSOR hPrevCursor;
BOOL fMustRun = FALSE;
BOOL fMustClose = FALSE;
BOOL fObjConverted = FALSE;
BOOL fDisplayChanged = FALSE;
BOOL fHaveCLSID = FALSE;
BOOL fHaveFmtUserType = FALSE;
OLECHAR szUserType[128];
LPOLESTR lpszUserType;
LPOLESTR lpszDefLabel;
BOOL fMustActivate;

/* NOTE: if we came to the Convert dialog because the user
** activated a non-registered object, then we should activate
** the object after the user has converted it or setup an
** ActivateAs server.
*/
fMustActivate = fServerNotRegistered;

_fmemset((LPOLEUICONVERT)&ct,0,sizeof(OLEUICONVERT));

fSelIsOleObject = ContainerDoc_IsSelAnOleObject(
(LPCONTAINERDOC)lpContainerDoc,
&IID_IDataObject,
(LPUNKNOWN FAR*)&lpDataObj,
&nIndex,
(LPCONTAINERLINE FAR*)&lpContainerLine
);

lpErrMsg = ErrMsgCantConvert;

if (! fSelIsOleObject)
goto error; // can NOT do Convert.

if (! lpContainerLine) {
OleStdRelease((LPUNKNOWN)lpDataObj);
goto error; // can NOT do Convert.
}

ct.cbStruct = sizeof(OLEUICONVERT);
ct.dwFlags = CF_SHOWHELPBUTTON;
ct.hWndOwner = lpContainerDoc->m_OleDoc.m_OutlineDoc.m_hWndDoc;
ct.lpszCaption = NULL;
ct.lpfnHook = NULL;
ct.lCustData = 0;
ct.hInstance = NULL;
ct.lpszTemplate = NULL;
ct.hResource = 0;
ct.fIsLinkedObject = ContainerLine_IsOleLink(lpContainerLine);
ct.dvAspect = lpContainerLine->m_dwDrawAspect;
ct.cClsidExclude = 0;
ct.lpClsidExclude = NULL;

if (! ct.fIsLinkedObject || !lpContainerLine->m_lpOleLink) {
/* NOTE: the object is an embedded object. we should first
** attempt to read the actual object CLSID, file data
** format, and full user type name that is written inside of
** the object's storage as this should be the most
** definitive information. if this fails we will ask the
** object what its class is and attempt to get the rest of
** the information out of the REGDB.
*/
hrErr=ReadClassStg(lpContainerLine->m_lpStg,(CLSID FAR*)&(ct.clsid));
if (hrErr == NOERROR)
fHaveCLSID = TRUE;
else {
OleDbgOutHResult("ReadClassStg returned", hrErr);
}
hrErr = ReadFmtUserTypeStg(
lpContainerLine->m_lpStg,
(CLIPFORMAT FAR*)&ct.wFormat,
&lpszUserType);
ct.lpszUserType = NULL;
if (lpszUserType)
{
int cch = OLESTRLEN(lpszUserType)+1;
ct.lpszUserType=OleStdMalloc(cch);
if (ct.lpszUserType)
{
W2A(lpszUserType, ct.lpszUserType, cch);
}
}
if (hrErr == NOERROR)
fHaveFmtUserType = TRUE;
else {
OleDbgOutHResult("ReadFmtUserTypeStg returned", hrErr);
}
} else {
/* NOTE: the object is a linked object. we should give the
** DisplayName of the link source as the default icon label.
*/
OLEDBG_BEGIN2("IOleLink::GetSourceDisplayName called\r\n")
hrErr = lpContainerLine->m_lpOleLink->lpVtbl->GetSourceDisplayName(
lpContainerLine->m_lpOleLink, &lpszDefLabel);
if (lpszDefLabel)
{
int cch = OLESTRLEN(lpszDefLabel) + 1;
ct.lpszDefLabel = OleStdMalloc(cch);
if (ct.lpszDefLabel)
{
W2A(lpszDefLabel, ct.lpszDefLabel, cch);
}
}
OLEDBG_END2
}

if (! fHaveCLSID) {
hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserClassID(
lpContainerLine->m_lpOleObj,
(CLSID FAR*)&ct.clsid
);
if (hrErr != NOERROR)
ct.clsid = CLSID_NULL;
}
if (! fHaveFmtUserType) {
ct.wFormat = 0;
if (OleStdGetUserTypeOfClass(
(CLSID FAR*)&ct.clsid,szUserType,128,NULL))
{
int cch = OLESTRLEN(szUserType) + 1;
ct.lpszUserType = OleStdMalloc(cch);
if (ct.lpszUserType)
{
W2A(szUserType, ct.lpszUserType, cch);
}
} else {
ct.lpszUserType = NULL;
}
}

if (lpContainerLine->m_dwDrawAspect == DVASPECT_ICON) {
ct.hMetaPict = OleStdGetData(
lpDataObj,
CF_METAFILEPICT,
NULL,
DVASPECT_ICON,
(LPSTGMEDIUM)&medium
);
} else {
ct.hMetaPict = NULL;
}
OleStdRelease((LPUNKNOWN)lpDataObj);

#if defined( OLE_VERSION )
OleApp_PreModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

OLEDBG_BEGIN3("OleUIConvert called\r\n")
uRet = OleUIConvert(&ct);
OLEDBG_END3

#if defined( OLE_VERSION )
OleApp_PostModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

// this may take a while, put up hourglass cursor
hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

if (uRet == OLEUI_OK) {

/*****************************************************************
** NOTE: the convert dialog actually allows the user to
** change two orthogonal properties of the object: the
** object's type/server and the object's display aspect.
** first we will execute the ConvertTo/ActivateAs action and
** then we will deal with any display aspect change. we want
** to be careful to only call IOleObject::Update once
** because this is an expensive operation; it results in
** launching the object's server.
*****************************************************************/

if (ct.dwFlags & CF_SELECTCONVERTTO &&
! IsEqualCLSID(&ct.clsid, &ct.clsidNew)) {

/* user selected CONVERT.
**
** NOTE: to achieve the "Convert To" at this point we
** need to take the following steps:
** 1. unload the object.
** 2. write the NEW CLSID and NEW user type name
** string into the storage of the object,
** BUT write the OLD format tag.
** 3. force an update to force the actual conversion of
** the data bits.
*/
lpErrMsg = ErrMsgConvertObj; // setup correct msg in case of error

ContainerLine_UnloadOleObject(lpContainerLine, OLECLOSE_SAVEIFDIRTY);

OLEDBG_BEGIN2("OleStdDoConvert called \r\n")
hrErr = OleStdDoConvert(
lpContainerLine->m_lpStg, (REFCLSID)&ct.clsidNew);
OLEDBG_END2
if (hrErr != NOERROR)
goto error;

// Reload the object
ContainerLine_LoadOleObject(lpContainerLine);

/* we need to force the object to run to complete the
** conversion. set flag to force OleRun to be called at
** end of function.
*/
fMustRun = TRUE;
fObjConverted = TRUE;

} else if (ct.dwFlags & CF_SELECTACTIVATEAS) {
/* user selected ACTIVATE AS.
**
** NOTE: to achieve the "Activate As" at this point we
** need to take the following steps:
** 1. unload ALL objects of the OLD class that app knows about
** 2. add the TreatAs tag in the registration database
** by calling CoTreatAsClass().
** 3. lazily it can reload the objects; when the objects
** are reloaded the TreatAs will take effect.
*/
lpErrMsg = ErrMsgActivateAsObj; // setup msg in case of error

ContainerDoc_UnloadAllOleObjectsOfClass(
lpContainerDoc,
(REFCLSID)&ct.clsid,
OLECLOSE_SAVEIFDIRTY
);

OLEDBG_BEGIN2("OleStdDoTreatAsClass called \r\n")
A2W(ct.lpszUserType, szUserType, 128);
hrErr = OleStdDoTreatAsClass(szUserType, (REFCLSID)&ct.clsid,
(REFCLSID)&ct.clsidNew);
OLEDBG_END2

// Reload the object
ContainerLine_LoadOleObject(lpContainerLine);

fMustActivate = TRUE; // we should activate this object
}

/*****************************************************************
** NOTE: now we will try to change the display if
** necessary.
*****************************************************************/

if (lpContainerLine->m_lpOleObj &&
ct.dvAspect != lpContainerLine->m_dwDrawAspect) {
/* user has selected to change display aspect between icon
** aspect and content aspect.
**
** NOTE: if we got here because the server was not
** registered, then we will NOT delete the object's
** original display aspect. because we do not have the
** original server, we can NEVER get it back. this is a
** safety precaution.
*/

hrErr = OleStdSwitchDisplayAspect(
lpContainerLine->m_lpOleObj,
&lpContainerLine->m_dwDrawAspect,
ct.dvAspect,
ct.hMetaPict,
!fServerNotRegistered, /* fDeleteOldAspect */
TRUE, /* fSetupViewAdvise */
(LPADVISESINK)&lpContainerLine->m_AdviseSink,
(BOOL FAR*)&fMustRun
);

if (hrErr == NOERROR)
fDisplayChanged = TRUE;

#if defined( INPLACE_CNTR )
ContainerDoc_UpdateInPlaceObjectRects(
lpContainerLine->m_lpDoc, nIndex);
#endif

} else if (ct.dvAspect == DVASPECT_ICON && ct.fObjectsIconChanged) {
hrErr = OleStdSetIconInCache(
lpContainerLine->m_lpOleObj,
ct.hMetaPict
);

if (hrErr == NOERROR)
fDisplayChanged = TRUE;
}

/* we deliberately run the object so that the update won't shut
** the server down.
*/
if (fMustActivate || fMustRun) {

/* if we force the object to run, then shut it down after
** the update. do NOT force the object to close if we
** want to activate the object or if the object was
** already running.
*/
if (!fMustActivate && !OleIsRunning(lpContainerLine->m_lpOleObj))
fMustClose = TRUE; // shutdown after update

hrErr = ContainerLine_RunOleObject(lpContainerLine);

if (fObjConverted &&
FAILED(hrErr) && hrErr!=OLE_E_STATIC) {

// ERROR: convert of the object failed.
// revert the storage to restore the original link.
// (NOTE: static object always return OLE_E_STATIC
// when told to run; this is NOT an error here.
// the OLE2 libraries have built in handlers for
// the static objects that do the conversion.
ContainerLine_UnloadOleObject(
lpContainerLine, OLECLOSE_NOSAVE);
lpContainerLine->m_lpStg->lpVtbl->Revert(
lpContainerLine->m_lpStg);
goto error;

} else if (fObjConverted) {
FORMATETC FmtEtc;
DWORD dwNewConnection;
LPOLECACHE lpOleCache = (LPOLECACHE)OleStdQueryInterface
((LPUNKNOWN)lpContainerLine->m_lpOleObj,&IID_IOleCache);

/* NOTE: we need to force the converted object to
** setup a new OLERENDER_DRAW cache. it is possible
** that the new object needs to cache different data
** in order to support drawing than the old object.
*/
if (lpOleCache &&
lpContainerLine->m_dwDrawAspect == DVASPECT_CONTENT) {
FmtEtc.cfFormat = 0; //NULL; // whatever is needed for Draw
FmtEtc.ptd = NULL;
FmtEtc.dwAspect = DVASPECT_CONTENT;
FmtEtc.lindex = -1;
FmtEtc.tymed = TYMED_NULL;

OLEDBG_BEGIN2("IOleCache::Cache called\r\n")
hrErr = lpOleCache->lpVtbl->Cache(
lpOleCache,
(LPFORMATETC)&FmtEtc,
ADVF_PRIMEFIRST,
(LPDWORD)&dwNewConnection
);
OLEDBG_END2
#if defined( _DEBUG )
if (! SUCCEEDED(hrErr))
OleDbgOutHResult("IOleCache::Cache returned", hrErr);
#endif
OleStdRelease((LPUNKNOWN)lpOleCache);

} 

// Close and force object to save; this will commit the stg
ContainerLine_CloseOleObject(
lpContainerLine, OLECLOSE_SAVEIFDIRTY);
fMustClose = FALSE; // we already closed the object
}
if (fMustClose)
ContainerLine_CloseOleObject(lpContainerLine,OLECLOSE_NOSAVE);
}

if (fDisplayChanged) {
/* the Object's display was changed, force a repaint of
** the line. note the extents of the object may have
** changed.
*/
ContainerLine_UpdateExtent(lpContainerLine, NULL);
LineList_ForceLineRedraw(lpLL, nIndex, TRUE);
}

if (fDisplayChanged || fObjConverted) {
/* mark ContainerDoc as now dirty. if display changed, then
** the extents of the object may have changed.
*/
OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, fDisplayChanged);
}

if (fMustActivate) {
ContainerLine_DoVerb(
lpContainerLine, OLEIVERB_PRIMARY, NULL, FALSE,FALSE);
}
}


if (ct.lpszUserType)
OleStdFree(ct.lpszUserType);

if (ct.lpszDefLabel)
OleStdFree(ct.lpszDefLabel);

if (ct.hMetaPict)
OleUIMetafilePictIconFree(ct.hMetaPict); // clean up metafile

SetCursor(hPrevCursor); // restore original cursor

return;

error:
if (ct.lpszUserType)
OleStdFree(ct.lpszUserType);

if (ct.hMetaPict)
OleUIMetafilePictIconFree(ct.hMetaPict); // clean up metafile

SetCursor(hPrevCursor); // restore original cursor
if (lpErrMsg)
OutlineApp_ErrorMessage(g_lpApp, lpErrMsg);
}


/* ContainerDoc_CloseAllOleObjects
** -------------------------------
** Close all OLE objects. This forces all OLE objects to transition
** from the running state to the loaded state.
**
** Returns TRUE if all objects closed successfully
** FALSE if any object could not be closed.
*/
BOOL ContainerDoc_CloseAllOleObjects(
LPCONTAINERDOC lpContainerDoc,
DWORD dwSaveOption
)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
int i;
LPLINE lpLine;
BOOL fStatus = TRUE;

for (i = 0; i < lpLL->m_nNumLines; i++) {
lpLine=LineList_GetLine(lpLL, i);

if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE))
if (! ContainerLine_CloseOleObject(
(LPCONTAINERLINE)lpLine,dwSaveOption))
fStatus = FALSE;
}

return fStatus;
}


/* ContainerDoc_UnloadAllOleObjectsOfClass
** ---------------------------------------
** Unload all OLE objects of a particular class. this is necessary
** when a class level "ActivateAs" (aka. TreatAs) is setup. the user
** can do this with the Convert dialog. for the TreatAs to take
** effect, all objects of the class have to loaded and reloaded.
*/
void ContainerDoc_UnloadAllOleObjectsOfClass(
LPCONTAINERDOC lpContainerDoc,
REFCLSID rClsid,
DWORD dwSaveOption
)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
int i;
LPLINE lpLine;
CLSID clsid;
HRESULT hrErr;


for (i = 0; i < lpLL->m_nNumLines; i++) {
lpLine=LineList_GetLine(lpLL, i);

if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

if (! lpContainerLine->m_lpOleObj)
continue; // this object is NOT loaded

hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserClassID(
lpContainerLine->m_lpOleObj,
(CLSID FAR*)&clsid
);
if (hrErr == NOERROR &&
( IsEqualCLSID((CLSID FAR*)&clsid,rClsid)
|| IsEqualCLSID(rClsid,&CLSID_NULL) ) ) {
ContainerLine_UnloadOleObject(lpContainerLine, dwSaveOption);
}
}
}
}


/* ContainerDoc_UpdateExtentOfAllOleObjects
** ----------------------------------------
** Update the extents of any OLE object that is marked that its size
** may have changed. when an IAdviseSink::OnViewChange notification
** is received, the corresponding ContainerLine is marked
** (m_fDoGetExtent==TRUE) and a message (WM_U_UPDATEOBJECTEXTENT) is
** posted to the document indicating that there are dirty objects.
** when this message is received, this function is called.
*/
void ContainerDoc_UpdateExtentOfAllOleObjects(LPCONTAINERDOC lpContainerDoc)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
int i;
LPLINE lpLine;
BOOL fStatus = TRUE;
#if defined( INPLACE_CNTR )
int nFirstUpdate = -1;
#endif

for (i = 0; i < lpLL->m_nNumLines; i++) {
lpLine=LineList_GetLine(lpLL, i);

if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

if (lpContainerLine->m_fDoGetExtent) {
ContainerLine_UpdateExtent(lpContainerLine, NULL);
#if defined( INPLACE_CNTR )
if (nFirstUpdate == -1)
nFirstUpdate = i;
#endif
}
}
}

#if defined( INPLACE_CNTR )
/* NOTE: after changing the extents of any line, we need to
** update the PosRect of the In-Place active
** objects (if any) that follow the first modified line.
*/
if (nFirstUpdate != -1)
ContainerDoc_UpdateInPlaceObjectRects(lpContainerDoc, nFirstUpdate+1);
#endif
}


BOOL ContainerDoc_SaveToFile(
LPCONTAINERDOC lpContainerDoc,
LPCOLESTR lpszFileName,
UINT uFormat,
BOOL fRemember
)
{
LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
LPOLEDOC lpOleDoc = (LPOLEDOC)lpContainerDoc;
LPSTORAGE lpDestStg;
BOOL fStatus;
BOOL fMustRelDestStg = FALSE;
HRESULT hrErr;
#if defined( OPTIONAL )
FILETIME filetimeBeforeSave;
#endif

if (fRemember) {
if (lpszFileName) {
fStatus = OutlineDoc_SetFileName(
lpOutlineDoc, (LPOLESTR)lpszFileName, NULL);
if (! fStatus) goto error;
}

/* The ContainerDoc keeps its storage open at all times. it is not
** necessary to reopen the file.
** if SaveAs is pending, then lpNewStg is the new destination for
** the save operation, else the existing storage is the dest.
*/
lpDestStg = (lpContainerDoc->m_lpNewStg ?
lpContainerDoc->m_lpNewStg : lpOleDoc->m_lpStg);

#if defined( OPTIONAL )
/* NOTE: an automatic link to an embedded object within the
** same container document (that uses ItemMonikers) will
** always be considered "out-of-date' by OLE. if a container
** application REALLY wants to fix this it can do one of the
** following:
** 1. implement a new moniker better than ItemMonikers
** that look into the objects storage to find the real last
** change time (rather then defaulting to that of the outer
** container file).
** or 2. using item monikers it is possible to fix the case
** where the container document is saved while the embedded
** object is running but it will NOT fix the case when the
** document is saved when the embedded object was only
** loaded. the fix is to:
** a. remember the time (T) before the save operation starts
** b. call IRunningObjectTable::NoteChangeTime(lpDoc, T)
** c. do the saving and commit the file
** d. call StgSetTimes to reset the file time to T
** e. remember time T in document structure and when the
** root storage is finally released reset the file time
** again to T (closing the file on DOS sets the time).
*/
CoFileTimeNow( &filetimeBeforeSave );
if (lpOleDoc->m_dwRegROT != 0) {
LPRUNNINGOBJECTTABLE lprot;

if (GetRunningObjectTable(0,&lprot) == NOERROR)
{
OleDbgOut2("IRunningObjectTable::NoteChangeTime called\r\n");
lprot->lpVtbl->NoteChangeTime(
lprot, lpOleDoc->m_dwRegROT, &filetimeBeforeSave );
lprot->lpVtbl->Release(lprot);
}
}
#endif
} else {
if (! lpszFileName)
goto error;

/* NOTE: since we are preforming a SaveCopyAs operation, we
** do not need to have the DocFile open in STGM_TRANSACTED mode.
** there is less overhead to use STGM_DIRECT mode.
*/
hrErr = StgCreateDocfile(
lpszFileName,
STGM_READWRITE|STGM_DIRECT|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
0,
&lpDestStg
);
OleDbgAssertSz(hrErr == NOERROR, "Could not create Docfile");
if (hrErr != NOERROR) {
OleDbgOutHResult("StgCreateDocfile returned", hrErr);
goto error;
}
fMustRelDestStg = TRUE;
}

/* NOTE: we must be sure to write our class ID into our
** storage. this information is used by OLE to determine the
** class of the data stored in our storage. Even for top
** "file-level" objects this information should be written to
** the file.
*/
hrErr = WriteClassStg(lpDestStg, &CLSID_APP);
if(hrErr != NOERROR) goto error;

fStatus = OutlineDoc_SaveSelToStg(
lpOutlineDoc,
NULL, // save all lines
uFormat,
lpDestStg,
FALSE, // fSameAsLoad
TRUE // remember this stg
);

if (fStatus)
fStatus = OleStdCommitStorage(lpDestStg);

if (fRemember) {
/* if SaveAs was pending, then release the old storage and remember
** the new storage as the active current storage. all data from
** the old storage has been copied into the new storage.
*/
if (lpContainerDoc->m_lpNewStg) {
OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg); // free old stg
lpOleDoc->m_lpStg = lpContainerDoc->m_lpNewStg; // save new stg
lpContainerDoc->m_lpNewStg = NULL;
}
if (! fStatus) goto error;

OutlineDoc_SetModified(lpOutlineDoc, FALSE, FALSE, FALSE);

#if defined( OPTIONAL )
/* reset time of file on disk to be time just prior to saving.
** NOTE: it would also be necessary to remember
** filetimeBeforeSave in the document structure and when the
** root storage is finally released reset the file time
** again to this value (closing the file on DOS sets the time).
*/
StgSetTimes(
lpOutlineDoc->m_szFileName, NULL, NULL, &filetimeBeforeSave);
#endif
}

if (fMustRelDestStg)
OleStdRelease((LPUNKNOWN)lpDestStg);
return TRUE;

error:
if (fMustRelDestStg)
OleStdRelease((LPUNKNOWN)lpDestStg);
OutlineApp_ErrorMessage(g_lpApp, ErrMsgSaving);
return FALSE;
}


/* ContainerDoc_ContainerLineDoVerbCommand
** ---------------------------------------
** Execute a verb of the OLE object in the current focus line.
*/
void ContainerDoc_ContainerLineDoVerbCommand(
LPCONTAINERDOC lpContainerDoc,
LONG iVerb
)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
int nIndex = LineList_GetFocusLineIndex(lpLL);
LPLINE lpLine = LineList_GetLine(lpLL, nIndex);
HCURSOR hPrevCursor;

if (! lpLine || (Line_GetLineType(lpLine) != CONTAINERLINETYPE) ) return;

// this may take a while, put up hourglass cursor
hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

ContainerLine_DoVerb((LPCONTAINERLINE) lpLine, iVerb, NULL, TRUE, TRUE);

SetCursor(hPrevCursor); // restore original cursor
}


/* ContainerDoc_GetNextStgName
** ---------------------------
** Generate the next unused name for a sub-storage to be used by an
** OLE object. The ContainerDoc keeps a counter. The storages for
** OLE objects are simply numbered (eg. Obj 0, Obj 1). A "long"
** integer worth of storage names should be more than enough than we
** will ever need.
**
** NOTE: when an OLE object is transfered via drag/drop or the
** clipboard, we attempt to keep the currently assigned name for the
** object (if not currently in use). thus it is possible that an
** object with a the next default name (eg. "Obj 5") already exists
** in the current document if an object with this name was privously
** transfered (pasted or dropped). we therefore loop until we find
** the next lowest unused name.
*/
void ContainerDoc_GetNextStgName(
LPCONTAINERDOC lpContainerDoc,
LPOLESTR lpszStgName,
int nLen
)
{
char szAnsiStgName[256];

wsprintf(szAnsiStgName, "%s %ld",
(LPSTR)DEFOBJNAMEPREFIX,
++(lpContainerDoc->m_nNextObjNo)
);

while (ContainerDoc_IsStgNameUsed(lpContainerDoc, lpszStgName) == TRUE) {
wsprintf(szAnsiStgName, "%s %ld",
(LPSTR)DEFOBJNAMEPREFIX,
++(lpContainerDoc->m_nNextObjNo)
);
}
A2W (szAnsiStgName, lpszStgName, nLen);
}


/* ContainerDoc_IsStgNameUsed
** --------------------------
** Check if a given StgName is already in use.
*/
BOOL ContainerDoc_IsStgNameUsed(
LPCONTAINERDOC lpContainerDoc,
LPOLESTR lpszStgName
)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
int i;
LPLINE lpLine;

for (i = 0; i < lpLL->m_nNumLines; i++) {
lpLine=LineList_GetLine(lpLL, i);

if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
if (OLESTRCMP(lpszStgName,
((LPCONTAINERLINE)lpLine)->m_szStgName) == 0) {
return TRUE; // Match FOUND!
}
}
}
return FALSE; // if we get here, then NO match was found.
}


LPSTORAGE ContainerDoc_GetStg(LPCONTAINERDOC lpContainerDoc)
{
return ((LPOLEDOC)lpContainerDoc)->m_lpStg;
}


/* ContainerDoc_GetSingleOleObject
** -------------------------------
** If the entire document contains a single OLE object, then
** return the desired interface of the object.
**
** Returns NULL if there is are multiple lines in the document or
** the single line is not a ContainerLine.
*/
LPUNKNOWN ContainerDoc_GetSingleOleObject(
LPCONTAINERDOC lpContainerDoc,
REFIID riid,
LPCONTAINERLINE FAR* lplpContainerLine
)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
LPLINE lpLine;
LPUNKNOWN lpObj = NULL;


if (lplpContainerLine)
*lplpContainerLine = NULL;

if (lpLL->m_nNumLines != 1)
{
return NULL; // doc does NOT contain a single line
}

lpLine=LineList_GetLine(lpLL, 0);

if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE))
lpObj = ContainerLine_GetOleObject((LPCONTAINERLINE)lpLine, riid);

if (lplpContainerLine)
*lplpContainerLine = (LPCONTAINERLINE)lpLine;

return lpObj;
}


/* ContainerDoc_IsSelAnOleObject
** -----------------------------
** Check if the selection is a single selection of an OLE object.
** if so, then optionally return the desired interface of the object
** and/or index of the ContainerLine containing the OLE object.
**
** Returns FALSE if there is a multiple selection or the single
** selection is not a ContainerLine.
*/
BOOL ContainerDoc_IsSelAnOleObject(
LPCONTAINERDOC lpContainerDoc,
REFIID riid,
LPUNKNOWN FAR* lplpvObj,
int FAR* lpnIndex,
LPCONTAINERLINE FAR* lplpContainerLine
)
{
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
LINERANGE lrSel;
int nNumSel;
LPLINE lpLine;


if (lplpvObj) *lplpvObj = NULL;
if (lpnIndex) *lpnIndex = -1;
if (lplpContainerLine) *lplpContainerLine = NULL;

nNumSel = LineList_GetSel(lpLL, (LPLINERANGE)&lrSel);
if (nNumSel != 1)
{
return FALSE; // selection is not a single line
}

lpLine = LineList_GetLine(lpLL, lrSel.m_nStartLine);

if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
if (lpnIndex)
*lpnIndex = lrSel.m_nStartLine;
if (lplpContainerLine)
*lplpContainerLine = (LPCONTAINERLINE)lpLine;
if (riid) {
*lplpvObj = ContainerLine_GetOleObject(
(LPCONTAINERLINE)lpLine,
riid
);
}

return (*lplpvObj ? TRUE : FALSE);
}

return FALSE;
}


/*************************************************************************
** ContainerDoc::IOleUILinkContainer interface implementation
*************************************************************************/

STDMETHODIMP CntrDoc_LinkCont_QueryInterface(
LPOLEUILINKCONTAINER lpThis,
REFIID riid,
LPVOID FAR* lplpvObj
)
{
LPOLEDOC lpOleDoc = (LPOLEDOC)
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;

return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}


STDMETHODIMP_(ULONG) CntrDoc_LinkCont_AddRef(LPOLEUILINKCONTAINER lpThis)
{
LPOLEDOC lpOleDoc = (LPOLEDOC)
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;

OleDbgAddRefMethod(lpThis, OLESTR("IOleUILinkContainer"));

return OleDoc_AddRef(lpOleDoc);
}


STDMETHODIMP_(ULONG) CntrDoc_LinkCont_Release(LPOLEUILINKCONTAINER lpThis)
{
LPOLEDOC lpOleDoc = (LPOLEDOC)
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;

OleDbgReleaseMethod(lpThis, "IOleUILinkContainer");

return OleDoc_Release(lpOleDoc);
}


STDMETHODIMP_(DWORD) CntrDoc_LinkCont_GetNextLink(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = NULL;

// artificial AddRef in case object is released
// during this call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_GetNextLink\r\n")

lpContainerLine = ContainerDoc_GetNextLink(
lpContainerDoc,
(LPCONTAINERLINE)dwLink
);

OLEDBG_END2

// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return (DWORD)lpContainerLine;
}


STDMETHODIMP CntrDoc_LinkCont_SetLinkUpdateOptions(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink,
DWORD dwUpdateOpt
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
LPOLELINK lpOleLink = lpContainerLine->m_lpOleLink;
SCODE sc = S_OK;
HRESULT hrErr;

// artificial AddRef in case object is released during call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_SetLinkUpdateOptions\r\n")

OleDbgAssert(lpContainerLine);

if (! lpOleLink) {
sc = E_FAIL;
goto error;
}

OLEDBG_BEGIN2("IOleLink::SetUpdateOptions called\r\n")
hrErr = lpOleLink->lpVtbl->SetUpdateOptions(
lpOleLink,
dwUpdateOpt
);
OLEDBG_END2

// save new link type update option
lpContainerLine->m_dwLinkType = dwUpdateOpt;

if (hrErr != NOERROR) {
OleDbgOutHResult("IOleLink::SetUpdateOptions returned", hrErr);
sc = hrErr;
goto error;
}

error:
// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

OLEDBG_END2
return sc;
}


STDMETHODIMP CntrDoc_LinkCont_GetLinkUpdateOptions(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink,
DWORD FAR* lpdwUpdateOpt
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
LPOLELINK lpOleLink = lpContainerLine->m_lpOleLink;
SCODE sc = S_OK;
HRESULT hrErr;

// artificial AddRef in case object is released during call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_GetLinkUpdateOptions\r\n")

OleDbgAssert(lpContainerLine);

if (! lpOleLink) {
sc = E_FAIL;
goto error;
}

OLEDBG_BEGIN2("IOleLink::GetUpdateOptions called\r\n")
hrErr = lpOleLink->lpVtbl->GetUpdateOptions(
lpOleLink,
lpdwUpdateOpt
);
OLEDBG_END2

// reset saved link type to ensure it is correct
lpContainerLine->m_dwLinkType = *lpdwUpdateOpt;

if (hrErr != NOERROR) {
OleDbgOutHResult("IOleLink::GetUpdateOptions returned", hrErr);
sc = hrErr;
goto error;
}

error:
OLEDBG_END2

// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return sc;
}


STDMETHODIMP CntrDoc_LinkCont_SetLinkSource(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink,
LPSTR lpszDisplayName,
ULONG lenFileName,
ULONG FAR* lpchEaten,
BOOL fValidateSource
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
SCODE sc = S_OK;
HRESULT hrErr;
LPOLELINK lpOleLink = lpContainerLine->m_lpOleLink;
LPBC lpbc = NULL;
LPMONIKER lpmk = NULL;
LPOLEOBJECT lpLinkSrcOleObj = NULL;
CLSID clsid = CLSID_NULL;
CLSID clsidOld = CLSID_NULL;
OLECHAR szDisplayName[256];


// artificial AddRef in case object is released during call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_SetLinkSource\r\n")

OleDbgAssert(lpContainerLine);

lpContainerLine->m_fLinkUnavailable = TRUE;

if (fValidateSource) {

/* NOTE: validate the link source by parsing the string
** into a Moniker. if this is successful, then the string is
** valid.
*/
hrErr = CreateBindCtx(0, (LPBC FAR*)&lpbc);
if (hrErr != NOERROR) {
sc = hrErr; // ERROR: OOM
goto cleanup;
}

// Get class of orignial link source if it is available
if (lpContainerLine->m_lpOleObj) {

OLEDBG_BEGIN2("IOleObject::GetUserClassID called\r\n")
hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserClassID(
lpContainerLine->m_lpOleObj, (CLSID FAR*)&clsidOld);
OLEDBG_END2
if (hrErr != NOERROR) clsidOld = CLSID_NULL;
}

A2W(lpszDisplayName, szDisplayName, 256);
hrErr = OleStdMkParseDisplayName(
&clsidOld,lpbc,szDisplayName,lpchEaten,(LPMONIKER FAR*)&lpmk);

if (hrErr != NOERROR) {
sc = hrErr; // ERROR in parsing moniker!
goto cleanup;
}
/* NOTE: the link source was validated; it successfully
** parsed into a Moniker. we can set the source of the link
** directly with this Moniker. if we want the link to be
** able to know the correct class for the new link source,
** we must bind to the moniker and get the CLSID. if we do
** not do this then methods like IOleObject::GetUserType
** will return nothing (NULL strings).
*/

hrErr = lpmk->lpVtbl->BindToObject(
lpmk,lpbc,NULL,&IID_IOleObject,(LPVOID FAR*)&lpLinkSrcOleObj);
if (hrErr == NOERROR) {
OLEDBG_BEGIN2("IOleObject::GetUserClassID called\r\n")
hrErr = lpLinkSrcOleObj->lpVtbl->GetUserClassID(
lpLinkSrcOleObj, (CLSID FAR*)&clsid);
OLEDBG_END2
lpContainerLine->m_fLinkUnavailable = FALSE;

/* get the short user type name of the link because it may
** have changed. we cache this name and must update our
** cache. this name is used all the time when we have to
** build the object verb menu. we cache this information
** to make it quicker to build the verb menu.
*/
if (lpContainerLine->m_lpszShortType) {
OleStdFree(lpContainerLine->m_lpszShortType);
lpContainerLine->m_lpszShortType = NULL;
}
OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
lpContainerLine->m_lpOleObj,
USERCLASSTYPE_SHORT,
/*(LPOLESTR FAR*)*/&lpContainerLine->m_lpszShortType
);
OLEDBG_END2
}
else
lpContainerLine->m_fLinkUnavailable = TRUE;
}
else {
LPMONIKER lpmkFile = NULL;
LPMONIKER lpmkItem = NULL;
char szDelim[2];
OLECHAR wszDelim[2];
char *lpszName;
OLECHAR wszName[256];

szDelim[0] = lpszDisplayName[(int)lenFileName];
szDelim[1] = '\0';
lpszDisplayName[(int)lenFileName] = '\0';

OLEDBG_BEGIN2("CreateFileMoniker called\r\n")
A2W(lpszDisplayName, szDisplayName, 256);
CreateFileMoniker(szDisplayName, (LPMONIKER FAR*)&lpmkFile);
OLEDBG_END2

lpszDisplayName[(int)lenFileName] = szDelim[0];

if (!lpmkFile)
goto cleanup;

if (strlen(lpszDisplayName) > (int)lenFileName) { // have item name
lpszName = lpszDisplayName + lenFileName + 1;

OLEDBG_BEGIN2("CreateItemMoniker called\r\n")
A2W(szDelim, wszDelim, 2);
A2W(lpszName, wszName, 256);
CreateItemMoniker(
wszDelim, wszName, (LPMONIKER FAR*)&lpmkItem);
OLEDBG_END2

if (!lpmkItem) {
OleStdRelease((LPUNKNOWN)lpmkFile);
goto cleanup;
}

OLEDBG_BEGIN2("CreateGenericComposite called\r\n")
CreateGenericComposite(lpmkFile, lpmkItem, (LPMONIKER FAR*)&lpmk);
OLEDBG_END2

if (lpmkFile)
OleStdRelease((LPUNKNOWN)lpmkFile);
if (lpmkItem)
OleStdRelease((LPUNKNOWN)lpmkItem);

if (!lpmk)
goto cleanup;
}
else
lpmk = lpmkFile;
}

if (! lpOleLink) {
OleDbgAssert(lpOleLink != NULL);
sc = E_FAIL;
goto cleanup;
}

if (lpmk) {

OLEDBG_BEGIN2("IOleLink::SetSourceMoniker called\r\n")
hrErr = lpOleLink->lpVtbl->SetSourceMoniker(
lpOleLink, lpmk, (REFCLSID)&clsid);
OLEDBG_END2

if (FAILED(hrErr)) {
OleDbgOutHResult("IOleLink::SetSourceMoniker returned",hrErr);
sc = hrErr;
goto cleanup;
}

/* NOTE: above we forced the link source moniker to bind.
** because we deliberately hold on to the bind context
** (lpbc) the link source object will not shut down. during
** the call to IOleLink::SetSourceMoniker, the link will
** connect to the running link source (the link internally
** calls BindIfRunning). it is important to initially allow
** the link to bind to the running object so that it can get
** an update of the presentation for its cache. we do not
** want the connection from our link to the link source be
** the only reason the link source stays running. thus we
** deliberately for the link to release (unbind) the source
** object, we then release the bind context, and then we
** allow the link to rebind to the link source if it is
** running anyway.
*/
if (lpbc && lpmk->lpVtbl->IsRunning(lpmk,lpbc,NULL,NULL) == NOERROR) {

OLEDBG_BEGIN2("IOleLink::Update called\r\n")
hrErr = lpOleLink->lpVtbl->Update(lpOleLink, NULL);
OLEDBG_END2

#if defined( _DEBUG )
if (FAILED(hrErr))
OleDbgOutHResult("IOleLink::Update returned",hrErr);
#endif

OLEDBG_BEGIN2("IOleLink::UnbindSource called\r\n")
hrErr = lpOleLink->lpVtbl->UnbindSource(lpOleLink);
OLEDBG_END2

#if defined( _DEBUG )
if (FAILED(hrErr))
OleDbgOutHResult("IOleLink::UnbindSource returned",hrErr);
#endif

if (lpLinkSrcOleObj) {
OleStdRelease((LPUNKNOWN)lpLinkSrcOleObj);
lpLinkSrcOleObj = NULL;

} 

if (lpbc) {
OleStdRelease((LPUNKNOWN)lpbc);
lpbc = NULL;
}

OLEDBG_BEGIN2("IOleLink::BindIfRunning called\r\n")
hrErr = lpOleLink->lpVtbl->BindIfRunning(lpOleLink);
OLEDBG_END2

#if defined( _DEBUG )
if (FAILED(hrErr))
OleDbgOutHResult("IOleLink::BindIfRunning returned",hrErr);
#endif
}
} else {
/* NOTE: the link source was NOT validated; it was NOT
** successfully parsed into a Moniker. we can only set the
** display name string as the source of the link. this link
** is not able to bind.
*/
OLEDBG_BEGIN2("IOleLink::SetSourceDisplayName called\r\n")
hrErr = lpOleLink->lpVtbl->SetSourceDisplayName(
lpOleLink, (LPCOLESTR)lpszDisplayName);
OLEDBG_END2

if (hrErr != NOERROR) {
OleDbgOutHResult("IOleLink::SetSourceDisplayName returned",hrErr);
sc = hrErr;
goto cleanup;
}
}

cleanup:
if (lpLinkSrcOleObj)
OleStdRelease((LPUNKNOWN)lpLinkSrcOleObj);
if (lpmk)
OleStdRelease((LPUNKNOWN)lpmk);
if (lpbc)
OleStdRelease((LPUNKNOWN)lpbc);

OLEDBG_END2
// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return sc;
}


STDMETHODIMP CntrDoc_LinkCont_GetLinkSource(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink,
LPSTR FAR* lplpszDisplayName,
ULONG FAR* lplenFileName,
LPSTR FAR* lplpszFullLinkType,
LPSTR FAR* lplpszShortLinkType,
BOOL FAR* lpfSourceAvailable,
BOOL FAR* lpfIsSelected
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
LPOLELINK lpOleLink = lpContainerLine->m_lpOleLink;
LPOLEOBJECT lpOleObj = NULL;
LPMONIKER lpmk = NULL;
LPMONIKER lpmkFirst = NULL;
LPBC lpbc = NULL;
SCODE sc = S_OK;
HRESULT hrErr;
LPOLESTR lpwszFullLinkType;
LPOLESTR lpwszShortLinkType;
LPOLESTR lpwszDisplayName;
int cch;

// artificial AddRef in case object is released during call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_GetLinkSource\r\n")

/* NOTE: we must make sure to set all out parameters to NULL. */
lpwszDisplayName = NULL;
lpwszFullLinkType = NULL;
lpwszShortLinkType= NULL;
*lplenFileName = 0;
*lpfSourceAvailable = !lpContainerLine->m_fLinkUnavailable;

OleDbgAssert(lpContainerLine);

if (! lpOleLink) {
OLEDBG_END2
sc = E_FAIL;
goto cleanup;
}

OLEDBG_BEGIN2("IOleLink::GetSourceMoniker called\r\n")
hrErr = lpOleLink->lpVtbl->GetSourceMoniker(
lpOleLink,
(LPMONIKER FAR*)&lpmk
);
OLEDBG_END2

if (hrErr == NOERROR) {
/* NOTE: the link has the Moniker form of the link source;
** this is therefore a validated link source. if the first
** part of the Moniker is a FileMoniker, then we need to
** return the length of the filename string. we need to
** return the ProgID associated with the link source as the
** "lpszShortLinkType". we need to return the
** FullUserTypeName associated with the link source as the
** "lpszFullLinkType".
*/

lpOleObj = (LPOLEOBJECT)OleStdQueryInterface(
(LPUNKNOWN)lpOleLink, &IID_IOleObject);
*lplpszFullLinkType = NULL;
*lplpszShortLinkType = NULL;
if (lpOleObj) {
lpOleObj->lpVtbl->GetUserType(
lpOleObj,
USERCLASSTYPE_FULL,
&lpwszFullLinkType
);
if (lpwszFullLinkType)
{
cch = OLESTRLEN(lpwszFullLinkType) + 1;
*lplpszFullLinkType = OleStdMalloc(cch);
if (*lplpszFullLinkType)
{
W2A(lpwszFullLinkType, *lplpszFullLinkType, cch);
}
}
lpOleObj->lpVtbl->GetUserType(
lpOleObj,
USERCLASSTYPE_SHORT,
&lpwszShortLinkType
);
if (lpwszShortLinkType)
{
cch = OLESTRLEN(lpwszShortLinkType) + 1;
*lplpszShortLinkType = OleStdMalloc(cch);
if (*lplpszShortLinkType)
{
W2A(lpwszShortLinkType, *lplpszShortLinkType, cch);
}
}
OleStdRelease((LPUNKNOWN)lpOleObj);
}
*lplenFileName = OleStdGetLenFilePrefixOfMoniker(lpmk);
lpmk->lpVtbl->Release(lpmk);
}

OLEDBG_BEGIN2("IOleLink::GetSourceDisplayName called\r\n")
hrErr = lpOleLink->lpVtbl->GetSourceDisplayName(
lpOleLink,
&lpwszDisplayName
);
if (lpwszDisplayName)
{
cch = OLESTRLEN(lpwszDisplayName) + 1;
*lplpszDisplayName = OleStdMalloc(cch);
if (*lplpszDisplayName)
{
W2A(lpwszDisplayName, *lplpszDisplayName, cch);
}
}
else
*lplpszDisplayName = NULL;
OLEDBG_END2

if (hrErr != NOERROR) {
OleDbgOutHResult("IOleLink::GetSourceDisplayName returned", hrErr);
OLEDBG_END2
sc = hrErr;
goto cleanup;
}

OLEDBG_END2

if (lpfIsSelected)
*lpfIsSelected = Line_IsSelected((LPLINE)lpContainerLine);

sc = NOERROR;

cleanup:
// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return sc;
}


STDMETHODIMP CntrDoc_LinkCont_OpenLinkSource(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
SCODE sc = S_OK;

// artificial AddRef in case object is destroyed during call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_OpenLinkSource\r\n")

OleDbgAssert(lpContainerLine);

if (! ContainerLine_DoVerb(
lpContainerLine, OLEIVERB_SHOW, NULL, TRUE, FALSE)) {
sc = E_FAIL;
}

lpContainerLine->m_fLinkUnavailable = (sc != S_OK);

OLEDBG_END2

// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return sc;
}


STDMETHODIMP CntrDoc_LinkCont_UpdateLink(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink,
BOOL fErrorMessage,
BOOL fErrorAction // ignore if fErrorMessage
// is FALSE
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
SCODE sc = S_OK;
// default to update of the link
HRESULT hrErr = S_FALSE;

// artificial AddRef in case object is destroyed during call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_UpdateLink\r\n")

OleDbgAssert(lpContainerLine);

if (! lpContainerLine->m_lpOleObj)
ContainerLine_LoadOleObject(lpContainerLine);

if (!fErrorMessage) {
OLEDBG_BEGIN2("IOleObject::IsUpToDate called\r\n")
hrErr = lpContainerLine->m_lpOleObj->lpVtbl->IsUpToDate(
lpContainerLine->m_lpOleObj
);
OLEDBG_END2
}

if (hrErr != NOERROR) {
OLEDBG_BEGIN2("IOleObject::Update called\r\n")
hrErr = lpContainerLine->m_lpOleObj->lpVtbl->Update(
lpContainerLine->m_lpOleObj
);
OLEDBG_END2
}

/* NOTE: If IOleObject::Update on the Link object returned
** OLE_E_CLASSDIFF because the link source is no longer
** the expected class, then the link should be re-created with
** the new link source. thus the link will be updated with the
** new link source.
*/
if (hrErr == OLE_E_CLASSDIFF)
hrErr = ContainerLine_ReCreateLinkBecauseClassDiff(lpContainerLine);

lpContainerLine->m_fLinkUnavailable = (hrErr != NOERROR);

if (hrErr != NOERROR) {
OleDbgOutHResult("IOleObject::Update returned", hrErr);
sc = hrErr;
if (fErrorMessage) {
ContainerLine_ProcessOleRunError(
lpContainerLine,hrErr,fErrorAction,FALSE/*fMenuInvoked*/);
}
}
/* NOTE: if the update of the object requires us to update our
** display, then we will automatically be sent a OnViewChange
** advise. thus we do not need to take any action here to force
** a repaint.
*/

OLEDBG_END2

// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return sc;
}


/* CntrDoc_LinkCont_CancelLink
** ---------------------------
** Convert the link to a static picture.
**
** NOTE: OleCreateStaticFromData can be used to create a static
** picture object.
*/
STDMETHODIMP CntrDoc_LinkCont_CancelLink(
LPOLEUILINKCONTAINER lpThis,
DWORD dwLink
)
{
LPCONTAINERDOC lpContainerDoc =
((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
LPLINE lpLine = NULL;
HDC hDC;
int nTab = 0;
OLECHAR szStgName[CWCSTORAGENAME];
LPCONTAINERLINE lpNewContainerLine = NULL;
LPDATAOBJECT lpSrcDataObj;
LPOLELINK lpOleLink = lpContainerLine->m_lpOleLink;
int nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);

// artificial AddRef in case object is destroyed during call
CntrDoc_LinkCont_AddRef(lpThis);

OLEDBG_BEGIN2("CntrDoc_LinkCont_CancelLink\r\n")

/* we will first break the connection of the link to its source. */
if (lpOleLink) {
lpContainerLine->m_dwLinkType = 0;
OLEDBG_BEGIN2("IOleLink::SetSourceMoniker called\r\n")
lpOleLink->lpVtbl->SetSourceMoniker(
lpOleLink, NULL, (REFCLSID)&CLSID_NULL);
OLEDBG_END2
}

lpSrcDataObj = (LPDATAOBJECT)ContainerLine_GetOleObject(
lpContainerLine,&IID_IDataObject);
if (! lpSrcDataObj)
goto error;

ContainerDoc_GetNextStgName(lpContainerDoc, szStgName, CWCSTORAGENAME);
nTab = Line_GetTabLevel((LPLINE)lpContainerLine);
hDC = LineList_GetDC(lpLL);

lpNewContainerLine = ContainerLine_CreateFromData(
hDC,
nTab,
lpContainerDoc,
lpSrcDataObj,
OLECREATEFROMDATA_STATIC,
0, /* no special cfFormat required */
(lpContainerLine->m_dwDrawAspect == DVASPECT_ICON),
NULL, /* hMetaPict */
szStgName
);
LineList_ReleaseDC(lpLL, hDC);

OleStdRelease((LPUNKNOWN)lpSrcDataObj);

if (! lpNewContainerLine)
goto error;

OutlineDoc_SetModified((LPOUTLINEDOC)lpContainerDoc, TRUE, TRUE, FALSE);

LineList_ReplaceLine(lpLL, (LPLINE)lpNewContainerLine, nIndex);

OLEDBG_END2
// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return NOERROR;

error:
OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not break the link."));
OLEDBG_END2
// release artificial AddRef
CntrDoc_LinkCont_Release(lpThis);

return E_FAIL;
}