. . . .

Windows App Program Examples

 

updated:2016.07.13

Prev   Next   Site Map   Home  
Text Size
Please reload page for style change


Win32Api C++ Source

/****************************************************************************
* C Source File:  PSVIEW.CPP
*
* Description:  CDX script debugger and instrument controller
* Pascal string viewer class. This combines a viewing window with a string
* buffer and management operations. Strings can be continuously inserted even
* as the window is resized, moved, minimized, maximized, and restored. The
* view window automatically scrolls to show the most recent insertions as they
* enter at the bottom, but the user can scroll backwards.  Vertical and
* horizontal scroll bars are dynamically added, removed, and resized to
* reflect the buffer contents. The oldest strings are deleted to provide room
* for new ones. The buffer size and string truncation (to reduce buffer
* consumption) are set by constructor arguments.
* ......................... Functions ......................................
* PsViewProc
* fillPsViewClass
* PsView::construct
* PsView::resize
* PsView::~PsView
* PsView::putRawString
* PsView::refresh
* PsView::putFmtString
* 
****************************************************************************/
#define PSVIEW_CPP    

/* ....................... Windows includes ...................*/
#include <windows.h>
/* ....................... Run Time Library includes ......... */
#include <stdarg.h>
#include <stdio.h>
/* ....................... Project includes ...................*/
#include "cdefs.h"
/* ....................... Application includes ...............*/
#include "sysdefs.h"
#include "usrmsg.h"
#include "cdxdbg.h"
#include "winutil.h"
#include "res.h"
#include "psview.h"
#include "mdi.h"

/*****************************************************************************
*                             DATA AND DECLARATIONS
*****************************************************************************/

int cacheMiss = 0;
/*****************************************************************************
*                                   FUNCTIONS
*****************************************************************************/

CriticalSection csPsView; // Let all instances share this one.

/****************************************************************************
* Function:     PsViewProc
* Description:  Window proc for Pascal String Viewer.
* Returns:      LRESULT: 0 = message handled.
* ...................... notes ............................................
* -----------  Display control elements ----------
* - width is the current window width in pixels.
* - height is the current window height in pixels.
* - maxTopIdx is the maximum scroll position in lines (one string per line). It
* is calculated as the total number of lines (strings) in the buffer minus the
* number of lines that can be displayed in the current window vertical space.
* To allow blank lines at top and bottom, 2 is added to this. e.g. if the
* window height supports 4 lines and the buffer contains 5 strings, maxTopIdx
* is 3. The vertical scroll range is set to maxTopIdx so that each step
* represents one line.
* - topIdx is the current scroll position (line number) within the maxTopIdx
* range.  This is the buffer index of the string displayed on the first line
* of the window. A simple approach to painting the window would be to entirely
* repaint it by writing each line from topIdx to topIdx + linesInWindow.
* However, this is inefficient if only part of the window needs to be
* repainted, for example, when an occluding windowing is removed. For greater
* efficiency, repainting is done by calculating the index of the first
* (idxBeg) and last (idxEnd) lines (strings) in the repainted rectangle.
*
* - top window line source. The string "indices", topIdx, idxBeg, etc,
* identify strings as if they were in an array. Since the strings are actually
* stored in a circular buffer without pointers, the only way to access a
* string based on its index is by iterating over the strings. This process can
* only move in a forward direction, as we know the address of a string only
* from the address and length (stored at the address) of the previous string.
* In the worst case (in terms of performance) we can always search from the
* beginning of the buffer. However, in most cases, we can significantly reduce
* the search time by starting at an index/pointer association cached during
* the previous window update. The topIdx and idxBeg are obvious caching
* candidates. In most cases, topIdx and idxBeg are equal, because the entire
* window is invalidated by most events, including insertions and scrolling.
* When idxBeg doesn't equal topIdx, it is greater and in nearly all cases
* would also be greater than the next idxBeg, forcing a full rescan.
* Therefore, topIdx is the better choice. Better yet would be one or two
* strings earlier than topIdx to decrease the chances of the next idxBeg being
* lower than the cached index. I didn't do this because, in some cases, topIdx
* is the lowest legal index and I didn't want to complicate the code with
* additional condition tests. The cache hit rate is reasonably good anyway.
* For example, a particular script downloaded in 1740 messages, which scrolled
* through the transmit display window with only 18 cache misses.
...........................................................................*/
LRESULT CALLBACK PsViewProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
  static UCHAR    *inspectThis = 0;
  static POINT    clientPoint;

  HDC         hdc;
  PAINTSTRUCT ps;
  PsView      *mrp;
  UCHAR       *mp;
  int         vsInc;
  int         hsInc;
  int         idx;
  int         hpos;
  int         vpos;
  int         idxBeg;
  int         idxBeyond; 

  mrp = (PsView *)GetWindowLong( hwnd, 0 );
  switch( msg )
  {
  case WM_SETFOCUS :
    restoreFocus = hwnd;
    maskMainMenu( MM_NOTEDIT, MM_NOTEDIT ); // No accelerators so enable all but EDIT.
    break;

  case WM_COMMAND :
    switch( LOWORD( wParam ) )
    {
    case IDCMD_PSVCLEAR :
      mrp->construct( 0 ); // Construct 0 means to reinitialize.
      break;

    case IDCMD_PSVCFG :
      PostMessage( wndUser, WM_COMMAND, IDCMD_CFG, 0 );
      break;

    case IDCMD_PSINSPECT :
      if( inspectThis != 0 )
      {
        userMessage( 0, MB_ICONERROR + MB_OK, 
          "You are already inspecting a message. To analyze another you must close the previous window." );
        return 0; // Don't allow another inspection until previous is closed.
      }
      idxBeg = clientPoint.y / mrp->charHeight + mrp->topIdx - 1;
      if( idxBeg < 0 ) // Clicked in blank line at top of window beginning of record.
        return 0;
      for( idx = 0, mp = mrp->first ; idx != idxBeg ; idx++ )
      {
        mp += *mp + 1;
        if( mp > mrp->bufEndMargin + 1 )
          return 0; // Abort if insertion error.
        if( *mp == 0 )
          mp = mrp->buf;
      }
/* Duplicate the message both to avoid the possibility of the message being
* removed by the independent receiver thread before inspection is complete and
* to provide the inspector with a copy that it can modify. To help with the 
* latter, extra space is allocated so substitutions may safely expand the 
* message. */
      inspectThis = (UCHAR*)malloc( *mp * 2 ); 
      if( inspectThis != 0 )
      {
        memcpy( inspectThis, mp, *mp + 1 );
        inspectMsg( inspectThis, mrp->msgSrcId );
        free( inspectThis );
        inspectThis = 0;
      }
      break;
    }
    return 0;

  case WM_RBUTTONDOWN :  // Windows standard is BUTTONUP but that is confusing.
    {
      clientPoint.y = HIWORD( lParam );
      popFloatingMenu( hwnd, lParam, psViewMenu ); 
    }
    return 0;

  case WM_KEYDOWN :
    if( wParam == VK_F1 )
    {
      popFloatingMenu( hwnd, 0, psViewMenu );
      return 0;
    }
    break;

  case WM_CREATE :
    return 0;

  case WM_MOVE :
    updateChildPos( mrp->myDesc, hwnd ); 
    break;

  case WM_SIZE :
    mrp->width = LOWORD( lParam );
    mrp->height = HIWORD( lParam );

    mrp->maxTopIdx = max( 0, mrp->nLines + 2 - mrp->height / mrp->charHeight );
    mrp->topIdx = min( mrp->topIdx, mrp->maxTopIdx );
    SetScrollRange( hwnd, SB_VERT, 0, mrp->maxTopIdx, FALSE );
    SetScrollPos  ( hwnd, SB_VERT, mrp->topIdx, TRUE );

    mrp->hsMax = max( 0, 2 + mrp->longest - mrp->width / mrp->widthCaps );
    mrp->hsPos = min( mrp->hsPos, mrp->hsMax );

    SetScrollRange( hwnd, SB_HORZ, 0, mrp->hsMax, FALSE );
    SetScrollPos  ( hwnd, SB_HORZ, mrp->hsPos, TRUE );
    updateChildPos( mrp->myDesc, hwnd );
    return 0;

  case WM_VSCROLL :
    switch( LOWORD( wParam ) )
    {
    case SB_TOP :
      vsInc = -mrp->topIdx;
      break;

    case SB_BOTTOM :
      vsInc = mrp->maxTopIdx - mrp->topIdx;
      break;

    case SB_LINEUP :
      vsInc = -1;
      break;

    case SB_LINEDOWN :
      vsInc = 1;
      break;

    case SB_PAGEUP :
      vsInc = min( -1, -mrp->height / mrp->charHeight );
      break;

    case SB_PAGEDOWN :
      vsInc = max( 1, mrp->height / mrp->charHeight );
      break;

    case SB_THUMBTRACK :
      vsInc = HIWORD( wParam ) - mrp->topIdx;
      break;

    default :
      vsInc = 0;
    }
    vsInc = max( -mrp->topIdx,
      min( vsInc, mrp->maxTopIdx - mrp->topIdx ));

    if( vsInc != 0 )
    {
      mrp->topIdx += vsInc;
      ScrollWindow( hwnd, 0, -mrp->charHeight * vsInc, NULL, NULL );
      SetScrollPos( hwnd, SB_VERT, mrp->topIdx, TRUE );
      UpdateWindow( hwnd );
    }
    return 0;

  case WM_HSCROLL :
    switch( LOWORD( wParam ) )
    {
    case SB_LINEUP :
      hsInc = -1;
      break;

    case SB_LINEDOWN :
      hsInc = 1;
      break;

    case SB_PAGEUP :
      hsInc = -8;
      break;

    case SB_PAGEDOWN :
      hsInc = 8;
      break;

    case SB_THUMBPOSITION :
      hsInc = HIWORD( wParam ) - mrp->hsPos;
      break;

    default :
      hsInc = 0;
    }
    hsInc = max( -mrp->hsPos,
      min( hsInc, mrp->hsMax - mrp->hsPos ));

    if( hsInc != 0 )
    {
      mrp->hsPos += hsInc;
      ScrollWindow( hwnd, - mrp->widthCaps * hsInc, 0, NULL, NULL );
      SetScrollPos( hwnd, SB_HORZ, mrp->hsPos, TRUE );
    }
    return 0;

  case WM_PAINT :
    EnterCriticalSection( &csPsView );

    hdc = BeginPaint( hwnd, &ps );
    SelectObject( hdc, GetStockObject( mrp->font ));

    idxBeg = max( 0, mrp->topIdx + ps.rcPaint.top / mrp->charHeight - 1 );
    idxBeyond = min( mrp->nLines,
      mrp->topIdx + ps.rcPaint.bottom / mrp->charHeight );   

    hpos = mrp->widthCaps * ( 1 - mrp->hsPos );

        // Find the top window line source. See note above.
    if( idxBeg >= mrp->cachedIdx )
    {
      idx = mrp->cachedIdx;
      mp = mrp->cachedPtr;
    }
    else
    {   // Don't use cache. Search from the beginning of the buffer.
      idx = 0;
      mp = mrp->first;
      cacheMiss++;
    }
    while( 1 )
    {
      if( mp > mrp->bufEndMargin + 1 )
      {   /* This is an insertion error because the 0 message length
sentinel should tell the wrap around point before the end of buffer. */
#if 1
        mp = mrp->buf;  // Incorrect message display but no crash.
#else
        reportSystemError( "PsView string extractor buffer overrun" );
        EndPaint( hwnd, &ps );
        LeaveCriticalSection( &csPsView );
        return 0;
#endif
      }
      if( *mp == 0 )
        mp = mrp->buf;
      if( idx == idxBeg )
      {
        mrp->cachedPtr = mp;
        mrp->cachedIdx = idx;
        break;
      }
      mp += *mp + 1;
      idx++;
    } 

    vpos = mrp->charHeight * ( 1 + idxBeg - mrp->topIdx );
    for( ; idx < idxBeyond ; idx++, vpos += mrp->charHeight )
    {
      if( *mp == 0 )  // 0 length is buffer wrap sentinel.
        mp = mrp->buf;
      TextOut( hdc, hpos, vpos, (const char *)( mp + 1 ), *mp );
      mp += *mp + 1;
    }
    EndPaint( hwnd, &ps );
    LeaveCriticalSection( &csPsView );
    return 0;

  case WM_SYSCOMMAND :
    if( wParam == SC_CLOSE ) // Tell parent so it can destroy the associated object.
      removeChild( mrp->myDesc );
    break;
  }
  return DefWindowProc( hwnd, msg, wParam, lParam );
}

