// MiniWindow.cpp : implementation of the CMiniWindow class
//


#include "stdafx.h"
#include "MUSHclient.h"
#include "doc.h"
#include "errors.h"


// constructor
CMiniWindow::CMiniWindow ()  :       
          m_oldBitmap (NULL),
          m_iWidth (0), m_iHeight (0),
          m_iPosition (0), m_iFlags (0),
          m_iBackgroundColour (0), m_bShow (false), 
          m_bTemporarilyHide (false) 
  {
  dc.CreateCompatibleDC(NULL);
  dc.SetTextAlign (TA_LEFT | TA_TOP);
  }  // end CMiniWindow::CMiniWindow  (constructor)

CMiniWindow::~CMiniWindow ()  // destructor
  {

  // get rid of old one if any
  if ((HBITMAP) m_Bitmap)
    {
		dc.SelectObject(m_oldBitmap);    // swap old one back
    m_Bitmap.DeleteObject ();        // delete the one we made
    }

  // delete our fonts
  for (FontMapIterator fit = m_Fonts.begin (); 
       fit != m_Fonts.end ();
       fit++)
         delete fit->second;

  m_Fonts.clear ();

  // delete our images
  for (ImageMapIterator it = m_Images.begin (); 
       it != m_Images.end ();
       it++)
         delete it->second;

  m_Images.clear ();

  // delete our hotspots
  for (HotspotMapIterator hit = m_Hotspots.begin (); 
       hit != m_Hotspots.end ();
       hit++)
         delete hit->second;

  m_Hotspots.clear ();

  }   // end CMiniWindow::~CMiniWindow  (destructor)


// a negative or zero value for the RH side of a rectange is considered offset from the right edge
// eg. -1 is 1 pixel in from right, 0 is the RH edge.
long CMiniWindow::FixRight (const long Right)
  {

  if (Right <= 0) 
    return m_iWidth + Right;

  return Right;
  }  // end CMiniWindow::FixRight

// a negative or zero value for the bottom of a rectange is considered offset from the bottom edge
long CMiniWindow::FixBottom (const long Bottom)
  {

  if (Bottom <= 0) 
    return m_iHeight + Bottom;

  return Bottom;

  }  // end  CMiniWindow::FixBottom


/* positions: 

  0 = strech to output view size 
  1 = stretch with aspect ratio

  2 = strech to owner size 
  3 = stretch with aspect ratio
  
  -- going clockwise here:

  -- top
  4 = top left
  5 = center left-right at top
  6 = top right

  -- rh side
  7 = on right, center top-bottom
  8 = on right, at bottom

  -- bottom
  9 = center left-right at bottom

  -- lh side
  10 = on left, at bottom  
  11 = on left, center top-bottom 

  -- middle
  12 = center all

  13 = tile


  */


// create (or re-create) a mini-window
void CMiniWindow::Create (long Left, long Top, long Width, long Height,
                          short Position, long Flags, 
                          COLORREF BackgroundColour)
  {
 
  m_Location.x           = Left            ;
  m_Location.y           = Top             ;
  m_iWidth               = Width           ;
  m_iHeight              = Height          ;
  m_iPosition            = Position        ;
  m_iFlags               = Flags           ;
  m_iBackgroundColour    = BackgroundColour;

  // get rid of old one if any
  if ((HBITMAP) m_Bitmap)
    {
		dc.SelectObject(m_oldBitmap);    // swap old one back
    m_Bitmap.DeleteObject ();
    }

  m_Bitmap.CreateBitmap (m_iWidth, m_iHeight, 1, GetDeviceCaps(dc, BITSPIXEL), NULL); 
	m_oldBitmap = dc.SelectObject (&m_Bitmap);
	dc.SetWindowOrg(0, 0);

  dc.FillSolidRect (0, 0, m_iWidth, m_iHeight, m_iBackgroundColour);

  m_bShow = false;

  // a newly created window has no hotspots
  DeleteAllHotspots ();

  } // end of MiniWindow::Create


// set/clear the show flag so the window becomes visible
void  CMiniWindow::Show (bool bShow)
  {
  m_bShow = bShow;
  }    // end of CMiniWindow::Show

/*

  Actions:

  1 = FrameRect        ( 1 pixel )
  2 = FillRect
  3 = InvertRect
  4 = 3D Rect    (Colour1 is top and left edge colour, Colour2 is bottom and right edge colour)
  5 = DrawEdge   (draws a 3d-style edge with optional fill)

  Colour1 = style of edge:

  EDGE_RAISED:      // 5     
  EDGE_ETCHED:      // 6     
  EDGE_BUMP:        // 9     
  EDGE_SUNKEN:      // 10    

  Colour2 = where to draw it:

  BF_TOPLEFT      0x3
  BF_TOPRIGHT     0x6
  BF_BOTTOMLEFT   0x9
  BF_BOTTOMRIGHT  0xC
  BF_RECT         0xF

  BF_DIAGONAL     0x0010

  // For diagonal lines, the BF_RECT flags specify the end point of the
  // vector bounded by the rectangle parameter.

  BF_DIAGONAL_ENDTOPLEFT      0x13
  BF_DIAGONAL_ENDTOPRIGHT     0x16
  BF_DIAGONAL_ENDBOTTOMLEFT   0x19
  BF_DIAGONAL_ENDBOTTOMRIGHT  0x1C

  Additional Colour2 flags:

  BF_MIDDLE       0x0800   Fill in the middle 
  BF_SOFT         0x1000   For softer buttons 
  BF_ADJUST       0x2000   Calculate the space left over 
  BF_FLAT         0x4000   For flat rather than 3D borders 
  BF_MONO         0x8000   For monochrome borders 


  6 = Flood Fill Border (fills to border specified by Colour1)

  7 = Flood Fill Surface (fills while on surface specified by Colour1)

*/

