summaryrefslogtreecommitdiff
path: root/plugins/TabSRMM/src/contactcache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/TabSRMM/src/contactcache.cpp')
-rw-r--r--plugins/TabSRMM/src/contactcache.cpp1244
1 files changed, 622 insertions, 622 deletions
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<CContactCache> 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<PROTOACCOUNT *>(::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("<undef>"));
- }
- 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<char *>(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<TCHAR *>(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<CContactCache> 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<PROTOACCOUNT *>(::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("<undef>"));
+ }
+ 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<char *>(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<TCHAR *>(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;
+}