Click here to Skip to main content
15,891,033 members
Articles / Web Development / HTML

Instrument Snapshot: How to Acquire and Render Screen-shots from Older Test Equipment

Rate me:
Please Sign up or sign in to vote.
4.89/5 (13 votes)
16 Dec 2016CPOL10 min read 64.4K   1.9K   18   23
An HPGL renderer and demo application for use in data acquisition

Introduction

I have more than once had the occasion to work with older model test equipment. Sometimes, engineers and technicians prefer the response of an older analog model over that of a newer piece of gear. Perhaps an organization has an investment in a wide range of legacy equipment and a limited budget for new. Much of that older equipment was designed to export graphical screen data to a pen plotter. Data plots would be printed and added to engineering notebooks along with design notes and computations.

Background

Getting screen capture data in a format that is readily incorporated into electronic documentation is fairly easy with new test equipment but can be a challenge with the older models. That was the situation I found myself in years ago when I was employed in the task of documenting the performance of various filters. I wanted to be able to to easily capture and save screen-shots from a Network Analyzer, specifically an HP8751A, but I couldn't find a software package to accomplish the task to my satisfaction. Thus, I set out to write something that would extract a screen-shot from this device with only a single button click.

Figure1

Figure 1: HP8751A Network Analyzer

Acquiring a screen-shot from an older piece of test equipment is usually a two step process: first request and receive the data, and second render the data into an image. Both of these objectives are more easily stated than accomplished but with some time and patience I figured out how to get the HP8751A to serve up the goods. Here's how I did it.

The Tools of the Trade

In order to work with instrumentation in an engineering capacity, it's good to have VISA. No, not the aesthetically pleasing piece of plastic[^], or the document stamp. VISA or Virtual Instrument Software Architecture[^] is a convenient standardized abstraction layer between your application and the many hardware interfaces and instrumentation buses in use today. Various test development products and environments such a NI LabVIEW (TM) are built on top of VISA.

VISA can usually be obtained for free from the vendor that you purchased equipment or an interface card from. I have used both Agilent (now Keysight) and National Instruments VISA drivers over the years. The installations include various development tools, but the following are the ones I find most useful.

  • Agilent IO Monitor and/or NI SPY (now called NIO Trace [^]) - check out The Spy Among Us -- Debugging I/O[^] for a nice write-up on how you can use these tools.
  • NI Visa Interactive Control and/or Agilent Interactive IO - These tools provide a way to send commands to instruments "on the fly" and are helpful for testing command sequences or seeing how an instrument will respond to a string of commands.

Playing Around

There are two ways to get a screen shot out of the HP8751A, one of them is to plot to file; If you do this; the Hewlett Packard Graphics Language (HPGL) defining the screen is written to a diskette that has been inserted into the drive, it may then be transfered it to a workstation via sneakernet [^]. The second way is to plot to a plotter connected to the General Purpose Interface Bus (GPIB) [^]. Once the Plotter draws the screen on to a sheet of paper, the image may be transferred to the workstation using the wooden table method [^]. The plot to file / sneakernet method was OK in a pinch but I wanted something more direct. Ditto for the second method (not to mention that I didn't even have a plotter in the lab.)

Perhaps there was a third way. "What if I could plot the screen to a workstation instead of a plotter?" I thought. I tested the idea by configuring the instrument to plot to the computer's GPIB address, fired up IO Monitor and pressed the PLOT button. All I got on the monitor was "OS" or something to that effect. I tried some different things without luck and then, with a little searching, found that "OS" was a request for output status. My instrument was attempting to handshake with a plotter and it wasn't going to divulge any HPGL unless it knew that there was a plotter connected and ready to receive the data! After looking around some more, I found that someone had already written a plotter emulator [^] and made it freely available.

I tried the plotter emulator and succeeded in retrieving an instrument initiated plot from my device. I then wanted to be able to initiate the plot from my workstation which I couldn't do with this application so I studied the code and took note of how the author treated the hand shake sequence in order to fool the HP8751A into thinking there was a genuine plotter on the other end. I experimented by firing up IO Monitor and sending the appropriate responses to status requests from the instrument and viola, I started to get chunks of HPGL data!

The result of all of this playing around was that I determined a sequence of events that needed to take place in order to make a host-initiated plot request from this instrument.

  • Open a Virtual Instrument Interface session for my virtual plotter (the workstation).
  • Configure the HP8751A to be the bus controller.
  • Set my virtual plotter's address to the one where the instrument expected to see a plotter.
  • Send the "PLOT" command.
  • Pass control of the bus from the workstation to the HP8751A.
  • Respond to status requests while buffering HPGL data.
  • On completion, reset the bus and return the instrument to default operation.

After coding it up and testing the process several times, I was confident that I had accomplished Step 1 of the two step process for acquiring a screen shot from an older piece of test equipment. Now I needed to work on step 2 rendering HPGL to an image.

The author of the plotter emulator, Mr. Miles, used a special library that he developed to render graphics. I could have gone this route but I had already found an HPGL parser (written in VB6) that was included in a code example in a sub folder of the Agilent VISA installation directory on my workstation. I liked how that the example used Windows GDI to draw the HPGL objects and, wanting to learn GDI, I translated this module into C and enhanced it to support a wider range of HPGL than it initially did.

Soon I had a small application that I could use to acquire and render a screen shot from the HP8751A Network Analyzer, but there were many other models of Network and Spectrum analyzer in the Lab and so whenever I had some spare time I played with them and figured out how to get plots out of each one.

Instrument Snap Shot Rises Again

Figure2

Figure 2: TDS420 Oscilloscope

Recently, the engineering group where I now work acquired two nice Tektronix TDS 420, 4 channel oscilloscopes. I wanted to be able to capture the screen of this model but the off-the-shelf software we have for that purpose wouldn't work with this older scope. So I dusted off my old snap shot and modified it to work with the TDS 420. While reviewing the code base, I realized I could improve upon the original design significantly with the application of code encapsulation patterns. I also wanted to give the user access to all of the plot rendering parameters - something the original application didn't do.

Figure3

Figure 3: Instrument Snapshot Demo

This demo is the fruit of that redesign effort. It does not support very many instruments, since I have only had access to so many pieces of gear. It is however, fairly easy to add code support for other fine old pieces of test equipment. It also should be fairly easy to take parts of this code and reuse them in other projects.

Under the Hood

The demo consists of several design elements:

  • A dialog and associated code to facilitate selection of, and communication with, a piece of test equipment.
  • A plot rendering module to convert instrument HPGL data to an image.
  • My Property Grid [^] to facilitate user access to the plot rendering module parameters.
  • A module to make reading and writing text based configuration or INI files a snap.
  • A GDI+ wrapper that provides a very simple C friendly interface to the GDI+ file format image encoders; in order that bitmaps might be saved to several popular image formats.
  • Various other goodies to play with.

It would be beyond the scope of this article to go into detail on each one of these elements; however, for the inquiring coder, here are some previews of what lies inside.

Test Equipment Communication

Here is an example of how I declare a virtual instrument to represent a communication point with an actual piece of test equipment:

C++
static VINSTRUMENT gInstrument; ///< pointer to virtual instrument

And connecting to that piece of equipment:

C++
// Set the virtual instrument properties for this connection
LPSTR supportedModels[] = { "TDS 420", "8711A",
"8751A", "8753C", "8753D", "4396A", NULL };

gInstrument.supportedModels = supportedModels;
gInstrument.supportedModelsCount = NELEMS(supportedModels) + 1;
gInstrument.instrumentClass = "Supported Instrument";

// Show browse dialog object to get instrument address and vi.
DlgConnect_Show(ghInstance, hwnd, &gInstrument);

// Verify connection
if (gInstrument.isConnected)
{
     //Do something

Then communicating with that piece of equipment:

C++
//Get current settings
WriteLine(gInstrument.vi, "HARDC?");
LPSTR strRes = Read(gInstrument.vi, MAX_PATH);
if (!IsEmptyString(strRes))
{
     //Do something

Converting HPGL To An Image

The following demonstrates the basic operation of the rendering engine using just the default properties. Here I declare a Plotter object to hold configuration settings, HPGL data and a graphical image:

C++
static LPPLOTTER glpScreenPlotter; ///< pointer to plotter object

Now I initialize it:

C++
glpScreenPlotter = New_Plotter();

Convert HPGL to an Image:

C++
// Assign the the desired output size
glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

//Set the data source to the buffer of HPGL commands  acquired from the instrument
glpScreenPlotter->plotCommandData = PlotData;

//Plot to image
HBITMAP hbmp = Plotter_Plot(glpScreenPlotter);

Note: It is unnecessary to delete the HBITMAP returned from Plotter_Plot(); it's resources are managed each time that the method is called and it is destroyed with a call to Plotter_Destroy() as I do in the demo's WM_CLOSE handler.

C++
static VOID Main_OnClose(HWND hwnd)
{
    Plotter_Destroy(glpScreenPlotter);
    EndDialog(hwnd, 0);
}

Read and Write Config Files

I just use an ini file wrapper. Here I read from a file:

C++
LPINIFILE lpFile = New_IniFile(filename);

//Plot Offset
lpp->offset_x = IniFile_ReadInteger(lpFile, _T("Plot Offset"), _T("x"), lpp->offset_x);
lpp->offset_y = IniFile_ReadInteger(lpFile, _T("Plot Offset"), _T("y"), lpp->offset_y);

//
// Read more stuff
//

IniFile_Destroy(lpFile); //All done so clean up

Now I write to a file:

C++
LPINIFILE lpFile = New_IniFile(filename);

//Plot Offset
IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("x"), lpp->offset_x);
IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("y"), lpp->offset_y);

//
// Write more stuff
//

IniFile_Destroy(lpFile); //All done so clean up

Write Images in Various File Formats

  • New_ImageFile(): Loads a subset of the GDI+ API and provides a pointer to the resources
  • ImageFile_Destroy(): Unloads GDI+ and frees resources
C++
static VOID MnuSavePlot_Click(HWND hwnd)
{
    OPENFILENAME ofn;
    TCHAR szFileName[MAX_PATH] = {0};

    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Png Image (*.png)\0*.png\0Gif Image 
    (*.gif)\0*.gif\0JPeg Image (*.jpg)\0*.jpg\0Tiff Image 
    (*.tif)\0*.tif\0Bitmap Image (*.bmp)\0*.bmp\0All Files (*.*)\0*.*\0";
    ofn.lpstrTitle = "Save an Image File";
    ofn.lpstrFile = szFileName;
    ofn.lpstrInitialDir = gPlotDirectory;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "png";

    if(GetSaveFileName(&ofn))
    {
        DoEvents(); // refresh screen

        // If the file name is not an empty string open it for saving.
        if (0 != _tcsncmp(szFileName, _T(""), NELEMS(szFileName)))
        {
             HBITMAP hbmp = Static_GetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP);

            if(NULL != hbmp)
            { 
                LPIMAGEFILE lpi = New_ImageFile();
                if(NULL != lpi)
                {
                    // file type selected in the dialog box.
                    // NOTE that the FilterIndex property is one-based.
                    switch (ofn.nFilterIndex)
                    {
                        case 5: //bmp
                        {
                            ImageFile_SaveBMP(lpi, hbmp, szFileName);
                        }
                        case 4: //tiff
                        {
                            BOOL fCompressLZW = TRUE;
                            ImageFile_SaveTIFF(lpi, hbmp, fCompressLZW, szFileName);
                        }
                            break;
                        case 3: //jpg
                        {
                            UINT uQuality = 100;
                            ImageFile_SaveJPEG(lpi, hbmp, uQuality, szFileName);
                        }
                            break;
                        case 2: //gif
                        {
                            ImageFile_SaveGIF(lpi, hbmp, szFileName);
                        }
                            break;
                        case 1: //png
                            //fallthrough
                        default:
                        {
                            ImageFile_SavePNG(lpi, hbmp, szFileName);
                        }
                    }
                    ImageFile_Destroy(lpi);
                }//if(NULL != lpi)
            }
        } 
    }
}

Using the Demo

The demo will not run unless you have VISA [^] installed on your computer. You will also need to install 488.2 GPIB support [^].

The demo currently will request a plot from the following pieces of equipment:

  • Tektronix TDS 420 4 channel oscilloscope
  • HP 8711A RF Network Analyzer
  • HP 8751A 8751A Baseband, IF and RF Network Analyzer
  • HP 8753C Network Analyzer
  • HP 8753D Network Analyzer, 30 kHz to 3 GHz
  • HP 4396A RF Network/Spectrum Analyzer

In addition to this, I added an import feature so that HPGL data files can be opened and rendered in the application. This allows you to play around with the rendering engine even if you don't have one of the above pieces of test equipment. There should be a screen-shot from the TDS 420, "test.plt", included in the demo zip file.

The demo also comes with a help file that covers basic operation.

Final Comments

I documented this source with Doxygen [^] comments for those that might find it helpful or useful. Your feedback is appreciated.