// various rectangle operations
long  CMiniWindow::RectOp (short Action, long Left, long Top, long Right, long Bottom, long Colour1, long Colour2)
  {
  switch (Action)

    {
    case 1:       // frame
      {
      CBrush br1;
      br1.CreateSolidBrush (Colour1);    
      dc.FrameRect (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), &br1);
      break; 
      }

    case 2:       // fill
      {
      CBrush br1;
      br1.CreateSolidBrush (Colour1);    
      dc.FillRect (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), &br1);
      break; 
      }

    case 3:       // invert
      {
      dc.InvertRect (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)));
      break; 
      }

    case 4:       // 3D rect
      {
      dc.Draw3dRect (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), Colour1, Colour2);
      break; 
      }

    case 5:       // DrawEdge
      {

      switch (Colour1)   //  nEdge
        {
        // must be one of these 4 flags
        case EDGE_RAISED:      // 5
        case EDGE_ETCHED:      // 6
        case EDGE_BUMP:        // 9
        case EDGE_SUNKEN:      // 10
             break;

        default: return eBadParameter;
        }

      if ((Colour2 & 0xFF) > 0x1F)
        return eBadParameter;

      dc.DrawEdge (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), Colour1, Colour2);
      break; 
      }
      
    case 6:       // Flood fill border
      {
      dc.FloodFill (Left, Top, Colour1);
      break; 
      }

    case 7:       // Flood fill surface
      {
      dc.ExtFloodFill (Left, Top, Colour1, FLOODFILLSURFACE);
      break; 
      }

    default: return eUnknownOption;

    } // end of switch

  return eOK;

  } // end of CMiniWindow::RectOp 
         
static long ValidatePenStyle (const long PenStyle, const long PenWidth)
  {

  switch (PenStyle)   
    {
      // must be one of these flags
      case PS_SOLID:           // 0  
      case PS_NULL:            // 5   
      case PS_INSIDEFRAME:     // 6
        break;
        
      case PS_DASH:            // 1       /* -------  */     
      case PS_DOT:             // 2       /* .......  */     
      case PS_DASHDOT:         // 3       /* _._._._  */     
      case PS_DASHDOTDOT:      // 4       /* _.._.._  */  
         if (PenWidth > 1) 
           return ePenStyleNotValid;
         break;

    default: return ePenStyleNotValid;
    }

  return eOK;
  }


static long ValidateBrushStyle (const long BrushStyle, 
                                const long PenColour, 
                                const long BrushColour, 
                                CBrush & br)
  {


  LOGBRUSH lb;
  lb.lbColor = PenColour; 

  switch (BrushStyle)   
    {
      // must be one of these flags
      case BS_SOLID:           // 0 
         lb.lbStyle = BS_SOLID;
         lb.lbColor = BrushColour; 
         break;
        
      case BS_NULL:            // 1                          
         lb.lbStyle = BS_NULL;
         break;

      // hatched styles:
      case 2:              
         lb.lbStyle = BS_HATCHED;
         lb.lbHatch = HS_HORIZONTAL;
         break;

      case 3:              
         lb.lbStyle = BS_HATCHED;
         lb.lbHatch = HS_VERTICAL;
         break;

      case 4:             
         lb.lbStyle = BS_HATCHED;
         lb.lbHatch = HS_FDIAGONAL;
         break;

      case 5:              
         lb.lbStyle = BS_HATCHED;
         lb.lbHatch = HS_BDIAGONAL;
         break;

      case 6:              
         lb.lbStyle = BS_HATCHED;
         lb.lbHatch = HS_CROSS;
         break;

      case 7:              
         lb.lbStyle = BS_HATCHED;
         lb.lbHatch = HS_DIAGCROSS;
         break;

         // each byte is a line, so 0xAA would be 10101010   (top line)
         //                         0x55 would be 01010101   (next line)
      case 8:       // fine hatch
        {
        CBitmap bitmap;
        WORD		wBits[] = { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 };

        bitmap.CreateBitmap (8, 8, 1, 1, wBits);
        br.CreatePatternBrush (&bitmap);
        return eOK;
        }

      case 9:      // medium hatch
        {
        CBitmap bitmap;
        WORD		wBits[] = { 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, };     

        bitmap.CreateBitmap (8, 8, 1, 1, wBits);
        br.CreatePatternBrush (&bitmap);
        return eOK;
        }

      case 10:       // coarse hatch
        {
        CBitmap bitmap;
        WORD		wBits[] = { 0x0F, 0x0F, 0x0F, 0x0F, 0xF0, 0xF0, 0xF0, 0xF0 };

        bitmap.CreateBitmap (8, 8, 1, 1, wBits);
        br.CreatePatternBrush (&bitmap);
        return eOK;
        }

      case 11:       // waves - horizontal
        {
        CBitmap bitmap;
        WORD		wBits[] = { 0xCC, 0x33, 0x00, 0x00, 0xCC, 0x33, 0x00, 0x00  };

        bitmap.CreateBitmap (8, 8, 1, 1, wBits);
        br.CreatePatternBrush (&bitmap);
        return eOK;
        }

      case 12:       // waves - vertical
        {
        CBitmap bitmap;
        WORD		wBits[] = { 0x11, 0x11, 0x22, 0x22, 0x11, 0x11, 0x22, 0x22  };

        bitmap.CreateBitmap (8, 8, 1, 1, wBits);
        br.CreatePatternBrush (&bitmap);
        return eOK;
        }

    default: return eBrushStyleNotValid;
    }

  br.CreateBrushIndirect (&lb);

  return eOK;
  }

/*

  Actions:

  1 = Ellipse
  2 = Rectangle (can have thicker pen style and be filled)
  3 = Round Rectangle
  4 = Chord (a closed figure bounded by the intersection of an ellipse and a line segment)
  5 = Pie (Draws a pie-shaped wedge by drawing an elliptical arc whose center and two endpoints are joined by lines)

  */

