///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // // Copyright (C) 2012-19 Miranda NG team, // Copyright (c) 2000-09 Miranda ICQ/IM project, // all portions of this codebase are copyrighted to the people // listed in contributors.txt. // // 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; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // part of tabSRMM messaging plugin for Miranda. // // (C) 2005-2010 by silvercircle _at_ gmail _dot_ com and contributors // // these are generic message handlers which are used by the message dialog window procedure. // calling them directly instead of using SendMessage() is faster. // also contains various callback functions for custom buttons #include "stdafx.h" ///////////////////////////////////////////////////////////////////////////////////////// // Save message log for given session as RTF document void CMsgDialog::DM_SaveLogAsRTF() const { if (m_hwndIEView != nullptr) { IEVIEWEVENT event = { sizeof(event) }; event.hwnd = m_hwndIEView; event.hContact = m_hContact; event.iType = IEE_SAVE_DOCUMENT; CallService(MS_IEVIEW_EVENT, 0, (LPARAM)&event); } else { wchar_t szFilter[MAX_PATH], szFilename[MAX_PATH]; mir_snwprintf(szFilter, L"%s%c*.rtf%c%c", TranslateT("Rich Edit file"), 0, 0, 0); mir_snwprintf(szFilename, L"%s.rtf", m_cache->getNick()); Utils::sanitizeFilename(szFilename); wchar_t szInitialDir[MAX_PATH + 2]; mir_snwprintf(szInitialDir, L"%s%s\\", M.getDataPath(), L"\\Saved message logs"); CreateDirectoryTreeW(szInitialDir); OPENFILENAME ofn = { 0 }; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = m_hwnd; ofn.lpstrFile = szFilename; ofn.lpstrFilter = szFilter; ofn.lpstrInitialDir = szInitialDir; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_HIDEREADONLY; ofn.lpstrDefExt = L"rtf"; if (GetSaveFileName(&ofn)) { EDITSTREAM stream = { 0 }; stream.dwCookie = (DWORD_PTR)szFilename; stream.dwError = 0; stream.pfnCallback = Utils::StreamOut; m_log.SendMsg(EM_STREAMOUT, SF_RTF | SF_USECODEPAGE, (LPARAM)&stream); } } } ///////////////////////////////////////////////////////////////////////////////////////// // checks if the balloon tooltip can be dismissed (usually called by WM_MOUSEMOVE events) void CMsgDialog::DM_DismissTip(const POINT& pt) { if (!IsWindowVisible(m_hwndTip)) return; RECT rc; GetWindowRect(m_hwndTip, &rc); if (PtInRect(&rc, pt)) return; if (abs(pt.x - m_ptTipActivation.x) > 5 || abs(pt.y - m_ptTipActivation.y) > 5) { SendMessage(m_hwndTip, TTM_TRACKACTIVATE, FALSE, 0); m_ptTipActivation.x = m_ptTipActivation.y = 0; } } ///////////////////////////////////////////////////////////////////////////////////////// // initialize the balloon tooltip for message window notifications void CMsgDialog::DM_InitTip() { m_hwndTip = CreateWindowEx(0, TOOLTIPS_CLASS, nullptr, WS_POPUP | TTS_NOPREFIX | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hwnd, nullptr, g_plugin.getInst(), (LPVOID)nullptr); memset(&ti, 0, sizeof(ti)); ti.cbSize = sizeof(ti); ti.lpszText = TranslateT("No status message"); ti.hinst = g_plugin.getInst(); ti.hwnd = m_hwnd; ti.uFlags = TTF_TRACK | TTF_IDISHWND | TTF_TRANSPARENT; ti.uId = (UINT_PTR)m_hwnd; SendMessage(m_hwndTip, TTM_ADDTOOL, 0, (LPARAM)&ti); SetWindowPos(m_hwndTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER); } ///////////////////////////////////////////////////////////////////////////////////////// // checks generic hotkeys valid for both IM and MUC sessions // // returns 1 for handled hotkeys, 0 otherwise. bool CMsgDialog::DM_GenericHotkeysCheck(MSG *message) { LRESULT mim_hotkey_check = Hotkey_Check(message, TABSRMM_HK_SECTION_GENERIC); switch (mim_hotkey_check) { case TABSRMM_HK_PASTEANDSEND: HandlePasteAndSend(); return true; case TABSRMM_HK_HISTORY: m_btnHistory.Click(); return true; case TABSRMM_HK_CONTAINEROPTIONS: if (m_pContainer->m_hWndOptions == nullptr) CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_CONTAINEROPTIONS), m_pContainer->m_hwnd, DlgProcContainerOptions, (LPARAM)m_pContainer); return true; case TABSRMM_HK_SEND: if (!(GetWindowLongPtr(m_message.GetHwnd(), GWL_STYLE) & ES_READONLY)) { PostMessage(m_hwnd, WM_COMMAND, IDOK, 0); return true; } break; case TABSRMM_HK_TOGGLEINFOPANEL: m_pPanel.setActive(!m_pPanel.isActive()); m_pPanel.showHide(); return true; case TABSRMM_HK_TOGGLETOOLBAR: SendMessage(m_hwnd, WM_COMMAND, IDC_TOGGLETOOLBAR, 0); return true; case TABSRMM_HK_CLEARLOG: tabClearLog(); return true; case TABSRMM_HK_TOGGLESIDEBAR: if (m_pContainer->m_pSideBar->isActive()) SendMessage(m_hwnd, WM_COMMAND, IDC_TOGGLESIDEBAR, 0); return true; case TABSRMM_HK_CLOSE_OTHER: CloseOtherTabs(GetDlgItem(m_pContainer->m_hwnd, IDC_MSGTABS), *this); return true; } return false; } LRESULT CMsgDialog::DM_MsgWindowCmdHandler(UINT cmd, WPARAM wParam, LPARAM lParam) { RECT rc; int iSelection; HMENU submenu; switch (cmd) { case IDC_SRMM_BOLD: case IDC_SRMM_ITALICS: case IDC_SRMM_UNDERLINE: case IDC_FONTSTRIKEOUT: if (m_SendFormat != 0) { // dont use formatting if disabled CHARFORMAT2 cf, cfOld; memset(&cf, 0, sizeof(CHARFORMAT2)); memset(&cfOld, 0, sizeof(CHARFORMAT2)); cfOld.cbSize = cf.cbSize = sizeof(CHARFORMAT2); cfOld.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_STRIKEOUT; m_message.SendMsg(EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM)&cfOld); BOOL isBold = (cfOld.dwEffects & CFE_BOLD) && (cfOld.dwMask & CFM_BOLD); BOOL isItalic = (cfOld.dwEffects & CFE_ITALIC) && (cfOld.dwMask & CFM_ITALIC); BOOL isUnderline = (cfOld.dwEffects & CFE_UNDERLINE) && (cfOld.dwMask & CFM_UNDERLINE); BOOL isStrikeout = (cfOld.dwEffects & CFM_STRIKEOUT) && (cfOld.dwMask & CFM_STRIKEOUT); int ctrlId = LOWORD(wParam); if (ctrlId == IDC_SRMM_BOLD && !IsWindowEnabled(GetDlgItem(m_hwnd, IDC_SRMM_BOLD))) break; if (ctrlId == IDC_SRMM_ITALICS && !IsWindowEnabled(GetDlgItem(m_hwnd, IDC_SRMM_ITALICS))) break; if (ctrlId == IDC_SRMM_UNDERLINE && !IsWindowEnabled(GetDlgItem(m_hwnd, IDC_SRMM_UNDERLINE))) break; if (ctrlId == IDC_FONTSTRIKEOUT && !IsWindowEnabled(GetDlgItem(m_hwnd, IDC_FONTSTRIKEOUT))) break; if (ctrlId == IDC_SRMM_BOLD) { cf.dwEffects = isBold ? 0 : CFE_BOLD; cf.dwMask = CFM_BOLD; CheckDlgButton(m_hwnd, IDC_SRMM_BOLD, !isBold ? BST_CHECKED : BST_UNCHECKED); } else if (ctrlId == IDC_SRMM_ITALICS) { cf.dwEffects = isItalic ? 0 : CFE_ITALIC; cf.dwMask = CFM_ITALIC; CheckDlgButton(m_hwnd, IDC_SRMM_ITALICS, !isItalic ? BST_CHECKED : BST_UNCHECKED); } else if (ctrlId == IDC_SRMM_UNDERLINE) { cf.dwEffects = isUnderline ? 0 : CFE_UNDERLINE; cf.dwMask = CFM_UNDERLINE; CheckDlgButton(m_hwnd, IDC_SRMM_UNDERLINE, !isUnderline ? BST_CHECKED : BST_UNCHECKED); } else if (ctrlId == IDC_FONTSTRIKEOUT) { cf.dwEffects = isStrikeout ? 0 : CFM_STRIKEOUT; cf.dwMask = CFM_STRIKEOUT; CheckDlgButton(m_hwnd, IDC_FONTSTRIKEOUT, !isStrikeout ? BST_CHECKED : BST_UNCHECKED); } m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); } break; case IDCANCEL: ShowWindow(m_pContainer->m_hwnd, SW_MINIMIZE); return FALSE; case IDC_CLOSE: PostMessage(m_hwnd, WM_CLOSE, 1, 0); break; case IDC_NAME: if (GetKeyState(VK_SHIFT) & 0x8000) // copy UIN Utils::CopyToClipBoard(m_cache->getUIN(), m_hwnd); else CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)(m_cache->getActiveContact()), 0); break; case IDC_SRMM_HISTORY: CallService(MS_HISTORY_SHOWCONTACTHISTORY, m_hContact, 0); break; case IDC_TIME: submenu = GetSubMenu(PluginConfig.g_hMenuContext, 2); MsgWindowUpdateMenu(submenu, MENU_LOGMENU); GetWindowRect(GetDlgItem(m_hwnd, IDC_TIME), &rc); iSelection = TrackPopupMenu(submenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, nullptr); return MsgWindowMenuHandler(iSelection, MENU_LOGMENU); case IDC_PROTOMENU: if (m_hContact) { submenu = GetSubMenu(PluginConfig.g_hMenuContext, 4); int iOldGlobalSendFormat = PluginConfig.m_SendFormat; int iLocalFormat = M.GetDword(m_hContact, "sendformat", 0); int iNewLocalFormat = iLocalFormat; GetWindowRect(GetDlgItem(m_hwnd, IDC_PROTOCOL), &rc); CheckMenuItem(submenu, ID_MODE_GLOBAL, MF_BYCOMMAND | (!(m_dwFlagsEx & MWF_SHOW_SPLITTEROVERRIDE) ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_MODE_PRIVATE, MF_BYCOMMAND | (m_dwFlagsEx & MWF_SHOW_SPLITTEROVERRIDE ? MF_CHECKED : MF_UNCHECKED)); // formatting menu.. CheckMenuItem(submenu, ID_GLOBAL_BBCODE, MF_BYCOMMAND | ((PluginConfig.m_SendFormat) ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_GLOBAL_OFF, MF_BYCOMMAND | ((PluginConfig.m_SendFormat == SENDFORMAT_NONE) ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_THISCONTACT_GLOBALSETTING, MF_BYCOMMAND | ((iLocalFormat == SENDFORMAT_NONE) ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_THISCONTACT_BBCODE, MF_BYCOMMAND | ((iLocalFormat > 0) ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_THISCONTACT_OFF, MF_BYCOMMAND | ((iLocalFormat == -1) ? MF_CHECKED : MF_UNCHECKED)); iSelection = TrackPopupMenu(submenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, nullptr); switch (iSelection) { case ID_MODE_GLOBAL: m_dwFlagsEx &= ~(MWF_SHOW_SPLITTEROVERRIDE); db_set_b(m_hContact, SRMSGMOD_T, "splitoverride", 0); LoadSplitter(); AdjustBottomAvatarDisplay(); DM_RecalcPictureSize(); Resize(); break; case ID_MODE_PRIVATE: m_dwFlagsEx |= MWF_SHOW_SPLITTEROVERRIDE; db_set_b(m_hContact, SRMSGMOD_T, "splitoverride", 1); LoadSplitter(); AdjustBottomAvatarDisplay(); DM_RecalcPictureSize(); Resize(); break; case ID_GLOBAL_BBCODE: PluginConfig.m_SendFormat = SENDFORMAT_BBCODE; break; case ID_GLOBAL_OFF: PluginConfig.m_SendFormat = SENDFORMAT_NONE; break; case ID_THISCONTACT_GLOBALSETTING: iNewLocalFormat = 0; break; case ID_THISCONTACT_BBCODE: iNewLocalFormat = SENDFORMAT_BBCODE; break; case ID_THISCONTACT_OFF: iNewLocalFormat = -1; break; } if (iNewLocalFormat == 0) db_unset(m_hContact, SRMSGMOD_T, "sendformat"); else if (iNewLocalFormat != iLocalFormat) db_set_dw(m_hContact, SRMSGMOD_T, "sendformat", iNewLocalFormat); if (PluginConfig.m_SendFormat != iOldGlobalSendFormat) db_set_b(0, SRMSGMOD_T, "sendformat", (BYTE)PluginConfig.m_SendFormat); if (iNewLocalFormat != iLocalFormat || PluginConfig.m_SendFormat != iOldGlobalSendFormat) { m_SendFormat = M.GetDword(m_hContact, "sendformat", PluginConfig.m_SendFormat); if (m_SendFormat == -1) // per contact override to disable it.. m_SendFormat = 0; Srmm_Broadcast(DM_CONFIGURETOOLBAR, 0, 1); } } break; case IDC_TOGGLETOOLBAR: if (lParam == 1) ApplyContainerSetting(m_pContainer, CNT_NOMENUBAR, m_pContainer->m_dwFlags & CNT_NOMENUBAR ? 0 : 1, true); else ApplyContainerSetting(m_pContainer, CNT_HIDETOOLBAR, m_pContainer->m_dwFlags & CNT_HIDETOOLBAR ? 0 : 1, true); break; case IDC_INFOPANELMENU: submenu = GetSubMenu(PluginConfig.g_hMenuContext, 7); GetWindowRect(GetDlgItem(m_hwnd, IDC_NAME), &rc); { bool bIsFavorite = M.IsFavorite(m_hContact); EnableMenuItem(submenu, ID_FAVORITES_ADDCONTACTTOFAVORITES, !bIsFavorite ? MF_ENABLED : MF_GRAYED); EnableMenuItem(submenu, ID_FAVORITES_REMOVECONTACTFROMFAVORITES, bIsFavorite ? MF_ENABLED : MF_GRAYED); } iSelection = TrackPopupMenu(submenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, nullptr); switch (iSelection) { case ID_FAVORITES_ADDCONTACTTOFAVORITES: M.SetFavorite(m_hContact, 1); AddContactToFavorites(m_hContact, m_cache->getNick(), m_cache->getProto(), m_wszStatus, m_wStatus, Skin_LoadProtoIcon(m_cache->getProto(), m_cache->getStatus()), 1, PluginConfig.g_hMenuFavorites); break; case ID_FAVORITES_REMOVECONTACTFROMFAVORITES: M.SetFavorite(m_hContact, 0); DeleteMenu(PluginConfig.g_hMenuFavorites, m_hContact, MF_BYCOMMAND); break; } break; case IDC_SENDMENU: submenu = GetSubMenu(PluginConfig.g_hMenuContext, 3); GetWindowRect(GetDlgItem(m_hwnd, IDOK), &rc); CheckMenuItem(submenu, ID_SENDMENU_SENDTOMULTIPLEUSERS, MF_BYCOMMAND | (m_sendMode & SMODE_MULTIPLE ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_SENDMENU_SENDDEFAULT, MF_BYCOMMAND | (m_sendMode == 0 ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_SENDMENU_SENDTOCONTAINER, MF_BYCOMMAND | (m_sendMode & SMODE_CONTAINER ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_SENDMENU_SENDLATER, MF_BYCOMMAND | (m_sendMode & SMODE_SENDLATER ? MF_CHECKED : MF_UNCHECKED)); CheckMenuItem(submenu, ID_SENDMENU_SENDWITHOUTTIMEOUTS, MF_BYCOMMAND | (m_sendMode & SMODE_NOACK ? MF_CHECKED : MF_UNCHECKED)); if (lParam) iSelection = TrackPopupMenu(submenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, nullptr); else iSelection = HIWORD(wParam); switch (iSelection) { case ID_SENDMENU_SENDTOMULTIPLEUSERS: m_sendMode ^= SMODE_MULTIPLE; if (m_sendMode & SMODE_MULTIPLE) DM_CreateClist(); else if (IsWindow(GetDlgItem(m_hwnd, IDC_CLIST))) DestroyWindow(GetDlgItem(m_hwnd, IDC_CLIST)); break; case ID_SENDMENU_SENDDEFAULT: m_sendMode = 0; break; case ID_SENDMENU_SENDTOCONTAINER: m_sendMode ^= SMODE_CONTAINER; RedrawWindow(m_hwnd, nullptr, nullptr, RDW_ERASENOW | RDW_UPDATENOW); break; case ID_SENDMENU_SENDLATER: if (sendLater->isAvail()) m_sendMode ^= SMODE_SENDLATER; else CWarning::show(CWarning::WARN_NO_SENDLATER, MB_OK | MB_ICONINFORMATION); break; case ID_SENDMENU_SENDWITHOUTTIMEOUTS: m_sendMode ^= SMODE_NOACK; if (m_sendMode & SMODE_NOACK) db_set_b(m_hContact, SRMSGMOD_T, "no_ack", 1); else db_unset(m_hContact, SRMSGMOD_T, "no_ack"); break; } db_set_b(m_hContact, SRMSGMOD_T, "no_ack", (BYTE)(m_sendMode & SMODE_NOACK ? 1 : 0)); SetWindowPos(m_message.GetHwnd(), nullptr, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOSIZE | SWP_NOMOVE); if (m_sendMode & SMODE_MULTIPLE || m_sendMode & SMODE_CONTAINER) { SetWindowPos(m_message.GetHwnd(), nullptr, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOCOPYBITS); RedrawWindow(m_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); } else { if (IsWindow(GetDlgItem(m_hwnd, IDC_CLIST))) DestroyWindow(GetDlgItem(m_hwnd, IDC_CLIST)); SetWindowPos(m_message.GetHwnd(), nullptr, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOCOPYBITS); RedrawWindow(m_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_ALLCHILDREN); } SendMessage(m_pContainer->m_hwnd, DM_QUERYCLIENTAREA, 0, (LPARAM)&rc); Resize(); DM_ScrollToBottom(1, 1); Utils::showDlgControl(m_hwnd, IDC_MULTISPLITTER, (m_sendMode & SMODE_MULTIPLE) ? SW_SHOW : SW_HIDE); Utils::showDlgControl(m_hwnd, IDC_CLIST, (m_sendMode & SMODE_MULTIPLE) ? SW_SHOW : SW_HIDE); break; case IDC_TOGGLESIDEBAR: SendMessage(m_pContainer->m_hwnd, WM_COMMAND, IDC_TOGGLESIDEBAR, 0); break; case IDC_PIC: GetClientRect(m_hwnd, &rc); m_bEditNotesActive = !m_bEditNotesActive; if (m_bEditNotesActive) { int iLen = GetWindowTextLength(m_message.GetHwnd()); if (iLen != 0) { ActivateTooltip(IDC_SRMM_MESSAGE, TranslateT("You cannot edit user notes when there are unsent messages")); m_bEditNotesActive = false; break; } if (!m_bIsAutosizingInput) { m_iSplitterSaved = m_iSplitterY; m_iSplitterY = rc.bottom / 2; SendMessage(m_hwnd, WM_SIZE, 1, 1); } ptrW wszText(db_get_wsa(m_hContact, "UserInfo", "MyNotes")); if (wszText != nullptr) m_message.SetText(wszText); } else { ptrW buf(m_message.GetText()); db_set_ws(m_hContact, "UserInfo", "MyNotes", buf); m_message.SetText(L""); if (!m_bIsAutosizingInput) { m_iSplitterY = m_iSplitterSaved; Resize(); DM_ScrollToBottom(0, 1); } } SetWindowPos(m_message.GetHwnd(), nullptr, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_NOCOPYBITS); RedrawWindow(m_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_UPDATENOW | RDW_ALLCHILDREN); if (m_bEditNotesActive) CWarning::show(CWarning::WARN_EDITUSERNOTES, MB_OK | MB_ICONINFORMATION); break; case IDM_CLEAR: tabClearLog(); break; case IDC_PROTOCOL: submenu = Menu_BuildContactMenu(m_hContact); if (lParam == 0) GetWindowRect(GetDlgItem(m_hwnd, IDC_PROTOCOL), &rc); else GetWindowRect((HWND)lParam, &rc); iSelection = TrackPopupMenu(submenu, TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, nullptr); if (iSelection) Clist_MenuProcessCommand(LOWORD(iSelection), MPCF_CONTACTMENU, m_hContact); DestroyMenu(submenu); break; // error control case IDC_CANCELSEND: DM_ErrorDetected(MSGERROR_CANCEL, 0); break; case IDC_RETRY: DM_ErrorDetected(MSGERROR_RETRY, 0); break; case IDC_MSGSENDLATER: DM_ErrorDetected(MSGERROR_SENDLATER, 0); break; case IDC_SELFTYPING: if (m_si == nullptr || m_si->iType == GCW_PRIVMESS) { if (m_hContact) { int iCurrentTypingMode = g_plugin.getByte(m_hContact, SRMSGSET_TYPING, g_plugin.getByte(SRMSGSET_TYPINGNEW, SRMSGDEFSET_TYPINGNEW)); if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON && iCurrentTypingMode) { DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); m_nTypeMode = PROTOTYPE_SELFTYPING_OFF; } g_plugin.setByte(m_hContact, SRMSGSET_TYPING, (BYTE)!iCurrentTypingMode); } } break; default: return 0; } return 1; } ///////////////////////////////////////////////////////////////////////////////////////// // initialize rich edit control (log and edit control) for both MUC and // standard IM session windows. void CMsgDialog::DM_InitRichEdit() { bool fIsChat = isChat(); COLORREF colour = fIsChat ? g_Settings.crLogBackground : m_pContainer->m_theme.bg; COLORREF inputcharcolor; char *szStreamOut = nullptr; if (!fIsChat && GetWindowTextLength(m_message.GetHwnd()) > 0) szStreamOut = m_message.GetRichTextRtf(); SetWindowText(m_message.GetHwnd(), L""); m_log.SendMsg(EM_SETBKGNDCOLOR, 0, colour); m_message.SendMsg(EM_SETBKGNDCOLOR, 0, m_pContainer->m_theme.inputbg); CHARFORMAT2A cf2; memset(&cf2, 0, sizeof(CHARFORMAT2A)); cf2.cbSize = sizeof(cf2); if (fIsChat) { LOGFONTA lf; LoadLogfont(FONTSECTION_IM, MSGFONTID_MESSAGEAREA, &lf, &inputcharcolor, FONTMODULE); cf2.dwMask = CFM_COLOR | CFM_FACE | CFM_CHARSET | CFM_SIZE | CFM_WEIGHT | CFM_ITALIC | CFM_BACKCOLOR; cf2.crTextColor = inputcharcolor; cf2.bCharSet = lf.lfCharSet; cf2.crBackColor = m_pContainer->m_theme.inputbg; strncpy(cf2.szFaceName, lf.lfFaceName, LF_FACESIZE); cf2.dwEffects = 0; cf2.wWeight = (WORD)lf.lfWeight; cf2.bPitchAndFamily = lf.lfPitchAndFamily; cf2.yHeight = abs(lf.lfHeight) * 15; } else { LOGFONTA lf = m_pContainer->m_theme.logFonts[MSGFONTID_MESSAGEAREA]; inputcharcolor = m_pContainer->m_theme.fontColors[MSGFONTID_MESSAGEAREA]; for (auto &it : Utils::rtf_clrs) if (it->clr == inputcharcolor) inputcharcolor = RGB(GetRValue(inputcharcolor), GetGValue(inputcharcolor), GetBValue(inputcharcolor) == 0 ? GetBValue(inputcharcolor) + 1 : GetBValue(inputcharcolor) - 1); cf2.dwMask = CFM_COLOR | CFM_FACE | CFM_CHARSET | CFM_SIZE | CFM_WEIGHT | CFM_BOLD | CFM_ITALIC; cf2.crTextColor = inputcharcolor; cf2.bCharSet = lf.lfCharSet; strncpy(cf2.szFaceName, lf.lfFaceName, LF_FACESIZE - 1); cf2.dwEffects = ((lf.lfWeight >= FW_BOLD) ? CFE_BOLD : 0) | (lf.lfItalic ? CFE_ITALIC : 0) | (lf.lfUnderline ? CFE_UNDERLINE : 0) | (lf.lfStrikeOut ? CFE_STRIKEOUT : 0); cf2.wWeight = (WORD)lf.lfWeight; cf2.bPitchAndFamily = lf.lfPitchAndFamily; cf2.yHeight = abs(lf.lfHeight) * 15; } m_message.SendMsg(EM_SETCHARFORMAT, SCF_DEFAULT, (LPARAM)&cf2); m_message.SendMsg(EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf2); /* WINE: fix send colour text. */ m_message.SendMsg(EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&cf2); /* WINE: fix send colour text. */ // setup the rich edit control(s) // LOG is always set to RTL, because this is needed for proper bidirectional operation later. // The real text direction is then enforced by the streaming code which adds appropiate paragraph // and textflow formatting commands to the PARAFORMAT2 pf2; memset(&pf2, 0, sizeof(PARAFORMAT2)); pf2.cbSize = sizeof(pf2); pf2.wEffects = PFE_RTLPARA; pf2.dwMask = PFM_RTLPARA; if (FindRTLLocale()) m_message.SendMsg(EM_SETPARAFORMAT, 0, (LPARAM)&pf2); if (!(m_dwFlags & MWF_LOG_RTL)) { pf2.wEffects = 0; m_message.SendMsg(EM_SETPARAFORMAT, 0, (LPARAM)&pf2); } m_message.SendMsg(EM_SETLANGOPTIONS, 0, (LPARAM)m_message.SendMsg(EM_GETLANGOPTIONS, 0, 0) & ~IMF_AUTOKEYBOARD); pf2.wEffects = PFE_RTLPARA; pf2.dwMask |= PFM_OFFSET; if (m_dwFlags & MWF_INITMODE) { pf2.dwMask |= (PFM_RIGHTINDENT | PFM_OFFSETINDENT); pf2.dxStartIndent = 30; pf2.dxRightIndent = 30; } pf2.dxOffset = m_pContainer->m_theme.left_indent + 30; if (!fIsChat) { ClearLog(); m_log.SendMsg(EM_SETPARAFORMAT, 0, (LPARAM)&pf2); m_log.SendMsg(EM_SETLANGOPTIONS, 0, (LPARAM)m_log.SendMsg(EM_GETLANGOPTIONS, 0, 0) & ~IMF_AUTOKEYBOARD); // set the scrollbars etc to RTL/LTR (only for manual RTL mode) if (m_dwFlags & MWF_LOG_RTL) { SetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE) | WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR); SetWindowLongPtr(m_log.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_log.GetHwnd(), GWL_EXSTYLE) | WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR); } else { SetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE) &~(WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR)); SetWindowLongPtr(m_log.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_log.GetHwnd(), GWL_EXSTYLE) &~(WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR)); } } if (szStreamOut != nullptr) { SETTEXTEX stx = { ST_DEFAULT, CP_UTF8 }; m_message.SendMsg(EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)szStreamOut); mir_free(szStreamOut); } } ///////////////////////////////////////////////////////////////////////////////////////// // set the states of defined database action buttons(only if button is a toggle) void CMsgDialog::DM_SetDBButtonStates() { ButtonItem *buttonItem = m_pContainer->m_buttonItems; MCONTACT hFinalContact = 0; HWND hwndContainer = m_pContainer->m_hwnd; while (buttonItem) { HWND hWnd = GetDlgItem(hwndContainer, buttonItem->uId); if (buttonItem->pfnCallback) buttonItem->pfnCallback(buttonItem, m_hwnd, this, hWnd); if (!(buttonItem->dwFlags & BUTTON_ISTOGGLE && buttonItem->dwFlags & BUTTON_ISDBACTION)) { buttonItem = buttonItem->nextItem; continue; } BOOL result = FALSE; char *szModule = buttonItem->szModule; char *szSetting = buttonItem->szSetting; if (buttonItem->dwFlags & BUTTON_DBACTIONONCONTACT || buttonItem->dwFlags & BUTTON_ISCONTACTDBACTION) { if (m_hContact == 0) { SendMessage(hWnd, BM_SETCHECK, BST_UNCHECKED, 0); buttonItem = buttonItem->nextItem; continue; } if (buttonItem->dwFlags & BUTTON_ISCONTACTDBACTION) szModule = GetContactProto(m_hContact); hFinalContact = m_hContact; } else hFinalContact = 0; switch (buttonItem->type) { case DBVT_BYTE: result = (db_get_b(hFinalContact, szModule, szSetting, 0) == buttonItem->bValuePush[0]); break; case DBVT_WORD: result = (db_get_w(hFinalContact, szModule, szSetting, 0) == *((WORD *)&buttonItem->bValuePush)); break; case DBVT_DWORD: result = (db_get_dw(hFinalContact, szModule, szSetting, 0) == *((DWORD *)&buttonItem->bValuePush)); break; case DBVT_ASCIIZ: ptrA szValue(db_get_sa(hFinalContact, szModule, szSetting)); if (szValue) result = !mir_strcmp((char*)buttonItem->bValuePush, szValue); break; } SendMessage(hWnd, BM_SETCHECK, result, 0); buttonItem = buttonItem->nextItem; } } void CMsgDialog::DM_ScrollToBottom(WPARAM wParam, LPARAM lParam) { if (m_dwFlagsEx & MWF_SHOW_SCROLLINGDISABLED) return; if (IsIconic(m_pContainer->m_hwnd)) m_dwFlags |= MWF_DEFERREDSCROLL; if (m_hwndIEView) { PostMessage(m_hwnd, DM_SCROLLIEVIEW, 0, 0); return; } if (m_hwndHPP) { SendMessage(m_hwnd, DM_SCROLLIEVIEW, 0, 0); return; } if (lParam) SendMessage(m_log.GetHwnd(), WM_SIZE, 0, 0); if (wParam == 1 && lParam == 1) { int len = GetWindowTextLength(m_log.GetHwnd()); SendMessage(m_log.GetHwnd(), EM_SETSEL, len - 1, len - 1); } if (wParam) SendMessage(m_log.GetHwnd(), WM_VSCROLL, MAKEWPARAM(SB_BOTTOM, 0), 0); else PostMessage(m_log.GetHwnd(), WM_VSCROLL, MAKEWPARAM(SB_BOTTOM, 0), 0); if (lParam) InvalidateRect(m_log.GetHwnd(), nullptr, FALSE); } void CMsgDialog::DM_RecalcPictureSize() { HBITMAP hbm = ((m_pPanel.isActive()) && m_pContainer->m_avatarMode != 3) ? m_hOwnPic : (m_ace ? m_ace->hbmPic : PluginConfig.g_hbmUnknown); if (hbm) { BITMAP bminfo; GetObject(hbm, sizeof(bminfo), &bminfo); CalcDynamicAvatarSize(&bminfo); Resize(); } else m_pic.cy = m_pic.cx = 60; } void CMsgDialog::DM_UpdateLastMessage() const { if (m_pContainer->m_hwndStatus == nullptr || m_pContainer->m_hwndActive != m_hwnd) return; wchar_t szBuf[100]; if (m_bShowTyping) { SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING]); mir_snwprintf(szBuf, TranslateT("%s is typing a message..."), m_cache->getNick()); } else if (m_bStatusSet) { SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, (LPARAM)m_szStatusIcon); SendMessage(m_pContainer->m_hwndStatus, SB_SETTEXT, 0, (LPARAM)m_szStatusText.c_str()); return; } else { SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, 0); if (m_pContainer->m_dwFlags & CNT_UINSTATUSBAR) mir_snwprintf(szBuf, L"UID: %s", m_cache->getUIN()); else if (m_lastMessage) { wchar_t date[64], time[64]; TimeZone_PrintTimeStamp(nullptr, m_lastMessage, L"d", date, _countof(date), 0); if (m_pContainer->m_dwFlags & CNT_UINSTATUSBAR && mir_wstrlen(date) > 6) date[mir_wstrlen(date) - 5] = 0; TimeZone_PrintTimeStamp(nullptr, m_lastMessage, L"t", time, _countof(time), 0); mir_snwprintf(szBuf, TranslateT("Last received: %s at %s"), date, time); } else szBuf[0] = 0; } SendMessage(m_pContainer->m_hwndStatus, SB_SETTEXT, 0, (LPARAM)szBuf); } ///////////////////////////////////////////////////////////////////////////////////////// // create embedded contact list control HWND CMsgDialog::DM_CreateClist() { if (!sendLater->isAvail()) { CWarning::show(CWarning::WARN_NO_SENDLATER, MB_OK | MB_ICONINFORMATION); m_sendMode &= ~SMODE_MULTIPLE; return nullptr; } HWND hwndClist = CreateWindowExA(0, "CListControl", "", WS_TABSTOP | WS_VISIBLE | WS_CHILD | 0x248, 184, 0, 30, 30, m_hwnd, (HMENU)IDC_CLIST, g_plugin.getInst(), nullptr); SendMessage(hwndClist, WM_TIMER, 14, 0); HANDLE hItem = (HANDLE)SendMessage(hwndClist, CLM_FINDCONTACT, m_hContact, 0); SetWindowLongPtr(hwndClist, GWL_EXSTYLE, GetWindowLongPtr(hwndClist, GWL_EXSTYLE) & ~CLS_EX_TRACKSELECT); SetWindowLongPtr(hwndClist, GWL_EXSTYLE, GetWindowLongPtr(hwndClist, GWL_EXSTYLE) | (CLS_EX_NOSMOOTHSCROLLING | CLS_EX_NOTRANSLUCENTSEL)); if (!PluginConfig.m_bAllowOfflineMultisend) SetWindowLongPtr(hwndClist, GWL_STYLE, GetWindowLongPtr(hwndClist, GWL_STYLE) | CLS_HIDEOFFLINE); if (hItem) SendMessage(hwndClist, CLM_SETCHECKMARK, (WPARAM)hItem, 1); SendMessage(hwndClist, CLM_SETHIDEEMPTYGROUPS, db_get_b(0, "CList", "HideEmptyGroups", SETTING_USEGROUPS_DEFAULT), 0); SendMessage(hwndClist, CLM_SETUSEGROUPS, db_get_b(0, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT), 0); SendMessage(hwndClist, CLM_FIRST + 106, 0, 1); SendMessage(hwndClist, CLM_AUTOREBUILD, 0, 0); if (hwndClist) RedrawWindow(hwndClist, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW); return hwndClist; } LRESULT CMsgDialog::DM_MouseWheelHandler(WPARAM wParam, LPARAM lParam) { POINT pt; GetCursorPos(&pt); RECT rc, rc1; GetWindowRect(m_message.GetHwnd(), &rc); if (PtInRect(&rc, pt)) return 1; if (m_pContainer->m_dwFlags & CNT_SIDEBAR) { GetWindowRect(GetDlgItem(m_pContainer->m_hwnd, IDC_SIDEBARUP), &rc); GetWindowRect(GetDlgItem(m_pContainer->m_hwnd, IDC_SIDEBARDOWN), &rc1); rc.bottom = rc1.bottom; if (PtInRect(&rc, pt)) { short amount = (short)(HIWORD(wParam)); SendMessage(m_pContainer->m_hwnd, WM_COMMAND, MAKELONG(amount > 0 ? IDC_SIDEBARUP : IDC_SIDEBARDOWN, 0), IDC_SRMM_MESSAGE); return 0; } } if (isChat()) { // scroll nick list by just hovering it RECT rcNicklist; GetWindowRect(m_nickList.GetHwnd(), &rcNicklist); if (PtInRect(&rcNicklist, pt)) { m_nickList.SendMsg(WM_MOUSEWHEEL, wParam, lParam); return 0; } } if (m_hwndIEView) GetWindowRect(m_hwndIEView, &rc); else if (m_hwndHPP) GetWindowRect(m_hwndHPP, &rc); else GetWindowRect(m_log.GetHwnd(), &rc); if (PtInRect(&rc, pt)) { HWND hwndLog = (m_hwndIEView || m_hwndHPP) ? m_hwndIWebBrowserControl : m_log.GetHwnd(); short wDirection = (short)HIWORD(wParam); if (hwndLog == nullptr) hwndLog = WindowFromPoint(pt); if (LOWORD(wParam) & MK_SHIFT || M.GetByte("fastscroll", 0)) { if (wDirection < 0) SendMessage(hwndLog, WM_VSCROLL, MAKEWPARAM(SB_PAGEDOWN, 0), 0); else if (wDirection > 0) SendMessage(hwndLog, WM_VSCROLL, MAKEWPARAM(SB_PAGEUP, 0), 0); } else SendMessage(hwndLog, WM_MOUSEWHEEL, wParam, lParam); return 0; } HWND hwndTab = GetDlgItem(m_pContainer->m_hwnd, IDC_MSGTABS); if (GetTabItemFromMouse(hwndTab, &pt) != -1) { SendMessage(hwndTab, WM_MOUSEWHEEL, wParam, -1); return 0; } return 1; } void CMsgDialog::DM_FreeTheme() { if (m_hTheme) { CloseThemeData(m_hTheme); m_hTheme = nullptr; } if (m_hThemeIP) { CloseThemeData(m_hThemeIP); m_hThemeIP = nullptr; } if (m_hThemeToolbar) { CloseThemeData(m_hThemeToolbar); m_hThemeToolbar = nullptr; } } void CMsgDialog::DM_ThemeChanged() { CSkinItem *item_log = &SkinItems[ID_EXTBKHISTORY]; CSkinItem *item_msg = &SkinItems[ID_EXTBKINPUTAREA]; m_hTheme = OpenThemeData(m_hwnd, L"EDIT"); if (m_hTheme != nullptr || (CSkin::m_skinEnabled && !item_log->IGNORED)) { SetWindowLongPtr(m_log.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_log.GetHwnd(), GWL_EXSTYLE) & ~WS_EX_STATICEDGE); if (isChat()) SetWindowLongPtr(m_nickList.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_nickList.GetHwnd(), GWL_EXSTYLE) & ~(WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); } if (m_hTheme != nullptr || (CSkin::m_skinEnabled && !item_msg->IGNORED)) SetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE) & ~WS_EX_STATICEDGE); m_hThemeIP = M.isAero() ? OpenThemeData(m_hwnd, L"ButtonStyle") : nullptr; m_hThemeToolbar = (M.isAero() || (!CSkin::m_skinEnabled && M.isVSThemed())) ? OpenThemeData(m_hwnd, L"REBAR") : nullptr; } ///////////////////////////////////////////////////////////////////////////////////////// // send out message typing notifications (MTN) when the // user is typing/editing text in the message input area. void CMsgDialog::DM_NotifyTyping(int mode) { if (!m_hContact) return; DeletePopupsForContact(m_hContact, PU_REMOVE_ON_TYPE); const char *szProto = m_cache->getActiveProto(); MCONTACT hContact = m_cache->getActiveContact(); // editing user notes or preparing a message for queued delivery -> don't send MTN if (m_bEditNotesActive || (m_sendMode & SMODE_SENDLATER)) return; // allow supression of sending out TN for the contact (NOTE: for metacontacts, do NOT use the subcontact handle) if (!g_plugin.getByte(hContact, SRMSGSET_TYPING, g_plugin.getByte(SRMSGSET_TYPINGNEW, SRMSGDEFSET_TYPINGNEW))) return; if (szProto == nullptr) // should not, but who knows... return; // check status and capabilities of the protocol DWORD typeCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0); if (!(typeCaps & PF4_SUPPORTTYPING)) return; DWORD protoStatus = Proto_GetStatus(szProto); if (protoStatus < ID_STATUS_ONLINE) return; // check visibility/invisibility lists to not "accidentially" send MTN to contacts who // should not see them (privacy issue) DWORD protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0); if (protoCaps & PF1_VISLIST && db_get_w(hContact, szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE) return; if (protoCaps & PF1_INVISLIST && protoStatus == ID_STATUS_INVISIBLE && db_get_w(hContact, szProto, "ApparentMode", 0) != ID_STATUS_ONLINE) return; // don't send to contacts which are not permanently added to the contact list, // unless the option to ignore added status is set. if (db_get_b(m_hContact, "CList", "NotOnList", 0) && !g_plugin.getByte(SRMSGSET_TYPINGUNKNOWN, SRMSGDEFSET_TYPINGUNKNOWN)) return; // End user check m_nTypeMode = mode; CallService(MS_PROTO_SELFISTYPING, hContact, m_nTypeMode); } void CMsgDialog::DM_OptionsApplied(WPARAM, LPARAM lParam) { m_szMicroLf[0] = 0; if (!(m_pContainer->m_theme.isPrivate)) { m_pContainer->LoadThemeDefaults(); m_dwFlags = m_pContainer->m_theme.dwFlags; } LoadLocalFlags(); m_hTimeZone = TimeZone_CreateByContact(m_hContact, nullptr, TZF_KNOWNONLY); m_bShowUIElements = (m_pContainer->m_dwFlags & CNT_HIDETOOLBAR) == 0; m_dwFlagsEx = M.GetByte(m_hContact, "splitoverride", 0) ? MWF_SHOW_SPLITTEROVERRIDE : 0; m_pPanel.getVisibility(); // small inner margins (padding) for the text areas m_log.SendMsg(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(0, 0)); m_message.SendMsg(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); GetSendFormat(); SetDialogToType(); SendMessage(m_hwnd, DM_CONFIGURETOOLBAR, 0, 0); DM_InitRichEdit(); if (m_hwnd == m_pContainer->m_hwndActive) SendMessage(m_pContainer->m_hwnd, WM_SIZE, 0, 0); InvalidateRect(m_message.GetHwnd(), nullptr, FALSE); if (!lParam) { if (IsIconic(m_pContainer->m_hwnd)) m_dwFlags |= MWF_DEFERREDREMAKELOG; else RemakeLog(); } ShowWindow(m_hwndPanelPicParent, SW_SHOW); EnableWindow(m_hwndPanelPicParent, TRUE); UpdateWindowIcon(); } void CMsgDialog::DM_Typing(bool fForceOff) { HWND hwndContainer = m_pContainer->m_hwnd; HWND hwndStatus = m_pContainer->m_hwndStatus; if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON && GetTickCount() - m_nLastTyping > TIMEOUT_TYPEOFF) DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); if (m_bShowTyping == 1) { if (m_nTypeSecs > 0) { m_nTypeSecs--; if (GetForegroundWindow() == hwndContainer) UpdateWindowIcon(); } else { if (!fForceOff) { m_bShowTyping = 2; m_nTypeSecs = 86400; mir_snwprintf(m_wszStatusBar, TranslateT("%s has entered text."), m_cache->getNick()); if (hwndStatus && m_pContainer->m_hwndActive == m_hwnd) SendMessage(hwndStatus, SB_SETTEXT, 0, (LPARAM)m_wszStatusBar); } UpdateWindowIcon(); HandleIconFeedback(this, (HICON)-1); CMsgDialog *dat_active = (CMsgDialog*)GetWindowLongPtr(m_pContainer->m_hwndActive, GWLP_USERDATA); if (dat_active && !dat_active->isChat()) m_pContainer->UpdateTitle(0); else m_pContainer->UpdateTitle(0, dat_active); if (!(m_pContainer->m_dwFlags & CNT_NOFLASH) && PluginConfig.m_FlashOnMTN) ReflashContainer(m_pContainer); } } else if (m_bShowTyping == 2) { if (m_nTypeSecs > 0) m_nTypeSecs--; else { m_wszStatusBar[0] = 0; m_bShowTyping = 0; } tabUpdateStatusBar(); } else if (m_nTypeSecs > 0) { mir_snwprintf(m_wszStatusBar, TranslateT("%s is typing a message"), m_cache->getNick()); m_nTypeSecs--; if (hwndStatus && m_pContainer->m_hwndActive == m_hwnd) { SendMessage(hwndStatus, SB_SETTEXT, 0, (LPARAM)m_wszStatusBar); SendMessage(hwndStatus, SB_SETICON, 0, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING]); } if (IsIconic(hwndContainer) || !IsActive()) { SetWindowText(hwndContainer, m_wszStatusBar); m_pContainer->m_dwFlags |= CNT_NEED_UPDATETITLE; if (!(m_pContainer->m_dwFlags & CNT_NOFLASH) && PluginConfig.m_FlashOnMTN) ReflashContainer(m_pContainer); } if (m_pContainer->m_hwndActive != m_hwnd) { if (m_bCanFlashTab) m_iFlashIcon = PluginConfig.g_IconTypingEvent; HandleIconFeedback(this, PluginConfig.g_IconTypingEvent); } else { // active tab may show icon if status bar is disabled if (!hwndStatus) { if (TabCtrl_GetItemCount(m_hwndParent) > 1 || !(m_pContainer->m_dwFlags & CNT_HIDETABS)) HandleIconFeedback(this, PluginConfig.g_IconTypingEvent); } } if ((GetForegroundWindow() != hwndContainer) || (m_pContainer->m_hwndStatus == nullptr) || (m_pContainer->m_hwndActive != m_hwnd)) m_pContainer->SetIcon(this, PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING]); m_bShowTyping = 1; } } ///////////////////////////////////////////////////////////////////////////////////////// // sync splitter position for all open sessions. // This cares about private / per container / MUC <> IM splitter syncing and everything. // called from IM and MUC windows via DM_SPLITTERGLOBALEVENT int CMsgDialog::DM_SplitterGlobalEvent(WPARAM wParam, LPARAM lParam) { CMsgDialog *srcDat = PluginConfig.lastSPlitterPos.pSrcDat; TContainerData *srcCnt = PluginConfig.lastSPlitterPos.pSrcContainer; bool fCntGlobal = (!m_pContainer->m_pSettings->fPrivate ? true : false); if (m_bIsAutosizingInput) return 0; RECT rcWin; GetWindowRect(m_hwnd, &rcWin); LONG newPos; if (wParam == 0 && lParam == 0) { if ((m_dwFlagsEx & MWF_SHOW_SPLITTEROVERRIDE) && this != srcDat) return 0; if (srcDat->isChat() == isChat()) newPos = PluginConfig.lastSPlitterPos.pos; else if (!srcDat->isChat() && isChat()) newPos = PluginConfig.lastSPlitterPos.pos + PluginConfig.lastSPlitterPos.off_im; else if (srcDat->isChat() && !isChat()) newPos = PluginConfig.lastSPlitterPos.pos + PluginConfig.lastSPlitterPos.off_im; else newPos = 0; if (this == srcDat) { m_pContainer->m_pSettings->iSplitterY = m_iSplitterY; if (fCntGlobal) SaveSplitter(); return 0; } if (!fCntGlobal && m_pContainer != srcCnt) return 0; if (srcCnt->m_pSettings->fPrivate && m_pContainer != srcCnt) return 0; // for inactive sessions, delay the splitter repositioning until they become // active (faster, avoid redraw/resize problems for minimized windows) if (IsIconic(m_pContainer->m_hwnd) || m_pContainer->m_hwndActive != m_hwnd) { m_dwFlagsEx |= MWF_EX_DELAYEDSPLITTER; m_wParam = newPos; m_lParam = PluginConfig.lastSPlitterPos.lParam; return 0; } } else newPos = wParam; LoadSplitter(); AdjustBottomAvatarDisplay(); DM_RecalcPictureSize(); Resize(); DM_ScrollToBottom(1, 1); if (this != srcDat) UpdateToolbarBG(); return 0; } void CMsgDialog::DM_AddDivider() { if (!(m_dwFlags & MWF_DIVIDERSET) && PluginConfig.m_bUseDividers) { if (GetWindowTextLength(m_log.GetHwnd()) > 0) m_dwFlags |= MWF_DIVIDERWANTED | MWF_DIVIDERSET; } } ///////////////////////////////////////////////////////////////////////////////////////// // incoming event handler void CMsgDialog::DM_EventAdded(WPARAM hContact, LPARAM lParam) { MEVENT hDbEvent = (MEVENT)lParam; DBEVENTINFO dbei = {}; db_event_get(hDbEvent, &dbei); if (m_hDbEventFirst == 0) m_hDbEventFirst = hDbEvent; bool bIsStatusChangeEvent = IsStatusEvent(dbei.eventType); bool bDisableNotify = (dbei.eventType == EVENTTYPE_MESSAGE && (dbei.flags & DBEF_READ)); if (!DbEventIsShown(&dbei)) return; if (dbei.eventType == EVENTTYPE_MESSAGE && !(dbei.flags & (DBEF_SENT))) { m_lastMessage = dbei.timestamp; m_wszStatusBar[0] = 0; if (m_bShowTyping) { m_nTypeSecs = 0; DM_Typing(true); m_bShowTyping = 0; } HandleIconFeedback(this, (HICON)-1); if (m_pContainer->m_hwndStatus) PostMessage(m_hwnd, DM_UPDATELASTMESSAGE, 0, 0); } // set the message log divider to mark new (maybe unseen) messages, if the container has // been minimized or in the background. if (!(dbei.flags & DBEF_SENT) && !bIsStatusChangeEvent) { if (PluginConfig.m_bDividersUsePopupConfig && PluginConfig.m_bUseDividers) { if (!MessageWindowOpened(m_hContact, nullptr)) DM_AddDivider(); } else if (PluginConfig.m_bUseDividers) { if (!m_pContainer->IsActive()) DM_AddDivider(); else if (m_pContainer->m_hwndActive != m_hwnd) DM_AddDivider(); } if (!bDisableNotify) tabSRMM_ShowPopup(hContact, hDbEvent, dbei.eventType, m_pContainer->m_bHidden ? 0 : 1, m_pContainer, m_hwnd, m_cache->getActiveProto()); if (IsWindowVisible(m_pContainer->m_hwnd)) m_pContainer->m_bHidden = false; } m_cache->updateStats(TSessionStats::UPDATE_WITH_LAST_RCV, 0); if (hDbEvent != m_hDbEventFirst) { if (!(m_dwFlagsEx & MWF_SHOW_SCROLLINGDISABLED)) StreamInEvents(hDbEvent, 1, 1, nullptr); else { if (m_iNextQueuedEvent >= m_iEventQueueSize) { m_hQueuedEvents = (MEVENT*)mir_realloc(m_hQueuedEvents, (m_iEventQueueSize + 10) * sizeof(MEVENT)); m_iEventQueueSize += 10; } m_hQueuedEvents[m_iNextQueuedEvent++] = hDbEvent; wchar_t szBuf[100]; mir_snwprintf(szBuf, TranslateT("Auto scrolling is disabled, %d message(s) queued (press F12 to enable it)"), m_iNextQueuedEvent); SetDlgItemText(m_hwnd, IDC_LOGFROZENTEXT, szBuf); RedrawWindow(GetDlgItem(m_hwnd, IDC_LOGFROZENTEXT), nullptr, nullptr, RDW_INVALIDATE); } } else RemakeLog(); // handle tab flashing if (!bDisableNotify && !bIsStatusChangeEvent) if ((TabCtrl_GetCurSel(m_hwndParent) != m_iTabID) && !(dbei.flags & DBEF_SENT)) { switch (dbei.eventType) { case EVENTTYPE_MESSAGE: m_iFlashIcon = PluginConfig.g_IconMsgEvent; break; case EVENTTYPE_FILE: m_iFlashIcon = PluginConfig.g_IconFileEvent; break; default: m_iFlashIcon = PluginConfig.g_IconMsgEvent; break; } SetTimer(m_hwnd, TIMERID_FLASHWND, TIMEOUT_FLASHWND, nullptr); m_bCanFlashTab = true; } // try to flash the contact list... if (!bDisableNotify) FlashOnClist(hDbEvent, &dbei); // autoswitch tab if option is set AND container is minimized (otherwise, we never autoswitch) // never switch for status changes... if (!(dbei.flags & DBEF_SENT) && !bIsStatusChangeEvent) { if (PluginConfig.m_bAutoSwitchTabs && m_pContainer->m_hwndActive != m_hwnd) { if ((IsIconic(m_pContainer->m_hwnd) && !IsZoomed(m_pContainer->m_hwnd)) || (PluginConfig.m_bHideOnClose && !IsWindowVisible(m_pContainer->m_hwnd))) { int iItem = GetTabIndexFromHWND(GetParent(m_hwnd), m_hwnd); if (iItem >= 0) { TabCtrl_SetCurSel(m_hwndParent, iItem); ShowWindow(m_pContainer->m_hwndActive, SW_HIDE); m_pContainer->m_hwndActive = m_hwnd; m_pContainer->UpdateTitle(m_hContact); m_pContainer->m_dwFlags |= CNT_DEFERREDTABSELECT; } } } } // flash window if it is not focused if (!bDisableNotify && !bIsStatusChangeEvent) if (!IsActive() && !(dbei.flags & DBEF_SENT)) { if (!(m_pContainer->m_dwFlags & CNT_NOFLASH) && !m_pContainer->IsActive()) FlashContainer(m_pContainer, 1, 0); m_pContainer->SetIcon(this, Skin_LoadIcon(SKINICON_EVENT_MESSAGE)); m_pContainer->m_dwFlags |= CNT_NEED_UPDATETITLE; } // play a sound if (!bDisableNotify && dbei.eventType == EVENTTYPE_MESSAGE && !(dbei.flags & (DBEF_SENT))) PlayIncomingSound(); if (m_pWnd) m_pWnd->Invalidate(); } void CMsgDialog::DM_HandleAutoSizeRequest(REQRESIZE* rr) { if (rr == nullptr || GetForegroundWindow() != m_pContainer->m_hwnd) return; if (!m_bIsAutosizingInput || m_iInputAreaHeight == -1) return; LONG heightLimit = M.GetDword("autoSplitMinLimit", 0); LONG iNewHeight = rr->rc.bottom - rr->rc.top; if (CSkin::m_skinEnabled && !SkinItems[ID_EXTBKINPUTAREA].IGNORED) iNewHeight += (SkinItems[ID_EXTBKINPUTAREA].MARGIN_TOP + SkinItems[ID_EXTBKINPUTAREA].MARGIN_BOTTOM - 2); if (heightLimit && iNewHeight < heightLimit) iNewHeight = heightLimit; if (iNewHeight == m_iInputAreaHeight) return; RECT rc; GetClientRect(m_hwnd, &rc); LONG cy = rc.bottom - rc.top; LONG panelHeight = (m_pPanel.isActive() ? m_pPanel.getHeight() : 0); if (iNewHeight > (cy - panelHeight) / 2) iNewHeight = (cy - panelHeight) / 2; m_dynaSplitter = iNewHeight - DPISCALEY_S(2); if (m_pContainer->m_dwFlags & CNT_BOTTOMTOOLBAR) m_dynaSplitter += DPISCALEY_S(22); m_iSplitterY = m_dynaSplitter + DPISCALEY_S(34); DM_RecalcPictureSize(); m_iInputAreaHeight = iNewHeight; UpdateToolbarBG(); DM_ScrollToBottom(1, 0); } ///////////////////////////////////////////////////////////////////////////////////////// // status icon stuff (by sje, used for indicating encryption status in the status bar // this is now part of the message window api static int OnSrmmIconChanged(WPARAM hContact, LPARAM) { if (hContact == 0) Srmm_Broadcast(DM_STATUSICONCHANGE, 0, 0); else { HWND hwnd = Srmm_FindWindow(hContact); if (hwnd) PostMessage(hwnd, DM_STATUSICONCHANGE, 0, 0); } return 0; } void CMsgDialog::DrawStatusIcons(HDC hDC, const RECT &rc, int gap) { int x = rc.left; int y = (rc.top + rc.bottom - PluginConfig.m_smcxicon) >> 1; SetBkMode(hDC, TRANSPARENT); int nIcon = 0; while (StatusIconData *sid = Srmm_GetNthIcon(m_hContact, nIcon++)) { if (!mir_strcmp(sid->szModule, MSG_ICON_MODULE)) { if (sid->dwId == MSG_ICON_SOUND) { DrawIconEx(hDC, x, y, PluginConfig.g_buttonBarIcons[ICON_DEFAULT_SOUNDS], PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); DrawIconEx(hDC, x, y, m_pContainer->m_dwFlags & CNT_NOSOUND ? PluginConfig.g_iconOverlayDisabled : PluginConfig.g_iconOverlayEnabled, PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); } else if (sid->dwId == MSG_ICON_UTN) { if (!isChat() || m_si->iType == GCW_PRIVMESS) { DrawIconEx(hDC, x, y, PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING], PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); DrawIconEx(hDC, x, y, g_plugin.getByte(m_hContact, SRMSGSET_TYPING, g_plugin.getByte(SRMSGSET_TYPINGNEW, SRMSGDEFSET_TYPINGNEW)) ? PluginConfig.g_iconOverlayEnabled : PluginConfig.g_iconOverlayDisabled, PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); } else CSkin::DrawDimmedIcon(hDC, x, y, PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING], 50); } else if (sid->dwId == MSG_ICON_SESSION) { DrawIconEx(hDC, x, y, PluginConfig.g_sideBarIcons[0], PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); } } else { HICON hIcon; if ((sid->flags & MBF_DISABLED) && sid->hIconDisabled) hIcon = sid->hIconDisabled; else hIcon = sid->hIcon; if ((sid->flags & MBF_DISABLED) && sid->hIconDisabled == nullptr) CSkin::DrawDimmedIcon(hDC, x, y, PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, hIcon, 50); else DrawIconEx(hDC, x, y, hIcon, 16, 16, 0, nullptr, DI_NORMAL); } x += PluginConfig.m_smcxicon + gap; } } void CMsgDialog::CheckStatusIconClick(POINT pt, const RECT &rc, int gap, int code) { if (code == NM_CLICK || code == NM_RCLICK) { POINT ptScreen; GetCursorPos(&ptScreen); if (!PtInRect(&rcLastStatusBarClick, ptScreen)) return; } UINT iconNum = (pt.x - (rc.left + 0)) / (PluginConfig.m_smcxicon + gap); StatusIconData *sid = Srmm_GetNthIcon(m_hContact, iconNum); if (sid == nullptr) return; if (!mir_strcmp(sid->szModule, MSG_ICON_MODULE)) { if (sid->dwId == MSG_ICON_SOUND && code != NM_RCLICK) { if (GetKeyState(VK_SHIFT) & 0x8000) { for (TContainerData *p = pFirstContainer; p; p = p->pNext) { p->m_dwFlags = ((m_pContainer->m_dwFlags & CNT_NOSOUND) ? p->m_dwFlags | CNT_NOSOUND : p->m_dwFlags & ~CNT_NOSOUND); InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); } } else { m_pContainer->m_dwFlags ^= CNT_NOSOUND; InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); } } else if (sid->dwId == MSG_ICON_UTN && code != NM_RCLICK && (!isChat() || m_si->iType == GCW_PRIVMESS)) { SendMessage(m_pContainer->m_hwndActive, WM_COMMAND, IDC_SELFTYPING, 0); InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); } else if (sid->dwId == MSG_ICON_SESSION) { if (code == NM_CLICK) PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_TRAYICONNOTIFY, 101, WM_LBUTTONUP); else if (code == NM_RCLICK) PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_TRAYICONNOTIFY, 101, WM_RBUTTONUP); } } else { StatusIconClickData sicd = { sizeof(sicd) }; GetCursorPos(&sicd.clickLocation); sicd.dwId = sid->dwId; sicd.szModule = sid->szModule; sicd.flags = (code == NM_RCLICK ? MBCF_RIGHTBUTTON : 0); Srmm_ClickStatusIcon(m_hContact, &sicd); InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); } } void CMsgDialog::DM_ErrorDetected(int type, int flag) { switch (type) { case MSGERROR_CANCEL: case MSGERROR_SENDLATER: if (m_dwFlags & MWF_ERRORSTATE) { m_cache->saveHistory(); if (type == MSGERROR_SENDLATER) sendQueue->doSendLater(m_iCurrentQueueError, this); // to be implemented at a later time m_iOpenJobs--; sendQueue->dec(); if (m_iCurrentQueueError >= 0 && m_iCurrentQueueError < SendQueue::NR_SENDJOBS) sendQueue->clearJob(m_iCurrentQueueError); m_iCurrentQueueError = -1; sendQueue->showErrorControls(this, FALSE); if (type != MSGERROR_CANCEL || (type == MSGERROR_CANCEL && flag == 0)) m_message.SetText(L""); sendQueue->checkQueue(this); int iNextFailed = sendQueue->findNextFailed(this); if (iNextFailed >= 0) sendQueue->handleError(this, iNextFailed); } break; case MSGERROR_RETRY: if (m_dwFlags & MWF_ERRORSTATE) { int resent = 0; m_cache->saveHistory(); if (m_iCurrentQueueError >= 0 && m_iCurrentQueueError < SendQueue::NR_SENDJOBS) { SendJob *job = sendQueue->getJobByIndex(m_iCurrentQueueError); if (job->iSendId == 0 && job->hContact == 0) break; job->iSendId = ProtoChainSend(job->hContact, PSS_MESSAGE, job->dwFlags, (LPARAM)job->szSendBuffer); resent++; } if (resent) { SendJob *job = sendQueue->getJobByIndex(m_iCurrentQueueError); SetTimer(m_hwnd, TIMERID_MSGSEND + m_iCurrentQueueError, PluginConfig.m_MsgTimeout, nullptr); job->iStatus = SendQueue::SQ_INPROGRESS; m_iCurrentQueueError = -1; sendQueue->showErrorControls(this, FALSE); m_message.SetText(L""); sendQueue->checkQueue(this); int iNextFailed = sendQueue->findNextFailed(this); if (iNextFailed >= 0) sendQueue->handleError(this, iNextFailed); } } } } int SI_InitStatusIcons() { StatusIconData sid = {}; sid.szModule = MSG_ICON_MODULE; sid.dwId = MSG_ICON_SOUND; // Sounds Srmm_AddIcon(&sid, &g_plugin); sid.dwId = MSG_ICON_UTN; Srmm_AddIcon(&sid, &g_plugin); sid.dwId = MSG_ICON_SESSION; Srmm_AddIcon(&sid, &g_plugin); HookEvent(ME_MSG_ICONSCHANGED, OnSrmmIconChanged); return 0; }