/* 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 #define _WIN32_WINNT 0x0400 #define __RPCASYNC_H__ /* header shows warnings in VS6 */ #include #include #define MIRANDA_VER 0x0600 #include #include #include #include #include #include "help.h" #include "m_help.h" #include "resource.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 BOOL 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,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,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,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); } SetWindowLong(hwndDlg,GWL_WNDPROC,(LONG)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)GetWindowLong(msg->hwnd,DWL_DLGPROC)==HelpDlgProc) break; #ifndef EDITOR if((DLGPROC)GetWindowLong(msg->hwnd,DWL_DLGPROC)==ShadowDlgProc) break; #endif { DWORD style,exStyle; struct DlgBoxSubclassData *buf; exStyle=GetWindowLong(msg->hwnd,GWL_EXSTYLE); if(exStyle&WS_EX_CONTEXTHELP) break; style=GetWindowLong(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)SetWindowLong(msg->hwnd,GWL_WNDPROC,(LONG)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; SetWindowLong(msg->hwnd,GWL_STYLE,style&(~(WS_MINIMIZEBOX|WS_MAXIMIZEBOX))); SetWindowLong(msg->hwnd,GWL_EXSTYLE,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 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 ServiceSetContextState(WPARAM wParam,LPARAM lParam) { int i; LONG flags; EnterCriticalSection(&csDlgBoxSubclass); for(i=0;i