Figure 3   VIRGIL

MainFrm.h


 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 // This program compiles with Visual C++ 4.1 on Windows 95
 // See Virgil.cpp
 // 
 #include "Dib.h"
 #include "PalHook.h" // to handle palette messages
 
 class CMainFrame : public CFrameWnd {
    DECLARE_DYNCREATE(CMainFrame)
 
 public:
    CMainFrame();
    virtual ~CMainFrame();
 
 protected:
    CStatusBar     m_wndStatusBar;   // status bar
    CPalMsgHandler m_palMsgHandler;  // handles palette messages
    CDib           m_dibImage;       // image bitmap
    CDib           m_dibMask;        // mask bitmap
    BOOL           m_bViewMask;      // whether to view mask or dib
 
    CString GetWhoItIs(CPoint pt) const; // Helper
 
    // Override for finding tool tip
    virtual int  OnToolHitTest(CPoint point, TOOLINFO* pTI) const;
 
    DECLARE_MESSAGE_MAP()
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnPaint();
    afx_msg void OnIndicatorWho(CCmdUI* pCmdUI);
    afx_msg void OnGetTooltipText(NMHDR* pNMHDR, LRESULT* plRes);
    afx_msg void OnViewMaskOrImage();
    afx_msg void OnUpdateViewMaskOrImage(CCmdUI* pCmdUI);
    afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
 };
