Kawe Mazidjatari 04bee896be Fix string/wstring type conflict
cppkore uses string/wstring as StringBase while we use std::string/std::wstring as string/wstring. Changed all types in cppkore to String/WString instead.
2022-05-21 21:51:35 +02:00

778 lines
17 KiB
C++

#include "stdafx.h"
#include "ToolTip.h"
namespace Forms
{
ToolTip::ToolTip()
: Control(), _Flags(0x33), _ToolTipIcon(ToolTipIcon::None), _DelayTimes{}
{
#if 0
// This is used to debug the tooltip state flags, we use the magic number 0x33 right now...
_Flags.set(0, true); // Active
_Flags.set(1, true); // Auto
_Flags.set(2, false); // ShowAlways
_Flags.set(3, false); // StripAmpersands
_Flags.set(4, true); // UseAnimation
_Flags.set(5, true); // UseFading
_Flags.set(6, false); // OwnerDraw
_Flags.set(7, false); // IsBalloon
_Flags.set(8, false); // TrackPosition
_Flags.set(9, false); // Cancelled
#endif
// Set the automatic time default
_DelayTimes[TTDT_AUTOMATIC] = DEFAULT_DELAY;
// Adjust the base times from auto
AdjustBaseFromAuto();
// Default tooltip background and foreground
this->SetBackColor(Drawing::GetSystemColor(Drawing::SystemColors::Info));
this->SetForeColor(Drawing::GetSystemColor(Drawing::SystemColors::InfoText));
// We are a tooltip control
this->_RTTI = ControlTypes::ToolTip;
}
void ToolTip::CreateControl(Control* Parent)
{
INITCOMMONCONTROLSEX iCC{};
iCC.dwSize = sizeof(iCC);
iCC.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&iCC);
Control::CreateControl(Parent);
}
bool ToolTip::Active()
{
return this->_Flags[0];
}
void ToolTip::SetActive(bool Value)
{
if (Active() != Value)
{
this->_Flags[0] = Value;
if (GetState(ControlStates::StateCreated))
SendMessageA(this->_Handle, TTM_ACTIVATE, (Value) ? 1 : 0, NULL);
}
}
uint32_t ToolTip::AutomaticDelay()
{
return _DelayTimes[TTDT_AUTOMATIC];
}
void ToolTip::SetAutomaticDelay(uint32_t Value)
{
SetDelayTime(TTDT_AUTOMATIC, Value);
}
uint32_t ToolTip::AutoPopDelay()
{
return _DelayTimes[TTDT_AUTOPOP];
}
void ToolTip::SetAutoPopDelay(uint32_t Value)
{
SetDelayTime(TTDT_AUTOPOP, Value);
}
bool ToolTip::IsBalloon()
{
return this->_Flags[7];
}
void ToolTip::SetIsBalloon(bool Value)
{
if (this->_Flags[7] != Value)
{
this->_Flags[7] = Value;
UpdateStyles();
}
}
uint32_t ToolTip::InitialDelay()
{
return _DelayTimes[TTDT_INITIAL];
}
void ToolTip::SetInitialDelay(uint32_t Value)
{
SetDelayTime(TTDT_INITIAL, Value);
}
bool ToolTip::OwnerDraw()
{
return this->_Flags[6];
}
void ToolTip::SetOwnerDraw(bool Value)
{
this->_Flags[6] = Value;
}
uint32_t ToolTip::ReshowDelay()
{
return _DelayTimes[TTDT_RESHOW];
}
void ToolTip::SetReshowDelay(uint32_t Value)
{
SetDelayTime(TTDT_RESHOW, Value);
}
bool ToolTip::ShowAlways()
{
return this->_Flags[2];
}
void ToolTip::SetShowAlways(bool Value)
{
if (this->_Flags[2] != Value)
{
this->_Flags[2] = Value;
UpdateStyles();
}
}
bool ToolTip::StripAmpersands()
{
return this->_Flags[3];
}
void ToolTip::SetStripAmpersands(bool Value)
{
if (this->_Flags[3] != Value)
{
this->_Flags[3] = Value;
UpdateStyles();
}
}
ToolTipIcon ToolTip::GetToolTipIcon()
{
return this->_ToolTipIcon;
}
void ToolTip::SetToolTipIcon(ToolTipIcon Value)
{
if (_ToolTipIcon != Value)
{
_ToolTipIcon = Value;
if (GetState(ControlStates::StateCreated))
{
String Title = !String::IsNullOrEmpty(_ToolTipTitle) ? _ToolTipTitle : String(" ");
SendMessageA(this->_Handle, TTM_SETTITLEA, (WPARAM)Value, (LPARAM)(char*)Title);
SendMessageA(this->_Handle, TTM_UPDATE, NULL, NULL);
}
}
}
String ToolTip::ToolTipTitle()
{
return this->_ToolTipTitle;
}
void ToolTip::SetToolTipTitle(const String& Value)
{
if (_ToolTipTitle != Value)
{
_ToolTipTitle = Value;
if (GetState(ControlStates::StateCreated))
{
SendMessageA(this->_Handle, TTM_SETTITLEA, (WPARAM)_ToolTipIcon, (LPARAM)(char*)_ToolTipTitle);
SendMessageA(this->_Handle, TTM_UPDATE, NULL, NULL);
}
}
}
bool ToolTip::UseAnimation()
{
return this->_Flags[4];
}
void ToolTip::SetUseAnimation(bool Value)
{
if (this->_Flags[4] != Value)
{
this->_Flags[4] = Value;
UpdateStyles();
}
}
bool ToolTip::UseFading()
{
return this->_Flags[5];
}
void ToolTip::SetUseFading(bool Value)
{
if (this->_Flags[5] != Value)
{
this->_Flags[5] = Value;
UpdateStyles();
}
}
void ToolTip::SetToolTip(Control* Ctrl, const String& Caption)
{
if (Ctrl->GetState(ControlStates::StateCreated))
{
// Check if we already setup the control...
if (this->_ControlCache.ContainsKey((uintptr_t)Ctrl))
{
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
Ti.hwnd = Ctrl->GetHandle();
Ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT | TTF_SUBCLASS;
Ti.uId = (UINT_PTR)Ctrl->GetHandle();
Ti.lpszText = (char*)Caption;
SendMessageA(this->_Handle, TTM_UPDATETIPTEXTA, NULL, (LPARAM)&Ti);
auto Ptr = (uintptr_t)Ctrl;
this->_ControlCache[Ptr] = Caption;
return;
}
// Add the item to the control cache
this->_ControlCache.Add((uintptr_t)Ctrl, Caption);
// Register the tooltip handler
this->RegisterTooltip(Ctrl);
// Hook if control gets deleted
Ctrl->HandleDestroyed += &ToolTip::ControlHandleDestroyed;
}
else
{
// Add the item and wait for creation
this->_ControlCache.Add((uintptr_t)Ctrl, Caption);
Ctrl->HandleCreated += &ToolTip::ControlHandleCreated;
}
}
void ToolTip::OnHandleCreated()
{
Control::OnHandleCreated();
if (OwnerDraw())
{
auto Style = (int)GetWindowLong(this->_Handle, GWL_STYLE);
Style &= WS_BORDER;
SetWindowLong(this->_Handle, GWL_STYLE, Style);
}
SendMessageA(this->_Handle, TTM_SETMAXTIPWIDTH, NULL, GetSystemMetrics(SM_CXMAXTRACK));
if (this->_Flags[1])
{
SetDelayTime(TTDT_AUTOMATIC, _DelayTimes[TTDT_AUTOMATIC]);
_DelayTimes[TTDT_AUTOPOP] = GetDelayTime(TTDT_AUTOPOP);
_DelayTimes[TTDT_INITIAL] = GetDelayTime(TTDT_INITIAL);
_DelayTimes[TTDT_RESHOW] = GetDelayTime(TTDT_RESHOW);
}
else
{
for (int32_t i = 0; i < 4; i++)
{
if (_DelayTimes[i] > 1)
SetDelayTime(i, _DelayTimes[i]);
}
}
SendMessageA(this->_Handle, TTM_ACTIVATE, (Active() == true) ? 1 : 0, NULL);
SendMessageA(this->_Handle, TTM_SETTIPBKCOLOR, Drawing::ColorToWin32(this->BackColor()), NULL);
SendMessageA(this->_Handle, TTM_SETTIPTEXTCOLOR, Drawing::ColorToWin32(this->ForeColor()), NULL);
if ((int)_ToolTipIcon > 0 || !String::IsNullOrEmpty(_ToolTipTitle))
{
String Title = !String::IsNullOrEmpty(_ToolTipTitle) ? _ToolTipTitle : String(" ");
SendMessageA(this->_Handle, TTM_SETTITLEA, (WPARAM)_ToolTipIcon, (LPARAM)(char*)Title);
}
}
void ToolTip::OnBackColorChanged()
{
if (GetState(ControlStates::StateCreated))
SendMessageA(this->_Handle, TTM_SETTIPBKCOLOR, Drawing::ColorToWin32(this->BackColor()), NULL);
Control::OnBackColorChanged();
}
void ToolTip::OnForeColorChanged()
{
if (GetState(ControlStates::StateCreated))
SendMessageA(this->_Handle, TTM_SETTIPTEXTCOLOR, Drawing::ColorToWin32(this->ForeColor()), NULL);
Control::OnForeColorChanged();
}
void ToolTip::OnPopup(const std::unique_ptr<PopupEventArgs>& EventArgs)
{
Popup.RaiseEvent(EventArgs, this);
}
void ToolTip::OnDraw(const std::unique_ptr<DrawToolTipEventArgs>& EventArgs)
{
Draw.RaiseEvent(EventArgs, this);
}
void ToolTip::RemoveAll()
{
for (auto& Kvp : this->_ControlCache)
{
auto Ctrl = (Control*)Kvp.Key();
if (GetState(ControlStates::StateCreated) && Ctrl->GetState(ControlStates::StateCreated))
{
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
Ti.hwnd = Ctrl->GetHandle();
Ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT | TTF_SUBCLASS;
Ti.uId = (UINT_PTR)Ctrl->GetHandle();
SendMessageA(this->_Handle, TTM_DELTOOLA, NULL, (LPARAM)& Ti);
}
}
this->_ControlCache.Clear();
}
void ToolTip::Remove(Control* Ctrl)
{
if (GetState(ControlStates::StateCreated) && Ctrl->GetState(ControlStates::StateCreated))
{
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
Ti.hwnd = Ctrl->GetHandle();
Ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT | TTF_SUBCLASS;
Ti.uId = (UINT_PTR)Ctrl->GetHandle();
SendMessageA(this->_Handle, TTM_DELTOOLA, NULL, (LPARAM)&Ti);
}
this->_ControlCache.Remove((uintptr_t)Ctrl);
}
void ToolTip::WndProc(Message& Msg)
{
switch (Msg.Msg)
{
case WM_REFLECT + WM_NOTIFY:
{
NMHDR* nMHDR = (NMHDR*)Msg.LParam;
if (nMHDR->code == TTN_SHOW && !this->_Flags[8])
{
WmShow();
}
else if (nMHDR->code == TTN_POP)
{
WmPop();
DefWndProc(Msg);
}
}
break;
case WM_WINDOWPOSCHANGING:
DefWndProc(Msg);
break;
case WM_WINDOWPOSCHANGED:
if (!WmWindowPosChanged())
DefWndProc(Msg);
break;
case WM_MOVE:
WmMove();
break;
case TTM_WINDOWFROMPOINT:
WmWindowFromPoint(Msg);
break;
case WM_PRINTCLIENT:
case WM_PAINT:
if (OwnerDraw() && !IsBalloon() && !this->_Flags[8])
{
PAINTSTRUCT Ps{};
auto Dc = BeginPaint(this->_Handle, &Ps);
Drawing::Rectangle Bounds(Ps.rcPaint.left, Ps.rcPaint.top, Ps.rcPaint.right - Ps.rcPaint.left, Ps.rcPaint.bottom - Ps.rcPaint.top);
if (Bounds.IsEmptyArea())
{
EndPaint(this->_Handle, &Ps);
return;
}
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
auto Result = (int32_t)SendMessageA(this->_Handle, TTM_GETCURRENTTOOLA, NULL, (LPARAM)&Ti);
if (Result != 0)
{
auto Font = this->GetFont();
auto Ctrl = Control::FromHandle(Ti.hwnd);
auto CtrlPtr = (uintptr_t)Ctrl;
auto& Value = this->_ControlCache[CtrlPtr];
OnDraw(std::make_unique<DrawToolTipEventArgs>(Dc, Ctrl, Ctrl, Bounds, Value, BackColor(), ForeColor(), Font));
}
EndPaint(this->_Handle, &Ps);
}
else
{
DefWndProc(Msg);
}
break;
default:
DefWndProc(Msg);
break;
}
}
CreateParams ToolTip::GetCreateParams()
{
CreateParams Cp{};
Cp.ClassName = "tooltips_class32";
if (ShowAlways())
Cp.Style = TTS_ALWAYSTIP;
if (IsBalloon())
Cp.Style |= TTS_BALLOON;
if (!StripAmpersands())
Cp.Style |= TTS_NOPREFIX;
if (!UseAnimation())
Cp.Style |= TTS_NOANIMATE;
if (!UseFading())
Cp.Style |= TTS_NOFADE;
return Cp;
}
void ToolTip::RegisterTooltip(Control* Ctrl)
{
auto Ptr = (uintptr_t)Ctrl;
auto Value = this->_ControlCache[Ptr];
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
Ti.hwnd = Ctrl->GetHandle();
Ti.uFlags = TTF_IDISHWND | TTF_TRANSPARENT | TTF_SUBCLASS;
Ti.uId = (UINT_PTR)Ctrl->GetHandle();
Ti.lpszText = (char*)Value;
SendMessageA(this->_Handle, TTM_ADDTOOLA, NULL, (LPARAM)&Ti);
Ctrl->HandleDestroyed += &ToolTip::ControlHandleDestroyed;
}
void ToolTip::ControlHandleCreated(Control* Sender)
{
Sender->HandleCreated -= &ToolTip::ControlHandleCreated;
// The tooltip should be a child of the form of the sender
auto FormOwner = Sender->FindForm();
if (FormOwner == nullptr)
return;
auto& ControlList = *FormOwner->Controls().get();
for (auto& Control : ControlList)
{
if (Control.GetType() == ControlTypes::ToolTip)
{
auto Tt = (ToolTip*)&Control;
if (Tt->_ControlCache.ContainsKey((uintptr_t)Sender))
{
Tt->RegisterTooltip(Sender);
break;
}
}
}
}
void ToolTip::ControlHandleDestroyed(Control* Sender)
{
Sender->HandleDestroyed -= &ToolTip::ControlHandleDestroyed;
// The tooltip should be a child of the form of the sender
auto FormOwner = Sender->FindForm();
if (FormOwner == nullptr || FormOwner->GetState(ControlStates::StateDisposed))
return;
auto& ControlList = *FormOwner->Controls().get();
for (auto& Control : ControlList)
{
if (Control.GetType() == ControlTypes::ToolTip)
{
auto Tt = (ToolTip*)&Control;
if (Tt->GetState(ControlStates::StateCreated) && Tt->_ControlCache.ContainsKey((uintptr_t)Sender))
{
Tt->Remove(Sender);
break;
}
}
}
}
void ToolTip::WmShow()
{
RECT R{};
GetWindowRect(this->_Handle, &R);
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
auto Result = (int32_t)SendMessageA(this->_Handle, TTM_GETCURRENTTOOLA, NULL, (LPARAM)&Ti);
if (Result != NULL)
{
Drawing::Rectangle Rc(R.left, R.top, R.right - R.left, R.bottom - R.top);
Drawing::Size CurrentToolTipSize;
Rc.GetSize(&CurrentToolTipSize);
auto Ctrl = Control::FromHandle(Ti.hwnd);
if (Ctrl == nullptr)
return;
auto EventArgs = std::make_unique<PopupEventArgs>(Ctrl, Ctrl, IsBalloon(), CurrentToolTipSize);
OnPopup(EventArgs);
GetWindowRect(this->_Handle, &R);
if (EventArgs->ToolTipSize.Equals(CurrentToolTipSize))
Rc.GetSize(&CurrentToolTipSize);
else
CurrentToolTipSize = EventArgs->ToolTipSize;
if (IsBalloon())
{
SendMessageA(this->_Handle, TTM_ADJUSTRECT, (WPARAM)1, (LPARAM)&R);
if ((R.bottom - R.top) > CurrentToolTipSize.Height)
CurrentToolTipSize.Height = (R.bottom - R.top);
}
if (!CurrentToolTipSize.Equals(Drawing::Size(R.right - R.left, R.bottom - R.top)))
{
auto CursorPos = Control::GetMousePosition();
HMONITOR MonHandle = MonitorFromPoint({ CursorPos.X, CursorPos.Y }, MONITOR_DEFAULTTONEAREST);
if (MonHandle != nullptr)
{
MONITORINFO Info;
Info.cbSize = sizeof(Info);
GetMonitorInfo(MonHandle, &Info);
int32_t MaxWidth = 0;
if (IsBalloon())
MaxWidth = min(CurrentToolTipSize.Width - 2 * (int32_t)XBALLOONOFFSET, (Info.rcWork.right - Info.rcWork.left));
else
MaxWidth = min(CurrentToolTipSize.Width, (Info.rcWork.right - Info.rcWork.left));
SendMessageA(this->_Handle, TTM_SETMAXTIPWIDTH, NULL, (LPARAM)MaxWidth);
}
}
if (EventArgs->Cancel)
{
this->_Flags[9] = true;
SetWindowPos(this->_Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOOWNERZORDER);
}
else
{
this->_Flags[9] = false;
SetWindowPos(this->_Handle, HWND_TOPMOST, R.left, R.top, CurrentToolTipSize.Width, CurrentToolTipSize.Height, SWP_NOACTIVATE | SWP_NOOWNERZORDER);
}
}
}
void ToolTip::WmPop()
{
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
auto Result = (int32_t)SendMessageA(this->_Handle, TTM_GETCURRENTTOOLA, NULL, (LPARAM)&Ti);
if (Result != 0)
{
auto Ctrl = Control::FromHandle(Ti.hwnd);
if (Ctrl == nullptr)
return;
// This should only happen on Type::Auto / Type::SemiAbsolute
auto CursorPos = Control::GetMousePosition();
HMONITOR MonHandle = MonitorFromPoint({ CursorPos.X, CursorPos.Y }, MONITOR_DEFAULTTONEAREST);
if (MonHandle != nullptr)
{
MONITORINFO Info;
Info.cbSize = sizeof(Info);
GetMonitorInfo(MonHandle, &Info);
SendMessageA(this->_Handle, TTM_SETMAXTIPWIDTH, NULL, (Info.rcWork.right - Info.rcWork.left));
}
// TODO: If we support single-shot tooltips, remove them here...
}
}
void ToolTip::WmMove()
{
RECT R{};
GetWindowRect(this->_Handle, &R);
TOOLINFOA Ti{};
Ti.cbSize = sizeof(Ti);
auto Result = (int32_t)SendMessageA(this->_Handle, TTM_GETCURRENTTOOLA, NULL, (LPARAM)&Ti);
if (Result != 0)
{
auto Win = Control::FromHandle(Ti.hwnd);
if (Win == nullptr)
return;
// TODO: If Info != Point::Empty:
// TODO: Reposition the control...
}
}
bool ToolTip::WmWindowPosChanged()
{
if (this->_Flags[9])
{
SendMessageA(this->_Handle, SW_HIDE, NULL, NULL);
return true;
}
return false;
}
void ToolTip::WmWindowFromPoint(Message& Msg)
{
POINT* Pt = (POINT*)Msg.LParam;
Drawing::Point ScreenCoords(Pt->x, Pt->y);
bool Result = false;
Msg.Result = GetWindowFromPoint(ScreenCoords, Result);
}
void ToolTip::AdjustBaseFromAuto()
{
_DelayTimes[TTDT_RESHOW] = _DelayTimes[TTDT_AUTOMATIC] / RESHOW_RATIO;
_DelayTimes[TTDT_AUTOPOP] = _DelayTimes[TTDT_AUTOMATIC] / AUTOPOP_RATIO;
_DelayTimes[TTDT_INITIAL] = _DelayTimes[TTDT_AUTOMATIC];
}
void ToolTip::SetDelayTime(uint32_t Type, uint32_t Time)
{
if (Type == TTDT_AUTOMATIC)
this->_Flags[1] = true;
else
this->_Flags[1] = false;
_DelayTimes[Type] = Time;
if (GetState(ControlStates::StateCreated) && Time >= 0)
{
SendMessageA(this->_Handle, TTM_SETDELAYTIME, (WPARAM)Type, (LPARAM)Time);
if (this->_Flags[1])
{
_DelayTimes[TTDT_AUTOPOP] = GetDelayTime(TTDT_AUTOPOP);
_DelayTimes[TTDT_INITIAL] = GetDelayTime(TTDT_INITIAL);
_DelayTimes[TTDT_RESHOW] = GetDelayTime(TTDT_RESHOW);
}
}
else if (this->_Flags[1])
{
AdjustBaseFromAuto();
}
}
uint32_t ToolTip::GetDelayTime(uint32_t Type)
{
if (GetState(ControlStates::StateCreated))
return (uint32_t)SendMessageA(this->_Handle, TTM_GETDELAYTIME, (WPARAM)Type, NULL);
else
return _DelayTimes[Type];
}
uintptr_t ToolTip::GetWindowFromPoint(Drawing::Point ScreenCoords, bool& Success)
{
HWND BaseHwnd = NULL;
Control* BaseVar = this->FindForm();
if (BaseVar != nullptr)
BaseHwnd = BaseVar->GetHandle();
HWND hWnd = NULL;
bool FinalMatch = false;
while (!FinalMatch)
{
auto Pt = ScreenCoords;
if (BaseVar != nullptr)
Pt = BaseVar->PointToClient(ScreenCoords);
auto Found = ChildWindowFromPointEx(BaseHwnd, { Pt.X, Pt.Y }, CWP_SKIPINVISIBLE);
if (Found == BaseHwnd)
{
hWnd = Found;
FinalMatch = true;
}
else if (Found == NULL)
{
FinalMatch = true;
}
else
{
BaseVar = Control::FromHandle(Found);
if (BaseVar == nullptr)
{
BaseVar = Control::FromChildHandle(Found);
if (BaseVar != nullptr)
hWnd = BaseVar->GetHandle();
FinalMatch = true;
}
else
BaseHwnd = BaseVar->GetHandle();
}
}
if (hWnd != NULL)
{
auto Ctrl = Control::FromHandle(hWnd);
if (Ctrl != nullptr)
{
Control* Current = Ctrl;
while (Current != nullptr && Current->Visible())
Current = Current->Parent();
if (Current != nullptr)
hWnd = NULL;
Success = true;
}
}
return (uintptr_t)hWnd;
}
}