History

  • February, 2, 2015: Version 1.0.0.0
  • March, 6, 2015: Version 1.1.0.0 - Eric Moberg added support for the following:
    • Tektronix TDS 640A, TDS 460A, TDS 754C, TDS 754D, TDS 644B, and TDS 740 oscilloscopes.
  • March, 13, 2015: Version 1.2.0.0 - Fixed bug that caused some Tektronics scope screen shot data to not fully transfer to the Application.
  • December, 16, 2016: Version 1.3.0.0 - Improved scaling of device plots.  Added support for devices that export rastor image screen shots.  Added support for the following devices:
    • HP Agilent 4395A, N9010A, E4445A, DSO80204B, and 8753ES

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwork with listen model as HPGL printer Pin
Member 1383480819-May-18 4:28
Member 1383480819-May-18 4:28 
GeneralMy vote of 5 Pin
FlaviaPoupard18-Dec-16 20:12
FlaviaPoupard18-Dec-16 20:12 
QuestionUsing demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
Harry_P19-Jun-16 7:39
Harry_P19-Jun-16 7:39 
AnswerRe: Using demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
David MacDermot19-Jun-16 17:59
David MacDermot19-Jun-16 17:59 
GeneralRe: Using demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
Harry_P19-Jun-16 19:14
Harry_P19-Jun-16 19:14 
GeneralRe: Using demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
David MacDermot20-Jun-16 7:14
David MacDermot20-Jun-16 7:14 
GeneralRe: Using demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
Harry_P19-Jun-16 20:13
Harry_P19-Jun-16 20:13 
GeneralRe: Using demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
David MacDermot20-Jun-16 6:59
David MacDermot20-Jun-16 6:59 
GeneralRe: Using demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
Harry_P20-Jun-16 11:59
Harry_P20-Jun-16 11:59 
GeneralRe: Using demo code with Agilent 82357B USB/GPIB to acquire screen grabs? Pin
Member 1383480819-May-18 4:13
Member 1383480819-May-18 4:13 
BugHere's an example of a color HPGL plot with the original bufferSize (2048) Pin
Eric Moberg10-Mar-15 9:20
Eric Moberg10-Mar-15 9:20 
GeneralRe: Here's an example of a color HPGL plot with the original bufferSize (2048) Pin
David MacDermot10-Mar-15 13:02
David MacDermot10-Mar-15 13:02 
GeneralRe: Here's an example of a color HPGL plot with the original bufferSize (2048) Pin
Eric Moberg11-Mar-15 9:03
Eric Moberg11-Mar-15 9:03 
GeneralRe: Here's an example of a color HPGL plot with the original bufferSize (2048) Pin
David MacDermot11-Mar-15 10:17
David MacDermot11-Mar-15 10:17 
GeneralRe: Here's an example of a color HPGL plot with the original bufferSize (2048) Pin
Eric Moberg11-Mar-15 10:43
Eric Moberg11-Mar-15 10:43 
NewsBug Fixed Pin
David MacDermot13-Mar-15 12:18
David MacDermot13-Mar-15 12:18 
GeneralI added support for a few more scopes Pin
Eric Moberg4-Mar-15 9:42
Eric Moberg4-Mar-15 9:42 
AnswerRe: I added support for a few more scopes Pin
David MacDermot4-Mar-15 13:49
David MacDermot4-Mar-15 13:49 
GeneralRe: I added support for a few more scopes Pin
Eric Moberg6-Mar-15 5:25
Eric Moberg6-Mar-15 5:25 
GeneralRe: I added support for a few more scopes Pin
Eric Moberg6-Mar-15 5:27
Eric Moberg6-Mar-15 5:27 
C
//////////////////////////////////////////////////////////////////////////////
///
/// @file main.c
///
/// @brief Main application module.
///
/// @author David MacDermot
///
/// @par Comments:
///         This source is distributed in the hope that it will be useful,
///         but WITHOUT ANY WARRANTY; without even the implied warranty of
///         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
/// 
/// @date 09-22-14
/// 
/// @todo 
///
/// @bug 
///
//////////////////////////////////////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <htmlhelp.h>
#include <stdio.h>
#include <tchar.h>
#include <visa.h>
#include <wchar.h>

#include "propertyGrid.h"
#include "resource.h"
#include "error.h"
#include "connect.h"
#include "hpPlotter.h"
#include "stringFunc.h"
#include "odmenu.h"
#include "iniFile.h"
#include "imageFile.h"
#include "windowState.h"

/** Global variables ********************************************************/

#define SNAPSHOTHELP _T("SnapShotHelp.chm")

/// @brief List of instruments that are currently supported by Instrument Snapshot
/// @note This list must be terminated by a NULL
static LPSTR supportedModels[] = { "TDS 420", "TDS 640A", "TDS 754C", "TDS 460A", "8711A", "8751A", 
	"8753C", "8753D", "4396A", "TDS 754D", "TDS 740", NULL };

static HANDLE ghInstance;
static HWND ghToolBar, ghGrid, ghPictureBox;

static BOOL gfDrag = FALSE; ///< splitter draging flag
static UINT guDrag = 100;   ///< splitter draging x position
static LONG nDivTop;        ///< splitter top y coord
static LONG nDivBtm;        ///< splitter bottom y coord
static LONG nOldDivX;       ///< splitter last x position

static BOOL gfHires = TRUE; ///< Set to TRUE if high res icons desired in build
static LPPLOTTER glpScreenPlotter; ///< pointer to plotter object
static VINSTRUMENT gInstrument; ///< pointer to virtual instrument
static PAGESETUPDLG gPsd; ///< pointer to page setup dialog object
static HGLOBAL ghDevMode; ///< device mode handle (used in printing)
static TCHAR gParentDirectory[MAX_PATH]; ///< The parent directory path
static TCHAR gConfigDirectory[MAX_PATH]; ///< Config files subdirectory path
static TCHAR gPlotDirectory[MAX_PATH]; ///< Saved plot files subdirectory path

/****************************************************************************/
/// @name Macroes
/// @{

/// @def DirectoryExists(lpPath)
///
/// @brief Check to see if a directory exists.
///
/// @param lpPath The path name to test.
///
/// @returns TRUE if directory exists. 
#define DirectoryExists(lpPath) (BOOL)(GetFileAttributes((lpPath)) != 0xFFFFFFFF) 
#define FileExists(lpPath) DirectoryExists(lpPath) 

/// @def IsEmptyString(lpsz)
///
/// @brief Test a string.
///
/// @param lpsz The string to test.
///
/// @returns TRUE if empty
#define IsEmptyString(lpsz) (BOOL)(NULL == lpsz || 0 == lpsz[0])

/// @def NELEMS(a)
///
/// @brief Computes number of elements of an array.
///
/// @param a An array.
#define NELEMS(a) (sizeof(a) / sizeof((a)[0]))

/// @def Refresh(hwnd)
///
/// @brief Refresh a window.
///
/// @param hwnd The handle to the window.
#define Refresh(hwnd) RedrawWindow((hwnd), NULL, NULL, \
    RDW_ERASE|RDW_INVALIDATE|RDW_UPDATENOW)

/// @def HEIGHT(rect)
///
/// @brief Given a RECT, Computes height.
///
/// @param rect A RECT struct.
#define HEIGHT(rect) ((LONG)(rect.bottom - rect.top))

/// @def WIDTH(rect)
///
/// @brief Given a RECT, Computes width.
///
/// @param rect A RECT struct.
#define WIDTH(rect) ((LONG)(rect.right - rect.left))

/// @}

#pragma region misc

/// @brief Get the information associated with a version info field.
///
/// @param csEntry - The field description string.
///
/// @par Usage:
///       GetVersionInfo(FieldDescriptionString) where FieldDescriptionString
///        may be one of the following values:
///        "Comments"
///        "CompanyName"
///        "FileDescription"
///        "InternalName"
///        "LegalCopyright"
///        "LegalTrademarks"
///        "OriginalFilename"
///        "PrivateBuild"
///        "ProductName"
///        "ProductVersion"
///        "SpecialBuild"
///
/// @par Comments:
///       Adapted from luetz, www.codeproject.com/cpp/GetLocalVersionInfos.asp
///
/// @returns LPTSTR The application info associated with
///                 a version info entry string.
static LPTSTR GetVersionInfo(LPTSTR csEntry)
{
    LPTSTR csRet = _T("");
    static TCHAR errStr[MAX_PATH];

    HRSRC hVersion = FindResource(GetModuleHandle(NULL),
        MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
    if (hVersion != NULL)
    {
        HGLOBAL hGlobal = LoadResource(GetModuleHandle(NULL), hVersion);

        if (hGlobal != NULL)
        {
            LPVOID versionInfo = LockResource(hGlobal);

            if (versionInfo != NULL)
            {
                DWORD vLen, langD;
                BOOL retVal;
                LPVOID retbuf = NULL;
                static TCHAR fileEntry[MAX_PATH];

                _stprintf(fileEntry, NELEMS(fileEntry), _T("\\VarFileInfo\\Translation"));
                retVal = VerQueryValue(versionInfo, fileEntry, (LPVOID *) & retbuf, (UINT *) & vLen);
                if (retVal && vLen == 4)
                {
                    memcpy(&langD, retbuf, 4);
                    _stprintf(fileEntry, NELEMS(fileEntry),
#ifdef _UNICODE
                        _T("\\StringFileInfo\\%02X%02X%02X%02X\\%ls"),
#else
                        _T("\\StringFileInfo\\%02X%02X%02X%02X\\%s"),
#endif
                        (langD & 0xff00) >> 8, langD & 0xff, (langD & 0xff000000) >> 24, (langD & 0xff0000) >> 16, csEntry);
                }
                else
                {
                    _stprintf(fileEntry, NELEMS(fileEntry),
#ifdef _UNICODE
                        _T("\\StringFileInfo\\%04X04B0\\%ls"),
#else
                        _T("\\StringFileInfo\\%04X04B0\\%s"),
#endif
                        GetUserDefaultLangID(), csEntry);
                }
                if (retVal == VerQueryValue(versionInfo, fileEntry, (LPVOID *) & retbuf, (UINT *) & vLen))
                {
                    csRet = (LPTSTR)retbuf;
                }
                else
                {
                    _stprintf(errStr, NELEMS(errStr),
#ifdef _UNICODE
                        _T("%ls Not Available"),
#else
                        _T("%s Not Available"),
#endif
                        csEntry);
                    csRet = errStr;
                }
            }
        }
        UnlockResource(hGlobal);
        FreeResource(hGlobal);
    }
    return csRet;
}

/// @brief Create a toolbar window tied to the menu and custom draw menu icons.
///
/// @param hwnd - The handle of the parent window.
/// @param wBitmapRsc - The resource identifier of the menu icon bitmaps.
///
/// @par Comments:
///       The toolbar is used to store the images that will be drawn in the associated
///        menues.
///
/// @returns HWND The handle of the newly created toolbar.
static HWND CreateToolbar_OwnerDrawMenu(HWND hwnd, WORD wBitmapRsc)
{
    HWND hToolBar;
    HIMAGELIST himl;
    TBBUTTON tbb;
    int i;

    //
    // Create the Toolbar window (the only band in ReBar).
    //
    hToolBar = CreateWindow(TOOLBARCLASSNAME, NULL, WS_CHILD | WS_VISIBLE | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT, 0, 0, 0, 0, hwnd, (HMENU)IDC_TOOLBAR, ghInstance, NULL);
    if (!hToolBar)
        return NULL;

    ToolBar_SetExtendedStyle(hToolBar, TBSTYLE_EX_DOUBLEBUFFER);

    //
    // Version control and single button bitmap size.
    //
    ToolBar_ButtonStructSize(hToolBar);
    ToolBar_SetBitmapSize(hToolBar, 16, 16);

    //
    // Add bitmap with all buttons to the toolbar.
    //
    himl = ImageList_Create(16, 16, ILC_COLOR32 | ILC_MASK, 16, 1);
    ImageList_AddMasked(himl, LoadImage(ghInstance, MAKEINTRESOURCE(wBitmapRsc), IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_DEFAULTCOLOR), RGB(0, 128, 128));

    ToolBar_SetImageList(hToolBar, himl);

    //
    // Add buttons to the toolbar.
    //

    // Table for *both* the toolbar and the menu images
    struct {
        WORD idCommand; // Command id.
        WORD iBitmap;   // Bitmap index (in IDR_BMP_MAINTOOLS).
        WORD fsStyle;   // Button style.
        WORD fsState;   // Button state.
    }
    gaToolbar[] =
    {
        MNU_OPENCONFIG,     3, TBSTYLE_BUTTON,  TBSTATE_ENABLED,
        0,                  0, TBSTYLE_SEP,     TBSTATE_ENABLED,
        MNU_IMPORT_HPGL,    11, TBSTYLE_BUTTON,  TBSTATE_ENABLED,
        0,                  0, TBSTYLE_SEP,     TBSTATE_ENABLED,
        MNU_GETPLOT,        8, TBSTYLE_BUTTON,  TBSTATE_ENABLED,
        MNU_REFRESHPLOT,    5, TBSTYLE_BUTTON,  TBSTATE_ENABLED, 
        MNU_SAVE,           2, TBSTYLE_BUTTON,  TBSTATE_ENABLED,
        0,                  0, TBSTYLE_SEP,     TBSTATE_ENABLED, 
        MNU_PRINT,          6, TBSTYLE_BUTTON,  TBSTATE_ENABLED, 
        0,                  0, TBSTYLE_SEP,     TBSTATE_ENABLED, 
        MNU_SNAPSHOT_HELP,  9, TBSTYLE_BUTTON,  TBSTATE_ENABLED,
        //
        // Hidden commands only for menu bitmaps.
        //
        MNU_CONNECTION,     1, TBSTYLE_BUTTON,  TBSTATE_HIDDEN,
        MNU_SAVECONFIG,     4, TBSTYLE_BUTTON,  TBSTATE_HIDDEN,
        MNU_PAGESETUP,      7, TBSTYLE_BUTTON,  TBSTATE_HIDDEN,
        MNU_EXIT,           10, TBSTYLE_BUTTON, TBSTATE_HIDDEN,
        MNU_ABOUT,          0, TBSTYLE_BUTTON,  TBSTATE_HIDDEN
    };

    for (i = 0; i < NELEMS(gaToolbar); i++)
    {
        tbb.iBitmap = gaToolbar[i].iBitmap;
        tbb.idCommand = gaToolbar[i].idCommand;
        tbb.fsState = gaToolbar[i].fsState;
        tbb.fsStyle = gaToolbar[i].fsStyle;
        tbb.iString = 0;
        ToolBar_AddButtons(hToolBar, 1, &tbb);
    }

    ToolBar_AutoSize(hToolBar);

    // Install the owner-draw menu handler. The rest is handled automatically!
    if (!InstallOwnerDrawMenuHandler(hwnd, hToolBar))
        return NULL;

    return hToolBar;
}

/// @brief Yields execution of the current thread so that
///         the operating system can process other events.
///
/// @returns VOID.
static VOID DoEvents(VOID)
{
    MSG Msg;
    while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
}

/// @brief Get the size of a control in a dialog.
///
/// @param hwnd Handle of dialog.
/// @param nIDDlgItem The identifier of the control.
///
/// @returns SIZE The dimensions of the control.
static SIZE GetDlgItemSize(HWND hwnd, INT nIDDlgItem)
{
    static SIZE sz;
    RECT rc;
    memset(&rc, 0, sizeof(RECT));
    GetClientRect(GetDlgItem(hwnd, nIDDlgItem), &rc);
    sz.cx = WIDTH(rc);
    sz.cy = HEIGHT(rc);
    return sz;
}

/// @brief Draw a line.
///
/// @param hdc A handle to a device context.
/// @param x1 From point x-coordinate.
/// @param y1 From point y-coordinate.
/// @param x2 To point x-coordinate.
/// @param y2 To point y-coordinate.
///
/// @returns VOID.
static VOID DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2)
{
    MoveToEx(hdc, x1, y1, NULL);
    LineTo(hdc, x2, y2);
}