void fillPsViewClass( WNDCLASSEX *wc )
{
  wc->lpszClassName  = "PsView";
  wc->lpfnWndProc    = PsViewProc;
  wc->hbrBackground  = ( HBRUSH ) GetStockObject( WHITE_BRUSH );
  wc->cbWndExtra     = sizeof( int ) +        // VIEWTEXT_FONT_IDX
    sizeof( long ) +   // VIEWTEXT_BUF_IDX 
    sizeof( long );    // VIEWTEXT_WND_IDX 
}
/****************************************************************************
* Function:     PsView::construct
* Description:  Non-inline portion of PsView constructor. The constructor
* takes many arguments, which are only passed through to CreateWindow. It is
* more efficient to do that one thing inline and then call this function to
* finish constructing the object.
*
* Returns:      Nothing
* Arguments:
*- USHORT bufsize tells how big to make the string buffer.
* - UCHAR maxline tells the maximum allowed string length. Longer strings are
* stored trucated (with length appropriately adjusted). This is used to reduce
* buffer consumption where the user doesn't really need to see the entire
* content of strings, e.g. list mode data messages.
*
...........................................................................*/
void PsView::construct( USHORT bufsize )
{
  HDC         hdc;
  TEXTMETRIC  tm;

  if( bufsize != 0 )
  {
    if( ( buf = (UCHAR *) calloc( bufsize, 1 )) == 0 )
      return;
    bufEndMargin = buf + bufsize - 2; // Ensures at least one byte for sentinel.
    SetWindowLong( hwnd, 0, (LONG)this );
    font = 
        //SYSTEM_FIXED_FONT; // Shows many happy faces as block, including 0 and FF.
      OEM_FIXED_FONT;  // Shows more happy faces but blank for 0 and FF.
        //SYSTEM_FONT;     // See Petzhold p. 280.

    hdc = GetDC( hwnd );
    SelectObject( hdc, GetStockObject( font ));
    GetTextMetrics( hdc, &tm );
        //cxChar = tm.tmAveCharWidth;
    widthCaps = ( tm.tmPitchAndFamily & 1 ? 3 : 2 ) * tm.tmAveCharWidth / 2;
    charHeight = tm.tmHeight + tm.tmExternalLeading;
    ReleaseDC( hwnd, hdc );
  }
  next = buf;
  first = buf;
  longest = 0;
  nLines = 0;
  topIdx = 0;
  cachedIdx = 0;
  cachedPtr = buf;
  scrollUpdateNeeded = false;
  repaintNeeded = false;
  putFmtString( "Top of Buffer" );
  ShowWindow( hwnd, SW_SHOWNORMAL );
  UpdateWindow( hwnd );
}
void PsView::resize( USHORT bufsize, UCHAR maxline )
{
  if( buf != 0 )
    free( buf );
  construct( bufsize );
  maxLen = maxline;
}
/****************************************************************************
* Function:     PsView::~PsView
* Description:  Destructor. Destroys the viewing window and frees the string
* buffer. 
* .............. notes ....................................................
* - The caller should 0 its PsView pointer immediately after calling this
* destructor, in which case it would serve no purpose to 0 hwnd (window
* handle) and buf (pointer) in the destructor. However, the application
* program may neglect to do this or it may create a static PsView which can't
* be destroyed.  To provide some protection against crashing, hwnd and buf are
* 0'd here.
...........................................................................*/
PsView::~PsView( void )
{
  if( hwnd != 0 )
  {
    DestroyWindow( hwnd ); 
    hwnd = 0;
  }
  if( buf != 0 )
  {
    free( buf );
    buf = 0;
  }
}
/****************************************************************************
* Function:     PsView::putRawString
* Description:  Insert Pascal string into this object's buffer. The string
* typically is a formatted message from putFmtString or an interface packet
* with the checksum or CRC removed (and the length byte adjusted).
* Returns:      Nothing  
* 
* Arguments:    UCHAR *pstr is pointer to Pascal string (first byte tells the
* length of the remainder of the string-- it doesn't count itself).
*
* Globals:      BOOL ignoreBadLen is local static that applies to all PsView
* objects. If a 0 length string is passed, a system error message is posted
* (normally via a WindowBox, but the user interface decides this). If it is
* decided (normally by selected user option) to ignore this message, it will
* be ignored for all PsView objects until the program is restarted. This
* should only be used for testing, as a bad pointer indicates either program
* error or OS failure (e.g. too many repaint messages while resizing any
* window).
* .................. notes ................................................
* - bufEndMargin points to the byte preceding the last byte. Since the length
* byte doesn't count itself, if an inserted string is one byte less than this,
* the last byte of the string will occupy the penultimate byte, leaving the
* last byte in the buffer for storing a wrap sentinel. Thus, a display string 
* iterator only needs to look for length = 0 (the sentinel) and shouldn't have
* to also watch for pointer > buffer end. However, it seems that if the user
* chooses to ignore a 0 length message attempt then something can go wrong in
* the buffer even though the message is discarded. Checking for buffer overrun
* is, therefore, enabled in the extractor to prevent system crash.
*
...........................................................................*/
void PsView::putRawString( UCHAR *pstr )
{
#define BADIDX 60000
  UCHAR   *newEnd;
  int     prevLineCnt = nLines;
  UCHAR   len;
  int     removeLines;

  EnterCriticalSection( &csPsView );
  if( *pstr == 0 )
  {
/* 0-length message. Callers should not pass these to here because they cause
* problems in all contexts, not just here. But, since this function crashes
* the program if it receives one of these, we guard against them just in case
* of error at a higher level. */
    LeaveCriticalSection( &csPsView );
    return; 
  }
  len = min( *pstr, maxLen );
  removeLines = 0;

/* If the inserted string would overrun the end of the buffer then leave a 0
* length sentinel in its place and wrap around to the beginning. If the first
* (oldest) string is after the insertion point then remove all strings from
* the first to the end of the buffer before writing the sentinel. */
  if( next + len >= bufEndMargin )
  {
    if( first >= next )
    {
      for( ; *first != 0 ; first += *first + 1 )
        removeLines++;
      first = buf;
    }
    *next = 0; // Leave sentinel.
    next = buf;
  }
/* If the inserted string would overrun the first (oldest) string in the
* buffer then remove strings, starting with first, until the inserted string
* does not overrun an existing string. While iterating over strings, first may
* encounter the sentinel before reaching the new insertion end point, because
* the sentinel position depends on the previous wrap around the buffer; i.e.
* just because the new insertion doesn't overrun the end of the buffer doesn't
* mean that it won't overrun the sentinel position. Therefore, we have to
* watch for the sentinel while stepping first through the buffer. */
  if( next <= first && nLines > 0 && ( newEnd = next + len ) >= first )
  {
    BOOL mayCrossCache = first < cachedPtr;
    for( ; *first != 0 && first <= next + len ; first += *first + 1 )
      removeLines++;
    if( *first == 0 )
      first = buf;
    if( mayCrossCache && first > cachedPtr )
      cachedIdx = BADIDX; // Overran the cached ptr, invalidating it.
  }
  *next = len;
  memcpy( next + 1, pstr + 1, len );
  next += len + 1;
  if( len > longest )
    longest = len;

/* If cachedIdx is above the lines removed from the beinning then its value
* decreases by the removed line count. Otherwise, it is one of the lines that
* has been removed and is now invalid. */
  cachedIdx = cachedIdx >= removeLines ? cachedIdx - removeLines : BADIDX;

  nLines -= --removeLines;
  topIdx -= removeLines;
  if( hwnd != 0 )
  {
    maxTopIdx = max( 0, nLines + 2 - height / charHeight );
/* Since new strings are added to the end of the buffer and appear at the
* end of the window, we may need to scroll, by moving topIdx, to get this
* latest addition to appear automatically without the user having to scroll. */
    if( topIdx > maxTopIdx )
      topIdx = maxTopIdx;
    if( removeLines != 0 )
      scrollUpdateNeeded = true;
    repaintNeeded = true;
  }
  LeaveCriticalSection( &csPsView );
}
/****************************************************************************
* Function:     PsView::refresh
* Description:  Update the scroll range and position and invalidate the
* viewing window if putRawString says these need to be done. PutRawString
* doesn't do these things itself because rapid string insertion floods the OS
* with scroll and paint messages, which not only waste CPU time but crash the
* OS (Windows own fault) if they occur during unguarded critical operations,
* particularly resizing of windows (any-- not just the PsView window).
* Instead, refresh is called at a moderate rate, e.g. 250 msec, to update as
* needed.
...........................................................................*/
void PsView::refresh( void )
{
  if( scrollUpdateNeeded )
  {
    SetScrollRange( hwnd, SB_VERT, 0, maxTopIdx, FALSE );
    SetScrollPos  ( hwnd, SB_VERT, topIdx, TRUE );
    scrollUpdateNeeded = false;
  }
  if( repaintNeeded )
  {
    InvalidateRect( hwnd, NULL, TRUE );
    repaintNeeded = false;
  }
}
/****************************************************************************
* Function:     PsView::putFmtString
* Description:  Convert a vararg C format string to rendered Pascal string
* and insert into this object's buffer.
* Returns:      Nothing
* Arguments:    char *format, ... is vararg C string format plus arguments.
* The rendered string will be truncated to no more than 89 characters.
...........................................................................*/
void PsView::putFmtString( char *format, ... )
{
  va_list arglist;
  char    psbuf[ 90 ];

  va_start( arglist, format );
  *psbuf = _vsnprintf( psbuf + 1, sizeof( psbuf ) - 2, (const char *)format, arglist );
  va_end( arglist );
  putRawString( (UCHAR *)psbuf );
}


Win32Api C++ Header

/****************************************************************************
* C Header File:  PSVIEW.H
*
* Description: CDX script debugger and instrument controller
* Pascal-like string scrolling viewer. Generic viewer for analyzer
* communication messages.
*
****************************************************************************/
/*****************************************************************************
*                                    PsView Class
* Pascal string viewer/recorder.
* This is used to display and record analyzer messages and C vararg type strings
* (converted to Pascal strings).
* .......................... notes ..........................................
* - maxLen is used to truncate long strings in order to conserve buffer space.
* In many cases, only the first part of long strings is really important, e.g.
* DM_LIST messages, most of which are nearly full and the specific data is not
* important to the viewer. This limit only affects the viewer. Messages saved
* in the echo log are not truncated in the file.
*
*****************************************************************************/
class PsView
{
  friend  LRESULT CALLBACK PsViewProc( HWND, UINT, WPARAM, LPARAM );
  HWND    owner;      // Affords faster access than GetWindowLong.
  void    *myDesc;    // Whatever the owner wants it to be.
  UINT    msgSrcId;   // Help for detailed message analysis and display.
  UCHAR   *buf;
  UCHAR   *bufEndMargin;  // Last byte in buf - 1.
  UCHAR   *first;     // Oldest string currently in buf.
  UCHAR   *next;      // Next string insertion point.
  UCHAR   longest;    // Longest string currently in buf.
  UCHAR   maxLen;     // Max string length. Truncate any longer ones.
  bool    scrollUpdateNeeded;
  bool    repaintNeeded;
  UCHAR   *cachedPtr;
  int     cachedIdx;
  int     font;
  int     widthCaps;
  int     charHeight;
  int     width;      // Window (client) width.
  int     height;     // Window (client) height.
  int     nLines;     // Number of strings (lines) in buf.
  int     topIdx;     // String number (buffer idx) of top line in window.
  int     maxTopIdx;  // Maximum topIdx for current window size/scroll and nLines.
  int     hsPos;      // Current horizontal scroll position.
  int     hsMax;      // Maximum horizontal scroll position.
  void    construct( USHORT bufsize ); // Non-inline portion of constructor.
public:
  HWND    hwnd;
  PsView(
      LPCTSTR title,        // window title
      int       x,          // horizontal position of window
      int       y,          // vertical position of window
      int       nWidth,     // window width
      int       nHeight,    // window height
      HWND      hWndParent, // handle to parent or owner window
      HINSTANCE hInstance,  // handle to application instance
      void      *ptr,       // assigned to myDesc.
      UINT      msgSrc,     // assigned to msgSrcId;
      USHORT    bufsize,    // Byte count of text buffer.
      UCHAR maxline = 254 ) // Maximum line length w/o truncation.

  {
    hwnd = CreateWindow(
        "PsView",               // registered class name            
        title,                  // window name                      
        WS_OVERLAPPEDWINDOW | 
               /* WS_SYSMENU |
                WS_THICKFRAME |
                WS_CAPTION |
                WS_MINIMIZEBOX |
                WS_MAXIMIZEBOX |   */
        WS_VSCROLL | WS_HSCROLL,                      
        x,                      // horizontal position of window    
        y,                      // vertical position of window      
        nWidth,                 // window width                     
        nHeight,                // window height                    
        hWndParent,             // handle to parent or owner window 
        NULL,                   // menu handle or child identifier  
        hInstance,              // handle to application instance   
        this );                 // window-creation data
    owner = hWndParent;         
    myDesc = ptr;
    msgSrcId = msgSrc;
    maxLen = maxline;
    construct( bufsize );
  }
  ~PsView( void );
  void resize( USHORT bufsize, UCHAR maxline );
  void setMaxLineLength( UCHAR maxline )
  {
    maxLen = maxline;
  }
  void putFmtString( char *format, ... ); // Insert C vararg string after converting to Pascal.
  void putRawString( UCHAR *pstr );        // Insert Pascal string.
  void refresh( void );                   // Update scroll and repaint if needed.
};

void fillPsViewClass( WNDCLASSEX *wc );


MFC C++ Source

/****************************************************************************
* C++ Source File:  PlotWnd.cpp
* Description: Dataman PlotWnd class implementation
* Functions defined here:
void prepMainPosChanging( void );
void CPlotWnd::zoomInOut( LPARAM lParam );
void CPlotWnd::onRangeOut( void );
BOOL CPlotWnd::PreCreateWindow(CREATESTRUCT& cs);
int CPlotWnd::OnCreate(LPCREATESTRUCT lpCreateStruct);
void CPlotWnd::OnSetFocus(CWnd* pOldWnd);
void CPlotWnd::OnCaptureChanged(CWnd *pWnd);
void CPlotWnd::OnSysCommand( UINT nID, LPARAM lParam );
void CPlotWnd::OnClose();
CPlotWnd::~CPlotWnd();
LRESULT CPlotWnd::OnSysKeyDown( WPARAM wp, LPARAM lp);
LRESULT CPlotWnd::OnSysKeyUp( WPARAM wp, LPARAM lp);
void CPlotWnd::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos);
void CPlotWnd::OnSize(UINT nType, int cx, int cy);
LRESULT CPlotWnd::OnExitSizeMove( WPARAM wp, LPARAM lp);
BOOL CPlotWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
void CPlotWnd::OnNcMouseMove(UINT nHitTest, CPoint point);
void CPlotWnd::OnMouseMove(UINT nFlags, CPoint point);
void CPlotWnd::OnTimer(UINT nIDEvent);
void CPlotWnd::OnLButtonDown(UINT nFlags, CPoint point);
void CPlotWnd::OnLButtonUp(UINT nFlags, CPoint point);
LRESULT CPlotWnd::OnUnInitMenuPopup( WPARAM wParam, LPARAM lParam );
void CPlotWnd::OnRButtonDown(UINT nFlags, CPoint point);
void CPlotWnd::OnRButtonUp(UINT nFlags, CPoint point);
void CPlotWnd::OnCmdrPlotRange( UINT nID );
void CPlotWnd::OnCmdPrevRange();
void CPlotWnd::OnCmdShowFocus();
void CPlotWnd::OnCmdDisconnect();
void CPlotWnd::OnCmdPlotFmt();
void CPlotWnd::OnCmdPlotSquare();
void CPlotWnd::OnCmdXlog();
void CPlotWnd::OnCmdYlog();
void CPlotWnd::OnCmdSwapXy();
void CPlotWnd::OnCmdrPlot11( UINT nID );
void CPlotWnd::OnCmdrPtr( UINT nID );
void CPlotWnd::OnCmdCoord();
void CPlotWnd::OnCmdrGateSelect( UINT nID );
void CPlotWnd::OnCmdDefGates();
void CPlotWnd::OnCmdGateCancel();
void CPlotWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
*
****************************************************************************/
/* ....................... Windows includes ...................*/
#include "stdafx.h"
/* ....................... Run Time Library includes ......... */
/* ....................... Project includes ...................*/
#define PLOTWND_CPP  
#include "cdefs.h"
#include "util.h"
#include "wutil.h"
#include "fcs.h"
#include "dataman.h"
#include "res.h"
#include "resmsg.h"
#include "PlotWnd.h"
#include "format.h"
#include "display.h"
#include "GateList.h"
#include "strings.h"
#include "draw.h"
#include "pointer.h"
#include "plotdef.h"

