From bacf5e0272c566c30e591152084daf2f684aebe8 Mon Sep 17 00:00:00 2001 From: Goraf Date: Fri, 5 Feb 2016 22:40:46 +0000 Subject: ContextHelp: initial commit (adopted) * working * 32/64-bit compilable git-svn-id: http://svn.miranda-ng.org/main/trunk@16225 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/ContextHelp/src/dlgboxsubclass.cpp | 492 +++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 plugins/ContextHelp/src/dlgboxsubclass.cpp (limited to 'plugins/ContextHelp/src/dlgboxsubclass.cpp') diff --git a/plugins/ContextHelp/src/dlgboxsubclass.cpp b/plugins/ContextHelp/src/dlgboxsubclass.cpp new file mode 100644 index 0000000000..ab0a63f4dd --- /dev/null +++ b/plugins/ContextHelp/src/dlgboxsubclass.cpp @@ -0,0 +1,492 @@ +/* +Miranda IM Help Plugin +Copyright (C) 2002 Richard Hughes, 2005-2007 H. Herkenrath + +This program 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 2 +of the License, or (at your option) any later version. + +This program 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 this program (Help-License.txt); if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ +#include "stdafx.h" + + +#define SC_CONTEXTHELP_SEPARATOR SC_SEPARATOR+1 +#define SC_CONTEXTHELP_DIALOG SC_CONTEXTHELP+1 + +#define PROP_CONTEXTSTATE _T("HelpPlugin_ContextState") +#define PROPF_MENUFORCED 0x01 // always show help context menu for ctl (override default) +#define PROPF_MENUDISABLED 0x02 // never show help context menu for ctl +#define PROPF_AUTOTIPFORCED 0x04 // always show autotip for ctl (override default) +#define PROPF_AUTOTIPDISABLED 0x08 // never show autotip for ctl + +extern HINSTANCE hInst; +extern HWND hwndHelpDlg; +extern WORD settingAutoTipDelay; +static HHOOK hMessageHook, hKeyboardHook, hEatNextMouseHook = NULL; +static HANDLE hServiceShowHelp, hServiceSetContext; + +struct DlgBoxSubclassData { + HWND hwndDlg; + WNDPROC pfnOldWndProc; + DWORD flags; +} static *dlgBoxSubclass = NULL; +static int dlgBoxSubclassCount = 0; +static CRITICAL_SECTION csDlgBoxSubclass; + +#define DBSDF_MINIMIZABLE 0x01 // WS_MINIMIZEBOX style was set on hooked window +#define DBSDF_MAXIMIZABLE 0x02 // WS_MAXIMIZEBOX style was set on hooked window + +struct FindChildAtPointData { + HWND hwnd; + POINT pt; + int bestArea; +}; + +// ChildWindowFromPoint() messes up with group boxes +static INT_PTR CALLBACK FindChildAtPointEnumProc(HWND hwnd, LPARAM lParam) +{ + if (IsWindowVisible(hwnd)) { + struct FindChildAtPointData *fcap = (struct FindChildAtPointData*)lParam; + RECT rcVisible, rc, rcParent; + GetWindowRect(hwnd, &rc); + GetWindowRect(GetParent(hwnd), &rcParent); + IntersectRect(&rcVisible, &rcParent, &rc); + if (PtInRect(&rcVisible, fcap->pt)) { + int thisArea = (rc.bottom - rc.top)*(rc.right - rc.left); + if (thisArea && (thisAreabestArea || fcap->bestArea == 0)) { + fcap->bestArea = thisArea; + fcap->hwnd = hwnd; + } + } + } + return TRUE; +} + +// IsChild() messes up with owned windows +int IsRealChild(HWND hwndParent, HWND hwnd) +{ + while (hwnd != NULL) { + if (hwnd == hwndParent) + return 1; + if (hwndParent == GetWindow(hwnd, GW_OWNER)) + return 0; + hwnd = GetParent(hwnd); + } + + return 0; +} + +static BOOL CALLBACK RemovePropForAllChildsEnumProc(HWND hwnd, LPARAM lParam) +{ + RemoveProp(hwnd, (TCHAR*)lParam); + + return TRUE; +} + +static HWND hwndMouseMoveDlg = NULL; +static UINT idMouseMoveTimer = 0; +static LONG cursorPos = MAKELONG(-1, -1); +static int openedAutoTip = 0; + +static void CALLBACK NoMouseMoveForDelayTimerProc(HWND hwnd, UINT msg, UINT idTimer, DWORD time) +{ + POINT pt; + HWND hwndCtl; + struct FindChildAtPointData fcap; + UNREFERENCED_PARAMETER(msg); + UNREFERENCED_PARAMETER(time); + + KillTimer(hwnd, idTimer); + if (idMouseMoveTimer != idTimer) + return; + idMouseMoveTimer = 0; + if (!settingAutoTipDelay || !IsWindow(hwndMouseMoveDlg)) + return; + + ZeroMemory(&fcap, sizeof(fcap)); + if (!GetCursorPos(&pt)) + return; + // ChildWindowFromPoint() messes up with group boxes + fcap.hwnd = NULL; + fcap.pt = pt; + EnumChildWindows(hwndMouseMoveDlg, (WNDENUMPROC)FindChildAtPointEnumProc, (LPARAM)&fcap); + hwndCtl = fcap.hwnd; + if (hwndCtl == NULL) { + ScreenToClient(hwndMouseMoveDlg, &pt); + hwndCtl = ChildWindowFromPointEx(hwndMouseMoveDlg, pt, CWP_SKIPINVISIBLE); + if (hwndCtl == NULL) + return; + } + + LONG flags = (LONG)GetProp(hwndCtl, PROP_CONTEXTSTATE); + if (flags&PROPF_AUTOTIPDISABLED) + return; + flags = SendMessage(hwndCtl, WM_GETDLGCODE, (WPARAM)VK_RETURN, (LPARAM)NULL); + if (flags&DLGC_HASSETSEL || flags&DLGC_WANTALLKEYS) + return; // autotips on edits are annoying + + CURSORINFO ci; + BOOL(WINAPI *pfnGetCursorInfo)(CURSORINFO*); + ci.cbSize = sizeof(ci); + *(FARPROC*)&pfnGetCursorInfo = GetProcAddress(GetModuleHandleA("USER32"), "GetCursorInfo"); + // if(pfnGetCursorInfo && IsWinVer2000Plus()) // call not safe for WinNT4 + if (pfnGetCursorInfo(&ci) && !(ci.flags&CURSOR_SHOWING)) + return; + + if (IsRealChild(hwndMouseMoveDlg, hwndCtl) && hwndHelpDlg == NULL) { + hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); + if (hwndHelpDlg == NULL) + return; + openedAutoTip = 1; + PostMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)hwndCtl); + } +} + +static LRESULT CALLBACK KeyboardInputHookProc(int code, WPARAM wParam, LPARAM lParam) +{ + if (code == HC_ACTION && idMouseMoveTimer != 0) { + KillTimer(NULL, idMouseMoveTimer); + idMouseMoveTimer = 0; + } + return CallNextHookEx(hKeyboardHook, code, wParam, lParam); +} + +// workaround for WM_HELP (SC_CONTEXTHELP causes an additional WM_LBUTTONUP when selecting) +static LRESULT CALLBACK EatNextMouseButtonUpHookProc(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0 && wParam == WM_LBUTTONUP) { + UnhookWindowsHookEx(hEatNextMouseHook); // unhook ourselves + hEatNextMouseHook = NULL; + return -1; + } + return CallNextHookEx(hEatNextMouseHook, code, wParam, lParam); +} + +static LRESULT CALLBACK DialogBoxSubclassProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + WNDPROC pfnWndProc; + DWORD flags; + int i; + + EnterCriticalSection(&csDlgBoxSubclass); + for (i = 0; i10) + return 0; + + if (idMouseMoveTimer) + KillTimer(NULL, idMouseMoveTimer); + idMouseMoveTimer = 0; + + ZeroMemory(&fcap, sizeof(fcap)); + POINTSTOPOINT(pt, MAKEPOINTS(lParam)); + // ChildWindowFromPoint() messes up with group boxes + fcap.hwnd = NULL; + fcap.pt = pt; + EnumChildWindows(hwndDlg, (WNDENUMPROC)FindChildAtPointEnumProc, (LPARAM)&fcap); + hwndCtl = fcap.hwnd; + if (hwndCtl == NULL) { + ScreenToClient(hwndDlg, &pt); + hwndCtl = ChildWindowFromPointEx(hwndDlg, pt, CWP_SKIPINVISIBLE); + if (hwndCtl == NULL) + break; + POINTSTOPOINT(pt, MAKEPOINTS(lParam)); + } + { + LONG flags = (LONG)GetProp(hwndCtl, PROP_CONTEXTSTATE); + if (flags&PROPF_MENUDISABLED) + break; + else if (!(flags&PROPF_MENUFORCED)) { + int type = GetControlType(hwndCtl); + // showing a context menu on these looks silly (multi components) + if (type == CTLTYPE_TOOLBAR || type == CTLTYPE_LISTVIEW || type == CTLTYPE_TREEVIEW || type == CTLTYPE_STATUSBAR || type == CTLTYPE_CLC) + break; + } + } + if (IsRealChild(hwndDlg, hwndCtl)) { + HMENU hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, SC_CONTEXTHELP, (hwndCtl == hwndDlg) ? TranslateT("&What's this Dialog?") : TranslateT("&What's this?")); + if (TrackPopupMenuEx(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_HORPOSANIMATION | TPM_VERPOSANIMATION | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, hwndDlg, NULL)) { + if (hwndHelpDlg == NULL) { + hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); + if (hwndHelpDlg == NULL) { + DestroyMenu(hMenu); + break; + } + } + SendMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)hwndCtl); + } + DestroyMenu(hMenu); + } + return 0; + } + case WM_HELP: + { + HELPINFO *hi = (HELPINFO*)lParam; + if (hi->iContextType != HELPINFO_WINDOW) break; + // fix for SHBrowseForFolder() dialog, which sends unhandled help to parent + if (!IsRealChild(hwndDlg, (HWND)hi->hItemHandle)) + break; + + if (idMouseMoveTimer) + KillTimer(NULL, idMouseMoveTimer); + idMouseMoveTimer = 0; + + if (!IsWindow(hwndHelpDlg)) { + hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); + if (hwndHelpDlg == NULL) + break; + } + SendMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)hi->hItemHandle); + // we need to eat the next WM_LBUTTONDOWN (if invoked by mouse) + if (GetKeyState(GetSystemMetrics(SM_SWAPBUTTON) ? VK_RBUTTON : VK_LBUTTON) & 0x8000 && hEatNextMouseHook == NULL) + hEatNextMouseHook = SetWindowsHookEx(WH_MOUSE, EatNextMouseButtonUpHookProc, NULL, GetCurrentThreadId()); + return TRUE; + } + case WM_NCDESTROY: + if (idMouseMoveTimer) + KillTimer(NULL, idMouseMoveTimer); + idMouseMoveTimer = 0; + EnumChildWindows(hwndDlg, RemovePropForAllChildsEnumProc, (LPARAM)PROP_CONTEXTSTATE); + { + TCHAR text[64]; + mir_sntprintf(text, sizeof(text), _T("unhooked window 0x%X for context help\n"), hwndDlg); + OutputDebugString(text); + } + SetWindowLongPtr(hwndDlg, GWLP_WNDPROC, (LONG_PTR)pfnWndProc); + break; + } + return CallWindowProc(pfnWndProc, hwndDlg, msg, wParam, lParam); +} + +static LRESULT CALLBACK HelpSendMessageHookProc(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + CWPSTRUCT *msg = (CWPSTRUCT*)lParam; + switch (msg->message) { + case WM_INITDIALOG: // dialogs and message boxes + if (GetClassLong(msg->hwnd, GCW_ATOM) != 32770) // class="#32770" + break; + if (msg->hwnd == hwndHelpDlg || (DLGPROC)GetWindowLongPtr(msg->hwnd, DWLP_DLGPROC) == HelpDlgProc) + break; +#ifndef EDITOR + if ((DLGPROC)GetWindowLongPtr(msg->hwnd, DWLP_DLGPROC) == ShadowDlgProc) + break; +#endif + { + LONG_PTR style, exStyle; + struct DlgBoxSubclassData *buf; + + exStyle = GetWindowLongPtr(msg->hwnd, GWL_EXSTYLE); + if (exStyle&WS_EX_CONTEXTHELP) + break; + style = GetWindowLongPtr(msg->hwnd, GWL_STYLE); + + EnterCriticalSection(&csDlgBoxSubclass); + buf = (struct DlgBoxSubclassData*)mir_realloc(dlgBoxSubclass, sizeof(struct DlgBoxSubclassData)*(dlgBoxSubclassCount + 1)); + if (buf == NULL) { + LeaveCriticalSection(&csDlgBoxSubclass); + break; + } + dlgBoxSubclass = buf; + dlgBoxSubclass[dlgBoxSubclassCount].hwndDlg = msg->hwnd; + dlgBoxSubclass[dlgBoxSubclassCount].pfnOldWndProc = (WNDPROC)SetWindowLongPtr(msg->hwnd, GWLP_WNDPROC, (LONG_PTR)DialogBoxSubclassProc); + dlgBoxSubclass[dlgBoxSubclassCount].flags = 0; + + // WS_EX_CONTEXTHELP cannot be used in conjunction WS_MINIMIZEBOX or WS_MAXIMIZEBOX + // solution: switch off WS_MINIMIZEBOX or WS_MAXIMIZEBOX when only one of them is present + if (!(style & WS_MINIMIZEBOX) || !(style & WS_MAXIMIZEBOX)) { + if (style & WS_MINIMIZEBOX) + dlgBoxSubclass[dlgBoxSubclassCount].flags |= DBSDF_MINIMIZABLE; + if (style & WS_MAXIMIZEBOX) + dlgBoxSubclass[dlgBoxSubclassCount].flags |= DBSDF_MAXIMIZABLE; + SetWindowLongPtr(msg->hwnd, GWL_STYLE, style&(~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX))); + SetWindowLongPtr(msg->hwnd, GWL_EXSTYLE, (LONG_PTR)exStyle | WS_EX_CONTEXTHELP); + } + dlgBoxSubclassCount++; + LeaveCriticalSection(&csDlgBoxSubclass); + } + { + HMENU hMenu; + hMenu = GetSystemMenu(msg->hwnd, FALSE); + if (hMenu != NULL && AppendMenu(hMenu, MF_SEPARATOR, SC_CONTEXTHELP_SEPARATOR, NULL)) { + AppendMenu(hMenu, MF_STRING, SC_CONTEXTHELP, TranslateT("&What's this?")); + AppendMenu(hMenu, MF_STRING, SC_CONTEXTHELP_DIALOG, TranslateT("&What's this Dialog?")); + } + } + { + TCHAR text[64]; + mir_sntprintf(text, sizeof(text), _T("hooked window 0x%X for context help\n"), msg->hwnd); + OutputDebugString(text); + } + break; + } + } + return CallNextHookEx(hMessageHook, code, wParam, lParam); +} + +static INT_PTR ServiceShowHelp(WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + if (!IsWindow((HWND)wParam)) + return 1; + if (idMouseMoveTimer) + KillTimer(NULL, idMouseMoveTimer); + idMouseMoveTimer = 0; + if (hwndHelpDlg == NULL) { + hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); + if (hwndHelpDlg == NULL) + return 2; + } + PostMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)wParam); + + return 0; +} + +static INT_PTR ServiceSetContextState(WPARAM wParam, LPARAM lParam) +{ + int i; + LONG flags; + EnterCriticalSection(&csDlgBoxSubclass); + for (i = 0; i < dlgBoxSubclassCount; i++) + if (IsRealChild(dlgBoxSubclass[i].hwndDlg, (HWND)wParam)) + break; + if (i == dlgBoxSubclassCount) { + LeaveCriticalSection(&csDlgBoxSubclass); + + return 2; + } + LeaveCriticalSection(&csDlgBoxSubclass); + flags = (lParam&HCSF_CONTEXTMENU) ? PROPF_MENUFORCED : PROPF_MENUDISABLED; + flags |= (lParam&HCSF_AUTOTIP) ? PROPF_AUTOTIPFORCED : PROPF_AUTOTIPDISABLED; + + return !SetProp((HWND)wParam, PROP_CONTEXTSTATE, (HANDLE)flags); +} + +int InstallDialogBoxHook(void) +{ + InitializeCriticalSection(&csDlgBoxSubclass); + hServiceShowHelp = CreateServiceFunction(MS_HELP_SHOWHELP, ServiceShowHelp); + hServiceSetContext = CreateServiceFunction(MS_HELP_SETCONTEXTSTATE, ServiceSetContextState); + hMessageHook = SetWindowsHookEx(WH_CALLWNDPROC, HelpSendMessageHookProc, NULL, GetCurrentThreadId()); // main thread + hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardInputHookProc, NULL, GetCurrentThreadId()); // main thread + + return hMessageHook == NULL; +} + +int RemoveDialogBoxHook(void) +{ + DestroyServiceFunction(hServiceShowHelp); // does NULL check + DestroyServiceFunction(hServiceSetContext); // does NULL check + UnhookWindowsHookEx(hMessageHook); + if (hKeyboardHook) + UnhookWindowsHookEx(hMessageHook); + if (hEatNextMouseHook) + UnhookWindowsHookEx(hEatNextMouseHook); + DeleteCriticalSection(&csDlgBoxSubclass); + for (int i = 0; i