// MiniWindow.cpp : implementation of the CMiniWindow class
//


#include "stdafx.h"
#include "MUSHclient.h"
#include "doc.h"
#include "MUSHview.h"
#include "errors.h"
#include "color.h"
#include "mainfrm.h"

#define PNG_NO_CONSOLE_IO
#include "png.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),
          m_last_mouse_update (0)
  {
  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

static int BytesPerLine (int nWidth, int nBitsPerPixel)
  {
  return ( (nWidth * nBitsPerPixel + 31) & (~31) ) / 8;
  }

// 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;
  }


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; //       "

    case 14:  SetUpVariantLong    (vaResult, m_last_mouseposition.x);  break; //  last mouse x position
    case 15:  SetUpVariantLong    (vaResult, m_last_mouseposition.y);  break; //  last mouse y position

    case 16:  SetUpVariantLong    (vaResult, m_last_mouse_update);  break;    //  last position update count

    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);
    }

  CString strFileName = FileName;

  strFileName.TrimLeft ();
  strFileName.TrimRight ();

  // no file name means get rid of image
  if (strFileName.IsEmpty ())
    return eOK;

  // have to be long enough to have x.bmp
  if (strFileName.GetLength () < 5)
    return eBadParameter;

  // handle PNG files separately
  if (strFileName.Right (4).CompareNoCase (".png") == 0)
    return LoadPngImage (ImageId, FileName);

  // must be .bmp or .png file
  if (strFileName.Right (4).CompareNoCase (".bmp") != 0)
    return eBadParameter;


  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


static void user_error_fn(png_structp png_ptr,
        png_const_charp error_msg)
  {
//  AfxMessageBox (error_msg);
  }

static void user_warning_fn(png_structp png_ptr,
    png_const_charp warning_msg)
  {
//  AfxMessageBox (warning_msg);

  }


long CMiniWindow::LoadPngImage (LPCTSTR ImageId, LPCTSTR FileName)
  {

  HBITMAP hbmp;
  
  long result =  LoadPng (FileName, hbmp);

  if (result != eOK) 
    return result;

  // make bitmap to add to images list
  CBitmap * pImage = new CBitmap;
  pImage->Attach (hbmp);
  m_Images [ImageId] = pImage;

  return eOK;
  } // end of CMiniWindow::LoadPngImage



// saves an image file
long CMiniWindow::Write (LPCTSTR FileName)
  {

  CString strFileName = FileName;

  strFileName.TrimLeft ();
  strFileName.TrimRight ();

  if (strFileName.IsEmpty ())
    return eNoNameSpecified;

  // have to be long enough to have x.bmp
  if (strFileName.GetLength () < 5)
    return eBadParameter;

  // must be .bmp or .png file
  if (strFileName.Right (4).CompareNoCase (".bmp") != 0 && 
      strFileName.Right (4).CompareNoCase (".png") != 0)
    return eBadParameter;

  // get window data
  CDC gDC;
  gDC.CreateCompatibleDC(&dc);
  CBitmap gbmp;

  BITMAPINFO bmi;
  ZeroMemory (&bmi, sizeof bmi);

  bmi.bmiHeader.biSize = sizeof bmi;
  bmi.bmiHeader.biWidth =          m_iWidth;       
  bmi.bmiHeader.biHeight =         m_iHeight;
  bmi.bmiHeader.biPlanes =         1;
  bmi.bmiHeader.biBitCount =       24;
  bmi.bmiHeader.biCompression =    BI_RGB;
  bmi.bmiHeader.biSizeImage =      m_iHeight * BytesPerLine (m_iWidth, 24);

  unsigned char * pA = NULL;

  HBITMAP hbmG = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**) &pA, NULL, 0);

  HBITMAP hOldAbmp = (HBITMAP) SelectObject(gDC.m_hDC, hbmG);

  // copy image from miniwindow to bitmap
  gDC.BitBlt (0, 0, m_iWidth, m_iHeight, &dc, 0, 0, SRCCOPY);  

  long iReturn = eOK;

    // must be .bmp or .png file
  if (strFileName.Right (4).CompareNoCase (".png") == 0)
    iReturn = WritePng (FileName, &bmi, pA);
  else
    {     // write BMP file

    // create requested file
	  CFile file;
	  if( !file.Open (FileName, CFile::modeWrite | CFile::modeCreate))
		  return eCouldNotOpenFile;

    BITMAPFILEHEADER	hdr;
    ZeroMemory (&hdr, sizeof hdr);

	  // Fill in the fields of the file header 
	  hdr.bfType		= ((WORD) ('M' << 8) | 'B');	// is always "BM"
	  hdr.bfOffBits	= sizeof hdr  + sizeof bmi.bmiHeader;
	  hdr.bfSize		=  hdr.bfOffBits + bmi.bmiHeader.biSizeImage;
 
    try
      { 
	    // Write the file header 
	    file.Write( &hdr, sizeof hdr);
  
	    // Write the bitmap info header 
	    file.Write( &bmi.bmiHeader, sizeof bmi.bmiHeader);

	    // Write the bits 
	    file.Write( pA,  bmi.bmiHeader.biSizeImage);
      }
    catch (CFileException * e)
      {
      e->Delete ();
      iReturn = eLogFileBadWrite;
      } // end of catching a file exception

    }   // end of writing BMP file

  // finished with bitmap
  SelectObject(gDC.m_hDC, hOldAbmp);
  DeleteObject (hbmG);

  return iReturn;

  } // end of CMiniWindow::Write

long CMiniWindow::WritePng (LPCTSTR FileName, const BITMAPINFO * bmi, unsigned char * pData)
  {
  // open file
  FILE *fp = fopen(FileName, "wb");
  if (!fp)
      return (eCouldNotOpenFile);

  // create PNG structure
  png_structp png_ptr = png_create_write_struct
       (PNG_LIBPNG_VER_STRING, NULL,
        user_error_fn, user_warning_fn);

  if (!png_ptr)
    {
    fclose (fp);
    return eLogFileBadWrite;
    }

  // create info structure
  png_infop info_ptr = png_create_info_struct(png_ptr);
  if (!info_ptr)
    {
    png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
    fclose (fp);
    return eLogFileBadWrite;
    }
  

  // if png fails it will longjmp back to here, so we destroy the structure,
  // close the file, and wrap up
  if (setjmp(png_jmpbuf(png_ptr)))
    {
    png_destroy_write_struct(&png_ptr, &info_ptr);
    fclose (fp);
    return eLogFileBadWrite;
    }

  // initialize IO
  png_init_io (png_ptr, fp);

  // tell PNG the file format
  png_set_IHDR  (png_ptr, 
                 info_ptr, 
                 bmi->bmiHeader.biWidth, 
                 bmi->bmiHeader.biHeight,
                 8,        // bits per pixel
                 PNG_COLOR_TYPE_RGB, 
                 PNG_INTERLACE_NONE,
                 PNG_COMPRESSION_TYPE_DEFAULT, 
                 PNG_FILTER_TYPE_DEFAULT);

  // set up pointer to each row of data
  vector<png_byte *> row_pointers;
  row_pointers.resize (bmi->bmiHeader.biHeight);

  long bpl = BytesPerLine (bmi->bmiHeader.biWidth, 24);

  long row;
  unsigned char * p = pData;

  // have to reverse row order
  for (row = 0; row < info_ptr->height; row++, p += bpl)
     row_pointers [bmi->bmiHeader.biHeight - row - 1] = p;

  // tell png where our pixel data is
  png_set_rows (png_ptr, info_ptr, &row_pointers [0]);

  // write out the image data
  png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_BGR, NULL);

  // wrap up
  png_write_end (png_ptr, info_ptr);
  
  // done with data
  png_destroy_write_struct (&png_ptr, &info_ptr);

  // done with file
  fclose (fp);

  return eOK;
  } // end of CMiniWindow::WritePng


/*

  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);

  dc.SetStretchBltMode (HALFTONE);  // looks better when squashed

  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 - SrcLeft, SrcBottom - SrcTop, 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;

  // bottom row comes first in a bitmap
  WORD		wBits[8] = { Row8, Row7, Row6, Row5, Row4, Row3, Row2, Row1 };

  pImage->CreateBitmap (8, 8, 1, 1, wBits);

  m_Images [ImageId] = pImage;
  return eOK;

  }  // end of CMiniWindow::CreateImage

// see: http://www.nathanm.com/photoshop-blending-math/
// and: http://www.pegtop.net/delphi/articles/blendmodes/                   

#define Blend_It(Op) \
   do \
      if (Opacity < 1.0) \
        for (i = 0; i < count; i++) \
          pB [i]     = Blend_Opacity (pA [i], pB [i], Op, Opacity);\
      else\
        for (i = 0; i < count; i ++)\
          pB [i]      = Op (pA [i], pB [i]);\
   while (false)

// we have to do this a row at a time, because there might be a filler at the end of each row
// and thus, a discontinuity which throws out the r/g/b sequence
#define Colour_Op(fR,fG,fB) \
   do \
     {  \
     for (row = 0; row < iHeight; row++)\
      {\
      long base = row * perline;\
      unsigned char rA, gA, bA, rB, gB, bB; \
      if (Opacity < 1.0) \
        for (i = 0; i < perline - 2; ) \
          {\
          bA = pA [base + i];     \
          gA = pA [base + i + 1]; \
          rA = pA [base + i + 2]; \
          bB = pB [base + i];     \
          gB = pB [base + i + 1]; \
          rB = pB [base + i + 2]; \
          pB [base + i] = Simple_Opacity (bB, fB, Opacity); \
          i++;\
          pB [base + i] = Simple_Opacity (gB, fG, Opacity); \
          i++;\
          pB [base + i] = Simple_Opacity (rB, fR, Opacity); \
          i++;\
          }\
      else\
        for (i = 0; i < perline - 2;)\
          {\
          bA = pA [base + i];     \
          gA = pA [base + i + 1]; \
          rA = pA [base + i + 2]; \
          bB = pB [base + i];     \
          gB = pB [base + i + 1]; \
          rB = pB [base + i + 2]; \
          pB [base + i] = fB;  \
          i++;\
          pB [base + i] = fG;  \
          i++;\
          pB [base + i] = fR;  \
          i++;\
          }\
        }\
       }\
   while (false)



// see also: CMUSHclientDoc::BlendPixel

long CMiniWindow::BlendImage(LPCTSTR ImageId, 
                  long Left, long Top, long Right, long Bottom, 
                  short Mode, double Opacity, 
                  long SrcLeft, long SrcTop, long SrcRight, long SrcBottom)
  {

  // constrain to what we actually have
  if (Left < 0)
    Left = 0;
  if (Top < 0)
    Top = 0;
  if (Right > m_iWidth)
    Right = m_iWidth;
  if (Bottom > m_iHeight)
    Bottom = m_iHeight;

  ImageMapIterator it = m_Images.find (ImageId);

  if (it == m_Images.end ())
    return eImageNotInstalled;

  if (Opacity < 0.0 || Opacity > 1.0)
    return eBadParameter;

  CBitmap * bitmap = it->second;

  BITMAP  bi;
  bitmap->GetBitmap(&bi);

  // calculate size of desired rectangle
  long iWidth = FixRight (Right) - Left;
  long iHeight = FixBottom (Bottom) - Top;

  // constrain to what we actually have
  if (SrcLeft < 0)
    SrcLeft = 0;
  if (SrcTop < 0)
    SrcTop = 0;
  if (SrcRight > bi.bmWidth )
    SrcRight = bi.bmWidth ;
  if (SrcBottom > bi.bmHeight)
    SrcBottom = bi.bmHeight;

  // adjust so that -1 means 1 from right
  if (SrcRight <= 0) 
    SrcRight = bi.bmWidth + SrcRight;

  if (SrcBottom <= 0) 
    SrcBottom =  bi.bmHeight + SrcBottom;

  // width and height are the smaller of the two rectangles
  iWidth = min (iWidth, SrcRight - SrcLeft);
  iHeight = min (iHeight, SrcBottom - SrcTop);

  if (iWidth <= 0 || iHeight <= 0)   // sanity check
    return eOK;

  BITMAPINFO bmi;
  ZeroMemory (&bmi, sizeof bmi);

  bmi.bmiHeader.biSize = sizeof bmi;
  bmi.bmiHeader.biWidth =          iWidth;       
  bmi.bmiHeader.biHeight =         iHeight;
  bmi.bmiHeader.biPlanes =         1;
  bmi.bmiHeader.biBitCount =       24;
  bmi.bmiHeader.biCompression =    BI_RGB;
  bmi.bmiHeader.biSizeImage =      iHeight * BytesPerLine (iWidth, 24);

  // upper layer (from image id)
  CDC A_DC;
  A_DC.CreateCompatibleDC(&dc);

  unsigned char * pA = NULL;

  HBITMAP hbmA = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**) &pA, NULL, 0);

  HBITMAP hOldAbmp = (HBITMAP) SelectObject(A_DC.m_hDC, hbmA);

  //copy part from image to upper layer
  CDC bmDC;
  bmDC.CreateCompatibleDC(&dc);
  CBitmap *pOldbmp = bmDC.SelectObject(bitmap);

  A_DC.BitBlt (0, 0, iWidth, iHeight, &bmDC, SrcLeft, SrcTop, SRCCOPY);  
  bmDC.SelectObject(pOldbmp);


  // base image (from miniwindow)

  CDC B_DC;
  B_DC.CreateCompatibleDC(&dc);
  CBitmap B_bmp;

  unsigned char * pB = NULL;

  HBITMAP hbmB = CreateDIBSection(dc.m_hDC, &bmi, DIB_RGB_COLORS, (void**) &pB, NULL, 0);

  HBITMAP hOldBbmp = (HBITMAP) SelectObject(B_DC.m_hDC, hbmB);

  // copy base image from miniwindow to bitmap
  B_DC.BitBlt (0, 0, iWidth, iHeight, &dc, Left, Top, SRCCOPY);  


  // manipulate image here   A = blend, B = base


  long count = bmi.bmiHeader.biSizeImage;
  long perline = BytesPerLine (iWidth, 24);
 
  long i, row;

  // precompute cos table
  static unsigned char cos_table [256];
  static bool bTableComputed = false;

  if (!bTableComputed)
    {
    double pi_div255 = 3.1415926535898 / 255.0;
    for (i = 0; i < 256; i++)
      {
      double a = 64.0 - cos ((double) i * pi_div255) * 64.0;
      cos_table [i] = (uint8) (a + 0.5);  // round
      }
    bTableComputed = true;
    } // table needs computing

  switch (Mode)
    {

    // normal modes
    case  1: Blend_It (Blend_Normal);       break;
    case  2: Blend_It (Blend_Average);      break;
    case  3: Blend_It (Blend_Interpolate);  break;

    case  4:  // dissolve - randomly choose pixels based on opacity
      for (i = 0; i < count; i++) 
        pB [i] = (genrand () < Opacity) ? pA [i] : pB [i];
      break;

    // darkening modes
    case  5: Blend_It (Blend_Darken);     break;
    case  6: Blend_It (Blend_Multiply);   break;
    case  7: Blend_It (Blend_ColorBurn);  break;
    case  8: Blend_It (Blend_LinearBurn); break;
    case  9: Blend_It (Blend_InverseColorBurn);  break;
    case 10: Blend_It (Blend_Subtract);   break;
    
    // lightening modes
    case 11: Blend_It (Blend_Lighten);    break;
    case 12: Blend_It (Blend_Screen);     break;
    case 13: Blend_It (Blend_ColorDodge); break;
    case 14: Blend_It (Blend_LinearDodge);break;
    case 15: Blend_It (Blend_InverseColorDodge);  break;
    case 16: Blend_It (Blend_Add);        break;
      
    // soft/hard light etc.
    case 17: Blend_It (Blend_Overlay);    break;
    case 18: Blend_It (Blend_SoftLight);  break;
    case 19: Blend_It (Blend_HardLight);  break;
    case 20: Blend_It (Blend_VividLight); break;
    case 21: Blend_It (Blend_LinearLight);break;
    case 22: Blend_It (Blend_PinLight);   break;
    case 23: Blend_It (Blend_HardMix);    break;

    // difference modes
    case 24: Blend_It (Blend_Difference); break;
    case 25: Blend_It (Blend_Exclusion);  break;

    // glow modes
    case 26: Blend_It (Blend_Reflect);    break;
    case 27: Blend_It (Blend_Glow);       break;
    case 28: Blend_It (Blend_Freeze);     break;
    case 29: Blend_It (Blend_Heat);       break;
        
    case 30: Blend_It (Blend_Negation);   break;
    case 31: Blend_It (Blend_Phoenix);    break;
    case 32: Blend_It (Blend_Stamp);      break;

    // logical modes
    case 33: Blend_It (Blend_Xor);      break;
    case 34: Blend_It (Blend_And);      break;
    case 35: Blend_It (Blend_Or);       break;

    // the follow modes take one colour from the blend and retain 2 from the base
    case 36: Colour_Op (rA, gB, bB); break;  // red  
    case 37: Colour_Op (rB, gA, bB); break;  // green
    case 38: Colour_Op (rB, gB, bA); break;  // blue 

    // the follow modes take two colours from the blend and retain 1 from the base
    case 39: Colour_Op (rA, gA, bB); break;  // yellow 
    case 40: Colour_Op (rB, gA, bA); break;  // cyan    
    case 41: Colour_Op (rA, gB, bA); break;  // magenta    

    // limit green
    case 42: Colour_Op (rA, (gA > rA) ? rA : gA, bA); break;  // green limited by red    
    case 43: Colour_Op (rA, (gA > bA) ? bA : gA, bA); break;  // green limited by blue    
    case 44: Colour_Op (rA, (gA > ((rA + bA) / 2)) ? ((rA + bA) / 2) : gA, bA); break;  // green limited by average of red and blue    

    // limit blue
    case 45: Colour_Op (rA, gA, (bA > rA) ? rA : bA); break;  // blue limited by red    
    case 46: Colour_Op (rA, gA, (bA > gA) ? gA : bA); break;  // blue limited by green    
    case 47: Colour_Op (rA, gA, (bA > ((rA + gA) / 2)) ? ((rA + gA) / 2) : bA); break; // blue limited by average of red and green    

    // limit red
    case 48: Colour_Op ((rA > gA) ? gA : rA, gA, bA); break;  // red limited by green    
    case 49: Colour_Op ((rA > bA) ? bA : rA, gA, bA); break;  // red limited by blue    
    case 50: Colour_Op ((rA > ((gA + bA) / 2)) ? ((gA + bA) / 2) : rA, gA, bA); break;  // red limited by average of green and blue    

    // select single colour
    case 51: Colour_Op (rA, 0, 0); break;  // red only       (looks red)
    case 52: Colour_Op (0, gA, 0); break;  // green only     (looks green)
    case 53: Colour_Op (0, 0, bA); break;  // blue only      (looks blue)

    // discard single colour
    case 54: Colour_Op (0, gA, bA); break;  // discard red    (looks cyan)
    case 55: Colour_Op (rA, 0, bA); break;  // discard green  (looks magenta)
    case 56: Colour_Op (rA, gA, 0); break;  // discard blue   (looks yellow)

    // one colour to all channels  (ie. it looks grey)
    case 57: Colour_Op (rA, rA, rA); break;  // all red   
    case 58: Colour_Op (gA, gA, gA); break;  // all green   
    case 59: Colour_Op (bA, bA, bA); break;  // all blue   

    case 60:           // Hue mode
      {
       for (row = 0; row < iHeight; row++)
        {
        long base = row * perline;
        unsigned char rA, gA, bA, rB, gB, bB; 
        for (i = 0; i < perline - 2; ) 
          {
          CColor cA, cB;    // A = blend, B = base
          bA = pA [base + i];     
          gA = pA [base + i + 1]; 
          rA = pA [base + i + 2]; 
          bB = pB [base + i];  
          gB = pB [base + i + 1];
          rB = pB [base + i + 2];

          cA.SetRGB (rA, gA, bA);
          cB.SetRGB (rB, gB, bB);

          cB.SetHue (cA.GetHue ());  // hue of blend image, others from base image
          pB [base + i] = Simple_Opacity (bB, cB.GetBlue (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (gB, cB.GetGreen (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (rB, cB.GetRed (), Opacity); 
          i++;
          }
         }  // end for each row
       }
      break;

    case 61:           // Saturation mode
      {
       for (row = 0; row < iHeight; row++)
        {
        long base = row * perline;
        unsigned char rA, gA, bA, rB, gB, bB; 
        for (i = 0; i < perline - 2; ) 
          {
          CColor cA, cB;    // A = blend, B = base
          bA = pA [base + i];     
          gA = pA [base + i + 1]; 
          rA = pA [base + i + 2]; 
          bB = pB [base + i];  
          gB = pB [base + i + 1];
          rB = pB [base + i + 2];

          cA.SetRGB (rA, gA, bA);
          cB.SetRGB (rB, gB, bB);

          cB.SetSaturation (cA.GetSaturation ());  // saturation of blend image, others from base image
          pB [base + i] = Simple_Opacity (bB, cB.GetBlue (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (gB, cB.GetGreen (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (rB, cB.GetRed (), Opacity); 
          i++;
          }
         }  // end for each row
       }
      break;

    case 62:           // Colour mode
      {
       for (row = 0; row < iHeight; row++)
        {
        long base = row * perline;
        unsigned char rA, gA, bA, rB, gB, bB; 
        for (i = 0; i < perline - 2; ) 
          {
          CColor cA, cB;    // A = blend, B = base
          bA = pA [base + i];     
          gA = pA [base + i + 1]; 
          rA = pA [base + i + 2]; 
          bB = pB [base + i];  
          gB = pB [base + i + 1];
          rB = pB [base + i + 2];

          cA.SetRGB (rA, gA, bA);
          cB.SetRGB (rB, gB, bB);

          cB.SetHue (cA.GetHue ());  // hue of blend image, 
          cB.SetSaturation (cA.GetSaturation ());  // saturation of blend image, luminance from base image
          pB [base + i] = Simple_Opacity (bB, cB.GetBlue (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (gB, cB.GetGreen (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (rB, cB.GetRed (), Opacity); 
          i++;
          }
         }  // end for each row
       }
      break;

      /*

    Note: I don't get the same results here as using Photoshop on the same images. Not quite sure why,
    some test code below seems to indicate that for all possible values of RGB, they map to HSL and back
    again without error.

    In the absence of any obvious bug, I am assuming that by plugging the Luminance from one colour into the
    L channel of another, I am creating an out-of-gamut colour, which then cannot be represented properly.

    I can't find, so far, any code that would limit the value to be in-gamut, so for now this Luminance (and
    Colour, Saturation and Hue too, probably) should be regarded as experimental.  - NJG - 2/Aug/2008.

    */

    case 63:           // Luminance mode
      {
       for (row = 0; row < iHeight; row++)
        {
        long base = row * perline;
        unsigned char rA, gA, bA, rB, gB, bB; 
        for (i = 0; i < perline - 2; ) 
          {
          CColor cA, cB;    // A = blend, B = base
          bA = pA [base + i];     
          gA = pA [base + i + 1]; 
          rA = pA [base + i + 2]; 
          bB = pB [base + i];  
          gB = pB [base + i + 1];
          rB = pB [base + i + 2];

          // bit of a fudge here, but what can you do?

          // this is going to brighten the really dark pixels, however at least then they
          // should retain their base colour, rather than be promoted up to something garish

//          if (rB < 12) rB = 12;
//          if (gB < 12) gB = 12;
//          if (bB < 12) bB = 12;

          cA.SetRGB (rA, gA, bA);
          cB.SetRGB (rB, gB, bB);

          cB.SetLuminance (cA.GetLuminance ());  // luminance from blend image, others from base image

          pB [base + i] = Simple_Opacity (bB, cB.GetBlue (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (gB, cB.GetGreen (), Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (rB, cB.GetRed (), Opacity); 
          i++;

          }
         }  // end for each row
       }
      break;

    case 64:           // HSL (hue to red, saturation to green, luminance to blue)
      {
       for (row = 0; row < iHeight; row++)
        {
        long base = row * perline;
        unsigned char rA, gA, bA, rB, gB, bB; 
        for (i = 0; i < perline - 2; ) 
          {
          CColor cA, cB;    // A = blend, B = base
          bA = pA [base + i];     
          gA = pA [base + i + 1]; 
          rA = pA [base + i + 2]; 
          bB = pB [base + i];  
          gB = pB [base + i + 1];
          rB = pB [base + i + 2];

          cA.SetRGB (rA, gA, bA);

          pB [base + i] = Simple_Opacity (bB, cA.GetLuminance () * 255, Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (gB, cA.GetSaturation () * 255, Opacity); 
          i++;
          pB [base + i] = Simple_Opacity (rB, (cA.GetHue () / 360.0) * 255, Opacity); 
          i++;

          }
         }  // end for each row
       }
      break;

      /*
    case 65: // testing  conversion to/from RGB to HSL and back again is consistent.
      {
      int r, g, b;
      CColor cA;  

      for (r = 0; r < 256; r ++)
        for (g = 0; g < 256; g ++)
          for (b = 0; b < 256; b ++)
            {
            cA.SetRGB (r, g, b);
            cA.GetLuminance ();  // force change to HSL
            ASSERT (r == cA.GetRed ());
            ASSERT (g == cA.GetGreen ());
            ASSERT (b == cA.GetBlue ());

            } 


      }
    break;

      */

    default: return eUnknownOption;
    }

  // copy result back

  dc.BitBlt (Left, Top, iWidth, iHeight, &B_DC, 0, 0, SRCCOPY);  


  SelectObject(A_DC.m_hDC, hOldAbmp);
  SelectObject(B_DC.m_hDC, hOldBbmp);

  DeleteObject (hbmA);
  DeleteObject (hbmB);

  return eOK;



  }   // end of CMiniWindow::BlendImage


// copies one miniwindow image as a stand-alone image
long CMiniWindow::ImageFromWindow(LPCTSTR ImageId, CMiniWindow * pSrcWindow)
  {


  ImageMapIterator it = m_Images.find (ImageId);

  if (it != m_Images.end ())
    {
    delete it->second;         // delete existing image
    m_Images.erase (it);
    }

  // make new bitmap of appropriate size to hold image from other miniwindow
  CBitmap * pImage = new CBitmap;
  pImage->CreateCompatibleBitmap (&dc, pSrcWindow->m_iWidth, pSrcWindow->m_iHeight);

  // prepare to copy it
  CDC bmDC;
  bmDC.CreateCompatibleDC(&dc);
  CBitmap *pOldbmp = bmDC.SelectObject(pImage);

  // copy into our bitmap
  bmDC.BitBlt (0, 0, pSrcWindow->m_iWidth, pSrcWindow->m_iHeight, &pSrcWindow->dc, 0, 0, SRCCOPY);  

  // done
  bmDC.SelectObject(pOldbmp);

  // save in map
  m_Images [ImageId] = pImage;
  return eOK;

  } // end of CMiniWindow::ImageFromWindow


static void HorizontalLinearGradient (const COLORREF StartColour, const double rdiff, const double gdiff, const double bdiff, 
                                      unsigned char * pBuffer,
                                      const long iWidth,
                                      const long iHeight)
  {
  double rval = GetRValue (StartColour),
        gval = GetGValue (StartColour),
        bval = GetBValue (StartColour);

  double rinc = rdiff / (double) (iWidth - 1), 
        ginc = gdiff / (double) (iWidth - 1), 
        binc = bdiff / (double) (iWidth - 1);

  long row, col;
  uint8 r = 0, g = 0, b = 0;

  int increment =  BytesPerLine (iWidth, 24);

   // main loop is columns
   for (col = 0; col < iWidth; col++)
     {
     r = (uint8) rval;
     g = (uint8) gval;
     b = (uint8) bval;
     unsigned char * p = pBuffer + col * 3;
     for (row = 0; row < iHeight; row++)
       {
       p [0] = b;
       p [1] = g;
       p [2] = r;
       p += increment;
       }  // end of each row
     rval += rinc;
     gval += ginc;
     bval += binc;                      
     }  // end of each column


  }  // end of HorizontalLinearGradient


static void VerticalLinearGradient (const COLORREF EndColour, const double rdiff, const double gdiff, const double bdiff, 
                                      unsigned char * pBuffer,
                                      const long iWidth,
                                      const long iHeight)
  {
  double rval = GetRValue (EndColour),
         gval = GetGValue (EndColour),
         bval = GetBValue (EndColour);

  double rinc = - rdiff / (double) (iHeight - 1), 
         ginc = - gdiff / (double) (iHeight - 1), 
         binc = - bdiff / (double) (iHeight - 1);

  long row, col;
  uint8 r = 0, g = 0, b = 0;

  int increment =  BytesPerLine (iWidth, 24);

   // main loop is rows
   for (row = 0; row < iHeight; row++)
     {
     r = (uint8) rval;
     g = (uint8) gval;
     b = (uint8) bval;
     unsigned char * p = pBuffer + increment * row;
     for (col = 0; col < iWidth; col++)
       {
       p [0] = b;
       p [1] = g;
       p [2] = r;
       p += 3;
       }  // end of each column
     rval += rinc;
     gval += ginc;
     bval += binc;                      
     }  // end of each row
   
  }  // end of VerticalLinearGradient


static void MakeTexture (const COLORREF Multiplier, 
                         unsigned char * pBuffer,
                         const long iWidth,
                         const long iHeight)
  {

  long   rval = GetRValue (Multiplier),
         gval = GetGValue (Multiplier),
         bval = GetBValue (Multiplier);

  long row, col;
  long c;

  int increment =  BytesPerLine (iWidth, 24);

   // main loop is columns
   for (col = 0; col < iWidth; col++)
     {
     unsigned char * p = pBuffer + col * 3;
     for (row = 0; row < iHeight; row++)
       {
       c = col ^ row;
       p [0] = c * bval;
       p [1] = c * gval;
       p [2] = c * rval;
       p += increment;
       }  // end of each row
     }  // end of each column


  }  // end of MakeTexture


// this is a load of old cobblers

#if 0

static void HorizontalLogGradient (const COLORREF StartColour, const double rdiff, const double gdiff, const double bdiff, 
                                      unsigned char * pBuffer,
                                      const long iWidth,
                                      const long iHeight,
                                      const bool rightToLeft)
  {
  double rval = GetRValue (StartColour),
         gval = GetGValue (StartColour),
         bval = GetBValue (StartColour);


  double rfactor = pow (abs (rdiff), (double) 1.0 / (double) iWidth);
  double gfactor = pow (abs (gdiff), (double) 1.0 / (double) iWidth);
  double bfactor = pow (abs (bdiff), (double) 1.0 / (double) iWidth);


  long row, col;
  uint8 r = 0, g = 0, b = 0;

  double rinc = rfactor, 
         ginc = gfactor, 
         binc = bfactor;

  int increment =  BytesPerLine (iWidth, 24);

   // main loop is columns
   for (col = 0; col < iWidth; col++)
     {


     r = (uint8) rval;
     g = (uint8) gval;
     b = (uint8) bval;
     unsigned char * p = pBuffer + col * 3;
     for (row = 0; row < iHeight; row++)
       {
       p [0] = b;
       p [1] = g;
       p [2] = r;
       p += increment;
       }  // end of each row

     rinc *= rfactor;
     ginc *= gfactor;
     binc *= bfactor;

     if (rightToLeft)
       {
       rval = GetRValue (StartColour) - rinc;
       gval = GetGValue (StartColour) - ginc;
       bval = GetBValue (StartColour) - binc;
       }
     else
       {
       rval = GetRValue (StartColour) + rinc;
       gval = GetGValue (StartColour) + ginc;
       bval = GetBValue (StartColour) + binc;
       }
     }  // end of each column


  }  // end of HorizontalLogGradient

#endif // 0

long CMiniWindow::Gradient(long Left, long Top, long Right, long Bottom, 
                      long StartColour, long EndColour, 
                      short Mode)
  {
  // calculate size of desired rectangle
  long iWidth = FixRight (Right) - Left;
  long iHeight = FixBottom (Bottom) - Top;

  if (iWidth <= 0 || iHeight <= 0)   // sanity check
    return eOK;

  // upper layer (from image id)
  CDC gDC;
  gDC.CreateCompatibleDC(&dc);
  CBitmap gbmp;

  BITMAPINFO bmi;
  ZeroMemory (&bmi, sizeof bmi);

  bmi.bmiHeader.biSize = sizeof bmi;
  bmi.bmiHeader.biWidth =          iWidth;       
  bmi.bmiHeader.biHeight =         iHeight;
  bmi.bmiHeader.biPlanes =         1;
  bmi.bmiHeader.biBitCount =       24;
  bmi.bmiHeader.biCompression =    BI_RGB;
  bmi.bmiHeader.biSizeImage =      iHeight * BytesPerLine (iWidth, 24);

  unsigned char * pA = NULL;

  HBITMAP hbmG = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**) &pA, NULL, 0);

  HBITMAP hOldAbmp = (HBITMAP) SelectObject(gDC.m_hDC, hbmG);


  double rdiff = GetRValue (EndColour) - GetRValue (StartColour),
         gdiff = GetGValue (EndColour) - GetGValue (StartColour),
         bdiff = GetBValue (EndColour) - GetBValue (StartColour);


  switch (Mode)
    {
    case 1 : // horizontal (left to right)
       HorizontalLinearGradient (StartColour, rdiff, gdiff, bdiff, pA, iWidth, iHeight);
       break;  // end of horizontal


    case 2 : // vertical  (top to bottom)
       VerticalLinearGradient (EndColour, rdiff, gdiff, bdiff, pA, iWidth, iHeight);
       break;  // end of vertical

    case 3 : // texture 
       MakeTexture (StartColour, pA, iWidth, iHeight);
       break;  // end of texture

    // forget it!

#if 0
    case 3 : // horizontal (left to right)
       HorizontalLogGradient (StartColour, rdiff, gdiff, bdiff, pA, iWidth, iHeight, false);
       break;  // end of horizontal

    case 4 : // horizontal (right to left)
       HorizontalLogGradient (EndColour, rdiff, gdiff, bdiff, pA, iWidth, iHeight, true);
       break;  // end of horizontal

#endif // 0

    default: return eUnknownOption;

    } // end of switch


  // copy result back

  dc.BitBlt (Left, Top, iWidth, iHeight, &gDC, 0, 0, SRCCOPY);  


  SelectObject(gDC.m_hDC, hOldAbmp);

  DeleteObject (hbmG);

  return eOK;

  } // end of CMiniWindow::Gradient

