winamp/Src/Plugins/Library/ml_devices/listWidgetTooltip.cpp
2024-09-24 14:54:57 +02:00

592 lines
14 KiB
C++

#include "main.h"
#include "./listWidgetInternal.h"
#include <strsafe.h>
#define TOOLTIP_MARGIN_LEFT_DLU 3
#define TOOLTIP_MARGIN_TOP_DLU 1
#define TOOLTIP_MARGIN_RIGHT_DLU 3
#define TOOLTIP_MARGIN_BOTTOM_DLU 1
#define TOOLTIP_DELAY_INITIAL 1000
#define TOOLTIP_DELAY_RESHOW 400
static ATOM LISTWIDGETTOOLTIP_PROP = 0;
static LRESULT WINAPI
ListWidgetTooltip_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
typedef struct ListWidgetTooltip
{
HWND window;
HWND owner;
BOOL active;
POINT position;
wchar_t *buffer;
ListWidgetItem *item;
ListWidgetItemPart part;
RECT partRect;
BOOL blockLocationChange;
WNDPROC originalProc;
DWORD showTime;
} ListWidgetTooltip;
static HWND
ListWidget_TooltipCreateWindow(HWND owner, WNDPROC *originalProc, HANDLE windowProperty)
{
HWND hwnd;
TOOLINFO ti;
hwnd = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_NOPARENTNOTIFY | WS_EX_TRANSPARENT | WS_EX_LAYERED,
TOOLTIPS_CLASS, NULL, WS_CLIPSIBLINGS | WS_POPUP | TTS_NOANIMATE | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
owner, NULL, NULL, NULL);
if ( hwnd == NULL )
return NULL;
MLSkinWindow2(Plugin_GetLibraryWindow(), hwnd, SKINNEDWND_TYPE_TOOLTIP,
SWS_USESKINFONT | SWS_USESKINCOLORS | SWS_USESKINCURSORS);
if (NULL != originalProc)
{
if (0 == LISTWIDGETTOOLTIP_PROP)
LISTWIDGETTOOLTIP_PROP = GlobalAddAtom(TEXT("ListWidgetTooltipProp"));
if (0 != LISTWIDGETTOOLTIP_PROP)
{
*originalProc = (WNDPROC)(LONG_PTR)SetWindowLongPtr(hwnd, GWLP_WNDPROC,
(LONGX86)(LONG_PTR)ListWidgetTooltip_WindowProc);
if (NULL != *originalProc &&
FALSE == SetProp(hwnd, MAKEINTATOM(LISTWIDGETTOOLTIP_PROP), windowProperty))
{
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)*originalProc);
*originalProc = NULL;
}
}
else
*originalProc = NULL;
}
SendMessage(hwnd, CCM_SETVERSION, 6, 0L);
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER);
ZeroMemory(&ti, sizeof(ti));
ti.cbSize = sizeof(ti);
ti.hwnd = owner;
ti.lpszText = LPSTR_TEXTCALLBACK;
ti.uFlags = 0/*TTF_TRACK | TTF_ABSOLUTE*/;
SendMessage(hwnd, TTM_ADDTOOL, 0, (LPARAM)&ti);
return hwnd;
}
ListWidgetTooltip*
ListWidget_TooltipCreate(HWND hwnd)
{
ListWidgetTooltip *tooltip;
tooltip = (ListWidgetTooltip*)malloc(sizeof(ListWidgetTooltip));
if (NULL == tooltip)
return FALSE;
ZeroMemory(tooltip, sizeof(ListWidgetTooltip));
tooltip->window = ListWidget_TooltipCreateWindow(hwnd, &tooltip->originalProc, tooltip);
if (NULL == tooltip->window)
{
ListWidget_TooltipDestroy(tooltip);
return NULL;
}
tooltip->owner = hwnd;
ListWidget_TooltipFontChanged(tooltip);
return tooltip;
}
void
ListWidget_TooltipDestroy(ListWidgetTooltip *tooltip)
{
if (NULL != tooltip)
{
if (NULL != tooltip->window)
DestroyWindow(tooltip->window);
String_Free(tooltip->buffer);
free(tooltip);
}
}
void
ListWidget_TooltipFontChanged(ListWidgetTooltip *tooltip)
{
WidgetStyle *style;
RECT marginsRect;
HDC hdc;
long maxWidth;
if (NULL == tooltip)
return;
if (NULL == tooltip->window)
{
// attempt to recover...
tooltip->window = ListWidget_TooltipCreateWindow(tooltip->owner, &tooltip->originalProc, tooltip);
if (NULL == tooltip->window)
return;
tooltip->active = FALSE;
tooltip->part = ListWidgetItemPart_None;
SetRectEmpty(&tooltip->partRect);
}
style = WIDGET_GET_STYLE(tooltip->owner);
if (NULL == style)
return;
hdc = GetDCEx(tooltip->window, NULL, DCX_CACHE | DCX_NORESETATTRS);
if (NULL != hdc)
{
HFONT font = (HFONT)SendMessage(tooltip->window, WM_GETFONT, 0, 0L);
HFONT prevFont = SelectFont(hdc, font);
maxWidth = Graphics_GetAveStrWidth(hdc, 36);
SelectFont(hdc, prevFont);
ReleaseDC(tooltip->window, hdc);
}
else
maxWidth = 100;
SendMessage(tooltip->window, TTM_SETMAXTIPWIDTH, 0, (LPARAM)maxWidth);
WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(marginsRect.left, style, TOOLTIP_MARGIN_LEFT_DLU, 3);
WIDGETSTYLE_DLU_TO_VERT_PX_MIN(marginsRect.top, style, TOOLTIP_MARGIN_TOP_DLU, 2);
WIDGETSTYLE_DLU_TO_HORZ_PX_MIN(marginsRect.right, style, TOOLTIP_MARGIN_RIGHT_DLU, 3);
WIDGETSTYLE_DLU_TO_VERT_PX_MIN(marginsRect.bottom, style, TOOLTIP_MARGIN_BOTTOM_DLU, 2);
SendMessage(tooltip->window, TTM_SETMARGIN, 0, (LPARAM)&marginsRect);
}
void
ListWidget_TooltipRelayMouseMessage(ListWidgetTooltip *tooltip, unsigned int message, unsigned int vKeys, const POINT *cursor)
{
if (NULL != tooltip &&
NULL != tooltip->window &&
FALSE != tooltip->active)
{
MSG msg;
msg.hwnd = tooltip->owner;
msg.message = message;
msg.wParam = (WPARAM)vKeys;
msg.lParam = MAKELPARAM(cursor->x, cursor->y);
SendMessage(tooltip->window, TTM_RELAYEVENT, 0, (LPARAM)&msg);
}
}
void
ListWidget_TooltipHide(ListWidgetTooltip *tooltip)
{
if (NULL == tooltip ||
NULL == tooltip->window ||
FALSE == tooltip->active)
{
return;
}
tooltip->active = FALSE;
tooltip->part = ListWidgetItemPart_None;
SetRectEmpty(&tooltip->partRect);
SendMessage(tooltip->window, TTM_ACTIVATE, FALSE, 0);
}
BOOL
ListWidget_TooltipActivate(ListWidgetTooltip *tooltip, const RECT *rect)
{
TOOLINFO ti;
POINT origin;
if (NULL == tooltip ||
NULL == tooltip->window)
{
return FALSE;
}
ZeroMemory(&ti, sizeof(ti));
ti.cbSize = sizeof(ti);
ti.hwnd = tooltip->owner;
ti.uId = 0;
if (FALSE == SendMessage(tooltip->window, TTM_GETTOOLINFO, 0, (LPARAM)&ti))
return FALSE;
if (FALSE != tooltip->active)
{
tooltip->active = FALSE;
SendMessage(tooltip->window, TTM_ACTIVATE, FALSE, 0);
}
else
SendMessage(tooltip->window, TTM_SETDELAYTIME, TTDT_INITIAL, MAKELPARAM(TOOLTIP_DELAY_INITIAL, 0));
CopyRect(&ti.rect, rect);
if (FALSE != ListWidget_GetViewOrigin(tooltip->owner, &origin))
OffsetRect(&ti.rect, origin.x, origin.y);
ti.lParam = NULL;
ti.lpszText = LPSTR_TEXTCALLBACK;
SendMessage(tooltip->window, TTM_SETTOOLINFO, 0, (LPARAM)&ti);
if (FALSE == tooltip->active)
{
KillTimer(tooltip->window, 4);
SendMessage(tooltip->window, TTM_ACTIVATE, TRUE, 0);
tooltip->active = TRUE;
}
return TRUE;
}
BOOL
ListWidget_TooltipUpdate(ListWidgetTooltip *tooltip, ListWidgetItem *item,
ListWidgetItemPart part, const RECT *partRect)
{
BOOL tooltipValid;
if (NULL == tooltip)
return FALSE;
if(FALSE == tooltip->active && NULL == item)
return FALSE;
if (0 != (ListWidgetItemPart_Activity & part))
{
part &= ~ListWidgetItemPart_Activity;
part |= ListWidgetItemPart_Frame;
}
tooltipValid = (NULL != item && ListWidgetItemPart_None != part);
if (tooltip->item == item &&
tooltip->part == part &&
(FALSE != (FALSE != tooltipValid) ?
EqualRect(&tooltip->partRect, partRect) :
IsRectEmpty(&tooltip->partRect)))
{
return FALSE;
}
tooltip->item = item;
tooltip->part = part;
if (FALSE != tooltipValid)
CopyRect(&tooltip->partRect, partRect);
else
SetRectEmpty(&tooltip->partRect);
if (FALSE == tooltipValid)
{
ListWidget_TooltipHide(tooltip);
return FALSE;
}
return ListWidget_TooltipActivate(tooltip, &tooltip->partRect);
}
static BOOL
ListWidget_TooltipFormatTip(ListWidget *self, HWND hwnd,
const RECT *rect, wchar_t *buffer, size_t bufferMax)
{
WidgetStyle *style;
ListWidgetItem *item;
ListWidgetItemPart part;
ListWidgetItemMetric metrics;
RECT partRect;
POINT pt, origin;
if (NULL == self || NULL == rect)
return FALSE;
style = WIDGET_GET_STYLE(hwnd);
if (NULL == style)
return FALSE;
if (FALSE == ListWidget_GetItemMetrics(style, &metrics))
return FALSE;
if (FALSE == ListWidget_GetViewOrigin(hwnd, &origin))
{
origin.x = 0;
origin.y = 0;
}
pt.x = (rect->left + (rect->right - rect->left)/2) - origin.x;
pt.y = (rect->top + (rect->bottom - rect->top)/2) - origin.y;
item = ListWidget_GetItemFromPoint(self, pt);
if (NULL == item)
return FALSE;
part = ListWidgetItemPart_Frame |
ListWidgetItemPart_Command |
ListWidgetItemPart_Spacebar |
ListWidgetItemPart_Title;
part = ListWidget_GetItemPartFromPoint(self, item, &metrics, pt, part, &partRect);
switch(part)
{
case ListWidgetItemPart_Command:
CopyRect(&partRect, rect);
OffsetRect(&partRect, -origin.x - item->rect.left, -origin.y - item->rect.top);
return ListWidget_FormatItemCommandTip(self, item, &partRect, buffer, bufferMax);
case ListWidgetItemPart_Activity:
case ListWidgetItemPart_Frame:
return ListWidget_FormatItemTip(self, item, buffer, bufferMax);
case ListWidgetItemPart_Spacebar:
return ListWidget_FormatItemSpaceTip(self, item, buffer, bufferMax);
case ListWidgetItemPart_Title:
return ListWidget_FormatItemTitleTip(self, item, buffer, bufferMax);
}
return FALSE;
}
static void
ListWidget_TooltipGetDispInfo(ListWidget *self, ListWidgetTooltip *tooltip, NMTTDISPINFO *dispInfo)
{
TOOLINFO ti;
if (NULL == dispInfo)
return;
ZeroMemory(&ti, sizeof(ti));
ti.cbSize = sizeof(ti);
ti.hwnd = tooltip->owner;
ti.uId = dispInfo->hdr.idFrom;
String_Free(tooltip->buffer);
tooltip->buffer = NULL;
if (FALSE != SendMessage(dispInfo->hdr.hwndFrom, TTM_GETTOOLINFO, 0, (LPARAM)&ti))
{
wchar_t buffer[4096] = {0};
if (FALSE != ListWidget_TooltipFormatTip(self, tooltip->owner, &ti.rect, buffer, ARRAYSIZE(buffer)))
tooltip->buffer = String_Duplicate(buffer);
}
dispInfo->lpszText = tooltip->buffer;
dispInfo->szText[0] = L'\0';
dispInfo->hinst = NULL;
dispInfo->uFlags = TTF_DI_SETITEM;
}
BOOL
ListWidget_TooltipProcessNotification(ListWidget *self, ListWidgetTooltip *tooltip, NMHDR *pnmh, LRESULT *result)
{
if (NULL == tooltip ||
NULL == pnmh ||
pnmh->hwndFrom != tooltip->window)
{
return FALSE;
}
switch(pnmh->code)
{
case TTN_GETDISPINFO:
ListWidget_TooltipGetDispInfo(self, tooltip, (NMTTDISPINFO*)pnmh);
break;
case TTN_SHOW:
if (0 == (WS_VISIBLE & GetWindowStyle(tooltip->window)))
tooltip->showTime = GetTickCount();
if (FALSE != tooltip->active)
{
SendMessage(tooltip->window, TTM_SETDELAYTIME, TTDT_INITIAL, MAKELPARAM(TOOLTIP_DELAY_RESHOW, 0));
}
break;
}
return TRUE;
}
ListWidgetItem *
ListWidget_TooltipGetCurrent(ListWidgetTooltip *tooltip, ListWidgetItemPart *part, RECT *partRect)
{
if (NULL == tooltip ||
FALSE == tooltip->active ||
NULL == tooltip->item)
{
return NULL;
}
if (NULL != part)
*part = tooltip->part;
if (NULL != partRect)
CopyRect(partRect, &tooltip->partRect);
return tooltip->item;
}
BOOL
ListWidget_TooltipGetChanged(ListWidgetTooltip *tooltip, ListWidgetItem *item,
ListWidgetItemPart part, const RECT *partRect)
{
if (NULL == tooltip)
return FALSE;
if (tooltip->item != item)
return TRUE;
if (NULL == item)
return FALSE;
if (tooltip->part != part)
return TRUE;
if (FALSE == EqualRect(&tooltip->partRect, partRect))
return TRUE;
return FALSE;
}
BOOL
ListWidget_TooltipUpdateText(ListWidget *self, ListWidgetTooltip *tooltip, ListWidgetItem *item, TooltipUpdateReason reason)
{
TOOLINFO ti;
ListWidgetItemPart mask;
unsigned int windowStyle;
unsigned long showTimeout, currentTime;
if (NULL == self ||
NULL == tooltip ||
NULL == tooltip->active ||
NULL == tooltip->window)
{
return FALSE;
}
if (NULL != item && tooltip->item != item)
return FALSE;
windowStyle = GetWindowStyle(tooltip->window);
if (0 == (WS_VISIBLE & windowStyle))
return FALSE;
showTimeout = (unsigned long)SendMessage(tooltip->window, TTM_GETDELAYTIME, (WPARAM)TTDT_AUTOPOP, 0L);
currentTime = GetTickCount();
if (currentTime < tooltip->showTime)
return FALSE;
currentTime -= tooltip->showTime;
if (showTimeout > (currentTime + 10))
showTimeout -= currentTime;
else
return FALSE;
KillTimer(tooltip->window, 4);
switch(reason)
{
case Tooltip_DeviceTitleChanged:
case Tooltip_DeviceModelChanged:
case Tooltip_DeviceStatusChanged:
mask = ListWidgetItemPart_Frame |
ListWidgetItemPart_Activity |
ListWidgetItemPart_Title;
if (0 == (mask & tooltip->part))
return FALSE;
break;
case Tooltip_DeviceSpaceChanged:
mask = ListWidgetItemPart_Frame |
ListWidgetItemPart_Activity |
ListWidgetItemPart_Spacebar;
if (0 == (mask & tooltip->part))
return FALSE;
break;
/*case Tooltip_DeviceActivityChanged:
mask = ListWidgetItemPart_Frame |
ListWidgetItemPart_Activity;
if (0 == (mask & tooltip->part))
return FALSE;
break;*/
}
ZeroMemory(&ti, sizeof(ti));
ti.cbSize = sizeof(ti);
ti.hwnd = tooltip->owner;
ti.uId = 0;
ti.lpszText = LPSTR_TEXTCALLBACK;
tooltip->blockLocationChange = TRUE;
SendMessage(tooltip->window, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
tooltip->blockLocationChange = FALSE;
SetTimer(tooltip->window, 4, showTimeout, 0);
return TRUE;
}
static LRESULT WINAPI
ListWidgetTooltip_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
ListWidgetTooltip *tooltip;
tooltip = (ListWidgetTooltip*)GetProp(hwnd, MAKEINTATOM(LISTWIDGETTOOLTIP_PROP));
if (NULL == tooltip ||
NULL == tooltip->originalProc)
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
switch(uMsg)
{
case WM_DESTROY:
RemoveProp(hwnd, MAKEINTATOM(LISTWIDGETTOOLTIP_PROP));
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONGX86)(LONG_PTR)tooltip->originalProc);
CallWindowProc(tooltip->originalProc, hwnd, uMsg, wParam, lParam);
tooltip->originalProc = NULL;
tooltip->window = NULL;
break;
case WM_WINDOWPOSCHANGING:
if (FALSE != tooltip->blockLocationChange)
{
WINDOWPOS *pwp;
pwp = (WINDOWPOS*)lParam;
if (NULL != pwp)
{
pwp->flags |= SWP_NOMOVE;
}
}
break;
}
return CallWindowProc(tooltip->originalProc, hwnd, uMsg, wParam, lParam);
}