/// @brief Draw an inverted line.
///
/// @param hdc A handle to a device context.
/// @param x1 From point x-coordinate.
/// @param y1 From point y-coordinate.
/// @param x2 To point x-coordinate.
/// @param y2 To point y-coordinate.
///
/// @returns VOID.
static VOID InvertLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2)
{
    INT nOldMode = SetROP2(hdc, R2_NOT);
    DrawLine(hdc, x1, y1, x2, y2);
    SetROP2(hdc, nOldMode);
}

BOOL isOverSlider(INT x)
{
    return abs(x - guDrag) < 5;
}

#pragma endregion misc

#pragma region Plotter PropertyGrid

/// @brief Load saved plotter configuration data in to the plotter object.
/// @param lpp The pointer to a plotter object.
/// @param filename The path name of a config file.
///
/// @returns VOID
VOID PlotterConfig_LoadFromFile(LPPLOTTER lpp, LPCTSTR filename)
{
    __try
    {
        LPINIFILE lpFile = New_IniFile(filename);

        //Plot Offset
        lpp->offset_x = IniFile_ReadInteger(lpFile, _T("Plot Offset"), _T("x"), lpp->offset_x);
        lpp->offset_y = IniFile_ReadInteger(lpFile, _T("Plot Offset"), _T("y"), lpp->offset_y);

        //Plot Text Offset
        lpp->offsetText_x = IniFile_ReadInteger(lpFile, _T("Text Offset"), _T("x"), lpp->offsetText_x);
        lpp->offsetText_y = IniFile_ReadInteger(lpFile, _T("Text Offset"), _T("y"), lpp->offsetText_y);

        //Plot Text Font
        lpp->fontWeight = IniFile_ReadInteger(lpFile, _T("Text Font"), _T("weight"), lpp->fontWeight);
        _tcsncpy(lpp->fontName, IniFile_ReadString(lpFile, _T("Text Font"), _T("name"), lpp->fontName), NELEMS(lpp->fontName));
        lpp->fontScale = IniFile_ReadInteger(lpFile, _T("Text Font"), _T("scale"), lpp->fontScale);

        //Plot Options
        ANTIALIASING aa = lpp->antiAliasing;
        LPTSTR szValue = 
            aa == x8 ? _T("x8") : 
            aa == x4 ? _T("x4") :
            aa == x2 ? _T("x2") : 
            _T("NONE"); //Default
        szValue = IniFile_ReadString(lpFile, _T("Plot Options"), _T("anti-ailiasing"), szValue);
        szValue = _tcsupr(szValue);
        lpp->antiAliasing = 
            0 == _tcsncmp(_T("X8"), szValue, _tcslen(szValue)) ? x8 : 
            0 == _tcsncmp(_T("X4"), szValue, _tcslen(szValue)) ? x4 :
            0 == _tcsncmp(_T("X2"), szValue, _tcslen(szValue)) ? x2 : 
            NONE; //Default
        BYTE r = GetRValue(lpp->backcolor);
        BYTE g = GetGValue(lpp->backcolor);
        BYTE b = GetBValue(lpp->backcolor);
        r = IniFile_ReadInteger(lpFile, _T("Plot Options"), _T("background color R"), r);
        g = IniFile_ReadInteger(lpFile, _T("Plot Options"), _T("background color G"), g);
        b = IniFile_ReadInteger(lpFile, _T("Plot Options"), _T("background color B"), b);
        lpp->backcolor = RGB(r,g,b);
        _tcsncpy(lpp->markerSymbol, IniFile_ReadString(lpFile, _T("Plot Options"), _T("marker symbol"), lpp->markerSymbol), NELEMS(lpp->markerSymbol));
        lpp->useColorPens = IniFile_ReadBoolean(lpFile, _T("Plot Options"), _T("use color pens"), lpp->useColorPens);
        lpp->useCustomLineTypes = IniFile_ReadBoolean(lpFile, _T("Plot Options"), _T("use custom line types"), lpp->useCustomLineTypes);

        //Pen configurations
        TCHAR szSection[MAX_PATH] = { 0 };
        //Note: Pen 0 is not used (it is a place holder since SP0 command = store pen)
        for (int i = 1; i < lpp->penCount; ++i)
        {
            _stprintf(szSection, NELEMS(szSection), _T("Pen %d"), i);
            lpp->penWidth[i] = IniFile_ReadInteger(lpFile, szSection, _T("width"), lpp->penWidth[i]);
            r = GetRValue(lpp->penColor[i]);
            g = GetGValue(lpp->penColor[i]);
            b = GetBValue(lpp->penColor[i]);
            r = IniFile_ReadInteger(lpFile, szSection, _T("color R"), r);
            g = IniFile_ReadInteger(lpFile, szSection, _T("color G"), g);
            b = IniFile_ReadInteger(lpFile, szSection, _T("color B"), b);
            lpp->penColor[i] = RGB(r,g,b);
            INT lt = lpp->customLineType[i];
            szValue = 
                lt == PS_DASHDOTDOT ? _T("Dash Dot Dot") : 
                lt == PS_DASHDOT ? _T("Dash Dot") : 
                lt == PS_DOT ? _T("Dot") : 
                lt == PS_DASH ? _T("Dash") :
                _T("Solid"); //Default
            szValue = IniFile_ReadString(lpFile, szSection, _T("line type"), szValue);
            szValue = _tcsupr(szValue);
            lpp->customLineType[i] = 
                0 == _tcsncmp(_T("DASH DOT DOT"), szValue, _tcslen(szValue)) ? PS_DASHDOTDOT :
                0 == _tcsncmp(_T("DASH DOT"), szValue, _tcslen(szValue)) ? PS_DASHDOT :
                0 == _tcsncmp(_T("DOT"), szValue, _tcslen(szValue)) ? PS_DOT : 
                0 == _tcsncmp(_T("DASH"), szValue, _tcslen(szValue)) ? PS_DASH :
                PS_SOLID; //Default
        }
        IniFile_Destroy(lpFile);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
    }
}

/// @brief Save plotter configuration data to a file.
/// @param lpp The pointer to a plotter object.
/// @param filename The path name of a config file.
///
/// @returns VOID
VOID PlotterConfig_SaveToFile(LPPLOTTER lpp, LPCTSTR filename)
{
    __try
    {
        if(FileExists(filename)) DeleteFile(filename);

        LPINIFILE lpFile = New_IniFile(filename);

        //Plot Offset
        IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("x"), lpp->offset_x);
        IniFile_WriteInteger(lpFile, _T("Plot Offset"), _T("y"), lpp->offset_y);
        IniFile_InsertSeparator(lpFile, _T("Plot Offset"));

        //Plot Text Offset
        IniFile_WriteInteger(lpFile, _T("Text Offset"), _T("x"), lpp->offsetText_x);
        IniFile_WriteInteger(lpFile, _T("Text Offset"), _T("y"), lpp->offsetText_y);
        IniFile_InsertSeparator(lpFile, _T("Text Offset"));

        //Plot Text Font
        IniFile_WriteInteger(lpFile, _T("Text Font"), _T("weight"), lpp->fontWeight);
        IniFile_WriteString(lpFile, _T("Text Font"), _T("name"), lpp->fontName);
        IniFile_WriteInteger(lpFile, _T("Text Font"), _T("scale"), lpp->fontScale);
        IniFile_InsertSeparator(lpFile, _T("Text Font"));

        //Plot Options
        IniFile_WriteString(lpFile, _T("Plot Options"), _T("//Note: Valid Anti-ailiasing Values"), 
            _T(" NONE, x2, x4, and x8"));
        ANTIALIASING aa = lpp->antiAliasing;
        IniFile_WriteString(lpFile, _T("Plot Options"), _T("anti-ailiasing"),
            aa == x8 ? _T("x8") : 
            aa == x4 ? _T("x4") :
            aa == x2 ? _T("x2") : 
            _T("NONE")); //Default
        IniFile_WriteInteger(lpFile, _T("Plot Options"), _T("background color R"), GetRValue(lpp->backcolor));
        IniFile_WriteInteger(lpFile, _T("Plot Options"), _T("background color G"), GetGValue(lpp->backcolor));
        IniFile_WriteInteger(lpFile, _T("Plot Options"), _T("background color B"), GetBValue(lpp->backcolor));
        IniFile_WriteString(lpFile, _T("Plot Options"), _T("marker symbol"), lpp->markerSymbol);
        IniFile_WriteBoolean(lpFile, _T("Plot Options"), _T("use color pens"), lpp->useColorPens);
        IniFile_WriteBoolean(lpFile, _T("Plot Options"), _T("use custom line types"), lpp->useCustomLineTypes);
        IniFile_InsertSeparator(lpFile, _T("Plot Options"));

        //Pen configurations
        IniFile_WriteString(lpFile, _T("Pen configurations"), _T("//Note: Valid Pen Widths"), 
            _T(" 1-20"));
        IniFile_WriteString(lpFile, _T("Pen configurations"), _T("//Note: Pen Colors"), 
            _T(" values as R, G, and B respectively and used when \'use color pens\' is set to \'True\'"));
        IniFile_WriteString(lpFile, _T("Pen configurations"), _T("//Note: Valid Line Types"), 
            _T(" Solid, Dash, Dot, Dash Dot, and Dash Dot Dot"));
        IniFile_InsertSeparator(lpFile, _T("Pen configurations"));

        TCHAR szSection[MAX_PATH] = { 0 };
        //Note: Pen 0 is not used (it is a place holder since SP0 command = store pen)
        for (int i = 1; i < lpp->penCount; ++i)
        {
            _stprintf(szSection, NELEMS(szSection), _T("pen %d"), i);
            IniFile_WriteInteger(lpFile, szSection, _T("width"), lpp->penWidth[i]);
            IniFile_WriteInteger(lpFile, szSection, _T("color R"), GetRValue(lpp->penColor[i]));
            IniFile_WriteInteger(lpFile, szSection, _T("color G"), GetGValue(lpp->penColor[i]));
            IniFile_WriteInteger(lpFile, szSection, _T("color B"), GetBValue(lpp->penColor[i]));
            INT lt = lpp->customLineType[i];
            IniFile_WriteString(lpFile, szSection, _T("line type"),
                lt == PS_DASH ? _T("Dash") : 
                lt == PS_DASHDOT ? _T("Dash Dot") :
                lt == PS_DASHDOTDOT ? _T("Dash Dot Dot") : 
                lt == PS_DOT ? _T("Dot") : 
                _T("Solid")); //Default
            IniFile_InsertSeparator(lpFile, szSection);
        }
        IniFile_Destroy(lpFile);
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
    }
}

