/*
Copyright (c) 2005 Victor Pavlychko (nullbyte@sotline.net.ua)
Copyright (C) 2012-25 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  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 m_toolbar;
	// main controls
	int m_iSplitter;
	CCtrlBase m_histWindow;
	NewstoryListData *m_histCtrl;
	// bookmarks
	HIMAGELIST hBookmarksIcons = 0;
	std::map> m_events; // for filtered events
	// searchbar
	HWND m_hwndBtnCloseSearch;
	// statusbar
	HWND m_hwndStatus;
	// filter bar
	HWND m_hwndChkDateFrom, m_hwndChkDateTo;
	HWND m_hwndDateFrom, m_hwndDateTo;
	InfoBarEvents ibMessages, ibFiles, ibUrls, ibTotal;
	OBJLIST m_arResults;
	static int stubSortResults(const void *p1, const void *p2)
	{
		return int((*(SearchResult **)p1)->ts) - int((*(SearchResult **)p2)->ts);
	}
	void DoGlobalSearch()
	{
		ptrW wszPattern(edtSearchText.GetText());
		if (!mir_wstrlen(wszPattern))
			return;
		// clear messages array first
		m_histCtrl->Clear();
		CharLowerW(wszPattern);
		DoSearchContact(0, wszPattern);
		for (auto &hContact : Contacts())
			DoSearchContact(hContact, wszPattern);
		qsort(m_arResults.getArray(), m_arResults.getCount(), sizeof(void *), stubSortResults);
		m_histCtrl->AddResults(m_arResults);
		m_arResults.destroy();
		BuildTimeTree();
	}
	void DoSearchContact(MCONTACT hContact, const wchar_t *pwszPattern)
	{
		DB::ECPTR pCursor(DB::Events(hContact));
		while (MEVENT hDbEvent = pCursor.FetchNext()) {
			DB::EventInfo dbei(hDbEvent);
			if (!dbei)
				continue;
			ptrW pwszText(dbei.getText());
			if (!mir_wstrlen(pwszText))
				continue;
			CharLowerW(pwszText);
			if (wcsstr(pwszText, pwszPattern))
				m_arResults.insert(new SearchResult(hContact, hDbEvent, dbei.getUnixtime()));
		}
	}
	void ShowHideControls()
	{
		int cmd = (m_dwOptions & WND_OPT_FILTERBAR) ? SW_SHOW : SW_HIDE;
		ShowWindow(ibMessages.hwndIco, cmd);
		ShowWindow(ibMessages.hwndIcoIn, cmd);
		ShowWindow(ibMessages.hwndIcoOut, cmd);
		ShowWindow(ibMessages.hwndTxt, cmd);
		ShowWindow(ibMessages.hwndTxtIn, cmd);
		ShowWindow(ibMessages.hwndTxtOut, cmd);
		ShowWindow(ibFiles.hwndIco, cmd);
		ShowWindow(ibFiles.hwndIcoIn, cmd);
		ShowWindow(ibFiles.hwndIcoOut, cmd);
		ShowWindow(ibFiles.hwndTxt, cmd);
		ShowWindow(ibFiles.hwndTxtIn, cmd);
		ShowWindow(ibFiles.hwndTxtOut, cmd);
		ShowWindow(ibUrls.hwndIco, cmd);
		ShowWindow(ibUrls.hwndIcoIn, cmd);
		ShowWindow(ibUrls.hwndIcoOut, cmd);
		ShowWindow(ibUrls.hwndTxt, cmd);
		ShowWindow(ibUrls.hwndTxtIn, cmd);
		ShowWindow(ibUrls.hwndTxtOut, cmd);
		ShowWindow(ibTotal.hwndIco, cmd);
		ShowWindow(ibTotal.hwndIcoIn, cmd);
		ShowWindow(ibTotal.hwndIcoOut, cmd);
		ShowWindow(ibTotal.hwndTxt, cmd);
		ShowWindow(ibTotal.hwndTxtIn, cmd);
		ShowWindow(ibTotal.hwndTxtOut, cmd);
		ShowWindow(m_hwndDateFrom, cmd);
		ShowWindow(m_hwndDateTo, cmd);
		ShowWindow(m_hwndChkDateFrom, cmd);
		ShowWindow(m_hwndChkDateTo, cmd);
		ShowWindow(GetDlgItem(m_hwnd, IDC_IB_SEPARATOR), cmd);
		cmd = (m_dwOptions & WND_OPT_SEARCHBAR) ? SW_SHOW : SW_HIDE;
		ShowWindow(m_hwndBtnCloseSearch, cmd);
		ShowWindow(btnFindNext.GetHwnd(), cmd);
		ShowWindow(btnFindPrev.GetHwnd(), cmd);
		ShowWindow(edtSearchText.GetHwnd(), cmd);
		if (cmd)
			SetFocus(edtSearchText.GetHwnd());
		bool bShowTime = (m_dwOptions & WND_OPT_TIMETREE) != 0;
		bool bShowBookmarks = (m_dwOptions & WND_OPT_BOOKMARKS) != 0;
		m_timeTree.Show(bShowTime);
		m_bookmarks.Show(bShowBookmarks);
		splitTime.Show(bShowTime || bShowBookmarks);
	}
	void UpdateTitle()
	{
		if (m_hContact == INVALID_CONTACT_ID)
			SetWindowText(m_hwnd, TranslateT("Global history search"));
		else
			SetWindowText(m_hwnd, TplFormatString(TPL_TITLE, m_hContact, 0));
	}
	void BuildBookmarksList()
	{
		if (!(m_dwOptions & WND_OPT_BOOKMARKS))
			return;
		m_events.clear();
		m_bookmarks.DeleteAllItems();
		if (hBookmarksIcons)
			ImageList_Destroy(hBookmarksIcons);
		hBookmarksIcons = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 1, 0);
		m_bookmarks.SetImageList(hBookmarksIcons, LVSIL_SMALL);
		if (m_hContact == INVALID_CONTACT_ID) {
			auto *pDB = db_get_current();
			int iIconId = ImageList_ReplaceIcon(hBookmarksIcons, -1, g_plugin.getIcon(IDI_USERS));
			m_bookmarks.AddItem(TranslateT("All results"), iIconId, INVALID_CONTACT_ID);
			std::map protoMap;
			for (auto &cc : Contacts()) {
				bool bAdded = false;
				DB::ECPTR pCursor(pDB->EventCursor(cc, 0));
				while (MEVENT hEvent = pCursor.FetchNext()) {
					DB::EventInfo dbei(hEvent, false);
					if (dbei && dbei.flags & DBEF_BOOKMARK) {
						if (!bAdded) {
							auto *pa = Proto_GetContactAccount(cc);
							char *szProto = (pa == nullptr) ? "SomeShit" : pa->szModuleName;
							auto pProto = protoMap.find(szProto);
							if (pProto == protoMap.end()) {
								iIconId = ImageList_ReplaceIcon(hBookmarksIcons, -1, Skin_LoadProtoIcon(szProto, ID_STATUS_ONLINE));
								protoMap[szProto] = iIconId;
							}
							else iIconId = (*pProto).second;
							m_bookmarks.AddItem(Clist_GetContactDisplayName(cc, 0), iIconId, cc);
							bAdded = true;
						}
						m_events[cc].push_back(hEvent);
					}
				}
			}
		}
		else {
			ImageList_ReplaceIcon(hBookmarksIcons, -1, g_plugin.getIcon(IDI_BOOKMARK));
			int numItems = m_histCtrl->totalCount;
			for (int i = 0; i < numItems; i++) {
				auto *pItem = m_histCtrl->GetItem(i);
				if (pItem->fetch())
					if (pItem->dbe.flags & DBEF_BOOKMARK)
						m_bookmarks.AddItem(pItem->getWBuf(), 0, i);
			}
		}
	}
	void BuildTimeTree()
	{
		if (!(m_dwOptions & WND_OPT_TIMETREE))
			return;
		m_timeTree.DeleteAllItems();
		auto &pArray = m_histCtrl->items;
		int numItems = pArray.getCount();
		int PrevYear = -1, PrevMonth = -1, PrevDay = -1;
		HTREEITEM hCurYear = 0, hCurMonth = 0, hCurDay = 0;
		for (int i = 0; i < numItems; i++) {
			auto *pItem = pArray.get(i, false);
			if (!pItem->fetch())
				continue;
			if (pItem->dbe.iTimestamp == 0)
				continue;
			struct tm ts = { 0 };
			time_t timestamp = pItem->dbe.getUnixtime();
			errno_t err = localtime_s(&ts, ×tamp);  /* statically alloced, local time correction */
			if (err != 0)
				return;
			int CurYear = ts.tm_year + 1900;
			int CurMonth = ts.tm_mon + 1;
			int CurDay = ts.tm_mday;
			wchar_t buf[50];
			TVINSERTSTRUCT tvi;
			tvi.hParent = nullptr;
			tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
			if (CurYear != PrevYear) {
				_itow(CurYear, buf, 10);
				tvi.item.pszText = buf;
				tvi.item.lParam = CurYear * 100 * 100;
				hCurYear = TreeView_InsertItem(m_timeTree.GetHwnd(), &tvi);
				PrevYear = CurYear;
			}
			if (CurMonth != PrevMonth) {
				tvi.hParent = hCurYear;
				tvi.item.pszText = TranslateW(months[CurMonth - 1]);
				tvi.item.lParam = (CurYear * 100 + CurMonth) * 100;
				hCurMonth = TreeView_InsertItem(m_timeTree.GetHwnd(), &tvi);
				PrevMonth = CurMonth;
			}
			if (CurDay != PrevDay) {
				_itow(CurDay, buf, 10);
				tvi.hParent = hCurMonth;
				tvi.item.pszText = buf;
				tvi.item.lParam = (CurYear * 100 + CurMonth) * 100 + CurDay;
				hCurDay = TreeView_InsertItem(m_timeTree.GetHwnd(), &tvi);
				PrevDay = CurDay;
			}
		}
		
		disableTimeTreeChange = true;
		HTREEITEM root = m_timeTree.GetRoot();
		m_timeTree.SelectItem(root);
	}
	HTREEITEM FindSibling(HTREEITEM root, int value)
	{
		if (root) {
			for (HTREEITEM hti = root; hti; hti = m_timeTree.GetNextSibling(hti)) {
				TVITEMEX tvi;
				tvi.mask = TVIF_PARAM;
				m_timeTree.GetItem(hti, &tvi);
				if (tvi.lParam == value)
					return hti;
			}
		}
		return nullptr;
	}
	void LocateDateTime(int iTimestamp)
	{
		struct tm ts = { 0 };
		time_t timestamp = iTimestamp;
		errno_t err = localtime_s(&ts, ×tamp);  /* statically alloced, local time correction */
		if (err != 0)
			return;
		int iValue = (ts.tm_year + 1900) * 100 * 100;
		HTREEITEM hti = FindSibling(m_timeTree.GetRoot(), iValue);
		iValue += (ts.tm_mon + 1) * 100;
		hti = FindSibling(m_timeTree.GetChild(hti), iValue);
		hti = FindSibling(m_timeTree.GetChild(hti), iValue + ts.tm_mday);
		if (hti) {
			disableTimeTreeChange = true;
			m_timeTree.SelectItem(hti);
		}
	}
	CSplitter splitTime;
	CCtrlEdit edtSearchText;
	CCtrlMButton btnUserInfo, btnSendMsg, btnUserMenu, btnCopy, btnOptions, btnFilter;
	CCtrlMButton btnCalendar, btnSearch, btnExport, btnFindNext, btnFindPrev, btnDelete, btnTimeTree, btnBookmarks;
	CCtrlListView m_bookmarks;
	CCtrlTreeView m_timeTree;