// various circle/ellipse/pie operations
long CMiniWindow::CircleOp (short Action, 
                            long Left, long Top, long Right, long Bottom, 
                            long PenColour, long PenStyle, long PenWidth, 
                            long BrushColour, long BrushStyle,
                            long Extra1, long Extra2, long Extra3, long Extra4)
  {
  long iResult = eUnknownOption;

  if (ValidatePenStyle (PenStyle, PenWidth))
    return ePenStyleNotValid;

  // validate and create requested bruch
  CBrush br;

  if (ValidateBrushStyle (BrushStyle, PenColour, BrushColour, br))
    return eBrushStyleNotValid;

  // create requested pen 
  CPen pen;
  pen.CreatePen (PenStyle, PenWidth, PenColour); 
  
  // select into DC
  CPen* oldPen = dc.SelectObject(&pen);
  CBrush* oldBrush = dc.SelectObject(&br);

  if (BrushStyle > 1 && BrushStyle <= 7)
    {
    dc.SetBkColor (BrushColour);      // for hatched brushes this is the background colour
    }
  else
  if (BrushStyle > 7)  // pattern brushes
    {
    dc.SetTextColor (BrushColour);      // for patterned brushes
    dc.SetBkColor (PenColour);      // for hatched brushes and patterned brushes
    }

  if (BrushColour != -1)
    dc.SetBkMode (OPAQUE);
  else
    dc.SetBkMode (TRANSPARENT);

  switch (Action)
                                  
    {
    case 1:       // ellipse
      {
      dc.Ellipse (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)));
      iResult = eOK;
      break; 
      }

    case 2:       // rectangle
      {
      dc.Rectangle (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)));
      iResult = eOK;
      break; 
      }

    case 3:       // round rectangle
      {
      dc.RoundRect (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), CPoint (Extra1, Extra2));
      iResult = eOK;
      break; 
      }

    case 4:       // chord
      {
      dc.Chord (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), 
                CPoint (Extra1, Extra2), 
                CPoint (Extra3, Extra4));
      iResult = eOK;
      break; 
      }

    case 5:       // pie
      {
      dc.Pie (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), 
                CPoint (Extra1, Extra2), 
                CPoint (Extra3, Extra4));
      iResult = eOK;
      break; 
      }

    } // end of switch

  // put things back
  dc.SelectObject (oldPen);
  dc.SelectObject (oldBrush);

  return iResult;

  } // end of CMiniWindow::CircleOp
                    

// add a font to our map of fonts by name (eg. "inventory")
long CMiniWindow::Font (LPCTSTR FontId,        // eg. "inventory"
                        LPCTSTR FontName,      // eg. "Courier New"
                       double Size, 
                       BOOL Bold, BOOL Italic, BOOL Underline, BOOL Strikeout, 
                       short Charset, short PitchAndFamily)
  {

  FontMapIterator it = m_Fonts.find (FontId);

  if (it != m_Fonts.end ())
    {
    delete it->second;         // delete existing font
    m_Fonts.erase (it);
    }

  CFont * pFont = new CFont;

  int lfHeight = -MulDiv(Size ? Size : 10.0, dc.GetDeviceCaps(LOGPIXELSY), 72);

  if (pFont->CreateFont (lfHeight, 
				                  0, // int nWidth, 
				                  0, // int nEscapement, 
				                  0, // int nOrientation, 
                          Bold ? FW_BOLD : FW_NORMAL, // int nWeight, 
                          Italic, // BYTE bItalic, 
                          Underline, // BYTE bUnderline, 
                          Strikeout, // BYTE cStrikeOut, 
                          Charset,
                          0, // BYTE nOutPrecision, 
                          0, // BYTE nClipPrecision, 
                          0, // BYTE nQuality, 
                          PitchAndFamily, 
                          FontName) == 0)
    {
    delete pFont;
    return eCannotAddFont;
    }


  m_Fonts [FontId] = pFont;
  return eOK;

  }   // end of CMiniWindow::Font 


static void SetUpVariantLong (VARIANT & tVariant, const long iContents)
  {
  VariantClear (&tVariant);
  tVariant.vt = VT_I4;
  tVariant.lVal = iContents; 
  }   // end of SetUpVariantLong

static void SetUpVariantString (VARIANT & tVariant, const CString & strContents)
  {
  VariantClear (&tVariant);
  tVariant.vt = VT_BSTR;
  tVariant.bstrVal = strContents.AllocSysString (); 
  }   // end of SetUpVariantString

static void SetUpVariantBool (VARIANT & tVariant, const BOOL iContents)
  {
  VariantClear (&tVariant);
  tVariant.vt = VT_BOOL;
  tVariant.boolVal = iContents; 
  }   // end of SetUpVariantBool


// return info about the select font
void CMiniWindow::FontInfo (LPCTSTR FontId, long InfoType, VARIANT & vaResult)
  {
  FontMapIterator it = m_Fonts.find (FontId);

  if (it == m_Fonts.end ())
    return;   // no such font

  CFont* oldFont = dc.SelectObject(it->second);    // select in the requested font
  
  TEXTMETRIC tm;
  dc.GetTextMetrics(&tm);

  CString rString;
  dc.GetTextFace (rString);

  switch (InfoType)
    {

    case  1:  SetUpVariantLong (vaResult, tm.tmHeight);           break;               
    case  2:  SetUpVariantLong (vaResult, tm.tmAscent);           break;               
    case  3:  SetUpVariantLong (vaResult, tm.tmDescent);          break;              
    case  4:  SetUpVariantLong (vaResult, tm.tmInternalLeading);  break;      
    case  5:  SetUpVariantLong (vaResult, tm.tmExternalLeading);  break;      
    case  6:  SetUpVariantLong (vaResult, tm.tmAveCharWidth);     break;         
    case  7:  SetUpVariantLong (vaResult, tm.tmMaxCharWidth);     break;         
    case  8:  SetUpVariantLong (vaResult, tm.tmWeight);           break;               
    case  9:  SetUpVariantLong (vaResult, tm.tmOverhang);         break;             
    case 10:  SetUpVariantLong (vaResult, tm.tmDigitizedAspectX); break;    
    case 11:  SetUpVariantLong (vaResult, tm.tmDigitizedAspectY); break;    
    case 12:  SetUpVariantLong (vaResult, tm.tmFirstChar);        break;           
    case 13:  SetUpVariantLong (vaResult, tm.tmLastChar);         break;            
    case 14:  SetUpVariantLong (vaResult, tm.tmDefaultChar);      break;         
    case 15:  SetUpVariantLong (vaResult, tm.tmBreakChar);        break;           
    case 16:  SetUpVariantLong (vaResult, tm.tmItalic);           break;              
    case 17:  SetUpVariantLong (vaResult, tm.tmUnderlined);       break;          
    case 18:  SetUpVariantLong (vaResult, tm.tmStruckOut);        break;           
    case 19:  SetUpVariantLong (vaResult, tm.tmPitchAndFamily);   break;      
    case 20:  SetUpVariantLong (vaResult, tm.tmCharSet);          break;             
    case 21:  SetUpVariantString (vaResult, rString);             break;             

    default:
      vaResult.vt = VT_NULL;
      break;

    } // end of switch

  dc.SelectObject(oldFont);
  }  // end of CMiniWindow::FontInfo

// return list of fonts we installed
void CMiniWindow::FontList (VARIANT & vaResult)
  {
  COleSafeArray sa;   // for array list

  long iCount = 0;
  
  // put the arrays into the array
  if (!m_Fonts.empty ())    // cannot create empty dimension
    {
    sa.CreateOneDim (VT_VARIANT, m_Fonts.size ());

    for (FontMapIterator it = m_Fonts.begin (); 
         it != m_Fonts.end ();
         it++)
           {
            // the array must be a bloody array of variants, or VBscript kicks up
            COleVariant v (it->first.c_str ());
            sa.PutElement (&iCount, &v);
            iCount++;
           }

    } // end of having at least one

	vaResult = sa.Detach ();


  } // end of  CMiniWindow::FontList



//helper function to calculate length of UTF8 string
static long CalculateUTF8length (LPCTSTR Text, size_t length)
  {
 
  int iBad = _pcre_valid_utf8 ((unsigned char  *) Text, length);
  if (iBad >= 0)
    return -1;

    // string is OK, calculate its length

  int i = 0;   // length

  // this algorithm assumes the UTF-8 is OK, based on the earlier check

  for (register const unsigned char *p = (const unsigned char *) Text ; 
       length-- > 0; 
       i++)
    {          
    register int ab;    // additional bytes
    register int c = *p++;  // this byte

    if (c < 128)
      continue;     // zero additional bytes

    ab = _pcre_utf8_table4 [c & 0x3f];  /* Number of additional bytes */

    length -= ab;  // we know string is valid already, so just skip the additional bytes (ab)
    p += ab;

    }

  return i;

  }   // end of CalculateUTF8length

// output text, ordinary or UTF8 - returns length of text
long CMiniWindow::Text (LPCTSTR FontId,  // which previously-created font
                        LPCTSTR Text,    // what to say
                        long Left, long Top, long Right, long Bottom, // where to say it
                        long Colour,       // colour to show it in
                        BOOL Unicode)      // true if UTF8

  {
  FontMapIterator it = m_Fonts.find (FontId);

  if (it == m_Fonts.end ())
    return -2;

  size_t length = strlen (Text);
  long utf8_length = 0;

  // give up if no text
  if (length <= 0)
    return 0;

  // quick sanity check on our UTF8 stuff
  if (Unicode)
    {
    utf8_length = CalculateUTF8length (Text, length);
    if (utf8_length < 0)
      return -3;    // ohno!
    }


  CFont* oldFont = dc.SelectObject(it->second);    // select in the requested font

  CSize textsize;

  dc.SetTextColor (Colour);  
  dc.SetBkMode (TRANSPARENT);

  if (Unicode)
    {
    vector<WCHAR> v (utf8_length);    // get correct size vector
    int iUnicodeCharacters = MultiByteToWideChar (CP_UTF8, 0, 
                              Text, length,            // input
                              &v [0], utf8_length);    // output

    ExtTextOutW (dc.m_hDC, Left, Top, ETO_CLIPPED, CRect (Left, Top, FixRight (Right), FixBottom (Bottom) ),
                  &v [0], iUnicodeCharacters, NULL);

    // now calculate width of Unicode pixels
    GetTextExtentPoint32W(
        dc.m_hDC,             // handle to device context
        &v [0],               // pointer to text string
        iUnicodeCharacters,   // number of characters in string
        &textsize             // pointer to structure for string size
      );

    }
  else
    {
    dc.ExtTextOut (Left, Top, ETO_CLIPPED, CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), 
                  Text, length, NULL);
    textsize = dc.GetTextExtent (Text, length);

    }


  dc.SelectObject(oldFont);

  return min (textsize.cx, FixRight (Right) - Left);  // if clipped, length is width of rectangle
  } // end of CMiniWindow::Text 


// measure text, ordinary or UTF8
long CMiniWindow::TextWidth (LPCTSTR FontId,  // which previously-created font
                             LPCTSTR Text,    // what to measure
                             BOOL Unicode)    // true if UTF8
  {

  FontMapIterator it = m_Fonts.find (FontId);

  if (it == m_Fonts.end ())
    return -2;

  size_t length = strlen (Text);
  long utf8_length = 0;

  // give up if no text
  if (length <= 0)
    return 0;

  // quick sanity check on our UTF8 stuff
  if (Unicode)
    {
    utf8_length = CalculateUTF8length (Text, length);
    if (utf8_length < 0)
      return -3;    // ohno!
    }


  CFont* oldFont = dc.SelectObject(it->second);    // select in the requested font

  CSize textsize;

  if (Unicode)
    {
    vector<WCHAR> v (utf8_length);    // get correct size vector
    int iUnicodeCharacters = MultiByteToWideChar (CP_UTF8, 0,     
                              Text, length,           // input
                              &v [0], utf8_length);   // output

    // now calculate width of Unicode pixels
    GetTextExtentPoint32W(
        dc.m_hDC,           // handle to device context
        &v [0],             // pointer to text string
        iUnicodeCharacters, // number of characters in string
        &textsize           // pointer to structure for string size
      );

    }
  else
    textsize = dc.GetTextExtent (Text, length);


  dc.SelectObject(oldFont);

  return textsize.cx;

  }  // end of  CMiniWindow::TextWidth


// draws a straight line
long CMiniWindow::Line (long x1, long y1, long x2, long y2, 
                        long PenColour, long PenStyle, long PenWidth)
  {

  if (ValidatePenStyle (PenStyle, PenWidth))
    return ePenStyleNotValid;

  dc.SetBkMode (TRANSPARENT);

  // create requested pen
  CPen pen;
  pen.CreatePen (PenStyle, PenWidth, PenColour);    
  CPen* oldPen = dc.SelectObject(&pen);

  dc.MoveTo (x1, y1);
  dc.LineTo (FixRight (x2), FixBottom (y2));

  // put things back
  dc.SelectObject (oldPen);

  return eOK;

  }  // end of CMiniWindow::Line


// draws an arc
long CMiniWindow::Arc (long Left, long Top, long Right, long Bottom, 
                        long x1, long y1, 
                        long x2, long y2, 
                        long PenColour, long PenStyle, long PenWidth)
  {

  if (ValidatePenStyle (PenStyle, PenWidth))
    return ePenStyleNotValid;

  dc.SetBkMode (TRANSPARENT);

  // create requested pen
  CPen pen;
  pen.CreatePen (PenStyle, PenWidth, PenColour);    
  CPen* oldPen = dc.SelectObject(&pen);

  dc.Arc(Left, Top, FixRight (Right), FixBottom (Bottom), 
          x1, y1, // from
          FixRight (x2), FixBottom (y2)); // to

  // put things back
  dc.SelectObject (oldPen);

  return eOK;

  }  // end of CMiniWindow::Arc


// return info about the window
void CMiniWindow::Info (long InfoType, VARIANT & vaResult)
  {

  switch (InfoType)
    {
    case  1:  SetUpVariantLong    (vaResult, m_Location.x);        break; // left 
    case  2:  SetUpVariantLong    (vaResult, m_Location.y);        break; // top
    case  3:  SetUpVariantLong    (vaResult, m_iWidth);            break; // width
    case  4:  SetUpVariantLong    (vaResult, m_iHeight);           break; // height
    case  5:  SetUpVariantBool    (vaResult, m_bShow);             break; // show flag
    case  6:  SetUpVariantBool    (vaResult, m_bTemporarilyHide);  break; // is it hidden right now?
    case  7:  SetUpVariantLong    (vaResult, m_iPosition);         break; // layout mode
    case  8:  SetUpVariantLong    (vaResult, m_iFlags);            break; // flags
    case  9:  SetUpVariantLong    (vaResult, m_iBackgroundColour); break; // background colour

    case 10:  SetUpVariantLong    (vaResult, m_rect.left);         break; // where it is right now
    case 11:  SetUpVariantLong    (vaResult, m_rect.top);          break; //       "
    case 12:  SetUpVariantLong    (vaResult, m_rect.right);        break; //       "
    case 13:  SetUpVariantLong    (vaResult, m_rect.bottom);       break; //       "

    default:
      vaResult.vt = VT_NULL;
      break;

    } // end of switch

  }  // end of CMiniWindow::Info


// loads an image file, ready for drawing into window
long CMiniWindow::LoadImage (LPCTSTR ImageId, LPCTSTR FileName)
  {
  ImageMapIterator it = m_Images.find (ImageId);

  if (it != m_Images.end ())
    {
    delete it->second;         // delete existing image
    m_Images.erase (it);
    }

  // no file name means get rid of image
  if (strlen (FileName) == 0)
    return eOK;

  HBITMAP hBmp = (HBITMAP)::LoadImage(
                  NULL,
                  FileName,
                  IMAGE_BITMAP,
                  0,
                  0,
                  LR_LOADFROMFILE|LR_CREATEDIBSECTION
                  );

   if (hBmp) 
     {
      CBitmap * pImage = new CBitmap;
      pImage->Attach (hBmp);
      m_Images [ImageId] = pImage;
    	return eOK;
     }  // end of having a bitmap loaded


   if (GetLastError () == 2)
     return eFileNotFound;

   return eUnableToLoadImage;


  } // end of CMiniWindow::LoadImage


/*

  for monitors with 256 colours or less, consider:

HDRAWDIB hdd = DrawDibOpen(); 
DrawDibDraw(hdd, hPaintDC,0,0,BITMAP_WIDTH,BITMAP_HEIGHT,&m_BitmapInfo,m_pBits,0,0,BITMAP_WIDTH,BITMAP_HEIGHT,DDF_HALFTONE);
DrawDibClose(hdd);

  */


