MOLE symbols with interactive maps
MoleSymbolsWindow.cpp
// Copyright 2010 ESRI
// 
// All rights reserved under the copyright laws of the United States
// and applicable international laws, treaties, and conventions.
// 
// You may freely redistribute and use this sample code, with or
// without modification, provided you include the original copyright
// notice and use restrictions.
// 
// See the use restrictions.
// 


#include <math.h>

#include "MoleSymbols.h"
#include "MoleSymbolsWindow.h"


////////////////////////////////////////////////////
// private member functions for MoleSymbolsWindow //
////////////////////////////////////////////////////


// add the named tool to the toolbar control
void MoleSymbolsWindow::addTool (const char *name)
{
    CComBSTR toolID ("esriControlCommands.");
    toolID.Append (name);
    long itemIndex;
    m_ipToolbarControl->AddItem (CComVariant(toolID), 0, -1, VARIANT_FALSE, 0, esriCommandStyleIconOnly, &itemIndex);
}


// create a new point instance and initialize its coordinates
IPointPtr MoleSymbolsWindow::createPoint (double x, double y)
{
    IPointPtr ipPoint (CLSID_Point);
    ipPoint->PutCoords (x, y);
    return ipPoint;
}


// generate a random floating-point number in the specified range [low, high)
double MoleSymbolsWindow::dRandom (double low, double high)
{
#if defined(ESRI_UNIX)
    return low + ((double)random() / (double)RAND_MAX) * (high - low);
#elif defined(ESRI_WINDOWS)
    return low + ((double)rand() / (double)RAND_MAX) * (high - low);
#endif
}


void MoleSymbolsWindow::drawSymbol (IPointPtr ipLocation, const char *sic, bool suppressRefresh)
{
    // the first time we create a symbol, display the wait cursor while MOLE loads up
    QCursor previousCursor (cursor());
    if ( m_firstTime )
        setCursor (QCursor(Qt::WaitCursor));

    IMoleSymbolPtr        ipMoleSymbol    (CLSID_MoleMarkerSymbol);
    IMarkerSymbolPtr      ipMarkerSymbol  (ipMoleSymbol);
    IMarkerElementPtr     ipMarkerElement (CLSID_MarkerElement);
    IElementPtr           ipElement       (ipMarkerElement);
    IActiveViewPtr        ipActiveView;
    IGraphicsContainerPtr ipGraphicsContainer;

    m_ipMapControl->get_ActiveView (&ipActiveView);
    ipActiveView->get_GraphicsContainer (&ipGraphicsContainer);

    // set up a MOLE symbol for the new graphic; minimally validate the symbol ID code
    if ( strlen(sic) == 15 )
        ipMoleSymbol->put_SymbolID (CComBSTR(sic));
    ipMoleSymbol->put_TextLabels (getLabels());

    // to remove the symbol's fill, uncomment this code
    //IMoleMarkerSymbolPtr ipMoleMarkerSymbol (ipMoleSymbol);
    //ipMoleMarkerSymbol->put_ShowFill (VARIANT_FALSE);

    // initialize the marker symbol size property
    double size = m_txtSize->text().toDouble();
    if ( size < 1 ) size = 36;
    ipMarkerSymbol->put_Size (size);

    // create the graphic element for the marker symbol and add it to the map
    ipMarkerElement->put_Symbol (ipMarkerSymbol);
    ipElement->put_Geometry (IGeometryPtr(ipLocation));
    ipGraphicsContainer->AddElement (ipElement, 0);
    if ( ! suppressRefresh )
        ipActiveView->PartialRefresh (esriViewGraphics, NULL, NULL);

    // update the user interface
    if ( m_firstTime )
    {
        setCursor (previousCursor);
        m_firstTime = false;
    }
    ++m_unitCount;
    updateTitle();
}


