summaryrefslogtreecommitdiff
path: root/plugins/NewStory/src/history_control.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/NewStory/src/history_control.cpp')
-rw-r--r--plugins/NewStory/src/history_control.cpp3082
1 files changed, 1541 insertions, 1541 deletions
diff --git a/plugins/NewStory/src/history_control.cpp b/plugins/NewStory/src/history_control.cpp
index 25779b0e7d..d035320ddb 100644
--- a/plugins/NewStory/src/history_control.cpp
+++ b/plugins/NewStory/src/history_control.cpp
@@ -1,1541 +1,1541 @@
-/*
-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"
-
-#define AVERAGE_ITEM_HEIGHT 100
-
-HANDLE htuLog = 0;
-
-void InitHotkeys()
-{
- HOTKEYDESC hkd = {};
- hkd.szSection.a = MODULENAME;
-
- hkd.szDescription.a = LPGEN("Toggle bookmark");
- hkd.pszName = "ns_bookmark";
- hkd.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL, 'B') | HKF_MIRANDA_LOCAL;
- hkd.lParam = HOTKEY_BOOKMARK;
- g_plugin.addHotkey(&hkd);
-
- hkd.szDescription.a = LPGEN("Search");
- hkd.pszName = "ns_search";
- hkd.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL, 'F') | HKF_MIRANDA_LOCAL;
- hkd.lParam = HOTKEY_SEARCH;
- g_plugin.addHotkey(&hkd);
-
- hkd.szDescription.a = LPGEN("Search forward");
- hkd.pszName = "ns_seek_forward";
- hkd.DefHotKey = HOTKEYCODE(0, VK_F3) | HKF_MIRANDA_LOCAL;
- hkd.lParam = HOTKEY_SEEK_FORWARD;
- g_plugin.addHotkey(&hkd);
-
- hkd.szDescription.a = LPGEN("Search backward");
- hkd.pszName = "ns_seek_back";
- hkd.DefHotKey = HOTKEYCODE(HOTKEYF_SHIFT, VK_F3) | HKF_MIRANDA_LOCAL;
- hkd.lParam = HOTKEY_SEEK_BACK;
- g_plugin.addHotkey(&hkd);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Control utilities, types and constants
-
-NewstoryListData::NewstoryListData(HWND _1) :
- m_hwnd(_1),
- redrawTimer(Miranda_GetSystemWindow(), LPARAM(this))
-{
- items.setOwner(_1);
-
- bSortAscending = g_plugin.bSortAscending;
-
- redrawTimer.OnEvent = Callback(this, &NewstoryListData::onTimer_Draw);
-}
-
-void NewstoryListData::onTimer_Draw(CTimer *pTimer)
-{
- pTimer->Stop();
-
- if (bWasAtBottom)
- EnsureVisible(totalCount - 1);
-
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::OnContextMenu(int index, POINT pt)
-{
- HMENU hMenu = NSMenu_Build(this, (index == -1) ? 0 : LoadItem(index));
-
- if (pMsgDlg != nullptr && pMsgDlg->isChat())
- Chat_CreateMenu(hMenu, pMsgDlg->getChat(), nullptr);
-
- TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, m_hwnd, nullptr);
- Menu_DestroyNestedMenu(hMenu);
-}
-
-void NewstoryListData::OnResize(int newWidth, int newHeight)
-{
- bool bDraw = false;
- if (newWidth != cachedWindowWidth) {
- cachedWindowWidth = newWidth;
- for (int i = 0; i < totalCount; i++)
- GetItem(i)->savedHeight = -1;
- bDraw = true;
- }
-
- if (newHeight != cachedWindowHeight) {
- cachedWindowHeight = newHeight;
- FixScrollPosition(true);
- bDraw = true;
- }
-
- if (bDraw)
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::AddChatEvent(SESSION_INFO *si, const LOGINFO *lin)
-{
- ScheduleDraw();
- items.addChatEvent(si, lin);
- totalCount++;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void NewstoryListData::AddEvent(MCONTACT hContact, MEVENT hFirstEvent, int iCount)
-{
- ScheduleDraw();
- items.addEvent(hContact, hFirstEvent, iCount);
- totalCount = items.getCount();
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void NewstoryListData::AddResults(const OBJLIST<SearchResult> &results)
-{
- ScheduleDraw();
- items.addResults(results);
- totalCount = items.getCount();
-}
-
-void NewstoryListData::AddSelection(int iFirst, int iLast)
-{
- int start = min(totalCount - 1, iFirst);
- int end = min(totalCount - 1, max(0, iLast));
- if (start > end)
- std::swap(start, end);
-
- for (int i = start; i <= end; ++i)
- if (auto *p = GetItem(i))
- p->m_bSelected = true;
-
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-bool NewstoryListData::AtBottom(void) const
-{
- if (scrollTopItem > cachedMaxTopItem)
- return true;
-
- if (scrollTopItem == cachedMaxTopItem && cachedMaxTopPixel >= scrollTopPixel)
- return true;
-
- return false;
-}
-
-bool NewstoryListData::AtTop(void) const
-{
- if (scrollTopItem < 0)
- return true;
-
- if (scrollTopItem == 0 && scrollTopPixel == 0)
- return true;
-
- return false;
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Edit box window procedure
-
-static LRESULT CALLBACK HistoryEditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
-{
- auto *pData = (NewstoryListData *)GetWindowLongPtr(GetParent(hwnd), 0);
-
- switch (msg) {
- case WM_KEYDOWN:
- switch (wParam) {
- case VK_RETURN:
- pData->EndEditItem(true);
- return 0;
- case VK_ESCAPE:
- pData->EndEditItem(false);
- return 0;
- }
- break;
-
- case WM_GETDLGCODE:
- if (lParam) {
- MSG *msg2 = (MSG *)lParam;
- if (msg2->message == WM_KEYDOWN && msg2->wParam == VK_TAB)
- return 0;
- if (msg2->message == WM_CHAR && msg2->wParam == '\t')
- return 0;
- }
- return DLGC_WANTMESSAGE;
-
- case WM_KILLFOCUS:
- pData->EndEditItem(false);
- return 0;
- }
-
- return mir_callNextSubclass(hwnd, HistoryEditWndProc, msg, wParam, lParam);
-}
-
-void NewstoryListData::BeginEditItem()
-{
- if (hwndEditBox)
- EndEditItem(false);
-
- if (scrollTopItem > caret)
- return;
-
- ItemData *item = LoadItem(caret);
- if (item->dbe.eventType != EVENTTYPE_MESSAGE)
- return;
-
- RECT rc; GetClientRect(m_hwnd, &rc);
- int height = rc.bottom - rc.top;
-
- int top = scrollTopPixel;
- int idx = scrollTopItem;
- int itemHeight = GetItemHeight(idx);
- while (top < height) {
- if (idx == caret)
- break;
-
- top += itemHeight;
- idx++;
- itemHeight = GetItemHeight(idx);
- }
-
- int fontid, colorid;
- item->getFontColor(fontid, colorid);
-
- // #4012 make sure that both single & double CRLF are now double
- CMStringW wszText(item->getWBuf());
- wszText.Replace(L"\r\n", L"\n");
- wszText.Replace(L"\n", L"\r\n");
-
- uint32_t dwStyle = WS_CHILD | WS_BORDER | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL;
- hwndEditBox = CreateWindow(L"EDIT", wszText, dwStyle, 0, top, rc.right - rc.left, itemHeight, m_hwnd, NULL, g_plugin.getInst(), NULL);
- mir_subclassWindow(hwndEditBox, HistoryEditWndProc);
- SendMessage(hwndEditBox, WM_SETFONT, (WPARAM)g_fontTable[fontid].hfnt, 0);
- SendMessage(hwndEditBox, EM_SETMARGINS, EC_RIGHTMARGIN, 100);
- ShowWindow(hwndEditBox, SW_SHOW);
- SetFocus(hwndEditBox);
- SetForegroundWindow(hwndEditBox);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void NewstoryListData::CalcBottom()
-{
- int maxTopItem = totalCount, tmp = 0;
- while (maxTopItem > 0 && tmp < cachedWindowHeight)
- tmp += GetItemHeight(--maxTopItem);
- cachedMaxTopItem = maxTopItem;
- cachedMaxTopPixel = (cachedWindowHeight < tmp) ? cachedWindowHeight - tmp : 0;
-}
-
-void NewstoryListData::Clear()
-{
- items.clear();
- totalCount = 0;
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::ClearSelection(int iFirst, int iLast)
-{
- int start = min(0, iFirst);
- int end = (iLast <= 0) ? totalCount - 1 : iLast;
- if (start > end)
- std::swap(start, end);
-
- for (int i = start; i <= end; ++i)
- if (auto *pItem = GetItem(i))
- pItem->m_bSelected = false;
-
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::Copy(bool bTextOnly)
-{
- Utils_ClipboardCopy(GatherSelected(bTextOnly));
-}
-
-void NewstoryListData::CopyPath()
-{
- if (auto *pItem = GetItem(caret))
- if (pItem->completed()) {
- DB::EventInfo dbei(pItem->hEvent);
- DB::FILE_BLOB blob(dbei);
- Utils_ClipboardCopy(blob.getLocalName());
- }
-}
-
-void NewstoryListData::CopyUrl()
-{
- if (auto *pItem = GetItem(caret))
- Srmm_DownloadOfflineFile(pItem->hContact, pItem->hEvent, OFD_COPYURL);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Delete events dialog
-
-class CDeleteEventsDlg : public CDlgBase
-{
- MCONTACT m_hContact;
- CCtrlCheck chkDelHistory, chkForEveryone;
-
-public:
- bool bDelHistory = false, bForEveryone = false;
-
- CDeleteEventsDlg(MCONTACT hContact) :
- CDlgBase(g_plugin, IDD_EMPTYHISTORY),
- chkDelHistory(this, IDC_DELSERVERHISTORY),
- chkForEveryone(this, IDC_BOTH)
- {
- if (char *szProto = Proto_GetBaseAccountName(hContact)) {
- bDelHistory = ProtoServiceExists(szProto, PS_EMPTY_SRV_HISTORY);
- bForEveryone = (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_DELETEFORALL) != 0;
- }
- }
-
- bool OnInitDialog() override
- {
- chkDelHistory.SetState(bDelHistory);
- chkDelHistory.Enable(bDelHistory);
-
- bool bEnabled = bDelHistory && bForEveryone;
- chkForEveryone.SetState(!bEnabled);
- chkForEveryone.Enable(bEnabled);
-
- LOGFONT lf;
- HFONT hFont = (HFONT)SendDlgItemMessage(m_hwnd, IDOK, WM_GETFONT, 0, 0);
- GetObject(hFont, sizeof(lf), &lf);
- lf.lfWeight = FW_BOLD;
- SendDlgItemMessage(m_hwnd, IDC_TOPLINE, WM_SETFONT, (WPARAM)CreateFontIndirect(&lf), 0);
-
- wchar_t szFormat[256], szFinal[256];
- GetDlgItemText(m_hwnd, IDC_TOPLINE, szFormat, _countof(szFormat));
- mir_snwprintf(szFinal, szFormat, Clist_GetContactDisplayName(m_hContact));
- SetDlgItemText(m_hwnd, IDC_TOPLINE, szFinal);
-
- SetFocus(GetDlgItem(m_hwnd, IDNO));
- SetWindowPos(m_hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
- return true;
- }
-
- bool OnApply() override
- {
- bDelHistory = chkDelHistory.IsChecked();
- bForEveryone = chkForEveryone.IsChecked();
- return true;
- }
-
- void OnDestroy() override
- {
- DeleteObject((HFONT)SendDlgItemMessage(m_hwnd, IDC_TOPLINE, WM_GETFONT, 0, 0));
- }
-};
-
-void NewstoryListData::DeleteItems(void)
-{
- CDeleteEventsDlg dlg(m_hContact);
- if (IDOK != dlg.DoModal())
- return;
-
- g_plugin.bDisableDelete = true;
-
- int firstSel = -1, flags = 0;
- if (dlg.bDelHistory)
- flags |= CDF_DEL_HISTORY;
- if (dlg.bForEveryone)
- flags |= CDF_FOR_EVERYONE;
-
- for (int i = totalCount - 1; i >= 0; i--) {
- auto *p = GetItem(i);
- if (!p->m_bSelected)
- continue;
-
- if (p->hEvent)
- db_event_delete(p->hEvent, flags);
- items.remove(i);
- totalCount--;
- firstSel = i;
- }
-
- g_plugin.bDisableDelete = false;
-
- if (firstSel != -1) {
- SetCaret(firstSel, false);
- SetSelection(firstSel, firstSel);
- FixScrollPosition(true);
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-
-void NewstoryListData::Download(int options)
-{
- if (auto *p = LoadItem(caret))
- Srmm_DownloadOfflineFile(p->hContact, p->hEvent, options);
-}
-
-void NewstoryListData::EndEditItem(bool bAccept)
-{
- if (hwndEditBox == nullptr)
- return;
-
- if (bAccept) {
- if ((GetWindowLong(hwndEditBox, GWL_STYLE) & ES_READONLY) == 0) {
- auto *pItem = GetItem(caret);
-
- int iTextLen = GetWindowTextLengthW(hwndEditBox);
- mir_free(pItem->wtext);
- pItem->wtext = (wchar_t *)mir_alloc((iTextLen + 1) * sizeof(wchar_t));
- GetWindowTextW(hwndEditBox, pItem->wtext, iTextLen+1);
- pItem->wtext[iTextLen] = 0;
-
- if (pItem->hContact && pItem->hEvent) {
- DBEVENTINFO dbei = pItem->dbe;
-
- ptrA szUtf(mir_utf8encodeW(pItem->wtext));
- dbei.cbBlob = (int)mir_strlen(szUtf) + 1;
- dbei.pBlob = szUtf.get();
- db_event_edit(pItem->hEvent, &dbei);
- }
-
- MTextDestroy(pItem->data); pItem->data = 0;
- pItem->savedHeight = -1;
- pItem->checkCreate(m_hwnd);
- }
- }
-
- DestroyWindow(hwndEditBox);
- hwndEditBox = nullptr;
-}
-
-void NewstoryListData::EnsureVisible(int item)
-{
- if (scrollTopItem >= item) {
- scrollTopItem = item;
- scrollTopPixel = 0;
- }
- else {
- RECT rc;
- GetClientRect(m_hwnd, &rc);
- int height = rc.bottom - rc.top;
- int idx = scrollTopItem;
- int itemHeight = GetItemHeight(idx);
- int top = itemHeight + scrollTopPixel;
- bool found = false;
- while (top < height) {
- if (idx == item) {
- itemHeight = GetItemHeight(idx);
- found = true;
- break;
- }
- top += itemHeight;
- idx++;
- itemHeight = GetItemHeight(idx);
- }
- if (!found) {
- scrollTopItem = item;
- scrollTopPixel = 0;
- }
- }
- FixScrollPosition();
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-int NewstoryListData::FindNext(const wchar_t *pwszText)
-{
- int idx = items.FindNext(caret, Filter(Filter::EVENTONLY, pwszText));
- if (idx == -1 && caret > 0)
- idx = items.FindNext(-1, Filter(Filter::EVENTONLY, pwszText));
-
- if (idx >= 0) {
- SetSelection(idx, idx);
- SetCaret(idx);
- }
- return idx;
-}
-
-int NewstoryListData::FindPrev(const wchar_t *pwszText)
-{
- int idx = items.FindPrev(caret, Filter(Filter::EVENTONLY, pwszText));
- if (idx == -1 && caret != totalCount - 1)
- idx = items.FindPrev(totalCount, Filter(Filter::EVENTONLY, pwszText));
-
- if (idx >= 0) {
- SetSelection(idx, idx);
- SetCaret(idx);
- }
- return idx;
-}
-
-void NewstoryListData::FixScrollPosition(bool bForce)
-{
- EndEditItem(false);
-
- if (bForce || cachedMaxTopItem != scrollTopItem)
- CalcBottom();
-
- if (scrollTopItem < 0)
- scrollTopItem = 0;
-
- if (bForce || scrollTopItem > cachedMaxTopItem || (scrollTopItem == cachedMaxTopItem && scrollTopPixel < cachedMaxTopPixel)) {
- scrollTopItem = cachedMaxTopItem;
- scrollTopPixel = cachedMaxTopPixel;
- }
-}
-
-CMStringW NewstoryListData::GatherSelected(bool bTextOnly)
-{
- CMStringW ret;
-
- int eventCount = totalCount;
- for (int i = 0; i < eventCount; i++) {
- ItemData *p = GetItem(i);
- if (!p->m_bSelected)
- continue;
-
- CMStringW wszText(bTextOnly ? p->wtext : p->formatString());
- RemoveBbcodes(wszText);
- ret.Append(wszText);
- ret.Append(L"\r\n");
- }
-
- return ret;
-}
-
-ItemData* NewstoryListData::GetItem(int idx) const
-{
- if (totalCount == 0)
- return nullptr;
-
- return (bSortAscending) ? items.get(idx, false) : items.get(totalCount - 1 - idx, false);
-}
-
-int NewstoryListData::GetItemFromPixel(int yPos)
-{
- if (!totalCount)
- return -1;
-
- RECT rc;
- GetClientRect(m_hwnd, &rc);
-
- int height = rc.bottom - rc.top;
- int current = scrollTopItem;
- int top = scrollTopPixel;
- int bottom = top + GetItemHeight(current);
- while (top <= height) {
- if (yPos >= top && yPos <= bottom)
- return current;
- if (++current >= totalCount)
- break;
- top = bottom;
- bottom = top + GetItemHeight(current);
- }
-
- return -1;
-}
-
-int NewstoryListData::GetItemHeight(int index)
-{
- if (auto *pItem = LoadItem(index))
- return GetItemHeight(pItem);
- return 0;
-}
-
-int NewstoryListData::GetItemHeight(ItemData *pItem)
-{
- if (pItem->savedHeight == -1) {
- HDC hdc = GetDC(m_hwnd);
- pItem->savedHeight = PaintItem(hdc, pItem, 0, cachedWindowWidth, false);
- ReleaseDC(m_hwnd, hdc);
- }
-
- return pItem->savedHeight;
-}
-
-bool NewstoryListData::HasSelection() const
-{
- for (int i = 0; i < totalCount; i++)
- if (auto *p = GetItem(i))
- if (p->m_bSelected)
- return true;
-
- return false;
-}
-
-void NewstoryListData::HitTotal(int yCurr, int yTotal)
-{
- int i = 0, y = yCurr;
- while (i < totalCount && y > 0) {
- auto *pItem = GetItem(i++);
- if (!pItem->m_bLoaded) {
- i = totalCount * (double(yCurr) / double(yTotal));
- y = 0;
- break;
- }
- else y -= GetItemHeight(pItem);
- }
-
- scrollTopItem = i;
- scrollTopPixel = y;
- FixScrollPosition();
-}
-
-ItemData* NewstoryListData::LoadItem(int idx)
-{
- if (totalCount == 0)
- return nullptr;
-
- mir_cslock lck(m_csItems);
- return (bSortAscending) ? items.get(idx, true) : items.get(totalCount - 1 - idx, true);
-}
-
-void NewstoryListData::OpenFolder()
-{
- if (auto *pItem = GetItem(caret)) {
- if (pItem->completed()) {
- DB::EventInfo dbei(pItem->hEvent);
- DB::FILE_BLOB blob(dbei);
- CMStringW wszFile(blob.getLocalName());
- int idx = wszFile.ReverseFind('\\');
- if (idx != -1)
- wszFile.Truncate(idx);
- ::ShellExecute(nullptr, L"open", wszFile, nullptr, nullptr, SW_SHOWNORMAL);
- }
- }
-}
-
-int NewstoryListData::PaintItem(HDC hdc, ItemData *pItem, int top, int width, bool bDraw)
-{
- // remove any selections that might be created by the BBCodes parser
- MTextSendMessage(m_hwnd, pItem->data, EM_SETSEL, 0, 0);
-
- // LOGFONT lfText;
- COLORREF clText, clBack, clLine;
- int fontid, colorid;
- pItem->getFontColor(fontid, colorid);
-
- clText = g_fontTable[fontid].cl;
- if (pItem->m_bHighlighted) {
- clText = g_fontTable[FONT_HIGHLIGHT].cl;
- clBack = g_colorTable[COLOR_HIGHLIGHT_BACK].cl;
- clLine = g_colorTable[COLOR_FRAME].cl;
- }
- else if (pItem->m_bSelected) {
- clText = g_colorTable[COLOR_SELTEXT].cl;
- clBack = g_colorTable[COLOR_SELBACK].cl;
- clLine = g_colorTable[COLOR_SELFRAME].cl;
- }
- else {
- clLine = g_colorTable[COLOR_FRAME].cl;
- clBack = g_colorTable[colorid].cl;
- }
-
- pItem->checkCreate(m_hwnd);
-
- SIZE sz;
- sz.cx = width - 2;
-
- POINT pos;
- pos.x = 2;
- pos.y = top + 2;
-
- if (g_plugin.bShowType) // Message type icon
- pos.x += 18;
-
- if (g_plugin.bShowDirecction) // Message direction icon
- pos.x += 18;
-
- if (pItem->dbe.flags & DBEF_BOOKMARK) // Bookmark icon
- pos.x += 18;
-
- sz.cx -= pos.x;
- if (pItem->m_bOfflineDownloaded != 0) // Download completed icon
- sz.cx -= 18;
-
- HFONT hfnt = (HFONT)SelectObject(hdc, g_fontTable[fontid].hfnt);
- MTextMeasure(hdc, &sz, pItem->data);
- SelectObject(hdc, hfnt);
-
- int height = sz.cy + 5;
- if (!bDraw)
- return height;
-
- HBRUSH hbr = CreateSolidBrush(clBack);
- RECT rc = { 0, top, width, top + height };
- FillRect(hdc, &rc, hbr);
- DeleteObject(hbr);
-
- SetTextColor(hdc, clText);
- SetBkMode(hdc, TRANSPARENT);
-
- pos.x = 2;
- HICON hIcon;
-
- // Message type icon
- if (g_plugin.bShowType) {
- switch (pItem->dbe.eventType) {
- case EVENTTYPE_MESSAGE:
- hIcon = g_plugin.getIcon(IDI_SENDMSG);
- break;
- case EVENTTYPE_FILE:
- hIcon = Skin_LoadIcon(SKINICON_EVENT_FILE);
- break;
- case EVENTTYPE_STATUSCHANGE:
- hIcon = g_plugin.getIcon(IDI_SIGNIN);
- break;
- default:
- hIcon = g_plugin.getIcon(IDI_UNKNOWN);
- break;
- }
- DrawIconEx(hdc, pos.x, pos.y, hIcon, 16, 16, 0, 0, DI_NORMAL);
- pos.x += 18;
- }
-
- // Direction icon
- if (g_plugin.bShowDirecction) {
- if (pItem->dbe.flags & DBEF_SENT)
- hIcon = g_plugin.getIcon(IDI_MSGOUT);
- else
- hIcon = g_plugin.getIcon(IDI_MSGIN);
- DrawIconEx(hdc, pos.x, pos.y, hIcon, 16, 16, 0, 0, DI_NORMAL);
- pos.x += 18;
- }
-
- // Bookmark icon
- if (pItem->dbe.flags & DBEF_BOOKMARK) {
- DrawIconEx(hdc, pos.x, pos.y, g_plugin.getIcon(IDI_BOOKMARK), 16, 16, 0, 0, DI_NORMAL);
- pos.x += 18;
- }
-
- // Finished icon
- if (pItem->m_bOfflineDownloaded != 0) {
- if (pItem->completed())
- DrawIconEx(hdc, width - 20, pos.y, g_plugin.getIcon(IDI_OK), 16, 16, 0, 0, DI_NORMAL);
- else {
- HPEN hpn = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 4, g_colorTable[COLOR_PROGRESS].cl));
- MoveToEx(hdc, rc.left, rc.bottom - 4, 0);
- LineTo(hdc, rc.left + (rc.right - rc.left) * int(pItem->m_bOfflineDownloaded) / 100, rc.bottom - 4);
- DeleteObject(SelectObject(hdc, hpn));
- }
- }
-
- hfnt = (HFONT)SelectObject(hdc, g_fontTable[fontid].hfnt);
- MTextDisplay(hdc, pos, sz, pItem->data);
- SelectObject(hdc, hfnt);
-
- HPEN hpn = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 1, clLine));
- MoveToEx(hdc, rc.left, rc.bottom - 1, 0);
- LineTo(hdc, rc.right, rc.bottom - 1);
- DeleteObject(SelectObject(hdc, hpn));
- return height;
-}
-
-void NewstoryListData::RecalcScrollBar()
-{
- if (totalCount == 0)
- return;
-
- int yTotal = 0, yTop = 0, numRec = 0;
- for (int i = 0; i < totalCount; i++) {
- if (i == scrollTopItem)
- yTop = yTotal - scrollTopPixel;
-
- auto *pItem = GetItem(i);
- if (pItem->m_bLoaded) {
- yTotal += GetItemHeight(pItem);
- numRec++;
- }
- }
-
- if (numRec != totalCount) {
- double averageH = double(yTotal) / double(numRec);
- yTotal = totalCount * averageH;
- yTop = scrollTopItem * averageH;
- }
-
- SCROLLINFO si = {};
- si.cbSize = sizeof(si);
- si.fMask = SIF_ALL;
- si.nMin = 0;
- si.nMax = yTotal;
- si.nPage = cachedWindowHeight;
- si.nPos = yTop;
-
- if (si.nPos != cachedScrollbarPos || si.nMax != cachedScrollbarMax) {
- cachedScrollbarPos = si.nPos;
- cachedScrollbarMax = si.nMax;
- SetScrollInfo(m_hwnd, SB_VERT, &si, TRUE);
- }
-}
-
-void NewstoryListData::Quote()
-{
- if (pMsgDlg) {
- CMStringW wszText(GatherSelected(true));
- RemoveBbcodes(wszText);
- pMsgDlg->SetMessageText(Srmm_Quote(wszText));
-
- SetFocus(pMsgDlg->GetInput());
- }
-}
-
-void NewstoryListData::Reply()
-{
- if (pMsgDlg)
- if (auto *pItem = GetItem(caret))
- pMsgDlg->SetQuoteEvent(pItem->hEvent);
-}
-
-void NewstoryListData::ScheduleDraw()
-{
- bWasAtBottom = AtBottom();
-
- redrawTimer.Stop();
- redrawTimer.Start(30);
-}
-
-void NewstoryListData::SetCaret(int idx, bool bEnsureVisible)
-{
- if (idx < totalCount) {
- caret = idx;
- if (bEnsureVisible)
- EnsureVisible(idx);
- }
-}
-
-void NewstoryListData::SetContact(MCONTACT hContact)
-{
- m_hContact = hContact;
-
- WindowList_Add(g_hNewstoryLogs, m_hwnd, hContact);
-}
-
-void NewstoryListData::SetDialog(CSrmmBaseDialog *pDlg)
-{
- if (pMsgDlg = pDlg)
- SetContact(pDlg->m_hContact);
-}
-
-void NewstoryListData::SetPos(int pos)
-{
- SetSelection((selStart == -1) ? pos : selStart, pos);
- SetCaret(pos);
-}
-
-void NewstoryListData::SetSelection(int iFirst, int iLast)
-{
- int start = min(totalCount - 1, iFirst);
- int end = min(totalCount - 1, max(0, iLast));
- if (start > end)
- std::swap(start, end);
-
- int count = totalCount;
- for (int i = 0; i < count; ++i) {
- auto *p = GetItem(i);
- if (i >= start && i <= end)
- p->m_bSelected = true;
- else
- p->m_bSelected = false;
- }
-
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::ToggleBookmark()
-{
- int eventCount = totalCount;
- for (int i = 0; i < eventCount; i++) {
- ItemData *p = GetItem(i);
- if (!p->m_bSelected)
- continue;
-
- if (p->dbe.flags & DBEF_BOOKMARK)
- p->dbe.flags &= ~DBEF_BOOKMARK;
- else
- p->dbe.flags |= DBEF_BOOKMARK;
- db_event_edit(p->hEvent, &p->dbe);
-
- p->setText(m_hwnd);
- }
-
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::ToggleSelection(int iFirst, int iLast)
-{
- int start = min(totalCount - 1, iFirst);
- int end = min(totalCount - 1, max(0, iLast));
- if (start > end)
- std::swap(start, end);
-
- for (int i = start; i <= end; ++i) {
- auto *p = GetItem(i);
- p->m_bSelected = !p->m_bSelected;
- }
-
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::TryUp(int iCount)
-{
- if (totalCount == 0)
- return;
-
- auto *pTop = GetItem(0);
- MCONTACT hContact = pTop->hContact;
- if (pTop->hEvent == 0 || hContact == 0)
- return;
-
- int i;
- for (i = 0; i < iCount; i++) {
- MEVENT hPrev = db_event_prev(hContact, pTop->hEvent);
- if (hPrev == 0)
- break;
-
- auto *p = items.insert(0);
- p->hContact = hContact;
- p->hEvent = hPrev;
- totalCount++;
- }
-
- ItemData *pPrev = nullptr;
- for (int j = 0; j < i + 1; j++) {
- auto *pItem = GetItem(j);
- pPrev = pItem->checkNext(pPrev, m_hwnd);
- }
-
- caret = 0;
- CalcBottom();
- FixScrollPosition();
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Navigation by coordinates
-
-void NewstoryListData::LineUp()
-{
- if (AtTop())
- TryUp(1);
- else
- ScrollUp(10);
-}
-
-void NewstoryListData::LineDown()
-{
- if (!AtBottom())
- ScrollDown(10);
-}
-
-void NewstoryListData::PageUp()
-{
- if (AtTop())
- TryUp(10);
- else
- ScrollUp(cachedWindowHeight);
-}
-
-void NewstoryListData::PageDown()
-{
- if (!AtBottom())
- ScrollDown(cachedWindowHeight);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Navigation by events
-
-void NewstoryListData::EventUp()
-{
- if (caret == 0)
- TryUp(1);
- else
- SetPos(caret - 1);
-}
-
-void NewstoryListData::EventDown()
-{
- if (caret < totalCount-1)
- SetPos(caret + 1);
-}
-
-void NewstoryListData::EventPageUp()
-{
- if (caret >= 10)
- SetPos(caret - 10);
- else
- TryUp(caret == 10 ? 1 : 10 - caret);
-}
-
-void NewstoryListData::EventPageDown()
-{
- if (caret < totalCount - 10)
- SetPos(caret + 10);
- else
- SetPos(totalCount - 1);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// Common navigation functions
-
-void NewstoryListData::ScrollBottom()
-{
- if (!totalCount)
- return;
-
- scrollTopItem = cachedMaxTopItem;
- scrollTopPixel = cachedMaxTopPixel;
- FixScrollPosition(true);
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::ScrollDown(int deltaY)
-{
- int iHeight = GetItemHeight(scrollTopItem) + scrollTopPixel;
- if (iHeight > deltaY)
- scrollTopPixel -= deltaY;
- else {
- deltaY -= iHeight;
-
- bool bFound = false;
- for (int i = scrollTopItem + 1; i < totalCount; i++) {
- iHeight = GetItemHeight(i);
- if (iHeight > deltaY) {
- scrollTopPixel = -deltaY;
- scrollTopItem = i;
- bFound = true;
- break;
- }
- deltaY -= iHeight;
- }
- if (!bFound)
- scrollTopItem = scrollTopPixel = 0;
- }
-
- FixScrollPosition();
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::ScrollTop()
-{
- scrollTopItem = scrollTopPixel = 0;
- FixScrollPosition();
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-void NewstoryListData::ScrollUp(int deltaY)
-{
- int reserveY = -scrollTopPixel; // distance in pixels between the top event beginning and the window top coordinate
-
- if (reserveY >= deltaY)
- scrollTopPixel += deltaY; // stay on the same event, just move up
- else {
- deltaY -= reserveY; // move to the appropriate event first, then calculate the gap
-
- bool bFound = false;
- for (int i = scrollTopItem - 1; i >= 0; i--) {
- int iHeight = GetItemHeight(i);
- if (iHeight > deltaY) {
- scrollTopPixel = deltaY - iHeight;
- scrollTopItem = i;
- bFound = true;
- break;
- }
- deltaY -= iHeight;
- }
-
- if (!bFound)
- scrollTopItem = scrollTopPixel = 0;
- }
-
- FixScrollPosition();
- InvalidateRect(m_hwnd, 0, FALSE);
-}
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// NewStory history control window procedure
-
-LRESULT CALLBACK NewstoryListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
-{
- int idx;
- POINT pt;
- NewstoryListData *data = (NewstoryListData *)GetWindowLongPtr(hwnd, 0);
-
- MSG message = { hwnd, msg, wParam, lParam };
- switch (Hotkey_Check(&message, MODULENAME)) {
- case HOTKEY_SEEK_FORWARD:
- PostMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(IDOK, BN_CLICKED), 1);
- break;
- case HOTKEY_SEEK_BACK:
- PostMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(IDC_FINDPREV, BN_CLICKED), 1);
- break;
- case HOTKEY_SEARCH:
- PostMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(IDC_SEARCH, BN_CLICKED), 1);
- break;
- case HOTKEY_BOOKMARK:
- data->ToggleBookmark();
- return 0;
- }
-
- switch (msg) {
- case WM_CREATE:
- data = new NewstoryListData(hwnd);
- SetWindowLongPtr(hwnd, 0, (LONG_PTR)data);
- if (!g_plugin.bOptVScroll)
- SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_VSCROLL);
- break;
-
- // History list control messages
- case NSM_SELECTITEMS:
- data->AddSelection(wParam, lParam);
- return 0;
-
- case NSM_SEEKTIME:
- {
- int eventCount = data->totalCount;
- for (int i = 0; i < eventCount; i++) {
- auto *p = data->GetItem(i);
- if (p->dbe.timestamp >= wParam) {
- data->SetSelection(i, i);
- data->SetCaret(i);
- break;
- }
-
- if (i == eventCount - 1) {
- data->SetSelection(i, i);
- data->SetCaret(i);
- }
- }
- }
- return TRUE;
-
- case NSM_ADDEVENT:
- data->AddEvent(wParam, lParam, 1);
- break;
-
- case NSM_SET_OPTIONS:
- data->bSortAscending = g_plugin.bSortAscending;
- data->scrollTopPixel = 0;
- data->FixScrollPosition(true);
- InvalidateRect(hwnd, 0, FALSE);
- break;
-
- case UM_ADDEVENT:
- if (data->pMsgDlg == nullptr)
- data->AddEvent(wParam, lParam, 1);
- break;
-
- case UM_EDITEVENT:
- idx = data->items.find(lParam);
- if (idx != -1) {
- auto *p = data->GetItem(idx);
- p->load(true);
- p->setText(data->m_hwnd);
- InvalidateRect(hwnd, 0, FALSE);
- }
- break;
-
- case UM_REMOVEEVENT:
- idx = data->items.find(lParam);
- if (idx != -1) {
- data->items.remove(idx);
- data->totalCount--;
- data->FixScrollPosition(true);
- InvalidateRect(hwnd, 0, FALSE);
- }
- break;
-
- case WM_SIZE:
- data->OnResize(LOWORD(lParam), HIWORD(lParam));
- break;
-
- case WM_COMMAND:
- if (NSMenu_Process(LOWORD(wParam), data))
- return 1;
- break;
-
- case WM_ERASEBKGND:
- return 1;
-
- case WM_PAINT:
- /* we get so many InvalidateRect()'s that there is no point painting,
- Windows in theory shouldn't queue up WM_PAINTs in this case but it does so
- we'll just ignore them */
- if (IsWindowVisible(hwnd)) {
- PAINTSTRUCT ps;
- HDC hdcWindow = BeginPaint(hwnd, &ps);
-
- RECT rc;
- GetClientRect(hwnd, &rc);
-
- HDC hdc = CreateCompatibleDC(hdcWindow);
- HBITMAP hbmSave = (HBITMAP)SelectObject(hdc, CreateCompatibleBitmap(hdcWindow, rc.right - rc.left, rc.bottom - rc.top));
-
- int height = rc.bottom - rc.top;
- int width = rc.right - rc.left;
- int top = data->scrollTopPixel;
-
- for (idx = data->scrollTopItem; top < height && idx < data->totalCount; idx++)
- top += data->PaintItem(hdc, data->LoadItem(idx), top, width, !data->hwndEditBox || data->caret != idx);
-
- data->cachedMaxDrawnItem = idx;
-
- if (top <= height) {
- RECT rc2;
- SetRect(&rc2, 0, top, width, height);
-
- HBRUSH hbr = CreateSolidBrush(g_colorTable[COLOR_BACK].cl);
- FillRect(hdc, &rc2, hbr);
- DeleteObject(hbr);
- }
-
- if (g_plugin.bOptVScroll)
- data->RecalcScrollBar();
- if (g_plugin.bDrawEdge)
- DrawEdge(hdc, &rc, BDR_SUNKENOUTER, BF_RECT);
-
- BitBlt(hdcWindow, 0, 0, rc.right, rc.bottom, hdc, 0, 0, SRCCOPY);
- DeleteObject(SelectObject(hdc, hbmSave));
- DeleteDC(hdc);
- EndPaint(hwnd, &ps);
- }
- break;
-
- case WM_CONTEXTMENU:
- pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
- if (pt.x == -1 && pt.y == -1)
- GetCursorPos(&pt);
-
- POINT pt2 = pt;
- ScreenToClient(hwnd, &pt2);
-
- idx = data->GetItemFromPixel(pt2.y);
- if (idx != -1) {
- if (data->caret != idx)
- data->EndEditItem(false);
- data->SetCaret(idx);
- if (!data->HasSelection())
- data->SetSelection(idx, idx);
- }
- data->OnContextMenu(idx, pt);
- break;
-
- case WM_KILLFOCUS:
- if (wParam && (HWND)wParam != data->hwndEditBox)
- data->EndEditItem(false);
- if (data->pMsgDlg && ((HWND)wParam == data->pMsgDlg->GetInput() || (HWND)wParam == data->pMsgDlg->GetHwnd()))
- data->ClearSelection(0, -1);
- return 0;
-
- case WM_SETFOCUS:
- return 0;
-
- case WM_GETDLGCODE:
- if (lParam) {
- MSG *msg2 = (MSG *)lParam;
- if (msg2->message == WM_KEYDOWN) {
- if (msg2->wParam == VK_TAB)
- return 0;
- if (msg2->wParam == VK_ESCAPE && !data->hwndEditBox)
- return 0;
- }
- else if (msg2->message == WM_CHAR) {
- if (msg2->wParam == '\t')
- return 0;
- if (msg2->wParam == 27 && !data->hwndEditBox)
- return 0;
- }
- }
- return DLGC_WANTMESSAGE;
-
- case WM_KEYDOWN:
- {
- bool isShift = (GetKeyState(VK_SHIFT) & 0x80) != 0;
- bool isCtrl = (GetKeyState(VK_CONTROL) & 0x80) != 0;
-
- if (!data->bWasShift && isShift)
- data->selStart = data->caret;
- else if (data->bWasShift && !isShift)
- data->selStart = -1;
- data->bWasShift = isShift;
-
- switch (wParam) {
- case VK_UP:
- if (g_plugin.bHppCompat)
- data->EventUp();
- else
- data->LineUp();
- break;
-
- case VK_DOWN:
- if (g_plugin.bHppCompat)
- data->EventDown();
- else
- data->LineDown();
- break;
-
- case VK_PRIOR:
- if (isCtrl)
- data->ScrollTop();
- else if (g_plugin.bHppCompat)
- data->EventPageUp();
- else
- data->PageUp();
- break;
-
- case VK_NEXT:
- if (isCtrl)
- data->ScrollBottom();
- else if (g_plugin.bHppCompat)
- data->EventPageDown();
- else
- data->PageDown();
- break;
-
- case VK_HOME:
- data->ScrollTop();
- break;
-
- case VK_END:
- data->ScrollBottom();
- break;
-
- case VK_F2:
- data->BeginEditItem();
- break;
-
- case VK_ESCAPE:
- if (data->hwndEditBox)
- data->EndEditItem(false);
- break;
-
- case VK_DELETE:
- data->DeleteItems();
- break;
-
- case VK_INSERT:
- case 'C':
- if (isCtrl)
- data->Copy();
- break;
-
- case 'A':
- if (isCtrl)
- data->AddSelection(0, data->totalCount);
- break;
- }
- }
- break;
-
- case WM_LBUTTONDOWN:
- pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
- idx = data->GetItemFromPixel(pt.y);
- if (idx >= 0) {
- if (data->caret != idx)
- data->EndEditItem(false);
-
- auto *pItem = data->LoadItem(idx);
-
- if (wParam & MK_CONTROL) {
- data->ToggleSelection(idx, idx);
- data->SetCaret(idx);
- }
- else if (wParam & MK_SHIFT) {
- data->AddSelection(data->caret, idx);
- data->SetCaret(idx);
- }
- else {
- pt.y -= pItem->savedTop;
-
- CMStringW wszUrl;
- if (pItem->isLink(hwnd, pt, &wszUrl)) {
- Utils_OpenUrlW(wszUrl);
- return 0;
- }
-
- data->selStart = idx;
- data->SetSelection(idx, idx);
- data->SetCaret(idx);
- }
- }
- SetFocus(hwnd);
- return 0;
-
- case WM_LBUTTONUP:
- pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
- idx = data->GetItemFromPixel(pt.y);
- if (idx >= 0)
- data->selStart = -1;
- break;
-
- case WM_LBUTTONDBLCLK:
- pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
- idx = data->GetItemFromPixel(pt.y);
- if (idx >= 0) {
- if (data->caret != idx)
- data->EndEditItem(false);
-
- auto *pItem = data->LoadItem(idx);
- pt.y -= pItem->savedTop;
-
- if (pItem->m_bOfflineFile) {
- Srmm_DownloadOfflineFile(pItem->hContact, pItem->hEvent, OFD_DOWNLOAD | OFD_RUN);
- return 0;
- }
-
- if (data->caret == idx) {
- data->BeginEditItem();
- return 0;
- }
- }
-
- SetFocus(hwnd);
- return 0;
-
- case WM_MOUSEMOVE:
- pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
- idx = data->GetItemFromPixel(pt.y);
- if (idx >= 0) {
- auto *pItem = data->LoadItem(idx);
- MTextSendMessage(hwnd, pItem->data, msg, wParam, lParam);
-
- HCURSOR hOldCursor = GetCursor();
- HCURSOR hNewCursor = LoadCursor(0, (pItem->isLink(hwnd, pt) || pItem->m_bOfflineFile) ? IDC_HAND : IDC_ARROW);
- if (hOldCursor != hNewCursor)
- SetCursor(hNewCursor);
-
- if (data->selStart != -1) {
- data->SetSelection(data->selStart, idx);
- InvalidateRect(hwnd, 0, FALSE);
- }
- }
- break;
-
- case WM_MOUSEWHEEL:
- if ((short)HIWORD(wParam) < 0)
- data->LineDown();
- else
- data->LineUp();
- return TRUE;
-
- case WM_VSCROLL:
- {
- int s_scrollTopItem = data->scrollTopItem;
- int s_scrollTopPixel = data->scrollTopPixel;
-
- switch (LOWORD(wParam)) {
- case SB_LINEUP:
- if (g_plugin.bHppCompat)
- data->EventUp();
- else
- data->LineUp();
- break;
-
- case SB_LINEDOWN:
- if (g_plugin.bHppCompat)
- data->EventDown();
- else
- data->LineDown();
- break;
-
- case SB_PAGEUP:
- if (g_plugin.bHppCompat)
- data->EventPageUp();
- else
- data->PageUp();
- break;
-
- case SB_PAGEDOWN:
- if (g_plugin.bHppCompat)
- data->EventPageDown();
- else
- data->PageDown();
- break;
-
- case SB_BOTTOM:
- data->ScrollBottom();
- break;
-
- case SB_TOP:
- data->ScrollTop();
- break;
-
- case SB_THUMBTRACK:
- SCROLLINFO si;
- si.cbSize = sizeof(si);
- si.fMask = SIF_ALL;
- GetScrollInfo(hwnd, SB_VERT, &si);
- data->HitTotal(si.nTrackPos, si.nMax);
- break;
-
- default:
- return 0;
- }
-
- if (s_scrollTopItem != data->scrollTopItem || s_scrollTopPixel != data->scrollTopPixel)
- InvalidateRect(hwnd, 0, FALSE);
- }
- break;
-
- case WM_CTLCOLORSTATIC:
- case WM_CTLCOLOREDIT:
- if (lParam == INT_PTR(data->hwndEditBox)) {
- SetBkColor((HDC)wParam, g_colorTable[COLOR_SELBACK].cl);
- return (LRESULT)g_plugin.hBackBrush;
- }
- break;
-
- case WM_DESTROY:
- WindowList_Add(g_hNewstoryLogs, hwnd);
- delete data;
- SetWindowLongPtr(hwnd, 0, 0);
- break;
- }
-
- return DefWindowProc(hwnd, msg, wParam, lParam);
-}
-
-void InitNewstoryControl()
-{
- htuLog = MTextRegister("Newstory", MTEXT_FANCY_DEFAULT | MTEXT_SYSTEM_HICONS | MTEXT_FANCY_SMILEYS);
-
- WNDCLASS wndclass = {};
- wndclass.style = /*CS_HREDRAW | CS_VREDRAW | */CS_DBLCLKS | CS_GLOBALCLASS;
- wndclass.lpfnWndProc = NewstoryListWndProc;
- wndclass.cbWndExtra = sizeof(void *);
- wndclass.hInstance = g_plugin.getInst();
- wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
- wndclass.lpszClassName = _T(NEWSTORYLIST_CLASS);
- RegisterClass(&wndclass);
-}
+/*
+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"
+
+#define AVERAGE_ITEM_HEIGHT 100
+
+HANDLE htuLog = 0;
+
+void InitHotkeys()
+{
+ HOTKEYDESC hkd = {};
+ hkd.szSection.a = MODULENAME;
+
+ hkd.szDescription.a = LPGEN("Toggle bookmark");
+ hkd.pszName = "ns_bookmark";
+ hkd.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL, 'B') | HKF_MIRANDA_LOCAL;
+ hkd.lParam = HOTKEY_BOOKMARK;
+ g_plugin.addHotkey(&hkd);
+
+ hkd.szDescription.a = LPGEN("Search");
+ hkd.pszName = "ns_search";
+ hkd.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL, 'F') | HKF_MIRANDA_LOCAL;
+ hkd.lParam = HOTKEY_SEARCH;
+ g_plugin.addHotkey(&hkd);
+
+ hkd.szDescription.a = LPGEN("Search forward");
+ hkd.pszName = "ns_seek_forward";
+ hkd.DefHotKey = HOTKEYCODE(0, VK_F3) | HKF_MIRANDA_LOCAL;
+ hkd.lParam = HOTKEY_SEEK_FORWARD;
+ g_plugin.addHotkey(&hkd);
+
+ hkd.szDescription.a = LPGEN("Search backward");
+ hkd.pszName = "ns_seek_back";
+ hkd.DefHotKey = HOTKEYCODE(HOTKEYF_SHIFT, VK_F3) | HKF_MIRANDA_LOCAL;
+ hkd.lParam = HOTKEY_SEEK_BACK;
+ g_plugin.addHotkey(&hkd);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Control utilities, types and constants
+
+NewstoryListData::NewstoryListData(HWND _1) :
+ m_hwnd(_1),
+ redrawTimer(Miranda_GetSystemWindow(), LPARAM(this))
+{
+ items.setOwner(_1);
+
+ bSortAscending = g_plugin.bSortAscending;
+
+ redrawTimer.OnEvent = Callback(this, &NewstoryListData::onTimer_Draw);
+}
+
+void NewstoryListData::onTimer_Draw(CTimer *pTimer)
+{
+ pTimer->Stop();
+
+ if (bWasAtBottom)
+ EnsureVisible(totalCount - 1);
+
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::OnContextMenu(int index, POINT pt)
+{
+ HMENU hMenu = NSMenu_Build(this, (index == -1) ? 0 : LoadItem(index));
+
+ if (pMsgDlg != nullptr && pMsgDlg->isChat())
+ Chat_CreateMenu(hMenu, pMsgDlg->getChat(), nullptr);
+
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, m_hwnd, nullptr);
+ Menu_DestroyNestedMenu(hMenu);
+}
+
+void NewstoryListData::OnResize(int newWidth, int newHeight)
+{
+ bool bDraw = false;
+ if (newWidth != cachedWindowWidth) {
+ cachedWindowWidth = newWidth;
+ for (int i = 0; i < totalCount; i++)
+ GetItem(i)->savedHeight = -1;
+ bDraw = true;
+ }
+
+ if (newHeight != cachedWindowHeight) {
+ cachedWindowHeight = newHeight;
+ FixScrollPosition(true);
+ bDraw = true;
+ }
+
+ if (bDraw)
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::AddChatEvent(SESSION_INFO *si, const LOGINFO *lin)
+{
+ ScheduleDraw();
+ items.addChatEvent(si, lin);
+ totalCount++;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void NewstoryListData::AddEvent(MCONTACT hContact, MEVENT hFirstEvent, int iCount)
+{
+ ScheduleDraw();
+ items.addEvent(hContact, hFirstEvent, iCount);
+ totalCount = items.getCount();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void NewstoryListData::AddResults(const OBJLIST<SearchResult> &results)
+{
+ ScheduleDraw();
+ items.addResults(results);
+ totalCount = items.getCount();
+}
+
+void NewstoryListData::AddSelection(int iFirst, int iLast)
+{
+ int start = min(totalCount - 1, iFirst);
+ int end = min(totalCount - 1, max(0, iLast));
+ if (start > end)
+ std::swap(start, end);
+
+ for (int i = start; i <= end; ++i)
+ if (auto *p = GetItem(i))
+ p->m_bSelected = true;
+
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+bool NewstoryListData::AtBottom(void) const
+{
+ if (scrollTopItem > cachedMaxTopItem)
+ return true;
+
+ if (scrollTopItem == cachedMaxTopItem && cachedMaxTopPixel >= scrollTopPixel)
+ return true;
+
+ return false;
+}
+
+bool NewstoryListData::AtTop(void) const
+{
+ if (scrollTopItem < 0)
+ return true;
+
+ if (scrollTopItem == 0 && scrollTopPixel == 0)
+ return true;
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Edit box window procedure
+
+static LRESULT CALLBACK HistoryEditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ auto *pData = (NewstoryListData *)GetWindowLongPtr(GetParent(hwnd), 0);
+
+ switch (msg) {
+ case WM_KEYDOWN:
+ switch (wParam) {
+ case VK_RETURN:
+ pData->EndEditItem(true);
+ return 0;
+ case VK_ESCAPE:
+ pData->EndEditItem(false);
+ return 0;
+ }
+ break;
+
+ case WM_GETDLGCODE:
+ if (lParam) {
+ MSG *msg2 = (MSG *)lParam;
+ if (msg2->message == WM_KEYDOWN && msg2->wParam == VK_TAB)
+ return 0;
+ if (msg2->message == WM_CHAR && msg2->wParam == '\t')
+ return 0;
+ }
+ return DLGC_WANTMESSAGE;
+
+ case WM_KILLFOCUS:
+ pData->EndEditItem(false);
+ return 0;
+ }
+
+ return mir_callNextSubclass(hwnd, HistoryEditWndProc, msg, wParam, lParam);
+}
+
+void NewstoryListData::BeginEditItem()
+{
+ if (hwndEditBox)
+ EndEditItem(false);
+
+ if (scrollTopItem > caret)
+ return;
+
+ ItemData *item = LoadItem(caret);
+ if (item->dbe.eventType != EVENTTYPE_MESSAGE)
+ return;
+
+ RECT rc; GetClientRect(m_hwnd, &rc);
+ int height = rc.bottom - rc.top;
+
+ int top = scrollTopPixel;
+ int idx = scrollTopItem;
+ int itemHeight = GetItemHeight(idx);
+ while (top < height) {
+ if (idx == caret)
+ break;
+
+ top += itemHeight;
+ idx++;
+ itemHeight = GetItemHeight(idx);
+ }
+
+ int fontid, colorid;
+ item->getFontColor(fontid, colorid);
+
+ // #4012 make sure that both single & double CRLF are now double
+ CMStringW wszText(item->getWBuf());
+ wszText.Replace(L"\r\n", L"\n");
+ wszText.Replace(L"\n", L"\r\n");
+
+ uint32_t dwStyle = WS_CHILD | WS_BORDER | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL;
+ hwndEditBox = CreateWindow(L"EDIT", wszText, dwStyle, 0, top, rc.right - rc.left, itemHeight, m_hwnd, NULL, g_plugin.getInst(), NULL);
+ mir_subclassWindow(hwndEditBox, HistoryEditWndProc);
+ SendMessage(hwndEditBox, WM_SETFONT, (WPARAM)g_fontTable[fontid].hfnt, 0);
+ SendMessage(hwndEditBox, EM_SETMARGINS, EC_RIGHTMARGIN, 100);
+ ShowWindow(hwndEditBox, SW_SHOW);
+ SetFocus(hwndEditBox);
+ SetForegroundWindow(hwndEditBox);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void NewstoryListData::CalcBottom()
+{
+ int maxTopItem = totalCount, tmp = 0;
+ while (maxTopItem > 0 && tmp < cachedWindowHeight)
+ tmp += GetItemHeight(--maxTopItem);
+ cachedMaxTopItem = maxTopItem;
+ cachedMaxTopPixel = (cachedWindowHeight < tmp) ? cachedWindowHeight - tmp : 0;
+}
+
+void NewstoryListData::Clear()
+{
+ items.clear();
+ totalCount = 0;
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::ClearSelection(int iFirst, int iLast)
+{
+ int start = min(0, iFirst);
+ int end = (iLast <= 0) ? totalCount - 1 : iLast;
+ if (start > end)
+ std::swap(start, end);
+
+ for (int i = start; i <= end; ++i)
+ if (auto *pItem = GetItem(i))
+ pItem->m_bSelected = false;
+
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::Copy(bool bTextOnly)
+{
+ Utils_ClipboardCopy(GatherSelected(bTextOnly));
+}
+
+void NewstoryListData::CopyPath()
+{
+ if (auto *pItem = GetItem(caret))
+ if (pItem->completed()) {
+ DB::EventInfo dbei(pItem->hEvent);
+ DB::FILE_BLOB blob(dbei);
+ Utils_ClipboardCopy(blob.getLocalName());
+ }
+}
+
+void NewstoryListData::CopyUrl()
+{
+ if (auto *pItem = GetItem(caret))
+ Srmm_DownloadOfflineFile(pItem->hContact, pItem->hEvent, OFD_COPYURL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Delete events dialog
+
+class CDeleteEventsDlg : public CDlgBase
+{
+ MCONTACT m_hContact;
+ CCtrlCheck chkDelHistory, chkForEveryone;
+
+public:
+ bool bDelHistory = false, bForEveryone = false;
+
+ CDeleteEventsDlg(MCONTACT hContact) :
+ CDlgBase(g_plugin, IDD_EMPTYHISTORY),
+ chkDelHistory(this, IDC_DELSERVERHISTORY),
+ chkForEveryone(this, IDC_BOTH)
+ {
+ if (char *szProto = Proto_GetBaseAccountName(hContact)) {
+ bDelHistory = ProtoServiceExists(szProto, PS_EMPTY_SRV_HISTORY);
+ bForEveryone = (CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_DELETEFORALL) != 0;
+ }
+ }
+
+ bool OnInitDialog() override
+ {
+ chkDelHistory.SetState(bDelHistory);
+ chkDelHistory.Enable(bDelHistory);
+
+ bool bEnabled = bDelHistory && bForEveryone;
+ chkForEveryone.SetState(!bEnabled);
+ chkForEveryone.Enable(bEnabled);
+
+ LOGFONT lf;
+ HFONT hFont = (HFONT)SendDlgItemMessage(m_hwnd, IDOK, WM_GETFONT, 0, 0);
+ GetObject(hFont, sizeof(lf), &lf);
+ lf.lfWeight = FW_BOLD;
+ SendDlgItemMessage(m_hwnd, IDC_TOPLINE, WM_SETFONT, (WPARAM)CreateFontIndirect(&lf), 0);
+
+ wchar_t szFormat[256], szFinal[256];
+ GetDlgItemText(m_hwnd, IDC_TOPLINE, szFormat, _countof(szFormat));
+ mir_snwprintf(szFinal, szFormat, Clist_GetContactDisplayName(m_hContact));
+ SetDlgItemText(m_hwnd, IDC_TOPLINE, szFinal);
+
+ SetFocus(GetDlgItem(m_hwnd, IDNO));
+ SetWindowPos(m_hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ bDelHistory = chkDelHistory.IsChecked();
+ bForEveryone = chkForEveryone.IsChecked();
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ DeleteObject((HFONT)SendDlgItemMessage(m_hwnd, IDC_TOPLINE, WM_GETFONT, 0, 0));
+ }
+};
+
+void NewstoryListData::DeleteItems(void)
+{
+ CDeleteEventsDlg dlg(m_hContact);
+ if (IDOK != dlg.DoModal())
+ return;
+
+ g_plugin.bDisableDelete = true;
+
+ int firstSel = -1, flags = 0;
+ if (dlg.bDelHistory)
+ flags |= CDF_DEL_HISTORY;
+ if (dlg.bForEveryone)
+ flags |= CDF_FOR_EVERYONE;
+
+ for (int i = totalCount - 1; i >= 0; i--) {
+ auto *p = GetItem(i);
+ if (!p->m_bSelected)
+ continue;
+
+ if (p->hEvent)
+ db_event_delete(p->hEvent, flags);
+ items.remove(i);
+ totalCount--;
+ firstSel = i;
+ }
+
+ g_plugin.bDisableDelete = false;
+
+ if (firstSel != -1) {
+ SetCaret(firstSel, false);
+ SetSelection(firstSel, firstSel);
+ FixScrollPosition(true);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void NewstoryListData::Download(int options)
+{
+ if (auto *p = LoadItem(caret))
+ Srmm_DownloadOfflineFile(p->hContact, p->hEvent, options);
+}
+
+void NewstoryListData::EndEditItem(bool bAccept)
+{
+ if (hwndEditBox == nullptr)
+ return;
+
+ if (bAccept) {
+ if ((GetWindowLong(hwndEditBox, GWL_STYLE) & ES_READONLY) == 0) {
+ auto *pItem = GetItem(caret);
+
+ int iTextLen = GetWindowTextLengthW(hwndEditBox);
+ mir_free(pItem->wtext);
+ pItem->wtext = (wchar_t *)mir_alloc((iTextLen + 1) * sizeof(wchar_t));
+ GetWindowTextW(hwndEditBox, pItem->wtext, iTextLen+1);
+ pItem->wtext[iTextLen] = 0;
+
+ if (pItem->hContact && pItem->hEvent) {
+ DBEVENTINFO dbei = pItem->dbe;
+
+ ptrA szUtf(mir_utf8encodeW(pItem->wtext));
+ dbei.cbBlob = (int)mir_strlen(szUtf) + 1;
+ dbei.pBlob = szUtf.get();
+ db_event_edit(pItem->hEvent, &dbei);
+ }
+
+ MTextDestroy(pItem->data); pItem->data = 0;
+ pItem->savedHeight = -1;
+ pItem->checkCreate(m_hwnd);
+ }
+ }
+
+ DestroyWindow(hwndEditBox);
+ hwndEditBox = nullptr;
+}
+
+void NewstoryListData::EnsureVisible(int item)
+{
+ if (scrollTopItem >= item) {
+ scrollTopItem = item;
+ scrollTopPixel = 0;
+ }
+ else {
+ RECT rc;
+ GetClientRect(m_hwnd, &rc);
+ int height = rc.bottom - rc.top;
+ int idx = scrollTopItem;
+ int itemHeight = GetItemHeight(idx);
+ int top = itemHeight + scrollTopPixel;
+ bool found = false;
+ while (top < height) {
+ if (idx == item) {
+ itemHeight = GetItemHeight(idx);
+ found = true;
+ break;
+ }
+ top += itemHeight;
+ idx++;
+ itemHeight = GetItemHeight(idx);
+ }
+ if (!found) {
+ scrollTopItem = item;
+ scrollTopPixel = 0;
+ }
+ }
+ FixScrollPosition();
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+int NewstoryListData::FindNext(const wchar_t *pwszText)
+{
+ int idx = items.FindNext(caret, Filter(Filter::EVENTONLY, pwszText));
+ if (idx == -1 && caret > 0)
+ idx = items.FindNext(-1, Filter(Filter::EVENTONLY, pwszText));
+
+ if (idx >= 0) {
+ SetSelection(idx, idx);
+ SetCaret(idx);
+ }
+ return idx;
+}
+
+int NewstoryListData::FindPrev(const wchar_t *pwszText)
+{
+ int idx = items.FindPrev(caret, Filter(Filter::EVENTONLY, pwszText));
+ if (idx == -1 && caret != totalCount - 1)
+ idx = items.FindPrev(totalCount, Filter(Filter::EVENTONLY, pwszText));
+
+ if (idx >= 0) {
+ SetSelection(idx, idx);
+ SetCaret(idx);
+ }
+ return idx;
+}
+
+void NewstoryListData::FixScrollPosition(bool bForce)
+{
+ EndEditItem(false);
+
+ if (bForce || cachedMaxTopItem != scrollTopItem)
+ CalcBottom();
+
+ if (scrollTopItem < 0)
+ scrollTopItem = 0;
+
+ if (bForce || scrollTopItem > cachedMaxTopItem || (scrollTopItem == cachedMaxTopItem && scrollTopPixel < cachedMaxTopPixel)) {
+ scrollTopItem = cachedMaxTopItem;
+ scrollTopPixel = cachedMaxTopPixel;
+ }
+}
+
+CMStringW NewstoryListData::GatherSelected(bool bTextOnly)
+{
+ CMStringW ret;
+
+ int eventCount = totalCount;
+ for (int i = 0; i < eventCount; i++) {
+ ItemData *p = GetItem(i);
+ if (!p->m_bSelected)
+ continue;
+
+ CMStringW wszText(bTextOnly ? p->wtext : p->formatString());
+ RemoveBbcodes(wszText);
+ ret.Append(wszText);
+ ret.Append(L"\r\n");
+ }
+
+ return ret;
+}
+
+ItemData* NewstoryListData::GetItem(int idx) const
+{
+ if (totalCount == 0)
+ return nullptr;
+
+ return (bSortAscending) ? items.get(idx, false) : items.get(totalCount - 1 - idx, false);
+}
+
+int NewstoryListData::GetItemFromPixel(int yPos)
+{
+ if (!totalCount)
+ return -1;
+
+ RECT rc;
+ GetClientRect(m_hwnd, &rc);
+
+ int height = rc.bottom - rc.top;
+ int current = scrollTopItem;
+ int top = scrollTopPixel;
+ int bottom = top + GetItemHeight(current);
+ while (top <= height) {
+ if (yPos >= top && yPos <= bottom)
+ return current;
+ if (++current >= totalCount)
+ break;
+ top = bottom;
+ bottom = top + GetItemHeight(current);
+ }
+
+ return -1;
+}
+
+int NewstoryListData::GetItemHeight(int index)
+{
+ if (auto *pItem = LoadItem(index))
+ return GetItemHeight(pItem);
+ return 0;
+}
+
+int NewstoryListData::GetItemHeight(ItemData *pItem)
+{
+ if (pItem->savedHeight == -1) {
+ HDC hdc = GetDC(m_hwnd);
+ pItem->savedHeight = PaintItem(hdc, pItem, 0, cachedWindowWidth, false);
+ ReleaseDC(m_hwnd, hdc);
+ }
+
+ return pItem->savedHeight;
+}
+
+bool NewstoryListData::HasSelection() const
+{
+ for (int i = 0; i < totalCount; i++)
+ if (auto *p = GetItem(i))
+ if (p->m_bSelected)
+ return true;
+
+ return false;
+}
+
+void NewstoryListData::HitTotal(int yCurr, int yTotal)
+{
+ int i = 0, y = yCurr;
+ while (i < totalCount && y > 0) {
+ auto *pItem = GetItem(i++);
+ if (!pItem->m_bLoaded) {
+ i = totalCount * (double(yCurr) / double(yTotal));
+ y = 0;
+ break;
+ }
+ else y -= GetItemHeight(pItem);
+ }
+
+ scrollTopItem = i;
+ scrollTopPixel = y;
+ FixScrollPosition();
+}
+
+ItemData* NewstoryListData::LoadItem(int idx)
+{
+ if (totalCount == 0)
+ return nullptr;
+
+ mir_cslock lck(m_csItems);
+ return (bSortAscending) ? items.get(idx, true) : items.get(totalCount - 1 - idx, true);
+}
+
+void NewstoryListData::OpenFolder()
+{
+ if (auto *pItem = GetItem(caret)) {
+ if (pItem->completed()) {
+ DB::EventInfo dbei(pItem->hEvent);
+ DB::FILE_BLOB blob(dbei);
+ CMStringW wszFile(blob.getLocalName());
+ int idx = wszFile.ReverseFind('\\');
+ if (idx != -1)
+ wszFile.Truncate(idx);
+ ::ShellExecute(nullptr, L"open", wszFile, nullptr, nullptr, SW_SHOWNORMAL);
+ }
+ }
+}
+
+int NewstoryListData::PaintItem(HDC hdc, ItemData *pItem, int top, int width, bool bDraw)
+{
+ // remove any selections that might be created by the BBCodes parser
+ MTextSendMessage(m_hwnd, pItem->data, EM_SETSEL, 0, 0);
+
+ // LOGFONT lfText;
+ COLORREF clText, clBack, clLine;
+ int fontid, colorid;
+ pItem->getFontColor(fontid, colorid);
+
+ clText = g_fontTable[fontid].cl;
+ if (pItem->m_bHighlighted) {
+ clText = g_fontTable[FONT_HIGHLIGHT].cl;
+ clBack = g_colorTable[COLOR_HIGHLIGHT_BACK].cl;
+ clLine = g_colorTable[COLOR_FRAME].cl;
+ }
+ else if (pItem->m_bSelected) {
+ clText = g_colorTable[COLOR_SELTEXT].cl;
+ clBack = g_colorTable[COLOR_SELBACK].cl;
+ clLine = g_colorTable[COLOR_SELFRAME].cl;
+ }
+ else {
+ clLine = g_colorTable[COLOR_FRAME].cl;
+ clBack = g_colorTable[colorid].cl;
+ }
+
+ pItem->checkCreate(m_hwnd);
+
+ SIZE sz;
+ sz.cx = width - 2;
+
+ POINT pos;
+ pos.x = 2;
+ pos.y = top + 2;
+
+ if (g_plugin.bShowType) // Message type icon
+ pos.x += 18;
+
+ if (g_plugin.bShowDirecction) // Message direction icon
+ pos.x += 18;
+
+ if (pItem->dbe.flags & DBEF_BOOKMARK) // Bookmark icon
+ pos.x += 18;
+
+ sz.cx -= pos.x;
+ if (pItem->m_bOfflineDownloaded != 0) // Download completed icon
+ sz.cx -= 18;
+
+ HFONT hfnt = (HFONT)SelectObject(hdc, g_fontTable[fontid].hfnt);
+ MTextMeasure(hdc, &sz, pItem->data);
+ SelectObject(hdc, hfnt);
+
+ int height = sz.cy + 5;
+ if (!bDraw)
+ return height;
+
+ HBRUSH hbr = CreateSolidBrush(clBack);
+ RECT rc = { 0, top, width, top + height };
+ FillRect(hdc, &rc, hbr);
+ DeleteObject(hbr);
+
+ SetTextColor(hdc, clText);
+ SetBkMode(hdc, TRANSPARENT);
+
+ pos.x = 2;
+ HICON hIcon;
+
+ // Message type icon
+ if (g_plugin.bShowType) {
+ switch (pItem->dbe.eventType) {
+ case EVENTTYPE_MESSAGE:
+ hIcon = g_plugin.getIcon(IDI_SENDMSG);
+ break;
+ case EVENTTYPE_FILE:
+ hIcon = Skin_LoadIcon(SKINICON_EVENT_FILE);
+ break;
+ case EVENTTYPE_STATUSCHANGE:
+ hIcon = g_plugin.getIcon(IDI_SIGNIN);
+ break;
+ default:
+ hIcon = g_plugin.getIcon(IDI_UNKNOWN);
+ break;
+ }
+ DrawIconEx(hdc, pos.x, pos.y, hIcon, 16, 16, 0, 0, DI_NORMAL);
+ pos.x += 18;
+ }
+
+ // Direction icon
+ if (g_plugin.bShowDirecction) {
+ if (pItem->dbe.flags & DBEF_SENT)
+ hIcon = g_plugin.getIcon(IDI_MSGOUT);
+ else
+ hIcon = g_plugin.getIcon(IDI_MSGIN);
+ DrawIconEx(hdc, pos.x, pos.y, hIcon, 16, 16, 0, 0, DI_NORMAL);
+ pos.x += 18;
+ }
+
+ // Bookmark icon
+ if (pItem->dbe.flags & DBEF_BOOKMARK) {
+ DrawIconEx(hdc, pos.x, pos.y, g_plugin.getIcon(IDI_BOOKMARK), 16, 16, 0, 0, DI_NORMAL);
+ pos.x += 18;
+ }
+
+ // Finished icon
+ if (pItem->m_bOfflineDownloaded != 0) {
+ if (pItem->completed())
+ DrawIconEx(hdc, width - 20, pos.y, g_plugin.getIcon(IDI_OK), 16, 16, 0, 0, DI_NORMAL);
+ else {
+ HPEN hpn = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 4, g_colorTable[COLOR_PROGRESS].cl));
+ MoveToEx(hdc, rc.left, rc.bottom - 4, 0);
+ LineTo(hdc, rc.left + (rc.right - rc.left) * int(pItem->m_bOfflineDownloaded) / 100, rc.bottom - 4);
+ DeleteObject(SelectObject(hdc, hpn));
+ }
+ }
+
+ hfnt = (HFONT)SelectObject(hdc, g_fontTable[fontid].hfnt);
+ MTextDisplay(hdc, pos, sz, pItem->data);
+ SelectObject(hdc, hfnt);
+
+ HPEN hpn = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 1, clLine));
+ MoveToEx(hdc, rc.left, rc.bottom - 1, 0);
+ LineTo(hdc, rc.right, rc.bottom - 1);
+ DeleteObject(SelectObject(hdc, hpn));
+ return height;
+}
+
+void NewstoryListData::RecalcScrollBar()
+{
+ if (totalCount == 0)
+ return;
+
+ int yTotal = 0, yTop = 0, numRec = 0;
+ for (int i = 0; i < totalCount; i++) {
+ if (i == scrollTopItem)
+ yTop = yTotal - scrollTopPixel;
+
+ auto *pItem = GetItem(i);
+ if (pItem->m_bLoaded) {
+ yTotal += GetItemHeight(pItem);
+ numRec++;
+ }
+ }
+
+ if (numRec != totalCount) {
+ double averageH = double(yTotal) / double(numRec);
+ yTotal = totalCount * averageH;
+ yTop = scrollTopItem * averageH;
+ }
+
+ SCROLLINFO si = {};
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_ALL;
+ si.nMin = 0;
+ si.nMax = yTotal;
+ si.nPage = cachedWindowHeight;
+ si.nPos = yTop;
+
+ if (si.nPos != cachedScrollbarPos || si.nMax != cachedScrollbarMax) {
+ cachedScrollbarPos = si.nPos;
+ cachedScrollbarMax = si.nMax;
+ SetScrollInfo(m_hwnd, SB_VERT, &si, TRUE);
+ }
+}
+
+void NewstoryListData::Quote()
+{
+ if (pMsgDlg) {
+ CMStringW wszText(GatherSelected(true));
+ RemoveBbcodes(wszText);
+ pMsgDlg->SetMessageText(Srmm_Quote(wszText));
+
+ SetFocus(pMsgDlg->GetInput());
+ }
+}
+
+void NewstoryListData::Reply()
+{
+ if (pMsgDlg)
+ if (auto *pItem = GetItem(caret))
+ pMsgDlg->SetQuoteEvent(pItem->hEvent);
+}
+
+void NewstoryListData::ScheduleDraw()
+{
+ bWasAtBottom = AtBottom();
+
+ redrawTimer.Stop();
+ redrawTimer.Start(30);
+}
+
+void NewstoryListData::SetCaret(int idx, bool bEnsureVisible)
+{
+ if (idx < totalCount) {
+ caret = idx;
+ if (bEnsureVisible)
+ EnsureVisible(idx);
+ }
+}
+
+void NewstoryListData::SetContact(MCONTACT hContact)
+{
+ m_hContact = hContact;
+
+ WindowList_Add(g_hNewstoryLogs, m_hwnd, hContact);
+}
+
+void NewstoryListData::SetDialog(CSrmmBaseDialog *pDlg)
+{
+ if (pMsgDlg = pDlg)
+ SetContact(pDlg->m_hContact);
+}
+
+void NewstoryListData::SetPos(int pos)
+{
+ SetSelection((selStart == -1) ? pos : selStart, pos);
+ SetCaret(pos);
+}
+
+void NewstoryListData::SetSelection(int iFirst, int iLast)
+{
+ int start = min(totalCount - 1, iFirst);
+ int end = min(totalCount - 1, max(0, iLast));
+ if (start > end)
+ std::swap(start, end);
+
+ int count = totalCount;
+ for (int i = 0; i < count; ++i) {
+ auto *p = GetItem(i);
+ if (i >= start && i <= end)
+ p->m_bSelected = true;
+ else
+ p->m_bSelected = false;
+ }
+
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::ToggleBookmark()
+{
+ int eventCount = totalCount;
+ for (int i = 0; i < eventCount; i++) {
+ ItemData *p = GetItem(i);
+ if (!p->m_bSelected)
+ continue;
+
+ if (p->dbe.flags & DBEF_BOOKMARK)
+ p->dbe.flags &= ~DBEF_BOOKMARK;
+ else
+ p->dbe.flags |= DBEF_BOOKMARK;
+ db_event_edit(p->hEvent, &p->dbe);
+
+ p->setText(m_hwnd);
+ }
+
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::ToggleSelection(int iFirst, int iLast)
+{
+ int start = min(totalCount - 1, iFirst);
+ int end = min(totalCount - 1, max(0, iLast));
+ if (start > end)
+ std::swap(start, end);
+
+ for (int i = start; i <= end; ++i) {
+ auto *p = GetItem(i);
+ p->m_bSelected = !p->m_bSelected;
+ }
+
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::TryUp(int iCount)
+{
+ if (totalCount == 0)
+ return;
+
+ auto *pTop = GetItem(0);
+ MCONTACT hContact = pTop->hContact;
+ if (pTop->hEvent == 0 || hContact == 0)
+ return;
+
+ int i;
+ for (i = 0; i < iCount; i++) {
+ MEVENT hPrev = db_event_prev(hContact, pTop->hEvent);
+ if (hPrev == 0)
+ break;
+
+ auto *p = items.insert(0);
+ p->hContact = hContact;
+ p->hEvent = hPrev;
+ totalCount++;
+ }
+
+ ItemData *pPrev = nullptr;
+ for (int j = 0; j < i + 1; j++) {
+ auto *pItem = GetItem(j);
+ pPrev = pItem->checkNext(pPrev, m_hwnd);
+ }
+
+ caret = 0;
+ CalcBottom();
+ FixScrollPosition();
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Navigation by coordinates
+
+void NewstoryListData::LineUp()
+{
+ if (AtTop())
+ TryUp(1);
+ else
+ ScrollUp(10);
+}
+
+void NewstoryListData::LineDown()
+{
+ if (!AtBottom())
+ ScrollDown(10);
+}
+
+void NewstoryListData::PageUp()
+{
+ if (AtTop())
+ TryUp(10);
+ else
+ ScrollUp(cachedWindowHeight);
+}
+
+void NewstoryListData::PageDown()
+{
+ if (!AtBottom())
+ ScrollDown(cachedWindowHeight);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Navigation by events
+
+void NewstoryListData::EventUp()
+{
+ if (caret == 0)
+ TryUp(1);
+ else
+ SetPos(caret - 1);
+}
+
+void NewstoryListData::EventDown()
+{
+ if (caret < totalCount-1)
+ SetPos(caret + 1);
+}
+
+void NewstoryListData::EventPageUp()
+{
+ if (caret >= 10)
+ SetPos(caret - 10);
+ else
+ TryUp(caret == 10 ? 1 : 10 - caret);
+}
+
+void NewstoryListData::EventPageDown()
+{
+ if (caret < totalCount - 10)
+ SetPos(caret + 10);
+ else
+ SetPos(totalCount - 1);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Common navigation functions
+
+void NewstoryListData::ScrollBottom()
+{
+ if (!totalCount)
+ return;
+
+ scrollTopItem = cachedMaxTopItem;
+ scrollTopPixel = cachedMaxTopPixel;
+ FixScrollPosition(true);
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::ScrollDown(int deltaY)
+{
+ int iHeight = GetItemHeight(scrollTopItem) + scrollTopPixel;
+ if (iHeight > deltaY)
+ scrollTopPixel -= deltaY;
+ else {
+ deltaY -= iHeight;
+
+ bool bFound = false;
+ for (int i = scrollTopItem + 1; i < totalCount; i++) {
+ iHeight = GetItemHeight(i);
+ if (iHeight > deltaY) {
+ scrollTopPixel = -deltaY;
+ scrollTopItem = i;
+ bFound = true;
+ break;
+ }
+ deltaY -= iHeight;
+ }
+ if (!bFound)
+ scrollTopItem = scrollTopPixel = 0;
+ }
+
+ FixScrollPosition();
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::ScrollTop()
+{
+ scrollTopItem = scrollTopPixel = 0;
+ FixScrollPosition();
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+void NewstoryListData::ScrollUp(int deltaY)
+{
+ int reserveY = -scrollTopPixel; // distance in pixels between the top event beginning and the window top coordinate
+
+ if (reserveY >= deltaY)
+ scrollTopPixel += deltaY; // stay on the same event, just move up
+ else {
+ deltaY -= reserveY; // move to the appropriate event first, then calculate the gap
+
+ bool bFound = false;
+ for (int i = scrollTopItem - 1; i >= 0; i--) {
+ int iHeight = GetItemHeight(i);
+ if (iHeight > deltaY) {
+ scrollTopPixel = deltaY - iHeight;
+ scrollTopItem = i;
+ bFound = true;
+ break;
+ }
+ deltaY -= iHeight;
+ }
+
+ if (!bFound)
+ scrollTopItem = scrollTopPixel = 0;
+ }
+
+ FixScrollPosition();
+ InvalidateRect(m_hwnd, 0, FALSE);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// NewStory history control window procedure
+
+LRESULT CALLBACK NewstoryListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ int idx;
+ POINT pt;
+ NewstoryListData *data = (NewstoryListData *)GetWindowLongPtr(hwnd, 0);
+
+ MSG message = { hwnd, msg, wParam, lParam };
+ switch (Hotkey_Check(&message, MODULENAME)) {
+ case HOTKEY_SEEK_FORWARD:
+ PostMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(IDOK, BN_CLICKED), 1);
+ break;
+ case HOTKEY_SEEK_BACK:
+ PostMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(IDC_FINDPREV, BN_CLICKED), 1);
+ break;
+ case HOTKEY_SEARCH:
+ PostMessage(GetParent(hwnd), WM_COMMAND, MAKELONG(IDC_SEARCH, BN_CLICKED), 1);
+ break;
+ case HOTKEY_BOOKMARK:
+ data->ToggleBookmark();
+ return 0;
+ }
+
+ switch (msg) {
+ case WM_CREATE:
+ data = new NewstoryListData(hwnd);
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR)data);
+ if (!g_plugin.bOptVScroll)
+ SetWindowLong(hwnd, GWL_STYLE, GetWindowLong(hwnd, GWL_STYLE) & ~WS_VSCROLL);
+ break;
+
+ // History list control messages
+ case NSM_SELECTITEMS:
+ data->AddSelection(wParam, lParam);
+ return 0;
+
+ case NSM_SEEKTIME:
+ {
+ int eventCount = data->totalCount;
+ for (int i = 0; i < eventCount; i++) {
+ auto *p = data->GetItem(i);
+ if (p->dbe.timestamp >= wParam) {
+ data->SetSelection(i, i);
+ data->SetCaret(i);
+ break;
+ }
+
+ if (i == eventCount - 1) {
+ data->SetSelection(i, i);
+ data->SetCaret(i);
+ }
+ }
+ }
+ return TRUE;
+
+ case NSM_ADDEVENT:
+ data->AddEvent(wParam, lParam, 1);
+ break;
+
+ case NSM_SET_OPTIONS:
+ data->bSortAscending = g_plugin.bSortAscending;
+ data->scrollTopPixel = 0;
+ data->FixScrollPosition(true);
+ InvalidateRect(hwnd, 0, FALSE);
+ break;
+
+ case UM_ADDEVENT:
+ if (data->pMsgDlg == nullptr)
+ data->AddEvent(wParam, lParam, 1);
+ break;
+
+ case UM_EDITEVENT:
+ idx = data->items.find(lParam);
+ if (idx != -1) {
+ auto *p = data->GetItem(idx);
+ p->load(true);
+ p->setText(data->m_hwnd);
+ InvalidateRect(hwnd, 0, FALSE);
+ }
+ break;
+
+ case UM_REMOVEEVENT:
+ idx = data->items.find(lParam);
+ if (idx != -1) {
+ data->items.remove(idx);
+ data->totalCount--;
+ data->FixScrollPosition(true);
+ InvalidateRect(hwnd, 0, FALSE);
+ }
+ break;
+
+ case WM_SIZE:
+ data->OnResize(LOWORD(lParam), HIWORD(lParam));
+ break;
+
+ case WM_COMMAND:
+ if (NSMenu_Process(LOWORD(wParam), data))
+ return 1;
+ break;
+
+ case WM_ERASEBKGND:
+ return 1;
+
+ case WM_PAINT:
+ /* we get so many InvalidateRect()'s that there is no point painting,
+ Windows in theory shouldn't queue up WM_PAINTs in this case but it does so
+ we'll just ignore them */
+ if (IsWindowVisible(hwnd)) {
+ PAINTSTRUCT ps;
+ HDC hdcWindow = BeginPaint(hwnd, &ps);
+
+ RECT rc;
+ GetClientRect(hwnd, &rc);
+
+ HDC hdc = CreateCompatibleDC(hdcWindow);
+ HBITMAP hbmSave = (HBITMAP)SelectObject(hdc, CreateCompatibleBitmap(hdcWindow, rc.right - rc.left, rc.bottom - rc.top));
+
+ int height = rc.bottom - rc.top;
+ int width = rc.right - rc.left;
+ int top = data->scrollTopPixel;
+
+ for (idx = data->scrollTopItem; top < height && idx < data->totalCount; idx++)
+ top += data->PaintItem(hdc, data->LoadItem(idx), top, width, !data->hwndEditBox || data->caret != idx);
+
+ data->cachedMaxDrawnItem = idx;
+
+ if (top <= height) {
+ RECT rc2;
+ SetRect(&rc2, 0, top, width, height);
+
+ HBRUSH hbr = CreateSolidBrush(g_colorTable[COLOR_BACK].cl);
+ FillRect(hdc, &rc2, hbr);
+ DeleteObject(hbr);
+ }
+
+ if (g_plugin.bOptVScroll)
+ data->RecalcScrollBar();
+ if (g_plugin.bDrawEdge)
+ DrawEdge(hdc, &rc, BDR_SUNKENOUTER, BF_RECT);
+
+ BitBlt(hdcWindow, 0, 0, rc.right, rc.bottom, hdc, 0, 0, SRCCOPY);
+ DeleteObject(SelectObject(hdc, hbmSave));
+ DeleteDC(hdc);
+ EndPaint(hwnd, &ps);
+ }
+ break;
+
+ case WM_CONTEXTMENU:
+ pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ if (pt.x == -1 && pt.y == -1)
+ GetCursorPos(&pt);
+
+ POINT pt2 = pt;
+ ScreenToClient(hwnd, &pt2);
+
+ idx = data->GetItemFromPixel(pt2.y);
+ if (idx != -1) {
+ if (data->caret != idx)
+ data->EndEditItem(false);
+ data->SetCaret(idx);
+ if (!data->HasSelection())
+ data->SetSelection(idx, idx);
+ }
+ data->OnContextMenu(idx, pt);
+ break;
+
+ case WM_KILLFOCUS:
+ if (wParam && (HWND)wParam != data->hwndEditBox)
+ data->EndEditItem(false);
+ if (data->pMsgDlg && ((HWND)wParam == data->pMsgDlg->GetInput() || (HWND)wParam == data->pMsgDlg->GetHwnd()))
+ data->ClearSelection(0, -1);
+ return 0;
+
+ case WM_SETFOCUS:
+ return 0;
+
+ case WM_GETDLGCODE:
+ if (lParam) {
+ MSG *msg2 = (MSG *)lParam;
+ if (msg2->message == WM_KEYDOWN) {
+ if (msg2->wParam == VK_TAB)
+ return 0;
+ if (msg2->wParam == VK_ESCAPE && !data->hwndEditBox)
+ return 0;
+ }
+ else if (msg2->message == WM_CHAR) {
+ if (msg2->wParam == '\t')
+ return 0;
+ if (msg2->wParam == 27 && !data->hwndEditBox)
+ return 0;
+ }
+ }
+ return DLGC_WANTMESSAGE;
+
+ case WM_KEYDOWN:
+ {
+ bool isShift = (GetKeyState(VK_SHIFT) & 0x80) != 0;
+ bool isCtrl = (GetKeyState(VK_CONTROL) & 0x80) != 0;
+
+ if (!data->bWasShift && isShift)
+ data->selStart = data->caret;
+ else if (data->bWasShift && !isShift)
+ data->selStart = -1;
+ data->bWasShift = isShift;
+
+ switch (wParam) {
+ case VK_UP:
+ if (g_plugin.bHppCompat)
+ data->EventUp();
+ else
+ data->LineUp();
+ break;
+
+ case VK_DOWN:
+ if (g_plugin.bHppCompat)
+ data->EventDown();
+ else
+ data->LineDown();
+ break;
+
+ case VK_PRIOR:
+ if (isCtrl)
+ data->ScrollTop();
+ else if (g_plugin.bHppCompat)
+ data->EventPageUp();
+ else
+ data->PageUp();
+ break;
+
+ case VK_NEXT:
+ if (isCtrl)
+ data->ScrollBottom();
+ else if (g_plugin.bHppCompat)
+ data->EventPageDown();
+ else
+ data->PageDown();
+ break;
+
+ case VK_HOME:
+ data->ScrollTop();
+ break;
+
+ case VK_END:
+ data->ScrollBottom();
+ break;
+
+ case VK_F2:
+ data->BeginEditItem();
+ break;
+
+ case VK_ESCAPE:
+ if (data->hwndEditBox)
+ data->EndEditItem(false);
+ break;
+
+ case VK_DELETE:
+ data->DeleteItems();
+ break;
+
+ case VK_INSERT:
+ case 'C':
+ if (isCtrl)
+ data->Copy();
+ break;
+
+ case 'A':
+ if (isCtrl)
+ data->AddSelection(0, data->totalCount);
+ break;
+ }
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ idx = data->GetItemFromPixel(pt.y);
+ if (idx >= 0) {
+ if (data->caret != idx)
+ data->EndEditItem(false);
+
+ auto *pItem = data->LoadItem(idx);
+
+ if (wParam & MK_CONTROL) {
+ data->ToggleSelection(idx, idx);
+ data->SetCaret(idx);
+ }
+ else if (wParam & MK_SHIFT) {
+ data->AddSelection(data->caret, idx);
+ data->SetCaret(idx);
+ }
+ else {
+ pt.y -= pItem->savedTop;
+
+ CMStringW wszUrl;
+ if (pItem->isLink(hwnd, pt, &wszUrl)) {
+ Utils_OpenUrlW(wszUrl);
+ return 0;
+ }
+
+ data->selStart = idx;
+ data->SetSelection(idx, idx);
+ data->SetCaret(idx);
+ }
+ }
+ SetFocus(hwnd);
+ return 0;
+
+ case WM_LBUTTONUP:
+ pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ idx = data->GetItemFromPixel(pt.y);
+ if (idx >= 0)
+ data->selStart = -1;
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ idx = data->GetItemFromPixel(pt.y);
+ if (idx >= 0) {
+ if (data->caret != idx)
+ data->EndEditItem(false);
+
+ auto *pItem = data->LoadItem(idx);
+ pt.y -= pItem->savedTop;
+
+ if (pItem->m_bOfflineFile) {
+ Srmm_DownloadOfflineFile(pItem->hContact, pItem->hEvent, OFD_DOWNLOAD | OFD_RUN);
+ return 0;
+ }
+
+ if (data->caret == idx) {
+ data->BeginEditItem();
+ return 0;
+ }
+ }
+
+ SetFocus(hwnd);
+ return 0;
+
+ case WM_MOUSEMOVE:
+ pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
+ idx = data->GetItemFromPixel(pt.y);
+ if (idx >= 0) {
+ auto *pItem = data->LoadItem(idx);
+ MTextSendMessage(hwnd, pItem->data, msg, wParam, lParam);
+
+ HCURSOR hOldCursor = GetCursor();
+ HCURSOR hNewCursor = LoadCursor(0, (pItem->isLink(hwnd, pt) || pItem->m_bOfflineFile) ? IDC_HAND : IDC_ARROW);
+ if (hOldCursor != hNewCursor)
+ SetCursor(hNewCursor);
+
+ if (data->selStart != -1) {
+ data->SetSelection(data->selStart, idx);
+ InvalidateRect(hwnd, 0, FALSE);
+ }
+ }
+ break;
+
+ case WM_MOUSEWHEEL:
+ if ((short)HIWORD(wParam) < 0)
+ data->LineDown();
+ else
+ data->LineUp();
+ return TRUE;
+
+ case WM_VSCROLL:
+ {
+ int s_scrollTopItem = data->scrollTopItem;
+ int s_scrollTopPixel = data->scrollTopPixel;
+
+ switch (LOWORD(wParam)) {
+ case SB_LINEUP:
+ if (g_plugin.bHppCompat)
+ data->EventUp();
+ else
+ data->LineUp();
+ break;
+
+ case SB_LINEDOWN:
+ if (g_plugin.bHppCompat)
+ data->EventDown();
+ else
+ data->LineDown();
+ break;
+
+ case SB_PAGEUP:
+ if (g_plugin.bHppCompat)
+ data->EventPageUp();
+ else
+ data->PageUp();
+ break;
+
+ case SB_PAGEDOWN:
+ if (g_plugin.bHppCompat)
+ data->EventPageDown();
+ else
+ data->PageDown();
+ break;
+
+ case SB_BOTTOM:
+ data->ScrollBottom();
+ break;
+
+ case SB_TOP:
+ data->ScrollTop();
+ break;
+
+ case SB_THUMBTRACK:
+ SCROLLINFO si;
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_ALL;
+ GetScrollInfo(hwnd, SB_VERT, &si);
+ data->HitTotal(si.nTrackPos, si.nMax);
+ break;
+
+ default:
+ return 0;
+ }
+
+ if (s_scrollTopItem != data->scrollTopItem || s_scrollTopPixel != data->scrollTopPixel)
+ InvalidateRect(hwnd, 0, FALSE);
+ }
+ break;
+
+ case WM_CTLCOLORSTATIC:
+ case WM_CTLCOLOREDIT:
+ if (lParam == INT_PTR(data->hwndEditBox)) {
+ SetBkColor((HDC)wParam, g_colorTable[COLOR_SELBACK].cl);
+ return (LRESULT)g_plugin.hBackBrush;
+ }
+ break;
+
+ case WM_DESTROY:
+ WindowList_Add(g_hNewstoryLogs, hwnd);
+ delete data;
+ SetWindowLongPtr(hwnd, 0, 0);
+ break;
+ }
+
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
+
+void InitNewstoryControl()
+{
+ htuLog = MTextRegister("Newstory", MTEXT_FANCY_DEFAULT | MTEXT_SYSTEM_HICONS | MTEXT_FANCY_SMILEYS);
+
+ WNDCLASS wndclass = {};
+ wndclass.style = /*CS_HREDRAW | CS_VREDRAW | */CS_DBLCLKS | CS_GLOBALCLASS;
+ wndclass.lpfnWndProc = NewstoryListWndProc;
+ wndclass.cbWndExtra = sizeof(void *);
+ wndclass.hInstance = g_plugin.getInst();
+ wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wndclass.lpszClassName = _T(NEWSTORYLIST_CLASS);
+ RegisterClass(&wndclass);
+}