/*
 * DRAWICON.C
 *
 * Functions to handle creation of metafiles with icons and labels
 * as well as functions to draw such metafiles with or without the label.
 *
 * The metafile is created with a comment that marks the records containing
 * the label code.  Drawing the metafile enumerates the records, draws
 * all records up to that point, then decides to either skip the label
 * or draw it.
 *
 * Copyright (c)1992 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1
#include "ole2ui.h"
#include "common.h"
#include "utility.h"
#include "geticon.h"

/*
 * Strings for metafile comments.  KEEP THESE IN SYNC WITH THE
 * STRINGS IN GETICON.C.
 */

static char szIconOnly[]="IconOnly";        //Where to stop to exclude label.




/*
 * OleUIMetafilePictIconFree
 *
 * Purpose:
 *  Deletes the metafile contained in a METAFILEPICT structure and
 *  frees the memory for the structure itself.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL metafilepict structure created in
 *                  OleUIMetafilePictFromIconAndLabel
 *
 * Return Value:
 *  None
 */

STDAPI_(void) OleUIMetafilePictIconFree(HGLOBAL hMetaPict)
   {
   LPMETAFILEPICT      pMF;

   if (NULL==hMetaPict)
      return;

   pMF=(LPMETAFILEPICT)GlobalLock(hMetaPict);

   if (NULL!=pMF)
      {
      if (NULL!=pMF->hMF)
         DeleteMetaFile(pMF->hMF);
      }

   GlobalUnlock(hMetaPict);
   GlobalFree(hMetaPict);
   return;
   }








/*
 * OleUIMetafilePictIconDraw
 *
 * Purpose:
 *  Draws the metafile from OleUIMetafilePictFromIconAndLabel, either with
 *  the label or without.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  pRect           LPRECT in which to draw the metafile.
 *  hMetaPict       HGLOBAL to the METAFILEPICT from
 *                  OleUIMetafilePictFromIconAndLabel
 *  fIconOnly       BOOL specifying to draw the label or not.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE if the
 *                  given metafilepict is invalid.
 */

STDAPI_(BOOL) OleUIMetafilePictIconDraw(HDC hDC, LPRECT pRect, HGLOBAL hMetaPict
                             , BOOL fIconOnly)
   {
   LPMETAFILEPICT  pMF;
   DRAWINFO        di;
   int             cx, cy;
   SIZE            size;
   POINT           point;

   if (NULL==hMetaPict)
      return FALSE;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   di.Rect = *pRect;
   di.fIconOnly = fIconOnly;

   //Transform to back to pixels
   cx=XformWidthInHimetricToPixels(hDC, pMF->xExt);
   cy=XformHeightInHimetricToPixels(hDC, pMF->yExt);

   SaveDC(hDC);

   SetMapMode(hDC, pMF->mm);
   SetViewportOrgEx(hDC, (pRect->right - cx) / 2, 0, &point);

   SetViewportExtEx(hDC, min ((pRect->right - cx) / 2 + cx, cx), cy, &size);

   if (fIconOnly)
      {
      // Since we've used the __export keyword on the
      // EnumMetafileIconDraw proc, we do not need to use
      // MakeProcInstance
      EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileIconDraw
         , (LPARAM)(LPDRAWINFO)&di);
      }
   else
      PlayMetaFile(hDC, pMF->hMF);

   RestoreDC(hDC, -1);

   GlobalUnlock(hMetaPict);
   return TRUE;
   }




/*
 * EnumMetafileIconDraw
 *
 * Purpose:
 *  EnumMetaFile callback function that draws either the icon only or
 *  the icon and label depending on given flags.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  lParam          LPARAM flags passed in EnumMetaFile.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */

int CALLBACK EXPORT EnumMetafileIconDraw(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPARAM lParam)
   {
   LPDRAWINFO lpdi = (LPDRAWINFO)lParam;

   /*
    * We play everything blindly except for DIBBITBLT (or DIBSTRETCHBLT)
    * and ESCAPE with MFCOMMENT.  For the BitBlts we change the x,y to
    * draw at (0,0) instead of wherever it was written to draw.  The
    * comment tells us there to stop if we don't want to draw the label.
    */

   //If we're playing icon only, stop enumeration at the comment.
   if (lpdi->fIconOnly)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         if (0==lstrcmpi(szIconOnly, (LPSTR)&pMFR->rdParm[2]))
            return 0;
         }

      /*
       * Check for the records in which we want to munge the coordinates.
       * destX is offset 6 for BitBlt, offset 9 for StretchBlt, either of
       * which may appear in the metafile.
       */
      if (META_DIBBITBLT==pMFR->rdFunction)
         pMFR->rdParm[6]=0;

      if (META_DIBSTRETCHBLT==pMFR->rdFunction)
           pMFR->rdParm[9] = 0;

      }


   PlayMetaFileRecord(hDC, phTable, pMFR, cObj);
   return 1;
   }





/*
 * OleUIMetafilePictExtractLabel
 *
 * Purpose:
 *  Retrieves the label string from metafile representation of an icon.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL to the METAFILEPICT containing the metafile.
 *  lpszLabel       LPSTR in which to store the label.
 *  cchLabel        UINT length of lpszLabel.
 *  lpWrapIndex     DWORD index of first character in last line. Can be NULL
 *                  if calling function doesn't care about word wrap.
 *
 * Return Value:
 *  UINT            Number of characters copied.
 */
STDAPI_(UINT) OleUIMetafilePictExtractLabel(HGLOBAL hMetaPict, LPOLESTR lpszLabel
                                , UINT cchLabel, LPDWORD lpWrapIndex)
   {
   LPMETAFILEPICT  pMF;
   LABELEXTRACT    le;
   HDC             hDC;

   /*
    * We extract the label by getting a screen DC and walking the metafile
    * records until we see the ExtTextOut record we put there.  That
    * record will have the string embedded in it which we then copy out.
    */

   if (NULL==hMetaPict || NULL==lpszLabel || 0==cchLabel)
      return FALSE;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   le.lpsz=lpszLabel;
   le.u.cch=cchLabel;
   le.Index=0;
   le.fFoundIconOnly=FALSE;
   le.fFoundSource=FALSE;  //Unused for this function.
   le.fFoundIndex=FALSE;   //Unused for this function.
   le.PrevIndex = 0;

   //Use a screen DC so we have something valid to pass in.
   hDC=GetDC(NULL);

   // Since we've used the EXPORT keyword on the
   // EnumMetafileExtractLabel proc, we do not need to use
   // MakeProcInstance

   EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileExtractLabel, (LONG)(LPLABELEXTRACT)&le);

   ReleaseDC(NULL, hDC);

   GlobalUnlock(hMetaPict);

   //Tell where we wrapped (if calling function cares)
   if (NULL != lpWrapIndex)
      *lpWrapIndex = le.PrevIndex;

   //Return amount of text copied
   return le.u.cch;
   }





/*
 * EnumMetafileExtractLabel
 *
 * Purpose:
 *  EnumMetaFile callback function that walks a metafile looking for
 *  ExtTextOut, then concatenates the text from each one into a buffer
 *  in lParam.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  pLE             LPLABELEXTRACT providing the destination buffer and length.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */

int CALLBACK EXPORT EnumMetafileExtractLabel(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPLABELEXTRACT pLE)
   {

   /*
    * We don't allow anything to happen until we see "IconOnly"
    * in an MFCOMMENT that is used to enable everything else.
    */
   if (!pLE->fFoundIconOnly)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         if (0==lstrcmpi(szIconOnly, (LPSTR)&pMFR->rdParm[2]))
            pLE->fFoundIconOnly=TRUE;
         }

      return 1;
      }

   //Enumerate all records looking for META_EXTTEXTOUT - there can be more
   //than one.
   if (META_EXTTEXTOUT==pMFR->rdFunction)
      {
      UINT        cchMax;
      LPOLESTR    lpszTemp;

       /*
       * If ExtTextOut has NULL fuOptions, then the rectangle is omitted
       * from the record, and the string starts at rdParm[4].  If
       * fuOptions is non-NULL, then the string starts at rdParm[8]
       * (since the rectange takes up four WORDs in the array).  In
       * both cases, the string continues for (rdParm[2]+1) >> 1
       * words.  We just cast a pointer to rdParm[8] to an LPSTR and
       * lstrcpyn into the buffer we were given.
       *
       * Note that we use element 8 in rdParm instead of 4 because we
       * passed ETO_CLIPPED in for the options on ExtTextOut--docs say
       * [4] which is rect doesn't exist if we passed zero there.
       *
       */

      cchMax=min(pLE->u.cch - pLE->Index, (UINT)pMFR->rdParm[2]);
      lpszTemp = (LPOLESTR)(((LPSTR)pLE->lpsz) + pLE->Index);

      A2W ((LPSTR)&(pMFR->rdParm[8]), lpszTemp, cchMax);
      lpszTemp[cchMax]='\0';

      pLE->u.cch = OLESTRLEN (pLE->lpsz);
      pLE->PrevIndex = pLE->Index;
      pLE->Index += cchMax;
      }

   return 1;
   }





/*
 * OleUIMetafilePictExtractIcon
 *
 * Purpose:
 *  Retrieves the icon from metafile into which DrawIcon was done before.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL to the METAFILEPICT containing the metafile.
 *
 * Return Value:
 *  HICON           Icon recreated from the data in the metafile.
 */
STDAPI_(HICON) OleUIMetafilePictExtractIcon(HGLOBAL hMetaPict)
   {
   LPMETAFILEPICT  pMF;
   HDC             hDC;
   ICONEXTRACT     ie;

   /*
    * We extract the label by getting a screen DC and walking the metafile
    * records until we see the ExtTextOut record we put there.  That
    * record will have the string embedded in it which we then copy out.
    */

   if (NULL==hMetaPict)
      return NULL;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   //Use a screen DC so we have something valid to pass in.
   hDC=GetDC(NULL);
   ie.fAND=TRUE;

   // We get information back in the ICONEXTRACT structure.
   // (Since we've used the EXPORT keyword on the
   // EnumMetafileExtractLabel proc, we do not need to use
   // MakeProcInstance)
   EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileExtractIcon, (LONG)(LPICONEXTRACT)&ie);

   ReleaseDC(NULL, hDC);
   GlobalUnlock(hMetaPict);

   return ie.hIcon;
   }





/*
 * EnumMetafileExtractIcon
 *
 * Purpose:
 *  EnumMetaFile callback function that walks a metafile looking for
 *  StretchBlt (3.1) and BitBlt (3.0) records.  We expect to see two
 *  of them, the first being the AND mask and the second being the XOR
 *  data.  We
 *  ExtTextOut, then copies the text into a buffer in lParam.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  pIE             LPICONEXTRACT providing the destination buffer and length.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */

int CALLBACK EXPORT EnumMetafileExtractIcon(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPICONEXTRACT pIE)
   {
   LPBITMAPINFO        lpBI;
   LPBITMAPINFOHEADER  lpBH;
   LPBYTE              lpbSrc;
   LPBYTE              lpbDst;
   UINT                uWidth, uHeight;
   DWORD               cb;
   HGLOBAL             hMem;
   BITMAP              bm;
   HBITMAP             hBmp;
   int                 cxIcon, cyIcon;


   //Continue enumeration if we don't see the records we want.
   if (META_DIBBITBLT!=pMFR->rdFunction && META_DIBSTRETCHBLT!=pMFR->rdFunction)
      return 1;

   /*
    * Windows 3.0 DrawIcon uses META_DIBBITBLT in whereas 3.1 uses
    * META_DIBSTRETCHBLT so we have to handle each case separately.
    */

   if (META_DIBBITBLT==pMFR->rdFunction)       //Win3.0
      {
      //Get dimensions and the BITMAPINFO struct.
      uHeight=pMFR->rdParm[1];
      uWidth =pMFR->rdParm[2];
      lpBI=(LPBITMAPINFO)&(pMFR->rdParm[8]);
      }

   if (META_DIBSTRETCHBLT==pMFR->rdFunction)   //Win3.1
      {
      //Get dimensions and the BITMAPINFO struct.
      uHeight=pMFR->rdParm[2];
      uWidth =pMFR->rdParm[3];
      lpBI=(LPBITMAPINFO)&(pMFR->rdParm[10]);
      }

   lpBH=(LPBITMAPINFOHEADER)&(lpBI->bmiHeader);

   //Pointer to the bits which follows the BITMAPINFO structure.
   lpbSrc=(LPBYTE)lpBI+lpBH->biSize;

   //Add the length of the color table.
   if (0!=lpBH->biClrUsed)
      lpbSrc+=(DWORD)(lpBH->biClrUsed*sizeof(RGBQUAD));
   else
      {
      /*
       * 1 << bc gives 2, 16, 256 for 1, 4, or 8 bits.  24-bit
       * bitmaps have no color table, so there's no need to
       * change lpbSrc.
       */
      if (lpBH->biBitCount <= 8)
          lpbSrc+=(DWORD)((1 << (lpBH->biBitCount))*sizeof(RGBQUAD));
      }


   /*
    * All the bits we have in lpbSrc are device-independent, so we
    * need to change them over to be device-dependent using SetDIBits.
    * Once we have a bitmap with the device-dependent bits, we can
    * GetBitmapBits to have buffers with the real data.
    *
    * For each pass we have to allocate memory for the bits.  We save
    * the memory for the mask between passes.
    */

   //Use CreateBitmap for ANY monochrome bitmaps
   if (pIE->fAND || 1==lpBH->biBitCount || lpBH->biBitCount > 8)
      hBmp=CreateBitmap((UINT)lpBH->biWidth, (UINT)lpBH->biHeight, 1, 1, NULL);
   else if (lpBH->biBitCount <= 8)
      hBmp=CreateCompatibleBitmap(hDC, (UINT)lpBH->biWidth, (UINT)lpBH->biHeight);

   if (!hBmp || !SetDIBits(hDC, hBmp, 0, (UINT)lpBH->biHeight, (LPVOID)lpbSrc, lpBI, DIB_RGB_COLORS))
      {
      if (!pIE->fAND)
         GlobalFree(pIE->hMemAND);

      DeleteObject(hBmp);
      return 0;
      }

   //Allocate memory and get the DDBits into it.
   GetObject(hBmp, sizeof(bm), &bm);

   cb=bm.bmHeight*bm.bmWidthBytes * bm.bmPlanes;

//    if (cb % 4 != 0)        // dword align
//      cb += 4 - (cb % 4);

   hMem=GlobalAlloc(GHND, cb);

   if (NULL==hMem)
      {
      if (NULL!=pIE->hMemAND)
         GlobalFree(pIE->hMemAND);

      DeleteObject(hBmp);
      return 0;
      }

   lpbDst=(LPBYTE)GlobalLock(hMem);
   GetBitmapBits(hBmp, cb, (LPVOID)lpbDst);

   DeleteObject(hBmp);
   GlobalUnlock(hMem);


   /*
    * If this is the first pass (pIE->fAND==TRUE) then save the memory
    * of the AND bits for the next pass.
    */
   if (pIE->fAND)
      {
      pIE->fAND=FALSE;
      pIE->hMemAND=hMem;

      //Continue enumeration looking for the next blt record.
      return 1;
      }
   else
      {
      //Get the AND pointer again.
      lpbSrc=(LPBYTE)GlobalLock(pIE->hMemAND);

      /*
       * Create the icon now that we have all the data.  lpbDst already
       * points to the XOR bits.
       */
      cxIcon = GetSystemMetrics(SM_CXICON);
      cyIcon = GetSystemMetrics(SM_CYICON);

      pIE->hIcon=CreateIcon(ghInst,
                       uWidth,
                       uHeight,
                       (BYTE)bm.bmPlanes,
                       (BYTE)bm.bmBitsPixel,
                       (LPVOID)lpbSrc,
                       (LPVOID)lpbDst);

      GlobalUnlock(pIE->hMemAND);
      GlobalFree(pIE->hMemAND);
      GlobalFree(hMem);

      //We're done so we can stop.
      return 0;
      }
   }





/*
 * OleUIMetafilePictExtractIconSource
 *
 * Purpose:
 *  Retrieves the filename and index of the icon source from a metafile
 *  created with OleUIMetafilePictFromIconAndLabel.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL to the METAFILEPICT containing the metafile.
 *  lpszSource      LPSTR in which to store the source filename.  This
 *                  buffer should be OLEUI_CCHPATHMAX characters.
 *  piIcon          UINT FAR * in which to store the icon's index
 *                  within lpszSource
 *
 * Return Value:
 *  BOOL            TRUE if the records were found, FALSE otherwise.
 */
STDAPI_(BOOL) OleUIMetafilePictExtractIconSource(HGLOBAL hMetaPict
   , LPOLESTR lpszSource, UINT FAR *piIcon)
   {
   LPMETAFILEPICT  pMF;
   LABELEXTRACT    le;
   HDC             hDC;

   /*
    * We will walk the metafile looking for the two comment records
    * following the IconOnly comment.  The flags fFoundIconOnly and
    * fFoundSource indicate if we have found IconOnly and if we have
    * found the source comment already.
    */

   if (NULL==hMetaPict || NULL==lpszSource || NULL==piIcon)
      return FALSE;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   le.lpsz=lpszSource;
   le.fFoundIconOnly=FALSE;
   le.fFoundSource=FALSE;
   le.fFoundIndex=FALSE;

   //Use a screen DC so we have something valid to pass in.
   hDC=GetDC(NULL);

   EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileExtractIconSource, (LONG)(LPLABELEXTRACT)&le);

   ReleaseDC(NULL, hDC);
   GlobalUnlock(hMetaPict);

   //Copy the icon index to the caller's variable.
   *piIcon=le.u.iIcon;

   //Check that we found everything.
   return (le.fFoundIconOnly && le.fFoundSource && le.fFoundIndex);
   }


/*
 * EnumMetafileExtractIconSource
 *
 * Purpose:
 *  EnumMetaFile callback function that walks a metafile skipping the first
 *  comment record, extracting the source filename from the second, and
 *  the index of the icon in the third.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  pLE             LPLABELEXTRACT providing the destination buffer and
 *                  area to store the icon index.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */
int CALLBACK EXPORT EnumMetafileExtractIconSource(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPLABELEXTRACT pLE)
   {
   LPSTR       psz;

   /*
    * We don't allow anything to happen until we see "IconOnly"
    * in an MFCOMMENT that is used to enable everything else.
    */
   if (!pLE->fFoundIconOnly)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         if (0==lstrcmpi(szIconOnly, (LPSTR)&pMFR->rdParm[2]))
            pLE->fFoundIconOnly=TRUE;
         }

      return 1;
      }

   //Now see if we find the source string.
   if (!pLE->fFoundSource)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         A2W  ((LPSTR)&pMFR->rdParm[2], pLE->lpsz, OLEUI_CCHPATHMAX);
         pLE->lpsz[OLEUI_CCHPATHMAX-1] = '\0';
         pLE->fFoundSource=TRUE;
         }

      return 1;
      }

   //Next comment will be the icon index.
   if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
      {
      /*
       * This string contains the icon index in string form,
       * so we need to convert back to a UINT.  After we see this
       * we can stop the enumeration.  The comment will have
       * a null terminator because we made sure to save it.
       */
      psz=(LPSTR)&pMFR->rdParm[2];
      pLE->u.iIcon=0;

      //Do Ye Olde atoi
      while (*psz)
         pLE->u.iIcon=(10*pLE->u.iIcon)+((*psz++)-'0');

      pLE->fFoundIndex=TRUE;
      return 0;
      }

   return 1;
   }