// get a collection of label properties for a MOLE symbol
IPropertySetPtr MoleSymbolsWindow::getLabels()
{
    IPropertySetPtr ipLabelSet (CLSID_PropertySet);

    // all of the below are supported - comment and uncomment to experiment
    ipLabelSet->SetProperty (CComBSTR("Name"),       CComVariant(CComBSTR("Name")));
    ipLabelSet->SetProperty (CComBSTR("Comment"),    CComVariant(CComBSTR("Comment")));
    //ipLabelSet->SetProperty (CComBSTR("Parent"),     CComVariant(CComBSTR("Parent")));
    //ipLabelSet->SetProperty (CComBSTR("Info"),       CComVariant(CComBSTR("Info")));
    //ipLabelSet->SetProperty (CComBSTR("Strength"),   CComVariant(CComBSTR("Strength")));
    //ipLabelSet->SetProperty (CComBSTR("EvalRating"), CComVariant(CComBSTR("EvalRating")));
    //ipLabelSet->SetProperty (CComBSTR("Location"),   CComVariant(CComBSTR("Location")));
    //ipLabelSet->SetProperty (CComBSTR("Alt_Depth"),  CComVariant(CComBSTR("Alt_Depth")));
    //ipLabelSet->SetProperty (CComBSTR("Speed"),      CComVariant(CComBSTR("Speed")));
    //ipLabelSet->SetProperty (CComBSTR("DTG"),        CComVariant(CComBSTR("DTG")));
    //ipLabelSet->SetProperty (CComBSTR("HQ"),         CComVariant(CComBSTR("HQ")));
    //ipLabelSet->SetProperty (CComBSTR("Quantity"),   CComVariant(CComBSTR("Quantity")));
    //ipLabelSet->SetProperty (CComBSTR("EType"),      CComVariant(CComBSTR("EType")));
    //ipLabelSet->SetProperty (CComBSTR("Effective"),  CComVariant(CComBSTR("Effective")));
    //ipLabelSet->SetProperty (CComBSTR("Signature"),  CComVariant(CComBSTR("Signature")));
    //ipLabelSet->SetProperty (CComBSTR("IFFSIF"),     CComVariant(CComBSTR("IFFSIF")));
    return ipLabelSet;
}


IColorPtr MoleSymbolsWindow::getRandomColor()
{
    // create a random opaque RGB color
    IRgbColorPtr rgb (CLSID_RgbColor);

#if defined(ESRI_UNIX)
    rgb->put_Red   (random() % 256);
    rgb->put_Green (random() % 256);
    rgb->put_Blue  (random() % 256);
#elif defined(ESRI_WINDOWS)
    rgb->put_Red   (rand() % 256);
    rgb->put_Green (rand() % 256);
    rgb->put_Blue  (rand() % 256);
#endif

    return rgb;
}


void MoleSymbolsWindow::moveGraphics (double deltaX, double deltaY)
{
    // move all selected graphics along a delta (change) vector
    printf ("moving delta = (%f, %f)\n", deltaX, deltaY);

    // get reference to graphics container and its selected elements
    IActiveViewPtr              ipActiveView;
    IGraphicsContainerPtr       ipGraphicsContainer;
    IGraphicsContainerSelectPtr ipGraphicsContainerSelect;
    IEnumElementPtr             ipEnumElement;
    IElementPtr                 ipElement;
    IGeometryPtr                ipGeometry;

    m_ipMapControl->get_ActiveView (&ipActiveView);
    ipActiveView->get_GraphicsContainer (&ipGraphicsContainer);
    ipGraphicsContainerSelect = ipGraphicsContainer;
    ipGraphicsContainerSelect->get_SelectedElements (&ipEnumElement);

    // iterate through the selected elements
    ipEnumElement->Reset();
    ipEnumElement->Next (&ipElement);
    while ( ipElement )
    {
        // apply the delta vector to each element's geometry and update it the container
        ipElement->get_Geometry (&ipGeometry);
        ITransform2DPtr(ipGeometry)->Move (deltaX, deltaY);
        ipElement->put_Geometry (ipGeometry);
        ipGraphicsContainer->UpdateElement (ipElement);
        ipEnumElement->Next (&ipElement);
    }
    // refresh the active view
    ipActiveView->PartialRefresh (esriViewGraphics, NULL, NULL);
}