/*****************************************************************************
*                             DATA AND DECLARATIONS
*****************************************************************************/

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


/* -------------- Configuration Items ---------- */
bool    minimizeToDefPos = true; // false makes standard Windows minimize.
bool    enableDblClkPtr = true;
UINT    zoomDelayMs = 300; /* If this is longer than double click time, double 
* click will incur only one zoom step before being recognized. If shorter, the 
* auto-repeat will cause at least one additional step before double click is 
* recognized. Note that zoomDelayms is in msec while dblClkTicks is in 
* CLOCKS_PER_SEC ticks. */

/* ----------------- End Configuration Items -------------- */
int     cntCPlotWnds = 0;

clock_t prevClock = 0;

bool    plot11 = false; /* Flag for Plot 1:1 menu selection to tell 
                         WM_SIZE to not invalidate integral X. */
HWND    wndPrev = 0;
HWND    wndDragLine = 0;
bool    justClosedMenu; /* Allows click in plot to close menu without 
                            activating anything. */
enum
{
  TIMER_NONE, TIMER_FIRST, TIMER_SECOND
} timerState;
enum
{
  RBU_NOTHING, RBU_HELPFOCUSBOX
} rbuttonUp = RBU_NOTHING;
bool    selfClose = false;
int     testIdx = 0;
bool    newFocus = false;
CPlotWnd* prevMruPlotWnd = 0;

BEGIN_MESSAGE_MAP(CPlotWnd, CWnd)
    //{{AFX_MSG_MAP(CPlotWnd)
  ON_WM_CREATE()
  ON_WM_SETFOCUS()
  ON_WM_CAPTURECHANGED()
  ON_WM_SYSCOMMAND()
  ON_WM_WINDOWPOSCHANGING()
  ON_WM_SIZE()
  ON_WM_PAINT()
  ON_WM_SETCURSOR()
  ON_WM_NCMOUSEMOVE()
  ON_WM_MOUSEMOVE()
  ON_WM_TIMER()
  ON_WM_LBUTTONDOWN()
  ON_WM_LBUTTONUP()
  ON_WM_RBUTTONUP()
  ON_WM_RBUTTONDOWN()
  ON_WM_CONTEXTMENU()
  ON_WM_INITMENUPOPUP()
  ON_WM_MEASUREITEM()
  ON_WM_DRAWITEM()
  ON_WM_NCRBUTTONDOWN()
  ON_WM_NCRBUTTONUP()
  ON_WM_KEYDOWN()
    //}}AFX_MSG_MAP
  ON_WM_CLOSE()
  ON_MESSAGE( WM_SYSKEYDOWN, OnSysKeyDown )
  ON_MESSAGE( WM_SYSKEYUP, OnSysKeyUp )
  ON_MESSAGE( WM_EXITSIZEMOVE, OnExitSizeMove )
  ON_MESSAGE( WM_UNINITMENUPOPUP, OnUnInitMenuPopup )
  ON_MESSAGE( WM_MENURBUTTONUP, OnMenuRbuttonUp )
  ON_COMMAND( IDMI_PREVPLOTRANGE, OnCmdPrevRange )
  ON_COMMAND( IDMI_SHOWFOCUS, OnCmdShowFocus )
  ON_COMMAND( IDMI_PLOTFORMAT, OnCmdPlotFmt )
  ON_COMMAND( IDMI_SQUARE, OnCmdPlotSquare )
  ON_COMMAND( IDMI_DISCONNECT, OnCmdDisconnect )
  ON_COMMAND( IDMI_XLOG, OnCmdXlog )
  ON_COMMAND( IDMI_YLOG, OnCmdYlog )
  ON_COMMAND( IDMI_SWAPXY, OnCmdSwapXy )
  ON_COMMAND( IDMI_COORD, OnCmdCoord )
  ON_COMMAND( IDMI_DEFGATES, OnCmdDefGates )
  ON_COMMAND( IDMI_GATECANCEL, OnCmdGateCancel )
  ON_COMMAND_RANGE( IDMI_PARMRANGE, IDMI_DATARANGE, OnCmdrPlotRange )
  ON_COMMAND_RANGE( IDMI_FIRSTPLOT11, IDMI_LASTPLOT11, OnCmdrPlot11 )
  ON_COMMAND_RANGE( IDMI_ZOOMOUT, IDMI_GATEPOLY, OnCmdrPtr )
  ON_COMMAND_RANGE( IDMI_FIRSTGATE, IDMI_LASTGATE, OnCmdrGateSelect )
  END_MESSAGE_MAP()

/* CPlotWnd class message functions defined in other modules: 
*- plotpaint.cpp: OnPaint
*- plotmenu.cpp: OnMenuRbuttonUp, OnInitMenuPopup, OnNcRButtonDown, 
*  OnContextMenu, OnMeasureItem, OnDrawItem
*/

/*****************************************************************************
*                      CPlotWnd SUPPORT  FUNCTIONS
*****************************************************************************/
/*******************************************************************************
* Function:     prepMainPosChanging
* Description:  Prepare any information useful to WindowPlots for repainting 
* themselves while the main window, which contains them, is being repositioned.
* This is essentially a code hoisting optimization. The CPlotWnds know when 
* they are being asked to repaint themselves due to the main window position 
* changing because they see mainPosChanging == POS_MOVING. 
* Returns:      Nothing
* Arguments:    None
* Globals:      
*- int mainPosChangingDensity is assigned the value of cntPlotWnds
*- int cntCPlotWnds
*................................... notes ...................................  
* - This calculates for the CPlotWnds a density reduction factor based on the
* number of CPlotWnds, the rationale being that it takes longer to plot many 
* windows and the more there are, the less sensitive the user will be to density
* reduction. e.g. if the user is watching the one and only plot while 
* repositioning the main window they will be more aware of it being washed out 
* than if there were more windows.
* - There are a number of other factors that the CPlotWnd function itself 
* takes into account during group repositioning. Some of these could be hoisted
* into the this function.
*.............................................................................*/
  void prepMainPosChanging( void )
{
  mainPosChangingDensity = cntCPlotWnds;
}
/*******************************************************************************
* Function:     CPlotWnd::zoomInOut
* Description:  Zoom in or out on left button down or zoom timer (auto-repeat)
* and (re)start the timer for auto-zooming.  
* Returns:      Nothing
*
* Arguments:    LPARAM lParam is a point equivalent in case of WM_LBUTTONDOWN in
* a window that already has the focus or the negative enum ZIO_NEWFOCUS for
* WM_LBUTTONDOWN in a window that is gaining the focus based on this event or
* ZIO_TIMER in case of WM_TIMER. 
*- lParam as point: if pointer function is zoom-in then capture the point for 
* centering the zoom focus. For both zoom-in and zoom-out do the next (or 
* first) zoom step and start the timer for auto-repeat.
*- ZIO_NEWFOCUS: just start the zoom timer. The first click in a window only 
* changes the focus but if the user holds the button down the timed auto-repeat
* will begin.
*- ZIO_TIMER: do the next zoom step and restart the timer. The only difference
* between the zoom step on TIMER vs. LBUTTONDOWN is that the latter (when not
* NEWFOCUS) captures the cursor point.
*
* Class data:
*- XyMinMax prev is previous X/Y-min/max assigned for previous range command. 
*- XyMinMax maxRange is maximum X/Y-min/max used for zoom box display.
*- XyMinMax minRange is minimum X/Y-min/max used for zoom box display.
*- XyMinMax now is current X and Y min and max data
*- XyParm   xyParm transfers cursor point from zoom in to paint for move cursor.
*- enum     add (ADD_NOTHING, ADD_PREVRANGE, ADD_MOVECURSOR) is assigned to provide
* painting instructions.
*
* Globals:
*- enum     ptrFunction  
*- enum (TIMER_NONE, TIMER_FIRST, TIMER_SECOND) timerState is used to tell whether
* to set the timer for the auto-repeat commencement delay (zoomDelayMs) or the 
* auto-reapeat rate. 
*- UINT     zoomDelayMs (default 300) tells the auto-repeat delay (not rate)
*
* ......................... notes ...........................................
*- The zoom timer has nothing to do with double-click timing, which is done by
* by tick capture and compare without using a timer.
*.............................................................................*/
void CPlotWnd::zoomInOut( LPARAM lParam )
{
  RECT    frame;

  if( lParam != ZIO_NEWFOCUS )
  {
    if( ptrFunction == PTR_ZOOMIN )
    {
      prev = maxRange;
/* If maxRange (assigned on zoom out) is assigned to prev here, the switch to 
* previous stays at the last zoom out inflection point. If CPlotWnd::now is 
* assigned instead, the zoomed out previous point moves in if the user stops 
* auto-zoom-in and then resumes zooming in. The stickier point is usually 
* better and the user can always move the zoom out point by changing the 
* direction twice, e.g. with two double clicks. For maxRange, not capturing on 
* auto-repeat might save a little processing time (1 test plus 1 jump vs. 6 
* moves) but for CPlotWnd::now, it changes the behavior, preventing the 
* previous view from following one step behind the auto-zoom-in view. */
      GetClientRect( &frame ); 
      if( lParam != ZIO_TIMER )
        xyParm = pointToParms( frame, lParam ); /* Get paramter 
* values at the cursor for use here and for subsequent repositioning of the 
* cursor, which is why we are using static xyParm here instead of automatic 
* xyp. */
      add = ADD_MOVECURSOR;
      mDatRng.x.zoomIn( xyParm.x, xDataMax );
      if( hasY() )
        mDatRng.y.zoomIn( xyParm.y, yDataMax );
      minRange = mDatRng;
    }
    else // PTR_ZOOMOUT
    {
      prev = minRange;
            // See note in zoomOut regarding return and focus box display.
      add = ADD_PREVRANGE;
      mDatRng.x.zoomOut( xDataMax );
      if( hasY() )
        mDatRng.y.zoomOut( yDataMax );
      maxRange = mDatRng;
    }
    moveCoordWnd( -1 ); /* During zooming if the coordinates window
* is open, the values may change a little due to resolution changes or a lot
* due to shifting focus, especially toward the end of zoom out. The first 
* mouse move after zooming stops would correct the display but it is nice
* to keep the display in sync with the actual cursor position. */
    RedrawWindow();        
  }
/* The zoom timer is started unless called here in response to WM_TIMER. The
* initial delay (zoomDelayMs) determines how long the button must be held
* down before auto-repeat begins. After this delay, the timer is reset for
* the auto-repeat rate (default 150 ms). After this, the timer is allowed
* to continuously retrigger itself so we don't do anything more here with
* it. */
  if( lParam != ZIO_TIMER )
  {
    nonIntegralX(); // Histogram can't be integral if we are changing the focus.
    SetTimer(TIMER_ZOOM, zoomDelayMs, 0 ); // auto-repeat commencement delay.
    timerState = TIMER_FIRST;
  }
  else if( timerState == TIMER_FIRST )
  {
    SetTimer( TIMER_ZOOM, 150, 0 ); // auto-repeat rate.
    timerState = TIMER_SECOND;
  }
/* While timerState is TIMER_SECOND we don't need to restart the timer because
* it keeps retriggering itself at the specified auto-repeat rate. */
}
/*******************************************************************************
* Function:     CPlotWnd::onRangeOut
* Description:  Ancillary functions when zooming or jumping out to a wider view.
* This does not do the sizing or focus operations but just determines whether to
* show the focus box and flash focus and invalidates the window to cause it to
* be repainted.
* Returns:      Nothing 
* Arguments:    None
*
* Class data:
*- XyMinMax now is current X and Y min and max.
*- XyMinMax prev is previous X/Y-min/max. 
*- int add may be assigned ADD_PREVRANGE to tell OnPaint to display the focus
* focus box.
*
* Globals:
*- BOOL flashFocusOnRange is a configuration item telling whether to flash a 
* cross at the focus.
*- bool doFlash is assigned true if this is a scatter plot and flashFocusOnRange
* is TRUE.  
*.............................................................................*/
void CPlotWnd::onRangeOut( void )
{
  if( hasY() )
  {
    if( mDatRng > prev )
      add = ADD_PREVRANGE;
    if( flashFocusOnRange )
      doFlash = true; /* Automatic flash is done only in scatter
* plots because it is annoying and not useful in histograms. User can still 
* ask for it explicitly in histograms. */
  }
  else
  {
    if( mDatRng.x > prev.x )
      add = ADD_PREVRANGE;
    nonIntegralX(); // Matters only to histogram.
  }
  InvalidateRect( 0, TRUE );
}

/*******************************************************************************
*                  SYSTEM MESSAGE FUNCTIONS
*******************************************************************************/

/*******************************************************************************
* Function:     CPlotWnd::PreCreateWindow
* Description:  Register the plot window class.
* Returns:      FALSE on any error
* Arguments:    CREATESTRUCT& cs is modified from its default CHILD 
* characteristics to WS_OVERLAPPEDWINDOW even though instances will be children
* of the view window.
* ...................... notes ...............................................   
*- The class is registered with the application icon, which contains both a 
* 32+32 and a 16*16 bitmap. The smaller one is automatically used.
*.............................................................................*/
BOOL CPlotWnd::PreCreateWindow(CREATESTRUCT& cs) 
{
  if( !CWnd::PreCreateWindow(cs) )
    return FALSE;

  cs.style = WS_OVERLAPPEDWINDOW + WS_VISIBLE;

  cs.lpszClass = AfxRegisterWndClass(
    CS_HREDRAW|CS_VREDRAW, 
    ::LoadCursor(NULL, IDC_ARROW), 
    HBRUSH(COLOR_WINDOW+1), 
    AfxGetApp()->LoadIcon( IDR_MAINFRAME ));

  return CWnd::PreCreateWindow(cs);
}
/*******************************************************************************
* Function:     CPlotWnd::OnCreate
* Description:  Create the plot window for a CPlotWnd object.
* Returns:      0 if successful, -1 if CWnd::OnCreate returns an error.
* Arguments:    LPCREATESTRUCT lpCreateStruct points to the same structure (or
* copy of it) that we configured in PreCreateWindow.
*
* Globals:
*- int cntCPlotWnds is incremented.
*- clock_t prevClock is assigned the current clock value just to ensure that it
* has a reasonable value at the next LBUTTONDOWN for determining double-click.
*- HWND wndDragLine is assigned 0 to indicate that a drag box or line is not 
* being drawn in any plot.
*
* ...................... notes ...............................................   
*- prevClock and wndDragLine don't have to be assigned specifically here, but this
* is as good a place as any.
*.............................................................................*/
int CPlotWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
  if( CWnd::OnCreate(lpCreateStruct) == -1 )
    return -1;
  cntCPlotWnds++;
  prevClock = clock();
  wndDragLine = 0;
  return 0;
}