/// @brief Display plotter configurations in grid.
///
/// @param lpp A pointer to a plotter object.
/// @param hGrid The handle of a property grid.
///
/// @returns VOID.
VOID Plotter_UpdateGrid(LPPLOTTER lpp, HWND hGrid)
{
    PropGrid_ResetContent(hGrid);

    PROPGRIDITEM Item = { 0 };
    TCHAR buf[MAX_PATH];
    _tmemset(buf, (TCHAR)0, NELEMS(buf));

    //Plot Offset
    Item.lpszCatalog = _T("Plot Offset");
    Item.lpszPropName = _T("x");
    _stprintf(buf, NELEMS(buf), "%d", lpp->offset_x);
    Item.lpCurValue = (LPARAM)buf;
    Item.lpszPropDesc = _T("Plot origin x coordinate offset.");
    Item.iItemType = PIT_EDIT;
    PropGrid_AddItem(hGrid, &Item);

    Item.lpszPropName = _T("y");
    _stprintf(buf, NELEMS(buf), "%d", lpp->offset_y);
    Item.lpCurValue = (LPARAM)buf;
    Item.lpszPropDesc = _T("Plot origin y coordinate offset.");
    Item.iItemType = PIT_EDIT;
    PropGrid_AddItem(hGrid, &Item);

    //Plot Text Offset
    Item.lpszCatalog = _T("Text Offset");
    Item.lpszPropName = _T("x");
    _stprintf(buf, NELEMS(buf), "%d", lpp->offsetText_x);
    Item.lpCurValue = (LPARAM)buf;
    Item.lpszPropDesc = _T("Plot text offset x.");
    Item.iItemType = PIT_EDIT;
    PropGrid_AddItem(hGrid, &Item);

    Item.lpszPropName = _T("y");
    _stprintf(buf, NELEMS(buf), "%d", lpp->offsetText_y);
    Item.lpCurValue = (LPARAM)buf;
    Item.lpszPropDesc = _T("Plot text offset y.");
    Item.iItemType = PIT_EDIT;
    PropGrid_AddItem(hGrid, &Item);

    //Plot Text Font
    Item.lpszCatalog = _T("Text Font");
    PROPGRIDFONTITEM ItemLf;
    GetObject((HFONT)GetStockObject(DEFAULT_GUI_FONT), sizeof(LOGFONT), &ItemLf.logFont);
    ItemLf.logFont.lfWeight = lpp->fontWeight;
    _tcsncpy(ItemLf.logFont.lfFaceName, lpp->fontName, NELEMS(ItemLf.logFont.lfFaceName));
    ItemLf.crFont = RGB(0, 0, 0);
    Item.lpszPropName = _T("Text Font");
    Item.lpCurValue = (LPARAM) & ItemLf;
    Item.lpszPropDesc = _T("Choose font name and weight.");
    Item.iItemType = PIT_FONT;
    PropGrid_AddItem(hGrid, &Item);

    Item.lpszPropName = _T("Font Scale");
    _stprintf(buf, NELEMS(buf), "%d", lpp->fontScale);
    Item.lpCurValue = (LPARAM)buf;
    Item.lpszPropDesc = _T("Scale of font used on plot (percent).");
    Item.iItemType = PIT_EDIT;
    PropGrid_AddItem(hGrid, &Item);

    //Plot Options
    Item.lpszCatalog = _T("Plot Options");
    Item.lpszPropName = _T("Anti-ailiasing");
    ANTIALIASING aa = lpp->antiAliasing;
    Item.lpCurValue = (LPARAM) 
            (aa == x8 ? _T("x8") : 
            aa == x4 ? _T("x4") :
            aa == x2 ? _T("x2") : 
            _T("NONE")); //Default
    Item.lpszzCmbItems = _T("NONE\0x2\0x4\0x8\0");
    Item.lpszPropDesc = _T("How much Anti-aliasing scale to apply to the image.");
    Item.iItemType = PIT_COMBO;
    PropGrid_AddItem(hGrid, &Item);

    Item.lpszPropName = _T("Background Color");
    Item.lpCurValue = (LPARAM)lpp->backcolor;
    Item.lpszPropDesc = _T("Color of the plot background (paper color).");
    Item.iItemType = PIT_COLOR;
    PropGrid_AddItem(hGrid, &Item);

    Item.lpszPropName = _T("Marker Symbol");
    Item.lpCurValue = (LPARAM)lpp->markerSymbol;
    Item.lpszPropDesc = _T("Ascii character or characters to use for markers (device dependent).");
    Item.iItemType = PIT_EDIT;
    PropGrid_AddItem(hGrid, &Item);

    Item.lpszPropName = _T("Use Color Pens");
    Item.lpCurValue = (LPARAM)lpp->useColorPens;
    Item.lpszPropDesc = _T("If checked specify the color for each pen.");
    Item.iItemType = PIT_CHECK;
    PropGrid_AddItem(hGrid, &Item);

    Item.lpszPropName = _T("Use Custom Line Types");
    Item.lpCurValue = (LPARAM)lpp->useCustomLineTypes;
    Item.lpszPropDesc = _T("If checked specify the line type for each pen.");
    Item.iItemType = PIT_CHECK;
    PropGrid_AddItem(hGrid, &Item);

    //Pen configurations
    TCHAR szCat[MAX_PATH] = { 0 };
    //Note: Pen 0 is not used (it is a place holder since SP0 command = store pen)
    for (int i = 1; i < lpp->penCount; ++i)
    {
        _stprintf(szCat, NELEMS(szCat), _T("Pen %d"), i);

        //Pen width
        Item.lpszCatalog = szCat;
        Item.lpszPropName = _T("Width");
        _stprintf(buf, NELEMS(buf), "%ld", lpp->penWidth[i]);
        Item.lpCurValue = (LPARAM)buf;
        Item.lpszzCmbItems = _T("1\0002\0003\0004\0005\0006\0007\0008\0009\00010 \
            \00011\00012\00013\00014\00015\00016\00017\00018\00019\00020\000");
        Item.lpszPropDesc = _T("The desired width of this pen (in pixels).");
        Item.iItemType = PIT_COMBO;
        PropGrid_AddItem(hGrid, &Item);

        if (lpp->useColorPens)
        {
            Item.lpszPropName = _T("Color");
            Item.lpCurValue = (LPARAM)lpp->penColor[i];
            Item.lpszPropDesc = _T("The desired Color of this pen.");
            Item.iItemType = PIT_COLOR;
            PropGrid_AddItem(hGrid, &Item);
        }
        if (lpp->useCustomLineTypes)
        {
            Item.lpszPropName = _T("Line Type");
            DWORD lt = lpp->customLineType[i];
            Item.lpCurValue = (LPARAM) 
                (lt == PS_DASH ? _T("Dash") : 
                lt == PS_DASHDOT ? _T("Dash Dot") :
                lt == PS_DASHDOTDOT ? _T("Dash Dot Dot") : 
                lt == PS_DOT ? _T("Dot") : 
                _T("Solid")); //Default
            Item.lpszzCmbItems = _T("Solid\0Dash\0Dot\0Dash Dot\0Dash Dot Dot\0");
            Item.lpszPropDesc = _T("The desired line type for this pen.");
            Item.iItemType = PIT_COMBO;
            PropGrid_AddItem(hGrid, &Item);
        }
    }

    PropGrid_ShowToolTips(hGrid, TRUE);
    PropGrid_ShowPropertyDescriptions(hGrid, TRUE);
    PropGrid_ExpandAllCatalogs(hGrid);
}

/// @brief Handle the PGN_PROPERTYCHANGE notification of the WM_NOTIFY message.
///
/// @param lpnmpg A pointer to the NMPPROPGRID message struct.
/// @param lpp A pointer to an instance of project data struct.
///
/// @par Comments:
///       Project data values are maintaned in the PROJECT struct.  These
///        values are loaded into the property grid and updated, each time
///        one of them changes, in this method.
///
/// @returns VOID.
static VOID Grid_OnPropertyChange(LPNMPROPGRID lpnmpg, LPPLOTTER lpp)
{
    HWND hGrid = lpnmpg->hdr.hwndFrom;
    LPPROPGRIDITEM Item = PropGrid_GetItemData(hGrid, lpnmpg->iIndex);

    switch (lpnmpg->iIndex)
    {
        //These item indexes do not change
        case 0:
            lpp->offset_x = atoi((LPTSTR)Item->lpCurValue);
            break;
        case 1:
            lpp->offset_y = atoi((LPTSTR)Item->lpCurValue);
            break;
        case 2:
            lpp->offsetText_x = atoi((LPTSTR)Item->lpCurValue);
            break;
        case 3:
            lpp->offsetText_y = atoi((LPTSTR)Item->lpCurValue);
            break;
        case 4:
            lpp->fontWeight = ((LPPROPGRIDFONTITEM)Item->lpCurValue)->logFont.lfWeight;
            _tcsncpy(lpp->fontName, ((LPPROPGRIDFONTITEM)Item->lpCurValue)->logFont.lfFaceName, 
                                NELEMS(((LPPROPGRIDFONTITEM)Item->lpCurValue)->logFont.lfFaceName));
            break;
        case 5:
            lpp->fontScale = atoi((LPTSTR)Item->lpCurValue);
            break;
        case 6:
        {
            LPTSTR szValue = Ucase((LPTSTR)Item->lpCurValue);
            lpp->antiAliasing = 
                0 == _tcsncmp(_T("X8"), szValue, _tcslen(szValue)) ? x8 : 
                0 == _tcsncmp(_T("X4"), szValue, _tcslen(szValue)) ? x4 :
                0 == _tcsncmp(_T("X2"), szValue, _tcslen(szValue)) ? x2 : 
                NONE; //Default
        }
            break;
        case 7:
            lpp->backcolor = (COLORREF)Item->lpCurValue;
            break;
        case 8:
            _tcsncpy(lpp->markerSymbol, (LPTSTR)Item->lpCurValue, NELEMS(lpp->markerSymbol));
            break;
        case 9:
            lpp->useColorPens = (BOOL)Item->lpCurValue;
            break;
        case 10:
            lpp->useCustomLineTypes = (BOOL)Item->lpCurValue;
            break;
        default: // Indexes of properties depend on whether or not lpp->useCustomLineType is checked.
        {
            //Calculate the pen and attribute that the property change belongs to
            int properties = 1;
            if(lpp->useColorPens) ++properties;
            if(lpp->useCustomLineTypes) ++properties;

            int pen = (int)((lpnmpg->iIndex - 11) / properties) + 1; //pens = 1 - 7
            int prop = (lpnmpg->iIndex - 11) % properties;
            switch (prop)
            {
                case 0:
                    lpp->penWidth[pen] = atoi((LPTSTR)Item->lpCurValue); break;
                case 1:
                    if(lpp->useColorPens)
                    {
                        lpp->penColor[pen] = (COLORREF)Item->lpCurValue; break;
                    }
                    //else fall through
                case 2:
                    if(lpp->useCustomLineTypes)
                    {
                        LPTSTR szValue = Ucase((LPTSTR)Item->lpCurValue);
                        lpp->customLineType[pen] = 
                            0 == _tcsncmp(_T("DASH"), szValue, _tcslen(szValue)) ? PS_DASH :
                            0 == _tcsncmp(_T("DASH DOT"), szValue, _tcslen(szValue)) ? PS_DASHDOT :
                            0 == _tcsncmp(_T("DASH DOT DOT"), szValue, _tcslen(szValue)) ? PS_DASHDOTDOT :
                            0 == _tcsncmp(_T("DOT"), szValue, _tcslen(szValue)) ? PS_DOT : 
                            PS_SOLID; //Default
                        break;
                    }
                    //else fall through
            }   //end switch
        }   //end default:
    }   //switch(lpnmpg->iIndex)

    if (8 < lpnmpg->iIndex && lpnmpg->iIndex < 11)
    {
        Plotter_UpdateGrid(lpp, hGrid); //Display only selected properties
        PropGrid_SetCurSel(hGrid, lpnmpg->iIndex);
    }
}

#pragma endregion Plotter PropertyGrid

/// @brief Handles WM_NOTIFY message.
///
/// @param hwnd  Handle of dialog.
/// @param id The id of component.
/// @param pnm - A pointer to the notification message header.
///
/// @returns BOOL Depends on notification.
static BOOL Main_OnNotify(HWND hwnd, INT id, LPNMHDR pnm)
{
    if (IDC_GRID == id && PGN_PROPERTYCHANGE == pnm->code)
    {
        Grid_OnPropertyChange((LPNMPROPGRID)pnm, glpScreenPlotter);
        return TRUE;
    }
    return FALSE;
}

#pragma region menuHelp

/// @brief Window procedure for the About dialog.
///
/// @param hDlg Handle of dialog.
/// @param uMsg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns BOOL depends on message.
static BOOL CALLBACK AboutDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            HWND hTitle = GetDlgItem(hDlg, IDC_ABOUT_TITLE);
            SetWindowFont(hTitle, CreateFont(20, 0, 0, 0, FW_HEAVY, FALSE, FALSE, FALSE, 
                    ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, 
                    VARIABLE_PITCH | FF_MODERN, NULL), TRUE);

            TCHAR buf[MAX_PATH] = { 0 };
            TCHAR ProductVersion[MAX_PATH] = { 0 };
            _tcscpy(ProductVersion, GetVersionInfo(_T("ProductVersion")));
            TCHAR LegalCopyright[MAX_PATH] = { 0 };
            _tcscpy(LegalCopyright, GetVersionInfo(_T("LegalCopyright")));
            TCHAR ProductName[MAX_PATH] = { 0 };
            _tcscpy(ProductName, GetVersionInfo(_T("ProductName")));

            _stprintf(buf, NELEMS(buf),
#ifdef _UNICODE
                _T("%ls version %ls, Copyright %ls\n")
                _T("David MacDermot\n")
                _T("%ls comes with ABSOLUTELY NO WARRANTY.\n")
                _T("Licensed under the terms of the Code Project Open License."),
#else
                _T("%s version %s, Copyright %s\n")
                _T("%s comes with ABSOLUTELY NO WARRANTY.\n")
                _T("Licensed under the terms of the Code Project Open License."),
#endif
                ProductName, ProductVersion, LegalCopyright, ProductName);

            Static_SetText(hTitle, ProductName);
            Static_SetText(GetDlgItem(hDlg, IDC_ABOUT_TEXT), buf);

            SetWindowPos(hDlg, NULL, ((LPPOINT)lParam)->x, ((LPPOINT)lParam)->y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
        }
            return TRUE;

        case WM_COMMAND:
            switch (wParam)
            {
                case IDOK:
                case IDCANCEL:
                    /*
                     * OK or Cancel was clicked, close the dialog.
                     */
                    EndDialog(hDlg, TRUE);
                    return TRUE;
            }
            break;
    }

    return FALSE;
}