static void GeneralFilter (unsigned char * inbuf, long iWidth, long iHeight, 
                            const long iPerLine, 
                            const double Options,
                            const double * iMatrix,
                            const double iDivisor)
  {

  unsigned char * pi;
  long total;

  long row, col, rgb;
  int i;
  int iLastByte = iWidth * 3;
  long window [5];

  if (Options != 2)    // 2 = vertical only
    {
    // horizontal
    // main loop is rows
    for (row = 0; row < iHeight; row++)
     {

     pi = inbuf + iPerLine * row;
   
    
     for (rgb = 0; rgb < 3; rgb++)
       {

       // preload 4 slots in window for edge case  (-2 to +1)
       for (col = 0; col < 4; col++)
          window [col + 1] = pi [min (max (rgb + (col * 3) - 6, 0), iLastByte - 1)];

       for (col = 0; col < iLastByte - 4; col += 3)
         {
         // we add 3 to get to the next pixel in that colour (R, G or B)

         // insert right-most byte into window
       
         window [0] = window [1];
         window [1] = window [2];
         window [2] = window [3];
         window [3] = window [4];
         window [4] = pi [min (max (col + rgb + 6, 0), iLastByte - 1)];

         total = 0;

         for (i = 0; i < 5; i++)
            total +=  window [i] * iMatrix [i];

         pi [col + rgb] = (uint8) (min (max (total / iDivisor, 0), 255));
         }   // end of each column
       }     // end of each colour

     }  // end of each row
    }   // horizontal wanted

  if (Options != 1)    // 1 = horizontal only
  {

  // vertical
    // main loop is columns
    for (col = 0; col < iLastByte; col++)
      {

       // preload 4 slots in window for edge case  (-2 to +1)
       for (row = 0; row < 4; row++)
         {
         long from = min (max (row - 2, 0), iHeight - 1);
         pi = inbuf + col + (from * iPerLine);
         window [row + 1] = *pi;
         }

      for (row = 0; row < iHeight; row++)
       {

       long from = min (max (row + 3, 0), iHeight - 1);
       pi = inbuf + col + (from * iPerLine);

       // insert right-most byte into window
     
       window [0] = window [1];
       window [1] = window [2];
       window [2] = window [3];
       window [3] = window [4];
       window [4] = *pi; 

       total = 0;

       for (i = 0; i < 5; i++)
          total +=  window [i] * iMatrix [i];

       pi = inbuf + col + (row * iPerLine);
       pi [0] = (uint8) (min (max (total / iDivisor, 0), 255));

       }  // end of each row
      }  // end of each column
    }  // vertical wanted

  } // end of Blur