public:
	CHistoryDlg(MCONTACT _hContact) :
		CDlgBase(g_plugin, IDD_HISTORY),
		m_arResults(10000),
		m_hContact(_hContact),
		m_iSplitter(g_splitter),
		m_bookmarks(this, IDC_BOOKMARKSLIST),
		m_timeTree(this, IDC_TIMETREEVIEW),
		m_histWindow(this, IDC_HISTORYCONTROL),
		splitTime(this, IDC_SPLITTERY),
		edtSearchText(this, IDC_SEARCHTEXT),
		btnCopy(this, IDC_COPY, g_plugin.getIcon(IDI_COPY), LPGEN("Copy")),
		btnExport(this, IDC_EXPORT, g_plugin.getIcon(IDI_EXPORT), LPGEN("Export...")),
		btnDelete(this, IDC_DELETE, SKINICON_OTHER_DELETE, LPGEN("Delete...")),
		btnFilter(this, IDC_FILTER, g_plugin.getIcon(IDI_FILTER), LPGEN("Filter")),
		btnSearch(this, IDC_SEARCH, g_plugin.getIcon(IDI_SEARCH), LPGEN("Search...")),
		btnOptions(this, IDC_LOGOPTIONS, g_plugin.getIcon(IDI_OPTIONS), LPGEN("Options")),
		btnSendMsg(this, IDC_MESSAGE, g_plugin.getIcon(IDI_SENDMSG), LPGEN("Send message")),
		btnCalendar(this, IDC_DATEPOPUP, g_plugin.getIcon(IDI_CALENDAR), LPGEN("Jump to date")),
		btnUserInfo(this, IDC_USERINFO, g_plugin.getIcon(IDI_USERINFO), LPGEN("User info")),
		btnUserMenu(this, IDC_USERMENU, g_plugin.getIcon(IDI_USERMENU), LPGEN("User menu")),
		btnFindNext(this, IDOK, g_plugin.getIcon(IDI_FINDNEXT), LPGEN("Find next")),
		btnFindPrev(this, IDC_FINDPREV, g_plugin.getIcon(IDI_FINDPREV), LPGEN("Find previous")),
		btnTimeTree(this, IDC_TIMETREE, g_plugin.getIcon(IDI_TIMETREE), LPGEN("Conversations")),
		btnBookmarks(this, IDC_BOOKMARKS, g_plugin.getIcon(IDI_BOOKMARK), LPGEN("Bookmarks"))
	{
		showFlags = g_plugin.getWord("showFlags", 0x7f);
		m_dwOptions = g_plugin.getDword("dwOptions");
		if (!GetDatabasePlugin("JSON")) {
			btnExport.Disable();
			btnExport.SetTooltip(LPGEN("You need to install the Import plugin to export events"));
		}
		if (m_hContact == INVALID_CONTACT_ID)
			m_dwOptions |= WND_OPT_SEARCHBAR;
		else if (m_hContact > 0) {
			m_toolbar.push_back(Button(btnUserMenu));
			m_toolbar.push_back(Button(btnUserInfo));
			m_toolbar.push_back(Button(btnSendMsg, Button::SPACED));
		}
		m_toolbar.push_back(Button(btnTimeTree));
		m_toolbar.push_back(Button(btnBookmarks));
		m_toolbar.push_back(Button(btnSearch));
		// m_toolbar.push_back(Button(btnFilter));
		m_toolbar.push_back(Button(btnCalendar, Button::SPACED));
		m_toolbar.push_back(Button(btnCopy));
		m_toolbar.push_back(Button(btnDelete));
		m_toolbar.push_back(Button(btnExport));
		m_toolbar.push_back(Button(btnOptions, Button::RIGHT));
		m_timeTree.OnSelChanged = Callback(this, &CHistoryDlg::onSelChanged_TimeTree);
		m_bookmarks.OnItemChanged = Callback(this, &CHistoryDlg::onSelChanged_Bookmarks);
		edtSearchText.OnChange = Callback(this, &CHistoryDlg::onChange_SearchText);
		splitTime.OnChange = Callback(this, &CHistoryDlg::onChange_Splitter);
		btnCopy.OnClick = Callback(this, &CHistoryDlg::onClick_Copy);
		btnExport.OnClick = Callback(this, &CHistoryDlg::onClick_Export);
		btnDelete.OnClick = Callback(this, &CHistoryDlg::onClick_Delete);
		btnFilter.OnClick = Callback(this, &CHistoryDlg::onClick_Filter);
		btnSearch.OnClick = Callback(this, &CHistoryDlg::onClick_Search);
		btnOptions.OnClick = Callback(this, &CHistoryDlg::onClick_Options);
		btnSendMsg.OnClick = Callback(this, &CHistoryDlg::onClick_Message);
		btnCalendar.OnClick = Callback(this, &CHistoryDlg::onClick_Calendar);
		btnFindPrev.OnClick = Callback(this, &CHistoryDlg::onClick_FindPrev);
		btnUserInfo.OnClick = Callback(this, &CHistoryDlg::onClick_UserInfo);
		btnUserMenu.OnClick = Callback(this, &CHistoryDlg::onClick_UserMenu);
		btnTimeTree.OnClick = Callback(this, &CHistoryDlg::onClick_TimeTree);
		btnBookmarks.OnClick = Callback(this, &CHistoryDlg::onClick_Bookmarks);
		m_hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_POPUPS));
		TranslateMenu(m_hMenu);
		HMENU hMenu = GetSubMenu(m_hMenu, 0);
		CheckMenuItem(hMenu, ID_FILTER_INCOMING, (showFlags & HIST_SHOW_IN) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hMenu, ID_FILTER_OUTGOING, (showFlags & HIST_SHOW_OUT) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hMenu, ID_FILTER_MESSAGES, (showFlags & HIST_SHOW_MSGS) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hMenu, ID_FILTER_FILES, (showFlags & HIST_SHOW_FILES) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hMenu, ID_FILTER_URLS, (showFlags & HIST_SHOW_URLS) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hMenu, ID_FILTER_STATUS, (showFlags & HIST_SHOW_STATUS) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hMenu, ID_FILTER_OTHER, (showFlags & HIST_SHOW_OTHER) ? MF_CHECKED : MF_UNCHECKED);
		CheckMenuItem(hMenu, ID_FILTER_AUTO, (showFlags & HIST_AUTO_FILTER) ? MF_CHECKED : MF_UNCHECKED);
	}
	bool OnInitDialog() override
	{
		// get handles
		m_hwndBtnCloseSearch = GetDlgItem(m_hwnd, IDC_SEARCHICON);
		m_hwndStatus = CreateWindowEx(0, STATUSCLASSNAME, NULL, WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0, m_hwnd, NULL, g_plugin.getInst(), NULL);
		SendMessage(m_hwndStatus, SB_SETMINHEIGHT, GetSystemMetrics(SM_CYSMICON), 0);
		// bookmarks
		RECT rc;
		GetClientRect(m_bookmarks.GetHwnd(), &rc);
		m_bookmarks.AddColumn(0, TranslateT("Bookmarks"), rc.right - rc.left);
		// filterbar
		btnFilter.MakePush();
		btnFilter.Hide();
		
		btnTimeTree.MakePush();
		if (m_dwOptions & WND_OPT_TIMETREE)
			btnTimeTree.Push(true);
		btnSearch.MakePush();
		if (m_dwOptions & WND_OPT_SEARCHBAR)
			btnSearch.Push(true);
		btnBookmarks.MakePush();
		if (m_dwOptions & WND_OPT_BOOKMARKS)
			btnBookmarks.Push(true);
		
		// filters
		m_hwndChkDateFrom = GetDlgItem(m_hwnd, IDC_CHK_DATE_FROM);
		m_hwndChkDateTo = GetDlgItem(m_hwnd, IDC_CHK_DATE_TO);
		m_hwndDateFrom = GetDlgItem(m_hwnd, IDC_DATE_FROM);
		m_hwndDateTo = GetDlgItem(m_hwnd, IDC_DATE_TO);
		ibMessages.hwndIco = GetDlgItem(m_hwnd, IDC_ICO_MESSAGES);
		ibMessages.hwndTxt = GetDlgItem(m_hwnd, IDC_TXT_MESSAGES);
		ibMessages.hwndIcoIn = GetDlgItem(m_hwnd, IDC_ICO_MESSAGES_IN);
		SendMessage(ibMessages.hwndIcoIn, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibMessages.hwndIcoIn, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibMessages.hwndIcoIn, BM_SETCHECK, BST_CHECKED, 0);
		ibMessages.hwndTxtIn = GetDlgItem(m_hwnd, IDC_TXT_MESSAGES_IN);
		ibMessages.hwndIcoOut = GetDlgItem(m_hwnd, IDC_ICO_MESSAGES_OUT);
		SendMessage(ibMessages.hwndIcoOut, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibMessages.hwndIcoOut, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibMessages.hwndIcoOut, BM_SETCHECK, BST_CHECKED, 0);
		ibMessages.hwndTxtOut = GetDlgItem(m_hwnd, IDC_TXT_MESSAGES_OUT);
		ibFiles.hwndIco = GetDlgItem(m_hwnd, IDC_ICO_FILES);
		ibFiles.hwndTxt = GetDlgItem(m_hwnd, IDC_TXT_FILES);
		ibFiles.hwndIcoIn = GetDlgItem(m_hwnd, IDC_ICO_FILES_IN);
		SendMessage(ibFiles.hwndIcoIn, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibFiles.hwndIcoIn, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibFiles.hwndIcoIn, BM_SETCHECK, BST_CHECKED, 0);
		ibFiles.hwndTxtIn = GetDlgItem(m_hwnd, IDC_TXT_FILES_IN);
		ibFiles.hwndIcoOut = GetDlgItem(m_hwnd, IDC_ICO_FILES_OUT);
		SendMessage(ibFiles.hwndIcoOut, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibFiles.hwndIcoOut, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibFiles.hwndIcoOut, BM_SETCHECK, BST_CHECKED, 0);
		ibFiles.hwndTxtOut = GetDlgItem(m_hwnd, IDC_TXT_FILES_OUT);
		ibUrls.hwndIco = GetDlgItem(m_hwnd, IDC_ICO_URLS);
		ibUrls.hwndTxt = GetDlgItem(m_hwnd, IDC_TXT_URLS);
		ibUrls.hwndIcoIn = GetDlgItem(m_hwnd, IDC_ICO_URLS_IN);
		SendMessage(ibUrls.hwndIcoIn, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibUrls.hwndIcoIn, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibUrls.hwndIcoIn, BM_SETCHECK, BST_CHECKED, 0);
		ibUrls.hwndTxtIn = GetDlgItem(m_hwnd, IDC_TXT_URLS_IN);
		ibUrls.hwndIcoOut = GetDlgItem(m_hwnd, IDC_ICO_URLS_OUT);
		SendMessage(ibUrls.hwndIcoOut, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibUrls.hwndIcoOut, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibUrls.hwndIcoOut, BM_SETCHECK, BST_CHECKED, 0);
		ibUrls.hwndTxtOut = GetDlgItem(m_hwnd, IDC_TXT_URLS_OUT);
		ibTotal.hwndIco = GetDlgItem(m_hwnd, IDC_ICO_TOTAL);
		ibTotal.hwndTxt = GetDlgItem(m_hwnd, IDC_TXT_TOTAL);
		ibTotal.hwndIcoIn = GetDlgItem(m_hwnd, IDC_ICO_TOTAL_IN);
		SendMessage(ibTotal.hwndIcoIn, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibTotal.hwndIcoIn, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibTotal.hwndIcoIn, BM_SETCHECK, BST_CHECKED, 0);
		ibTotal.hwndTxtIn = GetDlgItem(m_hwnd, IDC_TXT_TOTAL_IN);
		ibTotal.hwndIcoOut = GetDlgItem(m_hwnd, IDC_ICO_TOTAL_OUT);
		SendMessage(ibTotal.hwndIcoOut, BUTTONSETASFLATBTN, 0, 0);
		SendMessage(ibTotal.hwndIcoOut, BUTTONSETASPUSHBTN, 0, 0);
		SendMessage(ibTotal.hwndIcoOut, BM_SETCHECK, BST_CHECKED, 0);
		ibTotal.hwndTxtOut = GetDlgItem(m_hwnd, IDC_TXT_TOTAL_OUT);
		// Ask for layout
		PostMessage(m_hwnd, WM_SIZE, 0, 0);
		ShowHideControls();
		UpdateTitle();
		m_histCtrl = (NewstoryListData *)GetWindowLongPtr(m_histWindow.GetHwnd(), 0);
		if (m_hContact == 0 || m_hContact == INVALID_CONTACT_ID) {
			btnSendMsg.Hide();
			btnUserInfo.Hide();
			btnUserMenu.Hide();
		}
		if (m_hContact != INVALID_CONTACT_ID) {
			Utils_RestoreWindowPosition(m_hwnd, m_hContact, MODULENAME, "wnd_");
			WindowList_Add(g_hNewstoryWindows, m_hwnd, m_hContact);
			m_histCtrl->m_hContact = m_hContact;
			WindowList_Add(g_hNewstoryHistLogs, m_histCtrl->m_hwnd, m_hContact);
			m_histCtrl->AddEvent(m_hContact, 0, -1);
			BuildTimeTree();
			SetFocus(m_histWindow.GetHwnd());
		}
		else Utils_RestoreWindowPosition(m_hwnd, 0, MODULENAME, "glb_");
		OnResize();
		BuildBookmarksList();
		m_histCtrl->ScrollBottom();
		Window_SetIcon_IcoLib(m_hwnd, g_plugin.getIconHandle(IDI_NEWSTORY));
		SendMessage(GetDlgItem(m_hwnd, IDC_SEARCHICON), STM_SETICON, (WPARAM)g_plugin.getIcon(IDI_SEARCH), 0);
		SendMessage(ibMessages.hwndIco, STM_SETICON, (LPARAM)g_plugin.getIcon(IDI_SENDMSG), 0);
		SendMessage(ibMessages.hwndIcoIn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGIN));
		SendMessage(ibMessages.hwndIcoOut, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGOUT));
		SendMessage(ibFiles.hwndIco, STM_SETICON, (LPARAM)Skin_LoadIcon(SKINICON_EVENT_FILE), 0);
		SendMessage(ibFiles.hwndIcoIn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGIN));
		SendMessage(ibFiles.hwndIcoOut, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGOUT));
		SendMessage(ibUrls.hwndIco, STM_SETICON, (LPARAM)Skin_LoadIcon(SKINICON_EVENT_URL), 0);
		SendMessage(ibUrls.hwndIcoIn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGIN));
		SendMessage(ibUrls.hwndIcoOut, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGOUT));
		SendMessage(ibTotal.hwndIco, STM_SETICON, (LPARAM)g_plugin.getIcon(IDI_UNKNOWN), 0);
		SendMessage(ibTotal.hwndIcoIn, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGIN));
		SendMessage(ibTotal.hwndIcoOut, BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_MSGOUT));
		return true;
	}
	bool OnApply() override
	{
		if (m_bSearchChanged) {
			m_bSearchChanged = false;
			if (m_hContact == INVALID_CONTACT_ID)
				DoGlobalSearch();
		}
		int iOldCaret = m_histCtrl->caret;
		int res = m_histCtrl->FindNext(ptrW(edtSearchText.GetText()));
		if (res == -1)
			SetWindowTextW(m_hwndStatus, TranslateT("No more occurrences found"));
		else if (res < iOldCaret)
			SetWindowTextW(m_hwndStatus, TranslateT("Passed the end of history"));
		else
			SetWindowTextW(m_hwndStatus, L"");
		return false;
	}
	void OnResize() override
	{
		RECT rc;
		GetClientRect(m_hwnd, &rc);
		int y; // tmp vars
		int w = rc.right - rc.left;
		int h = rc.bottom - rc.top;
		HDWP hDwp = BeginDeferWindowPos(43 + (int)m_toolbar.size());
		// toolbar
		int hToolBar = TBTN_SIZE + WND_SPACING;
		int x = WND_SPACING;
		for (auto &it : m_toolbar) {
			if (it.options & Button::RIGHT)
				continue;
			hDwp = DeferWindowPos(hDwp, it.hwnd.GetHwnd(), 0, x, WND_SPACING, TBTN_SIZE, TBTN_SIZE, SWP_NOZORDER);
			x += TBTN_SIZE;
			if (it.options & Button::SPACED)
				x += 10;
		}
		x = w - WND_SPACING - TBTN_SIZE;
		for (auto &it : m_toolbar) {
			if (it.options & Button::RIGHT) {
				hDwp = DeferWindowPos(hDwp, it.hwnd.GetHwnd(), 0, x, WND_SPACING, TBTN_SIZE, TBTN_SIZE, SWP_NOZORDER);
				x -= TBTN_SIZE;
			}
		}
		// infobar
		//	hDwp = DeferWindowPos(hDwp, hwndIcoProtocol, 0,
		//		w-100+WND_SPACING, WND_SPACING,
		//		16, 16,
		//		SWP_NOZORDER);
		//	hDwp = DeferWindowPos(hDwp, hwndTxtNickname, 0,
		//		w-100+WND_SPACING*2+16, WND_SPACING,
		//		100, 16,
		//		SWP_NOZORDER);
		//	hDwp = DeferWindowPos(hDwp, hwndTxtUID, 0,
		//		w-100+WND_SPACING*2+16, WND_SPACING*2+16,
		//		100, 16,
		//		SWP_NOZORDER);
		// filter bar
		int hFilterBar = 0;
		if (m_dwOptions & WND_OPT_FILTERBAR) {
			hFilterBar = WND_SPACING + (16 + WND_SPACING) * 3;
			LayoutFilterBar(hDwp, WND_SPACING + (WND_SPACING + 75) * 0, WND_SPACING * 2 + hToolBar, 75, &ibMessages);
			LayoutFilterBar(hDwp, WND_SPACING + (WND_SPACING + 75) * 1, WND_SPACING * 2 + hToolBar, 75, &ibFiles);
			LayoutFilterBar(hDwp, WND_SPACING + (WND_SPACING + 75) * 2, WND_SPACING * 2 + hToolBar, 75, &ibUrls);
			LayoutFilterBar(hDwp, WND_SPACING + (WND_SPACING + 75) * 3, WND_SPACING * 2 + hToolBar, 75, &ibTotal);
			GetWindowRect(m_hwndChkDateFrom, &rc);
			x = rc.right - rc.left;
			GetWindowRect(m_hwndDateFrom, &rc);
			y = hToolBar + WND_SPACING + (WND_SPACING + (16 + WND_SPACING) * 3 - (rc.bottom - rc.top) * 2 - WND_SPACING) / 2;
			hDwp = DeferWindowPos(hDwp, m_hwndChkDateFrom, 0,
				w - x - (rc.right - rc.left) - WND_SPACING * 2, y,
				x, rc.bottom - rc.top,
				SWP_NOZORDER | SWP_NOACTIVATE);
			hDwp = DeferWindowPos(hDwp, m_hwndDateFrom, 0,
				w - (rc.right - rc.left) - WND_SPACING, y,
				rc.right - rc.left, rc.bottom - rc.top,
				SWP_NOZORDER | SWP_NOACTIVATE);
			hDwp = DeferWindowPos(hDwp, m_hwndChkDateTo, 0,
				w - x - (rc.right - rc.left) - WND_SPACING * 2, y + (rc.bottom - rc.top) + WND_SPACING,
				x, rc.bottom - rc.top,
				SWP_NOZORDER | SWP_NOACTIVATE);
			hDwp = DeferWindowPos(hDwp, m_hwndDateTo, 0,
				w - (rc.right - rc.left) - WND_SPACING, y + (rc.bottom - rc.top) + WND_SPACING,
				rc.right - rc.left, rc.bottom - rc.top,
				SWP_NOZORDER | SWP_NOACTIVATE);
			hDwp = DeferWindowPos(hDwp, GetDlgItem(m_hwnd, IDC_IB_SEPARATOR), 0,
				WND_SPACING, hToolBar + WND_SPACING,
				w - WND_SPACING * 2, 2,
				SWP_NOZORDER | SWP_NOACTIVATE);
		}
		// general
		GetWindowRect(m_hwndStatus, &rc);
		int hStatus = rc.bottom - rc.top;
		hDwp = DeferWindowPos(hDwp, m_hwndStatus, 0,
			0, h - hStatus,
			w, hStatus,
			SWP_NOZORDER | SWP_NOACTIVATE);
		// search bar
		int hSearch = 0;
		if (m_dwOptions & WND_OPT_SEARCHBAR) {
			GetWindowRect(edtSearchText.GetHwnd(), &rc);
			hSearch = rc.bottom - rc.top;
			hDwp = DeferWindowPos(hDwp, m_hwndBtnCloseSearch, 0,
				WND_SPACING, h - hSearch - hStatus - WND_SPACING,
				TBTN_SIZE, hSearch, SWP_NOZORDER | SWP_NOACTIVATE);
			hDwp = DeferWindowPos(hDwp, edtSearchText.GetHwnd(), 0,
				TBTN_SIZE + WND_SPACING * 2, h - hSearch - hStatus - WND_SPACING,
				w - WND_SPACING * 4 - TBTN_SIZE * 3, hSearch,
				SWP_NOZORDER | SWP_NOACTIVATE);
			hDwp = DeferWindowPos(hDwp, btnFindPrev.GetHwnd(), 0,
				w - WND_SPACING - TBTN_SIZE * 2, h - hSearch - hStatus - WND_SPACING,
				TBTN_SIZE, hSearch,
				SWP_NOZORDER | SWP_NOACTIVATE);
			hDwp = DeferWindowPos(hDwp, btnFindNext.GetHwnd(), 0,
				w - WND_SPACING - TBTN_SIZE * 1, h - hSearch - hStatus - WND_SPACING,
				TBTN_SIZE, hSearch,
				SWP_NOZORDER | SWP_NOACTIVATE);
			hSearch += WND_SPACING;
		}
		int iClientTop = WND_SPACING + hToolBar + hFilterBar;
		int iClientBottom = h - WND_SPACING - hSearch - hStatus - iClientTop;
		// time tree bar
		int hTimeTree = 0;
		bool bShowTime = (m_dwOptions & WND_OPT_TIMETREE) != 0;
		bool bShowBookmarks = (m_dwOptions & WND_OPT_BOOKMARKS) != 0;
		if (bShowTime || bShowBookmarks) {
			hTimeTree = m_iSplitter;
			hDwp = DeferWindowPos(hDwp, splitTime.GetHwnd(), 0, hTimeTree, iClientTop, WND_SPACING, iClientBottom, SWP_NOZORDER | SWP_NOACTIVATE);
			if (bShowTime && bShowBookmarks) {
				int hMiddle = iClientBottom / 2;
				hDwp = DeferWindowPos(hDwp, m_timeTree.GetHwnd(), 0, WND_SPACING, iClientTop, hTimeTree - WND_SPACING, hMiddle, SWP_NOZORDER | SWP_NOACTIVATE);
				hDwp = DeferWindowPos(hDwp, m_bookmarks.GetHwnd(), 0, WND_SPACING, iClientTop + hMiddle, hTimeTree - WND_SPACING, hMiddle, SWP_NOZORDER | SWP_NOACTIVATE);
			}
			else if (bShowTime)
				hDwp = DeferWindowPos(hDwp, m_timeTree.GetHwnd(), 0, WND_SPACING, iClientTop, hTimeTree - WND_SPACING, iClientBottom, SWP_NOZORDER | SWP_NOACTIVATE);
			else
				hDwp = DeferWindowPos(hDwp, m_bookmarks.GetHwnd(), 0, WND_SPACING, iClientTop, hTimeTree - WND_SPACING, iClientBottom, SWP_NOZORDER | SWP_NOACTIVATE);
		}
		hDwp = DeferWindowPos(hDwp, m_histWindow.GetHwnd(), 0,
			WND_SPACING + hTimeTree, iClientTop,
			w - WND_SPACING * 2 - hTimeTree, iClientBottom,
			SWP_NOZORDER | SWP_NOACTIVATE);
		EndDeferWindowPos(hDwp);
	}
	void OnDestroy() override
	{
		g_splitter = m_iSplitter;
		if (m_hContact != INVALID_CONTACT_ID)
			Utils_SaveWindowPosition(m_hwnd, m_hContact, MODULENAME, "wnd_");
		else
			Utils_SaveWindowPosition(m_hwnd, 0, MODULENAME, "glb_");
		g_plugin.setWord(0, "showFlags", showFlags);
		g_plugin.setDword(0, "dwOptions", m_dwOptions);
		Window_FreeIcon_IcoLib(m_hwnd);
		WindowList_Remove(g_hNewstoryWindows, m_hwnd);
		WindowList_Remove(g_hNewstoryHistLogs, m_histCtrl->m_hwnd);
		if (m_hwndStatus != nullptr) {
			DestroyWindow(m_hwndStatus);
			m_hwndStatus = nullptr;
		}
	}
	void onClick_Calendar(CCtrlButton *pButton)
	{
		RECT rc;
		GetWindowRect(pButton->GetHwnd(), &rc);
		CalendarTool_Show(m_hwnd, rc.left, rc.bottom);
	}
	void onClick_Copy(CCtrlButton *)
	{
		m_histCtrl->Copy();
	}
	void onClick_Delete(CCtrlButton *)
	{
		m_histCtrl->DeleteItems();
	}
	
	void onClick_TimeTree(CCtrlButton *)
	{
		if (m_dwOptions & WND_OPT_TIMETREE)
			m_dwOptions &= ~WND_OPT_TIMETREE;
		else
			m_dwOptions |= WND_OPT_TIMETREE;
		OnResize();
		ShowHideControls();
		BuildTimeTree();
	}
	void onClick_Bookmarks(CCtrlButton *)
	{
		if (m_dwOptions & WND_OPT_BOOKMARKS)
			m_dwOptions &= ~WND_OPT_BOOKMARKS;
		else
			m_dwOptions |= WND_OPT_BOOKMARKS;
		OnResize();
		ShowHideControls();
		BuildBookmarksList();
	}
	void onClick_Export(CCtrlButton *)
	{
		auto *pDriver = GetDatabasePlugin("JSON");
		if (pDriver == nullptr) {
			CMStringW wszText(TranslateT("The required plugin 'Import' isn't loaded to perform this operation."));
			if (ServiceExists(MS_PU_SHOWLIST)) {
				wszText.AppendFormat(L" %s", TranslateT("Do you want to install it using Plugin Updater?"));
				if (IDYES == MessageBoxW(m_hwnd, wszText, TranslateT("Missing plugin"), MB_YESNO | MB_ICONQUESTION))
					CallService(MS_PU_SHOWLIST);
			}
			else MessageBoxW(m_hwnd, wszText, TranslateT("Missing plugin"), MB_OK | MB_ICONERROR);
			return;
		}
		wchar_t FileName[MAX_PATH];
		VARSW tszMirDir(L"%miranda_userdata%\\NewStoryExport");
		if (db_mc_isMeta(m_hContact)) {
			CMStringW SubContactsList, MessageText;
			bool FirstTime = true;
			int subcount = db_mc_getSubCount(m_hContact);
			for (int i = 0; i < subcount; i++) {
				MCONTACT hSubContact = db_mc_getSub(m_hContact, i);
				char *subproto = Proto_GetBaseAccountName(hSubContact);
				ptrW subid(Contact::GetInfo(CNF_UNIQUEID, hSubContact, subproto));
				if (FirstTime)
					SubContactsList.Append(subid);
				else
					SubContactsList.AppendFormat(L"\r\n%s", subid.get());
				FirstTime = false;
			}
			MessageText.AppendFormat(TranslateT("It is metacontact. For export use one of this subcontacts:\r\n%s"), SubContactsList.c_str());
			MessageBox(m_hwnd, MessageText, TranslateT("Export warning"), MB_OK | MB_ICONWARNING);
			return;
		}
		OPENFILENAME ofn = { 0 };
		ofn.lStructSize = sizeof(ofn);
		CMStringW tszFilter, tszTitle, tszFileName;
		tszFilter.AppendFormat(L"%s (*.json)%c*.json%c%c", TranslateT("JSON files"), 0, 0, 0);
		tszTitle.AppendFormat(TranslateT("Export history"));
		ofn.lpstrFilter = tszFilter;
		ofn.hwndOwner = nullptr;
		ofn.lpstrTitle = tszTitle;
		ofn.lpstrFile = FileName;
		ofn.nMaxFile = MAX_PATH;
		ofn.nMaxFileTitle = MAX_PATH;
		ofn.Flags = OFN_HIDEREADONLY | OFN_SHAREAWARE | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
		ofn.lpstrInitialDir = tszMirDir;
		*FileName = '\0';
		ofn.lpstrDefExt = L"";
		if (!GetSaveFileName(&ofn))
			return;
		// create file
		CreatePathToFileW(FileName);
		if (PathFileExistsW(FileName))
			DeleteFileW(FileName);
		auto *pDB = pDriver->Export(FileName);
		pDB->BeginExport();
		pDB->ExportContact(m_hContact);
		// export events
		int iDone = 0;
		bAppendOnly = false;
		auto &arItems = m_histCtrl->items;
		int iCount = arItems.getCount();
		for (int i = 0; i < iCount; i++) {
			auto *pItem = arItems.get(i);
			if (pItem->m_bSelected) {
				DB::EventInfo dbei(pItem->dbe.getEvent());
				if (dbei)
					pDB->ExportEvent(dbei);
				iDone++;
			}
		}
		// no items selected? export whole history
		if (iDone == 0)
			for (int i = 0; i < iCount; i++) {
				auto *pItem = arItems.get(i);
				DB::EventInfo dbei(pItem->dbe.getEvent());
				if (dbei)
					pDB->ExportEvent(dbei);
			}
		// Close the file
		pDB->EndExport();
		MessageBox(m_hwnd, TranslateT("Complete"), TranslateT("History export"), MB_OK | MB_ICONINFORMATION);
	}
	void onClick_Filter(CCtrlButton *)
	{
		if (m_dwOptions & WND_OPT_FILTERBAR)
			m_dwOptions &= ~WND_OPT_FILTERBAR;
		else
			m_dwOptions |= WND_OPT_FILTERBAR;
		ShowHideControls();
		OnResize();
	}
	void onClick_FindPrev(CCtrlButton *)
	{
		int iOldCaret = m_histCtrl->caret;
		int res = m_histCtrl->FindPrev(ptrW(edtSearchText.GetText()));
		if (res == -1)
			SetWindowTextW(m_hwndStatus, TranslateT("No more occurrences found"));
		else if (res > iOldCaret)
			SetWindowTextW(m_hwndStatus, TranslateT("Passed the beginning of history"));
		else
			SetWindowTextW(m_hwndStatus, L"");
	}
	void onClick_Message(CCtrlButton *)
	{
		Clist_ContactDoubleClicked(m_hContact);
	}
	void onClick_Options(CCtrlButton *pButton)
	{
		RECT rc;
		GetWindowRect(pButton->GetHwnd(), &rc);
		switch (TrackPopupMenu(GetSubMenu(m_hMenu, 1), TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, NULL)) {
		case ID_LOGOPTIONS_OPTIONS:
			g_plugin.openOptions(L"History", L"NewStory", L"Advanced");
			break;
		case ID_LOGOPTIONS_TEMPLATES:
			g_plugin.openOptions(L"History", L"NewStory", L"Templates");
			break;
		case ID_LOGOPTIONS_EMPTYHISTORY:
			if (!CallService(MS_HISTORY_EMPTY, m_hContact, 0)) {
				m_histCtrl->Clear();
				UpdateTitle();
				BuildTimeTree();
			}
			break;
		}
		PostMessage(m_hwnd, WM_SIZE, 0, 0);
	}
	void onClick_Search(CCtrlButton *)
	{
		if (m_dwOptions & WND_OPT_SEARCHBAR)
			m_dwOptions &= ~WND_OPT_SEARCHBAR;
		else
			m_dwOptions |= WND_OPT_SEARCHBAR;
		ShowHideControls();
		OnResize();
	}
	void onClick_UserInfo(CCtrlButton *)
	{
		CallService(MS_USERINFO_SHOWDIALOG, m_hContact, 0);
	}
	void onClick_UserMenu(CCtrlButton *pButton)
	{
		RECT rc;
		GetWindowRect(pButton->GetHwnd(), &rc);
		HMENU hMenu = Menu_BuildContactMenu(m_hContact);
		TrackPopupMenu(hMenu, 0, rc.left, rc.bottom, 0, m_hwnd, NULL);
		DestroyMenu(hMenu);
	}
	void onChange_SearchText(CCtrlEdit *)
	{
		m_bSearchChanged = true;
		if (m_bInitialized)
			if (showFlags & HIST_AUTO_FILTER)
				PostMessage(m_hwnd, UM_REBUILD_LIST, 0, 0);
	}
	void onChange_Splitter(CSplitter *)
	{
		m_iSplitter = splitTime.GetPos();
	}
	INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam) override
	{
		if ((msg >= NSM_FIRST) && (msg < NSM_LAST)) {
			LPARAM result = m_histWindow.SendMsg(msg, wParam, lParam);
			SetWindowLongPtr(m_hwnd, DWLP_MSGRESULT, result);
			return result;
		}
		MSG message = { m_hwnd, msg, wParam, lParam };
		switch (Hotkey_Check(&message, MODULENAME)) {
		case HOTKEY_SEEK_FORWARD:
			btnFindNext.Click();
			break;
		case HOTKEY_SEEK_BACK:
			btnFindPrev.Click();
			break;
		case HOTKEY_SEARCH:
			btnSearch.Click();
			break;
		}
		switch (msg) {
		case WM_CHARTOITEM:
			if (!((GetKeyState(VK_CONTROL) & 0x80) || (GetKeyState(VK_MENU) & 0x80))) {
				wchar_t s[] = { LOWORD(wParam), 0 };
				SetWindowText(GetDlgItem(m_hwnd, IDC_SEARCHTEXT), s);
				SendMessage(GetDlgItem(m_hwnd, IDC_SEARCHTEXT), EM_SETSEL, 1, 1);
				SetFocus(GetDlgItem(m_hwnd, IDC_SEARCHTEXT));
			}
			return -1;
		case WM_COMMAND:
			if (Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, m_hContact))
				return TRUE;
			break;
			/*
			GetWindowRect(GetDlgItem(m_hwnd, LOWORD(wParam)), &rc);
//					uint32_t itemID = 0;
			bool doFilter = true;
			switch (TrackPopupMenu(GetSubMenu(hMenu, 1), TPM_RETURNCMD, rc.left, rc.bottom, 0, m_hwnd, NULL))
			{
				case ID_FILTER_INCOMING:
				{
					showFlags = toggleBit(showFlags, HIST_SHOW_IN);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_INCOMING,
						showFlags&HIST_SHOW_IN ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				case ID_FILTER_OUTGOING:
				{
					showFlags = toggleBit(showFlags, HIST_SHOW_OUT);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_OUTGOING,
						showFlags&HIST_SHOW_OUT ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				case ID_FILTER_MESSAGES:
				{
					showFlags = toggleBit(showFlags, HIST_SHOW_MSGS);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_MESSAGES,
						showFlags&HIST_SHOW_MSGS ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				case ID_FILTER_FILES:
				{
					showFlags = toggleBit(showFlags, HIST_SHOW_FILES);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_FILES,
						showFlags&HIST_SHOW_FILES ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				case ID_FILTER_URLS:
				{
					showFlags = toggleBit(showFlags, HIST_SHOW_URLS);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_URLS,
						showFlags&HIST_SHOW_URLS ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				case ID_FILTER_STATUS:
				{
					showFlags = toggleBit(showFlags, HIST_SHOW_STATUS);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_STATUS,
						showFlags&HIST_SHOW_STATUS ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				case ID_FILTER_OTHER:
				{
					showFlags = toggleBit(showFlags, HIST_SHOW_OTHER);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_OTHER,
						showFlags&HIST_SHOW_OTHER ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				case ID_FILTER_AUTO:
				{
					showFlags = toggleBit(showFlags, HIST_AUTO_FILTER);
					CheckMenuItem(GetSubMenu(hMenu, 1), ID_FILTER_AUTO,
						showFlags&HIST_AUTO_FILTER ? MF_CHECKED : MF_UNCHECKED);
					break;
				}
				default:
				{
					doFilter = false;
					break;
				}
			}
			if (doFilter)
				PostMessage(m_hwnd, UM_REBUILD_LIST, 0, 0);
			break;*/
		case UM_BOOKMARKS:
			BuildBookmarksList();
			break;
		case WM_USER + 0x600:
			if (wParam)
				m_histWindow.SendMsg(NSM_SEEKTIME, wParam, 0);
			break;
		
		case UM_LOCATETIME:
			if (m_dwOptions & WND_OPT_TIMETREE)
				if (auto *pItem = m_histCtrl->GetItem(wParam))
					LocateDateTime(pItem->dbe.getUnixtime());
			break;
		case UM_UPDATE_WINDOW:
			UpdateTitle();
			BuildTimeTree();
			break;
		}
		return CDlgBase::DlgProc(msg, wParam, lParam);
	}
	void onSelChanged_Bookmarks(CCtrlListView::TEventInfo *ev)
	{
		int idx = m_bookmarks.GetItemData(ev->nmlv->iItem);
		if (m_hContact == INVALID_CONTACT_ID) {
			m_histCtrl->Clear();
			if (idx == INVALID_CONTACT_ID) {
				for (auto &cc : m_events)
					for (auto &hEvent : cc.second)
						m_histCtrl->AddEvent(cc.first, hEvent, 1);
			}
			else for (auto &hEvent : m_events[idx])
				m_histCtrl->AddEvent(idx, hEvent, 1);
		}
		else m_histCtrl->SetPos(idx);
	}
	void onSelChanged_TimeTree(CCtrlTreeView::TEventInfo *)
	{
		if (disableTimeTreeChange) {
			disableTimeTreeChange = false;
			return;
		}
		HTREEITEM hti1 = m_timeTree.GetSelection();
		TVITEMEX tvi = {};
		tvi.hItem = hti1;
		tvi.mask = TVIF_HANDLE | TVIF_PARAM;
		m_timeTree.GetItem(&tvi);
		struct tm tm_sel = {};
		tm_sel.tm_isdst = 1;
		tm_sel.tm_mday = tvi.lParam % 100; tvi.lParam /= 100;
		tm_sel.tm_mon = tvi.lParam % 100;
		if (tm_sel.tm_mon)
			tm_sel.tm_mon--;
		tm_sel.tm_year = tvi.lParam / 100 - 1900;
		PostMessage(m_hwnd, WM_USER + 0x600, mktime(&tm_sel), 0);
	}
};
/////////////////////////////////////////////////////////////////////////////////////////
// Services
INT_PTR svcShowNewstory(WPARAM hContact, LPARAM)
{
	HWND hwnd = (HWND)WindowList_Find(g_hNewstoryWindows, hContact);
	if (!hwnd) {
		auto *pDlg = new CHistoryDlg(hContact);
		pDlg->Show();
		hwnd = pDlg->GetHwnd();
	}
	SetForegroundWindow(hwnd);
	SetFocus(hwnd);
	return 0;
}
INT_PTR svcGlobalSearch(WPARAM, LPARAM)
{
	return svcShowNewstory(INVALID_CONTACT_ID, 0);
}