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/datastore.cpp | 590 +++++++++++++++++++++ plugins/ContextHelp/src/dlgboxsubclass.cpp | 492 ++++++++++++++++++ plugins/ContextHelp/src/help.h | 146 ++++++ plugins/ContextHelp/src/helpdlg.cpp | 735 ++++++++++++++++++++++++++ plugins/ContextHelp/src/helppack.cpp | 520 +++++++++++++++++++ plugins/ContextHelp/src/main.cpp | 128 +++++ plugins/ContextHelp/src/options.cpp | 797 +++++++++++++++++++++++++++++ plugins/ContextHelp/src/resource.h | 61 +++ plugins/ContextHelp/src/stdafx.cxx | 18 + plugins/ContextHelp/src/stdafx.h | 52 ++ plugins/ContextHelp/src/streaminout.cpp | 771 ++++++++++++++++++++++++++++ plugins/ContextHelp/src/utils.cpp | 430 ++++++++++++++++ plugins/ContextHelp/src/version.h | 53 ++ 13 files changed, 4793 insertions(+) create mode 100644 plugins/ContextHelp/src/datastore.cpp create mode 100644 plugins/ContextHelp/src/dlgboxsubclass.cpp create mode 100644 plugins/ContextHelp/src/help.h create mode 100644 plugins/ContextHelp/src/helpdlg.cpp create mode 100644 plugins/ContextHelp/src/helppack.cpp create mode 100644 plugins/ContextHelp/src/main.cpp create mode 100644 plugins/ContextHelp/src/options.cpp create mode 100644 plugins/ContextHelp/src/resource.h create mode 100644 plugins/ContextHelp/src/stdafx.cxx create mode 100644 plugins/ContextHelp/src/stdafx.h create mode 100644 plugins/ContextHelp/src/streaminout.cpp create mode 100644 plugins/ContextHelp/src/utils.cpp create mode 100644 plugins/ContextHelp/src/version.h (limited to 'plugins/ContextHelp/src') diff --git a/plugins/ContextHelp/src/datastore.cpp b/plugins/ContextHelp/src/datastore.cpp new file mode 100644 index 0000000000..84c7474e21 --- /dev/null +++ b/plugins/ContextHelp/src/datastore.cpp @@ -0,0 +1,590 @@ +/* +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" + + +extern HWND hwndHelpDlg; + +struct DlgControlData { + int id; + int type; + TCHAR *szTitle; + char *szText; // ANSI or UTF-8 depending on _UNICODE defined (for RichEdit) +}; + +struct DialogData { + char *szId; + char *szModule; + struct DlgControlData *control; + int controlCount; + DWORD timeLoaded, timeLastUsed; + int changes; + LCID locale; + UINT defaultCodePage; + int isLocaleRTL; +}; + +static struct DialogData *dialogCache = NULL; +static int dialogCacheCount = 0; +static CRITICAL_SECTION csDialogCache; +static HANDLE hServiceFileChange, hFileChange; + +#define DIALOGCACHEEXPIRY 10*60*1000 // delete from cache after those milliseconds + +static INT_PTR ServiceFileChanged(WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + + EnterCriticalSection(&csDialogCache); + for (int i = 0; i < dialogCacheCount; i++) + dialogCache[i].timeLastUsed = 0; + LeaveCriticalSection(&csDialogCache); + + if ((HANDLE)wParam != NULL) + FindNextChangeNotification((HANDLE)wParam); + + return 0; +} + +void InitDialogCache(void) +{ + TCHAR szFilePath[MAX_PATH], *p; + + InitializeCriticalSection(&csDialogCache); + + hServiceFileChange = CreateServiceFunction("Help/HelpPackChanged", ServiceFileChanged); + hFileChange = INVALID_HANDLE_VALUE; + if (GetModuleFileName(NULL, szFilePath, sizeof(szFilePath))) { + p = _tcsrchr(szFilePath, _T('\\')); + if (p != NULL) + *(p + 1) = _T('\0'); + hFileChange = FindFirstChangeNotification(szFilePath, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE); + if (hFileChange != INVALID_HANDLE_VALUE) + CallService(MS_SYSTEM_WAITONHANDLE, (WPARAM)hFileChange, (LPARAM)"Help/HelpPackChanged"); + } +} + +static void FreeDialogCacheEntry(struct DialogData *entry) +{ + for (int i = 0; i < entry->controlCount; i++) { + mir_free(entry->control[i].szText); // does NULL check + mir_free(entry->control[i].szTitle); // does NULL check + } + mir_free(entry->control); // does NULL check + mir_free(entry->szId); // does NULL check + mir_free(entry->szModule); // does NULL check +} + +void FreeDialogCache(void) +{ + if (hFileChange != INVALID_HANDLE_VALUE) { + CallService(MS_SYSTEM_REMOVEWAIT, (WPARAM)hFileChange, 0); + FindCloseChangeNotification(hFileChange); + } + DestroyServiceFunction(hServiceFileChange); + + DeleteCriticalSection(&csDialogCache); + for (int i = 0; i < dialogCacheCount; i++) + FreeDialogCacheEntry(&dialogCache[i]); + dialogCacheCount = 0; + mir_free(dialogCache); // does NULL check + dialogCache = NULL; +} + +/**************************** LOAD HELP ***************************/ + +struct LoaderThreadStartParams { + HWND hwndCtl; + char *szDlgId; + char *szModule; + int ctrlId; +}; + +static void LoaderThread(void *arg) +{ + LoaderThreadStartParams *dtsp = (LoaderThreadStartParams *)arg; + + FILE *fp; + char line[4096]; + char *pszLine, *pszColon, *pszBuf; + int startOfLine = 0; + + WIN32_FIND_DATA wfd; + HANDLE hFind; + TCHAR szDir[MAX_PATH]; + TCHAR szSearch[MAX_PATH]; + TCHAR *p; + int success = 0; + + struct DialogData dialog; + struct DlgControlData *control; + void *buf; + ZeroMemory(&dialog, sizeof(dialog)); + + if (GetModuleFileName(NULL, szDir, sizeof(szDir))) { + p = _tcsrchr(szDir, _T('\\')); + if (p) + *p = _T('\0'); + mir_sntprintf(szSearch, _T("%s\\helppack_*.txt"), szDir); + + hFind = FindFirstFile(szSearch, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + if (lstrlen(wfd.cFileName)<4 || wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.')) + continue; + mir_sntprintf(szSearch, _T("%s\\%s"), szDir, wfd.cFileName); + success = 1; + break; + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + } + if (!success) { + if (!Miranda_Terminated() && IsWindow(hwndHelpDlg)) + PostMessage(hwndHelpDlg, M_HELPLOADFAILED, 0, (LPARAM)dtsp->hwndCtl); + return; + } + + fp = _tfopen(szSearch, _T("rt")); + if (fp == NULL) { + if (!Miranda_Terminated() && IsWindow(hwndHelpDlg)) + PostMessage(hwndHelpDlg, M_HELPLOADFAILED, 0, (LPARAM)dtsp->hwndCtl); + return; + } + fgets(line, sizeof(line), fp); + TrimString(line); + if (lstrcmpA(line, "Miranda Help Pack Version 1")) { + fclose(fp); + if (!Miranda_Terminated() && IsWindow(hwndHelpDlg)) + PostMessage(hwndHelpDlg, M_HELPLOADFAILED, 0, (LPARAM)dtsp->hwndCtl); + return; + } + + // headers + dialog.locale = LOCALE_USER_DEFAULT; + dialog.defaultCodePage = CP_ACP; + while (!feof(fp)) { + startOfLine = ftell(fp); + if (fgets(line, sizeof(line), fp) == NULL) + break; + TrimString(line); + if (IsEmpty(line) || line[0] == ';' || line[0] == '\0') + continue; + if (line[0] == '[') + break; + pszColon = strchr(line, ':'); + if (pszColon == NULL) { + fclose(fp); + if (!Miranda_Terminated() && IsWindow(hwndHelpDlg)) + PostMessage(hwndHelpDlg, M_HELPLOADFAILED, 0, (LPARAM)dtsp->hwndCtl); + return; + } + *pszColon = '\0'; + + // locale + if (!lstrcmpA(line, "Locale")) { + char szCP[6]; + TrimString(pszColon + 1); + dialog.locale = MAKELCID((USHORT)strtol(pszColon + 1, NULL, 16), SORT_DEFAULT); + // codepage + if (GetLocaleInfoA(dialog.locale, LOCALE_IDEFAULTANSICODEPAGE, szCP, sizeof(szCP))) { + szCP[5] = '\0'; // codepages have 5 digits at max + dialog.defaultCodePage = atoi(szCP); + } + } + } + + // RTL flag + // see also: http://blogs.msdn.com/michkap/archive/2006/03/03/542963.aspx + { + LOCALESIGNATURE sig; + dialog.isLocaleRTL = 0; + if (GetLocaleInfo(dialog.locale, LOCALE_FONTSIGNATURE, (LPTSTR)&sig, sizeof(sig) / sizeof(TCHAR))) + dialog.isLocaleRTL = (sig.lsUsb[3] & 0x8000000); // Win2000+: testing for 'Layout progress: horizontal from right to left' bit + switch (PRIMARYLANGID(LANGIDFROMLCID(dialog.locale))) { // prior to Win2000 + case LANG_ARABIC: + case LANG_HEBREW: + case LANG_FARSI: + dialog.isLocaleRTL = 1; + } + } + + // body + fseek(fp, startOfLine, SEEK_SET); + success = 1; + control = NULL; + while (!feof(fp)) { + if (fgets(line, sizeof(line), fp) == NULL) + break; + if (IsEmpty(line) || line[0] == ';' || line[0] == '\0') + continue; + TrimStringSimple(line); + + if (line[0] == '[' && line[lstrlenA(line) - 1] == ']') { + pszLine = line + 1; + line[lstrlenA(line) - 1] = '\0'; + + // module + pszColon = strrchr(pszLine, ':'); + if (pszColon == NULL) + continue; + *pszColon = '\0'; + pszColon++; + TrimString(pszLine); + if (lstrcmpiA(dtsp->szModule, pszLine)) + continue; + pszBuf = pszLine; + + // dlgid + pszLine = pszColon; + pszColon = strrchr(pszLine, '@'); + if (pszColon == NULL) + continue; + *pszColon = '\0'; + pszColon++; + TrimString(pszColon); + if (lstrcmpA(dtsp->szDlgId, pszColon)) + continue; + + if (dialog.szModule == NULL && dialog.szId == NULL) { + dialog.szModule = mir_strdup(pszBuf); + dialog.szId = mir_strdup(pszColon); + if (dialog.szId == NULL || dialog.szModule == NULL) { + success = 0; + break; + } + } + buf = (struct DlgControlData*)mir_realloc(dialog.control, sizeof(struct DlgControlData)*(dialog.controlCount + 1)); + if (buf == NULL) { + success = 0; + break; + } + dialog.controlCount++; + dialog.control = (struct DlgControlData*)buf; + control = &dialog.control[dialog.controlCount - 1]; + ZeroMemory(control, sizeof(*control)); + + // ctlid + TrimString(pszLine); + control->id = atoi(pszLine); + } + else if (control != NULL) { + pszLine = line; + + // ctltext + pszColon = strchr(pszLine, '='); + if (pszColon == NULL) + continue; + *pszColon = '\0'; + pszColon++; + TrimString(pszColon); + TrimString(pszLine); + if (*pszColon == '\0' || *pszLine == '\0') + continue; + int size = lstrlenA(pszLine) + 1; + control->szTitle = (WCHAR*)mir_alloc(size*sizeof(WCHAR)); + if (control->szTitle != NULL) { + *control->szTitle = _T('\0'); + MultiByteToWideChar(dialog.defaultCodePage, 0, pszLine, -1, control->szTitle, size); + } + + // text + control->szText = mir_utf8encodecp(pszColon, dialog.defaultCodePage); + control = NULL; // control done + } + } + fclose(fp); + + if (success) { + int i, dialogInserted = 0; + + dialog.timeLoaded = dialog.timeLastUsed = GetTickCount(); + EnterCriticalSection(&csDialogCache); + for (i = 0; i < dialogCacheCount; i++) { + if (dialogCache[i].timeLastUsed && dialogCache[i].timeLastUsed<(dialog.timeLoaded - DIALOGCACHEEXPIRY)) { + FreeDialogCacheEntry(&dialogCache[i]); + if (dialogInserted || !dialog.controlCount) { + MoveMemory(dialogCache + i, dialogCache + i + 1, sizeof(struct DialogData)*(dialogCacheCount - i - 1)); + dialogCacheCount--; + buf = (struct DialogData*)mir_realloc(dialogCache, sizeof(struct DialogData)*dialogCacheCount); + if (buf != NULL) + dialogCache = (struct DialogData*)buf; + else if (!dialogCacheCount) + dialogCache = NULL; + } + else { + dialogInserted = 1; + dialogCache[i] = dialog; + } + } + } + if (dialog.controlCount && !dialogInserted) { + buf = (struct DialogData*)mir_realloc(dialogCache, sizeof(struct DialogData)*(dialogCacheCount + 1)); + if (buf != NULL) { + dialogCacheCount++; + dialogCache = (struct DialogData*)buf; + dialogCache[dialogCacheCount - 1] = dialog; + dialogInserted = 1; + } + } + LeaveCriticalSection(&csDialogCache); + + if (!dialogInserted) { + mir_free(dialog.szId); // does NULL check + mir_free(dialog.szModule); // does NULL check + mir_free(dialog.control); // does NULL check + } + if (!Miranda_Terminated() && IsWindow(hwndHelpDlg)) + PostMessage(hwndHelpDlg, M_HELPLOADED, 0, (LPARAM)dtsp->hwndCtl); + } + if (!success) { + mir_free(dialog.szId); // does NULL check + mir_free(dialog.szModule); // does NULL check + mir_free(dialog.control); // does NULL check + if (!Miranda_Terminated() && IsWindow(hwndHelpDlg)) + PostMessage(hwndHelpDlg, M_HELPLOADFAILED, 0, (LPARAM)dtsp->hwndCtl); + } + + mir_free(dtsp->szDlgId); + mir_free(dtsp->szModule); + mir_free(dtsp); +} + +// mir_free() the return value +char *CreateControlIdentifier(const char *pszDlgId, const char *pszModule, int ctrlId, HWND hwndCtl) +{ + int size; + char *szId; + TCHAR szDefCtlText[128]; + GetControlTitle(hwndCtl, szDefCtlText, sizeof(szDefCtlText)); + size = lstrlenA(pszModule) + lstrlenA(pszDlgId) + sizeof(szDefCtlText) + 22; + szId = (char*)mir_alloc(size); + mir_snprintf(szId, size, "[%s:%i@%s]\r\n%S%s", pszModule, ctrlId, pszDlgId, szDefCtlText, szDefCtlText[0] ? "=" : ""); + + return szId; +} + +int GetControlHelp(HWND hwndCtl, const char *pszDlgId, const char *pszModule, int ctrlId, TCHAR **ppszTitle, char **ppszText, int *pType, LCID *pLocaleID, UINT *pCodePage, BOOL *pIsRTL, DWORD flags) +{ + int i, j, useNext = 0; + struct LoaderThreadStartParams *dtsp; + + EnterCriticalSection(&csDialogCache); + for (i = 0; i < dialogCacheCount; i++) { + if (!(flags&GCHF_DONTLOAD)) { + if (!dialogCache[i].timeLastUsed || dialogCache[i].timeLastUsed<(GetTickCount() - DIALOGCACHEEXPIRY)) { + struct DialogData *buf; + FreeDialogCacheEntry(&dialogCache[i]); + MoveMemory(dialogCache + i, dialogCache + i + 1, sizeof(struct DialogData)*(dialogCacheCount - i - 1)); + dialogCacheCount--; + buf = (struct DialogData*)mir_realloc(dialogCache, sizeof(struct DialogData)*dialogCacheCount); + if (buf != NULL) + dialogCache = (struct DialogData*)buf; + else if (!dialogCacheCount) + dialogCache = NULL; + i--; + continue; + } + } + if (lstrcmpA(pszDlgId, dialogCache[i].szId)) + break; + for (j = 0; j < dialogCache[i].controlCount; j++) { + if (ctrlId == dialogCache[i].control[j].id || useNext || dialogCache[i].control[j].id == 0) { + if (dialogCache[i].control[j].szTitle == NULL) { + useNext = 1; + continue; + } + if (ppszTitle) + *ppszTitle = dialogCache[i].control[j].szTitle; + if (ppszText) + *ppszText = dialogCache[i].control[j].szText; + if (pType) + *pLocaleID = dialogCache[i].locale; + if (pCodePage) + *pCodePage = CP_UTF8; + if (pIsRTL) + *pIsRTL = dialogCache[i].isLocaleRTL; + if (dialogCache[i].control[j].id != ctrlId && !useNext) + continue; + dialogCache[i].timeLastUsed = GetTickCount(); + LeaveCriticalSection(&csDialogCache); + return 0; + } + } + break; + } + + if (ppszTitle) + *ppszTitle = NULL; + if (ppszText) + *ppszText = NULL; + if (pType) + *pType = CTLTYPE_UNKNOWN; + if (pLocaleID) + *pLocaleID = LOCALE_USER_DEFAULT; + if (pCodePage) + *pCodePage = CP_ACP; + + if (!(flags&GCHF_DONTLOAD)) { + dtsp = (struct LoaderThreadStartParams*)mir_alloc(sizeof(struct LoaderThreadStartParams)); + if (dtsp == NULL) { + LeaveCriticalSection(&csDialogCache); + return 0; + } + dtsp->szDlgId = mir_strdup(pszDlgId); + dtsp->szModule = mir_strdup(pszModule); + if (dtsp->szDlgId == NULL || dtsp->szModule == NULL) { + mir_free(dtsp->szDlgId); // does NULL check + mir_free(dtsp->szModule); // does NULL check + mir_free(dtsp); + LeaveCriticalSection(&csDialogCache); + return 0; + } + dtsp->ctrlId = ctrlId; + dtsp->hwndCtl = hwndCtl; + mir_forkthread(LoaderThread, dtsp); + } + LeaveCriticalSection(&csDialogCache); + + return 1; +} + +/**************************** SAVE HELP ***************************/ + +#ifdef EDITOR +void SetControlHelp(const char *pszDlgId, const char *pszModule, int ctrlId, TCHAR *pszTitle, char *pszText, int type) +{ + int i, j; + int found = 0; + void *buf; + + EnterCriticalSection(&csDialogCache); + j = 0; + for (i = 0; i < dialogCacheCount; i++) { + if (lstrcmpA(pszDlgId, dialogCache[i].szId)) + continue; + for (j = 0; j < dialogCache[i].controlCount; j++) { + if (ctrlId == dialogCache[i].control[j].id) { + mir_free(dialogCache[i].control[j].szTitle); // does NULL check + mir_free(dialogCache[i].control[j].szText); // does NULL check + dialogCache[i].control[j].szTitle = NULL; + dialogCache[i].control[j].szText = NULL; + found = 1; + break; + } + } + if (!found) { + buf = (struct DlgControlData*)mir_realloc(dialogCache[i].control, sizeof(struct DlgControlData)*(dialogCache[i].controlCount + 1)); + if (buf == NULL) { + LeaveCriticalSection(&csDialogCache); + return; + } + dialogCache[i].control = (struct DlgControlData*)buf; + j = dialogCache[i].controlCount++; + found = 1; + } + break; + } + if (!found) { + buf = (struct DialogData*)mir_realloc(dialogCache, sizeof(struct DialogData)*(dialogCacheCount + 1)); + if (buf == NULL) { + LeaveCriticalSection(&csDialogCache); + return; + } + dialogCache = (struct DialogData*)buf; + dialogCache[i].control = (struct DlgControlData*)mir_alloc(sizeof(struct DlgControlData)); + if (dialogCache[i].control == NULL) { + LeaveCriticalSection(&csDialogCache); + return; + } + dialogCache[i].controlCount = 1; + i = dialogCacheCount; + j = 0; + + dialogCache[i].szId = mir_strdup(pszDlgId); + dialogCache[i].szModule = mir_strdup(pszModule); + if (dialogCache[i].szId == NULL || dialogCache[i].szModule == NULL) { + mir_free(dialogCache[i].szId); // does NULL check + mir_free(dialogCache[i].szModule); // does NULL check + LeaveCriticalSection(&csDialogCache); + return; + } + dialogCacheCount++; + dialogCache[i].timeLoaded = 0; + } + dialogCache[i].control[j].szTitle = mir_tstrdup(pszTitle); // does NULL arg check + dialogCache[i].control[j].szText = mir_strdup(pszText); // does NULL arg check + dialogCache[i].control[j].type = type; + dialogCache[i].control[j].id = ctrlId; + dialogCache[i].timeLastUsed = GetTickCount(); + dialogCache[i].changes = 1; + LeaveCriticalSection(&csDialogCache); +} + +static void DialogCacheSaveThread(void *unused) +{ + int success = 0; + UNREFERENCED_PARAMETER(unused); + + // TODO: port the following code to write to the helppack file instead + // (netlib code already removed) + /* + { WCHAR *szDecoded; + char *szEncoded=mir_strdup(dialogCache[i].control[j].szText); + if (mir_utf8decode(szEncoded, &szDecoded)) { + [...] + mir_free(szDecoded) + } + mir_free(szEncoded); // does NULL check + } + + + int i, j; + struct ResizableCharBuffer data={0}, dlgId={0}; + + for (i = 0; i < dialogCacheCount; i++) { + if(!dialogCache[i].changes) continue; + + AppendToCharBuffer(&data, "t \r\n", dialogCache[i].szId, dialogCache[i].szModule); + for (j = 0; j < dialogCache[i].controlCount; j++) { + AppendToCharBuffer(&data, "\r\n", dialogCache[i].control[j].id, dialogCache[i].control[j].type); + if (dialogCache[i].control[j].szTitle) + AppendToCharBuffer(&data, "%s\r\n", dialogCache[i].control[j].szTitle); + if (dialogCache[i].control[j].szText) + AppendToCharBuffer(&data, "%s\r\n", dialogCache[i].control[j].szText); + AppendToCharBuffer(&data, "\r\n"); + } + AppendToCharBuffer(&data, ""); + } + + if(success) { + dialogCache[i].changes = 0; + dialogCache[i].timeLoaded = GetTickCount(); + } + */ + MessageBoxEx(NULL, success ? TranslateT("Help saving complete.") : TranslateT("Help saving failed!"), TranslateT("Help Editor"), (success ? MB_ICONINFORMATION : MB_ICONERROR) | MB_OK | MB_SETFOREGROUND | MB_TOPMOST | MB_TASKMODAL, LANGIDFROMLCID(Langpack_GetDefaultLocale())); +} + +void SaveDialogCache(void) +{ + mir_forkthread(DialogCacheSaveThread, 0); +} +#endif // defined EDITOR 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 + +extern HINSTANCE hInst; +HWND hwndHelpDlg; + +static int HelpDialogResize(HWND hwndDlg, LPARAM lParam, UTILRESIZECONTROL *urc) +{ + UNREFERENCED_PARAMETER(hwndDlg); + UNREFERENCED_PARAMETER(lParam); + switch (urc->wId) { + case IDC_CTLTEXT: +#ifdef EDITOR + case IDC_DLGID: + case IDC_MODULE: +#endif + return RD_ANCHORX_WIDTH | RD_ANCHORY_TOP; + case IDC_TEXT: + return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT; + } + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; +} + +#ifndef EDITOR +INT_PTR CALLBACK ShadowDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(wParam); + switch (msg) { + case WM_INITDIALOG: + { + BOOL(WINAPI *pfnSetLayeredWindowAttributes)(HWND, COLORREF, BYTE, DWORD); + *(FARPROC*)&pfnSetLayeredWindowAttributes = GetProcAddress(GetModuleHandleA("USER32"), "SetLayeredWindowAttributes"); + if (!pfnSetLayeredWindowAttributes) { + *(HANDLE*)lParam = NULL; // hwndShadowDlg reset + DestroyWindow(hwndDlg); + return FALSE; + } + EnableWindow(hwndDlg, FALSE); + SetWindowLongPtr(hwndDlg, GWL_EXSTYLE, GetWindowLongPtr(hwndDlg, GWL_EXSTYLE) | WS_EX_LAYERED); + pfnSetLayeredWindowAttributes(hwndDlg, RGB(0, 0, 0), 96, LWA_ALPHA); + return FALSE; + } + case WM_CTLCOLORDLG: + return (INT_PTR)GetSysColorBrush(COLOR_WINDOWFRAME); + } + return FALSE; +} + +// in client coordinates +int GetCharRangeRect(HWND hwndEdit, LONG *cpMin, LONG cpMax, RECT *rcRange) +{ + LONG cpLineBreak; + + LONG nLine, nLinePrev; + if (*cpMin>cpMax) + return 1; + nLine = SendMessage(hwndEdit, EM_EXLINEFROMCHAR, 0, *cpMin); + for (cpLineBreak = *cpMin + 1; cpLineBreak <= cpMax; cpLineBreak++) { + nLinePrev = nLine; + nLine = SendMessage(hwndEdit, EM_EXLINEFROMCHAR, 0, cpLineBreak); + if (nLine != nLinePrev) + break; + } + cpMax = cpLineBreak - 1; + + POINTL pt; + if (SendMessage(hwndEdit, EM_SETTYPOGRAPHYOPTIONS, 0, 0)) { // test for richedit v3.0 + SendMessage(hwndEdit, EM_POSFROMCHAR, (WPARAM)&pt, *cpMin); + rcRange->left = pt.x; + rcRange->top = pt.y; + SendMessage(hwndEdit, EM_POSFROMCHAR, (WPARAM)&pt, cpMax); + rcRange->right = pt.x; + rcRange->bottom = pt.y; + } + else { + DWORD pos; + pos = SendMessage(hwndEdit, EM_POSFROMCHAR, (WPARAM)*cpMin, 0); + POINTSTOPOINT(pt, MAKEPOINTS(pos)); + rcRange->left = pt.x; + rcRange->top = pt.y; + pos = SendMessage(hwndEdit, EM_POSFROMCHAR, (WPARAM)cpMax, 0); + POINTSTOPOINT(pt, MAKEPOINTS(pos)); + rcRange->right = pt.x; + rcRange->bottom = pt.y; + } + + FORMATRANGE fr; + ZeroMemory(&fr, sizeof(fr)); + fr.chrg.cpMin = *cpMin; + fr.chrg.cpMax = cpMax; + fr.hdc = fr.hdcTarget = GetDC(hwndEdit); + if (fr.hdc == NULL) + return 1; + SendMessage(hwndEdit, EM_FORMATRANGE, 0, (LPARAM)&fr); + PostMessage(hwndEdit, EM_FORMATRANGE, 0, 0); // clear memory + rcRange->bottom += MulDiv(fr.rc.bottom, GetDeviceCaps(fr.hdc, LOGPIXELSY), 1440); // twips to pixels + ReleaseDC(hwndEdit, fr.hdc); + + *cpMin = cpLineBreak; + return 0; +} + +static HCURSOR hHandCursor; +static LRESULT CALLBACK HelpSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_KEYDOWN: + if (GetKeyState(VK_CONTROL) & 0x8000 && wParam == 'C') { + SendMessage(GetParent(hwnd), M_CLIPBOARDCOPY, 0, 0); + return 0; + } + break; + case WM_LBUTTONDBLCLK: + DestroyWindow(GetParent(hwnd)); + return 0; + case WM_CONTEXTMENU: + return DefWindowProc(hwnd, msg, wParam, lParam); // redirect to parent + case WM_SETCURSOR: // not available via EN_MSGFILTER + if (GetDlgCtrlID((HWND)wParam) == IDC_TEXT) { + POINT pt; + DWORD pos; + CHARRANGE rng; + pos = GetMessagePos(); + POINTSTOPOINT(pt, MAKEPOINTS(pos)); + ScreenToClient((HWND)wParam, &pt); + pos = SendMessage((HWND)wParam, EM_CHARFROMPOS, 0, (WPARAM)&pt); + if (IsHyperlink(pos, &rng.cpMin, &rng.cpMax, NULL)) { + RECT rc; + while (!GetCharRangeRect((HWND)wParam, &rng.cpMin, rng.cpMax, &rc)) + if (PtInRect(&rc, pt)) { + SetCursor(hHandCursor); + return TRUE; + } + } + } + break; + } + return CallWindowProc((WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA), hwnd, msg, wParam, lParam); +} +#endif // !defined EDITOR + +INT_PTR CALLBACK HelpDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hwndCtl = (HWND)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + static LCID locale; +#ifndef EDITOR + static HWND hwndShadowDlg; + static HWND hwndToolTip; +#endif + + switch (msg) { + case WM_INITDIALOG: + hwndHelpDlg = hwndDlg; +#ifdef EDITOR + TranslateDialogDefault(hwndDlg); + SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_DLGID), GWL_STYLE, GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_DLGID), GWL_STYLE) | SS_ENDELLIPSIS); + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_SETEVENTMASK, 0, ENM_KEYEVENTS); + { RECT rcDlg, rcWork; + if (SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, FALSE) && GetWindowRect(hwndDlg, &rcDlg)) + SetWindowPos(hwndDlg, 0, rcDlg.left, rcWork.bottom - rcDlg.bottom + rcDlg.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE); + } +#else + { + RECT rc, rcBuf; + SendDlgItemMessage(hwndDlg, IDC_CTLTEXT, EM_GETRECT, 0, (LPARAM)&rcBuf); + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_GETRECT, 0, (LPARAM)&rc); + rc.left = rcBuf.left; // sync richedit offset with edit + rc.right = rcBuf.right; + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_SETRECTNP, 0, (LPARAM)&rc); + } + SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_CTLTEXT), GWLP_USERDATA, SetWindowLong(GetDlgItem(hwndDlg, IDC_CTLTEXT), GWLP_WNDPROC, (LONG)HelpSubclassProc)); + SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_CARETSUCKER), GWLP_USERDATA, SetWindowLong(GetDlgItem(hwndDlg, IDC_CARETSUCKER), GWLP_WNDPROC, (LONG)HelpSubclassProc)); + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_MOUSEEVENTS | ENM_REQUESTRESIZE); + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_SETBKGNDCOLOR, 0, GetSysColor(COLOR_INFOBK)); + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_SETEDITSTYLE, SES_EXTENDBACKCOLOR, SES_EXTENDBACKCOLOR); + SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_TEXT), GWLP_USERDATA, SetWindowLong(GetDlgItem(hwndDlg, IDC_TEXT), GWLP_WNDPROC, (LONG)HelpSubclassProc)); + hwndShadowDlg = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SHADOW), hwndDlg, ShadowDlgProc, (LPARAM)&hwndShadowDlg); + hwndToolTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndDlg, NULL, hInst, NULL); + if (hwndToolTip != NULL) { + SetWindowPos(hwndToolTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + SendMessage(hwndToolTip, TTM_SETTIPBKCOLOR, GetSysColor(COLOR_WINDOW), 0); // yelleow on yellow looks silly + SendMessage(hwndToolTip, TTM_SETDELAYTIME, TTDT_AUTOMATIC, GetDoubleClickTime() * 3); + } + hHandCursor = (HCURSOR)LoadImage(NULL, IDC_HAND, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + if (hHandCursor == NULL) // use fallback out of miranda32.exe + hHandCursor = (HCURSOR)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(214), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); +#endif + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_SETLANGOPTIONS, 0, SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_GETLANGOPTIONS, 0, 0) | IMF_UIFONTS); + SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); + return TRUE; + case WM_SIZE: + { + /* UTILRESIZEDIALOG urd; + ZeroMemory(&urd,sizeof(urd)); + urd.cbSize=sizeof(urd); + urd.hInstance=hInst; + urd.hwndDlg=hwndDlg; + urd.lpTemplate=MAKEINTRESOURCEA(IDD_HELP); + urd.pfnResizer=HelpDialogResize; + CallService(MS_UTILS_RESIZEDIALOG,0,(LPARAM)&urd); */ + Utils_ResizeDialog(hwndDlg, hInst, MAKEINTRESOURCEA(IDD_HELP), HelpDialogResize); + InvalidateRect(hwndDlg, NULL, TRUE); +#ifdef EDITOR + break; +#endif + } +#ifndef EDITOR + case WM_MOVE: + if (IsWindow(hwndShadowDlg)) { + RECT rc; + HRGN hRgnShadow, hRgnDlg; + if (!GetWindowRect(hwndDlg, &rc)) + break; + hRgnShadow = CreateRectRgnIndirect(&rc); + if (hRgnShadow == NULL) + break; + OffsetRgn(hRgnShadow, 5, 5); + hRgnDlg = CreateRectRgnIndirect(&rc); + if (hRgnDlg == NULL) + break; + if (CombineRgn(hRgnShadow, hRgnShadow, hRgnDlg, RGN_DIFF) == ERROR) { + DeleteObject(hRgnShadow); + DeleteObject(hRgnDlg); + break; + } + DeleteObject(hRgnDlg); + OffsetRgn(hRgnShadow, -rc.left - 5, -rc.top - 5); + SetWindowRgn(hwndShadowDlg, hRgnShadow, FALSE); // system gets ownership of hRgnShadow + SetWindowPos(hwndShadowDlg, HWND_TOPMOST, rc.left + 5, rc.top + 5, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); + SetWindowPos(hwndDlg, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + } + break; + case WM_KEYDOWN: + if (GetKeyState(VK_CONTROL) & 0x8000 && wParam == 'C') { + SendMessage(hwndDlg, M_CLIPBOARDCOPY, 0, 0); + return TRUE; + } + break; + case WM_CONTEXTMENU: + { + HMENU hMenu; + POINT pt; + POINTSTOPOINT(pt, MAKEPOINTS(lParam)); + hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, WM_COPY, TranslateT("&Copy")); + if (TrackPopupMenuEx(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_HORPOSANIMATION | TPM_VERPOSANIMATION | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, hwndDlg, NULL)) + SendMessage(hwndDlg, M_CLIPBOARDCOPY, 0, 0); + DestroyMenu(hMenu); + } + return 0; + case WM_LBUTTONDBLCLK: + DestroyWindow(hwndDlg); + return TRUE; + case M_CLIPBOARDCOPY: + { + HWND hwnd = GetFocus(); + if (hwnd == GetDlgItem(hwndDlg, IDC_CTLTEXT) || hwnd == GetDlgItem(hwndDlg, IDC_TEXT)) { + CHARRANGE sel; + ZeroMemory(&sel, sizeof(sel)); + SendMessage(hwnd, EM_GETSEL, (WPARAM)&sel.cpMin, (LPARAM)&sel.cpMax); + if (sel.cpMin != sel.cpMax) { + SendMessage(hwnd, WM_COPY, 0, 0); + return TRUE; + } + } + } + if (OpenClipboard(hwndDlg)) { + HGLOBAL hglb; + int cch, len; + EmptyClipboard(); + hglb = GlobalAlloc(GMEM_MOVEABLE, sizeof(LCID)); + if (hglb != NULL) { + LCID *plocale = (LCID*)GlobalLock(hglb); // look at !! + if (plocale != NULL) { + *plocale = locale; + GlobalUnlock(hglb); + if (!SetClipboardData(CF_LOCALE, hglb)) + GlobalFree(hglb); // shell takes ownership + } + else + GlobalFree(hglb); + } + cch = GetWindowTextLength(GetDlgItem(hwndDlg, IDC_CTLTEXT)) + GetWindowTextLength(GetDlgItem(hwndDlg, IDC_TEXT)) + 3; + hglb = GlobalAlloc(GMEM_MOVEABLE, (cch + 1)*sizeof(TCHAR)); + if (hglb != NULL) { + TCHAR *pszText = (TCHAR*)GlobalLock(hglb); + if (pszText != NULL) { + if (!GetWindowText(GetDlgItem(hwndDlg, IDC_CTLTEXT), pszText, cch - 2)) pszText[0] = _T('\0'); + len = lstrlen(pszText); + if (GetWindowText(GetDlgItem(hwndDlg, IDC_TEXT), pszText + len + 2, cch - 2 - len) && len) { + pszText[len] = _T('\r'); + pszText[len + 1] = _T('\n'); + } + GlobalUnlock(hglb); + if (!SetClipboardData(CF_UNICODETEXT, hglb)) + GlobalFree(hglb); // shell takes ownership + } + else GlobalFree(hglb); + } + CloseClipboard(); + } + return TRUE; + case WM_CTLCOLORDLG: + case WM_CTLCOLORSTATIC: + SetTextColor((HDC)wParam, GetSysColor(COLOR_INFOTEXT)); + SetBkColor((HDC)wParam, GetSysColor(COLOR_INFOBK)); + return (BOOL)GetSysColorBrush(COLOR_INFOBK); + case WM_ACTIVATE: + if (LOWORD(wParam) != WA_INACTIVE) + break; + if (GetParent((HWND)lParam) == hwndDlg) + break; + // fall through + case WM_SYSCOLORCHANGE: + case WM_ACTIVATEAPP: + PostMessage(hwndDlg, WM_CLOSE, 0, 0); // no DestroyWindow() here! would cause recursion + break; +#endif // !defined EDITOR + case M_CHANGEHELPCONTROL: + if (hwndCtl == (HWND)lParam) + break; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + hwndCtl = (HWND)lParam; + SetDlgItemText(hwndDlg, IDC_CTLTEXT, NULL); +#ifdef EDITOR + { + TCHAR text[1024]; + char *szModule, *szDlgId; + + GetControlTitle(hwndCtl, text, sizeof(text)); + SetDlgItemText(hwndDlg, IDC_CTLTEXT, text); + mir_sntprintf(text, sizeof(text), TranslateT("Control ID: %d"), GetControlID(hwndCtl)); + SetDlgItemText(hwndDlg, IDC_CTLID, text); + + szDlgId = CreateDialogIdString(GetControlDialog(hwndCtl)); + mir_sntprintf(text, sizeof(text), TranslateT("Dialog ID: %hs"), (szDlgId != NULL) ? szDlgId : Translate("Unknown")); + mir_free(szDlgId); // does NULL check + SetDlgItemText(hwndDlg, IDC_DLGID, text); + + mir_sntprintf(text, sizeof(text), TranslateT("Type: %s"), TranslateTS(szControlTypeNames[GetControlType(hwndCtl)])); + SetDlgItemText(hwndDlg, IDC_CTLTYPE, text); + + szModule = GetControlModuleName(hwndCtl); + mir_sntprintf(text, sizeof(text), TranslateT("Module: %hs"), szModule ? szModule : Translate("Unknown")); + mir_free(szModule); // does NULL check + SetDlgItemText(hwndDlg, IDC_MODULE, text); + } +#endif // defined EDITOR + SetDlgItemText(hwndDlg, IDC_TEXT, NULL); + SendMessage(hwndDlg, M_LOADHELP, 0, 0); +#ifdef EDITOR + ShowWindow(hwndDlg, SW_SHOWNORMAL); +#else + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_REQUESTRESIZE, 0, 0); + return FALSE; +#endif + return TRUE; + case M_HELPLOADFAILED: + if (hwndCtl != (HWND)lParam) + break; +#ifdef EDITOR + EnableWindow(GetDlgItem(hwndDlg, IDC_TEXT), TRUE); + { + TCHAR text[2024]; + GetControlTitle(hwndCtl, text, sizeof(text)); + SetDlgItemText(hwndDlg, IDC_CTLTEXT, text); + } +#else + SetDlgItemText(hwndDlg, IDC_CTLTEXT, TranslateT("No Help Pack installed!")); +#endif + SetDlgItemText(hwndDlg, IDC_TEXT, NULL); + MessageBeep(MB_ICONERROR); + break; +#ifdef EDITOR + case M_SAVECOMPLETE: +#endif + case M_HELPLOADED: + if (hwndCtl != (HWND)lParam) + break; + case M_LOADHELP: + { + TCHAR *szTitle; + char *szText; + char *szDlgId, *szModule; + UINT codepage; + BOOL isRTL; + int id, loading; +#ifdef EDITOR + EnableWindow(GetDlgItem(hwndDlg, IDC_TEXT), TRUE); +#endif + szDlgId = CreateDialogIdString(GetControlDialog(hwndCtl)); + szModule = GetControlModuleName(hwndCtl); + id = GetControlID(hwndCtl); +#ifndef EDITOR + // show id string instead of help text when 'ctrl' key pressed + if (msg == M_LOADHELP && GetAsyncKeyState(VK_CONTROL) & 0x8000) { + char *buf; + HWND hwnd; + buf = CreateControlIdentifier(szDlgId ? szDlgId : "unknown", szModule ? szModule : "unknown", id, hwndCtl); + hwnd = GetDlgItem(hwndDlg, IDC_CTLTEXT); + SetWindowTextA(hwnd, buf); // accepts NULL + SetDlgItemText(hwndDlg, IDC_TEXT, NULL); + mir_free(buf); // does NULL check + mir_free(szDlgId); // does NULL check + mir_free(szModule); // does NULL check + break; + } +#endif + if (szDlgId == NULL || szModule == NULL) { + SetDlgItemText(hwndDlg, IDC_TEXT, NULL); +#ifndef EDITOR + SetDlgItemText(hwndDlg, IDC_CTLTEXT, TranslateT("No help available for this item.")); +#endif + mir_free(szDlgId); // does NULL check + mir_free(szModule); // does NULL check + break; + } + + loading = GetControlHelp(hwndCtl, szDlgId, szModule, id, &szTitle, &szText, NULL, &locale, &codepage, &isRTL, (msg == M_HELPLOADED) ? GCHF_DONTLOAD : 0); + if (!loading) { + if (szText) + StreamInHtml(GetDlgItem(hwndDlg, IDC_TEXT), szText, codepage, RGB(255, 0, 0)); + else + SetDlgItemText(hwndDlg, IDC_TEXT, NULL); + if (szTitle) { + TCHAR buf[128]; + RECT rc; + HFONT hFontPrev; + DWORD exStyle; + HWND hwndCtlText; + HDC hdc; + hwndCtlText = GetDlgItem(hwndDlg, IDC_CTLTEXT); + exStyle = GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_CTLTEXT), GWL_EXSTYLE); + hdc = GetDC(hwndCtlText); + { + DWORD(WINAPI *pfnGetLayout)(HDC); // obey right-to-left languages + *(FARPROC*)&pfnGetLayout = GetProcAddress(GetModuleHandleA("GDI32"), "GetLayout"); + if (pfnGetLayout) isRTL = (isRTL && !pfnGetLayout(hdc)); + if (isRTL) + exStyle |= WS_EX_RTLREADING | WS_EX_RIGHT; + else + exStyle &= ~(WS_EX_RTLREADING | WS_EX_RIGHT); + } + mir_sntprintf(buf, sizeof(buf) - 4, _T("%s"), szTitle); + if (hdc != NULL && hwndCtlText != NULL) { + SendMessage(hwndCtlText, EM_GETRECT, 0, (LPARAM)&rc); + hFontPrev = (HFONT)SelectObject(hdc, (HFONT)SendMessage(hwndCtlText, WM_GETFONT, 0, 0)); // look at !! + // doesn't actually draw the string due to DT_CALCRECT + DrawTextEx(hdc, buf, -1, &rc, DT_MODIFYSTRING | DT_CALCRECT | DT_EDITCONTROL | DT_END_ELLIPSIS | DT_INTERNAL | (isRTL ? (DT_RTLREADING | DT_RIGHT) : DT_LEFT) | DT_NOPREFIX | DT_SINGLELINE, NULL); + SelectObject(hdc, hFontPrev); + ReleaseDC(hwndCtlText, hdc); + } + SetWindowLongPtr(GetDlgItem(hwndDlg, IDC_CTLTEXT), GWL_EXSTYLE, exStyle); + SetWindowText(hwndCtlText, buf); + } + else + SetDlgItemText(hwndDlg, IDC_CTLTEXT, NULL); + } + else { + if (msg == M_HELPLOADED) { + SetDlgItemText(hwndDlg, IDC_TEXT, NULL); +#ifndef EDITOR + SetDlgItemText(hwndDlg, IDC_CTLTEXT, TranslateT("No help available for this item.")); +#endif + } + else { +#ifdef EDITOR + EnableWindow(GetDlgItem(hwndDlg, IDC_TEXT), FALSE); + SetDlgItemText(hwndDlg, IDC_TEXT, TranslateT("Loading...")); +#else + SetDlgItemText(hwndDlg, IDC_CTLTEXT, TranslateT("Loading...")); + SetDlgItemText(hwndDlg, IDC_TEXT, NULL); +#endif + } + } + mir_free(szDlgId); + mir_free(szModule); + break; + } + case WM_NOTIFY: + switch (((NMHDR*)lParam)->idFrom) { + case IDC_TEXT: + switch (((NMHDR*)lParam)->code) { +#ifdef EDITOR + case EN_MSGFILTER: + switch (((MSGFILTER*)lParam)->msg) { + case WM_CHAR: + switch (((MSGFILTER*)lParam)->wParam) { + case 'B' - 'A' + 1: + case 'I' - 'A' + 1: + case 'U' - 'A' + 1: + case 'H' - 'A' + 1: + case 'L' - 'A' + 1: + case 'S' - 'A' + 1: + case 'G' - 'A' + 1: + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + break; + case WM_KEYDOWN: + { + CHARFORMAT cf; + int changes = 0; + + ZeroMemory(&cf, sizeof(cf)); + if (!(GetKeyState(VK_CONTROL) & 0x8000)) + break; + cf.cbSize = sizeof(cf); + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf); + switch (((MSGFILTER*)lParam)->wParam) { + case 'B': + cf.dwEffects ^= CFE_BOLD; + cf.dwMask = CFM_BOLD; + changes = 1; + break; + case 'I': + cf.dwEffects ^= CFE_ITALIC; + cf.dwMask = CFM_ITALIC; + changes = 1; + break; + case 'U': + cf.dwEffects ^= CFE_UNDERLINE; + cf.dwMask = CFM_UNDERLINE; + changes = 1; + break; + case 'L': + { + CHOOSECOLOR cc; + COLORREF custCol[16]; + ZeroMemory(&custCol, sizeof(custCol)); + ZeroMemory(&cc, sizeof(cc)); + cc.lStructSize = sizeof(cc); + cc.hwndOwner = hwndDlg; + cc.lpCustColors = custCol; + cc.rgbResult = cf.crTextColor; + cc.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT; + if (!ChooseColor(&cc)) + break; + cf.crTextColor = 0; + cf.dwEffects = 0; + if (cc.rgbResult) + cf.crTextColor = cc.rgbResult; + else cf.dwEffects = CFE_AUTOCOLOR; + cf.dwMask = CFM_COLOR; + changes = 1; + break; + } + case 'H': + cf.dwEffects ^= CFE_STRIKEOUT; + cf.dwMask = CFM_STRIKEOUT; + changes = 1; + break; + case VK_OEM_PLUS: + cf.yHeight = ((GetKeyState(VK_SHIFT) & 0x8000) ? TEXTSIZE_BIG : TEXTSIZE_NORMAL) * 10; + cf.dwMask = CFM_SIZE; + changes = 1; + break; + case VK_OEM_MINUS: + cf.yHeight = TEXTSIZE_SMALL * 10; + cf.dwMask = CFM_SIZE; + changes = 1; + break; + case 'S': + { + TCHAR szTitle[1024]; + char *szText, *szDlgId, *szModule; + if (!GetDlgItemText(hwndDlg, IDC_CTLTEXT, szTitle, sizeof(szTitle))) + break; + szDlgId = CreateDialogIdString(GetControlDialog(hwndCtl)); + if (szDlgId == NULL) + break; + szText = StreamOutHtml(GetDlgItem(hwndDlg, IDC_TEXT)); + szModule = GetControlModuleName(hwndCtl); + if (szModule == NULL) { + mir_free(szDlgId); + break; + } + SetControlHelp(szDlgId, szModule, GetControlID(hwndCtl), szTitle, szText, GetControlType(hwndCtl)); + mir_free(szText); + mir_free(szDlgId); + mir_free(szModule); + SaveDialogCache(); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + case 'G': + SendMessage(hwndDlg, M_LOADHELP, 0, 0); + SetWindowLong(hwndDlg, DWL_MSGRESULT, TRUE); + return TRUE; + } + if (changes) { + SendDlgItemMessage(hwndDlg, IDC_TEXT, EM_SETCHARFORMAT, SCF_WORD | SCF_SELECTION, (LPARAM)&cf); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + break; + } + } + break; +#else // defined EDITOR + case EN_MSGFILTER: + { + MSGFILTER *msgf = (MSGFILTER*)lParam; + switch (msgf->msg) { + case WM_LBUTTONUP: + { + POINT pt; + DWORD pos; + CHARRANGE sel; + char *pszLink; + HWND hwndEdit = msgf->nmhdr.hwndFrom; + + ZeroMemory(&sel, sizeof(sel)); + SendMessage(msgf->nmhdr.hwndFrom, EM_EXGETSEL, 0, (LPARAM)&sel); + if (sel.cpMin != sel.cpMax) + break; + POINTSTOPOINT(pt, MAKEPOINTS(msgf->lParam)); + pos = SendMessage(hwndEdit, EM_CHARFROMPOS, 0, (WPARAM)&pt); + if (IsHyperlink(pos, NULL, NULL, &pszLink)) { + Utils_OpenUrl(pszLink); // pszLink is MBCS string in CP_ACP + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + } + break; + case WM_MOUSEMOVE: // register hyperlink tooltips when current + if (hwndToolTip != NULL) { + POINTL pt; + DWORD pos; + CHARRANGE rng; + char *pszLink; + + POINTSTOPOINT(pt, MAKEPOINTS(msgf->lParam)); + pos = SendMessage(msgf->nmhdr.hwndFrom, EM_CHARFROMPOS, 0, (WPARAM)&pt); + if (IsHyperlink(pos, &rng.cpMin, &rng.cpMax, &pszLink)) { // pszLink is MBCS in CP_ACP + TTTOOLINFOA ti = { 0 }; + ti.cbSize = sizeof(ti); + ti.hwnd = msgf->nmhdr.hwndFrom; + ti.uId = (UINT)rng.cpMin; + if (!SendMessage(hwndToolTip, TTM_GETTOOLINFOA, 0, (LPARAM)&ti)) { + LONG cpRectMin; + ti.uFlags = TTF_SUBCLASS; + ti.lpszText = pszLink; + cpRectMin = rng.cpMin; + while (!GetCharRangeRect(ti.hwnd, &rng.cpMin, rng.cpMax, &ti.rect)) { + ti.uId = (UINT)cpRectMin; + SendMessage(hwndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti); + cpRectMin = rng.cpMin; + } + } + } + } + break; + } + } + break; + case EN_REQUESTRESIZE: + { + RECT rcDlg, rcEdit, rcCtl, rcNew; + REQRESIZE *rr = (REQRESIZE*)lParam; + POINT ptScreenBottomRight; + + if (!GetWindowRect(hwndDlg, &rcDlg)) + break; + if (!GetWindowRect(hwndCtl, &rcCtl)) + break; + if (!GetWindowRect(GetDlgItem(hwndDlg, IDC_TEXT), &rcEdit)) + break; + rcNew.left = rcCtl.left + 30; + rcNew.top = rcCtl.bottom + 10; + rcNew.right = rcNew.left + (rr->rc.right - rr->rc.left) + (rcDlg.right - rcDlg.left) - (rcEdit.right - rcEdit.left) + (GetWindowLongPtr(GetDlgItem(hwndDlg, IDC_TEXT), GWL_STYLE)&WS_VSCROLL ? GetSystemMetrics(SM_CXVSCROLL) : 0); + if (GetWindowTextLength(GetDlgItem(hwndDlg, IDC_TEXT))) + rcNew.bottom = rcNew.top + min(GetSystemMetrics(SM_CYSCREEN) / 5, (rr->rc.bottom - rr->rc.top) + (rcDlg.bottom - rcDlg.top) - (rcEdit.bottom - rcEdit.top)); + else + rcNew.bottom = rcNew.top + min(GetSystemMetrics(SM_CYSCREEN) / 5, (rcDlg.bottom - rcDlg.top) - (rcEdit.bottom - rcEdit.top)); + if (GetSystemMetrics(SM_CXVIRTUALSCREEN)) { + ptScreenBottomRight.x = GetSystemMetrics(SM_CXVIRTUALSCREEN) + GetSystemMetrics(SM_XVIRTUALSCREEN); + ptScreenBottomRight.y = GetSystemMetrics(SM_CYVIRTUALSCREEN) + GetSystemMetrics(SM_YVIRTUALSCREEN); + } + else { + ptScreenBottomRight.x = GetSystemMetrics(SM_CXSCREEN); + ptScreenBottomRight.y = GetSystemMetrics(SM_CYSCREEN); + } + if (rcNew.right >= ptScreenBottomRight.x) + OffsetRect(&rcNew, ptScreenBottomRight.x - rcNew.right, 0); + if (rcNew.bottom >= ptScreenBottomRight.y) + OffsetRect(&rcNew, 0, ptScreenBottomRight.y - rcNew.bottom); + SetWindowPos(hwndDlg, 0, rcNew.left, rcNew.top, rcNew.right - rcNew.left, rcNew.bottom - rcNew.top, SWP_NOZORDER); + ShowWindow(hwndDlg, SW_SHOWNORMAL); + break; + } +#endif // defined EDITOR + } + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: // WM_CLOSE + DestroyWindow(hwndDlg); + return TRUE; + } + break; + case WM_DESTROY: +#ifndef EDITOR + FreeHyperlinkData(); + if (IsWindow(hwndShadowDlg)) + DestroyWindow(hwndShadowDlg); + if (hwndToolTip != NULL) + DestroyWindow(hwndToolTip); +#endif + hwndHelpDlg = NULL; + break; + } + return FALSE; +} diff --git a/plugins/ContextHelp/src/helppack.cpp b/plugins/ContextHelp/src/helppack.cpp new file mode 100644 index 0000000000..c8a21105aa --- /dev/null +++ b/plugins/ContextHelp/src/helppack.cpp @@ -0,0 +1,520 @@ +/* +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" + + +/**************************** LOAD PACK ***************************/ + +void TrimStringSimple(char *str) +{ + if (str[lstrlenA(str) - 1] == '\n') + str[lstrlenA(str) - 1] = '\0'; + if (str[lstrlenA(str) - 1] == '\r') + str[lstrlenA(str) - 1] = '\0'; +} + +void TrimString(char *str) +{ + int len, start; + len = lstrlenA(str); + while (str[0] != '\0' && ((unsigned char)str[len - 1] <= ' ')) + str[--len] = 0; + for (start = 0; str[start] && ((unsigned char)str[start] <= ' '); start++); + MoveMemory(str, str + start, len - start + 1); +} + +BOOL IsEmpty(const char *str) +{ + int i; + for (i = 0; str[i] != '\0'; i++) + if (str[i] != ' ' && str[i] != '\r' && str[i] != '\n') + return FALSE; + return TRUE; +} + +static void CleanupLanguage(char *szLanguage, LCID locale) +{ + char *p; + if (PRIMARYLANGID(LANGIDFROMLCID(locale)) != LANG_ENGLISH) { + /* remove any appended ' (default)' */ + p = strstr(szLanguage, " (default)"); + if (p != NULL) + *p = '\0'; + } +} + +static void CleanupAuthors(char *szAuthors) +{ + char *p, *p2; + /* remove trailing dot (if any) */ + p = &szAuthors[lstrlenA(szAuthors) - 1]; + if (*p == '.') + *p = '\0'; + /* remove any extra info in parentheses, which is ok + * but makes the list very long for some packs */ + for (;;) { + p = strchr(szAuthors, '('); + p2 = strchr(szAuthors, ')'); + if (p == NULL || p2 == NULL) { + p = strchr(szAuthors, '['); + p2 = strchr(szAuthors, ']'); + if (p == NULL || p2 == NULL) + break; + } + if (*(p - 1) == ' ') + --p; + MoveMemory(p, p2 + 1, lstrlenA(p2 + 1) + 1); + } +} + +static void CleanupEmail(char *szAuthorEmail) +{ + char c, *p, *pAt; + /* replace ' dot ' with '.' (may be removed) */ + p = strstr(szAuthorEmail, " dot "); + if (p != NULL) { + *p = '.'; + MoveMemory(p + 1, p + 5, lstrlenA(p + 5) + 1); + } + /* also allow ' at ' instead of '@' for obfuscation */ + p = strstr(szAuthorEmail, " at "); + if (p != NULL) { + *p = '@'; + MoveMemory(p + 1, p + 4, lstrlenA(p + 4) + 1); + } + /* is valid? */ + pAt = strchr(szAuthorEmail, '@'); + if (pAt == NULL) { + szAuthorEmail[0] = '\0'; + return; + } + /* strip-off extra text except exactly one email address + * this is needed as a click on the email addres brings up the mail client */ + for (c = ' ';; c = ',') { + p = strchr(pAt, c); + if (p != NULL) + *p = '\0'; + p = strrchr(szAuthorEmail, c); + if (p != NULL) + MoveMemory(szAuthorEmail, p + 1, lstrlenA(p + 1) + 1); + if (c == ',') + break; + } + p = strstr(szAuthorEmail, "__"); + if (p != NULL) + MoveMemory(szAuthorEmail, p + 2, lstrlenA(p + 2) + 1); + /* lower case */ + CharLowerA(szAuthorEmail); + /* 'none' specified */ + if (!lstrcmpiA(szAuthorEmail, "none")) + szAuthorEmail[0] = '\0'; +} + +static void CleanupLastModifiedUsing(char *szLastModifiedUsing, int nSize) +{ + char *p; + /* remove 'Unicode', as it doesn't matter */ + p = strstr(szLastModifiedUsing, " Unicode"); + if (p != NULL) MoveMemory(p, p + 8, lstrlenA(p + 8) + 1); + /* use 'Miranda IM' instead of 'Miranda' */ + p = strstr(szLastModifiedUsing, "Miranda"); + if (p != NULL && strncmp(p + 7, " IM", 3)) { + MoveMemory(p + 10, p + 7, lstrlenA(p + 7) + 1); + CopyMemory(p + 7, " IM", 3); + } + /* use 'Plugin' instead of 'plugin' */ + p = strstr(szLastModifiedUsing, " plugin"); + if (p != NULL) + CopyMemory(p, " Plugin", 7); + /* remove 'v' prefix */ + p = strstr(szLastModifiedUsing, " v0."); + if (p != NULL) + MoveMemory(p + 1, p + 2, lstrlenA(p + 2) + 1); + /* default if empty */ + if (!szLastModifiedUsing[0]) { + lstrcpynA(szLastModifiedUsing, MIRANDANAME" ", nSize); + CallService(MS_SYSTEM_GETVERSIONTEXT, nSize - lstrlenA(szLastModifiedUsing), (LPARAM)szLastModifiedUsing + lstrlenA(szLastModifiedUsing)); + } +} + +// pack struct should be initialized to zero before call +// pack->szFileName needs to be filled in before call +static BOOL LoadPackData(HELPPACK_INFO *pack, BOOL fEnabledPacks, const char *pszFileVersionHeader) +{ + FILE *fp; + TCHAR szFileName[MAX_PATH]; + char line[4096], *pszColon, *buf; + char szLanguageA[64]; /* same size as pack->szLanguage */ + /* + Miranda Help Pack Version 1 + Language: (optional) + Locale: 0809 + Authors: Miranda NG Development Team (multiple tags allowed) + Author-email: project-info at miranda-ng.org (" at " instead of "@" allowed) + Last-Modified-Using: Miranda IM 0.7 + Plugins-included: (multiple tags allowed) + X-FLName: name as used on the file listing (non-standard extension) + X-Version: 1.2.3.4 (non-standard extension) + see 'Help-Translation.txt' for some header quidelines + */ + if (!GetPackPath(szFileName, sizeof(szFileName), fEnabledPacks, pack->szFileName)) + return FALSE; + fp = _tfopen(szFileName, _T("rt")); + if (fp == NULL) + return FALSE; + fgets(line, sizeof(line), fp); + TrimString(line); + if (lstrcmpA(line, pszFileVersionHeader)) { + fclose(fp); + return FALSE; + } + pack->flags = HPF_NOLOCALE; + pack->Locale = LOCALE_USER_DEFAULT; + szLanguageA[0] = '\0'; + while (!feof(fp)) { + if (fgets(line, sizeof(line), fp) == NULL) + break; + TrimString(line); + if (IsEmpty(line) || line[0] == ';' || line[0] == '\0') + continue; + if (line[0] == '[') + break; + pszColon = strchr(line, ':'); + if (pszColon == NULL) + continue; + *pszColon = '\0'; + TrimString(pszColon + 1); + if (!lstrcmpA(line, "Language") && !pack->szLanguage[0]) + lstrcpynA(szLanguageA, pszColon + 1, sizeof(szLanguageA)); /* buffer safe */ + else if (!lstrcmpA(line, "Last-Modified-Using") && !pack->szLastModifiedUsing[0]) + lstrcpynA(pack->szLastModifiedUsing, pszColon + 1, sizeof(pack->szLastModifiedUsing)); /* buffer safe */ + else if (!lstrcmpA(line, "Authors")) { + buf = pack->szAuthors + lstrlenA(pack->szAuthors); /* allow multiple tags */ + if ((sizeof(pack->szAuthors) - lstrlenA(pack->szAuthors))>0) /* buffer safe */ + mir_snprintf(buf, sizeof(pack->szAuthors) - lstrlenA(pack->szAuthors), (pack->szAuthors[0] == '\0') ? "%s" : " %s", pszColon + 1); + } + else if (!lstrcmpA(line, "Author-email") && !pack->szAuthorEmail[0]) + lstrcpynA(pack->szAuthorEmail, pszColon + 1, sizeof(pack->szAuthorEmail)); /* buffer safe */ + else if (!lstrcmpA(line, "Locale") && pack->flags&HPF_NOLOCALE) { + pack->Locale = MAKELCID((USHORT)strtol(pszColon + 1, NULL, 16), SORT_DEFAULT); + if (pack->Locale) + pack->flags &= ~HPF_NOLOCALE; + } + else if (!lstrcmpA(line, "Plugins-included")) { + buf = pack->szPluginsIncluded + lstrlenA(pack->szPluginsIncluded); /* allow multiple tags */ + if ((sizeof(pack->szPluginsIncluded) - lstrlenA(pack->szPluginsIncluded))>0) /* buffer safe */ + mir_snprintf(buf, sizeof(pack->szPluginsIncluded) - lstrlenA(pack->szPluginsIncluded), (pack->szPluginsIncluded[0] == '\0') ? "%s" : ", %s", CharLowerA(pszColon + 1)); + } + else if (!lstrcmpA(line, "X-Version") && !pack->szVersion[0]) + lstrcpynA(pack->szVersion, pszColon + 1, sizeof(pack->szVersion)); /* buffer safe */ + else if (!lstrcmpA(line, "X-FLName") && !pack->szFLName[0]) + lstrcpynA(pack->szFLName, pszColon + 1, sizeof(pack->szFLName)); /* buffer safe */ + } + /* default */ + if (PRIMARYLANGID(LANGIDFROMLCID(pack->Locale)) == LANG_ENGLISH && strstr(szLanguageA, " (default)") != NULL) + pack->flags |= HPF_DEFAULT; + CleanupLanguage(szLanguageA, pack->Locale); + CleanupAuthors(pack->szAuthors); + CleanupEmail(pack->szAuthorEmail); + CleanupLastModifiedUsing(pack->szLastModifiedUsing, sizeof(pack->szLastModifiedUsing)); + /* codepage */ + if (!(pack->flags&HPF_NOLOCALE)) + if (GetLocaleInfoA(pack->Locale, LOCALE_IDEFAULTANSICODEPAGE, line, 6)) + pack->codepage = (WORD)atoi(line); /* CP_ACP on error */ + /* language */ + MultiByteToWideChar(pack->codepage, 0, szLanguageA, -1, pack->szLanguage, sizeof(pack->szLanguage)); + /* ensure the pack always has a language name */ + if (!pack->szLanguage[0] && !GetLocaleInfo(pack->Locale, LOCALE_SENGLANGUAGE, pack->szLanguage, sizeof(pack->szLanguage))) { + TCHAR *p; + lstrcpyn(pack->szLanguage, pack->szFileName, sizeof(pack->szLanguage)); /* buffer safe */ + p = _tcsrchr(pack->szLanguage, _T('.')); + if (p != NULL) + *p = '\0'; + } + /* ensure the pack always has a filelisting name */ + if (!pack->szFLName[0]) + lstrcatA(lstrcpyA(pack->szFLName, szLanguageA), " Help Pack"); /* buffer safe */ + fclose(fp); + + return TRUE; +} + +/**************************** ENUM PACKS **************************/ + +BOOL GetPackPath(TCHAR *pszPath, int nSize, BOOL fEnabledPacks, const TCHAR *pszFile) +{ + TCHAR *p; + /* main path */ + if (!GetModuleFileName(NULL, pszPath, nSize)) + return FALSE; + p = _tcsrchr(pszPath, _T('\\')); + if (p != NULL) + *(p + 1) = _T('\0'); + /* subdirectory */ + if (!fEnabledPacks) { + if (nSize<(lstrlen(pszPath) + 10)) + return FALSE; + lstrcat(pszPath, _T("Languages\\")); + } + /* file name */ + if (pszFile != NULL) { + if (nSize < (lstrlen(pszFile) + 11)) + return FALSE; + lstrcat(pszPath, pszFile); + } + + return TRUE; +} + +// callback is allowed to be NULL +// returns TRUE if any pack exists except default +BOOL EnumPacks(ENUM_PACKS_CALLBACK callback, const TCHAR *pszFilePattern, const char *pszFileVersionHeader, WPARAM wParam, LPARAM lParam) +{ + BOOL fPackFound = FALSE; + BOOL res = FALSE; + HELPPACK_INFO pack; + WIN32_FIND_DATA wfd; + HANDLE hFind; + + /* enabled packs */ + if (GetPackPath(pack.szFileName, sizeof(pack.szFileName), TRUE, pszFilePattern)) { + hFind = FindFirstFile(pack.szFileName, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + continue; + if ((lstrlen(wfd.cFileName) < 4) || wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.')) + continue; + + /* get data */ + ZeroMemory(&pack, sizeof(pack)); + lstrcpy(pack.szFileName, CharLower(wfd.cFileName)); /* buffer safe */ + if (LoadPackData(&pack, TRUE, pszFileVersionHeader)) { + pack.ftFileDate = wfd.ftLastWriteTime; + + /* enabled? */ + if (!fPackFound) + pack.flags |= HPF_ENABLED; + fPackFound = TRUE; + + /* callback */ + if (callback != NULL) + res = callback(&pack, wParam, lParam); + if (!res) { + FindClose(hFind); + return FALSE; + } + } + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + } + + /* disabled packs */ + if (GetPackPath(pack.szFileName, sizeof(pack.szFileName), FALSE, pszFilePattern)) { + hFind = FindFirstFile(pack.szFileName, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + continue; + if (lstrlen(wfd.cFileName) < 4 || wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.')) + continue; + + /* get data */ + ZeroMemory(&pack, sizeof(pack)); + lstrcpy(pack.szFileName, CharLower(wfd.cFileName)); /* buffer safe */ + if (LoadPackData(&pack, FALSE, pszFileVersionHeader)) { + pack.ftFileDate = wfd.ftLastWriteTime; + fPackFound = TRUE; + + /* callback */ + if (callback != NULL) + res = callback(&pack, wParam, lParam); + if (!res) { + FindClose(hFind); + return FALSE; + } + } + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + } + + return fPackFound; +} + +BOOL IsPluginIncluded(const HELPPACK_INFO *pack, char *pszFileBaseName) +{ + char *p; + if (!lstrcmpiA(pszFileBaseName, "png2dib") || !lstrcmpiA(pszFileBaseName, "loadavatars")) + return TRUE; /* workaround: does not need no translation */ + for (p = (char*)pack->szPluginsIncluded;;) { + p = strstr(p, CharLowerA(pszFileBaseName)); + if (p == NULL) + return FALSE; + if (p == pack->szPluginsIncluded || *(p - 1) == ' ' || *(p - 1) == ',') { + p += lstrlenA(pszFileBaseName) + 1; + if (*p == ',' || *p == ' ' || *p == 0) + return TRUE; + } + else + p += lstrlenA(pszFileBaseName) + 1; + } + + return FALSE; +} + +/**************************** SWITCH PACKS ************************/ + +BOOL EnablePack(const HELPPACK_INFO *pack, const TCHAR *pszFilePattern) +{ + TCHAR szFrom[MAX_PATH], szDest[MAX_PATH]; + + /* disable previous pack */ + if (GetPackPath(szFrom, sizeof(szFrom), TRUE, pszFilePattern)) { + WIN32_FIND_DATA wfd; + HANDLE hFind = FindFirstFile(szFrom, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + continue; + if (lstrlen(wfd.cFileName) < 4 || wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.')) + continue; + /* ensure dir exists */ + if (GetPackPath(szFrom, sizeof(szFrom), FALSE, NULL)) + CreateDirectory(szFrom, NULL); + /* move file */ + if (GetPackPath(szFrom, sizeof(szFrom), TRUE, wfd.cFileName)) + if (GetPackPath(szDest, sizeof(szDest), FALSE, wfd.cFileName)) + if (!MoveFile(szFrom, szDest) && GetLastError() == ERROR_ALREADY_EXISTS) { + DeleteFile(szDest); + MoveFile(szFrom, szDest); + } + break; + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + } + + /* enable current pack */ + if (GetPackPath(szFrom, sizeof(szFrom), FALSE, pack->szFileName)) + if (GetPackPath(szDest, sizeof(szDest), TRUE, pack->szFileName)) + return MoveFile(szFrom, szDest); + + return FALSE; +} + +void CorrectPacks(const TCHAR *pszFilePattern, const TCHAR *pszDefaultFile, BOOL fDisableAll) +{ + TCHAR szFrom[MAX_PATH], szDest[MAX_PATH], szDir[MAX_PATH], *pszFile; + BOOL fDirCreated = FALSE, fOneEnabled = FALSE; + HANDLE hFind; + WIN32_FIND_DATA wfd; + + /* main path */ + if (!GetModuleFileName(NULL, szDir, sizeof(szDir))) + return; + pszFile = _tcsrchr(szDir, _T('\\')); + if (pszFile != NULL) + *pszFile = _T('\0'); + + /* move wrongly placed packs from 'Plugins' to 'Language' */ + mir_sntprintf(szFrom, sizeof(szFrom), _T("%s\\Plugins\\%s"), szDir, pszFilePattern); + hFind = FindFirstFile(szFrom, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + continue; + if (lstrlen(wfd.cFileName) < 4 || wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.')) + continue; + + /* ensure dir exists */ + if (!fDirCreated && GetPackPath(szFrom, sizeof(szFrom), FALSE, NULL)) + fDirCreated = CreateDirectory(szFrom, NULL); + + /* move file */ + if (GetPackPath(szDest, sizeof(szDest), FALSE, wfd.cFileName)) { + mir_sntprintf(szFrom, sizeof(szFrom), _T("%s\\Plugins\\%s"), szDir, wfd.cFileName); + if (!MoveFile(szFrom, szDest) && GetLastError() == ERROR_ALREADY_EXISTS) { + DeleteFile(szDest); + MoveFile(szFrom, szDest); + } + } + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + + /* disable all packs except one */ + if (GetPackPath(szFrom, sizeof(szFrom), TRUE, pszFilePattern)) { + hFind = FindFirstFile(szFrom, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + continue; + if (lstrlen(wfd.cFileName) < 4 || wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.')) + continue; + /* skip first file */ + fOneEnabled = TRUE; + if (!fDisableAll) { + fDisableAll = TRUE; + continue; + } + /* ensure dir exists */ + if (!fDirCreated && GetPackPath(szFrom, sizeof(szFrom), FALSE, NULL)) + fDirCreated = CreateDirectory(szFrom, NULL); + /* move file */ + if (GetPackPath(szFrom, sizeof(szFrom), TRUE, wfd.cFileName)) + if (GetPackPath(szDest, sizeof(szDest), FALSE, wfd.cFileName)) { + if (!MoveFile(szFrom, szDest) && GetLastError() == ERROR_ALREADY_EXISTS) { + DeleteFile(szDest); + MoveFile(szFrom, szDest); + } + } + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + } + + /* ensure one is enabled if installed */ + if (!fOneEnabled) { + /* try to move english */ + if (GetPackPath(szFrom, sizeof(szFrom), FALSE, pszDefaultFile)) + if (GetPackPath(szDest, sizeof(szDest), TRUE, pszDefaultFile)) + fOneEnabled = MoveFile(szFrom, szDest); + /* fallback on other one */ + if (!fOneEnabled) + if (GetPackPath(szFrom, sizeof(szFrom), FALSE, pszFilePattern)) { + hFind = FindFirstFile(szFrom, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + do { + if (wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + continue; + if (lstrlen(wfd.cFileName) < 4 || wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.')) + continue; + /* move first file */ + if (GetPackPath(szFrom, sizeof(szFrom), FALSE, wfd.cFileName)) + if (GetPackPath(szDest, sizeof(szDest), TRUE, wfd.cFileName)) + MoveFile(szFrom, szDest); + break; + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + } + } +} diff --git a/plugins/ContextHelp/src/main.cpp b/plugins/ContextHelp/src/main.cpp new file mode 100644 index 0000000000..66cc756c67 --- /dev/null +++ b/plugins/ContextHelp/src/main.cpp @@ -0,0 +1,128 @@ +/* +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" + +HINSTANCE hInst; +extern HWND hwndHelpDlg; + +int hLangpack; + +PLUGININFOEX pluginInfo = { + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __AUTHOREMAIL, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {B16DF0B2-A08F-4bc6-8FA2-3D5FFFA2708D} + { 0xb16df0b2, 0xa08f, 0x4bc6, { 0x8f, 0xa2, 0x3d, 0x5f, 0xff, 0xa2, 0x70, 0x8d } } +}; + + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD, LPVOID) +{ + hInst = hinstDLL; + return TRUE; +} + +static void InstallFile(const TCHAR *pszFileName, const TCHAR *pszDestSubDir) +{ + TCHAR szFileFrom[MAX_PATH + 1], szFileTo[MAX_PATH + 1], *p; + HANDLE hFile; + + if (!GetModuleFileName(hInst, szFileFrom, sizeof(szFileFrom) - lstrlen(pszFileName))) + return; + p = _tcsrchr(szFileFrom, _T('\\')); + if (p != NULL) + *(++p) = 0; + lstrcat(szFileFrom, pszFileName); /* buffer safe */ + + hFile = CreateFile(szFileFrom, 0, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return; + CloseHandle(hFile); + + if (!GetModuleFileName(NULL, szFileTo, sizeof(szFileTo) - lstrlen(pszDestSubDir) - lstrlen(pszFileName))) + return; + p = _tcsrchr(szFileTo, _T('\\')); + if (p != NULL) + *(++p) = 0; + lstrcat(szFileTo, pszDestSubDir); /* buffer safe */ + CreateDirectory(szFileTo, NULL); + lstrcat(szFileTo, pszFileName); /* buffer safe */ + + if (!MoveFile(szFileFrom, szFileTo) && GetLastError() == ERROR_ALREADY_EXISTS) { + DeleteFile(szFileTo); + MoveFile(szFileFrom, szFileTo); + } +} + + +extern "C" __declspec(dllexport) PLUGININFOEX *MirandaPluginInfoEx(DWORD) +{ + return &pluginInfo; +} + + +extern "C" __declspec(dllexport) int Load(void) +{ + mir_getLP(&pluginInfo); + + INITCOMMONCONTROLSEX icc = { 0 }; + icc.dwSize = sizeof(icc); + icc.dwICC = ICC_UPDOWN_CLASS | ICC_TREEVIEW_CLASSES; + if (!InitCommonControlsEx(&icc)) + return 1; + + if (LoadLibrary(_T("riched20.dll")) == NULL) /* richedit v2.0 (Win98/NT4), v3.0 (WinXP/2000/Me+) */ + if (IDYES != MessageBoxEx(NULL, + TranslateT("The Help Plugin can not be loaded, riched20.dll is missing. If you are using WINE please make sure you have riched20.dll installed.\n\nPress 'Yes' to continue loading Miranda NG."), + TranslateT("Help Plugin"), MB_YESNO | MB_ICONWARNING | MB_SETFOREGROUND | MB_TOPMOST | MB_TASKMODAL, LANGIDFROMLCID(Langpack_GetDefaultLocale()))) + return 1; + + if (InstallDialogBoxHook()) + return 1; + + InitOptions(); + InitDialogCache(); + // InitUpdate(); + + // InstallFile(_T("Help-Readme.txt"),_T("Docs\\")); + // InstallFile(_T("Help-License.txt"),_T("Docs\\")); + // InstallFile(_T("Help-SDK.zip"),_T("Docs\\")); + + return 0; +} + +extern "C" __declspec(dllexport) int Unload(void) +{ + UninitOptions(); + // UninitUpdate(); + RemoveDialogBoxHook(); + + if (hwndHelpDlg != NULL) + DestroyWindow(hwndHelpDlg); + FreeDialogCache(); + + return 0; +} diff --git a/plugins/ContextHelp/src/options.cpp b/plugins/ContextHelp/src/options.cpp new file mode 100644 index 0000000000..1078bd2e4e --- /dev/null +++ b/plugins/ContextHelp/src/options.cpp @@ -0,0 +1,797 @@ +/* +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" + +typedef PLUGININFOEX* (__cdecl *pfnMirandaPluginInfo)(DWORD); + +WORD settingAutoTipDelay; /* auto show help after those milliseconds, 0 if disabled */ +extern HINSTANCE hInst; +static HANDLE hHookOptInit; +static HWND hwndLangOpt; + +/**************************** LANGUAGE OPTS ***********************/ + +/* these are only there for a short time to +* debug those radio buttons an Win9x */ +#define BOX(str) BOX2("%s (err:%i)",str,GetLastError()) +#define BOX2(fmt,p1,p2) { char str[256]; mir_snprintf(str,fmt,p1,p2); MessageBoxA(NULL,str,"dbg",0); } + +// ImageList_Destroy() the return value +// refresh on WM_THEMECHANGED +static HIMAGELIST CreateRadioImages(COLORREF clrBk, COLORREF clrText) +{ + /* draw bitmap */ + HDC hdcScreen = GetDC(NULL); + if (hdcScreen == NULL) + return NULL; + + HIMAGELIST himl = NULL; + HDC hdc = CreateCompatibleDC(NULL); // compatible to screen + if (hdc != NULL) { + SIZE size = { GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON) }; + RECT rc; + SetRect(&rc, 0, 0, 2 * size.cx, size.cy); + HBITMAP hbm = CreateCompatibleBitmap(hdcScreen, rc.right, rc.bottom); + if (hbm != NULL) { + HBITMAP hbmPrev = (HBITMAP)SelectObject(hdc, hbm); + if (hbmPrev != NULL) { // error on select? + HTHEME hTheme = OpenThemeData(NULL, L"Button"); + SetRect(&rc, 0, 0, size.cx, size.cy); + /* unchecked */ + if (!DrawThemeBackground(hTheme, hdc, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL, &rc, NULL)) { + /* checked */ + OffsetRect(&rc, size.cx, 0); + if (!DrawThemeBackground(hTheme, hdc, BP_RADIOBUTTON, RBS_CHECKEDNORMAL, &rc, NULL)) + himl = ImageList_Create(size.cx, size.cy, ILC_COLOR32 | ILC_MASK, 3, 0); + } + CloseThemeData(hTheme); + + /* the classic way */ + if (himl == NULL) { + HBRUSH hbrBk = CreateSolidBrush(clrBk); + if (hbrBk != NULL) { + FillRect(hdc, &rc, hbrBk); + DeleteObject(hbrBk); + HDC hdcMono = CreateCompatibleDC(hdc); + if (hdcMono != NULL) { + HBITMAP hbmMono = CreateBitmap(rc.right, rc.bottom, 1, 1, NULL); + if (hbmMono != NULL) { + HBITMAP hbmPrevMono = (HBITMAP)SelectObject(hdcMono, hbmMono); + if (hbmPrevMono != NULL) { /* error on select? */ + /* draws a black-and-white mask (see docs) + * we need to colorize it using BitBlt with text and background color */ + COLORREF clrPrevText = SetTextColor(hdc, clrText); + COLORREF clrPrevBk = SetBkColor(hdc, clrBk); + /* check mark is slightly smaller than icon size */ + RECT rcRadio; + SetRect(&rcRadio, 0, 0, GetSystemMetrics(SM_CXMENUCHECK), GetSystemMetrics(SM_CYMENUCHECK)); + if (rcRadio.right>size.cx) + rcRadio.right = size.cx; + if (rcRadio.bottom>size.cy) + rcRadio.bottom = size.cy; + SetRect(&rc, ((size.cx - rcRadio.right) / 2) + 1, ((size.cy - rcRadio.bottom) / 2) + 1, rcRadio.right + 1, rcRadio.bottom + 1); + /* unchecked */ + if (BitBlt(hdcMono, 0, 0, rcRadio.right, rcRadio.bottom, NULL, 0, 0, WHITENESS)) { /* white back */ + if (DrawFrameControl(hdcMono, &rcRadio, DFC_BUTTON, DFCS_BUTTONRADIO | DFCS_FLAT)) { + if (BitBlt(hdc, rc.left, rc.top, rcRadio.right, rcRadio.bottom, hdcMono, 0, 0, SRCCOPY | NOMIRRORBITMAP)) { + /* checked */ + OffsetRect(&rc, size.cx, 0); + if (BitBlt(hdcMono, 0, 0, rcRadio.right, rcRadio.bottom, NULL, 0, 0, WHITENESS)) {/* white back */ + if (DrawFrameControl(hdcMono, &rcRadio, DFC_BUTTON, DFCS_BUTTONRADIO | DFCS_FLAT | DFCS_CHECKED)) { + if (BitBlt(hdc, rc.left, rc.top, rcRadio.right, rcRadio.bottom, hdcMono, 0, 0, SRCCOPY | NOMIRRORBITMAP)) { + himl = ImageList_Create(size.cx, size.cy, ILC_COLOR | ILC_MASK, 3, 0); + if (himl == NULL) + BOX("img list creation failed"); + } + } + else + BOX("second DrawFrameControl() failed"); + } + else + BOX("second BitBlt() failed"); + } + else + BOX("intermediate BitBlt() failed"); + } + else + BOX("DrawFrameControl() failed"); + } + else + BOX("first BitBlt() failed"); + /* restore */ + SetBkColor(hdc, clrPrevBk); + SetTextColor(hdc, clrPrevText); + SelectObject(hdcMono, hbmPrevMono); + } + else + BOX("hbmPrevMono==NULL"); + DeleteObject(hbmMono); + } + else + BOX("hbmMono==NULL"); + DeleteDC(hdcMono); + } + else + BOX("hdcMono==NULL"); + } + } + SelectObject(hdc, hbmPrev); + /* create imagelist */ + if (himl != NULL) { + if (ImageList_AddMasked(himl, hbm, clrBk) == -1) + BOX("addmasked failed"); + } + else + BOX("Win9x: drawing code not reached"); + } + DeleteObject(hbm); + } + DeleteDC(hdc); + } + ReleaseDC(NULL, hdcScreen); + + return himl; +} + +static void CleanupPluginName(char *szShortName) +{ + char *p; + /* strip-off anything in brackets */ + for (p = szShortName; *p != '\0'; ++p) + if (*p == '(' || *p == '[') { + *p = '\0'; + break; + } + /* remove trailing space */ + int len = lstrlenA(szShortName); + while (szShortName[0] != '\0' && szShortName[len - 1] == ' ') + szShortName[--len] = 0; +} + +static void DisplayNotIncludedPlugins(HWND hwndListBox, const HELPPACK_INFO *pack) +{ + /* enum plugins */ + TCHAR szDir[MAX_PATH]; + if (GetModuleFileName(NULL, szDir, sizeof(szDir))) { + TCHAR *p = _tcsrchr(szDir, _T('\\')); + if (p != NULL) + *p = _T('\0'); + TCHAR szSearch[MAX_PATH]; + mir_sntprintf(szSearch, sizeof(szSearch), _T("%s\\Plugins\\*.dll"), szDir); + WIN32_FIND_DATA wfd; + HANDLE hFind = FindFirstFile(szSearch, &wfd); + if (hFind != INVALID_HANDLE_VALUE) { + DWORD mirandaVersion = CallService(MS_SYSTEM_GETVERSION, 0, 0); + SendMessage(hwndListBox, LB_SETLOCALE, Langpack_GetDefaultLocale(), 0); /* for sort order */ + SendMessage(hwndListBox, LB_INITSTORAGE, 128, lstrlenA(pack->szPluginsIncluded)); /* speed up */ + do { + if (wfd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + continue; + if ((lstrlen(wfd.cFileName)<4) || (wfd.cFileName[lstrlen(wfd.cFileName) - 4] != _T('.'))) + continue; + /* file name */ + lstrcpy(szSearch, wfd.cFileName); /* buffer safe */ + p = _tcsrchr(szSearch, _T('.')); + if (p != NULL) + *p = _T('\0'); + char cFileNameA[MAX_PATH]; + cFileNameA[0] = '\0'; + WideCharToMultiByte(CP_ACP, 0, szSearch, -1, cFileNameA, sizeof(cFileNameA), NULL, NULL); + if (IsPluginIncluded(pack, cFileNameA)) + continue; + + /* friendly name of the plugin */ + mir_sntprintf(szSearch, sizeof(szSearch), _T("%s\\Plugins\\%s"), szDir, wfd.cFileName); + HMODULE hModule = GetModuleHandle(szSearch); + BOOL fNeedsFree = (hModule == NULL); + if (hModule == NULL) { + hModule = LoadLibrary(szSearch); + if (hModule == NULL) + continue; + } + + /* plugin info */ + pfnMirandaPluginInfo pFunc = (pfnMirandaPluginInfo)GetProcAddress(hModule, "MirandaPluginInfoEx"); + if (pFunc != NULL) { /* both structs have the same header */ + PLUGININFOEX *pluginInfo = pFunc(mirandaVersion); + if (pluginInfo != NULL && pluginInfo->cbSize >= sizeof(PLUGININFOEX) && pluginInfo->shortName != NULL) { + char buf[128]; + lstrcpynA(buf, pluginInfo->shortName, sizeof(buf)); /* buffer safe */ + CleanupPluginName(buf); + + TCHAR buf2[128]; + mir_sntprintf(buf2, sizeof(buf2), _T("%hs (%s)"), buf, CharLower(wfd.cFileName)); + SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)buf2); + } + } + if (fNeedsFree) + FreeLibrary(hModule); + } while (FindNextFile(hFind, &wfd)); + FindClose(hFind); + } + } + /* all are included? */ + if (!SendMessage(hwndListBox, LB_GETCOUNT, 0, 0)) + SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TranslateT("All installed plugins are included.")); +} + +// pack is allowed to be NULL +static void DisplayPackInfo(HWND hwndDlg, const HELPPACK_INFO *pack) +{ + /* show/hide controls */ + if (IsWindowVisible(GetDlgItem(hwndDlg, IDC_NOPACK)) == (pack != NULL)) { + const int controls[] = { IDC_LANGNOTINCLUDEDLABEL, IDC_LANGNOTINCLUDED, IDC_LANGDATELABEL, IDC_LANGDATE, + IDC_LANGLOCALELABEL, IDC_LANGLOCALE, IDC_LANGVERSIONLABEL, IDC_LANGVERSION, + IDC_LANGMODUSINGLABEL, IDC_LANGMODUSING, IDC_LANGAUTHORSLABEL, IDC_LANGAUTHORS, + IDC_LANGEMAILLABEL, IDC_LANGEMAIL }; + for (int i = 0; i < sizeof(controls); i++) + ShowWindow(GetDlgItem(hwndDlg, controls[i]), (pack != NULL) ? SW_SHOW : SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_NOPACK), (pack != NULL) ? SW_HIDE : SW_SHOW); + SetDlgItemText(hwndDlg, IDC_LANGINFOFRAME, NULL); + } + if (pack == NULL) + return; + /* compute not-included from included list */ + SendDlgItemMessage(hwndDlg, IDC_LANGNOTINCLUDED, LB_RESETCONTENT, 0, 0); + DisplayNotIncludedPlugins(GetDlgItem(hwndDlg, IDC_LANGNOTINCLUDED), pack); + /* locale string */ + if (!(pack->flags&HPF_NOLOCALE)) { + TCHAR szLocaleName[128]; + szLocaleName[0] = _T('\0'); + /* can't use LOCALE_SNAME as it is not present on pre WinVista */ + if (!GetLocaleInfo(pack->Locale, LOCALE_SISO639LANGNAME, szLocaleName, sizeof(szLocaleName))) { /* Win98/NT4+ */ + if (!GetLocaleInfo(pack->Locale, LOCALE_SLANGUAGE, szLocaleName, sizeof(szLocaleName))) /* not unique! */ + szLocaleName[0] = _T('\0'); + } + else { + if (GetLocaleInfo(pack->Locale, LOCALE_SISO3166CTRYNAME, &szLocaleName[3], sizeof(szLocaleName) - 3)) /* Win98/NT4+ */ + szLocaleName[2] = _T('-'); + } + /* add some note if its incompatible */ + if (szLocaleName[0]) { + if (!IsValidLocale(pack->Locale, LCID_INSTALLED)) { + TCHAR *pszIncompat; + pszIncompat = TranslateT("(incompatible)"); + szLocaleName[sizeof(szLocaleName) - lstrlen(pszIncompat) - 1] = 0; + lstrcat(lstrcat(szLocaleName, _T(" ")), pszIncompat); /* buffer safe */ + } + SetDlgItemText(hwndDlg, IDC_LANGLOCALE, szLocaleName); + } + else + SetDlgItemText(hwndDlg, IDC_LANGLOCALE, TranslateT("Unknown")); + } + else + SetDlgItemText(hwndDlg, IDC_LANGLOCALE, TranslateT("Current")); + + /* file date */ + { + SYSTEMTIME stFileDate; + TCHAR szDate[128]; + szDate[0] = _T('\0'); + if (FileTimeToSystemTime(&pack->ftFileDate, &stFileDate)) + GetDateFormat(Langpack_GetDefaultLocale(), DATE_SHORTDATE, &stFileDate, NULL, szDate, sizeof(szDate)); + SetDlgItemText(hwndDlg, IDC_LANGDATE, szDate); + } + + /* version */ + SetDlgItemTextA(hwndDlg, IDC_LANGVERSION, pack->szVersion); + if (pack->szVersion[0] && pack->szFLName[0]) { + if (!IsWindowVisible(GetDlgItem(hwndDlg, IDC_LANGVERSIONLABEL))) { + ShowWindow(GetDlgItem(hwndDlg, IDC_LANGVERSIONLABEL), SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_LANGVERSION), SW_SHOW); + } + } + else { + ShowWindow(GetDlgItem(hwndDlg, IDC_LANGVERSIONLABEL), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_LANGVERSION), SW_HIDE); + } + + /* general */ + SetDlgItemTextA(hwndDlg, IDC_LANGMODUSING, pack->szLastModifiedUsing); + SetDlgItemTextA(hwndDlg, IDC_LANGAUTHORS, pack->szAuthors); + SetDlgItemTextA(hwndDlg, IDC_LANGEMAIL, pack->szAuthorEmail); + SetDlgItemText(hwndDlg, IDC_LANGINFOFRAME, TranslateTS(pack->szLanguage)); +} + +#define M_RELOADLIST (WM_APP+1) +#define M_SHOWFILECOL (WM_APP+2) + +static void DeletePackFile(HWND hwndDlg, HWND hwndList, int iItem, HELPPACK_INFO *pack) +{ + SHFILEOPSTRUCT sfo = { 0 }; + sfo.hwnd = hwndDlg; + sfo.wFunc = FO_DELETE; + sfo.fFlags = FOF_SIMPLEPROGRESS | FOF_SILENT; /* silent = no progress */ + /* double zero terminated */ + TCHAR szFileName[MAX_PATH]; + if (GetPackPath(szFileName, sizeof(szFileName) - 1, pack->flags&HPF_ENABLED, pack->szFileName)) { + szFileName[lstrlen(szFileName) + 1] = _T('\0'); + sfo.pFrom = szFileName; + /* ask to delete file */ + if (!SHFileOperation(&sfo) && !sfo.fAnyOperationsAborted) { + LVITEM lvi; + lvi.iItem = iItem; + lvi.iSubItem = 0; + lvi.mask = LVIF_STATE; + lvi.stateMask = LVIS_STATEIMAGEMASK | LVIS_SELECTED | LVIS_FOCUSED; + if (ListView_GetItem(hwndList, &lvi)) { + ListView_DeleteItem(hwndList, iItem); + /* enable/select next item at same position */ + int nCount = ListView_GetItemCount(hwndList); + lvi.iItem = (iItem < nCount) ? iItem : iItem - 1; + if (lvi.iItem != -1) { + ListView_SetItemState(hwndList, lvi.iItem, lvi.state, lvi.stateMask); + /* enable pack file */ + lvi.mask = LVIF_PARAM; + lvi.iSubItem = 0; + if (ListView_GetItem(hwndList, &lvi)) + EnablePack((HELPPACK_INFO*)lvi.lParam, _T("helppack_*.txt")); + } + else + DisplayPackInfo(hwndDlg, NULL); + if (nCount == 1) + SendMessage(hwndDlg, M_SHOWFILECOL, 0, FALSE); + } + } + } +} + +static INT_PTR CALLBACK InsertPackItemEnumProc(HELPPACK_INFO *pack, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(lParam); + + HELPPACK_INFO *pack2 = (HELPPACK_INFO*)mir_alloc(sizeof(HELPPACK_INFO)); + if (pack2 == NULL) + return FALSE; + CopyMemory(pack2, pack, sizeof(HELPPACK_INFO)); + + /* country flag icon */ + LVITEM lvi; + lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE; + if ((HIMAGELIST)lParam != NULL) { + HICON hIcon; + if (pack->flags&HPF_DEFAULT) /* not really needed, but to be consistent with LangMan */ + hIcon = (HICON)CallService(MS_FLAGS_CREATEMERGEDFLAGICON, CTRY_UNITED_STATES, CTRY_UNITED_KINGDOM); + else { + int countryId = 0xFFFF; /* Unknown */ + TCHAR szBuf[6]; + /* get country id from locale */ + if (!(pack->flags&HPF_NOLOCALE)) + if (GetLocaleInfo(pack->Locale, LOCALE_ICOUNTRY, szBuf, sizeof(szBuf))) + countryId = _ttoi(szBuf); + hIcon = (HICON)CallService(MS_FLAGS_LOADFLAGICON, countryId, 0); + } + lvi.iImage = (hIcon == NULL) ? -1 : ImageList_AddIcon((HIMAGELIST)lParam, hIcon); + lvi.mask |= LVIF_IMAGE; + } + + /* insert */ + lvi.iItem = lvi.iSubItem = 0; + lvi.stateMask = LVIS_STATEIMAGEMASK | LVIS_SELECTED; + lvi.state = INDEXTOSTATEIMAGEMASK((pack->flags&HPF_ENABLED) ? 2 : 1); + if (pack->flags&HPF_ENABLED) + lvi.state |= LVIS_SELECTED | LVIS_FOCUSED; + lvi.pszText = TranslateTS(pack->szLanguage); + lvi.lParam = (LPARAM)pack2; + ListView_InsertItem((HWND)wParam, &lvi); + + return TRUE; +} + +static int CALLBACK CompareListItem(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) +{ + int cmp = CompareString((LCID)lParamSort, 0, ((HELPPACK_INFO*)lParam1)->szLanguage, -1, ((HELPPACK_INFO*)lParam2)->szLanguage, -1); + if (cmp) + cmp -= 2; + if (!cmp) { + if (((HELPPACK_INFO*)lParam1)->flags&HPF_ENABLED) + cmp = -1; + else if (((HELPPACK_INFO*)lParam2)->flags&HPF_ENABLED) + cmp = 1; + } + + return cmp; +} + +static INT_PTR CALLBACK LangOptDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + HWND hwndList = GetDlgItem(hwndDlg, IDC_LANGLIST); + LVITEM lvi; + + switch (msg) { + case WM_INITDIALOG: + { + TranslateDialogDefault(hwndDlg); + hwndLangOpt = hwndDlg; + ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); + ListView_SetImageList(hwndList, CreateRadioImages(ListView_GetBkColor(hwndList), ListView_GetTextColor(hwndList)), LVSIL_STATE); /* auto-destroyed */ + { + LVCOLUMN lvc; + lvc.mask = LVCF_TEXT; + lvc.pszText = TranslateT("Installed Languages"); + ListView_InsertColumn(hwndList, 0, &lvc); + } + if (ServiceExists(MS_FLAGS_LOADFLAGICON)) + ListView_SetImageList(hwndList, ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR24, 8, 8), LVSIL_SMALL); + CorrectPacks(_T("helppack_*.txt"), _T("helppack_english.txt"), FALSE); + CheckDlgButton(hwndDlg, IDC_ENABLEHELPUPDATES, db_get_b(NULL, "HelpPlugin", "EnableHelpUpdates", SETTING_ENABLEHELPUPDATES_DEFAULT) != 0); + SendMessage(hwndDlg, M_RELOADLIST, 0, 0); + SendMessage(hwndDlg, M_SHOWFILECOL, 0, 1); + return TRUE; + } + case M_RELOADLIST: + { + /* init list */ + ListView_DeleteAllItems(hwndList); + ListView_DeleteColumn(hwndList, 1); /* if present */ + { + HIMAGELIST himl = ListView_GetImageList(hwndList, LVSIL_SMALL); + ImageList_RemoveAll(himl); + /* enum all packs */ + EnumPacks(InsertPackItemEnumProc, _T("helppack_*.txt"), "Miranda Help Pack Version 1", (WPARAM)hwndList, (LPARAM)himl); + /* nothing installed */ + if (!ListView_GetItemCount(hwndList)) + DisplayPackInfo(hwndDlg, NULL); + /* make it use current langpack locale for sort */ + ListView_SortItems(hwndList, CompareListItem, Langpack_GetDefaultLocale()); + CheckDlgButton(hwndDlg, IDC_ENABLEHELPUPDATES, db_get_b(NULL, "HelpPlugin", "EnableHelpUpdates", SETTING_ENABLEHELPUPDATES_DEFAULT) != 0); + /* show selection */ + int iItem = ListView_GetNextItem(hwndList, -1, LVNI_SELECTED); + if (iItem != -1) + ListView_EnsureVisible(hwndList, iItem, FALSE); + } + return TRUE; + } + case M_SHOWFILECOL: + if ((BOOL)lParam && ListView_GetItemCount(hwndList)>1) { + /* add column */ + LVCOLUMN lvc; + ListView_SetColumnWidth(hwndList, 0, LVSCW_AUTOSIZE_USEHEADER); + lvc.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; + lvc.pszText = TranslateT("File"); + lvc.cx = 160; + ListView_InsertColumn(hwndList, lvc.iSubItem = 1, &lvc); + ListView_SetColumnWidth(hwndList, 0, ListView_GetColumnWidth(hwndList, 0) - lvc.cx); + + /* add text */ + lvi.mask = LVIF_PARAM; + lvi.iSubItem = 0; + for (lvi.iItem = 0; ListView_GetItem(hwndList, &lvi); ++lvi.iItem) { + HELPPACK_INFO *pack = (HELPPACK_INFO*)lvi.lParam; + ListView_SetItemText(hwndList, lvi.iItem, 1, pack->szFileName); + } + } + else { + ListView_DeleteColumn(hwndList, 1); + ListView_SetColumnWidth(hwndList, 0, LVSCW_AUTOSIZE_USEHEADER); + } + return TRUE; + + case WM_THEMECHANGED: + case WM_SETTINGCHANGE: + { + HIMAGELIST himl = ListView_SetImageList(hwndList, CreateRadioImages(ListView_GetBkColor(hwndList), ListView_GetTextColor(hwndList)), LVSIL_STATE); /* auto-destroyed */ + if (himl != NULL) + ImageList_Destroy(himl); + } + break; + + case WM_CTLCOLORLISTBOX: /* mimic readonly edit */ + return (BOOL)SendMessage(hwndDlg, WM_CTLCOLORSTATIC, wParam, lParam); + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_LANGEMAIL: + { + char buf[512]; + lstrcpyA(buf, "mailto:"); + if (GetWindowTextA(GetDlgItem(hwndDlg, LOWORD(wParam)), &buf[7], sizeof(buf) - 7)) + // CallService(MS_UTILS_OPENURL,FALSE,(LPARAM)buf); // look at !! + return TRUE; + } + + case IDC_MORELANG: + // CallService(MS_UTILS_OPENURL,TRUE,(LPARAM)"http://miranda-ng.org/"); //look at !! + return TRUE; + + case IDC_ENABLEHELPUPDATES: + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); /* enable apply */ + return TRUE; + + case IDC_DOWNLOADLANG: + // ServiceShowLangDialog(0, 0); + return TRUE; + } + break; + + case WM_CONTEXTMENU: + if (GetDlgCtrlID((HWND)wParam) == IDC_LANGLIST) { + /* get item */ + LVHITTESTINFO hti; + POINTSTOPOINT(hti.pt, MAKEPOINTS(lParam)); + if (hti.pt.x == -1 && hti.pt.y == -1) { + /* keyboard invoked */ + hti.iItem = ListView_GetNextItem((HWND)wParam, -1, LVNI_SELECTED); + if (hti.iItem != -1) + break; + + RECT rc; + if (!ListView_GetItemRect((HWND)wParam, hti.iItem, &rc, LVIR_SELECTBOUNDS)) + break; + + hti.pt.x = rc.left + (rc.right - rc.left) / 2; + hti.pt.y = rc.top + (rc.bottom - rc.top) / 2; + ClientToScreen((HWND)wParam, &hti.pt); + } + else { + ScreenToClient((HWND)wParam, &hti.pt); + if (ListView_HitTest((HWND)wParam, &hti) == -1 || !(hti.flags&LVHT_ONITEM)) + break; + POINTSTOPOINT(hti.pt, MAKEPOINTS(lParam)); + } + /* param */ + lvi.iItem = hti.iItem; + lvi.iSubItem = 0; + lvi.mask = LVIF_PARAM; + if (!ListView_GetItem((HWND)wParam, &lvi)) + break; + + /* context menu */ + HELPPACK_INFO *pack = (HELPPACK_INFO*)lvi.lParam; + HMENU hContextMenu = CreatePopupMenu(); + if (hContextMenu != NULL) { + AppendMenu(hContextMenu, MF_STRING, 2, TranslateT("&Remove...")); + if (TrackPopupMenuEx(hContextMenu, TPM_RETURNCMD | TPM_NONOTIFY | TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_HORPOSANIMATION | TPM_VERPOSANIMATION, hti.pt.x, hti.pt.y, (HWND)wParam, NULL)) + DeletePackFile(hwndDlg, (HWND)wParam, hti.iItem, pack); + DestroyMenu(hContextMenu); + } + return TRUE; + } + break; + + case WM_NOTIFYFORMAT: + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, NFR_UNICODE); + return TRUE; + + case WM_NOTIFY: + NMHDR *nmhdr = (NMHDR*)lParam; + switch (nmhdr->idFrom) { + case IDC_LANGLIST: + switch (nmhdr->code) { + case LVN_DELETEITEM: + LVITEM lvi; + lvi.iItem = ((NMLISTVIEW*)lParam)->iItem; /* nmlv->lParam is invalid */ + lvi.iSubItem = 0; + lvi.mask = LVIF_PARAM; + if (ListView_GetItem(nmhdr->hwndFrom, &lvi)) + mir_free((HELPPACK_INFO*)lvi.lParam); + break; + + case LVN_ITEMCHANGED: + { + NMLISTVIEW *nmlv = (NMLISTVIEW*)lParam; + if (!(nmlv->uChanged&LVIF_STATE)) + break; + + /* display info and check radio item */ + if (nmlv->uNewState&LVIS_SELECTED && !(nmlv->uOldState&LVIS_SELECTED)) { + ListView_SetItemState(nmhdr->hwndFrom, nmlv->iItem, INDEXTOSTATEIMAGEMASK(2), LVIS_STATEIMAGEMASK); + DisplayPackInfo(hwndDlg, (HELPPACK_INFO*)nmlv->lParam); + } + /* disable all other radio items */ + else if (nmlv->uNewState&INDEXTOSTATEIMAGEMASK(2)) { + for (int iItem = ListView_GetItemCount(nmhdr->hwndFrom) - 1; iItem != -1; --iItem) + if (iItem != nmlv->iItem) + ListView_SetItemState(nmhdr->hwndFrom, iItem, INDEXTOSTATEIMAGEMASK(1), LVIS_STATEIMAGEMASK); + + /* enable apply */ + if (nmlv->uOldState) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + } + } + break; + + case LVN_KEYDOWN: + { + int iItem = ListView_GetNextItem(nmhdr->hwndFrom, -1, LVNI_SELECTED); + switch (((NMLVKEYDOWN*)lParam)->wVKey) { + case VK_SPACE: + ListView_SetItemState(nmhdr->hwndFrom, iItem, INDEXTOSTATEIMAGEMASK(2), LVIS_STATEIMAGEMASK); + break; + + case VK_DELETE: + lvi.iItem = iItem; + lvi.iSubItem = 0; + lvi.mask = LVIF_PARAM; + if (ListView_GetItem(nmhdr->hwndFrom, &lvi)) + DeletePackFile(hwndDlg, nmhdr->hwndFrom, iItem, (HELPPACK_INFO*)lvi.lParam); + break; + } + } + break; + + case NM_CLICK: + LVHITTESTINFO hti; + lParam = GetMessagePos(); + POINTSTOPOINT(hti.pt, MAKEPOINTS(lParam)); + ScreenToClient(nmhdr->hwndFrom, &hti.pt); + if (ListView_HitTest(nmhdr->hwndFrom, &hti) != -1) + if (hti.flags&(LVHT_ONITEMSTATEICON | LVHT_ONITEMICON)) /* one of them */ + ListView_SetItemState(nmhdr->hwndFrom, hti.iItem, LVIS_SELECTED, LVIS_SELECTED); + } /* switch nmhdr->code */ + break; + + case 0: + switch (nmhdr->code) { + case PSN_APPLY: + lvi.mask = LVIF_STATE | LVIF_PARAM; + lvi.stateMask = LVIS_STATEIMAGEMASK; + lvi.iSubItem = 0; + for (lvi.iItem = 0; ListView_GetItem(hwndList, &lvi); ++lvi.iItem) { + HELPPACK_INFO *pack = (HELPPACK_INFO*)lvi.lParam; + if (lvi.state&INDEXTOSTATEIMAGEMASK(2)) { + EnablePack(pack, _T("helppack_*.txt")); + pack->flags |= HPF_ENABLED; + } + else + pack->flags &= ~HPF_ENABLED; + } + db_set_b(NULL, "HelpPlugin", "EnableAutoUpdates", (BYTE)(IsDlgButtonChecked(hwndDlg, IDC_ENABLEHELPUPDATES) != 0)); + return TRUE; + } + } /* switch nmhdr->idFrom */ + break; + } + return FALSE; +} + +void ReloadLangOptList(void) +{ + if (hwndLangOpt != NULL) { + SendMessage(hwndLangOpt, M_RELOADLIST, 0, 0); + SendMessage(hwndLangOpt, M_SHOWFILECOL, 0, 1); + } +} + +/**************************** ADV OPTS ****************************/ + +static INT_PTR CALLBACK AdvOptDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + WORD val = settingAutoTipDelay; + if (!val) + val = db_get_w(NULL, "HelpPlugin", "AutoTipDelay", SETTING_AUTOTIPDELAY_DEFAULT); + CheckDlgButton(hwndDlg, IDC_AUTOTIPSENABLED, settingAutoTipDelay != 0); + SendDlgItemMessage(hwndDlg, IDC_AUTOTIPDELAYSPIN, UDM_SETRANGE, 0, MAKELPARAM(10000, 500)); + SendDlgItemMessage(hwndDlg, IDC_AUTOTIPDELAY, EM_SETLIMITTEXT, (WPARAM)5, 0); + { + UDACCEL uda = { 0, 500 }; + SendDlgItemMessage(hwndDlg, IDC_AUTOTIPDELAYSPIN, UDM_SETACCEL, 1, (LPARAM)&uda); + } + SetDlgItemInt(hwndDlg, IDC_AUTOTIPDELAY, val, FALSE); + SendDlgItemMessage(hwndDlg, IDC_AUTOTIPDELAYSPIN, UDM_SETPOS, 0, MAKELPARAM(val, 0)); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAYLABEL), settingAutoTipDelay != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAY), settingAutoTipDelay != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAYSPIN), settingAutoTipDelay != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAYMS), settingAutoTipDelay != 0); + } + return TRUE; + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_AUTOTIPSENABLED: + { + BOOL checked = IsDlgButtonChecked(hwndDlg, IDC_AUTOTIPSENABLED); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAYLABEL), checked); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAY), checked); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAYSPIN), checked); + EnableWindow(GetDlgItem(hwndDlg, IDC_AUTOTIPDELAYMS), checked); + break; + } + case IDC_AUTOTIPDELAY: + if (HIWORD(wParam) == EN_KILLFOCUS) { + WORD minVal = HIWORD(SendDlgItemMessage(hwndDlg, IDC_AUTOTIPDELAYSPIN, UDM_GETRANGE, 0, 0)); + if (GetDlgItemInt(hwndDlg, IDC_AUTOTIPDELAY, NULL, FALSE)idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + { + WORD minVal; + minVal = HIWORD(SendDlgItemMessage(hwndDlg, IDC_AUTOTIPDELAYSPIN, UDM_GETRANGE, 0, 0)); + if (GetDlgItemInt(hwndDlg, IDC_AUTOTIPDELAY, NULL, FALSE) < minVal) { + SetDlgItemInt(hwndDlg, IDC_AUTOTIPDELAY, minVal, FALSE); + SendDlgItemMessage(hwndDlg, IDC_AUTOTIPDELAYSPIN, UDM_SETPOS, 0, MAKELPARAM(minVal, 0)); + } + settingAutoTipDelay = (WORD)GetDlgItemInt(hwndDlg, IDC_AUTOTIPDELAY, NULL, FALSE); + db_set_w(NULL, "HelpPlugin", "AutoTipDelay", settingAutoTipDelay); + + if (!IsDlgButtonChecked(hwndDlg, IDC_AUTOTIPSENABLED)) + settingAutoTipDelay = 0; + db_set_b(NULL, "HelpPlugin", "AutoTipsEnabled", (BYTE)(settingAutoTipDelay != 0)); + return TRUE; + } + } + break; + } + break; + } + return FALSE; +} + +/**************************** INIT ********************************/ + +static int HelpOptInit(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.position = 1200000080; + odp.hInstance = hInst; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_LANG); + odp.ptszGroup = LPGENT("Customize"); // autotranslated + odp.ptszTitle = LPGENT("Help"); // autotranslated + + odp.ptszTab = LPGENT("Language"); // autotranslated + odp.flags = ODPF_TCHAR | ODPF_BOLDGROUPS; + odp.pfnDlgProc = LangOptDlgProc; + Options_AddPage(wParam, &odp); + +#ifdef EDITOR + odp.ptszTab = _T("Help Editor"); /* autotranslated */ +#else + odp.ptszTab = _T("Advanced"); /* autotranslated */ +#endif + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ADV); + odp.pfnDlgProc = AdvOptDlgProc; + Options_AddPage(wParam, &odp); + + return 0; +} + +void InitOptions(void) +{ + settingAutoTipDelay = 0; + if (db_get_b(NULL, "HelpPlugin", "AutoTipsEnabled", SETTING_AUTOTIPSENABLED_DEFAULT)) + settingAutoTipDelay = db_get_w(NULL, "HelpPlugin", "AutoTipDelay", SETTING_AUTOTIPDELAY_DEFAULT); + + hwndLangOpt = NULL; + hHookOptInit = HookEvent(ME_OPT_INITIALISE, HelpOptInit); + CorrectPacks(_T("helppack_*.txt"), _T("helppack_english.txt"), FALSE); +} + +void UninitOptions(void) +{ + UnhookEvent(hHookOptInit); +} \ No newline at end of file diff --git a/plugins/ContextHelp/src/resource.h b/plugins/ContextHelp/src/resource.h new file mode 100644 index 0000000000..e9f9b0622f --- /dev/null +++ b/plugins/ContextHelp/src/resource.h @@ -0,0 +1,61 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by resource.rc +// +#define IDD_HELP 101 +#define IDD_SHADOW 102 +#define IDD_OPT_LANG 103 +#define IDD_OPT_ADV 104 +#define IDD_UPDATENOTIFY 105 +#define IDD_DOWNLOADLANG 106 +#define IDC_CTLTEXT 1001 +#define IDC_TEXT 1002 +#define IDC_CTLTYPE 1003 +#define IDC_CTLID 1004 +#define IDC_MODULE 1005 +#define IDC_DLGID 1006 +#define IDC_CARETSUCKER 1007 +#define IDC_LANGLIST 1008 +#define IDC_LANGINFOFRAME 1009 +#define IDC_LANGAUTHORSLABEL 1010 +#define IDC_LANGAUTHORS 1011 +#define IDC_LANGEMAILLABEL 1012 +#define IDC_LANGEMAIL 1013 +#define IDC_LANGMODUSINGLABEL 1014 +#define IDC_LANGMODUSING 1015 +#define IDC_LANGDATELABEL 1016 +#define IDC_LANGDATE 1017 +#define IDC_LANGVERSIONLABEL 1018 +#define IDC_LANGVERSION 1019 +#define IDC_LANGLOCALELABEL 1020 +#define IDC_LANGLOCALE 1021 +#define IDC_LANGNOTINCLUDEDLABEL 1022 +#define IDC_LANGNOTINCLUDED 1023 +#define IDC_MORELANG 1024 +#define IDC_NOPACK 1025 +#define IDC_ENABLEHELPUPDATES 1026 +#define IDC_DOWNLOADLANG 1027 +#define IDC_LANGCOMBO 1028 +#define IDC_DOWNLOADALL 1029 +#define IDC_LOADING 1030 +#define IDC_LANGUAGELABEL 1031 +#define IDC_LANGUAGE 1032 +#define IDC_CURRENTVERSION 1033 +#define IDC_NEWVERSION 1034 +#define IDC_NEWVERSIONLABEL 1035 +#define IDC_AUTOTIPSENABLED 1036 +#define IDC_AUTOTIPDELAY 1037 +#define IDC_AUTOTIPDELAYSPIN 1038 +#define IDC_AUTOTIPDELAYLABEL 1039 +#define IDC_AUTOTIPDELAYMS 1040 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 107 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1041 +#define _APS_NEXT_SYMED_VALUE 2001 +#endif +#endif diff --git a/plugins/ContextHelp/src/stdafx.cxx b/plugins/ContextHelp/src/stdafx.cxx new file mode 100644 index 0000000000..0830d91bb3 --- /dev/null +++ b/plugins/ContextHelp/src/stdafx.cxx @@ -0,0 +1,18 @@ +/* +Copyright (C) 2012-16 Miranda NG project (http://miranda-ng.org) + +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 version 2 +of the License. + +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. If not, see . +*/ + +#include "stdafx.h" diff --git a/plugins/ContextHelp/src/stdafx.h b/plugins/ContextHelp/src/stdafx.h new file mode 100644 index 0000000000..20cac3503d --- /dev/null +++ b/plugins/ContextHelp/src/stdafx.h @@ -0,0 +1,52 @@ +/* +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. +*/ + +#ifndef CONTEXTHELP_COMMONHEADERS_H +#define CONTEXTHELP_COMMONHEADERS_H + +#include +#include +#include + +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define EDITOR +#include +#include "help.h" +#include "version.h" +#include "resource.h" + +extern HINSTANCE hInstance; + +#define USERAGENT_VERSION "0.2.1.2" + + +#endif //CONTEXTHELP_COMMONHEADERS_H \ No newline at end of file diff --git a/plugins/ContextHelp/src/streaminout.cpp b/plugins/ContextHelp/src/streaminout.cpp new file mode 100644 index 0000000000..460bc655c2 --- /dev/null +++ b/plugins/ContextHelp/src/streaminout.cpp @@ -0,0 +1,771 @@ +/* +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" + + +#include + +struct EditStreamData { + PBYTE pbBuff; + int cbBuff; + int iCurrent; +}; + +#ifndef EDITOR +struct HyperlinkData { + CHARRANGE range; + char *szLink; +} static *hyperlink = NULL; +static int hyperlinkCount = 0; +#endif + +static DWORD CALLBACK EditStreamInRtf(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) +{ + struct EditStreamData *esd = (struct EditStreamData*)dwCookie; + *pcb = min(esd->cbBuff - esd->iCurrent, cb); + CopyMemory(pbBuff, esd->pbBuff, *pcb); + esd->iCurrent += *pcb; + + return 0; +} + +#ifdef EDITOR +static DWORD CALLBACK EditStreamOutRtf(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) +{ + struct EditStreamData *esd = (struct EditStreamData*)dwCookie; + PBYTE buf = (PBYTE)mir_realloc(esd->pbBuff, esd->cbBuff + cb + 1); + if (buf == NULL) + return 0; + esd->pbBuff = buf; + esd->cbBuff += cb; + CopyMemory(esd->pbBuff + esd->iCurrent, pbBuff, cb); + esd->iCurrent += cb; + esd->pbBuff[esd->iCurrent] = '\0'; + *pcb = cb; + + return 0; +} +#endif + +struct { + const char *szSym; + char ch; +} static const htmlSymbolChars[] = { + { "lt", '<' }, + { "gt", '>' }, + { "amp", '&' }, + { "quot", '\"' }, + { "nbsp", ' ' }, +}; + +struct { + const char *szName; + const char *szClr; +} static const htmlColourNames[] = { + { "black", "000000" }, + { "maroon", "800000" }, + { "green", "008000" }, + { "olive", "808000" }, + { "navy", "000080" }, + { "purple", "800080" }, + { "teal", "008080" }, + { "silver", "C0C0C0" }, + { "gray", "808080" }, + { "red", "FF0000" }, + { "lime", "00FF00" }, + { "yellow", "FFFF00" }, + { "blue", "0000FF" }, + { "fuchsia", "FF00FF" }, + { "aqua", "00FFFF" }, + { "white", "FFFFFF" }, +}; + +// a quick test to see who's read their comp.lang.c FAQ: +#define stringize2(n) #n +#define stringize(n) stringize2(n) + +struct { + const char *szHtml; + const char *szRtf; +} static const simpleHtmlRtfConversions[] = { + { "i", "i" }, + { "/i", "i0" }, + { "b", "b" }, + { "/b", "b0" }, + { "u", "ul" }, + { "/u", "ul0" }, + { "big", "fs" stringize(TEXTSIZE_BIG) }, + { "/big", "fs" stringize(TEXTSIZE_NORMAL) }, + { "small", "fs" stringize(TEXTSIZE_SMALL) }, + { "/small", "fs" stringize(TEXTSIZE_NORMAL) }, + { "/font", "cf0" } +}; + +// mir_free() the return value +char *GetHtmlTagAttribute(const char *pszTag, const char *pszAttr) +{ + int iAttrName, iAttrNameEnd, iAttrEquals, iAttrValue, iAttrValueEnd, iAttrEnd; + int attrLen = lstrlenA(pszAttr); + + for (iAttrName = 0; !isspace(pszTag[iAttrName]) && pszTag[iAttrName] != '>'; iAttrName++); + for (;;) { + for (; isspace(pszTag[iAttrName]); iAttrName++); + if (pszTag[iAttrName] == '>' || pszTag[iAttrName] == '\0') + break; + for (iAttrNameEnd = iAttrName; isalnum(pszTag[iAttrNameEnd]); iAttrNameEnd++); + for (iAttrEquals = iAttrNameEnd; isspace(pszTag[iAttrEquals]); iAttrEquals++); + if (pszTag[iAttrEquals] != '=') { + iAttrName = iAttrEquals; + continue; + } + for (iAttrValue = iAttrEquals + 1; isspace(pszTag[iAttrValue]); iAttrValue++); + if (pszTag[iAttrValue] == '>' || pszTag[iAttrValue] == '\0') + break; + if (pszTag[iAttrValue] == '"' || pszTag[iAttrValue] == '\'') { + for (iAttrValueEnd = iAttrValue + 1; pszTag[iAttrValueEnd] && pszTag[iAttrValueEnd] != pszTag[iAttrValue]; iAttrValueEnd++); + iAttrValue++; + iAttrEnd = iAttrValueEnd + 1; + } + else { + for (iAttrValueEnd = iAttrValue; pszTag[iAttrValueEnd] && pszTag[iAttrValueEnd] != '>' && !isspace(pszTag[iAttrValueEnd]); iAttrValueEnd++); + iAttrEnd = iAttrValueEnd; + } + if (pszTag[iAttrValueEnd] == '\0') + break; + if (attrLen == iAttrNameEnd - iAttrName && !_strnicmp(pszAttr, pszTag + iAttrName, attrLen)) { + char *szValue; + szValue = (char*)mir_alloc(iAttrValueEnd - iAttrValue + 1); + if (szValue != NULL) { + CopyMemory(szValue, pszTag + iAttrValue, iAttrValueEnd - iAttrValue); + szValue[iAttrValueEnd - iAttrValue] = '\0'; + } + return szValue; + } + iAttrName = iAttrEnd; + } + return NULL; +} + +void StreamInHtml(HWND hwndEdit, const char *szHtml, UINT codepage, COLORREF clrBkgrnd) +{ + EDITSTREAM stream; + struct EditStreamData esd; + struct ResizableCharBuffer header, body; + COLORREF *colourTbl = NULL; + int colourTblCount = 0; + const char *pszHtml; + char *szThisTagHref = NULL; + int keywordAtBeginning = 1, paragraphBefore = 0, lineBreakBefore = 1; + int charCount = 0; + + ZeroMemory(&stream, sizeof(stream)); + ZeroMemory(&esd, sizeof(esd)); + ZeroMemory(&header, sizeof(header)); + ZeroMemory(&body, sizeof(body)); + +#ifndef EDITOR + FreeHyperlinkData(); +#endif + AppendToCharBuffer(&header, "{\\rtf1\\ansi\\ansicpg%u\\deff0{\\fonttbl{\\f0 Tahoma;}}", codepage); + for (pszHtml = szHtml; *pszHtml != '\0';) { + if (*pszHtml == '<') { + const char *pszTagEnd; + int iNameEnd, i; + char szTagName[16]; + + pszTagEnd = strchr(pszHtml + 1, '>'); + if (pszTagEnd == NULL) + break; + for (iNameEnd = 1; pszHtml[iNameEnd] != '\0' && pszHtml[iNameEnd] != '>' && !isspace(pszHtml[iNameEnd]); iNameEnd++); + CopyMemory(szTagName, pszHtml + 1, min(sizeof(szTagName), iNameEnd)); + szTagName[min(sizeof(szTagName), iNameEnd) - 1] = '\0'; + + for (i = 0; i < sizeof(simpleHtmlRtfConversions); i++) { + if (!lstrcmpiA(szTagName, simpleHtmlRtfConversions[i].szHtml)) { + AppendToCharBuffer(&body, "\\%s ", simpleHtmlRtfConversions[i].szRtf); + break; + } + } + if (i == sizeof(simpleHtmlRtfConversions)) { + if (!lstrcmpiA(szTagName, "br")) { + AppendToCharBuffer(&body, "\\par "); + charCount++; // linebreaks are characters + lineBreakBefore = 1; + } + else if (!lstrcmpiA(szTagName, "p") || !lstrcmpiA(szTagName, "/p")) { + if (charCount) + paragraphBefore = 1; + } + else if (!lstrcmpiA(szTagName, "a")) { + mir_free(szThisTagHref); // does NULL check + szThisTagHref = GetHtmlTagAttribute(pszHtml, "href"); +#ifdef EDITOR + if (szThisTagHref != NULL) + AppendToCharBuffer(&body, "\\strike "); +#else + if (szThisTagHref != NULL) { + struct HyperlinkData *buf = (struct HyperlinkData*)mir_realloc(hyperlink, sizeof(struct HyperlinkData)*(hyperlinkCount + 1)); + if (buf != NULL) { // hyperlinkCount increased at + hyperlink = buf; + hyperlink[hyperlinkCount].range.cpMin = paragraphBefore ? (charCount + 2) : charCount; + hyperlink[hyperlinkCount].range.cpMax = -1; + hyperlink[hyperlinkCount].szLink = NULL; + } + else { + mir_free(szThisTagHref); + szThisTagHref = NULL; + } + } +#endif + } + else if (!lstrcmpiA(szTagName, "/a")) { + if (szThisTagHref) { +#ifdef EDITOR + AppendToCharBuffer(&body, ":%s\\strike0 ", szThisTagHref); + mir_free(szThisTagHref); +#else + mir_utf8decodecp(szThisTagHref, CP_ACP, NULL); + hyperlink[hyperlinkCount].range.cpMax = charCount; + hyperlink[hyperlinkCount].szLink = szThisTagHref; + hyperlinkCount++; +#endif + szThisTagHref = NULL; + } + } + else if (!lstrcmpiA(szTagName, "font")) { + char *szColour = GetHtmlTagAttribute(pszHtml, "color"); + if (szColour != NULL) { + int i, freeColour = 1; + if (szColour[0] != '#' || lstrlenA(szColour) != 7) { + for (i = 0; i < sizeof(htmlColourNames); i++) { + if (!lstrcmpiA(szColour, htmlColourNames[i].szName)) { + mir_free(szColour); + szColour = (char*)htmlColourNames[i].szClr; + freeColour = 0; + break; + } + } + } + else + szColour++; + if (szColour != NULL) { + COLORREF colour; + char szRed[3], szGreen[3], szBlue[3]; + szRed[0] = szColour[0]; szRed[1] = szColour[1]; szRed[2] = '\0'; + szGreen[0] = szColour[2]; szGreen[1] = szColour[3]; szGreen[2] = '\0'; + szBlue[0] = szColour[4]; szBlue[1] = szColour[5]; szBlue[2] = '\0'; + colour = RGB(strtol(szRed, NULL, 16), strtol(szGreen, NULL, 16), strtol(szBlue, NULL, 16)); + if (freeColour) + mir_free(szColour); +#ifndef EDITOR + if (colour != clrBkgrnd) { // ensure color is visible +#else + UNREFERENCED_PARAMETER(clrBkgrnd); +#endif // !defined EDITOR + for (i = 0; i < colourTblCount; i++) + if (colourTbl[i] == colour) + break; + if (i == colourTblCount) { + COLORREF *buf = (COLORREF*)mir_realloc(colourTbl, (colourTblCount + 1)*sizeof(COLORREF)); + if (buf != NULL) { + colourTbl = buf; + colourTblCount++; + colourTbl[i] = colour; + } + } + AppendToCharBuffer(&body, "\\cf%d ", i + 2); +#ifndef EDITOR + } +#endif + } + } + } // endif font + } + pszHtml = pszTagEnd + 1; + } + else if (*pszHtml == '&') { + const char *pszTagEnd; + char szTag[16]; + int i; + + pszTagEnd = strchr(pszHtml + 1, ';'); + if (pszTagEnd == NULL) + break; + CopyMemory(szTag, pszHtml + 1, min(sizeof(szTag), pszTagEnd - pszHtml)); + szTag[min(sizeof(szTag), pszTagEnd - pszHtml) - 1] = '\0'; + if (szTag[0] == '#') { + int ch; + if (szTag[1] == 'x' || szTag[1] == 'X') + ch = strtol(szTag + 2, NULL, 16); + else + ch = strtol(szTag + 1, NULL, 10); + if (ch >= 0x100) + AppendToCharBuffer(&body, "\\u%d", ch); + else + AppendToCharBuffer(&body, "\\'%02x ", ch); + } + else { + for (i = 0; i < sizeof(htmlSymbolChars); i++) { + if (!lstrcmpiA(szTag, htmlSymbolChars[i].szSym)) { + AppendCharToCharBuffer(&body, htmlSymbolChars[i].ch); + charCount++; + break; + } + } + } + pszHtml = pszTagEnd + 1; + } + else if (*pszHtml != ' ' || (!lineBreakBefore && !paragraphBefore)) { + lineBreakBefore = 0; + if (paragraphBefore) { + AppendToCharBuffer(&body, "\\par\\par "); + charCount += 2; // linebreaks are characters + paragraphBefore = 0; + } + if ((BYTE)*pszHtml >= ' ') + charCount++; + if (*pszHtml == '\\' || *pszHtml == '{' || *pszHtml == '}') + AppendCharToCharBuffer(&body, '\\'); + AppendCharToCharBuffer(&body, *pszHtml++); + } + else + pszHtml++; + } + mir_free(szThisTagHref); // does NULL check + + { + COLORREF clr = GetSysColorBrush(COLOR_HOTLIGHT) ? GetSysColor(COLOR_HOTLIGHT) : RGB(0, 0, 255); + AppendToCharBuffer(&header, "{\\colortbl ;\\red%d\\green%d\\blue%d;", GetRValue(clr), GetGValue(clr), GetBValue(clr)); + for (int i = 0; i < colourTblCount; i++) + AppendToCharBuffer(&header, "\\red%d\\green%d\\blue%d;", GetRValue(colourTbl[i]), GetGValue(colourTbl[i]), GetBValue(colourTbl[i])); + } + AppendToCharBuffer(&header, "}\\pard\\fs16\\uc0"); + if (keywordAtBeginning) + AppendCharToCharBuffer(&header, ' '); + mir_free(colourTbl); // does NULL check + + if (header.sz != NULL) { + AppendToCharBuffer(&header, "%s}", body.sz ? body.sz : ""); + esd.pbBuff = (PBYTE)header.sz; + esd.cbBuff = header.iEnd; + stream.dwCookie = (DWORD)&esd; + stream.pfnCallback = (EDITSTREAMCALLBACK)EditStreamInRtf; + SendMessage(hwndEdit, EM_STREAMIN, SF_RTF, (LPARAM)&stream); + mir_free(header.sz); + } + mir_free(body.sz); // does NULL check + +#ifndef EDITOR + CHARFORMAT cf; + ZeroMemory(&cf, sizeof(cf)); + cf.cbSize = sizeof(cf); + cf.dwMask = CFM_UNDERLINE | CFM_COLOR; // CFE_LINK always uses RGB(0,0,255) instead of GetSysColor(COLOR_HOTLIGHT) + cf.dwEffects = CFE_UNDERLINE; // and ignores CFM_COLOR, so selfimplementing + cf.crTextColor = GetSysColorBrush(COLOR_HOTLIGHT) ? GetSysColor(COLOR_HOTLIGHT) : RGB(0, 0, 255); + for (int i = 0; i < hyperlinkCount; i++) { + SendMessage(hwndEdit, EM_EXSETSEL, 0, (LPARAM)&hyperlink[i].range); + SendMessage(hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); + } + SendMessage(hwndEdit, EM_SETSEL, 0, 0); +#endif +} + +#ifndef EDITOR +void FreeHyperlinkData(void) +{ + for (int i = 0; i= hyperlink[i].range.cpMin && cpPos <= hyperlink[i].range.cpMax) { + if (pcpMin) + *pcpMin = hyperlink[i].range.cpMin; + if (pcpMax) + *pcpMax = hyperlink[i].range.cpMax; + if (ppszLink) + *ppszLink = hyperlink[i].szLink; + + return 1; + } + if (pcpMin) + *pcpMin = -1; + if (pcpMax) + *pcpMax = -1; + if (ppszLink) + *ppszLink = NULL; + + return 0; +} + +#endif // !defined EDITOR + +#ifdef EDITOR +struct RtfGroupStackData { + BYTE bold, italic, underline, strikeout; + BYTE isDestination, isColourTbl, isFontTbl; + int colour; + int fontSize; + int unicodeSkip; + int charset; +}; + +char *StreamOutHtml(HWND hwndEdit) +{ + EDITSTREAM stream; + struct EditStreamData esd; + struct ResizableCharBuffer htmlOut, hyperlink, *output; + COLORREF *colourTbl = NULL; + int colourTblCount = 0; + struct RtfGroupStackData *groupStack; + int groupLevel; + int inFontTag = 0, inAnchorTag = 0, inBigTag = 0, inSmallTag = 0, lineBreakBefore = 0; + char *pszRtf; + int *fontTblCharsets = NULL; + int fontTblCount = 0; + int normalTextSize = 0; + void *buf; + + ZeroMemory(&stream, sizeof(stream)); + ZeroMemory(&esd, sizeof(esd)); + ZeroMemory(&htmlOut, sizeof(htmlOut)); + ZeroMemory(&hyperlink, sizeof(hyperlink)); + ZeroMemory(&output, sizeof(output)); + + stream.dwCookie = (DWORD)&esd; + stream.pfnCallback = EditStreamOutRtf; + SendMessage(hwndEdit, EM_STREAMOUT, (WPARAM)(CP_UTF8 << 16) | SF_USECODEPAGE | SF_RTFNOOBJS | SFF_PLAINRTF, (LPARAM)&stream); + if (esd.pbBuff == NULL) + return NULL; + + output = &htmlOut; + groupStack = (struct RtfGroupStackData*)mir_calloc(sizeof(struct RtfGroupStackData)); + if (groupStack != NULL) { + groupLevel = 0; + groupStack[0].unicodeSkip = 1; + for (pszRtf = (char*)esd.pbBuff; *pszRtf != '\0';) { + if (*pszRtf == '{') { + buf = (struct RtfGroupStackData*)mir_realloc(groupStack, sizeof(struct RtfGroupStackData)*(groupLevel + 2)); + if (buf == NULL) + break; + groupStack = (struct RtfGroupStackData*)buf; + groupStack[groupLevel] = groupStack[groupLevel]; + groupLevel++; + pszRtf++; + } + else if (*pszRtf == '}') { + groupLevel--; + if (groupStack[groupLevel].bold != groupStack[groupLevel + 1].bold) + AppendToCharBuffer(output, groupStack[groupLevel].bold ? "" : ""); + if (groupStack[groupLevel].italic != groupStack[groupLevel + 1].italic) + AppendToCharBuffer(output, groupStack[groupLevel].bold ? "" : ""); + if (groupStack[groupLevel].underline != groupStack[groupLevel + 1].underline) + AppendToCharBuffer(output, groupStack[groupLevel].bold ? "" : ""); + if (groupStack[groupLevel].strikeout != groupStack[groupLevel + 1].strikeout && groupStack[groupLevel + 1].strikeout) + if (inAnchorTag) { + AppendToCharBuffer(output, ""); + inAnchorTag = 0; + } + if (groupStack[groupLevel].colour != groupStack[groupLevel + 1].colour) + if (inFontTag) { + AppendToCharBuffer(output, ""); + inFontTag = 0; + } + if (groupStack[groupLevel].fontSize != groupStack[groupLevel + 1].fontSize) { + if (inBigTag) { + AppendToCharBuffer(output, ""); + inBigTag = 0; + } + if (inSmallTag) { + AppendToCharBuffer(output, ""); + inSmallTag = 0; + } + if (groupStack[groupLevel].fontSize"); + inSmallTag = 1; + } + else if (groupStack[groupLevel].fontSize>normalTextSize) { + AppendToCharBuffer(output, ""); inBigTag = 1; + } + } + if (groupLevel == 0) + break; + pszRtf++; + } + else if (*pszRtf == '\\' && pszRtf[1] == '*') { + groupStack[groupLevel].isDestination = 1; + pszRtf += 2; + } + else if (*pszRtf == '\\' && pszRtf[1] == '\'') { + char szHex[3] = "\0\0"; + char szChar[2]; + szHex[0] = pszRtf[2]; + if (pszRtf[2]) + szHex[1] = pszRtf[3]; + else + pszRtf--; + szChar[0] = (char)strtol(szHex, NULL, 16); szChar[1] = '\0'; + if (groupStack[groupLevel].charset) { + WCHAR szwChar[2]; + CHARSETINFO csi; + TranslateCharsetInfo((PDWORD)groupStack[groupLevel].charset, &csi, TCI_SRCCHARSET); + MultiByteToWideChar(csi.ciACP, 0, szChar, 1, szwChar, 2); + AppendToCharBuffer(output, "&#%u;", (WORD)szwChar[0]); + } + else + AppendToCharBuffer(output, "&#%u;", (BYTE)szChar[0]); + pszRtf += 4; + } + else if (*pszRtf == '\\' && isalpha(pszRtf[1])) { + char szControlWord[32]; + int iWordEnd; + int hasParam = 0; + int param = -1; + + for (iWordEnd = 1; isalpha(pszRtf[iWordEnd]); iWordEnd++); + CopyMemory(szControlWord, pszRtf + 1, min(sizeof(szControlWord), iWordEnd)); + szControlWord[min(sizeof(szControlWord), iWordEnd) - 1] = '\0'; + if (isdigit(pszRtf[iWordEnd]) || pszRtf[iWordEnd] == '-') { + hasParam = 1; + param = strtol(pszRtf + iWordEnd, &pszRtf, 10); + } + else + pszRtf = pszRtf + iWordEnd; + if (*pszRtf == ' ') + pszRtf++; + if (!lstrcmpiA(szControlWord, "colortbl")) { + groupStack[groupLevel].isColourTbl = 1; + buf = (COLORREF*)mir_realloc(colourTbl, sizeof(COLORREF)); + if (buf != NULL) { + colourTbl = (COLORREF*)buf; + colourTblCount = 1; + colourTbl[0] = 0; + } + groupStack[groupLevel].isDestination = 1; + } + else if (!lstrcmpiA(szControlWord, "fonttbl")) { + groupStack[groupLevel].isFontTbl = 1; + groupStack[groupLevel].isDestination = 1; + } + else if (!lstrcmpiA(szControlWord, "stylesheet")) { + groupStack[groupLevel].isDestination = 1; + } + else if (!lstrcmpiA(szControlWord, "red")) { + if (!hasParam || !colourTblCount) + break; + colourTbl[colourTblCount - 1] &= ~RGB(255, 0, 0); + colourTbl[colourTblCount - 1] |= RGB(param, 0, 0); + } + else if (!lstrcmpiA(szControlWord, "green")) { + if (!hasParam || !colourTblCount) + break; + colourTbl[colourTblCount - 1] &= ~RGB(0, 255, 0); + colourTbl[colourTblCount - 1] |= RGB(0, param, 0); + } + else if (!lstrcmpiA(szControlWord, "blue")) { + if (!hasParam || !colourTblCount) + break; + colourTbl[colourTblCount - 1] &= ~RGB(0, 0, 255); + colourTbl[colourTblCount - 1] |= RGB(0, 0, param); + } + else if (!lstrcmpiA(szControlWord, "f")) { + if (groupStack[groupLevel].isFontTbl) { + buf = (int*)mir_realloc(fontTblCharsets, sizeof(int)*(fontTblCount + 1)); + if (buf != NULL) { + fontTblCharsets = (int*)buf; + fontTblCharsets[fontTblCount] = 0; + fontTblCount++; + } + } + else { + if (hasParam && param >= 0 && param"); + if (hasParam && param) { + int i; + char szColour[7]; + wsprintfA(szColour, "%02x%02x%02x", GetRValue(colourTbl[param]), GetGValue(colourTbl[param]), GetBValue(colourTbl[param])); + for (i = 0; i < sizeof(htmlColourNames); i++) { + if (!lstrcmpiA(szColour, htmlColourNames[i].szClr)) { + AppendToCharBuffer(output, "", htmlColourNames[i].szName); + break; + } + } + if (i == sizeof(htmlColourNames)) + AppendToCharBuffer(output, "", szColour); + inFontTag = 1; + groupStack[groupLevel].colour = param; + } + else + groupStack[groupLevel].colour = 0; + } + else if (!lstrcmpiA(szControlWord, "fs")) { + if (normalTextSize == 0 && hasParam) { + normalTextSize = param; + groupStack[0].fontSize = normalTextSize; + } + if (inBigTag) { + AppendToCharBuffer(output, ""); + inBigTag = 0; + } + if (inSmallTag) { + AppendToCharBuffer(output, ""); + inSmallTag = 0; + } + if (hasParam) { + groupStack[groupLevel].fontSize = param; + if (groupStack[groupLevel].fontSize"); inSmallTag = 1; + } + else if (groupStack[groupLevel].fontSize>normalTextSize) { + AppendToCharBuffer(output, ""); inBigTag = 1; + } + } + } + else if (!lstrcmpiA(szControlWord, "uc")) { + if (hasParam) + groupStack[groupLevel].unicodeSkip = param; + } + else if (!lstrcmpiA(szControlWord, "u")) { + if (hasParam) { + AppendToCharBuffer(output, "&#%u;", param); + pszRtf += groupStack[groupLevel].unicodeSkip; + } + } + else if (!lstrcmpiA(szControlWord, "b")) { + if (!hasParam || param) { + groupStack[groupLevel].bold = 1; + AppendToCharBuffer(output, ""); + } + else { + groupStack[groupLevel].bold = 0; + AppendToCharBuffer(output, ""); + } + } + else if (!lstrcmpiA(szControlWord, "i")) { + if (!hasParam || param) { + groupStack[groupLevel].italic = 1; + AppendToCharBuffer(output, ""); + } + else { + groupStack[groupLevel].italic = 0; + AppendToCharBuffer(output, ""); + } + } + else if (!lstrcmpiA(szControlWord, "ul")) { + if (!hasParam || param) { + groupStack[groupLevel].underline = 1; + AppendToCharBuffer(output, ""); + } + else { + groupStack[groupLevel].underline = 0; + AppendToCharBuffer(output, ""); + } + } + else if (!lstrcmpiA(szControlWord, "ulnone")) { + groupStack[groupLevel].underline = 0; + AppendToCharBuffer(output, ""); + } + else if (!lstrcmpiA(szControlWord, "strike")) { + if (!hasParam || param) { + groupStack[groupLevel].strikeout = 1; + mir_free(hyperlink.sz); // does NULL check + hyperlink.iEnd = hyperlink.cbAlloced = 0; + hyperlink.sz = NULL; + output = &hyperlink; + } + else { + groupStack[groupLevel].strikeout = 0; + if (hyperlink.iEnd && hyperlink.sz != NULL) { + char *pszColon; + output = &htmlOut; + pszColon = strchr(hyperlink.sz, ':'); + if (pszColon == NULL) + pszColon = ""; + else + *pszColon++ = '\0'; + AppendToCharBuffer(output, "%s", pszColon, hyperlink.sz); + mir_free(hyperlink.sz); + hyperlink.iEnd = hyperlink.cbAlloced = 0; + hyperlink.sz = NULL; + } + } + } + else if (!lstrcmpiA(szControlWord, "par")) { + if (lineBreakBefore) + AppendToCharBuffer(output, "
"); + lineBreakBefore = 1; // richedit puts a \par right at the end + } + } + else { + int i; + if (*pszRtf == '\\') + pszRtf++; + if (!groupStack[groupLevel].isDestination) { + if (lineBreakBefore && (BYTE)*pszRtf >= ' ') { + AppendToCharBuffer(output, "
"); + lineBreakBefore = 0; + } + if (*pszRtf == ' ') + AppendCharToCharBuffer(output, *pszRtf); + else { + for (i = 0; i < sizeof(htmlSymbolChars); i++) { + if (*pszRtf == htmlSymbolChars[i].ch) { + AppendToCharBuffer(output, "&%s;", htmlSymbolChars[i].szSym); + break; + } + } + if (i == sizeof(htmlSymbolChars)) + AppendCharToCharBuffer(output, *pszRtf); + } + } + else if (groupStack[groupLevel].isColourTbl && *pszRtf == ';') { + buf = (COLORREF*)mir_realloc(colourTbl, sizeof(COLORREF)*(colourTblCount + 2)); + if (buf != NULL) { + colourTbl = (COLORREF*)buf; + colourTbl[colourTblCount] = 0; + colourTblCount++; + } + } + pszRtf++; + } + } + mir_free(groupStack); + } + mir_free(colourTbl); // does NULL check + mir_free(fontTblCharsets); // does NULL check + mir_free(hyperlink.sz); // does NULL check + + mir_free(esd.pbBuff); + return htmlOut.sz; +} +#endif // defined EDITOR diff --git a/plugins/ContextHelp/src/utils.cpp b/plugins/ContextHelp/src/utils.cpp new file mode 100644 index 0000000000..51d643c774 --- /dev/null +++ b/plugins/ContextHelp/src/utils.cpp @@ -0,0 +1,430 @@ +/* +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" + +#include + +#define CHARBUFFER_ALLOCSTEP 512 // allocated buffer increase in bytes + +#ifdef EDITOR +extern const TCHAR *szControlTypeNames[] = { + _T("Unknown"), _T("Dialog Box"), _T("Button"), _T("Check Box"), _T("Radio Button"), + _T("Text"), _T("Image"), _T("Edit Box"), _T("Group Box"), _T("Combo Box"), + _T("List Box"), _T("Spin Edit Box"), _T("Progress Bar"), _T("Slider"), _T("List View"), + _T("Tree View"), _T("Date/Time Picker"), _T("IP Address"), _T("Status Bar"), _T("Hyperlink"), + _T("Contact List"), _T("Scroll Bar"), _T("Animation"), _T("Hotkey"), _T("Tabs"), + _T("Colour Picker"), _T("Tool Bar"), _T("Combo Edit Box"), _T("Size Grip") }; +#endif + +int GetControlType(HWND hwndCtl) +{ + TCHAR szClassName[32]; + DWORD style; + + if (GetClassLong(hwndCtl, GCW_ATOM) == 32770) + return CTLTYPE_DIALOG; + if (!GetClassName(hwndCtl, szClassName, sizeof(szClassName))) + return CTLTYPE_UNKNOWN; + if (!lstrcmpi(szClassName, _T("MDIClient"))) + return CTLTYPE_DIALOG; + else if (!lstrcmpi(szClassName, _T("Static"))) { + if (GetClassName(GetParent(hwndCtl), szClassName, sizeof(szClassName)) && !lstrcmpi(szClassName, _T("ComboBox")) || !lstrcmpi(szClassName, WC_COMBOBOXEX)) + return CTLTYPE_COMBO; + style = GetWindowLongPtr(hwndCtl, GWL_STYLE); + switch (style&SS_TYPEMASK) { + case SS_BITMAP: + case SS_BLACKFRAME: + case SS_BLACKRECT: + case SS_ENHMETAFILE: + case SS_ETCHEDFRAME: + case SS_ETCHEDHORZ: + case SS_ETCHEDVERT: + case SS_WHITEFRAME: + case SS_WHITERECT: + case SS_GRAYFRAME: + case SS_GRAYRECT: + case SS_ICON: + case SS_OWNERDRAW: + return CTLTYPE_IMAGE; + } + return CTLTYPE_TEXT; + } + else if (GetClassLong(hwndCtl, GCW_ATOM) == 32772) // WinNT/2000/XP: icon titles + return CTLTYPE_IMAGE; // class="#32772" + else if (!lstrcmpi(szClassName, _T("Button"))) { + style = GetWindowLongPtr(hwndCtl, GWL_STYLE); + switch (style & 0x24) { + case BS_CHECKBOX: + case BS_AUTOCHECKBOX: + case BS_3STATE: + case BS_AUTO3STATE: + if (style&BS_PUSHLIKE) + break; + return CTLTYPE_CHECKBOX; + case BS_RADIOBUTTON: + case BS_AUTORADIOBUTTON: + if (style&BS_PUSHLIKE) + break; + return CTLTYPE_RADIO; + case BS_GROUPBOX: + return CTLTYPE_GROUP; + } + return CTLTYPE_BUTTON; + } + else if (!lstrcmpi(szClassName, MIRANDABUTTONCLASS)) + return CTLTYPE_BUTTON; + else if (!lstrcmpi(szClassName, _T("Edit"))) { + if (GetClassName(GetParent(hwndCtl), szClassName, sizeof(szClassName)) && !lstrcmpi(szClassName, _T("ComboBox"))) + return CTLTYPE_COMBO; + if (GetClassName(GetWindow(hwndCtl, GW_HWNDNEXT), szClassName, sizeof(szClassName)) && !lstrcmpi(szClassName, UPDOWN_CLASS)) + if ((HWND)SendMessage(GetWindow(hwndCtl, GW_HWNDNEXT), UDM_GETBUDDY, 0, 0) == hwndCtl) + return CTLTYPE_SPINEDIT; + return CTLTYPE_EDIT; + } + else if (!_tcsnicmp(szClassName, _T("RichEdit"), 8)) + return CTLTYPE_EDIT; // RICHEDIT,RichEdit20A,RichEdit20W,RichEdit50W and future versions + else if (!lstrcmpi(szClassName, _T("ListBox"))) { + style = GetWindowLongPtr(hwndCtl, GWL_STYLE); + if (style&LBS_COMBOBOX) + return CTLTYPE_COMBO; + return CTLTYPE_LIST; + } + else if (!lstrcmpi(szClassName, _T("ComboLBox")) || !lstrcmpi(szClassName, _T("ComboBox")) || !lstrcmpi(szClassName, WC_COMBOBOXEX)) + return CTLTYPE_COMBO; + else if (!lstrcmpi(szClassName, _T("ScrollBar"))) { + style = GetWindowLongPtr(hwndCtl, GWL_STYLE); + if (style&SBS_SIZEBOX) + return CTLTYPE_SIZEGRIP; + return CTLTYPE_SCROLL; + } + else if (!lstrcmpi(szClassName, WC_PAGESCROLLER)) + return CTLTYPE_SCROLL; + else if (!lstrcmpi(szClassName, UPDOWN_CLASS)) { + if (GetClassName((HWND)SendMessage(hwndCtl, UDM_GETBUDDY, 0, 0), szClassName, sizeof(szClassName)) && !lstrcmpi(szClassName, _T("Edit"))) + return CTLTYPE_SPINEDIT; + return CTLTYPE_SCROLL; + } + else if (!lstrcmpi(szClassName, PROGRESS_CLASS)) + return CTLTYPE_PROGRESS; + else if (!lstrcmpi(szClassName, TRACKBAR_CLASS)) + return CTLTYPE_SLIDER; + else if (!lstrcmpi(szClassName, WC_LISTVIEW) || !lstrcmpi(szClassName, WC_HEADER)) + return CTLTYPE_LISTVIEW; + else if (!lstrcmpi(szClassName, WC_TREEVIEW)) + return CTLTYPE_TREEVIEW; + else if (!lstrcmpi(szClassName, DATETIMEPICK_CLASS) || !lstrcmpi(szClassName, MONTHCAL_CLASS)) + return CTLTYPE_DATETIME; + else if (!lstrcmpi(szClassName, WC_IPADDRESS)) + return CTLTYPE_IP; + else if (!lstrcmpi(szClassName, STATUSCLASSNAME)) + return CTLTYPE_STATUSBAR; + else if (!lstrcmpi(szClassName, (LPCWSTR)CLISTCONTROL_CLASS)) + return CTLTYPE_CLC; // look at !! casting + else if (!lstrcmpi(szClassName, WNDCLASS_HYPERLINK) || !lstrcmpi(szClassName, _T("SysLink"))) + return CTLTYPE_HYPERLINK; + else if (!lstrcmpi(szClassName, ANIMATE_CLASS)) + return CTLTYPE_ANIMATION; + else if (!lstrcmpi(szClassName, HOTKEY_CLASS)) + return CTLTYPE_HOTKEY; + else if (!lstrcmpi(szClassName, WC_TABCONTROL)) + return CTLTYPE_TABS; + else if (!lstrcmpi(szClassName, (LPCWSTR)WNDCLASS_COLOURPICKER)) + return CTLTYPE_COLOUR; // look at !! casting + else if (!lstrcmpi(szClassName, TOOLBARCLASSNAME) || !lstrcmpi(szClassName, REBARCLASSNAME)) + return CTLTYPE_TOOLBAR; + switch (SendMessage(hwndCtl, WM_GETDLGCODE, 0, (LPARAM)NULL) & 0x2070) { + case DLGC_RADIOBUTTON: + return CTLTYPE_RADIO; + case DLGC_DEFPUSHBUTTON: + case DLGC_UNDEFPUSHBUTTON: + case DLGC_BUTTON: + return CTLTYPE_BUTTON; + } + hwndCtl = GetWindow(hwndCtl, GW_CHILD); // check for owner-extended control + if (hwndCtl != NULL) + return GetControlType(hwndCtl); + + return CTLTYPE_UNKNOWN; +} + +HWND GetControlDialog(HWND hwndCtl) +{ + TCHAR szClassName[32]; + while (hwndCtl != NULL) { + if (GetClassLong(hwndCtl, GCW_ATOM) == 32770) + return hwndCtl; + if (GetClassName(hwndCtl, szClassName, sizeof(szClassName))) + if (!lstrcmpi(szClassName, _T("MDIClient"))) + return hwndCtl; + hwndCtl = GetParent(hwndCtl); + } + + return hwndCtl; +} + +// never fails +int GetControlTitle(HWND hwndCtl, TCHAR *pszTitle, int cchTitle) +{ + TCHAR *p; + int res = 0; + if (cchTitle) + pszTitle[0] = _T('\0'); + switch (GetControlType(hwndCtl)) { + case CTLTYPE_DIALOG: + case CTLTYPE_BUTTON: + case CTLTYPE_CHECKBOX: + case CTLTYPE_RADIO: + case CTLTYPE_GROUP: + case CTLTYPE_TEXT: + case CTLTYPE_HYPERLINK: + res = GetWindowText(hwndCtl, pszTitle, cchTitle); + } + hwndCtl = GetWindow(hwndCtl, GW_HWNDPREV); + if (hwndCtl) + switch (GetControlType(hwndCtl)) { + case CTLTYPE_TEXT: + case CTLTYPE_GROUP: + res = GetWindowText(hwndCtl, pszTitle, cchTitle); + } + if (res) + for (p = pszTitle; *p != 0; p++) { + // strip-off ampersand (&) prefix character + if (*p == _T('&') && *(p + 1) != _T('&')) { + MoveMemory(p, p + 1, (lstrlen(p + 1) + 1)*sizeof(TCHAR)); + res--; + if (*(p + 1) == 0) + break; + } + // strip-off last ':' + if (*p == _T(':') && *(p + 1) == 0) { + *p = 0; + res--; + break; + } + } + + return res; +} + +// mir_free() the return value +char *GetControlModuleName(HWND hwndCtl) +{ + char szModule[512], szMainModule[512]; + char *pszFile, *buf; + + if (!GetModuleFileNameA(NULL, szMainModule, sizeof(szMainModule))) + return NULL; + buf = strrchr(szMainModule, '\\'); + if (buf != NULL) + *buf = '\0'; + else + buf = szMainModule; + + do { + if (!GetModuleFileNameA((HINSTANCE)GetWindowLongPtr(hwndCtl, GWLP_HINSTANCE), szModule, sizeof(szModule))) + return NULL; + pszFile = strrchr(szModule, '\\'); + if (pszFile != NULL) { + *pszFile = '\0'; + pszFile++; + } + else + pszFile = szModule; + if (lstrlenA(szModule)>lstrlenA(szMainModule)) + szModule[lstrlenA(szMainModule)] = '\0'; + if (!lstrcmpiA(szModule, szMainModule)) + break; // found miranda module + hwndCtl = GetParent(hwndCtl); + } while (hwndCtl != NULL); + + buf = strrchr(pszFile, '.'); + if (buf != NULL) + *buf++ = '\0'; + + return mir_strdup(pszFile); +} + + +struct CreateControlIdData { + int id; + HWND hwndCtl; +}; + +static BOOL CALLBACK CreateCtlIdEnumProc(HWND hwnd, LPARAM lParam) +{ + struct CreateControlIdData* ccid = (struct CreateControlIdData*)lParam; + TCHAR szClassName[32]; + if (GetClassLong(hwnd, GCW_ATOM) == 32770) // class="#32770" + return TRUE; + if (GetClassName(hwnd, szClassName, sizeof(szClassName))) + if (!lstrcmpi(szClassName, _T("MDIClient"))) + return TRUE; + if (GetWindowLongPtr(hwnd, GWL_ID) <= 0 || GetWindowLongPtr(hwnd, GWL_ID) == 0xFFFF) + ccid->id--; + if (hwnd == ccid->hwndCtl) + ccid->hwndCtl = NULL; + + return ccid->hwndCtl != NULL; +} + +int GetControlID(HWND hwndCtl) +{ + struct CreateControlIdData ccid; + TCHAR szClassName[32]; + + // obey context ID when set (rarely) + ccid.id = GetWindowContextHelpId(hwndCtl); + if (ccid.id != 0) + return ccid.id; + + if (GetClassName(hwndCtl, szClassName, sizeof(szClassName))) { + if (!lstrcmpi(szClassName, UPDOWN_CLASS)) { // handle spinner controls as a whole + DWORD style; + HWND hwndBuddy; + style = GetWindowLongPtr(hwndCtl, GWL_STYLE); + if (style&UDS_ALIGNRIGHT || style&UDS_ALIGNLEFT) { + hwndBuddy = (HWND)SendMessage(hwndCtl, UDM_GETBUDDY, 0, 0); + if (hwndBuddy != NULL) + hwndCtl = hwndBuddy; + } + } + else if (GetClassLong(hwndCtl, GCW_ATOM) == 32770 || !lstrcmpi(szClassName, _T("MDIClient"))) + return 0; // ensure this is always unset + } + ccid.id = GetWindowLongPtr(hwndCtl, GWL_ID); + if (ccid.id <= 0 || ccid.id == 0xFFFF) { + ccid.id = -1; + ccid.hwndCtl = hwndCtl; + EnumChildWindows(GetParent(hwndCtl), CreateCtlIdEnumProc, (LPARAM)&ccid); + if (ccid.hwndCtl != NULL) + return -1; + } + return ccid.id; +} + +// mir_free() the return value +static char *Base64Encode(PBYTE pBuf, int cbBuf) +{ + /*NETLIBBASE64 nlb64; + nlb64.pbDecoded=pBuf; + nlb64.cbDecoded=cbBuf; + nlb64.cchEncoded=Netlib_GetBase64EncodedBufferSize(nlb64.cbDecoded); + nlb64.pszEncoded=(char*)mir_alloc(nlb64.cchEncoded); + if(nlb64.pszEncoded==NULL || !CallService(MS_NETLIB_BASE64ENCODE,0,(LPARAM)&nlb64)) { + mir_free(nlb64.pszEncoded); // does NULL check + return NULL; + } + return nlb64.pszEncoded;*/ + + return (char*)mir_base64_encode(pBuf, cbBuf); +} + +struct CreateDialogIdBinaryData { + int alloced, count; + PBYTE buf; + HWND hwndParent; +}; + +static INT_PTR CALLBACK CreateDlgIdBinEnumProc(HWND hwnd, LPARAM lParam) +{ + struct CreateDialogIdBinaryData *cdib = (struct CreateDialogIdBinaryData*)lParam; + int type; + + if (GetParent(hwnd) != cdib->hwndParent) + return TRUE; + type = GetControlType(hwnd); + if (type == CTLTYPE_DIALOG || type == CTLTYPE_TEXT || type == CTLTYPE_GROUP) + return TRUE; + if (cdib->count + 3>cdib->alloced) { + PBYTE buf2; + buf2 = (PBYTE)mir_realloc(cdib->buf, cdib->alloced + 32); + if (buf2 == NULL) + return FALSE; + cdib->alloced += 32; + cdib->buf = buf2; + } + cdib->buf[cdib->count] = (BYTE)type; + *(PWORD)(cdib->buf + cdib->count + 1) = (WORD)GetWindowLongPtr(hwnd, GWL_ID); + cdib->count += 3; + + return TRUE; +} + +// mir_free() the return value +char *CreateDialogIdString(HWND hwndDlg) +{ + struct CreateDialogIdBinaryData cdib; + char *szRet; + + ZeroMemory(&cdib, sizeof(cdib)); + if (hwndDlg == NULL) + return NULL; + cdib.hwndParent = hwndDlg; + EnumChildWindows(hwndDlg, (WNDENUMPROC)CreateDlgIdBinEnumProc, (LPARAM)&cdib); + if (cdib.buf == NULL) + return NULL; + szRet = Base64Encode(cdib.buf, cdib.count); + mir_free(cdib.buf); + + return szRet; +} + + +void AppendCharToCharBuffer(struct ResizableCharBuffer *rcb, char c) +{ + if (rcb->cbAlloced <= rcb->iEnd + 1) { + char* buf = (char*)mir_realloc(rcb->sz, (rcb->cbAlloced + CHARBUFFER_ALLOCSTEP)); + if (buf == NULL) + return; + rcb->sz = buf; + rcb->cbAlloced += CHARBUFFER_ALLOCSTEP; + } + rcb->sz[rcb->iEnd++] = c; + rcb->sz[rcb->iEnd] = '\0'; +} + +void AppendToCharBuffer(struct ResizableCharBuffer *rcb, const char *fmt, ...) +{ + va_list va; + int charsDone; + char *buf; + + if (rcb->cbAlloced == 0) { + buf = (char*)mir_alloc(CHARBUFFER_ALLOCSTEP); + if (buf == NULL) + return; + rcb->sz = buf; + rcb->cbAlloced = CHARBUFFER_ALLOCSTEP; + } + va_start(va, fmt); + for (;;) { + charsDone = mir_vsnprintf(rcb->sz + rcb->iEnd, rcb->cbAlloced - rcb->iEnd, fmt, va); + if (charsDone >= 0) + break; // returns -1 when buffer not large enough + buf = (char*)mir_realloc(rcb->sz, rcb->cbAlloced + CHARBUFFER_ALLOCSTEP); + if (buf == NULL) { + charsDone = 0; + break; + } + rcb->sz = buf; + rcb->cbAlloced += CHARBUFFER_ALLOCSTEP; + } + va_end(va); + rcb->iEnd += charsDone; +} diff --git a/plugins/ContextHelp/src/version.h b/plugins/ContextHelp/src/version.h new file mode 100644 index 0000000000..a1007a2174 --- /dev/null +++ b/plugins/ContextHelp/src/version.h @@ -0,0 +1,53 @@ +/* +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. +*/ + +/* +#define NEEDED_MIRANDA_VERSION PLUGIN_MAKE_VERSION(0,6,0,0) +#define NEEDED_MIRANDA_VERSION_STR "0.6" +#define PLUGIN_VERSION PLUGIN_MAKE_VERSION(0,2,1,2) +#define FILE_VERSION 0,2,1,2 + +#ifdef _DEBUG + #define FILE_VERSION_STR "0.2.1.3 alpha" + #define USERAGENT_VERSION "0.2.1.3 (alpha)" +#else + #define FILE_VERSION_STR "0.2.1.2" + #define USERAGENT_VERSION "0.2.1.2" +#endif + +#define PLUGIN_EMAIL "hrathh users.sourceforge.net" +#define PLUGIN_EMAIL_ATT_POS 7 // position of the @-sign in the email adress above + + #define PLUGIN_WEBSITE "http://addons.miranda-im.org/details.php?action=viewfile&id=3475" +*/ + +#define __MAJOR_VERSION 0 +#define __MINOR_VERSION 2 +#define __RELEASE_NUM 1 +#define __BUILD_NUM 2 + +#include + +#define __PLUGIN_NAME "Context help" +#define __FILENAME "ContextHelp.dll" +#define __DESCRIPTION "Provides context sensitive help in all of the Miranda NG dialog boxes." +#define __AUTHOR "Richard Hughes, H. Herkenrath, Miranda NG Team" +#define __AUTHOREMAIL "" +#define __AUTHORWEB "http://miranda-ng.org/p/ContextHelp/" +#define __COPYRIGHT "© 2002 Richard Hughes, 2005-2007 H. Herkenrath, 2016-16 Miranda NG Team" -- cgit v1.2.3