/*******************************************************************************
*        SetFocus, CaptureChanged, SysCommand, Close, ~CPlotWnd
* ...................... notes .............................................
*- When a plot window is individually closed via its close button or window menu
* close item, if it doesn't have the focus then the focus should not change. If 
* it does have the focus then the focus should be passed to another plot window 
* in some way that makes sense. Windows hyperactive set focus complicates this. 
* Even just clicking the close button gives a window the focus and, after the 
* window is closed, Windows gives the focus away practically at random. If the 
* window being closed alreay has the focus then it does not receive a set focus 
* before close. We might be able to utilize this difference except that the 
* window that has the focus in fact has received a set focus message but long 
* before the close button was clicked. Consequently, without timing or 
* intervening messages being considered, we can't tell whether the OS has given 
* the window the focus just long enough to destroy it or the window already had 
* the focus. Further, although WM_NCLBUTTONDOWN + NC_CLOSE occurs soon after 
* WM_SETFOCUS, when the user clicks the close button of a window that doesn't 
* already have the focus, if the same window is closed via its Windows menu, the
* WM_SYSCOMMAND + NC_CLOSE message occurs only after intervening menu messages. 
* The simplest and most reliable solution seems to be to have OnSetFocus 
* maintain both the current focus, mruPlotWnd, and the previous, prevMruPlotWnd.
* Then, at SysCommand SC_CLOSE assign prevMruPlotWnd to mruPlotWnd. If the 
* closing window did not have the user focus this restores the user focus. If 
* the closing window did have the user focus then the focus is shifted to the 
* previous user focus. Although this shift can be justified, shifting to the 
* previous window in the list can be user for the user to track. However, the 
* latter is not technically feasible because of the difficulty of determining 
* whether the window being closed is the user's focus.
*
*- WM_CAPTURECHANGED indicates that the mouse capture has changed. However, here
* it is being used for the (inevitable) coincidence that this message always 
* follows WM_SETFOCUS but if mouse click in a window changes the focus to it, 
* the button down message occurs before the CAPTURECHANGED. By setting newFocus 
* in OnFocus and clearing it in OnCaptureChanged, we can use newFocus to block 
* inappropriate responses. For example, this is used to allow the first click 
* in a window to change the focus without automatically taking a zoom step (see 
* OnLButtonDown).
*
*- Like most windows in MFC, CPlotWnd comprises a class object with a window 
* (class member m_hWnd) reference. MFC expects the window to be destroyed by the 
* object's destructor and issues a debug warning if this isn't done. However, 
* we take a window-centric point of view in this program, with the window's 
* close or destroy deleting the CPlotWnd object. This enables simple, automatic 
* destruction of the object when the user closes the window. Consistent with 
* this mechanism, the program deletes CPlotWnd objects by closing their 
* windows, not by deleting them directly. Consequently, CPlotWindow doesn't 
* need a destructor to destroy the window, but, without this, the MFC warning 
* is confusing and obscures other types of warnings that may be important. To 
* prevent the warnings, the class destructor calls DestroyWindow. Conceptually, 
* it makes the most sense for the window to delete the class object at the last 
* message, WM_NCDESTROY, or the penultimate, WM_DESTROY. However, with the 
* destuctor calling DestroyWindow, this causes an endless loop. Therefore, 
* everything that needs to be done to remove the plot (i.e. removing it from 
* the plot list and deleting the object) is done in OnClose. After this point, 
* we don't need the class object, as the remaining messages all relate only to 
* destroying the window. 
*
********************************************************************************/ 

/*******************************************************************************
* Function:     CPlotWnd::OnSetFocus
* Description:  Response to WM_SETFOCUS 
* Returns:      Nothing
* Arguments:    Unused
* Globals: 
*- int mainPosChanging tells whether this message has occurred because the plots
* are being redrawn to fit the changing main frame, in which case we don't want
* to waste time with superfluous changes, especially with the coordinates
* window (if it is open).
*- bool updatingPlots is true when plots are being redrawn for a layout change.
* As with mainPosChanging, we don't want to waste time, and, in this case, we
* also want to avoid changing mruPlotWnd and prevMruPlotWnd.
*- CPlotWnd* mruPlotWindow and prevMruPlotWnd.
*- bool newFocus is assigned true here to indicate when the user has just 
* changed the focus to this window. If this has occurred by mouse click, newFocus
* blocks zoom or coordinates window toggle (depending on pointer function) until
* the second click, i.e. the first click after the window gets the focus.
*.............................................................................*/
void CPlotWnd::OnSetFocus(CWnd* pOldWnd) 
{
  if( mainPosChanging == POS_STABLE && ( !updatingPlots || mruPlotWnd == 0 ) )
  {
    prevMruPlotWnd = mruPlotWnd; 
    if( this != mruPlotWnd )   /* Without this condition, making a 
* selection in the pointer toolbar causes the coordinates window to be closed. */
    {
      if( mruPlotWnd != 0 )
      {
        prevMruPlotWnd = mruPlotWnd;
        newFocus = true; // Flag don't toggle coord window on first click.
      }
      mruPlotWnd = this;
      if( wndCoord != 0 )
        setCoordWnd( SCW_TOGGLE ); /* This opens a new coordinates
* window automatically when the focus shifts from a plot with the window to 
* another plot. */
    }
  }
    /*else
       newFocus = false; 
* WM_CAPTURECHANGED nearly always occurs after SET_FOCUS and assigns newFocus 
* false. However, when a plot inherits the focus because of another being 
* deleted, WM_CAPTURECHANGED is not generated when the user clicks this plot's 
* close button. In this case, newFocus won't be cleared until the plot layout 
* is finished updating and the focus reassigned. Since newFocus is used 
* only to block button down functions (specifically zoom) this delay doesn't 
* hurt. Unless newFocus becomes used for something involving plot updating, we 
* don't need to assign it false here. */
}
/*******************************************************************************
* Function:     CPlotWnd::OnCaptureChanged
* Description:  This message indicates that the mouse capture (i.e. which 
* window temporarily owns it) has changed but we use it to clear newFocus, so
* that subsequent clicks cause zooming or coordinates window toggle.
* Returns:      Nothing
* Arguments:    Ignored
* Globals:      bool newFocus is assigned false.
*.............................................................................*/
void CPlotWnd::OnCaptureChanged(CWnd *pWnd) 
{
  newFocus = false; // Without this, the coordinates window never toggles on click.
}
/*******************************************************************************
* Function:     CPlotWnd::OnSysCommand
* Description:  Generated by opening the plot window's window menu or by 
* selecting an item in this menu or by clicking the minimize, maximize, or
* close button. We are interested in the minimize and close whether by menu
* or button. If enabled, we take over the entire minimize function, minimizing
* not to an icon but to the tiled gallery position and restoring to the last
* non-gallery position. For CLOSE, CWnd::OnSysCommand is passed the command 
* but we first assign globals to direct the closing and focus shifting.
* Returns:      Nothing     
* Arguments:    
*- UINT nID tells the command type. We are only interested in SC_MINIMIZE and
* SC_CLOSE. It is assumed here that MFC has stripped off the low nibble that
* the OS uses internally. The wParam, from which nID is derived would need 
* &= 0xFFF0 before testing against any SC_ constants.
*- LPARAM lParam is ignored.
*
* Class data: 
*- int pos tracks the minimized/restored state of the window.
*
* Globals: 
*- bool minimizeToDefPos is a configuration item. true enables our minimize.
* false enables standard Windows minimize.
*- CPlotWnd* mruPlotWindow and prevMruPlotWnd.
*- bool updatingPlots is assigned true if SC_CLOSE to prevent OnFocus from 
* doing any more mruPlotWnd, prevMruPlotWnd, and coordinates window ownership 
* changes until we are done updating the layout. In addition to speeding up the 
* process, this ensures that, if the window with the focus is being closed and 
* the coordinates window exists, the wndCoordStatus remains WCS_PARENTDESTROYED 
* and doesn't become WCS_EXISTS, preventing the layout function from 
* reassigning the coordinates window to the correct plot. 
*- bool selfClose is assigned true to tell OnClose to request a new plot layout.
* ...................... notes ...............................................   
*- This is the earliest point where we know that the plots are going to be 
* updated. We probably could assign updatingPlots later, in OnClose for 
* example, since we are not expecting any more WM_SETFOCUS messages until we 
* being the updating process. However, doing it now guards against unexpected 
* WM_SETFOCUS events due to debugging or unusual circumstances.
*.............................................................................*/
void CPlotWnd::OnSysCommand( UINT nID, LPARAM lParam ) 
{
  if( minimizeToDefPos && nID == SC_MINIMIZE )
  {
/* If a maximized window is minimized to its gallery position (i.e. using our
* alternative to the standard OS minimize) the OS internally still thinks that
* it is maximized and disables size controls in the window menu and sizing
* border. ShowWindow normal corrects this. */
    if( IsZoomed() )
      ShowWindow( SW_SHOWNORMAL );
    if( pos != POS_DEF )
    {
      capturePreMinPos();
      setDefPos();
      pos = POS_DEF;
    }
    else
    {
      setPreMinPos();
      pos = POS_MOVED; 
    }
    return;
  }
  if( nID == SC_CLOSE )
  {
    mruPlotWnd = prevMruPlotWnd; /* Restore the actual user focus 
* prior to the OS changing it to the window about to be closed. */
    updatingPlots = true; /* Tell OnFocus to stop updating mruPlotWnd and
* prevMruPlotWnd until we are done updating. */
    selfClose = true;
  }
  CWnd::OnSysCommand(nID, lParam);
}
/*******************************************************************************
* Function:     CPlotWnd::OnClose
* Description:  Response to WM_CLOSE and called directly to destroy the window.
* Returns:      Nothing
* Arguments:    None
*
* Class members:
*- PlotArea* area is searched to find the index of this plot and of mruPlotWnd
* and this plot is removed from the list.
*
Globals:        
*- int  cntCPlotWnds is decremented.
*- CPlotWnd* mruPlotWnd is assigned 0 if this was the only plot. Otherwise, we
* find its index and request that the focus be given back to it after in the
* new plot layout. Note possible mruPlotWnd correction in OnSysCommand.
*- HWND coordWndParent tells whether this plot owned the (open) coordinates
* window.
*- CPlotWnd* gateDrawPlot tells if the user was in the middle of drawing/
* assigning a gate area when they decided to close the window, in which case
* gateDrawPlot is assigned 0.
*- bool selfClose if true tells that this window was locally closed and needs 
* tell higher authority to make a new layout.
*- int alloCnt is decremented to count the deletion of this CPlotWnd object, 
* which occurs here.
*
*................................... notes ...................................  
*- Before conversion to MFC, this function was the response to WM_DESTROY, the
* penultimate message sent to the window (the last is WM_NCDESTROY x82). The 
* PlotWindow object was no longer needed near the end of the function and was
* deleted. PlotWindow was derived from CWndList, itself derived from CWnd. To 
* prevent a recursive invocation of this function m_hWnd was assigned NULL before
* deleting the object.
* pw->m_hWnd = 0; // Tell MFC not to recursively destroy this window.
* delete pw;
* alloCnt--;
* After full conversion to MFC, this no longer worked. If the destructor didn't
* call DestroyWindow, MFC issued a warning but if it did then "delete this" and
* the destructor endlessly invoked each other. I changed this to be invoked on
* WM_CLOSE, but MFC seems to generate this only when closed by the close button
* and not when destroyed. Therefore, I made this function public so that plot
* list destroyers can call it instead of DestroyWindow.
*.............................................................................*/
void CPlotWnd::OnClose() 
{
  CPlotWnd    *pw2;
  PlotArea    *pa;
  int         mruIdx;
  int         plotIdx;

  cntCPlotWnds--; // <<<<<<<<<<<<< This should be pw->area.plotCnt or related somehow.

  KillTimer( TIMER_ZOOM );
  KillTimer( TIMER_FLASH );
  pw2 = 0;
  pa = area;

  if( cntCPlotWnds == 0 )
  {
    plotIdx = 0;
    mruPlotWnd = 0;
  }
  else
  { 
/* Determine the index of the plot to regain the focus. If mruPlotWnd occurs
* later than this one in the plot list then its index -1 will be its index
* under the new layout. */
    plotIdx = pa->plots->indexOf( this );
    mruIdx = pa->plots->indexOf( mruPlotWnd );
    plotIdx = mruIdx > plotIdx ? mruIdx - 1 : mruIdx;

/* Remove this plot from the plot list. */
    if( this == pa->plots )
      pa->plots = getNext();
    else
    {
      for( pw2 = pa->plots ; pw2 != 0 ; pw2 = pw2->getNext() )
        if( pw2->getNext() == this )
        {
          pw2->next = next;
          break;
        }
    }
  }
/* If this plot owned the (open) coordinates window then destroy the window 
* and indicate that a new one needs to be created and assigned to the new
* focus window in the new plot layout. */
  if( m_hWnd == coordWndParent )
  {
    setCoordWnd( SCW_OFF );
    wndCoordStatus = WCS_PARENTDESTROYED;
  }

  if( this == gateDrawPlot )
    gateDrawPlot = 0;

  if( --pa->plotCnt == 0 )
    pa->plots = 0;

  if( selfClose )
  {
    ::PostMessage( wndFrame, UWM_UPDATEPLOTS, (UINT)pa, LONG( plotIdx ));
    selfClose = false;
  }
  delete this; // <----------- window deletes object.
  countAllo( -1, ALLO_CPLOTWND, __FILE__, __LINE__ ); // Count CPlotWnd deletion for leak detection.
}
/*******************************************************************************
* Function:     CPlotWnd::~CPlotWnd
* Description:  CPlotWnd destructor just calls DestroyWindow. We are doing this
* here only to avoid a debug warning from MFC. 
* Returns:      Nothing
* Arguments:    None
*.............................................................................*/
CPlotWnd::~CPlotWnd()  
{
  DestroyWindow();
}

/*******************************************************************************
* Function:     CPlotWnd::OnSysKeyDown and OnSysKeyUp
* Description:  Transfer all Alt key events to the main frame so that menu bar
* operations always refer to the frame's menu and only popup menus (on right,
* F1, or tool bar) refer to the plot window.    
* Returns:      0
* Arguments:    ignored
* Globals:      HWND wndFrame is given the focus and passed the message.
*.............................................................................*/
LRESULT CPlotWnd::OnSysKeyDown( WPARAM wp, LPARAM lp)
{
  ::SetFocus( wndFrame );
  ::SendMessage( wndFrame, WM_SYSKEYDOWN, wp, lp );
  return 0;
}
LRESULT CPlotWnd::OnSysKeyUp( WPARAM wp, LPARAM lp)
{
  ::SetFocus( wndFrame );
  ::SendMessage( wndFrame, WM_SYSKEYUP, wp, lp );
  return 0;
}

