home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / com / inole2 / chap20 / patron / page.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-03  |  33.3 KB  |  1,405 lines

  1. /*
  2.  * PAGE.CPP
  3.  * Patron Chapter 20
  4.  *
  5.  * Implementation of parts of the CPage class; those member
  6.  * functions dealing with mouse events are in PAGEMOUS.CPP.
  7.  *
  8.  * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
  9.  *
  10.  * Kraig Brockschmidt, Microsoft
  11.  * Internet  :  kraigb@microsoft.com
  12.  * Compuserve:  >INTERNET:kraigb@microsoft.com
  13.  */
  14.  
  15.  
  16. #include "patron.h"
  17.  
  18.  
  19. /*
  20.  * CPage::CPage
  21.  * CPage::~CPage
  22.  *
  23.  * Constructor Parameters:
  24.  *  dwID            DWORD identifier for this page.
  25.  *  hWnd            HWND of the pages window (for repaints, etc).
  26.  *  pPG             PCPages to the Pages window.
  27.  */
  28.  
  29. CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
  30.     {
  31.     m_dwID     =dwID;
  32.     m_pIStorage=NULL;
  33.  
  34.     m_cOpens=0;
  35.     m_hWnd=hWnd;
  36.     m_pPG=pPG;
  37.  
  38.     m_dwIDNext      =0;
  39.     m_cTenants      =0;
  40.     m_hWndTenantList=NULL;
  41.     m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
  42.     m_pTenantCur    =NULL;
  43.  
  44.     m_uHTCode=HTNOWHERE;
  45.     m_uSizingFlags=0;
  46.     m_fTracking=FALSE;
  47.     m_hDC=NULL;
  48.  
  49.     m_fDragPending=FALSE;
  50.     m_fSizePending=FALSE;
  51.     m_fTimer=FALSE;
  52.  
  53.     //Get WIN.INI distance and delay values, with OLE defaults.
  54.     m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
  55.         , DD_DEFDRAGMINDIST);
  56.     m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
  57.         , DD_DEFDRAGDELAY);
  58.  
  59.     //CHAPTER20MOD
  60.     m_fReopen=FALSE;
  61.     //End CHAPTER20MOD
  62.  
  63.     return;
  64.     }
  65.  
  66.  
  67. CPage::~CPage(void)
  68.     {
  69.     if (m_fTimer)
  70.         KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
  71.  
  72.     m_hWnd=NULL;
  73.     Close(FALSE);
  74.     return;
  75.     }
  76.  
  77.  
  78.  
  79. /*
  80.  * CPage::GetID
  81.  *
  82.  * Return Value:
  83.  *  DWORD           dwID field in this page.
  84.  */
  85.  
  86. DWORD CPage::GetID(void)
  87.     {
  88.     return m_dwID;
  89.     }
  90.  
  91.  
  92.  
  93.  
  94.  
  95. /*
  96.  * CPage::Open
  97.  *
  98.  * Purpose:
  99.  *  Retrieves the IStorage associated with this page.  The IStorage
  100.  *  is owned by the page and thus the page always holds a reference
  101.  *  count.  The caller should call Close or delete this page to
  102.  *  match this open.
  103.  *
  104.  *  This function may be called multiple times resulting in
  105.  *  additional reference counts on the storage each of which must be
  106.  *  matched with a call to Close.  The last Close can be done
  107.  *  through delete.
  108.  *
  109.  * Parameters:
  110.  *  pIStorage       LPSTORAGE in which this page lives.
  111.  *
  112.  * Return Value:
  113.  *  BOOL            TRUE if opening succeeds, FALSE otherwise.
  114.  */
  115.  
  116. BOOL CPage::Open(LPSTORAGE pIStorage)
  117.     {
  118.     HRESULT                 hr=NOERROR;
  119.     LPSTREAM                pIStream;
  120.     DWORD                   dwMode;
  121.     OLECHAR                 szTemp[32];
  122.     TCHAR                   szCap[32];
  123.     BOOL                    fNew;
  124.     BOOL                    fCreated=FALSE;
  125.     TENANTLIST              tl;
  126.     PTENANTINFO             pti;
  127.     ULONG                   cb;
  128.     LPMALLOC                pIMalloc;
  129.     UINT                    i;
  130.     PCTenant                pTenant;
  131.     //CHAPTER20MOD
  132.     UINT                    cLinks;
  133.     LPOLELINK               pIOleLink;
  134.     LPUNKNOWN               pIUnknown;
  135.     UINT                    uRet;
  136.     OLEUIEDITLINKS          el;
  137.     PCIOleUILinkContainer   pIUILinks;
  138.     HWND                    hWndDoc;
  139.     //End CHAPTER20MOD
  140.  
  141.     fNew=(NULL==m_pIStorage);
  142.  
  143.     if (!fNew)
  144.         {
  145.         m_cOpens++;
  146.         m_pIStorage->AddRef();
  147.         return TRUE;
  148.         }
  149.  
  150.     if (NULL==pIStorage)
  151.         return FALSE;
  152.  
  153.     /*
  154.      * Attempt to open the storage under this ID.  If none,
  155.      * create one.  In either case, the IStorage is either
  156.      * saved in pPage or released.
  157.      */
  158.  
  159.     GetStorageName(szTemp);
  160.     dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;
  161.  
  162.     hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
  163.         , &m_pIStorage);
  164.  
  165.     if (FAILED(hr))
  166.         {
  167.         hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
  168.             , &m_pIStorage);
  169.         fCreated=TRUE;
  170.         }
  171.  
  172.     if (FAILED(hr))
  173.         return FALSE;
  174.  
  175.     m_cOpens++;
  176.  
  177.     if (NULL==m_hWndTenantList)
  178.         {
  179.         /*
  180.          * The first time we open this page, create the hidden
  181.          * listbox we'll use to track tenants.  We give it the
  182.          * owner-draw style so we can just store pointers in it.
  183.          */
  184.         m_hWndTenantList=CreateWindow(TEXT("listbox")
  185.             , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
  186.             , 0, 0, 100, 100, HWND_DESKTOP, NULL
  187.             , m_pPG->m_hInst, NULL);
  188.  
  189.         if (NULL==m_hWndTenantList)
  190.             return FALSE;
  191.         }
  192.  
  193.  
  194.     //If this is brand-new, we're done.
  195.     if (fCreated)
  196.         return TRUE;
  197.  
  198.  
  199.     /*
  200.      * Now open the stream we saved in Close and load all the
  201.      * tenants listed in there.  If there are none, then we don't
  202.      * have to load squat.
  203.      */
  204.  
  205.     hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
  206.         | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);
  207.  
  208.     if (FAILED(hr))
  209.         return FALSE;
  210.  
  211.     if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
  212.         {
  213.         pIStream->Read(&tl, sizeof(tl), NULL);
  214.         m_cTenants=tl.cTenants;
  215.         m_dwIDNext=tl.dwIDNext;
  216.         m_iTenantCur=0;
  217.  
  218.         cb=tl.cTenants*sizeof(TENANTINFO);
  219.  
  220.         if (0!=cb)
  221.             {
  222.             pti=(PTENANTINFO)pIMalloc->Alloc(cb);
  223.  
  224.             if (NULL!=pti)
  225.                 {
  226.                 pIStream->Read(pti, cb, NULL);
  227.  
  228.                 for (i=0; i < m_cTenants; i++)
  229.                     {
  230.                     if (TenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
  231.                         {
  232.                         pTenant->Load(m_pIStorage, (pti+i));
  233.  
  234.                         //CHAPTER20MOD
  235.                         //Make sure it knows about the show state.
  236.                         pTenant->ShowObjectType(m_pPG->m_fShowTypes);
  237.                         //End CHAPTER20MOD
  238.                         }
  239.                     }
  240.  
  241.                 pIMalloc->Free(pti);
  242.                 }
  243.             }
  244.  
  245.         pIMalloc->Release();
  246.         }
  247.  
  248.     pIStream->Release();
  249.  
  250.     //Get and select the first tenant
  251.     if (TenantGet(0, &m_pTenantCur, FALSE))
  252.         m_pTenantCur->Select(TRUE);
  253.  
  254.  
  255.     //CHAPTER20MOD
  256.     //If we just saved and closed, don't bother with updating links
  257.     if (m_fReopen)
  258.         {
  259.         m_fReopen=FALSE;
  260.         return TRUE;
  261.         }
  262.  
  263.     /*
  264.      * Update all the links in this page, showing the progress
  265.      * indicator as it's happening.  We use the same
  266.      * IOlUILinkContainer implementation as we do for the links
  267.      * dialog, passing it to OleUIUpdateLinks which does everything
  268.      * for us.
  269.      *
  270.      * We might also optimize this to not do anything if there are
  271.      * no automatic links, but it's not a big concern.
  272.      */
  273.  
  274.     //First, count the number of automatic links.
  275.     cLinks=0;
  276.  
  277.     for (i=0; i < m_cTenants; i++)
  278.         {
  279.         if (TenantGet(i, &pTenant, FALSE))
  280.             {
  281.             DWORD       dw;
  282.  
  283.             pTenant->ObjectGet(&pIUnknown);
  284.             hr=pIUnknown->QueryInterface(IID_IOleLink
  285.                 , (PPVOID)&pIOleLink);
  286.             pIUnknown->Release();
  287.  
  288.             if (FAILED(hr))
  289.                 continue;
  290.  
  291.             pIOleLink->GetUpdateOptions(&dw);
  292.             pIOleLink->Release();
  293.  
  294.             if (OLEUPDATE_ALWAYS==dw)
  295.                 cLinks++;
  296.             }
  297.         }
  298.  
  299.     //If we have any automatic links, invoke the update dialog.
  300.     if (0==cLinks)
  301.         return TRUE;
  302.  
  303.     //Create an IOleUILinkContainer instantiation.
  304.     if (!m_pPG->GetUILinkContainer(&pIUILinks))
  305.         return TRUE;    //Guess we can't update, oh well.
  306.  
  307.     hWndDoc=GetParent(m_hWnd);
  308.     LoadString(m_pPG->m_hInst, IDS_CAPTION, szCap, sizeof(szCap));
  309.  
  310.     if (!OleUIUpdateLinks(pIUILinks, hWndDoc, szCap, cLinks))
  311.         {
  312.         /*
  313.          * If updating failed, ask to show the links dialog.  NOTE:
  314.          * OleUIPromptUser has a variable wsprintf argument list
  315.          * after the hWnd parameter!  Use appropriate typecasting!
  316.          */
  317.         uRet=OleUIPromptUser(IDD_CANNOTUPDATELINK, hWndDoc, szCap);
  318.  
  319.        #ifdef IDC_PU_LINKS
  320.         if (IDC_PU_LINKS==uRet)
  321.        #else
  322.         if (ID_PU_LINKS==uRet)      //Win3.1
  323.        #endif
  324.             {
  325.             //Throw up the links dialog.
  326.             memset(&el, 0, sizeof(el));
  327.             el.cbStruct=sizeof(el);
  328.             el.hWndOwner=hWndDoc;
  329.             el.lpOleUILinkContainer=pIUILinks;
  330.             OleUIEditLinks(&el);
  331.             }
  332.         }
  333.  
  334.     m_pPG->m_fDirty=pIUILinks->m_fDirty;
  335.     pIUILinks->Release();
  336.     //End CHAPTER20MOD
  337.     return TRUE;
  338.     }
  339.  
  340.  
  341.  
  342.  
  343.  
  344. /*
  345.  * CPage::Close
  346.  *
  347.  * Purpose:
  348.  *  Possibly commits the storage, then releases it reversing the
  349.  *  reference count from Open.
  350.  *
  351.  * Parameters:
  352.  *  fCommit         BOOL indicating if we're to commit.
  353.  *
  354.  * Return Value:
  355.  *  None
  356.  */
  357.  
  358. void CPage::Close(BOOL fCommit)
  359.     {
  360.     if (NULL==m_pIStorage)
  361.         return;
  362.  
  363.     if (fCommit)
  364.         Update();
  365.  
  366.     m_pIStorage->Release();
  367.  
  368.     //If this was the last close, make all tenants loaded->passive
  369.     if (0==--m_cOpens)
  370.         {
  371.         UINT        i;
  372.         PCTenant    pTenant;
  373.  
  374.         m_pIStorage=NULL;
  375.  
  376.         for (i=0; i < m_cTenants; i++)
  377.             {
  378.             if (TenantGet(i, &pTenant, FALSE))
  379.                 {
  380.                 if (NULL!=m_hWnd)
  381.                     {
  382.                     //Open may select again, so this repaints.
  383.                     pTenant->Select(FALSE);
  384.                     }
  385.  
  386.                 pTenant->Close(FALSE);
  387.                 pTenant->Release();
  388.                 }
  389.             }
  390.  
  391.         DestroyWindow(m_hWndTenantList);
  392.         m_hWndTenantList=NULL;
  393.  
  394.         //CHAPTER20MOD
  395.         /*
  396.          * If we reopen this page right away, this flag will tell
  397.          * us to skip updating links again which would make us
  398.          * dirty right after a SaveAs operation.
  399.          */
  400.         m_fReopen=TRUE;
  401.         //End CHAPTER20MOD
  402.         }
  403.  
  404.     return;
  405.     }
  406.  
  407.  
  408.  
  409.  
  410. /*
  411.  * CPage::Update
  412.  *
  413.  * Purpose:
  414.  *  Forces a common on the page if it's open.
  415.  *
  416.  * Parameters:
  417.  *  None
  418.  *
  419.  * Return Value:
  420.  *  BOOL            TRUE if there are any open objects on this page,
  421.  *                  that is, we should remain open.
  422.  */
  423.  
  424. BOOL CPage::Update(void)
  425.     {
  426.     BOOL            fOpen=FALSE;
  427.     UINT            i;
  428.     PCTenant        pTenant;
  429.     HRESULT         hr;
  430.     LPSTREAM        pIStream;
  431.     TENANTLIST      tl;
  432.     PTENANTINFO     pti;
  433.     ULONG           cb;
  434.     LPMALLOC        pIMalloc;
  435.  
  436.     //Walk the list of objects and update them all as well.
  437.     for (i=0; i < m_cTenants; i++)
  438.         {
  439.         if (TenantGet(i, &pTenant, FALSE))
  440.             fOpen |= pTenant->Update();
  441.         }
  442.  
  443.     //Now write our own stream containing the tenant list.
  444.     hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
  445.         | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
  446.         , &pIStream);
  447.  
  448.     if (FAILED(hr))
  449.         return fOpen;
  450.  
  451.     if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
  452.         {
  453.         tl.cTenants=m_cTenants;
  454.         tl.dwIDNext=m_dwIDNext;
  455.  
  456.         pIStream->Write(&tl, sizeof(TENANTLIST), &cb);
  457.  
  458.         cb=m_cTenants*sizeof(TENANTINFO);
  459.         pti=(PTENANTINFO)pIMalloc->Alloc(cb);
  460.  
  461.         if (NULL!=pti)
  462.             {
  463.             for (i=0; i < m_cTenants; i++)
  464.                 {
  465.                 TenantGet(i, &pTenant, FALSE);
  466.                 pTenant->GetInfo((pti+i));
  467.                 }
  468.  
  469.             pIStream->Write(pti, cb, &cb);
  470.             pIMalloc->Free(pti);
  471.             }
  472.  
  473.         pIMalloc->Release();
  474.         }
  475.  
  476.     pIStream->Release();
  477.  
  478.     //Now commit the whole mess and we're done
  479.     if (NULL!=m_pIStorage)
  480.         m_pIStorage->Commit(STGC_DEFAULT);
  481.  
  482.     return fOpen;
  483.     }
  484.  
  485.  
  486.  
  487.  
  488.  
  489. /*
  490.  * CPage::Destroy
  491.  *
  492.  * Purpose:
  493.  *  Removes this page from the given storage.  The caller should
  494.  *  eventually delete this Page object to free the storage.
  495.  *
  496.  * Parameters:
  497.  *  pIStorage       LPSTORAGE contianing this page on which to call
  498.  *                  DestroyElement
  499.  *
  500.  * Return Value:
  501.  *  None
  502.  */
  503.  
  504. void CPage::Destroy(LPSTORAGE pIStorage)
  505.     {
  506.     if (NULL!=pIStorage)
  507.         {
  508.         OLECHAR szTemp[32];
  509.  
  510.         Close(FALSE);
  511.         GetStorageName(szTemp);
  512.         pIStorage->DestroyElement(szTemp);
  513.         }
  514.  
  515.     return;
  516.     }
  517.  
  518.  
  519.  
  520.  
  521. /*
  522.  * CPage::GetStorageName
  523.  *
  524.  * Parameters:
  525.  *  pszName         LPOLESTR to a buffer in which to store the
  526.  *                  storage name for this page.
  527.  *
  528.  * Return Value:
  529.  *  UINT            Number of characters stored.
  530.  */
  531.  
  532. UINT CPage::GetStorageName(LPOLESTR pszName)
  533.     {
  534.    #ifdef WIN32ANSI
  535.     char        szTemp[32];
  536.     UINT        cch;
  537.  
  538.     cch=wsprintf(szTemp, "Page %lu", m_dwID);
  539.     MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
  540.     return cch;
  541.    #else
  542.     return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
  543.    #endif
  544.     }
  545.  
  546.  
  547.  
  548.  
  549. /*
  550.  * CPage::Draw
  551.  *
  552.  * Purpose:
  553.  *  Draws the objects on this page to the given hDC.
  554.  *
  555.  * Parameters:
  556.  *  hDC             HDC on which to draw.
  557.  *  xOff, yOff      int offsets for the page.
  558.  *  fNoColor        BOOL indicating black & white screen rendering.
  559.  *  fPrinter        BOOL indicating hDC is on the printer.
  560.  *
  561.  * Return Value:
  562.  *  None
  563.  */
  564.  
  565. void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
  566.     , BOOL fPrinter)
  567.     {
  568.     int                 i;
  569.     PCTenant            pTenant;
  570.     HDC                 hIC=NULL;
  571.     PCOMBINEDEVICE      pcd=NULL;
  572.     DVTARGETDEVICE     *ptd=NULL;
  573.  
  574.     /*
  575.      * If printing, tell the tenant to forget the borders. Otherwise
  576.      * we leave xOff and yOff the same to account for scrolling.
  577.      */
  578.     if (fPrinter)
  579.         {
  580.         xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
  581.         yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;
  582.  
  583.         /*
  584.          * Get device information.  If this fails, ptd is
  585.          * NULL which is acceptable.
  586.          */
  587.         if (m_pPG->DevReadConfig(&pcd, &hIC))
  588.             ptd=&(pcd->td);
  589.         }
  590.  
  591.     for (i=(int)m_cTenants-1; i >=0; i--)
  592.         {
  593.         if (TenantGet(i, &pTenant, FALSE))
  594.             {
  595.             RECT        rc, rcWin;
  596.             RECTL       rcl;
  597.  
  598.             //Paint this tenant only if visible.
  599.             pTenant->RectGet(&rcl, TRUE);
  600.             RECTFROMRECTL(rc, rcl);
  601.             OffsetRect(&rc, -(int)m_pPG->m_xPos
  602.                 , -(int)m_pPG->m_yPos);
  603.             GetClientRect(m_hWnd, &rcWin);
  604.  
  605.             if (IntersectRect(&rc, &rc, &rcWin))
  606.                 {
  607.                 pTenant->Draw(hDC, ptd, hIC, xOff, yOff
  608.                     , fNoColor, fPrinter);
  609.                 }
  610.             }
  611.         }
  612.  
  613.     //Free whatever CPages::DevReadConfig returned.
  614.     if (NULL!=pcd)
  615.         {
  616.         LPMALLOC    pIMalloc;
  617.  
  618.         if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
  619.             {
  620.             pIMalloc->Free(pcd);
  621.             pIMalloc->Release();
  622.             }
  623.         }
  624.  
  625.     if (NULL!=hIC)
  626.         DeleteDC(hIC);
  627.  
  628.     return;
  629.     }
  630.  
  631.  
  632.  
  633.  
  634.  
  635.  
  636. /*
  637.  * CPage::TenantCreate
  638.  *
  639.  * Purpose:
  640.  *  Creates a new tenant of a specific type.
  641.  *
  642.  * Parameters:
  643.  *  tType           TENANTTYPE to create.
  644.  *  pv              LPVOID providing information for the new
  645.  *                  object creation.
  646.  *  pFE             LPFORMATETC describing how we want this
  647.  *                  rendered.
  648.  *  ppo             PPATRONOBJECT with placement data.
  649.  *  dwData          DWORD extra data to pass to the tenant.
  650.  *
  651.  * Return Value:
  652.  *  None
  653.  */
  654.  
  655. BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
  656.     , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
  657.     {
  658.     PCTenant    pTenant;
  659.     UINT        uRet;
  660.     int         x, y;
  661.     int         h, v;
  662.     POINTL      ptl;
  663.     SIZEL       szl;
  664.     RECTL       rcl;
  665.     RECT        rc;
  666.  
  667.     //New tenants go at top of the pile; zero index to TenantAdd.
  668.     if (!TenantAdd(0, m_dwIDNext, &pTenant))
  669.         return FALSE;
  670.  
  671.     uRet=pTenant->Create(tType, pv, pFE, &ptl, &szl, m_pIStorage
  672.         , ppo, dwData);
  673.  
  674.     if (CREATE_FAILED==uRet)
  675.         {
  676.         //Reverse Create AND TenantAdd
  677.         SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
  678.         pTenant->Destroy(m_pIStorage);
  679.  
  680.         pTenant->Release();
  681.         return FALSE;
  682.         }
  683.  
  684.     m_dwIDNext++;
  685.     m_cTenants++;
  686.  
  687.     if (NULL!=m_pTenantCur)
  688.         m_pTenantCur->Select(FALSE);
  689.  
  690.     m_iTenantCur=0;             //First one in the list now.
  691.     m_pTenantCur=pTenant;
  692.  
  693.     //Tell the tenant where it lives, default is (0,0) in print area
  694.     x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
  695.     y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;
  696.  
  697.     h=x;
  698.     v=y;
  699.  
  700.     if (CREATE_PLACEDOBJECT==uRet)
  701.         {
  702.         SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
  703.         RectConvertMappings(&rc, NULL, FALSE);
  704.  
  705.         //Make sure place point is on page, otherwise go to (0,0)
  706.         if (((int)ptl.x > x)
  707.             && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
  708.             x=(int)ptl.x;
  709.  
  710.         //m_pPG->m_cy is absolute
  711.         if (((int)ptl.y < y)
  712.             && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
  713.             y=(int)ptl.y;
  714.         }
  715.  
  716.     //Bounds check size of the object and fit to page as necessary.
  717.     if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
  718.         szl.cx=h+m_pPG->m_cx-x;
  719.  
  720.     //Remember that szl we get from Create is absolute
  721.     if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
  722.         szl.cy=-(int)(v-m_pPG->m_cy-y);
  723.  
  724.     SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
  725.     m_pTenantCur->RectSet(&rcl, FALSE, TRUE);
  726.  
  727.     //Force a repaint on this new guy
  728.     m_pTenantCur->Invalidate();
  729.     UpdateWindow(m_hWnd);
  730.  
  731.     m_pTenantCur->Select(TRUE);
  732.  
  733.     //CHAPTER20MOD
  734.     //Make sure this new tenant knows about showing it's type.
  735.     m_pTenantCur->ShowObjectType(m_pPG->m_fShowTypes);
  736.     //End CHAPTER20MOD
  737.  
  738.     //Activate new objects immediately and force a save on them
  739.     if (TENANTTYPE_EMBEDDEDOBJECT==tType)
  740.         {
  741.         m_pTenantCur->Activate(OLEIVERB_SHOW);
  742.         m_pTenantCur->Update();
  743.         }
  744.  
  745.     return TRUE;
  746.     }
  747.  
  748.  
  749.  
  750.  
  751.  
  752.  
  753. /*
  754.  * CPage::TenantDestroy
  755.  *
  756.  * Purpose:
  757.  *  Destroys the currently selected tenant on this page.
  758.  *
  759.  * Parameters:
  760.  *  None
  761.  *
  762.  * Return Value:
  763.  *  None
  764.  */
  765.  
  766. BOOL CPage::TenantDestroy(void)
  767.     {
  768.     if (NULL==m_pTenantCur)
  769.         {
  770.         MessageBeep(0);
  771.         return FALSE;
  772.         }
  773.  
  774.     SendMessage(m_hWndTenantList, LB_DELETESTRING
  775.         , m_iTenantCur, 0L);
  776.  
  777.     m_pTenantCur->Invalidate();
  778.     m_pTenantCur->Destroy(m_pIStorage);
  779.     m_pTenantCur->Release();
  780.     m_pTenantCur=NULL;
  781.  
  782.     //Update counts, etc., and select the next tenant in the list.
  783.     if (m_iTenantCur==m_cTenants-1)
  784.         m_iTenantCur--;
  785.  
  786.     if (0==--m_cTenants)
  787.         m_pTenantCur=NULL;
  788.     else
  789.         {
  790.         TenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
  791.         m_pTenantCur->Select(TRUE);
  792.         }
  793.  
  794.     UpdateWindow(m_hWnd);
  795.     return TRUE;
  796.     }
  797.  
  798.  
  799.  
  800.  
  801.  
  802. /*
  803.  * CPage::TenantClip
  804.  *
  805.  * Purpose:
  806.  *  Copies or cuts the currently selected tenant to the clipoard.
  807.  *
  808.  * Parameters:
  809.  *  fCut            BOOL TRUE to cut the object, FALSE to copy.
  810.  *
  811.  * Return Value:
  812.  *  BOOL            TRUE if successful, FALSE otherwise.
  813.  */
  814.  
  815. BOOL CPage::TenantClip(BOOL fCut)
  816.     {
  817.     LPDATAOBJECT    pIDataObject;
  818.     BOOL            fRet=FALSE;
  819.  
  820.     if (NULL==m_pTenantCur)
  821.         return FALSE;
  822.  
  823.     /*
  824.      * To perform a data transfer operation, we need to create a
  825.      * data object with the selected object's data inside. To do
  826.      * this we CoCreateInstance on CLSID_DataTransferObject
  827.      * (Also implemented in this chapter), retrieve data from the
  828.      * object we have, stuff that data into the transfer object,
  829.      * then stick that object on the clipboard.
  830.      *
  831.      * Since we'll want an identical object at other times, like for
  832.      * drag-drop, we use a private function to actually create it.
  833.      */
  834.  
  835.     pIDataObject=TransferObjectCreate(NULL);
  836.  
  837.     if (NULL!=pIDataObject)
  838.         {
  839.         if (SUCCEEDED(OleSetClipboard(pIDataObject)))
  840.             {
  841.             if (fCut)
  842.                 TenantDestroy();
  843.  
  844.             fRet=TRUE;
  845.             }
  846.  
  847.         pIDataObject->Release();
  848.         }
  849.  
  850.     return fRet;
  851.     }
  852.  
  853.  
  854.  
  855.  
  856.  
  857. /*
  858.  * CPage::FQueryObjectSelected
  859.  *
  860.  * Purpose:
  861.  *  Returns whether or not there is an object selected on this
  862.  *  page for Cut, Copy, Delete functions.
  863.  *
  864.  * Parameters:
  865.  *  hMenu           HMENU of the Edit menu.
  866.  *
  867.  * Return Value:
  868.  *  BOOL            TRUE if we have an object, FALSE otherwise.
  869.  */
  870.  
  871. BOOL CPage::FQueryObjectSelected(HMENU hMenu)
  872.     {
  873.     HMENU       hMenuTemp;
  874.  
  875.     /*
  876.      * This will only be called on WM_INITMENUPOPUP, we'll also
  877.      * use this function to create the Verb menu for this object.
  878.      */
  879.     if (NULL!=m_pTenantCur)
  880.         {
  881.         m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
  882.         return TRUE;
  883.         }
  884.  
  885.     OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
  886.         , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);
  887.  
  888.     return FALSE;
  889.     }
  890.  
  891.  
  892.  
  893.  
  894. /*
  895.  * CPage::ActivateObject
  896.  *
  897.  * Purpose:
  898.  *  Executes a verb on the currently selected object.
  899.  *
  900.  * Parameters:
  901.  *  iVerb           LONG of the selected verb.
  902.  *
  903.  * Return Value:
  904.  *  None
  905.  */
  906.  
  907. void CPage::ActivateObject(LONG iVerb)
  908.     {
  909.     if (NULL==m_pTenantCur)
  910.         return;
  911.  
  912.     m_pTenantCur->Activate(iVerb);
  913.     return;
  914.     }
  915.  
  916.  
  917.  
  918. //CHAPTER20MOD
  919.  
  920. /*
  921.  * CPage::ShowObjectTypes
  922.  *
  923.  * Purpose:
  924.  *  Loops through all the tenants and tells each one to turn on or
  925.  *  off the Show Objects features.
  926.  *
  927.  * Parameters:
  928.  *  fShow           BOOL indicating to show the type or not.
  929.  *
  930.  * Return Value:
  931.  *  None
  932.  */
  933.  
  934. void CPage::ShowObjectTypes(BOOL fShow)
  935.     {
  936.     PCTenant    pTenant;
  937.     UINT        i;
  938.  
  939.     for (i=0; i < m_cTenants; i++)
  940.         {
  941.         if (TenantGet(i, &pTenant, FALSE))
  942.             pTenant->ShowObjectType(fShow);
  943.         }
  944.  
  945.     return;
  946.     }
  947.  
  948.  
  949.  
  950.  
  951. /*
  952.  * CPage::NotifyTenantsOfRename
  953.  *
  954.  * Purpose:
  955.  *  Loops through all the tenants and informs each of the new
  956.  *  document name.
  957.  *
  958.  * Parameters:
  959.  *  pszFile         LPTSTR of the new filename.
  960.  *  pmk             LPMONKIER for the new filename.
  961.  *
  962.  * Return Value:
  963.  *  None
  964.  */
  965.  
  966. void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
  967.     {
  968.     PCTenant    pTenant;
  969.     UINT        i;
  970.  
  971.     for (i=0; i < m_cTenants; i++)
  972.         {
  973.         if (TenantGet(i, &pTenant, FALSE))
  974.             pTenant->NotifyOfRename(pszFile, pmk);
  975.         }
  976.  
  977.     return;
  978.     }
  979.  
  980.  
  981. /*
  982.  * CPage::ConvertObject
  983.  *
  984.  * Purpose:
  985.  *  Invokes and handles the results of the Convert dialog
  986.  *
  987.  * Parameters:
  988.  *  hWndFrame       HWND to use as the parent of the dialog.
  989.  *  fNoServer       BOOL indicating if this was called because
  990.  *                  ActivateObject failed.
  991.  *
  992.  * Return Value:
  993.  *  None
  994.  */
  995.  
  996. BOOL CPage::ConvertObject(HWND hWndFrame, BOOL fNoServer)
  997.     {
  998.     HRESULT         hr;
  999.     OLEUICONVERT    ct;
  1000.     TENANTTYPE      tType;
  1001.     TENANTINFO      ti;
  1002.     UINT            uRet;
  1003.     HCURSOR         hCur;
  1004.     BOOL            fActivate=fNoServer;
  1005.     SIZEL           szl;
  1006.  
  1007.     if (NULL==m_pTenantCur)
  1008.         return FALSE;
  1009.  
  1010.     tType=m_pTenantCur->TypeGet();
  1011.  
  1012.     if (TENANTTYPE_STATIC==tType)
  1013.         {
  1014.         MessageBeep(0);
  1015.         return FALSE;
  1016.         }
  1017.  
  1018.     //Get object information we may want.
  1019.     m_pTenantCur->GetInfo(&ti);
  1020.  
  1021.     //Fill the structure.
  1022.     memset(&ct, 0, sizeof(ct));
  1023.     ct.cbStruct=sizeof(OLEUICONVERT);
  1024.     ct.hWndOwner=hWndFrame;
  1025.     //CHAPTER20MOD
  1026.     ct.fIsLinkedObject=(TENANTTYPE_LINKEDOBJECT==tType);
  1027.     //End CHAPTER20MOD
  1028.     ct.dvAspect=ti.fe.dwAspect;
  1029.  
  1030.     m_pTenantCur->ObjectClassFormatAndIcon(&ct.clsid, &ct.wFormat
  1031.         , &ct.lpszUserType, &ct.hMetaPict, &ct.lpszDefLabel);
  1032.  
  1033.     uRet=OleUIConvert(&ct);
  1034.  
  1035.     if (OLEUI_OK==uRet)
  1036.         {
  1037.         //Potentially a long operation.
  1038.         hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));
  1039.  
  1040.         //Prevent multiple repaints.
  1041.         m_pTenantCur->EnableRepaint(FALSE);
  1042.  
  1043.         //First, let's bother with the iconic aspect switch.
  1044.         if ((DVASPECT_ICON==ct.dvAspect && ct.fObjectsIconChanged)
  1045.             || ct.dvAspect!=ti.fe.dwAspect)
  1046.             {
  1047.             HGLOBAL     hMem=NULL;
  1048.  
  1049.             //Only pass non-NULL handle for icon aspects.
  1050.             if (DVASPECT_ICON==ct.dvAspect)
  1051.                 hMem=ct.hMetaPict;
  1052.  
  1053.             m_pPG->m_fDirty=m_pTenantCur->SwitchOrUpdateAspect(hMem
  1054.                 , FALSE);
  1055.             }
  1056.  
  1057.         //Now change types around.
  1058.         if ((CF_SELECTCONVERTTO & ct.dwFlags)
  1059.             && ct.clsid!=ct.clsidNew)
  1060.             {
  1061.             LPSTORAGE   pIStorage;
  1062.  
  1063.             /*
  1064.              * User selected convert, so:
  1065.              *  1.  Unload the object (back to passive state)
  1066.              *  2.  Call INOLE_DoConvert, which calls WriteClassStg,
  1067.              *      WriteFmtUserTypeStg, and SetConvertStg.
  1068.              *  3.  Reload the object and force an update.
  1069.              */
  1070.  
  1071.             //This should be the only close necessary.
  1072.             m_pTenantCur->StorageGet(&pIStorage);
  1073.             m_pTenantCur->Close(TRUE);
  1074.  
  1075.             hr=INOLE_DoConvert(pIStorage, ct.clsidNew);
  1076.  
  1077.             //Need to commit the new type and format
  1078.             pIStorage->Commit(STGC_DEFAULT);
  1079.             pIStorage->Release();
  1080.  
  1081.             if (SUCCEEDED(hr))
  1082.                 {
  1083.                 LPUNKNOWN   pObj;
  1084.                 LPOLEOBJECT pIOleObject;
  1085.  
  1086.                 //Reload and update.
  1087.                 m_pTenantCur->Load(m_pIStorage, &ti);
  1088.  
  1089.                 m_pTenantCur->ObjectGet(&pObj);
  1090.                 pObj->QueryInterface(IID_IOleObject
  1091.                     , (PPVOID)&pIOleObject);
  1092.                 pIOleObject->Update();
  1093.                 pIOleObject->Release();
  1094.                 pObj->Release();
  1095.                 }
  1096.  
  1097.             m_pPG->m_fDirty=TRUE;
  1098.             }
  1099.  
  1100.  
  1101.         if (CF_SELECTACTIVATEAS & ct.dwFlags)
  1102.             {
  1103.             /*
  1104.              * User selected Activate As, so:
  1105.              *  1.  Add the TreatAs entry in the registry
  1106.              *      through CoTreatAsClass
  1107.              *  2.  Unload all objects of the old CLSID that you
  1108.              *      have loaded.
  1109.              *  3.  Reload objects as desired
  1110.              *  4.  Activate the current object.
  1111.              */
  1112.  
  1113.             hr=CoTreatAsClass(ct.clsid, ct.clsidNew);
  1114.  
  1115.             if (SUCCEEDED(hr))
  1116.                 {
  1117.                 PCTenant    pTenant;
  1118.                 UINT        i;
  1119.  
  1120.                 for (i=0; i < m_cTenants; i++)
  1121.                     {
  1122.                     if (TenantGet(i, &pTenant, FALSE))
  1123.                         {
  1124.                         pTenant->GetInfo(&ti);
  1125.                         pTenant->Close(FALSE);
  1126.                         pTenant->Load(m_pIStorage, &ti);
  1127.                         }
  1128.                     }
  1129.  
  1130.                 fActivate=TRUE;
  1131.                 }
  1132.             }
  1133.  
  1134.         //These two steps insure the object knows of the size.
  1135.         m_pTenantCur->SizeGet(&szl, FALSE);
  1136.         m_pTenantCur->SizeSet(&szl, FALSE, TRUE);
  1137.  
  1138.         m_pTenantCur->EnableRepaint(TRUE);
  1139.         m_pTenantCur->Repaint();
  1140.  
  1141.         if (fActivate)
  1142.             m_pTenantCur->Activate(OLEIVERB_SHOW);
  1143.  
  1144.         SetCursor(hCur);
  1145.         }
  1146.  
  1147.     CoTaskMemFree((void*)ct.lpszUserType);
  1148.     INOLE_MetafilePictIconFree(ct.hMetaPict);
  1149.  
  1150.     return TRUE;
  1151.     }
  1152.  
  1153.  
  1154.  
  1155.  
  1156.  
  1157.  
  1158. //CHAPTER17MOD
  1159.  
  1160. /*
  1161.  * CPage::FQueryLinksInPage
  1162.  *
  1163.  * Purpose:
  1164.  *  Pass through to current page to see if there are any linked
  1165.  *  objects.
  1166.  *
  1167.  * Parameters:
  1168.  *  None
  1169.  *
  1170.  * Return Value:
  1171.  *  None
  1172.  */
  1173.  
  1174. BOOL CPage::FQueryLinksInPage()
  1175.     {
  1176.     PCTenant    pTenant;
  1177.     UINT        i;
  1178.     BOOL        fRet=FALSE;
  1179.  
  1180.     for (i=0; i < m_cTenants; i++)
  1181.         {
  1182.         if (TenantGet(i, &pTenant, FALSE))
  1183.             fRet |= (pTenant->TypeGet()==TENANTTYPE_LINKEDOBJECT);
  1184.         }
  1185.  
  1186.     return fRet;
  1187.     }
  1188. //End CHAPTER20MOD
  1189.  
  1190.  
  1191.  
  1192.  
  1193.  
  1194. /*
  1195.  * CPage::TenantGet
  1196.  * (Protected)
  1197.  *
  1198.  * Purpose:
  1199.  *  Returns a tenant of a given index returning a BOOL so it's
  1200.  *  simple to use this function inside if statements.
  1201.  *
  1202.  * Parameters:
  1203.  *  iTenant         UINT tenant to retrieve 0 based.
  1204.  *  ppTenant        PCPage * in which to return the tenant
  1205.  *                  pointer
  1206.  *  fOpen           BOOL indicating if we should open this
  1207.  *                  tenant as well.
  1208.  *
  1209.  * Return Value:
  1210.  *  BOOL            TRUE if successful, FALSE otherwise.
  1211.  */
  1212.  
  1213. BOOL CPage::TenantGet(UINT iTenant, PCTenant *ppTenant
  1214.     , BOOL fOpen)
  1215.     {
  1216.     if (NULL==ppTenant)
  1217.         return FALSE;
  1218.  
  1219.     if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
  1220.         , iTenant, (LONG)ppTenant))
  1221.         {
  1222.         if (fOpen)
  1223.             (*ppTenant)->Open(m_pIStorage);
  1224.  
  1225.         return TRUE;
  1226.         }
  1227.  
  1228.     return FALSE;
  1229.     }
  1230.  
  1231.  
  1232.  
  1233.  
  1234.  
  1235.  
  1236.  
  1237. /*
  1238.  * CPage::TenantAdd
  1239.  * (Protected)
  1240.  *
  1241.  * Purpose:
  1242.  *  Creates a new tenant initialized to the given values.  The new
  1243.  *  tenants's storage is created if it does not already exist.  If
  1244.  *  fOpenStorage is set the the tenants's storage is opened and left
  1245.  *  opened.
  1246.  *
  1247.  * Parameters:
  1248.  *  iTenant         UINT Location at which to insert tenant; new
  1249.  *                  tenant is inserted after this position.  NOVALUE
  1250.  *                  for the end.
  1251.  *  dwID            DWORD ID for this tenant.
  1252.  *  ppNew           PCTenant * in which to store the new tenant.
  1253.  *
  1254.  * Return Value:
  1255.  *  BOOL            TRUE if the function succeeded, FALSE otherwise.
  1256.  */
  1257.  
  1258. BOOL CPage::TenantAdd(UINT iTenant, DWORD dwID
  1259.     , PCTenant *ppNew)
  1260.     {
  1261.     PCTenant    pTenant;
  1262.     LRESULT     lr;
  1263.  
  1264.     if (NULL!=ppNew)
  1265.         *ppNew=NULL;
  1266.  
  1267.     pTenant=new CTenant(dwID, m_hWnd, m_pPG);
  1268.  
  1269.     if (NULL==pTenant)
  1270.         return FALSE;
  1271.  
  1272.     //The constructor doesn't AddRef, so we need to.
  1273.     pTenant->AddRef();
  1274.  
  1275.     //Now try to add to the listbox.
  1276.     lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
  1277.         , (LONG)pTenant);
  1278.  
  1279.     if (lr < 0)
  1280.         {
  1281.         pTenant->Release();
  1282.         return FALSE;
  1283.         }
  1284.  
  1285.     *ppNew=pTenant;
  1286.     return TRUE;
  1287.     }
  1288.  
  1289.  
  1290.  
  1291.  
  1292.  
  1293. /*
  1294.  * CPage::TransferObjectCreate
  1295.  * (Protected)
  1296.  *
  1297.  * Purpose:
  1298.  *  Creates a DataTransferObject and stuff the current selection's
  1299.  *  data into it.
  1300.  *
  1301.  * Parameters:
  1302.  *  pptl            PPOINTL containing the pick point in device
  1303.  *                  units applicable only to drag-drop; since
  1304.  *                  drag-drop is inherently mouse oriented, we use
  1305.  *                  device units for the point.  Ignored if NULL.
  1306.  *
  1307.  * Return Value:
  1308.  *  LPDATAOBJECT    Pointer to the object created, NULL on failure
  1309.  */
  1310.  
  1311. LPDATAOBJECT CPage::TransferObjectCreate(PPOINTL pptl)
  1312.     {
  1313.     LPDATAOBJECT    pIDataObject;
  1314.     LPDATAOBJECT    pIDataT;
  1315.     PPATRONOBJECT   ppo;
  1316.     RECTL           rcl;
  1317.     LPUNKNOWN       pObj;
  1318.     FORMATETC       fe;
  1319.     STGMEDIUM       stm;
  1320.     HRESULT         hr;
  1321.  
  1322.     m_pTenantCur->ObjectGet(&pObj);
  1323.  
  1324.     hr=CoCreateInstance(CLSID_DataTransferObject, NULL
  1325.         , CLSCTX_INPROC_SERVER, IID_IDataObject
  1326.         , (PPVOID)&pIDataObject);
  1327.  
  1328.     if (FAILED(hr))
  1329.         return NULL;
  1330.  
  1331.     //Go get the data we should hold on to.
  1332.     hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);
  1333.  
  1334.     if (FAILED(hr))
  1335.         {
  1336.         pIDataObject->Release();
  1337.         pObj->Release();
  1338.         return NULL;
  1339.         }
  1340.  
  1341.     //Copy from known obj into transfer obj.  Ordering is important!
  1342.  
  1343.     //Generate placeable object structure
  1344.     stm.tymed=TYMED_HGLOBAL;
  1345.     stm.pUnkForRelease=NULL;
  1346.     stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));
  1347.  
  1348.     if (NULL==stm.hGlobal)
  1349.         {
  1350.         pIDataObject->Release();
  1351.         pObj->Release();
  1352.         return NULL;
  1353.         }
  1354.  
  1355.     ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);
  1356.  
  1357.     m_pTenantCur->SizeGet(&ppo->szl, FALSE);
  1358.     ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size
  1359.  
  1360.     m_pTenantCur->RectGet(&rcl, FALSE);
  1361.     ppo->ptl.x=rcl.left;
  1362.     ppo->ptl.y=rcl.top;
  1363.  
  1364.     if (NULL==pptl)
  1365.         {
  1366.         ppo->ptlPick.x=0;
  1367.         ppo->ptlPick.y=0;
  1368.         }
  1369.     else
  1370.         ppo->ptlPick=*pptl;
  1371.  
  1372.     m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);
  1373.  
  1374.     //CHAPTER20MOD
  1375.     //If this is a linked object, just copy a presentation
  1376.     if (TENANTTYPE_LINKEDOBJECT==m_pTenantCur->TypeGet())
  1377.         m_pTenantCur->FormatEtcGet(&ppo->fe, TRUE);
  1378.     //End CHAPTER20MOD
  1379.  
  1380.     SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
  1381.     pIDataObject->SetData(&fe, &stm, TRUE);
  1382.  
  1383.     /*
  1384.      * Here now we have to include CFSTR_EMBEDDEDOBJECT and
  1385.      * CFSTR_OBJECTDESCRIPTOR when what we have selected is, in
  1386.      * fact, a compound document object.  We'll just ask the tenant
  1387.      * to set these in pIDataObject since it knows what the object.
  1388.      * If we copy embedded object data, make sure the PATRONOBJECT
  1389.      * structure has the right format in it as well.
  1390.      */
  1391.     m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);
  1392.  
  1393.     GlobalUnlock(stm.hGlobal);
  1394.  
  1395.     //Copy the actual presentation.
  1396.     m_pTenantCur->FormatEtcGet(&fe, TRUE);
  1397.     pIDataT->GetData(&fe, &stm);
  1398.     pIDataObject->SetData(&fe, &stm, TRUE);
  1399.  
  1400.     pIDataT->Release();
  1401.  
  1402.     pObj->Release();
  1403.     return pIDataObject;    //Caller now responsible
  1404.     }
  1405.