2025-04-27 07:49:33 -04:00

4830 lines
123 KiB
C++

// Copyright (c) 1997-2001 Microsoft Corporation, All Rights Reserved
//***************************************************************************
//
// (c) 1996 by Microsoft Corporation
//
// agraph.cpp
//
// This file contains the code that draws the association graph.
//
// a-larryf 17-Sept-96 Created.
//
//***************************************************************************
#include "precomp.h"
#include <afxcmn.h>
#include "resource.h"
#ifndef _wbemidl_h
#define _wbemidl_h
#include <wbemidl.h>
#endif //_wbemidl_h
#include "icon.h"
#include "Methods.h"
#include "hmomutil.h"
#include "agraph.h"
#include "globals.h"
#include "SingleView.h"
#include "SingleViewCtl.h"
#include "path.h"
#include <math.h>
#include "utils.h"
#include "hmmverr.h"
#include "coloredt.h"
#include "logindlg.h"
#include "DlgRefQuery.h"
#include "hmmvtab.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define QUERY_TIMEOUT 10000
#define REFQUERY_DELAY_THRESHOLD_SECONDS 2
#define AVOID_LOADING_ENDPOINT_INSTANCES TRUE
#define MANY_NODES 10
enum {ID_HOVER_TIMER=1, ID_AGRAPH_UPDATE_TIMER };
#define MAX_LABEL_LENGTH 64
#define REFS_REDRAW_THREASHOLD 25
#define DY_ARC_HIT_MARGIN 4
#define CX_VIEW_MARGIN 16
#define CY_VIEW_MARGIN 16
#define DX_SCROLL_UNIT 16
#define DY_SCROLL_UNIT 16
#define CX_LABEL_LEADING 8
#define CY_LABEL_LEADING 4
#define CY_OBJECT_LEADING 8
#define CY_ASSOC_LINK_ICON 16
#define CX_ASSOC_LINK_ICON 16
#define CY_LABEL_FONT 12
#define CY_TEXT_LEADING 4
#define CONNECTION_POINT_RADIUS 4
#define CX_CONNECT_STUB 75
#define CX_CONNECT_STUB_SHORT 20
#define CY_TOOLTIP_MARGIN 4
#define CX_TOOLTIP_MARGIN 8
#define CX_ROOT_TITLE (2 * CX_CONNECT_STUB)
#define CY_ROOT_TITLE 1024
#define CX_ARC_SEGMENT2 20
#define CX_COLUMN1 (2 * CY_LEAF + CX_ARC_SEGMENT2)
#define CX_COLUMN2 (3 * CY_LEAF + CX_ARC_SEGMENT2)
#define CX_COLUMN3 (1 * CY_LEAF + CX_ARC_SEGMENT2)
#define ARROW_HALF_WIDTH 3
#define ARROW_LENGTH 10
#define DEFAULT_BACKGROUND_COLOR RGB(0xff, 0xff, 192) // Yellow background color
enum {ARCTYPE_ARROW_RIGHT, ARCTYPE_ARROW_LEFT, ARCTYPE_GENERIC, ARCTYPE_JOIN_ON_RIGHT};
IMPLEMENT_DYNCREATE(CAssocGraph, CWnd)
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CHoverText window
//
// The hover text window is used to display the popup tooltip text when
// the mouse hovers over an item in the association graph.
//
////////////////////////////////////////////////////////////////////////////
class CHoverText : public CStatic
{
// Construction
public:
CHoverText();
// Attributes
public:
// Operations
public:
BOOL Create(LPCTSTR pszHoverText, CFont& font, CPoint ptHover, CWnd* pwndParent);
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CHoverText)
public:
virtual BOOL DestroyWindow();
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CHoverText();
// Generated message map functions
protected:
//{{AFX_MSG(CHoverText)
afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
/////////////////////////////////////////////////////////////////////////////
//*****************************************************************
// DrawArrowHead
//
// Draw an arrow head. The arrow head is drawn such that it is
// centered on a vector with its tip on at the head of the vector
// and its tail is a line perpendicular to the vector. The arrowhead
// is drawn at a fixed size determined by #define constants.
//
// This function can draw the arrowhead at any angle, however arrowheads
// look best if they are drawn at angles which are multiples of 45 degrees.
// User feedback indicated that the association graph looks best when
// horizontal vectors are used with arrows pointing to the right. Thus,
// it may be possible to simplify this function in the future by eliminating
// the code that draws the arrowhead at an arbitrary angle. However, you
// should be sure that you will never need it as recreating this code will
// not be tivial.
//
// Parameters:
// CDC* pdc
// Pointer to the display context.
//
// CPoint ptVectorTail
// The tail of the vector.
//
// CPoint ptVectorHead
// The head of the vector.
//
// Returns:
// Nothing.
//
//****************************************************************************
void DrawArrowHead(CDC* pdc, CPoint ptVectorTail, CPoint ptVectorHead)
{
CPoint ptTail;
CPoint ptHead = ptVectorHead;
CPoint ptTailVertix1;
CPoint ptTailVertix2;
int cxDeltaVector = ptVectorHead.x - ptVectorTail.x;
int cyDeltaVector = ptVectorHead.y - ptVectorTail.y;
if (cxDeltaVector == 0) {
// The arrow points straight up or straight down
ptTailVertix1.x = ptVectorTail.x - ARROW_HALF_WIDTH;
ptTailVertix2.x = ptVectorTail.x + ARROW_HALF_WIDTH;
if (cyDeltaVector > 0) {
// The arrow is pointing up
ptTailVertix1.y = ptVectorHead.y - ARROW_LENGTH;
}
else {
// The arrow is pointing down
ptTailVertix1.y = ptVectorHead.y + ARROW_LENGTH;
}
ptTailVertix2.y = ptTailVertix1.y;
}
else if (cyDeltaVector == 0) {
// The arrow points straight left or right
if (cxDeltaVector > 0) {
// The arrow points directly right
ptTailVertix1.x = ptVectorHead.x - ARROW_LENGTH;
}
else {
// The arrow points directlly left;
ptTailVertix1.x = ptVectorHead.x + ARROW_LENGTH;
}
ptTailVertix2.x = ptTailVertix1.x;
ptTailVertix1.y = ptVectorHead.y + ARROW_HALF_WIDTH;
ptTailVertix2.y = ptVectorHead.y - ARROW_HALF_WIDTH;
}
else {
// First calculate where the tail midpoint will be
double ry = (double) cyDeltaVector;
double rx = (double) cxDeltaVector;
double rVectorLength = sqrt((double) (rx * rx + ry * ry)) - (double) ARROW_LENGTH;
double rSinTheta = ry / rVectorLength;
double rCosTheta = rx / rVectorLength;
CPoint ptTailMidpoint;
ptTailMidpoint.x = (int) (rVectorLength * rCosTheta) + ptVectorTail.x;
ptTailMidpoint.y = (int) (rVectorLength * rSinTheta) + ptVectorTail.y;
// Now we need to calculate the position of the two tail vetices
// on either side of the tail midpoint. We know that the line
// connecting the tail vertices is perpendicular to the arrow vector.
// Thus we can get the sin and cos of the line tail vector by negating
// the sin and cos of the arrow vector.
double rArrowVectorLength = (double) ARROW_HALF_WIDTH;
double rdxArrow = rArrowVectorLength * -rSinTheta;
double rdyArrow = rArrowVectorLength * rCosTheta;
ptTailVertix1.x = ptTailMidpoint.x - (int) (((double) ARROW_HALF_WIDTH) * rSinTheta);
ptTailVertix1.y = ptTailMidpoint.y + (int) (((double) ARROW_HALF_WIDTH) * rCosTheta);
ptTailVertix2.x = ptTailMidpoint.x + (int) (((double) ARROW_HALF_WIDTH) * rSinTheta);
ptTailVertix2.y = ptTailMidpoint.y - (int) (((double) ARROW_HALF_WIDTH) * rCosTheta);
}
POINT apt[3];
apt[0].x = ptTailVertix1.x;
apt[0].y = ptTailVertix1.y;
apt[1].x = ptTailVertix2.x;
apt[1].y = ptTailVertix2.y;
apt[2].x = ptHead.x;
apt[2].y = ptHead.y;
pdc->Polygon(apt, 3);
}
//**************************************************************************
// PointInParallelogram
//
// Check to see if the specified point is contained within a special case
// of a parallelogram that has vertical left and right sides. This function
// is used to check whether or not the mouse is hovering over the slanted
// portion of an association arc.
//
// Parameters:
// CPoint pt
// The point to test.
//
// CPoint ptTopLeft
// The top-left vertex of the parallelogram.
//
// CPoint ptBottomLeft
// The bottom-left vertex of the parallelogram.
//
// CPoint ptTopRight
// The top-right vertex of the parallelogram
//
// CPoint ptBottomRight
// The bottom-right vertex of the parallelogram.
//
// Returns:
// TRUE if the point is contained within the parallelogram. Note that
// TRUE should be returned if the point is on the left or top side.
//
//*****************************************************************************
BOOL PointInParallelogram(
CPoint pt,
CPoint ptTopLeft,
CPoint ptBottomLeft,
CPoint ptTopRight,
CPoint ptBottomRight)
{
// Verify that the parallelogram is a special case with vertical right and
// left sides.
ASSERT(ptTopLeft.x == ptBottomLeft.x);
ASSERT(ptTopRight.x == ptBottomRight.x);
if (pt.x < ptTopLeft.x || pt.x >= ptTopRight.x) {
return FALSE;
}
// At this point we know that the point is somewhere between the left and
// right edges of the parallelogram because these edges are vertical. Now
// we construct an imaginary vertical line that goes through the point and
// check the Y coordinates of the points where it intersects the top and
// bottom edges of the parallelogram. If the Y coordinate of the point we
// are testing falls between the Y coordinates of these two intersection points
// then the point is in the parallelogram.
//
// To find where this imaginary vertical line intersects the top and bottom
// edges of the parallelogram, we first compute the slope of the bottom edge,
// then we multiply the slope by the horizontal distance from the bottom-left
// vertex to the point. This gives us the Y coordinate of where the vertical
// line going though the point intersects the bottom edge of the parallelogram.
// Since we are dealing with a parallelogram and the left and right edges are
// vertical we then find the Y coordinate of intersection with the top edge
// by adding the difference between the Y coordinates of the two left verti.
float dyBottomEdge = (float) (ptBottomRight.y - ptBottomLeft.y);
float dxBottomEdge = (float) (ptBottomRight.x - ptBottomLeft.x);
float mBottomEdge = dyBottomEdge / dxBottomEdge;
float dxIntersectBottom = (float) (pt.x - ptBottomLeft.x);
float dyIntersectBottom = dxIntersectBottom * mBottomEdge;
float yIntersectBottom = ptBottomLeft.y + dyIntersectBottom;
float yIntersectTop = yIntersectBottom + (ptTopLeft.y - ptBottomLeft.y);
if ((pt.y < yIntersectTop) || (pt.y > yIntersectBottom)) {
// The point is above or below the parallelogram.
return FALSE;
}
// The point must be somewhere between the top and bottom edges of the
// paralleogram. A previous test determined that it was between the
// left and right edges, so at this point we know the point is contained
// in the parallelogram.
return TRUE;
}
//******************************************************************************
// PointNearArc
//
// This function tests to see if the specified point is near an arc in the graph.
// This test is done by seeing if the point is "near" the horizontal or slanted
// section of the arc.
//
// For the horizontal line-segments, the test is easy, just
// construct a rectangle that contains the line segment and extends above and below
// the line segment by a "margin" and then test to see if the point is within the
// rectangle.
//
// For the slanted line-segments, a parallelogram is constructed such that the top
// and bottom edges of the parallelogram run parallel to the slanted line segment.
// The left and right edges are vertical such that the top and bottom verti are
// DY_ARC_HIT_MARGIN above and below the respective endpoints.
//
// Parameters:
// CPoint pt
// The point to test.
//
// int iArcType
// ARCTYPE_GENERIC
// An arc with two segments. A slanted segment on the left and
// a horizontal segment on the right. The horizontal segment
// length is CX_ARC_SEGMENT.
//
// ARCTYPE_JOIN_ON_RIGHT
// An arc with two segments. A horizontal segment on the left
// and a slanted segment on the right. The horizontal segment
// length is CX_ARC_SEGMENT2
//
// ARCTYPE_ARROW_RIGHT
// An arc with two segments. A slanted segment on the left and a
// horizontal segment on the right. The horizontal segment length
// is CX_ARC_SEGMENT2.
//
// CPoint ptConnectLeft
// The left endpoint of the arc.
//
// CPoint ptConnectRight
// The right endpoint of the arc.
//
// Returns:
// TRUE if the point is "near" the arc. FALSE otherwise.
//
//************************************************************************************
BOOL PointNearArc(CPoint pt, int iArcType, CPoint ptConnectLeft, CPoint ptConnectRight)
{
CPoint ptHeadVertix;
CPoint ptTailVertix;
CPoint ptBreak;
// The verti of a parallelogram that contains the slanted portion of the arc.
// The top and bottom edges of this parallelogram run parallel to the arc's
// slanted line and the bottom edge runs below the arc's slanted line.
// The left and right edges of the parallelogram are vertical such that they
// are to the left and right endpoints of the arc's slanted line respectively.
CPoint ptPgvTopLeft;
CPoint ptPgvBottomLeft;
CPoint ptPgvTopRight;
CPoint ptPgvBottomRight;
CRect rc;
switch(iArcType) {
case ARCTYPE_GENERIC:
// This is the generic arc with two segments. The first segment
// is a slanted line on the left. The second segment is a horizontal
// line on the right.
// First check to see if the point is close to the slanted portion of the arc.
ptPgvTopLeft.x = ptConnectLeft.x;
ptPgvTopLeft.y = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
ptPgvBottomLeft.x = ptConnectLeft.x;
ptPgvBottomLeft.y = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
ptPgvTopRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvTopRight.y = ptConnectRight.y - DY_ARC_HIT_MARGIN;
ptPgvBottomRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvBottomRight.y = ptConnectRight.y + DY_ARC_HIT_MARGIN;
if (PointInParallelogram(pt, ptPgvTopLeft, ptPgvBottomLeft, ptPgvTopRight, ptPgvBottomRight)) {
return TRUE;
}
// Check to see if the point is close to the horizontal portion of the arc.
rc.left = ptConnectRight.x - CX_ARC_SEGMENT2;
rc.right = ptConnectRight.x;
rc.top = ptConnectRight.y - DY_ARC_HIT_MARGIN;
rc.bottom = ptConnectRight.y + DY_ARC_HIT_MARGIN;
return rc.PtInRect(pt);
break;
case ARCTYPE_JOIN_ON_RIGHT:
// This type of arc is what you might see to the right of the root node
// where multiple arcs fan out from to the right of a connection point
// on the right of the root node.
// Check to see if the point is close to the horizontal portion of the arc.
rc.left = ptConnectLeft.x;
rc.right = ptConnectLeft.x + CX_ARC_SEGMENT2;
rc.top = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
rc.bottom = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
if (rc.PtInRect(pt)) {
return TRUE;
}
// Check to see if the point is close to the slanted portion of the arc.
ptPgvTopLeft.x = ptConnectLeft.x + CX_ARC_SEGMENT2;
ptPgvTopLeft.y = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
ptPgvBottomLeft.x = ptConnectLeft.x + CX_ARC_SEGMENT2;
ptPgvBottomLeft.y = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
ptPgvTopRight.x = ptConnectRight.x;
ptPgvTopRight.y = ptConnectRight.y - DY_ARC_HIT_MARGIN;
ptPgvBottomRight.x = ptConnectRight.x;
ptPgvBottomRight.y = ptConnectRight.y + DY_ARC_HIT_MARGIN;
return PointInParallelogram(pt, ptPgvTopLeft, ptPgvBottomLeft, ptPgvTopRight, ptPgvBottomRight);
break;
case ARCTYPE_ARROW_RIGHT:
// Arrow from left to right with consisting of two line segments.
// The first line segment on the left is the slanted part, the second
// line segment on the right is horizontal and CX_ARC_SEGMENT2 in length.
if (ptConnectLeft.y == ptConnectRight.y) {
// The arrow is horizontal.
rc.left = ptConnectLeft.x;
rc.right = ptConnectRight.x;
rc.top = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
rc.bottom = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
return rc.PtInRect(pt);
}
else {
// Check to see if the point is near the horizontal portion of
// the arrow shaft to the
rc.left = ptConnectRight.x - CX_ARC_SEGMENT2;
rc.right = ptConnectRight.x;
rc.top = ptConnectRight.y - DY_ARC_HIT_MARGIN;
rc.bottom = ptConnectRight.y + DY_ARC_HIT_MARGIN;
if (rc.PtInRect(pt)) {
return TRUE;
}
// Check to see if the point is near the slanted portion of the
// arc.
// Check to see if the point is close to the slanted portion of the arc.
ptPgvTopLeft.x = ptConnectLeft.x;
ptPgvTopLeft.y = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
ptPgvBottomLeft.x = ptConnectLeft.x;
ptPgvBottomLeft.y = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
ptPgvTopRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvTopRight.y = ptConnectRight.y - DY_ARC_HIT_MARGIN;
ptPgvBottomRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvBottomRight.y = ptConnectRight.y + DY_ARC_HIT_MARGIN;
return PointInParallelogram(pt, ptPgvTopLeft, ptPgvBottomLeft, ptPgvTopRight, ptPgvBottomRight);
}
break;
}
return FALSE;
}
#if 0
//*****************************************************************
// DrawArcParallelogram
//
// This function was used for testing purposes only. It allows you
// to see the parallelogram that defines what "near the slanted part of
// the arc" means.
//
// Parameters:
// CDC* pdc
// Pointer to the DC to draw into.
//
//
// int iArcType
// ARCTYPE_GENERIC
// An arc with two segments. A slanted segment on the left and
// a horizontal segment on the right. The horizontal segment
// length is CX_ARC_SEGMENT.
//
// ARCTYPE_JOIN_ON_RIGHT
// An arc with two segments. A horizontal segment on the left
// and a slanted segment on the right. The horizontal segment
// length is CX_ARC_SEGMENT2
//
// ARCTYPE_ARROW_RIGHT
// An arc with two segments. A slanted segment on the left and a
// horizontal segment on the right. The horizontal segment length
// is CX_ARC_SEGMENT2.
//
// CPoint ptConnectLeft
// The left endpoint of the arc.
//
// CPoint ptConnectRight
// The right endpoint of the arc.
//
// Returns:
// Nothing.
//
//************************************************************************************
void DrawArcParallelogram(CDC* pdc, int iArcType, CPoint ptConnectLeft, CPoint ptConnectRight)
{
CPoint pt;
pt.x = (ptConnectRight.x + ptConnectLeft.x) / 2;
pt.y = (ptConnectRight.y + ptConnectLeft.y) / 2;
CPoint ptHeadVertix;
CPoint ptTailVertix;
CPoint ptBreak;
// The verti of a parallelogram that contains the slanted portion of the arc.
// The top and bottom edges of this parallelogram run parallel to the arc's
// slanted line and the bottom edge runs below the arc's slanted line.
// The left and right edges of the parallelogram are vertical such that they
// are to the left and right endpoints of the arc's slanted line respectively.
CPoint ptPgvTopLeft;
CPoint ptPgvBottomLeft;
CPoint ptPgvTopRight;
CPoint ptPgvBottomRight;
CRect rc;
switch(iArcType) {
case ARCTYPE_GENERIC:
// This is the generic arc with two segments. The first segment
// is a slanted line on the left. The second segment is a horizontal
// line on the right.
// First check to see if the point is close to the slanted portion of the arc.
ptPgvTopLeft.x = ptConnectLeft.x;
ptPgvTopLeft.y = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
ptPgvBottomLeft.x = ptConnectLeft.x;
ptPgvBottomLeft.y = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
ptPgvTopRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvTopRight.y = ptConnectRight.y - DY_ARC_HIT_MARGIN;
ptPgvBottomRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvBottomRight.y = ptConnectRight.y + DY_ARC_HIT_MARGIN;
break;
case ARCTYPE_JOIN_ON_RIGHT:
// This type of arc is what you might see to the right of the root node
// where multiple arcs fan out from to the right of a connection point
// on the right of the root node.
// Check to see if the point is close to the slanted portion of the arc.
ptPgvTopLeft.x = ptConnectLeft.x + CX_ARC_SEGMENT2;
ptPgvTopLeft.y = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
ptPgvBottomLeft.x = ptConnectLeft.x + CX_ARC_SEGMENT2;
ptPgvBottomLeft.y = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
ptPgvTopRight.x = ptConnectRight.x;
ptPgvTopRight.y = ptConnectRight.y - DY_ARC_HIT_MARGIN;
ptPgvBottomRight.x = ptConnectRight.x;
ptPgvBottomRight.y = ptConnectRight.y + DY_ARC_HIT_MARGIN;
break;
case ARCTYPE_ARROW_RIGHT:
// Arrow from left to right with consisting of two line segments.
// The first line segment on the left is the slanted part, the second
// line segment on the right is horizontal and CX_ARC_SEGMENT2 in length.
if (ptConnectLeft.y == ptConnectRight.y) {
return;
}
else {
// Check to see if the point is near the slanted portion of the
// arc.
// Check to see if the point is close to the slanted portion of the arc.
ptPgvTopLeft.x = ptConnectLeft.x;
ptPgvTopLeft.y = ptConnectLeft.y - DY_ARC_HIT_MARGIN;
ptPgvBottomLeft.x = ptConnectLeft.x;
ptPgvBottomLeft.y = ptConnectLeft.y + DY_ARC_HIT_MARGIN;
ptPgvTopRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvTopRight.y = ptConnectRight.y - DY_ARC_HIT_MARGIN;
ptPgvBottomRight.x = ptConnectRight.x - CX_ARC_SEGMENT2;
ptPgvBottomRight.y = ptConnectRight.y + DY_ARC_HIT_MARGIN;
}
break;
}
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
CPen* ppenSave = pdc->SelectObject(&pen);
pdc->MoveTo(ptPgvTopLeft);
pdc->LineTo(ptPgvTopRight);
pdc->LineTo(ptPgvBottomRight);
pdc->LineTo(ptPgvBottomLeft);
pdc->LineTo(ptPgvTopLeft);
pdc->SelectObject(ppenSave);
pdc->MoveTo(pt);
pdc->Ellipse(pt.x - 1,
pt.y - 1,
pt.x + 1,
pt.y + 1);
}
#endif //0
//******************************************************************************
// DrawArc
//
// This function draws the arcs that connect the icons in the association graph.
//
// Parameters:
// CDC* pdc
// Pointer to the display context to use.
//
// int iArcType
// ARCTYPE_GENERIC
// An arc with two segments. A slanted segment on the left and
// a horizontal segment on the right. The horizontal segment
// length is CX_ARC_SEGMENT.
//
// ARCTYPE_JOIN_ON_RIGHT
// An arc with two segments. A horizontal segment on the left
// and a slanted segment on the right. The horizontal segment
// length is CX_ARC_SEGMENT2
//
// ARCTYPE_ARROW_RIGHT
// An arc with two segments. A slanted segment on the left and a
// horizontal segment on the right. The horizontal segment length
// is CX_ARC_SEGMENT2.
//
// CPoint ptConnectLeft
// The left endpoint of the arc.
//
// CPoint ptConnectRight
// The right endpoint of the arc.
//
// Returns:
// Nothing.
//************************************************************************************
void DrawArc(CDC* pdc, int iArcType, CPoint ptConnectLeft, CPoint ptConnectRight)
{
CPoint ptHeadVertix;
CPoint ptTailVertix;
CPoint ptBreak;
switch(iArcType) {
case ARCTYPE_GENERIC:
pdc->MoveTo(ptConnectLeft);
pdc->LineTo(ptConnectRight.x - CX_ARC_SEGMENT2, ptConnectRight.y);
pdc->LineTo(ptConnectRight.x, ptConnectRight.y);
break;
case ARCTYPE_JOIN_ON_RIGHT:
pdc->MoveTo(ptConnectLeft);
pdc->LineTo(ptConnectLeft.x + CX_ARC_SEGMENT2, ptConnectLeft.y);
pdc->LineTo(ptConnectRight);
break;
case ARCTYPE_ARROW_RIGHT:
// Arrow from left to right with the break on the right
pdc->MoveTo(ptConnectLeft);
if (ptConnectLeft.y == ptConnectRight.y) {
pdc->LineTo(ptConnectRight);
DrawArrowHead(pdc, ptConnectLeft, ptConnectRight);
}
else {
ptBreak.y = ptConnectRight.y;
if ((ptConnectRight.x - ptConnectLeft.x) < CX_ARC_SEGMENT2) {
ptBreak.x = (ptConnectRight.x + ptConnectLeft.x) / 2;
}
else {
ptBreak.x = ptConnectRight.x - CX_ARC_SEGMENT2;
}
pdc->LineTo(ptBreak);
pdc->LineTo(ptConnectRight);
DrawArrowHead(pdc, ptBreak, ptConnectRight);
}
break;
}
}
/////////////////////////////////////////////////////////////////////////////
// CAssocGraph
//
// This is the primary class for the association graph.
//
/////////////////////////////////////////////////////////////////////////////
CAssocGraph::CAssocGraph()
{
CAssocGraph(NULL);
}
CAssocGraph::CAssocGraph(CSingleViewCtrl* psv)
{
m_bDoingRefresh = FALSE;
m_proot = new CRootNode(this);
m_psv = psv;
HMODULE hmod = GetModuleHandle(SZ_MODULE_NAME);
m_bDidInitialLayout = FALSE;
m_ptInitialScroll.x = 0;
m_ptInitialScroll.y = 0;
m_dwHoverItem = 0;
m_phover = NULL;
m_bNeedsRefresh = FALSE;
m_bBusyUpdatingWindow = FALSE;
CreateLabelFont(m_font);
if (psv) {
// Share the icon source with the main control to avoid redundant
// loading of icons.
m_pIconSource = psv->IconSource();
m_bThisOwnsIconSource = FALSE;
}
else {
m_pIconSource = new CIconSource(CSize(CX_SMALL_ICON, CY_SMALL_ICON), CSize(CX_LARGE_ICON, CY_LARGE_ICON));
m_bThisOwnsIconSource = TRUE;
}
m_pComparePaths = new CComparePaths;
}
CAssocGraph::~CAssocGraph()
{
m_psv->GetGlobalNotify()->RemoveClient((CNotifyClient*) this);
delete m_pComparePaths;
delete m_phover;
if (m_bThisOwnsIconSource) {
delete m_pIconSource;
}
delete m_proot;
}
BEGIN_MESSAGE_MAP(CAssocGraph, CWnd)
//{{AFX_MSG_MAP(CAssocGraph)
ON_WM_PAINT()
ON_WM_VSCROLL()
ON_WM_HSCROLL()
ON_WM_SIZE()
ON_WM_LBUTTONDBLCLK()
ON_WM_TIMER()
ON_WM_LBUTTONDOWN()
ON_COMMAND(ID_CMD_GOTO_NAMESPACE, OnCmdGotoNamespace)
ON_COMMAND(ID_CMD_MAKE_ROOT, OnCmdMakeRoot)
ON_COMMAND(ID_CMD_SHOW_PROPERTIES, OnCmdShowProperties)
ON_WM_MOUSEWHEEL()
ON_WM_SETFOCUS()
ON_WM_CONTEXTMENU()
ON_WM_KILLFOCUS()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//************************************************************************
// CAssocGraph::Create
//
// Create the association graph.
//
// Parameters:
// See the documentation for the MFC CWnd class.
//
// Returns:
// TRUE if the association graph window was created successfully, FALSE
// otherwise.
//
//************************************************************************
BOOL CAssocGraph::Create(CRect& rc, CWnd* pwndParent, UINT nId, BOOL bVisible)
{
DWORD dwStyle = WS_BORDER | WS_VSCROLL | WS_HSCROLL | WS_CHILD;
if (bVisible) {
dwStyle |= WS_VISIBLE;
}
BOOL bDidCreate = CWnd::Create(NULL, _T("CAssocGraph"), dwStyle, rc, pwndParent, nId);
if (bDidCreate) {
SetFont(&m_font, FALSE);
// !!!CR: These scroll ranges are obsolete!
SetScrollRange(SB_VERT, 1, 300);
SetScrollRange(SB_HORZ, 1, 300);
SetTimer(ID_HOVER_TIMER, 250, NULL);
}
m_psv->GetGlobalNotify()->AddClient((CNotifyClient*) this);
m_proot->Create(rc, this, bVisible);
return bDidCreate;
}
//*************************************************************************
// CAssocGraph::CreateLabelFont
//
// Create the font used to draw the labels for the icons etc.
//
// Parameters:
// [out] CFont& font
// The created font is returned through this parameter.
//
// Returns:
// Nothing.
//
//*************************************************************************
void CAssocGraph::CreateLabelFont(CFont& font)
{
CFont fontTmp;
fontTmp.CreateStockObject(SYSTEM_FONT);
LOGFONT logFont;
fontTmp.GetObject(sizeof(LOGFONT), &logFont);
logFont.lfHeight = CY_LABEL_FONT;
logFont.lfWeight = FW_NORMAL;
logFont.lfPitchAndFamily = VARIABLE_PITCH | FF_SWISS;
lstrcpy(logFont.lfFaceName, _T("MS Sans Serif"));
VERIFY(font.CreateFontIndirect(&logFont));
}
/////////////////////////////////////////////////////////////////////////////
// CAssocGraph message handlers
//***********************************************************************
// CAssocGraph::SetScrollRanges
//
// This method analyzes the association graph and sets the scroll ranges
// for the scroll bars so that it is possible to bring the entire
// association graph into view.
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//
//***********************************************************************
void CAssocGraph::SetScrollRanges()
{
CRect rcClient;
GetClientRect(rcClient);
CPoint ptCenterClient = rcClient.CenterPoint();
CRect rcCanvas = m_proot->m_rcBounds;
rcCanvas.InflateRect(CX_VIEW_MARGIN, CY_VIEW_MARGIN);
CPoint ptCenterCanvas = rcCanvas.CenterPoint();
int cxScrollLeft = (ptCenterCanvas.x - rcCanvas.left) - (ptCenterClient.x - rcClient.left);
int cxScrollRight = (rcCanvas.right - ptCenterCanvas.x) - (rcClient.right - ptCenterClient.x);
int cyScrollUp = (ptCenterCanvas.y - rcCanvas.top) - (ptCenterClient.y - rcClient.top);
int cyScrollDown = (rcCanvas.bottom - ptCenterCanvas.y) - (rcClient.bottom - ptCenterClient.y);
int nUnitsScrollLeft, nUnitsScrollRight, nUnitsScrollUp, nUnitsScrollDown;
if (cxScrollLeft > 0) {
nUnitsScrollLeft = (cxScrollLeft + (DX_SCROLL_UNIT - 1)) / DX_SCROLL_UNIT;
}
else {
nUnitsScrollLeft = 0;
}
if (cxScrollRight > 0) {
nUnitsScrollRight = (cxScrollRight + (DX_SCROLL_UNIT - 1)) / DX_SCROLL_UNIT;
}
else {
nUnitsScrollRight = 0;
}
if (cyScrollUp > 0) {
nUnitsScrollUp = (cyScrollUp + (DY_SCROLL_UNIT - 1)) / DY_SCROLL_UNIT;
}
else {
nUnitsScrollUp = 0;
}
if (cyScrollDown > 0) {
nUnitsScrollDown = (cyScrollDown + (DY_SCROLL_UNIT - 1)) / DY_SCROLL_UNIT;
}
else {
nUnitsScrollDown = 0;
}
SetScrollRange(SB_VERT, 0, nUnitsScrollUp + nUnitsScrollDown);
m_ptInitialScroll.y = nUnitsScrollUp;
SetScrollPos(SB_VERT, nUnitsScrollUp);
SetScrollRange(SB_HORZ, 0, nUnitsScrollLeft + nUnitsScrollRight);
m_ptInitialScroll.x = nUnitsScrollLeft;
SetScrollPos(SB_HORZ, nUnitsScrollLeft);
}
//********************************************************************
// CAssocGraph::OnPaint
//
// Paint the association graph.
//
// Parameters:
// None.
//
// Returns:
// Nothing.
//
//********************************************************************
void CAssocGraph::OnPaint()
{
CPaintDC dc(this); // device context for painting
Draw(&dc, &dc.m_ps.rcPaint, dc.m_ps.fErase);
}
void CAssocGraph::Draw(CDC* pdc, RECT* prcDraw, BOOL bErase)
{
pdc->SetBkMode(TRANSPARENT);
CFont fontLabel;
CreateLabelFont(fontLabel);
CFont* pfontSave;
pfontSave = pdc->SelectObject(&fontLabel);
CRect rcClient;
GetClientRect(rcClient);
// Check see if the layout for the association graph needs to be redone.
if (m_proot->NeedsLayout()) {
m_proot->Layout(pdc);
CPoint ptCenterRoot = m_proot->m_rcBounds.CenterPoint();
CPoint ptCenterClient = rcClient.CenterPoint();
CPoint ptRootOrigin;
ptRootOrigin.x = m_proot->m_ptOrigin.x + (ptCenterClient.x - ptCenterRoot.x);
ptRootOrigin.y = m_proot->m_ptOrigin.y + (ptCenterClient.y - ptCenterRoot.y);
m_bDidInitialLayout = TRUE;
SetScrollRanges();
// Setting the scroll range can alter the size of the client area if the
// scroll bars appear or disappear. Recalculate the ptRootOrigin using the
// updated client rect.
GetClientRect(rcClient);
ptCenterClient = rcClient.CenterPoint();
ptRootOrigin.x = m_proot->m_ptOrigin.x + (ptCenterClient.x - ptCenterRoot.x);
ptRootOrigin.y = m_proot->m_ptOrigin.y + (ptCenterClient.y - ptCenterRoot.y);
}
CBrush brBackground(DEFAULT_BACKGROUND_COLOR);
CBrush* pbrSave = (CBrush*) pdc->SelectObject(&brBackground);
pdc->SetBkColor(DEFAULT_BACKGROUND_COLOR);
// Erase the background
if (bErase) {
pdc->FillRect(prcDraw, &brBackground);
}
int ix = (rcClient.right - rcClient.left) / 4;
int iy = (rcClient.bottom - rcClient.top) / 2;
CPen pen1;
pen1.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
CPen* ppenSave = pdc->SelectObject(&pen1);
int iVScrollPos;
int iHScrollPos;
iVScrollPos = GetScrollPos(SB_VERT);
iHScrollPos = GetScrollPos(SB_HORZ);
CPoint ptClientCenterPoint = rcClient.CenterPoint();
CPoint ptBoundsCenterPoint = m_proot->m_rcBounds.CenterPoint();
m_ptOrg.x = ptBoundsCenterPoint.x - ptClientCenterPoint.x ;
m_ptOrg.x += (iHScrollPos - m_ptInitialScroll.x) * DX_SCROLL_UNIT;
m_ptOrg.y = ptBoundsCenterPoint.y - ptClientCenterPoint.y ;
m_ptOrg.y += (iVScrollPos - m_ptInitialScroll.y) * DY_SCROLL_UNIT;
pdc->SetWindowOrg(m_ptOrg);
m_proot->Draw(pdc, &brBackground);
pdc->SelectObject(ppenSave);
pdc->SelectObject(pfontSave);
pdc->SelectObject(pbrSave);
}
/////////////////////////////////////////////////////////////////////////
// Class CNode
//
// CNode is the base class for all the nodes that are displayed on the
// association graph. With some thought more "common" functionality
// could probably be moved from the derived classes to the CNode class.
//
////////////////////////////////////////////////////////////////////////
CNode::CNode(CAssocGraph* pAssocGraph)
{
m_pAssocGraph = pAssocGraph;
static DWORD s_ID = 1;
m_picon = NULL;
m_sizeIcon.cx = CX_LARGE_ICON;
m_sizeIcon.cy = CY_LARGE_ICON;
m_ptOrigin.x = 0;
m_ptOrigin.y = 0;
m_rcBounds.SetRectEmpty();
m_bstrLabel = NULL;
m_bEnabled = TRUE;
// There are two ID's associated with each node. The first ID is
// for the node itself. The second ID is for the arc leading to
// the node. These IDs are used to implement hover text labeling.
m_dwId = s_ID;
s_ID += 2;
}
CNode::~CNode()
{
if (m_bstrLabel) {
SysFreeString(m_bstrLabel);
}
}
//*****************************************************
// CNode::LimitLabelLength
//
// This method is called to limit the label length for
// nodes to something reasonable.
//
// Parameters:
// [in,out] CString& sLabel
//
// Returns:
// Nothing.
//
//*****************************************************
void CNode::LimitLabelLength(CString& sLabel)
{
if (sLabel.GetLength() < MAX_LABEL_LENGTH) {
return;
}
CString sTemp;
sTemp = sLabel.Right(MAX_LABEL_LENGTH);
sLabel = _T("...");
sLabel += sTemp;
}
BOOL CNode::InVerticalExtent(const CRect& rc) const
{
if ((rc.top > m_rcBounds.bottom) ||
(rc.bottom < m_rcBounds.top) ||
rc.IsRectEmpty() ||
m_rcBounds.IsRectEmpty()) {
return FALSE;
}
else {
return TRUE;
}
}
void CNode::SetLabel(BSTR bstrLabel)
{
if (m_bstrLabel) {
SysFreeString(m_bstrLabel);
}
m_bstrLabel = SysAllocString(bstrLabel);
}
void CNode::GetLabel(CString& sLabel)
{
if (m_bstrLabel) {
BStringToCString(sLabel, m_bstrLabel);
}
else {
sLabel.Empty();
}
}
void CNode::MoveTo(int ix, int iy)
{
int dx = ix - m_ptOrigin.x;
int dy = iy - m_ptOrigin.y;
m_ptOrigin.x = ix;
m_ptOrigin.y = iy;
m_rcBounds.OffsetRect(dx, dy);
}
void CNode::Draw(CDC* pdc, CBrush* pbrBackground)
{
}
void CNode::MeasureLabelText(CDC* pdc, CRect& rcLabelText)
{
rcLabelText.SetRectEmpty();
}
CHMomObjectNode::CHMomObjectNode(CAssocGraph* pAssocGraph) : CNode(pAssocGraph)
{
m_rc = CRect(0, 0, 0, 0);
m_bstrObjectPath = NULL;
m_bstrArcLabel = NULL;
}
CHMomObjectNode::~CHMomObjectNode()
{
if (m_bstrObjectPath) {
SysFreeString(m_bstrObjectPath);
}
if (m_bstrArcLabel) {
SysFreeString(m_bstrArcLabel);
}
}
void CHMomObjectNode::SetObjectPath(BSTR bstrObjectPath)
{
if (m_bstrObjectPath) {
SysFreeString(m_bstrObjectPath);
}
m_bstrObjectPath = SysAllocString(bstrObjectPath);
}
void CHMomObjectNode::GetObjectPath(CString& sObjectPath)
{
if (m_bstrObjectPath != NULL) {
BStringToCString(sObjectPath, m_bstrObjectPath);
}
else {
sObjectPath.Empty();
}
}
void CHMomObjectNode::SetArcLabel(BSTR bstrArcLabel)
{
if (m_bstrArcLabel) {
SysFreeString(m_bstrArcLabel);
}
m_bstrArcLabel = SysAllocString(bstrArcLabel);
}
void CHMomObjectNode::GetArcLabel(CString& sArcLabel)
{
if (m_bstrArcLabel != NULL) {
BStringToCString(sArcLabel, m_bstrArcLabel);
}
else {
sArcLabel.Empty();
}
}
void CHMomObjectNode::GetConnectionPoint(int iConnection, CPoint& pt)
{
// The origin is always at the top-left corner of the icon
pt.y = m_ptOrigin.y + m_sizeIcon.cy / 2;
switch(iConnection) {
case CONNECT_LEFT:
pt.x = m_ptOrigin.x;
break;
case CONNECT_RIGHT:
pt.x = m_ptOrigin.x + m_sizeIcon.cx;
break;
case ICON_LEFT_MIDPOINT:
pt.x = m_ptOrigin.x;
break;
case ICON_RIGHT_MIDPOINT:
pt.x = m_ptOrigin.x + m_sizeIcon.cx;
}
}
void CHMomObjectNode::Draw(CDC* pdc, CBrush* pbrBackground)
{
if (m_picon) {
m_picon->Draw(pdc, m_ptOrigin.x, m_ptOrigin.y, (HBRUSH) *pbrBackground);
}
}
void COutRef::Draw(CDC* pdc, CBrush* pbrBackground)
{
if (m_picon) {
m_picon->Draw(pdc, m_ptOrigin.x, m_ptOrigin.y, (HBRUSH) *pbrBackground);
}
// Draw the label centered vertically and to the right of the icon.
int ixText = m_ptOrigin.x + m_sizeIcon.cx + CX_LABEL_LEADING;
int iyText = m_ptOrigin.y + m_sizeIcon.cy / 2 - CY_LABEL_FONT/2;
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
pdc->TextOut(ixText, iyText, sLabel, sLabel.GetLength());
}
void COutRef::MeasureLabelText(CDC* pdc, CRect& rcLabelText)
{
// Draw the label centered vertically and to the right of the icon.
int ixText = m_ptOrigin.x + m_sizeIcon.cx + CX_LABEL_LEADING;
int iyText = m_ptOrigin.y + m_sizeIcon.cy / 2 - CY_LABEL_FONT/2;
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
int nSize = sLabel.GetLength();
CSize size = pdc->GetTextExtent( sLabel, nSize);
rcLabelText.top = iyText;
rcLabelText.left = ixText;
rcLabelText.bottom = iyText + size.cy;
rcLabelText.right = ixText + size.cx;
}
CAssocEndpoint::CAssocEndpoint(CAssocGraph* pAssocGraph) : CHMomObjectNode(pAssocGraph)
{
}
void CAssocEndpoint::Draw(CDC* pdc, CBrush* pbrBackground)
{
if (m_picon) {
m_picon->Draw(pdc, m_ptOrigin.x, m_ptOrigin.y, (HBRUSH) *pbrBackground);
}
// Draw the label centered vertically and to the right of the icon.
int ixText = m_ptOrigin.x + m_sizeIcon.cx + CX_LABEL_LEADING;
int iyText = m_ptOrigin.y + m_sizeIcon.cy / 2 - CY_LABEL_FONT/2;
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
pdc->TextOut(ixText, iyText, sLabel, sLabel.GetLength());
}
void CAssocEndpoint::MeasureLabelText(CDC* pdc, CRect& rcLabelText)
{
// Draw the label centered vertically and to the right of the icon.
int ixText = m_ptOrigin.x + m_sizeIcon.cx + CX_LABEL_LEADING;
int iyText = m_ptOrigin.y + m_sizeIcon.cy / 2 - CY_LABEL_FONT/2;
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
int nSize = sLabel.GetLength();
CSize size = pdc->GetTextExtent( sLabel, nSize);
rcLabelText.top = iyText;
rcLabelText.left = ixText;
rcLabelText.bottom = iyText + size.cy;
rcLabelText.right = ixText + size.cx;
}
void CAssocEndpoint::Layout(CDC* pdc)
{
CHMomObjectNode::Layout(pdc);
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
CSize sizeText = pdc->GetTextExtent(sLabel, sLabel.GetLength());
m_rcBounds.left = m_ptOrigin.x;
m_rcBounds.right = m_ptOrigin.x + m_sizeIcon.cx + CX_LABEL_LEADING + sizeText.cx;
if (sizeText.cy > m_sizeIcon.cy) {
m_rcBounds.top = m_ptOrigin.y + m_sizeIcon.cy / 2 + sizeText.cy / 2;
m_rcBounds.bottom = m_rcBounds.top + sizeText.cy;
}
else {
m_rcBounds.top = m_ptOrigin.y;
m_rcBounds.bottom = m_ptOrigin.y + m_sizeIcon.cy;
}
}
//**********************************************************************
// CAssocEndpoint::LButtonDblClk
//
// This method is called to test for a hit on this node when the left mouse
// button is double-clicked.
//
// Parameters:
// [in] CDC* pdc
// The display context for measuring the label text, etc.
//
// [in] CPoint point
// The point where the mouse was clicked.
//
// [out] CNode*& pnd
// If the mouse is clicked in this node's rectangle, a pointer to
// this node is returned here, otherwise its value is not modified.
//
// [out] BOOL& bJumpToObject
// TRUE if double-clicking this node should cause a jump to the
// corresponding object.
//
// [out] COleVariant& varObjectPath
// The path to this object.
//
// Returns:
// BOOL
// TRUE if the mouse click hit this node, FALSE otherwise.
//
//**************************************************************************
BOOL CAssocEndpoint::LButtonDblClk(CDC* pdc, CPoint point, CNode*& pnd, BOOL& bJumpToObject, COleVariant& varObjectPath)
{
bJumpToObject = FALSE;
CRect rcIcon(m_ptOrigin.x, m_ptOrigin.y, m_ptOrigin.x + m_sizeIcon.cx, m_ptOrigin.y + m_sizeIcon.cy);
if (rcIcon.PtInRect(point)) {
if (m_bEnabled) {
bJumpToObject = TRUE;
}
pnd = this;
varObjectPath = m_bstrObjectPath;
return TRUE;
}
CRect rcLabelText;
MeasureLabelText(pdc, rcLabelText);
if (rcLabelText.PtInRect(point)) {
if (m_bEnabled) {
bJumpToObject = TRUE;
}
pnd = this;
varObjectPath = m_bstrObjectPath;
return TRUE;
}
return FALSE;
}
COutRef::COutRef(CAssocGraph* pAssocGraph) : CHMomObjectNode(pAssocGraph)
{
}
//**********************************************************************
// COutRef::LButtonDblClk
//
// This method is called to test for a hit on this node when the left mouse
// button is double-clicked.
//
// Parameters:
// [in] CDC* pdc
// The display context for measuring the label text, etc.
//
// [in] CPoint point
// The point where the mouse was clicked.
//
// [out] CNode*& pnd
// If the mouse is clicked in this node's rectangle, a pointer to
// this node is returned here, otherwise its value is not modified.
//
// [out] BOOL& bJumpToObject
// TRUE if double-clicking this node should cause a jump to the
// corresponding object.
//
// [out] COleVariant& varObjectPath
// The path to this object.
//
// Returns:
// BOOL
// TRUE if the mouse click hit this node, FALSE otherwise.
//
//**************************************************************************
BOOL COutRef::LButtonDblClk(CDC* pdc, CPoint point, CNode*& pnd, BOOL& bJumpToObject, COleVariant& varObjectPath)
{
bJumpToObject = FALSE;
CRect rcIcon(m_ptOrigin.x, m_ptOrigin.y, m_ptOrigin.x + m_sizeIcon.cx, m_ptOrigin.y + m_sizeIcon.cy);
if (rcIcon.PtInRect(point)) {
if (m_bEnabled) {
bJumpToObject = TRUE;
}
pnd = this;
varObjectPath = m_bstrObjectPath;
return TRUE;
}
CRect rcLabelText;
MeasureLabelText(pdc, rcLabelText);
if (rcLabelText.PtInRect(point)) {
if (m_bEnabled) {
bJumpToObject = TRUE;
}
pnd = this;
varObjectPath = m_bstrObjectPath;
return TRUE;
}
return FALSE;
}
void COutRef::Layout(CDC* pdc)
{
CHMomObjectNode::Layout(pdc);
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
CSize sizeText = pdc->GetTextExtent(sLabel, sLabel.GetLength());
m_rcBounds.left = m_ptOrigin.x;
m_rcBounds.right = m_ptOrigin.x + m_sizeIcon.cx + CX_LABEL_LEADING + sizeText.cx;
if (sizeText.cy > m_sizeIcon.cy) {
m_rcBounds.top = m_ptOrigin.y + m_sizeIcon.cy / 2 + sizeText.cy / 2;
m_rcBounds.bottom = m_rcBounds.top + sizeText.cy;
}
else {
m_rcBounds.top = m_ptOrigin.y;
m_rcBounds.bottom = m_ptOrigin.y + m_sizeIcon.cy;
}
}
CInRef::CInRef(CAssocGraph* pAssocGraph) : CHMomObjectNode(pAssocGraph)
{
}
BOOL CInRef::LButtonDblClk(CDC* pdc, CPoint point, CNode*& pnd, BOOL& bJumpToObject, COleVariant& varObjectPath)
{
bJumpToObject = FALSE;
CRect rcIcon(m_ptOrigin.x, m_ptOrigin.y, m_ptOrigin.x + m_sizeIcon.cx, m_ptOrigin.y + m_sizeIcon.cy);
if (rcIcon.PtInRect(point)) {
if (m_bEnabled) {
bJumpToObject = TRUE;
}
pnd = this;
varObjectPath = m_bstrObjectPath;
return TRUE;
}
CRect rcLabelText;
MeasureLabelText(pdc, rcLabelText);
if (rcLabelText.PtInRect(point)) {
if (m_bEnabled) {
bJumpToObject = TRUE;
}
pnd = this;
varObjectPath = m_bstrObjectPath;
return TRUE;
return TRUE;
}
return FALSE;
}
void CInRef::Draw(CDC* pdc, CBrush* pbrBackground)
{
if (m_picon) {
m_picon->Draw(pdc, m_ptOrigin.x, m_ptOrigin.y, (HBRUSH) *pbrBackground);
}
// Draw the label centered vertically and to the right of the icon.
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
CSize sizeText = pdc->GetTextExtent(sLabel, sLabel.GetLength());
int ixText = m_ptOrigin.x - CX_LABEL_LEADING - sizeText.cx;
int iyText = m_ptOrigin.y + m_sizeIcon.cy / 2 - CY_LABEL_FONT/2;
LimitLabelLength(sLabel);
pdc->TextOut(ixText, iyText, sLabel, sLabel.GetLength());
}
void CInRef::MeasureLabelText(CDC* pdc, CRect& rcLabelText)
{
// Draw the label centered vertically and to the right of the icon.
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
int nSize = sLabel.GetLength();
CSize size = pdc->GetTextExtent( sLabel, nSize);
int ixText = m_ptOrigin.x - CX_LABEL_LEADING - size.cx;
int iyText = m_ptOrigin.y + m_sizeIcon.cy / 2 - CY_LABEL_FONT/2;
rcLabelText.top = iyText;
rcLabelText.left = ixText;
rcLabelText.bottom = iyText + size.cy;
rcLabelText.right = ixText + size.cx;
}
void CInRef::Layout(CDC* pdc)
{
CHMomObjectNode::Layout(pdc);
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
CSize sizeText = pdc->GetTextExtent(sLabel, sLabel.GetLength());
m_rcBounds.left = m_ptOrigin.x - CX_LABEL_LEADING - sizeText.cx;
m_rcBounds.right = m_ptOrigin.x + m_sizeIcon.cx;
if (sizeText.cy > m_sizeIcon.cy) {
m_rcBounds.top = m_ptOrigin.y + m_sizeIcon.cy / 2 + sizeText.cy / 2;
m_rcBounds.bottom = m_rcBounds.top + sizeText.cy;
}
else {
m_rcBounds.top = m_ptOrigin.y;
m_rcBounds.bottom = m_ptOrigin.y + m_sizeIcon.cy;
}
}
BOOL CHMomObjectNode::ContainsPoint(CPoint pt)
{
return m_rc.PtInRect(pt);
}
CRootNode::CRootNode(CAssocGraph* pAssocGraph) : CHMomObjectNode(pAssocGraph)
{
m_bNeedsLayout = TRUE;
m_cyInRefs = 0;
m_cyOutRefs = 0;
m_cyAssociations = 0;
m_bDidInitialLayout = FALSE;
m_rc.left = 0;
m_rc.top = 0;
m_rc.right = 32;
m_rc.bottom = 32;
m_peditTitle = new CColorEdit;
}
CRootNode::~CRootNode()
{
Clear();
delete m_peditTitle;
}
//************************************************************************
// CAssocGraph::Create
//
// Create the association graph.
//
// Parameters:
// See the documentation for the MFC CWnd class.
//
// Returns:
// TRUE if the association graph window was created successfully, FALSE
// otherwise.
//
//************************************************************************
BOOL CRootNode::Create(CRect& rc, CWnd* pwndParent, BOOL bVisible)
{
DWORD dwStyle = ES_CENTER |
WS_CHILD |
ES_AUTOHSCROLL |
ES_AUTOVSCROLL |
ES_MULTILINE |
ES_READONLY;
if (bVisible) {
dwStyle |= WS_VISIBLE;
}
BOOL bDidCreate = m_peditTitle->Create(dwStyle, rc, pwndParent, 0);
if (bDidCreate) {
m_peditTitle->SetBackColor(DEFAULT_BACKGROUND_COLOR);
m_peditTitle->SetFont(&m_pAssocGraph->GetFont());
}
return bDidCreate;
}
//*******************************************************************
// CRootNode::CheckMouseHover
//
// Check to see if the mouse is hovering over an item. If so, display
// its "tooltip" label. Note that the CToolTip class was not used
// because it is necessary to display the hover text when the mouse
// hovers over non-rectangular areas like the association arcs.
//
// Parameters:
// CPoint pt
// The point where the mouse was when the timer went off.
//
// DWORD* pdwItemID
// The place to return the id of the item that is being hovered over.
//
// COleVariant& varHoverLabel
// The place to return the label to be displayed if the mouse is
// hovering over an item.
//
// Returns:
// TRUE if the mouse is hovering over an item and a label should be
// displayed, FALSE otherwise.
//
//**********************************************************************
BOOL CRootNode::CheckMouseHover(CPoint& pt, DWORD* pdwItemID, COleVariant& varHoverLabel)
{
// The left end of the association arc is the right connection point of
// the root node.
CPoint ptAssocArcLeft;
GetConnectionPoint(CONNECT_RIGHT, ptAssocArcLeft);
const long nAssociations = (long) m_paAssociations.GetSize();
const long nOutRefs = (long) m_paRefsOut.GetSize();
const long nInRefs = (long) m_paRefsIn.GetSize();
// Check to see if the mouse is directly over an association icon or
// an arc to the association icon.
long lIndex;
CRect rcIcon;
CPoint ptThisRight;
CAssoc2Node* pAssociation;
CPoint ptAssocLeft;
if ((nAssociations == 1) && (nOutRefs == 0)) {
// Check to see if the mouse is located over the association icons.
pAssociation = (CAssoc2Node*) m_paAssociations[0];
rcIcon.left = pAssociation->m_ptOrigin.x;
rcIcon.top = pAssociation->m_ptOrigin.y;
rcIcon.right = rcIcon.left + pAssociation->m_sizeIcon.cx;
rcIcon.bottom = rcIcon.top + pAssociation->m_sizeIcon.cy;
if (rcIcon.PtInRect(pt)) {
varHoverLabel = pAssociation->GetLabel();
*pdwItemID = pAssociation->ID();
return TRUE;
}
GetConnectionPoint(ICON_RIGHT_MIDPOINT, ptThisRight);
pAssociation->GetConnectionPoint(ICON_RIGHT_MIDPOINT, ptAssocLeft);
if (PointNearArc(pt, ARCTYPE_GENERIC, ptThisRight, ptAssocLeft)) {
varHoverLabel = pAssociation->GetArcLabel();
*pdwItemID = pAssociation->ArcId();
return TRUE;
}
if (pAssociation->CheckMouseHover(pt, pdwItemID, varHoverLabel)) {
return TRUE;
}
}
else {
for (lIndex=0; lIndex < nAssociations; ++lIndex) {
// Check to see if the mouse is located over one of the association icons.
pAssociation = (CAssoc2Node*) m_paAssociations[lIndex];
rcIcon.left = pAssociation->m_ptOrigin.x;
rcIcon.top = pAssociation->m_ptOrigin.y;
rcIcon.right = rcIcon.left + pAssociation->m_sizeIcon.cx;
rcIcon.bottom = rcIcon.top + pAssociation->m_sizeIcon.cy;
if (rcIcon.PtInRect(pt)) {
varHoverLabel = pAssociation->GetLabel();
*pdwItemID = pAssociation->ID();
return TRUE;
}
// Check to see if the mouse is located over an association arc.
CPoint ptAssocArcRight;
pAssociation->GetConnectionPoint(CONNECT_LEFT, ptAssocArcRight);
if (nAssociations < MANY_NODES) {
if (PointNearArc(pt, ARCTYPE_GENERIC, ptAssocArcLeft, ptAssocArcRight)) {
varHoverLabel = pAssociation->GetArcLabel();
*pdwItemID = pAssociation->ArcId();
return TRUE;
}
}
else {
CPoint ptAssocArcLeftManyNodes;
ptAssocArcLeftManyNodes.x = ptAssocArcLeft.x;
ptAssocArcLeftManyNodes.y = ptAssocArcRight.y;
if (PointNearArc(pt, ARCTYPE_GENERIC, ptAssocArcLeftManyNodes, ptAssocArcRight)) {
varHoverLabel = pAssociation->GetArcLabel();
*pdwItemID = pAssociation->ArcId();
return TRUE;
}
}
if (pAssociation->CheckMouseHover(pt, pdwItemID, varHoverLabel)) {
return TRUE;
}
}
}
// Check to see if the mouse is located over one of the outref arcs.
CPoint ptOutrefLeft;
COutRef* pOutRef;
if (nOutRefs==1 && nAssociations==0) {
GetConnectionPoint(ICON_RIGHT_MIDPOINT, ptThisRight);
pOutRef = (COutRef*) m_paRefsOut[0];
pOutRef->GetConnectionPoint(ICON_RIGHT_MIDPOINT, ptOutrefLeft);
if (PointNearArc(pt, ARCTYPE_ARROW_RIGHT, ptThisRight, ptOutrefLeft)) {
varHoverLabel = pOutRef->GetArcLabel();
*pdwItemID = pOutRef->ID();
return TRUE;
}
}
else {
for (lIndex=0; lIndex < nOutRefs; ++lIndex) {
pOutRef = (COutRef*) m_paRefsOut[lIndex];
CPoint ptOutrefArcRight;
pOutRef->GetConnectionPoint(CONNECT_LEFT, ptOutrefLeft);
if (PointNearArc(pt, ARCTYPE_ARROW_RIGHT, ptAssocArcLeft, ptOutrefLeft)) {
varHoverLabel = pOutRef->GetArcLabel();
*pdwItemID = pOutRef->ID();
return TRUE;
}
}
}
if (nInRefs > 0) {
CHMomObjectNode* pInRef;
CPoint ptInrefArcRight;
CPoint ptThisLeft;
if (nInRefs == 1) {
// The line from the inref to the node extends right up to the
// node itself.
CPoint ptThisLeft;
GetConnectionPoint(ICON_LEFT_MIDPOINT, ptThisLeft);
pInRef = (CHMomObjectNode*) m_paRefsIn[0];
pInRef->GetConnectionPoint(ICON_RIGHT_MIDPOINT, ptInrefArcRight);
if (PointNearArc(pt, ARCTYPE_ARROW_RIGHT, ptInrefArcRight, ptThisLeft)) {
varHoverLabel = pInRef->GetArcLabel();
*pdwItemID = pInRef->ID();
return TRUE;
}
}
else {
GetConnectionPoint(CONNECT_LEFT, ptThisLeft);
// Check to see if the mouse is located over one of the inref arcs.
for (lIndex=0; lIndex < nInRefs; ++lIndex) {
pInRef = (CHMomObjectNode*) m_paRefsIn[lIndex];
pInRef->GetConnectionPoint(CONNECT_RIGHT, ptInrefArcRight);
if (PointNearArc(pt, ARCTYPE_JOIN_ON_RIGHT, ptInrefArcRight, ptThisLeft)) {
varHoverLabel = pInRef->GetArcLabel();
*pdwItemID = pInRef->ID();
return TRUE;
}
}
}
}
return FALSE;
}
void CRootNode::MoveTo(int ix, int iy)
{
int dx = ix - m_ptOrigin.x;
int dy = iy - m_ptOrigin.y;
CNode::MoveTo(ix, iy);
long nCount, lIndex;
nCount = (long) m_paAssociations.GetSize();
for (lIndex=0; lIndex < nCount; ++lIndex) {
CAssoc2Node* pAssociation = (CAssoc2Node*) m_paAssociations[lIndex];
pAssociation->MoveTo(pAssociation->m_ptOrigin.x + dx, pAssociation->m_ptOrigin.y + dy);
}
nCount = (long) m_paRefsOut.GetSize();
for (lIndex = 0; lIndex < nCount; ++lIndex) {
COutRef* pOutRef = (COutRef*) m_paRefsOut[lIndex];
pOutRef->MoveTo(pOutRef->m_ptOrigin.x + dx, pOutRef->m_ptOrigin.y + dy);
}
nCount = (long) m_paRefsIn.GetSize();
for (lIndex = 0; lIndex < nCount; ++lIndex) {
CInRef* pInRef = (CInRef*) m_paRefsIn[lIndex];
pInRef->MoveTo(pInRef->m_ptOrigin.x + dx, pInRef->m_ptOrigin.y + dy);
}
}
//**********************************************************************
// CRootNode::LButtonDblClk
//
// This method is called to test for a hit on this node when the left mouse
// button is double-clicked.
//
// Parameters:
// [in] CDC* pdc
// The display context for measuring the label text, etc.
//
// [in] CPoint point
// The point where the mouse was clicked.
//
// [out] CNode*& pnd
// If the mouse is clicked in this node's rectangle, a pointer to
// this node is returned here, otherwise NULL.
//
// [out] BOOL& bJumpToObject
// TRUE if double-clicking this node should cause a jump to the
// corresponding object.
//
// [out] COleVariant& varObjectPath
// The path to this object.
//
// Returns:
// BOOL
// TRUE if the mouse click hit this node, FALSE otherwise.
//
//**************************************************************************
BOOL CRootNode::LButtonDblClk(
/* [in] */ CDC* pdc,
/* [in] */ CPoint point,
/* [out] */ CNode*& pnd,
/* [out] */ BOOL& bJumpToObject,
/* [out] */ COleVariant& varObjectPath)
{
pnd = NULL;
bJumpToObject = FALSE;
if (!m_rcBounds.PtInRect(point)) {
return FALSE;
}
CRect rcIcon(0, 0, m_sizeIcon.cx, m_sizeIcon.cy);
if (rcIcon.PtInRect(point)) {
// The user clicked the current node, so a jump isn't required.
m_pAssocGraph->GetPath(varObjectPath);
bJumpToObject = FALSE;
return TRUE;
}
// Check to see if any of the associations were clicked.
long nCount, lIndex;
nCount = (long) m_paAssociations.GetSize();
for (lIndex=0; lIndex < nCount; ++lIndex) {
CAssoc2Node* pAssociation = (CAssoc2Node*) m_paAssociations[lIndex];
if (pAssociation->LButtonDblClk(pdc, point, pnd, bJumpToObject, varObjectPath)) {
return TRUE;
}
}
// Check to see if any of the out-refs were clicked.
nCount = (long) m_paRefsOut.GetSize();
for (lIndex = 0; lIndex < nCount; ++lIndex) {
COutRef* pOutRef = (COutRef*) m_paRefsOut[lIndex];
if (pOutRef->LButtonDblClk(pdc, point, pnd, bJumpToObject, varObjectPath)) {
return TRUE;
}
}
nCount = (long) m_paRefsIn.GetSize();
for (lIndex = 0; lIndex < nCount; ++lIndex) {
CInRef* pInRef = (CInRef*) m_paRefsIn[lIndex];
if (pInRef->LButtonDblClk(pdc, point, pnd, bJumpToObject, varObjectPath)) {
return TRUE;
}
}
CRect rcLabelText;
MeasureLabelText(pdc, rcLabelText);
if (rcLabelText.PtInRect(point)) {
varObjectPath = m_bstrObjectPath;
return TRUE;
}
return FALSE;
}
void CRootNode::Clear()
{
long nSize;
long lIndex;
m_bNeedsLayout = TRUE;
nSize = (long) m_paAssociations.GetSize();
for (lIndex = 0; lIndex < nSize; ++lIndex) {
CAssoc2Node* pAssoc = (CAssoc2Node*) m_paAssociations[lIndex];
delete pAssoc;
}
m_paAssociations.RemoveAll();
nSize = (long) m_paRefsIn.GetSize();
for (lIndex = 0; lIndex < nSize; ++lIndex) {
CInRef* pInRef = (CInRef*) m_paRefsIn[lIndex];
delete pInRef;
}
m_paRefsIn.RemoveAll();
nSize = (long) m_paRefsOut.GetSize();
for (lIndex =0; lIndex < nSize; ++lIndex) {
COutRef* pOutRef = (COutRef*) m_paRefsOut[lIndex];
delete pOutRef;
}
m_paRefsOut.RemoveAll();
if (m_bstrLabel) {
SysFreeString(m_bstrLabel);
m_bstrLabel = NULL;
}
if (m_bstrObjectPath) {
SysFreeString(m_bstrObjectPath);
m_bstrObjectPath = NULL;
}
m_peditTitle->SetWindowText(_T(""));
m_peditTitle->ShowWindow(SW_HIDE);
m_picon = NULL;
}
long CRootNode::AddAssociation(CIconSource* pIconSource, BOOL bIsClass)
{
m_bNeedsLayout = TRUE;
long lAssociation = (long) m_paAssociations.GetSize();
CAssoc2Node* pAssoc = new CAssoc2Node(m_pAssocGraph, pIconSource, bIsClass);
m_paAssociations.Add(pAssoc);
return lAssociation;
}
long CRootNode::AddInRef()
{
m_bNeedsLayout = TRUE;
long lInRef = (long) m_paRefsIn.GetSize();
CInRef* pInRef = new CInRef(m_pAssocGraph);
m_paRefsIn.Add(pInRef);
return lInRef;
}
long CRootNode::AddOutRef()
{
m_bNeedsLayout = TRUE;
long lOutRef = (long) m_paRefsOut.GetSize();
COutRef* pOutRef = new COutRef(m_pAssocGraph);
m_paRefsOut.Add(pOutRef);
return lOutRef;
}
CAssoc2Node::CAssoc2Node(CAssocGraph* pAssocGraph, CIconSource* pIconSource, BOOL bIsClass) : CHMomObjectNode(pAssocGraph)
{
m_sizeIcon.cx = CX_SMALL_ICON;
m_sizeIcon.cy = CY_SMALL_ICON;
m_picon = &pIconSource->GetAssocIcon(SMALL_ICON, bIsClass);
}
CAssoc2Node::~CAssoc2Node()
{
long nEndpoints = (long) m_paEndpoints.GetSize();
for (long lEndpoint=0; lEndpoint<nEndpoints; ++lEndpoint) {
CAssocEndpoint* pEndpoint;
pEndpoint = (CAssocEndpoint*) m_paEndpoints[lEndpoint];
delete pEndpoint;
}
m_paEndpoints.RemoveAll();
}
//**********************************************************************
// CAssoc2Node::LButtonDblClk
//
// This method is called to test for a hit on this node when the left mouse
// button is double-clicked.
//
// Parameters:
// [in] CDC* pdc
// The display context for measuring the label text, etc.
//
// [in] CPoint point
// The point where the mouse was clicked.
//
// [out] CNode*& pnd
// If the mouse is clicked in this node's rectangle, a pointer to
// this node is returned here, otherwise it is not modified.
//
// [out] BOOL& bJumpToObject
// TRUE if double-clicking this node should cause a jump to the
// corresponding object.
//
// [out] COleVariant& varObjectPath
// The path to this object.
//
// Returns:
// BOOL
// TRUE if the mouse click hit this node, FALSE otherwise.
//
//**************************************************************************
BOOL CAssoc2Node::LButtonDblClk(
/* [in] */ CDC* pdc,
/* [in] */ CPoint point,
/* [out] */ CNode*& pnd,
/* [out] */ BOOL& bJumpToObject,
/* [out] */ COleVariant& varObjectPath)
{
bJumpToObject = FALSE;
CRect rcIcon(m_ptOrigin.x, m_ptOrigin.y, m_ptOrigin.x + m_sizeIcon.cx, m_ptOrigin.y + m_sizeIcon.cy);
if (rcIcon.PtInRect(point)) {
if (m_bEnabled) {
bJumpToObject = TRUE;
}
varObjectPath = m_bstrObjectPath;
return TRUE;
}
long nEndpoints = (long) m_paEndpoints.GetSize();
for (long lEndpoint=0; lEndpoint < nEndpoints; ++lEndpoint) {
CAssocEndpoint* pEndpoint = (CAssocEndpoint*) m_paEndpoints[lEndpoint];
if (pEndpoint->LButtonDblClk(pdc, point, pnd, bJumpToObject, varObjectPath)) {
return TRUE;
}
}
return FALSE;
}
long CAssoc2Node::AddEndpoint()
{
long lEndpoint = (long) m_paEndpoints.GetSize();
CAssocEndpoint* pEndpoint = new CAssocEndpoint(m_pAssocGraph);
m_paEndpoints.Add(pEndpoint);
return lEndpoint;
}
//*******************************************************************
// CAssoc2Node::CheckMouseHover
//
// Check to see if the mouse is hovering over an arc connecting the
// association icon to an endpoint. If so, return TRUE so that the
// "arclabel" for the endpoint is displayed.
//
// Parameters:
// CPoint pt
// The point where the mouse was when the timer went off.
//
// DWORD* pdwItemID
// The place to return the id of the item that is being hovered over.
//
// COleVariant& varHoverLabel
// The place to return the label to be displayed if the mouse is
// hovering over an item.
//
// Returns:
// TRUE if the mouse is hovering over an item and a label should be
// displayed, FALSE otherwise.
//
//**********************************************************************
BOOL CAssoc2Node::CheckMouseHover(CPoint& pt, DWORD* pdwItemID, COleVariant& varHoverLabel)
{
// The left end of the endpoint arc is the right connection point of
// the association node.
CPoint ptEndpointArcLeft;
GetConnectionPoint(CONNECT_RIGHT, ptEndpointArcLeft);
// Check to see if the mouse is near an arc connecting the association to an
// endpoint.
long nCount, lIndex;
nCount = (long) m_paEndpoints.GetSize();
CAssocEndpoint* pEndpoint;
CPoint ptEndpointArcRight;
for (lIndex=0; lIndex < nCount; ++lIndex) {
// Check to see if the mouse is located over an association arc.
pEndpoint = (CAssocEndpoint*) m_paEndpoints[lIndex];
if (nCount == 1) {
pEndpoint->GetConnectionPoint(ICON_LEFT_MIDPOINT, ptEndpointArcRight);
}
else {
pEndpoint->GetConnectionPoint(CONNECT_LEFT, ptEndpointArcRight);
}
if (PointNearArc(pt, ARCTYPE_GENERIC, ptEndpointArcLeft, ptEndpointArcRight)) {
varHoverLabel = pEndpoint->GetArcLabel();
*pdwItemID = pEndpoint->ID();
return TRUE;
}
}
return FALSE;
}
void CAssoc2Node::Layout(CDC* pdc)
{
long nEndpoints = (long) m_paEndpoints.GetSize();
CPoint ptConnect;
GetConnectionPoint(CONNECT_RIGHT, ptConnect);
int iy = ptConnect.y - (nEndpoints * CY_LEAF) / 2 + CY_LEAF/2;
int ix;
if (nEndpoints > 1) {
ix = ptConnect.x + CX_COLUMN3;
}
else {
ix = ptConnect.x + CX_ARC_SEGMENT2;
}
m_rcBounds.SetRect(m_ptOrigin.x,
m_ptOrigin.y,
m_ptOrigin.x + m_sizeIcon.cx,
m_ptOrigin.y + m_sizeIcon.cy);
for (long lEndpoint=0; lEndpoint < nEndpoints; ++lEndpoint) {
CAssocEndpoint* pEndpoint = (CAssocEndpoint*) m_paEndpoints[lEndpoint];
pEndpoint->MoveTo(ix, iy - pEndpoint->m_sizeIcon.cy /2 );
pEndpoint->Layout(pdc);
m_rcBounds.UnionRect(m_rcBounds, pEndpoint->m_rcBounds);
iy += CY_LEAF;
}
}
void CAssoc2Node::MoveTo(int ix, int iy)
{
int dx = ix - m_ptOrigin.x;
int dy = iy - m_ptOrigin.y;
CNode::MoveTo(ix, iy);
long nEndpoints = (long) m_paEndpoints.GetSize();
for (long lEndpoint = 0; lEndpoint < nEndpoints; ++lEndpoint) {
CAssocEndpoint* pEndpoint = (CAssocEndpoint*) m_paEndpoints[lEndpoint];
pEndpoint->MoveTo(pEndpoint->m_ptOrigin.x + dx, pEndpoint->m_ptOrigin.y + dy);
}
m_rcBounds.OffsetRect(dx, dy);
}
void CAssoc2Node::Draw(CDC* pdc, CBrush* pbrBackground)
{
if (m_picon) {
m_picon->Draw(pdc, m_ptOrigin.x, m_ptOrigin.y, (HBRUSH) *pbrBackground);
}
CPoint ptVertix1;
CPoint ptVertix2;
CPoint ptVertix3;
CPoint ptIconMidpoint;
long nEndpoints = (long) m_paEndpoints.GetSize();
long lEndpoint;
CAssocEndpoint* pEndpoint;
GetConnectionPoint(ICON_RIGHT_MIDPOINT, ptIconMidpoint);
switch(nEndpoints) {
case 0:
break;
case 1:
// If there is only a single endpoint connected to the association icon
// then don't bother to draw the "connector" circle because the arc
// will not branch.
pEndpoint = (CAssocEndpoint*) m_paEndpoints[0];
pEndpoint->GetConnectionPoint(CONNECT_LEFT, ptVertix2);
DrawArc(pdc, ARCTYPE_GENERIC, ptIconMidpoint, ptVertix2);
pEndpoint->Draw(pdc, pbrBackground);
pEndpoint->GetConnectionPoint(CONNECT_RIGHT, ptVertix1);
break;
default:
// There is more than one endpoint connected to the association icon. This
// means that it is necessary to draw a "stub" and "connector" so that the
// arcs to the endpoints can branch out in a way that has eye-appeal.
GetConnectionPoint(CONNECT_RIGHT, ptVertix1);
pdc->MoveTo(ptIconMidpoint);
pdc->LineTo(ptVertix1);
for (lEndpoint =0; lEndpoint < nEndpoints; ++lEndpoint) {
pEndpoint = (CAssocEndpoint*) m_paEndpoints[lEndpoint];
pEndpoint->GetConnectionPoint(CONNECT_LEFT, ptVertix2);
DrawArc(pdc, ARCTYPE_GENERIC, ptVertix1, ptVertix2);
pEndpoint->Draw(pdc, pbrBackground);
}
// Draw the connection circle on top of the vertex of the arcs so
// that it looks nice.
pdc->Ellipse(ptVertix1.x - CONNECTION_POINT_RADIUS,
ptVertix1.y - CONNECTION_POINT_RADIUS,
ptVertix1.x + CONNECTION_POINT_RADIUS,
ptVertix1.y + CONNECTION_POINT_RADIUS);
break;
}
}
void CAssoc2Node::DrawBoundingRect(CDC* pdc)
{
CRect rc;
GetBoundingRect(rc);
CBrush brBlue(RGB(64, 64, 255));
pdc->FrameRect(&rc, &brBlue );
}
void CAssoc2Node::GetBoundingRect(CRect& rc)
{
long nEndpoints = (long) m_paEndpoints.GetSize();
rc.left = m_ptOrigin.x;
rc.right = m_ptOrigin.x + m_sizeIcon.cx + CX_COLUMN3 + CX_LARGE_ICON;
switch (nEndpoints) {
case 0:
rc.top = m_ptOrigin.y - m_sizeIcon.cy / 2;
rc.bottom = rc.top + m_sizeIcon.cy;
break;
case 1:
rc.top = m_ptOrigin.y - (CY_LEAF / 2) + (m_sizeIcon.cy / 2);
rc.bottom = rc.top + CY_LEAF;
break;
default:
rc.top = m_ptOrigin.y - ((nEndpoints * CY_LEAF) / 2) + (m_sizeIcon.cy / 2);
rc.bottom = rc.top + nEndpoints * CY_LEAF;
rc.right += CX_CONNECT_STUB_SHORT;
break;
}
}
int CAssoc2Node::RecalcHeight()
{
CRect rc;
GetBoundingRect(rc);
return rc.Height();
}
void CAssoc2Node::GetConnectionPoint(int iConnection, CPoint& pt)
{
// The origin is always at the top-left corner of the icon
if (iConnection == CONNECT_RIGHT) {
// The right connection point is skewed to the right when there
// is more than one endpoint to make room for the connector stub.
pt.y = m_ptOrigin.y + m_sizeIcon.cy / 2;
if (m_paEndpoints.GetSize() > 1) {
pt.x = m_ptOrigin.x + m_sizeIcon.cx + CX_CONNECT_STUB_SHORT;
}
else {
pt.x = m_ptOrigin.x + m_sizeIcon.cx;
}
}
else {
CHMomObjectNode::GetConnectionPoint(iConnection, pt);
}
}
void CRootNode::GetBoundingRect(CRect& rc)
{
// Return the bounding rectangle for the entire graph.
rc = m_rcBounds;
}
void CRootNode::Layout(CDC* pdc)
{
m_bDidInitialLayout = TRUE;
long lIndex;
// First compute the total height of the association graph, the InRefs graph
// and the OutRefs graph.
CAssoc2Node* pAssociation;
m_rcBounds.SetRect(m_ptOrigin.x,
m_ptOrigin.y,
m_ptOrigin.x + m_sizeIcon.cx,
m_ptOrigin.y + m_sizeIcon.cy);
CSize size;
m_cyAssociations = 0;
long nAssociations= (long) m_paAssociations.GetSize();
for (lIndex=0; lIndex<nAssociations; ++lIndex) {
pAssociation = (CAssoc2Node*) m_paAssociations[lIndex];
m_cyAssociations += pAssociation->RecalcHeight();
}
COutRef* pOutRef;
m_cyOutRefs = 0;
long nOutRefs = (long) m_paRefsOut.GetSize();
for (lIndex = 0; lIndex < nOutRefs; ++lIndex) {
pOutRef = (COutRef*) m_paRefsOut[lIndex];
m_cyOutRefs += pOutRef->RecalcHeight();
}
CInRef* pInRef;
m_cyInRefs = 0;
long nInRefs = (long) m_paRefsIn.GetSize();
for (lIndex = 0; lIndex < nInRefs; ++lIndex) {
pInRef = (CInRef*) m_paRefsIn[lIndex];
m_cyInRefs += pInRef->RecalcHeight();
}
// We will layout the associations in a column that is to the right of the
// current object. The layout is done such that the vertical midpoint of the bounding
// rectangle of the union of associations and outrefs is the vertical midpoint of
// the current object's icon.
int iy;
int ix;
iy = m_ptOrigin.y + m_sizeIcon.cy/2 - (m_cyAssociations + m_cyOutRefs) / 2;
if ((nAssociations + nOutRefs) < 2) {
ix = m_ptOrigin.x + CX_COLUMN1;
}
else {
ix = m_ptOrigin.x + CX_COLUMN2;
}
int cy;
for (lIndex=0; lIndex < nAssociations; ++lIndex) {
pAssociation = (CAssoc2Node*) m_paAssociations[lIndex];
cy = pAssociation->RecalcHeight();
pAssociation->MoveTo(ix, iy + (cy / 2) - (pAssociation->m_sizeIcon.cy / 2));
pAssociation->Layout(pdc);
iy += cy;
m_rcBounds.UnionRect(m_rcBounds, pAssociation->m_rcBounds);
}
// Layout the OutRefs
for (lIndex=0; lIndex < nOutRefs; ++lIndex) {
pOutRef = (COutRef*) m_paRefsOut[lIndex];
cy = pOutRef->RecalcHeight();
pOutRef->MoveTo(ix, iy + (cy / 2) - (pOutRef->m_sizeIcon.cy / 2));
pOutRef->Layout(pdc);
iy += cy;
m_rcBounds.UnionRect(m_rcBounds, pOutRef->m_rcBounds);
}
// Layout the InRefs
if (nInRefs == 1) {
ix = m_ptOrigin.x - CX_COLUMN1;
iy = m_ptOrigin.y;
}
else {
ix = m_ptOrigin.x - CX_COLUMN2;
iy = m_ptOrigin.y + m_sizeIcon.cy/2 - m_cyInRefs / 2;
}
for(lIndex=0; lIndex < nInRefs; ++lIndex) {
pInRef = (CInRef*) m_paRefsIn[lIndex];
pInRef->MoveTo(ix, iy);
pInRef->Layout(pdc);
iy += pInRef->RecalcHeight();
m_rcBounds.UnionRect(m_rcBounds, pInRef->m_rcBounds);
}
CRect rcLabel;
GetLabelRect(pdc, rcLabel);
m_rcBounds.UnionRect(m_rcBounds, rcLabel);
m_bNeedsLayout = FALSE;
}
void CRootNode::GetConnectionPoint(int iConnection, CPoint& pt)
{
pt.y = m_ptOrigin.y + m_sizeIcon.cy / 2;
switch(iConnection) {
case CONNECT_LEFT:
pt.x = m_ptOrigin.x - CX_CONNECT_STUB;
break;
case CONNECT_RIGHT:
pt.x = m_ptOrigin.x + m_sizeIcon.cx + CX_CONNECT_STUB;
break;
default:
CHMomObjectNode::GetConnectionPoint(iConnection, pt);
break;
}
}
void CRootNode::Draw(CDC* pdc, CBrush* pbrBackground)
{
CBrush brBlack(RGB(0, 0, 0));
CBrush* pbrPrev;
pbrPrev = pdc->SelectObject(&brBlack);
pdc->SetPolyFillMode(ALTERNATE);
pdc->SelectObject(pbrPrev);
if (m_picon) {
m_picon->Draw(pdc, m_ptOrigin.x, m_ptOrigin.y, (HBRUSH) *pbrBackground);
}
// pdc->DrawIcon(m_ptOrigin.x, m_ptOrigin.y, (HICON) icoRootNode);
const long nAssociations = (long) m_paAssociations.GetSize();
const long nOutRefs = (long) m_paRefsOut.GetSize();
const long nInRefs = (long) m_paRefsIn.GetSize();
CPoint ptConnectRight;
CPoint ptConnectLeft;
CPoint ptIconConnectRight;
BOOL bStubOnRight = (nAssociations + nOutRefs) > 0;
BOOL bStubOnLeft = nInRefs > 0;
GetConnectionPoint(CONNECT_RIGHT, ptConnectLeft);
GetConnectionPoint(ICON_RIGHT_MIDPOINT, ptIconConnectRight);
// Draw the short line for the stub on the right.
if (bStubOnRight) {
pdc->MoveTo(ptIconConnectRight);
pdc->LineTo(ptConnectLeft);
}
CRect rcClient;
m_pAssocGraph->GetClientRect(rcClient);
CPoint ptOrg = m_pAssocGraph->GetOrigin();
rcClient.OffsetRect(ptOrg);
// Draw the associations
long lAssociation;
if (nAssociations < MANY_NODES) {
// Draw the arcs to the assocations as a series of angled lines that fan out
// from a central connection point.
for (lAssociation = 0; lAssociation < nAssociations; ++lAssociation) {
CAssoc2Node* pAssociation = (CAssoc2Node*) m_paAssociations[lAssociation];
pAssociation->GetConnectionPoint(CONNECT_LEFT, ptConnectRight);
DrawArc(pdc, ARCTYPE_GENERIC, ptConnectLeft, ptConnectRight);
pAssociation->Draw(pdc, pbrBackground);
}
}
else {
// If there are too many nodes, it looks much better to draw the connection arcs
// as a series of horizontal lines attached to a vertical line (without this special
// case, the arcs would overlap and you would end up with an ugly black area where
// the arcs fan out from the common point.)
int iyMax = 0;
int iyMin = 0;
for (lAssociation = 0; lAssociation < nAssociations; ++lAssociation) {
CAssoc2Node* pAssociation = (CAssoc2Node*) m_paAssociations[lAssociation];
pAssociation->GetConnectionPoint(CONNECT_LEFT, ptConnectRight);
if (ptConnectRight.y > iyMax) {
iyMax = ptConnectRight.y;
}
if (ptConnectRight.y < iyMin) {
iyMin = ptConnectRight.y;
}
if (pAssociation->InVerticalExtent(rcClient)) {
pdc->MoveTo(ptConnectLeft.x, ptConnectRight.y);
pdc->LineTo(ptConnectRight.x, ptConnectRight.y);
pAssociation->Draw(pdc, pbrBackground);
}
}
pdc->MoveTo(ptConnectLeft.x, iyMin);
pdc->LineTo(ptConnectLeft.x, iyMax);
}
// Draw the OutRefs
long lOutRef;
for (lOutRef = 0; lOutRef < nOutRefs; ++lOutRef) {
CHMomObjectNode* pOutRef = (CHMomObjectNode*) m_paRefsOut[lOutRef];
if (pOutRef->InVerticalExtent(rcClient)) {
pOutRef->GetConnectionPoint(CONNECT_LEFT, ptConnectRight);
DrawArc(pdc, ARCTYPE_ARROW_RIGHT, ptConnectLeft, ptConnectRight);
pOutRef->Draw(pdc, pbrBackground);
}
}
if ((nOutRefs + nAssociations) > 1) {
if (bStubOnRight) {
pdc->Ellipse(ptConnectLeft.x - CONNECTION_POINT_RADIUS,
ptConnectLeft.y - CONNECTION_POINT_RADIUS,
ptConnectLeft.x + CONNECTION_POINT_RADIUS,
ptConnectLeft.y + CONNECTION_POINT_RADIUS);
}
}
CPoint ptConnectRoot;
CPoint ptConnectIcon;
GetConnectionPoint(ICON_LEFT_MIDPOINT, ptConnectIcon);
GetConnectionPoint(CONNECT_LEFT, ptConnectRoot);
if (bStubOnLeft) {
DrawArc(pdc, ARCTYPE_ARROW_RIGHT, ptConnectRoot, ptConnectIcon);
}
// Draw the InRefs
CPoint ptConnectNode;
for (long lInRef = 0; lInRef < nInRefs; ++lInRef) {
CHMomObjectNode* pInRef = (CHMomObjectNode*) m_paRefsIn[lInRef];
if (pInRef->InVerticalExtent(rcClient)) {
pInRef->GetConnectionPoint(CONNECT_RIGHT, ptConnectNode);
DrawArc(pdc, ARCTYPE_JOIN_ON_RIGHT, ptConnectNode, ptConnectRoot);
pInRef->Draw(pdc, pbrBackground);
}
}
DrawTitle(pdc, ptConnectLeft, ptConnectRight);
if (nInRefs > 1) {
if (bStubOnLeft) {
pdc->Ellipse(ptConnectRoot.x - CONNECTION_POINT_RADIUS,
ptConnectRoot.y - CONNECTION_POINT_RADIUS,
ptConnectRoot.x + CONNECTION_POINT_RADIUS,
ptConnectRoot.y + CONNECTION_POINT_RADIUS);
}
}
}
void CRootNode::SetLabel(BSTR bstrLabel)
{
CHMomObjectNode::SetLabel(bstrLabel);
CString sLabel = bstrLabel;
LimitLabelLength(sLabel);
m_peditTitle->SetWindowText(sLabel);
}
void CRootNode::DrawTitle(CDC* pdc, CPoint& ptConnectLeft, CPoint& ptConnectRight)
{
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
int nchLabel = sLabel.GetLength();
CPoint ptWindowOrigin = pdc->GetWindowOrg();
CRect rcText;
GetLabelRect(pdc, rcText);
int ixMid = (rcText.left + rcText.right) / 2;
rcText.left = ixMid - CX_ROOT_TITLE / 2;
rcText.right = rcText.left + CX_ROOT_TITLE;
rcText.bottom = rcText.top + CY_ROOT_TITLE;
ptWindowOrigin.x = -ptWindowOrigin.x;
ptWindowOrigin.y = -ptWindowOrigin.y;
rcText.OffsetRect(ptWindowOrigin);
m_peditTitle->MoveWindow(rcText);
m_peditTitle->ShowWindow(SW_SHOW);
m_peditTitle->RedrawWindow();
}
void CRootNode::MeasureLabelText(CDC* pdc, CRect& rcLabelText)
{
m_peditTitle->GetRect(rcLabelText);
}
void CRootNode::GetLabelRect(CDC* pdc, CRect& rcLabel)
{
CString sLabel;
GetLabel(sLabel);
LimitLabelLength(sLabel);
CSize sizeText = pdc->GetTextExtent(sLabel, sLabel.GetLength());
rcLabel.left = m_ptOrigin.x + m_sizeIcon.cx / 2 - sizeText.cx / 2;
rcLabel.top = m_ptOrigin.x + m_sizeIcon.cy + CY_LABEL_LEADING;
rcLabel.right = rcLabel.left + sizeText.cx;
rcLabel.bottom = rcLabel.top + sizeText.cy;
}
void CAssocGraph::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
CRect rcClient;
GetClientRect(rcClient);
INT iCurPos = GetScrollPos(SB_VERT);
INT nMinPos, nMaxPos;
GetScrollRange(SB_VERT, &nMinPos, &nMaxPos);
INT iNewPos = iCurPos;
switch(nSBCode) {
case SB_BOTTOM:
iNewPos = 150;
break;
case SB_LINEDOWN:
iNewPos = iCurPos + 1;
break;
case SB_LINEUP:
iNewPos = iCurPos - 1;
break;
case SB_PAGEDOWN:
iNewPos = iCurPos + rcClient.Height() / DY_SCROLL_UNIT;
break;
case SB_PAGEUP:
iNewPos = iCurPos - rcClient.Height() / DY_SCROLL_UNIT;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
iNewPos = nPos;
break;
case SB_ENDSCROLL:
// Nothing special happens when we are finished scrolling.
return;
break;
case SB_TOP:
iNewPos = nMinPos;
break;
default:
ASSERT(FALSE);
iNewPos = iCurPos;
return;
}
if (iNewPos > nMaxPos) {
iNewPos = nMaxPos;
}
else if (iNewPos < nMinPos) {
iNewPos = nMinPos;
}
if (iNewPos == iCurPos) {
return;
}
ScrollWindowEx(0, -(iNewPos - iCurPos) * DY_SCROLL_UNIT , rcClient, rcClient, NULL, NULL, SW_INVALIDATE | SW_SCROLLCHILDREN );
SetScrollPos(SB_VERT, iNewPos);
}
void CAssocGraph::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Add your message handler code here and/or call default
CRect rcClient;
GetClientRect(rcClient);
INT iCurPos = GetScrollPos(SB_HORZ);
INT nMinPos, nMaxPos;
GetScrollRange(SB_HORZ, &nMinPos, &nMaxPos);
INT iNewPos = iCurPos;
switch(nSBCode) {
case SB_LEFT:
case SB_LINELEFT:
iNewPos = iCurPos - 1;
break;
case SB_RIGHT:
case SB_LINERIGHT:
iNewPos = iCurPos + 1;
break;
case SB_PAGELEFT:
iNewPos = iCurPos - rcClient.Width() / DX_SCROLL_UNIT;
break;
case SB_PAGERIGHT:
iNewPos = iCurPos + rcClient.Width() / DX_SCROLL_UNIT;
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
iNewPos = nPos;
break;
}
// Make sure the new position is within the scroll range
if (iNewPos < nMinPos) {
iNewPos = nMinPos;
}
else if (iNewPos > nMaxPos) {
iNewPos = nMaxPos;
}
// Do nothing if the scroll position wasn't changed
if (iCurPos == iNewPos) {
return;
}
ScrollWindowEx((iCurPos - iNewPos) * DX_SCROLL_UNIT, 0, rcClient, rcClient, NULL, NULL, SW_INVALIDATE | SW_SCROLLCHILDREN );
SetScrollPos(SB_HORZ, iNewPos);
UpdateWindow();
}
void CAssocGraph::OnSize(UINT nType, int cx, int cy)
{
#if 0
CRect rc;
rc.top = 0;
rc.left = 0;
rc.right = cx;
rc.bottom = cy;
InvalidateRect(rc);
#endif //0
CWnd::OnSize(nType, cx, cy);
if (!m_proot->m_bDidInitialLayout) {
return;
}
SetScrollRanges();
UpdateWindow();
}
//**************************************************************
// CAssocGraph::Clear
//
// Clear the association graph so that no object is selected.
//
// Parameters:
// [in] const BOOL bRedrawWindow
// TRUE if the association graph window should be redrawn
// after being cleared.
//
// Returns:
// Nothing.
//
//**************************************************************
void CAssocGraph::Clear(const BOOL bRedrawWindow)
{
m_proot->Clear();
m_bNeedsRefresh = FALSE;
if (bRedrawWindow) {
RedrawWindow();
}
}
void CAssocGraph::PumpMessages()
{
// Must call Create() before using the dialog
ASSERT(m_hWnd!=NULL);
MSG msg;
// Handle dialog messages
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if(!IsDialogMessage(&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
//********************************************************************
// CAssocGraph::Refresh
//
// Load the association graph with information corresponding to the
// current object.
//
// Parameters:
// None.
//
// Returns:
// BOOL
// TRUE if the user canceled the refresh.
//
//********************************************************************
BOOL CAssocGraph::Refresh()
{
ASSERT(m_bDoingRefresh == FALSE);
m_bDoingRefresh = TRUE;
CWaitCursor wait;
m_bNeedsRefresh = FALSE;
m_bDidWarnAboutDanglingRefs = FALSE;
IWbemServices* pProvider = m_psv->GetProvider();
if (pProvider == NULL) {
m_bDoingRefresh = FALSE;
return FALSE;
}
CSelection* psel = &m_psv->Selection();
IWbemClassObject* pco = (IWbemClassObject*) *psel;
m_proot->Clear();
m_varPath.Clear();
m_varRelPath.Clear();
if (pco == NULL) {
m_bDoingRefresh = FALSE;
return FALSE;
}
// Get the full path to the object
CBSTR bsPropname;
bsPropname = _T("__PATH");
SCODE sc = pco->Get((BSTR) bsPropname, 0, &m_varPath, NULL, NULL);
if (FAILED(sc)) {
m_varPath = "";
}
// Get the relative path the the object
bsPropname = _T("__RELPATH");
sc = pco->Get((BSTR) bsPropname, 0, &m_varRelPath, NULL, NULL);
ASSERT(SUCCEEDED(sc));
if (FAILED(sc)) {
m_varRelPath = "";
}
ToBSTR(m_varPath);
ToBSTR(m_varRelPath);
BOOL bIsAssoc;
sc = ::ObjectIsAssocInstance(pco, bIsAssoc);
if (FAILED(sc)) {
bIsAssoc = FALSE;
}
BOOL bIsClass = ::IsClass(pco);
m_proot->m_picon = &m_pIconSource->LoadIcon(pProvider, m_varPath.bstrVal, LARGE_ICON, bIsClass, bIsAssoc);
if (m_psv->ObjectIsNewlyCreated(sc)) {
CSelection& sel = m_psv->Selection();
m_varLabel = sel.Title();
}
else {
if (m_psv->PathInCurrentNamespace(m_varPath.bstrVal)) {
GetObjectLabel(pco, m_varLabel);
}
else {
m_varLabel = m_varPath;
}
}
ASSERT(m_varLabel.vt == VT_BSTR);
m_proot->SetLabel(m_varLabel.bstrVal);
//m_proot->SetObjectPath(m_varRelPath.bstrVal);
m_proot->SetObjectPath(m_varPath.bstrVal);
InvalidateRect(NULL, TRUE);
UpdateWindow();
m_psv->EnableWindow(FALSE);
m_psv->Tabs().EnableWindow(FALSE);
BOOL bParentWasEnabled = m_psv->GetParent()->EnableWindow(FALSE);
HWND hwndFocus = ::GetFocus();
CQueryThread* pthreadQuery = new CQueryThread(m_varPath.bstrVal, REFQUERY_DELAY_THRESHOLD_SECONDS, this);
AddInRefsToGraph(pProvider, pco, pthreadQuery);
BOOL bWasCanceled = pthreadQuery->WasCanceled();
pthreadQuery->TerminateAndDelete();
if ((hwndFocus != NULL) && ::IsWindow(hwndFocus)) {
::SetFocus(hwndFocus);
}
m_psv->EnableWindow(TRUE);
m_psv->Tabs().EnableWindow(TRUE);
m_psv->GetParent()->EnableWindow(TRUE);
if (bWasCanceled) {
m_proot->Clear();
m_bNeedsRefresh = TRUE;
}
else {
AddOutRefsToGraph(pProvider, pco);
}
UpdateWindow();
RedrawWindow();
m_bDoingRefresh = FALSE;
return bWasCanceled;
}
//*********************************************************
// CAssocGraph::SyncContent
//
// Refresh the content of the association graph if the
// m_bNeedsRefresh flag is set. A refresh is necessary
// under the following conditions:
//
// A. A property or qualifier has been modified.
// B. A delayed loading of the association graph
// is in effect and it is now time to load the
// graph.
//
// Parameters:
// None.
//
// Returns:
// BOOL
// TRUE if the refresh was canceled.
//
//*********************************************************
BOOL CAssocGraph::SyncContent()
{
BOOL bCanceled = FALSE;
if (m_bNeedsRefresh) {
bCanceled = Refresh();
}
return bCanceled;
}
//**********************************************************
// CAssocGraph::CatchEvent
//
// Catch events so that the association graph is notified
// when there are changes to properties and so on so that
// the graph knows when it needs to be updated.
//
// Parameters:
// long lEvent
// The event id.
//
// Returns:
// Nothing.
//
//**********************************************************
void CAssocGraph::CatchEvent(long lEvent)
{
switch(lEvent) {
case NOTIFY_GRID_MODIFICATION_CHANGE:
m_bNeedsRefresh = TRUE;
}
}
//**********************************************************
// CAssocGraph::GetPropClassRef
//
// Given a pointer to a CIMOM class and the name of a reference
// property, return a path to the class that it references.
//
// Parameters:
// [in] IWbemClassObject* pco
// Pointer to the class
//
// [in] BSTR bstrPropname
// The property name
//
// [out] CString& sPath
// The path to the class is returned here.
//
//
// Returns:
// SCODE
// S_OK if a path is returned, a failure code if no path is
// returned.
//
//**********************************************************
SCODE CAssocGraph::GetPropClassRef(IWbemClassObject* pco, BSTR bstrPropname, CString& sPath)
{
SCODE sc;
CString sCimtype;
sc = ::GetCimtype(pco, bstrPropname, sCimtype);
if (FAILED(sc)) {
return E_FAIL;
}
if (sCimtype.CompareNoCase(_T("ref"))==0) {
// The cimtype doesn't contain the class name. Attempt to get the
// classname from the value of the reference property.
COleVariant varPropValue;
sc = pco->Get((BSTR) bstrPropname, 0, &varPropValue, NULL, NULL);
if (FAILED(sc)) {
ASSERT(FALSE);
return sc;
}
COleVariant varClass;
sc = ::ClassFromPath(varClass, varPropValue.bstrVal);
if (SUCCEEDED(sc)) {
sPath = varPropValue.bstrVal;
return S_OK;
}
return E_FAIL;
}
CString sSubstring = sCimtype.Left(4);
if (sSubstring.CompareNoCase(_T("ref:")) != 0) {
// This method should be called only for reference properties.
ASSERT(FALSE);
return E_FAIL;
}
int nSize = sCimtype.GetLength() - 4;
if (nSize <= 0) {
// The cimtype doesn't specify a class
ASSERT(FALSE);
return E_FAIL;
}
sPath = sCimtype.Right(nSize);
return S_OK;
}
//*******************************************************************
// CAssocGraph::AddOutRefsToGraph
//
// For each "ref" property in the current object add an "outref" to
// the graph. In the graph the outrefs are located to the right
// of the current object an arrow pointing right from the current object
// to the outref.
//
// Parameters:
// IWbemServices* pProvider
// Pointer to the HMOM provider.
//
// IWbemClassObject* pco
// Pointer to the current object.
//
// Returns:
// Nothing.
//
//*********************************************************************
void CAssocGraph::AddOutRefsToGraph(IWbemServices* pProvider, IWbemClassObject* pco)
{
SCODE sc;
BOOL bIsClass = ::IsClass(pco);
CMosNameArray aNames;
sc = aNames.LoadPropNames(pco, NULL, WBEM_FLAG_REFS_ONLY, NULL);
if (FAILED(sc)) {
return;
}
long nOutRefs = aNames.GetSize();
if (nOutRefs <= 0) {
return;
}
COleVariant varNamespace;
COleVariant varServer;
BSTR bstrNamespace;
BSTR bstrServer;
CIMTYPE cimtype;
CBSTR bsPropname;
bsPropname = _T("__NAMESPACE");
sc = pco->Get((BSTR) bsPropname, 0, &varNamespace, &cimtype, NULL);
if (SUCCEEDED(sc) && varNamespace.vt==VT_BSTR) {
bstrNamespace = varNamespace.bstrVal;
}
else {
bstrNamespace = NULL;
}
bsPropname = _T("__SERVER");
sc = pco->Get((BSTR) bsPropname, 0, &varServer, &cimtype, NULL);
if (SUCCEEDED(sc) && varServer.vt==VT_BSTR) {
bstrServer = varServer.bstrVal;
}
else {
bstrServer = NULL;
}
CString sRefPath;
for (long lOutRef=0; lOutRef < nOutRefs; ++lOutRef) {
BSTR bstrPropname = aNames[lOutRef];
COleVariant varOutRefPath;
if (bIsClass) {
// If we are working with a class, then we want to get the classname of
// the target from the CIMTYPE qualifier. The CIMTYPE qualifier
// should take the form "ref:ClassName".
sc =GetPropClassRef(pco, bstrPropname, sRefPath);
if (SUCCEEDED(sc)) {
varOutRefPath = sRefPath;
}
else {
varOutRefPath.Clear();
varOutRefPath.ChangeType(VT_BSTR);
}
}
else {
sc = pco->Get(aNames[lOutRef], 0, &varOutRefPath, NULL, NULL);
if (FAILED(sc)) {
ASSERT(FALSE);
varOutRefPath.Clear();
}
}
ToBSTR(varOutRefPath);
lOutRef = m_proot->AddOutRef();
COutRef* pOutRef = m_proot->GetOutRef(lOutRef);
COleVariant varOutRefLabel;
varOutRefLabel.Clear();
if (varOutRefPath.vt == VT_BSTR) {
BOOL bPathRefsClass = PathIsClass(sc, varOutRefPath.bstrVal);
if (FAILED(sc)) {
pOutRef->Disable();
}
if (*varOutRefPath.bstrVal == 0) {
// The outref path is empty. This can happen when we have a
// newly created object and the outrefs have not been defined yet.
// For this situation, we add the outrefs to the graph, but we
// leave the label empty.
pOutRef->m_picon = &m_pIconSource->GetGenericIcon(LARGE_ICON, bPathRefsClass);
varOutRefLabel = L"";
pOutRef->Disable();
}
else {
sc = GetLabelFromPath(varOutRefLabel, varOutRefPath.bstrVal);
if (FAILED(sc)) {
varOutRefLabel = L"";
}
pOutRef->m_picon = &m_pIconSource->LoadIcon(pProvider, varOutRefPath.bstrVal, LARGE_ICON, bPathRefsClass);
}
MakePathAbsolute(varOutRefPath, bstrServer, bstrNamespace);
pOutRef->SetObjectPath(varOutRefPath.bstrVal);
pOutRef->SetArcLabel(aNames[lOutRef]);
if (m_psv->PathInCurrentNamespace(varOutRefPath.bstrVal)) {
pOutRef->SetLabel(varOutRefLabel.bstrVal);
}
else {
pOutRef->SetLabel(varOutRefPath.bstrVal);
}
}
else {
pOutRef->SetObjectPath(L"");
pOutRef->m_picon = &m_pIconSource->LoadIcon(pProvider, L"", LARGE_ICON, FALSE);
pOutRef->SetArcLabel(aNames[lOutRef]);
pOutRef->SetLabel(L"");
pOutRef->Disable();
}
} // for
}
//**************************************************************
// CAssocGraph::IsAssociation
//
// Check to see if the given class object is an association.
// All associations have an object qualifier called "Association".
//
// Parameters:
// IWbemClassObject* pco
// The class object to examine.
//
// Returns:
// TRUE if the object is an association, FALSE if it isn't.
//
//*************************************************************
BOOL CAssocGraph::IsAssociation(IWbemClassObject* pco)
{
SCODE sc;
IWbemQualifierSet* pQualifierSet;
sc = pco->GetQualifierSet(&pQualifierSet);
ASSERT(SUCCEEDED(sc));
if (SUCCEEDED(sc)) {
COleVariant varAttribValue;
long lFlavor;
CBSTR bsQualName(_T("Association"));
sc = pQualifierSet->Get((BSTR) bsQualName, 0, &varAttribValue, &lFlavor);
pQualifierSet->Release();
if (SUCCEEDED(sc)) {
if (varAttribValue.vt != VT_BOOL) {
return FALSE;
}
return varAttribValue.boolVal;
}
return FALSE;
}
else {
return FALSE;
}
}
//***********************************************************************
// CAssocGraph::ObjectsAreIdentical
//
// Check to see if two HMOM objects are identical.
//
// Parameters:
// [in] IWbemClassObject* const pco1
// Pointer to the first class object to compare.
//
// [in] IWbemClassObject* const pco2
// Pointer to the second class object to compare.
//
// Returns:
// TRUE if the two class object pointers refer to the same object
// int the database, FALSE if they refer to two different objects.
//
//************************************************************************
BOOL CAssocGraph::ObjectsAreIdentical(
IWbemClassObject* pco1,
IWbemClassObject* pco2)
{
SCODE sc;
COleVariant varPath1;
COleVariant varPath2;
CBSTR bsPropname(_T("__PATH"));
sc = pco1->Get((BSTR) bsPropname, 0, &varPath1, NULL, NULL);
ASSERT(SUCCEEDED(sc));
if (FAILED(sc)) {
return FALSE;
}
ASSERT(varPath1.vt == VT_BSTR);
sc = pco2->Get((BSTR) bsPropname, 0, &varPath2, NULL, NULL);
ASSERT(SUCCEEDED(sc));
if (FAILED(sc)) {
return FALSE;
}
ASSERT(varPath2.vt == VT_BSTR);
return varPath1 == varPath2;
}
//**************************************************************
// CAssocGraph::IsCurrentObject
//
// Check to see if the given object is the same as the current
// object.
//
// Parameters;
// [out] SCODE sc
// S_OK if the test was performed, a failure code if the test
// could not be performed because of an HMOM error, etc.
//
// [in] IWbemClassObject* pcoSrc
// Pointer to the object to test
//
// Returns:
// TRUE if the given object is the same as the current object,
// FALSE otherwise.
//
// The absolute object path is used as the object id value.
//**************************************************************
BOOL CAssocGraph::IsCurrentObject(SCODE& sc, IWbemClassObject* pco) const
{
COleVariant varPath;
CBSTR bsPropname;
bsPropname = _T("__PATH");
sc = pco->Get((BSTR) bsPropname, 0, &varPath, NULL, NULL);
ASSERT(SUCCEEDED(sc));
if (FAILED(sc)) {
return FALSE;
}
ASSERT(varPath.vt == VT_BSTR);
ASSERT(m_varPath.vt == VT_BSTR);
if (varPath.vt != VT_BSTR) {
ToBSTR(varPath);
}
BOOL bIsCurrentObject = (varPath == m_varPath);
return bIsCurrentObject;
}
//**************************************************************
// CAssocGraph::IsCurrentObject
//
// Given a path, check to see if it points to the current object.
//
// Parameters;
// [out] SCODE sc
// S_OK if the test was performed, a failure code if the test
// could not be performed because of an HMOM error, etc.
//
// [in] BSTR bstrPath
// The path to test.
//
// Returns:
// TRUE if the given object is the same as the current object,
// FALSE otherwise.
//
// The absolute object path is used as the object id value.
//**************************************************************
BOOL CAssocGraph::IsCurrentObject(SCODE& sc, BSTR bstrPath) const
{
CSelection* psel = &m_psv->Selection();
IWbemClassObject* pco = (IWbemClassObject*) *psel;
ASSERT(bstrPath != NULL);
ASSERT(m_varPath.vt == VT_BSTR);
sc = S_OK;
BOOL bSameObject = m_pComparePaths->PathsRefSameObject(pco, m_varPath.bstrVal, bstrPath);
return bSameObject;
}
//****************************************************************************
// CAssocGraph::PropRefsCurrentObject
//
// Check to see if the given reference property in a source object refers to
// the current object.
//
// Parameters:
// [in] IWbemServices* pProv
// Pointer to the HMOM service provider.
//
// [in] BSTR bstrPropName
// The name of a reference property in the source object. Note that
// this property MUST BE A REFERENCE.
//
// [in] IWbemClassObject* pcoSrc
// The source object.
//
// Returns:
// TRUE if the specified property in the source object is a reference to
// the target object, FALSE otherwise.
//
//************************************************************************************
BOOL CAssocGraph::PropRefsCurrentObject(
/* in */ IWbemServices* pProvider,
/* in */ BSTR bstrRefPropName,
/* in */ IWbemClassObject* pcoSrc)
{
ASSERT(bstrRefPropName != NULL);
ASSERT(pcoSrc != NULL);
COleVariant varRefValue;
SCODE sc;
sc = pcoSrc->Get(bstrRefPropName, 0, &varRefValue, NULL, NULL);
ASSERT(SUCCEEDED(sc));
if (FAILED(sc)) {
return FALSE;
}
BOOL bIsCurrentObject;
BOOL bCurrentObjectIsClass = ::PathIsClass(sc, m_varPath.bstrVal);
if (FAILED(sc)) {
bCurrentObjectIsClass = FALSE;
}
if (varRefValue.vt == VT_NULL) {
// The value of the property is NULL. Is there any way to check
// to see if it is still a reference to the current object? If the
// current object is a class, yes! We can check the cymtype of the
// property to see if it is "ref:class". If the classes match
// then we consider it a reference to the current object.
if (bCurrentObjectIsClass) {
// Get the cimtype to see if
COleVariant varClass;
sc = ::ClassFromPath(varClass, m_varPath.bstrVal);
if (FAILED(sc) ) {
return FALSE;
}
if (varClass.vt != VT_BSTR) {
return FALSE;
}
CString sCimtype;
sc = ::GetCimtype(pcoSrc, bstrRefPropName, sCimtype);
if (FAILED(sc)) {
return FALSE;
}
CString s;
s = sCimtype.Left(4);
if (s.CompareNoCase(_T("ref:")) != 0) {
return FALSE;
}
s = sCimtype.Right(sCimtype.GetLength() - 4);
CString sClass;
sClass = varClass.bstrVal;
if (sClass.CompareNoCase(s) == 0) {
return TRUE;
}
else {
return FALSE;
}
}
}
else {
bIsCurrentObject = IsCurrentObject(sc, varRefValue.bstrVal);
if (!bIsCurrentObject && bCurrentObjectIsClass) {
COleVariant varClass1;
COleVariant varClass2;
SCODE sc1 = ::ClassFromPath(varClass1, m_varPath.bstrVal);
SCODE sc2 = ::ClassFromPath(varClass2, varRefValue.bstrVal);
if (SUCCEEDED(sc1) && SUCCEEDED(sc2)) {
if (::IsEqualNoCase(varClass1.bstrVal, varClass2.bstrVal)) {
return TRUE;
}
}
}
}
ASSERT(SUCCEEDED(sc));
return bIsCurrentObject;
}
//***********************************************************************
// CAssocGraph::FindRefToCurrentObject
//
// Given an object that references the current object, find the name
// of the property that contains the reference to the current object.
//
// Parameters:
// [in] IWbemServices* pProvider
// Pointer to the HMOM service provider.
//
// [in] IWbemClassObject* pco
// Pointer to the object that contains the reference to the
// current object.
//
// [out] COleVariant& varPropName
// The name of the property containing the reference is returned here.
//
// Returns:
// SCODE
// S_OK if successful, otherwise a failure code.
//
//************************************************************************
SCODE CAssocGraph::FindRefToCurrentObject(
IWbemServices* pProvider,
IWbemClassObject* pco,
COleVariant& varPropName)
{
// Now find the role to label the arc from the InRef to the Current object instance.
// This is the name of the reference property who's value points to the current
// object.
CMosNameArray aPropNames;
SCODE sc;
sc = aPropNames.LoadPropNames(pco, NULL, WBEM_FLAG_REFS_ONLY, NULL);
if (FAILED(sc)) {
ASSERT(FALSE);
}
// The referring object must have at least one "REF" property.
ASSERT(aPropNames.GetSize() > 0);
int nPropNames = aPropNames.GetSize();
for (int iPropName=0; iPropName < nPropNames; ++iPropName) {
BSTR bstrPropName = aPropNames[iPropName];
if (PropRefsCurrentObject(pProvider, bstrPropName, pco)) {
varPropName = bstrPropName;
return S_OK;
}
}
return E_FAIL;
}
//**************************************************************************
// CAssocGraph::AddAssociationToGraph
//
// Add a single association to the graph.
//
// Parameters:
// [in] IWbemServices* pProv
// Pointer to the HMOM service provider.
//
// [in] IWbemClassObject* pcoAssoc
// Pointer to the association class object.
//
// Returns:
// Nothing.
//
//**************************************************************************
void CAssocGraph::AddAssociationToGraph(
/* in */ IWbemServices* pProvider,
/* in */ IWbemClassObject* pcoAssoc)
{
BOOL bIsClass = ::IsClass(pcoAssoc);
CAssoc2Node* pAssocNode;
long lAssociation = m_proot->AddAssociation(m_pIconSource, bIsClass);
pAssocNode = m_proot->GetAssociation(lAssociation);
COleVariant varLabelValue;
GetObjectLabel(pcoAssoc, varLabelValue);
pAssocNode->SetLabel(ToBSTR(varLabelValue));
// Set the object path to follow when the user clicks on the
// "association" icon.
COleVariant varAssocPath;
CBSTR bsPropname;
bsPropname = _T("__PATH");
pcoAssoc->Get((BSTR) bsPropname, 0, &varAssocPath, NULL, NULL);
pAssocNode->SetObjectPath(ToBSTR(varAssocPath));
// Find the names of all reference properties in the association. This
// list of names will be used to determine the "role" name. The role is
// the property name who's value references the current object.
CMosNameArray aRefNames;
SCODE sc = aRefNames.LoadPropNames(pcoAssoc, NULL, WBEM_FLAG_REFS_ONLY, NULL);
if (FAILED(sc)) {
ASSERT(FALSE);
}
ASSERT(aRefNames.GetSize() > 0);
// Now that we've set the label, arcLabel, and object path
// for the association node, we now need to add the endpoints
// to the association node.
LONG lRefCurrentObject = -1;
AddAssociationEndpoints(pProvider, pAssocNode, pcoAssoc, bIsClass, aRefNames, &lRefCurrentObject);
// The method that adds the association endpoints should have determined which
// of the references contained in the association points back to the
// current object. The name of this reference property is the label for the
// arc from the root object to the association icon.
if (lRefCurrentObject >=0 ) {
pAssocNode->SetArcLabel(aRefNames[lRefCurrentObject]);
}
else {
// Control should come here only if the state of the database is inconsistent.
// However, the user should already have been warned about this anyway, so
// just make sure that the arc has an empty label.
pAssocNode->SetArcLabel(L"");
}
}
//***********************************************************************
// CAssocGraph::AddInrefToGraph
//
// Add a node representing a reference from an another object to the
// current object such that the referring object is not an association.
// The new node will reside on the left side of the graph.
//
// Note the interesting case where the current object and another object
// have mutual references to each other. This will result in an object
// appearing on both the left and right side of the graph. Hopefully the
// user won't be confused by this.
//
// Parameters:
// [in] IWbemServices* pProvider
// Pointer to the HMOM provider.
//
// [in] IWbemClassObject* pcoInref
// Pointer to the object containing a reference property that points
// to the target object.
//
//
// Returns:
// Nothing.
//
//******************************************************************************
void CAssocGraph::AddInrefToGraph(
/* in */ IWbemServices* pProvider,
/* in */ IWbemClassObject* pcoInref)
{
// Set the node's icon.
long lInref = m_proot->AddInRef();
CInRef* pInrefNode = m_proot->GetInRef(lInref);
// Set the node's label.
COleVariant varLabelValue;
COleVariant varInrefPath;
BOOL bGotPath = FALSE;
CBSTR bsPropname;
bsPropname = _T("__PATH");
SCODE sc = pcoInref->Get((BSTR) bsPropname, 0, &varInrefPath, NULL, NULL);
if (SUCCEEDED(sc) && (varInrefPath.vt == VT_BSTR)) {
bGotPath = TRUE;
if (m_psv->PathInCurrentNamespace(varInrefPath.bstrVal)) {
GetObjectLabel(pcoInref, varLabelValue);
}
else {
varLabelValue = varInrefPath;
}
}
else {
GetObjectLabel(pcoInref, varLabelValue);
}
pInrefNode->SetLabel(ToBSTR(varLabelValue));
// Use the property name of the reference as the label for the arc
// from the node to the current object.
COleVariant varPropName;
sc = FindRefToCurrentObject(pProvider, pcoInref, varPropName);
if (SUCCEEDED(sc)) {
pInrefNode->SetArcLabel(varPropName.bstrVal);
}
else {
pInrefNode->SetArcLabel(L"");
}
// Set the object path to follow when the user clicks on the
// "inref" icon.
if (bGotPath) {
pInrefNode->SetObjectPath(varInrefPath.bstrVal);
BOOL bPathRefsClass = PathIsClass(sc, varInrefPath.bstrVal);
pInrefNode->m_picon = &m_pIconSource->LoadIcon(pProvider, varInrefPath.bstrVal, LARGE_ICON, bPathRefsClass);
}
else {
pInrefNode->SetObjectPath(L"");
pInrefNode->m_picon = &m_pIconSource->GetGenericIcon(LARGE_ICON, FALSE);
pInrefNode->Disable();
}
}
//********************************************************************
// CAssocGraph::AddInRefsToGraph
//
// Query HMOM for all the objects that reference the current object.
// These referring objects may be either associations or ordinary
// "inrefs". Ordinary inrefs occur when there is an object that is not
// an association, but it has a property that is a reference to the
// current object.
//
// Parameters:
// [in] IWbemServices* pProvider
// Pointer to the provider.
//
// [in] IWbemClassObject* pco
// Pointer to the current object.
//
// [in] CQueryThread* pthreadQuery
// The dialog that allows the user to cancel the query.
//
// Returns:
// Nothing.
//
//*******************************************************************
void CAssocGraph::AddInRefsToGraph(
/* in */ IWbemServices* pProvider,
/* in */ IWbemClassObject* pco,
/* in */ CQueryThread* pthreadQuery)
{
SCODE sc;
// Find all the associations that reference the current object.
CString sQuery;
CString sObjectPath;
sObjectPath = m_varRelPath.bstrVal;
BOOL bIsClass = ::IsClass(pco);
if (bIsClass) {
sQuery = _T("references of {") + sObjectPath + _T("} where schemaonly");
}
else {
sQuery = _T("references of {") + sObjectPath + _T("}");
}
IEnumWbemClassObject* pEnum = NULL;
CBSTR bsQuery(sQuery);
CBSTR bsQueryLang(_T("WQL"));
sc = pProvider->ExecQuery((BSTR) bsQueryLang, (BSTR) bsQuery, WBEM_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum);
if (FAILED(sc)) {
HmmvErrorMsg(IDS_ERR_REFQUERY_FAILED, sc, FALSE, NULL, _T(__FILE__), __LINE__);
return;
}
// ConfigureSecurity(pEnum);
CString sNamespace;
CSelection& sel = m_psv->Selection();
sel.GetNamespace(sNamespace);
SetEnumInterfaceSecurity(sNamespace, pEnum, pProvider);
// Now we will enumerate all of the objects that reference this object.
// These objects will either be associations or "inref" classes that
// have a property that references this object.
int nRefs = 0;
pEnum->Reset();
IWbemClassObject* pcoRef;
while (TRUE) {
PumpMessages();
if (pthreadQuery->WasCanceled()) {
break;
}
unsigned long nReturned;
pcoRef = NULL;
sc = pEnum->Next(QUERY_TIMEOUT, 1, &pcoRef, &nReturned);
if (sc==WBEM_S_TIMEDOUT || sc==S_OK) {
if (nReturned > 0) {
pthreadQuery->AddToRefCount(nReturned);
ASSERT(nReturned == 1);
nRefs += nReturned;
if (IsAssociation(pcoRef)) {
AddAssociationToGraph(pProvider, pcoRef);
}
else {
AddInrefToGraph(pProvider, pcoRef);
}
pcoRef->Release();
}
if ((nRefs % REFS_REDRAW_THREASHOLD) == 0) {
RedrawWindow();
}
}
else {
break;
}
}
pEnum->Release();
}
//****************************************************************
// CAssocGraph::PropertyHasInQualifier
//
// Check to see if a given property has an "in" qualifer. This
// is used to identify a property in an association that points
// back to the current object when we found the association by
// a query such as "references of foo where schemaonly"
//
// Parameters:
// [in] IWbemClassObject* pcoAssoc
// Pointer to the association object containing various reference
// properties.
//
// [in] BSTR bstrPropname
// The name of a reference property to check for the "in" qualifier.
//
// Returns:
// BOOL
// TRUE if the given property has an "in" qualifer set to true.
//
//***********************************************************************
BOOL CAssocGraph::PropertyHasInQualifier(IWbemClassObject* pcoAssoc, BSTR bstrPropname)
{
CBSTR bsQualName;
bsQualName = _T("in");
SCODE sc;
BOOL bIsInRef = ::GetBoolPropertyQualifier(sc, pcoAssoc, bstrPropname, (BSTR) bsQualName);
if (FAILED(sc)) {
return FALSE;
}
return bIsInRef;
}
//****************************************************************
// CAssocGraph::PropertyHasOutQualifier
//
// Check to see if a given property has an "out" qualifer. This
// is used to identify a property in an association that does not point
// back to the current object when we found the association by
// a query such as "references of foo where schemaonly"
//
// Parameters:
// [in] IWbemClassObject* pcoAssoc
// Pointer to the association object containing various reference
// properties.
//
// [in] BSTR bstrPropname
// The name of a reference property to check for the "in" qualifier.
//
// Returns:
// BOOL
// TRUE if the given property has an "out" qualifer set to true.
//
//***********************************************************************
BOOL CAssocGraph::PropertyHasOutQualifier(IWbemClassObject* pcoAssoc, BSTR bstrPropname)
{
CBSTR bsQualName;
bsQualName = _T("out");
SCODE sc;
BOOL bIsOutRef = ::GetBoolPropertyQualifier(sc, pcoAssoc, bstrPropname, (BSTR) bsQualName);
if (FAILED(sc)) {
return FALSE;
}
return bIsOutRef;
}
//****************************************************************************
// CAssocGraph::AddAssociationEndpoints
//
// Add the endpoints that are displayed to the right of the association
// icon. Also, return the index of the property in the association that
// references the current object.
//
// Parameters:
// [in] IWbemServices* pProv
// Pointer to the HMOM provider.
//
// [in] CAssoc2Node* pAssocNode
// Pointer to the association node to add the endpoints to.
//
// [in] IWbemClassObject* pAssocInst
// Pointer to an HMOM association object that corresponds to
// the association node.
//
// [in] CMosNameArray& aRefNames
// An array containing the names of all the properties in the
// association that are references. There will be one endpoint
// per reference excluding the reference that points back to
// the current object.
//
// [in] BSTR bstrPathToCurrentObject
// The relative path to the current object.
//
// [out] LONG* plRefCurrentObject
// Pointer to the place to return the index of the reference
// to the current object in aRefNames.
//
// Returns:
// The index of the reference that points back to the current object. The
// index is returned via plRefCurrentObject. This index will be used to
// label the arc from the current object to the association icon.
//
//*************************************************************************
void CAssocGraph::AddAssociationEndpoints(
/* in */ IWbemServices* pProv,
/* in */ CAssoc2Node* pAssocNode,
/* in */ IWbemClassObject* pcoAssoc,
/* int */ BOOL bIsClass,
/* in */ CMosNameArray& aRefNames,
/* out */ LONG* plRefCurrentObject)
{
*plRefCurrentObject = -1;
COleVariant varNamespace;
COleVariant varServer;
BSTR bstrNamespace;
BSTR bstrServer;
CIMTYPE cimtype;
SCODE sc;
CBSTR bsPropname;
bsPropname = _T("__NAMESPACE");
sc = pcoAssoc->Get((BSTR) bsPropname, 0, &varNamespace, &cimtype, NULL);
if (SUCCEEDED(sc) && varNamespace.vt==VT_BSTR) {
bstrNamespace = varNamespace.bstrVal;
}
else {
bstrNamespace = NULL;
}
bsPropname = _T("__SERVER");
sc = pcoAssoc->Get((BSTR) bsPropname, 0, &varServer, &cimtype, NULL);
if (SUCCEEDED(sc) && varServer.vt==VT_BSTR) {
bstrServer = varServer.bstrVal;
}
else {
bstrServer = NULL;
}
long nRefs = aRefNames.GetSize();
for (long lRef=0; lRef < nRefs; ++lRef) {
BSTR bstrPropName = aRefNames[lRef];
COleVariant varPathEndpoint;
if (bIsClass) {
CString sCimtype;
sc = ::GetCimtype(pcoAssoc, bstrPropName, sCimtype);
CString sClass;
CString sTemp;
if (SUCCEEDED(sc)) {
int nSize = sCimtype.GetLength();
sTemp = sCimtype.Left(4);
if (sTemp.CompareNoCase(_T("ref:")) == 0) {
sClass = sCimtype.Right(nSize - 4);
}
varPathEndpoint = sClass;
}
else {
varPathEndpoint = "";
}
}
else {
// Get the value of the reference
SCODE sc = pcoAssoc->Get(bstrPropName, 0, &varPathEndpoint, NULL, NULL);
ASSERT(SUCCEEDED(sc));
if (FAILED(sc)) {
// For lack of anything better to do, ignore this bogus reference.
continue;
}
ToBSTR(varPathEndpoint);
}
// See whether or not the object we just got is the current object.
BOOL bIsCurrentObject = FALSE;
if (*plRefCurrentObject == -1) {
if (PropertyHasInQualifier(pcoAssoc, bstrPropName)) {
bIsCurrentObject = TRUE;
}
else {
if (!PropertyHasOutQualifier(pcoAssoc, bstrPropName)) {
bIsCurrentObject = IsCurrentObject(sc, varPathEndpoint.bstrVal);
ASSERT(SUCCEEDED(sc));
}
}
}
else {
bIsCurrentObject = FALSE;
}
if (bIsCurrentObject) {
*plRefCurrentObject = lRef;
continue;
}
// At this point, we have a reference property that does not
// point back to the current object. Add a new association endpoint that
// corresponds to the value of this reference property.
long lEndpoint = pAssocNode->AddEndpoint();
CAssocEndpoint* pEndpointNode = pAssocNode->GetEndpoint(lEndpoint);
pEndpointNode->SetArcLabel(aRefNames[lRef]);
if (varPathEndpoint.vt == VT_BSTR) {
BOOL bPathRefsClass = PathIsClass(sc, varPathEndpoint.bstrVal);
if ((bPathRefsClass && !bIsClass) || FAILED(sc)) {
pEndpointNode->Disable();
}
MakePathAbsolute(varPathEndpoint, bstrServer, bstrNamespace);
pEndpointNode->SetObjectPath(varPathEndpoint.bstrVal);
// Now all we need to do is set the endpoint's Label and icon. To do this, we
// must fetch the object to get its label property.
pEndpointNode->m_picon = &m_pIconSource->LoadIcon(pProv, varPathEndpoint.bstrVal, LARGE_ICON, bPathRefsClass);
if (m_psv->PathInCurrentNamespace(varPathEndpoint.bstrVal)) {
COleVariant varLabelValue;
GetLabelFromPath(varLabelValue, varPathEndpoint.bstrVal);
pEndpointNode->SetLabel(varLabelValue.bstrVal);
}
else {
pEndpointNode->SetLabel(varPathEndpoint.bstrVal);
}
}
else {
pEndpointNode->Disable();
pEndpointNode->SetObjectPath(L"");
pEndpointNode->m_picon = &m_pIconSource->LoadIcon(pProv, L"", LARGE_ICON, FALSE);
pEndpointNode->SetLabel(L"");
}
}
}
//**********************************************************************
// CAssocGraph::OnLButtonDblClk
//
// This method is called to test for a hit on this node when the left mouse
// button is double-clicked.
//
// Parameters:
// [in] CPoint point
// The point where the mouse was clicked.
//
// [out] CNode*& pnd
// If the mouse is clicked in this node's rectangle, a pointer to
// this node is returned here, otherwise it is not modified.
//
// [out] BOOL& bJumpToObject
// TRUE if double-clicking this node should cause a jump to the
// corresponding object.
//
// [out] COleVariant& varObjectPath
// The path to this object.
//
// Returns:
// BOOL
// TRUE if the mouse click hit this node, FALSE otherwise.
//
//**************************************************************************
void CAssocGraph::OnLButtonDblClk(UINT nFlags, CPoint point)
{
CWnd::OnLButtonDblClk(nFlags, point);
if (!::IsWindow(m_hWnd)) {
return;
}
CDC* pdc;
CFont* pfontPrev = NULL;
pdc = GetDC();
ASSERT(pdc != NULL);
pdc->AssertValid();
if (pdc == NULL) {
return;
}
pfontPrev = pdc->SelectObject(&m_font);
CNode* pndHit;
point.x += m_ptOrg.x;
point.y += m_ptOrg.y;
COleVariant varObjectPath;
BOOL bJumpToObject;
BOOL bHitObject = m_proot->LButtonDblClk(pdc, point, pndHit, bJumpToObject, varObjectPath);
pdc->SelectObject(pfontPrev);
ReleaseDC(pdc);
pdc = NULL;
if (bHitObject) {
if (bJumpToObject) {
ASSERT(varObjectPath.vt == VT_BSTR);
COleVariant varServer;
COleVariant varNamespace;
SCODE sc = ServerAndNamespaceFromPath(varServer, varNamespace, varObjectPath.bstrVal);
if (FAILED(sc)) {
HmmvErrorMsgStr(_T("Can't jump to this object because it has an invalid path."), sc, FALSE, NULL, _T(__FILE__), __LINE__);
return;
}
BSTR bstrServer = NULL;
BSTR bstrNamespace = NULL;
if (varServer.vt == VT_BSTR) {
bstrServer = varServer.bstrVal;
}
if (varNamespace.vt == VT_BSTR) {
bstrNamespace = varNamespace.bstrVal;
}
#ifdef PREVENT_INTERNAMESPACE_JUMP
if (!m_psv->IsCurrentNamespace(bstrServer, bstrNamespace)) {
HmmvErrorMsg(_T("Can't jump to this object because it resides in a different namespace"), sc, FALSE, NULL, __FILE__, __LINE__);
return;
}
#endif //PREVENT_INTERNAMESPACE_JUMP
m_psv->JumpToObjectPath(varObjectPath.bstrVal, TRUE);
SetFocus();
}
}
}
void CAssocGraph::OnLButtonDown(UINT nFlags, CPoint point)
{
CWnd::OnLButtonDown(nFlags, point);
}
void CAssocGraph::ShowHoverText(CPoint ptHoverText, COleVariant& varHoverText)
{
HideHoverText();
CString sHoverText;
VariantToCString(sHoverText, varHoverText);
CRect rcText;
rcText.bottom = ptHoverText.y - 8;
rcText.top = rcText.bottom - 20;
rcText.left = ptHoverText.x + 4;
rcText.right = rcText.left + 100;
m_phover = new CHoverText;
m_phover->Create(sHoverText, m_font, ptHoverText, this);
}
//***************************************************************
// CAssocGraph::OnContextMenu
//
// This method displays the context menu. It is called from
// PretranslateMessage.
//
// Parameters:
// CWnd* pwnd
// Pointer to the window that the event that triggered the
// menu occurred in.
//
// CPoint ptScreen
// The point, in screen coordinates, where the context menu
// should be displayed.
//
// Returns:
// Nothing.
//
//*****************************************************************
void CAssocGraph::OnContextMenu(CWnd* pwnd, CPoint ptScreen)
{
// CG: This function was added by the Pop-up Menu component
CPoint ptClient = ptScreen;
ScreenToClient(&ptClient);
// If the mouse was right-clicked outside the client rectangle do nothing.
CRect rcClient;
GetClientRect(rcClient);
if (!rcClient.PtInRect(ptClient)) {
return;
}
CNode* pndHit;
ptClient.x += m_ptOrg.x;
ptClient.y += m_ptOrg.y;
COleVariant varObjectPath;
BOOL bJumpToObject;
m_sContextPath.Empty();
ASSERT(::IsWindow(m_hWnd));
CDC* pdc = GetDC();
ASSERT(pdc != NULL);
CFont* pfontPrev = pdc->SelectObject(&m_font);
BOOL bHitObject = m_proot->LButtonDblClk(pdc, ptClient, pndHit, bJumpToObject, varObjectPath);
pdc->SelectObject(pfontPrev);
ReleaseDC(pdc);
if (bHitObject) {
m_sContextPath = varObjectPath.bstrVal;
}
else {
// Check to see if the mouse click hit the root node's icon.
CRect rcIcon(m_proot->m_ptOrigin.x, m_proot->m_ptOrigin.y, m_proot->m_ptOrigin.x + m_proot->m_sizeIcon.cx, m_proot->m_ptOrigin.y + m_proot->m_sizeIcon.cy);
if (rcIcon.PtInRect(ptClient)) {
m_proot->GetObjectPath(m_sContextPath);
}
else {
// Nothing interesting was hit, so don't display the context menu.
return;
}
}
#if 0
// If the mouse was right-clicked over the toolbar buttons, do nothing.
CRect rcTools;
m_pTitleBar->GetToolBarRect(rcTools);
m_pTitleBar->ClientToScreen(rcTools);
if (rcTools.PtInRect(ptScreen)) {
return;
}
#endif //0
CMenu menu;
VERIFY(menu.LoadMenu(CG_IDR_POPUP_AGRAPH));
CMenu* pPopup = menu.GetSubMenu(0);
ASSERT(pPopup != NULL);
CWnd* pWndPopupOwner = this;
CSelection& sel = m_psv->Selection();
BOOL bPathInCurrentNamespace = sel.PathInCurrentNamespace(varObjectPath.bstrVal);
if (bPathInCurrentNamespace) {
pPopup->EnableMenuItem(ID_CMD_GOTO_NAMESPACE, MF_GRAYED);
}
BOOL bDidRemove = FALSE;
long lEditMode = m_psv->GetEditMode();
if (lEditMode != EDITMODE_BROWSER) {
bDidRemove = pPopup->RemoveMenu(ID_CMD_MAKE_ROOT, MF_BYCOMMAND);
}
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, ptScreen.x, ptScreen.y,
pWndPopupOwner);
}
BOOL CAssocGraph::PreTranslateMessage(MSG* pMsg)
{
// CG: This block was added by the Pop-up Menu component
{
// Shift+F10: show pop-up menu.
if ((((pMsg->message == WM_KEYDOWN || pMsg->message == WM_SYSKEYDOWN) && // If we hit a key and
(pMsg->wParam == VK_F10) && (GetKeyState(VK_SHIFT) & ~1)) != 0) || // it's Shift+F10 OR
(pMsg->message == WM_CONTEXTMENU)) // Natural keyboard key
{
CRect rect;
GetClientRect(rect);
ClientToScreen(rect);
CPoint point = rect.TopLeft();
point.Offset(5, 5);
OnContextMenu(NULL, point);
return TRUE;
}
}
return CWnd::PreTranslateMessage(pMsg);
}
void CAssocGraph::HideHoverText()
{
delete m_phover;
m_phover = NULL;
}
void CAssocGraph::OnTimer(UINT_PTR nIDEvent)
{
// TODO: Add your message handler code here and/or call default
CWnd::OnTimer(nIDEvent);
switch(nIDEvent) {
case ID_AGRAPH_UPDATE_TIMER:
if (!m_bBusyUpdatingWindow) {
m_bBusyUpdatingWindow = TRUE;
RedrawWindow();
m_bBusyUpdatingWindow = FALSE;
}
break;
case ID_HOVER_TIMER:
CPoint ptMouse;
if (GetCursorPos(&ptMouse)) {
ScreenToClient(&ptMouse);
CPoint ptMouseTranslated = ptMouse;
ptMouseTranslated.x += m_ptOrg.x;
ptMouseTranslated.y += m_ptOrg.y;
COleVariant varLabel;
DWORD dwItem;
BOOL bCursorOverHoverItem = m_proot->CheckMouseHover(ptMouseTranslated, &dwItem, varLabel);
if (bCursorOverHoverItem) {
if (dwItem == m_dwHoverItem) {
if (!m_phover) {
ShowHoverText(ptMouse, varLabel);
}
}
else {
HideHoverText();
m_dwHoverItem = dwItem;
}
}
else {
if (m_phover) {
HideHoverText();
}
}
}
}
}
/////////////////////////////////////////////////////////////////////////////
// CHoverText
CHoverText::CHoverText()
{
}
CHoverText::~CHoverText()
{
}
BOOL CHoverText::Create(LPCTSTR pszText, CFont& font, CPoint ptHover, CWnd* pwndParent)
{
CRect rc;
rc.top = ptHover.y + 24;
rc.bottom = rc.top+1;
rc.left = ptHover.x - 16;
rc.right = rc.left+1;
(ptHover.x, ptHover.y, ptHover.x, ptHover.y);
BOOL bDidCreate = CStatic::Create(pszText, WS_BORDER | ES_CENTER, rc, pwndParent, GenerateWindowID());
if (!bDidCreate) {
return FALSE;
}
SetFont(&font);
ASSERT(::IsWindow(m_hWnd));
CDC* pdc = GetDC();
ASSERT(pdc != NULL);
CFont* pfontSave = (CFont*) pdc->SelectObject(&font);
int cchText;
#ifdef _UNICODE
cchText = wcslen(pszText);
#else
cchText = strlen(pszText);
#endif
CSize sizeText = pdc->GetTextExtent(pszText, cchText);
pdc->SelectObject(pfontSave);
sizeText.cy += CY_TOOLTIP_MARGIN;
sizeText.cx += CX_TOOLTIP_MARGIN;
rc.bottom = rc.top + sizeText.cy;
rc.right = rc.left + sizeText.cx;
MoveWindow(rc);
ShowWindow(SW_SHOW);
ReleaseDC(pdc);
return TRUE;
}
BEGIN_MESSAGE_MAP(CHoverText, CStatic)
//{{AFX_MSG_MAP(CHoverText)
ON_WM_CTLCOLOR_REFLECT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CHoverText message handlers
HBRUSH CHoverText::CtlColor(CDC* pDC, UINT nCtlColor)
{
pDC->SetTextColor(RGB(0, 0, 0));
pDC->SetBkColor( RGB(255, 255, 255)); // text bkgnd
return (HBRUSH) GetStockObject(WHITE_BRUSH); // Background
}
BOOL CHoverText::DestroyWindow()
{
KillTimer(ID_HOVER_TIMER);
// TODO: Add your specialized code here and/or call the base class
return CStatic::DestroyWindow();
}
void CAssocGraph::OnCmdGotoNamespace()
{
if (!m_sContextPath.IsEmpty()) {
CString sContextPath = m_sContextPath;
m_psv->GotoNamespace(sContextPath);
m_psv->MakeRoot(sContextPath);
}
}
void CAssocGraph::OnCmdMakeRoot()
{
if (!m_sContextPath.IsEmpty()) {
m_psv->MakeRoot(m_sContextPath);
}
}
void CAssocGraph::OnCmdShowProperties()
{
if (!m_sContextPath.IsEmpty()) {
m_psv->ShowObjectProperties(m_sContextPath);
}
}
void CAssocGraph::NotifyNamespaceChange()
{
Refresh();
}
//******************************************************************
// CAssocGraph::OnMouseWheel
//
// Handle WM_MOUSEWHEEL because we don't inherit from CScrollView.
//
// Parameters:
// See the MFC documentation.
//
// Returns:
// See the MFC documentation.
//
//******************************************************************
BOOL CAssocGraph::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
zDelta = zDelta / 120;
int iPos = GetScrollPos(SB_VERT);
int iMaxPos = GetScrollLimit(SB_VERT);
// Handle the cases where the scroll is a no-op.
if (zDelta == 0 ) {
return TRUE;
}
else if (zDelta < 0) {
if (iPos == iMaxPos) {
return TRUE;
}
}
else if (zDelta > 0) {
if (iPos == 0) {
return TRUE;
}
}
iPos = iPos - zDelta;
if (iPos < 0) {
iPos = 0;
}
else if (iPos >= iMaxPos) {
iPos = iMaxPos - 1;
if (iPos < 0) {
return TRUE;
}
}
UINT wParam;
wParam = (iPos << 16) | SB_THUMBPOSITION;
SendMessage(WM_VSCROLL, wParam);
return TRUE;
}
void CAssocGraph::OnSetFocus(CWnd* pOldWnd)
{
CWnd::OnSetFocus(pOldWnd);
m_psv->OnRequestUIActive();
}
void CAssocGraph::OnKillFocus(CWnd* pNewWnd)
{
CWnd::OnKillFocus(pNewWnd);
// TODO: Add your message handler code here
}