MainFrm.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 // This program compiles with Visual C++ 4.1 on Windows 95
 // See Virgil.cpp
 // 
 #include "StdAfx.h"
 #include "Virgil.h"
 #include "MainFrm.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #undef THIS_FILE
 static char THIS_FILE[] = __FILE__;
 #endif
 
 IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
 
 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_WM_CREATE()
    ON_WM_PAINT()
    ON_UPDATE_COMMAND_UI(ID_INDICATOR_WHO, OnIndicatorWho)
    ON_NOTIFY( TTN_NEEDTEXT, 0, OnGetTooltipText)
    ON_COMMAND(ID_VIEW_MASK, OnViewMaskOrImage)
    ON_UPDATE_COMMAND_UI(ID_VIEW_MASK, OnUpdateViewMaskOrImage)
    ON_WM_PALETTECHANGED()
    ON_WM_QUERYNEWPALETTE()
    ON_WM_GETMINMAXINFO()
 END_MESSAGE_MAP()
 
 static UINT indicators[] = {
    ID_SEPARATOR,        // status line indicator
    ID_INDICATOR_WHO     // "who" indicator
 };
 
 CMainFrame::CMainFrame()
 {
    m_bViewMask = FALSE;
 }
 
 CMainFrame::~CMainFrame()
 {
 }
 
 /////////////////
 // Create main window. In addition to the usual stuff:
 //
 // - load image and mask bitmaps
 // - set window size to be exactly same as bitmap
 // - install palette message handler
 // - enable tooltips
 //
 // Note: in order to make drawing simpler, so I don't have to calculate the
 // client area each time, I created this window with no toolbar. There are
 // no commands to put there anyway.
 //
 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
 {
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1) {
       return -1;
    } 
    if (!m_wndStatusBar.Create(this) ||
       !m_wndStatusBar.SetIndicators(indicators,
         sizeof(indicators)/sizeof(UINT))) {
       TRACE0("Failed to create status bar\n");
       return -1;      // fail to create
    }
 
    // Load image bitmap
    if (!m_dibImage.Load(AfxGetResourceHandle(), IDB_BITMAP)) {
       TRACE("*** Failed to load bitmap\n");
       return -1;
    }
    // Load mask bitmap
    if (!m_dibMask.Load(AfxGetResourceHandle(), IDB_BITMAPMASK)) {
       TRACE("*** Failed to load mask\n");
       return -1;
    }
    ASSERT(m_dibMask.GetSize() == m_dibImage.GetSize());
 
    // Readjust initial size
    MINMAXINFO mmi;
    OnGetMinMaxInfo(&mmi);
    SetWindowPos(NULL, 0, 0, mmi.ptMaxSize.x, mmi.ptMaxSize.y,
       SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE);
 
    // Install palette message handler
    m_palMsgHandler.Install(this, m_dibImage.GetPalette());
 
    EnableToolTips();
 
    return 0;
 }
 
 //////////////////
 // Paint function uses CDib to draw either image or mask
 //
 void CMainFrame::OnPaint() 
 {
    CPaintDC dc(this);
    CDib* pDIB = m_bViewMask ? &m_dibMask : &m_dibImage;
    pDIB->Draw(dc);
 }
 
 ////////////////
 // Update "who" indicator. 
 //
 void CMainFrame::OnIndicatorWho(CCmdUI* pCmdUI) 
 {
    CPoint pt;
    GetCursorPos(&pt);               // current mouse pos
    ScreenToClient(&pt);             // in client coords
    pCmdUI->SetText(GetWhoItIs(pt)); // find who and set text
 }
 
 //////////////////
 // Figure out what "tool"--i.e. person--the mouse is over, if any.
 //
 int CMainFrame::OnToolHitTest(CPoint pt, TOOLINFO* pTI) const
 {
    int nHit = -1; // assume none
    if (pTI && !(GetWhoItIs(pt).IsEmpty()) ) {
       // Need to return a unique value from last time:
       // so just return the point as an integer
       //
       nHit = MAKELONG(pt.x, pt.y);
       // setup TOOLINFO structure
       pTI->hwnd = m_hWnd;
       pTI->uId  = nHit;
       pTI->rect = CRect(CPoint(pt.x-1,pt.y-1),CSize(2,2));
       pTI->lpszText = LPSTR_TEXTCALLBACK;
       pTI->uFlags |= TTF_NOTBUTTON; // OR with TTF_ALWAYSTIP to show
                                     // tip even when window not active
    }
    return nHit;
 }
 
 //////////////////
 // Get tooltip text. idFrom is whatever I returned above as uID:
 // namely, the mouse point coded as integer.
 //
 void CMainFrame::OnGetTooltipText(NMHDR* pNMHDR, LRESULT* plRes)
 {
    TOOLTIPTEXT& ttt = *((TOOLTIPTEXT*)pNMHDR);
    strncpy(ttt.szText,
       GetWhoItIs(CPoint (pNMHDR->idFrom)),
       sizeof(ttt.szText));
 }
 
 //////////////////
 // Main helper to figure out who it is under a given point in the image.
 //
 CString CMainFrame::GetWhoItIs(CPoint pt) const
 {
    static struct {
       COLORREF color;
       LPCTSTR  name;
    } data [] = {
       // The entries in this table must match colors in the mask.
       // Each color identifies a person/object in the painting.
       //
       { RGB(255,  0,  0), "Virgil" },
       { RGB(  0,255,  0), "Octavia" },
       { RGB(  0,  0,255), "Octavia's Maiden" },
       { RGB(  0, 66, 66), "Unknown Character" },
       { RGB(255,255,  0), "Book 6 of the Aeneid" },
       { RGB(132,  0,132), "Unknown Character" },
       { RGB(  0,  0,132), "Ionic Column" },
       { RGB(  0,132,255), "Unknown Character" },
       { RGB(132, 66,  0), "Pretorian Guard" },
       { RGB(  0,132,  0), "Caesar Augustus" },
       { RGB(  0,132,132), "Drapes" },
       { 0, NULL }
    };
 
    // Create memory DC, select mask bitmap into it and call CDC::GetPixel
    // to get the color of the corresponding pixel in the mask
    //
    CDC dc;
    dc.CreateCompatibleDC(NULL);
    CBitmap * pOldBm = dc.SelectObject((CDib*)&m_dibMask);
    COLORREF color = dc.GetPixel(pt);   // mask pixel
    dc.SelectObject(pOldBm);            // restore 
    
    // Computee text. If I'm viewing the mask, generate
    // "RGB(x,y) = (R,G,B)" before name of character.
    //
    CString s;
    if (m_bViewMask) {
       s.Format("RGB(%d,%d)=(%d,%d,%d) ", pt.x, pt.y, 
          GetRValue(color), GetGValue(color), GetBValue(color));
    }
 
    if (color != RGB(255,255,255)) { // white = nothing
       // Do linear search through table
       for (int i=0; data[i].name; i++) {
          if (data[i].color==color)
             s += data[i].name;
       }
    }
    return s;
 }
 
 //////////////////
 // View Mask/View Image command: toggle flag
 //
 void CMainFrame::OnViewMaskOrImage() 
 {
    m_bViewMask = !m_bViewMask;
    Invalidate();
 }
 void CMainFrame::OnUpdateViewMaskOrImage(CCmdUI* pCmdUI) 
 {
    pCmdUI->SetText(m_bViewMask ? "&Image" : "&Mask");
 }
 
 //////////////////
 // Force window same size as image
 //
 void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
 {
    if (m_dibImage.m_hObject) {
       CSize sz = m_dibImage.GetSize(); // size of bitmap
       CRect rcWin, rcClient;
       GetWindowRect(&rcWin);
       GetClientRect(&rcClient);
       lpMMI->ptMaxSize = lpMMI->ptMinTrackSize = lpMMI->ptMaxTrackSize = 
          CPoint(sz.cx + rcWin.Width() - rcClient.Width(),
                 sz.cy + rcWin.Height()- rcClient.Height());
    }  
 }
