///////////////////////////////////////////////////////////////////////////////////////// // 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 // // This implements the group chat dialog window #include "stdafx.h" // externs... extern LRESULT CALLBACK SplitterSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); extern HRESULT(WINAPI *MyCloseThemeData)(HANDLE); int g_cLinesPerPage = 0; int g_iWheelCarryover = 0; static HKL hkl = nullptr; char szIndicators[] = { 0, '+', '%', '@', '!', '*' }; const CLSID IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80, 0x98, 0x00, 0xAA, 0x00, 0x47, 0xBE, 0x5D } }; ///////////////////////////////////////////////////////////////////////////////////////// // checking if theres's protected text at the point // emulates EN_LINK WM_NOTIFY to parent to process links static BOOL CheckCustomLink(HWND hwndRich, POINT *ptClient, UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL bUrlNeeded) { long res = 0, cnt = 0; long cpMin = 0, cpMax = 0; POINT ptEnd = { 0 }; IRichEditOle *RichEditOle = nullptr; ITextDocument *TextDocument = nullptr; ITextRange *TextRange = nullptr; ITextFont *TextFont = nullptr; BOOL bIsCustomLink = FALSE; POINT pt = *ptClient; ClientToScreen(hwndRich, &pt); do { if (!SendMessage(hwndRich, EM_GETOLEINTERFACE, 0, (LPARAM)&RichEditOle)) break; if (RichEditOle->QueryInterface(IID_ITextDocument, (void**)&TextDocument) != S_OK) break; if (TextDocument->RangeFromPoint(pt.x, pt.y, &TextRange) != S_OK) break; TextRange->GetStart(&cpMin); cpMax = cpMin + 1; TextRange->SetEnd(cpMax); if (TextRange->GetFont(&TextFont) != S_OK) break; TextFont->GetProtected(&res); if (res != tomTrue) break; TextRange->GetPoint(tomEnd + TA_BOTTOM + TA_RIGHT, &ptEnd.x, &ptEnd.y); if (pt.x > ptEnd.x || pt.y > ptEnd.y) break; if (bUrlNeeded) { TextRange->GetStoryLength(&cnt); for (; cpMin > 0; cpMin--) { res = tomTrue; TextRange->SetIndex(tomCharacter, cpMin + 1, tomTrue); TextFont->GetProtected(&res); if (res != tomTrue) { cpMin++; break; } } for (cpMax--; cpMax < cnt; cpMax++) { res = tomTrue; TextRange->SetIndex(tomCharacter, cpMax + 1, tomTrue); TextFont->GetProtected(&res); if (res != tomTrue) break; } } bIsCustomLink = (cpMin < cpMax); } while (FALSE); if (TextFont) TextFont->Release(); if (TextRange) TextRange->Release(); if (TextDocument) TextDocument->Release(); if (RichEditOle) RichEditOle->Release(); if (bIsCustomLink) { ENLINK enlink = {}; enlink.nmhdr.hwndFrom = hwndRich; enlink.nmhdr.idFrom = IDC_SRMM_LOG; enlink.nmhdr.code = EN_LINK; enlink.msg = uMsg; enlink.wParam = wParam; enlink.lParam = lParam; enlink.chrg.cpMin = cpMin; enlink.chrg.cpMax = cpMax; SendMessage(GetParent(hwndRich), WM_NOTIFY, IDC_SRMM_LOG, (LPARAM)&enlink); } return bIsCustomLink; } bool IsStringValidLink(wchar_t *pszText) { if (pszText == nullptr) return false; if (mir_wstrlen(pszText) < 5 || wcschr(pszText, '"')) return false; if (towlower(pszText[0]) == 'w' && towlower(pszText[1]) == 'w' && towlower(pszText[2]) == 'w' && pszText[3] == '.' && iswalnum(pszText[4])) return true; return wcsstr(pszText, L"://") != nullptr; } ///////////////////////////////////////////////////////////////////////////////////////// // called whenever a group chat tab becomes active(either by switching tabs or activating a // container window void CChatRoomDlg::UpdateWindowState(UINT msg) { if (m_si == nullptr) return; if (msg == WM_ACTIVATE) { if (m_pContainer->m_dwFlags & CNT_TRANSPARENCY) { DWORD trans = LOWORD(m_pContainer->m_pSettings->dwTransparency); SetLayeredWindowAttributes(m_pContainer->m_hwnd, CSkin::m_ContainerColorKey, (BYTE)trans, (m_pContainer->m_dwFlags & CNT_TRANSPARENCY ? LWA_ALPHA : 0)); } } if (m_hwndFilter) { POINT pt; GetCursorPos(&pt); RECT rcFilter; GetWindowRect(m_hwndFilter, &rcFilter); if (!PtInRect(&rcFilter, pt)) { SendMessage(m_hwndFilter, WM_CLOSE, 1, 1); m_hwndFilter = nullptr; } } if (m_bIsAutosizingInput && m_iInputAreaHeight == -1) { m_iInputAreaHeight = 0; m_message.SendMsg(EM_REQUESTRESIZE, 0, 0); } m_pPanel.dismissConfig(); m_dwUnread = 0; if (m_pWnd) { m_pWnd->activateTab(); m_pWnd->setOverlayIcon(nullptr, true); } if (m_pContainer->m_hwndSaved == m_hwnd) return; m_pContainer->m_hwndSaved = m_hwnd; g_chatApi.SetActiveSession(m_si); m_hTabIcon = m_hTabStatusIcon; if (m_iTabID >= 0) { if (db_get_w(m_si->hContact, m_si->pszModule, "ApparentMode", 0) != 0) db_set_w(m_si->hContact, m_si->pszModule, "ApparentMode", 0); if (g_clistApi.pfnGetEvent(m_si->hContact, 0)) g_clistApi.pfnRemoveEvent(m_si->hContact, GC_FAKE_EVENT); UpdateTitle(); m_hTabIcon = m_hTabStatusIcon; m_dwTickLastEvent = 0; m_dwFlags &= ~MWF_DIVIDERSET; if (KillTimer(m_hwnd, TIMERID_FLASHWND) || m_iFlashIcon) { FlashTab(false); m_bCanFlashTab = FALSE; m_iFlashIcon = nullptr; } if (m_pContainer->m_dwFlashingStarted != 0) { FlashContainer(m_pContainer, 0, 0); m_pContainer->m_dwFlashingStarted = 0; } m_pContainer->m_dwFlags &= ~CNT_NEED_UPDATETITLE; if (m_dwFlags & MWF_NEEDCHECKSIZE) PostMessage(m_hwnd, DM_SAVESIZE, 0, 0); SetFocus(m_message.GetHwnd()); m_dwLastActivity = GetTickCount(); m_pContainer->m_dwLastActivity = m_dwLastActivity; m_pContainer->m_pMenuBar->configureMenu(); UpdateTrayMenuState(this, FALSE); DM_SetDBButtonStates(); if (m_dwFlagsEx & MWF_EX_DELAYEDSPLITTER) { m_dwFlagsEx &= ~MWF_EX_DELAYEDSPLITTER; ShowWindow(m_pContainer->m_hwnd, SW_RESTORE); PostMessage(m_hwnd, DM_SPLITTERGLOBALEVENT, m_wParam, m_lParam); PostMessage(m_hwnd, WM_SIZE, 0, 0); m_wParam = m_lParam = 0; } } BB_SetButtonsPos(); if (M.isAero()) InvalidateRect(m_hwndParent, nullptr, FALSE); if (m_pContainer->m_dwFlags & CNT_SIDEBAR) m_pContainer->m_pSideBar->setActiveItem(this); if (m_pWnd) m_pWnd->Invalidate(); } ///////////////////////////////////////////////////////////////////////////////////////// // resizer callback for the group chat session window.Called from Mirandas dialog // resizing service int CChatRoomDlg::Resizer(UTILRESIZECONTROL *urc) { bool bToolbar = !(m_pContainer->m_dwFlags & CNT_HIDETOOLBAR); bool bBottomToolbar = (m_pContainer->m_dwFlags & CNT_BOTTOMTOOLBAR) != 0; bool bNick = m_si->iType != GCW_SERVER && m_bNicklistEnabled; bool bInfoPanel = m_pPanel.isActive(); int panelHeight = m_pPanel.getHeight() + 1; int iSplitterX = m_pContainer->m_pSettings->iSplitterX; RECT rcTabs; GetClientRect(m_hwnd, &rcTabs); if (m_bIsAutosizingInput) Utils::showDlgControl(m_hwnd, IDC_SPLITTERY, SW_HIDE); if (m_si->iType != GCW_SERVER) { m_nickList.Show(m_bNicklistEnabled); Utils::showDlgControl(m_hwnd, IDC_SPLITTERX, m_bNicklistEnabled ? SW_SHOW : SW_HIDE); m_btnNickList.Enable(true); m_btnFilter.Enable(true); if (m_si->iType == GCW_CHATROOM) m_btnChannelMgr.Enable(m_si->pMI->bChanMgr); } else { m_nickList.Hide(); Utils::showDlgControl(m_hwnd, IDC_SPLITTERX, SW_HIDE); } if (m_si->iType == GCW_SERVER) { m_btnNickList.Enable(false); m_btnFilter.Enable(false); m_btnChannelMgr.Enable(false); } switch (urc->wId) { case IDC_PANELSPLITTER: urc->rcItem.bottom = panelHeight; urc->rcItem.top = panelHeight - 2; return RD_ANCHORX_WIDTH | RD_ANCHORY_TOP; case IDC_SRMM_LOG: urc->rcItem.top = 0; urc->rcItem.left = 0; urc->rcItem.right = bNick ? urc->dlgNewSize.cx - iSplitterX : urc->dlgNewSize.cx; urc->rcItem.bottom = urc->dlgNewSize.cy - m_iSplitterY; if (!bToolbar || bBottomToolbar) urc->rcItem.bottom += DPISCALEY_S(21); if (bInfoPanel) urc->rcItem.top += panelHeight; if (CSkin::m_skinEnabled) { CSkinItem *item = &SkinItems[ID_EXTBKHISTORY]; if (!item->IGNORED) { urc->rcItem.left += item->MARGIN_LEFT; urc->rcItem.right -= item->MARGIN_RIGHT; urc->rcItem.top += item->MARGIN_TOP; urc->rcItem.bottom -= item->MARGIN_BOTTOM; } } return RD_ANCHORX_CUSTOM | RD_ANCHORY_CUSTOM; case IDC_SRMM_NICKLIST: urc->rcItem.top = 0; urc->rcItem.right = urc->dlgNewSize.cx; urc->rcItem.left = urc->dlgNewSize.cx - iSplitterX + 2; urc->rcItem.bottom = urc->dlgNewSize.cy - m_iSplitterY; if (!bToolbar || bBottomToolbar) urc->rcItem.bottom += DPISCALEY_S(21); if (bInfoPanel) urc->rcItem.top += panelHeight; if (CSkin::m_skinEnabled) { CSkinItem *item = &SkinItems[ID_EXTBKUSERLIST]; if (!item->IGNORED) { urc->rcItem.left += item->MARGIN_LEFT; urc->rcItem.right -= item->MARGIN_RIGHT; urc->rcItem.top += item->MARGIN_TOP; urc->rcItem.bottom -= item->MARGIN_BOTTOM; } } return RD_ANCHORX_CUSTOM | RD_ANCHORY_CUSTOM; case IDC_SPLITTERX: urc->rcItem.right = urc->dlgNewSize.cx - iSplitterX + 2; urc->rcItem.left = urc->dlgNewSize.cx - iSplitterX; urc->rcItem.bottom = urc->dlgNewSize.cy - m_iSplitterY; if (!bToolbar || bBottomToolbar) urc->rcItem.bottom += DPISCALEY_S(21); urc->rcItem.top = 0; if (bInfoPanel) urc->rcItem.top += panelHeight; return RD_ANCHORX_CUSTOM | RD_ANCHORY_CUSTOM; case IDC_SPLITTERY: urc->rcItem.right = urc->dlgNewSize.cx; urc->rcItem.top = urc->dlgNewSize.cy - m_iSplitterY + DPISCALEY_S(23); urc->rcItem.bottom = urc->rcItem.top + DPISCALEY_S(2); urc->rcItem.left = 0; urc->rcItem.bottom++; urc->rcItem.top++; return RD_ANCHORX_CUSTOM | RD_ANCHORY_CUSTOM; case IDC_SRMM_MESSAGE: urc->rcItem.right = urc->dlgNewSize.cx; urc->rcItem.top = urc->dlgNewSize.cy - m_iSplitterY + 3 + DPISCALEY_S(23); urc->rcItem.bottom = urc->dlgNewSize.cy; if (bBottomToolbar && bToolbar) urc->rcItem.bottom -= DPISCALEY_S(22); if (m_bIsAutosizingInput) urc->rcItem.top -= DPISCALEY_S(1); if (CSkin::m_skinEnabled) { CSkinItem *item = &SkinItems[ID_EXTBKINPUTAREA]; if (!item->IGNORED) { urc->rcItem.left += item->MARGIN_LEFT; urc->rcItem.right -= item->MARGIN_RIGHT; urc->rcItem.top += item->MARGIN_TOP; urc->rcItem.bottom -= item->MARGIN_BOTTOM; } } return RD_ANCHORX_CUSTOM | RD_ANCHORY_CUSTOM; } return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; } bool CChatRoomDlg::TabAutoComplete() { LRESULT lResult = m_message.SendMsg(EM_GETSEL, 0, 0); int start = LOWORD(lResult), end = HIWORD(lResult); m_message.SendMsg(EM_SETSEL, end, end); GETTEXTEX gt = { 0 }; gt.codepage = 1200; gt.flags = GTL_DEFAULT | GTL_PRECISE; int iLen = m_message.SendMsg(EM_GETTEXTLENGTHEX, (WPARAM)>, 0); if (iLen <= 0) return false; bool isTopic = false, isRoom = false; wchar_t *pszName = nullptr; wchar_t *pszText = (wchar_t*)mir_calloc((iLen + 10) * sizeof(wchar_t)); gt.flags = GT_DEFAULT; gt.cb = (iLen + 9) * sizeof(wchar_t); m_message.SendMsg(EM_GETTEXTEX, (WPARAM)>, (LPARAM)pszText); if (start > 1 && pszText[start - 1] == ' ' && pszText[start - 2] == ':') start -= 2; if (m_wszSearchResult != nullptr) { int cbResult = (int)mir_wstrlen(m_wszSearchResult); if (start >= cbResult && !wcsnicmp(m_wszSearchResult, pszText + start - cbResult, cbResult)) { start -= cbResult; goto LBL_SkipEnd; } } while (start > 0 && pszText[start - 1] != ' ' && pszText[start - 1] != 13 && pszText[start - 1] != VK_TAB) start--; LBL_SkipEnd: while (end < iLen && pszText[end] != ' ' && pszText[end] != 13 && pszText[end - 1] != VK_TAB) end++; if (pszText[start] == '#') isRoom = TRUE; else { int topicStart = start; while (topicStart > 0 && (pszText[topicStart - 1] == ' ' || pszText[topicStart - 1] == 13 || pszText[topicStart - 1] == VK_TAB)) topicStart--; if (topicStart > 5 && wcsstr(&pszText[topicStart - 6], L"/topic") == &pszText[topicStart - 6]) isTopic = TRUE; } if (m_wszSearchQuery == nullptr) { m_wszSearchQuery = mir_wstrndup(pszText + start, end - start); m_wszSearchResult = mir_wstrdup(m_wszSearchQuery); m_pLastSession = nullptr; } if (isTopic) pszName = m_si->ptszTopic; else if (isRoom) { m_pLastSession = SM_FindSessionAutoComplete(m_si->pszModule, m_si, m_pLastSession, m_wszSearchQuery, m_wszSearchResult); if (m_pLastSession != nullptr) pszName = m_pLastSession->ptszName; } else pszName = g_chatApi.UM_FindUserAutoComplete(m_si, m_wszSearchQuery, m_wszSearchResult); replaceStrW(m_wszSearchResult, nullptr); if (pszName != nullptr) { m_wszSearchResult = mir_wstrdup(pszName); if (end != start) { ptrW szReplace; if (!isRoom && !isTopic && g_Settings.bAddColonToAutoComplete && start == 0) { szReplace = (wchar_t*)mir_alloc((mir_wstrlen(pszName) + 4) * sizeof(wchar_t)); mir_wstrcpy(szReplace, pszName); mir_wstrcat(szReplace, g_Settings.bUseCommaAsColon ? L", " : L": "); pszName = szReplace; } m_message.SendMsg(EM_SETSEL, start, end); m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)pszName); } return true; } if (end != start) { m_message.SendMsg(EM_SETSEL, start, end); m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)m_wszSearchQuery); } replaceStrW(m_wszSearchQuery, nullptr); return false; } static void __cdecl phase2(SESSION_INFO *si) { Thread_SetName("TabSRMM: phase2"); Sleep(30); if (si && si->pDlg) si->pDlg->RedrawLog2(); } ///////////////////////////////////////////////////////////////////////////////////////// // the actual group chat session window procedure.Handles the entire chat session window // which is usually a (tabbed) child of a container class window. CChatRoomDlg::CChatRoomDlg(SESSION_INFO *si) : CTabBaseDlg(IDD_CHANNEL, si), m_btnOk(this, IDOK) { m_szProto = GetContactProto(m_hContact); m_bFilterEnabled = db_get_b(m_hContact, CHAT_MODULE, "FilterEnabled", m_bFilterEnabled) != 0; m_btnOk.OnClick = Callback(this, &CChatRoomDlg::onClick_OK); m_btnFilter.OnClick = Callback(this, &CChatRoomDlg::onClick_Filter); m_btnNickList.OnClick = Callback(this, &CChatRoomDlg::onClick_ShowNickList); m_message.OnChange = Callback(this, &CChatRoomDlg::onChange_Message); } CThumbBase* CChatRoomDlg::tabCreateThumb(CProxyWindow *pProxy) const { return new CThumbMUC(pProxy, m_si); } void CChatRoomDlg::tabClearLog() { SESSION_INFO *s = g_chatApi.SM_FindSession(m_si->ptszID, m_si->pszModule); if (s) { ClearLog(); g_chatApi.LM_RemoveAll(&s->pLog, &s->pLogEnd); s->iEventCount = 0; s->LastTime = 0; m_si->iEventCount = 0; m_si->LastTime = 0; m_si->pLog = s->pLog; m_si->pLogEnd = s->pLogEnd; PostMessage(m_hwnd, WM_MOUSEACTIVATE, 0, 0); } } bool CChatRoomDlg::OnInitDialog() { CTabBaseDlg::OnInitDialog(); m_si->pDlg = this; Chat_SetFilters(m_si); m_iSplitterY = m_pContainer->m_pSettings->iSplitterY; if (m_bIsAutosizingInput) m_iSplitterY = GetDefaultMinimumInputHeight(); // Typing support for GCW_PRIVMESS sessions if (m_si->iType == GCW_PRIVMESS) { m_nTypeMode = PROTOTYPE_SELFTYPING_OFF; SetTimer(m_hwnd, TIMERID_TYPE, 1000, nullptr); } m_pPanel.getVisibility(); m_pPanel.Configure(); SetWindowLongPtr(GetDlgItem(m_hwnd, IDC_PANELSPLITTER), GWLP_WNDPROC, (LONG_PTR)SplitterSubclassProc); NotifyEvent(MSG_WINDOW_EVT_OPENING); m_log.SendMsg(EM_AUTOURLDETECT, 1, 0); m_log.SendMsg(EM_SETEVENTMASK, 0, m_log.SendMsg(EM_GETEVENTMASK, 0, 0) | ENM_LINK | ENM_MOUSEEVENTS | ENM_KEYEVENTS); m_log.SendMsg(EM_LIMITTEXT, 0x7FFFFFFF, 0); m_log.SendMsg(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); m_message.SendMsg(EM_SETEVENTMASK, 0, ENM_REQUESTRESIZE | ENM_MOUSEEVENTS | ENM_SCROLL | ENM_KEYEVENTS | ENM_CHANGE); m_message.SendMsg(EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN, MAKELONG(3, 3)); m_pPanel.loadHeight(); if (PluginConfig.g_hMenuTrayUnread != nullptr && m_hContact != 0 && m_szProto != nullptr) UpdateTrayMenu(nullptr, m_wStatus, m_szProto, m_wszStatus, m_hContact, 0); m_log.SendMsg(EM_HIDESELECTION, TRUE, 0); GetMYUIN(); GetMyNick(); HWND hwndBtn = CreateWindowEx(0, L"MButtonClass", L"", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 0, 0, 6, DPISCALEY_S(20), m_hwnd, (HMENU)IDC_TOGGLESIDEBAR, g_plugin.getInst(), nullptr); CustomizeButton(hwndBtn); SendMessage(hwndBtn, BUTTONSETASTHEMEDBTN, 1, 0); SendMessage(hwndBtn, BUTTONSETCONTAINER, (LPARAM)m_pContainer, 0); SendMessage(hwndBtn, BUTTONSETASFLATBTN, FALSE, 0); SendMessage(hwndBtn, BUTTONSETASTOOLBARBUTTON, TRUE, 0); SendMessage(hwndBtn, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Expand or collapse the side bar"), BATF_UNICODE); DM_InitTip(); BB_InitDlgButtons(); SendMessage(m_hwnd, WM_CBD_LOADICONS, 0, 0); mir_subclassWindow(GetDlgItem(m_hwnd, IDC_SPLITTERX), SplitterSubclassProc); mir_subclassWindow(GetDlgItem(m_hwnd, IDC_SPLITTERY), SplitterSubclassProc); UpdateOptions(); UpdateStatusBar(); UpdateTitle(); m_hTabIcon = m_hTabStatusIcon; RECT rc; SendMessage(m_pContainer->m_hwnd, DM_QUERYCLIENTAREA, 0, (LPARAM)&rc); SetWindowPos(m_hwnd, HWND_TOP, rc.left, rc.top, (rc.right - rc.left), (rc.bottom - rc.top), 0); ShowWindow(m_hwnd, SW_SHOW); UpdateNickList(); m_pContainer->m_hwndActive = m_hwnd; NotifyEvent(MSG_WINDOW_EVT_OPEN); return true; } void CChatRoomDlg::OnDestroy() { // Typing support for GCW_PRIVMESS sessions if (m_si->iType == GCW_PRIVMESS) if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON) DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); if (g_clistApi.pfnGetEvent(m_si->hContact, 0)) g_clistApi.pfnRemoveEvent(m_si->hContact, GC_FAKE_EVENT); m_si->wState &= ~STATE_TALK; m_si->pDlg = nullptr; m_si = nullptr; NotifyEvent(MSG_WINDOW_EVT_CLOSING); DM_FreeTheme(); UpdateTrayMenuState(this, FALSE); // remove me from the tray menu (if still there) if (PluginConfig.g_hMenuTrayUnread) DeleteMenu(PluginConfig.g_hMenuTrayUnread, (UINT_PTR)m_hContact, MF_BYCOMMAND); if (m_hwndTip) DestroyWindow(m_hwndTip); int i = GetTabIndexFromHWND(m_hwndParent, m_hwnd); if (i >= 0) { SendMessage(m_hwndParent, WM_USER + 100, 0, 0); // clean up tooltip TabCtrl_DeleteItem(m_hwndParent, i); m_pContainer->UpdateTabs(); m_iTabID = -1; } if (m_pWnd) { delete m_pWnd; m_pWnd = nullptr; } NotifyEvent(MSG_WINDOW_EVT_CLOSE); m_pContainer->ClearMargins(); PostMessage(m_pContainer->m_hwnd, WM_SIZE, 0, 1); if (m_pContainer->m_dwFlags & CNT_SIDEBAR) m_pContainer->m_pSideBar->removeSession(this); mir_free(m_enteredText); CSuper::OnDestroy(); } void CChatRoomDlg::onClick_OK(CCtrlButton*) { if (GetSendButtonState(m_hwnd) == PBS_DISABLED) return; ptrA pszRtf(m_message.GetRichTextRtf()); g_chatApi.SM_AddCommand(m_si->ptszID, m_si->pszModule, pszRtf); CMStringW ptszText(ptrW(mir_utf8decodeW(pszRtf))); if (ptszText.IsEmpty()) return; DoRtfToTags(ptszText); ptszText.Trim(); if (m_si->pMI->bAckMsg) { m_message.Enable(false); m_message.SendMsg(EM_SETREADONLY, TRUE, 0); } else m_message.SetText(L""); Utils::enableDlgControl(m_hwnd, IDOK, false); // Typing support for GCW_PRIVMESS sessions if (m_si->iType == GCW_PRIVMESS) if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON) DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); bool fSound = true; if (ptszText[0] == '/' || m_si->iType == GCW_SERVER) fSound = false; Chat_DoEventHook(m_si, GC_USER_MESSAGE, nullptr, ptszText, 0); m_si->pMI->idleTimeStamp = time(0); UpdateStatusBar(); if (m_pContainer) if (fSound && !nen_options.iNoSounds && !(m_pContainer->m_dwFlags & CNT_NOSOUND)) Skin_PlaySound("ChatSent"); SetFocus(m_message.GetHwnd()); } void CChatRoomDlg::onClick_Filter(CCtrlButton *pButton) { if (!pButton->Enabled()) return; if (m_iLogFilterFlags == 0 && !m_bFilterEnabled) { MessageBox(nullptr, TranslateT("The filter cannot be enabled, because there are no event types selected either global or for this chat room"), TranslateT("Event filter error"), MB_OK); m_bFilterEnabled = false; } else m_bFilterEnabled = !m_bFilterEnabled; m_btnFilter.SendMsg(BUTTONSETOVERLAYICON, (LPARAM)(m_bFilterEnabled ? PluginConfig.g_iconOverlayEnabled : PluginConfig.g_iconOverlayDisabled), 0); if (m_bFilterEnabled && db_get_b(0, CHAT_MODULE, "RightClickFilter", 1) == 0) { ShowFilterMenu(); return; } RedrawLog(); UpdateTitle(); db_set_b(m_si->hContact, CHAT_MODULE, "FilterEnabled", m_bFilterEnabled); } void CChatRoomDlg::onClick_ShowNickList(CCtrlButton *pButton) { if (!pButton->Enabled() || m_si->iType == GCW_SERVER) return; m_bNicklistEnabled = !m_bNicklistEnabled; Resize(); if (CSkin::m_skinEnabled) InvalidateRect(m_hwnd, nullptr, TRUE); ScrollToBottom(); } void CChatRoomDlg::onChange_Message(CCtrlEdit*) { if (m_pContainer->m_hwndActive == m_hwnd) UpdateReadChars(); m_dwLastActivity = GetTickCount(); m_pContainer->m_dwLastActivity = m_dwLastActivity; m_btnOk.SendMsg(BUTTONSETASNORMAL, m_message.GetRichTextLength() != 0, 0); m_btnOk.Enable(m_message.GetRichTextLength() != 0); // Typing support for GCW_PRIVMESS sessions if (m_si->iType == GCW_PRIVMESS) { if (!(GetKeyState(VK_CONTROL) & 0x8000)) { m_nLastTyping = GetTickCount(); if (GetWindowTextLength(m_message.GetHwnd())) { if (m_nTypeMode == PROTOTYPE_SELFTYPING_OFF) { if (!(m_dwFlags & MWF_INITMODE)) DM_NotifyTyping(PROTOTYPE_SELFTYPING_ON); } } else if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON) DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); } } } ///////////////////////////////////////////////////////////////////////////////////////// void CChatRoomDlg::AddLog() { if (PluginConfig.m_bUseDividers) { if (PluginConfig.m_bDividersUsePopupConfig) { if (!MessageWindowOpened(0, m_hwnd)) DM_AddDivider(); } else { bool bInactive = !IsActive(); if (bInactive) DM_AddDivider(); else if (m_pContainer->m_hwndActive != m_hwnd) DM_AddDivider(); } } CSrmmBaseDialog::AddLog(); } void CChatRoomDlg::CloseTab() { int iTabs = TabCtrl_GetItemCount(m_hwndParent); if (iTabs == 1 && CMimAPI::m_shutDown == 0) { SendMessage(m_pContainer->m_hwnd, WM_CLOSE, 0, 1); return; } m_pContainer->m_iChilds--; int i = GetTabIndexFromHWND(m_hwndParent, m_hwnd); // after closing a tab, we need to activate the tab to the left side of // the previously open tab. // normally, this tab has the same index after the deletion of the formerly active tab // unless, of course, we closed the last (rightmost) tab. if (!m_pContainer->m_bDontSmartClose && iTabs > 1) { if (i == iTabs - 1) i--; else i++; TabCtrl_SetCurSel(m_hwndParent, i); m_pContainer->m_hwndActive = GetTabWindow(m_hwndParent, i); RECT rc; SendMessage(m_pContainer->m_hwnd, DM_QUERYCLIENTAREA, 0, (LPARAM)&rc); SetWindowPos(m_pContainer->m_hwndActive, HWND_TOP, rc.left, rc.top, (rc.right - rc.left), (rc.bottom - rc.top), SWP_SHOWWINDOW); ShowWindow(m_pContainer->m_hwndActive, SW_SHOW); SetForegroundWindow(m_pContainer->m_hwndActive); SetFocus(m_pContainer->m_hwndActive); SendMessage(m_pContainer->m_hwnd, WM_SIZE, 0, 0); } if (iTabs == 1) SendMessage(m_pContainer->m_hwnd, WM_CLOSE, 0, 1); else { PostMessage(m_pContainer->m_hwnd, WM_SIZE, 0, 0); DestroyWindow(m_hwnd); } } void CChatRoomDlg::RedrawLog() { m_si->LastTime = 0; if (m_si->pLog) { LOGINFO *pLog = m_si->pLog; if (m_si->iEventCount > 60) { int index = 0; while (index < 59) { if (pLog->next == nullptr) break; pLog = pLog->next; if ((m_si->iType != GCW_CHATROOM && m_si->iType != GCW_PRIVMESS) || !m_bFilterEnabled || (m_iLogFilterFlags & pLog->iType) != 0) index++; } StreamInEvents(pLog, TRUE); mir_forkThread(phase2, m_si); } else StreamInEvents(m_si->pLogEnd, TRUE); } else ClearLog(); } void CChatRoomDlg::ScrollToBottom() { DM_ScrollToBottom(0, 0); } void CChatRoomDlg::ShowFilterMenu() { m_hwndFilter = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FILTER), m_pContainer->m_hwnd, FilterWndProc, (LPARAM)this); TranslateDialogDefault(m_hwndFilter); RECT rcFilter, rcLog; GetClientRect(m_hwndFilter, &rcFilter); GetWindowRect(m_log.GetHwnd(), &rcLog); POINT pt; pt.x = rcLog.right; pt.y = rcLog.bottom; ScreenToClient(m_pContainer->m_hwnd, &pt); SetWindowPos(m_hwndFilter, HWND_TOP, pt.x - rcFilter.right, pt.y - rcFilter.bottom, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW); } void CChatRoomDlg::UpdateNickList() { int i = m_nickList.SendMsg(LB_GETTOPINDEX, 0, 0); m_nickList.SendMsg(LB_SETCOUNT, m_si->getUserList().getCount(), 0); m_nickList.SendMsg(LB_SETTOPINDEX, i, 0); UpdateTitle(); m_hTabIcon = m_hTabStatusIcon; } void CChatRoomDlg::UpdateOptions() { MODULEINFO *pInfo = m_si ? m_si->pMI : nullptr; if (pInfo) { m_btnBold.Enable(pInfo->bBold); m_btnItalic.Enable(pInfo->bItalics); m_btnUnderline.Enable(pInfo->bUnderline); m_btnColor.Enable(pInfo->bColor); m_btnBkColor.Enable(pInfo->bBkgColor); if (m_si->iType == GCW_CHATROOM) m_btnChannelMgr.Enable(pInfo->bChanMgr); } m_log.SendMsg(EM_SETBKGNDCOLOR, 0, db_get_dw(0, FONTMODULE, SRMSGSET_BKGCOLOUR, SRMSGDEFSET_BKGCOLOUR)); DM_InitRichEdit(); m_btnOk.SendMsg(BUTTONSETASNORMAL, TRUE, 0); m_nickList.SetItemHeight(0, g_Settings.iNickListFontHeight); InvalidateRect(m_nickList.GetHwnd(), nullptr, TRUE); m_btnFilter.SendMsg(BUTTONSETOVERLAYICON, (LPARAM)(m_bFilterEnabled ? PluginConfig.g_iconOverlayEnabled : PluginConfig.g_iconOverlayDisabled), 0); Resize(); RedrawLog2(); } void CChatRoomDlg::UpdateStatusBar() { if (m_pContainer->m_hwndActive != m_hwnd || m_pContainer->m_hwndStatus == nullptr || CMimAPI::m_shutDown || m_wszStatusBar[0]) return; if (m_si->pszModule == nullptr) return; //Mad: strange rare crash here... MODULEINFO *mi = m_si->pMI; if (!mi->ptszModDispName) return; int x = 12; x += Chat_GetTextPixelSize(mi->ptszModDispName, (HFONT)SendMessage(m_pContainer->m_hwndStatus, WM_GETFONT, 0, 0), true); x += GetSystemMetrics(SM_CXSMICON); wchar_t szFinalStatusBarText[512]; if (m_pPanel.isActive()) { time_t now = time(0); DWORD diff = (now - mi->idleTimeStamp) / 60; if (diff >= 1) { if (diff > 59) { DWORD hours = diff / 60; DWORD minutes = diff % 60; mir_snwprintf(mi->tszIdleMsg, TranslateT(", %d %s, %d %s idle"), hours, hours > 1 ? TranslateT("hours") : TranslateT("hour"), minutes, minutes > 1 ? TranslateT("minutes") : TranslateT("minute")); } else mir_snwprintf(mi->tszIdleMsg, TranslateT(", %d %s idle"), diff, diff > 1 ? TranslateT("minutes") : TranslateT("minute")); } else mi->tszIdleMsg[0] = 0; mir_snwprintf(szFinalStatusBarText, TranslateT("%s on %s%s"), m_wszMyNickname, mi->ptszModDispName, mi->tszIdleMsg); } else { if (m_si->ptszStatusbarText) mir_snwprintf(szFinalStatusBarText, L"%s %s", mi->ptszModDispName, m_si->ptszStatusbarText); else wcsncpy_s(szFinalStatusBarText, mi->ptszModDispName, _TRUNCATE); } SendMessage(m_pContainer->m_hwndStatus, SB_SETTEXT, 0, (LPARAM)szFinalStatusBarText); tabUpdateStatusBar(); m_pPanel.Invalidate(); if (m_pWnd) m_pWnd->Invalidate(); } void CChatRoomDlg::UpdateTitle() { m_wStatus = m_si->wStatus; const wchar_t *szNick = m_cache->getNick(); if (mir_wstrlen(szNick) > 0) { if (M.GetByte("cuttitle", 0)) CutContactName(szNick, m_wszTitle, _countof(m_wszTitle)); else wcsncpy_s(m_wszTitle, szNick, _TRUNCATE); } CMStringW wszTitle; HICON hIcon = nullptr; int nUsers = m_si->getUserList().getCount(); switch (m_si->iType) { case GCW_CHATROOM: hIcon = Skin_LoadProtoIcon(m_si->pszModule, (m_wStatus <= ID_STATUS_OFFLINE) ? ID_STATUS_OFFLINE : m_wStatus); wszTitle.Format((nUsers == 1) ? TranslateT("%s: chat room (%u user%s)") : TranslateT("%s: chat room (%u users%s)"), szNick, nUsers, m_bFilterEnabled ? TranslateT(", event filter active") : L""); break; case GCW_PRIVMESS: hIcon = Skin_LoadProtoIcon(m_si->pszModule, (m_wStatus <= ID_STATUS_OFFLINE) ? ID_STATUS_OFFLINE : m_wStatus); if (nUsers == 1) wszTitle.Format(TranslateT("%s: message session"), szNick); else wszTitle.Format(TranslateT("%s: message session (%u users)"), szNick, nUsers); break; case GCW_SERVER: wszTitle.Format(L"%s: Server", szNick); hIcon = LoadIconEx("window"); break; default: return; } if (m_pWnd) { m_pWnd->updateTitle(m_wszTitle); m_pWnd->updateIcon(hIcon); } m_hTabStatusIcon = hIcon; if (m_cache->getStatus() != m_cache->getOldStatus()) { wcsncpy_s(m_wszStatus, Clist_GetStatusModeDescription(m_wStatus, 0), _TRUNCATE); TCITEM item = {}; item.mask = TCIF_TEXT; item.pszText = m_wszTitle; TabCtrl_SetItem(m_hwndParent, m_iTabID, &item); } SetWindowText(m_hwnd, wszTitle); if (m_pContainer->m_hwndActive == m_hwnd) { m_pContainer->UpdateTitle(0, this); UpdateStatusBar(); } } ///////////////////////////////////////////////////////////////////////////////////////// // subclassing for the message history display(rich edit control in which the chat history appears) LRESULT CChatRoomDlg::WndProc_Log(UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_NCCALCSIZE: return CSkin::NcCalcRichEditFrame(m_log.GetHwnd(), this, ID_EXTBKHISTORY, msg, wParam, lParam, stubLogProc); case WM_NCPAINT: return CSkin::DrawRichEditFrame(m_log.GetHwnd(), this, ID_EXTBKHISTORY, msg, wParam, lParam, stubLogProc); case WM_COPY: return WMCopyHandler(msg, wParam, lParam); case WM_SETCURSOR: if (g_Settings.bClickableNicks && (LOWORD(lParam) == HTCLIENT)) { POINT pt; GetCursorPos(&pt); ScreenToClient(m_log.GetHwnd(), &pt); if (CheckCustomLink(m_log.GetHwnd(), &pt, msg, wParam, lParam, FALSE)) return TRUE; } break; case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_RBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: if (g_Settings.bClickableNicks) { POINT pt = { LOWORD(lParam), HIWORD(lParam) }; CheckCustomLink(m_log.GetHwnd(), &pt, msg, wParam, lParam, TRUE); } break; case WM_LBUTTONUP: if (g_Settings.bClickableNicks) { POINT pt = { LOWORD(lParam), HIWORD(lParam) }; CheckCustomLink(m_log.GetHwnd(), &pt, msg, wParam, lParam, TRUE); } if (M.GetByte("autocopy", 1)) { CHARRANGE sel; SendMessage(m_log.GetHwnd(), EM_EXGETSEL, 0, (LPARAM)&sel); if (sel.cpMin != sel.cpMax) { SendMessage(m_log.GetHwnd(), WM_COPY, 0, 0); sel.cpMin = sel.cpMax; SendMessage(m_log.GetHwnd(), EM_EXSETSEL, 0, (LPARAM)&sel); SetFocus(m_message.GetHwnd()); } } break; case WM_KEYDOWN: if (wParam == 0x57 && GetKeyState(VK_CONTROL) & 0x8000) { // ctrl-w (close window) PostMessage(m_hwnd, WM_CLOSE, 0, 1); return TRUE; } if (wParam == VK_INSERT && GetKeyState(VK_CONTROL) & 0x8000) return WMCopyHandler(msg, wParam, lParam); break; case WM_SYSKEYUP: if (wParam == VK_MENU) { ProcessHotkeysByMsgFilter(m_log, msg, wParam, lParam); return 0; } break; case WM_SYSKEYDOWN: m_bkeyProcessed = false; if (ProcessHotkeysByMsgFilter(m_log, msg, wParam, lParam)) { m_bkeyProcessed = true; return 0; } break; case WM_SYSCHAR: if (m_bkeyProcessed) { m_bkeyProcessed = false; return 0; } break; case WM_CHAR: bool isCtrl, isShift, isAlt; KbdState(isShift, isCtrl, isAlt); if (wParam == 0x03 && isCtrl) // Ctrl+C return WMCopyHandler(msg, wParam, lParam); break; } return CSuper::WndProc_Log(msg, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////////////////// // subclassing for the message input control(a richedit text control) LRESULT CChatRoomDlg::WndProc_Message(UINT msg, WPARAM wParam, LPARAM lParam) { if (m_bkeyProcessed && (msg == WM_KEYUP)) { GetKeyboardState(kstate); if (!(kstate[VK_CONTROL] & 0x80) && !(kstate[VK_SHIFT] & 0x80)) m_bkeyProcessed = false; return 0; } switch (msg) { case WM_NCCALCSIZE: return CSkin::NcCalcRichEditFrame(m_message.GetHwnd(), this, ID_EXTBKINPUTAREA, msg, wParam, lParam, stubMessageProc); case WM_NCPAINT: return CSkin::DrawRichEditFrame(m_message.GetHwnd(), this, ID_EXTBKINPUTAREA, msg, wParam, lParam, stubMessageProc); case WM_CONTEXTMENU: POINT pt; GetCursorPos(&pt); { HMENU hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_CONTEXT)); HMENU hSubMenu = GetSubMenu(hMenu, 2); RemoveMenu(hSubMenu, 9, MF_BYPOSITION); RemoveMenu(hSubMenu, 8, MF_BYPOSITION); RemoveMenu(hSubMenu, 4, MF_BYPOSITION); EnableMenuItem(hSubMenu, IDM_PASTEFORMATTED, MF_BYCOMMAND | (m_si->pMI->bBold ? MF_ENABLED : MF_GRAYED)); TranslateMenu(hSubMenu); CHARRANGE sel, all = { 0, -1 }; m_message.SendMsg(EM_EXGETSEL, 0, (LPARAM)&sel); if (sel.cpMin == sel.cpMax) { EnableMenuItem(hSubMenu, IDM_COPY, MF_BYCOMMAND | MF_GRAYED); EnableMenuItem(hSubMenu, IDM_CUT, MF_BYCOMMAND | MF_GRAYED); } MessageWindowPopupData mwpd = { sizeof(mwpd) }; mwpd.uType = MSG_WINDOWPOPUP_SHOWING; mwpd.uFlags = MSG_WINDOWPOPUP_INPUT; mwpd.hContact = m_hContact; mwpd.hwnd = m_message.GetHwnd(); mwpd.hMenu = hSubMenu; mwpd.pt = pt; NotifyEventHooks(g_chatApi.hevWinPopup, 0, (LPARAM)&mwpd); int iSelection = TrackPopupMenu(hSubMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_hwnd, nullptr); mwpd.selection = iSelection; mwpd.uType = MSG_WINDOWPOPUP_SELECTED; NotifyEventHooks(g_chatApi.hevWinPopup, 0, (LPARAM)&mwpd); switch (iSelection) { case IDM_COPY: m_message.SendMsg(WM_COPY, 0, 0); break; case IDM_CUT: m_message.SendMsg(WM_CUT, 0, 0); break; case IDM_PASTE: case IDM_PASTEFORMATTED: m_message.SendMsg(EM_PASTESPECIAL, (iSelection == IDM_PASTE) ? CF_UNICODETEXT : 0, 0); break; case IDM_COPYALL: m_message.SendMsg(EM_EXSETSEL, 0, (LPARAM)&all); m_message.SendMsg(WM_COPY, 0, 0); m_message.SendMsg(EM_EXSETSEL, 0, (LPARAM)&sel); break; case IDM_SELECTALL: m_message.SendMsg(EM_EXSETSEL, 0, (LPARAM)&all); break; } DestroyMenu(hMenu); } return TRUE; case WM_MOUSEWHEEL: if (DM_MouseWheelHandler(wParam, lParam) == 0) return 0; m_iLastEnterTime = 0; break; case WM_SYSKEYUP: if (wParam == VK_MENU) { ProcessHotkeysByMsgFilter(m_message, msg, wParam, lParam); return 0; } break; case WM_SYSKEYDOWN: m_bkeyProcessed = false; if (ProcessHotkeysByMsgFilter(m_message, msg, wParam, lParam)) { m_bkeyProcessed = true; return 0; } break; case WM_SYSCHAR: if (m_bkeyProcessed) { m_bkeyProcessed = false; // preceeding key event has been processed by miranda hotkey service return 0; } if ((wParam >= '0' && wParam <= '9') && (GetKeyState(VK_MENU) & 0x8000)) { // ALT-1 -> ALT-0 direct tab selection BYTE bChar = (BYTE)wParam; int iIndex = (bChar == '0') ? 10 : bChar - (BYTE)'0'; SendMessage(m_pContainer->m_hwnd, DM_SELECTTAB, DM_SELECT_BY_INDEX, (LPARAM)iIndex); return 0; } break; case WM_CHAR: bool isShift, isAlt, isCtrl; KbdState(isShift, isCtrl, isAlt); if (!isAlt && !isCtrl && !(m_pContainer->m_dwFlags & CNT_NOSOUND) && wParam != VK_ESCAPE && !(wParam == VK_TAB && PluginConfig.m_bAllowTab)) Skin_PlaySound("SoundOnTyping"); if (isCtrl && !isAlt && !isShift) switch (wParam) { case 0x17: PostMessage(m_hwnd, WM_CLOSE, 0, 1); return 0; } break; case WM_KEYDOWN: KbdState(isShift, isCtrl, isAlt); // sound on typing.. if (!isAlt && wParam == VK_DELETE) Skin_PlaySound("SoundOnTyping"); if (wParam != VK_ESCAPE) if (ProcessHotkeys(wParam, isShift, isCtrl, isAlt)) return true; if (wParam == VK_INSERT && !isShift && !isCtrl && !isAlt) { m_bInsertMode = !m_bInsertMode; m_message.OnChange(&m_message); } if (wParam == VK_CAPITAL || wParam == VK_NUMLOCK) m_message.OnChange(&m_message); if (isCtrl && isAlt && !isShift) { switch (wParam) { case VK_UP: case VK_DOWN: case VK_PRIOR: case VK_NEXT: case VK_HOME: case VK_END: WPARAM wp = 0; if (wParam == VK_UP) wp = MAKEWPARAM(SB_LINEUP, 0); else if (wParam == VK_PRIOR) wp = MAKEWPARAM(SB_PAGEUP, 0); else if (wParam == VK_NEXT) wp = MAKEWPARAM(SB_PAGEDOWN, 0); else if (wParam == VK_HOME) wp = MAKEWPARAM(SB_TOP, 0); else if (wParam == VK_END) { DM_ScrollToBottom(0, 0); return 0; } else if (wParam == VK_DOWN) wp = MAKEWPARAM(SB_LINEDOWN, 0); m_log.SendMsg(WM_VSCROLL, wp, 0); return 0; } } if (wParam == VK_RETURN) { if (isShift) { if (PluginConfig.m_bSendOnShiftEnter) { PostMessage(m_hwnd, WM_COMMAND, IDOK, 0); return 0; } break; } if ((isCtrl && !isShift) ^ (0 != PluginConfig.m_bSendOnEnter)) { PostMessage(m_hwnd, WM_COMMAND, IDOK, 0); return 0; } if (!PluginConfig.m_bSendOnEnter && !PluginConfig.m_bSendOnDblEnter) break; if (isCtrl) break; if (PluginConfig.m_bSendOnDblEnter) { if (m_iLastEnterTime + 2 < time(0)) { m_iLastEnterTime = time(0); break; } m_message.SendMsg(WM_KEYDOWN, VK_BACK, 0); m_message.SendMsg(WM_KEYUP, VK_BACK, 0); PostMessage(m_hwnd, WM_COMMAND, IDOK, 0); return 0; } PostMessage(m_hwnd, WM_COMMAND, IDOK, 0); return 0; } else m_iLastEnterTime = 0; if ((wParam == VK_NEXT && isCtrl && !isShift) || (wParam == VK_TAB && isCtrl && !isShift)) { // CTRL-TAB (switch tab/window) SendMessage(m_pContainer->m_hwnd, DM_SELECTTAB, DM_SELECT_NEXT, 0); return TRUE; } if ((wParam == VK_PRIOR && isCtrl && !isShift) || (wParam == VK_TAB && isCtrl && isShift)) { // CTRL_SHIFT-TAB (switch tab/window) SendMessage(m_pContainer->m_hwnd, DM_SELECTTAB, DM_SELECT_PREV, 0); return TRUE; } if (wParam == VK_TAB && !isCtrl && !isShift) { // tab-autocomplete m_message.SendMsg(WM_SETREDRAW, FALSE, 0); bool fCompleted = TabAutoComplete(); m_message.SendMsg(WM_SETREDRAW, TRUE, 0); RedrawWindow(m_message.GetHwnd(), nullptr, nullptr, RDW_INVALIDATE); if (!fCompleted && !PluginConfig.m_bAllowTab) { if ((GetSendButtonState(GetHwnd()) != PBS_DISABLED)) SetFocus(m_btnOk.GetHwnd()); else SetFocus(m_log.GetHwnd()); } return 0; } if (wParam != VK_RIGHT && wParam != VK_LEFT) { replaceStrW(m_wszSearchQuery, nullptr); replaceStrW(m_wszSearchResult, nullptr); } if (wParam == VK_F4 && isCtrl && !isAlt) { // ctrl-F4 (close tab) CloseTab(); return 0; } if (wParam == VK_NEXT || wParam == VK_PRIOR) { m_log.SendMsg(msg, wParam, lParam); m_iLastEnterTime = 0; return 0; } if (wParam == VK_UP && isCtrl && !isAlt) { char *lpPrevCmd = g_chatApi.SM_GetPrevCommand(m_si->ptszID, m_si->pszModule); if (!m_si->lpCurrentCommand || !m_si->lpCurrentCommand->last) { // Next command is not defined. It means currently entered text is not saved in the history and it // need to be saved in the window context. char *enteredText = m_message.GetRichTextRtf(); if (m_enteredText) mir_free(m_enteredText); m_enteredText = enteredText; } m_message.SendMsg(WM_SETREDRAW, FALSE, 0); LOGFONTA lf; LoadLogfont(FONTSECTION_IM, MSGFONTID_MESSAGEAREA, &lf, nullptr, FONTMODULE); SETTEXTEX ste; ste.flags = ST_DEFAULT; ste.codepage = CP_ACP; if (lpPrevCmd) m_message.SendMsg(EM_SETTEXTEX, (WPARAM)&ste, (LPARAM)lpPrevCmd); else m_message.SetText(L""); GETTEXTLENGTHEX gtl = { 0 }; gtl.flags = GTL_PRECISE; gtl.codepage = CP_ACP; int iLen = m_message.SendMsg(EM_GETTEXTLENGTHEX, (WPARAM)>l, 0); m_message.SendMsg(EM_SCROLLCARET, 0, 0); m_message.SendMsg(WM_SETREDRAW, TRUE, 0); RedrawWindow(m_message.GetHwnd(), nullptr, nullptr, RDW_INVALIDATE); m_message.SendMsg(EM_SETSEL, iLen, iLen); m_iLastEnterTime = 0; return 0; } if (wParam == VK_DOWN && isCtrl && !isAlt) { m_message.SendMsg(WM_SETREDRAW, FALSE, 0); SETTEXTEX ste; ste.flags = ST_DEFAULT; ste.codepage = CP_ACP; char *lpPrevCmd = g_chatApi.SM_GetNextCommand(m_si->ptszID, m_si->pszModule); if (lpPrevCmd) m_message.SendMsg(EM_SETTEXTEX, (WPARAM)&ste, (LPARAM)lpPrevCmd); else if (m_enteredText) { // If we cannot load the message from history, load the last edited text. m_message.SendMsg(EM_SETTEXTEX, (WPARAM)&ste, (LPARAM)m_enteredText); mir_free(m_enteredText); m_enteredText = nullptr; } GETTEXTLENGTHEX gtl = { 0 }; gtl.flags = GTL_PRECISE; gtl.codepage = CP_ACP; int iLen = m_message.SendMsg(EM_GETTEXTLENGTHEX, (WPARAM)>l, 0); m_message.SendMsg(EM_SCROLLCARET, 0, 0); m_message.SendMsg(WM_SETREDRAW, TRUE, 0); RedrawWindow(m_message.GetHwnd(), nullptr, nullptr, RDW_INVALIDATE); m_message.SendMsg(EM_SETSEL, iLen, iLen); m_iLastEnterTime = 0; return 0; } __fallthrough; case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_KILLFOCUS: m_iLastEnterTime = 0; break; case WM_KEYUP: case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: RefreshButtonStatus(); break; case WM_ERASEBKGND: return !CSkin::m_skinEnabled; } return CSuper::WndProc_Message(msg, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////////////////// // subclassing for the nickname list control.It is an ownerdrawn listbox LRESULT CChatRoomDlg::WndProc_Nicklist(UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_NCCALCSIZE: if (CSkin::m_DisableScrollbars) { RECT lpRect; GetClientRect(m_nickList.GetHwnd(), &lpRect); LONG itemHeight = m_nickList.SendMsg(LB_GETITEMHEIGHT, 0, 0); g_cLinesPerPage = (lpRect.bottom - lpRect.top) / itemHeight; } return CSkin::NcCalcRichEditFrame(m_nickList.GetHwnd(), this, ID_EXTBKUSERLIST, msg, wParam, lParam, stubNicklistProc); case WM_NCPAINT: return CSkin::DrawRichEditFrame(m_nickList.GetHwnd(), this, ID_EXTBKUSERLIST, msg, wParam, lParam, stubNicklistProc); case WM_MOUSEWHEEL: if (CSkin::m_DisableScrollbars) { UINT uScroll; short zDelta = (short)HIWORD(wParam); if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &uScroll, 0)) uScroll = 3; /* default value */ if (uScroll == WHEEL_PAGESCROLL) uScroll = g_cLinesPerPage; if (uScroll == 0) return 0; zDelta += g_iWheelCarryover; /* Accumulate wheel motion */ int dLines = zDelta * (int)uScroll / WHEEL_DELTA; //Record the unused portion as the next carryover. g_iWheelCarryover = zDelta - dLines * WHEEL_DELTA / (int)uScroll; // scrolling. while (abs(dLines)) { if (dLines > 0) { m_nickList.SendMsg(WM_VSCROLL, SB_LINEUP, 0); dLines--; } else { m_nickList.SendMsg(WM_VSCROLL, SB_LINEDOWN, 0); dLines++; } } return 0; } break; case WM_KEYDOWN: if (wParam == 0x57 && GetKeyState(VK_CONTROL) & 0x8000) { // ctrl-w (close window) PostMessage(m_hwnd, WM_CLOSE, 0, 1); return TRUE; } if (wParam == VK_ESCAPE || wParam == VK_UP || wParam == VK_DOWN || wParam == VK_NEXT || wParam == VK_PRIOR || wParam == VK_TAB || wParam == VK_HOME || wParam == VK_END) { m_wszSearch[0] = 0; m_iSearchItem = -1; } break; case WM_SETFOCUS: case WM_KILLFOCUS: m_wszSearch[0] = 0; m_iSearchItem = -1; break; case WM_CHAR: case WM_UNICHAR: // simple incremental search for the user (nick) - list control // typing esc or movement keys will clear the current search string if (wParam == 27 && m_wszSearch[0]) { // escape - reset everything m_wszSearch[0] = 0; m_iSearchItem = -1; break; } if (wParam == '\b' && m_wszSearch[0]) // backspace m_wszSearch[mir_wstrlen(m_wszSearch) - 1] = '\0'; else if (wParam < ' ') break; else { if (mir_wstrlen(m_wszSearch) >= _countof(m_wszSearch) - 2) { MessageBeep(MB_OK); break; } wchar_t szNew[2]; szNew[0] = (wchar_t)wParam; szNew[1] = '\0'; mir_wstrcat(m_wszSearch, szNew); } if (m_wszSearch[0]) { // iterate over the (sorted) list of nicknames and search for the // string we have int i, iItems = m_nickList.SendMsg(LB_GETCOUNT, 0, 0); for (i = 0; i < iItems; i++) { USERINFO *ui = g_chatApi.UM_FindUserFromIndex(m_si, i); if (ui) { if (!wcsnicmp(ui->pszNick, m_wszSearch, mir_wstrlen(m_wszSearch))) { m_nickList.SendMsg(LB_SETSEL, FALSE, -1); m_nickList.SendMsg(LB_SETSEL, TRUE, i); m_iSearchItem = i; InvalidateRect(m_nickList.GetHwnd(), nullptr, FALSE); return 0; } } } if (i == iItems) { MessageBeep(MB_OK); m_wszSearch[mir_wstrlen(m_wszSearch) - 1] = '\0'; return 0; } } break; } return CSuper::WndProc_Nicklist(msg, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////////////////// // subclassing for the message filter dialog (set and configure event filters for the // current session static UINT _eventorder[] = { GC_EVENT_ACTION, GC_EVENT_MESSAGE, GC_EVENT_NICK, GC_EVENT_JOIN, GC_EVENT_PART, GC_EVENT_TOPIC, GC_EVENT_ADDSTATUS, GC_EVENT_INFORMATION, GC_EVENT_QUIT, GC_EVENT_KICK, GC_EVENT_NOTICE }; INT_PTR CALLBACK CChatRoomDlg::FilterWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { CChatRoomDlg *pDlg = (CChatRoomDlg*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); switch (uMsg) { case WM_INITDIALOG: pDlg = (CChatRoomDlg*)lParam; SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); { DWORD dwMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "FilterMask", 0); DWORD dwFlags = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "FilterFlags", 0); DWORD dwPopupMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "PopupMask", 0); DWORD dwPopupFlags = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "PopupFlags", 0); DWORD dwTrayMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "TrayIconMask", 0); DWORD dwTrayFlags = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "TrayIconFlags", 0); for (int i = 0; i < _countof(_eventorder); i++) { CheckDlgButton(hwndDlg, IDC_1 + i, dwMask & _eventorder[i] ? (dwFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED) : BST_INDETERMINATE); CheckDlgButton(hwndDlg, IDC_P1 + i, dwPopupMask & _eventorder[i] ? (dwPopupFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED) : BST_INDETERMINATE); CheckDlgButton(hwndDlg, IDC_T1 + i, dwTrayMask & _eventorder[i] ? (dwTrayFlags & _eventorder[i] ? BST_CHECKED : BST_UNCHECKED) : BST_INDETERMINATE); } } return FALSE; case WM_CTLCOLOREDIT: case WM_CTLCOLORSTATIC: SetTextColor((HDC)wParam, RGB(60, 60, 150)); SetBkColor((HDC)wParam, GetSysColor(COLOR_WINDOW)); return (INT_PTR)GetSysColorBrush(COLOR_WINDOW); case WM_CLOSE: if (wParam == 1 && lParam == 1) { int iFlags = 0; DWORD dwMask = 0; for (int i = 0; i < _countof(_eventorder); i++) { int result = IsDlgButtonChecked(hwndDlg, IDC_1 + i); dwMask |= (result != BST_INDETERMINATE ? _eventorder[i] : 0); iFlags |= (result == BST_CHECKED ? _eventorder[i] : 0); } if (iFlags & GC_EVENT_ADDSTATUS) iFlags |= GC_EVENT_REMOVESTATUS; if (pDlg) { if (dwMask == 0) { db_unset(pDlg->m_hContact, CHAT_MODULE, "FilterFlags"); db_unset(pDlg->m_hContact, CHAT_MODULE, "FilterMask"); } else { db_set_dw(pDlg->m_hContact, CHAT_MODULE, "FilterFlags", iFlags); db_set_dw(pDlg->m_hContact, CHAT_MODULE, "FilterMask", dwMask); } } dwMask = iFlags = 0; for (int i = 0; i < _countof(_eventorder); i++) { int result = IsDlgButtonChecked(hwndDlg, IDC_P1 + i); dwMask |= (result != BST_INDETERMINATE ? _eventorder[i] : 0); iFlags |= (result == BST_CHECKED ? _eventorder[i] : 0); } if (iFlags & GC_EVENT_ADDSTATUS) iFlags |= GC_EVENT_REMOVESTATUS; if (pDlg) { if (dwMask == 0) { db_unset(pDlg->m_hContact, CHAT_MODULE, "PopupFlags"); db_unset(pDlg->m_hContact, CHAT_MODULE, "PopupMask"); } else { db_set_dw(pDlg->m_hContact, CHAT_MODULE, "PopupFlags", iFlags); db_set_dw(pDlg->m_hContact, CHAT_MODULE, "PopupMask", dwMask); } } dwMask = iFlags = 0; for (int i = 0; i < _countof(_eventorder); i++) { int result = IsDlgButtonChecked(hwndDlg, IDC_T1 + i); dwMask |= (result != BST_INDETERMINATE ? _eventorder[i] : 0); iFlags |= (result == BST_CHECKED ? _eventorder[i] : 0); } if (iFlags & GC_EVENT_ADDSTATUS) iFlags |= GC_EVENT_REMOVESTATUS; if (pDlg) { if (dwMask == 0) { db_unset(pDlg->m_hContact, CHAT_MODULE, "TrayIconFlags"); db_unset(pDlg->m_hContact, CHAT_MODULE, "TrayIconMask"); } else { db_set_dw(pDlg->m_hContact, CHAT_MODULE, "TrayIconFlags", iFlags); db_set_dw(pDlg->m_hContact, CHAT_MODULE, "TrayIconMask", dwMask); } Chat_SetFilters(pDlg->m_si); if (pDlg->m_bFilterEnabled) { if (pDlg->m_iLogFilterFlags == 0) pDlg->onClick_Filter(&pDlg->m_btnFilter); pDlg->RedrawLog(); db_set_b(pDlg->m_hContact, CHAT_MODULE, "FilterEnabled", pDlg->m_bFilterEnabled); } } } DestroyWindow(hwndDlg); break; case WM_DESTROY: SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); break; } return FALSE; } ///////////////////////////////////////////////////////////////////////////////////////// INT_PTR CChatRoomDlg::DlgProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { POINT pt, tmp, cur; RECT rc; switch (uMsg) { case WM_SETFOCUS: if (CMimAPI::m_shutDown) break; UpdateWindowState(WM_SETFOCUS); SetFocus(m_message.GetHwnd()); return 1; case WM_TIMECHANGE: RedrawLog(); break; case WM_CBD_LOADICONS: Srmm_UpdateToolbarIcons(m_hwnd); return 0; case WM_SIZE: if (m_ipFieldHeight == 0) m_ipFieldHeight = CInfoPanel::m_ipConfig.height1; if (wParam == SIZE_MAXIMIZED) ScrollToBottom(); if (!IsIconic(m_hwnd)) { int panelHeight = m_pPanel.getHeight() + 1; GetClientRect(m_hwnd, &rc); int cx = rc.right; CTabBaseDlg::DlgProc(uMsg, 0, 0); // call basic window resizer BB_SetButtonsPos(); rc.left = panelHeight <= CInfoPanel::LEFT_OFFSET_LOGO ? panelHeight : CInfoPanel::LEFT_OFFSET_LOGO; rc.right = cx; rc.top = 1; rc.bottom = (panelHeight > CInfoPanel::DEGRADE_THRESHOLD ? rc.top + m_ipFieldHeight - 2 : panelHeight - 1); m_rcNick = rc; rc.left = panelHeight <= CInfoPanel::LEFT_OFFSET_LOGO ? panelHeight : CInfoPanel::LEFT_OFFSET_LOGO; rc.right = cx; rc.bottom = panelHeight - 2; rc.top = m_rcNick.bottom + 1; m_rcUIN = rc; if (m_hwndIEView || m_hwndHPP) ResizeIeView(); DetermineMinHeight(); } return 0; case DM_TYPING: // Typing support for GCW_PRIVMESS sessions if (m_si->iType == GCW_PRIVMESS) { int preTyping = m_nTypeSecs != 0; m_nTypeSecs = (int)lParam > 0 ? (int)lParam : 0; if (m_nTypeSecs) m_bShowTyping = 0; SetWindowLongPtr(m_hwnd, DWLP_MSGRESULT, preTyping); } return TRUE; case WM_CTLCOLORLISTBOX: SetBkColor((HDC)wParam, g_Settings.crUserListBGColor); return (INT_PTR)g_chatApi.hListBkgBrush; case WM_MEASUREITEM: { MEASUREITEMSTRUCT *mis = (MEASUREITEMSTRUCT *)lParam; if (mis->CtlType == ODT_MENU) { if (m_pPanel.isHovered()) { mis->itemHeight = 0; mis->itemWidth = 6; return TRUE; } return Menu_MeasureItem(lParam); } mis->itemHeight = g_Settings.iNickListFontHeight; } return TRUE; case WM_DRAWITEM: { DRAWITEMSTRUCT *dis = (DRAWITEMSTRUCT *)lParam; if (dis->CtlType == ODT_MENU) { if (m_pPanel.isHovered()) { DrawMenuItem(dis, (HICON)dis->itemData, 0); return TRUE; } return Menu_DrawItem(lParam); } if (dis->CtlID == IDC_SRMM_NICKLIST) { int x_offset = 0; int index = dis->itemID; USERINFO *ui = g_chatApi.UM_FindUserFromIndex(m_si, index); if (ui == nullptr) return TRUE; int height = dis->rcItem.bottom - dis->rcItem.top; if (height & 1) height++; int offset = (height == 10) ? 0 : height / 2; HICON hIcon = g_chatApi.SM_GetStatusIcon(m_si, ui); HFONT hFont = g_Settings.UserListFonts[ui->iStatusEx]; HFONT hOldFont = (HFONT)SelectObject(dis->hDC, hFont); SetBkMode(dis->hDC, TRANSPARENT); int nickIndex = 0; for (int i = 0; i < STATUSICONCOUNT; i++) { if (hIcon == g_chatApi.hIcons[ICON_STATUS0 + i]) { nickIndex = i; break; } } if (dis->itemState & ODS_SELECTED) { FillRect(dis->hDC, &dis->rcItem, g_Settings.SelectionBGBrush); SetTextColor(dis->hDC, g_Settings.nickColors[6]); } else { FillRect(dis->hDC, &dis->rcItem, g_chatApi.hListBkgBrush); if (g_Settings.bColorizeNicks && nickIndex != 0) SetTextColor(dis->hDC, g_Settings.nickColors[nickIndex - 1]); else SetTextColor(dis->hDC, g_Settings.UserListColors[ui->iStatusEx]); } x_offset = 2; if (g_Settings.bShowContactStatus && g_Settings.bContactStatusFirst && ui->ContactStatus) { HICON icon = Skin_LoadProtoIcon(m_si->pszModule, ui->ContactStatus); DrawIconEx(dis->hDC, x_offset, dis->rcItem.top + offset - 8, icon, 16, 16, 0, nullptr, DI_NORMAL); IcoLib_ReleaseIcon(icon); x_offset += 18; } if (g_Settings.bClassicIndicators) { char szTemp[3]; szTemp[1] = 0; szTemp[0] = szIndicators[nickIndex]; if (szTemp[0]) { SIZE szUmode; GetTextExtentPoint32A(dis->hDC, szTemp, 1, &szUmode); TextOutA(dis->hDC, x_offset, dis->rcItem.top, szTemp, 1); x_offset += szUmode.cx + 2; } else x_offset += 8; } else { DrawIconEx(dis->hDC, x_offset, dis->rcItem.top + offset - 5, hIcon, 10, 10, 0, nullptr, DI_NORMAL); x_offset += 12; } if (g_Settings.bShowContactStatus && !g_Settings.bContactStatusFirst && ui->ContactStatus) { HICON icon = Skin_LoadProtoIcon(m_si->pszModule, ui->ContactStatus); DrawIconEx(dis->hDC, x_offset, dis->rcItem.top + offset - 8, icon, 16, 16, 0, nullptr, DI_NORMAL); IcoLib_ReleaseIcon(icon); x_offset += 18; } SIZE sz; if (m_iSearchItem != -1 && m_iSearchItem == index && m_wszSearch[0]) { COLORREF clr_orig = GetTextColor(dis->hDC); GetTextExtentPoint32(dis->hDC, ui->pszNick, (int)mir_wstrlen(m_wszSearch), &sz); SetTextColor(dis->hDC, RGB(250, 250, 0)); TextOut(dis->hDC, x_offset, (dis->rcItem.top + dis->rcItem.bottom - sz.cy) / 2, ui->pszNick, (int)mir_wstrlen(m_wszSearch)); SetTextColor(dis->hDC, clr_orig); x_offset += sz.cx; TextOut(dis->hDC, x_offset, (dis->rcItem.top + dis->rcItem.bottom - sz.cy) / 2, ui->pszNick + mir_wstrlen(m_wszSearch), int(mir_wstrlen(ui->pszNick) - mir_wstrlen(m_wszSearch))); } else { GetTextExtentPoint32(dis->hDC, ui->pszNick, (int)mir_wstrlen(ui->pszNick), &sz); TextOut(dis->hDC, x_offset, (dis->rcItem.top + dis->rcItem.bottom - sz.cy) / 2, ui->pszNick, (int)mir_wstrlen(ui->pszNick)); SelectObject(dis->hDC, hOldFont); } return TRUE; } } break; case WM_CONTEXTMENU: { DWORD idFrom = GetDlgCtrlID((HWND)wParam); if (idFrom >= MIN_CBUTTONID && idFrom <= MAX_CBUTTONID) Srmm_ClickToolbarIcon(m_hContact, idFrom, m_hwnd, 1); } break; case DM_SPLITTERMOVED: RECT rcLog; GetWindowRect(m_log.GetHwnd(), &rcLog); if ((HWND)lParam == GetDlgItem(m_hwnd, IDC_SPLITTERX)) { GetClientRect(m_hwnd, &rc); pt.x = wParam, pt.y = 0; ScreenToClient(m_hwnd, &pt); int iSplitterX = rc.right - pt.x + 1; if (iSplitterX < 35) iSplitterX = 35; if (iSplitterX > rc.right - rc.left - 35) iSplitterX = rc.right - rc.left - 35; m_pContainer->m_pSettings->iSplitterX = iSplitterX; Resize(); } else if ((HWND)lParam == GetDlgItem(m_hwnd, IDC_SPLITTERY) || lParam == -1) { GetClientRect(m_hwnd, &rc); rc.top += (m_pPanel.isActive() ? m_pPanel.getHeight() + 40 : 30); pt.x = 0, pt.y = wParam; ScreenToClient(m_hwnd, &pt); m_iSplitterY = rc.bottom - pt.y + 3 + DPISCALEY_S(23); int iMinHeight = DPISCALEY_S(23) + ((m_pContainer->m_dwFlags & CNT_BOTTOMTOOLBAR) ? 21 : 0); if (m_iSplitterY < iMinHeight) m_iSplitterY = iMinHeight; if (m_iSplitterY > rc.bottom - rc.top - DPISCALEY_S(40)) m_iSplitterY = rc.bottom - rc.top - DPISCALEY_S(40); m_pContainer->m_pSettings->iSplitterY = m_iSplitterY; UpdateToolbarBG(); Resize(); } else if ((HWND)lParam == GetDlgItem(m_hwnd, IDC_PANELSPLITTER)) { pt.x = 0, pt.y = wParam; ScreenToClient(m_hwnd, &pt); GetClientRect(m_log.GetHwnd(), &rc); if ((pt.y + 2 >= MIN_PANELHEIGHT + 2) && (pt.y + 2 < 100) && (pt.y + 2 < rc.bottom - 30)) m_pPanel.setHeight(pt.y + 2); RedrawWindow(m_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); if (M.isAero()) InvalidateRect(m_hwndParent, nullptr, FALSE); SendMessage(m_hwnd, WM_SIZE, DM_SPLITTERMOVED, 0); break; } break; case WM_TIMER: if (wParam == TIMERID_FLASHWND) if (m_bCanFlashTab) FlashTab(true); // Typing support for GCW_PRIVMESS sessions if (m_si->iType == GCW_PRIVMESS && wParam == TIMERID_TYPE) DM_Typing(false); break; case WM_ACTIVATE: case WM_MOUSEACTIVATE: if (LOWORD(wParam) != WA_ACTIVE) { m_pContainer->m_hwndSaved = nullptr; break; } UpdateWindowState(WM_ACTIVATE); return 1; case WM_NOTIFY: switch (((LPNMHDR)lParam)->code) { case EN_MSGFILTER: bool isShift, isCtrl, isMenu; { UINT msg = ((MSGFILTER *)lParam)->msg; WPARAM wp = ((MSGFILTER *)lParam)->wParam; LPARAM lp = ((MSGFILTER *)lParam)->lParam; KbdState(isShift, isCtrl, isMenu); MSG message; message.hwnd = m_hwnd; message.message = msg; message.lParam = lp; message.wParam = wp; if (msg == WM_SYSKEYUP) { if (wp == VK_MENU) if (!m_bkeyProcessed && !(GetKeyState(VK_CONTROL) & 0x8000) && !(GetKeyState(VK_SHIFT) & 0x8000) && !(lp & (1 << 24))) m_pContainer->m_pMenuBar->autoShow(); return _dlgReturn(m_hwnd, 0); } if (msg == WM_MOUSEMOVE) { GetCursorPos(&pt); DM_DismissTip(pt); m_pPanel.trackMouse(pt); break; } if (msg == WM_KEYDOWN) { if ((wp == VK_INSERT && isShift && !isCtrl && !isMenu) || (wp == 'V' && !isShift && !isMenu && isCtrl)) { m_message.SendMsg(EM_PASTESPECIAL, CF_UNICODETEXT, 0); ((MSGFILTER*)lParam)->msg = WM_NULL; ((MSGFILTER*)lParam)->wParam = 0; ((MSGFILTER*)lParam)->lParam = 0; return _dlgReturn(m_hwnd, 1); } } if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN) m_pContainer->m_pMenuBar->Cancel(); if ((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) && !(GetKeyState(VK_RMENU) & 0x8000)) { if (DM_GenericHotkeysCheck(&message)) { m_bkeyProcessed = true; return _dlgReturn(m_hwnd, 1); } LRESULT mim_hotkey_check = Hotkey_Check(&message, TABSRMM_HK_SECTION_GC); if (mim_hotkey_check) m_bkeyProcessed = true; switch (mim_hotkey_check) { case TABSRMM_HK_CHANNELMGR: onClick_ChanMgr(&m_btnChannelMgr); return _dlgReturn(m_hwnd, 1); case TABSRMM_HK_FILTERTOGGLE: onClick_Filter(&m_btnFilter); InvalidateRect(m_btnFilter.GetHwnd(), nullptr, TRUE); return _dlgReturn(m_hwnd, 1); case TABSRMM_HK_LISTTOGGLE: onClick_ShowNickList(&m_btnNickList); return _dlgReturn(m_hwnd, 1); case TABSRMM_HK_MUC_SHOWSERVER: if (m_si->iType != GCW_SERVER) Chat_DoEventHook(m_si, GC_USER_MESSAGE, nullptr, L"/servershow", 0); return _dlgReturn(m_hwnd, 1); } } if (msg == WM_KEYDOWN && ((NMHDR*)lParam)->idFrom != IDC_SRMM_MESSAGE) { if ((wp == VK_NEXT && isCtrl && !isShift) || (wp == VK_TAB && isCtrl && !isShift)) // CTRL-TAB (switch tab/window) SendMessage(m_pContainer->m_hwnd, DM_SELECTTAB, DM_SELECT_NEXT, 0); else if ((wp == VK_PRIOR && isCtrl && !isShift) || (wp == VK_TAB && isCtrl && isShift)) // CTRL_SHIFT-TAB (switch tab/window) SendMessage(m_pContainer->m_hwnd, DM_SELECTTAB, DM_SELECT_PREV, 0); } if (msg == WM_KEYDOWN && wp == VK_TAB) { if (((NMHDR*)lParam)->idFrom == IDC_SRMM_LOG) { SetFocus(m_message.GetHwnd()); return _dlgReturn(m_hwnd, 1); } } if (((LPNMHDR)lParam)->idFrom == IDC_SRMM_LOG && ((MSGFILTER *)lParam)->msg == WM_RBUTTONUP) return _dlgReturn(m_hwnd, 1); } break; case EN_REQUESTRESIZE: if (((LPNMHDR)lParam)->idFrom == IDC_SRMM_MESSAGE) DM_HandleAutoSizeRequest((REQRESIZE *)lParam); break; case EN_LINK: if (((LPNMHDR)lParam)->idFrom == IDC_SRMM_LOG) { switch (((ENLINK*)lParam)->msg) { case WM_RBUTTONDOWN: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: CHARRANGE sel; SendMessage(((LPNMHDR)lParam)->hwndFrom, EM_EXGETSEL, 0, (LPARAM)&sel); if (sel.cpMin == sel.cpMax) { UINT msg = ((ENLINK*)lParam)->msg; m_pContainer->m_pMenuBar->Cancel(); TEXTRANGE tr; tr.lpstrText = nullptr; tr.chrg = ((ENLINK*)lParam)->chrg; tr.lpstrText = (wchar_t*)mir_alloc(sizeof(wchar_t) * (tr.chrg.cpMax - tr.chrg.cpMin + 2)); SendMessage(((LPNMHDR)lParam)->hwndFrom, EM_GETTEXTRANGE, 0, (LPARAM)&tr); BOOL isLink = IsStringValidLink(tr.lpstrText); if (isLink) // handled by core break; // clicked a nick name if (g_Settings.bClickableNicks) { if (msg == WM_RBUTTONDOWN) { for (auto &ui : m_si->getUserList()) { if (mir_wstrcmp(ui->pszNick, tr.lpstrText)) continue; pt.x = (short)LOWORD(((ENLINK*)lParam)->lParam); pt.y = (short)HIWORD(((ENLINK*)lParam)->lParam); ClientToScreen(((NMHDR*)lParam)->hwndFrom, &pt); RunUserMenu(m_hwnd, ui, pt); break; } return TRUE; } if (msg == WM_LBUTTONUP) { CHARRANGE chr; m_message.SendMsg(EM_EXGETSEL, 0, (LPARAM)&chr); wchar_t tszAplTmpl[] = L"%s:"; size_t bufSize = mir_wstrlen(tr.lpstrText) + mir_wstrlen(tszAplTmpl) + 3; wchar_t *tszTmp = (wchar_t*)mir_alloc(bufSize * sizeof(wchar_t)), *tszAppeal = tszTmp; TEXTRANGE tr2; tr2.lpstrText = (LPTSTR)mir_alloc(sizeof(wchar_t) * 2); if (chr.cpMin) { // prepend nick with space if needed tr2.chrg.cpMin = chr.cpMin - 1; tr2.chrg.cpMax = chr.cpMin; m_message.SendMsg(EM_GETTEXTRANGE, 0, (LPARAM)&tr2); if (!iswspace(*tr2.lpstrText)) *tszTmp++ = ' '; mir_wstrcpy(tszTmp, tr.lpstrText); } else // in the beginning of the message window mir_snwprintf(tszAppeal, bufSize, tszAplTmpl, tr.lpstrText); size_t st = mir_wstrlen(tszAppeal); if (chr.cpMax != -1) { tr2.chrg.cpMin = chr.cpMax; tr2.chrg.cpMax = chr.cpMax + 1; // if there is no space after selection, // or there is nothing after selection at all... if (!m_message.SendMsg(EM_GETTEXTRANGE, 0, (LPARAM)&tr2) || !iswspace(*tr2.lpstrText)) { tszAppeal[st++] = ' '; tszAppeal[st++] = '\0'; } } else { tszAppeal[st++] = ' '; tszAppeal[st++] = '\0'; } m_message.SendMsg(EM_REPLACESEL, FALSE, (LPARAM)tszAppeal); mir_free((void*)tr2.lpstrText); mir_free((void*)tszAppeal); } } SetFocus(m_message.GetHwnd()); mir_free(tr.lpstrText); return TRUE; } } break; } break; } break; case WM_LBUTTONDOWN: GetCursorPos(&tmp); if (!m_pPanel.isHovered()) { cur.x = (SHORT)tmp.x; cur.y = (SHORT)tmp.y; SendMessage(m_pContainer->m_hwnd, WM_NCLBUTTONDOWN, HTCAPTION, *((LPARAM*)(&cur))); } break; case WM_LBUTTONUP: GetCursorPos(&tmp); if (m_pPanel.isHovered()) m_pPanel.handleClick(tmp); else { cur.x = (SHORT)tmp.x; cur.y = (SHORT)tmp.y; SendMessage(m_pContainer->m_hwnd, WM_NCLBUTTONUP, HTCAPTION, *((LPARAM*)(&cur))); } break; case WM_MOUSEMOVE: GetCursorPos(&pt); DM_DismissTip(pt); m_pPanel.trackMouse(pt); break; case WM_APPCOMMAND: { DWORD cmd = GET_APPCOMMAND_LPARAM(lParam); if (cmd == APPCOMMAND_BROWSER_BACKWARD || cmd == APPCOMMAND_BROWSER_FORWARD) { SendMessage(m_pContainer->m_hwnd, DM_SELECTTAB, cmd == APPCOMMAND_BROWSER_BACKWARD ? DM_SELECT_PREV : DM_SELECT_NEXT, 0); return 1; } } break; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_TOGGLESIDEBAR: SendMessage(m_pContainer->m_hwnd, WM_COMMAND, IDC_TOGGLESIDEBAR, 0); break; case IDCANCEL: ShowWindow(m_pContainer->m_hwnd, SW_MINIMIZE); return FALSE; case IDC_CLOSE: CloseTab(); break; case IDC_SELFTYPING: // Typing support for GCW_PRIVMESS sessions if (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; } break; case WM_KEYDOWN: SetFocus(m_message.GetHwnd()); break; case WM_ERASEBKGND: RECT rcClient, rcWindow; { HDC hdc = (HDC)wParam; UINT item_ids[3] = { ID_EXTBKUSERLIST, ID_EXTBKHISTORY, ID_EXTBKINPUTAREA }; UINT ctl_ids[3] = { IDC_SRMM_NICKLIST, IDC_SRMM_LOG, IDC_SRMM_MESSAGE }; HANDLE hbp = nullptr; HDC hdcMem = nullptr; HBITMAP hbm, hbmOld; GetClientRect(m_hwnd, &rcClient); LONG cx = rcClient.right - rcClient.left; LONG cy = rcClient.bottom - rcClient.top; if (CMimAPI::m_haveBufferedPaint) { hbp = CSkin::InitiateBufferedPaint(hdc, rcClient, hdcMem); hbm = hbmOld = nullptr; } else { hdcMem = CreateCompatibleDC(hdc); hbm = CSkin::CreateAeroCompatibleBitmap(rcClient, hdc); hbmOld = (HBITMAP)SelectObject(hdcMem, hbm); } if (CSkin::m_skinEnabled && !M.isAero()) { CSkin::SkinDrawBG(m_hwnd, m_pContainer->m_hwnd, m_pContainer, &rcClient, hdcMem); for (int i = 0; i < 3; i++) { CSkinItem *item = &SkinItems[item_ids[i]]; if (!item->IGNORED) { GetWindowRect(GetDlgItem(m_hwnd, ctl_ids[i]), &rcWindow); pt.x = rcWindow.left; pt.y = rcWindow.top; ScreenToClient(m_hwnd, &pt); rc.left = pt.x - item->MARGIN_LEFT; rc.top = pt.y - item->MARGIN_TOP; rc.right = rc.left + item->MARGIN_RIGHT + (rcWindow.right - rcWindow.left) + item->MARGIN_LEFT; rc.bottom = rc.top + item->MARGIN_BOTTOM + (rcWindow.bottom - rcWindow.top) + item->MARGIN_TOP; CSkin::DrawItem(hdcMem, &rc, item); } } } else { CSkin::FillBack(hdcMem, &rcClient); if (M.isAero()) { LONG temp = rcClient.bottom; rcClient.bottom = m_pPanel.isActive() ? m_pPanel.getHeight() + 5 : 5; FillRect(hdcMem, &rcClient, (HBRUSH)GetStockObject(BLACK_BRUSH)); rcClient.bottom = temp; } } GetClientRect(m_hwnd, &rc); m_pPanel.renderBG(hdcMem, rc, &SkinItems[ID_EXTBKINFOPANELBG], M.isAero()); m_pPanel.renderContent(hdcMem); if (!CSkin::m_skinEnabled) RenderToolbarBG(hdcMem, rcClient); if (hbp) CSkin::FinalizeBufferedPaint(hbp, &rcClient); else { BitBlt(hdc, 0, 0, cx, cy, hdcMem, 0, 0, SRCCOPY); SelectObject(hdcMem, hbmOld); DeleteObject(hbm); DeleteDC(hdcMem); } if (!m_fLimitedUpdate) SetAeroMargins(m_pContainer); } return TRUE; case WM_NCPAINT: if (CSkin::m_skinEnabled) return 0; break; case WM_PAINT: PAINTSTRUCT ps; BeginPaint(m_hwnd, &ps); EndPaint(m_hwnd, &ps); return 0; case WM_RBUTTONUP: GetCursorPos(&pt); if (!m_pPanel.invokeConfigDialog(pt)) { HMENU subMenu = GetSubMenu(PluginConfig.g_hMenuContext, 0); MsgWindowUpdateMenu(subMenu, MENU_TABCONTEXT); int iSelection = TrackPopupMenu(subMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_hwnd, nullptr); if (iSelection >= IDM_CONTAINERMENU) { char szIndex[10]; _itoa_s(iSelection - IDM_CONTAINERMENU, szIndex, 10); ptrW wszContainer(db_get_wsa(0, "TAB_ContainersW", szIndex)); if (wszContainer != nullptr) SendMessage(m_hwnd, DM_CONTAINERSELECTED, 0, wszContainer); break; } MsgWindowMenuHandler(iSelection, MENU_TABCONTEXT); } break; case WM_LBUTTONDBLCLK: if (LOWORD(lParam) < 30) ScrollToBottom(); break; case WM_CLOSE: if (wParam == 0 && lParam == 0) { if (GetCapture() != nullptr) return TRUE; if (PluginConfig.m_EscapeCloses == 1) { SendMessage(m_pContainer->m_hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); return TRUE; } if (PluginConfig.m_bHideOnClose && PluginConfig.m_EscapeCloses == 2) { ShowWindow(m_pContainer->m_hwnd, SW_HIDE); return TRUE; } _dlgReturn(m_hwnd, TRUE); } CloseTab(); return 0; case DM_SAVESIZE: if (m_dwFlags & MWF_NEEDCHECKSIZE) lParam = 0; m_dwFlags &= ~MWF_NEEDCHECKSIZE; if (m_dwFlags & MWF_WASBACKGROUNDCREATE) m_dwFlags &= ~MWF_INITMODE; SendMessage(m_pContainer->m_hwnd, DM_QUERYCLIENTAREA, 0, (LPARAM)&rcClient); MoveWindow(m_hwnd, rcClient.left, rcClient.top, (rcClient.right - rcClient.left), (rcClient.bottom - rcClient.top), TRUE); if (m_dwFlags & MWF_WASBACKGROUNDCREATE) { m_dwFlags &= ~MWF_WASBACKGROUNDCREATE; Resize(); pt.x = pt.y = 0; m_log.SendMsg(EM_SETSCROLLPOS, 0, (LPARAM)&pt); } else { Resize(); if (lParam == 0) DM_ScrollToBottom(1, 1); } return 0; case WM_CBD_UPDATED: if (lParam) CB_ChangeButton((CustomButtonData*)lParam); else BB_InitDlgButtons(); BB_SetButtonsPos(); return 0; case WM_CBD_REMOVED: if (lParam) CB_DestroyButton((DWORD)wParam, (DWORD)lParam); else CB_DestroyAllButtons(); break; case DM_CONFIGURETOOLBAR: Resize(); break; case DM_SMILEYOPTIONSCHANGED: RedrawLog(); break; case EM_THEMECHANGED: DM_FreeTheme(); DM_ThemeChanged(); return 0; case WM_DWMCOMPOSITIONCHANGED: BB_RefreshTheme(); m_pContainer->ClearMargins(); VerifyProxy(); break; } return CTabBaseDlg::DlgProc(uMsg, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////////////////// // chat session creator void ShowRoom(TContainerData *pContainer, SESSION_INFO *si) { if (si == nullptr) return; if (si->pDlg != nullptr) { ActivateExistingTab(si->pDlg->m_pContainer, si->pDlg->GetHwnd()); return; } wchar_t szName[CONTAINER_NAMELEN + 2]; szName[0] = 0; if (pContainer == nullptr) if (si->pDlg != nullptr) pContainer = si->pDlg->m_pContainer; if (pContainer == nullptr) { GetContainerNameForContact(si->hContact, szName, CONTAINER_NAMELEN); if (!g_Settings.bOpenInDefault && !mir_wstrcmp(szName, L"default")) wcsncpy(szName, L"Chat Rooms", CONTAINER_NAMELEN); szName[CONTAINER_NAMELEN] = 0; pContainer = FindContainerByName(szName); } if (pContainer == nullptr) pContainer = CreateContainer(szName, FALSE, si->hContact); if (pContainer == nullptr) return; // smth went wrong, nothing to do here MCONTACT hContact = si->hContact; if (Srmm_FindWindow(hContact) != nullptr) return; if (hContact != 0 && M.GetByte("limittabs", 0) && !wcsncmp(pContainer->m_wszName, L"default", 6)) if ((pContainer = FindMatchingContainer(L"default")) == nullptr) if ((pContainer = CreateContainer(L"default", CNT_CREATEFLAG_CLONED, hContact)) == nullptr) return; wchar_t *contactName = Clist_GetContactDisplayName(hContact); // cut nickname if larger than x chars... wchar_t newcontactname[128]; if (mir_wstrlen(contactName) > 0) { if (M.GetByte("cuttitle", 0)) CutContactName(contactName, newcontactname, _countof(newcontactname)); else wcsncpy_s(newcontactname, contactName, _TRUNCATE); } else wcsncpy_s(newcontactname, L"_U_", _TRUNCATE); HWND hwndTab = GetDlgItem(pContainer->m_hwnd, IDC_MSGTABS); // hide the active tab if (pContainer->m_hwndActive) ShowWindow(pContainer->m_hwndActive, SW_HIDE); int iTabIndex_wanted = M.GetDword(hContact, "tabindex", pContainer->m_iChilds * 100); int iCount = TabCtrl_GetItemCount(hwndTab); pContainer->m_iTabIndex = iCount; if (iCount > 0) { for (int i = iCount - 1; i >= 0; i--) { HWND hwnd = GetTabWindow(hwndTab, i); CSrmmWindow *dat = (CSrmmWindow*)GetWindowLongPtr(hwnd, GWLP_USERDATA); if (dat) { int relPos = M.GetDword(dat->m_hContact, "tabindex", i * 100); if (iTabIndex_wanted <= relPos) pContainer->m_iTabIndex = i; } } } TCITEM item = {}; item.pszText = newcontactname; item.mask = TCIF_TEXT | TCIF_IMAGE; int iTabId = TabCtrl_InsertItem(hwndTab, pContainer->m_iTabIndex, &item); SendMessage(hwndTab, EM_REFRESHWITHOUTCLIP, 0, 0); TabCtrl_SetCurSel(hwndTab, iTabId); pContainer->m_iChilds++; CChatRoomDlg *pDlg = new CChatRoomDlg(si); pDlg->m_iTabID = iTabId; pDlg->m_pContainer = pContainer; pDlg->SetParent(hwndTab); pDlg->Create(); HWND hwndNew = pDlg->GetHwnd(); item.lParam = (LPARAM)hwndNew; TabCtrl_SetItem(hwndTab, iTabId, &item); if (pContainer->m_dwFlags & CNT_SIDEBAR) pContainer->m_pSideBar->addSession(pDlg, pContainer->m_iTabIndex); SendMessage(pContainer->m_hwnd, WM_SIZE, 0, 0); // if the container is minimized, then pop it up... if (IsIconic(pContainer->m_hwnd)) { SendMessage(pContainer->m_hwnd, WM_SYSCOMMAND, SC_RESTORE, 0); SetFocus(pContainer->m_hwndActive); } if (PluginConfig.m_bHideOnClose && !IsWindowVisible(pContainer->m_hwnd)) { WINDOWPLACEMENT wp = { 0 }; wp.length = sizeof(wp); GetWindowPlacement(pContainer->m_hwnd, &wp); BroadCastContainer(pContainer, DM_CHECKSIZE, 0, 0); // make sure all tabs will re-check layout on activation if (wp.showCmd == SW_SHOWMAXIMIZED) ShowWindow(pContainer->m_hwnd, SW_SHOWMAXIMIZED); else { ShowWindow(pContainer->m_hwnd, SW_SHOWNORMAL); } SendMessage(pContainer->m_hwndActive, WM_SIZE, 0, 0); SetFocus(hwndNew); } else { SetFocus(hwndNew); RedrawWindow(pContainer->m_hwnd, nullptr, nullptr, RDW_INVALIDATE); UpdateWindow(pContainer->m_hwnd); if (GetForegroundWindow() != pContainer->m_hwnd) SetForegroundWindow(pContainer->m_hwnd); } if (PluginConfig.m_bIsWin7 && PluginConfig.m_useAeroPeek && CSkin::m_skinEnabled && !M.GetByte("forceAeroPeek", 0)) CWarning::show(CWarning::WARN_AEROPEEK_SKIN, MB_ICONWARNING | MB_OK); }