/* * DOCUMENT.CPP * Freeloader Chapter 11 * * Implementation of the CFreeloaderDoc derivation of CDocument. * We create a default handler object and use it for drawing, data * caching, and serialization. * * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved * * Kraig Brockschmidt, Microsoft * Internet : kraigb@microsoft.com * Compuserve: >INTERNET:kraigb@microsoft.com */ #include "freeload.h" /* * CFreeloaderDoc::CFreeloaderDoc * CFreeloaderDoc::~CFreeloaderDoc * * Constructor Parameters: * hInst HINSTANCE of the application. * pFR PCFrame of the frame object. * pAdv PCDocumentAdviseSink to notify on events */ CFreeloaderDoc::CFreeloaderDoc(HINSTANCE hInst, PCFrame pFR , PCDocumentAdviseSink pAdv) : CDocument(hInst, pFR, pAdv) { m_pIStorage=NULL; m_pIUnknown=NULL; m_dwConn=0; m_clsID=CLSID_NULL; return; } CFreeloaderDoc::~CFreeloaderDoc(void) { ReleaseObject(); ReleaseInterface(m_pIStorage); return; } /* * CFreeloaderDoc::ReleaseObject * * Purpose: * Centralizes cleanup code for the object and its cache. * * Parameters: * None * * Return Value: * None */ void CFreeloaderDoc::ReleaseObject(void) { LPOLECACHE pIOleCache; HRESULT hr; if (0!=m_dwConn) { hr=m_pIUnknown->QueryInterface(IID_IOleCache , (PPVOID)&pIOleCache); if (SUCCEEDED(hr)) { pIOleCache->Uncache(m_dwConn); pIOleCache->Release(); } } ReleaseInterface(m_pIUnknown); CoFreeUnusedLibraries(); m_dwConn=0; return; } /* * CFreeloaderDoc::FInit * * Purpose: * Initializes an already created document window. Here we * only change the stringtable bounds. * * Parameters: * pDI PDOCUMENTINIT containing initialization * parameters. * * Return Value: * BOOL TRUE if the function succeeded, FALSE otherwise. */ BOOL CFreeloaderDoc::FInit(PDOCUMENTINIT pDI) { //Change the stringtable range to our customization. pDI->idsMin=IDS_DOCUMENTMIN; pDI->idsMax=IDS_DOCUMENTMAX; //Do default initialization return CDocument::Init(pDI); } /* * CFreeloaderDoc::FMessageHook * * Purpose: * Processes WM_PAINT for the document so we can draw the object. * * Parameters: * * pLRes LRESULT * in which to store the return * value for the message. * * Return Value: * BOOL TRUE to prevent further processing, * FALSE otherwise. */ BOOL CFreeloaderDoc::FMessageHook(HWND hWnd, UINT iMsg , WPARAM wParam, LPARAM lParam, LRESULT *pLRes) { PAINTSTRUCT ps; HDC hDC; RECT rc; RECTL rcl; LPVIEWOBJECT2 pIViewObject2; HRESULT hr; HGLOBAL hMem; LPTSTR psz; if (WM_PAINT!=iMsg) return FALSE; hDC=BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rc); //Draw the object with IViewObject2::Draw, allowing ESC if (NULL!=m_pIUnknown) { hMem = RenderFormat(CF_UNICODETEXT); if (NULL!=hMem) { psz = (LPTSTR)GlobalLock(hMem); TextOut(hDC, rc.right/4, rc.bottom/4, psz, lstrlen(psz)); GlobalUnlock(hMem); } else{ hr=m_pIUnknown->QueryInterface(IID_IViewObject2 , (PPVOID)&pIViewObject2); if (SUCCEEDED(hr)) { //Put "Hit Esc to stop" in the status line m_pFR->StatusLine()->MessageSet(PSZ(IDS_HITESCTOSTOP)); RECTLFROMRECT(rcl, rc); pIViewObject2->Draw(DVASPECT_CONTENT, -1, NULL, NULL , 0, hDC, &rcl, NULL, ContinuePaint, 0); pIViewObject2->Release(); m_pFR->StatusLine()->MessageDisplay(ID_MESSAGEREADY); } } } EndPaint(hWnd, &ps); return FALSE; } /* * ContinuePaint * * Purpose: * Callback function for IViewObject2::Draw that allows us to * abort a long repaint. This implementation watches the * Esc key through GetAsyncKeyState. * * Parameters: * dwContinue DWORD custom data passed to IViewObject::Draw * which in our case holds the document pointer. * * Return Value: * BOOL TRUE to continue painting, FALSE to stop it. */ BOOL CALLBACK ContinuePaint(DWORD dwContinue) { return !(GetAsyncKeyState(VK_ESCAPE) < 0); } /* * CFreeloaderDoc::Load * * Purpose: * Loads a given document without any user interface overwriting * the previous contents of the window. * * Parameters: * fChangeFile BOOL indicating if we're to update the window * title and the filename from using this file. * pszFile LPTSTR to the filename to load, NULL if the file * is new and untitled. * * Return Value: * UINT An error value from DOCERR_... */ UINT CFreeloaderDoc::Load(BOOL fChangeFile, LPTSTR pszFile) { HRESULT hr; LPSTORAGE pIStorage; LPUNKNOWN pIUnknown; LPPERSISTSTORAGE pIPersistStorage; DWORD dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE; CLSID clsID; if (NULL==pszFile) { //Create a new temp file. hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE | STGM_DELETEONRELEASE, 0, &pIStorage); if (FAILED(hr)) return DOCERR_COULDNOTOPEN; m_pIStorage=pIStorage; FDirtySet(FALSE); Rename(NULL); return DOCERR_NONE; } //Attempt to open the storage. hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage); if (FAILED(hr)) return DOCERR_COULDNOTOPEN; /* * When we previously called IPersistStorage::Save, we saved * the class of this object type with WriteClassStg. Now * we read that CLSID, create a data cache for it, then * have it reload its data into the cache through * IPersistStorage::Load. */ hr=ReadClassStg(pIStorage, &clsID); hr=CreateDataCache(NULL, clsID, IID_IUnknown , (PPVOID)&pIUnknown); if (FAILED(hr)) { pIStorage->Release(); return DOCERR_READFAILURE; } //Get IPersistStorage for the data we hold. pIUnknown->QueryInterface(IID_IPersistStorage , (PPVOID)&pIPersistStorage); //Load might fail because the object is already open... hr=pIPersistStorage->Load(pIStorage); pIPersistStorage->Release(); if (FAILED(hr)) { pIUnknown->Release(); pIStorage->Release(); return DOCERR_READFAILURE; } m_pIStorage=pIStorage; m_pIUnknown=pIUnknown; Rename(pszFile); SizeToGraphic(FALSE); FDirtySet(FALSE); return DOCERR_NONE; } /* * CFreeloaderDoc::Save * * Purpose: * Writes the file to a known filename, requiring that the user * has previously used FileOpen or FileSaveAs in order to have * a filename. * * Parameters: * uType UINT indicating the type of file the user * requested to save in the File Save As dialog. * pszFile LPTSTR under which to save. If NULL, use the * current name. * * Return Value: * UINT An error value from DOCERR_... */ UINT CFreeloaderDoc::Save(UINT uType, LPTSTR pszFile) { HRESULT hr; LPSTORAGE pIStorage; LPPERSISTSTORAGE pIPersistStorage; CLSID clsID; //If we have no data object, there's nothing to save. if (NULL==m_pIUnknown) return DOCERR_WRITEFAILURE; //Get IPersistStorage for the data we hold. hr=m_pIUnknown->QueryInterface(IID_IPersistStorage , (PPVOID)&pIPersistStorage); if (FAILED(hr)) return DOCERR_WRITEFAILURE; //Save or Save As with the same file is just a commit. if (NULL==pszFile || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile))) { pIPersistStorage->Save(m_pIStorage, TRUE); m_pIStorage->Commit(STGC_ONLYIFCURRENT); pIPersistStorage->SaveCompleted(NULL); pIPersistStorage->Release(); FDirtySet(FALSE); return DOCERR_NONE; } /* * When we're given a name, open the storage, creating it new * ifit does not exist or overwriting the old one. Then CopyTo * from the current to the new, Commit the new, then Release * the old. */ hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage); if (FAILED(hr)) return DOCERR_COULDNOTOPEN; //Insure the image is up to date, then tell it we're changing pIPersistStorage->Save(m_pIStorage, TRUE); pIPersistStorage->HandsOffStorage(); //Save the class, bitmap or metafile if (FAILED(pIPersistStorage->GetClassID(&clsID))) clsID=m_clsID; hr=WriteClassStg(m_pIStorage, clsID); hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage); if (FAILED(hr)) { pIPersistStorage->SaveCompleted(m_pIStorage); pIPersistStorage->Release(); pIStorage->Release(); return DOCERR_WRITEFAILURE; } pIStorage->Commit(STGC_ONLYIFCURRENT); /* * Revert changes on the original storage. If this was a temp * file, it's deleted since we used STGM_DELETEONRELEASE. */ m_pIStorage->Release(); //Make this new storage current m_pIStorage=pIStorage; pIPersistStorage->SaveCompleted(m_pIStorage); pIPersistStorage->Release(); Rename(pszFile); FDirtySet(FALSE); return DOCERR_NONE; } /* * CFreeloaderDoc::Clip * * Purpose: * Places a private format, a metafile, and a bitmap of the display * on the clipboard, optionally implementing Cut by deleting the * data in the current window after rendering. * * Parameters: * hWndFrame HWND of the main window. * fCut BOOL indicating cut (TRUE) or copy (FALSE). * * Return Value: * BOOL TRUE if successful, FALSE otherwise. */ BOOL CFreeloaderDoc::Clip(HWND hWndFrame, BOOL fCut) { BOOL fRet=TRUE; static UINT rgcf[4]={CF_METAFILEPICT, CF_DIB, CF_BITMAP,CF_UNICODETEXT}; const UINT cFormats=4; UINT i; HGLOBAL hMem; if (NULL==m_pIUnknown) return FALSE; if (!OpenClipboard(hWndFrame)) return FALSE; //Clean out whatever junk is in the clipboard. EmptyClipboard(); for (i=0; i < cFormats; i++) { hMem=RenderFormat(rgcf[i]); if (NULL!=hMem) { SetClipboardData(rgcf[i], hMem); fRet=TRUE; break; } } //Free clipboard ownership. CloseClipboard(); //If we're cutting, clean out the cache and the object we hold. if (fRet && fCut) { ReleaseObject(); InvalidateRect(m_hWnd, NULL, TRUE); UpdateWindow(m_hWnd); FDirtySet(TRUE); } return fRet; } /* * CFreeloaderDoc::RenderFormat * * Purpose: * Renders a specific clipboard format into global memory. * * Parameters: * cf UINT format to render. * * Return Value: * HGLOBAL Global memory handle containing the data. */ HGLOBAL CFreeloaderDoc::RenderFormat(UINT cf) { LPDATAOBJECT pIDataObject; FORMATETC fe; STGMEDIUM stm; if (NULL==m_pIUnknown) return NULL; //We only have to ask the data object (cache) for the data. switch (cf) { case CF_METAFILEPICT: stm.tymed=TYMED_MFPICT; break; case CF_DIB: stm.tymed=TYMED_HGLOBAL; break; case CF_BITMAP: stm.tymed=TYMED_GDI; break; case CF_UNICODETEXT: stm.tymed=TYMED_HGLOBAL; break; default: return NULL; } stm.hGlobal=NULL; SETDefFormatEtc(fe, cf, stm.tymed); m_pIUnknown->QueryInterface(IID_IDataObject , (PPVOID)&pIDataObject); pIDataObject->GetData(&fe, &stm); pIDataObject->Release(); return stm.hGlobal; } /* * CFreeloaderDoc::FQueryPaste * * Purpose: * Determines if we can paste data from the clipboard. * * Parameters: * None * * Return Value: * BOOL TRUE if data is available, FALSE otherwise. */ BOOL CFreeloaderDoc::FQueryPaste(void) { return IsClipboardFormatAvailable(CF_BITMAP) || IsClipboardFormatAvailable(CF_DIB) || IsClipboardFormatAvailable(CF_METAFILEPICT) || IsClipboardFormatAvailable(CF_UNICODETEXT); } /* * CFreeloaderDoc::Paste * * Purpose: * Retrieves the private data format from the clipboard and sets it * to the current figure in the editor window. * * Note that if this function is called, then the clipboard format * is available because the Paste menu item is only enabled if the * format is present. * * Parameters: * hWndFrame HWND of the main window. * * Return Value: * BOOL TRUE if successful, FALSE otherwise. */ BOOL CFreeloaderDoc::Paste(HWND hWndFrame) { UINT cf=0; HGLOBAL hMem; HRESULT hr; DWORD dwConn; LPUNKNOWN pIUnknown; LPOLECACHE pIOleCache; LPPERSISTSTORAGE pIPersistStorage; FORMATETC fe; STGMEDIUM stm; CLSID clsID; if (!OpenClipboard(hWndFrame)) return FALSE; /* * Try to get data in order of metafile, dib, bitmap. We set * stm.tymed up front so if we actually get something a call * to ReleaseStgMedium will clean it up for us. */ stm.pUnkForRelease=NULL; stm.tymed=TYMED_HGLOBAL; hMem=GetClipboardData(CF_METAFILEPICT); if (NULL!=hMem) cf=CF_METAFILEPICT; if (0==cf) { stm.tymed=TYMED_HGLOBAL; hMem=GetClipboardData(CF_DIB); if (NULL!=hMem) cf=CF_DIB; } if (0==cf) { stm.tymed=TYMED_HGLOBAL; hMem=GetClipboardData(CF_BITMAP); if (NULL!=hMem) cf=CF_BITMAP; } if (0==cf) { stm.tymed=TYMED_HGLOBAL; hMem=GetClipboardData(CF_UNICODETEXT); if (NULL!=hMem) cf=CF_UNICODETEXT; } stm.hGlobal=OleDuplicateData(hMem, cf, NULL); CloseClipboard(); //Didn't get anything? Then we're finished. if (0==cf) return FALSE; //This now describes the data we have. SETDefFormatEtc(fe, cf, stm.tymed); /* * Create a data cache to deal with this data using * either CoCreateInstance for an OLE-supported CLSID or * CreateDataCache. The first will go through all the * exercises of looking up the CLSID in the regDB, finding * the OLE DLL (registered for these classes), getting a class * factory, and using IClassFactory::CreateInstance. The * second goes into OLE directly, creating the same object * with less overhead. Thus we use CreateDataCache. */ /* !! GCC !! I found these to be extraneous. if (CF_METAFILEPICT==cf) clsID=CLSID_Picture_Metafile; else if (CF_UNICODETEXT==cf) clsID=CLSID_NULL; else clsID=CLSID_Picture_Dib; */ hr=CreateDataCache(NULL, CLSID_Picture_Metafile, IID_IUnknown , (PPVOID)&pIUnknown); if (FAILED(hr)) { ReleaseStgMedium(&stm); return FALSE; } /* * Our contract says we provide storage through * IPersistStorage::InitNew. We know that the object we're * dealing with supports IPersistStorage and IOleCache, so * we don't bother to check return values. I guess we're * living dangerously... */ pIUnknown->QueryInterface(IID_IPersistStorage , (PPVOID)&pIPersistStorage); pIPersistStorage->InitNew(m_pIStorage); pIPersistStorage->Release(); /* * Now that we have the cache object, shove the data into it. * No advise flags are necessary for static data. */ pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache); pIOleCache->Cache(&fe, ADVF_PRIMEFIRST, &dwConn); hr=pIOleCache->SetData(&fe, &stm, TRUE); pIOleCache->Release(); if (FAILED(hr)) { ReleaseStgMedium(&stm); pIUnknown->Release(); return FALSE; } //Now that that's all done, replace our current with the new. ReleaseObject(); m_pIUnknown=pIUnknown; m_dwConn=dwConn; m_clsID=clsID; FDirtySet(TRUE); InvalidateRect(m_hWnd, NULL, TRUE); UpdateWindow(m_hWnd); return TRUE; } /* * CFreeloaderDoc::SizeToGraphic * * Purpose: * Determines if we can size the window to the contained * graphic and alternately performs the operation. * * Parameters: * fQueryOnly BOOL indicating if we just want to know * if sizing is possible (TRUE) or that we * want to perform the sizing (FALSE). * * Return Value: * BOOL TRUE if data is available, FALSE otherwise. */ BOOL CFreeloaderDoc::SizeToGraphic(BOOL fQueryOnly) { HRESULT hr; LPVIEWOBJECT2 pIViewObject2; SIZEL szl; RECT rc; UINT cx, cy; HDC hDC; DWORD dwStyle; if (NULL==m_pIUnknown) return FALSE; m_pIUnknown->QueryInterface(IID_IViewObject2 , (PPVOID)&pIViewObject2); if (FAILED(hr)) return FALSE; if (fQueryOnly) { pIViewObject2->Release(); return TRUE; } hr=pIViewObject2->GetExtent(DVASPECT_CONTENT, -1, NULL, &szl); pIViewObject2->Release(); if (FAILED(hr)) return FALSE; //Calculate new doc rectangle based on these extents. hDC=GetDC(NULL); cx=MulDiv((int)szl.cx, GetDeviceCaps(hDC, LOGPIXELSX) , HIMETRIC_PER_INCH); cy=MulDiv((int)szl.cy, GetDeviceCaps(hDC, LOGPIXELSY) , HIMETRIC_PER_INCH); ReleaseDC(NULL, hDC); SetRect(&rc, 0, 0, cx, cy); dwStyle=GetWindowLong(m_hWnd, GWL_STYLE); AdjustWindowRect(&rc, dwStyle, FALSE); /* * If the window is currently maximized, then we have to * restore it first before sizing. */ if (IsZoomed(m_hWnd)) ShowWindow(m_hWnd, SW_RESTORE); SetWindowPos(m_hWnd, NULL, 0, 0 , rc.right-rc.left , rc.bottom-rc.top , SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); return TRUE; }