/// @brief Recursively search for menu item and get it's bounding rect.
///
/// @param hwnd Handle of dialog.
/// @param hMenu Handle of a menu.
/// @param id The id of a menu item.
/// @param prc The address of a RECT.
///
/// @returns TRUE if successfull otherwise FALSE.
static BOOL GetMenuItemRectEx(HWND hwnd, HMENU hMenu, INT id, LPRECT prc)
{
    static MENUITEMINFO mii = { 0 };
    BOOL fRTN = FALSE;
    mii.cbSize = sizeof mii;
    mii.fMask = MIIM_ID | MIIM_SUBMENU;
    for (int pos = 0, ret = 1; ret; pos++)
    {
        ret = GetMenuItemInfo(hMenu, pos, TRUE, &mii);
        if (NULL != mii.hSubMenu)
        {
            if (GetMenuItemRectEx(hwnd, mii.hSubMenu, id, prc))
            {
                fRTN = TRUE;
                break;
            }
        }
        else if (id == mii.wID)
        {
            GetMenuItemRect(hwnd, hMenu, pos, prc);
            fRTN = TRUE;
            break;
        }
    }
    return fRTN;
}

/// @brief Handles the MNU_ABOUT click event.
///
/// @param hwnd  Handle of dialog.
/// @param id  Id of menu subitem.
///
/// @returns VOID.
static VOID MnuAbout_Click(HWND hwnd, INT id)
{
    RECT rc = { 0 };
    GetMenuItemRectEx(hwnd, GetMenu(hwnd), id, &rc);
    DialogBoxParam(ghInstance, MAKEINTRESOURCE(DLG_ABOUT), hwnd, (DLGPROC)AboutDlgProc, (LPARAM) (LPPOINT) & rc.right);
}

/// @brief Handle the Snap Shot Help menu item click .
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuSnapshotHelp_Click(HWND hwnd)
{
    TCHAR buf[MAX_PATH] = { 0 };
    _stprintf(buf, NELEMS(buf),
#ifdef _UNICODE
        _T("%ls\\%ls"),
#else
        _T("%s\\%s"),
#endif
        gParentDirectory, SNAPSHOTHELP);
    HtmlHelp(hwnd, buf, HH_DISPLAY_TOC, 0);
}

/// @brief Handles WM_HELP message.
///
/// @param hwnd  Handle of dialog.
/// @param lpHelpInfo Pointer to a HELPINFO structure that contains information
///                    about the menu item, control, dialog box, or window
///                    for which help is requested.
///
/// @returns BOOL TRUE if handled.
static BOOL Main_OnHelp(HWND hwnd, LPHELPINFO lpHelpInfo)
{
    //MnuWizardHelp_Click(hwnd);
    return TRUE;
}

#pragma endregion menuHelp

#pragma region mnuPrint

/// @brief Show the Print dialog and Return a HDC for the selected printer.
///
/// @returns VOID.
VOID InitPrintDevMode(VOID)
{
    DEVMODE *pDevMode;

    ghDevMode = GlobalAlloc(GHND, sizeof(DEVMODE)); // allocate a moveable block of memory and initialize it to zeroes.
    if (ghDevMode)
    {
        pDevMode = (DEVMODE *)GlobalLock(ghDevMode);    // lock the memory and return a pointer to it
        if (pDevMode)
        {
            pDevMode->dmSize = sizeof(DEVMODE); // set the size of the DEVMODE structure
            pDevMode->dmFields = DM_ORIENTATION;    // tell Windows that I will be setting the dmOrientation field
            pDevMode->dmOrientation = DMORIENT_LANDSCAPE;   // set the orientation to landscape
        }
        GlobalUnlock(ghDevMode);    // unlock the memory for other functions to use this
    }
    // ghDevMode is now ready to be used in a PAGESETUPDLG structure

    // populate PAGESETUPDLG structure with default margin values 
    //  against the event that Page Setup is not called
    gPsd.rtMargin.left = gPsd.rtMinMargin.left = 166;
    gPsd.rtMargin.top = gPsd.rtMinMargin.top = 236;
    gPsd.rtMargin.right = gPsd.rtMinMargin.right = 166;
    gPsd.rtMargin.bottom = gPsd.rtMinMargin.bottom = 236;
}

/// @brief Show the Print dialog and Return a HDC for the selected printer.
///
/// @param hwnd The handle of the dialog.
///
/// @returns HDC The handle of the printer device context.
HDC GetPrinterDC(HWND hwnd)
{
    PRINTDLG pdlg;
    memset(&pdlg, 0, sizeof(PRINTDLG));
    pdlg.lStructSize = sizeof(PRINTDLG);
    pdlg.hDevMode = ghDevMode;
    pdlg.hDevNames = gPsd.hDevNames;
    pdlg.hwndOwner = hwnd;
    pdlg.Flags = PD_NOPAGENUMS | PD_NOSELECTION | PD_RETURNDC;
    PrintDlg(&pdlg);
    DoEvents();
    return pdlg.hDC;
}

/// @brief Handle the Print menu item click.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuPrint_Click(HWND hwnd)
{
    HDC hdcPrinter;
    int iWidth;
    int iHeight;
    DOCINFO di;
    DIBSECTION ds;
    LPPLOTTER lpPrintPlotter;

    // Do you have a valid window?
    if (!IsWindow(hwnd))
        return;

    if (IsEmptyString(glpScreenPlotter->plotCommandData))
        return;

    // Get HDC for the selected printer.
    hdcPrinter = GetPrinterDC(hwnd);
    if (!hdcPrinter)
        return;

    lpPrintPlotter = Plotter_Clone(glpScreenPlotter);
    if (NULL == lpPrintPlotter)
    {
        DeleteDC(hdcPrinter);
        return;
    }

    // Prepare the DOCINFO.
    ZeroMemory(&di, sizeof(di));
    di.cbSize = sizeof(di);
    di.lpszDocName = _T("Snap Shot");

    // Initialize the print job.
    if (StartDoc(hdcPrinter, &di) > 0)
    {
        // Prepare to send a page.
        if (StartPage(hdcPrinter) > 0)
        {
            // Antiailas plot to level that looks good on paper
            lpPrintPlotter->antiAliasing = x4;
            lpPrintPlotter->desiredPlotSize.cx = 1074;
            lpPrintPlotter->desiredPlotSize.cy = 778;

            HBITMAP memBMP = Plotter_Plot(lpPrintPlotter);

            // Retrieve the information describing the surface.
            if (memBMP != NULL)
            {
                GetObject(memBMP, sizeof(ds), &ds);

                // Get the resolution of the printer device in square pixels
                iWidth = GetDeviceCaps(hdcPrinter, HORZRES);
                iHeight = GetDeviceCaps(hdcPrinter, VERTRES);

                // Maintain aspect ratio of printed image
                if (iHeight > iWidth)
                {
                    //
                    // Page is taller than it is wide (portrait mode)
                    //
                    // Scale the height to conform to the client area's size
                    //
                    iHeight = iWidth * ds.dsBm.bmHeight / ds.dsBm.bmWidth;
                }

                // Print the contents of the surface.
                HDC memDC = CreateCompatibleDC(hdcPrinter);
                HBITMAP hbmOld = (HBITMAP)SelectObject(memDC, memBMP);

                StretchBlt(hdcPrinter, gPsd.rtMargin.left, gPsd.rtMargin.top, iWidth - (gPsd.rtMargin.right + gPsd.rtMargin.left), iHeight - (gPsd.rtMargin.bottom + gPsd.rtMargin.top), memDC, 0, 0, ds.dsBm.bmWidth, ds.dsBm.bmHeight, SRCCOPY);

                SelectObject(memDC, hbmOld);
                DeleteDC(memDC);
            }
            // Let the driver know the page is done.
            EndPage(hdcPrinter);
        }
        // Let the driver know the document is done.
        EndDoc(hdcPrinter);
    }
    // Clean up the objects you created.
    Plotter_Destroy(lpPrintPlotter);    // Also deletes memBMP
    DeleteDC(hdcPrinter);
}

/// @brief Handle the Page Setup menu item click .
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuPageSetup_Click(HWND hwnd)
{
    //Set the structure size
    gPsd.lStructSize = sizeof(PAGESETUPDLG);
    //Set the owner window
    gPsd.hwndOwner = hwnd;
    //Set the application instance
    gPsd.hInstance = ghInstance;
    //Set the hDevMode to customized settings
    gPsd.hDevMode = ghDevMode;
    //No extra flags
    gPsd.Flags = PSD_MARGINS;
    //Show the pagesetup dialog
    PageSetupDlg(&gPsd);
}

#pragma endregion mnuPrint

#pragma region mnuGetPlot

/// @brief Read plot data from an instrument by simulating the response of a plotter
///         in order to get the HPGL data from the instrument.
///
/// @par Comments:
///         The method used to get Plot data out of the 8751A and 4396A Network Analyzers involves
///         emulating an HP7470 plotter and is loosly based on John Miles open source 7470 plotter
///         emulator passive listener mode.
///         Link: http://www.speakeasy.org/~jmiles1/ke5fx/gpib/7470.htm
///
/// @param pInstr A pointer to a Virtual Instrument connection.
/// @param Plotter_vi A virtual instrument id for this application.
///
/// @returns LPSTR A pointer to a temporary buffer of raw HPGL commands.
static LPSTR GetPlotData(LPVINSTRUMENT pInstr, ULONG Plotter_vi)
{
    static LPSTR PlotData;
    PlotData = ";";
    ULONG actual;

    int status;
    int bufSize = MAX_PATH;
    char buf[bufSize];

    //initialize "OS"
    LPSTR OS_status = "24";
    BOOL replied;

    __try   //to start up the virtual plotter and Listen
    {
        while (1)
        {
            //Read data from the bus until there is nothing more to read.
            status = viRead(Plotter_vi, (UCHAR *)buf, bufSize, &actual);

            Sleep(1);   //Prevent not ready "pinch wheels up" error

            if (status < VI_SUCCESS)    //Read timed out or there was an error
            {
                //data transfer from this instrument terminates with VI_ERROR_TMO
                if (status == VI_ERROR_TMO)
                {
                    break;  //timeout occured read operation compleated
                }
                else if (status == VI_ERROR_USER_BUF)
                {
                    // Cycle the vi to clear up VISA bug
                    viClose(Plotter_vi);
                    viOpen(pInstr->rm, Join(2, StrToken(pInstr->address, ":", 1), "::INTFC"), 0, 1000, &Plotter_vi);
                    viClose(Plotter_vi);
                    return FALSE;
                }
                else
                {
                    CheckStatus(Plotter_vi, status);
                    __leave;
                }
            }   // if (Read_Status < VI_SUCCESS)

            // NULL terminate the string
            buf[actual] = 0;

            //Each time a successfull read takes place it is necessary to analyze the data.
            //First we must extract the result string from the buffer.
            //Trim spaces, semicolons, end-of-text (ETX), and line feed from formatting.
            //Remove any extra end-of-text (ETX) from result string.
            StrStrip(buf);

            //Check result string for certain specific mnemonics and handle them individually.

            //Failing to write responses to the mnemonics onto the bus will cause a timeout
            //to occur resulting in an incomplete transmission of data.
            if (!IsEmptyString(buf))
            {
                _strupr(buf);   //Convert to uppercase

                replied = FALSE;
                if (0 == strncmp(buf, "OE", 2))
                {
                    WriteLine(Plotter_vi, "0");
                    replied = TRUE;
                }
                else if (0 == strncmp(buf, "OH", 2))
                {
                    WriteLine(Plotter_vi, "0,0,10000,7500");
                    replied = TRUE;
                }
                else if (0 == strncmp(buf, "OI", 2))
                {
                    WriteLine(Plotter_vi, "7470A");
                    replied = TRUE;
                }
                else if (0 == strncmp(buf, "OP", 2))
                {
                    WriteLine(Plotter_vi, "0,0,10000,7500");
                    replied = TRUE;
                }
                else if (0 == strncmp(buf, "OS", 2))
                {
                    WriteLine(Plotter_vi, OS_status);
                    OS_status = "16";
                    replied = TRUE;
                }
                else    //default
                {
                    if (!replied)
                    {
                        PlotData = Join(3, PlotData, buf, ";");
                        if ((NULL != strstr(buf, "SP;")) || (NULL != strstr(buf, "SP0;")))
                            break;  //read operation compleated
                    }
                }
            }   //if(str_cmp(result,NULL)!=0)
        }   //while(1)
    }   //__try
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
        return "";
    }
    return PlotData;
}