/*******************************************************************************
*                                SIZE FUNCTIONS
*******************************************************************************/

/*******************************************************************************
* Function:     CPlotWnd::OnWindowPosChanging
* Description:  WM_WINDOWPOSCHANGING preceeds any position change whether due 
* to the user directly acting on the window or to program action. We are 
* interested in this message only if the plot is supposed to be square, in which
* case this is the most efficient and flicker-free place to force the window 
* square.   
* Returns:      Nothing     
* Arguments:    WINDOWPOS FAR* lpwndpos contains proposed X and Y dimensions of
* the window. If the window is supposed to be square, we force the larger of 
* these (if either) to the value of the smaller.
*.............................................................................*/
void CPlotWnd::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos) 
{
  if( isSquare() )
  {
    if( lpwndpos->cx > lpwndpos->cy )
      lpwndpos->cx = lpwndpos->cy;
    else if( lpwndpos->cy > lpwndpos->cx )
      lpwndpos->cy = lpwndpos->cx;
  }
  else
    CWnd::OnWindowPosChanging(lpwndpos);
}
/*******************************************************************************
* Function:     CPlotWnd::OnSize
* Description:  Response to WM_SIZE, which can come from functions, overall 
* layout/size changes, or from the user specifically resizing this window. In 
* any case, in response to the size change here if a gate region is being drawn
* we reset the drawing process because changing the size invalidates the 
* relationship between the region's control points and the plotted events. In 
* most cases of size change we also invalidate the plot's integral X condition 
* if it is a histogram that was plotted integral X (plot11). The only time we 
* don't do this is on the first size change in response to the plot11 request 
* itself.
* Returns:      Nothing
* Arguments:    ignored.
* Class data: 
*- int gating tells the gate drawing state of this plot. Changing the window
* size cancels any gate drawing in progress.
*
* Globals: 
*- int ptrFunction tells the current pointer/cursor function. Here we only care
* whether it is a gate draw or zooming type.
*- int polyIdx is the vertex count of any polygon gate drawing in progress. It
* is assigned 0 here because all gate drawing is aborted.
*- bool plot11 when true tells that this message is in response to OnCmdrPlot11
* requesting that the plot be redrawn at an integral X size, in which case 
* this (and only this) size change does not change the plot's integral X state.
*.............................................................................*/
void CPlotWnd::OnSize(UINT nType, int cx, int cy) 
{
  if( gating >= GATE_DRAWING )
  {
    gating = ptrFunction >= PTR_RECT ? GATE_DRAWREADY : GATE_NOTHING;
    polyIdx = 0;
  }
/* If the user has just selected an integral histogram plot from the Plot 1:1 
* sub-menu then don't change the integral X selection because this WM_SIZE is 
* in response to that. This is a one-time thing. At any other time, a size 
* change invalidates integral X. */
  if( !plot11 )
    nonIntegralX();
  else
    plot11 = false;
  CWnd::OnSize(nType, cx, cy);
}
/*******************************************************************************
* Function:     CPlotWnd::OnExitSizeMove
* Description:  Called after a plot window has individually (by user) changed 
* size or location. 
* Returns:      0
* Arguments:    ignored
* Class data: 
*- int pos is assigned POS_MOVED, indicating that it is now in a non-default
* position.
*.............................................................................*/
LRESULT CPlotWnd::OnExitSizeMove( WPARAM wp, LPARAM lp)
{
  pos = POS_MOVED;
  return 0;
}

/*******************************************************************************
*                        LEFT BUTTON, MOUSE, AND TIMER
* .............................. notes .......................................
*- Mouse move and left button clicks control both the zoom box and the 
* coordinates window but the two are largely independent. The zoom calculations 
* could be simplified by using some values already known to the coordinates 
* window but the coordinates window might be owned by a different window or not 
* open at all, in which case the zoom calculations would still be required. It 
* is simpler for the zoom calculations to ignore the possible presence of an 
* active and relevant coordinates window. In response to mouse move messages, 
* the zoom box is redrawn only if it is active and the coordinates window 
* window is moved and redrawn only if it is active. The two are independent of 
* each other.
*
*- Petzold suggests setting the window cursor at WM_MOUSEMOVE but this is not 
* sufficient and is actually too much work if applied uniformly to all cursor 
* types. Instead, when the user changes the pointer function, the CPlotWnd 
* class cursor is changed. Thus, the cursor is not set at mouse move and 
* OnSetCursor is a relatively rare event. See addition notes in 
* setPointerFunction in pointer.cpp.
*
*******************************************************************************/

/*******************************************************************************
* Function:     CPlotWnd::OnSetCursor
* Description:  Set the cursor according to the current pointer type. 
* Returns:      Reflected return from CWnd::OnSetCursor
* Arguments:    
*- CWnd* pWnd and UINT message are ignored.
*- UINT nHitTest tells whether the cursor is in the client or non-client area of
* the window. We set the cursor only in client area.
*.............................................................................*/
BOOL CPlotWnd::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
  if( nHitTest == HTCLIENT )
  {
    setPlotCursor();
    return 0;
  }
  return CWnd::OnSetCursor(pWnd, nHitTest, message);
}
/*******************************************************************************
* Function:     CPlotWnd::OnNcMouseMove
* Description:  Mouse movement in the non-client area of this window. Hide the
* coordinates window (if it exists) and kill any auto-zoom.
* Returns:      Nothing
* Arguments:    ignored
* Globals:      enum timerState is assigned TIMER_NONE
*.............................................................................*/
void CPlotWnd::OnNcMouseMove(UINT nHitTest, CPoint point) 
{
/* Kill the zoom timer (in case it is active-- if not, trying to kill it doesn't
* hurt). If this is not done now, the user may start zooming, pull the cursor 
* out of the client area, and then release the button where the LBUTTONUP 
* message will not be seen by this window. This would leave the auto-zoom self-
* regenerating without a means to stop it. */
  KillTimer( TIMER_ZOOM );
  timerState = TIMER_NONE;
  hideCoordWnd();
}
/*******************************************************************************
* Function:     CPlotWnd::OnMouseMove
* Description:  Mouse movement in the client area.
* Returns:      Nothing 
* Arguments:   
*- UINT nFlags is ignored.
*- CPoint point tells the location of the cursor hot spot. If a drag line/box 
* is being drawn in this plot, the previous endpoint is replaced by this point.
* Class data: 
*- POINT array zoom contains drag line or region corner points.

* Globals:  
*- bool justClosedMenu may be assigned false here, ending the click response
* lockout.
*- HWND wndDragLine tells whether the user is drawing a gate region or zoom-in
* box. If so then this must be the window in which the drawing is taking place.
*- HWND coordWndParent tells which, if any plot owns the coordinates window. If
* this plot then the coordinates window is moved along with the cursor.
*
* ...................... notes ...............................................   
*- If the user clicks a mouse button in the plot to close the floating menu, 
* the resulting message stream is BUTTONDOWN, MOUSEMOVE, BUTTONUP. The 
* MOUSEMOVE doesn't need to be processed but doing so wouldn't hurt but both 
* button messages have to be discarded. But we also want to end the lockout 
* state as soon as possible. MOUSEMOVE is the best for this because it occurs 
* before the user does anything else. But if the lockout is always cleared at 
* MOUSEMOVE, the BUTTONUPs will not be locked out when necessary. So, if either 
* button is depressed, MOUSEMOVE immediately returns, doing nothing. Then, at 
* BUTTONUP, the lockout is cleared and the event otherwise not processed. 
*.............................................................................*/
void CPlotWnd::OnMouseMove(UINT nFlags, CPoint point) 
{
  if( justClosedMenu )
  {
    if( !Depressed( VK_LBUTTON ) && !Depressed( VK_RBUTTON ) )
      justClosedMenu = false;
    return;
  }
    //setPlotCursor(); This isn't necessary because class cursor is correct.

/* If a drag line/box is being drawn in any window it must be this one. Erase
* the previous line or region and replace it using the new endpoint. */
  if( wndDragLine != 0 )
  {
    drawDragLine(); // Invert again to remove previous.
        //LparamToPt( zoom[1], lParam );
    zoom[1].x = point.x;
    zoom[1].y = point.y;
    drawDragLine();
  }
  if( coordWndParent == m_hWnd ) // &&  wndCoord != 0 
        //moveCoordWnd( lParam );
    moveCoordWnd( MAKELONG( point.x, point.y ));
}
/*******************************************************************************
* Function:     CPlotWnd::OnTimer
* Description:  Focus point flash or auto-repeat zoom timeout. For focus point
* flash we call killFlash, which kills the timer and removes the flash cross.
* For auto-repeat zoom we call zoomInOut, which repeats the zoom in or out just
* like a repeating left click.
* Returns:      Nothing   
* Arguments:    UINT nIDEvent is TIMER_FLASH or TIMER_ZOOM. Note that there is
* no timer for double-click, which is handled by time sampling.
*
* Class data:   enum ZIO_TIMER is passed to zoomInOut to tell it that is being
* called in response to WM_TIMER.
*
* ...................... notes ............................................... 
*- The auto-repeat zoom timer is eventually killed on left button up.  
*.............................................................................*/
void CPlotWnd::OnTimer(UINT nIDEvent) 
{
  if( nIDEvent == TIMER_FLASH )
    killFlash();
  else
    zoomInOut( CPlotWnd::ZIO_TIMER );
}
/*******************************************************************************
* Function:     CPlotWnd::OnLButtonDown
* Description:  Response to WM_LBUTTONDOWN in the client area. This event 
* causes a variety of responses largely depending on the current pointer 
* function. If it is ellipse or rectangle gate draw or zoom range then a region 
* box is started. If it is polygon gate draw, the first or next segment of a 
* polygon is started. If it is zoom-in or zoom-out, the next zoom step is 
* taken. However, if the pointer function is a gate draw type and this point is 
* very near to the previous, the drawing function is not done but the 
* coordinates window state is toggled. Additionally, if the pointer is zoom-
* out, zoom-in, or zoom-range, double-click is detected here and causes the the 
* zoom direction (in or out) to reverse. The first step in this new direction 
* will be taken in this case.
* Returns:      Nothing
* Arguments: 
*- UINT nFlags is ignored
*- CPoint point tells the coordinates of the cursor hot spot.
*   
* Class data: 
*- int gating tells the gating state of this plot. It is assigned 
* GATE_DRAWREADY or GATE_NOTHING here.
*- POINT array zoom contains zoom range box or gate region diagonal points. If 
* a draw box/line is started here, both points are assigned the value of the 
* points argument and the resulting single-pixel inversion drawn so that the 
* next draw event (on mouse move) has something to erase (re-invert).
*
* Globals:
*- bool justClosedMenu if true tells to not respond to the event.
*- CPlotWnd *gateDrawPlot tells the plot, if any, in which a gate region is
* currently being drawn (by the user). The window receiving this message 
* always becomes or remains the gate drawing plot if the pointer is a gate
* draw type. If a gate region was being drawn in another plot, that one is
* aborted.
*- bool enableDblClkPtr is a configuration item, which, if true, tells that
* we will respond to double-click. The user may disable or change the time
* delay of double-click to suit personal perference.
*- clock_t dblClkTicks tells the number of tick in a double-click event. It
* is assigned CLOCKS_PER_SEC / 4 (250ms) default.
*- clock_t prevClock tells the time of the previous left button down event.
*- int polyIdx is the polygon gate draw region segment count. It is assigned
* 0 here if this has become the new gate drawing plot.
*- HWND wndDragLine tells in which, if any, window a drag box/line is being
* drawn. It is assigned this plot's m_hWnd.
*
* ...................... notes ...............................................
*- Left button action can be disabled for two reasons, that it has apparently 
* been used to close a floating menu or to move the focus. These situations are 
* indicated, respectively, by justClosedMenu and newFocus. The lockout 
* situations are short-lived and mostly controlled elsewhere. Here, regarding 
* justClosedMenu we just do nothing if it is true. If newFocus, we start the 
* auto-repeat timer without taking a zoom step and clear the flag, ending the 
* new focus lockout, whose only purpose is to prevent a zoom step on the first 
* click.
*.............................................................................*/
void CPlotWnd::OnLButtonDown(UINT nFlags, CPoint point) 
{
  clock_t curClock;
  POINT   pt;
  RECT    frame;

  LPARAM  lParam = MAKELONG( point.x, point.y );

  if( justClosedMenu )
    return;

/* If a gate is not currently being drawn in any plot then if ptrFunction is one
* of the gate types, make this plot draw ready, else make it gate nothing state
* (this is probably not required, as all plots should already be gate nothing 
* if no gate is being drawn). Note that gateDrawPlot is not assigned ' this' at 
* this point because the fact that it was 0 is used later in this function. */
  if( gateDrawPlot == 0 )
    gating = ptrFunction >= PTR_RECT ? GATE_DRAWREADY : GATE_NOTHING;
/* If a gate is being drawn in another plot then wipe it out and make this one 
* the new gating plot. */
  else if( gateDrawPlot != this )
  {
    wipeGateDraw();
    gating = GATE_DRAWREADY;
    gateDrawPlot = this;
  }

  if( gating == GATE_NOTHING )
  {
    if( enableDblClkPtr )
    {
      curClock = clock();
      if( prevClock + dblClkTicks >= curClock ) /* clock_t is long 
* (signed) so this calculation fails around the sign inflection point. Since the 
* worst that can happen is one missed double click, we don't care. */
      {
        prevClock = curClock;
        togglePointer();
      }
      else
        prevClock = curClock;
    }
  }
  else  // gating >= GATE_DRAWREADY.
  {
    if( gateDrawPlot == 0 )
    {
      gateDrawPlot = this;
      setAllGatingState( GATE_NOTHING );
      polyIdx = 0; // In case DRAW_POLY
    }
    if( ptrFunction == PTR_POLY && hasY() )
    {
      if( ( lParam = setPolygonPt( lParam, WM_LBUTTONDOWN )) < 0 )
        return; // SPP_FAIL or SPP_DONE. Either way, stop drawing.
      else
        goto setdrawbox; /* Normal case unless/until the user clicks
                         near the first point, closing the polygon. */
    }
/* If the gate region is rectangle or ellipse and the user has already dragged 
* one out but then presses the left button again, either they are unhappy with 
* the region and want to draw a different one or they want to toggle the 
* coordinates window. We can distinguish between the do possibilities by where 
* the user clicks. If it's the same as the drag endpoint, the user has clicked 
* without moving, indicating that they want to toggle the coordinates window. If
* they move and then click, they obviously are trying to draw a new region. To 
* toggle the coordinates window in this situation, we have to do it here because
* WM_LBUTTONUP compares the two corners of the zoom box to determine whether the
* user has clicked without moving and here we want to keep the zoom box in 
* which specifically this is not the case. */
    if( gating == GATE_DRAWDONE )
    {
      LparamToPt( pt, lParam );
      if( isPtNearPt( pt, zoom[1], clickRange ) )
      {
        setCoordWnd();
        return;
      }
      drawDragLine(); // Invert again to remove previous.
    }
    gating = GATE_DRAWING;
    goto setdrawbox;
  }
  if( ptrFunction == PTR_ZOOMRANGE )
  {
    setdrawbox:  // zoom range or any gate type (rectangle, ellipse, or poly).
    wndDragLine = m_hWnd;
    LparamToPt( zoom[0], lParam );
    zoom[1] = zoom[0];
    drawDragLine(); // Draw the initial inversion box.
    SetCapture();
    return;
  }
  if( newFocus ) //&& ptrFunction <= PTR_ZOOMIN ) 
  {
/* With the zoom in and zoom out pointers the first click in a window is used 
* to change the focus. Only subsequent clicks or auto-zoom invoke zooming. 
* Otherwise, only by clicking in the caption bar could the focus be changed 
* without immediately changing the view, which is inconvenient especially when 
* the user is just trying to see coordinates-- as long as they are required to 
* move the focus to a plot in order to see its coordinates, the least we can do 
* is make this easy to do. This same treated can be given to all pointer types to 
* simplify the code a little but it has no effect on them. */
    newFocus = false;
/* Set up initial reference frame for auto-zooming on the timer and then go
* start the timer. */
    GetClientRect( &frame ); 
    xyParm = pointToParms( frame, lParam );  
    zoomInOut( ZIO_NEWFOCUS ); 
  }
  else
    zoomInOut( lParam );
}
/*******************************************************************************
* Function:     CPlotWnd::OnLButtonUp
* Description:  Response to WM_LBUTTONUP in the client area. As with LBUTTONUP, 
* this event causes a variety of responses largely depending on the current 
* pointer function. In most cases the action taken here finishes a process 
* started in OnLButtonDown but zooming (in and out) and double-click are not 
* handled here in any way. If a rectangle or ellipse gate or a zoom-range was 
* started in OnLButtonDown, it is finished here. If a polygon segment was 
* started, it is also finished here. If the pointer function is one of the 
* gating types, clicking without dragging is detected here and the response is 
* to toggle (on/off) the coordinates window. When drawing a polygon by dragging 
* segments, a single click toggles the coordinates window but when drawing by 
* clicking vertices, a second click is needed to toggle the window.
*
* Returns:      Nothing
* Arguments:    
*- UINT nFlags is ignored
*- CPoint point tells the coordinates of the cursor hot spot.
*
* Class data:
*- POINT array zoom contains the beginning (at OnLButtonDown) and ending points 
* of the drag. If these are very close the event pair is considered a click 
* instead of drag.
*- int gating tells the gating state of this plot. If it indicates that a gate 
* draw is in progress then if the pointer is ellipse or rectangle, the region 
* is finished and gating is assigned GATE_DRAWDONE here; if the pointer is 
* polygon, a vertex is set.
*- XyMinMax prev and now are the previous (extreme) and current min/max X and
* Y coordinates of this plot. When the pointer is zoom range, prev is assigned
* the value of now and then 'now' is assigned the (appropriately ordered) zoom
* box coordinates.
*- XyMinMax minRange is assigned the new value of 'now'.
* 
* Globals:   
*- bool justClosedMenu if true tells to not respond to the event.
*- enum timerState is assigned TIMER_NONE and the ZOOM_TIMER is killed.
*- int ptrFunction tells the current pointer function.
*- bool newFocus if true tells that the user has just moved the focus to this
* plot. If the first event pair is a click then it is taken to mean that the
* user is just trying to change the focus. The coordinates window is not toggled
* and if the pointer is polygon, the starting point assumed by OnLButtonDown is
* wiped out.
*- HWND wndDragLine tells the window in which a box or line is currently being
* drawn. This is assigned in OnLButtonDown and will be either 0 or this window
* because the mouse is captured at OnLButtonDown. For all pointer types except
* polygon it is assigned 0 here.
*
* ...................... notes ............................................... 
*- As with the rectangle and ellipse regions, a polygon gate segment is always 
* started in OnLButtonDown and finished here. However, the polygon uniquely 
* allows the user to set vertices without dragging simply by clicking. Here we 
* analyze the two events to determine how to draw the segment.
*.............................................................................*/
void CPlotWnd::OnLButtonUp(UINT nFlags, CPoint point) 
{
  RECT        frame;
  XyMinMax    xym;
  XyParm      xyp;
  bool        click;

  LPARAM  lParam = MAKELONG( point.x, point.y );

  if( justClosedMenu )
  {
    justClosedMenu = false;
    return;
  }
  KillTimer( TIMER_ZOOM ); // Any zooming in progress is killed on button up.
  timerState = TIMER_NONE;
  click = isPtNearPt( zoom[0], zoom[1], clickRange ); 
/* If click is true it means that the mouse button was depressed and released 
* without moving the pointer (beyond a little jitter). */

/* If click in a new focus window when the pointer is polygon, we need to reset
* the polygon drawing (which had been prepared for an initial drag line) in 
* order to avoid setting a point. Setting a point on the first click can be 
* confusing if the user is only trying to check coordinates in the plot. If 
* click in a plot that is not a new focus and the pointer is rectangle or 
* ellipse then toggle the coordinates window. The zoom pointers can't toggle the 
* coordinates window and the polyon can only do it on double-click, which only 
* seems like double on the first point; on all subsequent points it feels like 
* a single click. */
  if( ptrFunction == PTR_POLY && hasY() )
  {
    if( click && newFocus )
    {
      ReleaseCapture();
      wndDragLine = 0;
      wipeGateDraw();
      return;
    }
  }
  else if( click && !newFocus && ptrFunction >= PTR_RECT )
    setCoordWnd();

  if( gating == GATE_DRAWING )
  {
    wndDragLine = 0;
    ReleaseCapture();
    if( ptrFunction != PTR_POLY || !hasY() )
    {
      if( !click )
        gating = GATE_DRAWDONE;
/* WM_LBUTTONDOWN treats click without movement after drawing a rectangle or 
* ellipse as a request to toggle the coordinates window, which is effected at 
* that code. If we wanted this to mean to discard the gate region, here we 
* could call drawDragLine to eliminate the drag box and wipeGateDraw to clean 
* up. */
    }
    else
      polyLbuttonUp( lParam );
    return;
  }
  if( ptrFunction != PTR_ZOOMRANGE )
    return;

  if( wndDragLine == m_hWnd )
  {
    ReleaseCapture();
    wndDragLine = 0;
    GetClientRect( &frame ); // Always top,left = 0,0.
    if( !click )
    {
      nonIntegralX(); // Zooming invalidates Plot1:1
      prev = mDatRng; // Save current x/y-min/max as new previous.

/* Because the user may have started the range box at any corner, the begin
* and end point X and Y coordinates don't necessarily match min and max, so
* min and max are tested and assigned appropriately. */
      xyp = pointToParms( frame, PtToLparam( zoom[0]));
      xym.x.vmin = xyp.x;
      xym.y.vmin = xyp.y;
      xyp = pointToParms( frame, lParam );
      xym.x.vmax = xyp.x;
      xym.y.vmax = xyp.y;
      if( xym.x.vmin < xym.x.vmax )
        mDatRng.x = xym.x;
      else
      {   // Swap min and max
        mDatRng.x.vmin = xym.x.vmax; // min = max
        mDatRng.x.vmax = xym.x.vmin; // max = min
      }
      if( xym.y.vmin < xym.y.vmax )
        mDatRng.y = xym.y;
      else
      {   // Swap min and max
        mDatRng.y.vmin = xym.y.vmax; // min = max
        mDatRng.y.vmax = xym.y.vmin; // max = min
      }
      minRange = mDatRng;
      InvalidateRect( 0, TRUE );
    }
  }
  else
    wndDragLine = 0;
}

