From 91d5b91fe12c9733db9fd4d8eb84bfbe674b0a2c Mon Sep 17 00:00:00 2001 From: Rozhuk Ivan Date: Tue, 25 Nov 2014 20:29:32 +0000 Subject: TabSRMM null ptr fixes and code cleanup require for review. git-svn-id: http://svn.miranda-ng.org/main/trunk@11065 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/TabSRMM/src/contactcache.cpp | 1244 +++++++++++++++++----------------- 1 file changed, 622 insertions(+), 622 deletions(-) (limited to 'plugins/TabSRMM/src/contactcache.cpp') diff --git a/plugins/TabSRMM/src/contactcache.cpp b/plugins/TabSRMM/src/contactcache.cpp index 53ef5a3884..22a445fe3d 100644 --- a/plugins/TabSRMM/src/contactcache.cpp +++ b/plugins/TabSRMM/src/contactcache.cpp @@ -1,622 +1,622 @@ -/* - * astyle --force-indent=tab=4 --brackets=linux --indent-switches - * --pad=oper --one-line=keep-blocks --unpad=paren - * - * Miranda NG: the free IM client for Microsoft* Windows* - * - * Copyright (c) 2000-09 Miranda ICQ/IM project, - * all portions of this codebase are copyrighted to the people - * listed in contributors.txt. - * - * This programm 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 "commonheaders.h" - -static OBJLIST arContacts(50, NumericKeySortT); - -static DBCachedContact ccInvalid = { 0 }; - -CContactCache::CContactCache(const MCONTACT hContact) -{ - m_hContact = hContact; - m_wOldStatus = m_wStatus = ID_STATUS_OFFLINE; - - if (hContact) { - if ((cc = db_get_contact(hContact)) != NULL) { - initPhaseTwo(); - return; - } - } - - cc = &ccInvalid; - m_szAccount = C_INVALID_ACCOUNT; - m_isMeta = false; - m_Valid = 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 = 0; - if (cc->szProto) { - PROTOACCOUNT *acc = reinterpret_cast(::CallService(MS_PROTO_GETACCOUNT, 0, (LPARAM)cc->szProto)); - if (acc && acc->tszAccountName) - m_szAccount = acc->tszAccountName; - } - - m_Valid = (cc->szProto != 0 && m_szAccount != 0) ? true : false; - if (m_Valid) { - m_isMeta = db_mc_isMeta(cc->contactID) != 0; // don't use cc->IsMeta() here - if (m_isMeta) - updateMeta(); - updateState(); - updateFavorite(); - } - else { - cc->szProto = C_INVALID_PROTO; - 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 = 0; - m_wMetaStatus = 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_hwnd) - ::SendMessage(m_hwnd, WM_CLOSE, 1, 2); -} - -void CContactCache::updateState() -{ - updateNick(); - updateStatus(); -} - -/** - * 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_Valid) { - TCHAR *tszNick = pcli->pfnGetContactDisplayName(getActiveContact(), 0); - if (tszNick) - fChanged = (_tcscmp(m_szNick, tszNick) ? true : false); - mir_sntprintf(m_szNick, 80, _T("%s"), tszNick ? tszNick : _T("")); - } - return fChanged; -} - -/** - * update status mode - * @return bool: true if status mode has changed, false if not. - */ -bool CContactCache::updateStatus() -{ - if (!m_Valid) - return false; - - m_wOldStatus = m_wStatus; - m_wStatus = (WORD)db_get_w(getActiveContact(), getActiveProto(), "Status", ID_STATUS_OFFLINE); - return m_wOldStatus != m_wStatus; -} - -/** - * 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_Valid) { - MCONTACT hOldSub = m_hSub; - m_hSub = db_mc_getSrmmSub(cc->contactID); - m_szMetaProto = GetContactProto(m_hSub); - m_wMetaStatus = (WORD)db_get_w(m_hSub, m_szMetaProto, "Status", ID_STATUS_OFFLINE); - PROTOACCOUNT *pa = ProtoGetAccount(m_szMetaProto); - if (pa) - m_szAccount = pa->tszAccountName; - - if (hOldSub != m_hSub) { - updateStatus(); - updateNick(); - updateUIN(); - } - } - else { - m_hSub = 0; - m_szMetaProto = NULL; - m_wMetaStatus = 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_Valid) { - CONTACTINFO ci = { sizeof(ci) }; - ci.hContact = getActiveContact(); - ci.szProto = const_cast(getActiveProto()); - ci.dwFlag = CNF_DISPLAYUID | CNF_TCHAR; - if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM)&ci)) { - switch (ci.type) { - case CNFT_ASCIIZ: - mir_sntprintf(m_szUIN, SIZEOF(m_szUIN), _T("%s"), reinterpret_cast(ci.pszVal)); - mir_free((void*)ci.pszVal); - break; - case CNFT_DWORD: - mir_sntprintf(m_szUIN, SIZEOF(m_szUIN), _T("%u"), ci.dVal); - break; - } - } - } - - return false; -} - -void CContactCache::updateStats(int iType, size_t value) -{ - if (m_stats == 0) - allocStats(); - - 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; - } -} - -void CContactCache::allocStats() -{ - if (m_stats == 0) { - m_stats = new TSessionStats; - ::ZeroMemory(m_stats, sizeof(TSessionStats)); - } -} - -/** - * 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 hwnd: window handle - * @param dat: _MessageWindowData - window data structure - */ -void CContactCache::setWindowData(const HWND hwnd, TWindowData *dat) -{ - m_hwnd = hwnd; - m_dat = dat; - if (hwnd && dat && m_history == 0) - allocHistory(); - if (hwnd) - updateStatusMsg(); - else { - /* release memory - not needed when window isn't open */ - if (m_szStatusMsg) { - mir_free(m_szStatusMsg); - m_szStatusMsg = 0; - } - if (m_ListeningInfo) { - mir_free(m_ListeningInfo); - m_ListeningInfo = 0; - } - if (m_xStatusMsg) { - mir_free(m_xStatusMsg); - m_xStatusMsg = 0; - } - } -} - -/** - * 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(WPARAM wParam, LPARAM lParam) -{ - size_t iLength = 0, iStreamLength = 0; - int oldTop = 0; - char* szFromStream = NULL; - - if (m_hwnd == 0 || m_dat == 0) - return; - - if (wParam) { - oldTop = m_iHistoryTop; - m_iHistoryTop = (int)wParam; - } - - szFromStream = ::Message_GetFromStream(GetDlgItem(m_hwnd, IDC_MESSAGE), m_dat, (CP_UTF8 << 16) | (SF_RTFNOOBJS | SFF_PLAINRTF | SF_USECODEPAGE | SF_NCRFORNONASCII)); - - iLength = iStreamLength = (strlen(szFromStream) + 1); - - if (iLength > 0 && m_history != NULL) { - if ((m_iHistoryTop == m_iHistorySize) && oldTop == 0) { // shift the stack down... - TInputHistory ihTemp = m_history[0]; - m_iHistoryTop--; - ::MoveMemory((void*)&m_history[0], (void*)&m_history[1], (m_iHistorySize - 1) * sizeof(TInputHistory)); - m_history[m_iHistoryTop] = ihTemp; - } - if (iLength > m_history[m_iHistoryTop].lLen) { - if (m_history[m_iHistoryTop].szText == NULL) { - if (iLength < HISTORY_INITIAL_ALLOCSIZE) - iLength = HISTORY_INITIAL_ALLOCSIZE; - m_history[m_iHistoryTop].szText = (TCHAR*)mir_alloc(iLength); - m_history[m_iHistoryTop].lLen = iLength; - } else { - if (iLength > m_history[m_iHistoryTop].lLen) { - m_history[m_iHistoryTop].szText = (TCHAR*)mir_realloc(m_history[m_iHistoryTop].szText, iLength); - m_history[m_iHistoryTop].lLen = iLength; - } - } - } - ::CopyMemory(m_history[m_iHistoryTop].szText, szFromStream, iStreamLength); - if (!oldTop) { - if (m_iHistoryTop < m_iHistorySize) { - m_iHistoryTop++; - m_iHistoryCurrent = m_iHistoryTop; - } - } - } - if (szFromStream) - mir_free(szFromStream); - if (oldTop) - m_iHistoryTop = oldTop; -} - -/** - * 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_hwnd == 0 || m_dat == 0) - return; - - if (m_history != NULL && m_history[0].szText != NULL) { // at least one entry needs to be alloced, otherwise we get a nice infinite loop ;) - HWND hwndEdit = ::GetDlgItem(m_hwnd, IDC_MESSAGE); - SETTEXTEX stx = {ST_DEFAULT, CP_UTF8}; - - if (m_dat->dwFlags & MWF_NEEDHISTORYSAVE) { - m_iHistoryCurrent = m_iHistoryTop; - if (::GetWindowTextLengthA(hwndEdit) > 0) - saveHistory(m_iHistorySize, 0); - else - m_history[m_iHistorySize].szText[0] = (TCHAR)'\0'; - } - if (wParam == VK_UP) { - if (m_iHistoryCurrent == 0) - return; - m_iHistoryCurrent--; - } - else { - m_iHistoryCurrent++; - if (m_iHistoryCurrent > m_iHistoryTop) - m_iHistoryCurrent = m_iHistoryTop; - } - if (m_iHistoryCurrent == m_iHistoryTop) { - if (m_history[m_iHistorySize].szText != NULL) { // replace the temp buffer - ::SetWindowText(hwndEdit, _T("")); - ::SendMessage(hwndEdit, EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)m_history[m_iHistorySize].szText); - ::SendMessage(hwndEdit, EM_SETSEL, -1, -1); - } - } - else { - if (m_history[m_iHistoryCurrent].szText != NULL) { - ::SetWindowText(hwndEdit, _T("")); - ::SendMessage(hwndEdit, EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)m_history[m_iHistoryCurrent].szText); - ::SendMessage(hwndEdit, EM_SETSEL, -1, -1); - } - else ::SetWindowText(hwndEdit, _T("")); - } - ::SendMessage(m_hwnd, WM_COMMAND, MAKEWPARAM(::GetDlgCtrlID(hwndEdit), EN_CHANGE), (LPARAM)hwndEdit); - m_dat->dwFlags &= ~MWF_NEEDHISTORYSAVE; - } -} - -/** - * allocate the input history (on-demand, when it is requested by - * opening a message window for this contact). - * - * note: it allocs historysize + 1 elements, because the + 1 is used - * for the temporary buffer which saves the current input line when - * using input history scrolling. - */ -void CContactCache::allocHistory() -{ - m_iHistorySize = M.GetByte("historysize", 15); - if (m_iHistorySize < 10) - m_iHistorySize = 10; - m_history = (TInputHistory *)mir_alloc(sizeof(TInputHistory) * (m_iHistorySize + 1)); - m_iHistoryCurrent = 0; - m_iHistoryTop = 0; - if (m_history) - ZeroMemory(m_history, sizeof(TInputHistory) * m_iHistorySize); - m_history[m_iHistorySize].szText = (TCHAR*)mir_alloc((HISTORY_INITIAL_ALLOCSIZE + 1) * sizeof(TCHAR)); - m_history[m_iHistorySize].lLen = HISTORY_INITIAL_ALLOCSIZE; -} - -/** - * release additional memory resources - */ -void CContactCache::releaseAlloced() -{ - if (m_stats) { - delete m_stats; - m_stats = 0; - } - - if (m_history) { - for (int i = 0; i <= m_iHistorySize; i++) - mir_free(m_history[i].szText); - - mir_free(m_history); - m_history = 0; - } - - mir_free(m_szStatusMsg); - m_szStatusMsg = NULL; -} - -/** - * when a contact is deleted, mark it as invalid in the cache and release - * all memory it has allocated. - */ -void CContactCache::deletedHandler() -{ - m_Valid = false; - if (m_hwnd) - ::SendMessage(m_hwnd, WM_CLOSE, 1, 2); - - releaseAlloced(); - m_hContact = (MCONTACT)-1; -} - -/** - * udpate favorite or recent state. runs when user manually adds - * or removes a user from that list or when database setting is - * changed from elsewhere - */ -void CContactCache::updateFavorite() -{ - m_isFavorite = db_get_b(m_hContact, SRMSGMOD_T, "isFavorite", 0) != 0; - m_isRecent = M.GetDword(m_hContact, "isRecent", 0) ? true : false; -} - -/** - * 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_Valid) - return; - - MCONTACT hContact = getActiveContact(); - - if (szKey == 0 || (szKey && !strcmp("StatusMsg", szKey))) { - if (m_szStatusMsg) - mir_free(m_szStatusMsg); - m_szStatusMsg = 0; - ptrT szStatus(db_get_tsa(hContact, "CList", "StatusMsg")); - if (szStatus != 0) - m_szStatusMsg = (lstrlen(szStatus) > 0 ? getNormalizedStatusMsg(szStatus) : 0); - } - if (szKey == 0 || (szKey && !strcmp("ListeningTo", szKey))) { - if (m_ListeningInfo) - mir_free(m_ListeningInfo); - m_ListeningInfo = 0; - ptrT szListeningTo(db_get_tsa(hContact, cc->szProto, "ListeningTo")); - if (szListeningTo != 0 && *szListeningTo) - m_ListeningInfo = szListeningTo.detouch(); - } - if (szKey == 0 || (szKey && !strcmp("XStatusMsg", szKey))) { - if (m_xStatusMsg) - mir_free(m_xStatusMsg); - m_xStatusMsg = 0; - ptrT szXStatusMsg(db_get_tsa(hContact, cc->szProto, "XStatusMsg")); - if (szXStatusMsg != 0 && *szXStatusMsg) - m_xStatusMsg = szXStatusMsg.detouch(); - } - 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 == NULL) { - 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 (int i=0; i < arContacts.getCount(); i++) { - CContactCache &c = arContacts[i]; - 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 TCHAR*: original status message - * @param fStripAll bool: strip all cr/lf sequences and replace them with spaces (use for title bar) - * @return TCHAR*: converted status message. CALLER is responsible to mir_free it, MUST use mir_free() - */ -TCHAR* CContactCache::getNormalizedStatusMsg(const TCHAR *src, bool fStripAll) -{ - size_t k = 0, i = 0; - TCHAR* tszResult = 0; - - if (src == 0 || lstrlen(src) < 2) - return 0; - - tstring dest; - - for (i=0; i < _tcslen(src); i++) { - if (src[i] == 0x0d || src[i] == '\t') - continue; - if (i && src[i] == (TCHAR)0x0a) { - if (fStripAll) { - dest.append(_T(" ")); - continue; - } - dest.append(_T("\n")); - continue; - } - dest += src[i]; - } - - if (i) { - tszResult = (TCHAR*)mir_alloc((dest.length() + 1) * sizeof(TCHAR)); - _tcscpy(tszResult, dest.c_str()); - tszResult[dest.length()] = 0; - } - return tszResult; -} - -/** - * retrieve the tab/title icon for the corresponding session. - */ -HICON CContactCache::getIcon(int& iSize) const -{ - if (!m_dat || !m_hwnd) - return LoadSkinnedProtoIcon(cc->szProto, m_wStatus); - - if (m_dat->dwFlags & MWF_ERRORSTATE) - return PluginConfig.g_iconErr; - if (m_dat->mayFlashTab) - return m_dat->iFlashIcon; - - if (m_dat->si && m_dat->iFlashIcon) { - int sizeX, sizeY; - Utils::getIconSize(m_dat->iFlashIcon, sizeX, sizeY); - iSize = sizeX; - return m_dat->iFlashIcon; - } - if (m_dat->hTabIcon == m_dat->hTabStatusIcon && m_dat->hXStatusIcon) - return m_dat->hXStatusIcon; - return m_dat->hTabIcon; -} - -int CContactCache::getMaxMessageLength() -{ - 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)) { - if (m_hwnd) - ::SendDlgItemMessage(m_hwnd, IDC_MESSAGE, EM_EXLIMITTEXT, 0, 20000); - } - else { - if (m_hwnd) - ::SendDlgItemMessage(m_hwnd, IDC_MESSAGE, EM_EXLIMITTEXT, 0, (LPARAM)m_nMax); - } - } - else { - if (m_hwnd) - ::SendDlgItemMessage(m_hwnd, IDC_MESSAGE, EM_EXLIMITTEXT, 0, 20000); - m_nMax = 20000; - } - } - return m_nMax; -} +/* + * astyle --force-indent=tab=4 --brackets=linux --indent-switches + * --pad=oper --one-line=keep-blocks --unpad=paren + * + * Miranda NG: the free IM client for Microsoft* Windows* + * + * Copyright (c) 2000-09 Miranda ICQ/IM project, + * all portions of this codebase are copyrighted to the people + * listed in contributors.txt. + * + * This programm 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 "commonheaders.h" + +static OBJLIST arContacts(50, NumericKeySortT); + +static DBCachedContact ccInvalid = { 0 }; + +CContactCache::CContactCache(const MCONTACT hContact) +{ + m_hContact = hContact; + m_wOldStatus = m_wStatus = ID_STATUS_OFFLINE; + + if (hContact) { + if ((cc = db_get_contact(hContact)) != NULL) { + initPhaseTwo(); + return; + } + } + + cc = &ccInvalid; + m_szAccount = C_INVALID_ACCOUNT; + m_isMeta = false; + m_Valid = 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 = 0; + if (cc->szProto) { + PROTOACCOUNT *acc = reinterpret_cast(::CallService(MS_PROTO_GETACCOUNT, 0, (LPARAM)cc->szProto)); + if (acc && acc->tszAccountName) + m_szAccount = acc->tszAccountName; + } + + m_Valid = (cc->szProto != 0 && m_szAccount != 0) ? true : false; + if (m_Valid) { + m_isMeta = db_mc_isMeta(cc->contactID) != 0; // don't use cc->IsMeta() here + if (m_isMeta) + updateMeta(); + updateState(); + updateFavorite(); + } + else { + cc->szProto = C_INVALID_PROTO; + 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 = 0; + m_wMetaStatus = 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_hwnd) + ::SendMessage(m_hwnd, WM_CLOSE, 1, 2); +} + +void CContactCache::updateState() +{ + updateNick(); + updateStatus(); +} + +/** + * 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_Valid) { + TCHAR *tszNick = pcli->pfnGetContactDisplayName(getActiveContact(), 0); + if (tszNick) + fChanged = (_tcscmp(m_szNick, tszNick) ? true : false); + mir_sntprintf(m_szNick, 80, _T("%s"), tszNick ? tszNick : _T("")); + } + return fChanged; +} + +/** + * update status mode + * @return bool: true if status mode has changed, false if not. + */ +bool CContactCache::updateStatus() +{ + if (!m_Valid) + return false; + + m_wOldStatus = m_wStatus; + m_wStatus = (WORD)db_get_w(getActiveContact(), getActiveProto(), "Status", ID_STATUS_OFFLINE); + return m_wOldStatus != m_wStatus; +} + +/** + * 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_Valid) { + MCONTACT hOldSub = m_hSub; + m_hSub = db_mc_getSrmmSub(cc->contactID); + m_szMetaProto = GetContactProto(m_hSub); + m_wMetaStatus = (WORD)db_get_w(m_hSub, m_szMetaProto, "Status", ID_STATUS_OFFLINE); + PROTOACCOUNT *pa = ProtoGetAccount(m_szMetaProto); + if (pa) + m_szAccount = pa->tszAccountName; + + if (hOldSub != m_hSub) { + updateStatus(); + updateNick(); + updateUIN(); + } + } + else { + m_hSub = 0; + m_szMetaProto = NULL; + m_wMetaStatus = 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_Valid) { + CONTACTINFO ci = { sizeof(ci) }; + ci.hContact = getActiveContact(); + ci.szProto = const_cast(getActiveProto()); + ci.dwFlag = CNF_DISPLAYUID | CNF_TCHAR; + if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM)&ci)) { + switch (ci.type) { + case CNFT_ASCIIZ: + mir_sntprintf(m_szUIN, SIZEOF(m_szUIN), _T("%s"), reinterpret_cast(ci.pszVal)); + mir_free((void*)ci.pszVal); + break; + case CNFT_DWORD: + mir_sntprintf(m_szUIN, SIZEOF(m_szUIN), _T("%u"), ci.dVal); + break; + } + } + } + + return false; +} + +void CContactCache::updateStats(int iType, size_t value) +{ + if (m_stats == 0) + allocStats(); + + 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; + } +} + +void CContactCache::allocStats() +{ + if (m_stats == 0) { + m_stats = new TSessionStats; + ::ZeroMemory(m_stats, sizeof(TSessionStats)); + } +} + +/** + * 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 hwnd: window handle + * @param dat: _MessageWindowData - window data structure + */ +void CContactCache::setWindowData(const HWND hwnd, TWindowData *dat) +{ + m_hwnd = hwnd; + m_dat = dat; + if (hwnd && dat && m_history == 0) + allocHistory(); + if (hwnd) + updateStatusMsg(); + else { + /* release memory - not needed when window isn't open */ + if (m_szStatusMsg) { + mir_free(m_szStatusMsg); + m_szStatusMsg = 0; + } + if (m_ListeningInfo) { + mir_free(m_ListeningInfo); + m_ListeningInfo = 0; + } + if (m_xStatusMsg) { + mir_free(m_xStatusMsg); + m_xStatusMsg = 0; + } + } +} + +/** + * 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(WPARAM wParam, LPARAM lParam) +{ + size_t iLength = 0, iStreamLength = 0; + int oldTop = 0; + char* szFromStream = NULL; + + if (m_hwnd == 0 || m_dat == 0) + return; + + if (wParam) { + oldTop = m_iHistoryTop; + m_iHistoryTop = (int)wParam; + } + + szFromStream = ::Message_GetFromStream(GetDlgItem(m_hwnd, IDC_MESSAGE), m_dat, (CP_UTF8 << 16) | (SF_RTFNOOBJS | SFF_PLAINRTF | SF_USECODEPAGE | SF_NCRFORNONASCII)); + if (szFromStream != NULL) { + iLength = iStreamLength = (strlen(szFromStream) + 1); + + if (iLength > 0 && m_history != NULL) { // XXX: iLength > 1 ? + if ((m_iHistoryTop == m_iHistorySize) && oldTop == 0) { // shift the stack down... + TInputHistory ihTemp = m_history[0]; + m_iHistoryTop--; + ::MoveMemory((void*)&m_history[0], (void*)&m_history[1], (m_iHistorySize - 1) * sizeof(TInputHistory)); + m_history[m_iHistoryTop] = ihTemp; + } + if (iLength > m_history[m_iHistoryTop].lLen) { + if (m_history[m_iHistoryTop].szText == NULL) { + if (iLength < HISTORY_INITIAL_ALLOCSIZE) + iLength = HISTORY_INITIAL_ALLOCSIZE; + m_history[m_iHistoryTop].szText = (TCHAR*)mir_alloc(iLength); + m_history[m_iHistoryTop].lLen = iLength; + } else { + if (iLength > m_history[m_iHistoryTop].lLen) { + m_history[m_iHistoryTop].szText = (TCHAR*)mir_realloc(m_history[m_iHistoryTop].szText, iLength); + m_history[m_iHistoryTop].lLen = iLength; + } + } + } + ::CopyMemory(m_history[m_iHistoryTop].szText, szFromStream, iStreamLength); + if (!oldTop) { + if (m_iHistoryTop < m_iHistorySize) { + m_iHistoryTop++; + m_iHistoryCurrent = m_iHistoryTop; + } + } + } + mir_free(szFromStream); + } + if (oldTop) + m_iHistoryTop = oldTop; +} + +/** + * 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_hwnd == 0 || m_dat == 0) + return; + + if (m_history != NULL && m_history[0].szText != NULL) { // at least one entry needs to be alloced, otherwise we get a nice infinite loop ;) + HWND hwndEdit = ::GetDlgItem(m_hwnd, IDC_MESSAGE); + SETTEXTEX stx = {ST_DEFAULT, CP_UTF8}; + + if (m_dat->dwFlags & MWF_NEEDHISTORYSAVE) { + m_iHistoryCurrent = m_iHistoryTop; + if (::GetWindowTextLengthA(hwndEdit) > 0) + saveHistory(m_iHistorySize, 0); + else + m_history[m_iHistorySize].szText[0] = (TCHAR)'\0'; + } + if (wParam == VK_UP) { + if (m_iHistoryCurrent == 0) + return; + m_iHistoryCurrent--; + } + else { + m_iHistoryCurrent++; + if (m_iHistoryCurrent > m_iHistoryTop) + m_iHistoryCurrent = m_iHistoryTop; + } + if (m_iHistoryCurrent == m_iHistoryTop) { + if (m_history[m_iHistorySize].szText != NULL) { // replace the temp buffer + ::SetWindowText(hwndEdit, _T("")); + ::SendMessage(hwndEdit, EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)m_history[m_iHistorySize].szText); + ::SendMessage(hwndEdit, EM_SETSEL, -1, -1); + } + } + else { + if (m_history[m_iHistoryCurrent].szText != NULL) { + ::SetWindowText(hwndEdit, _T("")); + ::SendMessage(hwndEdit, EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)m_history[m_iHistoryCurrent].szText); + ::SendMessage(hwndEdit, EM_SETSEL, -1, -1); + } + else ::SetWindowText(hwndEdit, _T("")); + } + ::SendMessage(m_hwnd, WM_COMMAND, MAKEWPARAM(::GetDlgCtrlID(hwndEdit), EN_CHANGE), (LPARAM)hwndEdit); + m_dat->dwFlags &= ~MWF_NEEDHISTORYSAVE; + } +} + +/** + * allocate the input history (on-demand, when it is requested by + * opening a message window for this contact). + * + * note: it allocs historysize + 1 elements, because the + 1 is used + * for the temporary buffer which saves the current input line when + * using input history scrolling. + */ +void CContactCache::allocHistory() +{ + m_iHistorySize = M.GetByte("historysize", 15); + if (m_iHistorySize < 10) + m_iHistorySize = 10; + m_history = (TInputHistory *)mir_alloc(sizeof(TInputHistory) * (m_iHistorySize + 1)); + m_iHistoryCurrent = 0; + m_iHistoryTop = 0; + if (m_history) + ZeroMemory(m_history, sizeof(TInputHistory) * m_iHistorySize); + m_history[m_iHistorySize].szText = (TCHAR*)mir_alloc((HISTORY_INITIAL_ALLOCSIZE + 1) * sizeof(TCHAR)); + m_history[m_iHistorySize].lLen = HISTORY_INITIAL_ALLOCSIZE; +} + +/** + * release additional memory resources + */ +void CContactCache::releaseAlloced() +{ + if (m_stats) { + delete m_stats; + m_stats = 0; + } + + if (m_history) { + for (int i = 0; i <= m_iHistorySize; i++) + mir_free(m_history[i].szText); + + mir_free(m_history); + m_history = 0; + } + + mir_free(m_szStatusMsg); + m_szStatusMsg = NULL; +} + +/** + * when a contact is deleted, mark it as invalid in the cache and release + * all memory it has allocated. + */ +void CContactCache::deletedHandler() +{ + m_Valid = false; + if (m_hwnd) + ::SendMessage(m_hwnd, WM_CLOSE, 1, 2); + + releaseAlloced(); + m_hContact = (MCONTACT)-1; +} + +/** + * udpate favorite or recent state. runs when user manually adds + * or removes a user from that list or when database setting is + * changed from elsewhere + */ +void CContactCache::updateFavorite() +{ + m_isFavorite = db_get_b(m_hContact, SRMSGMOD_T, "isFavorite", 0) != 0; + m_isRecent = M.GetDword(m_hContact, "isRecent", 0) ? true : false; +} + +/** + * 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_Valid) + return; + + MCONTACT hContact = getActiveContact(); + + if (szKey == 0 || (szKey && !strcmp("StatusMsg", szKey))) { + if (m_szStatusMsg) + mir_free(m_szStatusMsg); + m_szStatusMsg = 0; + ptrT szStatus(db_get_tsa(hContact, "CList", "StatusMsg")); + if (szStatus != 0) + m_szStatusMsg = (lstrlen(szStatus) > 0 ? getNormalizedStatusMsg(szStatus) : 0); + } + if (szKey == 0 || (szKey && !strcmp("ListeningTo", szKey))) { + if (m_ListeningInfo) + mir_free(m_ListeningInfo); + m_ListeningInfo = 0; + ptrT szListeningTo(db_get_tsa(hContact, cc->szProto, "ListeningTo")); + if (szListeningTo != 0 && *szListeningTo) + m_ListeningInfo = szListeningTo.detouch(); + } + if (szKey == 0 || (szKey && !strcmp("XStatusMsg", szKey))) { + if (m_xStatusMsg) + mir_free(m_xStatusMsg); + m_xStatusMsg = 0; + ptrT szXStatusMsg(db_get_tsa(hContact, cc->szProto, "XStatusMsg")); + if (szXStatusMsg != 0 && *szXStatusMsg) + m_xStatusMsg = szXStatusMsg.detouch(); + } + 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 == NULL) { + 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 (int i=0; i < arContacts.getCount(); i++) { + CContactCache &c = arContacts[i]; + 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 TCHAR*: original status message + * @param fStripAll bool: strip all cr/lf sequences and replace them with spaces (use for title bar) + * @return TCHAR*: converted status message. CALLER is responsible to mir_free it, MUST use mir_free() + */ +TCHAR* CContactCache::getNormalizedStatusMsg(const TCHAR *src, bool fStripAll) +{ + size_t k = 0, i = 0; + TCHAR* tszResult = 0; + + if (src == 0 || lstrlen(src) < 2) + return 0; + + tstring dest; + + for (i=0; i < _tcslen(src); i++) { + if (src[i] == 0x0d || src[i] == '\t') + continue; + if (i && src[i] == (TCHAR)0x0a) { + if (fStripAll) { + dest.append(_T(" ")); + continue; + } + dest.append(_T("\n")); + continue; + } + dest += src[i]; + } + + if (i) { + tszResult = (TCHAR*)mir_alloc((dest.length() + 1) * sizeof(TCHAR)); + _tcscpy(tszResult, dest.c_str()); + tszResult[dest.length()] = 0; + } + return tszResult; +} + +/** + * retrieve the tab/title icon for the corresponding session. + */ +HICON CContactCache::getIcon(int& iSize) const +{ + if (!m_dat || !m_hwnd) + return LoadSkinnedProtoIcon(cc->szProto, m_wStatus); + + if (m_dat->dwFlags & MWF_ERRORSTATE) + return PluginConfig.g_iconErr; + if (m_dat->mayFlashTab) + return m_dat->iFlashIcon; + + if (m_dat->si && m_dat->iFlashIcon) { + int sizeX, sizeY; + Utils::getIconSize(m_dat->iFlashIcon, sizeX, sizeY); + iSize = sizeX; + return m_dat->iFlashIcon; + } + if (m_dat->hTabIcon == m_dat->hTabStatusIcon && m_dat->hXStatusIcon) + return m_dat->hXStatusIcon; + return m_dat->hTabIcon; +} + +int CContactCache::getMaxMessageLength() +{ + 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)) { + if (m_hwnd) + ::SendDlgItemMessage(m_hwnd, IDC_MESSAGE, EM_EXLIMITTEXT, 0, 20000); + } + else { + if (m_hwnd) + ::SendDlgItemMessage(m_hwnd, IDC_MESSAGE, EM_EXLIMITTEXT, 0, (LPARAM)m_nMax); + } + } + else { + if (m_hwnd) + ::SendDlgItemMessage(m_hwnd, IDC_MESSAGE, EM_EXLIMITTEXT, 0, 20000); + m_nMax = 20000; + } + } + return m_nMax; +} -- cgit v1.2.3