static void Noise (unsigned char * inbuf, long iWidth, long iHeight, long iPerLine, double Options)
  {

  unsigned char * pi = inbuf;
  long count = iPerLine * iHeight;
  long i, c;
  double threshold = Options / 100.0;
  for (i = 0; i < count; i++)
    {
    c = *pi;
    c += (128 - genrand () * 256) * threshold;
    *pi++  = CLAMP (c);
    }

  } // end of Noise

static void MonoNoise (unsigned char * inbuf, long iWidth, long iHeight, long iPerLine, double Options)
  {

  unsigned char * pi = inbuf;
  long count = iPerLine * iHeight / 3;
  long i, j, c, row;
  double threshold = Options / 100.0;


  for (row = 0; row < iHeight; row++)
    {
    unsigned char * pi = inbuf + (row * iPerLine);
    for (i = 0; i < iWidth; i ++) 
      {
      j = (128 - genrand () * 256) * threshold;

      c = *pi + j;
      *pi++  = CLAMP (c);
      c = *pi + j;
      *pi++  = CLAMP (c);
      c = *pi + j;
      *pi++  = CLAMP (c);
      }
     }  // end for each row


  } // end of MonoNoise


static void Brightness (unsigned char * inbuf, long iWidth, long iHeight, long iPerLine, double Options)
  {

  unsigned char * pi = inbuf;
  long count = iPerLine * iHeight;
  long i, c;
  for (i = 0; i < count; i++)
    {
    c = *pi;
    c += Options;
    *pi++  = CLAMP (c);
    }

  } // end of Brightness