/*******************************************************************************
*                       RIGHT BUTTON AND MENU FUNCTIONS
* ..................... notes .................................................
*- Rbutton operations:
* WM_RBUTTONUP presents help for client area artifacts detected at WM_RBUTTONDOWN.
* WM_RBUTTONDOWN presents floating functional (not help) menus.
* WM_NCRBUTTONDOWN presents system menu help.
* WM_NCRBUTTONUP presents help for caption, min/max/close buttons, and sizing borders.
* WM_MENURBUTTONUP presents help for menu items.
* WM_CONTEXTMENU closes floating functional or help menu just to keep it from hanging
  around in some circustances.
*
*******************************************************************************/

/*******************************************************************************
* Function:     CPlotWnd::OnUnInitMenuPopup
* Description:  This event signals the closing of an action or help menu. This
* can occur without the user directly closing the menu, for example, by 
* focussing on another application. Therefore, here our matching menu class is
* closed.
* Returns:      0       
* Arguments:    
*- WPARAM wParam is the handle of the closing menu.
*- LPARAM lParam is ignored.
*
* Globals:
*- FloatMenu plotActMenu is the class associated with any action menu. Its 
* close class function is called.
*- HMENU floatHelpMenu is closed by calling closeMenu, which destroys the 
* menu and assigns 0 to the handle. Note that when the OS closes one of these
* types of menus, it does not free its resources. The application must do it.
*- bool justClosedMenu is assigned true.
*
* ...................... notes ...............................................   
* - The floating popup context menu automatically closes on selection and on 
* click in the parent window. But both left and right click in the window serve 
* functions, left toggling the coordinates window and right (re)opening the 
* menu. To enable clicking in the parent window to close the menu without side 
* effects, we take advantage of the fact that there will be no mouse movement 
* between the menu closing caused by mouse button clicking and the button down 
* message. At WM_UNINITMENUPOPUP justClosedWindow is assigned true. At every 
* WM_MOUSEMOVE it is assigned false. At either button down message, if this 
* variable is true, the click was used to close the menu. XP and ME differ 
* regarding right click in parent window. XP closes the menu while ME does 
* nothing. ME could be made to mimic XP by DestroyMenu at WM_CONTEXTMENU but 
* this would require changing the attached menu to a floating popup. 
*.............................................................................*/
LRESULT CPlotWnd::OnUnInitMenuPopup( WPARAM wParam, LPARAM lParam )
{
  if( (HMENU)wParam == plotActMenu.menu )
  {
    justClosedMenu = true;
    plotActMenu.close();
  }
  else
    closeMenu( &floatHelpMenu ); 
  return 0;
}
/*******************************************************************************
* Function:     CPlotWnd::OnRButtonDown
* Description:  Open the floating action menu for this plot unless the cursor is
* in the focus box, in which case tell OnRButtonDown to open the help menu for
* the focus box. 
* Returns:      Nothing
* Arguments:    
*- UINT nFlags is ignored
*- CPoint point, which tells the position of the cursor hot spot, is used for the
* upper-left corner of the floating popup menu.
* Class data:
*- int add if ADD_PREVRANGE tells OnPaint to draw the focus box, i.e. that focus
* box show is enabled and the user has just zoomed out and, therefore, the box is
* displayed.  
* 
* Globals: 
*- HWND wndDragLine, bool justClosedMenu, and timerState are all checked to 
* determine whether there is any process going on that shouldn't be interrupted.
*- FloatMenu plotActMenu is shared by all floating action menus.
*- enum rbuttonUp is assigned RBU_HELPFOCUSBOX if it is determined that the
* focus box help menu should be opened instead of an action menu.
*
* ...................... notes ...............................................
*- Plot windows don't normally have a context help menu because the right button
* opens their action menu and it can't open both action and help. If a focus box
* is displayed, its area is treated differently, with right click opening a help
* describing the focus box. OnRButtonDown has to detect this area to avoid 
* opening the action menu. RButtonUp is used in most other situations to open 
* help. For consistent feel (subliminal for most users) the request to open 
* focus box help is passed to OnRButtonDown by rbuttonUp = RBU_HELPFOCUSBOX. 
* Other areas, such as axis labels, could be given similar treatment but if too
* much of the plot becomes help instead of action the user may be frustrated
* trying to open the action menu.   
*.............................................................................*/
void CPlotWnd::OnRButtonDown(UINT nFlags, CPoint point) 
{
  LPARAM  lParam = MAKELONG( point.x, point.y );

  if( wndDragLine != 0 || justClosedMenu || timerState != TIMER_NONE )
    return; /* Don't interrupt ZOOM BOX being dragged out or auto-
* zooming in progress. This is for users like me that have an involuntary 
* twitch on the right button while holding down the left. */
  plotActMenu.setPos( lParam );
  if( add == ADD_PREVRANGE && 
    isPtInRect( plotActMenu.pos, focusBox ) )
  {
    rbuttonUp = RBU_HELPFOCUSBOX;
    return;
  }
  openContextMenu();
}
/*******************************************************************************
* Function:     CPlotWnd::OnRButtonUp
* Description:  Open a help menu for this plot. For most of the area of a plot
* window right button opens the action menu but if the cursor is located in the
* focus box, it opens the focus box help. OnRButtonDown determines whether this
* is the case. If it is the help menu is opened on button up for consistent
* feel with other help activation situations.
* Returns:      Nothing
* Arguments:    ignored 
* Class data:   HWND m_hWnd 
* Globals: 
*- enum rbuttonUp has been assigned RBU_HELPFOCUSBOX by OnRButtonDown if the 
* cursor is located in the focus box.
*- HMENU floatHelpMenu is shared by all floating help menus. Each menu is
* loaded for display and unloaded when closed.
*- char array helpFocusBox is the help message.
*- bool justClosedMenu is assigned false.
*.............................................................................*/
void CPlotWnd::OnRButtonUp(UINT nFlags, CPoint point) 
{
  if( rbuttonUp == RBU_HELPFOCUSBOX )
  {
    popStringMenu( &floatHelpMenu, m_hWnd, PCM_NEW,
      0, helpFocusBox );
    rbuttonUp = RBU_NOTHING;
  }
  justClosedMenu = false; /* Without this if the user closes the context menu
* (action or help) by right clicking in the plot, they won't be able to open
* the menu again without moving the cursor a little (OnMouseMove clears 
* justClosedMenu). This sounds like a minor problem but it prevents repeated
* click-open/close cycling, which is a common user activity. */
}

/*******************************************************************************
*                            COMMANDS AND KEYBOARD
*******************************************************************************/