bool MoleSymbolsWindow::selectElements (IPointPtr ipPoint, IActiveViewPtr ipActiveView, IEnvelopePtr ipSelectedBounds)
{
    // this function is written in such a way that it should be pastable
    double x, y;
    ipPoint->QueryCoords (&x, &y);
    printf ("selecting graphics near (%f, %f)\n", x, y);

    IGraphicsContainerPtr       ipGraphicsContainer;
    IGraphicsContainerSelectPtr ipGraphicsContainerSelect;
    IScreenDisplayPtr           ipScreenDisplay;
    IEnumElementPtr             ipEnumElement;
    bool                        selected (false);
    bool                        refreshRequired (false);

    ipActiveView->get_GraphicsContainer (&ipGraphicsContainer);
    ipActiveView->get_ScreenDisplay (&ipScreenDisplay);
    ipGraphicsContainerSelect = ipGraphicsContainer;

    // start with a precise search, and then widen the tolerance if nothing is found
    // (you may need to change these tolerances if using this code in your own application)
    ipGraphicsContainer->LocateElements (ipPoint, 0.0000001, &ipEnumElement);
    if ( ! ipEnumElement )
        ipGraphicsContainer->LocateElements (ipPoint, 0.5, &ipEnumElement);

    // if no elements were selected
    if ( ! ipEnumElement )
    {
        // if the previous selection is nonempty
        long count;
        ipGraphicsContainerSelect->get_ElementSelectionCount (&count);
        if ( count > 0 )
        {
            // clear the selection and refresh the display
            printf ("clearing selection\n");
            ipGraphicsContainerSelect->UnselectAllElements();
            ipSelectedBounds->SetEmpty();
            refreshRequired = true;
        }
        // else do nothing
    }
    else
    {
        // get the extent of the selected elements
        IEnvelopePtr ipEnvelope (CLSID_Envelope);
        IElementPtr ipElement;
        ipEnumElement->Next (&ipElement);
        while ( ipElement )
        {
            // establish selectedBounds as the extent of all selected elements
            ipElement->QueryBounds (ipScreenDisplay, ipEnvelope);
            ipSelectedBounds->Union (ipEnvelope);
            ipEnumElement->Next (&ipElement);
        }
        // add all the newly selected elements to the graphics container's selection
        ipEnumElement->Reset();
        ipGraphicsContainerSelect->SelectElements (ipEnumElement);
        refreshRequired = selected = true;
    }
    // refresh the display if anything has changed
    if ( refreshRequired )
        ipActiveView->PartialRefresh (esriViewGraphics, NULL, NULL);

    // return true if any elements on the display are currently selected; selectedBounds has their extent
    return selected;
}


// put the number of symbols in the title bar, when there are any
void MoleSymbolsWindow::updateTitle()
{
    if ( m_unitCount == 0 )
        setWindowTitle ("MOLE Symbols");
    else if ( m_unitCount == 1 )
        setWindowTitle ("Mole Symbols (1 unit)");
    else
        setWindowTitle (QString("Mole Symbols (%1 units)").arg(m_unitCount));
}


////////////////////////////////////
// Qt slots for MoleSymbolsWindow //
////////////////////////////////////


void MoleSymbolsWindow::add200()
{
    // this one takes a little while, especially when it's the first one chosen
    QCursor previousCursor (cursor());
    if ( m_firstTime )
        setCursor (QCursor(Qt::WaitCursor));

    // create concentric rings of units centered around where the user last clicked
    double centerLon, centerLat;
    m_ipLastMouseClick->QueryCoords (&centerLon, &centerLat);
    const double circleRadiusInRad = 1.0;
    const int numberPerCircle = 10;
    for ( int i = 0; i < 200; ++i )
    {
        // draw a random symbol at the next position in the pattern
        double currentRadius = (i / numberPerCircle) * circleRadiusInRad + circleRadiusInRad;
        double currentAngle = (i % numberPerCircle) *  2.0 * 3.1415926536 / (double)numberPerCircle;
        drawSymbol (
            createPoint(
                centerLon + (currentRadius * sin(currentAngle)),
                centerLat + (currentRadius * cos(currentAngle))
            ),
            getSIC(),
            true
        );
    }
    //refresh the display and restore the cursor
    IActiveViewPtr ipActiveView;
    m_ipMapControl->get_ActiveView (&ipActiveView);
    ipActiveView->PartialRefresh (esriViewGraphics, NULL, NULL);
    setCursor (previousCursor);
}


