You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

639 lines
18 KiB
C++

// XTPTipWindow.cpp : implementation of the CXTPTipWindow class.
//
// This file is a part of the XTREME CONTROLS MFC class library.
// (c)1998-2012 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////
// Thanks to Michael Berlenz for helping to extend the CXTPTipWindow class.
#include "stdafx.h"
#include "Common/XTPDrawHelpers.h"
#include "Common/XTPColorManager.h"
#include "Common/XTPSystemHelpers.h"
#include "../Util/XTPGlobal.h"
#include "XTPTipWindow.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define CS_DROPSHADOW 0x00020000
/////////////////////////////////////////////////////////////////////////////
// CXTPTipWindow
/////////////////////////////////////////////////////////////////////////////
CXTPTipWindow::CXTPTipWindow()
{
m_sizeMargin = CSize(0, 0);
m_nLineSpace = 0;
m_dwTipStyle = 0;
m_nElapseTimerEventID = 1;
m_pParentWnd = NULL;
m_strTitle = _T("");
m_strDescrip = _T("");
m_crBackColor = ::GetSysColor(COLOR_INFOBK);
m_crTextColor = ::GetSysColor(COLOR_INFOTEXT);
m_sizeTitle = CSize(0, 0);
// m_bMouseWasClickedSoDoNotShowTip = FALSE;
m_ptMousePos = CPoint(0, 0);
m_bCenterHorz = FALSE;
m_nDelayTimerEventID = 2;
m_bDelayTimerRunning = FALSE;
m_pointTipOffsetPos = CPoint(0, 20); //20 = mouse cursor height
m_pnTabStopPositions = NULL;
m_nTabPositions = 0;
m_bSystemShadow = FALSE;
m_rectTipArea.SetRectEmpty();
}
CXTPTipWindow::~CXTPTipWindow()
{
}
BOOL CXTPTipWindow::Create(CWnd* pParentWnd)
{
// register the wnd class.
XTPDrawHelpers()->RegisterWndClass(0, _T("XTPTipWindow"), CS_SAVEBITS | CS_HREDRAW | CS_VREDRAW);
// call the base class for creation.
if (!CWnd::CreateEx(0, _T("XTPTipWindow"), _T(""), WS_POPUP, 0, 0, 0, 0, pParentWnd->m_hWnd, 0, NULL))
{
TRACE0("Failed to create popup window.\n");
return FALSE;
}
m_pParentWnd = pParentWnd;
return TRUE;
}
BEGIN_MESSAGE_MAP(CXTPTipWindow, CWnd)
//{{AFX_MSG_MAP(CXTPTipWindow)
ON_WM_MOUSEMOVE()
ON_WM_KILLFOCUS()
ON_WM_PAINT()
ON_WM_TIMER()
ON_WM_ERASEBKGND()
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
ON_WM_MBUTTONDOWN()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CXTPTipWindow::CreateShadow()
{
if (((m_dwTipStyle & (TWS_XTP_ALPHASHADOW | TWS_XTP_DROPSHADOW)) == (TWS_XTP_ALPHASHADOW | TWS_XTP_DROPSHADOW)) &&
XTPSystemVersion()->IsWinXPOrGreater()) // Windows XP only
{
SetClassLong(m_hWnd, GCL_STYLE, GetClassLong(m_hWnd, GCL_STYLE) | CS_DROPSHADOW);
m_bSystemShadow = TRUE;
}
else
{
SetClassLong(m_hWnd, GCL_STYLE, GetClassLong(m_hWnd, GCL_STYLE) & ~CS_DROPSHADOW);
m_bSystemShadow = FALSE;
}
}
BOOL CXTPTipWindow::ShowTipWindow(const CPoint& point, DWORD dwTipStyle/*= TWS_XTP_DROPSHADOW*/, UINT nDelay/*= 0*/, UINT nElapse/*= 5000*/, BOOL bCenterHorz /*= FALSE*/)
{
//Check for valid tip text
if (m_strTitle.IsEmpty() && m_strDescrip.IsEmpty())
return FALSE;
// Save values for InitializeSize() function
m_ptMousePos = point;
m_bCenterHorz = bCenterHorz;
// Return if process is running
if (m_bDelayTimerRunning || IsWindowVisible())
return FALSE;
// Analyze tip text
m_arStrings.RemoveAll();
if (m_strDescrip.Find(_T("\n")) != -1)
{
CString strDesc(m_strDescrip);
while (strDesc.Find(_T("\n")) != -1)
{
int nDelim = strDesc.Find(_T("\n"));
CString strAdd = strDesc.Left(nDelim);
m_arStrings.Add(strAdd);
strDesc = strDesc.Mid(nDelim + 1);
}
m_arStrings.Add(strDesc);
}
// Save the tip style
m_dwTipStyle = dwTipStyle;
CreateShadow();
if (!nDelay)
{
// Show the tip window
InitializeSize(m_ptMousePos, m_bCenterHorz);
SetWindowPos(&wndTop, m_rcScreenWindow.left, m_rcScreenWindow.top, m_rcShadow.right - m_rcWindow.left,
m_rcShadow.bottom - m_rcWindow.top, SWP_SHOWWINDOW | SWP_NOACTIVATE); //see also InitializeSize()
SetCapture();
}
// If a time-out value was specified, start the timer.
if (nElapse || nDelay)
{
if (nDelay)
{
SetTimer(m_nDelayTimerEventID, nDelay, NULL); //if timer runns out, window will be shown
m_bDelayTimerRunning = TRUE;
}
if (nElapse)
SetTimer(m_nElapseTimerEventID, nDelay + nElapse, NULL); //if timer runns out, window will be hide
}
return TRUE; // success!
}
//////////////////////////////////////////////////////////////////////////
// ShowTipWindow
// ----------------------------------------------------------------------------
// rectCtrl = if the mouse is inside this rectangle the tip will be shown
// => client coordinates of the window that owns this tip window
// pointMousePos => client coordinates of the window that owns this tip window
//
BOOL CXTPTipWindow::ShowTipWindow(const CRect& rectCtrl, const CPoint& pointMousePos, DWORD dwTipStyle/*= TWS_XTP_DROPSHADOW*/, UINT nDelay/*= 0*/, UINT nElapse/*= 5000*/, BOOL bCenterHorz /*= FALSE*/, BOOL bShowTipAtCursorPos/*= FALSE*/)
{
//Check for valid tip text
// if (m_strTitle.IsEmpty() && m_strDescrip.IsEmpty()) //Do not process this check here!
// return FALSE;
//Check for zero rectangle
if (rectCtrl.IsRectNull())
return ShowTipWindow(pointMousePos, dwTipStyle, nDelay, nElapse, bCenterHorz);
//Check mouse was clicked -> do not show tip window
// if (m_bMouseWasClickedSoDoNotShowTip && rectCtrl == m_rectLast)
// return FALSE;
//In case the tip window is visible hide it
HideTipWindow();
//Calculate offset for bShowTipAtCursorPos = TRUE
CPoint pointMove = pointMousePos - rectCtrl.TopLeft();
pointMove.Offset(m_pointTipOffsetPos);
//Copy rectCtrl and move rectangle to zero origin
m_rectTipArea = rectCtrl;
//Offset rectangle
CPoint ptTipWindowPos = rectCtrl.TopLeft();
if (bShowTipAtCursorPos) //else show window at top left corner of rectCtrl
{
ptTipWindowPos += pointMove;
}
// m_bMouseWasClickedSoDoNotShowTip = FALSE;
//m_rectLast = rectCtrl;
m_pParentWnd->ClientToScreen(&ptTipWindowPos);
m_pParentWnd->ClientToScreen(&m_rectTipArea);
return ShowTipWindow(ptTipWindowPos, dwTipStyle, nDelay, nElapse, bCenterHorz);
}
void CXTPTipWindow::HideTipWindow()
{
// Kill the timers
KillTimer(m_nElapseTimerEventID);
KillTimer(m_nDelayTimerEventID);
m_bDelayTimerRunning = FALSE;
// Release capture
if (GetCapture() == this)
ReleaseCapture();
// Hide the tip window
if (IsWindowVisible())
ShowWindow(SW_HIDE);
//Initialize variables
m_rectTipArea.SetRectEmpty();
}
void CXTPTipWindow::InitializeSize(const CPoint& point, BOOL bCenterHorz /*FALSE*/)
{
// Create a temporary window dc, we are not actually
// going to paint anything, we just want to get the sizes
// of the title and description text.
CWindowDC dc(NULL);
// Get the size of the title string.
CFont* pOldFont = dc.SelectObject(&XTPAuxData().fontBold);
m_sizeTitle = dc.GetTabbedTextExtent(m_strTitle, m_nTabPositions, m_pnTabStopPositions);
// Get the size of the description string.
dc.SelectObject(&XTPAuxData().font);
CSize sizeDescrip(0, 0); // description text size.
if (m_arStrings.GetSize() == 0)
{
sizeDescrip = dc.GetTabbedTextExtent(m_strDescrip, m_nTabPositions, m_pnTabStopPositions);
}
else
{
TEXTMETRIC tm; // get the text metrics for the device context that is using the
dc.GetTextMetrics(&tm); // control's font, this will give us the text height in pixels.
CSize sizeTemp;
int nStrings;
for (nStrings = 0; nStrings < m_arStrings.GetSize(); ++nStrings)
{
// Strings could be empty because only a line break could be in the line.
// A empty string will return zero text extend. But we still have to count this line!
if (m_arStrings[nStrings].IsEmpty())
sizeDescrip.cy += tm.tmHeight;
else
{
sizeTemp = dc.GetTabbedTextExtent(m_arStrings[nStrings], m_nTabPositions, m_pnTabStopPositions);
sizeDescrip.cy += sizeTemp.cy;
sizeDescrip.cx = __max(sizeDescrip.cx, sizeTemp.cx);
}
}
}
// Restore GDI object.
dc.SelectObject(pOldFont);
// Define the width of the tip window.
int nWidth = __max(m_sizeTitle.cx, sizeDescrip.cx);
nWidth = (m_sizeMargin.cx * 2 + nWidth);
// Define the height of the tip window.
int nHeight = (m_sizeMargin.cy * 2 + ((m_strTitle.GetLength() > 0) ? m_sizeTitle.cy + m_nLineSpace : 0) + sizeDescrip.cy);
// Set the sizes for the tip window and its shadow.
m_rcScreenWindow.left = point.x;
m_rcScreenWindow.top = point.y;
m_rcScreenWindow.right = point.x + nWidth + 2; //+2 for better view
m_rcScreenWindow.bottom = point.y + nHeight;
if (bCenterHorz)
m_rcScreenWindow.OffsetRect(-m_rcScreenWindow.Width()/2, 0);
// ensure that the tip window is visible.
CRect rcWork = XTPMultiMonitor()->GetWorkArea(point);
CSize size = m_rcScreenWindow.Size();
// move right
if (m_rcScreenWindow.left < rcWork.left)
{
m_rcScreenWindow.left = rcWork.left;
m_rcScreenWindow.right = m_rcScreenWindow.left + size.cx;
}
// move left
else if (m_rcScreenWindow.right > rcWork.right)
{
m_rcScreenWindow.right = rcWork.right;
m_rcScreenWindow.left = m_rcScreenWindow.right - size.cx;
}
// move up
if (m_rcScreenWindow.bottom > rcWork.bottom)
{
m_rcScreenWindow.bottom = rcWork.bottom;
m_rcScreenWindow.top = m_rcScreenWindow.bottom - size.cy;
}
// Initialize the size of the shadow rect.
m_rcWindow = m_rcScreenWindow;
m_rcWindow.top = 0;
m_rcWindow.left = 0;
m_rcWindow.right = m_rcScreenWindow.Width();
m_rcWindow.bottom = m_rcScreenWindow.Height();
m_rcShadow = m_rcWindow;
if ((m_dwTipStyle & TWS_XTP_DROPSHADOW) && !m_bSystemShadow)
m_rcShadow.OffsetRect(5, 5);
}
void CXTPTipWindow::OnKillFocus(CWnd* pNewWnd)
{
CWnd::OnKillFocus(pNewWnd);
HideTipWindow();
// Repaint the area where the shadow was displayed.
if ((m_dwTipStyle & TWS_XTP_DROPSHADOW) && !m_bSystemShadow)
{
m_pParentWnd->RedrawWindow(0, 0,
RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE | RDW_ALLCHILDREN);
}
}
int CXTPTipWindow::CheckValue(int iValue)
{
return ((iValue > 255) ? 255 : ((iValue < 0) ? 0 : iValue));
}
COLORREF CXTPTipWindow::AlphaPixel(COLORREF crPixel, int i)
{
return RGB(
CheckValue(GetRValue(crPixel)-i),
CheckValue(GetGValue(crPixel)-i),
CheckValue(GetBValue(crPixel)-i));
}
void CXTPTipWindow::DrawShadowRect(CDC* pDC, const CRect& rcShadow)
{
if ((m_dwTipStyle & TWS_XTP_ALPHASHADOW) == 0)
{
// Bit pattern for a monochrome brush with every
// other pixel turned off
WORD bits[] =
{
0x0055, 0x00AA, 0x0055, 0x00AA,
0x0055, 0x00AA, 0x0055, 0x00AA
};
// Need a monochrome pattern bitmap
CBitmap bitmap;
bitmap.CreateBitmap(8, 8, 1, 1, &bits);
// Create the pattern brush
CBrush brush;
brush.CreatePatternBrush(&bitmap);
CBrush *pOldBrush = pDC->SelectObject(&brush);
// Turn every other pixel to black
COLORREF clrBk = pDC->SetBkColor(RGB(0x00, 0x00, 0x00));
COLORREF clrText = pDC->SetTextColor(RGB(0xFF, 0xFF, 0xFF));
// 0x00A000C9 is the ROP code to AND the brush with the destination
pDC->PatBlt(rcShadow.left, rcShadow.top, rcShadow.Width(), rcShadow.Height(),
(DWORD)0x00A000C9); //DPa - raster code
// Restore the device context
pDC->SelectObject(pOldBrush);
pDC->SetTextColor(clrText);
pDC->SetBkColor(clrBk);
brush.DeleteObject();
bitmap.DeleteObject();
}
else
{
CRect rc1 = rcShadow;
CRect rc2 = rcShadow;
CRect rc3 = rcShadow;
CRect rc4 = rcShadow;
CRect rc5 = rcShadow;
CRect rc6 = rcShadow;
rc2.DeflateRect(1, 1);
rc3.DeflateRect(2, 2);
rc4.DeflateRect(3, 3);
rc5.DeflateRect(4, 4);
rc6.DeflateRect(5, 5);
int cx = rcShadow.Width() + 5;
int cy = rcShadow.Height() + 5;
int x;
for (x = 5; x < cx; ++x)
{
int y;
for (y = 5; y < cy; ++y)
{
CPoint pt(x, y);
int iAlpha = 0;
// area covered by menu...continue.
if (rc6.PtInRect(pt))
{
continue;
}
else if (rc5.PtInRect(pt))
{
iAlpha = 75;
}
else if (rc4.PtInRect(pt))
{
iAlpha = 60;
}
else if (rc3.PtInRect(pt))
{
iAlpha = 45;
}
else if (rc2.PtInRect(pt))
{
iAlpha = 30;
}
else if (rc1.PtInRect(pt))
{
iAlpha = 15;
}
if (iAlpha != 0)
{
COLORREF crAlpha = AlphaPixel(pDC->GetPixel(x, y), iAlpha);
pDC->SetPixel(x, y, crAlpha);
}
}
}
}
}
void CXTPTipWindow::OnPaint()
{
CPaintDC dc(this); // device context for painting
// Set the background mode to transparent.
dc.SetBkMode(TRANSPARENT);
// Draw a shadow on the the parent window, we will need the
// parent's device context to do this.
if ((m_dwTipStyle & TWS_XTP_DROPSHADOW) && !m_bSystemShadow)
DrawShadowRect(&dc, m_rcShadow);
// use the system tooltip colors for text and background.
dc.SetTextColor(m_crTextColor);
dc.FillSolidRect(m_rcWindow, m_crBackColor);
// Draw a border around the window.
dc.Draw3dRect(m_rcWindow, GetXtremeColor(COLOR_3DDKSHADOW),
GetXtremeColor(COLOR_3DDKSHADOW));
// Draw an inner light grey border.
if (m_dwTipStyle & TWS_XTP_THICKBORDER)
{
CRect rect(m_rcWindow);
rect.DeflateRect(1, 1);
dc.Draw3dRect(rect, GetXtremeColor(COLOR_3DSHADOW),
GetXtremeColor(COLOR_3DSHADOW));
}
// Draw the title text.
CFont* pOldFont = dc.SelectObject(&XTPAuxData().fontBold);
dc.TabbedTextOut(m_sizeMargin.cx + 1, m_sizeMargin.cy, m_strTitle, m_nTabPositions,
m_pnTabStopPositions, m_sizeMargin.cx + 1);
// Draw the description text.
dc.SelectObject(&XTPAuxData().font);
if (m_arStrings.GetSize() == 0)
dc.TabbedTextOut(m_sizeMargin.cx + 1, m_sizeMargin.cy + ((m_strTitle.GetLength() > 0) ? m_sizeTitle.cy + m_nLineSpace : 0),
m_strDescrip, m_nTabPositions, m_pnTabStopPositions, m_sizeMargin.cx + 1);
else
{
// Get the string height for stepping through
CSize sizeDesc = dc.GetTabbedTextExtent(m_arStrings[0], m_nTabPositions, m_pnTabStopPositions);
// The second parameter is long because we add the height of the strings already done, and also
// we don't add anything to get below the title if the length of the title is 0.
int nStrings;
for (nStrings = 0; nStrings < m_arStrings.GetSize(); ++nStrings)
dc.TabbedTextOut(m_sizeMargin.cx + 1,
m_sizeMargin.cy + (nStrings * sizeDesc.cy) + ((m_strTitle.GetLength() > 0) ? m_sizeTitle.cy + m_nLineSpace : 0),
m_arStrings[nStrings], m_nTabPositions, m_pnTabStopPositions, m_sizeMargin.cx + 1);
}
// Restore the device context.
dc.SelectObject(pOldFont);
}
void CXTPTipWindow::OnTimer(UINT_PTR nIDEvent)
{
if (m_nElapseTimerEventID == nIDEvent)
{
// Hide the tip window.
HideTipWindow();
}
if (m_nDelayTimerEventID == nIDEvent)
{
KillTimer(m_nDelayTimerEventID);
if (!m_rectTipArea.IsRectNull())
{
//If someone moves the mouse over a tip area in the client window
//it may happen that the tip delay timer will be started. During this
//delay time the user may move the mouse out of the application window.
//After the timer runs out the tip window will be shown but because
//of the fast mouse move the cursor will not be located inside the tip area.
//Because the cursor my located outside the application we will not get
//the mouse move messages at CXTPTipWindow::OnMouseMove().
//So better check if the mouse is still located inside the tip area.
//If not do not show the tip window.
CPoint pointMouse;
GetCursorPos(&pointMouse);
if (!m_rectTipArea.PtInRect(pointMouse))
return;
}
// Show the tip window
InitializeSize(m_ptMousePos, m_bCenterHorz); //Calculates m_rcScreenWindow values
SetWindowPos(&wndTop, m_rcScreenWindow.left, m_rcScreenWindow.top, m_rcScreenWindow.Width(), m_rcScreenWindow.Height(), SWP_SHOWWINDOW | SWP_NOACTIVATE); //+5 because of the shadow
SetCapture();
m_bDelayTimerRunning = FALSE;
}
CWnd::OnTimer(nIDEvent);
}
BOOL CXTPTipWindow::OnEraseBkgnd(CDC* /*pDC*/)
{
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
//
// Hide the tip window and forward the message to the window at mouse point.
//
void CXTPTipWindow::ForwardMessage(UINT uiMsg, UINT nFlags, CPoint point)
{
// m_bMouseWasClickedSoDoNotShowTip = TRUE;
HideTipWindow();
CPoint pt(point);
ClientToScreen(&pt);
CWnd* pClick = WindowFromPoint(pt);
if (!pClick->IsChild(this) && pClick != this)
{
LRESULT iHitTest = pClick->SendMessage(WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y));
if (AfxGetMainWnd() != NULL)
pClick->SendMessage(WM_MOUSEACTIVATE, (LPARAM)AfxGetMainWnd()->m_hWnd, MAKELPARAM(iHitTest , uiMsg));
pClick->ScreenToClient(&pt);
pClick->SendMessage(uiMsg, nFlags, MAKELPARAM(pt.x, pt.y));
}
CWnd::OnLButtonDown(nFlags, point);
}
void CXTPTipWindow::OnLButtonDown(UINT nFlags, CPoint point)
{
ForwardMessage(WM_LBUTTONDOWN, nFlags, point);
}
void CXTPTipWindow::OnRButtonDown(UINT nFlags, CPoint point)
{
ForwardMessage(WM_RBUTTONDOWN, nFlags, point);
}
void CXTPTipWindow::OnMButtonDown(UINT nFlags, CPoint point)
{
ForwardMessage(WM_MBUTTONDOWN, nFlags, point);
}
void CXTPTipWindow::OnMouseMove(UINT /*nFlags*/, CPoint point)
{
ClientToScreen( &point );
if (!m_rectTipArea.IsRectNull() && !m_rectTipArea.PtInRect(point))
HideTipWindow();
}
void CXTPTipWindow::SetTipText(LPCTSTR lpszTitle, LPCTSTR lpszDescrip, BOOL bRedraw)
{
m_strTitle = lpszTitle; //Single line
// Avoid uggly outputed rectangle character in the tip window
m_strDescrip = lpszDescrip; //Could be multi-line
m_strDescrip.Remove(_T('\r'));
if (bRedraw)
{
InvalidateRect(NULL);
}
}
void CXTPTipWindow::SetTextTabStopPositions(LPINT pnTabStopPositions, int nTabPositions)
{
//pointer to integer array containing the tab stop positions (could be a static array)
m_pnTabStopPositions = pnTabStopPositions;
//number of array elements
m_nTabPositions = nTabPositions;
}