// draw a previously-loaded image into the window
long CMiniWindow::DrawImage(LPCTSTR ImageId, 
               long Left, long Top, long Right, long Bottom, 
               short Mode,
               long SrcLeft, long SrcTop, long SrcRight, long SrcBottom)
  {

  ImageMapIterator it = m_Images.find (ImageId);

  if (it == m_Images.end ())
    return eImageNotInstalled;

  CBitmap * bitmap = it->second;

  dc.SetBkMode (TRANSPARENT);

  BITMAP  bi;
  bitmap->GetBitmap(&bi);

  CDC bmDC;
  bmDC.CreateCompatibleDC(&dc);
  CBitmap *pOldbmp = bmDC.SelectObject(bitmap);

  // adjust so that -1 means 1 from right
  if (SrcRight <= 0) 
    SrcRight = bi.bmWidth + SrcRight;

  if (SrcBottom <= 0) 
    SrcBottom =  bi.bmHeight + SrcBottom;

  // calculate size of desired rectangle
  long iWidth = SrcRight - SrcLeft;
  long iHeight = SrcBottom - SrcTop;

  if (iWidth > 0 && iHeight > 0)   // sanity check
    switch (Mode)
      {
      case 1: dc.BitBlt (Left, Top, iWidth, iHeight, &bmDC, SrcLeft, SrcTop, SRCCOPY);  
              break;      // straight copy

      case 2: dc.StretchBlt (Left, Top, FixRight (Right) - Left, FixBottom (Bottom) - Top, &bmDC, 
                             SrcLeft, SrcTop, SrcRight, SrcBottom, SRCCOPY);
              break;      // stretch

      case 3:        // transparency, och!
        {
        COLORREF crOldBack = dc.SetBkColor (RGB (255, 255, 255));    // white
	      COLORREF crOldText = dc.SetTextColor (RGB (0, 0, 0));        // black
	      CDC dcTrans;   // transparency mask


	      // Create a memory dc for the mask
	      dcTrans.CreateCompatibleDC(&dc);

	      // Create the mask bitmap for the subset of the main image
	      CBitmap bitmapTrans;
	      bitmapTrans.CreateBitmap(iWidth, iHeight, 1, 1, NULL);

	      // Select the mask bitmap into the appropriate dc
	      CBitmap* pOldBitmapTrans = dcTrans.SelectObject(&bitmapTrans);

        // Our transparent pixel will be at 0,0 (top left corner) of original image (not subimage)
        COLORREF crOldBackground = bmDC.SetBkColor (::GetPixel (bmDC, 0, 0));

	      // Build mask based on transparent colour at location 0, 0
	      dcTrans.BitBlt (0, 0, iWidth, iHeight, &bmDC, SrcLeft, SrcTop, SRCCOPY);

	      // Do the work 
	      dc.BitBlt (Left, Top, iWidth, iHeight, &bmDC, SrcLeft, SrcTop, SRCINVERT);
	      dc.BitBlt (Left, Top, iWidth, iHeight, &dcTrans, 0, 0, SRCAND);
	      dc.BitBlt (Left, Top, iWidth, iHeight, &bmDC, SrcLeft, SrcTop, SRCINVERT);

	      // Restore settings
	      dcTrans.SelectObject(pOldBitmapTrans);
	      dc.SetBkColor(crOldBack);
	      dc.SetTextColor(crOldText);
        bmDC.SetBkColor(crOldBackground);
        }
        break;

      default: return eBadParameter;
      } // end of switch

  bmDC.SelectObject(pOldbmp);

  return eOK;
  }  // end of CMiniWindow::DrawImage


// return list of images
void CMiniWindow::ImageList(VARIANT & vaResult)
  {
  COleSafeArray sa;   // for array list

  long iCount = 0;
  
  // put the arrays into the array
  if (!m_Images.empty ())    // cannot create empty dimension
    {
    sa.CreateOneDim (VT_VARIANT, m_Images.size ());

    for (ImageMapIterator it = m_Images.begin (); 
         it != m_Images.end ();
         it++)
           {
            // the array must be a bloody array of variants, or VBscript kicks up
            COleVariant v (it->first.c_str ());
            sa.PutElement (&iCount, &v);
            iCount++;
           }

    } // end of having at least one

	vaResult = sa.Detach ();

  }   // end of CMiniWindow::ImageList



// return info about the selected image
void CMiniWindow::ImageInfo (LPCTSTR ImageId, long InfoType, VARIANT & vaResult)
  {
  ImageMapIterator it = m_Images.find (ImageId);

  if (it == m_Images.end ())
    return;   // no such Image

  CBitmap * bitmap = it->second;

  BITMAP  bi;
  bitmap->GetBitmap(&bi);

  switch (InfoType)
    {

    case  1:  SetUpVariantLong (vaResult, bi.bmType);      break;
    case  2:  SetUpVariantLong (vaResult, bi.bmWidth);     break;
    case  3:  SetUpVariantLong (vaResult, bi.bmHeight);    break;
    case  4:  SetUpVariantLong (vaResult, bi.bmWidthBytes);break;
    case  5:  SetUpVariantLong (vaResult, bi.bmPlanes);    break;
    case  6:  SetUpVariantLong (vaResult, bi.bmBitsPixel); break;

     default:
      vaResult.vt = VT_NULL;
      break;

    } // end of switch

  }  // end of CMiniWindow::ImageInfo


// draw bezier curves
long CMiniWindow::Bezier(LPCTSTR Points, long PenColour, long PenStyle, long PenWidth)
  {

  if (ValidatePenStyle (PenStyle, PenWidth))
    return ePenStyleNotValid;

  vector<string> v;

  StringToVector (Points, v, ",");

  int iCount = v.size ();

  // must have at least a start point (2 items), plus one extra (3 points)
  if (iCount < 8)
    return eInvalidNumberOfPoints;

  // and has to be 2 more than 6 items  (1 more than 3n points)
  if ((iCount % 6) != 2)
    return eInvalidNumberOfPoints;

  iCount = iCount / 2;  // number of points

  vector<POINT> points (iCount);

  int iCurrent = 0;

  for (vector<string>::const_iterator i = v.begin (); i != v.end (); i++)
    {
    if (!IsStringNumber (*i, true))
      return eInvalidPoint;
    points [iCurrent].x = atol (i->c_str ());
    i++;  // we know this is safe becaue of earlier check (we must have pairs of numbers)
    if (!IsStringNumber (*i, true))
      return eInvalidPoint;
    points [iCurrent].y = atol (i->c_str ());
    iCurrent++;  // onto next point
    }

  dc.SetBkMode (TRANSPARENT);

  // create requested pen
  CPen pen;
  pen.CreatePen (PenStyle, PenWidth, PenColour);    
  CPen* oldPen = dc.SelectObject(&pen);

  dc.PolyBezier(&points [0], iCount); 

  // put things back
  dc.SelectObject (oldPen);

  return eOK;


  }   // end of CMiniWindow::Bezier