/// @brief Get a plot from an HP8711A and render it to the screen.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
void getScreenPlot8711A(HWND hwnd)
{
    LPSTR PlotData;
    PlotData = ";";

    ULONG actual;
    ULONG Plotter_vi;

    int status, pltAddress;
    int bufSize = MAX_PATH;
    char buf[bufSize];

    BOOL initialized = FALSE;
    DWORD delay = 500;  //msec delay
    __try
    {
        //Open an Interface session "Plotter_vi" based on gInstrument.address
        status = viOpen(gInstrument.rm, Join(2, StrToken(gInstrument.address, ":", 1), "::INTFC"), 0, 500, &Plotter_vi);
        CheckStatus(Plotter_vi, status);

        //Configure device output to Plot HPGL
        WriteLine(gInstrument.vi, "HCOP:DEV2:COL ON");
        WriteLine(gInstrument.vi, "HCOP:DEV2:LANG HPGL; PORT GPIB");

        //Request the Plotter address from the instrument's configuration so that
        //it can be used to set the Plotter's primary bus address.
        WriteLine(gInstrument.vi, "SYST:COMM:GPIB:HCOP:ADDR?");
        Sleep(delay);

        pltAddress = atoi(Read(gInstrument.vi, bufSize));

        //set plotter address
        SetPrimaryAddress(Plotter_vi, pltAddress);
        Sleep(2 * delay);   //Long Delay for fast GPIB Card

        //request PLOT
        WriteLine(gInstrument.vi, "HCOP;*WAI"); //plot
        Sleep(delay);

        PassControl(Plotter_vi, gInstrument.address);
        Sleep(delay);

        //Due to differences in data acquisition we will not use the GetPlotData method
        //instead, the following is an adaptation of that method.
        while (1)
        {
            //instrument sends after initial attempt to read
            //first read causes timeout and returns nothing
            if (!initialized)
            {
                viRead(Plotter_vi, (UCHAR *)buf, bufSize, &actual);
                initialized = TRUE;
            }

            status = viRead(Plotter_vi, (UCHAR *)buf, bufSize, &actual);
            Sleep(1);   //Prevent not ready "pinch wheels up" error

            if (status < VI_SUCCESS)    //Read timed out or there was an error
            {
                //data transfer from this instrument terminates with VI_ERROR_TMO
                if (status == VI_ERROR_TMO)
                {
                    //Get and format Whatever is left in buffer, add it to Data arraylist.
                    buf[actual] = 0;
                    StrStrip(buf);
                    PlotData = Join(2, PlotData, buf);
                    break;  //timeout occured read operation compleated
                }
                else
                {
                    CheckStatus(Plotter_vi, status);
                    __leave;
                }
            }   // if (Read_Status < VI_SUCCESS)

            //Trim spaces, semicolons, end-of-text (ETX), and line feed from formatting.
            //Remove any extra end-of-text (ETX) from result string and add it to Data arraylist.
            buf[actual] = 0;    //Null terminate
            StrStrip(buf);
            PlotData = Join(2, PlotData, buf);
        }   //while
    }   //__try

    __finally
    {
        //Reset GPIB
        SendIFC(Plotter_vi);

        //Release Plotter
        viClose(Plotter_vi);

        //Restore local mode...
        SendGoToLocal(gInstrument.vi);
    }
    //Skip next section if we did not get plot
    if (IsEmptyString(PlotData))
        return;

    // Assign the display window
    glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

    //Set the data source to the data returned from GetHPGLPlotData
    glpScreenPlotter->plotCommandData = PlotData;

    //This paints the image to the PictureBox
    Static_SetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP, Plotter_Plot(glpScreenPlotter));
}

/// @brief Get a plot from an HP8711A and render it to the screen.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
void getScreenPlot8751A(HWND hwnd)
{
    // This sub handles both the 8751A and the 4396A

    LPSTR PlotData;
    ULONG Plotter_vi;
    int status, pltAddress;
    int bufSize = MAX_PATH;
    char buf[bufSize];

    DWORD delay = 500;  //msec delay
    __try
    {
        //Open an Interface session "Plotter_vi" based on gInstrument.address
        status = viOpen(gInstrument.rm, Join(2, StrToken(gInstrument.address, ":", 1), "::INTFC"), 0, 1000, &Plotter_vi);
        CheckStatus(Plotter_vi, status);

        //Make sure that the instrument's address of controller property
        //is the same as the instrument's address so that there won't be
        //any strange errors when Pass Control is issued
        WriteLine(gInstrument.vi, Join(2, "ADDRCONT ", StrToken(gInstrument.address, ":", 3)));
        Sleep(delay);

        //Request the Plotter address from the instrument's configuration so that
        //it can be used to set the Plotter's primary bus address.
        WriteLine(gInstrument.vi, "ADDRPLOT?");
        Sleep(delay);

        pltAddress = atoi(Read(gInstrument.vi, bufSize));

        //set plotter address
        SetPrimaryAddress(Plotter_vi, pltAddress);
        Sleep(2 * delay);   //Long Delay for fast GPIB Card

        //request PLOT
        WriteLine(gInstrument.vi, "PLOT");
        Sleep(delay);   //Delay for 4396A

        PassControl(Plotter_vi, gInstrument.address);
        Sleep(delay);   //Delay for 4396A

        //Read the plot data
        PlotData = GetPlotData(&gInstrument, Plotter_vi);
    }   //__try

    __finally
    {
        //Reset GPIB
        SendIFC(Plotter_vi);

        //Release Plotter
        viClose(Plotter_vi);

        //Restore local mode...
        SendGoToLocal(gInstrument.vi);
    }
    //Skip next section if we did not get plot
    if (IsEmptyString(PlotData))
        return;

    // Assign the display window
    glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

    //Set the data source to the data returned from GetHPGLPlotData
    glpScreenPlotter->plotCommandData = PlotData;

    //This paints the image to the PictureBox
    Static_SetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP, Plotter_Plot(glpScreenPlotter));
}

/// @brief Get a plot from a Tektronix TDS420 and render it to the screen.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
void getScreenPlotTDSxxx(HWND hwnd, ViUInt32 bufferSize)
{
    LPSTR PlotData = "";
    char oldSettings[MAX_PATH];

    __try
    {
        //Get current settings
        WriteLine(gInstrument.vi, "HARDC?");
        LPSTR strRes = Read(gInstrument.vi, MAX_PATH);
        if (!IsEmptyString(strRes))
        {
            LPSTR *cmd;
            INT count;
            Split(strRes, ";", &cmd, &count);
            sprintf(oldSettings, "HARDC:FORM %s\nHARDC:PORT %s\nHARDC:LAY %s\n", cmd[0], cmd[1], cmd[2]);
            FreeSplitList(cmd);
        }

        //Write desired settings
        WriteLine(gInstrument.vi, "HARDC:FORM HPG\nHARDC:PORT GPI\nHARDC:LAY LAN\n");

        //Request plot
        WriteLine(gInstrument.vi, "HARDC STAR");

        ViStatus status;
        ViUInt32 actual;
        //ViUInt32 bufferSize = 2048;
        ViByte buf[bufferSize];
        while (1)
        {
            // Read the results
            status = viRead(gInstrument.vi, buf, bufferSize, &actual);

            Sleep(100);

            // NULL terminate the string
            buf[actual] = 0;

            if ((VI_ERROR_TMO == status) || IsEmptyString(buf))
                break;  //No more bytes to read - all done

            if (status >= VI_SUCCESS)
            {
                PlotData = Join(2, PlotData, buf);
            }
        }
    }
    __finally
    {
        // Restore previous instrument settings...
        WriteLine(gInstrument.vi, oldSettings);

        //Restore local mode...
        SendGoToLocal(gInstrument.vi);
    }

    //Skip next section if we did not get plot
    if (IsEmptyString(PlotData))
        return;

    // Assign the display window
    glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

    //Set the data source to the data returned from GetHPGLPlotData
    glpScreenPlotter->plotCommandData = PlotData;

    //This paints the image to the PictureBox
    Static_SetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP, Plotter_Plot(glpScreenPlotter));
}

/// @brief Get a plot from an HP 8753C and render it to the screen.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
void getScreenPlot8753C(HWND hwnd)
{
    LPSTR PlotData = "";
    int bufSize = MAX_PATH;
    char buf[bufSize];
    int oldWarn, oldDone;

    __try
    {
        //Get current settings
        WriteLine(gInstrument.vi, "BEEPWARN?");
        oldWarn = atoi(Read(gInstrument.vi, bufSize));

        WriteLine(gInstrument.vi, "BEEPDONE?");
        oldDone = atoi(Read(gInstrument.vi, bufSize));

        //KEY 34 = "Entry Off" removes any error message from screen"
        WriteLine(gInstrument.vi, "KEY 34");
        Sleep(50);

        WriteLine(gInstrument.vi, "OUTPPLOT");  //Faster than "PLOT"

        for (int count = 0; count <= 4; count++)
        {
            if (count < 4)  //First three reads = "initial plot commands"
            {
                strncpy(buf, Read(gInstrument.vi, bufSize), bufSize);
                PlotData = Join(2, PlotData, buf);
            }
            else
            {
                bufSize = 150000;
                char buf[bufSize];

                //Read remaining byte data
                strncpy(buf, Read(gInstrument.vi, bufSize), bufSize);
                PlotData = Join(2, PlotData, buf);
            }
        }
    }
    __finally
    {
        // Restore previous instrument settings...
        char cmdStr[MAX_PATH];
        sprintf(cmdStr, "BEEPWARN%d; BEEPDONE%d", oldWarn, oldDone);
        WriteLine(gInstrument.vi, cmdStr);

        //Restore local mode...
        SendGoToLocal(gInstrument.vi);
    }
    //Skip next section if we did not get plot
    if (IsEmptyString(PlotData))
        return;

    // Assign the display window
    glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

    //Set the data source to the data returned from GetHPGLPlotData
    glpScreenPlotter->plotCommandData = PlotData;

    //This paints the image to the PictureBox
    Static_SetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP, Plotter_Plot(glpScreenPlotter));
}

/// @brief Get a plot from an HP 8753D and render it to the screen.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
void getScreenPlot8753D(HWND hwnd)
{
    LPBYTE PlotData;
    ULONG count;

    int bufSize = MAX_PATH;
    char buf[bufSize];

    int readBuff = 150000;
    int oldWarn, oldDone, oldStamp;

    __try
    {
        //Get current settings
        WriteLine(gInstrument.vi, "BEEPWARN?");
        oldWarn = atoi(Read(gInstrument.vi, bufSize));

        WriteLine(gInstrument.vi, "BEEPDONE?");
        oldDone = atoi(Read(gInstrument.vi, bufSize));

        WriteLine(gInstrument.vi, "TIMESTAM?");
        oldStamp = atoi(Read(gInstrument.vi, bufSize));

        WriteLine(gInstrument.vi, "TIMESTAM OFF");

        //KEY 34 = "Entry Off" removes any error message from screen"
        WriteLine(gInstrument.vi, "KEY 34");
        Sleep(50);

        WriteLine(gInstrument.vi, "OUTPPLOT");  //Faster than "PLOT"

        //Read the byte data
        PlotData = ReadBytes(gInstrument.vi, readBuff, &count);
        //Null terminate
        PlotData[count] = '\0';
    }
    __finally
    {
        // Restore previous instrument settings...
        char cmdStr[MAX_PATH];
        sprintf(cmdStr, "BEEPWARN%d; BEEPDONE%d; TIMESTAM%d", oldWarn, oldDone, oldStamp);
        WriteLine(gInstrument.vi, cmdStr);

        //Restore local mode...
        SendGoToLocal(gInstrument.vi);
    }
    //Skip next section if we did not get plot
    if (IsEmptyString((LPSTR)PlotData))
        return;

    // Assign the display window
    glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

    //Set the data source to the data returned from GetHPGLPlotData
    glpScreenPlotter->plotCommandData = (LPSTR)PlotData;

    //This paints the image to the PictureBox
    Static_SetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP, Plotter_Plot(glpScreenPlotter));
}

