diff options
author | dartraiden <wowemuh@gmail.com> | 2024-02-19 20:13:12 +0300 |
---|---|---|
committer | dartraiden <wowemuh@gmail.com> | 2024-02-19 20:16:52 +0300 |
commit | 2f47988d1ef6672929762fba39fa69ef1b4dfa7a (patch) | |
tree | baa3433b11b1c44ab5a12c919ab004a24d645043 /plugins/NewStory/src/history_array.cpp | |
parent | f50885f33f7d1e129d719f06a43cc73518e0ffb1 (diff) |
NewStory:
- add [cX] tag to variables help
- convert sources to CR+LF
Diffstat (limited to 'plugins/NewStory/src/history_array.cpp')
-rw-r--r-- | plugins/NewStory/src/history_array.cpp | 1440 |
1 files changed, 720 insertions, 720 deletions
diff --git a/plugins/NewStory/src/history_array.cpp b/plugins/NewStory/src/history_array.cpp index cfde628b0b..61209fa455 100644 --- a/plugins/NewStory/src/history_array.cpp +++ b/plugins/NewStory/src/history_array.cpp @@ -1,720 +1,720 @@ -/* -Copyright (c) 2005 Victor Pavlychko (nullbyte@sotline.net.ua) -Copyright (C) 2012-24 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" - -extern HANDLE htuLog; - -///////////////////////////////////////////////////////////////////////////////////////// -// Filters - -bool Filter::check(ItemData *item) const -{ - if (!item) return false; - if (!(flags & EVENTONLY)) { - if (item->dbe.flags & DBEF_SENT) { - if (!(flags & OUTGOING)) - return false; - } - else { - if (!(flags & INCOMING)) - return false; - } - switch (item->dbe.eventType) { - case EVENTTYPE_MESSAGE: - if (!(flags & MESSAGES)) - return false; - break; - case EVENTTYPE_FILE: - if (!(flags & FILES)) - return false; - break; - case EVENTTYPE_STATUSCHANGE: - if (!(flags & STATUS)) - return false; - break; - default: - if (!(flags & OTHER)) - return false; - } - } - - if (flags & (EVENTTEXT | EVENTONLY)) { - if (item->m_bLoaded) - return CheckFilter(item->wtext, text); - - if (!item->fetch()) - return false; - - return CheckFilter(ptrW(DbEvent_GetTextW(&item->dbe)), text); - } - - return true; -}; - -///////////////////////////////////////////////////////////////////////////////////////// -// Event - -ItemData::ItemData() -{ - memset(this, 0, sizeof(*this)); - m_grouping = g_plugin.bMsgGrouping ? -1 : GROUPING_NONE; - savedHeight = -1; -} - -ItemData::~ItemData() -{ - mir_free(wtext); - if (dbe.szReplyId) - mir_free((char*)dbe.szReplyId); - if (data) - MTextDestroy(data); -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static bool isEqual(const ItemData *p1, const ItemData *p2) -{ - if (p1->hContact != p2->hContact) - return false; - if (p1->dbe.eventType != p2->dbe.eventType) - return false; - if ((p1->dbe.flags & DBEF_SENT) != (p2->dbe.flags & DBEF_SENT)) - return false; - if (p1->dbe.timestamp / 86400 != p2->dbe.timestamp / 86400) - return false; - return true; -} - -ItemData* ItemData::checkPrev(ItemData *pPrev, HWND hwnd) -{ - m_grouping = GROUPING_NONE; - if (!pPrev || !g_plugin.bMsgGrouping) - return this; - - // we don't group anything but messages - if (!fetch()) - return this; - - if (dbe.eventType != EVENTTYPE_MESSAGE) - return this; - - if (isEqual(this, pPrev)) { - if (pPrev->m_grouping == GROUPING_NONE) { - pPrev->m_grouping = GROUPING_HEAD; - if (pPrev->m_bLoaded) - pPrev->setText(hwnd); - } - m_grouping = GROUPING_ITEM; - } - return this; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -ItemData* ItemData::checkNext(ItemData *pPrev, HWND hwnd) -{ - m_grouping = GROUPING_NONE; - if (!pPrev || !g_plugin.bMsgGrouping) - return this; - - // we don't group anything but messages - if (!fetch()) - return this; - - if (dbe.eventType != EVENTTYPE_MESSAGE) - return this; - - pPrev->fetch(); - if (isEqual(this, pPrev)) { - if (pPrev->m_grouping == GROUPING_NONE) { - pPrev->m_grouping = GROUPING_HEAD; - if (pPrev->m_bLoaded) - pPrev->setText(hwnd); - } - m_grouping = GROUPING_ITEM; - if (m_bLoaded) - setText(hwnd); - } - return this; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -static bool isEqualGC(const ItemData *p1, const ItemData *p2) -{ - if (p1->dbe.eventType != p2->dbe.eventType) - return false; - - if (!p1->wszNick || !p2->wszNick) - return false; - - if (wcscmp(p1->wszNick, p2->wszNick)) - return false; - - if (p1->dbe.timestamp / 86400 != p2->dbe.timestamp / 86400) - return false; - return true; -} - -ItemData* ItemData::checkPrevGC(ItemData *pPrev, HWND hwnd) -{ - m_grouping = GROUPING_NONE; - if (!pPrev || !g_plugin.bMsgGrouping) - return this; - - if (dbe.eventType != EVENTTYPE_MESSAGE) - return this; - - if (isEqualGC(this, pPrev)) { - if (pPrev->m_grouping == GROUPING_NONE) { - pPrev->m_grouping = GROUPING_HEAD; - if (pPrev->m_bLoaded) - pPrev->setText(hwnd); - } - m_grouping = GROUPING_ITEM; - } - return this; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void ItemData::checkCreate(HWND hwnd) -{ - if (data == nullptr) { - setText(hwnd); - MTextSetParent(data, hwnd); - MTextActivate(data, true); - } -} - -bool ItemData::isLink(HWND hwnd, POINT pt, CMStringW *pwszUrl) const -{ - int cp = MTextSendMessage(0, data, EM_CHARFROMPOS, 0, LPARAM(&pt)); - if (cp == -1) - return false; - - if (!isLinkChar(hwnd, cp)) - return false; - - if (pwszUrl) { - CHARRANGE sel = { cp, cp }; - while (isLinkChar(hwnd, sel.cpMin-1)) - sel.cpMin--; - - while (isLinkChar(hwnd, sel.cpMax)) - sel.cpMax++; - - if (sel.cpMax > sel.cpMin) { - pwszUrl->Truncate(sel.cpMax - sel.cpMin + 1); - - TEXTRANGE tr = { 0 }; - tr.chrg = sel; - tr.lpstrText = pwszUrl->GetBuffer(); - int iRes = MTextSendMessage(0, data, EM_GETTEXTRANGE, 0, (LPARAM)&tr); - if (iRes > 0) - pwszUrl->Trim(); - else - pwszUrl->Empty(); - } - } - return true; -} - -bool ItemData::isLinkChar(HWND hwnd, int idx) const -{ - if (idx < 0) - return false; - - CHARRANGE sel = { idx, idx + 1 }; - MTextSendMessage(hwnd, data, EM_EXSETSEL, 0, LPARAM(&sel)); - - CHARFORMAT2 cf = {}; - cf.cbSize = sizeof(cf); - cf.dwMask = CFM_LINK; - uint32_t res = MTextSendMessage(hwnd, data, EM_GETCHARFORMAT, SCF_SELECTION, LPARAM(&cf)); - return ((res & CFM_LINK) && (cf.dwEffects & CFE_LINK)) || ((res & CFM_REVISED) && (cf.dwEffects & CFE_REVISED)); -} - -bool ItemData::fetch(void) -{ - // if this event is virtual (for example, in group chats), don't try to laod it - if (!hEvent) - return false; - - if (!dbe) { - if (!dbe.fetch(hEvent)) - return false; - - if (dbe.szReplyId) - dbe.szReplyId = mir_strdup(dbe.szReplyId); - } - return true; -} - -void ItemData::fill(int tmpl) -{ - switch (tmpl) { - case TPL_MESSAGE: - dbe.eventType = EVENTTYPE_MESSAGE; - break; - case TPL_MSG_HEAD: - m_grouping = GROUPING_HEAD; - dbe.eventType = EVENTTYPE_MESSAGE; - break; - case TPL_MSG_GRP: - m_grouping = GROUPING_ITEM; - dbe.eventType = EVENTTYPE_MESSAGE; - break; - case TPL_FILE: - dbe.eventType = EVENTTYPE_FILE; - break; - case TPL_SIGN: - dbe.eventType = EVENTTYPE_STATUSCHANGE; - break; - case TPL_AUTH: - dbe.eventType = EVENTTYPE_AUTHREQUEST; - break; - case TPL_ADDED: - dbe.eventType = EVENTTYPE_ADDED; - break; - case TPL_PRESENCE: - dbe.eventType = EVENTTYPE_JABBER_PRESENCE; - break; - default: - dbe.eventType = 9247; - break; - } -} - -int ItemData::getTemplate() const -{ - switch (dbe.eventType) { - case EVENTTYPE_MESSAGE: - switch (m_grouping) { - case GROUPING_HEAD: return TPL_MSG_HEAD; - case GROUPING_ITEM: return TPL_MSG_GRP; - } - return TPL_MESSAGE; - - case EVENTTYPE_FILE: return TPL_FILE; - case EVENTTYPE_STATUSCHANGE: return TPL_SIGN; - case EVENTTYPE_AUTHREQUEST: return TPL_AUTH; - case EVENTTYPE_ADDED: return TPL_ADDED; - case EVENTTYPE_JABBER_PRESENCE: return TPL_PRESENCE; - default: - return TPL_OTHER; - } -} - -int ItemData::getCopyTemplate() const -{ - switch (dbe.eventType) { - case EVENTTYPE_MESSAGE: return TPL_COPY_MESSAGE; - case EVENTTYPE_FILE: return TPL_COPY_FILE; - case EVENTTYPE_STATUSCHANGE: return TPL_COPY_SIGN; - case EVENTTYPE_AUTHREQUEST: return TPL_COPY_AUTH; - case EVENTTYPE_ADDED: return TPL_COPY_ADDED; - case EVENTTYPE_JABBER_PRESENCE: return TPL_COPY_PRESENCE; - default: - return TPL_COPY_OTHER; - } -} - -void ItemData::getFontColor(int &fontId, int &colorId) const -{ - switch (dbe.eventType) { - case EVENTTYPE_MESSAGE: - fontId = !(dbe.flags & DBEF_SENT) ? FONT_INMSG : FONT_OUTMSG; - colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INMSG : COLOR_OUTMSG; - break; - - case EVENTTYPE_FILE: - fontId = !(dbe.flags & DBEF_SENT) ? FONT_INFILE : FONT_OUTFILE; - colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INFILE : COLOR_OUTFILE; - break; - - case EVENTTYPE_STATUSCHANGE: - fontId = FONT_STATUS; - colorId = COLOR_STATUS; - break; - - case EVENTTYPE_AUTHREQUEST: - fontId = FONT_INOTHER; - colorId = COLOR_INOTHER; - break; - - case EVENTTYPE_ADDED: - fontId = FONT_INOTHER; - colorId = COLOR_INOTHER; - break; - - case EVENTTYPE_JABBER_PRESENCE: - fontId = !(dbe.flags & DBEF_SENT) ? FONT_INOTHER : FONT_OUTOTHER; - colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INOTHER : COLOR_OUTOTHER; - break; - - default: - fontId = !(dbe.flags & DBEF_SENT) ? FONT_INOTHER : FONT_OUTOTHER; - colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INOTHER : COLOR_OUTOTHER; - break; - } -} - -void ItemData::load(bool bLoadAlways) -{ - if (!bLoadAlways && m_bLoaded) - return; - - if (!fetch()) - return; - - m_bLoaded = true; - hContact = dbe.hContact; // save true contact - - switch (dbe.eventType) { - case EVENTTYPE_MESSAGE: - markRead(); - __fallthrough; - - case EVENTTYPE_STATUSCHANGE: - wtext = mir_utf8decodeW((char *)dbe.pBlob); - break; - - case EVENTTYPE_FILE: - { - DB::FILE_BLOB blob(dbe); - if (blob.isOffline()) { - m_bOfflineFile = true; - if (blob.isCompleted()) - m_bOfflineDownloaded = 100; - else - m_bOfflineDownloaded = uint8_t(100.0 * blob.getTransferred() / blob.getSize()); - - CMStringW buf; - buf.Append(blob.getName() ? blob.getName() : TranslateT("Unnamed")); - - if (auto *pwszDescr = blob.getDescr()) { - buf.Append(L" - "); - buf.Append(pwszDescr); - } - - if (uint32_t size = blob.getSize()) - buf.AppendFormat(TranslateT(" %u KB"), size < 1024 ? 1 : unsigned(blob.getSize() / 1024)); - - wtext = buf.Detach(); - markRead(); - break; - } - - wchar_t buf[MAX_PATH]; - File::GetReceivedFolder(hContact, buf, _countof(buf)); - - CMStringW wszFileName = buf; - wszFileName.Append(blob.getName()); - - // if a filename contains spaces, URL will be broken - if (wszFileName.Find(' ') != -1) { - wchar_t wszShortPath[MAX_PATH]; - if (GetShortPathNameW(wszFileName, wszShortPath, _countof(wszShortPath))) { - wszFileName = wszShortPath; - wszFileName.MakeLower(); - } - } - - wszFileName.Replace('\\', '/'); - wszFileName.Insert(0, L"file://"); - wtext = wszFileName.Detach(); - } - break; - - default: - wtext = DbEvent_GetTextW(&dbe); - break; - } - - if (dbe.szReplyId) - if (MEVENT hReply = db_event_getById(dbe.szModule, dbe.szReplyId)) { - DB::EventInfo dbei(hReply); - if (dbei) { - CMStringW str(L"> "); - - if (dbei.flags & DBEF_SENT) { - if (char *szProto = Proto_GetBaseAccountName(hContact)) - str.AppendFormat(L"%s %s: ", ptrW(Contact::GetInfo(CNF_DISPLAY, 0, szProto)).get(), TranslateT("wrote")); - } - else str.AppendFormat(L"%s %s: ", Clist_GetContactDisplayName(hContact, 0), TranslateT("wrote")); - - ptrW wszText(DbEvent_GetTextW(&dbei)); - if (mir_wstrlen(wszText) > 43) - wcscpy(wszText.get() + 40, L"..."); - str.Append(wszText); - str.Append(L"\r\n"); - str.Append(wtext); - - mir_free(wtext); - wtext = str.Detach(); - } - } - - dbe.unload(); -} - -void ItemData::markRead() -{ - if (!(dbe.flags & DBEF_SENT)) - dbe.wipeNotify(hEvent); -} - -void ItemData::setText(HWND hwnd) -{ - if (data) - MTextDestroy(data); - - data = MTextCreateEx2(hwnd, htuLog, formatRtf().GetBuffer(), MTEXT_FLG_RTF); - MTextSetProto(data, hContact); - savedHeight = -1; -} - -// Array -HistoryArray::HistoryArray() : - pages(50), - strings(50, wcscmp) -{ -} - -HistoryArray::~HistoryArray() -{ - clear(); -} - -void HistoryArray::clear() -{ - for (auto &str : strings) - mir_free(str); - strings.destroy(); - - pages.destroy(); - iLastPageCounter = 0; -} - -void HistoryArray::addChatEvent(SESSION_INFO *si, const LOGINFO *lin) -{ - if (si == nullptr) - return; - - int numItems = getCount(); - auto &p = allocateItem(); - p.hContact = si->hContact; - - if (si->pMI->bDatabase && lin->hEvent) { - p.hEvent = lin->hEvent; - p.load(); - } - else { - CMStringW wszText; - bool bTextUsed = Chat_GetDefaultEventDescr(si, lin, wszText); - if (!bTextUsed && lin->ptszText) { - if (!wszText.IsEmpty()) - wszText.AppendChar(' '); - wszText.Append(g_chatApi.RemoveFormatting(lin->ptszText)); - } - - p.wtext = wszText.Detach(); - p.m_bLoaded = true; - p.m_bHighlighted = lin->bIsHighlighted; - p.dbe.timestamp = lin->time; - if (lin->bIsMe) - p.dbe.flags |= DBEF_SENT; - - switch (lin->iType) { - case GC_EVENT_MESSAGE: - case GC_EVENT_INFORMATION: - p.dbe.eventType = EVENTTYPE_MESSAGE; - break; - - case GC_EVENT_SETCONTACTSTATUS: - p.dbe.eventType = EVENTTYPE_STATUSCHANGE; - break; - - case GC_EVENT_JOIN: - case GC_EVENT_PART: - case GC_EVENT_QUIT: - p.dbe.eventType = EVENTTYPE_JABBER_PRESENCE; - break; - - default: - p.dbe.eventType = EVENTTYPE_OTHER; - break; - } - } - - if (lin->ptszNick) { - p.wszNick = strings.find(lin->ptszNick); - if (p.wszNick == nullptr) { - p.wszNick = mir_wstrdup(lin->ptszNick); - strings.insert(p.wszNick); - } - p.checkPrevGC((numItems == 0) ? nullptr : get(numItems - 1), hwndOwner); - } -} - -bool HistoryArray::addEvent(MCONTACT hContact, MEVENT hEvent, int count) -{ - if (count == -1) - count = MAXINT; - - int numItems = getCount(); - auto *pPrev = (numItems == 0) ? nullptr : get(numItems - 1); - - if (count == 1) { - auto &p = allocateItem(); - p.hContact = hContact; - p.hEvent = hEvent; - pPrev = p.checkPrev(pPrev, hwndOwner); - } - else { - DB::ECPTR pCursor(DB::Events(hContact, hEvent)); - for (int i = 0; i < count; i++) { - hEvent = pCursor.FetchNext(); - if (!hEvent) - break; - - auto &p = allocateItem(); - p.hContact = hContact; - p.hEvent = hEvent; - pPrev = p.checkPrev(pPrev, hwndOwner); - } - } - - return true; -} - -void HistoryArray::addResults(const OBJLIST<SearchResult> &pArray) -{ - int numItems = getCount(); - auto *pPrev = (numItems == 0) ? nullptr : get(numItems - 1); - - for (auto &it : pArray) { - auto &p = allocateItem(); - p.hContact = it->hContact; - p.hEvent = it->hEvent; - p.m_bIsResult = true; - pPrev = p.checkPrev(pPrev, hwndOwner); - } -} - -ItemData& HistoryArray::allocateItem() -{ - if (iLastPageCounter == HIST_BLOCK_SIZE) { - pages.insert(new ItemBlock()); - iLastPageCounter = 0; - } - else if (pages.getCount() == 0) - pages.insert(new ItemBlock); - - auto &p = pages[pages.getCount() - 1]; - return p.data[iLastPageCounter++]; -} - -int HistoryArray::find(MEVENT hEvent) -{ - int i = 0; - for (auto &it : pages) - for (auto &p : it->data) { - if (p.hEvent == hEvent) - return i; - i++; - } - - return -1; -} - -ItemData* HistoryArray::get(int id, bool bLoad) const -{ - int pageNo = id / HIST_BLOCK_SIZE; - if (pageNo >= pages.getCount()) - return nullptr; - - auto *p = &pages[pageNo].data[id % HIST_BLOCK_SIZE]; - if (bLoad && !p->m_bLoaded) - p->load(); - return p; -} - -int HistoryArray::getCount() const -{ - int nPages = pages.getCount(); - return (nPages == 0) ? 0 : (nPages - 1) * HIST_BLOCK_SIZE + iLastPageCounter; -} - -int HistoryArray::find(int id, int dir, const Filter &filter) -{ - int count = getCount(); - for (int i = id + dir; i >= 0 && i < count; i += dir) - if (filter.check(get(i))) - return i; - - return -1; -} - -ItemData* HistoryArray::insert(int pos) -{ - int count = getCount(); - ItemData *pNew = &allocateItem(); - ItemData *pPrev = get(count-1, false); - - for (int i = count; i >= pos; i--) { - memcpy(pNew, pPrev, sizeof(ItemData)); - pNew = pPrev; - pPrev = get(i - 1, false); - } - - ItemData tmp; - memcpy(pNew, &tmp, sizeof(tmp)); - return pNew; -} - -void HistoryArray::remove(int id) -{ - int pageNo = id / HIST_BLOCK_SIZE; - if (pageNo >= pages.getCount()) - return; - - auto &pPage = pages[pageNo]; - int offset = id % HIST_BLOCK_SIZE; - - ItemData tmp; - memcpy(&tmp, pPage.data + offset, sizeof(ItemData)); - - if (offset != HIST_BLOCK_SIZE - 1) - memmove(&pPage.data[offset], &pPage.data[offset+1], sizeof(ItemData) * (HIST_BLOCK_SIZE - 1 - offset)); - - for (int i = pageNo + 1; i < pages.getCount(); i++) { - auto &prev = pages[i - 1], &curr = pages[i]; - memcpy(&prev.data[HIST_BLOCK_SIZE - 1], curr.data, sizeof(ItemData)); - memmove(&curr.data, &curr.data[1], sizeof(ItemData) * (HIST_BLOCK_SIZE - 1)); - memset(&curr.data[HIST_BLOCK_SIZE - 1], 0, sizeof(ItemData)); - } - - if (iLastPageCounter == 1) { - pages.remove(pages.getCount() - 1); - iLastPageCounter = HIST_BLOCK_SIZE; - } - else iLastPageCounter--; -} +/*
+Copyright (c) 2005 Victor Pavlychko (nullbyte@sotline.net.ua)
+Copyright (C) 2012-24 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"
+
+extern HANDLE htuLog;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Filters
+
+bool Filter::check(ItemData *item) const
+{
+ if (!item) return false;
+ if (!(flags & EVENTONLY)) {
+ if (item->dbe.flags & DBEF_SENT) {
+ if (!(flags & OUTGOING))
+ return false;
+ }
+ else {
+ if (!(flags & INCOMING))
+ return false;
+ }
+ switch (item->dbe.eventType) {
+ case EVENTTYPE_MESSAGE:
+ if (!(flags & MESSAGES))
+ return false;
+ break;
+ case EVENTTYPE_FILE:
+ if (!(flags & FILES))
+ return false;
+ break;
+ case EVENTTYPE_STATUSCHANGE:
+ if (!(flags & STATUS))
+ return false;
+ break;
+ default:
+ if (!(flags & OTHER))
+ return false;
+ }
+ }
+
+ if (flags & (EVENTTEXT | EVENTONLY)) {
+ if (item->m_bLoaded)
+ return CheckFilter(item->wtext, text);
+
+ if (!item->fetch())
+ return false;
+
+ return CheckFilter(ptrW(DbEvent_GetTextW(&item->dbe)), text);
+ }
+
+ return true;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Event
+
+ItemData::ItemData()
+{
+ memset(this, 0, sizeof(*this));
+ m_grouping = g_plugin.bMsgGrouping ? -1 : GROUPING_NONE;
+ savedHeight = -1;
+}
+
+ItemData::~ItemData()
+{
+ mir_free(wtext);
+ if (dbe.szReplyId)
+ mir_free((char*)dbe.szReplyId);
+ if (data)
+ MTextDestroy(data);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool isEqual(const ItemData *p1, const ItemData *p2)
+{
+ if (p1->hContact != p2->hContact)
+ return false;
+ if (p1->dbe.eventType != p2->dbe.eventType)
+ return false;
+ if ((p1->dbe.flags & DBEF_SENT) != (p2->dbe.flags & DBEF_SENT))
+ return false;
+ if (p1->dbe.timestamp / 86400 != p2->dbe.timestamp / 86400)
+ return false;
+ return true;
+}
+
+ItemData* ItemData::checkPrev(ItemData *pPrev, HWND hwnd)
+{
+ m_grouping = GROUPING_NONE;
+ if (!pPrev || !g_plugin.bMsgGrouping)
+ return this;
+
+ // we don't group anything but messages
+ if (!fetch())
+ return this;
+
+ if (dbe.eventType != EVENTTYPE_MESSAGE)
+ return this;
+
+ if (isEqual(this, pPrev)) {
+ if (pPrev->m_grouping == GROUPING_NONE) {
+ pPrev->m_grouping = GROUPING_HEAD;
+ if (pPrev->m_bLoaded)
+ pPrev->setText(hwnd);
+ }
+ m_grouping = GROUPING_ITEM;
+ }
+ return this;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+ItemData* ItemData::checkNext(ItemData *pPrev, HWND hwnd)
+{
+ m_grouping = GROUPING_NONE;
+ if (!pPrev || !g_plugin.bMsgGrouping)
+ return this;
+
+ // we don't group anything but messages
+ if (!fetch())
+ return this;
+
+ if (dbe.eventType != EVENTTYPE_MESSAGE)
+ return this;
+
+ pPrev->fetch();
+ if (isEqual(this, pPrev)) {
+ if (pPrev->m_grouping == GROUPING_NONE) {
+ pPrev->m_grouping = GROUPING_HEAD;
+ if (pPrev->m_bLoaded)
+ pPrev->setText(hwnd);
+ }
+ m_grouping = GROUPING_ITEM;
+ if (m_bLoaded)
+ setText(hwnd);
+ }
+ return this;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool isEqualGC(const ItemData *p1, const ItemData *p2)
+{
+ if (p1->dbe.eventType != p2->dbe.eventType)
+ return false;
+
+ if (!p1->wszNick || !p2->wszNick)
+ return false;
+
+ if (wcscmp(p1->wszNick, p2->wszNick))
+ return false;
+
+ if (p1->dbe.timestamp / 86400 != p2->dbe.timestamp / 86400)
+ return false;
+ return true;
+}
+
+ItemData* ItemData::checkPrevGC(ItemData *pPrev, HWND hwnd)
+{
+ m_grouping = GROUPING_NONE;
+ if (!pPrev || !g_plugin.bMsgGrouping)
+ return this;
+
+ if (dbe.eventType != EVENTTYPE_MESSAGE)
+ return this;
+
+ if (isEqualGC(this, pPrev)) {
+ if (pPrev->m_grouping == GROUPING_NONE) {
+ pPrev->m_grouping = GROUPING_HEAD;
+ if (pPrev->m_bLoaded)
+ pPrev->setText(hwnd);
+ }
+ m_grouping = GROUPING_ITEM;
+ }
+ return this;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void ItemData::checkCreate(HWND hwnd)
+{
+ if (data == nullptr) {
+ setText(hwnd);
+ MTextSetParent(data, hwnd);
+ MTextActivate(data, true);
+ }
+}
+
+bool ItemData::isLink(HWND hwnd, POINT pt, CMStringW *pwszUrl) const
+{
+ int cp = MTextSendMessage(0, data, EM_CHARFROMPOS, 0, LPARAM(&pt));
+ if (cp == -1)
+ return false;
+
+ if (!isLinkChar(hwnd, cp))
+ return false;
+
+ if (pwszUrl) {
+ CHARRANGE sel = { cp, cp };
+ while (isLinkChar(hwnd, sel.cpMin-1))
+ sel.cpMin--;
+
+ while (isLinkChar(hwnd, sel.cpMax))
+ sel.cpMax++;
+
+ if (sel.cpMax > sel.cpMin) {
+ pwszUrl->Truncate(sel.cpMax - sel.cpMin + 1);
+
+ TEXTRANGE tr = { 0 };
+ tr.chrg = sel;
+ tr.lpstrText = pwszUrl->GetBuffer();
+ int iRes = MTextSendMessage(0, data, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
+ if (iRes > 0)
+ pwszUrl->Trim();
+ else
+ pwszUrl->Empty();
+ }
+ }
+ return true;
+}
+
+bool ItemData::isLinkChar(HWND hwnd, int idx) const
+{
+ if (idx < 0)
+ return false;
+
+ CHARRANGE sel = { idx, idx + 1 };
+ MTextSendMessage(hwnd, data, EM_EXSETSEL, 0, LPARAM(&sel));
+
+ CHARFORMAT2 cf = {};
+ cf.cbSize = sizeof(cf);
+ cf.dwMask = CFM_LINK;
+ uint32_t res = MTextSendMessage(hwnd, data, EM_GETCHARFORMAT, SCF_SELECTION, LPARAM(&cf));
+ return ((res & CFM_LINK) && (cf.dwEffects & CFE_LINK)) || ((res & CFM_REVISED) && (cf.dwEffects & CFE_REVISED));
+}
+
+bool ItemData::fetch(void)
+{
+ // if this event is virtual (for example, in group chats), don't try to laod it
+ if (!hEvent)
+ return false;
+
+ if (!dbe) {
+ if (!dbe.fetch(hEvent))
+ return false;
+
+ if (dbe.szReplyId)
+ dbe.szReplyId = mir_strdup(dbe.szReplyId);
+ }
+ return true;
+}
+
+void ItemData::fill(int tmpl)
+{
+ switch (tmpl) {
+ case TPL_MESSAGE:
+ dbe.eventType = EVENTTYPE_MESSAGE;
+ break;
+ case TPL_MSG_HEAD:
+ m_grouping = GROUPING_HEAD;
+ dbe.eventType = EVENTTYPE_MESSAGE;
+ break;
+ case TPL_MSG_GRP:
+ m_grouping = GROUPING_ITEM;
+ dbe.eventType = EVENTTYPE_MESSAGE;
+ break;
+ case TPL_FILE:
+ dbe.eventType = EVENTTYPE_FILE;
+ break;
+ case TPL_SIGN:
+ dbe.eventType = EVENTTYPE_STATUSCHANGE;
+ break;
+ case TPL_AUTH:
+ dbe.eventType = EVENTTYPE_AUTHREQUEST;
+ break;
+ case TPL_ADDED:
+ dbe.eventType = EVENTTYPE_ADDED;
+ break;
+ case TPL_PRESENCE:
+ dbe.eventType = EVENTTYPE_JABBER_PRESENCE;
+ break;
+ default:
+ dbe.eventType = 9247;
+ break;
+ }
+}
+
+int ItemData::getTemplate() const
+{
+ switch (dbe.eventType) {
+ case EVENTTYPE_MESSAGE:
+ switch (m_grouping) {
+ case GROUPING_HEAD: return TPL_MSG_HEAD;
+ case GROUPING_ITEM: return TPL_MSG_GRP;
+ }
+ return TPL_MESSAGE;
+
+ case EVENTTYPE_FILE: return TPL_FILE;
+ case EVENTTYPE_STATUSCHANGE: return TPL_SIGN;
+ case EVENTTYPE_AUTHREQUEST: return TPL_AUTH;
+ case EVENTTYPE_ADDED: return TPL_ADDED;
+ case EVENTTYPE_JABBER_PRESENCE: return TPL_PRESENCE;
+ default:
+ return TPL_OTHER;
+ }
+}
+
+int ItemData::getCopyTemplate() const
+{
+ switch (dbe.eventType) {
+ case EVENTTYPE_MESSAGE: return TPL_COPY_MESSAGE;
+ case EVENTTYPE_FILE: return TPL_COPY_FILE;
+ case EVENTTYPE_STATUSCHANGE: return TPL_COPY_SIGN;
+ case EVENTTYPE_AUTHREQUEST: return TPL_COPY_AUTH;
+ case EVENTTYPE_ADDED: return TPL_COPY_ADDED;
+ case EVENTTYPE_JABBER_PRESENCE: return TPL_COPY_PRESENCE;
+ default:
+ return TPL_COPY_OTHER;
+ }
+}
+
+void ItemData::getFontColor(int &fontId, int &colorId) const
+{
+ switch (dbe.eventType) {
+ case EVENTTYPE_MESSAGE:
+ fontId = !(dbe.flags & DBEF_SENT) ? FONT_INMSG : FONT_OUTMSG;
+ colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INMSG : COLOR_OUTMSG;
+ break;
+
+ case EVENTTYPE_FILE:
+ fontId = !(dbe.flags & DBEF_SENT) ? FONT_INFILE : FONT_OUTFILE;
+ colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INFILE : COLOR_OUTFILE;
+ break;
+
+ case EVENTTYPE_STATUSCHANGE:
+ fontId = FONT_STATUS;
+ colorId = COLOR_STATUS;
+ break;
+
+ case EVENTTYPE_AUTHREQUEST:
+ fontId = FONT_INOTHER;
+ colorId = COLOR_INOTHER;
+ break;
+
+ case EVENTTYPE_ADDED:
+ fontId = FONT_INOTHER;
+ colorId = COLOR_INOTHER;
+ break;
+
+ case EVENTTYPE_JABBER_PRESENCE:
+ fontId = !(dbe.flags & DBEF_SENT) ? FONT_INOTHER : FONT_OUTOTHER;
+ colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INOTHER : COLOR_OUTOTHER;
+ break;
+
+ default:
+ fontId = !(dbe.flags & DBEF_SENT) ? FONT_INOTHER : FONT_OUTOTHER;
+ colorId = !(dbe.flags & DBEF_SENT) ? COLOR_INOTHER : COLOR_OUTOTHER;
+ break;
+ }
+}
+
+void ItemData::load(bool bLoadAlways)
+{
+ if (!bLoadAlways && m_bLoaded)
+ return;
+
+ if (!fetch())
+ return;
+
+ m_bLoaded = true;
+ hContact = dbe.hContact; // save true contact
+
+ switch (dbe.eventType) {
+ case EVENTTYPE_MESSAGE:
+ markRead();
+ __fallthrough;
+
+ case EVENTTYPE_STATUSCHANGE:
+ wtext = mir_utf8decodeW((char *)dbe.pBlob);
+ break;
+
+ case EVENTTYPE_FILE:
+ {
+ DB::FILE_BLOB blob(dbe);
+ if (blob.isOffline()) {
+ m_bOfflineFile = true;
+ if (blob.isCompleted())
+ m_bOfflineDownloaded = 100;
+ else
+ m_bOfflineDownloaded = uint8_t(100.0 * blob.getTransferred() / blob.getSize());
+
+ CMStringW buf;
+ buf.Append(blob.getName() ? blob.getName() : TranslateT("Unnamed"));
+
+ if (auto *pwszDescr = blob.getDescr()) {
+ buf.Append(L" - ");
+ buf.Append(pwszDescr);
+ }
+
+ if (uint32_t size = blob.getSize())
+ buf.AppendFormat(TranslateT(" %u KB"), size < 1024 ? 1 : unsigned(blob.getSize() / 1024));
+
+ wtext = buf.Detach();
+ markRead();
+ break;
+ }
+
+ wchar_t buf[MAX_PATH];
+ File::GetReceivedFolder(hContact, buf, _countof(buf));
+
+ CMStringW wszFileName = buf;
+ wszFileName.Append(blob.getName());
+
+ // if a filename contains spaces, URL will be broken
+ if (wszFileName.Find(' ') != -1) {
+ wchar_t wszShortPath[MAX_PATH];
+ if (GetShortPathNameW(wszFileName, wszShortPath, _countof(wszShortPath))) {
+ wszFileName = wszShortPath;
+ wszFileName.MakeLower();
+ }
+ }
+
+ wszFileName.Replace('\\', '/');
+ wszFileName.Insert(0, L"file://");
+ wtext = wszFileName.Detach();
+ }
+ break;
+
+ default:
+ wtext = DbEvent_GetTextW(&dbe);
+ break;
+ }
+
+ if (dbe.szReplyId)
+ if (MEVENT hReply = db_event_getById(dbe.szModule, dbe.szReplyId)) {
+ DB::EventInfo dbei(hReply);
+ if (dbei) {
+ CMStringW str(L"> ");
+
+ if (dbei.flags & DBEF_SENT) {
+ if (char *szProto = Proto_GetBaseAccountName(hContact))
+ str.AppendFormat(L"%s %s: ", ptrW(Contact::GetInfo(CNF_DISPLAY, 0, szProto)).get(), TranslateT("wrote"));
+ }
+ else str.AppendFormat(L"%s %s: ", Clist_GetContactDisplayName(hContact, 0), TranslateT("wrote"));
+
+ ptrW wszText(DbEvent_GetTextW(&dbei));
+ if (mir_wstrlen(wszText) > 43)
+ wcscpy(wszText.get() + 40, L"...");
+ str.Append(wszText);
+ str.Append(L"\r\n");
+ str.Append(wtext);
+
+ mir_free(wtext);
+ wtext = str.Detach();
+ }
+ }
+
+ dbe.unload();
+}
+
+void ItemData::markRead()
+{
+ if (!(dbe.flags & DBEF_SENT))
+ dbe.wipeNotify(hEvent);
+}
+
+void ItemData::setText(HWND hwnd)
+{
+ if (data)
+ MTextDestroy(data);
+
+ data = MTextCreateEx2(hwnd, htuLog, formatRtf().GetBuffer(), MTEXT_FLG_RTF);
+ MTextSetProto(data, hContact);
+ savedHeight = -1;
+}
+
+// Array
+HistoryArray::HistoryArray() :
+ pages(50),
+ strings(50, wcscmp)
+{
+}
+
+HistoryArray::~HistoryArray()
+{
+ clear();
+}
+
+void HistoryArray::clear()
+{
+ for (auto &str : strings)
+ mir_free(str);
+ strings.destroy();
+
+ pages.destroy();
+ iLastPageCounter = 0;
+}
+
+void HistoryArray::addChatEvent(SESSION_INFO *si, const LOGINFO *lin)
+{
+ if (si == nullptr)
+ return;
+
+ int numItems = getCount();
+ auto &p = allocateItem();
+ p.hContact = si->hContact;
+
+ if (si->pMI->bDatabase && lin->hEvent) {
+ p.hEvent = lin->hEvent;
+ p.load();
+ }
+ else {
+ CMStringW wszText;
+ bool bTextUsed = Chat_GetDefaultEventDescr(si, lin, wszText);
+ if (!bTextUsed && lin->ptszText) {
+ if (!wszText.IsEmpty())
+ wszText.AppendChar(' ');
+ wszText.Append(g_chatApi.RemoveFormatting(lin->ptszText));
+ }
+
+ p.wtext = wszText.Detach();
+ p.m_bLoaded = true;
+ p.m_bHighlighted = lin->bIsHighlighted;
+ p.dbe.timestamp = lin->time;
+ if (lin->bIsMe)
+ p.dbe.flags |= DBEF_SENT;
+
+ switch (lin->iType) {
+ case GC_EVENT_MESSAGE:
+ case GC_EVENT_INFORMATION:
+ p.dbe.eventType = EVENTTYPE_MESSAGE;
+ break;
+
+ case GC_EVENT_SETCONTACTSTATUS:
+ p.dbe.eventType = EVENTTYPE_STATUSCHANGE;
+ break;
+
+ case GC_EVENT_JOIN:
+ case GC_EVENT_PART:
+ case GC_EVENT_QUIT:
+ p.dbe.eventType = EVENTTYPE_JABBER_PRESENCE;
+ break;
+
+ default:
+ p.dbe.eventType = EVENTTYPE_OTHER;
+ break;
+ }
+ }
+
+ if (lin->ptszNick) {
+ p.wszNick = strings.find(lin->ptszNick);
+ if (p.wszNick == nullptr) {
+ p.wszNick = mir_wstrdup(lin->ptszNick);
+ strings.insert(p.wszNick);
+ }
+ p.checkPrevGC((numItems == 0) ? nullptr : get(numItems - 1), hwndOwner);
+ }
+}
+
+bool HistoryArray::addEvent(MCONTACT hContact, MEVENT hEvent, int count)
+{
+ if (count == -1)
+ count = MAXINT;
+
+ int numItems = getCount();
+ auto *pPrev = (numItems == 0) ? nullptr : get(numItems - 1);
+
+ if (count == 1) {
+ auto &p = allocateItem();
+ p.hContact = hContact;
+ p.hEvent = hEvent;
+ pPrev = p.checkPrev(pPrev, hwndOwner);
+ }
+ else {
+ DB::ECPTR pCursor(DB::Events(hContact, hEvent));
+ for (int i = 0; i < count; i++) {
+ hEvent = pCursor.FetchNext();
+ if (!hEvent)
+ break;
+
+ auto &p = allocateItem();
+ p.hContact = hContact;
+ p.hEvent = hEvent;
+ pPrev = p.checkPrev(pPrev, hwndOwner);
+ }
+ }
+
+ return true;
+}
+
+void HistoryArray::addResults(const OBJLIST<SearchResult> &pArray)
+{
+ int numItems = getCount();
+ auto *pPrev = (numItems == 0) ? nullptr : get(numItems - 1);
+
+ for (auto &it : pArray) {
+ auto &p = allocateItem();
+ p.hContact = it->hContact;
+ p.hEvent = it->hEvent;
+ p.m_bIsResult = true;
+ pPrev = p.checkPrev(pPrev, hwndOwner);
+ }
+}
+
+ItemData& HistoryArray::allocateItem()
+{
+ if (iLastPageCounter == HIST_BLOCK_SIZE) {
+ pages.insert(new ItemBlock());
+ iLastPageCounter = 0;
+ }
+ else if (pages.getCount() == 0)
+ pages.insert(new ItemBlock);
+
+ auto &p = pages[pages.getCount() - 1];
+ return p.data[iLastPageCounter++];
+}
+
+int HistoryArray::find(MEVENT hEvent)
+{
+ int i = 0;
+ for (auto &it : pages)
+ for (auto &p : it->data) {
+ if (p.hEvent == hEvent)
+ return i;
+ i++;
+ }
+
+ return -1;
+}
+
+ItemData* HistoryArray::get(int id, bool bLoad) const
+{
+ int pageNo = id / HIST_BLOCK_SIZE;
+ if (pageNo >= pages.getCount())
+ return nullptr;
+
+ auto *p = &pages[pageNo].data[id % HIST_BLOCK_SIZE];
+ if (bLoad && !p->m_bLoaded)
+ p->load();
+ return p;
+}
+
+int HistoryArray::getCount() const
+{
+ int nPages = pages.getCount();
+ return (nPages == 0) ? 0 : (nPages - 1) * HIST_BLOCK_SIZE + iLastPageCounter;
+}
+
+int HistoryArray::find(int id, int dir, const Filter &filter)
+{
+ int count = getCount();
+ for (int i = id + dir; i >= 0 && i < count; i += dir)
+ if (filter.check(get(i)))
+ return i;
+
+ return -1;
+}
+
+ItemData* HistoryArray::insert(int pos)
+{
+ int count = getCount();
+ ItemData *pNew = &allocateItem();
+ ItemData *pPrev = get(count-1, false);
+
+ for (int i = count; i >= pos; i--) {
+ memcpy(pNew, pPrev, sizeof(ItemData));
+ pNew = pPrev;
+ pPrev = get(i - 1, false);
+ }
+
+ ItemData tmp;
+ memcpy(pNew, &tmp, sizeof(tmp));
+ return pNew;
+}
+
+void HistoryArray::remove(int id)
+{
+ int pageNo = id / HIST_BLOCK_SIZE;
+ if (pageNo >= pages.getCount())
+ return;
+
+ auto &pPage = pages[pageNo];
+ int offset = id % HIST_BLOCK_SIZE;
+
+ ItemData tmp;
+ memcpy(&tmp, pPage.data + offset, sizeof(ItemData));
+
+ if (offset != HIST_BLOCK_SIZE - 1)
+ memmove(&pPage.data[offset], &pPage.data[offset+1], sizeof(ItemData) * (HIST_BLOCK_SIZE - 1 - offset));
+
+ for (int i = pageNo + 1; i < pages.getCount(); i++) {
+ auto &prev = pages[i - 1], &curr = pages[i];
+ memcpy(&prev.data[HIST_BLOCK_SIZE - 1], curr.data, sizeof(ItemData));
+ memmove(&curr.data, &curr.data[1], sizeof(ItemData) * (HIST_BLOCK_SIZE - 1));
+ memset(&curr.data[HIST_BLOCK_SIZE - 1], 0, sizeof(ItemData));
+ }
+
+ if (iLastPageCounter == 1) {
+ pages.remove(pages.getCount() - 1);
+ iLastPageCounter = HIST_BLOCK_SIZE;
+ }
+ else iLastPageCounter--;
+}
|