// draw a polygon (straight lines)
long CMiniWindow::Polygon(LPCTSTR Points, 
                         long PenColour, short PenStyle, long PenWidth, 
                         long BrushColour, long BrushStyle, 
                         BOOL Close,
                         BOOL Winding)
  {

  if (ValidatePenStyle (PenStyle, PenWidth))
    return ePenStyleNotValid;

  // validate and create requested bruch
  CBrush br;

  if (ValidateBrushStyle (BrushStyle, PenColour, BrushColour, br))
    return eBrushStyleNotValid;

  vector<string> v;

  StringToVector (Points, v, ",");

  int iCount = v.size ();

  // must have at least a start point (2 items), plus one extra (1 point)
  if (iCount < 4)
    return eInvalidNumberOfPoints;

  // it has to be divisible by 2
  if ((iCount % 2) != 0)
    return eInvalidNumberOfPoints;

  iCount = iCount / 2;  // number of points

  vector<POINT> points (iCount);

  int iCurrent = 0;

  for (vector<string>::const_iterator i = v.begin (); i != v.end (); i++)
    {
    if (!IsStringNumber (*i, true))
      return eInvalidPoint;
    points [iCurrent].x = atol (i->c_str ());
    i++;  // we know this is safe becaue of earlier check (we must have pairs of numbers)
    if (!IsStringNumber (*i, true))
      return eInvalidPoint;
    points [iCurrent].y = atol (i->c_str ());
    iCurrent++;  // onto next point
    }

  // create requested pen 
  CPen pen;
  pen.CreatePen (PenStyle, PenWidth, PenColour);    

  // select pen and brush into device context
  CPen* oldPen = dc.SelectObject(&pen);
  CBrush* oldBrush = dc.SelectObject(&br);

  dc.SetPolyFillMode (Winding ? WINDING : ALTERNATE);

  if (BrushStyle > 1 && BrushStyle <= 7)
    {
    dc.SetBkColor (BrushColour);      // for hatched brushes this is the background colour
    }
  else
  if (BrushStyle > 7)  // pattern brushes
    {
    dc.SetTextColor (BrushColour);      // for patterned brushes
    dc.SetBkColor (PenColour);      // for hatched brushes and patterned brushes
    }

  if (BrushColour != -1)
    dc.SetBkMode (OPAQUE);
  else
    dc.SetBkMode (TRANSPARENT);

  if (Close)
    dc.Polygon(&points [0], iCount); 
  else
    dc.Polyline(&points [0], iCount); 

  // put things back
  dc.SelectObject (oldPen);
  dc.SelectObject (oldBrush);

  return eOK;

  }   // end of CMiniWindow::Polygon
                           
// reposition window
long CMiniWindow::Position(long Left, long Top, 
                           short Position, 
                           long Flags) 
  {
  m_Location.x           = Left     ;
  m_Location.y           = Top      ;
  m_iPosition            = Position ;
  m_iFlags               = Flags    ;

  return eOK;

  } // end of  CMiniWindow::Position


/*

  Cursor values:

  0:  arrow                                
  1:  hand                                 
  2:  I-beam                               
  3:  + symbol                           
  4:  wait (hour-glass)                    
  5:  up arrow                             
  6:  arrow nw-se                          
  7:  arrow ne-sw                          
  8:  arrow e-w                            
  9:  arrow n-s                            
 10:  arrow - all ways                     
 11:  (X) no, no, I won't do that, but ... 
 12:  help  (? symbol)                     

  */

// add a hotspot for handling mouse-over, mouse up/down events
long CMiniWindow::AddHotspot(LPCTSTR HotspotId, 
                             string sPluginID,
                             long Left, long Top, long Right, long Bottom, 
                             LPCTSTR MouseOver, 
                             LPCTSTR CancelMouseOver, 
                             LPCTSTR MouseDown, 
                             LPCTSTR CancelMouseDown, 
                             LPCTSTR MouseUp, 
                             LPCTSTR TooltipText,
                             long Cursor, 
                             long Flags)
  {

  if (strlen (MouseOver) > 0 && CheckLabel (MouseOver))
    return eInvalidObjectLabel;
  if (strlen (CancelMouseOver) > 0 && CheckLabel (CancelMouseOver))
    return eInvalidObjectLabel;
  if (strlen (MouseDown) > 0 && CheckLabel (MouseDown))
    return eInvalidObjectLabel;
  if (strlen (CancelMouseDown) > 0 && CheckLabel (CancelMouseDown))
    return eInvalidObjectLabel;
  if (strlen (MouseUp) > 0 && CheckLabel (MouseUp))
    return eInvalidObjectLabel;

  // can't switch plugins here :)
  if (!m_sCallbackPlugin.empty () && m_sCallbackPlugin != sPluginID)
    return eHotspotPluginChanged;

  m_sCallbackPlugin = sPluginID;

  HotspotMapIterator it = m_Hotspots.find (HotspotId);

  if (it != m_Hotspots.end ())
    {
    delete it->second;         // delete existing font
    m_Hotspots.erase (it);
    if (m_sMouseOverHotspot == HotspotId)
      m_sMouseOverHotspot.erase ();

    if (m_sMouseDownHotspot == HotspotId)
      m_sMouseDownHotspot.erase ();

    }

  CHotspot * pHotspot = new CHotspot;

  pHotspot->m_rect              = CRect (Left, Top, FixRight (Right), FixBottom (Bottom));
  pHotspot->m_sMouseOver        = MouseOver;
  pHotspot->m_sCancelMouseOver  = CancelMouseOver;
  pHotspot->m_sMouseDown        = MouseDown;
  pHotspot->m_sCancelMouseDown  = CancelMouseDown;
  pHotspot->m_sMouseUp          = MouseUp;
  pHotspot->m_sTooltipText      = TooltipText;
  pHotspot->m_Cursor            = Cursor;
  pHotspot->m_Flags             = Flags;

  m_Hotspots [HotspotId] = pHotspot;

  return eOK;
  }    // end of CMiniWindow::AddHotspot

// remove a previously-installed hotspot
long CMiniWindow::DeleteHotspot(LPCTSTR HotspotId)
  {

  HotspotMapIterator it = m_Hotspots.find (HotspotId);

  if (it == m_Hotspots.end ())
    return eHotspotNotInstalled;   // no such hotspot

  delete it->second;

  m_Hotspots.erase (it);

  if (m_sMouseOverHotspot == HotspotId)
    m_sMouseOverHotspot.erase ();

  if (m_sMouseDownHotspot == HotspotId)
    m_sMouseDownHotspot.erase ();

  if (m_Hotspots.empty ())
    m_sCallbackPlugin.erase ();

  return eOK;
  }    // end of CMiniWindow::DeleteHotspot