void MoleSymbolsWindow::addArea()
{
    // create the symbol using a default symbol ID code (obstacle restricted area)
    IMoleSymbolPtr ipMoleSymbol (CLSID_MoleFillSymbol);
    ipMoleSymbol->put_SymbolID (CComBSTR("GUMPOGR-------X"));
    ipMoleSymbol->put_TextLabels (getLabels());
    ipMoleSymbol->put_TextSize (3.0);

    // override the default fill color and outline symbol - these settings are optional
    //ILineSymbolPtr ipLineSymbol (CLSID_SimpleLineSymbol);
    //ipLineSymbol->put_Color (getrandomColor());
    //ipLineSymbol->put_Width (dRandom(1, 5));
    //IFillSymbolPtr ipFillSymbol (ipMoleSymbol);
    //ipFillSymbol->put_Outline (ipLineSymbol);
    //ipFillSymbol->put_Color (getrandomColor());

    // center the polygon somewhere inside the current map extent
    IActiveViewPtr ipActiveView;
    m_ipMapControl->get_ActiveView (&ipActiveView);
    IEnvelopePtr ipExtent;
    ipActiveView->get_Extent (&ipExtent);
    double xMin, yMin, xMax, yMax;
    ipExtent->QueryCoords (&xMin, &yMin, &xMax, &yMax);
    double lat = dRandom (yMin, yMax);
    double lon = dRandom (xMin, xMax);

    // create a new polygon geometry for this symbol (four points in this example)
    // place the four corners somewhere within a specified threshold of the center
    IPointCollectionPtr ipPointCollection (CLSID_Polygon);
    const double threshold = 20;
    ipPointCollection->AddPoint (createPoint(lon, dRandom(lat, lat + threshold)), NULL, NULL);
    ipPointCollection->AddPoint (createPoint(dRandom(lon, lon + threshold), lat), NULL, NULL);
    ipPointCollection->AddPoint (createPoint(lon, dRandom(lat - threshold, lat)), NULL, NULL);
    ipPointCollection->AddPoint (createPoint(dRandom(lon - threshold, lon), lat), NULL, NULL);

    // set up the graphic element with the random geometry
    IFillShapeElementPtr ipFillShapeElement (CLSID_PolygonElement);
    ipFillShapeElement->put_Symbol (IFillSymbolPtr(ipMoleSymbol));
    IElementPtr ipElement (ipFillShapeElement);
    ipElement->put_Geometry (IGeometryPtr(ipPointCollection));

    // add the new element to the map and update the user interface
    IGraphicsContainerPtr ipGraphicsContainer;
    ipActiveView->get_GraphicsContainer (&ipGraphicsContainer);
    ipGraphicsContainer->AddElement (ipElement, 0);
    ipActiveView->PartialRefresh (esriViewGraphics, NULL, NULL);
    ++m_unitCount;
    updateTitle();
}


void MoleSymbolsWindow::addLine()
{
    // create the symbol using a default symbol ID code (fix task line)
    IMoleSymbolPtr ipMoleSymbol (CLSID_MoleLineSymbol);
    ipMoleSymbol->put_SymbolID (CComBSTR("GUTPF---------X"));
    ipMoleSymbol->put_TextLabels (getLabels());
    ipMoleSymbol->put_TextSize (2.0);

    // override the default line color and width - these settings are optional
    //ILineSymbolPtr ipLineSymbol (ipMoleSymbol);
    //ipLineSymbol->put_Color (getrandomColor());
    //ipLineSymbol->put_Width (dRandom(1, 5));

    // place the first endpoint of the line somewhere inside the current map extent
    IActiveViewPtr ipActiveView;
    m_ipMapControl->get_ActiveView (&ipActiveView);
    IEnvelopePtr ipExtent;
    ipActiveView->get_Extent (&ipExtent);
    double xMin, yMin, xMax, yMax;
    ipExtent->QueryCoords (&xMin, &yMin, &xMax, &yMax);
    double lat = dRandom (yMin, yMax);
    double lon = dRandom (xMin, xMax);

    // create a new line geometry for the symbol - this symbol requires two points
    IPointCollectionPtr ipPointCollection (CLSID_Polyline);
    ipPointCollection->AddPoint (createPoint(lon, lat), NULL, NULL);

    // place the second endpoint somewhere within a specified threshold of the first
    const double threshold = 20;
    ipPointCollection->AddPoint (
        createPoint(
            dRandom(lon - threshold, lon + threshold),
            dRandom(lat - threshold, lat + threshold)
        ),
        NULL,
        NULL
    );
    // set up the graphic element with the random geometry
    ILineElementPtr ipLineElement (CLSID_LineElement);
    ipLineElement->put_Symbol (ILineSymbolPtr(ipMoleSymbol));
    IElementPtr ipElement (ipLineElement);
    ipElement->put_Geometry (IGeometryPtr(ipPointCollection));

    // add the new element to the map and update the user interface
    IGraphicsContainerPtr ipGraphicsContainer;
    ipActiveView->get_GraphicsContainer (&ipGraphicsContainer);
    ipGraphicsContainer->AddElement (ipElement, 0);
    ipActiveView->PartialRefresh (esriViewGraphics, NULL, NULL);
    ++m_unitCount;
    updateTitle();
}


