📄 tooltips.c
字号:
/*
* Tool tip control
*
* Copyright 1998, 1999 Eric Kohl
* Copyright 2004 Robert Shearman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* NOTES
*
* This code was audited for completeness against the documented features
* of Comctl32.dll version 6.0 on Sep. 08, 2004, by Robert Shearman.
*
* Unless otherwise noted, we believe this code to be complete, as per
* the specification mentioned above.
* If you discover missing features or bugs please note them below.
*
* TODO:
* - Custom draw support.
* - Animation.
* - Links.
* - Messages:
* o TTM_ADJUSTRECT
* o TTM_GETTITLEA
* o TTM_GETTTILEW
* o TTM_POPUP
* - Styles:
* o TTS_NOANIMATE
* o TTS_NOFADE
* o TTS_CLOSE
*
* Testing:
* - Run tests using Waite Group Windows95 API Bible Volume 2.
* The second cdrom (chapter 3) contains executables activate.exe,
* curtool.exe, deltool.exe, enumtools.exe, getinfo.exe, getiptxt.exe,
* hittest.exe, needtext.exe, newrect.exe, updtext.exe and winfrpt.exe.
*
* Timer logic.
*
* One important point to remember is that tools don't necessarily get
* a WM_MOUSEMOVE once the cursor leaves the tool, an example is when
* a tool sets TTF_IDISHWND (i.e. an entire window is a tool) because
* here WM_MOUSEMOVEs only get sent when the cursor is inside the
* client area. Therefore the only reliable way to know that the
* cursor has left a tool is to keep a timer running and check the
* position every time it expires. This is the role of timer
* ID_TIMERLEAVE.
*
*
* On entering a tool (detected in a relayed WM_MOUSEMOVE) we start
* ID_TIMERSHOW, if this times out and we're still in the tool we show
* the tip. On showing a tip we start both ID_TIMERPOP and
* ID_TIMERLEAVE. On hiding a tooltip we kill ID_TIMERPOP.
* ID_TIMERPOP is restarted on every relayed WM_MOUSEMOVE. If
* ID_TIMERPOP expires the tool is hidden and ID_TIMERPOP is killed.
* ID_TIMERLEAVE remains running - this is important as we need to
* determine when the cursor leaves the tool.
*
* When ID_TIMERLEAVE expires or on a relayed WM_MOUSEMOVE if we're
* still in the tool do nothing (apart from restart ID_TIMERPOP if
* this is a WM_MOUSEMOVE) (ID_TIMERLEAVE remains running). If we've
* left the tool and entered another one then hide the tip and start
* ID_TIMERSHOW with time ReshowTime and kill ID_TIMERLEAVE. If we're
* outside all tools hide the tip and kill ID_TIMERLEAVE. On Relayed
* mouse button messages hide the tip but leave ID_TIMERLEAVE running,
* this again will let us keep track of when the cursor leaves the
* tool.
*
*
* infoPtr->nTool is the tool the mouse was on on the last relayed MM
* or timer expiry or -1 if the mouse was not on a tool.
*
* infoPtr->nCurrentTool is the tool for which the tip is currently
* displaying text for or -1 if the tip is not shown. Actually this
* will only ever be infoPtr-nTool or -1, so it could be changed to a
* BOOL.
*
*/
#include <stdarg.h>
#include <string.h>
#include "windef.h"
#include "winbase.h"
#include "wine/unicode.h"
#include "wingdi.h"
#include "winuser.h"
#include "winnls.h"
#include "commctrl.h"
#include "comctl32.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(tooltips);
static HICON hTooltipIcons[TTI_ERROR+1];
typedef struct
{
UINT uFlags;
HWND hwnd;
BOOL bNotifyUnicode;
UINT_PTR uId;
RECT rect;
HINSTANCE hinst;
LPWSTR lpszText;
LPARAM lParam;
} TTTOOL_INFO;
typedef struct
{
WCHAR szTipText[INFOTIPSIZE];
BOOL bActive;
BOOL bTrackActive;
UINT uNumTools;
COLORREF clrBk;
COLORREF clrText;
HFONT hFont;
HFONT hTitleFont;
INT xTrackPos;
INT yTrackPos;
INT nMaxTipWidth;
INT nTool; /* tool that mouse was on on last relayed mouse move */
INT nCurrentTool;
INT nTrackTool;
INT nReshowTime;
INT nAutoPopTime;
INT nInitialTime;
RECT rcMargin;
BOOL bToolBelow;
LPWSTR pszTitle;
HICON hTitleIcon;
TTTOOL_INFO *tools;
} TOOLTIPS_INFO;
#define ID_TIMERSHOW 1 /* show delay timer */
#define ID_TIMERPOP 2 /* auto pop timer */
#define ID_TIMERLEAVE 3 /* tool leave timer */
#define TOOLTIPS_GetInfoPtr(hWindow) ((TOOLTIPS_INFO *)GetWindowLongPtrW (hWindow, 0))
/* offsets from window edge to start of text */
#define NORMAL_TEXT_MARGIN 2
#define BALLOON_TEXT_MARGIN (NORMAL_TEXT_MARGIN+8)
/* value used for CreateRoundRectRgn that specifies how much
* each corner is curved */
#define BALLOON_ROUNDEDNESS 20
#define BALLOON_STEMHEIGHT 13
#define BALLOON_STEMWIDTH 10
#define BALLOON_STEMINDENT 20
#define BALLOON_ICON_TITLE_SPACING 8 /* horizontal spacing between icon and title */
#define BALLOON_TITLE_TEXT_SPACING 8 /* vertical spacing between icon/title and main text */
#define ICON_HEIGHT 16
#define ICON_WIDTH 16
static LRESULT CALLBACK
TOOLTIPS_SubclassProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uId, DWORD_PTR dwRef);
inline static UINT_PTR
TOOLTIPS_GetTitleIconIndex(HICON hIcon)
{
UINT i;
for (i = 0; i <= TTI_ERROR; i++)
if (hTooltipIcons[i] == hIcon)
return i;
return (UINT_PTR)hIcon;
}
static void
TOOLTIPS_InitSystemSettings (TOOLTIPS_INFO *infoPtr)
{
NONCLIENTMETRICSW nclm;
infoPtr->clrBk = GetSysColor (COLOR_INFOBK);
infoPtr->clrText = GetSysColor (COLOR_INFOTEXT);
DeleteObject (infoPtr->hFont);
nclm.cbSize = sizeof(nclm);
SystemParametersInfoW (SPI_GETNONCLIENTMETRICS, sizeof(nclm), &nclm, 0);
infoPtr->hFont = CreateFontIndirectW (&nclm.lfStatusFont);
DeleteObject (infoPtr->hTitleFont);
nclm.lfStatusFont.lfWeight = FW_BOLD;
infoPtr->hTitleFont = CreateFontIndirectW (&nclm.lfStatusFont);
}
static void
TOOLTIPS_Refresh (HWND hwnd, HDC hdc)
{
TOOLTIPS_INFO *infoPtr = TOOLTIPS_GetInfoPtr(hwnd);
RECT rc;
INT oldBkMode;
HFONT hOldFont;
HBRUSH hBrush;
UINT uFlags = DT_EXTERNALLEADING;
HRGN hRgn = NULL;
DWORD dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
if (infoPtr->nMaxTipWidth > -1)
uFlags |= DT_WORDBREAK;
if (GetWindowLongW (hwnd, GWL_STYLE) & TTS_NOPREFIX)
uFlags |= DT_NOPREFIX;
GetClientRect (hwnd, &rc);
hBrush = CreateSolidBrush(infoPtr->clrBk);
oldBkMode = SetBkMode (hdc, TRANSPARENT);
SetTextColor (hdc, infoPtr->clrText);
if (dwStyle & TTS_BALLOON)
{
/* create a region to store result into */
hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
/* fill the background */
FillRgn(hdc, hRgn, hBrush);
DeleteObject(hBrush);
hBrush = NULL;
}
else
{
/* fill the background */
FillRect(hdc, &rc, hBrush);
DeleteObject(hBrush);
hBrush = NULL;
}
if ((dwStyle & TTS_BALLOON) || infoPtr->pszTitle)
{
/* calculate text rectangle */
rc.left += (BALLOON_TEXT_MARGIN + infoPtr->rcMargin.left);
rc.top += (BALLOON_TEXT_MARGIN + infoPtr->rcMargin.top);
rc.right -= (BALLOON_TEXT_MARGIN + infoPtr->rcMargin.right);
rc.bottom -= (BALLOON_TEXT_MARGIN + infoPtr->rcMargin.bottom);
if(infoPtr->bToolBelow) rc.top += BALLOON_STEMHEIGHT;
if (infoPtr->pszTitle)
{
RECT rcTitle = {rc.left, rc.top, rc.right, rc.bottom};
int height;
BOOL icon_present;
/* draw icon */
icon_present = infoPtr->hTitleIcon &&
DrawIconEx(hdc, rc.left, rc.top, infoPtr->hTitleIcon,
ICON_WIDTH, ICON_HEIGHT, 0, NULL, DI_NORMAL);
if (icon_present)
rcTitle.left += ICON_WIDTH + BALLOON_ICON_TITLE_SPACING;
rcTitle.bottom = rc.top + ICON_HEIGHT;
/* draw title text */
hOldFont = SelectObject (hdc, infoPtr->hTitleFont);
height = DrawTextW(hdc, infoPtr->pszTitle, -1, &rcTitle, DT_BOTTOM | DT_SINGLELINE | DT_NOPREFIX);
SelectObject (hdc, hOldFont);
rc.top += height + BALLOON_TITLE_TEXT_SPACING;
}
}
else
{
/* calculate text rectangle */
rc.left += (NORMAL_TEXT_MARGIN + infoPtr->rcMargin.left);
rc.top += (NORMAL_TEXT_MARGIN + infoPtr->rcMargin.top);
rc.right -= (NORMAL_TEXT_MARGIN + infoPtr->rcMargin.right);
rc.bottom -= (NORMAL_TEXT_MARGIN + infoPtr->rcMargin.bottom);
}
/* draw text */
hOldFont = SelectObject (hdc, infoPtr->hFont);
DrawTextW (hdc, infoPtr->szTipText, -1, &rc, uFlags);
/* be polite and reset the things we changed in the dc */
SelectObject (hdc, hOldFont);
SetBkMode (hdc, oldBkMode);
if (dwStyle & TTS_BALLOON)
{
/* frame region because default window proc doesn't do it */
INT width = GetSystemMetrics(SM_CXDLGFRAME) - GetSystemMetrics(SM_CXEDGE);
INT height = GetSystemMetrics(SM_CYDLGFRAME) - GetSystemMetrics(SM_CYEDGE);
hBrush = GetSysColorBrush(COLOR_WINDOWFRAME);
FrameRgn(hdc, hRgn, hBrush, width, height);
}
if (hRgn)
DeleteObject(hRgn);
}
static void TOOLTIPS_GetDispInfoA(HWND hwnd, TOOLTIPS_INFO *infoPtr, TTTOOL_INFO *toolPtr)
{
NMTTDISPINFOA ttnmdi;
/* fill NMHDR struct */
ZeroMemory (&ttnmdi, sizeof(NMTTDISPINFOA));
ttnmdi.hdr.hwndFrom = hwnd;
ttnmdi.hdr.idFrom = toolPtr->uId;
ttnmdi.hdr.code = TTN_GETDISPINFOA;
ttnmdi.lpszText = (LPSTR)&ttnmdi.szText;
ttnmdi.uFlags = toolPtr->uFlags;
ttnmdi.lParam = toolPtr->lParam;
TRACE("hdr.idFrom = %x\n", ttnmdi.hdr.idFrom);
SendMessageW(toolPtr->hwnd, WM_NOTIFY,
(WPARAM)toolPtr->uId, (LPARAM)&ttnmdi);
if (IS_INTRESOURCE(ttnmdi.lpszText)) {
LoadStringW(ttnmdi.hinst, LOWORD(ttnmdi.lpszText),
infoPtr->szTipText, INFOTIPSIZE);
if (ttnmdi.uFlags & TTF_DI_SETITEM) {
toolPtr->hinst = ttnmdi.hinst;
toolPtr->lpszText = (LPWSTR)ttnmdi.lpszText;
}
}
else if (ttnmdi.lpszText == 0) {
/* no text available */
infoPtr->szTipText[0] = '\0';
}
else if (ttnmdi.lpszText != LPSTR_TEXTCALLBACKA) {
INT max_len = (ttnmdi.lpszText == &ttnmdi.szText[0]) ?
sizeof(ttnmdi.szText)/sizeof(ttnmdi.szText[0]) : -1;
MultiByteToWideChar(CP_ACP, 0, ttnmdi.lpszText, max_len,
infoPtr->szTipText, INFOTIPSIZE);
if (ttnmdi.uFlags & TTF_DI_SETITEM) {
INT len = MultiByteToWideChar(CP_ACP, 0, ttnmdi.lpszText,
max_len, NULL, 0);
toolPtr->hinst = 0;
toolPtr->lpszText = Alloc (len * sizeof(WCHAR));
MultiByteToWideChar(CP_ACP, 0, ttnmdi.lpszText, -1,
toolPtr->lpszText, len);
}
}
else {
ERR("recursive text callback!\n");
infoPtr->szTipText[0] = '\0';
}
}
static void TOOLTIPS_GetDispInfoW(HWND hwnd, TOOLTIPS_INFO *infoPtr, TTTOOL_INFO *toolPtr)
{
NMTTDISPINFOW ttnmdi;
/* fill NMHDR struct */
ZeroMemory (&ttnmdi, sizeof(NMTTDISPINFOW));
ttnmdi.hdr.hwndFrom = hwnd;
ttnmdi.hdr.idFrom = toolPtr->uId;
ttnmdi.hdr.code = TTN_GETDISPINFOW;
ttnmdi.lpszText = (LPWSTR)&ttnmdi.szText;
ttnmdi.uFlags = toolPtr->uFlags;
ttnmdi.lParam = toolPtr->lParam;
TRACE("hdr.idFrom = %x\n", ttnmdi.hdr.idFrom);
SendMessageW(toolPtr->hwnd, WM_NOTIFY,
(WPARAM)toolPtr->uId, (LPARAM)&ttnmdi);
if (IS_INTRESOURCE(ttnmdi.lpszText)) {
LoadStringW(ttnmdi.hinst, LOWORD(ttnmdi.lpszText),
infoPtr->szTipText, INFOTIPSIZE);
if (ttnmdi.uFlags & TTF_DI_SETITEM) {
toolPtr->hinst = ttnmdi.hinst;
toolPtr->lpszText = ttnmdi.lpszText;
}
}
else if (ttnmdi.lpszText == 0) {
/* no text available */
infoPtr->szTipText[0] = '\0';
}
else if (ttnmdi.lpszText != LPSTR_TEXTCALLBACKW) {
INT max_len = (ttnmdi.lpszText == &ttnmdi.szText[0]) ?
sizeof(ttnmdi.szText)/sizeof(ttnmdi.szText[0]) : INFOTIPSIZE-1;
lstrcpynW(infoPtr->szTipText, ttnmdi.lpszText, max_len);
if (ttnmdi.uFlags & TTF_DI_SETITEM) {
INT len = max(strlenW(ttnmdi.lpszText), max_len);
toolPtr->hinst = 0;
toolPtr->lpszText = Alloc ((len+1) * sizeof(WCHAR));
memcpy(toolPtr->lpszText, ttnmdi.lpszText, (len+1) * sizeof(WCHAR));
}
}
else {
ERR("recursive text callback!\n");
infoPtr->szTipText[0] = '\0';
}
}
static void
TOOLTIPS_GetTipText (HWND hwnd, TOOLTIPS_INFO *infoPtr, INT nTool)
{
TTTOOL_INFO *toolPtr = &infoPtr->tools[nTool];
if (IS_INTRESOURCE(toolPtr->lpszText) && toolPtr->hinst) {
/* load a resource */
TRACE("load res string %p %x\n",
toolPtr->hinst, LOWORD(toolPtr->lpszText));
LoadStringW (toolPtr->hinst, LOWORD(toolPtr->lpszText),
infoPtr->szTipText, INFOTIPSIZE);
}
else if (toolPtr->lpszText) {
if (toolPtr->lpszText == LPSTR_TEXTCALLBACKW) {
if (toolPtr->bNotifyUnicode)
TOOLTIPS_GetDispInfoW(hwnd, infoPtr, toolPtr);
else
TOOLTIPS_GetDispInfoA(hwnd, infoPtr, toolPtr);
}
else {
/* the item is a usual (unicode) text */
lstrcpynW (infoPtr->szTipText, toolPtr->lpszText, INFOTIPSIZE);
}
}
else {
/* no text available */
infoPtr->szTipText[0] = L'\0';
}
TRACE("%s\n", debugstr_w(infoPtr->szTipText));
}
static void
TOOLTIPS_CalcTipSize (HWND hwnd, TOOLTIPS_INFO *infoPtr, LPSIZE lpSize)
{
HDC hdc;
HFONT hOldFont;
DWORD style = GetWindowLongW(hwnd, GWL_STYLE);
UINT uFlags = DT_EXTERNALLEADING | DT_CALCRECT;
RECT rc = {0, 0, 0, 0};
SIZE title = {0, 0};
if (infoPtr->nMaxTipWidth > -1) {
rc.right = infoPtr->nMaxTipWidth;
uFlags |= DT_WORDBREAK;
}
if (style & TTS_NOPREFIX)
uFlags |= DT_NOPREFIX;
TRACE("%s\n", debugstr_w(infoPtr->szTipText));
hdc = GetDC (hwnd);
if (infoPtr->pszTitle)
{
RECT rcTitle = {0, 0, 0, 0};
TRACE("title %s\n", debugstr_w(infoPtr->pszTitle));
if (infoPtr->hTitleIcon)
{
title.cx = ICON_WIDTH;
title.cy = ICON_HEIGHT;
}
if (title.cx != 0) title.cx += BALLOON_ICON_TITLE_SPACING;
hOldFont = SelectObject (hdc, infoPtr->hTitleFont);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -