From 1979fd80424d16b2e489f9b57d01d9c7811d25a2 Mon Sep 17 00:00:00 2001 From: dartraiden <wowemuh@gmail.com> Date: Mon, 2 Jan 2023 21:10:29 +0300 Subject: Update copyrights --- plugins/TabSRMM/src/ImageDataObject.cpp | 2 +- plugins/TabSRMM/src/TSButton.cpp | 2 +- plugins/TabSRMM/src/chat.h | 2 +- plugins/TabSRMM/src/chat_log.cpp | 2 +- plugins/TabSRMM/src/chat_main.cpp | 2 +- plugins/TabSRMM/src/chat_manager.cpp | 2 +- plugins/TabSRMM/src/chat_options.cpp | 2 +- plugins/TabSRMM/src/chat_tools.cpp | 2 +- plugins/TabSRMM/src/contactcache.cpp | 956 ++--- plugins/TabSRMM/src/contactcache.h | 2 +- plugins/TabSRMM/src/container.cpp | 2 +- plugins/TabSRMM/src/containeroptions.cpp | 2 +- plugins/TabSRMM/src/controls.cpp | 2 +- plugins/TabSRMM/src/controls.h | 2 +- plugins/TabSRMM/src/eventpopups.cpp | 2 +- plugins/TabSRMM/src/functions.h | 2 +- plugins/TabSRMM/src/generic_msghandlers.cpp | 2716 ++++++------- plugins/TabSRMM/src/globals.cpp | 2 +- plugins/TabSRMM/src/globals.h | 2 +- plugins/TabSRMM/src/hotkeyhandler.cpp | 2 +- plugins/TabSRMM/src/infopanel.cpp | 2 +- plugins/TabSRMM/src/infopanel.h | 2 +- plugins/TabSRMM/src/mim.cpp | 976 ++--- plugins/TabSRMM/src/mim.h | 2 +- plugins/TabSRMM/src/modplus.cpp | 2 +- plugins/TabSRMM/src/msgdialog.cpp | 2 +- plugins/TabSRMM/src/msgdlgother.cpp | 5716 +++++++++++++-------------- plugins/TabSRMM/src/msgdlgutils.cpp | 2 +- plugins/TabSRMM/src/msgdlgutils.h | 2 +- plugins/TabSRMM/src/msglog.cpp | 2 +- plugins/TabSRMM/src/msgoptions.cpp | 2 +- plugins/TabSRMM/src/msgs.cpp | 2 +- plugins/TabSRMM/src/msgs.h | 2 +- plugins/TabSRMM/src/muchighlight.cpp | 2 +- plugins/TabSRMM/src/muchighlight.h | 2 +- plugins/TabSRMM/src/nen.h | 2 +- plugins/TabSRMM/src/selectcontainer.cpp | 2 +- plugins/TabSRMM/src/sendlater.cpp | 2 +- plugins/TabSRMM/src/sendlater.h | 2 +- plugins/TabSRMM/src/sendqueue.cpp | 2 +- plugins/TabSRMM/src/sendqueue.h | 2 +- plugins/TabSRMM/src/sidebar.cpp | 2 +- plugins/TabSRMM/src/sidebar.h | 2 +- plugins/TabSRMM/src/srmm.cpp | 288 +- plugins/TabSRMM/src/stdafx.cxx | 2 +- plugins/TabSRMM/src/stdafx.h | 2 +- plugins/TabSRMM/src/tabctrl.cpp | 2 +- plugins/TabSRMM/src/taskbar.cpp | 2 +- plugins/TabSRMM/src/taskbar.h | 2 +- plugins/TabSRMM/src/templates.cpp | 248 +- plugins/TabSRMM/src/themeio.cpp | 2 +- plugins/TabSRMM/src/themes.cpp | 2 +- plugins/TabSRMM/src/themes.h | 2 +- plugins/TabSRMM/src/userprefs.cpp | 2 +- plugins/TabSRMM/src/utils.cpp | 2 +- plugins/TabSRMM/src/utils.h | 2 +- plugins/TabSRMM/src/version.h | 2 +- plugins/TabSRMM/src/warning.cpp | 656 +-- 58 files changed, 5829 insertions(+), 5829 deletions(-) (limited to 'plugins/TabSRMM/src') diff --git a/plugins/TabSRMM/src/ImageDataObject.cpp b/plugins/TabSRMM/src/ImageDataObject.cpp index 82c7756cef..6da7a5fda6 100644 --- a/plugins/TabSRMM/src/ImageDataObject.cpp +++ b/plugins/TabSRMM/src/ImageDataObject.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/TSButton.cpp b/plugins/TabSRMM/src/TSButton.cpp index 7ab8576319..444787e681 100644 --- a/plugins/TabSRMM/src/TSButton.cpp +++ b/plugins/TabSRMM/src/TSButton.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/chat.h b/plugins/TabSRMM/src/chat.h index 42df2909a2..75ceea90cf 100644 --- a/plugins/TabSRMM/src/chat.h +++ b/plugins/TabSRMM/src/chat.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/chat_log.cpp b/plugins/TabSRMM/src/chat_log.cpp index e25c14e6fa..73884d4e84 100644 --- a/plugins/TabSRMM/src/chat_log.cpp +++ b/plugins/TabSRMM/src/chat_log.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/chat_main.cpp b/plugins/TabSRMM/src/chat_main.cpp index c9662c5dbc..b398955f21 100644 --- a/plugins/TabSRMM/src/chat_main.cpp +++ b/plugins/TabSRMM/src/chat_main.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/chat_manager.cpp b/plugins/TabSRMM/src/chat_manager.cpp index dab25a0ddf..bc540bed44 100644 --- a/plugins/TabSRMM/src/chat_manager.cpp +++ b/plugins/TabSRMM/src/chat_manager.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/chat_options.cpp b/plugins/TabSRMM/src/chat_options.cpp index f8c1c99341..710a46a0af 100644 --- a/plugins/TabSRMM/src/chat_options.cpp +++ b/plugins/TabSRMM/src/chat_options.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/chat_tools.cpp b/plugins/TabSRMM/src/chat_tools.cpp index d2d7fdc31f..78a316ea6f 100644 --- a/plugins/TabSRMM/src/chat_tools.cpp +++ b/plugins/TabSRMM/src/chat_tools.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/contactcache.cpp b/plugins/TabSRMM/src/contactcache.cpp index 88b06217ca..2071555d94 100644 --- a/plugins/TabSRMM/src/contactcache.cpp +++ b/plugins/TabSRMM/src/contactcache.cpp @@ -1,478 +1,478 @@ -///////////////////////////////////////////////////////////////////////////////////////// -// Miranda NG: the free IM client for Microsoft* Windows* -// -// Copyright (C) 2012-22 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 -// -// contact cache implementation -// -// the contact cache provides various services to the message window(s) -// it also abstracts meta contacts. - -#include "stdafx.h" - -static OBJLIST<CContactCache> arContacts(50, NumericKeySortT); - -static DBCachedContact ccInvalid; - -CContactCache::CContactCache(MCONTACT hContact) : - m_hContact(hContact), - m_history(10) -{ - if (hContact) { - if ((cc = db_get_contact(hContact)) != nullptr) { - initPhaseTwo(); - return; - } - } - - cc = &ccInvalid; - m_szAccount = C_INVALID_ACCOUNT; - m_isMeta = false; - m_isValid = false; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// 2nd part of the object initialization that must be callable during the -// object's lifetime (not only on construction). - -void CContactCache::initPhaseTwo() -{ - m_szAccount = nullptr; - if (cc->szProto) { - PROTOACCOUNT *acc = Proto_GetAccount(cc->szProto); - if (acc && acc->tszAccountName) - m_szAccount = acc->tszAccountName; - } - - m_isValid = (cc->szProto != nullptr && m_szAccount != nullptr) ? true : false; - if (m_isValid) { - m_iStatus = db_get_w(m_hContact, cc->szProto, "Status", ID_STATUS_OFFLINE); - m_isMeta = db_mc_isMeta(cc->contactID) != 0; // don't use cc->IsMeta() here - if (m_isMeta) - updateMeta(); - updateNick(); - } - else { - m_szAccount = C_INVALID_ACCOUNT; - m_isMeta = false; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// reset meta contact information.Used when meta contacts are disabled -// on user's request. - -void CContactCache::resetMeta() -{ - m_isMeta = false; - m_szMetaProto = nullptr; - m_iMetaStatus = ID_STATUS_OFFLINE; - initPhaseTwo(); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// if the contact has an open message window, close it. -// window procedure will use setWindowData() to reset m_hwnd to 0. - -void CContactCache::closeWindow() -{ - if (m_dat) { - m_dat->m_bForcedClose = true; - m_dat->Close(); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// update private copy of the nick name.Use contact list name cache -// -// @return bool: true if nick has changed. - -bool CContactCache::updateNick() -{ - bool fChanged = false; - if (m_isValid) { - wchar_t *tszNick = Clist_GetContactDisplayName(getActiveContact()); - if (tszNick && mir_wstrcmp(m_szNick, tszNick)) - fChanged = true; - wcsncpy_s(m_szNick, (tszNick ? tszNick : L"<undef>"), _TRUNCATE); - } - return fChanged; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// update meta(subcontact and - protocol) status.This runs when the -// MC protocol fires one of its events OR when a relevant database value changes -// in the master contact. - -void CContactCache::updateMeta() -{ - if (m_isValid) { - MCONTACT hOldSub = m_hSub; - m_hSub = db_mc_getSrmmSub(cc->contactID); - m_szMetaProto = Proto_GetBaseAccountName(m_hSub); - m_iMetaStatus = (uint16_t)db_get_w(m_hSub, m_szMetaProto, "Status", ID_STATUS_OFFLINE); - PROTOACCOUNT *pa = Proto_GetAccount(m_szMetaProto); - if (pa) - m_szAccount = pa->tszAccountName; - - if (hOldSub != m_hSub) { - updateNick(); - updateUIN(); - } - } - else { - m_hSub = 0; - m_szMetaProto = nullptr; - m_iMetaStatus = ID_STATUS_OFFLINE; - m_xStatus = 0; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// obtain the UIN.This is only maintained for open message windows -// it also run when the subcontact for a MC changes. - -bool CContactCache::updateUIN() -{ - m_szUIN[0] = 0; - - if (m_isValid) { - ptrW uid(Contact::GetInfo(CNF_DISPLAYUID, getActiveContact(), getActiveProto())); - if (uid != nullptr) - wcsncpy_s(m_szUIN, uid, _TRUNCATE); - } - - return false; -} - -void CContactCache::updateStats(int iType, size_t value) -{ - if (m_stats == nullptr) - m_stats = new TSessionStats(); - - switch (iType) { - case TSessionStats::UPDATE_WITH_LAST_RCV: - if (!m_stats->lastReceivedChars) - break; - m_stats->iReceived++; - m_stats->messageCount++; - m_stats->iReceivedBytes += m_stats->lastReceivedChars; - m_stats->lastReceivedChars = 0; - break; - case TSessionStats::INIT_TIMER: - m_stats->started = time(0); - break; - case TSessionStats::SET_LAST_RCV: - m_stats->lastReceivedChars = (unsigned int)value; - break; - case TSessionStats::BYTES_SENT: - m_stats->iSent++; - m_stats->messageCount++; - m_stats->iSentBytes += (unsigned int)value; - break; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -//set the window data for this contact.The window procedure of the message -// dialog will use this in WM_INITDIALOG and WM_DESTROY to tell the cache -// that a message window is open for this contact. -// -// @param dat: CMsgDialog* - window data structure - -void CContactCache::setWindowData(CMsgDialog *dat) -{ - m_dat = dat; - - if (dat) { - updateStatusMsg(); - } - else { - // release memory - not needed when window isn't open - replaceStrW(m_szStatusMsg, nullptr); - replaceStrW(m_ListeningInfo, nullptr); - replaceStrW(m_xStatusMsg, nullptr); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// saves message to the input history. -// it's using streamout in UTF8 format - no unicode "issues" and all RTF formatting is saved to the history. - -void CContactCache::saveHistory() -{ - if (m_dat == nullptr) - return; - - CCtrlRichEdit &pEntry = m_dat->GetEntry(); - ptrA szFromStream(pEntry.GetRichTextRtf()); - if (szFromStream != nullptr) { - m_iHistoryCurrent = -1; - m_history.insert(szFromStream.detach()); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// handle the input history scrolling for the message input area -// @param wParam: VK_ keyboard code (VK_UP or VK_DOWN) - -void CContactCache::inputHistoryEvent(WPARAM wParam) -{ - if (m_dat == nullptr) - return; - - CCtrlRichEdit &pEntry = m_dat->GetEntry(); - if (m_history.getCount() > 0) { - char *pszText; - if (wParam == VK_UP) { - if (m_iHistoryCurrent == 0) - return; - - if (m_iHistoryCurrent < 0) - m_iHistoryCurrent = m_history.getCount()-1; - else - m_iHistoryCurrent--; - pszText = m_history[m_iHistoryCurrent]; - } - else { - if (m_iHistoryCurrent == -1) - return; - - if (m_iHistoryCurrent == m_history.getCount() - 1) { - m_iHistoryCurrent = -1; - pszText = ""; - } - else { - m_iHistoryCurrent++; - pszText = m_history[m_iHistoryCurrent]; - } - } - - SETTEXTEX stx = { ST_DEFAULT, CP_UTF8 }; - pEntry.SendMsg(EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)pszText); - pEntry.SendMsg(EM_SETSEL, -1, -1); - } - - pEntry.OnChange(&pEntry); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// release additional memory resources - -void CContactCache::releaseAlloced() -{ - if (m_stats) { - delete m_stats; - m_stats = nullptr; - } - - for (auto &it : m_history) - mir_free(it); - m_history.destroy(); - - mir_free(m_szStatusMsg); - m_szStatusMsg = nullptr; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// when a contact is deleted, mark it as invalid in the cache and release -// all memory it has allocated. - -void CContactCache::deletedHandler() -{ - cc = &ccInvalid; - m_isValid = false; - if (m_dat) { - m_dat->m_bForcedClose = true; - - // this message must be sent async to allow a contact to rest in peace before window gets closed - ::PostMessage(m_dat->GetHwnd(), WM_CLOSE, 1, 2); - } - - releaseAlloced(); - m_hContact = INVALID_CONTACT_ID; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// update all or only the given status message information from the database -// -// @param szKey: char* database key name or 0 to reload all messages - -void CContactCache::updateStatusMsg(const char *szKey) -{ - if (!m_isValid) - return; - - MCONTACT hContact = getActiveContact(); - - if (szKey == nullptr || (szKey && !mir_strcmp("StatusMsg", szKey))) { - if (m_szStatusMsg) - mir_free(m_szStatusMsg); - m_szStatusMsg = nullptr; - ptrW szStatus(db_get_wsa(hContact, "CList", "StatusMsg")); - if (szStatus != 0) - m_szStatusMsg = (mir_wstrlen(szStatus) > 0 ? getNormalizedStatusMsg(szStatus) : nullptr); - } - if (szKey == nullptr || (szKey && !mir_strcmp("ListeningTo", szKey))) { - if (m_ListeningInfo) - mir_free(m_ListeningInfo); - m_ListeningInfo = nullptr; - ptrW szListeningTo(db_get_wsa(hContact, cc->szProto, "ListeningTo")); - if (szListeningTo != 0 && *szListeningTo) - m_ListeningInfo = szListeningTo.detach(); - } - if (szKey == nullptr || (szKey && !mir_strcmp("XStatusMsg", szKey))) { - if (m_xStatusMsg) - mir_free(m_xStatusMsg); - m_xStatusMsg = nullptr; - ptrW szXStatusMsg(db_get_wsa(hContact, cc->szProto, "XStatusMsg")); - if (szXStatusMsg != 0 && *szXStatusMsg) - m_xStatusMsg = szXStatusMsg.detach(); - } - m_xStatus = db_get_b(hContact, cc->szProto, "XStatusId", 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// retrieve contact cache entry for the given contact.It _never_ returns zero, for a hContact -// 0, it retrieves a dummy object. -// Non-existing cache entries are created on demand. -// -// @param hContact: contact handle -// @return CContactCache* pointer to the cache entry for this contact - -CContactCache* CContactCache::getContactCache(MCONTACT hContact) -{ - CContactCache *cc = arContacts.find((CContactCache*)&hContact); - if (cc == nullptr) { - cc = new CContactCache(hContact); - arContacts.insert(cc); - } - return cc; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// when the state of the meta contacts protocol changes from enabled to disabled -// (or vice versa), this updates the contact cache -// -// it is ONLY called from the DBSettingChanged() event handler when the relevant -// database value is touched. - -int CContactCache::cacheUpdateMetaChanged(WPARAM bMetaEnabled, LPARAM) -{ - for (auto &c : arContacts) { - if (c->isMeta() && !bMetaEnabled) { - c->closeWindow(); - c->resetMeta(); - } - - // meta contacts are enabled, but current contact is a subcontact - > close window - if (bMetaEnabled && c->isSubContact()) - c->closeWindow(); - - // reset meta contact information, if metacontacts protocol became avail - if (bMetaEnabled && !c->cc->IsMeta()) - c->resetMeta(); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// normalize the status message with proper cr / lf sequences. -// @param src wchar_t*: original status message -// @param fStripAll bool: strip all cr/lf sequences and replace them with spaces (use for title bar) -// @return wchar_t*: converted status message. CALLER is responsible to mir_free it, MUST use mir_free() - -wchar_t* CContactCache::getNormalizedStatusMsg(const wchar_t *src, bool fStripAll) -{ - if (src == nullptr || mir_wstrlen(src) < 2) - return nullptr; - - CMStringW dest; - - for (int i = 0; src[i] != 0; i++) { - if (src[i] == 0x0d || src[i] == '\t') - continue; - if (i && src[i] == (wchar_t)0x0a) { - if (fStripAll) { - dest.AppendChar(' '); - continue; - } - dest.AppendChar('\n'); - continue; - } - dest.AppendChar(src[i]); - } - - return mir_wstrndup(dest, dest.GetLength()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// retrieve the tab / title icon for the corresponding session. - -HICON CContactCache::getIcon(int &iSize) const -{ - if (!m_dat) - return Skin_LoadProtoIcon(cc->szProto, getStatus()); - - if (m_dat->m_bErrorState) - return PluginConfig.g_iconErr; - if (m_dat->m_bCanFlashTab) - return m_dat->m_iFlashIcon; - - if (m_dat->isChat() && m_dat->m_iFlashIcon) { - int sizeX, sizeY; - Utils::getIconSize(m_dat->m_iFlashIcon, sizeX, sizeY); - iSize = sizeX; - return m_dat->m_iFlashIcon; - } - if (m_dat->m_hTabIcon == m_dat->m_hTabStatusIcon && m_dat->m_hXStatusIcon) - return m_dat->m_hXStatusIcon; - return m_dat->m_hTabIcon; -} - -size_t CContactCache::getMaxMessageLength() -{ - if (m_nMax == 0) { - MCONTACT hContact = getActiveContact(); - LPCSTR szProto = getActiveProto(); - if (szProto) { - m_nMax = CallProtoService(szProto, PS_GETCAPS, PFLAG_MAXLENOFMESSAGE, hContact); - if (m_nMax) { - if (M.GetByte("autosplit", 0)) - m_nMax = 20000; - } - else m_nMax = 20000; - - m_dat->LimitMessageText(m_nMax); - } - } - return m_nMax; -} - -bool CContactCache::updateStatus(int iStatus) -{ - m_iOldStatus = m_iStatus; - m_iStatus = iStatus; - return m_iOldStatus != iStatus; -} +///////////////////////////////////////////////////////////////////////////////////////// +// Miranda NG: the free IM client for Microsoft* Windows* +// +// Copyright (C) 2012-23 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 +// +// contact cache implementation +// +// the contact cache provides various services to the message window(s) +// it also abstracts meta contacts. + +#include "stdafx.h" + +static OBJLIST<CContactCache> arContacts(50, NumericKeySortT); + +static DBCachedContact ccInvalid; + +CContactCache::CContactCache(MCONTACT hContact) : + m_hContact(hContact), + m_history(10) +{ + if (hContact) { + if ((cc = db_get_contact(hContact)) != nullptr) { + initPhaseTwo(); + return; + } + } + + cc = &ccInvalid; + m_szAccount = C_INVALID_ACCOUNT; + m_isMeta = false; + m_isValid = false; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// 2nd part of the object initialization that must be callable during the +// object's lifetime (not only on construction). + +void CContactCache::initPhaseTwo() +{ + m_szAccount = nullptr; + if (cc->szProto) { + PROTOACCOUNT *acc = Proto_GetAccount(cc->szProto); + if (acc && acc->tszAccountName) + m_szAccount = acc->tszAccountName; + } + + m_isValid = (cc->szProto != nullptr && m_szAccount != nullptr) ? true : false; + if (m_isValid) { + m_iStatus = db_get_w(m_hContact, cc->szProto, "Status", ID_STATUS_OFFLINE); + m_isMeta = db_mc_isMeta(cc->contactID) != 0; // don't use cc->IsMeta() here + if (m_isMeta) + updateMeta(); + updateNick(); + } + else { + m_szAccount = C_INVALID_ACCOUNT; + m_isMeta = false; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// reset meta contact information.Used when meta contacts are disabled +// on user's request. + +void CContactCache::resetMeta() +{ + m_isMeta = false; + m_szMetaProto = nullptr; + m_iMetaStatus = ID_STATUS_OFFLINE; + initPhaseTwo(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// if the contact has an open message window, close it. +// window procedure will use setWindowData() to reset m_hwnd to 0. + +void CContactCache::closeWindow() +{ + if (m_dat) { + m_dat->m_bForcedClose = true; + m_dat->Close(); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// update private copy of the nick name.Use contact list name cache +// +// @return bool: true if nick has changed. + +bool CContactCache::updateNick() +{ + bool fChanged = false; + if (m_isValid) { + wchar_t *tszNick = Clist_GetContactDisplayName(getActiveContact()); + if (tszNick && mir_wstrcmp(m_szNick, tszNick)) + fChanged = true; + wcsncpy_s(m_szNick, (tszNick ? tszNick : L"<undef>"), _TRUNCATE); + } + return fChanged; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// update meta(subcontact and - protocol) status.This runs when the +// MC protocol fires one of its events OR when a relevant database value changes +// in the master contact. + +void CContactCache::updateMeta() +{ + if (m_isValid) { + MCONTACT hOldSub = m_hSub; + m_hSub = db_mc_getSrmmSub(cc->contactID); + m_szMetaProto = Proto_GetBaseAccountName(m_hSub); + m_iMetaStatus = (uint16_t)db_get_w(m_hSub, m_szMetaProto, "Status", ID_STATUS_OFFLINE); + PROTOACCOUNT *pa = Proto_GetAccount(m_szMetaProto); + if (pa) + m_szAccount = pa->tszAccountName; + + if (hOldSub != m_hSub) { + updateNick(); + updateUIN(); + } + } + else { + m_hSub = 0; + m_szMetaProto = nullptr; + m_iMetaStatus = ID_STATUS_OFFLINE; + m_xStatus = 0; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// obtain the UIN.This is only maintained for open message windows +// it also run when the subcontact for a MC changes. + +bool CContactCache::updateUIN() +{ + m_szUIN[0] = 0; + + if (m_isValid) { + ptrW uid(Contact::GetInfo(CNF_DISPLAYUID, getActiveContact(), getActiveProto())); + if (uid != nullptr) + wcsncpy_s(m_szUIN, uid, _TRUNCATE); + } + + return false; +} + +void CContactCache::updateStats(int iType, size_t value) +{ + if (m_stats == nullptr) + m_stats = new TSessionStats(); + + switch (iType) { + case TSessionStats::UPDATE_WITH_LAST_RCV: + if (!m_stats->lastReceivedChars) + break; + m_stats->iReceived++; + m_stats->messageCount++; + m_stats->iReceivedBytes += m_stats->lastReceivedChars; + m_stats->lastReceivedChars = 0; + break; + case TSessionStats::INIT_TIMER: + m_stats->started = time(0); + break; + case TSessionStats::SET_LAST_RCV: + m_stats->lastReceivedChars = (unsigned int)value; + break; + case TSessionStats::BYTES_SENT: + m_stats->iSent++; + m_stats->messageCount++; + m_stats->iSentBytes += (unsigned int)value; + break; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +//set the window data for this contact.The window procedure of the message +// dialog will use this in WM_INITDIALOG and WM_DESTROY to tell the cache +// that a message window is open for this contact. +// +// @param dat: CMsgDialog* - window data structure + +void CContactCache::setWindowData(CMsgDialog *dat) +{ + m_dat = dat; + + if (dat) { + updateStatusMsg(); + } + else { + // release memory - not needed when window isn't open + replaceStrW(m_szStatusMsg, nullptr); + replaceStrW(m_ListeningInfo, nullptr); + replaceStrW(m_xStatusMsg, nullptr); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// saves message to the input history. +// it's using streamout in UTF8 format - no unicode "issues" and all RTF formatting is saved to the history. + +void CContactCache::saveHistory() +{ + if (m_dat == nullptr) + return; + + CCtrlRichEdit &pEntry = m_dat->GetEntry(); + ptrA szFromStream(pEntry.GetRichTextRtf()); + if (szFromStream != nullptr) { + m_iHistoryCurrent = -1; + m_history.insert(szFromStream.detach()); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// handle the input history scrolling for the message input area +// @param wParam: VK_ keyboard code (VK_UP or VK_DOWN) + +void CContactCache::inputHistoryEvent(WPARAM wParam) +{ + if (m_dat == nullptr) + return; + + CCtrlRichEdit &pEntry = m_dat->GetEntry(); + if (m_history.getCount() > 0) { + char *pszText; + if (wParam == VK_UP) { + if (m_iHistoryCurrent == 0) + return; + + if (m_iHistoryCurrent < 0) + m_iHistoryCurrent = m_history.getCount()-1; + else + m_iHistoryCurrent--; + pszText = m_history[m_iHistoryCurrent]; + } + else { + if (m_iHistoryCurrent == -1) + return; + + if (m_iHistoryCurrent == m_history.getCount() - 1) { + m_iHistoryCurrent = -1; + pszText = ""; + } + else { + m_iHistoryCurrent++; + pszText = m_history[m_iHistoryCurrent]; + } + } + + SETTEXTEX stx = { ST_DEFAULT, CP_UTF8 }; + pEntry.SendMsg(EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)pszText); + pEntry.SendMsg(EM_SETSEL, -1, -1); + } + + pEntry.OnChange(&pEntry); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// release additional memory resources + +void CContactCache::releaseAlloced() +{ + if (m_stats) { + delete m_stats; + m_stats = nullptr; + } + + for (auto &it : m_history) + mir_free(it); + m_history.destroy(); + + mir_free(m_szStatusMsg); + m_szStatusMsg = nullptr; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// when a contact is deleted, mark it as invalid in the cache and release +// all memory it has allocated. + +void CContactCache::deletedHandler() +{ + cc = &ccInvalid; + m_isValid = false; + if (m_dat) { + m_dat->m_bForcedClose = true; + + // this message must be sent async to allow a contact to rest in peace before window gets closed + ::PostMessage(m_dat->GetHwnd(), WM_CLOSE, 1, 2); + } + + releaseAlloced(); + m_hContact = INVALID_CONTACT_ID; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// update all or only the given status message information from the database +// +// @param szKey: char* database key name or 0 to reload all messages + +void CContactCache::updateStatusMsg(const char *szKey) +{ + if (!m_isValid) + return; + + MCONTACT hContact = getActiveContact(); + + if (szKey == nullptr || (szKey && !mir_strcmp("StatusMsg", szKey))) { + if (m_szStatusMsg) + mir_free(m_szStatusMsg); + m_szStatusMsg = nullptr; + ptrW szStatus(db_get_wsa(hContact, "CList", "StatusMsg")); + if (szStatus != 0) + m_szStatusMsg = (mir_wstrlen(szStatus) > 0 ? getNormalizedStatusMsg(szStatus) : nullptr); + } + if (szKey == nullptr || (szKey && !mir_strcmp("ListeningTo", szKey))) { + if (m_ListeningInfo) + mir_free(m_ListeningInfo); + m_ListeningInfo = nullptr; + ptrW szListeningTo(db_get_wsa(hContact, cc->szProto, "ListeningTo")); + if (szListeningTo != 0 && *szListeningTo) + m_ListeningInfo = szListeningTo.detach(); + } + if (szKey == nullptr || (szKey && !mir_strcmp("XStatusMsg", szKey))) { + if (m_xStatusMsg) + mir_free(m_xStatusMsg); + m_xStatusMsg = nullptr; + ptrW szXStatusMsg(db_get_wsa(hContact, cc->szProto, "XStatusMsg")); + if (szXStatusMsg != 0 && *szXStatusMsg) + m_xStatusMsg = szXStatusMsg.detach(); + } + m_xStatus = db_get_b(hContact, cc->szProto, "XStatusId", 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// retrieve contact cache entry for the given contact.It _never_ returns zero, for a hContact +// 0, it retrieves a dummy object. +// Non-existing cache entries are created on demand. +// +// @param hContact: contact handle +// @return CContactCache* pointer to the cache entry for this contact + +CContactCache* CContactCache::getContactCache(MCONTACT hContact) +{ + CContactCache *cc = arContacts.find((CContactCache*)&hContact); + if (cc == nullptr) { + cc = new CContactCache(hContact); + arContacts.insert(cc); + } + return cc; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// when the state of the meta contacts protocol changes from enabled to disabled +// (or vice versa), this updates the contact cache +// +// it is ONLY called from the DBSettingChanged() event handler when the relevant +// database value is touched. + +int CContactCache::cacheUpdateMetaChanged(WPARAM bMetaEnabled, LPARAM) +{ + for (auto &c : arContacts) { + if (c->isMeta() && !bMetaEnabled) { + c->closeWindow(); + c->resetMeta(); + } + + // meta contacts are enabled, but current contact is a subcontact - > close window + if (bMetaEnabled && c->isSubContact()) + c->closeWindow(); + + // reset meta contact information, if metacontacts protocol became avail + if (bMetaEnabled && !c->cc->IsMeta()) + c->resetMeta(); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// normalize the status message with proper cr / lf sequences. +// @param src wchar_t*: original status message +// @param fStripAll bool: strip all cr/lf sequences and replace them with spaces (use for title bar) +// @return wchar_t*: converted status message. CALLER is responsible to mir_free it, MUST use mir_free() + +wchar_t* CContactCache::getNormalizedStatusMsg(const wchar_t *src, bool fStripAll) +{ + if (src == nullptr || mir_wstrlen(src) < 2) + return nullptr; + + CMStringW dest; + + for (int i = 0; src[i] != 0; i++) { + if (src[i] == 0x0d || src[i] == '\t') + continue; + if (i && src[i] == (wchar_t)0x0a) { + if (fStripAll) { + dest.AppendChar(' '); + continue; + } + dest.AppendChar('\n'); + continue; + } + dest.AppendChar(src[i]); + } + + return mir_wstrndup(dest, dest.GetLength()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// retrieve the tab / title icon for the corresponding session. + +HICON CContactCache::getIcon(int &iSize) const +{ + if (!m_dat) + return Skin_LoadProtoIcon(cc->szProto, getStatus()); + + if (m_dat->m_bErrorState) + return PluginConfig.g_iconErr; + if (m_dat->m_bCanFlashTab) + return m_dat->m_iFlashIcon; + + if (m_dat->isChat() && m_dat->m_iFlashIcon) { + int sizeX, sizeY; + Utils::getIconSize(m_dat->m_iFlashIcon, sizeX, sizeY); + iSize = sizeX; + return m_dat->m_iFlashIcon; + } + if (m_dat->m_hTabIcon == m_dat->m_hTabStatusIcon && m_dat->m_hXStatusIcon) + return m_dat->m_hXStatusIcon; + return m_dat->m_hTabIcon; +} + +size_t CContactCache::getMaxMessageLength() +{ + if (m_nMax == 0) { + MCONTACT hContact = getActiveContact(); + LPCSTR szProto = getActiveProto(); + if (szProto) { + m_nMax = CallProtoService(szProto, PS_GETCAPS, PFLAG_MAXLENOFMESSAGE, hContact); + if (m_nMax) { + if (M.GetByte("autosplit", 0)) + m_nMax = 20000; + } + else m_nMax = 20000; + + m_dat->LimitMessageText(m_nMax); + } + } + return m_nMax; +} + +bool CContactCache::updateStatus(int iStatus) +{ + m_iOldStatus = m_iStatus; + m_iStatus = iStatus; + return m_iOldStatus != iStatus; +} diff --git a/plugins/TabSRMM/src/contactcache.h b/plugins/TabSRMM/src/contactcache.h index 80a7ff18b2..1168b60258 100644 --- a/plugins/TabSRMM/src/contactcache.h +++ b/plugins/TabSRMM/src/contactcache.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/container.cpp b/plugins/TabSRMM/src/container.cpp index c26ddddb23..2c6c218631 100644 --- a/plugins/TabSRMM/src/container.cpp +++ b/plugins/TabSRMM/src/container.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/containeroptions.cpp b/plugins/TabSRMM/src/containeroptions.cpp index f956233387..96eaf7480d 100644 --- a/plugins/TabSRMM/src/containeroptions.cpp +++ b/plugins/TabSRMM/src/containeroptions.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/controls.cpp b/plugins/TabSRMM/src/controls.cpp index 4e72e2c8e2..1ac916f6cc 100644 --- a/plugins/TabSRMM/src/controls.cpp +++ b/plugins/TabSRMM/src/controls.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/controls.h b/plugins/TabSRMM/src/controls.h index c3ff022fd1..b878f347d4 100644 --- a/plugins/TabSRMM/src/controls.h +++ b/plugins/TabSRMM/src/controls.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/eventpopups.cpp b/plugins/TabSRMM/src/eventpopups.cpp index 37731f2f44..3b35900a1a 100644 --- a/plugins/TabSRMM/src/eventpopups.cpp +++ b/plugins/TabSRMM/src/eventpopups.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/functions.h b/plugins/TabSRMM/src/functions.h index e8c57f4476..dc0bb47c5f 100644 --- a/plugins/TabSRMM/src/functions.h +++ b/plugins/TabSRMM/src/functions.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/generic_msghandlers.cpp b/plugins/TabSRMM/src/generic_msghandlers.cpp index 9399fdc313..f704717da6 100644 --- a/plugins/TabSRMM/src/generic_msghandlers.cpp +++ b/plugins/TabSRMM/src/generic_msghandlers.cpp @@ -1,1358 +1,1358 @@ -///////////////////////////////////////////////////////////////////////////////////////// -// Miranda NG: the free IM client for Microsoft* Windows* -// -// Copyright (C) 2012-22 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_rtf.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: - m_pContainer->OptionsDialog(); - return true; - - 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(m_pContainer->m_hwndTabs, *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_ClipboardCopy(m_cache->getUIN()); - else - CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)(m_cache->getActiveContact()), 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: - submenu = GetSubMenu(PluginConfig.g_hMenuContext, 4); - { - bool iOldGlobalSendFormat = g_plugin.bSendFormat; - int iLocalFormat = M.GetDword(m_hContact, "sendformat", 0); - int iNewLocalFormat = iLocalFormat; - - GetWindowRect(GetDlgItem(m_hwnd, IDC_PROTOCOL), &rc); - - CheckMenuItem(submenu, ID_MODE_GLOBAL, !m_bSplitterOverride ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_MODE_PRIVATE, m_bSplitterOverride ? MF_CHECKED : MF_UNCHECKED); - - // formatting menu.. - CheckMenuItem(submenu, ID_GLOBAL_BBCODE, (g_plugin.bSendFormat) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_GLOBAL_OFF, (g_plugin.bSendFormat == SENDFORMAT_NONE) ? MF_CHECKED : MF_UNCHECKED); - - CheckMenuItem(submenu, ID_THISCONTACT_GLOBALSETTING, (iLocalFormat == SENDFORMAT_NONE) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_THISCONTACT_BBCODE, (iLocalFormat > 0) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_THISCONTACT_OFF, (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_bSplitterOverride = false; - db_set_b(m_hContact, SRMSGMOD_T, "splitoverride", 0); - LoadSplitter(); - AdjustBottomAvatarDisplay(); - DM_RecalcPictureSize(); - Resize(); - break; - - case ID_MODE_PRIVATE: - m_bSplitterOverride = true; - db_set_b(m_hContact, SRMSGMOD_T, "splitoverride", 1); - LoadSplitter(); - AdjustBottomAvatarDisplay(); - DM_RecalcPictureSize(); - Resize(); - break; - - case ID_GLOBAL_BBCODE: - g_plugin.bSendFormat = SENDFORMAT_BBCODE; - break; - - case ID_GLOBAL_OFF: - g_plugin.bSendFormat = 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 (iNewLocalFormat != iLocalFormat || g_plugin.bSendFormat != iOldGlobalSendFormat) { - m_SendFormat = M.GetDword(m_hContact, "sendformat", g_plugin.bSendFormat); - 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) - m_pContainer->cfg.flags.m_bNoMenuBar = !m_pContainer->cfg.flags.m_bNoMenuBar; - else - m_pContainer->cfg.flags.m_bHideToolbar = !m_pContainer->cfg.flags.m_bHideToolbar; - m_pContainer->ApplySetting(true); - break; - - case IDC_SENDMENU: - submenu = GetSubMenu(PluginConfig.g_hMenuContext, 3); - - GetWindowRect(GetDlgItem(m_hwnd, IDOK), &rc); - CheckMenuItem(submenu, ID_SENDMENU_SENDTOMULTIPLEUSERS, (m_sendMode & SMODE_MULTIPLE) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_SENDMENU_SENDDEFAULT, m_sendMode == 0 ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_SENDMENU_SENDTOCONTAINER, (m_sendMode & SMODE_CONTAINER) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_SENDMENU_SENDLATER, (m_sendMode & SMODE_SENDLATER) ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(submenu, ID_SENDMENU_SENDWITHOUTTIMEOUTS, (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::Avail) - 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", (uint8_t)(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); - } - m_pContainer->QueryClientArea(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 (AllowTyping()) { - int iCurrentTypingMode = g_plugin.getByte(m_hContact, SRMSGSET_TYPING, g_plugin.bTypingNew); - if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON && iCurrentTypingMode) { - DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); - m_nTypeMode = PROTOTYPE_SELFTYPING_OFF; - } - g_plugin.setByte(m_hContact, SRMSGSET_TYPING, (uint8_t)!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() -{ - char *szStreamOut = nullptr; - if (!isChat() && GetWindowTextLength(m_message.GetHwnd()) > 0) - szStreamOut = m_message.GetRichTextRtf(); - SetWindowText(m_message.GetHwnd(), L""); - - m_pLog->UpdateOptions(); - - m_message.SendMsg(EM_SETBKGNDCOLOR, 0, m_pContainer->m_theme.inputbg); - - CHARFORMAT2 cf2 = {}; - cf2.cbSize = sizeof(cf2); - - if (isChat()) { - LOGFONTW lf; - COLORREF inputcharcolor; - LoadMsgDlgFont(FONTSECTION_IM, MSGFONTID_MESSAGEAREA, &lf, &inputcharcolor); - - 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; - wcsncpy_s(cf2.szFaceName, lf.lfFaceName, _TRUNCATE); - cf2.dwEffects = 0; - cf2.wWeight = (uint16_t)lf.lfWeight; - cf2.bPitchAndFamily = lf.lfPitchAndFamily; - cf2.yHeight = abs(lf.lfHeight) * 15; - } - else { - LOGFONTW lf = m_pContainer->m_theme.logFonts[MSGFONTID_MESSAGEAREA]; - COLORREF 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; - wcsncpy_s(cf2.szFaceName, lf.lfFaceName, _TRUNCATE); - 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 = (uint16_t)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); - - 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); - else - SetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_message.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 (buttonItem->dwFlags & BUTTON_ISCONTACTDBACTION) - szModule = Proto_GetBaseAccountName(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) == *((uint16_t *)&buttonItem->bValuePush)); - break; - case DBVT_DWORD: - result = (db_get_dw(hFinalContact, szModule, szSetting, 0) == *((uint32_t *)&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_bScrollingDisabled) - return; - - if (IsIconic(m_pContainer->m_hwnd)) - m_bDeferredScroll = true; - - if (m_iLogMode == WANT_BUILTIN_LOG) - ((CLogWindow *)m_pLog)->ScrollToBottom(wParam != 0, lParam != 0); - else - m_pLog->ScrollToBottom(); -} - -void CMsgDialog::DM_RecalcPictureSize() -{ - HBITMAP hbm = ((m_pPanel.isActive()) && m_pContainer->cfg.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->cfg.flags.m_bUinStatusBar) - 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->cfg.flags.m_bUinStatusBar && 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::Avail) { - 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 (!g_plugin.bAllowOfflineMultisend) - SetWindowLongPtr(hwndClist, GWL_STYLE, GetWindowLongPtr(hwndClist, GWL_STYLE) | CLS_HIDEOFFLINE); - - if (hItem) - SendMessage(hwndClist, CLM_SETCHECKMARK, (WPARAM)hItem, 1); - - SendMessage(hwndClist, CLM_SETHIDEEMPTYGROUPS, Clist::HideEmptyGroups, 0); - SendMessage(hwndClist, CLM_SETUSEGROUPS, Clist::UseGroups, 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; - GetWindowRect(m_message.GetHwnd(), &rc); - if (PtInRect(&rc, pt)) - return 1; - - 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; - } - } - - GetWindowRect(m_pLog->GetHwnd(), &rc); - if (PtInRect(&rc, pt)) { - short wDirection = (short)HIWORD(wParam); - - if (LOWORD(wParam) & MK_SHIFT || M.GetByte("fastscroll", 0)) { - if (wDirection < 0) - SendMessage(m_pLog->GetHwnd(), WM_VSCROLL, MAKEWPARAM(SB_PAGEDOWN, 0), 0); - else if (wDirection > 0) - SendMessage(m_pLog->GetHwnd(), WM_VSCROLL, MAKEWPARAM(SB_PAGEUP, 0), 0); - } - else SendMessage(m_pLog->GetHwnd(), WM_MOUSEWHEEL, wParam, lParam); - return 0; - } - - if (GetTabItemFromMouse(m_pContainer->m_hwndTabs, &pt) != -1) { - SendMessage(m_pContainer->m_hwndTabs, 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)) { - if (m_iLogMode == WANT_BUILTIN_LOG) - LOG()->DisableStaticEdge(); - - 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) -{ - 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.bTypingNew)) - return; - - if (szProto == nullptr) // should not, but who knows... - return; - - // check status and capabilities of the protocol - uint32_t typeCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0); - if (!(typeCaps & PF4_SUPPORTTYPING)) - return; - - if (isChat()) { - m_nTypeMode = mode; - Chat_DoEventHook(m_si, GC_USER_TYPNOTIFY, 0, 0, m_nTypeMode); - } - else { - uint32_t 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) - uint32_t 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 (!Contact::OnList(m_hContact) && !g_plugin.bTypingUnknown) - return; - - // End user check - m_nTypeMode = mode; - CallService(MS_PROTO_SELFISTYPING, hContact, m_nTypeMode); - } -} - -void CMsgDialog::DM_OptionsApplied(bool bRemakeLog) -{ - 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->cfg.flags.m_bHideToolbar) == 0; - m_bSplitterOverride = M.GetByte(m_hContact, "splitoverride", 0) != 0; - m_pPanel.getVisibility(); - - // small inner margins (padding) for the text areas - 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 (bRemakeLog) { - if (IsIconic(m_pContainer->m_hwnd)) - m_bDeferredRemakeLog = true; - else if (isChat()) - RedrawLog(); - 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; - - if (!isChat()) - 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->cfg.flags.m_bNoFlash && PluginConfig.m_FlashOnMTN) - m_pContainer->ReflashContainer(); - } - } - 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_pUserTyping) ? m_pUserTyping->pszNick : 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->cfg.flags.m_bNeedsUpdateTitle = true; - if (!m_pContainer->cfg.flags.m_bNoFlash && PluginConfig.m_FlashOnMTN) - m_pContainer->ReflashContainer(); - } - - 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->cfg.flags.m_bHideTabs) - 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->cfg.fPrivate ? true : false); - - if (m_bIsAutosizingInput) - return 0; - - RECT rcWin; - GetWindowRect(m_hwnd, &rcWin); - - LONG newPos; - if (wParam == 0 && lParam == 0) { - if (m_bSplitterOverride && 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->cfg.iSplitterY = m_iSplitterY; - if (fCntGlobal) - SaveSplitter(); - return 0; - } - - if (!fCntGlobal && m_pContainer != srcCnt) - return 0; - if (srcCnt->cfg.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_bDelayedSplitter = true; - 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_bDividerSet && g_plugin.bUseDividers) - if (GetWindowTextLength(m_pLog->GetHwnd()) > 0) - m_bDividerSet = m_bDividerWanted = true; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// incoming event handler - -void CMsgDialog::DM_EventAdded(WPARAM, 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 (g_plugin.bDividersUsePopupConfig && g_plugin.bUseDividers) { - if (!MessageWindowOpened(m_hContact, nullptr)) - DM_AddDivider(); - } - else if (g_plugin.bUseDividers) { - if (!m_pContainer->IsActive()) - DM_AddDivider(); - else if (m_pContainer->m_hwndActive != m_hwnd) - DM_AddDivider(); - } - - if (IsWindowVisible(m_pContainer->m_hwnd)) - m_pContainer->m_bHidden = false; - } - m_cache->updateStats(TSessionStats::UPDATE_WITH_LAST_RCV, 0); - - if (hDbEvent != m_hDbEventFirst || isChat()) - StreamEvents(hDbEvent, 1, 1); - 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; - } - timerFlash.Start(TIMEOUT_FLASHWND); - 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 (g_plugin.bAutoSwitchTabs && m_pContainer->m_hwndActive != m_hwnd) { - if ((IsIconic(m_pContainer->m_hwnd) && !IsZoomed(m_pContainer->m_hwnd)) || (g_plugin.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->cfg.flags.m_bDeferredTabSelect = true; - } - } - } - } - - // flash window if it is not focused - if (!bDisableNotify && !bIsStatusChangeEvent) - if (!IsActive() && !(dbei.flags & DBEF_SENT)) { - if (!m_pContainer->cfg.flags.m_bNoFlash && !m_pContainer->IsActive()) - m_pContainer->FlashContainer(1, 0); - m_pContainer->SetIcon(this, Skin_LoadIcon(SKINICON_EVENT_MESSAGE)); - m_pContainer->cfg.flags.m_bNeedsUpdateTitle = true; - } - - // 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->cfg.flags.m_bBottomToolbar) - 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->cfg.flags.m_bNoSound ? - PluginConfig.g_iconOverlayDisabled : PluginConfig.g_iconOverlayEnabled, - PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); - } - else if (sid->dwId == MSG_ICON_UTN) { - if (AllowTyping()) { - 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.bTypingNew) ? - 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 { - 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->cfg.flags.m_bNoSound = m_pContainer->cfg.flags.m_bNoSound; - InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); - } - } - else { - m_pContainer->cfg.flags.m_bNoSound = !m_pContainer->cfg.flags.m_bNoSound; - InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); - } - } - else if (sid->dwId == MSG_ICON_UTN && code != NM_RCLICK && AllowTyping()) { - SendMessage(m_pContainer->m_hwndActive, WM_COMMAND, IDC_SELFTYPING, 0); - InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); - } - } - 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_bErrorState) { - 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 || 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_bErrorState) { - 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); - - HookEvent(ME_MSG_ICONSCHANGED, OnSrmmIconChanged); - return 0; -} +///////////////////////////////////////////////////////////////////////////////////////// +// Miranda NG: the free IM client for Microsoft* Windows* +// +// Copyright (C) 2012-23 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_rtf.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: + m_pContainer->OptionsDialog(); + return true; + + 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(m_pContainer->m_hwndTabs, *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_ClipboardCopy(m_cache->getUIN()); + else + CallService(MS_USERINFO_SHOWDIALOG, (WPARAM)(m_cache->getActiveContact()), 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: + submenu = GetSubMenu(PluginConfig.g_hMenuContext, 4); + { + bool iOldGlobalSendFormat = g_plugin.bSendFormat; + int iLocalFormat = M.GetDword(m_hContact, "sendformat", 0); + int iNewLocalFormat = iLocalFormat; + + GetWindowRect(GetDlgItem(m_hwnd, IDC_PROTOCOL), &rc); + + CheckMenuItem(submenu, ID_MODE_GLOBAL, !m_bSplitterOverride ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_MODE_PRIVATE, m_bSplitterOverride ? MF_CHECKED : MF_UNCHECKED); + + // formatting menu.. + CheckMenuItem(submenu, ID_GLOBAL_BBCODE, (g_plugin.bSendFormat) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_GLOBAL_OFF, (g_plugin.bSendFormat == SENDFORMAT_NONE) ? MF_CHECKED : MF_UNCHECKED); + + CheckMenuItem(submenu, ID_THISCONTACT_GLOBALSETTING, (iLocalFormat == SENDFORMAT_NONE) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_THISCONTACT_BBCODE, (iLocalFormat > 0) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_THISCONTACT_OFF, (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_bSplitterOverride = false; + db_set_b(m_hContact, SRMSGMOD_T, "splitoverride", 0); + LoadSplitter(); + AdjustBottomAvatarDisplay(); + DM_RecalcPictureSize(); + Resize(); + break; + + case ID_MODE_PRIVATE: + m_bSplitterOverride = true; + db_set_b(m_hContact, SRMSGMOD_T, "splitoverride", 1); + LoadSplitter(); + AdjustBottomAvatarDisplay(); + DM_RecalcPictureSize(); + Resize(); + break; + + case ID_GLOBAL_BBCODE: + g_plugin.bSendFormat = SENDFORMAT_BBCODE; + break; + + case ID_GLOBAL_OFF: + g_plugin.bSendFormat = 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 (iNewLocalFormat != iLocalFormat || g_plugin.bSendFormat != iOldGlobalSendFormat) { + m_SendFormat = M.GetDword(m_hContact, "sendformat", g_plugin.bSendFormat); + 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) + m_pContainer->cfg.flags.m_bNoMenuBar = !m_pContainer->cfg.flags.m_bNoMenuBar; + else + m_pContainer->cfg.flags.m_bHideToolbar = !m_pContainer->cfg.flags.m_bHideToolbar; + m_pContainer->ApplySetting(true); + break; + + case IDC_SENDMENU: + submenu = GetSubMenu(PluginConfig.g_hMenuContext, 3); + + GetWindowRect(GetDlgItem(m_hwnd, IDOK), &rc); + CheckMenuItem(submenu, ID_SENDMENU_SENDTOMULTIPLEUSERS, (m_sendMode & SMODE_MULTIPLE) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_SENDMENU_SENDDEFAULT, m_sendMode == 0 ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_SENDMENU_SENDTOCONTAINER, (m_sendMode & SMODE_CONTAINER) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_SENDMENU_SENDLATER, (m_sendMode & SMODE_SENDLATER) ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(submenu, ID_SENDMENU_SENDWITHOUTTIMEOUTS, (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::Avail) + 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", (uint8_t)(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); + } + m_pContainer->QueryClientArea(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 (AllowTyping()) { + int iCurrentTypingMode = g_plugin.getByte(m_hContact, SRMSGSET_TYPING, g_plugin.bTypingNew); + if (m_nTypeMode == PROTOTYPE_SELFTYPING_ON && iCurrentTypingMode) { + DM_NotifyTyping(PROTOTYPE_SELFTYPING_OFF); + m_nTypeMode = PROTOTYPE_SELFTYPING_OFF; + } + g_plugin.setByte(m_hContact, SRMSGSET_TYPING, (uint8_t)!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() +{ + char *szStreamOut = nullptr; + if (!isChat() && GetWindowTextLength(m_message.GetHwnd()) > 0) + szStreamOut = m_message.GetRichTextRtf(); + SetWindowText(m_message.GetHwnd(), L""); + + m_pLog->UpdateOptions(); + + m_message.SendMsg(EM_SETBKGNDCOLOR, 0, m_pContainer->m_theme.inputbg); + + CHARFORMAT2 cf2 = {}; + cf2.cbSize = sizeof(cf2); + + if (isChat()) { + LOGFONTW lf; + COLORREF inputcharcolor; + LoadMsgDlgFont(FONTSECTION_IM, MSGFONTID_MESSAGEAREA, &lf, &inputcharcolor); + + 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; + wcsncpy_s(cf2.szFaceName, lf.lfFaceName, _TRUNCATE); + cf2.dwEffects = 0; + cf2.wWeight = (uint16_t)lf.lfWeight; + cf2.bPitchAndFamily = lf.lfPitchAndFamily; + cf2.yHeight = abs(lf.lfHeight) * 15; + } + else { + LOGFONTW lf = m_pContainer->m_theme.logFonts[MSGFONTID_MESSAGEAREA]; + COLORREF 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; + wcsncpy_s(cf2.szFaceName, lf.lfFaceName, _TRUNCATE); + 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 = (uint16_t)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); + + 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); + else + SetWindowLongPtr(m_message.GetHwnd(), GWL_EXSTYLE, GetWindowLongPtr(m_message.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 (buttonItem->dwFlags & BUTTON_ISCONTACTDBACTION) + szModule = Proto_GetBaseAccountName(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) == *((uint16_t *)&buttonItem->bValuePush)); + break; + case DBVT_DWORD: + result = (db_get_dw(hFinalContact, szModule, szSetting, 0) == *((uint32_t *)&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_bScrollingDisabled) + return; + + if (IsIconic(m_pContainer->m_hwnd)) + m_bDeferredScroll = true; + + if (m_iLogMode == WANT_BUILTIN_LOG) + ((CLogWindow *)m_pLog)->ScrollToBottom(wParam != 0, lParam != 0); + else + m_pLog->ScrollToBottom(); +} + +void CMsgDialog::DM_RecalcPictureSize() +{ + HBITMAP hbm = ((m_pPanel.isActive()) && m_pContainer->cfg.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->cfg.flags.m_bUinStatusBar) + 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->cfg.flags.m_bUinStatusBar && 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::Avail) { + 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 (!g_plugin.bAllowOfflineMultisend) + SetWindowLongPtr(hwndClist, GWL_STYLE, GetWindowLongPtr(hwndClist, GWL_STYLE) | CLS_HIDEOFFLINE); + + if (hItem) + SendMessage(hwndClist, CLM_SETCHECKMARK, (WPARAM)hItem, 1); + + SendMessage(hwndClist, CLM_SETHIDEEMPTYGROUPS, Clist::HideEmptyGroups, 0); + SendMessage(hwndClist, CLM_SETUSEGROUPS, Clist::UseGroups, 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; + GetWindowRect(m_message.GetHwnd(), &rc); + if (PtInRect(&rc, pt)) + return 1; + + 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; + } + } + + GetWindowRect(m_pLog->GetHwnd(), &rc); + if (PtInRect(&rc, pt)) { + short wDirection = (short)HIWORD(wParam); + + if (LOWORD(wParam) & MK_SHIFT || M.GetByte("fastscroll", 0)) { + if (wDirection < 0) + SendMessage(m_pLog->GetHwnd(), WM_VSCROLL, MAKEWPARAM(SB_PAGEDOWN, 0), 0); + else if (wDirection > 0) + SendMessage(m_pLog->GetHwnd(), WM_VSCROLL, MAKEWPARAM(SB_PAGEUP, 0), 0); + } + else SendMessage(m_pLog->GetHwnd(), WM_MOUSEWHEEL, wParam, lParam); + return 0; + } + + if (GetTabItemFromMouse(m_pContainer->m_hwndTabs, &pt) != -1) { + SendMessage(m_pContainer->m_hwndTabs, 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)) { + if (m_iLogMode == WANT_BUILTIN_LOG) + LOG()->DisableStaticEdge(); + + 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) +{ + 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.bTypingNew)) + return; + + if (szProto == nullptr) // should not, but who knows... + return; + + // check status and capabilities of the protocol + uint32_t typeCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0); + if (!(typeCaps & PF4_SUPPORTTYPING)) + return; + + if (isChat()) { + m_nTypeMode = mode; + Chat_DoEventHook(m_si, GC_USER_TYPNOTIFY, 0, 0, m_nTypeMode); + } + else { + uint32_t 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) + uint32_t 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 (!Contact::OnList(m_hContact) && !g_plugin.bTypingUnknown) + return; + + // End user check + m_nTypeMode = mode; + CallService(MS_PROTO_SELFISTYPING, hContact, m_nTypeMode); + } +} + +void CMsgDialog::DM_OptionsApplied(bool bRemakeLog) +{ + 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->cfg.flags.m_bHideToolbar) == 0; + m_bSplitterOverride = M.GetByte(m_hContact, "splitoverride", 0) != 0; + m_pPanel.getVisibility(); + + // small inner margins (padding) for the text areas + 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 (bRemakeLog) { + if (IsIconic(m_pContainer->m_hwnd)) + m_bDeferredRemakeLog = true; + else if (isChat()) + RedrawLog(); + 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; + + if (!isChat()) + 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->cfg.flags.m_bNoFlash && PluginConfig.m_FlashOnMTN) + m_pContainer->ReflashContainer(); + } + } + 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_pUserTyping) ? m_pUserTyping->pszNick : 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->cfg.flags.m_bNeedsUpdateTitle = true; + if (!m_pContainer->cfg.flags.m_bNoFlash && PluginConfig.m_FlashOnMTN) + m_pContainer->ReflashContainer(); + } + + 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->cfg.flags.m_bHideTabs) + 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->cfg.fPrivate ? true : false); + + if (m_bIsAutosizingInput) + return 0; + + RECT rcWin; + GetWindowRect(m_hwnd, &rcWin); + + LONG newPos; + if (wParam == 0 && lParam == 0) { + if (m_bSplitterOverride && 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->cfg.iSplitterY = m_iSplitterY; + if (fCntGlobal) + SaveSplitter(); + return 0; + } + + if (!fCntGlobal && m_pContainer != srcCnt) + return 0; + if (srcCnt->cfg.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_bDelayedSplitter = true; + 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_bDividerSet && g_plugin.bUseDividers) + if (GetWindowTextLength(m_pLog->GetHwnd()) > 0) + m_bDividerSet = m_bDividerWanted = true; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// incoming event handler + +void CMsgDialog::DM_EventAdded(WPARAM, 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 (g_plugin.bDividersUsePopupConfig && g_plugin.bUseDividers) { + if (!MessageWindowOpened(m_hContact, nullptr)) + DM_AddDivider(); + } + else if (g_plugin.bUseDividers) { + if (!m_pContainer->IsActive()) + DM_AddDivider(); + else if (m_pContainer->m_hwndActive != m_hwnd) + DM_AddDivider(); + } + + if (IsWindowVisible(m_pContainer->m_hwnd)) + m_pContainer->m_bHidden = false; + } + m_cache->updateStats(TSessionStats::UPDATE_WITH_LAST_RCV, 0); + + if (hDbEvent != m_hDbEventFirst || isChat()) + StreamEvents(hDbEvent, 1, 1); + 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; + } + timerFlash.Start(TIMEOUT_FLASHWND); + 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 (g_plugin.bAutoSwitchTabs && m_pContainer->m_hwndActive != m_hwnd) { + if ((IsIconic(m_pContainer->m_hwnd) && !IsZoomed(m_pContainer->m_hwnd)) || (g_plugin.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->cfg.flags.m_bDeferredTabSelect = true; + } + } + } + } + + // flash window if it is not focused + if (!bDisableNotify && !bIsStatusChangeEvent) + if (!IsActive() && !(dbei.flags & DBEF_SENT)) { + if (!m_pContainer->cfg.flags.m_bNoFlash && !m_pContainer->IsActive()) + m_pContainer->FlashContainer(1, 0); + m_pContainer->SetIcon(this, Skin_LoadIcon(SKINICON_EVENT_MESSAGE)); + m_pContainer->cfg.flags.m_bNeedsUpdateTitle = true; + } + + // 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->cfg.flags.m_bBottomToolbar) + 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->cfg.flags.m_bNoSound ? + PluginConfig.g_iconOverlayDisabled : PluginConfig.g_iconOverlayEnabled, + PluginConfig.m_smcxicon, PluginConfig.m_smcyicon, 0, nullptr, DI_NORMAL); + } + else if (sid->dwId == MSG_ICON_UTN) { + if (AllowTyping()) { + 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.bTypingNew) ? + 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 { + 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->cfg.flags.m_bNoSound = m_pContainer->cfg.flags.m_bNoSound; + InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); + } + } + else { + m_pContainer->cfg.flags.m_bNoSound = !m_pContainer->cfg.flags.m_bNoSound; + InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); + } + } + else if (sid->dwId == MSG_ICON_UTN && code != NM_RCLICK && AllowTyping()) { + SendMessage(m_pContainer->m_hwndActive, WM_COMMAND, IDC_SELFTYPING, 0); + InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); + } + } + 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_bErrorState) { + 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 || 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_bErrorState) { + 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); + + HookEvent(ME_MSG_ICONSCHANGED, OnSrmmIconChanged); + return 0; +} diff --git a/plugins/TabSRMM/src/globals.cpp b/plugins/TabSRMM/src/globals.cpp index 953996a5cd..a80ff3f2b2 100644 --- a/plugins/TabSRMM/src/globals.cpp +++ b/plugins/TabSRMM/src/globals.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/globals.h b/plugins/TabSRMM/src/globals.h index fb52d01df2..db6229428e 100644 --- a/plugins/TabSRMM/src/globals.h +++ b/plugins/TabSRMM/src/globals.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/hotkeyhandler.cpp b/plugins/TabSRMM/src/hotkeyhandler.cpp index ebbd290ec5..5deb191552 100644 --- a/plugins/TabSRMM/src/hotkeyhandler.cpp +++ b/plugins/TabSRMM/src/hotkeyhandler.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/infopanel.cpp b/plugins/TabSRMM/src/infopanel.cpp index 69b1400fcb..37d433cd7f 100644 --- a/plugins/TabSRMM/src/infopanel.cpp +++ b/plugins/TabSRMM/src/infopanel.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/infopanel.h b/plugins/TabSRMM/src/infopanel.h index ead415ea29..ffa8c884a1 100644 --- a/plugins/TabSRMM/src/infopanel.h +++ b/plugins/TabSRMM/src/infopanel.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/mim.cpp b/plugins/TabSRMM/src/mim.cpp index 736c9c5e86..ca1e9c3272 100644 --- a/plugins/TabSRMM/src/mim.cpp +++ b/plugins/TabSRMM/src/mim.cpp @@ -1,488 +1,488 @@ -///////////////////////////////////////////////////////////////////////////////////////// -// Miranda NG: the free IM client for Microsoft* Windows* -// -// Copyright (C) 2012-22 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 -// -// wraps some parts of Miranda API -// Also, OS dependent stuff (visual styles api etc.) - -#include "stdafx.h" - -PDTTE CMimAPI::m_pfnDrawThemeTextEx = nullptr; -DEFICA CMimAPI::m_pfnDwmExtendFrameIntoClientArea = nullptr; -DICE CMimAPI::m_pfnDwmIsCompositionEnabled = nullptr; -DRT CMimAPI::m_pfnDwmRegisterThumbnail = nullptr; -BPI CMimAPI::m_pfnBufferedPaintInit = nullptr; -BPU CMimAPI::m_pfnBufferedPaintUninit = nullptr; -BBP CMimAPI::m_pfnBeginBufferedPaint = nullptr; -EBP CMimAPI::m_pfnEndBufferedPaint = nullptr; -BBW CMimAPI::m_pfnDwmBlurBehindWindow = nullptr; -DGC CMimAPI::m_pfnDwmGetColorizationColor = nullptr; -BPSA CMimAPI::m_pfnBufferedPaintSetAlpha = nullptr; -DWMIIB CMimAPI::m_pfnDwmInvalidateIconicBitmaps = nullptr; -DWMSWA CMimAPI::m_pfnDwmSetWindowAttribute = nullptr; -DWMUT CMimAPI::m_pfnDwmUpdateThumbnailProperties = nullptr; -DURT CMimAPI::m_pfnDwmUnregisterThumbnail = nullptr; -DSIT CMimAPI::m_pfnDwmSetIconicThumbnail = nullptr; -DSILP CMimAPI::m_pfnDwmSetIconicLivePreviewBitmap = nullptr; -bool CMimAPI::m_shutDown = 0; -wchar_t CMimAPI::m_userDir[] = L"\0"; - -bool CMimAPI::m_haveBufferedPaint = false; - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMimAPI::FoldersPathChanged(WPARAM, LPARAM) -{ - return M.foldersPathChanged(); -} - -void CMimAPI::configureCustomFolders() -{ - m_hDataPath = FoldersRegisterCustomPathW(LPGEN("TabSRMM"), LPGEN("Data path"), const_cast<wchar_t *>(getDataPath())); - m_hSkinsPath = FoldersRegisterCustomPathW(LPGEN("Skins"), LPGEN("TabSRMM"), const_cast<wchar_t *>(getSkinPath())); - m_hAvatarsPath = FoldersRegisterCustomPathW(LPGEN("Avatars"), LPGEN("Saved TabSRMM avatars"), const_cast<wchar_t *>(getSavedAvatarPath())); - m_hChatLogsPath = FoldersRegisterCustomPathW(LPGEN("TabSRMM"), LPGEN("Group chat logs root"), const_cast<wchar_t *>(getChatLogPath())); - - if (m_hDataPath) - HookEvent(ME_FOLDERS_PATH_CHANGED, CMimAPI::FoldersPathChanged); - - foldersPathChanged(); -} - -INT_PTR CMimAPI::foldersPathChanged() -{ - wchar_t szTemp[MAX_PATH + 2]; - - if (m_hDataPath) { - szTemp[0] = 0; - FoldersGetCustomPathW(m_hDataPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getDataPath())); - wcsncpy_s(m_szProfilePath, szTemp, _TRUNCATE); - - szTemp[0] = 0; - FoldersGetCustomPathW(m_hSkinsPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getSkinPath())); - wcsncpy_s(m_szSkinsPath, (MAX_PATH - 1), szTemp, _TRUNCATE); - Utils::ensureTralingBackslash(m_szSkinsPath); - - szTemp[0] = 0; - FoldersGetCustomPathW(m_hAvatarsPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getSavedAvatarPath())); - wcsncpy_s(m_szSavedAvatarsPath, szTemp, _TRUNCATE); - - szTemp[0] = 0; - FoldersGetCustomPathW(m_hChatLogsPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getChatLogPath())); - wcsncpy_s(m_szChatLogsPath, (MAX_PATH - 1), szTemp, _TRUNCATE); - Utils::ensureTralingBackslash(m_szChatLogsPath); - } - - CreateDirectoryTreeW(m_szProfilePath); - CreateDirectoryTreeW(m_szSkinsPath); - CreateDirectoryTreeW(m_szSavedAvatarsPath); - - Skin->extractSkinsAndLogo(true); - Skin->setupAeroSkins(); - return 0; -} - -const wchar_t* CMimAPI::getUserDir() -{ - if (m_userDir[0] == 0) { - if (ServiceExists(MS_FOLDERS_REGISTER_PATH)) - wcsncpy_s(m_userDir, L"%miranda_userdata%", _TRUNCATE); - else - wcsncpy_s(m_userDir, VARSW(L"%miranda_userdata%"), _TRUNCATE); - - Utils::ensureTralingBackslash(m_userDir); - } - return m_userDir; -} - -void CMimAPI::InitPaths() -{ - const wchar_t *szUserdataDir = getUserDir(); - - mir_snwprintf(m_szProfilePath, L"%stabSRMM", szUserdataDir); - if (ServiceExists(MS_FOLDERS_REGISTER_PATH)) { - wcsncpy_s(m_szChatLogsPath, L"%miranda_logpath%", _TRUNCATE); - wcsncpy_s(m_szSkinsPath, L"%miranda_path%\\Skins\\TabSRMM", _TRUNCATE); - } - else { - wcsncpy_s(m_szChatLogsPath, VARSW(L"%miranda_logpath%"), _TRUNCATE); - wcsncpy_s(m_szSkinsPath, VARSW(L"%miranda_path%\\Skins\\TabSRMM"), _TRUNCATE); - } - - Utils::ensureTralingBackslash(m_szChatLogsPath); - replaceStrW(g_Settings.pszLogDir, m_szChatLogsPath); - - Utils::ensureTralingBackslash(m_szSkinsPath); - - mir_snwprintf(m_szSavedAvatarsPath, L"%s\\Saved Contact Pictures", m_szProfilePath); -} - -bool CMimAPI::getAeroState() -{ - m_isAero = m_DwmActive = false; - if (IsWinVerVistaPlus()) { - BOOL result = FALSE; - m_DwmActive = (m_pfnDwmIsCompositionEnabled && (m_pfnDwmIsCompositionEnabled(&result) == S_OK) && result); - m_isAero = (CSkin::m_skinEnabled == false) && GetByte("useAero", 1) && CSkin::m_fAeroSkinsValid && m_DwmActive; - - } - m_isVsThemed = IsThemeActive() != 0; - return m_isAero; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Initialize various Win32 API functions which are not common to all versions of Windows. -// We have to work with functions pointers here. - -void CMimAPI::InitAPI() -{ - m_hUxTheme = nullptr; - m_hDwmApi = nullptr; - - // vista+ DWM API - if (IsWinVerVistaPlus()) { - m_hDwmApi = Utils::loadSystemLibrary(L"\\dwmapi.dll"); - if (m_hDwmApi) { - m_pfnDwmExtendFrameIntoClientArea = (DEFICA)GetProcAddress(m_hDwmApi, "DwmExtendFrameIntoClientArea"); - m_pfnDwmIsCompositionEnabled = (DICE)GetProcAddress(m_hDwmApi, "DwmIsCompositionEnabled"); - m_pfnDwmRegisterThumbnail = (DRT)GetProcAddress(m_hDwmApi, "DwmRegisterThumbnail"); - m_pfnDwmBlurBehindWindow = (BBW)GetProcAddress(m_hDwmApi, "DwmEnableBlurBehindWindow"); - m_pfnDwmGetColorizationColor = (DGC)GetProcAddress(m_hDwmApi, "DwmGetColorizationColor"); - m_pfnDwmInvalidateIconicBitmaps = (DWMIIB)GetProcAddress(m_hDwmApi, "DwmInvalidateIconicBitmaps"); - m_pfnDwmSetWindowAttribute = (DWMSWA)GetProcAddress(m_hDwmApi, "DwmSetWindowAttribute"); - m_pfnDwmUpdateThumbnailProperties = (DWMUT)GetProcAddress(m_hDwmApi, "DwmUpdateThumbnailProperties"); - m_pfnDwmUnregisterThumbnail = (DURT)GetProcAddress(m_hDwmApi, "DwmUnregisterThumbnail"); - m_pfnDwmSetIconicThumbnail = (DSIT)GetProcAddress(m_hDwmApi, "DwmSetIconicThumbnail"); - m_pfnDwmSetIconicLivePreviewBitmap = (DSILP)GetProcAddress(m_hDwmApi, "DwmSetIconicLivePreviewBitmap"); - } - - // additional uxtheme APIs (Vista+) - m_hUxTheme = Utils::loadSystemLibrary(L"\\uxtheme.dll"); - if (m_hUxTheme) { - m_pfnDrawThemeTextEx = (PDTTE)GetProcAddress(m_hUxTheme, "DrawThemeTextEx"); - m_pfnBeginBufferedPaint = (BBP)GetProcAddress(m_hUxTheme, "BeginBufferedPaint"); - m_pfnEndBufferedPaint = (EBP)GetProcAddress(m_hUxTheme, "EndBufferedPaint"); - m_pfnBufferedPaintInit = (BPI)GetProcAddress(m_hUxTheme, "BufferedPaintInit"); - m_pfnBufferedPaintUninit = (BPU)GetProcAddress(m_hUxTheme, "BufferedPaintUnInit"); - m_pfnBufferedPaintSetAlpha = (BPSA)GetProcAddress(m_hUxTheme, "BufferedPaintSetAlpha"); - m_haveBufferedPaint = (m_pfnBeginBufferedPaint != nullptr && m_pfnEndBufferedPaint != nullptr) ? true : false; - if (m_haveBufferedPaint) - m_pfnBufferedPaintInit(); - } - } - else m_haveBufferedPaint = false; - - switch (GetByte("default_ieview", -1)) { - case 1: - db_set_s(0, "SRMM", "Logger", "ieview"); - __fallthrough; - - case 0: - db_unset(0, SRMSGMOD_T, "default_ieview"); - } - - switch (GetByte("default_hpp", -1)) { - case 1: - db_set_s(0, "SRMM", "Logger", "hpp"); - __fallthrough; - - case 0: - db_unset(0, SRMSGMOD_T, "default_hpp"); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// hook subscriber function for incoming message typing events - -int CMimAPI::TypingMessage(WPARAM hContact, LPARAM nSecs) -{ - int foundWin = 0, preTyping = 0; - BOOL fShowOnClist = TRUE; - - auto *pDlg = Srmm_FindDialog(hContact); - MCONTACT hMeta = db_mc_getMeta(hContact); - if (hMeta) { - if (!pDlg) - pDlg = Srmm_FindDialog(hMeta); - hContact = hMeta; - } - - if (pDlg && g_plugin.getByte(SRMSGSET_SHOWTYPING, SRMSGDEFSET_SHOWTYPING)) - preTyping = pDlg->Typing(nSecs); - - if (pDlg && IsWindowVisible(pDlg->GetHwnd())) - foundWin = MessageWindowOpened(0, pDlg); - else - foundWin = 0; - - TContainerData *pContainer = nullptr; - if (pDlg) { - pContainer = pDlg->m_pContainer; - if (pContainer == nullptr) // should never happen - return 0; - } - - if (g_plugin.getByte(SRMSGSET_SHOWTYPINGCLIST, SRMSGDEFSET_SHOWTYPINGCLIST)) { - if (!pDlg && !g_plugin.getByte(SRMSGSET_SHOWTYPINGNOWINOPEN, 1)) - fShowOnClist = false; - if (pDlg && !g_plugin.getByte(SRMSGSET_SHOWTYPINGWINOPEN, 1)) - fShowOnClist = false; - } - else fShowOnClist = false; - - if ((!foundWin || !pContainer->cfg.flags.m_bNoSound) && preTyping != (nSecs != 0)) - Skin_PlaySound(nSecs ? "TNStart" : "TNStop"); - - if (g_plugin.bPopups) { - BOOL fShow = false; - int iMode = M.GetByte("MTN_PopupMode", 0); - - switch (iMode) { - case 0: - fShow = true; - break; - case 1: - if (!foundWin || !(pContainer && pContainer->m_hwndActive == pDlg->GetHwnd() && GetForegroundWindow() == pContainer->m_hwnd)) - fShow = true; - break; - case 2: - if (pDlg == nullptr) - fShow = true; - else { - if (g_plugin.bHideOnClose) { - TContainerData *pCont = pDlg->m_pContainer; - if (pCont && pCont->m_bHidden) - fShow = true; - } - } - break; - } - if (fShow) - TN_TypingMessage(hContact, nSecs); - } - - if (nSecs) { - wchar_t szTip[256]; - mir_snwprintf(szTip, TranslateT("%s is typing a message"), Clist_GetContactDisplayName(hContact)); - if (fShowOnClist && g_plugin.getByte("ShowTypingBalloon", 0)) - Clist_TrayNotifyW(nullptr, TranslateT("Typing notification"), szTip, NIIF_INFO, 1000 * 4); - - if (fShowOnClist) { - g_clistApi.pfnRemoveEvent(hContact, 1); - - CLISTEVENT cle = {}; - cle.hContact = hContact; - cle.hDbEvent = 1; - cle.flags = CLEF_ONLYAFEW | CLEF_UNICODE; - cle.hIcon = PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING]; - cle.pszService = MS_MSG_TYPINGMESSAGE; - cle.szTooltip.w = szTip; - g_clistApi.pfnAddEvent(&cle); - } - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// this is the global ack dispatcher.It handles both ACKTYPE_MESSAGE and ACKTYPE_AVATAR events -// for ACKTYPE_MESSAGE it searches the corresponding send job in the queue and, if found, dispatches -// it to the owners window -// -// ACKTYPE_AVATAR no longer handled here, because we have avs services now. - -int CMimAPI::ProtoAck(WPARAM, LPARAM lParam) -{ - ACKDATA *pAck = (ACKDATA*)lParam; - - if ((pAck != nullptr) && (pAck->type == ACKTYPE_MESSAGE)) { - int i = 0, iFound = SendQueue::NR_SENDJOBS; - SendJob *jobs = sendQueue->getJobByIndex(0); - MCONTACT hMeta = db_mc_getMeta(pAck->hContact); - for (int j = 0; j < SendQueue::NR_SENDJOBS; j++) { - SendJob &p = jobs[j]; - if (pAck->hProcess == (HANDLE)p.iSendId && pAck->hContact == p.hContact) { - CMsgDialog *dat = p.hOwnerWnd ? (CMsgDialog*)GetWindowLongPtr(p.hOwnerWnd, GWLP_USERDATA) : nullptr; - if (dat == nullptr) { - sendQueue->ackMessage(nullptr, (WPARAM)MAKELONG(j, i), lParam); - return 0; - } - if (dat->m_hContact == p.hContact || dat->m_hContact == hMeta) { - iFound = j; - break; - } - } - } - if (iFound == SendQueue::NR_SENDJOBS) // no matching send info found in the queue - SendLater::processAck(pAck); - else // try to find the process handle in the list of open send later jobs - SendMessage(jobs[iFound].hOwnerWnd, HM_EVENTSENT, (WPARAM)MAKELONG(iFound, i), lParam); - } - return 0; -} - -int CMimAPI::PrebuildContactMenu(WPARAM hContact, LPARAM) -{ - if (hContact == 0) - return 0; - - bool bEnabled = false; - char *szProto = Proto_GetBaseAccountName(hContact); - if (szProto) { - // leave this menu item hidden for chats - if (!Contact::IsGroupChat(hContact, szProto)) - if (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IMSEND) - bEnabled = true; - } - - Menu_ShowItem(PluginConfig.m_hMenuItem, bEnabled); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// this handler is called first in the message window chain - it will handle events for which a message window -// is already open. if not, it will do nothing and the 2nd handler(MessageEventAdded) will perform all -// the needed actions. -// -// this handler POSTs the event to the message window procedure - so it is fast and can exit quickly which will -// improve the overall responsiveness when receiving messages. - -int CMimAPI::DispatchNewEvent(WPARAM hContact, LPARAM hDbEvent) -{ - if (hContact) { - Utils::sendContactMessage(hContact, HM_DBEVENTADDED, hContact, hDbEvent); - - // we're in meta and an event belongs to a sub - MCONTACT hReal = db_event_getContact(hDbEvent); - if (hReal != hContact) - Utils::sendContactMessage(hReal, HM_DBEVENTADDED, hContact, hDbEvent); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Message event added is called when a new message is added to the database -// if no session is open for the contact, this function will determine if and how a new message -// session(tab) must be created. -// -// if a session is already created, it just does nothing and DispatchNewEvent() will take care. - -int CMimAPI::MessageEventAdded(WPARAM hContact, LPARAM hDbEvent) -{ - if (hContact == 0) - return 0; - - DBEVENTINFO dbei = {}; - db_event_get(hDbEvent, &dbei); - - auto *pDlg = Srmm_FindDialog(hContact); - if (pDlg == nullptr) - pDlg = Srmm_FindDialog(db_event_getContact(hDbEvent)); - - BOOL isCustomEvent = IsCustomEvent(dbei.eventType); - BOOL isShownCustomEvent = DbEventIsForMsgWindow(&dbei); - if (dbei.markedRead() || (isCustomEvent && !isShownCustomEvent)) - return 0; - - g_clistApi.pfnRemoveEvent(hContact, 1); - - bool bAutoPopup = g_plugin.bAutoPopup; - bool bAutoCreate = g_plugin.bAutoTabs; - bool bAutoContainer = g_plugin.bAutoContainer; - - if (pDlg) { - TContainerData *pTargetContainer = pDlg->m_pContainer; - if (pTargetContainer == nullptr || !g_plugin.bHideOnClose || IsWindowVisible(pTargetContainer->m_hwnd)) - return 0; - - WINDOWPLACEMENT wp = { 0 }; - wp.length = sizeof(wp); - GetWindowPlacement(pTargetContainer->m_hwnd, &wp); - - wchar_t szName[CONTAINER_NAMELEN + 1]; - GetContainerNameForContact(hContact, szName, CONTAINER_NAMELEN); - - if (bAutoPopup || bAutoCreate) { - if (bAutoPopup) { - if (wp.showCmd == SW_SHOWMAXIMIZED) - ShowWindow(pTargetContainer->m_hwnd, SW_SHOWMAXIMIZED); - else - ShowWindow(pTargetContainer->m_hwnd, SW_SHOWNOACTIVATE); - return 0; - } - - TContainerData *pContainer = FindContainerByName(szName); - if (pContainer != nullptr) { - if (bAutoContainer) { - ShowWindow(pTargetContainer->m_hwnd, SW_SHOWMINNOACTIVE); - return 0; - } - goto nowindowcreate; - } - else if (bAutoContainer) { - ShowWindow(pTargetContainer->m_hwnd, SW_SHOWMINNOACTIVE); - return 0; - } - } - } - else { - switch (dbei.eventType) { - case EVENTTYPE_AUTHREQUEST: - case EVENTTYPE_ADDED: - case EVENTTYPE_FILE: - return 0; - } - } - - if (!NEN::bNoSounds) - Skin_PlaySound("AlertMsg"); - - if (NEN::bNoAutoPopup) - goto nowindowcreate; - - PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_CREATECONTAINER, hContact, hDbEvent); - return 0; - -nowindowcreate: - // for tray support, we add the event to the tray menu. otherwise we send it back to - // the contact list for flashing - if (!(dbei.flags & DBEF_READ)) { - AddUnreadContact(hContact); - - wchar_t toolTip[256]; - mir_snwprintf(toolTip, TranslateT("Message from %s"), Clist_GetContactDisplayName(hContact)); - - CLISTEVENT cle = {}; - cle.hContact = hContact; - cle.hDbEvent = hDbEvent; - cle.flags = CLEF_UNICODE; - cle.hIcon = Skin_LoadIcon(SKINICON_EVENT_MESSAGE); - cle.pszService = MS_MSG_READMESSAGE; - cle.szTooltip.w = toolTip; - g_clistApi.pfnAddEvent(&cle); - } - return 0; -} - -CMimAPI M; +///////////////////////////////////////////////////////////////////////////////////////// +// Miranda NG: the free IM client for Microsoft* Windows* +// +// Copyright (C) 2012-23 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 +// +// wraps some parts of Miranda API +// Also, OS dependent stuff (visual styles api etc.) + +#include "stdafx.h" + +PDTTE CMimAPI::m_pfnDrawThemeTextEx = nullptr; +DEFICA CMimAPI::m_pfnDwmExtendFrameIntoClientArea = nullptr; +DICE CMimAPI::m_pfnDwmIsCompositionEnabled = nullptr; +DRT CMimAPI::m_pfnDwmRegisterThumbnail = nullptr; +BPI CMimAPI::m_pfnBufferedPaintInit = nullptr; +BPU CMimAPI::m_pfnBufferedPaintUninit = nullptr; +BBP CMimAPI::m_pfnBeginBufferedPaint = nullptr; +EBP CMimAPI::m_pfnEndBufferedPaint = nullptr; +BBW CMimAPI::m_pfnDwmBlurBehindWindow = nullptr; +DGC CMimAPI::m_pfnDwmGetColorizationColor = nullptr; +BPSA CMimAPI::m_pfnBufferedPaintSetAlpha = nullptr; +DWMIIB CMimAPI::m_pfnDwmInvalidateIconicBitmaps = nullptr; +DWMSWA CMimAPI::m_pfnDwmSetWindowAttribute = nullptr; +DWMUT CMimAPI::m_pfnDwmUpdateThumbnailProperties = nullptr; +DURT CMimAPI::m_pfnDwmUnregisterThumbnail = nullptr; +DSIT CMimAPI::m_pfnDwmSetIconicThumbnail = nullptr; +DSILP CMimAPI::m_pfnDwmSetIconicLivePreviewBitmap = nullptr; +bool CMimAPI::m_shutDown = 0; +wchar_t CMimAPI::m_userDir[] = L"\0"; + +bool CMimAPI::m_haveBufferedPaint = false; + +///////////////////////////////////////////////////////////////////////////////////////// + +int CMimAPI::FoldersPathChanged(WPARAM, LPARAM) +{ + return M.foldersPathChanged(); +} + +void CMimAPI::configureCustomFolders() +{ + m_hDataPath = FoldersRegisterCustomPathW(LPGEN("TabSRMM"), LPGEN("Data path"), const_cast<wchar_t *>(getDataPath())); + m_hSkinsPath = FoldersRegisterCustomPathW(LPGEN("Skins"), LPGEN("TabSRMM"), const_cast<wchar_t *>(getSkinPath())); + m_hAvatarsPath = FoldersRegisterCustomPathW(LPGEN("Avatars"), LPGEN("Saved TabSRMM avatars"), const_cast<wchar_t *>(getSavedAvatarPath())); + m_hChatLogsPath = FoldersRegisterCustomPathW(LPGEN("TabSRMM"), LPGEN("Group chat logs root"), const_cast<wchar_t *>(getChatLogPath())); + + if (m_hDataPath) + HookEvent(ME_FOLDERS_PATH_CHANGED, CMimAPI::FoldersPathChanged); + + foldersPathChanged(); +} + +INT_PTR CMimAPI::foldersPathChanged() +{ + wchar_t szTemp[MAX_PATH + 2]; + + if (m_hDataPath) { + szTemp[0] = 0; + FoldersGetCustomPathW(m_hDataPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getDataPath())); + wcsncpy_s(m_szProfilePath, szTemp, _TRUNCATE); + + szTemp[0] = 0; + FoldersGetCustomPathW(m_hSkinsPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getSkinPath())); + wcsncpy_s(m_szSkinsPath, (MAX_PATH - 1), szTemp, _TRUNCATE); + Utils::ensureTralingBackslash(m_szSkinsPath); + + szTemp[0] = 0; + FoldersGetCustomPathW(m_hAvatarsPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getSavedAvatarPath())); + wcsncpy_s(m_szSavedAvatarsPath, szTemp, _TRUNCATE); + + szTemp[0] = 0; + FoldersGetCustomPathW(m_hChatLogsPath, szTemp, MAX_PATH, const_cast<wchar_t *>(getChatLogPath())); + wcsncpy_s(m_szChatLogsPath, (MAX_PATH - 1), szTemp, _TRUNCATE); + Utils::ensureTralingBackslash(m_szChatLogsPath); + } + + CreateDirectoryTreeW(m_szProfilePath); + CreateDirectoryTreeW(m_szSkinsPath); + CreateDirectoryTreeW(m_szSavedAvatarsPath); + + Skin->extractSkinsAndLogo(true); + Skin->setupAeroSkins(); + return 0; +} + +const wchar_t* CMimAPI::getUserDir() +{ + if (m_userDir[0] == 0) { + if (ServiceExists(MS_FOLDERS_REGISTER_PATH)) + wcsncpy_s(m_userDir, L"%miranda_userdata%", _TRUNCATE); + else + wcsncpy_s(m_userDir, VARSW(L"%miranda_userdata%"), _TRUNCATE); + + Utils::ensureTralingBackslash(m_userDir); + } + return m_userDir; +} + +void CMimAPI::InitPaths() +{ + const wchar_t *szUserdataDir = getUserDir(); + + mir_snwprintf(m_szProfilePath, L"%stabSRMM", szUserdataDir); + if (ServiceExists(MS_FOLDERS_REGISTER_PATH)) { + wcsncpy_s(m_szChatLogsPath, L"%miranda_logpath%", _TRUNCATE); + wcsncpy_s(m_szSkinsPath, L"%miranda_path%\\Skins\\TabSRMM", _TRUNCATE); + } + else { + wcsncpy_s(m_szChatLogsPath, VARSW(L"%miranda_logpath%"), _TRUNCATE); + wcsncpy_s(m_szSkinsPath, VARSW(L"%miranda_path%\\Skins\\TabSRMM"), _TRUNCATE); + } + + Utils::ensureTralingBackslash(m_szChatLogsPath); + replaceStrW(g_Settings.pszLogDir, m_szChatLogsPath); + + Utils::ensureTralingBackslash(m_szSkinsPath); + + mir_snwprintf(m_szSavedAvatarsPath, L"%s\\Saved Contact Pictures", m_szProfilePath); +} + +bool CMimAPI::getAeroState() +{ + m_isAero = m_DwmActive = false; + if (IsWinVerVistaPlus()) { + BOOL result = FALSE; + m_DwmActive = (m_pfnDwmIsCompositionEnabled && (m_pfnDwmIsCompositionEnabled(&result) == S_OK) && result); + m_isAero = (CSkin::m_skinEnabled == false) && GetByte("useAero", 1) && CSkin::m_fAeroSkinsValid && m_DwmActive; + + } + m_isVsThemed = IsThemeActive() != 0; + return m_isAero; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Initialize various Win32 API functions which are not common to all versions of Windows. +// We have to work with functions pointers here. + +void CMimAPI::InitAPI() +{ + m_hUxTheme = nullptr; + m_hDwmApi = nullptr; + + // vista+ DWM API + if (IsWinVerVistaPlus()) { + m_hDwmApi = Utils::loadSystemLibrary(L"\\dwmapi.dll"); + if (m_hDwmApi) { + m_pfnDwmExtendFrameIntoClientArea = (DEFICA)GetProcAddress(m_hDwmApi, "DwmExtendFrameIntoClientArea"); + m_pfnDwmIsCompositionEnabled = (DICE)GetProcAddress(m_hDwmApi, "DwmIsCompositionEnabled"); + m_pfnDwmRegisterThumbnail = (DRT)GetProcAddress(m_hDwmApi, "DwmRegisterThumbnail"); + m_pfnDwmBlurBehindWindow = (BBW)GetProcAddress(m_hDwmApi, "DwmEnableBlurBehindWindow"); + m_pfnDwmGetColorizationColor = (DGC)GetProcAddress(m_hDwmApi, "DwmGetColorizationColor"); + m_pfnDwmInvalidateIconicBitmaps = (DWMIIB)GetProcAddress(m_hDwmApi, "DwmInvalidateIconicBitmaps"); + m_pfnDwmSetWindowAttribute = (DWMSWA)GetProcAddress(m_hDwmApi, "DwmSetWindowAttribute"); + m_pfnDwmUpdateThumbnailProperties = (DWMUT)GetProcAddress(m_hDwmApi, "DwmUpdateThumbnailProperties"); + m_pfnDwmUnregisterThumbnail = (DURT)GetProcAddress(m_hDwmApi, "DwmUnregisterThumbnail"); + m_pfnDwmSetIconicThumbnail = (DSIT)GetProcAddress(m_hDwmApi, "DwmSetIconicThumbnail"); + m_pfnDwmSetIconicLivePreviewBitmap = (DSILP)GetProcAddress(m_hDwmApi, "DwmSetIconicLivePreviewBitmap"); + } + + // additional uxtheme APIs (Vista+) + m_hUxTheme = Utils::loadSystemLibrary(L"\\uxtheme.dll"); + if (m_hUxTheme) { + m_pfnDrawThemeTextEx = (PDTTE)GetProcAddress(m_hUxTheme, "DrawThemeTextEx"); + m_pfnBeginBufferedPaint = (BBP)GetProcAddress(m_hUxTheme, "BeginBufferedPaint"); + m_pfnEndBufferedPaint = (EBP)GetProcAddress(m_hUxTheme, "EndBufferedPaint"); + m_pfnBufferedPaintInit = (BPI)GetProcAddress(m_hUxTheme, "BufferedPaintInit"); + m_pfnBufferedPaintUninit = (BPU)GetProcAddress(m_hUxTheme, "BufferedPaintUnInit"); + m_pfnBufferedPaintSetAlpha = (BPSA)GetProcAddress(m_hUxTheme, "BufferedPaintSetAlpha"); + m_haveBufferedPaint = (m_pfnBeginBufferedPaint != nullptr && m_pfnEndBufferedPaint != nullptr) ? true : false; + if (m_haveBufferedPaint) + m_pfnBufferedPaintInit(); + } + } + else m_haveBufferedPaint = false; + + switch (GetByte("default_ieview", -1)) { + case 1: + db_set_s(0, "SRMM", "Logger", "ieview"); + __fallthrough; + + case 0: + db_unset(0, SRMSGMOD_T, "default_ieview"); + } + + switch (GetByte("default_hpp", -1)) { + case 1: + db_set_s(0, "SRMM", "Logger", "hpp"); + __fallthrough; + + case 0: + db_unset(0, SRMSGMOD_T, "default_hpp"); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// hook subscriber function for incoming message typing events + +int CMimAPI::TypingMessage(WPARAM hContact, LPARAM nSecs) +{ + int foundWin = 0, preTyping = 0; + BOOL fShowOnClist = TRUE; + + auto *pDlg = Srmm_FindDialog(hContact); + MCONTACT hMeta = db_mc_getMeta(hContact); + if (hMeta) { + if (!pDlg) + pDlg = Srmm_FindDialog(hMeta); + hContact = hMeta; + } + + if (pDlg && g_plugin.getByte(SRMSGSET_SHOWTYPING, SRMSGDEFSET_SHOWTYPING)) + preTyping = pDlg->Typing(nSecs); + + if (pDlg && IsWindowVisible(pDlg->GetHwnd())) + foundWin = MessageWindowOpened(0, pDlg); + else + foundWin = 0; + + TContainerData *pContainer = nullptr; + if (pDlg) { + pContainer = pDlg->m_pContainer; + if (pContainer == nullptr) // should never happen + return 0; + } + + if (g_plugin.getByte(SRMSGSET_SHOWTYPINGCLIST, SRMSGDEFSET_SHOWTYPINGCLIST)) { + if (!pDlg && !g_plugin.getByte(SRMSGSET_SHOWTYPINGNOWINOPEN, 1)) + fShowOnClist = false; + if (pDlg && !g_plugin.getByte(SRMSGSET_SHOWTYPINGWINOPEN, 1)) + fShowOnClist = false; + } + else fShowOnClist = false; + + if ((!foundWin || !pContainer->cfg.flags.m_bNoSound) && preTyping != (nSecs != 0)) + Skin_PlaySound(nSecs ? "TNStart" : "TNStop"); + + if (g_plugin.bPopups) { + BOOL fShow = false; + int iMode = M.GetByte("MTN_PopupMode", 0); + + switch (iMode) { + case 0: + fShow = true; + break; + case 1: + if (!foundWin || !(pContainer && pContainer->m_hwndActive == pDlg->GetHwnd() && GetForegroundWindow() == pContainer->m_hwnd)) + fShow = true; + break; + case 2: + if (pDlg == nullptr) + fShow = true; + else { + if (g_plugin.bHideOnClose) { + TContainerData *pCont = pDlg->m_pContainer; + if (pCont && pCont->m_bHidden) + fShow = true; + } + } + break; + } + if (fShow) + TN_TypingMessage(hContact, nSecs); + } + + if (nSecs) { + wchar_t szTip[256]; + mir_snwprintf(szTip, TranslateT("%s is typing a message"), Clist_GetContactDisplayName(hContact)); + if (fShowOnClist && g_plugin.getByte("ShowTypingBalloon", 0)) + Clist_TrayNotifyW(nullptr, TranslateT("Typing notification"), szTip, NIIF_INFO, 1000 * 4); + + if (fShowOnClist) { + g_clistApi.pfnRemoveEvent(hContact, 1); + + CLISTEVENT cle = {}; + cle.hContact = hContact; + cle.hDbEvent = 1; + cle.flags = CLEF_ONLYAFEW | CLEF_UNICODE; + cle.hIcon = PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING]; + cle.pszService = MS_MSG_TYPINGMESSAGE; + cle.szTooltip.w = szTip; + g_clistApi.pfnAddEvent(&cle); + } + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// this is the global ack dispatcher.It handles both ACKTYPE_MESSAGE and ACKTYPE_AVATAR events +// for ACKTYPE_MESSAGE it searches the corresponding send job in the queue and, if found, dispatches +// it to the owners window +// +// ACKTYPE_AVATAR no longer handled here, because we have avs services now. + +int CMimAPI::ProtoAck(WPARAM, LPARAM lParam) +{ + ACKDATA *pAck = (ACKDATA*)lParam; + + if ((pAck != nullptr) && (pAck->type == ACKTYPE_MESSAGE)) { + int i = 0, iFound = SendQueue::NR_SENDJOBS; + SendJob *jobs = sendQueue->getJobByIndex(0); + MCONTACT hMeta = db_mc_getMeta(pAck->hContact); + for (int j = 0; j < SendQueue::NR_SENDJOBS; j++) { + SendJob &p = jobs[j]; + if (pAck->hProcess == (HANDLE)p.iSendId && pAck->hContact == p.hContact) { + CMsgDialog *dat = p.hOwnerWnd ? (CMsgDialog*)GetWindowLongPtr(p.hOwnerWnd, GWLP_USERDATA) : nullptr; + if (dat == nullptr) { + sendQueue->ackMessage(nullptr, (WPARAM)MAKELONG(j, i), lParam); + return 0; + } + if (dat->m_hContact == p.hContact || dat->m_hContact == hMeta) { + iFound = j; + break; + } + } + } + if (iFound == SendQueue::NR_SENDJOBS) // no matching send info found in the queue + SendLater::processAck(pAck); + else // try to find the process handle in the list of open send later jobs + SendMessage(jobs[iFound].hOwnerWnd, HM_EVENTSENT, (WPARAM)MAKELONG(iFound, i), lParam); + } + return 0; +} + +int CMimAPI::PrebuildContactMenu(WPARAM hContact, LPARAM) +{ + if (hContact == 0) + return 0; + + bool bEnabled = false; + char *szProto = Proto_GetBaseAccountName(hContact); + if (szProto) { + // leave this menu item hidden for chats + if (!Contact::IsGroupChat(hContact, szProto)) + if (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IMSEND) + bEnabled = true; + } + + Menu_ShowItem(PluginConfig.m_hMenuItem, bEnabled); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// this handler is called first in the message window chain - it will handle events for which a message window +// is already open. if not, it will do nothing and the 2nd handler(MessageEventAdded) will perform all +// the needed actions. +// +// this handler POSTs the event to the message window procedure - so it is fast and can exit quickly which will +// improve the overall responsiveness when receiving messages. + +int CMimAPI::DispatchNewEvent(WPARAM hContact, LPARAM hDbEvent) +{ + if (hContact) { + Utils::sendContactMessage(hContact, HM_DBEVENTADDED, hContact, hDbEvent); + + // we're in meta and an event belongs to a sub + MCONTACT hReal = db_event_getContact(hDbEvent); + if (hReal != hContact) + Utils::sendContactMessage(hReal, HM_DBEVENTADDED, hContact, hDbEvent); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Message event added is called when a new message is added to the database +// if no session is open for the contact, this function will determine if and how a new message +// session(tab) must be created. +// +// if a session is already created, it just does nothing and DispatchNewEvent() will take care. + +int CMimAPI::MessageEventAdded(WPARAM hContact, LPARAM hDbEvent) +{ + if (hContact == 0) + return 0; + + DBEVENTINFO dbei = {}; + db_event_get(hDbEvent, &dbei); + + auto *pDlg = Srmm_FindDialog(hContact); + if (pDlg == nullptr) + pDlg = Srmm_FindDialog(db_event_getContact(hDbEvent)); + + BOOL isCustomEvent = IsCustomEvent(dbei.eventType); + BOOL isShownCustomEvent = DbEventIsForMsgWindow(&dbei); + if (dbei.markedRead() || (isCustomEvent && !isShownCustomEvent)) + return 0; + + g_clistApi.pfnRemoveEvent(hContact, 1); + + bool bAutoPopup = g_plugin.bAutoPopup; + bool bAutoCreate = g_plugin.bAutoTabs; + bool bAutoContainer = g_plugin.bAutoContainer; + + if (pDlg) { + TContainerData *pTargetContainer = pDlg->m_pContainer; + if (pTargetContainer == nullptr || !g_plugin.bHideOnClose || IsWindowVisible(pTargetContainer->m_hwnd)) + return 0; + + WINDOWPLACEMENT wp = { 0 }; + wp.length = sizeof(wp); + GetWindowPlacement(pTargetContainer->m_hwnd, &wp); + + wchar_t szName[CONTAINER_NAMELEN + 1]; + GetContainerNameForContact(hContact, szName, CONTAINER_NAMELEN); + + if (bAutoPopup || bAutoCreate) { + if (bAutoPopup) { + if (wp.showCmd == SW_SHOWMAXIMIZED) + ShowWindow(pTargetContainer->m_hwnd, SW_SHOWMAXIMIZED); + else + ShowWindow(pTargetContainer->m_hwnd, SW_SHOWNOACTIVATE); + return 0; + } + + TContainerData *pContainer = FindContainerByName(szName); + if (pContainer != nullptr) { + if (bAutoContainer) { + ShowWindow(pTargetContainer->m_hwnd, SW_SHOWMINNOACTIVE); + return 0; + } + goto nowindowcreate; + } + else if (bAutoContainer) { + ShowWindow(pTargetContainer->m_hwnd, SW_SHOWMINNOACTIVE); + return 0; + } + } + } + else { + switch (dbei.eventType) { + case EVENTTYPE_AUTHREQUEST: + case EVENTTYPE_ADDED: + case EVENTTYPE_FILE: + return 0; + } + } + + if (!NEN::bNoSounds) + Skin_PlaySound("AlertMsg"); + + if (NEN::bNoAutoPopup) + goto nowindowcreate; + + PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_CREATECONTAINER, hContact, hDbEvent); + return 0; + +nowindowcreate: + // for tray support, we add the event to the tray menu. otherwise we send it back to + // the contact list for flashing + if (!(dbei.flags & DBEF_READ)) { + AddUnreadContact(hContact); + + wchar_t toolTip[256]; + mir_snwprintf(toolTip, TranslateT("Message from %s"), Clist_GetContactDisplayName(hContact)); + + CLISTEVENT cle = {}; + cle.hContact = hContact; + cle.hDbEvent = hDbEvent; + cle.flags = CLEF_UNICODE; + cle.hIcon = Skin_LoadIcon(SKINICON_EVENT_MESSAGE); + cle.pszService = MS_MSG_READMESSAGE; + cle.szTooltip.w = toolTip; + g_clistApi.pfnAddEvent(&cle); + } + return 0; +} + +CMimAPI M; diff --git a/plugins/TabSRMM/src/mim.h b/plugins/TabSRMM/src/mim.h index e3081a18d5..b9d094868a 100644 --- a/plugins/TabSRMM/src/mim.h +++ b/plugins/TabSRMM/src/mim.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/modplus.cpp b/plugins/TabSRMM/src/modplus.cpp index 7fbcb934dc..a8daa352ef 100644 --- a/plugins/TabSRMM/src/modplus.cpp +++ b/plugins/TabSRMM/src/modplus.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/msgdialog.cpp b/plugins/TabSRMM/src/msgdialog.cpp index dbf57a6c4e..9c8aa11318 100644 --- a/plugins/TabSRMM/src/msgdialog.cpp +++ b/plugins/TabSRMM/src/msgdialog.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/msgdlgother.cpp b/plugins/TabSRMM/src/msgdlgother.cpp index edaa0bacbc..db968ba08e 100644 --- a/plugins/TabSRMM/src/msgdlgother.cpp +++ b/plugins/TabSRMM/src/msgdlgother.cpp @@ -1,2858 +1,2858 @@ -///////////////////////////////////////////////////////////////////////////////////////// -// Miranda NG: the free IM client for Microsoft* Windows* -// -// Copyright (C) 2012-22 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 -// -// Helper functions for the message dialog. - -#include "stdafx.h" - -UINT_PTR CALLBACK OpenFileSubclass(HWND hwnd, UINT msg, WPARAM, LPARAM lParam); - -///////////////////////////////////////////////////////////////////////////////////////// -// show the balloon tooltip control. - -void CMsgDialog::ActivateTooltip(int iCtrlId, const wchar_t *pwszMessage) -{ - if (!IsIconic(m_pContainer->m_hwnd) && m_pContainer->m_hwndActive == m_hwnd) - m_pPanel.showTip(iCtrlId, pwszMessage); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::AddLog() -{ - if (g_plugin.bUseDividers) { - if (g_plugin.bDividersUsePopupConfig) { - if (!MessageWindowOpened(0, this)) - DM_AddDivider(); - } - else { - if (!IsActive()) - DM_AddDivider(); - else if (m_pContainer->m_hwndActive != m_hwnd) - DM_AddDivider(); - } - } - - CSrmmBaseDialog::AddLog(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::AdjustBottomAvatarDisplay() -{ - GetAvatarVisibility(); - - bool bInfoPanel = m_pPanel.isActive(); - HBITMAP hbm = (bInfoPanel && m_pContainer->cfg.avatarMode != 3) ? m_hOwnPic : (m_ace ? m_ace->hbmPic : PluginConfig.g_hbmUnknown); - if (hbm) { - if (m_dynaSplitter == 0 || m_iSplitterY == 0) - LoadSplitter(); - m_dynaSplitter = m_iSplitterY - DPISCALEY_S(34); - DM_RecalcPictureSize(); - Utils::showDlgControl(m_hwnd, IDC_CONTACTPIC, m_bShowAvatar ? SW_SHOW : SW_HIDE); - InvalidateRect(GetDlgItem(m_hwnd, IDC_CONTACTPIC), nullptr, TRUE); - } - else { - Utils::showDlgControl(m_hwnd, IDC_CONTACTPIC, m_bShowAvatar ? SW_SHOW : SW_HIDE); - m_pic.cy = m_pic.cx = DPISCALEY_S(60); - InvalidateRect(GetDlgItem(m_hwnd, IDC_CONTACTPIC), nullptr, TRUE); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// calculates avatar layouting, based on splitter position to find the optimal size -// for the avatar w/o disturbing the toolbar too much. - -void CMsgDialog::CalcDynamicAvatarSize(BITMAP *bminfo) -{ - if (m_bWasBackgroundCreate || m_pContainer->cfg.flags.m_bDeferredConfigure || m_pContainer->cfg.flags.m_bCreateMinimized || IsIconic(m_pContainer->m_hwnd)) - return; // at this stage, the layout is not yet ready... - - RECT rc; - GetClientRect(m_hwnd, &rc); - - BOOL bBottomToolBar = m_pContainer->cfg.flags.m_bBottomToolbar; - BOOL bToolBar = m_pContainer->cfg.flags.m_bHideToolbar ? 0 : 1; - int iSplitOffset = m_bIsAutosizingInput ? 1 : 0; - - double picAspect = (bminfo->bmWidth == 0 || bminfo->bmHeight == 0) ? 1.0 : (double)(bminfo->bmWidth / (double)bminfo->bmHeight); - double picProjectedWidth = (double)((m_dynaSplitter - ((bBottomToolBar && bToolBar) ? DPISCALEX_S(24) : 0) + ((m_bShowUIElements) ? DPISCALEX_S(28) : DPISCALEX_S(2)))) * picAspect; - - if ((rc.right - (int)picProjectedWidth) > (m_iButtonBarReallyNeeds) && !PluginConfig.m_bAlwaysFullToolbarWidth && bToolBar) - m_iRealAvatarHeight = m_dynaSplitter + 3 + (m_bShowUIElements ? DPISCALEY_S(28) : DPISCALEY_S(2)); - else - m_iRealAvatarHeight = m_dynaSplitter + DPISCALEY_S(6) + DPISCALEY_S(iSplitOffset); - - m_iRealAvatarHeight -= ((bBottomToolBar && bToolBar) ? DPISCALEY_S(22) : 0); - - if (PluginConfig.m_LimitStaticAvatarHeight > 0) - m_iRealAvatarHeight = min(m_iRealAvatarHeight, PluginConfig.m_LimitStaticAvatarHeight); - - if (M.GetByte(m_hContact, "dontscaleavatars", M.GetByte("dontscaleavatars", 0))) - m_iRealAvatarHeight = min(bminfo->bmHeight, m_iRealAvatarHeight); - - double aspect = (bminfo->bmHeight != 0) ? (double)m_iRealAvatarHeight / (double)bminfo->bmHeight : 1.0; - double newWidth = (double)bminfo->bmWidth * aspect; - if (newWidth > (double)(rc.right) * 0.8) - newWidth = (double)(rc.right) * 0.8; - m_pic.cy = m_iRealAvatarHeight + 2; - m_pic.cx = (int)newWidth + 2; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::CloseTab() -{ - int iTabs = TabCtrl_GetItemCount(m_hwndParent); - if (iTabs == 1) { - 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; - m_pContainer->QueryClientArea(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); - DestroyWindow(m_hwnd); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// calculate the minimum required client height for the given message -// window layout -// -// the container will use this in its WM_GETMINMAXINFO handler to set -// minimum tracking height. - -void CMsgDialog::DetermineMinHeight() -{ - RECT rc; - LONG height = (m_pPanel.isActive() ? m_pPanel.getHeight() + 2 : 0); - if (!m_pContainer->cfg.flags.m_bHideToolbar) - height += DPISCALEY_S(24); // toolbar - GetClientRect(m_message.GetHwnd(), &rc); - height += rc.bottom; // input area - height += 40; // min space for log area and some padding - - m_pContainer->m_uChildMinHeight = height; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// convert rich edit code to bbcode (if wanted). Otherwise, strip all RTF formatting -// tags and return plain text - -static wchar_t tszRtfBreaks[] = L" \\\n\r"; - -static void CreateColorMap(CMStringW &Text, int iCount, COLORREF *pSrc, int *pDst) -{ - const wchar_t *pszText = Text; - int iIndex = 1; - - static const wchar_t *lpszFmt = L"\\red%[^ \x5b\\]\\green%[^ \x5b\\]\\blue%[^ \x5b;];"; - wchar_t szRed[10], szGreen[10], szBlue[10]; - - const wchar_t *p1 = wcsstr(pszText, L"\\colortbl"); - if (!p1) - return; - - const wchar_t *pEnd = wcschr(p1, '}'); - - const wchar_t *p2 = wcsstr(p1, L"\\red"); - - for (int i = 0; i < iCount; i++) - pDst[i] = -1; - - while (p2 && p2 < pEnd) { - if (swscanf(p2, lpszFmt, &szRed, &szGreen, &szBlue) > 0) { - for (int i = 0; i < iCount; i++) { - if (pSrc[i] == RGB(_wtoi(szRed), _wtoi(szGreen), _wtoi(szBlue))) - pDst[i] = iIndex; - } - } - iIndex++; - p1 = p2; - p1++; - - p2 = wcsstr(p1, L"\\red"); - } -} - -static int RtfColorToIndex(int iNumColors, int *pIndex, int iCol) -{ - for (int i = 0; i < iNumColors; i++) - if (pIndex[i] == iCol) - return i; - - return -1; -} - -BOOL CMsgDialog::DoRtfToTags(CMStringW &pszText) const -{ - if (pszText.IsEmpty()) - return FALSE; - - // used to filter out attributes which are already set for the default message input area font - auto &lf = m_pContainer->m_theme.logFonts[MSGFONTID_MESSAGEAREA]; - - // create an index of colors in the module and map them to - // corresponding colors in the RTF color table - int iNumColors = Utils::rtf_clrs.getCount(); - int *pIndex = (int *)_alloca(iNumColors * sizeof(int)); - COLORREF *pColors = (COLORREF *)_alloca(iNumColors * sizeof(COLORREF)); - for (int i = 0; i < iNumColors; i++) - pColors[i] = Utils::rtf_clrs[i].clr; - CreateColorMap(pszText, iNumColors, pColors, pIndex); - - // scan the file for rtf commands and remove or parse them - int idx = pszText.Find(L"\\pard"); - if (idx == -1) { - if ((idx = pszText.Find(L"\\ltrpar")) == -1) - return FALSE; - idx += 7; - } - else idx += 5; - - MODULEINFO *mi = (isChat()) ? m_si->pMI : nullptr; - - bool bInsideColor = false, bInsideUl = false; - CMStringW res; - - // iterate through all characters, if rtf control character found then take action - for (const wchar_t *p = pszText.GetString() + idx; *p;) { - switch (*p) { - case '\\': - if (p[1] == '\\' || p[1] == '{' || p[1] == '}') { // escaped characters - res.AppendChar(p[1]); - p += 2; break; - } - if (p[1] == '~') { // non-breaking space - res.AppendChar(0xA0); - p += 2; break; - } - - if (!wcsncmp(p, L"\\cf", 3)) { // foreground color - int iCol = _wtoi(p + 3); - int iInd = RtfColorToIndex(iNumColors, pIndex, iCol); - - if (iCol > 0) { - if (isChat()) { - if (mi && mi->bColor) { - if (iInd >= 0) { - if (!(res.IsEmpty() && m_pContainer->m_theme.fontColors[MSGFONTID_MESSAGEAREA] == pColors[iInd])) - res.AppendFormat(L"%%c%u", iInd); - } - else if (!res.IsEmpty()) - res.Append(L"%%C"); - } - } - else res.AppendFormat((iInd >= 0) ? (bInsideColor ? L"[/color][color=%s]" : L"[color=%s]") : (bInsideColor ? L"[/color]" : L""), Utils::rtf_clrs[iInd].szName); - } - - bInsideColor = iInd >= 0; - } - else if (!wcsncmp(p, L"\\highlight", 10)) { // background color - if (isChat()) { - if (mi && mi->bBkgColor) { - int iInd = RtfColorToIndex(iNumColors, pIndex, _wtoi(p + 10)); - if (iInd >= 0) { - // if the entry field is empty & the color passed is the back color, skip it - if (!(res.IsEmpty() && m_pContainer->m_theme.inputbg == pColors[iInd])) - res.AppendFormat(L"%%f%u", iInd); - } - else if (!res.IsEmpty()) - res.AppendFormat(L"%%F"); - } - } - } - else if (!wcsncmp(p, L"\\line", 5)) { // soft line break; - res.AppendChar('\n'); - } - else if (!wcsncmp(p, L"\\endash", 7)) { - res.AppendChar(0x2013); - } - else if (!wcsncmp(p, L"\\emdash", 7)) { - res.AppendChar(0x2014); - } - else if (!wcsncmp(p, L"\\bullet", 7)) { - res.AppendChar(0x2022); - } - else if (!wcsncmp(p, L"\\ldblquote", 10)) { - res.AppendChar(0x201C); - } - else if (!wcsncmp(p, L"\\rdblquote", 10)) { - res.AppendChar(0x201D); - } - else if (!wcsncmp(p, L"\\lquote", 7)) { - res.AppendChar(0x2018); - } - else if (!wcsncmp(p, L"\\rquote", 7)) { - res.AppendChar(0x2019); - } - else if (!wcsncmp(p, L"\\b", 2)) { //bold - if (isChat()) { - if (mi && mi->bBold) - res.Append((p[2] != '0') ? L"%b" : L"%B"); - } - else { - if (!(lf.lfWeight == FW_BOLD)) // only allow bold if the font itself isn't a bold one, otherwise just strip it.. - if (m_SendFormat) - res.Append((p[2] != '0') ? L"[b]" : L"[/b]"); - } - } - else if (!wcsncmp(p, L"\\i", 2)) { // italics - if (isChat()) { - if (mi && mi->bItalics) - res.Append((p[2] != '0') ? L"%i" : L"%I"); - } - else { - if (!lf.lfItalic && m_SendFormat) - res.Append((p[2] != '0') ? L"[i]" : L"[/i]"); - } - } - else if (!wcsncmp(p, L"\\strike", 7)) { // strike-out - if (!lf.lfStrikeOut && m_SendFormat) - res.Append((p[7] != '0') ? L"[s]" : L"[/s]"); - } - else if (!wcsncmp(p, L"\\ul", 3)) { // underlined - if (isChat()) { - if (mi && mi->bUnderline) - res.Append((p[3] != '0') ? L"%u" : L"%U"); - } - else { - if (!lf.lfUnderline && m_SendFormat) { - if (p[3] == 0 || wcschr(tszRtfBreaks, p[3])) { - res.Append(L"[u]"); - bInsideUl = true; - } - else if (!wcsncmp(p + 3, L"none", 4)) { - if (bInsideUl) - res.Append(L"[/u]"); - bInsideUl = false; - } - } - } - } - else if (!wcsncmp(p, L"\\tab", 4)) { // tab - res.AppendChar('\t'); - } - else if (p[1] == '\'') { // special character - if (p[2] != ' ' && p[2] != '\\') { - wchar_t tmp[10]; - - if (p[3] != ' ' && p[3] != '\\') { - wcsncpy(tmp, p + 2, 3); - tmp[3] = 0; - } - else { - wcsncpy(tmp, p + 2, 2); - tmp[2] = 0; - } - - // convert string containing char in hex format to int. - wchar_t *stoppedHere; - res.AppendChar(wcstol(tmp, &stoppedHere, 16)); - } - } - - p++; // skip initial slash - p += wcscspn(p, tszRtfBreaks); - if (*p == ' ') - p++; - break; - - case '{': // other RTF control characters - case '}': - p++; - break; - - case '%': // double % for stupid chat engine - if (isChat()) - res.Append(L"%%"); - else - res.AppendChar(*p); - p++; - break; - - default: // other text that should not be touched - res.AppendChar(*p++); - break; - } - } - - if (bInsideColor && !isChat()) - res.Append(L"[/color]"); - if (bInsideUl) - res.Append(L"[/u]"); - - pszText = res; - return TRUE; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::EnableSendButton(bool bMode) const -{ - SendDlgItemMessage(m_hwnd, IDOK, BUTTONSETASNORMAL, bMode, 0); - SendDlgItemMessage(m_hwnd, IDC_PIC, BUTTONSETASNORMAL, m_bEditNotesActive ? TRUE : (!bMode && m_iOpenJobs == 0) ? TRUE : FALSE, 0); - - HWND hwndOK = GetDlgItem(GetParent(GetParent(m_hwnd)), IDOK); - if (IsWindow(hwndOK)) - SendMessage(hwndOK, BUTTONSETASNORMAL, bMode, 0); -} - -void CMsgDialog::EnableSending(bool bMode) const -{ - m_message.SendMsg(EM_SETREADONLY, !bMode, 0); - Utils::enableDlgControl(m_hwnd, IDC_CLIST, bMode); - EnableSendButton(bMode); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::FindFirstEvent() -{ - int historyMode = g_plugin.getByte(m_hContact, SRMSGSET_LOADHISTORY, -1); - if (historyMode == -1) - historyMode = (int)g_plugin.getByte(SRMSGSET_LOADHISTORY, SRMSGDEFSET_LOADHISTORY); - - m_hDbEventFirst = db_event_firstUnread(m_hContact); - - if (m_bActualHistory) - historyMode = LOADHISTORY_COUNT; - - DBEVENTINFO dbei = {}; - DB::ECPTR pCursor(DB::EventsRev(m_hContact, m_hDbEventFirst)); - - switch (historyMode) { - case LOADHISTORY_COUNT: - int i; - - // ability to load only current session's history - if (m_bActualHistory) - i = m_cache->getSessionMsgCount(); - else - i = g_plugin.getWord(SRMSGSET_LOADCOUNT, SRMSGDEFSET_LOADCOUNT); - - for (; i > 0; i--) { - MEVENT hPrevEvent = pCursor.FetchNext(); - if (hPrevEvent == 0) - break; - - dbei.cbBlob = 0; - m_hDbEventFirst = hPrevEvent; - db_event_get(m_hDbEventFirst, &dbei); - if (!DbEventIsShown(&dbei)) - i++; - } - break; - - case LOADHISTORY_TIME: - if (m_hDbEventFirst == 0) - dbei.timestamp = time(0); - else - db_event_get(m_hDbEventFirst, &dbei); - - uint32_t firstTime = dbei.timestamp - 60 * g_plugin.getWord(SRMSGSET_LOADTIME, SRMSGDEFSET_LOADTIME); - - while (MEVENT hPrevEvent = pCursor.FetchNext()) { - dbei.cbBlob = 0; - db_event_get(hPrevEvent, &dbei); - if (dbei.timestamp < firstTime) - break; - m_hDbEventFirst = hPrevEvent; - } - break; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// returns != 0 when one of the installed keyboard layouts belongs to an rtl language -// used to find out whether we need to configure the message input box for bidirectional mode - -int CMsgDialog::FindRTLLocale() -{ - int result = 0; - - if (m_iHaveRTLLang == 0) { - HKL layouts[20]; - memset(layouts, 0, sizeof(layouts)); - GetKeyboardLayoutList(20, layouts); - for (int i = 0; i < 20 && layouts[i]; i++) { - uint16_t wCtype2[5]; - LCID lcid = MAKELCID(LOWORD(layouts[i]), 0); - GetStringTypeA(lcid, CT_CTYPE2, "���", 3, wCtype2); - if (wCtype2[0] == C2_RIGHTTOLEFT || wCtype2[1] == C2_RIGHTTOLEFT || wCtype2[2] == C2_RIGHTTOLEFT) - result = 1; - } - m_iHaveRTLLang = (result ? 1 : -1); - } - else result = m_iHaveRTLLang == 1 ? 1 : 0; - - return result; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::FlashOnClist(MEVENT hEvent, DBEVENTINFO *dbei) -{ - m_dwTickLastEvent = GetTickCount(); - - if ((GetForegroundWindow() != m_pContainer->m_hwnd || m_pContainer->m_hwndActive != m_hwnd) && !(dbei->flags & DBEF_SENT) && dbei->eventType == EVENTTYPE_MESSAGE) { - m_dwUnread++; - AddUnreadContact(m_hContact); - } - - if (hEvent == 0) - return; - - if (!g_plugin.bFlashOnClist) - return; - - if ((GetForegroundWindow() != m_pContainer->m_hwnd || m_pContainer->m_hwndActive != m_hwnd) && !(dbei->flags & DBEF_SENT) && dbei->eventType == EVENTTYPE_MESSAGE && !m_bFlashClist) { - CLISTEVENT cle = {}; - cle.hContact = m_hContact; - cle.hDbEvent = hEvent; - cle.hIcon = Skin_LoadIcon(SKINICON_EVENT_MESSAGE); - cle.pszService = MS_MSG_READMESSAGE; - g_clistApi.pfnAddEvent(&cle); - - m_bFlashClist = true; - m_hFlashingEvent = hEvent; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// flash a tab icon if mode = true, otherwise restore default icon -// store flashing state into bState - -void CMsgDialog::FlashTab(bool bInvertMode) -{ - if (bInvertMode) - m_bTabFlash = !m_bTabFlash; - - TCITEM item = {}; - item.mask = TCIF_IMAGE; - TabCtrl_SetItem(m_hwndParent, m_iTabID, &item); - if (m_pContainer->cfg.flags.m_bSideBar) - m_pContainer->m_pSideBar->updateSession(this); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// this translates formatting tags into rtf sequences... -// flags: loword = words only for simple * /_ formatting -// hiword = bbcode support (strip bbcodes if 0) - -static wchar_t *w_bbcodes_begin[] = { L"[b]", L"[i]", L"[u]", L"[s]", L"[color=" }; -static wchar_t *w_bbcodes_end[] = { L"[/b]", L"[/i]", L"[/u]", L"[/s]", L"[/color]" }; - -static wchar_t *formatting_strings_begin[] = { L"b1 ", L"i1 ", L"u1 ", L"s1 ", L"c1 " }; -static wchar_t *formatting_strings_end[] = { L"b0 ", L"i0 ", L"u0 ", L"s0 ", L"c0 " }; - -void CMsgDialog::FormatRaw(CMStringW &msg, int flags, bool isSent) -{ - bool clr_was_added = false; - int beginmark = 0, endmark = 0, tempmark = 0; - int i, endindex; - - if (m_dwFlags & MWF_LOG_BBCODE) { - beginmark = 0; - while (true) { - for (i = 0; i < _countof(w_bbcodes_begin); i++) - if ((tempmark = msg.Find(w_bbcodes_begin[i], 0)) != -1) - break; - - if (i >= _countof(w_bbcodes_begin)) - break; - - beginmark = tempmark; - endindex = i; - endmark = msg.Find(w_bbcodes_end[i], beginmark); - if (endindex == 4) { // color - int closing = msg.Find(L"]", beginmark); - bool was_added = false; - - if (closing == -1) { // must be an invalid [color=] tag w/o closing bracket - msg.SetAt(beginmark, ' '); - continue; - } - - CMStringW colorname = msg.Mid(beginmark + 7, closing - beginmark - 7); -search_again: - bool clr_found = false; - for (int ii = 0; ii < Utils::rtf_clrs.getCount(); ii++) { - auto &rtfc = Utils::rtf_clrs[ii]; - if (!wcsicmp(colorname, rtfc.szName)) { - closing = beginmark + 7 + (int)mir_wstrlen(rtfc.szName); - if (endmark != -1) { - msg.Delete(endmark, 8); - msg.Insert(endmark, L"c0 "); - } - msg.Delete(beginmark, closing - beginmark + 1); - - wchar_t szTemp[5]; - msg.Insert(beginmark, L"cxxx "); - mir_snwprintf(szTemp, L"%02d", MSGDLGFONTCOUNT + 13 + ii); - msg.SetAt(beginmark + 3, szTemp[0]); - msg.SetAt(beginmark + 4, szTemp[1]); - clr_found = true; - if (was_added) { - wchar_t wszTemp[100]; - mir_snwprintf(wszTemp, L"##col##%06u:%04u", endmark - closing, ii); - wszTemp[99] = 0; - msg.Insert(beginmark, wszTemp); - } - break; - } - } - if (!clr_found) { - int c_closing = colorname.Find(L"]"); - if (c_closing == -1) - c_closing = colorname.GetLength(); - const wchar_t *wszColname = colorname.c_str(); - if (endmark != -1 && c_closing > 2 && c_closing <= 6 && iswalnum(colorname[0]) && iswalnum(colorname[c_closing - 1])) { - Utils::RTF_ColorAdd(wszColname); - if (!was_added) { - clr_was_added = was_added = true; - goto search_again; - } - else goto invalid_code; - } - else { -invalid_code: - if (endmark != -1) - msg.Delete(endmark, 8); - if (closing != -1 && closing < endmark) - msg.Delete(beginmark, (closing - beginmark) + 1); - else - msg.SetAt(beginmark, ' '); - } - } - continue; - } - - if (endmark != -1) { - msg.Delete(endmark, 4); - msg.Insert(endmark, formatting_strings_end[i]); - } - msg.Delete(beginmark, 3); - msg.Insert(beginmark, L" "); - msg.Insert(beginmark, formatting_strings_begin[i]); - } - } - - if ((m_dwFlags & MWF_LOG_TEXTFORMAT) && msg.Find(L"://") == -1) { - while ((beginmark = msg.Find(L"*/_", beginmark)) != -1) { - wchar_t endmarker = msg[beginmark]; - if (LOWORD(flags)) { - if (beginmark > 0 && !iswspace(msg[beginmark - 1]) && !iswpunct(msg[beginmark - 1])) { - beginmark++; - continue; - } - - // search a corresponding endmarker which fulfills the criteria - INT_PTR mark = beginmark + 1; - while ((endmark = msg.Find(endmarker, mark)) != -1) { - if (iswpunct(msg[endmark + 1]) || iswspace(msg[endmark + 1]) || msg[endmark + 1] == 0 || wcschr(L"*/_", msg[endmark + 1]) != nullptr) - goto ok; - mark = endmark + 1; - } - break; - } - else { - if ((endmark = msg.Find(endmarker, beginmark + 1)) == -1) - break; - } -ok: - if ((endmark - beginmark) < 2) { - beginmark++; - continue; - } - - int index = 0; - switch (endmarker) { - case '*': - index = 0; - break; - case '/': - index = 1; - break; - case '_': - index = 2; - break; - } - - // check if the code enclosed by simple formatting tags is a valid smiley code and skip formatting if - // it really is one. - if (PluginConfig.g_SmileyAddAvail && (endmark > (beginmark + 1))) { - CMStringW smcode = msg.Mid(beginmark, (endmark - beginmark) + 1); - - SMADD_BATCHPARSE2 smbp = {}; - smbp.cbSize = sizeof(smbp); - smbp.Protocolname = m_cache->getActiveProto(); - smbp.flag = SAFL_TCHAR | SAFL_PATH | (isSent ? SAFL_OUTGOING : 0); - smbp.str = (wchar_t*)smcode.c_str(); - smbp.hContact = m_hContact; - - SMADD_BATCHPARSERES *smbpr = (SMADD_BATCHPARSERES *)CallService(MS_SMILEYADD_BATCHPARSE, 0, (LPARAM)&smbp); - if (smbpr) { - CallService(MS_SMILEYADD_BATCHFREE, 0, (LPARAM)smbpr); - beginmark = endmark + 1; - continue; - } - } - msg.Delete(endmark, 1); - msg.Insert(endmark, formatting_strings_end[index]); - msg.Delete(beginmark, 1); - msg.Insert(beginmark, formatting_strings_begin[index]); - } - } - - m_bClrAdded = clr_was_added; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// format the title bar string for IM chat sessions using placeholders. -// the caller must mir_free() the returned string - -static wchar_t* Trunc500(wchar_t *str) -{ - if (mir_wstrlen(str) > 500) - str[500] = 0; - return str; -} - -bool CMsgDialog::FormatTitleBar(const wchar_t *szFormat, CMStringW &dest) -{ - for (const wchar_t *src = szFormat; *src; src++) { - if (*src != '%') { - dest.AppendChar(*src); - continue; - } - - switch (*++src) { - case 'n': - dest.Append(m_cache->getNick()); - break; - - case 'p': - case 'a': - dest.Append(m_cache->getRealAccount()); - break; - - case 's': - dest.Append(m_wszStatus); - break; - - case 'u': - dest.Append(m_cache->getUIN()); - break; - - case 'c': - dest.Append(!mir_wstrcmp(m_pContainer->m_wszName, L"default") ? TranslateT("Default container") : m_pContainer->m_wszName); - break; - - case 'o': - { - const char *szProto = m_cache->getActiveProto(); - if (szProto) - dest.Append(_A2T(szProto)); - } - break; - - case 'x': - { - uint8_t xStatus = m_cache->getXStatusId(); - if (m_wStatus != ID_STATUS_OFFLINE && xStatus > 0 && xStatus <= 31) { - ptrW szXStatus(db_get_wsa(m_hContact, m_szProto, "XStatusName")); - dest.Append((szXStatus != nullptr) ? Trunc500(szXStatus) : xStatusDescr[xStatus - 1]); - } - } - break; - - case 'm': - { - uint8_t xStatus = m_cache->getXStatusId(); - if (m_wStatus != ID_STATUS_OFFLINE && xStatus > 0 && xStatus <= 31) { - ptrW szXStatus(db_get_wsa(m_hContact, m_szProto, "XStatusName")); - dest.Append((szXStatus != nullptr) ? Trunc500(szXStatus) : xStatusDescr[xStatus - 1]); - } - else dest.Append(m_wszStatus[0] ? m_wszStatus : L"(undef)"); - } - break; - - // status message (%T will skip the "No status message" for empty messages) - case 't': - case 'T': - { - ptrW tszStatus(m_cache->getNormalizedStatusMsg(m_cache->getStatusMsg(), true)); - if (tszStatus) - dest.Append(tszStatus); - else if (*src == 't') - dest.Append(TranslateT("No status message")); - } - break; - - case 'g': - { - ptrW tszGroup(Clist_GetGroup(m_hContact)); - if (tszGroup != nullptr) - dest.Append(tszGroup); - } - break; - - case 0: // wrongly formed format string - return true; - } - } - - return true; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// retrieve the visiblity of the avatar window, depending on the global setting -// and local mode - -bool CMsgDialog::GetAvatarVisibility() -{ - uint8_t bAvatarMode = m_pContainer->cfg.avatarMode; - uint8_t bOwnAvatarMode = m_pContainer->cfg.ownAvatarMode; - char hideOverride = (char)M.GetByte(m_hContact, "hideavatar", -1); - - // infopanel visible, consider own avatar display - m_bShowAvatar = false; - if (m_si) - bAvatarMode = 1; - - if (m_pPanel.isActive() && bAvatarMode != 3) { - if (!bOwnAvatarMode) { - m_bShowAvatar = (m_hOwnPic && m_hOwnPic != PluginConfig.g_hbmUnknown); - if (!m_hwndContactPic) - m_hwndContactPic = CreateWindowEx(WS_EX_TOPMOST, AVATAR_CONTROL_CLASS, L"", WS_VISIBLE | WS_CHILD, 1, 1, 1, 1, GetDlgItem(m_hwnd, IDC_CONTACTPIC), (HMENU)nullptr, nullptr, nullptr); - } - - switch (bAvatarMode) { - case 2: - m_bShowInfoAvatar = false; - break; - case 0: - m_bShowInfoAvatar = true; - case 1: - HBITMAP hbm = ((m_ace && !(m_ace->dwFlags & AVS_HIDEONCLIST)) ? m_ace->hbmPic : nullptr); - if (hbm == nullptr && !bAvatarMode) { - m_bShowInfoAvatar = false; - break; - } - - if (!m_hwndPanelPic) { - m_hwndPanelPic = CreateWindowEx(WS_EX_TOPMOST, AVATAR_CONTROL_CLASS, L"", WS_VISIBLE | WS_CHILD, 1, 1, 1, 1, m_hwndPanelPicParent, (HMENU)7000, nullptr, nullptr); - if (m_hwndPanelPic) - SendMessage(m_hwndPanelPic, AVATAR_SETAEROCOMPATDRAWING, 0, TRUE); - } - - if (bAvatarMode != 0) - m_bShowInfoAvatar = (hbm && hbm != PluginConfig.g_hbmUnknown); - break; - } - - if (m_bShowInfoAvatar) - m_bShowInfoAvatar = hideOverride == 0 ? false : m_bShowInfoAvatar; - else - m_bShowInfoAvatar = hideOverride == 1 ? true : m_bShowInfoAvatar; - - Utils::setAvatarContact(m_hwndPanelPic, m_hContact); - SendMessage(m_hwndContactPic, AVATAR_SETPROTOCOL, 0, (LPARAM)m_cache->getActiveProto()); - } - else { - m_bShowInfoAvatar = false; - - switch (bAvatarMode) { - case 0: // globally on - m_bShowAvatar = true; - LBL_Check: - if (!m_hwndContactPic) - m_hwndContactPic = CreateWindowEx(WS_EX_TOPMOST, AVATAR_CONTROL_CLASS, L"", WS_VISIBLE | WS_CHILD, 1, 1, 1, 1, GetDlgItem(m_hwnd, IDC_CONTACTPIC), (HMENU)nullptr, nullptr, nullptr); - break; - case 2: // globally OFF - m_bShowAvatar = false; - break; - case 3: // on, if present - case 1: - HBITMAP hbm = (m_ace && !(m_ace->dwFlags & AVS_HIDEONCLIST)) ? m_ace->hbmPic : nullptr; - m_bShowAvatar = (hbm && hbm != PluginConfig.g_hbmUnknown); - goto LBL_Check; - } - - if (m_bShowAvatar) - m_bShowAvatar = hideOverride == 0 ? 0 : m_bShowAvatar; - else - m_bShowAvatar = hideOverride == 1 ? 1 : m_bShowAvatar; - - // reloads avatars - if (m_hwndPanelPic) { // shows contact or user picture, depending on panel visibility - SendMessage(m_hwndContactPic, AVATAR_SETPROTOCOL, 0, (LPARAM)m_cache->getActiveProto()); - Utils::setAvatarContact(m_hwndPanelPic, m_hContact); - } - else Utils::setAvatarContact(m_hwndContactPic, m_hContact); - } - return m_bShowAvatar; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::GetClientIcon() -{ - if (m_hClientIcon) - DestroyIcon(m_hClientIcon); - - m_hClientIcon = nullptr; - if (ServiceExists(MS_FP_GETCLIENTICONT)) { - ptrW tszMirver(db_get_wsa(m_cache->getActiveContact(), m_cache->getActiveProto(), "MirVer")); - if (tszMirver) - m_hClientIcon = Finger_GetClientIcon(tszMirver, 1); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -HICON CMsgDialog::GetMyContactIcon(const CMOption<bool> *opt) -{ - int bUseMeta = (opt == nullptr) ? false : M.GetByte(opt->GetDBSettingName(), mir_strcmp(opt->GetDBSettingName(), "MetaiconTab") == 0); - if (bUseMeta) - return Skin_LoadProtoIcon(m_cache->getProto(), m_cache->getStatus()); - return Skin_LoadProtoIcon(m_cache->getActiveProto(), m_cache->getActiveStatus()); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::GetMyNick() -{ - ptrW tszNick(Contact::GetInfo(CNF_CUSTOMNICK, 0, m_cache->getActiveProto())); - if (tszNick == nullptr) - tszNick = Contact::GetInfo(CNF_NICK, 0, m_cache->getActiveProto()); - if (tszNick != nullptr) { - if (mir_wstrlen(tszNick) == 0 || !mir_wstrcmp(tszNick, TranslateT("'(Unknown contact)'"))) - wcsncpy_s(m_wszMyNickname, (m_myUin[0] ? m_myUin : TranslateT("'(Unknown contact)'")), _TRUNCATE); - else - wcsncpy_s(m_wszMyNickname, tszNick, _TRUNCATE); - } - else wcsncpy_s(m_wszMyNickname, L"<undef>", _TRUNCATE); // same here -} - -///////////////////////////////////////////////////////////////////////////////////////// -// retrieve both buddys and my own UIN for a message session and store them in the message -// window *dat respects metacontacts and uses the current protocol if the contact is a MC - -void CMsgDialog::GetMYUIN() -{ - ptrW uid(Contact::GetInfo(CNF_DISPLAYUID, 0, m_cache->getActiveProto())); - if (uid != nullptr) - wcsncpy_s(m_myUin, uid, _TRUNCATE); - else - m_myUin[0] = 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// returns the status of Send button - -LRESULT CMsgDialog::GetSendButtonState() -{ - return m_btnOk.SendMsg(BUTTONGETSTATEID, TRUE, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// reads send format and configures the toolbar buttons -// if mode == 0, int only configures the buttons and does not change send format - -void CMsgDialog::GetSendFormat() -{ - m_SendFormat = M.GetDword(m_hContact, "sendformat", g_plugin.bSendFormat); - if (m_SendFormat == -1) // per contact override to disable it.. - m_SendFormat = 0; - else if (m_SendFormat == 0) - m_SendFormat = g_plugin.bSendFormat; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -HICON CMsgDialog::GetXStatusIcon() const -{ - uint8_t xStatus = m_cache->getXStatusId(); - if (xStatus == 0) - return nullptr; - - if (!ProtoServiceExists(m_cache->getActiveProto(), PS_GETCUSTOMSTATUSICON)) - return nullptr; - - return (HICON)(CallProtoService(m_cache->getActiveProto(), PS_GETCUSTOMSTATUSICON, xStatus, 0)); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// paste contents of the clipboard into the message input area and send it immediately - -void CMsgDialog::HandlePasteAndSend() -{ - // is feature disabled? - if (!g_plugin.bPasteAndSend) { - ActivateTooltip(IDC_SRMM_MESSAGE, TranslateT("The 'paste and send' feature is disabled. You can enable it on the 'General' options page in the 'Sending messages' section")); - return; - } - - m_message.SendMsg(EM_PASTESPECIAL, CF_UNICODETEXT, 0); - if (GetWindowTextLength(m_message.GetHwnd()) > 0) - SendMessage(m_hwnd, WM_COMMAND, IDOK, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// convert the avatar bitmap to icon format so that it can be used on the task bar -// tries to keep correct aspect ratio of the avatar image -// -// @param dat: _MessageWindowData* pointer to the window data -// @return HICON: the icon handle - -HICON CMsgDialog::IconFromAvatar() const -{ - if (!ServiceExists(MS_AV_GETAVATARBITMAP)) - return nullptr; - - AVATARCACHEENTRY *ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, m_hContact, 0); - if (ace == nullptr || ace->hbmPic == nullptr) - return nullptr; - - LONG lIconSize = Win7Taskbar->getIconSize(); - double dNewWidth, dNewHeight; - Utils::scaleAvatarHeightLimited(ace->hbmPic, dNewWidth, dNewHeight, lIconSize); - - // resize picture to fit it on the task bar, use an image list for converting it to - // 32bpp icon format. hTaskbarIcon will cache it until avatar is changed - HBITMAP hbmResized = ::Image_Resize(ace->hbmPic, RESIZEBITMAP_STRETCH, dNewWidth, dNewHeight); - HIMAGELIST hIml_c = ::ImageList_Create(lIconSize, lIconSize, ILC_COLOR32 | ILC_MASK, 1, 0); - - RECT rc = { 0, 0, lIconSize, lIconSize }; - - HDC hdc = ::GetDC(m_pContainer->m_hwnd); - HDC dc = ::CreateCompatibleDC(hdc); - HDC dcResized = ::CreateCompatibleDC(hdc); - - ReleaseDC(m_pContainer->m_hwnd, hdc); - - HBITMAP hbmNew = CSkin::CreateAeroCompatibleBitmap(rc, dc); - HBITMAP hbmOld = reinterpret_cast<HBITMAP>(::SelectObject(dc, hbmNew)); - HBITMAP hbmOldResized = reinterpret_cast<HBITMAP>(::SelectObject(dcResized, hbmResized)); - - LONG ix = (lIconSize - (LONG)dNewWidth) / 2; - LONG iy = (lIconSize - (LONG)dNewHeight) / 2; - CSkin::m_default_bf.SourceConstantAlpha = M.GetByte("taskBarIconAlpha", 255); - GdiAlphaBlend(dc, ix, iy, (LONG)dNewWidth, (LONG)dNewHeight, dcResized, 0, 0, (LONG)dNewWidth, (LONG)dNewHeight, CSkin::m_default_bf); - - CSkin::m_default_bf.SourceConstantAlpha = 255; - ::SelectObject(dc, hbmOld); - ::ImageList_Add(hIml_c, hbmNew, nullptr); - ::DeleteObject(hbmNew); - ::DeleteDC(dc); - - ::SelectObject(dcResized, hbmOldResized); - if (hbmResized != ace->hbmPic) - ::DeleteObject(hbmResized); - ::DeleteDC(dcResized); - HICON hIcon = ::ImageList_GetIcon(hIml_c, 0, ILD_NORMAL); - ::ImageList_RemoveAll(hIml_c); - ::ImageList_Destroy(hIml_c); - return hIcon; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// is window active or not? - -bool CMsgDialog::IsActive() const -{ - return m_pContainer->IsActive() && m_pContainer->m_hwndActive == m_hwnd; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// read keyboard state and return the state of the modifier keys - -void CMsgDialog::KbdState(bool &isShift, bool &isControl, bool &isAlt) -{ - GetKeyboardState(kstate); - isShift = (kstate[VK_SHIFT] & 0x80) != 0; - isControl = (kstate[VK_CONTROL] & 0x80) != 0; - isAlt = (kstate[VK_MENU] & 0x80) != 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::LimitMessageText(int iLen) -{ - if (this != nullptr) - m_message.SendMsg(EM_EXLIMITTEXT, 0, iLen); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::LoadContactAvatar() -{ - m_ace = Utils::loadAvatarFromAVS(m_bIsMeta ? db_mc_getSrmmSub(m_hContact) : m_hContact); - - BITMAP bm; - if (m_ace && m_ace->hbmPic) - GetObject(m_ace->hbmPic, sizeof(bm), &bm); - else if (m_ace == nullptr) - GetObject(PluginConfig.g_hbmUnknown, sizeof(bm), &bm); - else - return; - - AdjustBottomAvatarDisplay(); - CalcDynamicAvatarSize(&bm); - - if (!m_pPanel.isActive() || m_pContainer->cfg.avatarMode == 3) { - m_iRealAvatarHeight = 0; - PostMessage(m_hwnd, WM_SIZE, 0, 0); - } - else if (m_pPanel.isActive()) - GetAvatarVisibility(); - - if (m_pWnd != nullptr) - m_pWnd->verifyDwmState(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::LoadOwnAvatar() -{ - if (ServiceExists(MS_AV_GETMYAVATAR)) - m_ownAce = (AVATARCACHEENTRY *)CallService(MS_AV_GETMYAVATAR, 0, (LPARAM)(m_cache->getActiveProto())); - else - m_ownAce = nullptr; - - if (m_ownAce) - m_hOwnPic = m_ownAce->hbmPic; - else - m_hOwnPic = PluginConfig.g_hbmUnknown; - - if (m_pPanel.isActive() && m_pContainer->cfg.avatarMode != 3) { - BITMAP bm; - - m_iRealAvatarHeight = 0; - AdjustBottomAvatarDisplay(); - GetObject(m_hOwnPic, sizeof(bm), &bm); - CalcDynamicAvatarSize(&bm); - Resize(); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::LoadSettings() -{ - m_clrInputBG = m_pContainer->m_theme.inputbg; - LoadMsgDlgFont(FONTSECTION_IM, MSGFONTID_MESSAGEAREA, nullptr, &m_clrInputFG); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::LoadSplitter() -{ - if (m_bIsAutosizingInput) { - m_iSplitterY = (m_pContainer->cfg.flags.m_bBottomToolbar) ? DPISCALEY_S(46 + 22) : DPISCALEY_S(46); - - if (CSkin::m_skinEnabled && !SkinItems[ID_EXTBKINPUTAREA].IGNORED) - m_iSplitterY += (SkinItems[ID_EXTBKINPUTAREA].MARGIN_BOTTOM + SkinItems[ID_EXTBKINPUTAREA].MARGIN_TOP - 2); - return; - } - - if (!m_bSplitterOverride) { - if (!m_pContainer->cfg.fPrivate) - m_iSplitterY = (int)M.GetDword("splitsplity", 60); - else - m_iSplitterY = m_pContainer->cfg.iSplitterY; - } - else m_iSplitterY = (int)M.GetDword(m_hContact, "splitsplity", M.GetDword("splitsplity", 60)); - - if (m_iSplitterY < MINSPLITTERY) - m_iSplitterY = 150; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::LogEvent(DBEVENTINFO &dbei) -{ - if (m_iLogMode != WANT_BUILTIN_LOG) { - dbei.flags |= DBEF_TEMPORARY; - - MEVENT hDbEvent = db_event_add(m_hContact, &dbei); - if (hDbEvent) { - m_pLog->LogEvents(hDbEvent, 1, true); - db_event_delete(hDbEvent); - } - } - else LOG()->LogEvents(0, 1, true, &dbei); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// draw various elements of the message window, like avatar(s), info panel fields -// and the color formatting menu - -int CMsgDialog::MsgWindowDrawHandler(DRAWITEMSTRUCT *dis) -{ - if ((dis->hwndItem == GetDlgItem(m_hwnd, IDC_CONTACTPIC) && m_bShowAvatar) || (dis->hwndItem == m_hwnd && m_pPanel.isActive())) { - HBITMAP hbmAvatar = m_ace ? m_ace->hbmPic : PluginConfig.g_hbmUnknown; - if (hbmAvatar == nullptr) - return TRUE; - - int top, cx, cy; - RECT rcClient, rcFrame; - bool bPanelPic = (dis->hwndItem == m_hwnd); - if (bPanelPic && !m_bShowInfoAvatar) - return TRUE; - - RECT rc; - GetClientRect(m_hwnd, &rc); - if (bPanelPic) { - rcClient = dis->rcItem; - cx = (rcClient.right - rcClient.left); - cy = (rcClient.bottom - rcClient.top) + 1; - } - else { - GetClientRect(dis->hwndItem, &rcClient); - cx = rcClient.right; - cy = rcClient.bottom; - } - - if (cx < 5 || cy < 5) - return TRUE; - - HDC hdcDraw = CreateCompatibleDC(dis->hDC); - HBITMAP hbmDraw = CreateCompatibleBitmap(dis->hDC, cx, cy); - HBITMAP hbmOld = (HBITMAP)SelectObject(hdcDraw, hbmDraw); - - bool bAero = M.isAero(); - - HRGN clipRgn = nullptr; - HBRUSH hOldBrush = (HBRUSH)SelectObject(hdcDraw, bAero ? (HBRUSH)GetStockObject(HOLLOW_BRUSH) : GetSysColorBrush(COLOR_3DFACE)); - rcFrame = rcClient; - - if (!bPanelPic) { - top = (cy - m_pic.cy) / 2; - RECT rcEdge = { 0, top, m_pic.cx, top + m_pic.cy }; - if (CSkin::m_skinEnabled) - CSkin::SkinDrawBG(dis->hwndItem, m_pContainer->m_hwnd, m_pContainer, &dis->rcItem, hdcDraw); - else if (PluginConfig.m_fillColor) { - HBRUSH br = CreateSolidBrush(PluginConfig.m_fillColor); - FillRect(hdcDraw, &rcFrame, br); - DeleteObject(br); - } - else if (bAero && CSkin::m_pCurrentAeroEffect) { - COLORREF clr = PluginConfig.m_tbBackgroundHigh ? PluginConfig.m_tbBackgroundHigh : - (CSkin::m_pCurrentAeroEffect ? CSkin::m_pCurrentAeroEffect->m_clrToolbar : 0xf0f0f0); - - HBRUSH br = CreateSolidBrush(clr); - FillRect(hdcDraw, &rcFrame, br); - DeleteObject(br); - } - else FillRect(hdcDraw, &rcFrame, GetSysColorBrush(COLOR_3DFACE)); - - HPEN hPenBorder = CreatePen(PS_SOLID, 1, CSkin::m_avatarBorderClr); - HPEN hPenOld = (HPEN)SelectObject(hdcDraw, hPenBorder); - - if (CSkin::m_bAvatarBorderType == 1) - Rectangle(hdcDraw, rcEdge.left, rcEdge.top, rcEdge.right, rcEdge.bottom); - else if (CSkin::m_bAvatarBorderType == 2) { - clipRgn = CreateRoundRectRgn(rcEdge.left, rcEdge.top, rcEdge.right + 1, rcEdge.bottom + 1, 6, 6); - SelectClipRgn(hdcDraw, clipRgn); - - HBRUSH hbr = CreateSolidBrush(CSkin::m_avatarBorderClr); - FrameRgn(hdcDraw, clipRgn, hbr, 1, 1); - DeleteObject(hbr); - DeleteObject(clipRgn); - } - - SelectObject(hdcDraw, hPenOld); - DeleteObject(hPenBorder); - } - - if (bPanelPic) { - bool bBorder = (CSkin::m_bAvatarBorderType ? true : false); - - int border_off = bBorder ? 1 : 0; - int iMaxHeight = m_iPanelAvatarY - (bBorder ? 2 : 0); - int iMaxWidth = m_iPanelAvatarX - (bBorder ? 2 : 0); - - rcFrame.left = rcFrame.top = 0; - rcFrame.right = (rcClient.right - rcClient.left); - rcFrame.bottom = (rcClient.bottom - rcClient.top); - - rcFrame.left = rcFrame.right - (LONG)m_iPanelAvatarX; - rcFrame.bottom = (LONG)m_iPanelAvatarY; - - int height_off = (cy - iMaxHeight - (bBorder ? 2 : 0)) / 2; - rcFrame.top += height_off; - rcFrame.bottom += height_off; - - SendMessage(m_hwndPanelPic, AVATAR_SETAEROCOMPATDRAWING, 0, bAero ? TRUE : FALSE); - SetWindowPos(m_hwndPanelPic, HWND_TOP, rcFrame.left + border_off, rcFrame.top + border_off, - iMaxWidth, iMaxHeight, SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_NOSENDCHANGING); - } - - SelectObject(hdcDraw, hOldBrush); - if (!bPanelPic) - BitBlt(dis->hDC, 0, 0, cx, cy, hdcDraw, 0, 0, SRCCOPY); - SelectObject(hdcDraw, hbmOld); - DeleteObject(hbmDraw); - DeleteDC(hdcDraw); - return TRUE; - } - - if (dis->hwndItem == GetDlgItem(m_hwnd, IDC_STATICTEXT) || dis->hwndItem == GetDlgItem(m_hwnd, IDC_LOGFROZENTEXT)) { - wchar_t szWindowText[256]; - if (CSkin::m_skinEnabled) { - SetTextColor(dis->hDC, CSkin::m_DefaultFontColor); - CSkin::SkinDrawBG(dis->hwndItem, m_pContainer->m_hwnd, m_pContainer, &dis->rcItem, dis->hDC); - } - else { - SetTextColor(dis->hDC, GetSysColor(COLOR_BTNTEXT)); - CSkin::FillBack(dis->hDC, &dis->rcItem); - } - GetWindowText(dis->hwndItem, szWindowText, _countof(szWindowText)); - szWindowText[255] = 0; - SetBkMode(dis->hDC, TRANSPARENT); - DrawText(dis->hDC, szWindowText, -1, &dis->rcItem, DT_SINGLELINE | DT_VCENTER | DT_NOCLIP | DT_END_ELLIPSIS); - return TRUE; - } - - if (dis->hwndItem == GetDlgItem(m_hwnd, IDC_STATICERRORICON)) { - if (CSkin::m_skinEnabled) - CSkin::SkinDrawBG(dis->hwndItem, m_pContainer->m_hwnd, m_pContainer, &dis->rcItem, dis->hDC); - else - CSkin::FillBack(dis->hDC, &dis->rcItem); - DrawIconEx(dis->hDC, (dis->rcItem.right - dis->rcItem.left) / 2 - 8, (dis->rcItem.bottom - dis->rcItem.top) / 2 - 8, - PluginConfig.g_iconErr, 16, 16, 0, nullptr, DI_NORMAL); - return TRUE; - } - - if (dis->CtlType == ODT_MENU && m_pPanel.isHovered()) { - DrawMenuItem(dis, (HICON)dis->itemData, 0); - return TRUE; - } - - return Menu_DrawItem((LPARAM)dis); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMsgDialog::MsgWindowUpdateMenu(HMENU submenu, int menuID) -{ - bool bInfoPanel = m_pPanel.isActive(); - - if (menuID == MENU_TABCONTEXT) { - EnableMenuItem(submenu, ID_TABMENU_LEAVECHATROOM, (isChat() && ProtoServiceExists(m_szProto, PS_LEAVECHAT)) ? MF_ENABLED : MF_GRAYED); - EnableMenuItem(submenu, ID_TABMENU_ATTACHTOCONTAINER, (M.GetByte("useclistgroups", 0) || M.GetByte("singlewinmode", 0)) ? MF_GRAYED : MF_ENABLED); - EnableMenuItem(submenu, ID_TABMENU_CLEARSAVEDTABPOSITION, (M.GetDword(m_hContact, "tabindex", -1) != -1) ? MF_ENABLED : MF_GRAYED); - } - else if (menuID == MENU_PICMENU) { - wchar_t *szText = nullptr; - char avOverride = (char)M.GetByte(m_hContact, "hideavatar", -1); - HMENU visMenu = GetSubMenu(submenu, 0); - BOOL picValid = bInfoPanel ? (m_hOwnPic != nullptr) : (m_ace && m_ace->hbmPic && m_ace->hbmPic != PluginConfig.g_hbmUnknown); - - MENUITEMINFO mii = { 0 }; - mii.cbSize = sizeof(mii); - mii.fMask = MIIM_STRING; - - EnableMenuItem(submenu, ID_PICMENU_SAVETHISPICTUREAS, picValid ? MF_ENABLED : MF_GRAYED); - - CheckMenuItem(visMenu, ID_VISIBILITY_DEFAULT, avOverride == -1 ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(visMenu, ID_VISIBILITY_HIDDENFORTHISCONTACT, avOverride == 0 ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(visMenu, ID_VISIBILITY_VISIBLEFORTHISCONTACT, avOverride == 1 ? MF_CHECKED : MF_UNCHECKED); - - CheckMenuItem(submenu, ID_PICMENU_ALWAYSKEEPTHEBUTTONBARATFULLWIDTH, PluginConfig.m_bAlwaysFullToolbarWidth ? MF_CHECKED : MF_UNCHECKED); - if (!bInfoPanel) { - EnableMenuItem(submenu, ID_PICMENU_SETTINGS, ServiceExists(MS_AV_GETAVATARBITMAP) ? MF_ENABLED : MF_GRAYED); - szText = TranslateT("Contact picture settings..."); - EnableMenuItem(submenu, 0, MF_BYPOSITION | MF_ENABLED); - } - else { - EnableMenuItem(submenu, 0, MF_BYPOSITION | MF_GRAYED); - EnableMenuItem(submenu, ID_PICMENU_SETTINGS, (ServiceExists(MS_AV_SETMYAVATARW) && CallService(MS_AV_CANSETMYAVATAR, (WPARAM)(m_cache->getActiveProto()), 0)) ? MF_ENABLED : MF_GRAYED); - szText = TranslateT("Set your avatar..."); - } - mii.dwTypeData = szText; - mii.cch = (int)mir_wstrlen(szText) + 1; - SetMenuItemInfo(submenu, ID_PICMENU_SETTINGS, FALSE, &mii); - } - else if (menuID == MENU_PANELPICMENU) { - HMENU visMenu = GetSubMenu(submenu, 0); - char avOverride = (char)M.GetByte(m_hContact, "hideavatar", -1); - - CheckMenuItem(visMenu, ID_VISIBILITY_DEFAULT, avOverride == -1 ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(visMenu, ID_VISIBILITY_HIDDENFORTHISCONTACT, avOverride == 0 ? MF_CHECKED : MF_UNCHECKED); - CheckMenuItem(visMenu, ID_VISIBILITY_VISIBLEFORTHISCONTACT, avOverride == 1 ? MF_CHECKED : MF_UNCHECKED); - - EnableMenuItem(submenu, ID_PICMENU_SETTINGS, ServiceExists(MS_AV_GETAVATARBITMAP) ? MF_ENABLED : MF_GRAYED); - EnableMenuItem(submenu, ID_PANELPICMENU_SAVETHISPICTUREAS, (m_ace && m_ace->hbmPic && m_ace->hbmPic != PluginConfig.g_hbmUnknown) ? MF_ENABLED : MF_GRAYED); - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// update state of the container - this is called whenever a tab becomes active, no matter how and -// deals with various things like updating the title bar, removing flashing icons, updating the -// session list, switching the keyboard layout (autolocale active) and the general container status. -// -// it protects itself from being called more than once per session activation and is valid for -// normal IM sessions *only*. Group chat sessions have their own activation handler (see chat/window.c) - - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMsgDialog::MsgWindowMenuHandler(int selection, int menuId) -{ - if (menuId == MENU_PICMENU || menuId == MENU_PANELPICMENU || menuId == MENU_TABCONTEXT) { - switch (selection) { - case ID_TABMENU_ATTACHTOCONTAINER: - SelectContainer(); - return 1; - case ID_TABMENU_CONTAINEROPTIONS: - m_pContainer->OptionsDialog(); - return 1; - case ID_TABMENU_CLOSECONTAINER: - SendMessage(m_pContainer->m_hwnd, WM_CLOSE, 0, 0); - return 1; - case ID_TABMENU_CLOSETAB: - PostMessage(m_hwnd, WM_CLOSE, 1, 0); - return 1; - case ID_TABMENU_SAVETABPOSITION: - db_set_dw(m_hContact, SRMSGMOD_T, "tabindex", m_iTabID * 100); - break; - case ID_TABMENU_CLEARSAVEDTABPOSITION: - db_unset(m_hContact, SRMSGMOD_T, "tabindex"); - break; - case ID_TABMENU_LEAVECHATROOM: - if (isChat()) { - char *szProto = Proto_GetBaseAccountName(m_hContact); - if (szProto) - CallProtoService(szProto, PS_LEAVECHAT, m_hContact, 0); - } - return 1; - - case ID_VISIBILITY_DEFAULT: - case ID_VISIBILITY_HIDDENFORTHISCONTACT: - case ID_VISIBILITY_VISIBLEFORTHISCONTACT: - { - uint8_t avOverrideMode; - if (selection == ID_VISIBILITY_DEFAULT) - avOverrideMode = -1; - else if (selection == ID_VISIBILITY_VISIBLEFORTHISCONTACT) - avOverrideMode = 1; - else - avOverrideMode = 0; - db_set_b(m_hContact, SRMSGMOD_T, "hideavatar", avOverrideMode); - } - - ShowPicture(false); - Resize(); - DM_ScrollToBottom(0, 1); - return 1; - - case ID_PICMENU_ALWAYSKEEPTHEBUTTONBARATFULLWIDTH: - PluginConfig.m_bAlwaysFullToolbarWidth = !PluginConfig.m_bAlwaysFullToolbarWidth; - db_set_b(0, SRMSGMOD_T, "alwaysfulltoolbar", (uint8_t)PluginConfig.m_bAlwaysFullToolbarWidth); - Srmm_Broadcast(DM_CONFIGURETOOLBAR, 0, 1); - break; - - case ID_PICMENU_SAVETHISPICTUREAS: - if (m_pPanel.isActive()) - SaveAvatarToFile(m_hOwnPic, 1); - else if (m_ace) - SaveAvatarToFile(m_ace->hbmPic, 0); - break; - - case ID_PANELPICMENU_SAVETHISPICTUREAS: - if (m_ace) - SaveAvatarToFile(m_ace->hbmPic, 0); - break; - - case ID_PICMENU_SETTINGS: - if (menuId == MENU_PICMENU) { - if (m_pPanel.isActive()) { - if (ServiceExists(MS_AV_SETMYAVATARW) && CallService(MS_AV_CANSETMYAVATAR, (WPARAM)(m_cache->getActiveProto()), 0)) - CallService(MS_AV_SETMYAVATARW, (WPARAM)(m_cache->getActiveProto()), 0); - return TRUE; - } - } - CallService(MS_AV_CONTACTOPTIONS, m_hContact, (LPARAM)m_hwnd); - return 1; - } - } - else if (menuId == MENU_LOGMENU) { - switch (selection) { - case ID_MESSAGELOGSETTINGS_GLOBAL: - g_plugin.openOptions(nullptr, L"Message sessions", L"Message log"); - return 1; - - case ID_MESSAGELOGSETTINGS_FORTHISCONTACT: - CallService(MS_TABMSG_SETUSERPREFS, m_hContact, 0); - return 1; - } - } - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::NotifyDeliveryFailure() const -{ - if (!g_plugin.bErrorPopup || !Popup_Enabled()) - return; - - POPUPDATAW ppd = {}; - ppd.lchContact = m_hContact; - ppd.PluginWindowProc = Utils::PopupDlgProcError; - ppd.lchIcon = PluginConfig.g_iconErr; - ppd.iSeconds = NEN::iDelayErr; - if (!NEN::bColDefaultErr) { - ppd.colorText = NEN::colTextErr; - ppd.colorBack = NEN::colBackErr; - } - wcsncpy_s(ppd.lpwzContactName, m_cache->getNick(), _TRUNCATE); - wcsncpy_s(ppd.lpwzText, TranslateT("A message delivery has failed.\nClick to open the message window."), _TRUNCATE); - PUAddPopupW(&ppd); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::PlayIncomingSound() const -{ - int iPlay = m_pContainer->MustPlaySound(this); - if (iPlay) { - if (GetForegroundWindow() == m_pContainer->m_hwnd && m_pContainer->m_hwndActive == m_hwnd) - Skin_PlaySound("RecvMsgActive"); - else - Skin_PlaySound("RecvMsgInactive"); - } -} - -void CMsgDialog::RemakeLog() -{ - m_szMicroLf[0] = 0; - m_lastEventTime = 0; - m_iLastEventType = -1; - StreamEvents(m_hDbEventFirst, -1, 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// saves a contact picture to disk -// takes hbm (bitmap handle) and bool isOwnPic (1 == save the picture as your own avatar) -// requires AVS service (Miranda 0.7+) - -void CMsgDialog::SaveAvatarToFile(HBITMAP hbm, int isOwnPic) -{ - wchar_t szFinalFilename[MAX_PATH]; - time_t t = time(0); - struct tm *lt = localtime(&t); - uint32_t setView = 1; - - wchar_t szTimestamp[100]; - mir_snwprintf(szTimestamp, L"%04u %02u %02u_%02u%02u", lt->tm_year + 1900, lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min); - - wchar_t *szProto = mir_a2u(m_cache->getActiveProto()); - - wchar_t szFinalPath[MAX_PATH]; - mir_snwprintf(szFinalPath, L"%s\\%s", M.getSavedAvatarPath(), szProto); - mir_free(szProto); - - if (CreateDirectory(szFinalPath, nullptr) == 0) { - if (GetLastError() != ERROR_ALREADY_EXISTS) { - MessageBox(nullptr, TranslateT("Error creating destination directory"), - TranslateT("Save contact picture"), MB_OK | MB_ICONSTOP); - return; - } - } - - wchar_t szBaseName[MAX_PATH]; - if (isOwnPic) - mir_snwprintf(szBaseName, L"My Avatar_%s", szTimestamp); - else - mir_snwprintf(szBaseName, L"%s_%s", m_cache->getNick(), szTimestamp); - - mir_snwprintf(szFinalFilename, L"%s.png", szBaseName); - - // do not allow / or \ or % in the filename - Utils::sanitizeFilename(szFinalFilename); - - wchar_t filter[MAX_PATH]; - mir_snwprintf(filter, L"%s%c*.bmp;*.png;*.jpg;*.gif%c%c", TranslateT("Image files"), 0, 0, 0); - - OPENFILENAME ofn = { 0 }; - ofn.lpstrDefExt = L"png"; - ofn.lpstrFilter = filter; - ofn.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLESIZING | OFN_ENABLEHOOK; - ofn.lpfnHook = OpenFileSubclass; - ofn.lStructSize = sizeof(ofn); - ofn.lpstrFile = szFinalFilename; - ofn.lpstrInitialDir = szFinalPath; - ofn.nMaxFile = MAX_PATH; - ofn.nMaxFileTitle = MAX_PATH; - ofn.lCustData = (LPARAM)& setView; - if (GetSaveFileName(&ofn)) { - if (PathFileExists(szFinalFilename)) - if (MessageBox(nullptr, TranslateT("The file exists. Do you want to overwrite it?"), TranslateT("Save contact picture"), MB_YESNO | MB_ICONQUESTION) == IDNO) - return; - - IMGSRVC_INFO ii; - ii.cbSize = sizeof(ii); - ii.pwszName = szFinalFilename; - ii.hbm = hbm; - ii.dwMask = IMGI_HBITMAP; - ii.fif = FIF_UNKNOWN; // get the format from the filename extension. png is default. - Image_Save(&ii); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::SaveSplitter() -{ - if (m_bIsAutosizingInput) - return; - - if (m_iSplitterY < DPISCALEY_S(MINSPLITTERY) || m_iSplitterY < 0) - m_iSplitterY = DPISCALEY_S(MINSPLITTERY); - - if (m_bSplitterOverride) - db_set_dw(m_hContact, SRMSGMOD_T, "splitsplity", m_iSplitterY); - else { - if (m_pContainer->cfg.fPrivate) - m_pContainer->cfg.iSplitterY = m_iSplitterY; - else - db_set_dw(0, SRMSGMOD_T, "splitsplity", m_iSplitterY); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// send a pasted bitmap by file transfer. - -static LIST<wchar_t> vTempFilenames(5); - -void CMsgDialog::SendHBitmapAsFile(HBITMAP hbmp) const -{ - const wchar_t *mirandatempdir = L"Miranda"; - const wchar_t *filenametemplate = L"\\clp-%Y%m%d-%H%M%S0.jpg"; - wchar_t filename[MAX_PATH]; - size_t tempdirlen = GetTempPath(MAX_PATH, filename); - bool fSend = true; - - const char *szProto = m_cache->getActiveProto(); - int wMyStatus = Proto_GetStatus(szProto); - - uint32_t protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0); - uint32_t typeCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0); - - // check protocol capabilities, status modes and visibility lists (privacy) - // to determine whether the file can be sent. Throw a warning if any of - // these checks fails. - if (!(protoCaps & PF1_FILESEND)) - fSend = false; - - if ((ID_STATUS_OFFLINE == wMyStatus) || (ID_STATUS_OFFLINE == m_cache->getActiveStatus() && !(typeCaps & PF4_OFFLINEFILES))) - fSend = false; - - if (protoCaps & PF1_VISLIST && db_get_w(m_cache->getActiveContact(), szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE) - fSend = false; - - if (protoCaps & PF1_INVISLIST && wMyStatus == ID_STATUS_INVISIBLE && db_get_w(m_cache->getActiveContact(), szProto, "ApparentMode", 0) != ID_STATUS_ONLINE) - fSend = false; - - if (!fSend) { - CWarning::show(CWarning::WARN_SENDFILE, MB_OK | MB_ICONEXCLAMATION | CWarning::CWF_NOALLOWHIDE); - return; - } - - if (tempdirlen <= 0 || tempdirlen >= MAX_PATH - mir_wstrlen(mirandatempdir) - mir_wstrlen(filenametemplate) - 2) // -2 is because %Y takes 4 symbols - filename[0] = 0; // prompt for a new name - else { - mir_wstrcpy(filename + tempdirlen, mirandatempdir); - if ((GetFileAttributes(filename) == INVALID_FILE_ATTRIBUTES || ((GetFileAttributes(filename) & FILE_ATTRIBUTE_DIRECTORY) == 0)) && CreateDirectory(filename, nullptr) == 0) - filename[0] = 0; - else { - tempdirlen = mir_wstrlen(filename); - - time_t rawtime; - time(&rawtime); - const tm *timeinfo; - timeinfo = _localtime32((__time32_t *)& rawtime); - wcsftime(filename + tempdirlen, MAX_PATH - tempdirlen, filenametemplate, timeinfo); - size_t firstnumberpos = tempdirlen + 14; - size_t lastnumberpos = tempdirlen + 20; - while (GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES) { // while it exists - for (size_t pos = lastnumberpos; pos >= firstnumberpos; pos--) - if (filename[pos]++ != '9') - break; - else - if (pos == firstnumberpos) - filename[0] = 0; // all filenames exist => prompt for a new name - else - filename[pos] = '0'; - } - } - } - - if (filename[0] == 0) { // prompting to save - wchar_t filter[MAX_PATH]; - mir_snwprintf(filter, L"%s%c*.jpg%c%c", TranslateT("JPEG-compressed images"), 0, 0, 0); - - OPENFILENAME dlg; - dlg.lStructSize = sizeof(dlg); - dlg.lpstrFilter = filter; - dlg.nFilterIndex = 1; - dlg.lpstrFile = filename; - dlg.nMaxFile = MAX_PATH; - dlg.Flags = OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; - dlg.lpstrDefExt = L"jpg"; - if (!GetSaveFileName(&dlg)) - return; - } - - IMGSRVC_INFO ii; - ii.cbSize = sizeof(ii); - ii.hbm = hbmp; - ii.pwszName = filename; - ii.dwMask = IMGI_HBITMAP; - ii.fif = FIF_JPEG; - if (!Image_Save(&ii)) { - CWarning::show(CWarning::WARN_SAVEFILE, MB_OK | MB_ICONEXCLAMATION | CWarning::CWF_NOALLOWHIDE); - return; - } - - vTempFilenames.insert(mir_wstrdup(filename)); - - wchar_t *ppFiles[2] = { filename, nullptr }; - CallService(MS_FILE_SENDSPECIFICFILEST, m_cache->getActiveContact(), (LPARAM)&ppFiles); -} - -// remove all temporary files created by the "send clipboard as file" feature. -void TSAPI CleanTempFiles() -{ - for (auto &it : vTempFilenames) { - DeleteFileW(it); - mir_free(it); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Sets a status bar text for a contact - -void CMsgDialog::SetStatusText(const wchar_t *wszText, HICON hIcon) -{ - if (wszText != nullptr) { - m_bStatusSet = true; - m_szStatusText = wszText; - m_szStatusIcon = hIcon; - } - else { - m_bStatusSet = false; - m_szStatusText.Empty(); - m_szStatusIcon = nullptr; - } - - tabUpdateStatusBar(); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// 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 CMsgDialog::FilterWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - CMsgDialog *pDlg = (CMsgDialog *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); - switch (uMsg) { - case WM_INITDIALOG: - pDlg = (CMsgDialog *)lParam; - SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); - { - uint32_t dwMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "FilterMask", 0); - uint32_t dwFlags = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "FilterFlags", 0); - - uint32_t dwPopupMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "PopupMask", 0); - uint32_t dwPopupFlags = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "PopupFlags", 0); - - uint32_t dwTrayMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "TrayIconMask", 0); - uint32_t 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 && pDlg) { - int iFlags = 0; - uint32_t 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 (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 (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 (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->getChat()); - - if (pDlg->m_bFilterEnabled) { - if (pDlg->m_iLogFilterFlags == 0) - pDlg->m_btnFilter.Click(); - 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; -} - -void CMsgDialog::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_pLog->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 CMsgDialog::ShowPicture(bool showNewPic) -{ - if (!m_pPanel.isActive()) - m_pic.cy = m_pic.cx = DPISCALEY_S(60); - - if (showNewPic) { - if (m_pPanel.isActive() && m_pContainer->cfg.avatarMode != 3) { - if (!m_hwndPanelPic) { - InvalidateRect(m_hwnd, nullptr, TRUE); - UpdateWindow(m_hwnd); - Resize(); - } - return; - } - AdjustBottomAvatarDisplay(); - } - else { - m_bShowAvatar = !m_bShowAvatar; - db_set_b(m_hContact, SRMSGMOD_T, "MOD_ShowPic", m_bShowAvatar); - } - - RECT rc; - GetWindowRect(GetDlgItem(m_hwnd, IDC_CONTACTPIC), &rc); - if (m_minEditBoxSize.cy + DPISCALEY_S(3) > m_iSplitterY) - SplitterMoved(rc.bottom - m_minEditBoxSize.cy, GetDlgItem(m_hwnd, IDC_SPLITTERY)); - if (!showNewPic) - SetDialogToType(); - else - Resize(); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// show a modified context menu for the richedit control(s) - -void CMsgDialog::ShowPopupMenu(const CCtrlBase &pCtrl, POINT pt) -{ - CHARRANGE sel, all = { 0, -1 }; - - HMENU hSubMenu, hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_CONTEXT)); - if (pCtrl.GetCtrlId() == IDC_SRMM_LOG) - hSubMenu = GetSubMenu(hMenu, 0); - else { - hSubMenu = GetSubMenu(hMenu, 2); - EnableMenuItem(hSubMenu, IDM_PASTEFORMATTED, m_SendFormat != 0 ? MF_ENABLED : MF_GRAYED); - EnableMenuItem(hSubMenu, ID_EDITOR_PASTEANDSENDIMMEDIATELY, g_plugin.bPasteAndSend ? MF_ENABLED : MF_GRAYED); - CheckMenuItem(hSubMenu, ID_EDITOR_SHOWMESSAGELENGTHINDICATOR, PluginConfig.m_visualMessageSizeIndicator ? MF_CHECKED : MF_UNCHECKED); - EnableMenuItem(hSubMenu, ID_EDITOR_SHOWMESSAGELENGTHINDICATOR, m_pContainer->m_hwndStatus ? MF_ENABLED : MF_GRAYED); - } - TranslateMenu(hSubMenu); - pCtrl.SendMsg(EM_EXGETSEL, 0, (LPARAM)& sel); - if (sel.cpMin == sel.cpMax) { - EnableMenuItem(hSubMenu, IDM_COPY, MF_GRAYED); - EnableMenuItem(hSubMenu, IDM_QUOTE, MF_GRAYED); - if (pCtrl.GetCtrlId() == IDC_SRMM_MESSAGE) - EnableMenuItem(hSubMenu, IDM_CUT, MF_GRAYED); - } - - if (pCtrl.GetCtrlId() == IDC_SRMM_LOG) { - InsertMenuA(hSubMenu, 6, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); - CheckMenuItem(hSubMenu, ID_LOG_FREEZELOG, m_bScrollingDisabled ? MF_CHECKED : MF_UNCHECKED); - } - - MessageWindowPopupData mwpd; - // First notification - mwpd.uType = MSG_WINDOWPOPUP_SHOWING; - mwpd.uFlags = (pCtrl.GetCtrlId() == IDC_SRMM_LOG ? MSG_WINDOWPOPUP_LOG : MSG_WINDOWPOPUP_INPUT); - mwpd.hContact = m_hContact; - mwpd.hwnd = pCtrl.GetHwnd(); - mwpd.hMenu = hSubMenu; - mwpd.selection = 0; - mwpd.pt = pt; - NotifyEventHooks(g_chatApi.hevWinPopup, 0, (LPARAM)& mwpd); - - int iSelection = TrackPopupMenu(hSubMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_hwnd, nullptr); - - // Second notification - mwpd.selection = iSelection; - mwpd.uType = MSG_WINDOWPOPUP_SELECTED; - NotifyEventHooks(g_chatApi.hevWinPopup, 0, (LPARAM)& mwpd); - - switch (iSelection) { - case IDM_COPY: - pCtrl.SendMsg(WM_COPY, 0, 0); - break; - case IDM_CUT: - pCtrl.SendMsg(WM_CUT, 0, 0); - break; - case IDM_PASTE: - case IDM_PASTEFORMATTED: - if (pCtrl.GetCtrlId() == IDC_SRMM_MESSAGE) - pCtrl.SendMsg(EM_PASTESPECIAL, (iSelection == IDM_PASTE) ? CF_UNICODETEXT : 0, 0); - break; - case IDM_COPYALL: - pCtrl.SendMsg(EM_EXSETSEL, 0, (LPARAM)& all); - pCtrl.SendMsg(WM_COPY, 0, 0); - pCtrl.SendMsg(EM_EXSETSEL, 0, (LPARAM)& sel); - break; - case IDM_QUOTE: - SendMessage(m_hwnd, WM_COMMAND, IDC_QUOTE, 0); - break; - case IDM_SELECTALL: - pCtrl.SendMsg(EM_EXSETSEL, 0, (LPARAM)& all); - break; - case IDM_CLEAR: - tabClearLog(); - break; - case ID_LOG_FREEZELOG: - SendDlgItemMessage(m_hwnd, IDC_SRMM_LOG, WM_KEYDOWN, VK_F12, 0); - break; - case ID_EDITOR_SHOWMESSAGELENGTHINDICATOR: - PluginConfig.m_visualMessageSizeIndicator = !PluginConfig.m_visualMessageSizeIndicator; - db_set_b(0, SRMSGMOD_T, "msgsizebar", (uint8_t)PluginConfig.m_visualMessageSizeIndicator); - Srmm_Broadcast(DM_CONFIGURETOOLBAR, 0, 0); - Resize(); - if (m_pContainer->m_hwndStatus) - RedrawWindow(m_pContainer->m_hwndStatus, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); - break; - case ID_EDITOR_PASTEANDSENDIMMEDIATELY: - HandlePasteAndSend(); - break; - } - - if (pCtrl.GetCtrlId() == IDC_SRMM_LOG) - RemoveMenu(hSubMenu, 7, MF_BYPOSITION); - DestroyMenu(hMenu); -} - -void CMsgDialog::SplitterMoved(int coord, HWND hwnd) -{ - POINT pt; - RECT rc; - - switch (GetDlgCtrlID(hwnd)) { - case IDC_MULTISPLITTER: - GetClientRect(m_hwnd, &rc); - pt.x = coord; - pt.y = 0; - ScreenToClient(m_hwnd, &pt); - { - int oldSplitterX = m_iMultiSplit; - m_iMultiSplit = rc.right - pt.x; - if (m_iMultiSplit < 25) - m_iMultiSplit = 25; - - if (m_iMultiSplit > ((rc.right - rc.left) - 80)) - m_iMultiSplit = oldSplitterX; - } - Resize(); - break; - - case IDC_SPLITTERX: - GetClientRect(m_hwnd, &rc); - pt.x = coord, 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->cfg.iSplitterX = iSplitterX; - } - Resize(); - break; - - case IDC_SPLITTERY: - GetClientRect(m_hwnd, &rc); - rc.top += (m_pPanel.isActive() ? m_pPanel.getHeight() + 40 : 30); - pt.x = 0; - pt.y = coord; - ScreenToClient(m_hwnd, &pt); - { - int oldSplitterY = m_iSplitterY; - int oldDynaSplitter = m_dynaSplitter; - - m_iSplitterY = rc.bottom - pt.y + DPISCALEY_S(23); - - // attempt to fix splitter troubles.. - // hardcoded limits... better solution is possible, but this works for now - int bottomtoolbarH = 0; - if (m_pContainer->cfg.flags.m_bBottomToolbar) - bottomtoolbarH = 22; - - if (m_iSplitterY < (DPISCALEY_S(MINSPLITTERY) + 5 + bottomtoolbarH)) { // min splitter size - m_iSplitterY = (DPISCALEY_S(MINSPLITTERY) + 5 + bottomtoolbarH); - m_dynaSplitter = m_iSplitterY - DPISCALEY_S(34); - DM_RecalcPictureSize(); - } - else if (m_iSplitterY > (rc.bottom - rc.top)) { - m_iSplitterY = oldSplitterY; - m_dynaSplitter = oldDynaSplitter; - DM_RecalcPictureSize(); - } - else { - m_dynaSplitter = (rc.bottom - pt.y) - DPISCALEY_S(11); - DM_RecalcPictureSize(); - } - } - UpdateToolbarBG(); - Resize(); - break; - - case IDC_PANELSPLITTER: - GetClientRect(m_pLog->GetHwnd(), &rc); - - POINT pnt = { 0, coord }; - ScreenToClient(m_hwnd, &pnt); - if ((pnt.y + 2 >= MIN_PANELHEIGHT + 2) && (pnt.y + 2 < 100) && (pnt.y + 2 < rc.bottom - 30)) - m_pPanel.setHeight(pnt.y + 2, true); - - RedrawWindow(m_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); - if (M.isAero()) - InvalidateRect(GetParent(m_hwnd), nullptr, FALSE); - break; - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::StreamEvents(MEVENT hDbEventFirst, int count, bool bAppend) -{ - m_pLog->LogEvents(hDbEventFirst, count, bAppend); - - DM_ScrollToBottom(0, 0); - if (bAppend && hDbEventFirst) - m_hDbEventLast = hDbEventFirst; - else - m_hDbEventLast = db_event_last(m_hContact); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// sent by the select container dialog box when a container was selected... - -void CMsgDialog::SwitchToContainer(const wchar_t *szNewName) -{ - if (!mir_wstrcmp(szNewName, TranslateT("Default container"))) - szNewName = CGlobals::m_default_container_name; - - int iOldItems = TabCtrl_GetItemCount(m_hwndParent); - if (!wcsncmp(m_pContainer->m_wszName, szNewName, CONTAINER_NAMELEN)) - return; - - TContainerData *pNewContainer = FindContainerByName(szNewName); - if (pNewContainer == nullptr) - if ((pNewContainer = CreateContainer(szNewName, FALSE, m_hContact)) == nullptr) - return; - - db_set_ws(m_hContact, SRMSGMOD_T, "containerW", szNewName); - PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_DOCREATETAB, (WPARAM)pNewContainer, m_hContact); - if (iOldItems > 1) // there were more than 1 tab, container is still valid - SendMessage(m_pContainer->m_hwndActive, WM_SIZE, 0, 0); - SetForegroundWindow(pNewContainer->m_hwnd); - SetActiveWindow(pNewContainer->m_hwnd); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -bool CMsgDialog::TabAutoComplete() -{ - LRESULT lResult = m_message.SendMsg(EM_GETSEL, 0, 0); - int start = LOWORD(lResult), end = HIWORD(lResult); - int origStart = start, origEnd = end; - 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)& gt, 0); - if (iLen <= 0) - return false; - - bool isTopic = false, isRoom = false; - 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)& gt, (LPARAM)pszText); - - 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; - } - - const wchar_t *pszName = 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) { - if (end != start) { - CMStringW szReplace; - if (!isRoom && !isTopic && start == 0) { - szReplace = pszName; - if (mir_wstrlen(g_Settings.pwszAutoText)) - szReplace.Append(g_Settings.pwszAutoText); - szReplace.AppendChar(' '); - m_wszSearchResult = szReplace.Detach(); - pszName = m_wszSearchResult; - } - else m_wszSearchResult = mir_wstrdup(pszName); - - m_message.SendMsg(EM_SETSEL, start, end); - m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)pszName); - } - else m_wszSearchResult = mir_wstrdup(pszName); - - return true; - } - - if (end != start) { - m_message.SendMsg(EM_SETSEL, start, end); - m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)m_wszSearchQuery); - } - m_message.SendMsg(EM_SETSEL, origStart, origEnd); - replaceStrW(m_wszSearchQuery, nullptr); - return false; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::tabClearLog() -{ - if (isChat()) { - g_chatApi.LM_RemoveAll(&m_si->pLog, &m_si->pLogEnd); - m_si->iEventCount = 0; - m_si->LastTime = 0; - PostMessage(m_hwnd, WM_MOUSEACTIVATE, 0, 0); - } - - m_pLog->Clear(); - m_hDbEventFirst = 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -CThumbBase *CMsgDialog::tabCreateThumb(CProxyWindow *pProxy) const -{ - if (isChat()) - return new CThumbMUC(pProxy, m_si); - - return new CThumbIM(pProxy); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// update all status bar fields and force a redraw of the status bar. - -void CMsgDialog::tabUpdateStatusBar() const -{ - if (m_pContainer->m_hwndStatus && m_pContainer->m_hwndActive == m_hwnd) { - if (!isChat()) { - if (m_wszStatusBar[0]) { - SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING]); - SendMessage(m_pContainer->m_hwndStatus, SB_SETTEXT, 0, (LPARAM)m_wszStatusBar); - } - 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()); - } - else { - SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, 0); - DM_UpdateLastMessage(); - } - } - 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()); - } - else SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, 0); - } - UpdateReadChars(); - InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); - SendMessage(m_pContainer->m_hwndStatus, WM_USER + 101, 0, (LPARAM)this); - } -} - -int CMsgDialog::Typing(int secs) -{ - if (!AllowTyping()) - return 0; - - int preTyping = m_nTypeSecs != 0; - - setTyping(m_nTypeSecs = (secs > 0) ? secs : 0); - if (m_nTypeSecs) - m_bShowTyping = 0; - - return preTyping; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::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 CMsgDialog::UpdateOptions() -{ - GetSendFormat(); - - 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); - - CSuper::UpdateOptions(); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// update the status bar field which displays the number of characters in the input area -// and various indicators (caps lock, num lock, insert mode). - -void CMsgDialog::UpdateReadChars() const -{ - if (!m_pContainer->m_hwndStatus || m_pContainer->m_hwndActive != m_hwnd) - return; - - int len; - if (isChat()) - len = GetWindowTextLength(m_message.GetHwnd()); - else { - // retrieve text length in UTF8 bytes, because this is the relevant length for most protocols - GETTEXTLENGTHEX gtxl = { 0 }; - gtxl.codepage = CP_UTF8; - gtxl.flags = GTL_DEFAULT | GTL_PRECISE | GTL_NUMBYTES; - - len = m_message.SendMsg(EM_GETTEXTLENGTHEX, (WPARAM)>xl, 0); - } - - BOOL fCaps = (GetKeyState(VK_CAPITAL) & 1); - BOOL fNum = (GetKeyState(VK_NUMLOCK) & 1); - - wchar_t szBuf[20]; szBuf[0] = 0; - if (m_bInsertMode) - mir_wstrcat(szBuf, L"O"); - if (fCaps) - mir_wstrcat(szBuf, L"C"); - if (fNum) - mir_wstrcat(szBuf, L"N"); - if (m_bInsertMode || fCaps || fNum) - mir_wstrcat(szBuf, L" | "); - - wchar_t buf[128]; - mir_snwprintf(buf, L"%s%s %d/%d", szBuf, m_lcID, m_iOpenJobs, len); - SendMessage(m_pContainer->m_hwndStatus, SB_SETTEXT, 1, (LPARAM)buf); - if (PluginConfig.m_visualMessageSizeIndicator) - InvalidateRect(m_pContainer->m_hwndStatus, nullptr, FALSE); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::UpdateSaveAndSendButton() -{ - GETTEXTLENGTHEX gtxl = { 0 }; - gtxl.codepage = CP_UTF8; - gtxl.flags = GTL_DEFAULT | GTL_PRECISE | GTL_NUMBYTES; - - int len = SendDlgItemMessage(m_hwnd, IDC_SRMM_MESSAGE, EM_GETTEXTLENGTHEX, (WPARAM)>xl, 0); - if (len && GetSendButtonState() == PBS_DISABLED) - EnableSendButton(true); - else if (len == 0 && GetSendButtonState() != PBS_DISABLED) - EnableSendButton(false); - - if (len) { // looks complex but avoids flickering on the button while typing. - if (!m_bSaveBtn) { - SendDlgItemMessage(m_hwnd, IDC_CLOSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_BUTTON_SAVE]); - SendDlgItemMessage(m_hwnd, IDC_CLOSE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Save and close session"), BATF_UNICODE); - m_bSaveBtn = true; - } - } - else { - SendDlgItemMessage(m_hwnd, IDC_CLOSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_BUTTON_CANCEL]); - SendDlgItemMessage(m_hwnd, IDC_CLOSE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Close session"), BATF_UNICODE); - m_bSaveBtn = false; - } - m_textLen = len; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::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); - uint32_t diff = (now - mi->idleTimeStamp) / 60; - if (diff >= 1) { - if (diff > 59) { - uint32_t hours = diff / 60; - uint32_t 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 CMsgDialog::UpdateTitle() -{ - if (isChat()) { - 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(); - } - } - else { - uint32_t dwOldIdle = m_idle; - const char *szActProto = nullptr; - - m_wszStatus[0] = 0; - - if (m_iTabID == -1) - return; - - TCITEM item = {}; - - bool bChanged = false; - wchar_t newtitle[128]; - if (m_szProto) { - szActProto = m_cache->getProto(); - - bool bHasName = (m_cache->getUIN()[0] != 0); - m_idle = m_cache->getIdleTS(); - m_bIsIdle = m_idle != 0; - - m_wStatus = m_cache->getStatus(); - wcsncpy_s(m_wszStatus, Clist_GetStatusModeDescription(m_szProto == nullptr ? ID_STATUS_OFFLINE : m_wStatus, 0), _TRUNCATE); - - wchar_t newcontactname[128]; newcontactname[0] = 0; - if (PluginConfig.m_bCutContactNameOnTabs) - CutContactName(m_cache->getNick(), newcontactname, _countof(newcontactname)); - else - wcsncpy_s(newcontactname, m_cache->getNick(), _TRUNCATE); - - Utils::DoubleAmpersands(newcontactname, _countof(newcontactname)); - - if (newcontactname[0] != 0) { - if (g_plugin.bStatusOnTabs) - mir_snwprintf(newtitle, L"%s (%s)", newcontactname, m_wszStatus); - else - wcsncpy_s(newtitle, newcontactname, _TRUNCATE); - } - else wcsncpy_s(newtitle, L"Forward", _TRUNCATE); - - if (mir_wstrcmp(newtitle, m_wszTitle)) - bChanged = true; - else if (m_wStatus != m_wOldStatus) - bChanged = true; - - UpdateWindowIcon(); - - wchar_t fulluin[256]; - if (m_bIsMeta) - mir_snwprintf(fulluin, - TranslateT("UID: %s (Shift+click -> copy to clipboard)\nClick for user's details\nRight click for metacontact control\nClick dropdown to add or remove user from your favorites."), - bHasName ? m_cache->getUIN() : TranslateT("No UID")); - else - mir_snwprintf(fulluin, - TranslateT("UID: %s (Shift+click -> copy to clipboard)\nClick for user's details\nClick dropdown to change this contact's favorite status."), - bHasName ? m_cache->getUIN() : TranslateT("No UID")); - - SendDlgItemMessage(m_hwnd, IDC_NAME, BUTTONADDTOOLTIP, (WPARAM)fulluin, BATF_UNICODE); - } - else wcsncpy_s(newtitle, L"Message Session", _TRUNCATE); - - m_wOldStatus = m_wStatus; - if (m_idle != dwOldIdle || bChanged) { - if (bChanged) { - item.mask |= TCIF_TEXT; - item.pszText = m_wszTitle; - wcsncpy_s(m_wszTitle, newtitle, _TRUNCATE); - if (m_pWnd) - m_pWnd->updateTitle(m_cache->getNick()); - } - if (m_iTabID >= 0) { - TabCtrl_SetItem(m_hwndParent, m_iTabID, &item); - if (m_pContainer->cfg.flags.m_bSideBar) - m_pContainer->m_pSideBar->updateSession(this); - } - if (m_pContainer->m_hwndActive == m_hwnd && bChanged) - m_pContainer->UpdateTitle(m_hContact); - - m_pPanel.Invalidate(); - if (m_pWnd) - m_pWnd->Invalidate(); - } - - // care about MetaContacts and update the statusbar icon with the currently "most online" contact... - if (m_bIsMeta) { - PostMessage(m_hwnd, DM_OWNNICKCHANGED, 0, 0); - if (m_pContainer->cfg.flags.m_bUinStatusBar) - DM_UpdateLastMessage(); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void CMsgDialog::UpdateWindowIcon() -{ - if (m_hXStatusIcon) { - DestroyIcon(m_hXStatusIcon); - m_hXStatusIcon = nullptr; - } - - if (LPCSTR szProto = m_cache->getProto()) { - m_hTabIcon = m_hTabStatusIcon = GetMyContactIcon(&g_plugin.bMetaTab); - if (g_plugin.bUseXStatus) - m_hXStatusIcon = GetXStatusIcon(); - - SendDlgItemMessage(m_hwnd, IDC_PROTOCOL, BUTTONSETASDIMMED, m_bIsIdle, 0); - SendDlgItemMessage(m_hwnd, IDC_PROTOCOL, BM_SETIMAGE, IMAGE_ICON, (LPARAM)(m_hXStatusIcon ? m_hXStatusIcon : GetMyContactIcon(&g_plugin.bMetaBar))); - - if (m_pContainer->m_hwndActive == m_hwnd) - m_pContainer->SetIcon(this, m_hXStatusIcon ? m_hXStatusIcon : m_hTabIcon); - - if (m_pWnd) - m_pWnd->updateIcon(m_hXStatusIcon ? m_hXStatusIcon : m_hTabIcon); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// called whenever a group chat tab becomes active(either by switching tabs or activating a -// container window - -void CMsgDialog::UpdateWindowState(UINT msg) -{ - if (m_iTabID < 0) - return; - - if (msg == WM_ACTIVATE) { - if (m_pContainer->cfg.flags.m_bTransparent) { - uint32_t trans = LOWORD(m_pContainer->cfg.dwTransparency); - SetLayeredWindowAttributes(m_pContainer->m_hwnd, CSkin::m_ContainerColorKey, (uint8_t)trans, (m_pContainer->cfg.flags.m_bTransparent ? 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; - m_dwTickLastEvent = 0; - m_bDividerSet = false; - - if (m_pContainer->m_dwFlashingStarted != 0) { - m_pContainer->FlashContainer(0, 0); - m_pContainer->m_dwFlashingStarted = 0; - } - - if (m_si) { - g_chatApi.SetActiveSession(m_si); - m_hTabIcon = m_hTabStatusIcon; - - 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; - if (timerFlash.Stop() || m_iFlashIcon) { - FlashTab(false); - m_bCanFlashTab = FALSE; - m_iFlashIcon = nullptr; - } - - m_pContainer->cfg.flags.m_bNeedsUpdateTitle = false; - - if (m_bNeedCheckSize) - 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(); - } - else { - if (timerFlash.Stop()) { - FlashTab(false); - m_bCanFlashTab = false; - } - - if (m_bFlashClist) { - m_bFlashClist = false; - if (m_hFlashingEvent != 0) - g_clistApi.pfnRemoveEvent(m_hContact, m_hFlashingEvent); - m_hFlashingEvent = 0; - } - m_pContainer->cfg.flags.m_bNeedsUpdateTitle = false; - - if (m_bDeferredRemakeLog && !IsIconic(m_pContainer->m_hwnd)) { - RemakeLog(); - m_bDeferredRemakeLog = false; - } - - if (m_bNeedCheckSize) - PostMessage(m_hwnd, DM_SAVESIZE, 0, 0); - - m_pContainer->m_hIconTaskbarOverlay = nullptr; - m_pContainer->UpdateTitle(m_hContact); - - tabUpdateStatusBar(); - m_dwLastActivity = GetTickCount(); - m_pContainer->m_dwLastActivity = m_dwLastActivity; - - m_pContainer->m_pMenuBar->configureMenu(); - g_arUnreadWindows.remove(HANDLE(m_hContact)); - - m_pPanel.Invalidate(); - - if (m_bDeferredScroll) { - m_bDeferredScroll = false; - DM_ScrollToBottom(0, 1); - } - } - - DM_SetDBButtonStates(); - - if (m_bDelayedSplitter) { - m_bDelayedSplitter = false; - 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->cfg.flags.m_bSideBar) - m_pContainer->m_pSideBar->setActiveItem(this, msg == WM_ACTIVATE); - - if (m_pWnd) - m_pWnd->Invalidate(); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// generic handler for the WM_COPY message in message log/chat history richedit control(s). -// it filters out the invisible event boundary markers from the text copied to the clipboard. -// WINE Fix: overwrite clippboad data from original control data - -LRESULT CMsgDialog::WMCopyHandler(UINT msg, WPARAM wParam, LPARAM lParam) -{ - LRESULT result = mir_callNextSubclass(m_pLog->GetHwnd(), stubLogProc, msg, wParam, lParam); - - ptrA szFromStream(LOG()->GetRichTextRtf(true, true)); - if (szFromStream != nullptr) { - ptrW converted(mir_utf8decodeW(szFromStream)); - if (converted != nullptr) { - Utils::FilterEventMarkers(converted); - Utils_ClipboardCopy(converted); - } - } - - return result; -} +///////////////////////////////////////////////////////////////////////////////////////// +// Miranda NG: the free IM client for Microsoft* Windows* +// +// Copyright (C) 2012-23 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 +// +// Helper functions for the message dialog. + +#include "stdafx.h" + +UINT_PTR CALLBACK OpenFileSubclass(HWND hwnd, UINT msg, WPARAM, LPARAM lParam); + +///////////////////////////////////////////////////////////////////////////////////////// +// show the balloon tooltip control. + +void CMsgDialog::ActivateTooltip(int iCtrlId, const wchar_t *pwszMessage) +{ + if (!IsIconic(m_pContainer->m_hwnd) && m_pContainer->m_hwndActive == m_hwnd) + m_pPanel.showTip(iCtrlId, pwszMessage); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::AddLog() +{ + if (g_plugin.bUseDividers) { + if (g_plugin.bDividersUsePopupConfig) { + if (!MessageWindowOpened(0, this)) + DM_AddDivider(); + } + else { + if (!IsActive()) + DM_AddDivider(); + else if (m_pContainer->m_hwndActive != m_hwnd) + DM_AddDivider(); + } + } + + CSrmmBaseDialog::AddLog(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::AdjustBottomAvatarDisplay() +{ + GetAvatarVisibility(); + + bool bInfoPanel = m_pPanel.isActive(); + HBITMAP hbm = (bInfoPanel && m_pContainer->cfg.avatarMode != 3) ? m_hOwnPic : (m_ace ? m_ace->hbmPic : PluginConfig.g_hbmUnknown); + if (hbm) { + if (m_dynaSplitter == 0 || m_iSplitterY == 0) + LoadSplitter(); + m_dynaSplitter = m_iSplitterY - DPISCALEY_S(34); + DM_RecalcPictureSize(); + Utils::showDlgControl(m_hwnd, IDC_CONTACTPIC, m_bShowAvatar ? SW_SHOW : SW_HIDE); + InvalidateRect(GetDlgItem(m_hwnd, IDC_CONTACTPIC), nullptr, TRUE); + } + else { + Utils::showDlgControl(m_hwnd, IDC_CONTACTPIC, m_bShowAvatar ? SW_SHOW : SW_HIDE); + m_pic.cy = m_pic.cx = DPISCALEY_S(60); + InvalidateRect(GetDlgItem(m_hwnd, IDC_CONTACTPIC), nullptr, TRUE); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// calculates avatar layouting, based on splitter position to find the optimal size +// for the avatar w/o disturbing the toolbar too much. + +void CMsgDialog::CalcDynamicAvatarSize(BITMAP *bminfo) +{ + if (m_bWasBackgroundCreate || m_pContainer->cfg.flags.m_bDeferredConfigure || m_pContainer->cfg.flags.m_bCreateMinimized || IsIconic(m_pContainer->m_hwnd)) + return; // at this stage, the layout is not yet ready... + + RECT rc; + GetClientRect(m_hwnd, &rc); + + BOOL bBottomToolBar = m_pContainer->cfg.flags.m_bBottomToolbar; + BOOL bToolBar = m_pContainer->cfg.flags.m_bHideToolbar ? 0 : 1; + int iSplitOffset = m_bIsAutosizingInput ? 1 : 0; + + double picAspect = (bminfo->bmWidth == 0 || bminfo->bmHeight == 0) ? 1.0 : (double)(bminfo->bmWidth / (double)bminfo->bmHeight); + double picProjectedWidth = (double)((m_dynaSplitter - ((bBottomToolBar && bToolBar) ? DPISCALEX_S(24) : 0) + ((m_bShowUIElements) ? DPISCALEX_S(28) : DPISCALEX_S(2)))) * picAspect; + + if ((rc.right - (int)picProjectedWidth) > (m_iButtonBarReallyNeeds) && !PluginConfig.m_bAlwaysFullToolbarWidth && bToolBar) + m_iRealAvatarHeight = m_dynaSplitter + 3 + (m_bShowUIElements ? DPISCALEY_S(28) : DPISCALEY_S(2)); + else + m_iRealAvatarHeight = m_dynaSplitter + DPISCALEY_S(6) + DPISCALEY_S(iSplitOffset); + + m_iRealAvatarHeight -= ((bBottomToolBar && bToolBar) ? DPISCALEY_S(22) : 0); + + if (PluginConfig.m_LimitStaticAvatarHeight > 0) + m_iRealAvatarHeight = min(m_iRealAvatarHeight, PluginConfig.m_LimitStaticAvatarHeight); + + if (M.GetByte(m_hContact, "dontscaleavatars", M.GetByte("dontscaleavatars", 0))) + m_iRealAvatarHeight = min(bminfo->bmHeight, m_iRealAvatarHeight); + + double aspect = (bminfo->bmHeight != 0) ? (double)m_iRealAvatarHeight / (double)bminfo->bmHeight : 1.0; + double newWidth = (double)bminfo->bmWidth * aspect; + if (newWidth > (double)(rc.right) * 0.8) + newWidth = (double)(rc.right) * 0.8; + m_pic.cy = m_iRealAvatarHeight + 2; + m_pic.cx = (int)newWidth + 2; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::CloseTab() +{ + int iTabs = TabCtrl_GetItemCount(m_hwndParent); + if (iTabs == 1) { + 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; + m_pContainer->QueryClientArea(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); + DestroyWindow(m_hwnd); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// calculate the minimum required client height for the given message +// window layout +// +// the container will use this in its WM_GETMINMAXINFO handler to set +// minimum tracking height. + +void CMsgDialog::DetermineMinHeight() +{ + RECT rc; + LONG height = (m_pPanel.isActive() ? m_pPanel.getHeight() + 2 : 0); + if (!m_pContainer->cfg.flags.m_bHideToolbar) + height += DPISCALEY_S(24); // toolbar + GetClientRect(m_message.GetHwnd(), &rc); + height += rc.bottom; // input area + height += 40; // min space for log area and some padding + + m_pContainer->m_uChildMinHeight = height; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// convert rich edit code to bbcode (if wanted). Otherwise, strip all RTF formatting +// tags and return plain text + +static wchar_t tszRtfBreaks[] = L" \\\n\r"; + +static void CreateColorMap(CMStringW &Text, int iCount, COLORREF *pSrc, int *pDst) +{ + const wchar_t *pszText = Text; + int iIndex = 1; + + static const wchar_t *lpszFmt = L"\\red%[^ \x5b\\]\\green%[^ \x5b\\]\\blue%[^ \x5b;];"; + wchar_t szRed[10], szGreen[10], szBlue[10]; + + const wchar_t *p1 = wcsstr(pszText, L"\\colortbl"); + if (!p1) + return; + + const wchar_t *pEnd = wcschr(p1, '}'); + + const wchar_t *p2 = wcsstr(p1, L"\\red"); + + for (int i = 0; i < iCount; i++) + pDst[i] = -1; + + while (p2 && p2 < pEnd) { + if (swscanf(p2, lpszFmt, &szRed, &szGreen, &szBlue) > 0) { + for (int i = 0; i < iCount; i++) { + if (pSrc[i] == RGB(_wtoi(szRed), _wtoi(szGreen), _wtoi(szBlue))) + pDst[i] = iIndex; + } + } + iIndex++; + p1 = p2; + p1++; + + p2 = wcsstr(p1, L"\\red"); + } +} + +static int RtfColorToIndex(int iNumColors, int *pIndex, int iCol) +{ + for (int i = 0; i < iNumColors; i++) + if (pIndex[i] == iCol) + return i; + + return -1; +} + +BOOL CMsgDialog::DoRtfToTags(CMStringW &pszText) const +{ + if (pszText.IsEmpty()) + return FALSE; + + // used to filter out attributes which are already set for the default message input area font + auto &lf = m_pContainer->m_theme.logFonts[MSGFONTID_MESSAGEAREA]; + + // create an index of colors in the module and map them to + // corresponding colors in the RTF color table + int iNumColors = Utils::rtf_clrs.getCount(); + int *pIndex = (int *)_alloca(iNumColors * sizeof(int)); + COLORREF *pColors = (COLORREF *)_alloca(iNumColors * sizeof(COLORREF)); + for (int i = 0; i < iNumColors; i++) + pColors[i] = Utils::rtf_clrs[i].clr; + CreateColorMap(pszText, iNumColors, pColors, pIndex); + + // scan the file for rtf commands and remove or parse them + int idx = pszText.Find(L"\\pard"); + if (idx == -1) { + if ((idx = pszText.Find(L"\\ltrpar")) == -1) + return FALSE; + idx += 7; + } + else idx += 5; + + MODULEINFO *mi = (isChat()) ? m_si->pMI : nullptr; + + bool bInsideColor = false, bInsideUl = false; + CMStringW res; + + // iterate through all characters, if rtf control character found then take action + for (const wchar_t *p = pszText.GetString() + idx; *p;) { + switch (*p) { + case '\\': + if (p[1] == '\\' || p[1] == '{' || p[1] == '}') { // escaped characters + res.AppendChar(p[1]); + p += 2; break; + } + if (p[1] == '~') { // non-breaking space + res.AppendChar(0xA0); + p += 2; break; + } + + if (!wcsncmp(p, L"\\cf", 3)) { // foreground color + int iCol = _wtoi(p + 3); + int iInd = RtfColorToIndex(iNumColors, pIndex, iCol); + + if (iCol > 0) { + if (isChat()) { + if (mi && mi->bColor) { + if (iInd >= 0) { + if (!(res.IsEmpty() && m_pContainer->m_theme.fontColors[MSGFONTID_MESSAGEAREA] == pColors[iInd])) + res.AppendFormat(L"%%c%u", iInd); + } + else if (!res.IsEmpty()) + res.Append(L"%%C"); + } + } + else res.AppendFormat((iInd >= 0) ? (bInsideColor ? L"[/color][color=%s]" : L"[color=%s]") : (bInsideColor ? L"[/color]" : L""), Utils::rtf_clrs[iInd].szName); + } + + bInsideColor = iInd >= 0; + } + else if (!wcsncmp(p, L"\\highlight", 10)) { // background color + if (isChat()) { + if (mi && mi->bBkgColor) { + int iInd = RtfColorToIndex(iNumColors, pIndex, _wtoi(p + 10)); + if (iInd >= 0) { + // if the entry field is empty & the color passed is the back color, skip it + if (!(res.IsEmpty() && m_pContainer->m_theme.inputbg == pColors[iInd])) + res.AppendFormat(L"%%f%u", iInd); + } + else if (!res.IsEmpty()) + res.AppendFormat(L"%%F"); + } + } + } + else if (!wcsncmp(p, L"\\line", 5)) { // soft line break; + res.AppendChar('\n'); + } + else if (!wcsncmp(p, L"\\endash", 7)) { + res.AppendChar(0x2013); + } + else if (!wcsncmp(p, L"\\emdash", 7)) { + res.AppendChar(0x2014); + } + else if (!wcsncmp(p, L"\\bullet", 7)) { + res.AppendChar(0x2022); + } + else if (!wcsncmp(p, L"\\ldblquote", 10)) { + res.AppendChar(0x201C); + } + else if (!wcsncmp(p, L"\\rdblquote", 10)) { + res.AppendChar(0x201D); + } + else if (!wcsncmp(p, L"\\lquote", 7)) { + res.AppendChar(0x2018); + } + else if (!wcsncmp(p, L"\\rquote", 7)) { + res.AppendChar(0x2019); + } + else if (!wcsncmp(p, L"\\b", 2)) { //bold + if (isChat()) { + if (mi && mi->bBold) + res.Append((p[2] != '0') ? L"%b" : L"%B"); + } + else { + if (!(lf.lfWeight == FW_BOLD)) // only allow bold if the font itself isn't a bold one, otherwise just strip it.. + if (m_SendFormat) + res.Append((p[2] != '0') ? L"[b]" : L"[/b]"); + } + } + else if (!wcsncmp(p, L"\\i", 2)) { // italics + if (isChat()) { + if (mi && mi->bItalics) + res.Append((p[2] != '0') ? L"%i" : L"%I"); + } + else { + if (!lf.lfItalic && m_SendFormat) + res.Append((p[2] != '0') ? L"[i]" : L"[/i]"); + } + } + else if (!wcsncmp(p, L"\\strike", 7)) { // strike-out + if (!lf.lfStrikeOut && m_SendFormat) + res.Append((p[7] != '0') ? L"[s]" : L"[/s]"); + } + else if (!wcsncmp(p, L"\\ul", 3)) { // underlined + if (isChat()) { + if (mi && mi->bUnderline) + res.Append((p[3] != '0') ? L"%u" : L"%U"); + } + else { + if (!lf.lfUnderline && m_SendFormat) { + if (p[3] == 0 || wcschr(tszRtfBreaks, p[3])) { + res.Append(L"[u]"); + bInsideUl = true; + } + else if (!wcsncmp(p + 3, L"none", 4)) { + if (bInsideUl) + res.Append(L"[/u]"); + bInsideUl = false; + } + } + } + } + else if (!wcsncmp(p, L"\\tab", 4)) { // tab + res.AppendChar('\t'); + } + else if (p[1] == '\'') { // special character + if (p[2] != ' ' && p[2] != '\\') { + wchar_t tmp[10]; + + if (p[3] != ' ' && p[3] != '\\') { + wcsncpy(tmp, p + 2, 3); + tmp[3] = 0; + } + else { + wcsncpy(tmp, p + 2, 2); + tmp[2] = 0; + } + + // convert string containing char in hex format to int. + wchar_t *stoppedHere; + res.AppendChar(wcstol(tmp, &stoppedHere, 16)); + } + } + + p++; // skip initial slash + p += wcscspn(p, tszRtfBreaks); + if (*p == ' ') + p++; + break; + + case '{': // other RTF control characters + case '}': + p++; + break; + + case '%': // double % for stupid chat engine + if (isChat()) + res.Append(L"%%"); + else + res.AppendChar(*p); + p++; + break; + + default: // other text that should not be touched + res.AppendChar(*p++); + break; + } + } + + if (bInsideColor && !isChat()) + res.Append(L"[/color]"); + if (bInsideUl) + res.Append(L"[/u]"); + + pszText = res; + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::EnableSendButton(bool bMode) const +{ + SendDlgItemMessage(m_hwnd, IDOK, BUTTONSETASNORMAL, bMode, 0); + SendDlgItemMessage(m_hwnd, IDC_PIC, BUTTONSETASNORMAL, m_bEditNotesActive ? TRUE : (!bMode && m_iOpenJobs == 0) ? TRUE : FALSE, 0); + + HWND hwndOK = GetDlgItem(GetParent(GetParent(m_hwnd)), IDOK); + if (IsWindow(hwndOK)) + SendMessage(hwndOK, BUTTONSETASNORMAL, bMode, 0); +} + +void CMsgDialog::EnableSending(bool bMode) const +{ + m_message.SendMsg(EM_SETREADONLY, !bMode, 0); + Utils::enableDlgControl(m_hwnd, IDC_CLIST, bMode); + EnableSendButton(bMode); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::FindFirstEvent() +{ + int historyMode = g_plugin.getByte(m_hContact, SRMSGSET_LOADHISTORY, -1); + if (historyMode == -1) + historyMode = (int)g_plugin.getByte(SRMSGSET_LOADHISTORY, SRMSGDEFSET_LOADHISTORY); + + m_hDbEventFirst = db_event_firstUnread(m_hContact); + + if (m_bActualHistory) + historyMode = LOADHISTORY_COUNT; + + DBEVENTINFO dbei = {}; + DB::ECPTR pCursor(DB::EventsRev(m_hContact, m_hDbEventFirst)); + + switch (historyMode) { + case LOADHISTORY_COUNT: + int i; + + // ability to load only current session's history + if (m_bActualHistory) + i = m_cache->getSessionMsgCount(); + else + i = g_plugin.getWord(SRMSGSET_LOADCOUNT, SRMSGDEFSET_LOADCOUNT); + + for (; i > 0; i--) { + MEVENT hPrevEvent = pCursor.FetchNext(); + if (hPrevEvent == 0) + break; + + dbei.cbBlob = 0; + m_hDbEventFirst = hPrevEvent; + db_event_get(m_hDbEventFirst, &dbei); + if (!DbEventIsShown(&dbei)) + i++; + } + break; + + case LOADHISTORY_TIME: + if (m_hDbEventFirst == 0) + dbei.timestamp = time(0); + else + db_event_get(m_hDbEventFirst, &dbei); + + uint32_t firstTime = dbei.timestamp - 60 * g_plugin.getWord(SRMSGSET_LOADTIME, SRMSGDEFSET_LOADTIME); + + while (MEVENT hPrevEvent = pCursor.FetchNext()) { + dbei.cbBlob = 0; + db_event_get(hPrevEvent, &dbei); + if (dbei.timestamp < firstTime) + break; + m_hDbEventFirst = hPrevEvent; + } + break; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// returns != 0 when one of the installed keyboard layouts belongs to an rtl language +// used to find out whether we need to configure the message input box for bidirectional mode + +int CMsgDialog::FindRTLLocale() +{ + int result = 0; + + if (m_iHaveRTLLang == 0) { + HKL layouts[20]; + memset(layouts, 0, sizeof(layouts)); + GetKeyboardLayoutList(20, layouts); + for (int i = 0; i < 20 && layouts[i]; i++) { + uint16_t wCtype2[5]; + LCID lcid = MAKELCID(LOWORD(layouts[i]), 0); + GetStringTypeA(lcid, CT_CTYPE2, "���", 3, wCtype2); + if (wCtype2[0] == C2_RIGHTTOLEFT || wCtype2[1] == C2_RIGHTTOLEFT || wCtype2[2] == C2_RIGHTTOLEFT) + result = 1; + } + m_iHaveRTLLang = (result ? 1 : -1); + } + else result = m_iHaveRTLLang == 1 ? 1 : 0; + + return result; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::FlashOnClist(MEVENT hEvent, DBEVENTINFO *dbei) +{ + m_dwTickLastEvent = GetTickCount(); + + if ((GetForegroundWindow() != m_pContainer->m_hwnd || m_pContainer->m_hwndActive != m_hwnd) && !(dbei->flags & DBEF_SENT) && dbei->eventType == EVENTTYPE_MESSAGE) { + m_dwUnread++; + AddUnreadContact(m_hContact); + } + + if (hEvent == 0) + return; + + if (!g_plugin.bFlashOnClist) + return; + + if ((GetForegroundWindow() != m_pContainer->m_hwnd || m_pContainer->m_hwndActive != m_hwnd) && !(dbei->flags & DBEF_SENT) && dbei->eventType == EVENTTYPE_MESSAGE && !m_bFlashClist) { + CLISTEVENT cle = {}; + cle.hContact = m_hContact; + cle.hDbEvent = hEvent; + cle.hIcon = Skin_LoadIcon(SKINICON_EVENT_MESSAGE); + cle.pszService = MS_MSG_READMESSAGE; + g_clistApi.pfnAddEvent(&cle); + + m_bFlashClist = true; + m_hFlashingEvent = hEvent; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// flash a tab icon if mode = true, otherwise restore default icon +// store flashing state into bState + +void CMsgDialog::FlashTab(bool bInvertMode) +{ + if (bInvertMode) + m_bTabFlash = !m_bTabFlash; + + TCITEM item = {}; + item.mask = TCIF_IMAGE; + TabCtrl_SetItem(m_hwndParent, m_iTabID, &item); + if (m_pContainer->cfg.flags.m_bSideBar) + m_pContainer->m_pSideBar->updateSession(this); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// this translates formatting tags into rtf sequences... +// flags: loword = words only for simple * /_ formatting +// hiword = bbcode support (strip bbcodes if 0) + +static wchar_t *w_bbcodes_begin[] = { L"[b]", L"[i]", L"[u]", L"[s]", L"[color=" }; +static wchar_t *w_bbcodes_end[] = { L"[/b]", L"[/i]", L"[/u]", L"[/s]", L"[/color]" }; + +static wchar_t *formatting_strings_begin[] = { L"b1 ", L"i1 ", L"u1 ", L"s1 ", L"c1 " }; +static wchar_t *formatting_strings_end[] = { L"b0 ", L"i0 ", L"u0 ", L"s0 ", L"c0 " }; + +void CMsgDialog::FormatRaw(CMStringW &msg, int flags, bool isSent) +{ + bool clr_was_added = false; + int beginmark = 0, endmark = 0, tempmark = 0; + int i, endindex; + + if (m_dwFlags & MWF_LOG_BBCODE) { + beginmark = 0; + while (true) { + for (i = 0; i < _countof(w_bbcodes_begin); i++) + if ((tempmark = msg.Find(w_bbcodes_begin[i], 0)) != -1) + break; + + if (i >= _countof(w_bbcodes_begin)) + break; + + beginmark = tempmark; + endindex = i; + endmark = msg.Find(w_bbcodes_end[i], beginmark); + if (endindex == 4) { // color + int closing = msg.Find(L"]", beginmark); + bool was_added = false; + + if (closing == -1) { // must be an invalid [color=] tag w/o closing bracket + msg.SetAt(beginmark, ' '); + continue; + } + + CMStringW colorname = msg.Mid(beginmark + 7, closing - beginmark - 7); +search_again: + bool clr_found = false; + for (int ii = 0; ii < Utils::rtf_clrs.getCount(); ii++) { + auto &rtfc = Utils::rtf_clrs[ii]; + if (!wcsicmp(colorname, rtfc.szName)) { + closing = beginmark + 7 + (int)mir_wstrlen(rtfc.szName); + if (endmark != -1) { + msg.Delete(endmark, 8); + msg.Insert(endmark, L"c0 "); + } + msg.Delete(beginmark, closing - beginmark + 1); + + wchar_t szTemp[5]; + msg.Insert(beginmark, L"cxxx "); + mir_snwprintf(szTemp, L"%02d", MSGDLGFONTCOUNT + 13 + ii); + msg.SetAt(beginmark + 3, szTemp[0]); + msg.SetAt(beginmark + 4, szTemp[1]); + clr_found = true; + if (was_added) { + wchar_t wszTemp[100]; + mir_snwprintf(wszTemp, L"##col##%06u:%04u", endmark - closing, ii); + wszTemp[99] = 0; + msg.Insert(beginmark, wszTemp); + } + break; + } + } + if (!clr_found) { + int c_closing = colorname.Find(L"]"); + if (c_closing == -1) + c_closing = colorname.GetLength(); + const wchar_t *wszColname = colorname.c_str(); + if (endmark != -1 && c_closing > 2 && c_closing <= 6 && iswalnum(colorname[0]) && iswalnum(colorname[c_closing - 1])) { + Utils::RTF_ColorAdd(wszColname); + if (!was_added) { + clr_was_added = was_added = true; + goto search_again; + } + else goto invalid_code; + } + else { +invalid_code: + if (endmark != -1) + msg.Delete(endmark, 8); + if (closing != -1 && closing < endmark) + msg.Delete(beginmark, (closing - beginmark) + 1); + else + msg.SetAt(beginmark, ' '); + } + } + continue; + } + + if (endmark != -1) { + msg.Delete(endmark, 4); + msg.Insert(endmark, formatting_strings_end[i]); + } + msg.Delete(beginmark, 3); + msg.Insert(beginmark, L" "); + msg.Insert(beginmark, formatting_strings_begin[i]); + } + } + + if ((m_dwFlags & MWF_LOG_TEXTFORMAT) && msg.Find(L"://") == -1) { + while ((beginmark = msg.Find(L"*/_", beginmark)) != -1) { + wchar_t endmarker = msg[beginmark]; + if (LOWORD(flags)) { + if (beginmark > 0 && !iswspace(msg[beginmark - 1]) && !iswpunct(msg[beginmark - 1])) { + beginmark++; + continue; + } + + // search a corresponding endmarker which fulfills the criteria + INT_PTR mark = beginmark + 1; + while ((endmark = msg.Find(endmarker, mark)) != -1) { + if (iswpunct(msg[endmark + 1]) || iswspace(msg[endmark + 1]) || msg[endmark + 1] == 0 || wcschr(L"*/_", msg[endmark + 1]) != nullptr) + goto ok; + mark = endmark + 1; + } + break; + } + else { + if ((endmark = msg.Find(endmarker, beginmark + 1)) == -1) + break; + } +ok: + if ((endmark - beginmark) < 2) { + beginmark++; + continue; + } + + int index = 0; + switch (endmarker) { + case '*': + index = 0; + break; + case '/': + index = 1; + break; + case '_': + index = 2; + break; + } + + // check if the code enclosed by simple formatting tags is a valid smiley code and skip formatting if + // it really is one. + if (PluginConfig.g_SmileyAddAvail && (endmark > (beginmark + 1))) { + CMStringW smcode = msg.Mid(beginmark, (endmark - beginmark) + 1); + + SMADD_BATCHPARSE2 smbp = {}; + smbp.cbSize = sizeof(smbp); + smbp.Protocolname = m_cache->getActiveProto(); + smbp.flag = SAFL_TCHAR | SAFL_PATH | (isSent ? SAFL_OUTGOING : 0); + smbp.str = (wchar_t*)smcode.c_str(); + smbp.hContact = m_hContact; + + SMADD_BATCHPARSERES *smbpr = (SMADD_BATCHPARSERES *)CallService(MS_SMILEYADD_BATCHPARSE, 0, (LPARAM)&smbp); + if (smbpr) { + CallService(MS_SMILEYADD_BATCHFREE, 0, (LPARAM)smbpr); + beginmark = endmark + 1; + continue; + } + } + msg.Delete(endmark, 1); + msg.Insert(endmark, formatting_strings_end[index]); + msg.Delete(beginmark, 1); + msg.Insert(beginmark, formatting_strings_begin[index]); + } + } + + m_bClrAdded = clr_was_added; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// format the title bar string for IM chat sessions using placeholders. +// the caller must mir_free() the returned string + +static wchar_t* Trunc500(wchar_t *str) +{ + if (mir_wstrlen(str) > 500) + str[500] = 0; + return str; +} + +bool CMsgDialog::FormatTitleBar(const wchar_t *szFormat, CMStringW &dest) +{ + for (const wchar_t *src = szFormat; *src; src++) { + if (*src != '%') { + dest.AppendChar(*src); + continue; + } + + switch (*++src) { + case 'n': + dest.Append(m_cache->getNick()); + break; + + case 'p': + case 'a': + dest.Append(m_cache->getRealAccount()); + break; + + case 's': + dest.Append(m_wszStatus); + break; + + case 'u': + dest.Append(m_cache->getUIN()); + break; + + case 'c': + dest.Append(!mir_wstrcmp(m_pContainer->m_wszName, L"default") ? TranslateT("Default container") : m_pContainer->m_wszName); + break; + + case 'o': + { + const char *szProto = m_cache->getActiveProto(); + if (szProto) + dest.Append(_A2T(szProto)); + } + break; + + case 'x': + { + uint8_t xStatus = m_cache->getXStatusId(); + if (m_wStatus != ID_STATUS_OFFLINE && xStatus > 0 && xStatus <= 31) { + ptrW szXStatus(db_get_wsa(m_hContact, m_szProto, "XStatusName")); + dest.Append((szXStatus != nullptr) ? Trunc500(szXStatus) : xStatusDescr[xStatus - 1]); + } + } + break; + + case 'm': + { + uint8_t xStatus = m_cache->getXStatusId(); + if (m_wStatus != ID_STATUS_OFFLINE && xStatus > 0 && xStatus <= 31) { + ptrW szXStatus(db_get_wsa(m_hContact, m_szProto, "XStatusName")); + dest.Append((szXStatus != nullptr) ? Trunc500(szXStatus) : xStatusDescr[xStatus - 1]); + } + else dest.Append(m_wszStatus[0] ? m_wszStatus : L"(undef)"); + } + break; + + // status message (%T will skip the "No status message" for empty messages) + case 't': + case 'T': + { + ptrW tszStatus(m_cache->getNormalizedStatusMsg(m_cache->getStatusMsg(), true)); + if (tszStatus) + dest.Append(tszStatus); + else if (*src == 't') + dest.Append(TranslateT("No status message")); + } + break; + + case 'g': + { + ptrW tszGroup(Clist_GetGroup(m_hContact)); + if (tszGroup != nullptr) + dest.Append(tszGroup); + } + break; + + case 0: // wrongly formed format string + return true; + } + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// retrieve the visiblity of the avatar window, depending on the global setting +// and local mode + +bool CMsgDialog::GetAvatarVisibility() +{ + uint8_t bAvatarMode = m_pContainer->cfg.avatarMode; + uint8_t bOwnAvatarMode = m_pContainer->cfg.ownAvatarMode; + char hideOverride = (char)M.GetByte(m_hContact, "hideavatar", -1); + + // infopanel visible, consider own avatar display + m_bShowAvatar = false; + if (m_si) + bAvatarMode = 1; + + if (m_pPanel.isActive() && bAvatarMode != 3) { + if (!bOwnAvatarMode) { + m_bShowAvatar = (m_hOwnPic && m_hOwnPic != PluginConfig.g_hbmUnknown); + if (!m_hwndContactPic) + m_hwndContactPic = CreateWindowEx(WS_EX_TOPMOST, AVATAR_CONTROL_CLASS, L"", WS_VISIBLE | WS_CHILD, 1, 1, 1, 1, GetDlgItem(m_hwnd, IDC_CONTACTPIC), (HMENU)nullptr, nullptr, nullptr); + } + + switch (bAvatarMode) { + case 2: + m_bShowInfoAvatar = false; + break; + case 0: + m_bShowInfoAvatar = true; + case 1: + HBITMAP hbm = ((m_ace && !(m_ace->dwFlags & AVS_HIDEONCLIST)) ? m_ace->hbmPic : nullptr); + if (hbm == nullptr && !bAvatarMode) { + m_bShowInfoAvatar = false; + break; + } + + if (!m_hwndPanelPic) { + m_hwndPanelPic = CreateWindowEx(WS_EX_TOPMOST, AVATAR_CONTROL_CLASS, L"", WS_VISIBLE | WS_CHILD, 1, 1, 1, 1, m_hwndPanelPicParent, (HMENU)7000, nullptr, nullptr); + if (m_hwndPanelPic) + SendMessage(m_hwndPanelPic, AVATAR_SETAEROCOMPATDRAWING, 0, TRUE); + } + + if (bAvatarMode != 0) + m_bShowInfoAvatar = (hbm && hbm != PluginConfig.g_hbmUnknown); + break; + } + + if (m_bShowInfoAvatar) + m_bShowInfoAvatar = hideOverride == 0 ? false : m_bShowInfoAvatar; + else + m_bShowInfoAvatar = hideOverride == 1 ? true : m_bShowInfoAvatar; + + Utils::setAvatarContact(m_hwndPanelPic, m_hContact); + SendMessage(m_hwndContactPic, AVATAR_SETPROTOCOL, 0, (LPARAM)m_cache->getActiveProto()); + } + else { + m_bShowInfoAvatar = false; + + switch (bAvatarMode) { + case 0: // globally on + m_bShowAvatar = true; + LBL_Check: + if (!m_hwndContactPic) + m_hwndContactPic = CreateWindowEx(WS_EX_TOPMOST, AVATAR_CONTROL_CLASS, L"", WS_VISIBLE | WS_CHILD, 1, 1, 1, 1, GetDlgItem(m_hwnd, IDC_CONTACTPIC), (HMENU)nullptr, nullptr, nullptr); + break; + case 2: // globally OFF + m_bShowAvatar = false; + break; + case 3: // on, if present + case 1: + HBITMAP hbm = (m_ace && !(m_ace->dwFlags & AVS_HIDEONCLIST)) ? m_ace->hbmPic : nullptr; + m_bShowAvatar = (hbm && hbm != PluginConfig.g_hbmUnknown); + goto LBL_Check; + } + + if (m_bShowAvatar) + m_bShowAvatar = hideOverride == 0 ? 0 : m_bShowAvatar; + else + m_bShowAvatar = hideOverride == 1 ? 1 : m_bShowAvatar; + + // reloads avatars + if (m_hwndPanelPic) { // shows contact or user picture, depending on panel visibility + SendMessage(m_hwndContactPic, AVATAR_SETPROTOCOL, 0, (LPARAM)m_cache->getActiveProto()); + Utils::setAvatarContact(m_hwndPanelPic, m_hContact); + } + else Utils::setAvatarContact(m_hwndContactPic, m_hContact); + } + return m_bShowAvatar; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::GetClientIcon() +{ + if (m_hClientIcon) + DestroyIcon(m_hClientIcon); + + m_hClientIcon = nullptr; + if (ServiceExists(MS_FP_GETCLIENTICONT)) { + ptrW tszMirver(db_get_wsa(m_cache->getActiveContact(), m_cache->getActiveProto(), "MirVer")); + if (tszMirver) + m_hClientIcon = Finger_GetClientIcon(tszMirver, 1); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +HICON CMsgDialog::GetMyContactIcon(const CMOption<bool> *opt) +{ + int bUseMeta = (opt == nullptr) ? false : M.GetByte(opt->GetDBSettingName(), mir_strcmp(opt->GetDBSettingName(), "MetaiconTab") == 0); + if (bUseMeta) + return Skin_LoadProtoIcon(m_cache->getProto(), m_cache->getStatus()); + return Skin_LoadProtoIcon(m_cache->getActiveProto(), m_cache->getActiveStatus()); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::GetMyNick() +{ + ptrW tszNick(Contact::GetInfo(CNF_CUSTOMNICK, 0, m_cache->getActiveProto())); + if (tszNick == nullptr) + tszNick = Contact::GetInfo(CNF_NICK, 0, m_cache->getActiveProto()); + if (tszNick != nullptr) { + if (mir_wstrlen(tszNick) == 0 || !mir_wstrcmp(tszNick, TranslateT("'(Unknown contact)'"))) + wcsncpy_s(m_wszMyNickname, (m_myUin[0] ? m_myUin : TranslateT("'(Unknown contact)'")), _TRUNCATE); + else + wcsncpy_s(m_wszMyNickname, tszNick, _TRUNCATE); + } + else wcsncpy_s(m_wszMyNickname, L"<undef>", _TRUNCATE); // same here +} + +///////////////////////////////////////////////////////////////////////////////////////// +// retrieve both buddys and my own UIN for a message session and store them in the message +// window *dat respects metacontacts and uses the current protocol if the contact is a MC + +void CMsgDialog::GetMYUIN() +{ + ptrW uid(Contact::GetInfo(CNF_DISPLAYUID, 0, m_cache->getActiveProto())); + if (uid != nullptr) + wcsncpy_s(m_myUin, uid, _TRUNCATE); + else + m_myUin[0] = 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// returns the status of Send button + +LRESULT CMsgDialog::GetSendButtonState() +{ + return m_btnOk.SendMsg(BUTTONGETSTATEID, TRUE, 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// reads send format and configures the toolbar buttons +// if mode == 0, int only configures the buttons and does not change send format + +void CMsgDialog::GetSendFormat() +{ + m_SendFormat = M.GetDword(m_hContact, "sendformat", g_plugin.bSendFormat); + if (m_SendFormat == -1) // per contact override to disable it.. + m_SendFormat = 0; + else if (m_SendFormat == 0) + m_SendFormat = g_plugin.bSendFormat; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +HICON CMsgDialog::GetXStatusIcon() const +{ + uint8_t xStatus = m_cache->getXStatusId(); + if (xStatus == 0) + return nullptr; + + if (!ProtoServiceExists(m_cache->getActiveProto(), PS_GETCUSTOMSTATUSICON)) + return nullptr; + + return (HICON)(CallProtoService(m_cache->getActiveProto(), PS_GETCUSTOMSTATUSICON, xStatus, 0)); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// paste contents of the clipboard into the message input area and send it immediately + +void CMsgDialog::HandlePasteAndSend() +{ + // is feature disabled? + if (!g_plugin.bPasteAndSend) { + ActivateTooltip(IDC_SRMM_MESSAGE, TranslateT("The 'paste and send' feature is disabled. You can enable it on the 'General' options page in the 'Sending messages' section")); + return; + } + + m_message.SendMsg(EM_PASTESPECIAL, CF_UNICODETEXT, 0); + if (GetWindowTextLength(m_message.GetHwnd()) > 0) + SendMessage(m_hwnd, WM_COMMAND, IDOK, 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// convert the avatar bitmap to icon format so that it can be used on the task bar +// tries to keep correct aspect ratio of the avatar image +// +// @param dat: _MessageWindowData* pointer to the window data +// @return HICON: the icon handle + +HICON CMsgDialog::IconFromAvatar() const +{ + if (!ServiceExists(MS_AV_GETAVATARBITMAP)) + return nullptr; + + AVATARCACHEENTRY *ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, m_hContact, 0); + if (ace == nullptr || ace->hbmPic == nullptr) + return nullptr; + + LONG lIconSize = Win7Taskbar->getIconSize(); + double dNewWidth, dNewHeight; + Utils::scaleAvatarHeightLimited(ace->hbmPic, dNewWidth, dNewHeight, lIconSize); + + // resize picture to fit it on the task bar, use an image list for converting it to + // 32bpp icon format. hTaskbarIcon will cache it until avatar is changed + HBITMAP hbmResized = ::Image_Resize(ace->hbmPic, RESIZEBITMAP_STRETCH, dNewWidth, dNewHeight); + HIMAGELIST hIml_c = ::ImageList_Create(lIconSize, lIconSize, ILC_COLOR32 | ILC_MASK, 1, 0); + + RECT rc = { 0, 0, lIconSize, lIconSize }; + + HDC hdc = ::GetDC(m_pContainer->m_hwnd); + HDC dc = ::CreateCompatibleDC(hdc); + HDC dcResized = ::CreateCompatibleDC(hdc); + + ReleaseDC(m_pContainer->m_hwnd, hdc); + + HBITMAP hbmNew = CSkin::CreateAeroCompatibleBitmap(rc, dc); + HBITMAP hbmOld = reinterpret_cast<HBITMAP>(::SelectObject(dc, hbmNew)); + HBITMAP hbmOldResized = reinterpret_cast<HBITMAP>(::SelectObject(dcResized, hbmResized)); + + LONG ix = (lIconSize - (LONG)dNewWidth) / 2; + LONG iy = (lIconSize - (LONG)dNewHeight) / 2; + CSkin::m_default_bf.SourceConstantAlpha = M.GetByte("taskBarIconAlpha", 255); + GdiAlphaBlend(dc, ix, iy, (LONG)dNewWidth, (LONG)dNewHeight, dcResized, 0, 0, (LONG)dNewWidth, (LONG)dNewHeight, CSkin::m_default_bf); + + CSkin::m_default_bf.SourceConstantAlpha = 255; + ::SelectObject(dc, hbmOld); + ::ImageList_Add(hIml_c, hbmNew, nullptr); + ::DeleteObject(hbmNew); + ::DeleteDC(dc); + + ::SelectObject(dcResized, hbmOldResized); + if (hbmResized != ace->hbmPic) + ::DeleteObject(hbmResized); + ::DeleteDC(dcResized); + HICON hIcon = ::ImageList_GetIcon(hIml_c, 0, ILD_NORMAL); + ::ImageList_RemoveAll(hIml_c); + ::ImageList_Destroy(hIml_c); + return hIcon; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// is window active or not? + +bool CMsgDialog::IsActive() const +{ + return m_pContainer->IsActive() && m_pContainer->m_hwndActive == m_hwnd; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// read keyboard state and return the state of the modifier keys + +void CMsgDialog::KbdState(bool &isShift, bool &isControl, bool &isAlt) +{ + GetKeyboardState(kstate); + isShift = (kstate[VK_SHIFT] & 0x80) != 0; + isControl = (kstate[VK_CONTROL] & 0x80) != 0; + isAlt = (kstate[VK_MENU] & 0x80) != 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::LimitMessageText(int iLen) +{ + if (this != nullptr) + m_message.SendMsg(EM_EXLIMITTEXT, 0, iLen); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::LoadContactAvatar() +{ + m_ace = Utils::loadAvatarFromAVS(m_bIsMeta ? db_mc_getSrmmSub(m_hContact) : m_hContact); + + BITMAP bm; + if (m_ace && m_ace->hbmPic) + GetObject(m_ace->hbmPic, sizeof(bm), &bm); + else if (m_ace == nullptr) + GetObject(PluginConfig.g_hbmUnknown, sizeof(bm), &bm); + else + return; + + AdjustBottomAvatarDisplay(); + CalcDynamicAvatarSize(&bm); + + if (!m_pPanel.isActive() || m_pContainer->cfg.avatarMode == 3) { + m_iRealAvatarHeight = 0; + PostMessage(m_hwnd, WM_SIZE, 0, 0); + } + else if (m_pPanel.isActive()) + GetAvatarVisibility(); + + if (m_pWnd != nullptr) + m_pWnd->verifyDwmState(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::LoadOwnAvatar() +{ + if (ServiceExists(MS_AV_GETMYAVATAR)) + m_ownAce = (AVATARCACHEENTRY *)CallService(MS_AV_GETMYAVATAR, 0, (LPARAM)(m_cache->getActiveProto())); + else + m_ownAce = nullptr; + + if (m_ownAce) + m_hOwnPic = m_ownAce->hbmPic; + else + m_hOwnPic = PluginConfig.g_hbmUnknown; + + if (m_pPanel.isActive() && m_pContainer->cfg.avatarMode != 3) { + BITMAP bm; + + m_iRealAvatarHeight = 0; + AdjustBottomAvatarDisplay(); + GetObject(m_hOwnPic, sizeof(bm), &bm); + CalcDynamicAvatarSize(&bm); + Resize(); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::LoadSettings() +{ + m_clrInputBG = m_pContainer->m_theme.inputbg; + LoadMsgDlgFont(FONTSECTION_IM, MSGFONTID_MESSAGEAREA, nullptr, &m_clrInputFG); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::LoadSplitter() +{ + if (m_bIsAutosizingInput) { + m_iSplitterY = (m_pContainer->cfg.flags.m_bBottomToolbar) ? DPISCALEY_S(46 + 22) : DPISCALEY_S(46); + + if (CSkin::m_skinEnabled && !SkinItems[ID_EXTBKINPUTAREA].IGNORED) + m_iSplitterY += (SkinItems[ID_EXTBKINPUTAREA].MARGIN_BOTTOM + SkinItems[ID_EXTBKINPUTAREA].MARGIN_TOP - 2); + return; + } + + if (!m_bSplitterOverride) { + if (!m_pContainer->cfg.fPrivate) + m_iSplitterY = (int)M.GetDword("splitsplity", 60); + else + m_iSplitterY = m_pContainer->cfg.iSplitterY; + } + else m_iSplitterY = (int)M.GetDword(m_hContact, "splitsplity", M.GetDword("splitsplity", 60)); + + if (m_iSplitterY < MINSPLITTERY) + m_iSplitterY = 150; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::LogEvent(DBEVENTINFO &dbei) +{ + if (m_iLogMode != WANT_BUILTIN_LOG) { + dbei.flags |= DBEF_TEMPORARY; + + MEVENT hDbEvent = db_event_add(m_hContact, &dbei); + if (hDbEvent) { + m_pLog->LogEvents(hDbEvent, 1, true); + db_event_delete(hDbEvent); + } + } + else LOG()->LogEvents(0, 1, true, &dbei); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// draw various elements of the message window, like avatar(s), info panel fields +// and the color formatting menu + +int CMsgDialog::MsgWindowDrawHandler(DRAWITEMSTRUCT *dis) +{ + if ((dis->hwndItem == GetDlgItem(m_hwnd, IDC_CONTACTPIC) && m_bShowAvatar) || (dis->hwndItem == m_hwnd && m_pPanel.isActive())) { + HBITMAP hbmAvatar = m_ace ? m_ace->hbmPic : PluginConfig.g_hbmUnknown; + if (hbmAvatar == nullptr) + return TRUE; + + int top, cx, cy; + RECT rcClient, rcFrame; + bool bPanelPic = (dis->hwndItem == m_hwnd); + if (bPanelPic && !m_bShowInfoAvatar) + return TRUE; + + RECT rc; + GetClientRect(m_hwnd, &rc); + if (bPanelPic) { + rcClient = dis->rcItem; + cx = (rcClient.right - rcClient.left); + cy = (rcClient.bottom - rcClient.top) + 1; + } + else { + GetClientRect(dis->hwndItem, &rcClient); + cx = rcClient.right; + cy = rcClient.bottom; + } + + if (cx < 5 || cy < 5) + return TRUE; + + HDC hdcDraw = CreateCompatibleDC(dis->hDC); + HBITMAP hbmDraw = CreateCompatibleBitmap(dis->hDC, cx, cy); + HBITMAP hbmOld = (HBITMAP)SelectObject(hdcDraw, hbmDraw); + + bool bAero = M.isAero(); + + HRGN clipRgn = nullptr; + HBRUSH hOldBrush = (HBRUSH)SelectObject(hdcDraw, bAero ? (HBRUSH)GetStockObject(HOLLOW_BRUSH) : GetSysColorBrush(COLOR_3DFACE)); + rcFrame = rcClient; + + if (!bPanelPic) { + top = (cy - m_pic.cy) / 2; + RECT rcEdge = { 0, top, m_pic.cx, top + m_pic.cy }; + if (CSkin::m_skinEnabled) + CSkin::SkinDrawBG(dis->hwndItem, m_pContainer->m_hwnd, m_pContainer, &dis->rcItem, hdcDraw); + else if (PluginConfig.m_fillColor) { + HBRUSH br = CreateSolidBrush(PluginConfig.m_fillColor); + FillRect(hdcDraw, &rcFrame, br); + DeleteObject(br); + } + else if (bAero && CSkin::m_pCurrentAeroEffect) { + COLORREF clr = PluginConfig.m_tbBackgroundHigh ? PluginConfig.m_tbBackgroundHigh : + (CSkin::m_pCurrentAeroEffect ? CSkin::m_pCurrentAeroEffect->m_clrToolbar : 0xf0f0f0); + + HBRUSH br = CreateSolidBrush(clr); + FillRect(hdcDraw, &rcFrame, br); + DeleteObject(br); + } + else FillRect(hdcDraw, &rcFrame, GetSysColorBrush(COLOR_3DFACE)); + + HPEN hPenBorder = CreatePen(PS_SOLID, 1, CSkin::m_avatarBorderClr); + HPEN hPenOld = (HPEN)SelectObject(hdcDraw, hPenBorder); + + if (CSkin::m_bAvatarBorderType == 1) + Rectangle(hdcDraw, rcEdge.left, rcEdge.top, rcEdge.right, rcEdge.bottom); + else if (CSkin::m_bAvatarBorderType == 2) { + clipRgn = CreateRoundRectRgn(rcEdge.left, rcEdge.top, rcEdge.right + 1, rcEdge.bottom + 1, 6, 6); + SelectClipRgn(hdcDraw, clipRgn); + + HBRUSH hbr = CreateSolidBrush(CSkin::m_avatarBorderClr); + FrameRgn(hdcDraw, clipRgn, hbr, 1, 1); + DeleteObject(hbr); + DeleteObject(clipRgn); + } + + SelectObject(hdcDraw, hPenOld); + DeleteObject(hPenBorder); + } + + if (bPanelPic) { + bool bBorder = (CSkin::m_bAvatarBorderType ? true : false); + + int border_off = bBorder ? 1 : 0; + int iMaxHeight = m_iPanelAvatarY - (bBorder ? 2 : 0); + int iMaxWidth = m_iPanelAvatarX - (bBorder ? 2 : 0); + + rcFrame.left = rcFrame.top = 0; + rcFrame.right = (rcClient.right - rcClient.left); + rcFrame.bottom = (rcClient.bottom - rcClient.top); + + rcFrame.left = rcFrame.right - (LONG)m_iPanelAvatarX; + rcFrame.bottom = (LONG)m_iPanelAvatarY; + + int height_off = (cy - iMaxHeight - (bBorder ? 2 : 0)) / 2; + rcFrame.top += height_off; + rcFrame.bottom += height_off; + + SendMessage(m_hwndPanelPic, AVATAR_SETAEROCOMPATDRAWING, 0, bAero ? TRUE : FALSE); + SetWindowPos(m_hwndPanelPic, HWND_TOP, rcFrame.left + border_off, rcFrame.top + border_off, + iMaxWidth, iMaxHeight, SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS | SWP_DEFERERASE | SWP_NOSENDCHANGING); + } + + SelectObject(hdcDraw, hOldBrush); + if (!bPanelPic) + BitBlt(dis->hDC, 0, 0, cx, cy, hdcDraw, 0, 0, SRCCOPY); + SelectObject(hdcDraw, hbmOld); + DeleteObject(hbmDraw); + DeleteDC(hdcDraw); + return TRUE; + } + + if (dis->hwndItem == GetDlgItem(m_hwnd, IDC_STATICTEXT) || dis->hwndItem == GetDlgItem(m_hwnd, IDC_LOGFROZENTEXT)) { + wchar_t szWindowText[256]; + if (CSkin::m_skinEnabled) { + SetTextColor(dis->hDC, CSkin::m_DefaultFontColor); + CSkin::SkinDrawBG(dis->hwndItem, m_pContainer->m_hwnd, m_pContainer, &dis->rcItem, dis->hDC); + } + else { + SetTextColor(dis->hDC, GetSysColor(COLOR_BTNTEXT)); + CSkin::FillBack(dis->hDC, &dis->rcItem); + } + GetWindowText(dis->hwndItem, szWindowText, _countof(szWindowText)); + szWindowText[255] = 0; + SetBkMode(dis->hDC, TRANSPARENT); + DrawText(dis->hDC, szWindowText, -1, &dis->rcItem, DT_SINGLELINE | DT_VCENTER | DT_NOCLIP | DT_END_ELLIPSIS); + return TRUE; + } + + if (dis->hwndItem == GetDlgItem(m_hwnd, IDC_STATICERRORICON)) { + if (CSkin::m_skinEnabled) + CSkin::SkinDrawBG(dis->hwndItem, m_pContainer->m_hwnd, m_pContainer, &dis->rcItem, dis->hDC); + else + CSkin::FillBack(dis->hDC, &dis->rcItem); + DrawIconEx(dis->hDC, (dis->rcItem.right - dis->rcItem.left) / 2 - 8, (dis->rcItem.bottom - dis->rcItem.top) / 2 - 8, + PluginConfig.g_iconErr, 16, 16, 0, nullptr, DI_NORMAL); + return TRUE; + } + + if (dis->CtlType == ODT_MENU && m_pPanel.isHovered()) { + DrawMenuItem(dis, (HICON)dis->itemData, 0); + return TRUE; + } + + return Menu_DrawItem((LPARAM)dis); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int CMsgDialog::MsgWindowUpdateMenu(HMENU submenu, int menuID) +{ + bool bInfoPanel = m_pPanel.isActive(); + + if (menuID == MENU_TABCONTEXT) { + EnableMenuItem(submenu, ID_TABMENU_LEAVECHATROOM, (isChat() && ProtoServiceExists(m_szProto, PS_LEAVECHAT)) ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(submenu, ID_TABMENU_ATTACHTOCONTAINER, (M.GetByte("useclistgroups", 0) || M.GetByte("singlewinmode", 0)) ? MF_GRAYED : MF_ENABLED); + EnableMenuItem(submenu, ID_TABMENU_CLEARSAVEDTABPOSITION, (M.GetDword(m_hContact, "tabindex", -1) != -1) ? MF_ENABLED : MF_GRAYED); + } + else if (menuID == MENU_PICMENU) { + wchar_t *szText = nullptr; + char avOverride = (char)M.GetByte(m_hContact, "hideavatar", -1); + HMENU visMenu = GetSubMenu(submenu, 0); + BOOL picValid = bInfoPanel ? (m_hOwnPic != nullptr) : (m_ace && m_ace->hbmPic && m_ace->hbmPic != PluginConfig.g_hbmUnknown); + + MENUITEMINFO mii = { 0 }; + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STRING; + + EnableMenuItem(submenu, ID_PICMENU_SAVETHISPICTUREAS, picValid ? MF_ENABLED : MF_GRAYED); + + CheckMenuItem(visMenu, ID_VISIBILITY_DEFAULT, avOverride == -1 ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(visMenu, ID_VISIBILITY_HIDDENFORTHISCONTACT, avOverride == 0 ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(visMenu, ID_VISIBILITY_VISIBLEFORTHISCONTACT, avOverride == 1 ? MF_CHECKED : MF_UNCHECKED); + + CheckMenuItem(submenu, ID_PICMENU_ALWAYSKEEPTHEBUTTONBARATFULLWIDTH, PluginConfig.m_bAlwaysFullToolbarWidth ? MF_CHECKED : MF_UNCHECKED); + if (!bInfoPanel) { + EnableMenuItem(submenu, ID_PICMENU_SETTINGS, ServiceExists(MS_AV_GETAVATARBITMAP) ? MF_ENABLED : MF_GRAYED); + szText = TranslateT("Contact picture settings..."); + EnableMenuItem(submenu, 0, MF_BYPOSITION | MF_ENABLED); + } + else { + EnableMenuItem(submenu, 0, MF_BYPOSITION | MF_GRAYED); + EnableMenuItem(submenu, ID_PICMENU_SETTINGS, (ServiceExists(MS_AV_SETMYAVATARW) && CallService(MS_AV_CANSETMYAVATAR, (WPARAM)(m_cache->getActiveProto()), 0)) ? MF_ENABLED : MF_GRAYED); + szText = TranslateT("Set your avatar..."); + } + mii.dwTypeData = szText; + mii.cch = (int)mir_wstrlen(szText) + 1; + SetMenuItemInfo(submenu, ID_PICMENU_SETTINGS, FALSE, &mii); + } + else if (menuID == MENU_PANELPICMENU) { + HMENU visMenu = GetSubMenu(submenu, 0); + char avOverride = (char)M.GetByte(m_hContact, "hideavatar", -1); + + CheckMenuItem(visMenu, ID_VISIBILITY_DEFAULT, avOverride == -1 ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(visMenu, ID_VISIBILITY_HIDDENFORTHISCONTACT, avOverride == 0 ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(visMenu, ID_VISIBILITY_VISIBLEFORTHISCONTACT, avOverride == 1 ? MF_CHECKED : MF_UNCHECKED); + + EnableMenuItem(submenu, ID_PICMENU_SETTINGS, ServiceExists(MS_AV_GETAVATARBITMAP) ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(submenu, ID_PANELPICMENU_SAVETHISPICTUREAS, (m_ace && m_ace->hbmPic && m_ace->hbmPic != PluginConfig.g_hbmUnknown) ? MF_ENABLED : MF_GRAYED); + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// update state of the container - this is called whenever a tab becomes active, no matter how and +// deals with various things like updating the title bar, removing flashing icons, updating the +// session list, switching the keyboard layout (autolocale active) and the general container status. +// +// it protects itself from being called more than once per session activation and is valid for +// normal IM sessions *only*. Group chat sessions have their own activation handler (see chat/window.c) + + +///////////////////////////////////////////////////////////////////////////////////////// + +int CMsgDialog::MsgWindowMenuHandler(int selection, int menuId) +{ + if (menuId == MENU_PICMENU || menuId == MENU_PANELPICMENU || menuId == MENU_TABCONTEXT) { + switch (selection) { + case ID_TABMENU_ATTACHTOCONTAINER: + SelectContainer(); + return 1; + case ID_TABMENU_CONTAINEROPTIONS: + m_pContainer->OptionsDialog(); + return 1; + case ID_TABMENU_CLOSECONTAINER: + SendMessage(m_pContainer->m_hwnd, WM_CLOSE, 0, 0); + return 1; + case ID_TABMENU_CLOSETAB: + PostMessage(m_hwnd, WM_CLOSE, 1, 0); + return 1; + case ID_TABMENU_SAVETABPOSITION: + db_set_dw(m_hContact, SRMSGMOD_T, "tabindex", m_iTabID * 100); + break; + case ID_TABMENU_CLEARSAVEDTABPOSITION: + db_unset(m_hContact, SRMSGMOD_T, "tabindex"); + break; + case ID_TABMENU_LEAVECHATROOM: + if (isChat()) { + char *szProto = Proto_GetBaseAccountName(m_hContact); + if (szProto) + CallProtoService(szProto, PS_LEAVECHAT, m_hContact, 0); + } + return 1; + + case ID_VISIBILITY_DEFAULT: + case ID_VISIBILITY_HIDDENFORTHISCONTACT: + case ID_VISIBILITY_VISIBLEFORTHISCONTACT: + { + uint8_t avOverrideMode; + if (selection == ID_VISIBILITY_DEFAULT) + avOverrideMode = -1; + else if (selection == ID_VISIBILITY_VISIBLEFORTHISCONTACT) + avOverrideMode = 1; + else + avOverrideMode = 0; + db_set_b(m_hContact, SRMSGMOD_T, "hideavatar", avOverrideMode); + } + + ShowPicture(false); + Resize(); + DM_ScrollToBottom(0, 1); + return 1; + + case ID_PICMENU_ALWAYSKEEPTHEBUTTONBARATFULLWIDTH: + PluginConfig.m_bAlwaysFullToolbarWidth = !PluginConfig.m_bAlwaysFullToolbarWidth; + db_set_b(0, SRMSGMOD_T, "alwaysfulltoolbar", (uint8_t)PluginConfig.m_bAlwaysFullToolbarWidth); + Srmm_Broadcast(DM_CONFIGURETOOLBAR, 0, 1); + break; + + case ID_PICMENU_SAVETHISPICTUREAS: + if (m_pPanel.isActive()) + SaveAvatarToFile(m_hOwnPic, 1); + else if (m_ace) + SaveAvatarToFile(m_ace->hbmPic, 0); + break; + + case ID_PANELPICMENU_SAVETHISPICTUREAS: + if (m_ace) + SaveAvatarToFile(m_ace->hbmPic, 0); + break; + + case ID_PICMENU_SETTINGS: + if (menuId == MENU_PICMENU) { + if (m_pPanel.isActive()) { + if (ServiceExists(MS_AV_SETMYAVATARW) && CallService(MS_AV_CANSETMYAVATAR, (WPARAM)(m_cache->getActiveProto()), 0)) + CallService(MS_AV_SETMYAVATARW, (WPARAM)(m_cache->getActiveProto()), 0); + return TRUE; + } + } + CallService(MS_AV_CONTACTOPTIONS, m_hContact, (LPARAM)m_hwnd); + return 1; + } + } + else if (menuId == MENU_LOGMENU) { + switch (selection) { + case ID_MESSAGELOGSETTINGS_GLOBAL: + g_plugin.openOptions(nullptr, L"Message sessions", L"Message log"); + return 1; + + case ID_MESSAGELOGSETTINGS_FORTHISCONTACT: + CallService(MS_TABMSG_SETUSERPREFS, m_hContact, 0); + return 1; + } + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::NotifyDeliveryFailure() const +{ + if (!g_plugin.bErrorPopup || !Popup_Enabled()) + return; + + POPUPDATAW ppd = {}; + ppd.lchContact = m_hContact; + ppd.PluginWindowProc = Utils::PopupDlgProcError; + ppd.lchIcon = PluginConfig.g_iconErr; + ppd.iSeconds = NEN::iDelayErr; + if (!NEN::bColDefaultErr) { + ppd.colorText = NEN::colTextErr; + ppd.colorBack = NEN::colBackErr; + } + wcsncpy_s(ppd.lpwzContactName, m_cache->getNick(), _TRUNCATE); + wcsncpy_s(ppd.lpwzText, TranslateT("A message delivery has failed.\nClick to open the message window."), _TRUNCATE); + PUAddPopupW(&ppd); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::PlayIncomingSound() const +{ + int iPlay = m_pContainer->MustPlaySound(this); + if (iPlay) { + if (GetForegroundWindow() == m_pContainer->m_hwnd && m_pContainer->m_hwndActive == m_hwnd) + Skin_PlaySound("RecvMsgActive"); + else + Skin_PlaySound("RecvMsgInactive"); + } +} + +void CMsgDialog::RemakeLog() +{ + m_szMicroLf[0] = 0; + m_lastEventTime = 0; + m_iLastEventType = -1; + StreamEvents(m_hDbEventFirst, -1, 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// saves a contact picture to disk +// takes hbm (bitmap handle) and bool isOwnPic (1 == save the picture as your own avatar) +// requires AVS service (Miranda 0.7+) + +void CMsgDialog::SaveAvatarToFile(HBITMAP hbm, int isOwnPic) +{ + wchar_t szFinalFilename[MAX_PATH]; + time_t t = time(0); + struct tm *lt = localtime(&t); + uint32_t setView = 1; + + wchar_t szTimestamp[100]; + mir_snwprintf(szTimestamp, L"%04u %02u %02u_%02u%02u", lt->tm_year + 1900, lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min); + + wchar_t *szProto = mir_a2u(m_cache->getActiveProto()); + + wchar_t szFinalPath[MAX_PATH]; + mir_snwprintf(szFinalPath, L"%s\\%s", M.getSavedAvatarPath(), szProto); + mir_free(szProto); + + if (CreateDirectory(szFinalPath, nullptr) == 0) { + if (GetLastError() != ERROR_ALREADY_EXISTS) { + MessageBox(nullptr, TranslateT("Error creating destination directory"), + TranslateT("Save contact picture"), MB_OK | MB_ICONSTOP); + return; + } + } + + wchar_t szBaseName[MAX_PATH]; + if (isOwnPic) + mir_snwprintf(szBaseName, L"My Avatar_%s", szTimestamp); + else + mir_snwprintf(szBaseName, L"%s_%s", m_cache->getNick(), szTimestamp); + + mir_snwprintf(szFinalFilename, L"%s.png", szBaseName); + + // do not allow / or \ or % in the filename + Utils::sanitizeFilename(szFinalFilename); + + wchar_t filter[MAX_PATH]; + mir_snwprintf(filter, L"%s%c*.bmp;*.png;*.jpg;*.gif%c%c", TranslateT("Image files"), 0, 0, 0); + + OPENFILENAME ofn = { 0 }; + ofn.lpstrDefExt = L"png"; + ofn.lpstrFilter = filter; + ofn.Flags = OFN_HIDEREADONLY | OFN_EXPLORER | OFN_ENABLESIZING | OFN_ENABLEHOOK; + ofn.lpfnHook = OpenFileSubclass; + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFile = szFinalFilename; + ofn.lpstrInitialDir = szFinalPath; + ofn.nMaxFile = MAX_PATH; + ofn.nMaxFileTitle = MAX_PATH; + ofn.lCustData = (LPARAM)& setView; + if (GetSaveFileName(&ofn)) { + if (PathFileExists(szFinalFilename)) + if (MessageBox(nullptr, TranslateT("The file exists. Do you want to overwrite it?"), TranslateT("Save contact picture"), MB_YESNO | MB_ICONQUESTION) == IDNO) + return; + + IMGSRVC_INFO ii; + ii.cbSize = sizeof(ii); + ii.pwszName = szFinalFilename; + ii.hbm = hbm; + ii.dwMask = IMGI_HBITMAP; + ii.fif = FIF_UNKNOWN; // get the format from the filename extension. png is default. + Image_Save(&ii); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::SaveSplitter() +{ + if (m_bIsAutosizingInput) + return; + + if (m_iSplitterY < DPISCALEY_S(MINSPLITTERY) || m_iSplitterY < 0) + m_iSplitterY = DPISCALEY_S(MINSPLITTERY); + + if (m_bSplitterOverride) + db_set_dw(m_hContact, SRMSGMOD_T, "splitsplity", m_iSplitterY); + else { + if (m_pContainer->cfg.fPrivate) + m_pContainer->cfg.iSplitterY = m_iSplitterY; + else + db_set_dw(0, SRMSGMOD_T, "splitsplity", m_iSplitterY); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// send a pasted bitmap by file transfer. + +static LIST<wchar_t> vTempFilenames(5); + +void CMsgDialog::SendHBitmapAsFile(HBITMAP hbmp) const +{ + const wchar_t *mirandatempdir = L"Miranda"; + const wchar_t *filenametemplate = L"\\clp-%Y%m%d-%H%M%S0.jpg"; + wchar_t filename[MAX_PATH]; + size_t tempdirlen = GetTempPath(MAX_PATH, filename); + bool fSend = true; + + const char *szProto = m_cache->getActiveProto(); + int wMyStatus = Proto_GetStatus(szProto); + + uint32_t protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0); + uint32_t typeCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0); + + // check protocol capabilities, status modes and visibility lists (privacy) + // to determine whether the file can be sent. Throw a warning if any of + // these checks fails. + if (!(protoCaps & PF1_FILESEND)) + fSend = false; + + if ((ID_STATUS_OFFLINE == wMyStatus) || (ID_STATUS_OFFLINE == m_cache->getActiveStatus() && !(typeCaps & PF4_OFFLINEFILES))) + fSend = false; + + if (protoCaps & PF1_VISLIST && db_get_w(m_cache->getActiveContact(), szProto, "ApparentMode", 0) == ID_STATUS_OFFLINE) + fSend = false; + + if (protoCaps & PF1_INVISLIST && wMyStatus == ID_STATUS_INVISIBLE && db_get_w(m_cache->getActiveContact(), szProto, "ApparentMode", 0) != ID_STATUS_ONLINE) + fSend = false; + + if (!fSend) { + CWarning::show(CWarning::WARN_SENDFILE, MB_OK | MB_ICONEXCLAMATION | CWarning::CWF_NOALLOWHIDE); + return; + } + + if (tempdirlen <= 0 || tempdirlen >= MAX_PATH - mir_wstrlen(mirandatempdir) - mir_wstrlen(filenametemplate) - 2) // -2 is because %Y takes 4 symbols + filename[0] = 0; // prompt for a new name + else { + mir_wstrcpy(filename + tempdirlen, mirandatempdir); + if ((GetFileAttributes(filename) == INVALID_FILE_ATTRIBUTES || ((GetFileAttributes(filename) & FILE_ATTRIBUTE_DIRECTORY) == 0)) && CreateDirectory(filename, nullptr) == 0) + filename[0] = 0; + else { + tempdirlen = mir_wstrlen(filename); + + time_t rawtime; + time(&rawtime); + const tm *timeinfo; + timeinfo = _localtime32((__time32_t *)& rawtime); + wcsftime(filename + tempdirlen, MAX_PATH - tempdirlen, filenametemplate, timeinfo); + size_t firstnumberpos = tempdirlen + 14; + size_t lastnumberpos = tempdirlen + 20; + while (GetFileAttributes(filename) != INVALID_FILE_ATTRIBUTES) { // while it exists + for (size_t pos = lastnumberpos; pos >= firstnumberpos; pos--) + if (filename[pos]++ != '9') + break; + else + if (pos == firstnumberpos) + filename[0] = 0; // all filenames exist => prompt for a new name + else + filename[pos] = '0'; + } + } + } + + if (filename[0] == 0) { // prompting to save + wchar_t filter[MAX_PATH]; + mir_snwprintf(filter, L"%s%c*.jpg%c%c", TranslateT("JPEG-compressed images"), 0, 0, 0); + + OPENFILENAME dlg; + dlg.lStructSize = sizeof(dlg); + dlg.lpstrFilter = filter; + dlg.nFilterIndex = 1; + dlg.lpstrFile = filename; + dlg.nMaxFile = MAX_PATH; + dlg.Flags = OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST; + dlg.lpstrDefExt = L"jpg"; + if (!GetSaveFileName(&dlg)) + return; + } + + IMGSRVC_INFO ii; + ii.cbSize = sizeof(ii); + ii.hbm = hbmp; + ii.pwszName = filename; + ii.dwMask = IMGI_HBITMAP; + ii.fif = FIF_JPEG; + if (!Image_Save(&ii)) { + CWarning::show(CWarning::WARN_SAVEFILE, MB_OK | MB_ICONEXCLAMATION | CWarning::CWF_NOALLOWHIDE); + return; + } + + vTempFilenames.insert(mir_wstrdup(filename)); + + wchar_t *ppFiles[2] = { filename, nullptr }; + CallService(MS_FILE_SENDSPECIFICFILEST, m_cache->getActiveContact(), (LPARAM)&ppFiles); +} + +// remove all temporary files created by the "send clipboard as file" feature. +void TSAPI CleanTempFiles() +{ + for (auto &it : vTempFilenames) { + DeleteFileW(it); + mir_free(it); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Sets a status bar text for a contact + +void CMsgDialog::SetStatusText(const wchar_t *wszText, HICON hIcon) +{ + if (wszText != nullptr) { + m_bStatusSet = true; + m_szStatusText = wszText; + m_szStatusIcon = hIcon; + } + else { + m_bStatusSet = false; + m_szStatusText.Empty(); + m_szStatusIcon = nullptr; + } + + tabUpdateStatusBar(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// 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 CMsgDialog::FilterWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CMsgDialog *pDlg = (CMsgDialog *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + switch (uMsg) { + case WM_INITDIALOG: + pDlg = (CMsgDialog *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + { + uint32_t dwMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "FilterMask", 0); + uint32_t dwFlags = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "FilterFlags", 0); + + uint32_t dwPopupMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "PopupMask", 0); + uint32_t dwPopupFlags = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "PopupFlags", 0); + + uint32_t dwTrayMask = db_get_dw(pDlg->m_hContact, CHAT_MODULE, "TrayIconMask", 0); + uint32_t 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 && pDlg) { + int iFlags = 0; + uint32_t 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 (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 (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 (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->getChat()); + + if (pDlg->m_bFilterEnabled) { + if (pDlg->m_iLogFilterFlags == 0) + pDlg->m_btnFilter.Click(); + 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; +} + +void CMsgDialog::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_pLog->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 CMsgDialog::ShowPicture(bool showNewPic) +{ + if (!m_pPanel.isActive()) + m_pic.cy = m_pic.cx = DPISCALEY_S(60); + + if (showNewPic) { + if (m_pPanel.isActive() && m_pContainer->cfg.avatarMode != 3) { + if (!m_hwndPanelPic) { + InvalidateRect(m_hwnd, nullptr, TRUE); + UpdateWindow(m_hwnd); + Resize(); + } + return; + } + AdjustBottomAvatarDisplay(); + } + else { + m_bShowAvatar = !m_bShowAvatar; + db_set_b(m_hContact, SRMSGMOD_T, "MOD_ShowPic", m_bShowAvatar); + } + + RECT rc; + GetWindowRect(GetDlgItem(m_hwnd, IDC_CONTACTPIC), &rc); + if (m_minEditBoxSize.cy + DPISCALEY_S(3) > m_iSplitterY) + SplitterMoved(rc.bottom - m_minEditBoxSize.cy, GetDlgItem(m_hwnd, IDC_SPLITTERY)); + if (!showNewPic) + SetDialogToType(); + else + Resize(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// show a modified context menu for the richedit control(s) + +void CMsgDialog::ShowPopupMenu(const CCtrlBase &pCtrl, POINT pt) +{ + CHARRANGE sel, all = { 0, -1 }; + + HMENU hSubMenu, hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_CONTEXT)); + if (pCtrl.GetCtrlId() == IDC_SRMM_LOG) + hSubMenu = GetSubMenu(hMenu, 0); + else { + hSubMenu = GetSubMenu(hMenu, 2); + EnableMenuItem(hSubMenu, IDM_PASTEFORMATTED, m_SendFormat != 0 ? MF_ENABLED : MF_GRAYED); + EnableMenuItem(hSubMenu, ID_EDITOR_PASTEANDSENDIMMEDIATELY, g_plugin.bPasteAndSend ? MF_ENABLED : MF_GRAYED); + CheckMenuItem(hSubMenu, ID_EDITOR_SHOWMESSAGELENGTHINDICATOR, PluginConfig.m_visualMessageSizeIndicator ? MF_CHECKED : MF_UNCHECKED); + EnableMenuItem(hSubMenu, ID_EDITOR_SHOWMESSAGELENGTHINDICATOR, m_pContainer->m_hwndStatus ? MF_ENABLED : MF_GRAYED); + } + TranslateMenu(hSubMenu); + pCtrl.SendMsg(EM_EXGETSEL, 0, (LPARAM)& sel); + if (sel.cpMin == sel.cpMax) { + EnableMenuItem(hSubMenu, IDM_COPY, MF_GRAYED); + EnableMenuItem(hSubMenu, IDM_QUOTE, MF_GRAYED); + if (pCtrl.GetCtrlId() == IDC_SRMM_MESSAGE) + EnableMenuItem(hSubMenu, IDM_CUT, MF_GRAYED); + } + + if (pCtrl.GetCtrlId() == IDC_SRMM_LOG) { + InsertMenuA(hSubMenu, 6, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + CheckMenuItem(hSubMenu, ID_LOG_FREEZELOG, m_bScrollingDisabled ? MF_CHECKED : MF_UNCHECKED); + } + + MessageWindowPopupData mwpd; + // First notification + mwpd.uType = MSG_WINDOWPOPUP_SHOWING; + mwpd.uFlags = (pCtrl.GetCtrlId() == IDC_SRMM_LOG ? MSG_WINDOWPOPUP_LOG : MSG_WINDOWPOPUP_INPUT); + mwpd.hContact = m_hContact; + mwpd.hwnd = pCtrl.GetHwnd(); + mwpd.hMenu = hSubMenu; + mwpd.selection = 0; + mwpd.pt = pt; + NotifyEventHooks(g_chatApi.hevWinPopup, 0, (LPARAM)& mwpd); + + int iSelection = TrackPopupMenu(hSubMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_hwnd, nullptr); + + // Second notification + mwpd.selection = iSelection; + mwpd.uType = MSG_WINDOWPOPUP_SELECTED; + NotifyEventHooks(g_chatApi.hevWinPopup, 0, (LPARAM)& mwpd); + + switch (iSelection) { + case IDM_COPY: + pCtrl.SendMsg(WM_COPY, 0, 0); + break; + case IDM_CUT: + pCtrl.SendMsg(WM_CUT, 0, 0); + break; + case IDM_PASTE: + case IDM_PASTEFORMATTED: + if (pCtrl.GetCtrlId() == IDC_SRMM_MESSAGE) + pCtrl.SendMsg(EM_PASTESPECIAL, (iSelection == IDM_PASTE) ? CF_UNICODETEXT : 0, 0); + break; + case IDM_COPYALL: + pCtrl.SendMsg(EM_EXSETSEL, 0, (LPARAM)& all); + pCtrl.SendMsg(WM_COPY, 0, 0); + pCtrl.SendMsg(EM_EXSETSEL, 0, (LPARAM)& sel); + break; + case IDM_QUOTE: + SendMessage(m_hwnd, WM_COMMAND, IDC_QUOTE, 0); + break; + case IDM_SELECTALL: + pCtrl.SendMsg(EM_EXSETSEL, 0, (LPARAM)& all); + break; + case IDM_CLEAR: + tabClearLog(); + break; + case ID_LOG_FREEZELOG: + SendDlgItemMessage(m_hwnd, IDC_SRMM_LOG, WM_KEYDOWN, VK_F12, 0); + break; + case ID_EDITOR_SHOWMESSAGELENGTHINDICATOR: + PluginConfig.m_visualMessageSizeIndicator = !PluginConfig.m_visualMessageSizeIndicator; + db_set_b(0, SRMSGMOD_T, "msgsizebar", (uint8_t)PluginConfig.m_visualMessageSizeIndicator); + Srmm_Broadcast(DM_CONFIGURETOOLBAR, 0, 0); + Resize(); + if (m_pContainer->m_hwndStatus) + RedrawWindow(m_pContainer->m_hwndStatus, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); + break; + case ID_EDITOR_PASTEANDSENDIMMEDIATELY: + HandlePasteAndSend(); + break; + } + + if (pCtrl.GetCtrlId() == IDC_SRMM_LOG) + RemoveMenu(hSubMenu, 7, MF_BYPOSITION); + DestroyMenu(hMenu); +} + +void CMsgDialog::SplitterMoved(int coord, HWND hwnd) +{ + POINT pt; + RECT rc; + + switch (GetDlgCtrlID(hwnd)) { + case IDC_MULTISPLITTER: + GetClientRect(m_hwnd, &rc); + pt.x = coord; + pt.y = 0; + ScreenToClient(m_hwnd, &pt); + { + int oldSplitterX = m_iMultiSplit; + m_iMultiSplit = rc.right - pt.x; + if (m_iMultiSplit < 25) + m_iMultiSplit = 25; + + if (m_iMultiSplit > ((rc.right - rc.left) - 80)) + m_iMultiSplit = oldSplitterX; + } + Resize(); + break; + + case IDC_SPLITTERX: + GetClientRect(m_hwnd, &rc); + pt.x = coord, 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->cfg.iSplitterX = iSplitterX; + } + Resize(); + break; + + case IDC_SPLITTERY: + GetClientRect(m_hwnd, &rc); + rc.top += (m_pPanel.isActive() ? m_pPanel.getHeight() + 40 : 30); + pt.x = 0; + pt.y = coord; + ScreenToClient(m_hwnd, &pt); + { + int oldSplitterY = m_iSplitterY; + int oldDynaSplitter = m_dynaSplitter; + + m_iSplitterY = rc.bottom - pt.y + DPISCALEY_S(23); + + // attempt to fix splitter troubles.. + // hardcoded limits... better solution is possible, but this works for now + int bottomtoolbarH = 0; + if (m_pContainer->cfg.flags.m_bBottomToolbar) + bottomtoolbarH = 22; + + if (m_iSplitterY < (DPISCALEY_S(MINSPLITTERY) + 5 + bottomtoolbarH)) { // min splitter size + m_iSplitterY = (DPISCALEY_S(MINSPLITTERY) + 5 + bottomtoolbarH); + m_dynaSplitter = m_iSplitterY - DPISCALEY_S(34); + DM_RecalcPictureSize(); + } + else if (m_iSplitterY > (rc.bottom - rc.top)) { + m_iSplitterY = oldSplitterY; + m_dynaSplitter = oldDynaSplitter; + DM_RecalcPictureSize(); + } + else { + m_dynaSplitter = (rc.bottom - pt.y) - DPISCALEY_S(11); + DM_RecalcPictureSize(); + } + } + UpdateToolbarBG(); + Resize(); + break; + + case IDC_PANELSPLITTER: + GetClientRect(m_pLog->GetHwnd(), &rc); + + POINT pnt = { 0, coord }; + ScreenToClient(m_hwnd, &pnt); + if ((pnt.y + 2 >= MIN_PANELHEIGHT + 2) && (pnt.y + 2 < 100) && (pnt.y + 2 < rc.bottom - 30)) + m_pPanel.setHeight(pnt.y + 2, true); + + RedrawWindow(m_hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); + if (M.isAero()) + InvalidateRect(GetParent(m_hwnd), nullptr, FALSE); + break; + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::StreamEvents(MEVENT hDbEventFirst, int count, bool bAppend) +{ + m_pLog->LogEvents(hDbEventFirst, count, bAppend); + + DM_ScrollToBottom(0, 0); + if (bAppend && hDbEventFirst) + m_hDbEventLast = hDbEventFirst; + else + m_hDbEventLast = db_event_last(m_hContact); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// sent by the select container dialog box when a container was selected... + +void CMsgDialog::SwitchToContainer(const wchar_t *szNewName) +{ + if (!mir_wstrcmp(szNewName, TranslateT("Default container"))) + szNewName = CGlobals::m_default_container_name; + + int iOldItems = TabCtrl_GetItemCount(m_hwndParent); + if (!wcsncmp(m_pContainer->m_wszName, szNewName, CONTAINER_NAMELEN)) + return; + + TContainerData *pNewContainer = FindContainerByName(szNewName); + if (pNewContainer == nullptr) + if ((pNewContainer = CreateContainer(szNewName, FALSE, m_hContact)) == nullptr) + return; + + db_set_ws(m_hContact, SRMSGMOD_T, "containerW", szNewName); + PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_DOCREATETAB, (WPARAM)pNewContainer, m_hContact); + if (iOldItems > 1) // there were more than 1 tab, container is still valid + SendMessage(m_pContainer->m_hwndActive, WM_SIZE, 0, 0); + SetForegroundWindow(pNewContainer->m_hwnd); + SetActiveWindow(pNewContainer->m_hwnd); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +bool CMsgDialog::TabAutoComplete() +{ + LRESULT lResult = m_message.SendMsg(EM_GETSEL, 0, 0); + int start = LOWORD(lResult), end = HIWORD(lResult); + int origStart = start, origEnd = end; + 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)& gt, 0); + if (iLen <= 0) + return false; + + bool isTopic = false, isRoom = false; + 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)& gt, (LPARAM)pszText); + + 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; + } + + const wchar_t *pszName = 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) { + if (end != start) { + CMStringW szReplace; + if (!isRoom && !isTopic && start == 0) { + szReplace = pszName; + if (mir_wstrlen(g_Settings.pwszAutoText)) + szReplace.Append(g_Settings.pwszAutoText); + szReplace.AppendChar(' '); + m_wszSearchResult = szReplace.Detach(); + pszName = m_wszSearchResult; + } + else m_wszSearchResult = mir_wstrdup(pszName); + + m_message.SendMsg(EM_SETSEL, start, end); + m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)pszName); + } + else m_wszSearchResult = mir_wstrdup(pszName); + + return true; + } + + if (end != start) { + m_message.SendMsg(EM_SETSEL, start, end); + m_message.SendMsg(EM_REPLACESEL, TRUE, (LPARAM)m_wszSearchQuery); + } + m_message.SendMsg(EM_SETSEL, origStart, origEnd); + replaceStrW(m_wszSearchQuery, nullptr); + return false; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::tabClearLog() +{ + if (isChat()) { + g_chatApi.LM_RemoveAll(&m_si->pLog, &m_si->pLogEnd); + m_si->iEventCount = 0; + m_si->LastTime = 0; + PostMessage(m_hwnd, WM_MOUSEACTIVATE, 0, 0); + } + + m_pLog->Clear(); + m_hDbEventFirst = 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +CThumbBase *CMsgDialog::tabCreateThumb(CProxyWindow *pProxy) const +{ + if (isChat()) + return new CThumbMUC(pProxy, m_si); + + return new CThumbIM(pProxy); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// update all status bar fields and force a redraw of the status bar. + +void CMsgDialog::tabUpdateStatusBar() const +{ + if (m_pContainer->m_hwndStatus && m_pContainer->m_hwndActive == m_hwnd) { + if (!isChat()) { + if (m_wszStatusBar[0]) { + SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_DEFAULT_TYPING]); + SendMessage(m_pContainer->m_hwndStatus, SB_SETTEXT, 0, (LPARAM)m_wszStatusBar); + } + 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()); + } + else { + SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, 0); + DM_UpdateLastMessage(); + } + } + 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()); + } + else SendMessage(m_pContainer->m_hwndStatus, SB_SETICON, 0, 0); + } + UpdateReadChars(); + InvalidateRect(m_pContainer->m_hwndStatus, nullptr, TRUE); + SendMessage(m_pContainer->m_hwndStatus, WM_USER + 101, 0, (LPARAM)this); + } +} + +int CMsgDialog::Typing(int secs) +{ + if (!AllowTyping()) + return 0; + + int preTyping = m_nTypeSecs != 0; + + setTyping(m_nTypeSecs = (secs > 0) ? secs : 0); + if (m_nTypeSecs) + m_bShowTyping = 0; + + return preTyping; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::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 CMsgDialog::UpdateOptions() +{ + GetSendFormat(); + + 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); + + CSuper::UpdateOptions(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// update the status bar field which displays the number of characters in the input area +// and various indicators (caps lock, num lock, insert mode). + +void CMsgDialog::UpdateReadChars() const +{ + if (!m_pContainer->m_hwndStatus || m_pContainer->m_hwndActive != m_hwnd) + return; + + int len; + if (isChat()) + len = GetWindowTextLength(m_message.GetHwnd()); + else { + // retrieve text length in UTF8 bytes, because this is the relevant length for most protocols + GETTEXTLENGTHEX gtxl = { 0 }; + gtxl.codepage = CP_UTF8; + gtxl.flags = GTL_DEFAULT | GTL_PRECISE | GTL_NUMBYTES; + + len = m_message.SendMsg(EM_GETTEXTLENGTHEX, (WPARAM)>xl, 0); + } + + BOOL fCaps = (GetKeyState(VK_CAPITAL) & 1); + BOOL fNum = (GetKeyState(VK_NUMLOCK) & 1); + + wchar_t szBuf[20]; szBuf[0] = 0; + if (m_bInsertMode) + mir_wstrcat(szBuf, L"O"); + if (fCaps) + mir_wstrcat(szBuf, L"C"); + if (fNum) + mir_wstrcat(szBuf, L"N"); + if (m_bInsertMode || fCaps || fNum) + mir_wstrcat(szBuf, L" | "); + + wchar_t buf[128]; + mir_snwprintf(buf, L"%s%s %d/%d", szBuf, m_lcID, m_iOpenJobs, len); + SendMessage(m_pContainer->m_hwndStatus, SB_SETTEXT, 1, (LPARAM)buf); + if (PluginConfig.m_visualMessageSizeIndicator) + InvalidateRect(m_pContainer->m_hwndStatus, nullptr, FALSE); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::UpdateSaveAndSendButton() +{ + GETTEXTLENGTHEX gtxl = { 0 }; + gtxl.codepage = CP_UTF8; + gtxl.flags = GTL_DEFAULT | GTL_PRECISE | GTL_NUMBYTES; + + int len = SendDlgItemMessage(m_hwnd, IDC_SRMM_MESSAGE, EM_GETTEXTLENGTHEX, (WPARAM)>xl, 0); + if (len && GetSendButtonState() == PBS_DISABLED) + EnableSendButton(true); + else if (len == 0 && GetSendButtonState() != PBS_DISABLED) + EnableSendButton(false); + + if (len) { // looks complex but avoids flickering on the button while typing. + if (!m_bSaveBtn) { + SendDlgItemMessage(m_hwnd, IDC_CLOSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_BUTTON_SAVE]); + SendDlgItemMessage(m_hwnd, IDC_CLOSE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Save and close session"), BATF_UNICODE); + m_bSaveBtn = true; + } + } + else { + SendDlgItemMessage(m_hwnd, IDC_CLOSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)PluginConfig.g_buttonBarIcons[ICON_BUTTON_CANCEL]); + SendDlgItemMessage(m_hwnd, IDC_CLOSE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Close session"), BATF_UNICODE); + m_bSaveBtn = false; + } + m_textLen = len; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::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); + uint32_t diff = (now - mi->idleTimeStamp) / 60; + if (diff >= 1) { + if (diff > 59) { + uint32_t hours = diff / 60; + uint32_t 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 CMsgDialog::UpdateTitle() +{ + if (isChat()) { + 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(); + } + } + else { + uint32_t dwOldIdle = m_idle; + const char *szActProto = nullptr; + + m_wszStatus[0] = 0; + + if (m_iTabID == -1) + return; + + TCITEM item = {}; + + bool bChanged = false; + wchar_t newtitle[128]; + if (m_szProto) { + szActProto = m_cache->getProto(); + + bool bHasName = (m_cache->getUIN()[0] != 0); + m_idle = m_cache->getIdleTS(); + m_bIsIdle = m_idle != 0; + + m_wStatus = m_cache->getStatus(); + wcsncpy_s(m_wszStatus, Clist_GetStatusModeDescription(m_szProto == nullptr ? ID_STATUS_OFFLINE : m_wStatus, 0), _TRUNCATE); + + wchar_t newcontactname[128]; newcontactname[0] = 0; + if (PluginConfig.m_bCutContactNameOnTabs) + CutContactName(m_cache->getNick(), newcontactname, _countof(newcontactname)); + else + wcsncpy_s(newcontactname, m_cache->getNick(), _TRUNCATE); + + Utils::DoubleAmpersands(newcontactname, _countof(newcontactname)); + + if (newcontactname[0] != 0) { + if (g_plugin.bStatusOnTabs) + mir_snwprintf(newtitle, L"%s (%s)", newcontactname, m_wszStatus); + else + wcsncpy_s(newtitle, newcontactname, _TRUNCATE); + } + else wcsncpy_s(newtitle, L"Forward", _TRUNCATE); + + if (mir_wstrcmp(newtitle, m_wszTitle)) + bChanged = true; + else if (m_wStatus != m_wOldStatus) + bChanged = true; + + UpdateWindowIcon(); + + wchar_t fulluin[256]; + if (m_bIsMeta) + mir_snwprintf(fulluin, + TranslateT("UID: %s (Shift+click -> copy to clipboard)\nClick for user's details\nRight click for metacontact control\nClick dropdown to add or remove user from your favorites."), + bHasName ? m_cache->getUIN() : TranslateT("No UID")); + else + mir_snwprintf(fulluin, + TranslateT("UID: %s (Shift+click -> copy to clipboard)\nClick for user's details\nClick dropdown to change this contact's favorite status."), + bHasName ? m_cache->getUIN() : TranslateT("No UID")); + + SendDlgItemMessage(m_hwnd, IDC_NAME, BUTTONADDTOOLTIP, (WPARAM)fulluin, BATF_UNICODE); + } + else wcsncpy_s(newtitle, L"Message Session", _TRUNCATE); + + m_wOldStatus = m_wStatus; + if (m_idle != dwOldIdle || bChanged) { + if (bChanged) { + item.mask |= TCIF_TEXT; + item.pszText = m_wszTitle; + wcsncpy_s(m_wszTitle, newtitle, _TRUNCATE); + if (m_pWnd) + m_pWnd->updateTitle(m_cache->getNick()); + } + if (m_iTabID >= 0) { + TabCtrl_SetItem(m_hwndParent, m_iTabID, &item); + if (m_pContainer->cfg.flags.m_bSideBar) + m_pContainer->m_pSideBar->updateSession(this); + } + if (m_pContainer->m_hwndActive == m_hwnd && bChanged) + m_pContainer->UpdateTitle(m_hContact); + + m_pPanel.Invalidate(); + if (m_pWnd) + m_pWnd->Invalidate(); + } + + // care about MetaContacts and update the statusbar icon with the currently "most online" contact... + if (m_bIsMeta) { + PostMessage(m_hwnd, DM_OWNNICKCHANGED, 0, 0); + if (m_pContainer->cfg.flags.m_bUinStatusBar) + DM_UpdateLastMessage(); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CMsgDialog::UpdateWindowIcon() +{ + if (m_hXStatusIcon) { + DestroyIcon(m_hXStatusIcon); + m_hXStatusIcon = nullptr; + } + + if (LPCSTR szProto = m_cache->getProto()) { + m_hTabIcon = m_hTabStatusIcon = GetMyContactIcon(&g_plugin.bMetaTab); + if (g_plugin.bUseXStatus) + m_hXStatusIcon = GetXStatusIcon(); + + SendDlgItemMessage(m_hwnd, IDC_PROTOCOL, BUTTONSETASDIMMED, m_bIsIdle, 0); + SendDlgItemMessage(m_hwnd, IDC_PROTOCOL, BM_SETIMAGE, IMAGE_ICON, (LPARAM)(m_hXStatusIcon ? m_hXStatusIcon : GetMyContactIcon(&g_plugin.bMetaBar))); + + if (m_pContainer->m_hwndActive == m_hwnd) + m_pContainer->SetIcon(this, m_hXStatusIcon ? m_hXStatusIcon : m_hTabIcon); + + if (m_pWnd) + m_pWnd->updateIcon(m_hXStatusIcon ? m_hXStatusIcon : m_hTabIcon); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// called whenever a group chat tab becomes active(either by switching tabs or activating a +// container window + +void CMsgDialog::UpdateWindowState(UINT msg) +{ + if (m_iTabID < 0) + return; + + if (msg == WM_ACTIVATE) { + if (m_pContainer->cfg.flags.m_bTransparent) { + uint32_t trans = LOWORD(m_pContainer->cfg.dwTransparency); + SetLayeredWindowAttributes(m_pContainer->m_hwnd, CSkin::m_ContainerColorKey, (uint8_t)trans, (m_pContainer->cfg.flags.m_bTransparent ? 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; + m_dwTickLastEvent = 0; + m_bDividerSet = false; + + if (m_pContainer->m_dwFlashingStarted != 0) { + m_pContainer->FlashContainer(0, 0); + m_pContainer->m_dwFlashingStarted = 0; + } + + if (m_si) { + g_chatApi.SetActiveSession(m_si); + m_hTabIcon = m_hTabStatusIcon; + + 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; + if (timerFlash.Stop() || m_iFlashIcon) { + FlashTab(false); + m_bCanFlashTab = FALSE; + m_iFlashIcon = nullptr; + } + + m_pContainer->cfg.flags.m_bNeedsUpdateTitle = false; + + if (m_bNeedCheckSize) + 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(); + } + else { + if (timerFlash.Stop()) { + FlashTab(false); + m_bCanFlashTab = false; + } + + if (m_bFlashClist) { + m_bFlashClist = false; + if (m_hFlashingEvent != 0) + g_clistApi.pfnRemoveEvent(m_hContact, m_hFlashingEvent); + m_hFlashingEvent = 0; + } + m_pContainer->cfg.flags.m_bNeedsUpdateTitle = false; + + if (m_bDeferredRemakeLog && !IsIconic(m_pContainer->m_hwnd)) { + RemakeLog(); + m_bDeferredRemakeLog = false; + } + + if (m_bNeedCheckSize) + PostMessage(m_hwnd, DM_SAVESIZE, 0, 0); + + m_pContainer->m_hIconTaskbarOverlay = nullptr; + m_pContainer->UpdateTitle(m_hContact); + + tabUpdateStatusBar(); + m_dwLastActivity = GetTickCount(); + m_pContainer->m_dwLastActivity = m_dwLastActivity; + + m_pContainer->m_pMenuBar->configureMenu(); + g_arUnreadWindows.remove(HANDLE(m_hContact)); + + m_pPanel.Invalidate(); + + if (m_bDeferredScroll) { + m_bDeferredScroll = false; + DM_ScrollToBottom(0, 1); + } + } + + DM_SetDBButtonStates(); + + if (m_bDelayedSplitter) { + m_bDelayedSplitter = false; + 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->cfg.flags.m_bSideBar) + m_pContainer->m_pSideBar->setActiveItem(this, msg == WM_ACTIVATE); + + if (m_pWnd) + m_pWnd->Invalidate(); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// generic handler for the WM_COPY message in message log/chat history richedit control(s). +// it filters out the invisible event boundary markers from the text copied to the clipboard. +// WINE Fix: overwrite clippboad data from original control data + +LRESULT CMsgDialog::WMCopyHandler(UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT result = mir_callNextSubclass(m_pLog->GetHwnd(), stubLogProc, msg, wParam, lParam); + + ptrA szFromStream(LOG()->GetRichTextRtf(true, true)); + if (szFromStream != nullptr) { + ptrW converted(mir_utf8decodeW(szFromStream)); + if (converted != nullptr) { + Utils::FilterEventMarkers(converted); + Utils_ClipboardCopy(converted); + } + } + + return result; +} diff --git a/plugins/TabSRMM/src/msgdlgutils.cpp b/plugins/TabSRMM/src/msgdlgutils.cpp index daec4e8349..eff4705074 100644 --- a/plugins/TabSRMM/src/msgdlgutils.cpp +++ b/plugins/TabSRMM/src/msgdlgutils.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/msgdlgutils.h b/plugins/TabSRMM/src/msgdlgutils.h index b6666fd6ce..b9cd0fd792 100644 --- a/plugins/TabSRMM/src/msgdlgutils.h +++ b/plugins/TabSRMM/src/msgdlgutils.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/msglog.cpp b/plugins/TabSRMM/src/msglog.cpp index b4143a69a6..e12507d8e0 100644 --- a/plugins/TabSRMM/src/msglog.cpp +++ b/plugins/TabSRMM/src/msglog.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/msgoptions.cpp b/plugins/TabSRMM/src/msgoptions.cpp index b87a801874..b71a71dbc7 100644 --- a/plugins/TabSRMM/src/msgoptions.cpp +++ b/plugins/TabSRMM/src/msgoptions.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/msgs.cpp b/plugins/TabSRMM/src/msgs.cpp index 7753b418d0..1c4448bff0 100644 --- a/plugins/TabSRMM/src/msgs.cpp +++ b/plugins/TabSRMM/src/msgs.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/msgs.h b/plugins/TabSRMM/src/msgs.h index 693f5aa08c..382ad1f54d 100644 --- a/plugins/TabSRMM/src/msgs.h +++ b/plugins/TabSRMM/src/msgs.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/muchighlight.cpp b/plugins/TabSRMM/src/muchighlight.cpp index ffa1759fa9..9eb5cd96f5 100644 --- a/plugins/TabSRMM/src/muchighlight.cpp +++ b/plugins/TabSRMM/src/muchighlight.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/muchighlight.h b/plugins/TabSRMM/src/muchighlight.h index 197e130859..7338765c40 100644 --- a/plugins/TabSRMM/src/muchighlight.h +++ b/plugins/TabSRMM/src/muchighlight.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/nen.h b/plugins/TabSRMM/src/nen.h index bca0b37885..d40d7367b6 100644 --- a/plugins/TabSRMM/src/nen.h +++ b/plugins/TabSRMM/src/nen.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/selectcontainer.cpp b/plugins/TabSRMM/src/selectcontainer.cpp index c8189f9622..dc835292ca 100644 --- a/plugins/TabSRMM/src/selectcontainer.cpp +++ b/plugins/TabSRMM/src/selectcontainer.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/sendlater.cpp b/plugins/TabSRMM/src/sendlater.cpp index 1ad1690076..1e8614d1fd 100644 --- a/plugins/TabSRMM/src/sendlater.cpp +++ b/plugins/TabSRMM/src/sendlater.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/sendlater.h b/plugins/TabSRMM/src/sendlater.h index bfe97b5fe6..5dc2a58df3 100644 --- a/plugins/TabSRMM/src/sendlater.h +++ b/plugins/TabSRMM/src/sendlater.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/sendqueue.cpp b/plugins/TabSRMM/src/sendqueue.cpp index 4e8372b7e4..be8566f188 100644 --- a/plugins/TabSRMM/src/sendqueue.cpp +++ b/plugins/TabSRMM/src/sendqueue.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/sendqueue.h b/plugins/TabSRMM/src/sendqueue.h index 89f32b17f8..7dfb4ff9b1 100644 --- a/plugins/TabSRMM/src/sendqueue.h +++ b/plugins/TabSRMM/src/sendqueue.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/sidebar.cpp b/plugins/TabSRMM/src/sidebar.cpp index c3014aa210..8f6de0a0f3 100644 --- a/plugins/TabSRMM/src/sidebar.cpp +++ b/plugins/TabSRMM/src/sidebar.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/sidebar.h b/plugins/TabSRMM/src/sidebar.h index b02e742d37..2e67eae807 100644 --- a/plugins/TabSRMM/src/sidebar.h +++ b/plugins/TabSRMM/src/sidebar.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/srmm.cpp b/plugins/TabSRMM/src/srmm.cpp index 9c9c3c6d2f..388565d140 100644 --- a/plugins/TabSRMM/src/srmm.cpp +++ b/plugins/TabSRMM/src/srmm.cpp @@ -1,144 +1,144 @@ -///////////////////////////////////////////////////////////////////////////////////////// -// Miranda NG: the free IM client for Microsoft* Windows* -// -// Copyright (C) 2012-22 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 -// -// plugin loading functions and global exports. - -#include "stdafx.h" - -LOGFONT lfDefault = { 0 }; - -/* - * miranda interfaces - */ - -CMPlugin g_plugin; - -///////////////////////////////////////////////////////////////////////////////////////// - -PLUGININFOEX pluginInfoEx = { - sizeof(PLUGININFOEX), - __PLUGIN_NAME, - PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), - __DESCRIPTION, - __AUTHOR, - __COPYRIGHT, - __AUTHORWEB, - UNICODE_AWARE, - // {6CA5F042-7A7F-47CC-A715-FC8C46FBF434} - { 0x6ca5f042, 0x7a7f, 0x47cc, { 0xa7, 0x15, 0xfc, 0x8c, 0x46, 0xfb, 0xf4, 0x34 } } -}; - -CMPlugin::CMPlugin() : - PLUGIN<CMPlugin>("SRMsg", pluginInfoEx), - - // main settings - bAutoMin(SRMSGMOD_T, "AutoMin", false), - bAutoCopy(SRMSGMOD_T, "autocopy", true), - bAutoTabs(SRMSGMOD_T, "autotabs", true), - bAllowTab(SRMSGMOD_T, "tabmode", false), - bAutoClose(SRMSGMOD_T, "AutoClose", false), - bAutoPopup(SRMSGMOD_T, "AutoPopup", false), - bAutoSplit(SRMSGMOD_T, "autosplit", false), - bDeleteTemp(SRMSGMOD_T, "deletetemp", false), - bUseXStatus(SRMSGMOD_T, "use_xicons", true), - bSendFormat(SRMSGMOD_T, "sendformat", false), - bHideOnClose(SRMSGMOD_T, "hideonclose", false), - bStatusOnTabs(SRMSGMOD_T, "tabstatus", true), - bFlashOnClist(SRMSGMOD_T, "flashcl", false), - bPasteAndSend(SRMSGMOD_T, "pasteandsend", true), - bAutoContainer(SRMSGMOD_T, "autocontainer", true), - bAutoSwitchTabs(SRMSGMOD_T, "autoswitchtabs", true), - bPopupContainer(SRMSGMOD_T, "cpopup", true), - bDetailedTooltips(SRMSGMOD_T, "d_tooltips", false), - bUseSameSplitSize(SRMSGMOD_T, "usesamesplitsize", true), - bAllowOfflineMultisend(SRMSGMOD_T, "AllowOfflineMultisend", true), - - // advanced options - bMetaBar(SRMSGMOD_T, "MetaiconBar", true), - bMetaTab(SRMSGMOD_T, "MetaiconTab", true), - bShowDesc(SRMSGMOD_T, "ShowClientDescription", false), - bCloseSend(SRMSGMOD_T, "adv_AutoClose_2", false), - bErrorPopup(SRMSGMOD_T, "adv_ErrorPopups", true), - - // chat settings - bOpenInDefault(CHAT_MODULE, "DefaultContainer", true), - bCreateWindowOnHighlight(CHAT_MODULE, "CreateWindowOnHighlight", false), - bBBCodeInPopups(CHAT_MODULE, "BBCodeInPopups", false), - bClassicIndicators(CHAT_MODULE, "ClassicIndicators", false), - bLogClassicIndicators(CHAT_MODULE, "LogClassicIndicators", false), - bAlternativeSorting(CHAT_MODULE, "AlternativeSorting", true), - bAnnoyingHighlight(CHAT_MODULE, "AnnoyingHighlight", false), - bLogSymbols(CHAT_MODULE, "LogSymbols", true), - bClickableNicks(CHAT_MODULE, "ClickableNicks", true), - bColorizeNicks(CHAT_MODULE, "ColorizeNicks", true), - bColorizeNicksInLog(CHAT_MODULE, "ColorizeNicksInLog", true), - bScaleIcons(CHAT_MODULE, "ScaleIcons", true), - bNewLineAfterNames(CHAT_MODULE, "NewlineAfterNames", false), - - // typing settings - bPopups(TypingModule, "TypingPopup", true), - bTypingNew(TypingModule, "DefaultTyping", true), - bTypingUnknown(TypingModule, "UnknownTyping", false), - - // log options - bUseDividers(SRMSGMOD_T, "usedividers", false), - bLogStatusChanges(SRMSGMOD_T, "logstatuschanges", false), - bDividersUsePopupConfig(SRMSGMOD_T, "div_popupconfig", false) -{} - -///////////////////////////////////////////////////////////////////////////////////////// - -extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_SRMM, MIID_LAST }; - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMPlugin::Load() -{ - SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfDefault), &lfDefault, FALSE); - - hLogger = RegisterSrmmLog(this, "built-in", LPGENW("tabSRMM internal log"), &logBuilder); - - Chat_Load(); - - return LoadSendRecvMessageModule(); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int CMPlugin::Unload() -{ - UnregisterSrmmLog(hLogger); - FreeLogFonts(); - Chat_Unload(); - int iRet = SplitmsgShutdown(); - Skin->setupTabCloseBitmap(true); - Skin->UnloadAeroTabs(); - CleanTempFiles(); - SendLater::shutDown(); - delete Skin; - delete sendQueue; - return iRet; -} +///////////////////////////////////////////////////////////////////////////////////////// +// Miranda NG: the free IM client for Microsoft* Windows* +// +// Copyright (C) 2012-23 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 +// +// plugin loading functions and global exports. + +#include "stdafx.h" + +LOGFONT lfDefault = { 0 }; + +/* + * miranda interfaces + */ + +CMPlugin g_plugin; + +///////////////////////////////////////////////////////////////////////////////////////// + +PLUGININFOEX pluginInfoEx = { + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {6CA5F042-7A7F-47CC-A715-FC8C46FBF434} + { 0x6ca5f042, 0x7a7f, 0x47cc, { 0xa7, 0x15, 0xfc, 0x8c, 0x46, 0xfb, 0xf4, 0x34 } } +}; + +CMPlugin::CMPlugin() : + PLUGIN<CMPlugin>("SRMsg", pluginInfoEx), + + // main settings + bAutoMin(SRMSGMOD_T, "AutoMin", false), + bAutoCopy(SRMSGMOD_T, "autocopy", true), + bAutoTabs(SRMSGMOD_T, "autotabs", true), + bAllowTab(SRMSGMOD_T, "tabmode", false), + bAutoClose(SRMSGMOD_T, "AutoClose", false), + bAutoPopup(SRMSGMOD_T, "AutoPopup", false), + bAutoSplit(SRMSGMOD_T, "autosplit", false), + bDeleteTemp(SRMSGMOD_T, "deletetemp", false), + bUseXStatus(SRMSGMOD_T, "use_xicons", true), + bSendFormat(SRMSGMOD_T, "sendformat", false), + bHideOnClose(SRMSGMOD_T, "hideonclose", false), + bStatusOnTabs(SRMSGMOD_T, "tabstatus", true), + bFlashOnClist(SRMSGMOD_T, "flashcl", false), + bPasteAndSend(SRMSGMOD_T, "pasteandsend", true), + bAutoContainer(SRMSGMOD_T, "autocontainer", true), + bAutoSwitchTabs(SRMSGMOD_T, "autoswitchtabs", true), + bPopupContainer(SRMSGMOD_T, "cpopup", true), + bDetailedTooltips(SRMSGMOD_T, "d_tooltips", false), + bUseSameSplitSize(SRMSGMOD_T, "usesamesplitsize", true), + bAllowOfflineMultisend(SRMSGMOD_T, "AllowOfflineMultisend", true), + + // advanced options + bMetaBar(SRMSGMOD_T, "MetaiconBar", true), + bMetaTab(SRMSGMOD_T, "MetaiconTab", true), + bShowDesc(SRMSGMOD_T, "ShowClientDescription", false), + bCloseSend(SRMSGMOD_T, "adv_AutoClose_2", false), + bErrorPopup(SRMSGMOD_T, "adv_ErrorPopups", true), + + // chat settings + bOpenInDefault(CHAT_MODULE, "DefaultContainer", true), + bCreateWindowOnHighlight(CHAT_MODULE, "CreateWindowOnHighlight", false), + bBBCodeInPopups(CHAT_MODULE, "BBCodeInPopups", false), + bClassicIndicators(CHAT_MODULE, "ClassicIndicators", false), + bLogClassicIndicators(CHAT_MODULE, "LogClassicIndicators", false), + bAlternativeSorting(CHAT_MODULE, "AlternativeSorting", true), + bAnnoyingHighlight(CHAT_MODULE, "AnnoyingHighlight", false), + bLogSymbols(CHAT_MODULE, "LogSymbols", true), + bClickableNicks(CHAT_MODULE, "ClickableNicks", true), + bColorizeNicks(CHAT_MODULE, "ColorizeNicks", true), + bColorizeNicksInLog(CHAT_MODULE, "ColorizeNicksInLog", true), + bScaleIcons(CHAT_MODULE, "ScaleIcons", true), + bNewLineAfterNames(CHAT_MODULE, "NewlineAfterNames", false), + + // typing settings + bPopups(TypingModule, "TypingPopup", true), + bTypingNew(TypingModule, "DefaultTyping", true), + bTypingUnknown(TypingModule, "UnknownTyping", false), + + // log options + bUseDividers(SRMSGMOD_T, "usedividers", false), + bLogStatusChanges(SRMSGMOD_T, "logstatuschanges", false), + bDividersUsePopupConfig(SRMSGMOD_T, "div_popupconfig", false) +{} + +///////////////////////////////////////////////////////////////////////////////////////// + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_SRMM, MIID_LAST }; + +///////////////////////////////////////////////////////////////////////////////////////// + +int CMPlugin::Load() +{ + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfDefault), &lfDefault, FALSE); + + hLogger = RegisterSrmmLog(this, "built-in", LPGENW("tabSRMM internal log"), &logBuilder); + + Chat_Load(); + + return LoadSendRecvMessageModule(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int CMPlugin::Unload() +{ + UnregisterSrmmLog(hLogger); + FreeLogFonts(); + Chat_Unload(); + int iRet = SplitmsgShutdown(); + Skin->setupTabCloseBitmap(true); + Skin->UnloadAeroTabs(); + CleanTempFiles(); + SendLater::shutDown(); + delete Skin; + delete sendQueue; + return iRet; +} diff --git a/plugins/TabSRMM/src/stdafx.cxx b/plugins/TabSRMM/src/stdafx.cxx index f64d25234b..ebbde0ade1 100644 --- a/plugins/TabSRMM/src/stdafx.cxx +++ b/plugins/TabSRMM/src/stdafx.cxx @@ -1,5 +1,5 @@ /* -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org) +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License diff --git a/plugins/TabSRMM/src/stdafx.h b/plugins/TabSRMM/src/stdafx.h index e09f24597b..4c87b4c428 100644 --- a/plugins/TabSRMM/src/stdafx.h +++ b/plugins/TabSRMM/src/stdafx.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/tabctrl.cpp b/plugins/TabSRMM/src/tabctrl.cpp index 29d2b0511b..35523e14f7 100644 --- a/plugins/TabSRMM/src/tabctrl.cpp +++ b/plugins/TabSRMM/src/tabctrl.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/taskbar.cpp b/plugins/TabSRMM/src/taskbar.cpp index a6eb6b8a2d..d49d937af5 100644 --- a/plugins/TabSRMM/src/taskbar.cpp +++ b/plugins/TabSRMM/src/taskbar.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/taskbar.h b/plugins/TabSRMM/src/taskbar.h index 92f8dcf85d..ed407dadbd 100644 --- a/plugins/TabSRMM/src/taskbar.h +++ b/plugins/TabSRMM/src/taskbar.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/templates.cpp b/plugins/TabSRMM/src/templates.cpp index 369c5ee4fe..a27a3df00f 100644 --- a/plugins/TabSRMM/src/templates.cpp +++ b/plugins/TabSRMM/src/templates.cpp @@ -1,124 +1,124 @@ -///////////////////////////////////////////////////////////////////////////////////////// -// Miranda NG: the free IM client for Microsoft* Windows* -// -// Copyright (C) 2012-22 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 -// -// Simple editor for the message log templates - -#include "stdafx.h" - -/* -* hardcoded default set of templates for both LTR and RTL. -* cannot be changed and may be used at any time to "revert" to a working layout -*/ - -char* TemplateNames[TMPL_MAX] = -{ - LPGEN("Message In"), - LPGEN("Message Out"), - LPGEN("Group In (Start)"), - LPGEN("Group Out (Start)"), - LPGEN("Group In (Inner)"), - LPGEN("Group Out (Inner)"), - LPGEN("Status change"), - LPGEN("Error message") -}; - -wchar_t* TemplateNamesW[TMPL_MAX] = -{ - LPGENW("Message In"), - LPGENW("Message Out"), - LPGENW("Group In (Start)"), - LPGENW("Group Out (Start)"), - LPGENW("Group In (Inner)"), - LPGENW("Group Out (Inner)"), - LPGENW("Status change"), - LPGENW("Error message") -}; - -TTemplateSet LTR_Default = -{ - TRUE, - L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", - L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", - L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", - L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", - L"%S %T%|%M", - L"%S %T%|%M", - L"%I %S %&r, %&T, %N %M%! ", - L"%I%S %r, %T, %e%l%M", - "Default LTR" -}; - -TTemplateSet RTL_Default = -{ - TRUE, - L"%I %N %r%n%S %T%|%M", - L"%I %N %r%n%S %T%|%M", - L"%I %N %r%n%S %T%|%M", - L"%I %N %r%n%S %T%|%M", - L"%S %T%|%M", - L"%S %T%|%M", - L"%I%S %r, %T, %N %M%! ", - L"%I%S %r, %T, %e%l%M", - "Default RTL" -}; - -TTemplateSet LTR_Active, RTL_Active; - -/* -* loads template set overrides from hContact into the given set of already existing -* templates -*/ - -static void LoadTemplatesFrom(TTemplateSet *tSet, MCONTACT hContact, int rtl) -{ - for (int i = 0; i < TMPL_MAX; i++) { - DBVARIANT dbv = { 0 }; - if (db_get_ws(hContact, rtl ? RTLTEMPLATES_MODULE : TEMPLATES_MODULE, TemplateNames[i], &dbv)) - continue; - if (dbv.type == DBVT_ASCIIZ || dbv.type == DBVT_WCHAR) - wcsncpy_s(tSet->szTemplates[i], dbv.pwszVal, _TRUNCATE); - db_free(&dbv); - } -} - -void LoadDefaultTemplates() -{ - LTR_Active = LTR_Default; - RTL_Active = RTL_Default; - - if (db_get_b(0, RTLTEMPLATES_MODULE, "setup", 0) < 2) { - for (int i = 0; i < TMPL_MAX; i++) - db_set_ws(0, RTLTEMPLATES_MODULE, TemplateNames[i], RTL_Default.szTemplates[i]); - db_set_b(0, RTLTEMPLATES_MODULE, "setup", 2); - } - if (db_get_b(0, TEMPLATES_MODULE, "setup", 0) < 2) { - for (int i = 0; i < TMPL_MAX; i++) - db_set_ws(0, TEMPLATES_MODULE, TemplateNames[i], LTR_Default.szTemplates[i]); - db_set_b(0, TEMPLATES_MODULE, "setup", 2); - } - LoadTemplatesFrom(<R_Active, 0, 0); - LoadTemplatesFrom(&RTL_Active, 0, 1); -} +///////////////////////////////////////////////////////////////////////////////////////// +// Miranda NG: the free IM client for Microsoft* Windows* +// +// Copyright (C) 2012-23 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 +// +// Simple editor for the message log templates + +#include "stdafx.h" + +/* +* hardcoded default set of templates for both LTR and RTL. +* cannot be changed and may be used at any time to "revert" to a working layout +*/ + +char* TemplateNames[TMPL_MAX] = +{ + LPGEN("Message In"), + LPGEN("Message Out"), + LPGEN("Group In (Start)"), + LPGEN("Group Out (Start)"), + LPGEN("Group In (Inner)"), + LPGEN("Group Out (Inner)"), + LPGEN("Status change"), + LPGEN("Error message") +}; + +wchar_t* TemplateNamesW[TMPL_MAX] = +{ + LPGENW("Message In"), + LPGENW("Message Out"), + LPGENW("Group In (Start)"), + LPGENW("Group Out (Start)"), + LPGENW("Group In (Inner)"), + LPGENW("Group Out (Inner)"), + LPGENW("Status change"), + LPGENW("Error message") +}; + +TTemplateSet LTR_Default = +{ + TRUE, + L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", + L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", + L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", + L"%I %N %?&r%\\&E%\\!, %\\T%\\!: %?n%?S %?T%?|%M", + L"%S %T%|%M", + L"%S %T%|%M", + L"%I %S %&r, %&T, %N %M%! ", + L"%I%S %r, %T, %e%l%M", + "Default LTR" +}; + +TTemplateSet RTL_Default = +{ + TRUE, + L"%I %N %r%n%S %T%|%M", + L"%I %N %r%n%S %T%|%M", + L"%I %N %r%n%S %T%|%M", + L"%I %N %r%n%S %T%|%M", + L"%S %T%|%M", + L"%S %T%|%M", + L"%I%S %r, %T, %N %M%! ", + L"%I%S %r, %T, %e%l%M", + "Default RTL" +}; + +TTemplateSet LTR_Active, RTL_Active; + +/* +* loads template set overrides from hContact into the given set of already existing +* templates +*/ + +static void LoadTemplatesFrom(TTemplateSet *tSet, MCONTACT hContact, int rtl) +{ + for (int i = 0; i < TMPL_MAX; i++) { + DBVARIANT dbv = { 0 }; + if (db_get_ws(hContact, rtl ? RTLTEMPLATES_MODULE : TEMPLATES_MODULE, TemplateNames[i], &dbv)) + continue; + if (dbv.type == DBVT_ASCIIZ || dbv.type == DBVT_WCHAR) + wcsncpy_s(tSet->szTemplates[i], dbv.pwszVal, _TRUNCATE); + db_free(&dbv); + } +} + +void LoadDefaultTemplates() +{ + LTR_Active = LTR_Default; + RTL_Active = RTL_Default; + + if (db_get_b(0, RTLTEMPLATES_MODULE, "setup", 0) < 2) { + for (int i = 0; i < TMPL_MAX; i++) + db_set_ws(0, RTLTEMPLATES_MODULE, TemplateNames[i], RTL_Default.szTemplates[i]); + db_set_b(0, RTLTEMPLATES_MODULE, "setup", 2); + } + if (db_get_b(0, TEMPLATES_MODULE, "setup", 0) < 2) { + for (int i = 0; i < TMPL_MAX; i++) + db_set_ws(0, TEMPLATES_MODULE, TemplateNames[i], LTR_Default.szTemplates[i]); + db_set_b(0, TEMPLATES_MODULE, "setup", 2); + } + LoadTemplatesFrom(<R_Active, 0, 0); + LoadTemplatesFrom(&RTL_Active, 0, 1); +} diff --git a/plugins/TabSRMM/src/themeio.cpp b/plugins/TabSRMM/src/themeio.cpp index 7648b8f90b..deaecbfcc6 100644 --- a/plugins/TabSRMM/src/themeio.cpp +++ b/plugins/TabSRMM/src/themeio.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/themes.cpp b/plugins/TabSRMM/src/themes.cpp index 14a762cc0a..7c48a65d5e 100644 --- a/plugins/TabSRMM/src/themes.cpp +++ b/plugins/TabSRMM/src/themes.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/themes.h b/plugins/TabSRMM/src/themes.h index 471c1a73f5..2e7bd1bf1c 100644 --- a/plugins/TabSRMM/src/themes.h +++ b/plugins/TabSRMM/src/themes.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/userprefs.cpp b/plugins/TabSRMM/src/userprefs.cpp index 573f2b883e..880112e041 100644 --- a/plugins/TabSRMM/src/userprefs.cpp +++ b/plugins/TabSRMM/src/userprefs.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/utils.cpp b/plugins/TabSRMM/src/utils.cpp index 74fab10f8a..484b5dca1d 100644 --- a/plugins/TabSRMM/src/utils.cpp +++ b/plugins/TabSRMM/src/utils.cpp @@ -1,7 +1,7 @@ //////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/utils.h b/plugins/TabSRMM/src/utils.h index f60cebe2e3..66716896ba 100644 --- a/plugins/TabSRMM/src/utils.h +++ b/plugins/TabSRMM/src/utils.h @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // -// Copyright (C) 2012-22 Miranda NG team, +// Copyright (C) 2012-23 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. diff --git a/plugins/TabSRMM/src/version.h b/plugins/TabSRMM/src/version.h index 37a59b1f3a..376079948b 100644 --- a/plugins/TabSRMM/src/version.h +++ b/plugins/TabSRMM/src/version.h @@ -10,4 +10,4 @@ #define __DESCRIPTION "IM and group chat module for Miranda NG." #define __AUTHOR "The Miranda developers team and contributors" #define __AUTHORWEB "https://miranda-ng.org/p/TabSRMM" -#define __COPYRIGHT "© 2012-22 Miranda NG team, 2000-2010 Miranda Project and contributors." +#define __COPYRIGHT "© 2012-23 Miranda NG team, 2000-2010 Miranda Project and contributors." diff --git a/plugins/TabSRMM/src/warning.cpp b/plugins/TabSRMM/src/warning.cpp index c5bf30736f..fe93ea374c 100644 --- a/plugins/TabSRMM/src/warning.cpp +++ b/plugins/TabSRMM/src/warning.cpp @@ -1,328 +1,328 @@ -/* -Copyright (C) 2012-22 Miranda NG team (https://miranda-ng.org) - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation version 2 -of the License. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "stdafx.h" - -using namespace CWarning; - -static MWindowList hWindowList; - -class CWarningImpl -{ - ptrW m_szTitle, m_szText; - UINT m_uId; - HFONT m_hFontCaption = nullptr; - uint32_t m_dwFlags; - HWND m_hwnd = nullptr; - bool m_fIsModal; - -public: - CWarningImpl(const wchar_t *tszTitle, const wchar_t *tszText, const UINT uId, const uint32_t dwFlags) : - m_szTitle(mir_wstrdup(tszTitle)), - m_szText(mir_wstrdup(tszText)) - { - m_uId = uId; - m_dwFlags = dwFlags; - m_fIsModal = ((m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL) ? true : false); - } - - ~CWarningImpl() - { - if (m_hFontCaption) - ::DeleteObject(m_hFontCaption); - } - - // static function to construct and show the dialog, returns the user's choice - LRESULT ShowDialog() const - { - if (!m_fIsModal) { - ::CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_WARNING), nullptr, stubDlgProc, LPARAM(this)); - return 0; - } - - return ::DialogBoxParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_WARNING), nullptr, stubDlgProc, LPARAM(this)); - } - - ///////////////////////////////////////////////////////////////////////////////////////// - // stub dlg procedure.Just register the object pointer in WM_INITDIALOG - - static INT_PTR CALLBACK stubDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) - { - CWarningImpl *w = reinterpret_cast<CWarningImpl *>(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); - if (w) - return(w->dlgProc(hwnd, msg, wParam, lParam)); - - switch (msg) { - case WM_INITDIALOG: - w = reinterpret_cast<CWarningImpl *>(lParam); - if (w) { - ::SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); - return(w->dlgProc(hwnd, msg, wParam, lParam)); - } - break; - } - return FALSE; - } - - ///////////////////////////////////////////////////////////////////////////////////////// - // dialog procedure for the warning dialog box - - INT_PTR CALLBACK dlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) - { - switch (msg) { - case WM_INITDIALOG: - m_hwnd = hwnd; - - ::SetWindowTextW(hwnd, TranslateT("TabSRMM warning message")); - ::Window_SetSkinIcon_IcoLib(hwnd, SKINICON_OTHER_MIRANDA); - ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_AUTOURLDETECT, TRUE, 0); - ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_SETEVENTMASK, 0, ENM_LINK); - - TranslateDialogDefault(hwnd); - { - CMStringW str(FORMAT, RTF_DEFAULT_HEADER, 0, 0, 0, 30 * 15); - str.Append(m_szText); - str.Append(L"}"); - str.Replace(L"\n", L"\\line "); - SETTEXTEX stx = {ST_SELECTION, CP_UTF8}; - ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_SETTEXTEX, (WPARAM)&stx, T2Utf(str)); - - ::SetDlgItemTextW(hwnd, IDC_CAPTION, m_szTitle); - - if (m_dwFlags & CWF_NOALLOWHIDE) - Utils::showDlgControl(hwnd, IDC_DONTSHOWAGAIN, SW_HIDE); - if (m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL) { - Utils::showDlgControl(hwnd, IDOK, SW_HIDE); - ::SetFocus(::GetDlgItem(hwnd, IDCANCEL)); - } - else { - Utils::showDlgControl(hwnd, IDCANCEL, SW_HIDE); - Utils::showDlgControl(hwnd, IDYES, SW_HIDE); - Utils::showDlgControl(hwnd, IDNO, SW_HIDE); - ::SetFocus(::GetDlgItem(hwnd, IDOK)); - } - - UINT uResId = 0; - if ((m_dwFlags & MB_ICONERROR) || (m_dwFlags & MB_ICONHAND)) - uResId = 32513; - else if ((m_dwFlags & MB_ICONEXCLAMATION) || (m_dwFlags & MB_ICONWARNING)) - uResId = 32515; - else if ((m_dwFlags & MB_ICONASTERISK) || (m_dwFlags & MB_ICONINFORMATION)) - uResId = 32516; - else if (m_dwFlags & MB_ICONQUESTION) - uResId = 32514; - - HICON hIcon; - if (uResId) - hIcon = reinterpret_cast<HICON>(::LoadImage(nullptr, MAKEINTRESOURCE(uResId), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); - else - hIcon = ::Skin_LoadIcon(SKINICON_EVENT_MESSAGE, true); - ::SendDlgItemMessageW(hwnd, IDC_WARNICON, STM_SETICON, reinterpret_cast<WPARAM>(hIcon), 0); - - if (!(m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL)) - ::ShowWindow(hwnd, SW_SHOWNORMAL); - - WindowList_Add(hWindowList, hwnd, (UINT_PTR)hwnd); - } - return TRUE; - - case WM_CTLCOLORSTATIC: - { - HWND hwndChild = reinterpret_cast<HWND>(lParam); - UINT id = ::GetDlgCtrlID(hwndChild); - if (nullptr == m_hFontCaption) { - HFONT hFont = reinterpret_cast<HFONT>(::SendDlgItemMessage(hwnd, IDC_CAPTION, WM_GETFONT, 0, 0)); - LOGFONT lf = {0}; - - ::GetObject(hFont, sizeof(lf), &lf); - lf.lfHeight = (int)((double)lf.lfHeight * 1.7f); - m_hFontCaption = ::CreateFontIndirect(&lf); - ::SendDlgItemMessage(hwnd, IDC_CAPTION, WM_SETFONT, (WPARAM)m_hFontCaption, FALSE); - } - - if (IDC_CAPTION == id) { - ::SetTextColor(reinterpret_cast<HDC>(wParam), ::GetSysColor(COLOR_HIGHLIGHT)); - ::SendMessage(hwndChild, WM_SETFONT, (WPARAM)m_hFontCaption, FALSE); - } - - if (IDC_WARNGROUP != id && IDC_DONTSHOWAGAIN != id) { - ::SetBkColor((HDC)wParam, ::GetSysColor(COLOR_WINDOW)); - return reinterpret_cast<INT_PTR>(::GetSysColorBrush(COLOR_WINDOW)); - } - } - break; - - case WM_COMMAND: - switch (LOWORD(wParam)) { - case IDOK: - case IDYES: - case IDNO: - if (::IsDlgButtonChecked(hwnd, IDC_DONTSHOWAGAIN)) { - uint32_t newVal = M.GetDword("cWarningsL", 0) | ((uint32_t)1L << m_uId); - db_set_dw(0, SRMSGMOD_T, "cWarningsL", newVal); - - if (LOWORD(wParam) != IDNO) { - newVal = M.GetDword("cWarningsV", 0) | ((uint32_t)1L << m_uId); - db_set_dw(0, SRMSGMOD_T, "cWarningsV", newVal); - } - } - __fallthrough; - - case IDCANCEL: - if (!m_fIsModal && (IDOK == LOWORD(wParam) || IDCANCEL == LOWORD(wParam))) // modeless dialogs can receive a IDCANCEL from destroyAll() - ::DestroyWindow(hwnd); - else - ::EndDialog(hwnd, LOWORD(wParam)); - break; - } - break; - - case WM_NOTIFY: - switch (((NMHDR *)lParam)->code) { - case EN_LINK: - switch (((ENLINK *)lParam)->msg) { - case WM_LBUTTONUP: - ENLINK *e = reinterpret_cast<ENLINK *>(lParam); - - const wchar_t *wszUrl = Utils::extractURLFromRichEdit(e, ::GetDlgItem(hwnd, IDC_WARNTEXT)); - if (wszUrl) { - Utils_OpenUrlW(wszUrl); - mir_free(const_cast<wchar_t *>(wszUrl)); - } - } - } - break; - - case WM_DESTROY: - ::SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); - delete this; - - WindowList_Remove(hWindowList, hwnd); - Window_FreeIcon_IcoLib(hwnd); - break; - } - - return FALSE; - } -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// implementation of the CWarningImpl class -// -// IMPORTANT note to translators for translation of the warning dialogs: -// -// Make sure to NOT remove the pipe character ( | ) from the strings. This separates the -// warning title from the actual warning text. -// -// Also, do NOT insert multiple | characters in the translated string. Not well-formatted -// warnings cannot be translated and the plugin will show the untranslated versions. -// -// strings marked with a NOT TRANSLATABLE comment cannot be translated at all. This -// will be used for important and critical error messages only. -// -// some strings are empty, this is intentional and used for error messages that share -// the message with other possible error notifications (popups, tool tips etc.) -// -// Entries that do not use the LPGENW() macro are NOT TRANSLATABLE, so don't bother translating them. - -static wchar_t *Warnings[] = { - nullptr, - LPGENW("Save file|Unable to save temporary file"), // WARN_SAVEFILE - LPGENW("Edit user notes|You are editing the user notes. Click the button again or use the hotkey (default: Alt+N) to save the notes and return to normal messaging mode"), /* WARN_EDITUSERNOTES */ - LPGENW("Missing component|The icon pack is missing. Please install it to the default icons folder.\n\nNo icons will be available"), /* WARN_ICONPACKMISSING */ - LPGENW("Aero peek warning|You have enabled Aero Peek features and loaded a custom container window skin\n\nThis can result in minor visual anomalies in the live preview feature."), /* WARN_AEROPEEKSKIN */ - LPGENW("File transfer problem|Sending the image by file transfer failed.\n\nPossible reasons: File transfers not supported, either you or the target contact is offline, or you are invisible and the target contact is not on your visibility list."), /* WARN_IMGSVC_MISSING */ - LPGENW("Settings problem|The option \\b1 History -> Imitate IEView API\\b0 is enabled and the History++ plugin is active. This can cause problems when using IEView as message log viewer.\n\nShould I correct the option (a restart is required)?"), /* WARN_HPP_APICHECK */ - LPGENW("Configuration issue|The unattended send feature is disabled. The \\b1 send later\\b0 and \\b1 send to multiple contacts\\b0 features depend on it.\n\nYou must enable it under \\b1Options -> Message sessions -> Advanced tweaks\\b0. Changing this option requires a restart."), /* WARN_NO_SENDLATER */ - LPGENW("Closing Window|You are about to close a window with multiple tabs open.\n\nProceed?"), /* WARN_CLOSEWINDOW */ - LPGENW("Closing options dialog|To reflect the changes done by importing a theme in the options dialog, the dialog must be closed after loading a theme \\b1 and unsaved changes might be lost\\b0 .\n\nDo you want to continue?"), /* WARN_OPTION_CLOSE */ - LPGENW("Loading a theme|Loading a color and font theme can overwrite the settings defined by your skin.\n\nDo you want to continue?"), /* WARN_THEME_OVERWRITE */ -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// send cancel message to all open warning dialogs so they are destroyed -// before TabSRMM is unloaded. -// -// called by the OkToExit handler in globals.cpp - -void CWarning::destroyAll() -{ - if (hWindowList) - WindowList_Broadcast(hWindowList, WM_COMMAND, MAKEWPARAM(IDCANCEL, 0), 0); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// show a warning dialog using the id value. Check whether the user has chosen to -// not show this message again. This has room for 64 different warning dialogs, which -// should be enough in the first place. Extending it should not be too hard though. - -LRESULT CWarning::show(const int uId, uint32_t dwFlags, const wchar_t *tszTxt) -{ - if (hWindowList == nullptr) - hWindowList = WindowList_Create(); - - // don't open new warnings when shutdown was initiated (modal ones will otherwise - // block the shutdown) - if (CMimAPI::m_shutDown) - return -1; - - wchar_t *_s = nullptr; - if (tszTxt) - _s = const_cast<wchar_t *>(tszTxt); - else { - if (uId == -1) - return -1; - - if (dwFlags & CWF_UNTRANSLATED) - _s = TranslateW(Warnings[uId]); - else { - // revert to untranslated warning when the translated message - // is not well-formatted. - _s = TranslateW(Warnings[uId]); - - if (mir_wstrlen(_s) < 3 || nullptr == wcschr(_s, '|')) - _s = TranslateW(Warnings[uId]); - } - } - - if (mir_wstrlen(_s) > 3 && wcschr(_s, '|') != nullptr) { - if (uId >= 0 && !(dwFlags & CWF_NOALLOWHIDE)) { - uint32_t val = M.GetDword("cWarningsL", 0); - uint32_t mask = ((__int64)1L) << uId; - if (mask & val) { - bool bResult = (M.GetDword("cWarningsV", 0) & mask) != 0; - if (dwFlags & MB_YESNO || dwFlags & MB_YESNOCANCEL) - return (bResult) ? IDYES : IDNO; - return IDOK; - } - } - - ptrW s(mir_wstrdup(_s)); - wchar_t *separator_pos = wcschr(s, '|'); - - if (separator_pos) { - *separator_pos = 0; - - CWarningImpl *w = new CWarningImpl(s, separator_pos + 1, uId, dwFlags); - if (dwFlags & MB_YESNO || dwFlags & MB_YESNOCANCEL) - return w->ShowDialog(); - - w->ShowDialog(); - } - } - return -1; -} +/* +Copyright (C) 2012-23 Miranda NG team (https://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +using namespace CWarning; + +static MWindowList hWindowList; + +class CWarningImpl +{ + ptrW m_szTitle, m_szText; + UINT m_uId; + HFONT m_hFontCaption = nullptr; + uint32_t m_dwFlags; + HWND m_hwnd = nullptr; + bool m_fIsModal; + +public: + CWarningImpl(const wchar_t *tszTitle, const wchar_t *tszText, const UINT uId, const uint32_t dwFlags) : + m_szTitle(mir_wstrdup(tszTitle)), + m_szText(mir_wstrdup(tszText)) + { + m_uId = uId; + m_dwFlags = dwFlags; + m_fIsModal = ((m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL) ? true : false); + } + + ~CWarningImpl() + { + if (m_hFontCaption) + ::DeleteObject(m_hFontCaption); + } + + // static function to construct and show the dialog, returns the user's choice + LRESULT ShowDialog() const + { + if (!m_fIsModal) { + ::CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_WARNING), nullptr, stubDlgProc, LPARAM(this)); + return 0; + } + + return ::DialogBoxParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_WARNING), nullptr, stubDlgProc, LPARAM(this)); + } + + ///////////////////////////////////////////////////////////////////////////////////////// + // stub dlg procedure.Just register the object pointer in WM_INITDIALOG + + static INT_PTR CALLBACK stubDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) + { + CWarningImpl *w = reinterpret_cast<CWarningImpl *>(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (w) + return(w->dlgProc(hwnd, msg, wParam, lParam)); + + switch (msg) { + case WM_INITDIALOG: + w = reinterpret_cast<CWarningImpl *>(lParam); + if (w) { + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); + return(w->dlgProc(hwnd, msg, wParam, lParam)); + } + break; + } + return FALSE; + } + + ///////////////////////////////////////////////////////////////////////////////////////// + // dialog procedure for the warning dialog box + + INT_PTR CALLBACK dlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) + { + switch (msg) { + case WM_INITDIALOG: + m_hwnd = hwnd; + + ::SetWindowTextW(hwnd, TranslateT("TabSRMM warning message")); + ::Window_SetSkinIcon_IcoLib(hwnd, SKINICON_OTHER_MIRANDA); + ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_AUTOURLDETECT, TRUE, 0); + ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_SETEVENTMASK, 0, ENM_LINK); + + TranslateDialogDefault(hwnd); + { + CMStringW str(FORMAT, RTF_DEFAULT_HEADER, 0, 0, 0, 30 * 15); + str.Append(m_szText); + str.Append(L"}"); + str.Replace(L"\n", L"\\line "); + SETTEXTEX stx = {ST_SELECTION, CP_UTF8}; + ::SendDlgItemMessage(hwnd, IDC_WARNTEXT, EM_SETTEXTEX, (WPARAM)&stx, T2Utf(str)); + + ::SetDlgItemTextW(hwnd, IDC_CAPTION, m_szTitle); + + if (m_dwFlags & CWF_NOALLOWHIDE) + Utils::showDlgControl(hwnd, IDC_DONTSHOWAGAIN, SW_HIDE); + if (m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL) { + Utils::showDlgControl(hwnd, IDOK, SW_HIDE); + ::SetFocus(::GetDlgItem(hwnd, IDCANCEL)); + } + else { + Utils::showDlgControl(hwnd, IDCANCEL, SW_HIDE); + Utils::showDlgControl(hwnd, IDYES, SW_HIDE); + Utils::showDlgControl(hwnd, IDNO, SW_HIDE); + ::SetFocus(::GetDlgItem(hwnd, IDOK)); + } + + UINT uResId = 0; + if ((m_dwFlags & MB_ICONERROR) || (m_dwFlags & MB_ICONHAND)) + uResId = 32513; + else if ((m_dwFlags & MB_ICONEXCLAMATION) || (m_dwFlags & MB_ICONWARNING)) + uResId = 32515; + else if ((m_dwFlags & MB_ICONASTERISK) || (m_dwFlags & MB_ICONINFORMATION)) + uResId = 32516; + else if (m_dwFlags & MB_ICONQUESTION) + uResId = 32514; + + HICON hIcon; + if (uResId) + hIcon = reinterpret_cast<HICON>(::LoadImage(nullptr, MAKEINTRESOURCE(uResId), IMAGE_ICON, 0, 0, LR_SHARED | LR_DEFAULTSIZE)); + else + hIcon = ::Skin_LoadIcon(SKINICON_EVENT_MESSAGE, true); + ::SendDlgItemMessageW(hwnd, IDC_WARNICON, STM_SETICON, reinterpret_cast<WPARAM>(hIcon), 0); + + if (!(m_dwFlags & MB_YESNO || m_dwFlags & MB_YESNOCANCEL)) + ::ShowWindow(hwnd, SW_SHOWNORMAL); + + WindowList_Add(hWindowList, hwnd, (UINT_PTR)hwnd); + } + return TRUE; + + case WM_CTLCOLORSTATIC: + { + HWND hwndChild = reinterpret_cast<HWND>(lParam); + UINT id = ::GetDlgCtrlID(hwndChild); + if (nullptr == m_hFontCaption) { + HFONT hFont = reinterpret_cast<HFONT>(::SendDlgItemMessage(hwnd, IDC_CAPTION, WM_GETFONT, 0, 0)); + LOGFONT lf = {0}; + + ::GetObject(hFont, sizeof(lf), &lf); + lf.lfHeight = (int)((double)lf.lfHeight * 1.7f); + m_hFontCaption = ::CreateFontIndirect(&lf); + ::SendDlgItemMessage(hwnd, IDC_CAPTION, WM_SETFONT, (WPARAM)m_hFontCaption, FALSE); + } + + if (IDC_CAPTION == id) { + ::SetTextColor(reinterpret_cast<HDC>(wParam), ::GetSysColor(COLOR_HIGHLIGHT)); + ::SendMessage(hwndChild, WM_SETFONT, (WPARAM)m_hFontCaption, FALSE); + } + + if (IDC_WARNGROUP != id && IDC_DONTSHOWAGAIN != id) { + ::SetBkColor((HDC)wParam, ::GetSysColor(COLOR_WINDOW)); + return reinterpret_cast<INT_PTR>(::GetSysColorBrush(COLOR_WINDOW)); + } + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + case IDYES: + case IDNO: + if (::IsDlgButtonChecked(hwnd, IDC_DONTSHOWAGAIN)) { + uint32_t newVal = M.GetDword("cWarningsL", 0) | ((uint32_t)1L << m_uId); + db_set_dw(0, SRMSGMOD_T, "cWarningsL", newVal); + + if (LOWORD(wParam) != IDNO) { + newVal = M.GetDword("cWarningsV", 0) | ((uint32_t)1L << m_uId); + db_set_dw(0, SRMSGMOD_T, "cWarningsV", newVal); + } + } + __fallthrough; + + case IDCANCEL: + if (!m_fIsModal && (IDOK == LOWORD(wParam) || IDCANCEL == LOWORD(wParam))) // modeless dialogs can receive a IDCANCEL from destroyAll() + ::DestroyWindow(hwnd); + else + ::EndDialog(hwnd, LOWORD(wParam)); + break; + } + break; + + case WM_NOTIFY: + switch (((NMHDR *)lParam)->code) { + case EN_LINK: + switch (((ENLINK *)lParam)->msg) { + case WM_LBUTTONUP: + ENLINK *e = reinterpret_cast<ENLINK *>(lParam); + + const wchar_t *wszUrl = Utils::extractURLFromRichEdit(e, ::GetDlgItem(hwnd, IDC_WARNTEXT)); + if (wszUrl) { + Utils_OpenUrlW(wszUrl); + mir_free(const_cast<wchar_t *>(wszUrl)); + } + } + } + break; + + case WM_DESTROY: + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); + delete this; + + WindowList_Remove(hWindowList, hwnd); + Window_FreeIcon_IcoLib(hwnd); + break; + } + + return FALSE; + } +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// implementation of the CWarningImpl class +// +// IMPORTANT note to translators for translation of the warning dialogs: +// +// Make sure to NOT remove the pipe character ( | ) from the strings. This separates the +// warning title from the actual warning text. +// +// Also, do NOT insert multiple | characters in the translated string. Not well-formatted +// warnings cannot be translated and the plugin will show the untranslated versions. +// +// strings marked with a NOT TRANSLATABLE comment cannot be translated at all. This +// will be used for important and critical error messages only. +// +// some strings are empty, this is intentional and used for error messages that share +// the message with other possible error notifications (popups, tool tips etc.) +// +// Entries that do not use the LPGENW() macro are NOT TRANSLATABLE, so don't bother translating them. + +static wchar_t *Warnings[] = { + nullptr, + LPGENW("Save file|Unable to save temporary file"), // WARN_SAVEFILE + LPGENW("Edit user notes|You are editing the user notes. Click the button again or use the hotkey (default: Alt+N) to save the notes and return to normal messaging mode"), /* WARN_EDITUSERNOTES */ + LPGENW("Missing component|The icon pack is missing. Please install it to the default icons folder.\n\nNo icons will be available"), /* WARN_ICONPACKMISSING */ + LPGENW("Aero peek warning|You have enabled Aero Peek features and loaded a custom container window skin\n\nThis can result in minor visual anomalies in the live preview feature."), /* WARN_AEROPEEKSKIN */ + LPGENW("File transfer problem|Sending the image by file transfer failed.\n\nPossible reasons: File transfers not supported, either you or the target contact is offline, or you are invisible and the target contact is not on your visibility list."), /* WARN_IMGSVC_MISSING */ + LPGENW("Settings problem|The option \\b1 History -> Imitate IEView API\\b0 is enabled and the History++ plugin is active. This can cause problems when using IEView as message log viewer.\n\nShould I correct the option (a restart is required)?"), /* WARN_HPP_APICHECK */ + LPGENW("Configuration issue|The unattended send feature is disabled. The \\b1 send later\\b0 and \\b1 send to multiple contacts\\b0 features depend on it.\n\nYou must enable it under \\b1Options -> Message sessions -> Advanced tweaks\\b0. Changing this option requires a restart."), /* WARN_NO_SENDLATER */ + LPGENW("Closing Window|You are about to close a window with multiple tabs open.\n\nProceed?"), /* WARN_CLOSEWINDOW */ + LPGENW("Closing options dialog|To reflect the changes done by importing a theme in the options dialog, the dialog must be closed after loading a theme \\b1 and unsaved changes might be lost\\b0 .\n\nDo you want to continue?"), /* WARN_OPTION_CLOSE */ + LPGENW("Loading a theme|Loading a color and font theme can overwrite the settings defined by your skin.\n\nDo you want to continue?"), /* WARN_THEME_OVERWRITE */ +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// send cancel message to all open warning dialogs so they are destroyed +// before TabSRMM is unloaded. +// +// called by the OkToExit handler in globals.cpp + +void CWarning::destroyAll() +{ + if (hWindowList) + WindowList_Broadcast(hWindowList, WM_COMMAND, MAKEWPARAM(IDCANCEL, 0), 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// show a warning dialog using the id value. Check whether the user has chosen to +// not show this message again. This has room for 64 different warning dialogs, which +// should be enough in the first place. Extending it should not be too hard though. + +LRESULT CWarning::show(const int uId, uint32_t dwFlags, const wchar_t *tszTxt) +{ + if (hWindowList == nullptr) + hWindowList = WindowList_Create(); + + // don't open new warnings when shutdown was initiated (modal ones will otherwise + // block the shutdown) + if (CMimAPI::m_shutDown) + return -1; + + wchar_t *_s = nullptr; + if (tszTxt) + _s = const_cast<wchar_t *>(tszTxt); + else { + if (uId == -1) + return -1; + + if (dwFlags & CWF_UNTRANSLATED) + _s = TranslateW(Warnings[uId]); + else { + // revert to untranslated warning when the translated message + // is not well-formatted. + _s = TranslateW(Warnings[uId]); + + if (mir_wstrlen(_s) < 3 || nullptr == wcschr(_s, '|')) + _s = TranslateW(Warnings[uId]); + } + } + + if (mir_wstrlen(_s) > 3 && wcschr(_s, '|') != nullptr) { + if (uId >= 0 && !(dwFlags & CWF_NOALLOWHIDE)) { + uint32_t val = M.GetDword("cWarningsL", 0); + uint32_t mask = ((__int64)1L) << uId; + if (mask & val) { + bool bResult = (M.GetDword("cWarningsV", 0) & mask) != 0; + if (dwFlags & MB_YESNO || dwFlags & MB_YESNOCANCEL) + return (bResult) ? IDYES : IDNO; + return IDOK; + } + } + + ptrW s(mir_wstrdup(_s)); + wchar_t *separator_pos = wcschr(s, '|'); + + if (separator_pos) { + *separator_pos = 0; + + CWarningImpl *w = new CWarningImpl(s, separator_pos + 1, uId, dwFlags); + if (dwFlags & MB_YESNO || dwFlags & MB_YESNOCANCEL) + return w->ShowDialog(); + + w->ShowDialog(); + } + } + return -1; +} -- cgit v1.2.3