void MoleSymbolsWindow::addMoleSymbol (bool checked)
{
    // make this button exclusive both in the Qt toolbar and in the ToolbarControl
    // the primary logic is in OnMouseDown
    printf ("add MOLE symbol = %s\n", (checked ? "checked" : "unchecked"));
    if ( checked )
    {
        m_btnSelect->setChecked (false);
        m_ipToolbarControl->SetBuddyControl (NULL);
        m_ipToolbarControl->put_Enabled (VARIANT_FALSE);
    }
    else
    {
        m_ipToolbarControl->SetBuddyControl (m_ipMapControl);
        m_ipToolbarControl->put_Enabled (VARIANT_TRUE);
    }
}


void MoleSymbolsWindow::moveUnits()
{
    // get reference to graphics container and its selected elements
    IActiveViewPtr              ipActiveView;
    IGraphicsContainerPtr       ipGraphicsContainer;
    IGraphicsContainerSelectPtr ipGraphicsContainerSelect;

    m_ipMapControl->get_ActiveView (&ipActiveView);
    ipActiveView->get_GraphicsContainer (&ipGraphicsContainer);
    ipGraphicsContainerSelect = ipGraphicsContainer;

    // MoveGraphics only applies to units in the selection - this will erase any previous selection
    ipGraphicsContainerSelect->SelectAllElements();
    moveGraphics (0.75, 0.75);
    ipGraphicsContainerSelect->UnselectAllElements();
    ipActiveView->PartialRefresh (esriViewGraphics, NULL, NULL);
    m_ipSelectedBounds->SetEmpty();
}


void MoleSymbolsWindow::select (bool checked)
{
    // make this button exclusive both in the Qt toolbar and in the ToolbarControl
    // the primary logic is in OnMouseDown, OnMouseMove, and OnMouseUp
    printf ("select = %s\n", (checked ? "checked" : "unchecked"));
    if ( checked )
    {
        m_btnAddMoleSymbol->setChecked (false);
        m_ipToolbarControl->SetBuddyControl (NULL);
        m_ipToolbarControl->put_Enabled (VARIANT_FALSE);
    }
    else
    {
        m_ipToolbarControl->SetBuddyControl (m_ipMapControl);
        m_ipToolbarControl->put_Enabled (VARIANT_TRUE);
    }
}


////////////////////////////////////////////////////////
// IMapControlEvents2 implementation - event handlers //
////////////////////////////////////////////////////////


void MoleSymbolsWindow::OnMapReplaced (VARIANT newMap)
{
    // update the window title
    m_unitCount = 0;
    updateTitle();
}


void MoleSymbolsWindow::OnMouseDown (long button, long shift, long x, long y, double mapX, double mapY)
{
    m_ipLastMouseClick->PutCoords (mapX, mapY);
    if ( m_btnAddMoleSymbol->isChecked() )
    {
        // "Add MOLE Symbol" command:  draw a symbol at the click point
        drawSymbol (m_ipLastMouseClick, m_txtSIC->text().toAscii(), false);
        m_txtSIC->setText (getSIC());
    }
    else if ( m_btnSelect->isChecked() )
    {
        IActiveViewPtr ipActiveView;
        m_ipMapControl->get_ActiveView (&ipActiveView);
        if ( selectElements(m_ipLastMouseClick, ipActiveView, m_ipSelectedBounds) )
        {
            // "Select & Drag Graphics" command:  initialize mouse tracking to move the selected elements
            printf ("mouse down at (%f, %f)\n", mapX, mapY);

            // the envelope feedback draws a rectangle of the selected elements' extent following the mouse
            IMoveEnvelopeFeedbackPtr ipMoveEnvelopeFeedback (CLSID_MoveEnvelopeFeedback);
            ipMoveEnvelopeFeedback->Start (m_ipSelectedBounds, m_ipLastMouseClick);
            m_ipMoveFeedback = ipMoveEnvelopeFeedback;
            IScreenDisplayPtr ipScreenDisplay;
            ipActiveView->get_ScreenDisplay (&ipScreenDisplay);
            m_ipMoveFeedback->putref_Display (ipScreenDisplay);
        }
    }
}


