2025-05-13 19:45:22 +03:00

1333 lines
39 KiB
C

/*
* Process Hacker -
* graph control
*
* Copyright (C) 2010-2016 wj32
*
* This file is part of Process Hacker.
*
* Process Hacker is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Process Hacker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ph.h>
#include <guisup.h>
#include <graph.h>
#include <math.h>
#define COLORREF_TO_BITS(Color) (_byteswap_ulong(Color) >> 8)
typedef struct _PHP_GRAPH_CONTEXT
{
HWND Handle;
ULONG Style;
ULONG_PTR Id;
PH_GRAPH_DRAW_INFO DrawInfo;
PH_GRAPH_OPTIONS Options;
BOOLEAN NeedsUpdate;
BOOLEAN NeedsDraw;
HDC BufferedContext;
HBITMAP BufferedOldBitmap;
HBITMAP BufferedBitmap;
PVOID BufferedBits;
RECT BufferedContextRect;
HDC FadeOutContext;
HBITMAP FadeOutOldBitmap;
HBITMAP FadeOutBitmap;
PVOID FadeOutBits;
RECT FadeOutContextRect;
HWND TooltipHandle;
WNDPROC TooltipOldWndProc;
POINT LastCursorLocation;
} PHP_GRAPH_CONTEXT, *PPHP_GRAPH_CONTEXT;
LRESULT CALLBACK PhpGraphWndProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
);
RECT PhNormalGraphTextMargin = { 5, 5, 5, 5 };
RECT PhNormalGraphTextPadding = { 3, 3, 3, 3 };
BOOLEAN PhGraphControlInitialization(
VOID
)
{
WNDCLASSEX c = { sizeof(c) };
c.style = CS_GLOBALCLASS | CS_DBLCLKS;
c.lpfnWndProc = PhpGraphWndProc;
c.cbClsExtra = 0;
c.cbWndExtra = sizeof(PVOID);
c.hInstance = PhLibImageBase;
c.hIcon = NULL;
c.hCursor = LoadCursor(NULL, IDC_ARROW);
c.hbrBackground = NULL;
c.lpszMenuName = NULL;
c.lpszClassName = PH_GRAPH_CLASSNAME;
c.hIconSm = NULL;
if (!RegisterClassEx(&c))
return FALSE;
return TRUE;
}
FORCEINLINE VOID PhpGetGraphPoint(
_In_ PPH_GRAPH_DRAW_INFO DrawInfo,
_In_ ULONG Index,
_Out_ PULONG H1,
_Out_ PULONG H2
)
{
if (Index < DrawInfo->LineDataCount)
{
FLOAT f1;
FLOAT f2;
f1 = DrawInfo->LineData1[Index];
if (f1 < 0)
f1 = 0;
if (f1 > 1)
f1 = 1;
*H1 = (ULONG)(f1 * (DrawInfo->Height - 1));
if (DrawInfo->Flags & PH_GRAPH_USE_LINE_2)
{
f2 = f1 + DrawInfo->LineData2[Index];
if (f2 < 0)
f2 = 0;
if (f2 > 1)
f2 = 1;
*H2 = (ULONG)(f2 * (DrawInfo->Height - 1));
}
else
{
*H2 = *H1;
}
}
else
{
*H1 = 0;
*H2 = 0;
}
}
/**
* Draws a graph directly to memory.
*
* \param hdc The DC to draw to. This is only used when drawing text.
* \param Bits The bits in a bitmap.
* \param DrawInfo A structure which contains graphing information.
*
* \remarks The following information is fixed:
* \li The graph is fixed to the origin (0, 0).
* \li The total size of the bitmap is assumed to be \a Width and \a Height in \a DrawInfo.
* \li \a Step is fixed at 2.
* \li If \ref PH_GRAPH_USE_LINE_2 is specified in \a Flags, \ref PH_GRAPH_OVERLAY_LINE_2 is never
* used.
*/
VOID PhDrawGraphDirect(
_In_ HDC hdc,
_In_ PVOID Bits,
_In_ PPH_GRAPH_DRAW_INFO DrawInfo
)
{
PULONG bits = Bits;
LONG width = DrawInfo->Width;
LONG height = DrawInfo->Height;
LONG numberOfPixels = width * height;
ULONG flags = DrawInfo->Flags;
LONG i;
LONG x;
BOOLEAN intermediate = FALSE; // whether we are currently between two data positions
ULONG dataIndex = 0; // the data index of the current position
ULONG h1_i; // the line 1 height value to the left of the current position
ULONG h1_o; // the line 1 height value at the current position
ULONG h2_i; // the line 1 + line 2 height value to the left of the current position
ULONG h2_o; // the line 1 + line 2 height value at the current position
ULONG h1; // current pixel
ULONG h1_left; // current pixel
ULONG h2; // current pixel
ULONG h2_left; // current pixel
LONG mid;
LONG h1_low1;
LONG h1_high1;
LONG h1_low2;
LONG h1_high2;
LONG h2_low1;
LONG h2_high1;
LONG h2_low2;
LONG h2_high2;
LONG old_low2;
LONG old_high2;
ULONG lineColor1;
ULONG lineBackColor1;
ULONG lineColor2;
ULONG lineBackColor2;
FLOAT gridHeight;
LONG gridYThreshold;
ULONG gridYCounter;
ULONG gridColor;
FLOAT gridBase;
FLOAT gridLevel;
ULONG yLabelMax;
ULONG yLabelDataIndex;
lineColor1 = COLORREF_TO_BITS(DrawInfo->LineColor1);
lineBackColor1 = COLORREF_TO_BITS(DrawInfo->LineBackColor1);
lineColor2 = COLORREF_TO_BITS(DrawInfo->LineColor2);
lineBackColor2 = COLORREF_TO_BITS(DrawInfo->LineBackColor2);
if (DrawInfo->BackColor == 0)
{
memset(bits, 0, numberOfPixels * 4);
}
else
{
PhFillMemoryUlong(bits, COLORREF_TO_BITS(DrawInfo->BackColor), numberOfPixels);
}
x = width - 1;
h1_low2 = MAXLONG;
h1_high2 = 0;
h2_low2 = MAXLONG;
h2_high2 = 0;
PhpGetGraphPoint(DrawInfo, 0, &h1_i, &h2_i);
if (flags & (PH_GRAPH_USE_GRID_X | PH_GRAPH_USE_GRID_Y))
{
gridHeight = max(DrawInfo->GridHeight, 0);
gridLevel = gridHeight;
gridYThreshold = DrawInfo->GridYThreshold;
gridYCounter = DrawInfo->GridWidth - (DrawInfo->GridXOffset * DrawInfo->Step) % DrawInfo->GridWidth - 1;
gridColor = COLORREF_TO_BITS(DrawInfo->GridColor);
}
if ((flags & (PH_GRAPH_USE_GRID_Y | PH_GRAPH_LOGARITHMIC_GRID_Y)) == (PH_GRAPH_USE_GRID_Y | PH_GRAPH_LOGARITHMIC_GRID_Y))
{
// Pre-process to find the largest integer n such that GridHeight*GridBase^n < 1.
gridBase = DrawInfo->GridBase;
if (gridBase > 1)
{
DOUBLE logBase;
DOUBLE exponent;
DOUBLE high;
logBase = log(gridBase);
exponent = ceil(-log(gridHeight) / logBase) - 1; // Works for both GridHeight > 1 and GridHeight < 1
high = exp(exponent * logBase);
gridLevel = (FLOAT)(gridHeight * high);
if (gridLevel < 0 || !isfinite(gridLevel))
gridLevel = 0;
if (gridLevel > 1)
gridLevel = 1;
}
else
{
// This is an error.
gridLevel = 0;
}
}
if (flags & PH_GRAPH_LABEL_MAX_Y)
{
yLabelMax = h2_i;
yLabelDataIndex = 0;
}
while (x >= 0)
{
// Calculate the height of the graph at this point.
if (!intermediate)
{
h1_o = h1_i;
h2_o = h2_i;
// Pull in new data.
dataIndex++;
PhpGetGraphPoint(DrawInfo, dataIndex, &h1_i, &h2_i);
h1 = h1_o;
h1_left = (h1_i + h1_o) / 2;
h2 = h2_o;
h2_left = (h2_i + h2_o) / 2;
if ((flags & PH_GRAPH_LABEL_MAX_Y) && dataIndex < DrawInfo->LabelMaxYIndexLimit)
{
if (yLabelMax <= h2_i)
{
yLabelMax = h2_i;
yLabelDataIndex = dataIndex;
}
}
}
else
{
h1 = h1_left;
h1_left = h1_i;
h2 = h2_left;
h2_left = h2_i;
}
// The graph is drawn right-to-left. There is one iteration of the loop per horizontal pixel.
// There is a fixed step value of 2, so every other iteration is a mid-point (intermediate)
// iteration with a height value of (left + right) / 2. In order to rasterize the outline,
// effectively in each iteration half of the line is drawn at the current column and the other
// half is drawn in the column to the left.
// Rasterize the data outline.
// h?_low2 to h?_high2 is the vertical line to the left of the current pixel.
// h?_low1 to h?_high1 is the vertical line at the current pixel.
// We merge (union) the old h?_low2 to h?_high2 line with the current line in each iteration.
//
// For example:
//
// X represents a data point. M represents the mid-point between two data points ("intermediate").
// X, M and x are all part of the outline. # represents the background filled in during
// the current loop iteration.
//
// slope > 0: slope < 0:
//
// X < high1 X < high2 (of next loop iteration)
// x x
// x < low1 x < low2 (of next loop iteration)
// x# < high2 x < high1 (of next loop iteration)
// M# < low2 M < low1 (of next loop iteration)
// x# < high1 (of next loop iteration) x < high2
// x# < low1 (of next loop iteration) x < low2
// x # < high2 (of next loop iteration) x < high1
// x # x
// X # < low2 (of next loop iteration) X < low1
// # #
// ^ ^
// ^| current pixel ^| current pixel
// | |
// | left of current pixel | left of current pixel
//
// In both examples above, the line low2-high2 will be merged with the line low1-high1 of
// the next iteration.
mid = ((h1_left + h1) / 2) * width;
old_low2 = h1_low2;
old_high2 = h1_high2;
if (h1_left < h1) // slope > 0
{
h1_low2 = h1_left * width;
h1_high2 = mid;
h1_low1 = mid + width;
h1_high1 = h1 * width;
}
else // slope < 0
{
h1_high2 = h1_left * width;
h1_low2 = mid + width;
h1_high1 = mid;
h1_low1 = h1 * width;
}
// Merge the lines.
if (h1_low1 > old_low2)
h1_low1 = old_low2;
if (h1_high1 < old_high2)
h1_high1 = old_high2;
// Fix up values for the current horizontal offset.
h1_low1 += x;
h1_high1 += x;
if (flags & PH_GRAPH_USE_LINE_2)
{
mid = ((h2_left + h2) / 2) * width;
old_low2 = h2_low2;
old_high2 = h2_high2;
if (h2_left < h2) // slope > 0
{
h2_low2 = h2_left * width;
h2_high2 = mid;
h2_low1 = mid + width;
h2_high1 = h2 * width;
}
else // slope < 0
{
h2_high2 = h2_left * width;
h2_low2 = mid + width;
h2_high1 = mid;
h2_low1 = h2 * width;
}
// Merge the lines.
if (h2_low1 > old_low2)
h2_low1 = old_low2;
if (h2_high1 < old_high2)
h2_high1 = old_high2;
// Fix up values for the current horizontal offset.
h2_low1 += x;
h2_high1 += x;
}
// Fill in the background.
if (flags & PH_GRAPH_USE_LINE_2)
{
for (i = h1_high1 + width; i < h2_low1; i += width)
{
bits[i] = lineBackColor2;
}
}
for (i = x; i < h1_low1; i += width)
{
bits[i] = lineBackColor1;
}
// Draw the grid.
if (flags & PH_GRAPH_USE_GRID_X)
{
// Draw the vertical grid line.
if (gridYCounter == 0)
{
for (i = x; i < numberOfPixels; i += width)
{
bits[i] = gridColor;
}
}
gridYCounter++;
if (gridYCounter == DrawInfo->GridWidth)
gridYCounter = 0;
}
if (flags & PH_GRAPH_USE_GRID_Y)
{
FLOAT level;
LONG h;
LONG h_last;
// Draw the horizontal grid line.
if (flags & PH_GRAPH_LOGARITHMIC_GRID_Y)
{
level = gridLevel;
h = (LONG)(level * (height - 1));
h_last = height + gridYThreshold - 1;
while (TRUE)
{
if (h <= h_last - gridYThreshold)
{
bits[x + h * width] = gridColor;
h_last = h;
}
else
{
break;
}
level /= gridBase;
h = (LONG)(level * (height - 1));
}
}
else
{
level = gridHeight;
h = (LONG)(level * (height - 1));
h_last = 0;
while (h < height - 1)
{
if (h >= h_last + gridYThreshold)
{
bits[x + h * width] = gridColor;
h_last = h;
}
level += gridHeight;
h = (LONG)(level * (height - 1));
}
}
}
// Draw the outline (line 1 is allowed to paint over line 2).
if (flags & PH_GRAPH_USE_LINE_2)
{
for (i = h2_low1; i <= h2_high1; i += width) // exclude pixel in the middle
{
bits[i] = lineColor2;
}
}
for (i = h1_low1; i <= h1_high1; i += width)
{
bits[i] = lineColor1;
}
intermediate = !intermediate;
x--;
}
if ((flags & PH_GRAPH_LABEL_MAX_Y) && yLabelDataIndex < DrawInfo->LineDataCount)
{
FLOAT value;
PPH_STRING label;
value = DrawInfo->LineData1[yLabelDataIndex];
if (flags & PH_GRAPH_USE_LINE_2)
value += DrawInfo->LineData2[yLabelDataIndex];
if (label = DrawInfo->LabelYFunction(DrawInfo, yLabelDataIndex, value, DrawInfo->LabelYFunctionParameter))
{
HFONT oldFont = NULL;
SIZE textSize;
RECT rect;
if (DrawInfo->LabelYFont)
oldFont = SelectObject(hdc, DrawInfo->LabelYFont);
SetTextColor(hdc, DrawInfo->LabelYColor);
SetBkMode(hdc, TRANSPARENT);
GetTextExtentPoint32(hdc, label->Buffer, (ULONG)label->Length / 2, &textSize);
rect.bottom = height - yLabelMax - PhNormalGraphTextPadding.top;
rect.top = rect.bottom - textSize.cy;
if (rect.top < PhNormalGraphTextPadding.top)
{
rect.top = PhNormalGraphTextPadding.top;
rect.bottom = rect.top + textSize.cy;
}
rect.left = 0;
rect.right = width - min((LONG)yLabelDataIndex * 2, width) - PhNormalGraphTextPadding.right;
DrawText(hdc, label->Buffer, (ULONG)label->Length / 2, &rect, DT_NOCLIP | DT_RIGHT);
if (oldFont)
SelectObject(hdc, oldFont);
PhDereferenceObject(label);
}
}
if (DrawInfo->Text.Buffer)
{
HFONT oldFont = NULL;
if (DrawInfo->TextFont)
oldFont = SelectObject(hdc, DrawInfo->TextFont);
// Fill in the text box.
SetDCBrushColor(hdc, DrawInfo->TextBoxColor);
FillRect(hdc, &DrawInfo->TextBoxRect, GetStockObject(DC_BRUSH));
// Draw the text.
SetTextColor(hdc, DrawInfo->TextColor);
SetBkMode(hdc, TRANSPARENT);
DrawText(hdc, DrawInfo->Text.Buffer, (ULONG)DrawInfo->Text.Length / 2, &DrawInfo->TextRect, DT_NOCLIP);
if (oldFont)
SelectObject(hdc, oldFont);
}
}
/**
* Sets the text in a graphing information structure.
*
* \param hdc The DC to perform calculations from.
* \param DrawInfo A structure which contains graphing information. The structure is modified to
* contain the new text information.
* \param Text The text.
* \param Margin The margins of the text box from the edges of the graph.
* \param Padding The padding within the text box.
* \param Align The alignment of the text box.
*/
VOID PhSetGraphText(
_In_ HDC hdc,
_Inout_ PPH_GRAPH_DRAW_INFO DrawInfo,
_In_ PPH_STRINGREF Text,
_In_ PRECT Margin,
_In_ PRECT Padding,
_In_ ULONG Align
)
{
HFONT oldFont = NULL;
SIZE textSize;
PH_RECTANGLE boxRectangle;
PH_RECTANGLE textRectangle;
if (DrawInfo->TextFont)
oldFont = SelectObject(hdc, DrawInfo->TextFont);
DrawInfo->Text = *Text;
GetTextExtentPoint32(hdc, Text->Buffer, (ULONG)Text->Length / 2, &textSize);
if (oldFont)
SelectObject(hdc, oldFont);
// Calculate the box rectangle.
boxRectangle.Width = textSize.cx + Padding->left + Padding->right;
boxRectangle.Height = textSize.cy + Padding->top + Padding->bottom;
if (Align & PH_ALIGN_LEFT)
boxRectangle.Left = Margin->left;
else if (Align & PH_ALIGN_RIGHT)
boxRectangle.Left = DrawInfo->Width - boxRectangle.Width - Margin->right;
else
boxRectangle.Left = (DrawInfo->Width - boxRectangle.Width) / 2;
if (Align & PH_ALIGN_TOP)
boxRectangle.Top = Margin->top;
else if (Align & PH_ALIGN_BOTTOM)
boxRectangle.Top = DrawInfo->Height - boxRectangle.Height - Margin->bottom;
else
boxRectangle.Top = (DrawInfo->Height - boxRectangle.Height) / 2;
// Calculate the text rectangle.
textRectangle.Left = boxRectangle.Left + Padding->left;
textRectangle.Top = boxRectangle.Top + Padding->top;
textRectangle.Width = textSize.cx;
textRectangle.Height = textSize.cy;
// Save the rectangles.
DrawInfo->TextRect = PhRectangleToRect(textRectangle);
DrawInfo->TextBoxRect = PhRectangleToRect(boxRectangle);
}
VOID PhpCreateGraphContext(
_Out_ PPHP_GRAPH_CONTEXT *Context
)
{
PPHP_GRAPH_CONTEXT context;
context = PhAllocate(sizeof(PHP_GRAPH_CONTEXT));
memset(context, 0, sizeof(PHP_GRAPH_CONTEXT));
context->DrawInfo.Width = 3;
context->DrawInfo.Height = 3;
context->DrawInfo.Flags = PH_GRAPH_USE_GRID_X;
context->DrawInfo.Step = 2;
context->DrawInfo.BackColor = RGB(0xef, 0xef, 0xef);
context->DrawInfo.LineDataCount = 0;
context->DrawInfo.LineData1 = NULL;
context->DrawInfo.LineData2 = NULL;
context->DrawInfo.LineColor1 = RGB(0x00, 0xff, 0x00);
context->DrawInfo.LineColor2 = RGB(0xff, 0x00, 0x00);
context->DrawInfo.LineBackColor1 = RGB(0x00, 0x77, 0x00);
context->DrawInfo.LineBackColor2 = RGB(0x77, 0x00, 0x00);
context->DrawInfo.GridColor = RGB(0xc7, 0xc7, 0xc7);
context->DrawInfo.GridWidth = 20;
context->DrawInfo.GridHeight = 0.25f;
context->DrawInfo.GridXOffset = 0;
context->DrawInfo.GridYThreshold = 10;
context->DrawInfo.GridBase = 2.0f;
context->DrawInfo.LabelYColor = RGB(0x77, 0x77, 0x77);
context->DrawInfo.LabelMaxYIndexLimit = -1;
context->DrawInfo.TextColor = RGB(0x00, 0xff, 0x00);
context->DrawInfo.TextBoxColor = RGB(0x00, 0x22, 0x00);
context->Options.FadeOutBackColor = RGB(0xef, 0xef, 0xef);
context->Options.FadeOutWidth = 100;
*Context = context;
}
VOID PhpFreeGraphContext(
_Inout_ _Post_invalid_ PPHP_GRAPH_CONTEXT Context
)
{
PhFree(Context);
}
static PWSTR PhpMakeGraphTooltipContextAtom(
VOID
)
{
PH_DEFINE_MAKE_ATOM(L"PhLib_GraphTooltipContext");
}
static VOID PhpDeleteBufferedContext(
_In_ PPHP_GRAPH_CONTEXT Context
)
{
if (Context->BufferedContext)
{
// The original bitmap must be selected back into the context, otherwise the bitmap can't be
// deleted.
SelectObject(Context->BufferedContext, Context->BufferedOldBitmap);
DeleteObject(Context->BufferedBitmap);
DeleteDC(Context->BufferedContext);
Context->BufferedContext = NULL;
Context->BufferedBitmap = NULL;
Context->BufferedBits = NULL;
}
}
static VOID PhpCreateBufferedContext(
_In_ PPHP_GRAPH_CONTEXT Context
)
{
HDC hdc;
BITMAPINFOHEADER header;
PhpDeleteBufferedContext(Context);
GetClientRect(Context->Handle, &Context->BufferedContextRect);
hdc = GetDC(Context->Handle);
Context->BufferedContext = CreateCompatibleDC(hdc);
memset(&header, 0, sizeof(BITMAPINFOHEADER));
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = Context->BufferedContextRect.right;
header.biHeight = Context->BufferedContextRect.bottom;
header.biPlanes = 1;
header.biBitCount = 32;
Context->BufferedBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &Context->BufferedBits, NULL, 0);
ReleaseDC(Context->Handle, hdc);
Context->BufferedOldBitmap = SelectObject(Context->BufferedContext, Context->BufferedBitmap);
}
static VOID PhpDeleteFadeOutContext(
_In_ PPHP_GRAPH_CONTEXT Context
)
{
if (Context->FadeOutContext)
{
SelectObject(Context->FadeOutContext, Context->FadeOutOldBitmap);
DeleteObject(Context->FadeOutBitmap);
DeleteDC(Context->FadeOutContext);
Context->FadeOutContext = NULL;
Context->FadeOutBitmap = NULL;
Context->FadeOutBits = NULL;
}
}
static VOID PhpCreateFadeOutContext(
_In_ PPHP_GRAPH_CONTEXT Context
)
{
HDC hdc;
BITMAPINFOHEADER header;
ULONG i;
ULONG j;
ULONG height;
COLORREF backColor;
ULONG fadeOutWidth;
FLOAT fadeOutWidthSquared;
ULONG currentAlpha;
ULONG currentColor;
PhpDeleteFadeOutContext(Context);
GetClientRect(Context->Handle, &Context->FadeOutContextRect);
Context->FadeOutContextRect.right = Context->Options.FadeOutWidth;
hdc = GetDC(Context->Handle);
Context->FadeOutContext = CreateCompatibleDC(hdc);
memset(&header, 0, sizeof(BITMAPINFOHEADER));
header.biSize = sizeof(BITMAPINFOHEADER);
header.biWidth = Context->FadeOutContextRect.right;
header.biHeight = Context->FadeOutContextRect.bottom;
header.biPlanes = 1;
header.biBitCount = 32;
Context->FadeOutBitmap = CreateDIBSection(hdc, (BITMAPINFO *)&header, DIB_RGB_COLORS, &Context->FadeOutBits, NULL, 0);
ReleaseDC(Context->Handle, hdc);
Context->FadeOutOldBitmap = SelectObject(Context->FadeOutContext, Context->FadeOutBitmap);
if (!Context->FadeOutBits)
return;
height = Context->FadeOutContextRect.bottom;
backColor = Context->Options.FadeOutBackColor;
fadeOutWidth = Context->Options.FadeOutWidth;
fadeOutWidthSquared = (FLOAT)fadeOutWidth * fadeOutWidth;
for (i = 0; i < fadeOutWidth; i++)
{
currentAlpha = 255 - (ULONG)((FLOAT)(i * i) / fadeOutWidthSquared * 255);
currentColor =
((backColor & 0xff) * currentAlpha / 255) +
((((backColor >> 8) & 0xff) * currentAlpha / 255) << 8) +
((((backColor >> 16) & 0xff) * currentAlpha / 255) << 16) +
(currentAlpha << 24);
for (j = i; j < height * fadeOutWidth; j += fadeOutWidth)
{
((PULONG)Context->FadeOutBits)[j] = currentColor;
}
}
}
VOID PhpUpdateDrawInfo(
_In_ HWND hwnd,
_In_ PPHP_GRAPH_CONTEXT Context
)
{
PH_GRAPH_GETDRAWINFO getDrawInfo;
Context->DrawInfo.Width = Context->BufferedContextRect.right;
Context->DrawInfo.Height = Context->BufferedContextRect.bottom;
getDrawInfo.Header.hwndFrom = hwnd;
getDrawInfo.Header.idFrom = Context->Id;
getDrawInfo.Header.code = GCN_GETDRAWINFO;
getDrawInfo.DrawInfo = &Context->DrawInfo;
SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&getDrawInfo);
}
VOID PhpDrawGraphControl(
_In_ HWND hwnd,
_In_ PPHP_GRAPH_CONTEXT Context
)
{
if (Context->BufferedBits)
PhDrawGraphDirect(Context->BufferedContext, Context->BufferedBits, &Context->DrawInfo);
if (Context->Style & GC_STYLE_FADEOUT)
{
BLENDFUNCTION blendFunction;
if (!Context->FadeOutContext)
PhpCreateFadeOutContext(Context);
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.BlendFlags = 0;
blendFunction.SourceConstantAlpha = 255;
blendFunction.AlphaFormat = AC_SRC_ALPHA;
GdiAlphaBlend(
Context->BufferedContext,
0,
0,
Context->Options.FadeOutWidth,
Context->FadeOutContextRect.bottom,
Context->FadeOutContext,
0,
0,
Context->Options.FadeOutWidth,
Context->FadeOutContextRect.bottom,
blendFunction
);
}
if (Context->Style & GC_STYLE_DRAW_PANEL)
{
PH_GRAPH_DRAWPANEL drawPanel;
drawPanel.Header.hwndFrom = hwnd;
drawPanel.Header.idFrom = Context->Id;
drawPanel.Header.code = GCN_DRAWPANEL;
drawPanel.hdc = Context->BufferedContext;
drawPanel.Rect = Context->BufferedContextRect;
SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&drawPanel);
}
}
LRESULT CALLBACK PhpGraphWndProc(
_In_ HWND hwnd,
_In_ UINT uMsg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
PPHP_GRAPH_CONTEXT context;
context = (PPHP_GRAPH_CONTEXT)GetWindowLongPtr(hwnd, 0);
if (uMsg == WM_CREATE)
{
PhpCreateGraphContext(&context);
SetWindowLongPtr(hwnd, 0, (LONG_PTR)context);
}
if (!context)
return DefWindowProc(hwnd, uMsg, wParam, lParam);
switch (uMsg)
{
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
{
if (context->TooltipHandle)
{
MSG message;
message.hwnd = hwnd;
message.message = uMsg;
message.wParam = wParam;
message.lParam = lParam;
SendMessage(context->TooltipHandle, TTM_RELAYEVENT, 0, (LPARAM)&message);
}
}
break;
}
switch (uMsg)
{
case WM_CREATE:
{
CREATESTRUCT *createStruct = (CREATESTRUCT *)lParam;
context->Handle = hwnd;
context->Style = createStruct->style;
context->Id = (ULONG_PTR)createStruct->hMenu;
}
break;
case WM_DESTROY:
{
if (context->TooltipHandle)
DestroyWindow(context->TooltipHandle);
PhpDeleteFadeOutContext(context);
PhpDeleteBufferedContext(context);
PhpFreeGraphContext(context);
SetWindowLongPtr(hwnd, 0, (LONG_PTR)NULL);
}
break;
case WM_STYLECHANGED:
{
STYLESTRUCT *styleStruct = (STYLESTRUCT *)lParam;
if (wParam == GWL_STYLE)
{
context->Style = styleStruct->styleNew;
context->NeedsDraw = TRUE;
}
}
break;
case WM_SIZE:
{
// Force a re-create of the buffered context.
PhpCreateBufferedContext(context);
PhpDeleteFadeOutContext(context);
PhpUpdateDrawInfo(hwnd, context);
context->NeedsDraw = TRUE;
InvalidateRect(hwnd, NULL, FALSE);
if (context->TooltipHandle)
{
TOOLINFO toolInfo;
memset(&toolInfo, 0, sizeof(TOOLINFO));
toolInfo.cbSize = sizeof(TOOLINFO);
toolInfo.hwnd = hwnd;
toolInfo.uId = 1;
GetClientRect(hwnd, &toolInfo.rect);
SendMessage(context->TooltipHandle, TTM_NEWTOOLRECT, 0, (LPARAM)&toolInfo);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT paintStruct;
HDC hdc;
if (hdc = BeginPaint(hwnd, &paintStruct))
{
if (!context->BufferedContext)
PhpCreateBufferedContext(context);
if (context->NeedsUpdate)
{
PhpUpdateDrawInfo(hwnd, context);
context->NeedsUpdate = FALSE;
}
if (context->NeedsDraw)
{
PhpDrawGraphControl(hwnd, context);
context->NeedsDraw = FALSE;
}
BitBlt(
hdc,
paintStruct.rcPaint.left,
paintStruct.rcPaint.top,
paintStruct.rcPaint.right - paintStruct.rcPaint.left,
paintStruct.rcPaint.bottom - paintStruct.rcPaint.top,
context->BufferedContext,
paintStruct.rcPaint.left,
paintStruct.rcPaint.top,
SRCCOPY
);
EndPaint(hwnd, &paintStruct);
}
}
return 0;
case WM_ERASEBKGND:
return 1;
case WM_NCPAINT:
{
HRGN updateRegion;
updateRegion = (HRGN)wParam;
if (updateRegion == (HRGN)1) // HRGN_FULL
updateRegion = NULL;
// Themed border
if (context->Style & WS_BORDER)
{
HDC hdc;
ULONG flags;
RECT rect;
// Note the use of undocumented flags below. GetDCEx doesn't work without these.
flags = DCX_WINDOW | DCX_LOCKWINDOWUPDATE | 0x10000;
if (updateRegion)
flags |= DCX_INTERSECTRGN | 0x40000;
if (hdc = GetDCEx(hwnd, updateRegion, flags))
{
GetClientRect(hwnd, &rect);
rect.right += 2;
rect.bottom += 2;
SetDCBrushColor(hdc, RGB(0x8f, 0x8f, 0x8f));
FrameRect(hdc, &rect, GetStockObject(DC_BRUSH));
ReleaseDC(hwnd, hdc);
return 0;
}
}
}
break;
case WM_NOTIFY:
{
LPNMHDR header = (LPNMHDR)lParam;
if (header->hwndFrom == context->TooltipHandle)
{
switch (header->code)
{
case TTN_GETDISPINFO:
{
LPNMTTDISPINFO dispInfo = (LPNMTTDISPINFO)header;
POINT point;
RECT clientRect;
PH_GRAPH_GETTOOLTIPTEXT getTooltipText;
GetCursorPos(&point);
ScreenToClient(hwnd, &point);
GetClientRect(hwnd, &clientRect);
getTooltipText.Header.hwndFrom = hwnd;
getTooltipText.Header.idFrom = context->Id;
getTooltipText.Header.code = GCN_GETTOOLTIPTEXT;
getTooltipText.Index = (clientRect.right - point.x - 1) / context->DrawInfo.Step;
getTooltipText.TotalCount = context->DrawInfo.LineDataCount;
getTooltipText.Text.Buffer = NULL;
getTooltipText.Text.Length = 0;
SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&getTooltipText);
if (getTooltipText.Text.Buffer)
{
dispInfo->lpszText = getTooltipText.Text.Buffer;
}
}
break;
}
}
}
break;
case WM_SETCURSOR:
{
if (context->Options.DefaultCursor)
{
SetCursor(context->Options.DefaultCursor);
return TRUE;
}
}
break;
case WM_MOUSEMOVE:
{
if (context->TooltipHandle)
{
POINT point;
GetCursorPos(&point);
ScreenToClient(hwnd, &point);
if (context->LastCursorLocation.x != point.x || context->LastCursorLocation.y != point.y)
{
SendMessage(context->TooltipHandle, TTM_UPDATE, 0, 0);
context->LastCursorLocation = point;
}
}
}
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_RBUTTONDBLCLK:
{
PH_GRAPH_MOUSEEVENT mouseEvent;
RECT clientRect;
GetClientRect(hwnd, &clientRect);
mouseEvent.Header.hwndFrom = hwnd;
mouseEvent.Header.idFrom = context->Id;
mouseEvent.Header.code = GCN_MOUSEEVENT;
mouseEvent.Message = uMsg;
mouseEvent.Keys = (ULONG)wParam;
mouseEvent.Point.x = LOWORD(lParam);
mouseEvent.Point.y = HIWORD(lParam);
mouseEvent.Index = (clientRect.right - mouseEvent.Point.x - 1) / context->DrawInfo.Step;
mouseEvent.TotalCount = context->DrawInfo.LineDataCount;
SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM)&mouseEvent);
}
break;
case GCM_GETDRAWINFO:
{
PPH_GRAPH_DRAW_INFO drawInfo = (PPH_GRAPH_DRAW_INFO)lParam;
memcpy(drawInfo, &context->DrawInfo, sizeof(PH_GRAPH_DRAW_INFO));
}
return TRUE;
case GCM_SETDRAWINFO:
{
PPH_GRAPH_DRAW_INFO drawInfo = (PPH_GRAPH_DRAW_INFO)lParam;
ULONG width;
ULONG height;
width = context->DrawInfo.Width;
height = context->DrawInfo.Height;
memcpy(&context->DrawInfo, drawInfo, sizeof(PH_GRAPH_DRAW_INFO));
context->DrawInfo.Width = width;
context->DrawInfo.Height = height;
}
return TRUE;
case GCM_DRAW:
{
PhpUpdateDrawInfo(hwnd, context);
context->NeedsDraw = TRUE;
}
return TRUE;
case GCM_MOVEGRID:
{
LONG increment = (LONG)wParam;
context->DrawInfo.GridXOffset += increment;
}
return TRUE;
case GCM_GETBUFFEREDCONTEXT:
return (LRESULT)context->BufferedContext;
case GCM_SETTOOLTIP:
{
if (wParam)
{
TOOLINFO toolInfo = { sizeof(toolInfo) };
context->TooltipHandle = CreateWindow(
TOOLTIPS_CLASS,
NULL,
WS_POPUP | WS_EX_TRANSPARENT | TTS_NOPREFIX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
PhLibImageBase,
NULL
);
SetWindowPos(context->TooltipHandle, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
toolInfo.uFlags = 0;
toolInfo.hwnd = hwnd;
toolInfo.uId = 1;
toolInfo.lpszText = LPSTR_TEXTCALLBACK;
GetClientRect(hwnd, &toolInfo.rect);
SendMessage(context->TooltipHandle, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
SendMessage(context->TooltipHandle, TTM_SETDELAYTIME, TTDT_INITIAL, 0);
SendMessage(context->TooltipHandle, TTM_SETDELAYTIME, TTDT_AUTOPOP, MAXSHORT);
// Allow newlines (-1 doesn't work)
SendMessage(context->TooltipHandle, TTM_SETMAXTIPWIDTH, 0, MAXSHORT);
}
else
{
DestroyWindow(context->TooltipHandle);
context->TooltipHandle = NULL;
}
}
return TRUE;
case GCM_UPDATETOOLTIP:
{
if (!context->TooltipHandle)
return FALSE;
SendMessage(context->TooltipHandle, TTM_UPDATE, 0, 0);
}
return TRUE;
case GCM_GETOPTIONS:
memcpy((PPH_GRAPH_OPTIONS)lParam, &context->Options, sizeof(PH_GRAPH_OPTIONS));
return TRUE;
case GCM_SETOPTIONS:
memcpy(&context->Options, (PPH_GRAPH_OPTIONS)lParam, sizeof(PH_GRAPH_OPTIONS));
PhpDeleteFadeOutContext(context);
return TRUE;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
/**
* Initializes a graph buffer management structure.
*
* \param Buffers The buffer management structure.
*/
VOID PhInitializeGraphBuffers(
_Out_ PPH_GRAPH_BUFFERS Buffers
)
{
Buffers->AllocatedCount = 0;
Buffers->Data1 = NULL;
Buffers->Data2 = NULL;
Buffers->Valid = FALSE;
}
/**
* Frees resources used by a graph buffer management structure.
*
* \param Buffers The buffer management structure.
*/
VOID PhDeleteGraphBuffers(
_Inout_ PPH_GRAPH_BUFFERS Buffers
)
{
if (Buffers->Data1) PhFree(Buffers->Data1);
if (Buffers->Data2) PhFree(Buffers->Data2);
}
/**
* Sets up a graphing information structure with information from a graph buffer management
* structure.
*
* \param Buffers The buffer management structure.
* \param DrawInfo The graphing information structure.
* \param DataCount The number of data points currently required. The buffers are resized if needed.
*/
VOID PhGetDrawInfoGraphBuffers(
_Inout_ PPH_GRAPH_BUFFERS Buffers,
_Inout_ PPH_GRAPH_DRAW_INFO DrawInfo,
_In_ ULONG DataCount
)
{
DrawInfo->LineDataCount = min(DataCount, PH_GRAPH_DATA_COUNT(DrawInfo->Width, DrawInfo->Step));
// Do we need to allocate or re-allocate the data buffers?
if (Buffers->AllocatedCount < DrawInfo->LineDataCount)
{
if (Buffers->Data1)
PhFree(Buffers->Data1);
if ((DrawInfo->Flags & PH_GRAPH_USE_LINE_2) && Buffers->Data2)
PhFree(Buffers->Data2);
Buffers->AllocatedCount *= 2;
if (Buffers->AllocatedCount < DrawInfo->LineDataCount)
Buffers->AllocatedCount = DrawInfo->LineDataCount;
Buffers->Data1 = PhAllocate(Buffers->AllocatedCount * sizeof(FLOAT));
if (DrawInfo->Flags & PH_GRAPH_USE_LINE_2)
{
Buffers->Data2 = PhAllocate(Buffers->AllocatedCount * sizeof(FLOAT));
}
Buffers->Valid = FALSE;
}
DrawInfo->LineData1 = Buffers->Data1;
DrawInfo->LineData2 = Buffers->Data2;
}
VOID PhInitializeGraphState(
_Out_ PPH_GRAPH_STATE State
)
{
PhInitializeGraphBuffers(&State->Buffers);
State->Text = NULL;
State->TooltipText = NULL;
State->TooltipIndex = -1;
}
VOID PhDeleteGraphState(
_Inout_ PPH_GRAPH_STATE State
)
{
PhDeleteGraphBuffers(&State->Buffers);
if (State->Text) PhDereferenceObject(State->Text);
if (State->TooltipText) PhDereferenceObject(State->TooltipText);
}
VOID PhGraphStateGetDrawInfo(
_Inout_ PPH_GRAPH_STATE State,
_In_ PPH_GRAPH_GETDRAWINFO GetDrawInfo,
_In_ ULONG DataCount
)
{
PhGetDrawInfoGraphBuffers(&State->Buffers, GetDrawInfo->DrawInfo, DataCount);
}