static void BrightnessMultiply (unsigned char * inbuf, long iWidth, long iHeight, long iPerLine, double Options)
  {

  unsigned char * pi = inbuf;
  long count = iPerLine * iHeight;
  long i, c;
  for (i = 0; i < count; i++)
    {
    c = *pi;
    c *= Options;
    *pi++  = CLAMP (c);
    }

  } // end of BrightnessMultiply

static void Contrast (unsigned char * inbuf, long iWidth, long iHeight, long iPerLine, double Options)
  {

  unsigned char lookup [256];  // lookup table for speed

  unsigned char * pi = inbuf;
  long count = iPerLine * iHeight;
  long i;
  double c;
  
  for (i = 0; i < 256; i++)
    {
    c = i - 128;    // center on zero
    c *= Options;  // multiply by contrast
    c += 128;  // put back
    lookup [i] = CLAMP (c);
    }  // end of for loop

  // now convert image using lookup table
  for (i = 0; i < count; i++)
    *pi++ = lookup [*pi];

  } // end of Contrast

static void Gamma (unsigned char * inbuf, long iWidth, long iHeight, long iPerLine, double Options)
  {

  unsigned char lookup [256];  // lookup table for speed

  unsigned char * pi = inbuf;
  long count = iPerLine * iHeight;
  long i;
  double c;
  if (Options < 0.0)
    Options = 0.0;

  for (i = 0; i < 256; i++)
    {
    c = ( (double) i) / 255.0;  // normalize it
    c = pow (c, Options);
    c *= 255;
    lookup [i] = CLAMP (c);
    }  // end of for loop

  // now convert image using lookup table
  for (i = 0; i < count; i++)
    *pi++ = lookup [*pi];
    
  } // end of Gamma