void MoleSymbolsWindow::OnMouseMove (long button, long shift, long x, long y, double mapX, double mapY)
{
    // update the current map location of the mouse
    m_ipCurrentMouseLocation->PutCoords (mapX, mapY);

    // "Select & Drag Graphics" command:  move the feedback graphic
    if ( m_btnSelect->isChecked() && button == 1 && m_ipMoveFeedback )
        m_ipMoveFeedback->MoveTo (m_ipCurrentMouseLocation);
}


void MoleSymbolsWindow::OnMouseUp (long button, long shift, long x, long y, double mapX, double mapY)
{
    if ( m_ipMoveFeedback )
    {
        // stop the feedback graphic and save its geometry for future use
        IMoveEnvelopeFeedbackPtr(m_ipMoveFeedback)->Stop (&m_ipSelectedBounds);
        m_ipMoveFeedback = 0;
        double startX, startY;
        m_ipLastMouseClick->QueryCoords (&startX, &startY);
        if ( startX != mapX || startY != mapY )
        {
            // only update the graphics if the minimum move time has elapsed
            printf ("finish drag at (%f, %f)\n", mapX, mapY);
            moveGraphics (mapX - startX, mapY - startY);
        }
    }
}


////////////////////////////////
// constructor and destructor //
////////////////////////////////