/*******************************************************************************
*                           Plot Main Context Menus
* These are two similar menus, IDM_HISTOGRAM for histograms and IDM_SCATTER for
* scatter plots. The items near the top are identical for the two. These menus 
* pop up when the user right-clicks in a plot.
*******************************************************************************/
/*******************************************************************************
* Function:     CPlotWnd::OnCmdrPlotRange
* Description:  Command range of only two items, IDMI_PARMRANGE and 
* IDMI_DATARANGE, which select full parameter range or data range plotting. 
* Returns:      Nothing     
* Arguments:    UINT nID is IDMI_PARMRANGE or IDMI_DATARANGE.
*
* Class data: 
*- XyMinMax now and prev are the current and previous (extreme) X and Y min and 
* max data. prev is assigned the value of now and now is changed to either the
* full parameter range (X and Y) or the data range (i.e. max is the highest
* value in the data set).
*
* Globals:   
*- enum add is assigned ADD_NOTHING, telling OnPaint to simply paint the plot 
* without adding things like range box or centering the cursor. Not showing the 
* range box is especially important when the user switches between data and 
* full parameter range since the difference is usually enough to trigger the 
* box and in this context the box is useless and distracting.
*.............................................................................*/
void CPlotWnd::OnCmdrPlotRange( UINT nID )
{
  prev = mDatRng;
  mDatRng.x.vmin = 0;
  mDatRng.y.vmin = 0;
  if( nID == IDMI_PARMRANGE )
  {
    mDatRng.x.vmax = xParmMax;
    mDatRng.y.vmax = yParmMax;
  }
  else // IDMI_DATARANGE
  {
    mDatRng.x.vmax = xDataMax;
    mDatRng.y.vmax = yDataMax;
  }
  add = ADD_NOTHING;
  onRangeOut();
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdPrevRange
* Description:  Plot menu item IDMI_PREVPLOTRANGE to repaint the plot
* at the previous subrange.
* Class data: XyMinMax now and prev are swapped to display the previous and 
* support toggling between the two range views.
*.............................................................................*/
void CPlotWnd::OnCmdPrevRange()
{
  XyMinMax xym = mDatRng;
  mDatRng = prev;
  prev = xym;
  onRangeOut();
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdShowFocus
* Description:  Plot menu item IDMI_SHOWFOCUS to show or hide the focus box.
* Class data: enum add is ADD_PREVRANGE to tell OnPaint to show the focus box.
* We use this to tell how to toggle, from on to off and vice versa.
* Globals: 
*- BOOL flashFocusOnRange is a configuration item. If TRUE then flash 
* crosshairs in the center of the focus box when it changes from off to on.
*- bool doFlash is assigned true to tell showFocusBox to flash crosshairs.
*.............................................................................*/
void CPlotWnd::OnCmdShowFocus()
{
  if( add == ADD_PREVRANGE )
  {
    add = ADD_NOTHING;
    repaintWindow( m_hWnd );
  }
  else
  {
    add = ADD_PREVRANGE;
    if( flashFocusOnRange )
      doFlash = true;
    showFocusBox( 0 );
  }
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdDisconnect
* Description:  Plot menu item IDMI_DISCONNECT toggles the plot's connection to
* the gallery. When the plot is "minimized" into the gallery it always moves 
* and resizes with the gallery. However, when it is unminimized out of the 
* gallery it will move with the gallery if connected or not move with it if
* disconnected. In either case, it doesn't resize with the gallery and the user
* can move it independently of gallery.
* Class data: BOOL disconnect is toggled between TRUE and FALSE.
*.............................................................................*/
void CPlotWnd::OnCmdDisconnect()
{
  disconnect ^= TRUE;
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdPlotFmt
* Description:  Plot menu item IDMI_PLOTFORMAT opens the plot format dialog 
* passing this plot window for any plot-specific attributes.
*.............................................................................*/
void CPlotWnd::OnCmdPlotFmt()
{
  doPlotFmtDlg( this );
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdPlotSquare
* Description:  Plot menu item IDMI_SQUARE tells to make the plot window square
* or not. 
* Class data:   
*- BOOL square is toggled between TRUE and FALSE.
*- enum pos is POS_DEF if the plot is in the gallery position, in which case on
* change to non-square it is stretched to the gallery size (and aspect ratio).
*.............................................................................*/
void CPlotWnd::OnCmdPlotSquare()
{
  nonIntegralX();
  square ^= TRUE;
  if( square )
    makeSquare(); /* Make it square regardless of whether it is maximized,
                     in the gallery, or free. */
  else // Change to not square depends on the window's location.
  {
    if( IsZoomed() ) // Maximized. pos may be POS_DEF so test IsZoomed first.
    {
/* If the plot is changed from square to not square while maximized the OS 
* doesn't change its position to fill the screen even if we call ShowWindow
* SW_MAXIMIZE. However, if this is preceded by a different show state then
* the maximize will be done. The pre-state that produces the least flicker 
* is SW_HIDE. */
      ShowWindow( SW_HIDE ); 
      ShowWindow( SW_MAXIMIZE );
    }
    else if( pos == POS_DEF ) // In the gallery
      setDefPos();
        /* No immediate change if the window is free. It's just that the user
        can now stretch it into non-square forms. */
  }
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdXlog
* Description:  Plot menu item IDMI_XLOG toggles the plot's X axis between log
* and linear. Both histograms and scatter plots have this item.
* Class data:   BOOL xLog is toggled between TRUE and FALSE.
*.............................................................................*/
void CPlotWnd::OnCmdXlog()
{
  xLog ^= TRUE;
  nonIntegralX();
  repaintWindow( m_hWnd );
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdYlog
* Description:  Scatter plot menu item IDMI_YLOG toggles the plot's Y axis 
* between log and linear.
* Class data:   BOOL yLog is toggled between TRUE and FALSE.
*.............................................................................*/
void CPlotWnd::OnCmdYlog()
{
  yLog ^= TRUE;
  repaintWindow( m_hWnd );
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdSwapXy
* Description:  Scatter menu item IDMI_SWAPXY. The axes are swapped by calling 
* swapXy.
*.............................................................................*/
void CPlotWnd::OnCmdSwapXy()
{
  swapXy(); 
  repaintWindow( m_hWnd );
}

/*******************************************************************************
*                               Plot11 Submenu
* This is an extensible submenu of the histogram action menu. 
*******************************************************************************/
/*******************************************************************************
* Function:     CPlotWnd::OnCmdrPlot11
* Description:  Histogram menu command range IDMI_FIRSTPLOT11 to IDMI_LASTPLOT11.
* Each of these items selects a different specific integral ratio of data range
* to pixel range. Since the ratio determines the width of the plot, the number
* of valid ratios is limited by the data range and screen size and, therefore,
* the number of items in the menu varies. The first item is always 
* IDMI_FIRSTPLOT11. 
* Returns:      Nothing   
* Arguments:    UINT nID is from IDMI_FIRSTPLOT11 to IDMI_LASTPLOT11
* Class data:   bool plot11 is assigned true. 
* ...................... notes ............................................... 
*- The available integral plot selections are calculated by OnInitMenuPopup as 
* it fills in the plot11Menu menu. It stores the divisor in plotRanges. When
* the user selects one of these items OnCmdrPlot11 is called. Here we call
* selectIntegralPlot, which consults plotRanges to determine how large to make
* the plot window.
*.............................................................................*/
void CPlotWnd::OnCmdrPlot11( UINT nID )
{
  selectIntegralPlot( nID - IDMI_FIRSTPLOT11 );
  plot11 = true; /* Tell WM_SIZE to leave integral X alone this 
* one time. Any direct user size change invalidates integral X. */
}

/*******************************************************************************
*                               Pointer Submenu
* These are two nearly identical submenus of the plot main context menus. In 
* these the user selects pointer type and whether to show the coordinates 
* window.
*******************************************************************************/
/*******************************************************************************
* Function:     CPlotWnd::OnCmdrPtr
* Description:  Command range pointer type selection. These are in the Pointer 
* submenu of menus IDM_HISTOGRAM and IDM_SCATTER. Menu item IDs IDMI_ZOOMOUT, 
* IDMI_ZOOMIN, IDMI_ZOOMRANGE, IDMI_GATERECT, IDMI_GATEELLIPSE, and 
* IDMI_GATEPOLY select the corresponding pointers PTR_ZOOMOUT, PTR_ZOOMIN, 
* PTR_ZOOMRANGE, PTR_RECT, PTR_ELLIPSE, and PTR_POLY.
* Returns:      Nothing     
* Arguments:    UINT nID is the ID of the select item.
* ...................... notes ...............................................   
*- The menu item IDs must be contiguous and the pointer type enums must be 
* contiguous and both sets need to be in corresponding order but they can't 
* have the same values because the pointer enum is used as a 0-based index.
*.............................................................................*/
void CPlotWnd::OnCmdrPtr( UINT nID )
{
  setPointerFunction( PTR_ZOOMOUT + nID - IDMI_ZOOMOUT );
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdCoord
* Description:  Toggle the coordinates window on/off. 
* ...................... notes ...............................................   
*- This just calls setCoordWnd. The only reason that setCoordWnd isn't the 
* direct command function is that has several command variations, as indicated 
* by the mode argument.
*.............................................................................*/
void CPlotWnd::OnCmdCoord()
{
  setCoordWnd( SCW_TOGGLE );
}

/*******************************************************************************
*                             Gate Assignment Menu
* This menu, IDM_GATEASSIGN, pops up on right-click in a plot instead of the 
* main context menu when the user has finished drawing a gating area.
*******************************************************************************/
/*******************************************************************************
* Function:     CPlotWnd::OnCmdrGateSelect
* Description:  Selection from the extensible application-drawn menu of gate 
* types. This is a submenu of the gate assignment menu. The selected gate type 
* is assigned to all events within the gating region drawn by the user.  
* Returns:      Nothing
* Arguments:    UINT nID is from IDMI_FIRSTGATE to IDMI_LASSTGATE. The number of
* items in the menu varies from 1 to 25.
*
* Class data:   
*- enum gating is assigned GATE_CLASSIFY if 'this' is a scatter plot, telling
* the next OnPaint to find and tag the events in the gate region.
*
* Globals:  
*- int gateIdx is assigned the 0-based gate selection index.
*- bool addToRegion is assigned true or false to tell OnPaint, when it serves
* as the gate region selector, whether to add to or replace any matching region.
*- CPlotWnd *gateDrawPlot is assigned 0, indicating that, after this, there is
* no gate draw in progress.
*- HWND wndFrame is the main frame. If 'this' is a histogram, wndFrame is sent 
* a UWM_PAINTPLOTS message. This is not necessary with scatter plots because we 
* set up conditions here to tell OnPaint to identify the events in the gate 
* region and, after this, OnPaint automatically redraws all plots.
*
* ...................... notes ............................................... 
*- The gate list menu is created when OpenContextMenu calls fillGateMenu. The 
* menu items are drawn by OnMeasureItem and OnDrawItem.  
*- For rectangle and ellipse, the UL-BR diagonal is described by zoom[0] and 
* zoom[1]. A polygon is described by polyIdx points in the polygon array. 
*- OnPaint does the job of finding and tagging the events in the gate region 
* that the user has just drawn in a scatter plot. It is used for this purpose 
* because it has most of the code needed for this in order to paint the plot 
* and color the various regions. The much simpler assignHistGate function is 
* called to do the same thing for a gating region in a histogram.
*.............................................................................*/
void CPlotWnd::OnCmdrGateSelect( UINT nID )
{
  gateIdx = nID - IDMI_FIRSTGATE;
  addToRegion = false;
  switch( CGateList::createGate( gateIdx, dset ) )
  {
  case AGA_FAIL:
    showConstMessage( m_hWnd, MB_ICONERROR+MB_OK, 
      "Unable to add gate assignment" );
    return;
  case AGA_EXISTS:
    switch( showConstMessage( m_hWnd, MB_ICONQUESTION+MB_YESNOCANCEL,
      "Do you want to add to the existing region?" ) )
    {
    case IDYES:
      addToRegion = true;
      break;
    case IDCANCEL:
      return;
    }
    break;
  }

  if( !dat.evClass.ready() )
    prepEvClass( dset );
  if( !hasY() )
  {
    assignHistGate( gateIdx, addToRegion );
    ::PostMessage( wndFrame, UWM_PAINTPLOTS, 0, 0 );
    wipeGateDraw();
    gateDrawPlot = 0;
  }
  else
  {
    gateDrawPlot = 0;
    gating = GATE_CLASSIFY;
    OnPaint(); // "Repaint" the plot in which this gate 
// region was drawn. Because gating = GATE_CLASSIFY, CPlotWnd::OnPaint 
// (plotPaint.cpp) will determine which events are in this region.
  }
  gateList->RedrawWindow();
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdDefGates
* Description:  Gate assignment menu item IDMI_DEFGATES invokes the gate define
* dialog. Thus, after drawing a region, if the user doesn't see the gate type
* they want in the gate list menu, they can go to this dialog, define a new
* gate type or load a different scheme, and return to assigning a gate type to
* the region without having to redraw the region.
* ...................... notes ...............................................   
*- This function exists only because defineGateTypes is deliberately not a 
* CPlotWnd class function. The gate scheme is independent of plots.
*.............................................................................*/
void CPlotWnd::OnCmdDefGates()
{
  defineGateTypes();
}
/*******************************************************************************
* Function:     CPlotWnd::OnCmdGateCancel
* Description:  This is the Cancel Gate Region item in the IDM_GATEASSIGN menu,
* which is the context action menu after the user has drawn a gate region. All
* we do is reset the gate draw process. 
* ...................... notes ...............................................   
*- wipeGateDraw could be the direct command function except that it is 
* deliberately not a class function so that it can be called from places that
* don't know in which, if any, plot a gate region is being drawn.
*.............................................................................*/
void CPlotWnd::OnCmdGateCancel()
{
  wipeGateDraw();
}

/*******************************************************************************
* Function:     CPlotWnd::OnKeyDown
* Description:  Key press when a plot has the focus. ALT is intercepted and 
* passed to the main frame by OnSysCommand so we see only Ctl, Shift, and ASCII
* keys here. Most plot window key functions are hot keys for plot menu items. 
* Since we ignore Shift and Ctl, nearly all of these keys work in all forms. 
* Only Ctl+C doesn't work as the hot key C because it is special to the OS. Non-
* hot keys are F1, which opens the context menu, and the arrow keys, which move 
* the cursor. Shift does work with the arrow keys, doubling the rate of 
* movement.
* Returns:      Nothing     
* Arguments:    UINT nChar is the ASCII key
*.............................................................................*/
void CPlotWnd::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
  POINT   pt;
  int     idx;

  bool    shiftKey = Depressed( VK_SHIFT );
  bool    ctlKey = Depressed( VK_CONTROL );

  switch( nChar )
  {
  case 'O' :
    setPointerFunction( PTR_ZOOMOUT );
    break;
  case 'I' :
    setPointerFunction( PTR_ZOOMIN );
    break;
  case 'Z' :
    setPointerFunction( PTR_ZOOMRANGE );
    break;
  case 'R' :
    setPointerFunction( PTR_RECT );
    break;
  case 'E' :
    setPointerFunction( PTR_ELLIPSE );
    break;
  case 'P' :
    if( !ctlKey )
      setPointerFunction( PTR_POLY );
    else
    {
      if( shiftKey )
        loadDataset( LOAD_DATASET_PREV );
      else
        loadNextFile( PREVIOUS );
    }
    break;
  case 'N':
        // Doesn't need Ctl for file or dataset because these are all N is used for.
    if( shiftKey )
      loadDataset( LOAD_DATASET_NEXT );
    else
      loadNextFile( NEXT );
    break; 
  case 'C' :
    setCoordWnd( SCW_TOGGLE );
    break;
  case 'F' :  // Full Parameter Range.
    OnCmdrPlotRange( IDMI_PARMRANGE );
    break;
  case 'D' :  // Full Data Range.
    OnCmdrPlotRange( IDMI_DATARANGE );
    break;
  case 'S' :  // Previous Sub-Range.
    OnCmdPrevRange();
    break;
  case 'B' :
    OnCmdShowFocus(); // Toggle show/hide
    break;

  case VK_F1 :
    mruPlotWnd->openContextMenu( XyToLparam( 20, 20 ));
    break;
  case VK_LEFT :
  case VK_RIGHT :
  case VK_UP :
  case VK_DOWN :
    idx = shiftKey ? 20 : 1;
    GetCursorPos( &pt );
    SetCursorPos(
      nChar == VK_LEFT ?  pt.x - idx : 
    nChar == VK_RIGHT ? pt.x + idx : pt.x,
      nChar == VK_UP ?    pt.y - idx :
    nChar == VK_DOWN ?  pt.y + idx : pt.y );
    break;
  }
}


MFC C++ Header

/****************************************************************************
* C++ Header File:  PlotWnd.h   
* Description: Dataman list data plotting definitions
****************************************************************************/
#ifndef PLOTWND_H
#define PLOTWND_H

#define NO_PARM MAX_PARM + 1

class XySel // X and Y parameter selector, i.e. pair of indices.
{
public:
  USHORT   x;
  USHORT   y;
  bool operator==( XySel xys )
  {
    return xys.x == x && xys.y == y;
  }
  bool    hasY( void )
  {
    return y <= MAX_PARM;
  }
  void    noY( void )
  {
    y = NO_PARM;
  }
};

class PlotArea;

class XyParm // X and Y parameter values, i.e. data.
{
public:
  USHORT x;
  USHORT y;
};

class MinMax
{
public:
  USHORT  vmin;
  USHORT  vmax;
  bool operator == ( MinMax& m )
  {
    return vmin = m.vmin && vmax == m.vmax;
  }
  bool operator != ( MinMax& m )
  {
    return vmin != m.vmin || vmax != m.vmax;
  }
  bool operator > ( MinMax& m ) 
  {
    return vmin <= m.vmin && vmax >= m.vmax && *this != m;
  }
  bool zoomIn( USHORT parm, USHORT maxParm );
  bool zoomOut( USHORT maxParm );
};

class XyMinMax
{
public:
  MinMax  x;
  MinMax  y;
  bool operator == ( XyMinMax& xym );
  bool operator != ( XyMinMax& xym );
  bool operator > ( XyMinMax& xym );
};

enum
{
  PWE_NOMATCH = 1, PWE_MEMFAIL
}; // PlotWndErr.err
typedef struct
{
  int err;
} PlotWndErr; // Thrown by CPlotWnd constructor
typedef struct
{
  int begin; int end;
} LabelExtent;

class CPlotWnd : public CWndList 
{
// Permanent attributes of this plot.
public:
  ListDat dat;    // This must be first or change zeroing in constructor.
  DataSet *dset;      // Source for master copy of EvClass.
private:
/* .............. private data ................*/
  PlotArea* area;
  char*   xShortName;
  char*   xLongName;
  char*   yShortName;
  char*   yLongName;
  UINT    xParmMax;   // Max X parameter (FCS Range - 1)
  UINT    yParmMax;   // Max Y parameter (FCS Range - 1)
  UINT    xDataMax;   // Max X data value.
  UINT    yDataMax;   // Max Y data value.
  XySel   xysel;
    // Selectable attributes of this plot.
  BOOL    xLog;       // Plot X log instead of linear.
  BOOL    yLog;       // Plot Y log instead of linear.
  COLORREF focusBoxColor;
    // Working variables
  LabelExtent mLabelExtentX;
  LabelExtent mLabelExtentY;
  WindowPos defPos; // Position when created or group reposition. Minimize restores.
  RECT     preMinRect; // Captured before minimize for toggle.
  XyMinMax mDatRng;   // Current X and Y min and max data in this plot.   
  XyMinMax prev;      // Previous X/Y-min/max used for Previous Range.
  XyMinMax minRange;  // Minimum X/Y-min/max used for zoom box display.
  XyMinMax maxRange;  // Maximum X/Y-min/max used for zoom box display.
  double  xToParm; // Ratio of parm range to pixel X range used for range tip.
  int     xDiv;       // For integral histogram mapping.
  RECT    focusBox;   // Recorded for flash focus.
  POINT   zoom[2];    // Zoom box and gate region corner points.
  int     add;        // ADD_NOTHING, ADD_PREVRANGE, ADD_MOVECURSOR 
/* ............ private functions .................*/
  void    selectIntegralPlot( int rangeIdx );
  enum
  {
    ZIO_TIMER = -1, ZIO_NEWFOCUS = -2
  };
  void    zoomInOut( LPARAM lParam );
  void    onRangeOut( void );
    /* draw.cpp */
  void    flashFocus( HDC inHdc );
  void    flashPoint( POINT pt );
  void    killFlash();
  void    showFocusBox( HDC inHdc );
  void    drawDragLine();
  void    drawGateRegion( HDC hdc );
  enum
  {
    SPP_FAIL = -10, SPP_DONE
  }; // setPolygonPt returns.
  LPARAM  setPolygonPt( LPARAM lParam, int msg );
  void    polyLbuttonUp( LPARAM lp );
  void    closePoly();
    /* eventGate.cpp */
  bool    initEvClass( void );
  bool    checkEvClass( void ) 
  {
    return dat.evClass.ready() ? true : initEvClass();
  }
    /* plot.cpp */
  bool    makeSquare();
  void    swapXy();
    /* pointer.cpp */
  void    setPlotCursor();
  void    moveCoordWnd( LPARAM lParam );
  void    togglePointer();
    /* pointparm.cpp */
  XyParm  pointToParms( RECT frame, LPARAM lParam );
    /* plotgate.cpp */
  void    assignHistGate( int gateIdx, bool addToRegion );
    /* plotfmt.cpp */
  void    labelAxisX( HDC hdc, int xExtent, int yExtent );
  void    labelAxisY( HDC hdc, int yPos );

public:
/* .......... public data ................*/
  BOOL    square;     // Keep this window square.
  BOOL    disconnect; // Don't track gallery if not in it.
  int     pos;        //  POS_DEF, POS_MOVED
  int     gating; // GATE_NOTHING, GATE_DRAWREADY, GATE_DRAWING, GATE_DRAWDONE

/* .......... public functions ..............*/
  CPlotWnd( PlotArea *area, WindowPos pos, char *title, 
    DataSet *ds, XySel xys ); // plot.cpp
  ~CPlotWnd(); 
  CPlotWnd* getNext( void )
  {
    return(CPlotWnd*)next;
  }
  bool    hasY( void )
  {
    return xysel.hasY();
  }
  void    noY( void )
  {
    xysel.noY();
  }
  bool    mIsXlog( void )
  {
    return xLog == TRUE;
  } // This plot or all.
  bool    mIsYlog( void )
  {
    return yLog == TRUE;
  } // This plot or all.
  bool    isSquare( void )
  {
    return square == TRUE;
  } // This plot or all.
  bool    isIntegralX( void )
  {
    return xDiv > 0;
  }
  void    nonIntegralX( void )
  {
    xDiv = -1;
  }
  USHORT  parmToIntegralX( USHORT xParm )
  {
    return xParm / xDiv;
  }
  USHORT  integralXtoParm( int x )
  {
    return x * xDiv;
  }
  void    capturePreMinPos()
  {
    GetWindowRect( &preMinRect );
  }
  void    setDefPos() 
  {
    SetWindowPos( 0, defPos.x, defPos.y, 
      defPos.width, defPos.height, SWP_NOZORDER );
  } 
  void    setPreMinPos() 
  {
    SetWindowPos( 0, preMinRect.left, 
      preMinRect.top, preMinRect.right - preMinRect.left,
      preMinRect.bottom - preMinRect.top, SWP_NOZORDER ); 
  }
  void    assignDefPos( WindowPos& pos )
  {
    defPos = pos;
  }
  void    assignPreMinPos( WindowPos& pos ) 
  {
    preMinRect.left = pos.x;
    preMinRect.top = pos.y;
    preMinRect.right = pos.x + pos.width;
    preMinRect.bottom = pos.y + pos.height; 
  }
  void    displaceDefPos( int xdis, int ydis ) 
  {
    defPos.x += xdis; defPos.y += ydis;
  }
  void    positionDefPos( WindowPos *wp )
  {
    defPos = *wp;
  }

    /* plotmenu.cpp */
  void    openContextMenu( LPARAM lp = PFM_CLIENT );

    /* hist.cpp */
  USHORT* makeHist( int *inRangeCnt, USHORT *maxBinCnt, int binCnt, 
    USHORT *xpts );

  friend BOOL CALLBACK PlotSelDlgProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam );
  friend LRESULT CALLBACK PlotProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
  friend bool doPlotFmtDlg( CPlotWnd *pw );

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CPlotWnd)
protected:
  virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    //}}AFX_VIRTUAL

    // Generated message map functions
protected:
    //{{AFX_MSG(CPlotWnd)
  afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
  afx_msg void OnSetFocus(CWnd* pOldWnd);
  afx_msg void OnCaptureChanged(CWnd *pWnd);
  afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
  afx_msg void OnWindowPosChanging(WINDOWPOS FAR* lpwndpos);
  afx_msg void OnSize(UINT nType, int cx, int cy);
  afx_msg void OnPaint(); 
  afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
  afx_msg void OnNcMouseMove(UINT nHitTest, CPoint point);
  afx_msg void OnMouseMove(UINT nFlags, CPoint point);
  afx_msg void OnTimer(UINT nIDEvent);
  afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
  afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
  afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
  afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
  afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
  afx_msg void OnNcRButtonDown(UINT nHitTest, CPoint point);
  afx_msg void OnNcRButtonUp(UINT nHitTest, CPoint point);
  afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);
  afx_msg void OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu);
  afx_msg void OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct);
  afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);
    //}}AFX_MSG

  LRESULT OnSysKeyDown( WPARAM, LPARAM );
  LRESULT OnSysKeyUp( WPARAM, LPARAM );
  LRESULT OnExitSizeMove( WPARAM, LPARAM );
  LRESULT OnUnInitMenuPopup( WPARAM, LPARAM );
  LRESULT OnMenuRbuttonUp( WPARAM, LPARAM );
  void OnCmdPrevRange();
  void OnCmdShowFocus();
  void OnCmdPlotFmt();
  void OnCmdPlotSquare();
  void OnCmdDisconnect();
  void OnCmdXlog();
  void OnCmdYlog();
  void OnCmdSwapXy();
  void OnCmdCoord();
  void OnCmdDefGates();
  void OnCmdGateCancel();
  void OnCmdrPlotRange( UINT nID );
  void OnCmdrPlot11( UINT nID );
  void OnCmdrPtr( UINT nID );
  void OnCmdrGateSelect( UINT nID );
public:
  afx_msg void OnClose();

  DECLARE_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.    
}

;
// Access functions for local copy of xDiv for speedup.
inline bool    isIntegralX( int xDiv )
{
  return xDiv > 0;
}
inline void    nonIntegralX( int &xDiv )
{
  xDiv = -1;
}
inline USHORT  parmToIntegralX( int xDiv, USHORT xParm )
{
  return xParm / xDiv;
}

class PlotArea
{
public:
  PlotArea*   next;
  int         areaId;
  int         plotCnt;
  CPlotWnd*   plots;
  CPlotWnd*   lastPlot;
  DataSet*    dataSet;
  FcsFile*    fcsFile;
  void        update( int mruPlotIdx = -1 );
  CPlotWnd*   remake( XySel* list, int cnt );
  CPlotWnd*   init( FcsFile* ff );
};

#define BASE 2.718282  // For log. Use 10.000 for log10.
#define logOf(I) USHORT( log( (double)I ) * 1000.0 )

// #define logOf(I) USHORT( log10( (double)I ) * 1000.0 );

/* ................. plot.cpp ........................*/
void    squareAllPlots( BOOL setSquare );
void    paintPlots( CPlotWnd* pw );
void    paintAllPlots( void );
void    hidePlots( CPlotWnd* pw );
void    showPlots( CPlotWnd* pw );
void    displacePlotWnds( CPlotWnd* pw, short xdis, short ydis );
void    positionCPlotWnds( CPlotWnd* pw, WindowPos* positions, int cnt );
void    delPlots( CPlotWnd** pwRoot );
bool    focusMruPlot( void );
void    wipeGateDraw( void );

enum
{
  WCS_NONE, WCS_PARENTDESTROYED, WCS_EXISTS
}; // wndCoordStatus

#ifdef PLOTWND_CPP
CPlotWnd*   mruPlotWnd = 0;
PlotArea    plotArea = { 0, 0, 0, 0, 0, 0, 0};
PlotArea*   areaList = &plotArea;
bool        updatingPlots = false;
int         wndCoordStatus = WCS_NONE;
HWND        wndCoord = 0;       // plot coordinates window
CWnd*       cWndView;
#else
extern CPlotWnd*    mruPlotWnd;
extern PlotArea     plotArea;
extern PlotArea*    areaList;
extern bool         updatingPlots;
extern int          wndCoordStatus;
extern HWND         wndCoord;
extern CWnd*        cWndView;
#endif


#ifdef LAYOUT_CPP
int     exitMruPlotIdx = 0;
int     exitIsCoordWnd;
#else
extern int  exitMruPlotIdx;
extern int  exitIsCoordWnd;
#endif


/* .................................. pointer.cpp ...................*/
enum
{
  SCW_TOGGLE, SCW_ON, SCW_OFF
}; 
void    setCoordWnd( int mode = SCW_TOGGLE );

#endif  // not defined PLOTWND_H




Prev   Next   Site Map   Home   Top   Valid HTML   Valid CSS