/// @brief Handles the MNU_OPENCONFIG click event.
///
/// @param hwnd  Handle of dialog.
///
/// @returns VOID.
static VOID MnuOpenConfig_Click(HWND hwnd)
{
    //Memory leak investigation results:
    // I thought that I had a leak here somewhere.  After thoroughly
    // reviewing wizard code I found and plugged some small leaks that 
    // normally would likely never be noticed because of the way this 
    // utility is normally used.
    //
    //Application still apeared to leak somewhere and I isolated it to 
    // GetOpenFileName().  Each time it's called, it appears to leak 
    // appx 400k bytes.  However if the application sits unused for a 
    // while the allocation gradually decreases.  A look around the web 
    // confirmed that I was not alone in this observation.
    //
    //Turns out the GetOpenFilename() API will load up a bunch of shell code 
    // in order to handle the dialog with all of its functionality. This 
    // results in loading multiple DLLs, creating a bunch of threads, 
    // and generally sending your memory utilization extremely high.
    //
    //Unfortunately it doesn't look like there is a way to reclaim these 
    // resources which remain in the process for performance reasons.
    //
    //In the end it was nice to know that I wasn't going mad after all.
    __try
    {
        TCHAR filename[MAX_PATH] = {0};
        OPENFILENAME ofn;
        memset(&ofn, 0, sizeof(ofn));
        ofn.lStructSize = sizeof(ofn);
        ofn.hwndOwner = hwnd;
        ofn.lpstrFilter = _T("Instrument Snapshot Configuration (*.cfg)\0*.cfg\0All Files (*.*)\0*.*\0");
        ofn.lpstrTitle = _T("Open configuration file");
        ofn.lpstrInitialDir = gConfigDirectory;
        ofn.lpstrFile = filename;
        ofn.nMaxFile = MAX_PATH;
        ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
        ofn.lpstrDefExt = _T("cfg");
        if (GetOpenFileName(&ofn))
        {
            HWND hGrid = GetDlgItem(hwnd,IDC_GRID);
            DoEvents();
            PlotterConfig_LoadFromFile(glpScreenPlotter, filename);
            Plotter_UpdateGrid(glpScreenPlotter, hGrid); //Display only selected properties
            PropGrid_SetCurSel(hGrid,0);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
    }
}

/// @brief Handles the MNU_SAVECONFIG click event.
///
/// @param hwnd  Handle of dialog.
///
/// @returns VOID.
static VOID MnuSaveConfig_Click(HWND hwnd)
{
    __try
    {
        OPENFILENAME ofn;
        TCHAR filename[MAX_PATH] = {0};
        memset(&ofn, 0, sizeof(ofn));
        ofn.lStructSize = sizeof(ofn);
        ofn.hwndOwner = hwnd;
        ofn.lpstrFilter = _T("Instrument Snapshot Configuration (*.cfg)\0*.cfg\0All Files (*.*)\0*.*\0");
        ofn.lpstrTitle = _T("Save configuration file");
        ofn.lpstrInitialDir = gConfigDirectory;
        ofn.lpstrFile = filename;
        ofn.nMaxFile = MAX_PATH;
        ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
        ofn.lpstrDefExt = _T("cfg");
        if (GetSaveFileName(&ofn))
        {
            DoEvents(); // refresh screen
            PlotterConfig_SaveToFile(glpScreenPlotter, filename);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
    }
}

/// @brief Handle the Get Plot menu item click.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuGetPlot_Click(HWND hwnd)
{
    HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
    HMENU hMenu = GetMenu(hwnd);
    EnableMenuItem(hMenu, MNU_GETPLOT, MF_GRAYED);
    EnableMenuItem(hMenu, MNU_REFRESHPLOT, MF_GRAYED);
    EnableMenuItem(hMenu, MNU_PRINT, MF_GRAYED);
    EnableMenuItem(hMenu, MNU_SAVE, MF_GRAYED);

    ToolBar_EnableButton(ghToolBar, MNU_GETPLOT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_REFRESHPLOT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_PRINT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_SAVE, FALSE);

    DrawMenuBar(hwnd);  // refresh does not redraw this area of window
    __try
    {
        // Reset PictureBox
        Static_SetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP, NULL);
        Refresh(hwnd);

        while (1) //SelectCase
        {
            if (0 == _tcsnicmp(gInstrument.model, _T("8711A"), 5))
            {
                getScreenPlot8711A(hwnd);
                break;
            }
            if (0 == _tcsnicmp(gInstrument.model, _T("8751A"), 5))
            {
                getScreenPlot8751A(hwnd);
                break;
            }
            if (0 == _tcsnicmp(gInstrument.model, _T("8753C"), 5))
            {
                getScreenPlot8753C(hwnd);
                break;
            }
            if (0 == _tcsnicmp(gInstrument.model, _T("8753D"), 5))
            {
                getScreenPlot8753D(hwnd);
                break;
            }
            if (0 == _tcsnicmp(gInstrument.model, _T("4396A"), 5))
            {
                getScreenPlot8751A(hwnd);
                break;
            }
            if (0 == _tcsnicmp(gInstrument.model, _T("TDS 420"), 7)
				|| 0 == _tcsnicmp(gInstrument.model, _T("TDS 460A"), 8)
				|| 0 == _tcsnicmp(gInstrument.model, _T("TDS 640A"), 8)) 
            {
                getScreenPlotTDSxxx(hwnd, 2048);
                break;
            }
			if (0 == _tcsnicmp(gInstrument.model, _T("TDS 754D"), 8)
				|| 0 == _tcsnicmp(gInstrument.model, _T("TDS 754C"), 8)
				|| 0 == _tcsnicmp(gInstrument.model, _T("TDS 740"), 7))
			{
                getScreenPlotTDSxxx(hwnd, 4096);	//Bigger buffer for color HPGL
                break;
            }

            // case else
            {
                MessageBox(NULL, _T("No such model number supported"), _T("Error"), MB_OK | MB_ICONERROR | MB_TOPMOST);
            }
            break;

        }   //while(1) //SelectCase

    }   //__try
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
    }

    SetCursor(hCur); // restore default cursor

    EnableMenuItem(hMenu, MNU_GETPLOT, MF_ENABLED);
    ToolBar_EnableButton(ghToolBar, MNU_GETPLOT, TRUE);

    if(NULL != glpScreenPlotter->plotCommandData)
    {
        EnableMenuItem(hMenu, MNU_REFRESHPLOT, MF_ENABLED);
        EnableMenuItem(hMenu, MNU_PRINT, MF_ENABLED);
        EnableMenuItem(hMenu, MNU_SAVE, MF_ENABLED);

        ToolBar_EnableButton(ghToolBar, MNU_REFRESHPLOT, TRUE);
        ToolBar_EnableButton(ghToolBar, MNU_PRINT, TRUE);
        ToolBar_EnableButton(ghToolBar, MNU_SAVE, TRUE);
    }

    DrawMenuBar(hwnd);  // refresh does not redraw this area of window
}

/// @brief Handle the MNU_REFRESHPLOT menu item click.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuRefreshPlot_Click(HWND hwnd)
{
    //Skip next section if we did not get plot
    if (IsEmptyString(glpScreenPlotter->plotCommandData))
        return;

    // Assign the display window
    glpScreenPlotter->desiredPlotSize = GetDlgItemSize(hwnd, IDC_PICTUREBOX);

    //This paints the image to the PictureBox
    Static_SetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP, Plotter_Plot(glpScreenPlotter));
}

#pragma endregion mnuGetPlot

/// @brief Read HPGL file.
///
/// @returns VOID.
static VOID ReadHPGL(LPTSTR szFileName)
{
    HANDLE hFile;
    hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ,
                        NULL, OPEN_EXISTING, 0, NULL);

    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD numread, length;
        if(0 == (length = GetFileSize(hFile, 0)))
            return;
        length += 1;

        BYTE buf[length];
        ReadFile(hFile, &buf, NELEMS(buf), &numread, 0);
		glpScreenPlotter->plotCommandData = Join(1,buf);
    }
    CloseHandle(hFile);
}

/// @brief Handle the Save menu item click .
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuImportHPGL_Click(HWND hwnd)
{
    HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
    HMENU hMenu = GetMenu(hwnd);
	BOOL fGetPlotGreyed = MF_GRAYED & GetMenuState(hMenu, MNU_GETPLOT, MF_BYCOMMAND);

    EnableMenuItem(hMenu, MNU_GETPLOT, MF_GRAYED);
    EnableMenuItem(hMenu, MNU_REFRESHPLOT, MF_GRAYED);
    EnableMenuItem(hMenu, MNU_PRINT, MF_GRAYED);
    EnableMenuItem(hMenu, MNU_SAVE, MF_GRAYED);

    ToolBar_EnableButton(ghToolBar, MNU_GETPLOT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_REFRESHPLOT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_PRINT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_SAVE, FALSE);

    DrawMenuBar(hwnd);  // refresh does not redraw this area of window

    __try
    {
        TCHAR filename[MAX_PATH] = {0};
        OPENFILENAME ofn;
        memset(&ofn, 0, sizeof(ofn));
        ofn.lStructSize = sizeof(ofn);
        ofn.hwndOwner = hwnd;
        ofn.lpstrFilter = "Raw HPGL (*.plt)\0*.plt\0All Files (*.*)\0*.*\0";
        ofn.lpstrTitle = "Open HPGL plot file";
        ofn.lpstrInitialDir = gPlotDirectory;;
        ofn.lpstrFile = filename;
        ofn.nMaxFile = MAX_PATH;
        ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
        ofn.lpstrDefExt = _T("plt");
        if (GetOpenFileName(&ofn))
        {
            ReadHPGL(filename);
			MnuRefreshPlot_Click(hwnd);
        }
    }
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
    }

    SetCursor(hCur); // restore default cursor

	if(!fGetPlotGreyed)
	{
    	EnableMenuItem(hMenu, MNU_GETPLOT, MF_ENABLED);
		ToolBar_EnableButton(ghToolBar, MNU_GETPLOT, TRUE);
	}

    if(NULL != glpScreenPlotter->plotCommandData)
    {
        EnableMenuItem(hMenu, MNU_REFRESHPLOT, MF_ENABLED);
        EnableMenuItem(hMenu, MNU_PRINT, MF_ENABLED);
        EnableMenuItem(hMenu, MNU_SAVE, MF_ENABLED);

        ToolBar_EnableButton(ghToolBar, MNU_REFRESHPLOT, TRUE);
        ToolBar_EnableButton(ghToolBar, MNU_PRINT, TRUE);
        ToolBar_EnableButton(ghToolBar, MNU_SAVE, TRUE);
    }

    DrawMenuBar(hwnd);  // refresh does not redraw this area of window
}

/// @brief Write HPGL text to file.
///
/// @returns VOID.
static VOID WriteHPGL(LPTSTR szFileName)
{
	INT iLen = strlen(glpScreenPlotter->plotCommandData) + 1;
	HANDLE hFile;	// file handle for opened file
	DWORD dwBytesWritten;

	hFile = CreateFile(szFileName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL);

	if (INVALID_HANDLE_VALUE == hFile)
	{
		MessageBox(NULL, "Unable to open file", "OpenFileError", MB_OK | MB_ICONERROR | MB_TOPMOST);
		return;
	}
	WriteFile(hFile, glpScreenPlotter->plotCommandData, iLen, &dwBytesWritten, NULL);

	CloseHandle(hFile);
}

/// @brief Handle the Save menu item click .
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuSavePlot_Click(HWND hwnd)
{
	OPENFILENAME ofn;
	TCHAR szFileName[MAX_PATH] = { 0 };

	ZeroMemory(&ofn, sizeof(ofn));

	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = hwnd;
	ofn.lpstrFilter = "Png Image (*.png)\0*.png\0Gif Image (*.gif)\0*.gif\0JPeg Image (*.jpg)\0*.jpg\0Tiff Image (*.tif)\0*.tif\0Bitmap Image (*.bmp)\0*.bmp\0Raw HPGL (*.plt)\0*.plt\0All Files (*.*)\0*.*\0";
	ofn.lpstrTitle = "Save an Image File";
	ofn.lpstrFile = szFileName;
	ofn.lpstrInitialDir = gPlotDirectory;
	ofn.nMaxFile = MAX_PATH;
	ofn.Flags = OFN_EXPLORER | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
	ofn.lpstrDefExt = "png";

	if (GetSaveFileName(&ofn))
	{
		DoEvents();	// refresh screen

		// If the file name is not an empty string open it for saving.
		if (0 != _tcsncmp(szFileName, _T(""), NELEMS(szFileName)))
		{
			HBITMAP hbmp = Static_GetImage(GetDlgItem(hwnd, IDC_PICTUREBOX), IMAGE_BITMAP);

			if (NULL != hbmp)
			{
				if (6 == ofn.nFilterIndex)	//Raw HPGL
				{
					WriteHPGL(szFileName);
				}
				else
				{
					LPIMAGEFILE lpi = New_ImageFile();
					if (NULL != lpi)
					{
						// file type selected in the dialog box.
						// NOTE that the FilterIndex property is one-based.
						switch (ofn.nFilterIndex)
						{
							case 5:	//bmp
							{
								ImageFile_SaveBMP(lpi, hbmp, szFileName);
							}
							case 4:	//tiff
							{
								BOOL fCompressLZW = TRUE;
								ImageFile_SaveTIFF(lpi, hbmp, fCompressLZW, szFileName);
							}
								break;
							case 3:	//jpg
							{
								UINT uQuality = 100;
								ImageFile_SaveJPEG(lpi, hbmp, uQuality, szFileName);
							}
								break;
							case 2:	//gif
							{
								ImageFile_SaveGIF(lpi, hbmp, szFileName);
							}
								break;
							case 1:	//png
								//fallthrough
							default:
							{
								ImageFile_SavePNG(lpi, hbmp, szFileName);
							}
						}
						ImageFile_Destroy(lpi);
					}	//if(NULL != lpi)
				}
			}
		}
	}
}

/// @brief Handle the Connection menu item click .
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID MnuConnection_Click(HWND hwnd)
{
    HCURSOR hCur = SetCursor(LoadCursor(NULL, IDC_WAIT));
    __try
    {
        HMENU mnuMain = GetMenu(hwnd);

        // Set the virtual instrument properties for this connection 
        gInstrument.supportedModels = supportedModels;
        gInstrument.supportedModelsCount = NELEMS(supportedModels) + 1;
        gInstrument.instrumentClass = "Supported Instrument";

        // Show browse dialog object to get instrument address and vi.
        DlgConnect_Show(ghInstance, hwnd, &gInstrument);

        // Verify connection
        if (!gInstrument.isConnected)
        {
            EnableMenuItem(mnuMain, MNU_GETPLOT, MF_GRAYED);
            EnableMenuItem(mnuMain, MNU_REFRESHPLOT, MF_GRAYED);
            EnableMenuItem(mnuMain, MNU_PRINT, MF_GRAYED);
            EnableMenuItem(mnuMain, MNU_SAVE, MF_GRAYED);

            ToolBar_EnableButton(ghToolBar, MNU_GETPLOT, FALSE);
            ToolBar_EnableButton(ghToolBar, MNU_REFRESHPLOT, FALSE);
            ToolBar_EnableButton(ghToolBar, MNU_PRINT, FALSE);
            ToolBar_EnableButton(ghToolBar, MNU_SAVE, FALSE);

            DrawMenuBar(hwnd);  // refresh does not redraw this area of window
        }
        else
        {
            DoEvents(); //Refresh screen

            SendGoToLocal(gInstrument.vi);

            EnableMenuItem(mnuMain, MNU_GETPLOT, MF_ENABLED);
            ToolBar_EnableButton(ghToolBar, MNU_GETPLOT, TRUE);

            DrawMenuBar(hwnd);  // refresh does not redraw this area of window
        }
    }

    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        SetLastError(GetExceptionCode());
        ErrorHandler();
    }
    SetCursor(hCur);    // restore default cursor
}

