/* Copyright (C) 2012-24 Miranda NG team (https://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" using namespace CWarning; static MWindowList hWindowList; class CWarningImpl { ptrW m_szTitle, m_szText; UINT m_uId; HFONT m_hFontCaption = nullptr; uint32_t m_dwFlags; HWND m_hwnd = nullptr; bool m_fIsModal; public: CWarningImpl(const wchar_t *tszTitle, const wchar_t *tszText, const UINT uId, const uint32_t dwFlags) : m_szTitle(mir_wstrdup(tszTitle)), m_szText(mir_wstrdup(tszText)) { m_uId = uId; m_dwFlags = dwFlags; m_fIsModal = ((m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL) ? true : false); } ~CWarningImpl() { if (m_hFontCaption) ::DeleteObject(m_hFontCaption); } // static function to construct and show the dialog, returns the user's choice LRESULT ShowDialog() const { if (!m_fIsModal) { ::CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_WARNING), nullptr, stubDlgProc, LPARAM(this)); return 0; } return ::DialogBoxParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_WARNING), nullptr, stubDlgProc, LPARAM(this)); } ///////////////////////////////////////////////////////////////////////////////////////// // stub dlg procedure.Just register the object pointer in WM_INITDIALOG static INT_PTR CALLBACK stubDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { CWarningImpl *w = reinterpret_cast(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); if (w) return(w->dlgProc(hwnd, msg, wParam, lParam)); switch (msg) { case WM_INITDIALOG: w = reinterpret_cast(lParam); if (w) { ::SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); return(w->dlgProc(hwnd, msg, wParam, lParam)); } break; } return FALSE; } ///////////////////////////////////////////////////////////////////////////////////////// // dialog procedure for the warning dialog box INT_PTR CALLBACK dlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: m_hwnd = hwnd; ::SetWindowTextW(hwnd, TranslateT("TabSRMM warning message")); ::Window_SetSkinIcon_IcoLib(hwnd, SKINICON_OTHER_MIRANDA); ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_AUTOURLDETECT, TRUE, 0); ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_SETEVENTMASK, 0, ENM_LINK); TranslateDialogDefault(hwnd); { CMStringW str(FORMAT, RTF_DEFAULT_HEADER, 0, 0, 0, 30 * 15); str.Append(m_szText); str.Append(L"}"); str.Replace(L"\n", L"\\line "); SETTEXTEX stx = {ST_SELECTION, CP_UTF8}; ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_SETTEXTEX, (WPARAM)&stx, T2Utf(str)); ::SetDlgItemTextW(hwnd, IDC_CAPTION, m_szTitle); if (m_dwFlags & CWF_NOALLOWHIDE) Utils::showDlgControl(hwnd, IDC_DONTSHOWAGAIN, SW_HIDE); if (m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL) { Utils::showDlgControl(hwnd, IDOK, SW_HIDE); ::SetFocus(::GetDlgItem(hwnd, IDCANCEL)); } else { Utils::showDlgControl(hwnd, IDCANCEL, SW_HIDE); Utils::showDlgControl(hwnd, IDYES, SW_HIDE); Utils::showDlgControl(hwnd, IDNO, SW_HIDE); ::SetFocus(::GetDlgItem(hwnd, IDOK)); } UINT uResId = 0; if ((m_dwFlags & MB_ICONERROR) || (m_dwFlags & MB_ICONHAND)) uResId = 32513; else if ((m_dwFlags & MB_ICONEXCLAMATION) || (m_dwFlags & MB_ICONWARNING)) uResId = 32515; else if ((m_dwFlags & MB_ICONASTERISK) || (m_dwFlags & MB_ICONINFORMATION)) uResId = 32516; else if (m_dwFlags & MB_ICONQUESTION) uResId = 32514; HICON hIcon; if (uResId) hIcon = reinterpret_cast(::LoadImage(nullptr, MAKEINTRESOURCE(uResId), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); else hIcon = ::Skin_LoadIcon(SKINICON_EVENT_MESSAGE, true); ::SendDlgItemMessageW(hwnd, IDC_WARNICON, STM_SETICON, reinterpret_cast(hIcon), 0); if (!(m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL)) ::ShowWindow(hwnd, SW_SHOWNORMAL); WindowList_Add(hWindowList, hwnd, (UINT_PTR)hwnd); } return TRUE; case WM_CTLCOLORSTATIC: { HWND hwndChild = reinterpret_cast(lParam); UINT id = ::GetDlgCtrlID(hwndChild); if (nullptr == m_hFontCaption) { HFONT hFont = reinterpret_cast(::SendDlgItemMessage(hwnd, IDC_CAPTION, WM_GETFONT, 0, 0)); LOGFONT lf = {0}; ::GetObject(hFont, sizeof(lf), &lf); lf.lfHeight = (int)((double)lf.lfHeight * 1.7f); m_hFontCaption = ::CreateFontIndirect(&lf); ::SendDlgItemMessage(hwnd, IDC_CAPTION, WM_SETFONT, (WPARAM)m_hFontCaption, FALSE); } if (IDC_CAPTION == id) { ::SetTextColor(reinterpret_cast(wParam), ::GetSysColor(COLOR_HIGHLIGHT)); ::SendMessage(hwndChild, WM_SETFONT, (WPARAM)m_hFontCaption, FALSE); } if (IDC_WARNGROUP != id && IDC_DONTSHOWAGAIN != id) { ::SetBkColor((HDC)wParam, ::GetSysColor(COLOR_WINDOW)); return reinterpret_cast(::GetSysColorBrush(COLOR_WINDOW)); } } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: case IDYES: case IDNO: if (::IsDlgButtonChecked(hwnd, IDC_DONTSHOWAGAIN)) { uint32_t newVal = M.GetDword("cWarningsL", 0) | ((uint32_t)1L << m_uId); db_set_dw(0, SRMSGMOD_T, "cWarningsL", newVal); if (LOWORD(wParam) != IDNO) { newVal = M.GetDword("cWarningsV", 0) | ((uint32_t)1L << m_uId); db_set_dw(0, SRMSGMOD_T, "cWarningsV", newVal); } } __fallthrough; case IDCANCEL: if (!m_fIsModal && (IDOK == LOWORD(wParam) || IDCANCEL == LOWORD(wParam))) // modeless dialogs can receive a IDCANCEL from destroyAll() ::DestroyWindow(hwnd); else ::EndDialog(hwnd, LOWORD(wParam)); break; } break; case WM_NOTIFY: switch (((NMHDR *)lParam)->code) { case EN_LINK: switch (((ENLINK *)lParam)->msg) { case WM_LBUTTONUP: ENLINK *e = reinterpret_cast(lParam); const wchar_t *wszUrl = Utils::extractURLFromRichEdit(e, ::GetDlgItem(hwnd, IDC_WARNTEXT)); if (wszUrl) { Utils_OpenUrlW(wszUrl); mir_free(const_cast(wszUrl)); } } } break; case WM_DESTROY: ::SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); delete this; WindowList_Remove(hWindowList, hwnd); Window_FreeIcon_IcoLib(hwnd); break; } return FALSE; } }; ///////////////////////////////////////////////////////////////////////////////////////// // implementation of the CWarningImpl class // // IMPORTANT note to translators for translation of the warning dialogs: // // Make sure to NOT remove the pipe character ( | ) from the strings. This separates the // warning title from the actual warning text. // // Also, do NOT insert multiple | characters in the translated string. Not well-formatted // warnings cannot be translated and the plugin will show the untranslated versions. // // strings marked with a NOT TRANSLATABLE comment cannot be translated at all. This // will be used for important and critical error messages only. // // some strings are empty, this is intentional and used for error messages that share // the message with other possible error notifications (popups, tool tips etc.) // // Entries that do not use the LPGENW() macro are NOT TRANSLATABLE, so don't bother translating them. static wchar_t *Warnings[] = { nullptr, LPGENW("Save file|Unable to save temporary file"), // WARN_SAVEFILE LPGENW("Edit user notes|You are editing the user notes. Click the button again or use the hotkey (default: Alt+N) to save the notes and return to normal messaging mode"), /* WARN_EDITUSERNOTES */ LPGENW("Missing component|The icon pack is missing. Please install it to the default icons folder.\n\nNo icons will be available"), /* WARN_ICONPACKMISSING */ LPGENW("Aero peek warning|You have enabled Aero Peek features and loaded a custom container window skin\n\nThis can result in minor visual anomalies in the live preview feature."), /* WARN_AEROPEEKSKIN */ LPGENW("File transfer problem|Sending the image by file transfer failed.\n\nPossible reasons: File transfers not supported, either you or the target contact is offline, or you are invisible and the target contact is not on your visibility list."), /* WARN_IMGSVC_MISSING */ LPGENW("Settings problem|The option \\b1 History -> Imitate IEView API\\b0 is enabled and the History++ plugin is active. This can cause problems when using IEView as message log viewer.\n\nShould I correct the option (a restart is required)?"), /* WARN_HPP_APICHECK */ LPGENW("Configuration issue|The unattended send feature is disabled. The \\b1 send later\\b0 and \\b1 send to multiple contacts\\b0 features depend on it.\n\nYou must enable it under \\b1Options -> Message sessions -> Advanced tweaks\\b0. Changing this option requires a restart."), /* WARN_NO_SENDLATER */ LPGENW("Closing Window|You are about to close a window with multiple tabs open.\n\nProceed?"), /* WARN_CLOSEWINDOW */ LPGENW("Closing options dialog|To reflect the changes done by importing a theme in the options dialog, the dialog must be closed after loading a theme \\b1 and unsaved changes might be lost\\b0 .\n\nDo you want to continue?"), /* WARN_OPTION_CLOSE */ LPGENW("Loading a theme|Loading a color and font theme can overwrite the settings defined by your skin.\n\nDo you want to continue?"), /* WARN_THEME_OVERWRITE */ }; ///////////////////////////////////////////////////////////////////////////////////////// // send cancel message to all open warning dialogs so they are destroyed // before TabSRMM is unloaded. // // called by the OkToExit handler in globals.cpp void CWarning::destroyAll() { if (hWindowList) WindowList_Broadcast(hWindowList, WM_COMMAND, MAKEWPARAM(IDCANCEL, 0), 0); } ///////////////////////////////////////////////////////////////////////////////////////// // show a warning dialog using the id value. Check whether the user has chosen to // not show this message again. This has room for 64 different warning dialogs, which // should be enough in the first place. Extending it should not be too hard though. LRESULT CWarning::show(const int uId, uint32_t dwFlags, const wchar_t *tszTxt) { if (hWindowList == nullptr) hWindowList = WindowList_Create(); // don't open new warnings when shutdown was initiated (modal ones will otherwise // block the shutdown) if (CMimAPI::m_shutDown) return -1; wchar_t *_s = nullptr; if (tszTxt) _s = const_cast(tszTxt); else { if (uId == -1) return -1; if (dwFlags & CWF_UNTRANSLATED) _s = TranslateW(Warnings[uId]); else { // revert to untranslated warning when the translated message // is not well-formatted. _s = TranslateW(Warnings[uId]); if (mir_wstrlen(_s) < 3 || nullptr == wcschr(_s, '|')) _s = TranslateW(Warnings[uId]); } } if (mir_wstrlen(_s) > 3 && wcschr(_s, '|') != nullptr) { if (uId >= 0 && !(dwFlags & CWF_NOALLOWHIDE)) { uint32_t val = M.GetDword("cWarningsL", 0); uint32_t mask = ((__int64)1L) << uId; if (mask & val) { bool bResult = (M.GetDword("cWarningsV", 0) & mask) != 0; if (dwFlags & MB_YESNO || dwFlags & MB_YESNOCANCEL) return (bResult) ? IDYES : IDNO; return IDOK; } } ptrW s(mir_wstrdup(_s)); wchar_t *separator_pos = wcschr(s, '|'); if (separator_pos) { *separator_pos = 0; CWarningImpl *w = new CWarningImpl(s, separator_pos + 1, uId, dwFlags); if (dwFlags & MB_YESNO || dwFlags & MB_YESNOCANCEL) return w->ShowDialog(); w->ShowDialog(); } } return -1; }