summaryrefslogtreecommitdiff
path: root/plugins/NewStory/src/history_array.cpp
diff options
context:
space:
mode:
authordartraiden <wowemuh@gmail.com>2024-02-19 20:13:12 +0300
committerdartraiden <wowemuh@gmail.com>2024-02-19 20:16:52 +0300
commit2f47988d1ef6672929762fba39fa69ef1b4dfa7a (patch)
treebaa3433b11b1c44ab5a12c919ab004a24d645043 /plugins/NewStory/src/history_array.cpp
parentf50885f33f7d1e129d719f06a43cc73518e0ffb1 (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.cpp1440
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--;
+}