// return list of all hotspots in this miniwindow
void CMiniWindow::HotspotList(VARIANT & vaResult)
  {
  COleSafeArray sa;   // for array list

  long iCount = 0;
  
  // put the arrays into the array
  if (!m_Hotspots.empty ())    // cannot create empty dimension
    {
    sa.CreateOneDim (VT_VARIANT, m_Hotspots.size ());

    for (HotspotMapIterator it = m_Hotspots.begin (); 
         it != m_Hotspots.end ();
         it++)
           {
            // the array must be a bloody array of variants, or VBscript kicks up
            COleVariant v (it->first.c_str ());
            sa.PutElement (&iCount, &v);
            iCount++;
           }

    } // end of having at least one

	vaResult = sa.Detach ();

  }    // end of CMiniWindow::HotspotList

// delete all hotspots
long CMiniWindow::DeleteAllHotspots()
  {
  // delete our hotspots
  for (HotspotMapIterator hit = m_Hotspots.begin (); 
       hit != m_Hotspots.end ();
       hit++)
         delete hit->second;

  m_Hotspots.clear ();
  m_sMouseOverHotspot.erase ();
  m_sMouseDownHotspot.erase ();
  m_sCallbackPlugin.erase ();
  return eOK;
  }    // end of CMiniWindow::DeleteAllHotspots

// get information about a hotspot
void CMiniWindow::HotspotInfo(LPCTSTR HotspotId, long InfoType, VARIANT & vaResult)
  {

  HotspotMapIterator it = m_Hotspots.find (HotspotId);

  if (it == m_Hotspots.end ())
    return;   // no such hotspot

  CHotspot * pHotspot = it->second;

  switch (InfoType)
    {

    case  1:  SetUpVariantLong   (vaResult, pHotspot->m_rect.left);                 break; // left 
    case  2:  SetUpVariantLong   (vaResult, pHotspot->m_rect.top);                  break; // top
    case  3:  SetUpVariantLong   (vaResult, pHotspot->m_rect.right);                break; // right
    case  4:  SetUpVariantLong   (vaResult, pHotspot->m_rect.bottom);               break; // bottom
    case  5:  SetUpVariantString (vaResult, pHotspot->m_sMouseOver.c_str ());       break;             
    case  6:  SetUpVariantString (vaResult, pHotspot->m_sCancelMouseOver.c_str ()); break;             
    case  7:  SetUpVariantString (vaResult, pHotspot->m_sMouseDown.c_str ());       break;             
    case  8:  SetUpVariantString (vaResult, pHotspot->m_sCancelMouseDown.c_str ()); break;             
    case  9:  SetUpVariantString (vaResult, pHotspot->m_sMouseUp.c_str ());         break;             
    case 10:  SetUpVariantString (vaResult, pHotspot->m_sTooltipText.c_str ());     break;             
    case 11:  SetUpVariantLong   (vaResult, pHotspot->m_Cursor);                    break; // cursor code
    case 12:  SetUpVariantLong   (vaResult, pHotspot->m_Flags);                     break; // flags

    default:
      vaResult.vt = VT_NULL;
      break;

    } // end of switch

  }    // end of CMiniWindow::HotspotInfo


long CMiniWindow::ImageOp(short Action, 
                          long Left, long Top, long Right, long Bottom, 
                          long PenColour, long PenStyle, long PenWidth, 
                          long BrushColour, LPCTSTR ImageId, 
                          long EllipseWidth, long EllipseHeight)
  {

  long iResult = eUnknownOption;

  ImageMapIterator it = m_Images.find (ImageId);

  if (it == m_Images.end ())
    return eImageNotInstalled;

  CBitmap * bitmap = it->second;

  dc.SetBkMode (OPAQUE);

  // for monochrome bitmaps, 1-bits will come out in pen colour, and 0-bits will come out in background colour
  dc.SetTextColor (BrushColour);  // for patterned brushes
  dc.SetBkColor (PenColour);      // for hatched brushes and patterned brushes

  if (ValidatePenStyle (PenStyle, PenWidth))
    return ePenStyleNotValid;

  CBrush br;

  br.CreatePatternBrush (bitmap);

  // create requested pen 
  CPen pen;
  pen.CreatePen (PenStyle, PenWidth, PenColour); 
  
  // select into DC
  CPen* oldPen = dc.SelectObject(&pen);
  CBrush* oldBrush = dc.SelectObject(&br);

  switch (Action)
                                  
    {
    case 1:       // ellipse
      {
      dc.Ellipse (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)));
      iResult = eOK;
      break; 
      }

    case 2:       // rectangle
      {
      dc.Rectangle (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)));
      iResult = eOK;
      break; 
      }

    case 3:       // round rectangle
      {                                                                                                                                         
      dc.RoundRect (CRect (Left, Top, FixRight (Right), FixBottom (Bottom)), CPoint (EllipseWidth, EllipseHeight));
      iResult = eOK;
      break; 
      }


    } // end of switch

  // put things back
  dc.SelectObject (oldPen);
  dc.SelectObject (oldBrush);

  return iResult;


  }   // end of  CMiniWindow::ImageOp

long CMiniWindow::CreateImage(LPCTSTR ImageId, long Row1, long Row2, long Row3, long Row4, long Row5, long Row6, long Row7, long Row8)
  {

  ImageMapIterator it = m_Images.find (ImageId);

  if (it != m_Images.end ())
    {
    delete it->second;         // delete existing image
    m_Images.erase (it);
    }

  CBitmap * pImage = new CBitmap;

  WORD		wBits[8] = { Row1, Row2, Row3, Row4, Row5, Row6, Row7, Row8 };

  pImage->CreateBitmap (8, 8, 1, 1, wBits);

  m_Images [ImageId] = pImage;
  return eOK;

  }  // end of CMiniWindow::CreateImage