static void ColourBrightness (unsigned char * inbuf, long iWidth, long iHeight, 
                              long iPerLine, double Options, long iColour)
  {

  long i, row, c;

  for (row = 0; row < iHeight; row++)
    {
    unsigned char * pi = inbuf + (row * iPerLine) + iColour;
    for (i = 0; i < iWidth; i ++) 
      {
      c = *pi;
      c += Options;
      *pi  = CLAMP (c);
      pi += 3;
      }
     }  // end for each row

  } // end of ColourBrightness


static void ColourBrightnessMultiply (unsigned char * inbuf, long iWidth, long iHeight, 
                              long iPerLine, double Options, long iColour)
  {

  long i, row, c;

  for (row = 0; row < iHeight; row++)
    {
    unsigned char * pi = inbuf + (row * iPerLine) + iColour;
    for (i = 0; i < iWidth; i ++) 
      {
      c = *pi;
      c *= Options;
      *pi  = CLAMP (c);
      pi += 3;
      }
     }  // end for each row

  } // end of ColourBrightnessMultiply


static void ColourContrast (unsigned char * inbuf, long iWidth, long iHeight, 
                            long iPerLine, double Options, long iColour)
  {

  unsigned char lookup [256];  // lookup table for speed

  long i, row;
  double c;
  
  for (i = 0; i < 256; i++)
    {
    c = i - 128;    // center on zero
    c *= Options;  // multiply by contrast
    c += 128;  // put back
    lookup [i] = CLAMP (c);
    }  // end of for loop

  // now convert image using lookup table
  for (row = 0; row < iHeight; row++)
    {
    unsigned char * pi = inbuf + (row * iPerLine) + iColour;
    for (i = 0; i < iWidth; i ++) 
      {
      *pi = lookup [*pi];
      pi += 3;
      }
     }  // end for each row

    } // end of ColourContrast

static void ColourGamma (unsigned char * inbuf, long iWidth, long iHeight, 
                         long iPerLine, double Options, long iColour)
  {

  unsigned char lookup [256];  // lookup table for speed

  long i, row;
  double c;

  if (Options < 0.0)
    Options = 0.0;

  for (i = 0; i < 256; i++)
    {
    c = ( (double) i) / 255.0;  // normalize it
    c = pow (c, Options);
    c *= 255;
    lookup [i] = CLAMP (c);
    }  // end of for loop

  // now convert image using lookup table
  for (row = 0; row < iHeight; row++)
    {
    unsigned char * pi = inbuf + (row * iPerLine) + iColour;
    for (i = 0; i < iWidth; i ++) 
      {
      *pi = lookup [*pi];
      pi += 3;
      }
     }  // end for each row
    
  } // end of ColourGamma


// see: http://en.wikipedia.org/wiki/Grayscale

static void MakeGreyscale (unsigned char * inbuf, long iWidth, long iHeight, long iPerLine, double Options, const bool bLinear)
  {

  unsigned char * pi = inbuf;
  long count = iPerLine * iHeight / 3;
  long i, row;
  double c;


  for (row = 0; row < iHeight; row++)
    {
    unsigned char * pi = inbuf + (row * iPerLine);
    for (i = 0; i < iWidth; i ++) 
      {

      if (bLinear)
        {
        c =  pi [0] + pi [1] + pi [2];
        c /= 3;
        }
      else
        c = pi [0] * 0.11 + // blue   (perceptual)
            pi [1] * 0.59 + // green
            pi [2] * 0.30;  // red

      *pi++  = CLAMP (c);
      *pi++  = CLAMP (c);
      *pi++  = CLAMP (c);
      }
     }  // end for each row


  } // end of MakeGreyscale


static void Average (unsigned char * pBuffer,
                      const long iWidth,
                      const long iHeight)
  {

  long row, col;
  __int64 r = 0, g = 0, b = 0, count = 0;

  int increment =  BytesPerLine (iWidth, 24);

  // find average

  // main loop is columns
  for (col = 0; col < iWidth; col++)
   {
   unsigned char * p = pBuffer + col * 3;
   for (row = 0; row < iHeight; row++)
     {
     b += p [0];
     g += p [1];
     r += p [2];
     count++;
     p += increment;
     }  // end of each row
   }  // end of each column

  r /= count;
  g /= count;
  b /= count;

  // apply average

  // main loop is columns
  for (col = 0; col < iWidth; col++)
   {
   unsigned char * p = pBuffer + col * 3;
   for (row = 0; row < iHeight; row++)
     {
     p [0] = CLAMP (b);
     p [1] = CLAMP (g);
     p [2] = CLAMP (r);
     p += increment;
     }  // end of each row
   }  // end of each column

  }  // end of Average