/// @brief Handle the WM_CLOSE message.
///
/// @param hwnd The handle of the dialog.
///
/// @returns VOID.
static VOID Main_OnClose(HWND hwnd)
{
    Plotter_Destroy(glpScreenPlotter);
    EndDialog(hwnd, 0);
}

/// @brief Handles WM_COMMAND message.
///
/// @param hwnd  Handle of dialog.
/// @param id The id of the sender.
/// @param hwndCtl The hwnd of the sender.
/// @param codeNotify The notification code sent.
///
/// @returns VOID.
static VOID Main_OnCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify)
{
    //If the grid lost focus make sure that what ever property changed get's changed
    PropGrid_SetCurSel(GetDlgItem(hwnd,IDC_GRID),0);

    switch (id)
    {
        case MNU_OPENCONFIG:
            MnuOpenConfig_Click(hwnd);
            break;
        case MNU_SAVECONFIG:
            MnuSaveConfig_Click(hwnd);
            break;
        case MNU_GETPLOT:
            MnuGetPlot_Click(hwnd);
            break;
        case MNU_REFRESHPLOT:
            MnuRefreshPlot_Click(hwnd);
            break;
		case MNU_IMPORT_HPGL:
			MnuImportHPGL_Click(hwnd);
			break;
        case MNU_SAVE:
            MnuSavePlot_Click(hwnd);
            break;
        case MNU_SNAPSHOT_HELP:
            MnuSnapshotHelp_Click(hwnd);
            break;
        case MNU_ABOUT:
            MnuAbout_Click(hwnd, id);
            break;
        case MNU_CONNECTION:
            MnuConnection_Click(hwnd);
            break;
        case MNU_PAGESETUP:
            MnuPageSetup_Click(hwnd);
            break;
        case MNU_PRINT:
            MnuPrint_Click(hwnd);
            break;
        case MNU_EXIT:
            Main_OnClose(hwnd);
            break;
    }
}

/// @brief Handles WM_INITDIALOG message.
///
/// @param hwnd  Handle of dialog.
/// @param hwndFocus The handle of dialog control to have initial focus.
/// @param lParam - An initialization parameter.
///
/// @returns BOOL TRUE to direct Windows to set the keyboard focus to the
///           control given by hwndFocus. Otherwise, it should return FALSE
///           to prevent Windows from setting the default keyboard focus.
static BOOL Main_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
    GetModuleFileName(ghInstance, gParentDirectory, NELEMS(gParentDirectory));

    LPTSTR p = &gParentDirectory[_tcslen(gParentDirectory) + 1];
    while(p && *p != _T('\\'))
        p--;

    *p = _T('\0');
    
    _stprintf(gConfigDirectory, NELEMS(gConfigDirectory),
#ifdef _UNICODE
        _T("%ls\\Configurations"),
#else
        _T("%s\\Configurations"),
#endif
        gParentDirectory);

    if (!DirectoryExists(gConfigDirectory))
        CreateDirectory(gConfigDirectory, NULL);

    _stprintf(gPlotDirectory, NELEMS(gPlotDirectory),
#ifdef _UNICODE
        _T("%ls\\Plots"),
#else
        _T("%s\\Plots"),
#endif
        gParentDirectory);

    if (!DirectoryExists(gPlotDirectory))
        CreateDirectory(gPlotDirectory, NULL);

    InitPrintDevMode();
    glpScreenPlotter = New_Plotter();

    //
    // Dialog menus and tools
    //
    ghToolBar = CreateToolbar_OwnerDrawMenu(hwnd, gfHires ? IDR_BMP_MNUES_HI : IDR_BMP_MNUES);
    if (NULL == ghToolBar)
        return FALSE;

    HMENU mnuMain = GetMenu(hwnd);
    EnableMenuItem(mnuMain, MNU_GETPLOT, MF_GRAYED);
    EnableMenuItem(mnuMain, MNU_REFRESHPLOT, MF_GRAYED);
    EnableMenuItem(mnuMain, MNU_PRINT, MF_GRAYED);
    EnableMenuItem(mnuMain, MNU_SAVE, MF_GRAYED);

    ToolBar_EnableButton(ghToolBar, MNU_GETPLOT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_REFRESHPLOT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_PRINT, FALSE);
    ToolBar_EnableButton(ghToolBar, MNU_SAVE, FALSE);

    DrawMenuBar(hwnd); // refresh does not redraw this area of window

    //
    // Get other dialog item handles
    //
    ghGrid = GetDlgItem(hwnd, IDC_GRID);
    ghPictureBox = GetDlgItem(hwnd, IDC_PICTUREBOX);

    //
    // Configure grid
    //
    RECT rc = { 0 };
    GetClientRect(ghGrid, &rc);
    guDrag = rc.right + 5;

    Plotter_UpdateGrid(glpScreenPlotter, ghGrid);
    ShowWindow(ghGrid, SW_SHOW);

    //
    // Other stuff
    //
    InstallWindowStateHandler(hwnd, gParentDirectory);

    return FALSE;
}

#pragma region WM_SIZE and Splitter

/// @brief Handles WM_SIZE message.
///
/// @param hwnd  Handle of dialog.
/// @param state Specifies the type of resizing requested.
/// @param cx The width of client area.
/// @param cy The height of client area.
///
/// @returns VOID.
static VOID Main_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
    FORWARD_WM_SIZE(ghToolBar, state, cx, cy, SendMessage);

    RECT rc = { 0 };
    GetWindowRect(ghToolBar, &rc);
    LONG lToolHeight = HEIGHT(rc);
    MoveWindow(ghGrid, 0, lToolHeight + 1, guDrag - 1, cy - (lToolHeight + 2), TRUE);
    MoveWindow(ghPictureBox, guDrag + 1, lToolHeight + 1, cx - (guDrag + 1), cy - (lToolHeight + 2), TRUE);

    Refresh(hwnd);
}

static VOID Main_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
    if (isOverSlider(x))
    {
        RECT rcWindow;
        GetWindowRect(hwnd, &rcWindow);
        rcWindow.left += 10;
        rcWindow.right -= 10;

        //Capture the mouse & Do not let it leave the dialog boundary
        SetCapture(hwnd);
        ClipCursor(&rcWindow);
        gfDrag = TRUE;


        GetWindowRect(ghToolBar, &rcWindow);

        RECT rcClient;
        GetClientRect(ghGrid, &rcClient);

        nDivTop = HEIGHT(rcWindow) + 2;
        nDivBtm = nDivTop + HEIGHT(rcClient);
        nOldDivX = x;

        HDC hdc = GetDC(hwnd);
        HPEN hOld = SelectObject(hdc, CreatePen(PS_SOLID, 3, 0));
        InvertLine(hdc, x, nDivTop, x, nDivBtm);
        DeleteObject(SelectObject(hdc, hOld));
        ReleaseDC(hwnd, hdc);
    }
}

static VOID Main_OnLButtonUp(HWND hwnd, int x, int y, UINT keyFlags)
{
    if (gfDrag)
    {
        gfDrag = FALSE;
        ReleaseCapture();
        ClipCursor(NULL);

        HDC hdc = GetDC(hwnd);
        HPEN hOld = SelectObject(hdc, CreatePen(PS_SOLID, 3, 0));
        InvertLine(hdc, x, nDivTop, x, nDivBtm);
        DeleteObject(SelectObject(hdc, hOld));
        ReleaseDC(hwnd, hdc);

        //Set the divider position to the new value
        guDrag = x;

        //Trigger a resize
        RECT rc;
        GetClientRect(hwnd, &rc);
        Main_OnSize(hwnd, 0, WIDTH(rc), HEIGHT(rc));

        //Refresh plot (if any)
        MnuRefreshPlot_Click(hwnd);
    }
}

static VOID Main_OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
{
    if (isOverSlider(x))
        SetCursor(LoadCursor(NULL, IDC_SIZEWE));
    else
        SetCursor(LoadCursor(NULL, IDC_ARROW));

    if (gfDrag)
    {
        HDC hdc = GetDC(hwnd);
        HPEN hOld = SelectObject(hdc, CreatePen(PS_SOLID, 3, 0));
        //Remove old divider line
        InvertLine(hdc, nOldDivX, nDivTop, nOldDivX, nDivBtm);
        //Draw new divider line
        InvertLine(hdc, x, nDivTop, x, nDivBtm);
        DeleteObject(SelectObject(hdc, hOld));
        ReleaseDC(hwnd, hdc);

        nOldDivX = x;
    }
}

#pragma endregion   //WM_SIZE and Splitter

/// @brief Handles WM_GETMINMAXINFO message.
///
/// @param hwnd  Handle of dialog.
/// @param lpMinMaxInfo A pointer to a MINMAXINFO structure 
///         that contains the default maximized position and dimensions, 
///         and the default minimum and maximum tracking sizes. An application 
///         can override the defaults by setting the members of this structure.
///
/// @returns VOID.
static VOID Main_OnGetMinMaxInfo(HWND hwnd, LPMINMAXINFO lpMinMaxInfo)
{
    lpMinMaxInfo->ptMinTrackSize.x = 546;   //<-- Min. width (in pixels) of your window
    lpMinMaxInfo->ptMinTrackSize.y = 444;   //<-- Min. height (in pixels) of your window
/*  lpMinMaxInfo->ptMaxTrackSize.x = 640; //<-- Max. width (in pixels) of your window
    lpMinMaxInfo->ptMaxTrackSize.y = 480; //<-- Max. height (in pixels) of your window
    lpMinMaxInfo->ptMaxPosition.x = 0;    //<-- Left position (in pixels) of your window when maximized
    lpMinMaxInfo->ptMaxPosition.y = 0;    //<-- Top position (in pixels) of your window when maximized
 */
}

/// @brief Window procedure for the main dialog.
///
/// @param hwndDlg Handle of the main dialog.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns LRESULT depends on message.
static BOOL CALLBACK Main_DlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        HANDLE_DLGMSG(hwndDlg, WM_HELP, Main_OnHelp);
        HANDLE_DLGMSG(hwndDlg, WM_CLOSE, Main_OnClose);
        HANDLE_DLGMSG(hwndDlg, WM_COMMAND, Main_OnCommand);
        HANDLE_DLGMSG(hwndDlg, WM_INITDIALOG, Main_OnInitDialog);
        HANDLE_DLGMSG(hwndDlg, WM_SIZE, Main_OnSize);
        HANDLE_DLGMSG(hwndDlg, WM_LBUTTONDOWN, Main_OnLButtonDown);
        HANDLE_DLGMSG(hwndDlg, WM_LBUTTONUP, Main_OnLButtonUp);
        HANDLE_DLGMSG(hwndDlg, WM_MOUSEMOVE, Main_OnMouseMove);
        HANDLE_DLGMSG(hwndDlg, WM_GETMINMAXINFO, Main_OnGetMinMaxInfo);
        HANDLE_DLGMSG(hwndDlg, WM_NOTIFY, Main_OnNotify);

        case WM_NOTIFYFORMAT:
#ifdef UNICODE
            return SetDlgMsgResult(hwndDlg, WM_NOTIFYFORMAT, NFR_UNICODE);
#else
            return SetDlgMsgResult(hwndDlg, WM_NOTIFYFORMAT, NFR_ANSI);
#endif
        //// TODO: Add dialog message crackers here...
        default:
            return FALSE;
    }
}

/// @brief Initialize the application.  Register a window class,
///         create and display the main window and enter the
///         message loop.
///
/// @param hInstance Handle to current instance.
/// @param hPrevInstance Handle to previous instance.
/// @param lpszCmdLine Pointer to command line.
/// @param nCmdShow Show state of window.
///
/// @returns INT If the function succeeds, terminating when it receives
///           a WM_QUIT message, it should return the exit value contained in
///           that message’s wParam parameter. If the function terminates
///           before entering the message loop, it should return 0.
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
    INITCOMMONCONTROLSEX icc;
    WNDCLASSEX wcx;

    ghInstance = hInstance;

    /* Initialize common controls. Also needed for MANIFEST's */
    /*
     * TODO: set the ICC_???_CLASSES that you need.
     */
    icc.dwSize = sizeof(icc);
    icc.dwICC = ICC_WIN95_CLASSES /*|ICC_COOL_CLASSES|ICC_DATE_CLASSES|ICC_PAGESCROLLER_CLASS|ICC_USEREX_CLASSES|... */ ;
    InitCommonControlsEx(&icc);
    InitPropertyGrid(hInstance);

    /* Get system dialog information */
    wcx.cbSize = sizeof(wcx);
    if (!GetClassInfoEx(NULL, MAKEINTRESOURCE(32770), &wcx))
        return 0;

    /* Add our own stuff */
    wcx.hInstance = hInstance;
    wcx.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_ICO_MAIN));
    wcx.lpszClassName = _T("SnapShotClass");
    if (!RegisterClassEx(&wcx))
        return 0;

    gfHires = NULL == strstr(lpszCmdLine, "/b");

    /* The user interface is a modal dialog box */
    return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)Main_DlgProc);
}

NewsRe: I added support for a few more scopes Pin
David MacDermot6-Mar-15 11:07
David MacDermot6-Mar-15 11:07 
QuestionFine article Pin
Matt Scarpino6-Feb-15 14:01
mvaMatt Scarpino6-Feb-15 14:01 
SuggestionCheck out this article for more on VISA and instrumentation. Pin
David MacDermot6-Feb-15 6:49
David MacDermot6-Feb-15 6:49 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.