MoleSymbolsWindow::MoleSymbolsWindow() :
    m_firstTime              (true),
    m_ipCurrentMouseLocation (createPoint(0, 0)),
    m_ipLastMouseClick       (createPoint(0, 0)),
    m_ipSelectedBounds       (CLSID_Envelope),
    m_unitCount              (0)
{
    printf ("creating widgets\n");

    // create the child widgets of this window
    QWidget     *toolbar            = new QWidget     (this);
    QToolButton *btnAdd200          = new QToolButton (toolbar);
    QToolButton *btnMoveUnits       = new QToolButton (toolbar);
                 m_btnSelect        = new QToolButton (toolbar);
                 m_btnAddMoleSymbol = new QToolButton (toolbar);
                 m_txtSIC           = new QLineEdit   (getSIC(), toolbar);
                 m_txtSize          = new QLineEdit   ("36", toolbar);
    QToolButton *btnAddLine         = new QToolButton (toolbar);
    QToolButton *btnAddArea         = new QToolButton (toolbar);
    QSplitter   *split              = new QSplitter   (this);
    QAxCtl      *tlbControl         = new QAxCtl      (AoPROGID_ToolbarControl, toolbar, "Toolbar Control");
    QAxCtl      *tocControl         = new QAxCtl      (AoPROGID_TOCControl,     split,   "TOC Control");
    QAxCtl      *mapControl         = new QAxCtl      (AoPROGID_MapControl,     split,   "Map Control");

    // set properties on some of the widgets
    tlbControl->setMinimumHeight (28);
    tlbControl->setMaximumHeight (28);
    split->setOpaqueResize (false);
    QList<int> sizes (split->sizes());
    sizes[0] = 250;
    sizes[1] = 650;
    split->setSizes (sizes);

    // set the properties on the toolbar widgets
    m_btnSelect       ->setCheckable (true);
    m_btnAddMoleSymbol->setCheckable (true);
    btnAdd200         ->setIcon (QIcon("Add.png"));
    btnMoveUnits      ->setIcon (QIcon("Move.png"));
    m_btnSelect       ->setIcon (QIcon("Select.png"));
    m_btnAddMoleSymbol->setIcon (QIcon("Jazz.png"));
    btnAddLine        ->setIcon (QIcon("Line.png"));
    btnAddArea        ->setIcon (QIcon("Area.png"));
    btnAdd200         ->setToolTip ("Create 200 graphics");
    btnMoveUnits      ->setToolTip ("Move units");
    m_btnSelect       ->setToolTip ("Select & drag graphics");
    m_txtSIC          ->setToolTip ("ID code of MOLE symbol to add");
    m_txtSize         ->setToolTip ("Size of MOLE symbol to add");
    btnAddLine        ->setToolTip ("Add a sample line graphic");
    btnAddArea        ->setToolTip ("Add a sample area graphic");
    m_btnAddMoleSymbol->setToolTip ("Add MOLE symbol");

    // establish the toolbar layout
    QHBoxLayout *toolbarLayout = new QHBoxLayout (toolbar);
    toolbarLayout->setMargin (0);
    toolbarLayout->addWidget (btnAdd200);
    toolbarLayout->addWidget (btnMoveUnits);
    toolbarLayout->addWidget (m_btnSelect);
    toolbarLayout->addWidget (m_btnAddMoleSymbol);
    toolbarLayout->addWidget (m_txtSIC);
    toolbarLayout->addWidget (m_txtSize);
    toolbarLayout->addWidget (btnAddLine);
    toolbarLayout->addWidget (btnAddArea);
    toolbarLayout->addWidget (tlbControl, 1);

    // establish the main window layout
    QVBoxLayout *rootLayout = new QVBoxLayout (this);
    rootLayout->setSpacing (0);
    rootLayout->setMargin (2);
    rootLayout->addWidget (toolbar);
    rootLayout->addWidget (split, 1);

    // set properties on this widget and connect event handlers
    resize (900, 600);
    updateTitle();
    connect (btnAdd200,          SIGNAL(clicked()),     SLOT(add200()));
    connect (btnMoveUnits,       SIGNAL(clicked()),     SLOT(moveUnits()));
    connect (m_btnSelect,        SIGNAL(clicked(bool)), SLOT(select(bool)));
    connect (m_btnAddMoleSymbol, SIGNAL(clicked(bool)), SLOT(addMoleSymbol(bool)));
    connect (btnAddLine,         SIGNAL(clicked()),     SLOT(addLine()));
    connect (btnAddArea,         SIGNAL(clicked()),     SLOT(addArea()));

    printf ("setting up the controls\n");

    // extricate the COM interfaces of the ArcGIS controls
    IUnknownPtr ipUnknown;
    mapControl->getInterface (&ipUnknown);
    m_ipMapControl = ipUnknown;
    if ( ! m_ipMapControl ) ABORT;
    tlbControl->getInterface (&ipUnknown);
    m_ipToolbarControl = ipUnknown;
    if ( ! m_ipToolbarControl ) ABORT;
    tocControl->getInterface (&ipUnknown);
    ITOCControlPtr ipTocControl (ipUnknown);
    if ( ! ipTocControl ) ABORT;

    // buddy up and load the toolbar
    ipTocControl->SetBuddyControl (m_ipMapControl);
    m_ipToolbarControl->SetBuddyControl (m_ipMapControl);
    addTool ("ControlsOpenDocCommand");
    addTool ("ControlsAddDataCommand");
    addTool ("ControlsSaveAsDocCommand");
    addTool ("ControlsMapZoomPanTool");
    addTool ("ControlsMapFullExtentCommand");
    addTool ("ControlsMapZoomToLastExtentBackCommand");
    addTool ("ControlsMapZoomToLastExtentForwardCommand");
    addTool ("ControlsMapZoomToolControl");
    addTool ("ControlsSelectTool");
    addTool ("ControlsMapGoToCommand");
    addTool ("ControlsMapIdentifyTool");
    addTool ("ControlsMapMeasureTool");
    m_ipToolbarControl->put_ToolTips (VARIANT_TRUE);

    // register for map control events - use static cast because argument type != interface
    m_ipMapControlEventsHelper.CreateInstance (CLSID_MapControlEvents2Listener);
    m_ipMapControlEventsHelper->Startup (static_cast<IMapControlEvents2Helper*>(this));
    m_ipMapControlEventsHelper->AdviseEvents (m_ipMapControl, NULL);

    printf ("loading the map\n");

    // load a default map file
    CComVariant vUndefined ((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
  CComBSTR bstmp;
    m_ipMapControl->LoadMxFile ((bstmp = dsDataPath("Continents/Continents.mxd")), vUndefined, vUndefined);
  SysFreeString(bstmp);
}


MoleSymbolsWindow::~MoleSymbolsWindow()
{
    printf ("deregistering event handler\n");

    m_ipMapControlEventsHelper->UnadviseEvents();
    m_ipMapControlEventsHelper->Shutdown();

    printf ("releasing MOLE resources\n");

    IMoleCoreHelperPtr ipMoleCoreHelper (CLSID_MoleCoreHelper);
    ipMoleCoreHelper->ReleaseForceElementRenderer();

    // the controls are in the Qt tree, but dereference them for completeness
    m_ipMapControl             = 0;
    m_ipMapControlEventsHelper = 0;
    m_ipToolbarControl         = 0;
}