// see also:  CMUSHclientDoc::FilterPixel
long CMiniWindow::Filter(long Left, long Top, long Right, long Bottom, 
                  short Operation, double Options)
  {

  // constrain to what we actually have
  if (Left < 0)
    Left = 0;
  if (Top < 0)
    Top = 0;
  if (Right > m_iWidth)
    Right = m_iWidth;
  if (Bottom > m_iHeight)
    Bottom = m_iHeight;

  // calculate size of desired rectangle
  long iWidth = FixRight (Right) - Left;
  long iHeight = FixBottom (Bottom) - Top;

  if (iWidth < 1 || iHeight < 1)   // sanity check
    return eOK;

  // upper layer (from image id)
  CDC gDC;
  gDC.CreateCompatibleDC(&dc);
  CBitmap gbmp;

  BITMAPINFO bmi;
  ZeroMemory (&bmi, sizeof bmi);

  bmi.bmiHeader.biSize = sizeof bmi;
  bmi.bmiHeader.biWidth =          iWidth;       
  bmi.bmiHeader.biHeight =         iHeight;
  bmi.bmiHeader.biPlanes =         1;
  bmi.bmiHeader.biBitCount =       24;
  bmi.bmiHeader.biCompression =    BI_RGB;
  bmi.bmiHeader.biSizeImage =      iHeight * BytesPerLine (iWidth, 24);

  unsigned char * pA = NULL;

  HBITMAP hbmG = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**) &pA, NULL, 0);

  HBITMAP hOldAbmp = (HBITMAP) SelectObject(gDC.m_hDC, hbmG);

  // copy base image from miniwindow to bitmap
  gDC.BitBlt (0, 0, iWidth, iHeight, &dc, Left, Top, SRCCOPY);  

  // manipulate image here

   switch (Operation)
     {
     case 1: Noise    (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options); break;
     case 2: MonoNoise(pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options); break;
     case 3: // blur
       {
       double iMatrix [5] = { 1, 1, 1, 1, 1 };
       GeneralFilter     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, iMatrix, 5); 
       }
       break;
     case 4:  // sharpen
       {
       double iMatrix [5] = { -1, -1, 7, -1, -1 };
       GeneralFilter     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, iMatrix, 3); 
       }
       break;
     case 5:  // edge detect
       {
       double iMatrix [5] = { 0, 2.5, -6, 2.5, 0 };
       GeneralFilter     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, iMatrix, 1); 
       }
       break;
     case 6:  // emboss
       {
       double iMatrix [5] = { 1, 2, 1, -1, -2 };
       GeneralFilter     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, iMatrix, 1); 
       }
       break;
     case  7: Brightness        (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options); break;      // additive brightness
     case  8: Contrast          (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options); break;
     case  9: Gamma             (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options); break;
     case 10: ColourBrightness  (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 2); break;    // red
     case 11: ColourContrast    (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 2); break;    // red
     case 12: ColourGamma       (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 2); break;    // red
     case 13: ColourBrightness  (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 1); break;    // green
     case 14: ColourContrast    (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 1); break;    // green
     case 15: ColourGamma       (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 1); break;    // green
     case 16: ColourBrightness  (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 0); break;    // blue
     case 17: ColourContrast    (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 0); break;    // blue
     case 18: ColourGamma       (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 0); break;    // blue
     case 19: MakeGreyscale     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, true); break;    // linear
     case 20: MakeGreyscale     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, false); break;    // perceptual
     case 21: BrightnessMultiply(pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options); break;          // multiplicative brightness
     case 22: ColourBrightnessMultiply(pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 2); break;   // red
     case 23: ColourBrightnessMultiply(pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 1); break;   // green
     case 24: ColourBrightnessMultiply(pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, 0); break;   // blue
     case 25: // lesser blur
       {
       double iMatrix [5] = { 0, 1, 1, 1, 0 };
       GeneralFilter     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, iMatrix, 3); 
       }
       break;
     case 26: // minor blur
       {
       double iMatrix [5] = { 0, 0.5, 1, 0.5, 0 };
       GeneralFilter     (pA, iWidth, iHeight, BytesPerLine (iWidth, 24), Options, iMatrix, 2); 
       }
       break;
     case 27: Average             (pA, iWidth, iHeight); break;



     default: return eUnknownOption;

     }

  // copy result back

  dc.BitBlt (Left, Top, iWidth, iHeight, &gDC, 0, 0, SRCCOPY);  


  SelectObject(gDC.m_hDC, hOldAbmp);

  DeleteObject (hbmG);

  return eOK;

  } // end of CMiniWindow::Filter 



long CMiniWindow::SetPixel(long x, long y, long Colour)
  {
  dc.SetPixelV(x, y, Colour);
  return eOK;
  } // end of CMiniWindow::SetPixel


long CMiniWindow::GetPixel(long x, long y)
  {
  return dc.GetPixel(x, y);
  } // end of CMiniWindow::GetPixel


long CMiniWindow::MergeImageAlpha(LPCTSTR ImageId, LPCTSTR MaskId, 
                                  long Left, long Top, long Right, long Bottom, 
                                  short Mode, double Opacity, 
                                  long SrcLeft, long SrcTop, long SrcRight, long SrcBottom)
  {

  // constrain to what we actually have
  if (Left < 0)
    Left = 0;
  if (Top < 0)
    Top = 0;
  if (Right > m_iWidth)
    Right = m_iWidth;
  if (Bottom > m_iHeight)
    Bottom = m_iHeight;

  if (Opacity < 0.0 || Opacity > 1.0)
    return eBadParameter;

  // image to be merged
  ImageMapIterator it = m_Images.find (ImageId);

  if (it == m_Images.end ())
    return eImageNotInstalled;

  CBitmap * bitmap = it->second;

  // mask image
  it = m_Images.find (MaskId);

  if (it == m_Images.end ())
    return eImageNotInstalled;

  CBitmap * mask_bitmap = it->second;

  BITMAP  bi;
  bitmap->GetBitmap(&bi);
                                                  
  // calculate size of desired rectangle
  long iWidth = FixRight (Right) - Left;
  long iHeight = FixBottom (Bottom) - Top;

  // constrain to what we actually have
  if (SrcLeft < 0)
    SrcLeft = 0;
  if (SrcTop < 0)
    SrcTop = 0;
  if (SrcRight > bi.bmWidth )
    SrcRight = bi.bmWidth ;
  if (SrcBottom > bi.bmHeight)
    SrcBottom = bi.bmHeight;

  // adjust so that -1 means 1 from right
  if (SrcRight <= 0) 
    SrcRight = bi.bmWidth + SrcRight;

  if (SrcBottom <= 0) 
    SrcBottom =  bi.bmHeight + SrcBottom;

  // width and height are the smaller of the two rectangles
  iWidth = min (iWidth, SrcRight - SrcLeft);
  iHeight = min (iHeight, SrcBottom - SrcTop);

  if (iWidth <= 0 || iHeight <= 0)   // sanity check
    return eOK;

  mask_bitmap->GetBitmap(&bi);

  // adjust down in case we don't use whole image
  SrcRight = SrcLeft + iWidth;
  SrcBottom = SrcTop + iHeight;

  // mask must at least be as big as the image we are merging
  if (bi.bmWidth < SrcRight)
    return eBadParameter;
  if (bi.bmHeight < SrcBottom)
    return eBadParameter;


  // merge layer (from image id)
  CDC A_DC;
  A_DC.CreateCompatibleDC(&dc);

  BITMAPINFO bmi;
  ZeroMemory (&bmi, sizeof bmi);

  bmi.bmiHeader.biSize = sizeof bmi;
  bmi.bmiHeader.biWidth =          iWidth;       
  bmi.bmiHeader.biHeight =         iHeight;
  bmi.bmiHeader.biPlanes =         1;
  bmi.bmiHeader.biBitCount =       24;
  bmi.bmiHeader.biCompression =    BI_RGB;
  bmi.bmiHeader.biSizeImage =      iHeight * BytesPerLine (iWidth, 24);

  unsigned char * pA = NULL;

  HBITMAP hbmA = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**) &pA, NULL, 0);

  HBITMAP hOldAbmp = (HBITMAP) SelectObject(A_DC.m_hDC, hbmA);

  CDC bmDC;   // for loading bitmaps into
  bmDC.CreateCompatibleDC(&dc);

  //copy part from image to upper layer
  CBitmap *pOldbmp = bmDC.SelectObject(bitmap);
  A_DC.BitBlt (0, 0, iWidth, iHeight, &bmDC, SrcLeft, SrcTop, SRCCOPY);  
  bmDC.SelectObject(pOldbmp);


  // base image (from miniwindow)

  CDC B_DC;
  B_DC.CreateCompatibleDC(&dc);
  CBitmap B_bmp;

  unsigned char * pB = NULL;

  HBITMAP hbmB = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**) &pB, NULL, 0);

  HBITMAP hOldBbmp = (HBITMAP) SelectObject(B_DC.m_hDC, hbmB);

  // copy base image from miniwindow to bitmap
  B_DC.BitBlt (0, 0, iWidth, iHeight, &dc, Left, Top, SRCCOPY);  


  // mask image 

  CDC M_DC;
  M_DC.CreateCompatibleDC(&dc);
  CBitmap M_bmp;

  unsigned char * pM = NULL;

  HBITMAP hbmM = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**) &pM, NULL, 0);

  HBITMAP hOldMbmp = (HBITMAP) SelectObject(M_DC.m_hDC, hbmM);

  // copy mask image from image to bitmap
  pOldbmp = bmDC.SelectObject(mask_bitmap);
  M_DC.BitBlt (0, 0, iWidth, iHeight, &bmDC, SrcLeft, SrcTop, SRCCOPY);  
  bmDC.SelectObject(pOldbmp);

  long count = bmi.bmiHeader.biSizeImage;
  long perline = BytesPerLine (iWidth, 24);

  long i;

  // do the merge