Virgil.h

 ////////////////////////////////////////////////////////////////
 // VIRGIL 1997 Microsoft Systems Journal. 
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 // See Virgil.cpp
 // 
 #include "resource.h"
 
 class CMyApp : public CWinApp {
 public:
    CMyApp();
    virtual BOOL InitInstance();
 protected:
    DECLARE_MESSAGE_MAP()
    afx_msg void OnAppAbout();
 };
Virgil.cpp

 ////////////////////////////////////////////////////////////////
 // VIRGIL 1997 Microsoft Systems Journal. 
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 // VIRGIL illustrates how to implement a bitmap with arbitrarily shaped
 // "hot" regions that are detectable by clicking. It also shows how to
 // use tooltips in your main window.
 //
 // Compiles with VC++ 4.1 or later.
 
 #include "stdafx.h"
 #include "Virgil.h"
 #include "mainfrm.h"
 #include "TraceWin.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #undef THIS_FILE
 static char THIS_FILE[] = __FILE__;
 #endif
 
 BEGIN_MESSAGE_MAP(CMyApp, CWinApp)
    ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
 END_MESSAGE_MAP()
 
 CMyApp::CMyApp()
 {
 }
 
 CMyApp theApp;
 
 //////////////////
 // Standard initialization. No doc/view.
 //
 BOOL CMyApp::InitInstance()
 {
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
       return FALSE;
    pMainFrame->ShowWindow(m_nCmdShow);
    pMainFrame->UpdateWindow();
    m_pMainWnd = pMainFrame;
    return TRUE;
 }
 
 //////////////////
 // Generic Dib Control is like static,
 // but paints DIB with true colors.
 //
 class CDibCtl : public CStatic {
 protected:
    DECLARE_DYNAMIC(CDibCtl);
    CDib m_dib;
 public:
    CDibCtl(UINT nID);
    CPalette* GetPalette() { return m_dib.GetPalette(); }
    DECLARE_MESSAGE_MAP()
    afx_msg void OnPaint();
 };
 
 IMPLEMENT_DYNAMIC(CDibCtl, CStatic);
 
 BEGIN_MESSAGE_MAP(CDibCtl, CStatic)
    ON_WM_PAINT()
 END_MESSAGE_MAP()
 
 //////////////////
 // Constructor: Load DIB from resource
 //
 CDibCtl::CDibCtl(UINT nID)
 {
    m_dib.Load(AfxGetResourceHandle(), nID);
 }
 
 //////////////////
 // Paint the DIB. Should really take into account
 // all the stytes here (sunken, raised, etc.)
 //
 void CDibCtl::OnPaint()
 {
    CPaintDC dc(this);
    m_dib.Draw(dc);
 }
 
 //////////////////
 // About dialog with DIB in it.
 //
 class CAboutDlg : public CDialog {
    CPalMsgHandler m_palMsgHandler; // handles palette messages
    CDibCtl m_ctlDib;               // DIB control
 public:
    CAboutDlg() : CDialog(IDD_ABOUTBOX), m_ctlDib(IDB_BITMAP1) { }
    virtual BOOL OnInitDialog();
 };
 
 //////////////////
 // Dialog created:
 // - subclass DIB control
 // - install palette handler
 //
 // The palette handler will realize the DIB's palette in the foreground
 // whenever the dialog gains focus, and in the background whenever the
 // parent window gets WM_PALETTECHANGED. Only because the CMainFrame also
 // has a CPalMsgHandler in it--which it already does to draw the image.
 // If your mainframe doesn't have a CPalMsgHandler to draw a bitmap, you must
 // add one to propagate palette messages to any child dialogs it may create.
 //
 BOOL CAboutDlg::OnInitDialog()
 {
    BOOL bRet = CDialog::OnInitDialog();
    m_ctlDib.SubclassDlgItem(IDB_BITMAP1, this);
    m_palMsgHandler.Install(this, m_ctlDib.GetPalette());
    return bRet;
 }
 
 //////////////////
 // About command: run the About dialog
 //
 void CMyApp::OnAppAbout()
 {
    CAboutDlg().DoModal();
 }