From 2f47988d1ef6672929762fba39fa69ef1b4dfa7a Mon Sep 17 00:00:00 2001 From: dartraiden Date: Mon, 19 Feb 2024 20:13:12 +0300 Subject: NewStory: - add [cX] tag to variables help - convert sources to CR+LF --- plugins/NewStory/src/calendartool.cpp | 128 +- plugins/NewStory/src/fonts.cpp | 264 +-- plugins/NewStory/src/fonts.h | 114 +- plugins/NewStory/src/history.h | 84 +- plugins/NewStory/src/history_array.cpp | 1440 +++++++------- plugins/NewStory/src/history_array.h | 328 ++-- plugins/NewStory/src/history_control.cpp | 3082 +++++++++++++++--------------- plugins/NewStory/src/history_control.h | 196 +- plugins/NewStory/src/history_dlg.cpp | 2446 ++++++++++++------------ plugins/NewStory/src/history_log.cpp | 232 +-- plugins/NewStory/src/history_menus.cpp | 602 +++--- plugins/NewStory/src/history_svc.cpp | 124 +- plugins/NewStory/src/main.cpp | 406 ++-- plugins/NewStory/src/options.cpp | 669 +++---- plugins/NewStory/src/resource.h | 257 ++- plugins/NewStory/src/stdafx.h | 236 ++- plugins/NewStory/src/templates.cpp | 946 ++++----- plugins/NewStory/src/templates.h | 144 +- plugins/NewStory/src/utils.cpp | 227 ++- plugins/NewStory/src/utils.h | 24 +- 20 files changed, 5968 insertions(+), 5981 deletions(-) (limited to 'plugins') diff --git a/plugins/NewStory/src/calendartool.cpp b/plugins/NewStory/src/calendartool.cpp index ab158c10bd..d7c3a4bb27 100644 --- a/plugins/NewStory/src/calendartool.cpp +++ b/plugins/NewStory/src/calendartool.cpp @@ -1,64 +1,64 @@ -/* -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 . -*/ - -#include "stdafx.h" - -INT_PTR CALLBACK CalendarToolDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - switch (msg) { - case WM_INITDIALOG: - // This causes ALL miranda dialogs to have drop-shadow enabled. That's bad =( - // SetClassLong(hwnd, GCL_STYLE, GetClassLong(hwnd, GCL_STYLE)|CS_DROPSHADOW); - SetWindowPos(hwnd, HWND_TOP, LOWORD(lParam), HIWORD(lParam), 0, 0, SWP_NOSIZE); - return TRUE; - - case WM_ACTIVATE: - if (wParam == WA_INACTIVE) - PostMessage(hwnd, WM_CLOSE, 0, 0); - break; - - case WM_NOTIFY: - { - LPNMHDR hdr = (LPNMHDR)lParam; - if ((hdr->idFrom == IDC_MONTHCALENDAR) && (hdr->code == MCN_SELECT)) { - LPNMSELCHANGE lpnmsc = (LPNMSELCHANGE)lParam; - struct tm tm_sel; - tm_sel.tm_hour = tm_sel.tm_min = tm_sel.tm_sec = 0; - tm_sel.tm_isdst = 1; - tm_sel.tm_mday = lpnmsc->stSelStart.wDay; - tm_sel.tm_mon = lpnmsc->stSelStart.wMonth - 1; - tm_sel.tm_year = lpnmsc->stSelStart.wYear - 1900; - PostMessage(GetParent(hwnd), WM_USER + 0x600, mktime(&tm_sel), 0); - EndDialog(hwnd, 0); - } - } - return TRUE; - - case WM_CLOSE: - DestroyWindow(hwnd); - return TRUE; - } - return FALSE; -} - -time_t CalendarTool_Show(HWND hwnd, int x, int y) -{ - HWND hwndCalendar = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_CALENDARTOOL), hwnd, CalendarToolDlgProc, MAKELONG(x, y)); - ShowWindow(hwndCalendar, SW_SHOW); - return 0; -} +/* +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 . +*/ + +#include "stdafx.h" + +INT_PTR CALLBACK CalendarToolDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + // This causes ALL miranda dialogs to have drop-shadow enabled. That's bad =( + // SetClassLong(hwnd, GCL_STYLE, GetClassLong(hwnd, GCL_STYLE)|CS_DROPSHADOW); + SetWindowPos(hwnd, HWND_TOP, LOWORD(lParam), HIWORD(lParam), 0, 0, SWP_NOSIZE); + return TRUE; + + case WM_ACTIVATE: + if (wParam == WA_INACTIVE) + PostMessage(hwnd, WM_CLOSE, 0, 0); + break; + + case WM_NOTIFY: + { + LPNMHDR hdr = (LPNMHDR)lParam; + if ((hdr->idFrom == IDC_MONTHCALENDAR) && (hdr->code == MCN_SELECT)) { + LPNMSELCHANGE lpnmsc = (LPNMSELCHANGE)lParam; + struct tm tm_sel; + tm_sel.tm_hour = tm_sel.tm_min = tm_sel.tm_sec = 0; + tm_sel.tm_isdst = 1; + tm_sel.tm_mday = lpnmsc->stSelStart.wDay; + tm_sel.tm_mon = lpnmsc->stSelStart.wMonth - 1; + tm_sel.tm_year = lpnmsc->stSelStart.wYear - 1900; + PostMessage(GetParent(hwnd), WM_USER + 0x600, mktime(&tm_sel), 0); + EndDialog(hwnd, 0); + } + } + return TRUE; + + case WM_CLOSE: + DestroyWindow(hwnd); + return TRUE; + } + return FALSE; +} + +time_t CalendarTool_Show(HWND hwnd, int x, int y) +{ + HWND hwndCalendar = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_CALENDARTOOL), hwnd, CalendarToolDlgProc, MAKELONG(x, y)); + ShowWindow(hwndCalendar, SW_SHOW); + return 0; +} diff --git a/plugins/NewStory/src/fonts.cpp b/plugins/NewStory/src/fonts.cpp index 844f5d0f62..a85652fdb1 100644 --- a/plugins/NewStory/src/fonts.cpp +++ b/plugins/NewStory/src/fonts.cpp @@ -1,132 +1,132 @@ -/* -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 . -*/ - -#include "stdafx.h" - -MyColourID g_colorTable[COLOR_COUNT] = -{ - { LPGEN("Incoming name"), "ColorNickIn", RGB(0x00, 0x00, 0x00) }, - { LPGEN("Outgoing name"), "ColorNickOut", RGB(0x00, 0x00, 0x00) }, - - { LPGEN("Incoming messages"), "ColorMsgIn", RGB(0xd6, 0xf5, 0xc0) }, - { LPGEN("Outgoing messages"), "ColorMsgOut", RGB(0xf5, 0xe7, 0xd8) }, - - { LPGEN("Incoming files"), "ColorFileIn", RGB(0xe3, 0xee, 0x9b) }, - { LPGEN("Outgoing files"), "ColorFileOut", RGB(0xe3, 0xee, 0x9b) }, - - { LPGEN("Status changes"), "ColorStatus", RGB(0xf0, 0xf0, 0xf0) }, - - { LPGEN("Other incoming events"), "ColorIn", RGB(0xff, 0xff, 0xff) }, - { LPGEN("Other outgoing events"), "ColorOut", RGB(0xff, 0xff, 0xff) }, - - { LPGEN("Selected item's text"), "ColorSelTxt", RGB(0xff, 0xff, 0xff) }, - { LPGEN("Selected item's background"), "ColorSel", GetSysColor(COLOR_HIGHLIGHT) }, - { LPGEN("Selected item's frame"), "ColorSelFrm", GetSysColor(COLOR_HIGHLIGHTTEXT) }, - - { LPGEN("Highlighted messages"), "ColorHighlight", RGB(0xf0, 0xf0, 0xf0) }, - { LPGEN("Grid background"), "Background", RGB(0xff, 0xff, 0xff) }, - { LPGEN("Separator"), "Separator", RGB(0x60, 0x60, 0x60) }, - { LPGEN("Progress indicator"), "Progress", RGB(0xff, 0x00, 0x00) }, -}; - -MyFontID g_fontTable[FONT_COUNT] = -{ - { LPGEN("Incoming messages"), "FontMsgIn" }, - { LPGEN("Outgoing messages"), "FontMsgOut" }, - - { LPGEN("Incoming files"), "FontFileIn" }, - { LPGEN("Outgoing files"), "FontFileOut" }, - - { LPGEN("Status changes"), "FontStatus" }, - { LPGEN("Highlighted messages"), "FontHighlight", DBFONTF_BOLD, RGB(0x7f, 0, 0) }, - - { LPGEN("Other incoming events"), "FontIn" }, - { LPGEN("Other outgoing events"), "FontOut" }, -}; - -int evtFontsChanged(WPARAM, LPARAM) -{ - for (auto &it : g_colorTable) - it.cl = Colour_Get(MODULENAME, it.szName); - - DeleteObject(g_plugin.hBackBrush); - g_plugin.hBackBrush = CreateSolidBrush(g_colorTable[COLOR_SELBACK].cl); - - for (auto &it : g_fontTable) { - it.cl = (COLORREF)Font_Get(MODULENAME, it.szName, &it.lf); - - DeleteObject(it.hfnt); - it.hfnt = CreateFontIndirectA(&it.lf); - } - - WindowList_Broadcast(g_hNewstoryWindows, UM_REDRAWLISTH, 0, 0); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -void InitFonts() -{ - HookEvent(ME_FONT_RELOAD, evtFontsChanged); - HookEvent(ME_COLOUR_RELOAD, evtFontsChanged); - - ColourID cid = {}; - strncpy_s(cid.group, MODULENAME, _TRUNCATE); - strncpy_s(cid.dbSettingsGroup, MODULENAME, _TRUNCATE); - - for (auto &it : g_colorTable) { - cid.order = int(&it - g_colorTable); - strncpy_s(cid.name, it.szName, _TRUNCATE); - strncpy_s(cid.setting, it.szSetting, _TRUNCATE); - cid.defcolour = it.defaultValue; - g_plugin.addColor(&cid); - it.cl = Colour_Get(cid.group, cid.name); - } - - g_plugin.hBackBrush = CreateSolidBrush(g_colorTable[COLOR_SELBACK].cl); - - LOGFONT lfDefault; - SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfDefault), &lfDefault, FALSE); - - FontID fontid = {}; - fontid.flags = FIDF_DEFAULTVALID; - strncpy_s(fontid.group, MODULENAME, _TRUNCATE); - strncpy_s(fontid.dbSettingsGroup, MODULENAME, _TRUNCATE); - strncpy_s(fontid.deffontsettings.szFace, _T2A(lfDefault.lfFaceName), _TRUNCATE); - fontid.deffontsettings.size = -12; - - for (auto &it : g_fontTable) { - fontid.order = int(&it - g_fontTable); - strncpy_s(fontid.name, it.szName, _TRUNCATE); - strncpy_s(fontid.setting, it.szSetting, _TRUNCATE); - fontid.deffontsettings.style = it.style; - fontid.deffontsettings.colour = it.defaultValue; - g_plugin.addFont(&fontid); - - it.cl = Font_Get(MODULENAME, it.szName, &it.lf); - it.hfnt = CreateFontIndirectA(&it.lf); - } -} - -void DestroyFonts() -{ - DeleteObject(g_plugin.hBackBrush); - - for (auto &it : g_fontTable) - DeleteObject(it.hfnt); -} +/* +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 . +*/ + +#include "stdafx.h" + +MyColourID g_colorTable[COLOR_COUNT] = +{ + { LPGEN("Incoming name"), "ColorNickIn", RGB(0x00, 0x00, 0x00) }, + { LPGEN("Outgoing name"), "ColorNickOut", RGB(0x00, 0x00, 0x00) }, + + { LPGEN("Incoming messages"), "ColorMsgIn", RGB(0xd6, 0xf5, 0xc0) }, + { LPGEN("Outgoing messages"), "ColorMsgOut", RGB(0xf5, 0xe7, 0xd8) }, + + { LPGEN("Incoming files"), "ColorFileIn", RGB(0xe3, 0xee, 0x9b) }, + { LPGEN("Outgoing files"), "ColorFileOut", RGB(0xe3, 0xee, 0x9b) }, + + { LPGEN("Status changes"), "ColorStatus", RGB(0xf0, 0xf0, 0xf0) }, + + { LPGEN("Other incoming events"), "ColorIn", RGB(0xff, 0xff, 0xff) }, + { LPGEN("Other outgoing events"), "ColorOut", RGB(0xff, 0xff, 0xff) }, + + { LPGEN("Selected item's text"), "ColorSelTxt", RGB(0xff, 0xff, 0xff) }, + { LPGEN("Selected item's background"), "ColorSel", GetSysColor(COLOR_HIGHLIGHT) }, + { LPGEN("Selected item's frame"), "ColorSelFrm", GetSysColor(COLOR_HIGHLIGHTTEXT) }, + + { LPGEN("Highlighted messages"), "ColorHighlight", RGB(0xf0, 0xf0, 0xf0) }, + { LPGEN("Grid background"), "Background", RGB(0xff, 0xff, 0xff) }, + { LPGEN("Separator"), "Separator", RGB(0x60, 0x60, 0x60) }, + { LPGEN("Progress indicator"), "Progress", RGB(0xff, 0x00, 0x00) }, +}; + +MyFontID g_fontTable[FONT_COUNT] = +{ + { LPGEN("Incoming messages"), "FontMsgIn" }, + { LPGEN("Outgoing messages"), "FontMsgOut" }, + + { LPGEN("Incoming files"), "FontFileIn" }, + { LPGEN("Outgoing files"), "FontFileOut" }, + + { LPGEN("Status changes"), "FontStatus" }, + { LPGEN("Highlighted messages"), "FontHighlight", DBFONTF_BOLD, RGB(0x7f, 0, 0) }, + + { LPGEN("Other incoming events"), "FontIn" }, + { LPGEN("Other outgoing events"), "FontOut" }, +}; + +int evtFontsChanged(WPARAM, LPARAM) +{ + for (auto &it : g_colorTable) + it.cl = Colour_Get(MODULENAME, it.szName); + + DeleteObject(g_plugin.hBackBrush); + g_plugin.hBackBrush = CreateSolidBrush(g_colorTable[COLOR_SELBACK].cl); + + for (auto &it : g_fontTable) { + it.cl = (COLORREF)Font_Get(MODULENAME, it.szName, &it.lf); + + DeleteObject(it.hfnt); + it.hfnt = CreateFontIndirectA(&it.lf); + } + + WindowList_Broadcast(g_hNewstoryWindows, UM_REDRAWLISTH, 0, 0); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void InitFonts() +{ + HookEvent(ME_FONT_RELOAD, evtFontsChanged); + HookEvent(ME_COLOUR_RELOAD, evtFontsChanged); + + ColourID cid = {}; + strncpy_s(cid.group, MODULENAME, _TRUNCATE); + strncpy_s(cid.dbSettingsGroup, MODULENAME, _TRUNCATE); + + for (auto &it : g_colorTable) { + cid.order = int(&it - g_colorTable); + strncpy_s(cid.name, it.szName, _TRUNCATE); + strncpy_s(cid.setting, it.szSetting, _TRUNCATE); + cid.defcolour = it.defaultValue; + g_plugin.addColor(&cid); + it.cl = Colour_Get(cid.group, cid.name); + } + + g_plugin.hBackBrush = CreateSolidBrush(g_colorTable[COLOR_SELBACK].cl); + + LOGFONT lfDefault; + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfDefault), &lfDefault, FALSE); + + FontID fontid = {}; + fontid.flags = FIDF_DEFAULTVALID; + strncpy_s(fontid.group, MODULENAME, _TRUNCATE); + strncpy_s(fontid.dbSettingsGroup, MODULENAME, _TRUNCATE); + strncpy_s(fontid.deffontsettings.szFace, _T2A(lfDefault.lfFaceName), _TRUNCATE); + fontid.deffontsettings.size = -12; + + for (auto &it : g_fontTable) { + fontid.order = int(&it - g_fontTable); + strncpy_s(fontid.name, it.szName, _TRUNCATE); + strncpy_s(fontid.setting, it.szSetting, _TRUNCATE); + fontid.deffontsettings.style = it.style; + fontid.deffontsettings.colour = it.defaultValue; + g_plugin.addFont(&fontid); + + it.cl = Font_Get(MODULENAME, it.szName, &it.lf); + it.hfnt = CreateFontIndirectA(&it.lf); + } +} + +void DestroyFonts() +{ + DeleteObject(g_plugin.hBackBrush); + + for (auto &it : g_fontTable) + DeleteObject(it.hfnt); +} diff --git a/plugins/NewStory/src/fonts.h b/plugins/NewStory/src/fonts.h index 0a441c1931..08183d0d01 100644 --- a/plugins/NewStory/src/fonts.h +++ b/plugins/NewStory/src/fonts.h @@ -1,58 +1,58 @@ -#ifndef __fonts_h__ -#define __fonts_h__ - -///////////////////////////////////////////////////////////////////////////////////////// - -enum -{ - COLOR_INNICK, COLOR_OUTNICK, - COLOR_INMSG, COLOR_OUTMSG, - COLOR_INFILE, COLOR_OUTFILE, - COLOR_STATUS, - COLOR_INOTHER, COLOR_OUTOTHER, - COLOR_SELTEXT, COLOR_SELBACK, COLOR_SELFRAME, - COLOR_HIGHLIGHT_BACK, COLOR_BACK, COLOR_FRAME, COLOR_PROGRESS, - COLOR_COUNT -}; - -struct MyColourID -{ - const char *szName, *szSetting; - COLORREF defaultValue, cl; -}; - -extern MyColourID g_colorTable[COLOR_COUNT]; - -///////////////////////////////////////////////////////////////////////////////////////// - -enum -{ - FONT_INMSG, - FONT_OUTMSG, - FONT_INFILE, - FONT_OUTFILE, - FONT_STATUS, - FONT_HIGHLIGHT, - FONT_INOTHER, - FONT_OUTOTHER, - FONT_COUNT -}; - -struct MyFontID -{ - const char *szName, *szSetting; - uint8_t style; - - COLORREF defaultValue, cl; - LOGFONTA lf; - HFONT hfnt; -}; - -extern MyFontID g_fontTable[FONT_COUNT]; - -///////////////////////////////////////////////////////////////////////////////////////// - -void InitFonts(); -void DestroyFonts(); - +#ifndef __fonts_h__ +#define __fonts_h__ + +///////////////////////////////////////////////////////////////////////////////////////// + +enum +{ + COLOR_INNICK, COLOR_OUTNICK, + COLOR_INMSG, COLOR_OUTMSG, + COLOR_INFILE, COLOR_OUTFILE, + COLOR_STATUS, + COLOR_INOTHER, COLOR_OUTOTHER, + COLOR_SELTEXT, COLOR_SELBACK, COLOR_SELFRAME, + COLOR_HIGHLIGHT_BACK, COLOR_BACK, COLOR_FRAME, COLOR_PROGRESS, + COLOR_COUNT +}; + +struct MyColourID +{ + const char *szName, *szSetting; + COLORREF defaultValue, cl; +}; + +extern MyColourID g_colorTable[COLOR_COUNT]; + +///////////////////////////////////////////////////////////////////////////////////////// + +enum +{ + FONT_INMSG, + FONT_OUTMSG, + FONT_INFILE, + FONT_OUTFILE, + FONT_STATUS, + FONT_HIGHLIGHT, + FONT_INOTHER, + FONT_OUTOTHER, + FONT_COUNT +}; + +struct MyFontID +{ + const char *szName, *szSetting; + uint8_t style; + + COLORREF defaultValue, cl; + LOGFONTA lf; + HFONT hfnt; +}; + +extern MyFontID g_fontTable[FONT_COUNT]; + +///////////////////////////////////////////////////////////////////////////////////////// + +void InitFonts(); +void DestroyFonts(); + #endif // __fonts_h__ \ No newline at end of file diff --git a/plugins/NewStory/src/history.h b/plugins/NewStory/src/history.h index f71bab9fed..ef1d9f5961 100644 --- a/plugins/NewStory/src/history.h +++ b/plugins/NewStory/src/history.h @@ -1,43 +1,43 @@ -#ifndef __history_h__ -#define __history_h__ - -#define EVENTTYPE_OTHER 12345 -#define EVENTTYPE_STATUSCHANGE 25368 -#define EVENTTYPE_JABBER_PRESENCE 2001 - -enum -{ - UM_LOADCONTACT = WM_USER + 1, - - UM_REBUILDLIST, - UM_FILTERLIST, - UM_REDRAWLIST, - UM_REDRAWLISTH, - - UM_ADDEVENT, - UM_ADDEVENTFILTER, - UM_REMOVEEVENT, - UM_EDITEVENT, - - UM_SELECTED, - - UM_GETEVENTCOUNT, - UM_GETEVENT, - UM_GETEVENTTEXT, - UM_GETEVENTCONTACT, - UM_GETEVENTHANDLE, - - UM_BOOKMARKS = WM_USER + 0x601, -}; - -extern MWindowList g_hNewstoryWindows, g_hNewstoryLogs; - -void InitMenus(); -void InitHotkeys(); - -INT_PTR svcShowNewstory(WPARAM, LPARAM); -INT_PTR svcGlobalSearch(WPARAM, LPARAM); - -CSrmmLogWindow* __cdecl NewStory_Stub(CMsgDialog &pDlg); - +#ifndef __history_h__ +#define __history_h__ + +#define EVENTTYPE_OTHER 12345 +#define EVENTTYPE_STATUSCHANGE 25368 +#define EVENTTYPE_JABBER_PRESENCE 2001 + +enum +{ + UM_LOADCONTACT = WM_USER + 1, + + UM_REBUILDLIST, + UM_FILTERLIST, + UM_REDRAWLIST, + UM_REDRAWLISTH, + + UM_ADDEVENT, + UM_ADDEVENTFILTER, + UM_REMOVEEVENT, + UM_EDITEVENT, + + UM_SELECTED, + + UM_GETEVENTCOUNT, + UM_GETEVENT, + UM_GETEVENTTEXT, + UM_GETEVENTCONTACT, + UM_GETEVENTHANDLE, + + UM_BOOKMARKS = WM_USER + 0x601, +}; + +extern MWindowList g_hNewstoryWindows, g_hNewstoryLogs; + +void InitMenus(); +void InitHotkeys(); + +INT_PTR svcShowNewstory(WPARAM, LPARAM); +INT_PTR svcGlobalSearch(WPARAM, LPARAM); + +CSrmmLogWindow* __cdecl NewStory_Stub(CMsgDialog &pDlg); + #endif // __history_h__ \ No newline at end of file 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 . -*/ - -#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 &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 . +*/ + +#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 &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--; +} diff --git a/plugins/NewStory/src/history_array.h b/plugins/NewStory/src/history_array.h index afeb93e54c..df0142308f 100644 --- a/plugins/NewStory/src/history_array.h +++ b/plugins/NewStory/src/history_array.h @@ -1,164 +1,164 @@ -#ifndef __history_array__ -#define __history_array__ - -enum -{ - GROUPING_NONE = 0, - GROUPING_HEAD = 1, - GROUPING_ITEM = 2, -}; - -CMStringW TplFormatString(int tpl, MCONTACT hContact, ItemData *item); - -struct ItemData -{ - MCONTACT hContact; - MEVENT hEvent; - - bool m_bSelected, m_bHighlighted; - bool m_bLoaded, m_bIsResult; - bool m_bOfflineFile; - uint8_t m_grouping, m_bOfflineDownloaded; - - int savedTop, savedHeight; - - DB::EventInfo dbe; - wchar_t *wtext; - wchar_t *wszNick; - - HText data; - - ItemData(); - ~ItemData(); - - ItemData* checkNext(ItemData *pPrev, HWND hwnd); - ItemData* checkPrev(ItemData *pPrev, HWND hwnd); - ItemData* checkPrevGC(ItemData *pPrev, HWND hwnd); - void checkCreate(HWND hwnd); - void markRead(); - void setText(HWND hwnd); - - bool completed() const { return m_bOfflineDownloaded == 100; } - bool fetch(void); - void fill(int tmpl); - void load(bool bLoad = false); - bool isLink(HWND, POINT pt, CMStringW *url = nullptr) const; - bool isLinkChar(HWND, int idx) const; - - int getTemplate() const; - int getCopyTemplate() const; - void getFontColor(int &fontId, int &colorId) const; - - CMStringA formatRtf(const wchar_t *pwszStr = 0); - CMStringW formatString() { return TplFormatString(getTemplate(), hContact, this); } - CMStringW formatStringEx(wchar_t *sztpl); - - inline wchar_t* getWBuf() - { - load(); - return wtext; - } -}; - -class Filter -{ - uint16_t flags; - ptrW text; - -public: - enum - { - INCOMING = 0x001, - OUTGOING = 0x002, - MESSAGES = 0x004, - FILES = 0x008, - STATUS = 0x020, - OTHER = 0x040, - EVENTTEXT = 0x080, - EVENTONLY = 0x100, - }; - - __forceinline Filter(uint16_t aFlags, const wchar_t *wText) : - flags(aFlags), - text(mir_wstrdup(wText)) - { - } - - bool check(ItemData *item) const; -}; - -enum -{ - FILTER_TIME = 0x01, - FILTER_TYPE = 0x02, - FILTER_DIRECTION = 0x04, - FILTER_TEXT = 0x08, - FILTER_UNICODE = 0x10, - - FTYPE_MESSAGE = 0x01, - FTYPE_FILE = 0x02, - FTYPE_URL = 0x04, - FTYPE_STATUS = 0x08, - FTYPE_OTHER = 0x10, - FTYPE_INCOMING = 0x20, - FTYPE_OUTGOING = 0x40 -}; - -#define HIST_BLOCK_SIZE 1000 - -struct ItemBlock : public MZeroedObject -{ - ItemData data[HIST_BLOCK_SIZE]; -}; - -struct SearchResult -{ - SearchResult(MCONTACT _1, MEVENT _2, uint32_t _3) : - hContact(_1), - hEvent(_2), - ts(_3) - {} - - MCONTACT hContact; - MEVENT hEvent; - uint32_t ts; -}; - -class HistoryArray -{ - LIST strings; - OBJLIST pages; - int iLastPageCounter = 0; - MWindow hwndOwner = 0; - - ItemData& allocateItem(void); - -public: - HistoryArray(); - ~HistoryArray(); - - bool addEvent(MCONTACT hContact, MEVENT hEvent, int count); - void addChatEvent(SESSION_INFO *si, const LOGINFO *pEvent); - void addResults(const OBJLIST &pArray); - void clear(); - int find(MEVENT hEvent); - int find(int id, int dir, const Filter &filter); - int getCount() const; - void remove(int idx); - void reset() - { - clear(); - pages.insert(new ItemBlock()); - } - void setOwner(MWindow hwnd) { - hwndOwner = hwnd; - } - - ItemData* get(int id, bool bLoad = false) const; - ItemData* insert(int idx); - - __forceinline int FindNext(int id, const Filter &filter) { return find(id, +1, filter); } - __forceinline int FindPrev(int id, const Filter &filter) { return find(id, -1, filter); } -}; - -#endif // __history_array__ +#ifndef __history_array__ +#define __history_array__ + +enum +{ + GROUPING_NONE = 0, + GROUPING_HEAD = 1, + GROUPING_ITEM = 2, +}; + +CMStringW TplFormatString(int tpl, MCONTACT hContact, ItemData *item); + +struct ItemData +{ + MCONTACT hContact; + MEVENT hEvent; + + bool m_bSelected, m_bHighlighted; + bool m_bLoaded, m_bIsResult; + bool m_bOfflineFile; + uint8_t m_grouping, m_bOfflineDownloaded; + + int savedTop, savedHeight; + + DB::EventInfo dbe; + wchar_t *wtext; + wchar_t *wszNick; + + HText data; + + ItemData(); + ~ItemData(); + + ItemData* checkNext(ItemData *pPrev, HWND hwnd); + ItemData* checkPrev(ItemData *pPrev, HWND hwnd); + ItemData* checkPrevGC(ItemData *pPrev, HWND hwnd); + void checkCreate(HWND hwnd); + void markRead(); + void setText(HWND hwnd); + + bool completed() const { return m_bOfflineDownloaded == 100; } + bool fetch(void); + void fill(int tmpl); + void load(bool bLoad = false); + bool isLink(HWND, POINT pt, CMStringW *url = nullptr) const; + bool isLinkChar(HWND, int idx) const; + + int getTemplate() const; + int getCopyTemplate() const; + void getFontColor(int &fontId, int &colorId) const; + + CMStringA formatRtf(const wchar_t *pwszStr = 0); + CMStringW formatString() { return TplFormatString(getTemplate(), hContact, this); } + CMStringW formatStringEx(wchar_t *sztpl); + + inline wchar_t* getWBuf() + { + load(); + return wtext; + } +}; + +class Filter +{ + uint16_t flags; + ptrW text; + +public: + enum + { + INCOMING = 0x001, + OUTGOING = 0x002, + MESSAGES = 0x004, + FILES = 0x008, + STATUS = 0x020, + OTHER = 0x040, + EVENTTEXT = 0x080, + EVENTONLY = 0x100, + }; + + __forceinline Filter(uint16_t aFlags, const wchar_t *wText) : + flags(aFlags), + text(mir_wstrdup(wText)) + { + } + + bool check(ItemData *item) const; +}; + +enum +{ + FILTER_TIME = 0x01, + FILTER_TYPE = 0x02, + FILTER_DIRECTION = 0x04, + FILTER_TEXT = 0x08, + FILTER_UNICODE = 0x10, + + FTYPE_MESSAGE = 0x01, + FTYPE_FILE = 0x02, + FTYPE_URL = 0x04, + FTYPE_STATUS = 0x08, + FTYPE_OTHER = 0x10, + FTYPE_INCOMING = 0x20, + FTYPE_OUTGOING = 0x40 +}; + +#define HIST_BLOCK_SIZE 1000 + +struct ItemBlock : public MZeroedObject +{ + ItemData data[HIST_BLOCK_SIZE]; +}; + +struct SearchResult +{ + SearchResult(MCONTACT _1, MEVENT _2, uint32_t _3) : + hContact(_1), + hEvent(_2), + ts(_3) + {} + + MCONTACT hContact; + MEVENT hEvent; + uint32_t ts; +}; + +class HistoryArray +{ + LIST strings; + OBJLIST pages; + int iLastPageCounter = 0; + MWindow hwndOwner = 0; + + ItemData& allocateItem(void); + +public: + HistoryArray(); + ~HistoryArray(); + + bool addEvent(MCONTACT hContact, MEVENT hEvent, int count); + void addChatEvent(SESSION_INFO *si, const LOGINFO *pEvent); + void addResults(const OBJLIST &pArray); + void clear(); + int find(MEVENT hEvent); + int find(int id, int dir, const Filter &filter); + int getCount() const; + void remove(int idx); + void reset() + { + clear(); + pages.insert(new ItemBlock()); + } + void setOwner(MWindow hwnd) { + hwndOwner = hwnd; + } + + ItemData* get(int id, bool bLoad = false) const; + ItemData* insert(int idx); + + __forceinline int FindNext(int id, const Filter &filter) { return find(id, +1, filter); } + __forceinline int FindPrev(int id, const Filter &filter) { return find(id, -1, filter); } +}; + +#endif // __history_array__ 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 . -*/ - -#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 &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 . +*/ + +#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 &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); +} diff --git a/plugins/NewStory/src/history_control.h b/plugins/NewStory/src/history_control.h index eb14bf6cc9..34821bd1e4 100644 --- a/plugins/NewStory/src/history_control.h +++ b/plugins/NewStory/src/history_control.h @@ -1,98 +1,98 @@ -#ifndef __history_control_h__ -#define __history_control_h__ - -#define NEWSTORYLIST_CLASS "NewstoryList" - -struct NewstoryListData : public MZeroedObject -{ - NewstoryListData(HWND); - - mir_cs m_csItems; - HistoryArray items; - - int scrollTopItem; // topmost item - int scrollTopPixel; // y coord of topmost item, this should be negative or zero - int caret; - int selStart = -1; - int cachedMaxTopItem; // the largest ID of top item to avoid empty space - int cachedMaxTopPixel; - int cachedWindowWidth = -1, cachedWindowHeight = -1; - int cachedMaxDrawnItem = -1; - int cachedScrollbarPos = -1, cachedScrollbarMax = -1; - int totalCount; - - RECT rcLastPaint; - MCONTACT m_hContact = INVALID_CONTACT_ID; - - bool bWasShift, bSortAscending, bWasAtBottom; - - HWND m_hwnd; - HWND hwndEditBox; - - CTimer redrawTimer; - CSrmmBaseDialog *pMsgDlg = nullptr; - - void OnContextMenu(int index, POINT pt); - void OnResize(int newWidth, int newHeight); - - void onTimer_Draw(CTimer *pTimer); - - void AddChatEvent(SESSION_INFO *si, const LOGINFO *lin); - void AddEvent(MCONTACT hContact, MEVENT hFirstEvent, int iCount); - void AddResults(const OBJLIST &results); - void AddSelection(int iFirst, int iLast); - bool AtBottom(void) const; - bool AtTop(void) const; - void BeginEditItem(); - void CalcBottom(); - void Clear(); - void ClearSelection(int iFirst, int iLast); - void Copy(bool bTextOnly = false); - void CopyPath(); - void CopyUrl(); - void DeleteItems(void); - void Download(int iOptions); - void EndEditItem(bool bAccept); - void EnsureVisible(int item); - void EventUp(); - void EventDown(); - void EventPageUp(); - void EventPageDown(); - int FindNext(const wchar_t *pwszText); - int FindPrev(const wchar_t *pwszText); - void FixScrollPosition(bool bForce = false); - CMStringW GatherSelected(bool bTextOnly); - ItemData* GetItem(int idx) const; - int GetItemFromPixel(int yPos); - int GetItemHeight(int index); - int GetItemHeight(ItemData *pItem); - bool HasSelection() const; - void HitTotal(int yCurr, int yTotal); - void LineUp(); - void LineDown(); - ItemData* LoadItem(int idx); - void OpenFolder(); - void PageUp(); - void PageDown(); - int PaintItem(HDC hdc, ItemData* pItem, int top, int width, bool bDraw); - void Quote(); - void RecalcScrollBar(); - void Reply(); - void ScheduleDraw(); - void ScrollBottom(); - void ScrollDown(int deltaY); - void ScrollTop(); - void ScrollUp(int deltaY); - void SetCaret(int idx, bool bEnsureVisible = true); - void SetContact(MCONTACT hContact); - void SetDialog(CSrmmBaseDialog *pDialog); - void SetPos(int pos); - void SetSelection(int iFirst, int iLast); - void ToggleBookmark(); - void ToggleSelection(int iFirst, int iLast); - void TryUp(int iCount); -}; - -void InitNewstoryControl(); - -#endif // __history_control_h__ +#ifndef __history_control_h__ +#define __history_control_h__ + +#define NEWSTORYLIST_CLASS "NewstoryList" + +struct NewstoryListData : public MZeroedObject +{ + NewstoryListData(HWND); + + mir_cs m_csItems; + HistoryArray items; + + int scrollTopItem; // topmost item + int scrollTopPixel; // y coord of topmost item, this should be negative or zero + int caret; + int selStart = -1; + int cachedMaxTopItem; // the largest ID of top item to avoid empty space + int cachedMaxTopPixel; + int cachedWindowWidth = -1, cachedWindowHeight = -1; + int cachedMaxDrawnItem = -1; + int cachedScrollbarPos = -1, cachedScrollbarMax = -1; + int totalCount; + + RECT rcLastPaint; + MCONTACT m_hContact = INVALID_CONTACT_ID; + + bool bWasShift, bSortAscending, bWasAtBottom; + + HWND m_hwnd; + HWND hwndEditBox; + + CTimer redrawTimer; + CSrmmBaseDialog *pMsgDlg = nullptr; + + void OnContextMenu(int index, POINT pt); + void OnResize(int newWidth, int newHeight); + + void onTimer_Draw(CTimer *pTimer); + + void AddChatEvent(SESSION_INFO *si, const LOGINFO *lin); + void AddEvent(MCONTACT hContact, MEVENT hFirstEvent, int iCount); + void AddResults(const OBJLIST &results); + void AddSelection(int iFirst, int iLast); + bool AtBottom(void) const; + bool AtTop(void) const; + void BeginEditItem(); + void CalcBottom(); + void Clear(); + void ClearSelection(int iFirst, int iLast); + void Copy(bool bTextOnly = false); + void CopyPath(); + void CopyUrl(); + void DeleteItems(void); + void Download(int iOptions); + void EndEditItem(bool bAccept); + void EnsureVisible(int item); + void EventUp(); + void EventDown(); + void EventPageUp(); + void EventPageDown(); + int FindNext(const wchar_t *pwszText); + int FindPrev(const wchar_t *pwszText); + void FixScrollPosition(bool bForce = false); + CMStringW GatherSelected(bool bTextOnly); + ItemData* GetItem(int idx) const; + int GetItemFromPixel(int yPos); + int GetItemHeight(int index); + int GetItemHeight(ItemData *pItem); + bool HasSelection() const; + void HitTotal(int yCurr, int yTotal); + void LineUp(); + void LineDown(); + ItemData* LoadItem(int idx); + void OpenFolder(); + void PageUp(); + void PageDown(); + int PaintItem(HDC hdc, ItemData* pItem, int top, int width, bool bDraw); + void Quote(); + void RecalcScrollBar(); + void Reply(); + void ScheduleDraw(); + void ScrollBottom(); + void ScrollDown(int deltaY); + void ScrollTop(); + void ScrollUp(int deltaY); + void SetCaret(int idx, bool bEnsureVisible = true); + void SetContact(MCONTACT hContact); + void SetDialog(CSrmmBaseDialog *pDialog); + void SetPos(int pos); + void SetSelection(int iFirst, int iLast); + void ToggleBookmark(); + void ToggleSelection(int iFirst, int iLast); + void TryUp(int iCount); +}; + +void InitNewstoryControl(); + +#endif // __history_control_h__ diff --git a/plugins/NewStory/src/history_dlg.cpp b/plugins/NewStory/src/history_dlg.cpp index 4ce49fbd20..0ace485a79 100644 --- a/plugins/NewStory/src/history_dlg.cpp +++ b/plugins/NewStory/src/history_dlg.cpp @@ -1,1223 +1,1223 @@ -/* -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 . -*/ - -#include "stdafx.h" - -static CMOption g_splitter(MODULENAME, "SplitterY", 100); - -///////////////////////////////////////////////////////////////////////////////////////// -// Main history dialog - -enum -{ - HIST_SHOW_IN = 0x001, - HIST_SHOW_OUT = 0x002, - HIST_SHOW_MSGS = 0x004, - HIST_SHOW_FILES = 0x008, - HIST_SHOW_URLS = 0x010, - HIST_SHOW_STATUS = 0x020, - HIST_SHOW_OTHER = 0x040, - HIST_AUTO_FILTER = 0x080, -}; - -enum -{ - WND_OPT_TIMETREE = 0x01, - WND_OPT_SEARCHBAR = 0x02, - WND_OPT_FILTERBAR = 0x04, - WND_OPT_BOOKMARKS = 0x08, -}; - -enum -{ - WND_SPACING = 4, - TBTN_SIZE = 25, -}; - -struct InfoBarEvents -{ - HWND hwndIco, hwndIcoIn, hwndIcoOut; - HWND hwndTxt, hwndTxtIn, hwndTxtOut; -}; - -void LayoutFilterBar(HDWP hDwp, int x, int y, int w, InfoBarEvents *ib) -{ - hDwp = DeferWindowPos(hDwp, ib->hwndIco, 0, - x, y, 16, 16, SWP_NOZORDER); - hDwp = DeferWindowPos(hDwp, ib->hwndTxt, 0, - x + 16 + WND_SPACING, y, w - 16 - WND_SPACING, 16, SWP_NOZORDER); - - hDwp = DeferWindowPos(hDwp, ib->hwndIcoIn, 0, - x + 16, y + 16 + WND_SPACING, 16, 16, SWP_NOZORDER); - hDwp = DeferWindowPos(hDwp, ib->hwndTxtIn, 0, - x + 32 + WND_SPACING, y + 16 + WND_SPACING, w - WND_SPACING - 32, 16, SWP_NOZORDER); - - hDwp = DeferWindowPos(hDwp, ib->hwndIcoOut, 0, - x + 16, y + (16 + WND_SPACING) * 2, 16, 16, SWP_NOZORDER); - DeferWindowPos(hDwp, ib->hwndTxtOut, 0, - x + 32 + WND_SPACING, y + (16 + WND_SPACING) * 2, w - WND_SPACING - 32, 16, SWP_NOZORDER); -} - -class CHistoryDlg : public CDlgBase -{ - HMENU m_hMenu; - uint16_t showFlags; - bool m_bSearchChanged = false; - MCONTACT m_hContact; - int lastYear = -1, lastMonth = -1, lastDay = -1; - HTREEITEM hLastYear = 0, hLastMonth = 0, hLastDay = 0; - bool disableTimeTreeChange = false; - bool bAppendOnly; - - // window flags - uint32_t m_dwOptions = 0; - - // toolbar buttons - struct Button - { - enum { RIGHT = 1, SPACED = 2 }; - - Button(CCtrlMButton &_1, int _2 = 0) : - hwnd(_1), - options(_2) - {} - - CCtrlMButton &hwnd; - int options; - }; - std::vector