#define Blend_Mask(A,B,M) ((uint8) ((A * M + B * (255 - M)) / 255) )

  switch (Mode)
    {
    case 0:     // normal
      if (Opacity < 1.0) 
        {
        for (i = 0; i < count; i++) 
          {
          pB [i] = Simple_Opacity (pB [i], Blend_Mask (pA [i], pB [i], pM [i]), Opacity);
          }
        }
      else
        {
        for (i = 0; i < count; i ++)
          pB [i]  = Blend_Mask (pA [i], pB [i], pM [i]);
        }
      break;  // end of mode 0

    case 1:  // transparent on pixel at 0,0
      {
      COLORREF opaque = ::GetPixel (A_DC, 0, 0);

      long row;
     for (row = 0; row < iHeight; row++)
      {
      long base = row * perline;
      unsigned char rA, gA, bA, rB, gB, bB; 
      if (Opacity < 1.0) 
        for (i = 0; i < perline - 2; ) 
          {
          bA = pA [base + i];     
          gA = pA [base + i + 1]; 
          rA = pA [base + i + 2]; 
          bB = pB [base + i];     
          gB = pB [base + i + 1]; 
          rB = pB [base + i + 2]; 

          // if this pixel is the opaque one, take the base pixel instead
          if (RGB (rA, gA, bA) == opaque)
            {
            rA = rB;
            gA = gB;
            bA = bB;
            } 

          pB [base + i] = Simple_Opacity (bB, Blend_Mask (bA, bB, pM [base + i]), Opacity);
          i++;

          pB [base + i] = Simple_Opacity (gB, Blend_Mask (gA, gB, pM [base + i]), Opacity);
          i++;
          
          pB [base + i] = Simple_Opacity (rB, Blend_Mask (rA, rB, pM [base + i]), Opacity);
          i++;
          }
      else
        for (i = 0; i < perline - 2;)
          {
          bA = pA [base + i];     
          gA = pA [base + i + 1]; 
          rA = pA [base + i + 2]; 
          bB = pB [base + i];     
          gB = pB [base + i + 1]; 
          rB = pB [base + i + 2]; 

          // if this pixel is the opaque one, take the base pixel instead
          if (RGB (rA, gA, bA) == opaque)
            {
            rA = rB;
            gA = gB;
            bA = bB;
            }

          pB [base + i] = Blend_Mask (bA, bB, pM [base + i]);
          i++;

          pB [base + i] = Blend_Mask (gA, gB, pM [base + i]);
          i++;
          
          pB [base + i] = Blend_Mask (rA, rB, pM [base + i]);
          i++;
          }   // end Opacity == 1
        }     // end for loop


      }
      break;    // end of mode 1

    default: return eUnknownOption;

    } // end of switch on Mode


  // copy result back

  dc.BitBlt (Left, Top, iWidth, iHeight, &B_DC, 0, 0, SRCCOPY);  


  SelectObject(A_DC.m_hDC, hOldAbmp);
  SelectObject(B_DC.m_hDC, hOldBbmp);
  SelectObject(M_DC.m_hDC, hOldMbmp);

  DeleteObject (hbmA);
  DeleteObject (hbmB);
  DeleteObject (hbmM);

  return eOK;

  } // end of CMiniWindow::MergeImageAlpha


#define MXP_FIRST_MENU 10000
#define MXP_MENU_COUNT 30   // number of MXP menu items we support

static CString strMXP_menu_item [MXP_MENU_COUNT];

CString CMiniWindow::Menu(long Left, long Top, LPCTSTR Items, CMUSHView* pView)
  {
CString strResult;

  // can't menu if not visible
  if (!m_bShow || m_bTemporarilyHide)
    return strResult;

  // can't if outside window
  if (Left < 0 || Left > m_iWidth ||
      Top < 0 || Top > m_iHeight)
      return strResult;

  // make relative to miniwindow
  Left += m_rect.left;
  Top  += m_rect.top;

  vector<string> v;

  StringToVector (Items, v, "|");

  int iCount = v.size ();

  // must have at least one item
  if (iCount < 1)
    return strResult;

#if 0 // AARRRRRRRRGGGGGGGGGGGHHHHHHHHHHHHHHH!!!!!!!!!!!!!!!!!!!

  // if we are doing this in response to a mouse-down, we have to cancel the
  // mouse-down event now, or things get confused

  if (!pView->m_sPreviousMiniWindow.empty ())
    {
    CMUSHclientDoc* pDoc = pView->GetDocument();

    MiniWindowMapIterator it = pDoc->m_MiniWindows.find (pView->m_sPreviousMiniWindow);
  
    if (it != pDoc->m_MiniWindows.end ())
      {

      CMiniWindow * old_mw = it->second;

      // cancel previous move-down hotspot
      if (!old_mw->m_sMouseDownHotspot.empty ())   // HotspotId was used
        {
        // lookup that HotspotId
        HotspotMapIterator it = old_mw->m_Hotspots.find (old_mw->m_sMouseDownHotspot);

        // call CancelMouseDown for that hotspot, if it exists
        if (it != old_mw->m_Hotspots.end ())
          {
          m_last_mouseup = m_last_mousemove;
          pView->Send_Mouse_Event_To_Plugin (old_mw->m_sCallbackPlugin,
                                      it->second->m_sCancelMouseDown, 
                                      old_mw->m_sMouseDownHotspot);
          }
        old_mw->m_sMouseDownHotspot.erase ();  // no mouse-down right now
        }   // we had previous hotspot

      } // previous window still exists

    pView->m_sPreviousMiniWindow.erase ();  // no longer have a previous window
	  ReleaseCapture();   // Release the mouse capture established at
    	    					    // the beginning of the mouse click.

    } // released mouse in different window

#endif    // AARRRRRRRRGGGGGGGGGGGHHHHHHHHHHHHHHH!!!!!!!!!!!!!!!!!!!



CPoint menupoint (Left, Top);

  CMenu menu;
	VERIFY(menu.LoadMenu(IDR_MXP_MENU));

	CMenu* pPopup = menu.GetSubMenu(0);
	ASSERT(pPopup != NULL);
	CWnd* pWndPopupOwner = (CWnd *) pView;

  pPopup->DeleteMenu (0, MF_BYPOSITION);  // get rid of dummy item

	while (pWndPopupOwner->GetStyle() & WS_CHILD)
		pWndPopupOwner = pWndPopupOwner->GetParent();

  ClientToScreen(pView->m_hWnd, &menupoint);

  int j = 0;

  for (vector<string>::const_iterator i = v.begin (); i != v.end (); i++)
    {
    CString strItem = i->c_str ();

    if (*i == "-" || *i == "")
      pPopup->AppendMenu (MF_SEPARATOR, 0, "");
    else if (strItem.Left (1) == "^")
      pPopup->AppendMenu (MF_STRING | MF_GRAYED, 0, strItem.Mid (1));
    else
      {
      strMXP_menu_item [j] = strItem;
      pPopup->AppendMenu (MF_STRING | MF_ENABLED, MXP_FIRST_MENU + j, strItem);
      j++;
      if (j >= MXP_MENU_COUNT)
        break;
      }
    }

  // without this line the auto-enable always set "no items" to active
  Frame.m_bAutoMenuEnable  = FALSE;

	int iResult = pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, 
                          menupoint.x, 
                          menupoint.y,
			                    pWndPopupOwner);

  // put things back how they were
  Frame.m_bAutoMenuEnable  = TRUE;

  if (iResult > 0)
    strResult = strMXP_menu_item [iResult - MXP_FIRST_MENU];

  return  strResult;  // will be empty for errors or cancelled


  }  // end of CMiniWindow::Menu

