/*
Copyright (C) 2012-24 Miranda NG team (https://miranda-ng.org)
This program is free software; you can redistribute it &/|
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 | 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"
/////////////////////////////////////////////////////////////////////////////////////////
// patterns
bool QSMainDlg::CheckPattern(CRowItem *pRow)
{
	if (m_patterns.getCount() == 0)
		return true;
	
	for (auto &p : m_patterns)
		p->res = 0;
	int i = 0;
	for (auto &it : g_plugin.m_columns) {
		if (it->bEnabled && it->bFilter && pRow->pValues[i].text != nullptr) {
			CMStringW buf(pRow->pValues[i].text);
			buf.MakeLower();
			for (auto &p : m_patterns)
				if (!p->res && buf.Find(p->str) != -1)
					p->res = true;
		}
		i++;
	}
	bool result = true;
	for (auto &p : m_patterns)
		result = result && p->res;
	return result;
}
void QSMainDlg::MakePattern(const wchar_t *pwszPattern)
{
	m_patterns.destroy();
	if (mir_wstrlen(pwszPattern) == 0)
		return;
	// m_wszPatternBuf works as a storage for patterns, we store pointers to it in m_patterns
	m_wszPatternBuf = mir_wstrdup(pwszPattern);
	CharLowerW(m_wszPatternBuf);
	
	for (wchar_t *p = m_wszPatternBuf; *p; ) {
		auto *pWord = wcspbrk(p, L" \"");
		if (pWord == nullptr) {
			m_patterns.insert(new Pattern(p));
			return;
		}
		bool isSpace = pWord[0] == ' ';
		// there's some valuable info between p and pWord
		if (pWord != p) {
			*pWord = 0;
			m_patterns.insert(new Pattern(p));
		}
		if (isSpace) {
			p = ltrimpw(pWord + 1); // skip all spaces
		}
		else {
			auto *pEnd = wcschr(++pWord, '\"');
			
			// treat the rest of line as one pattern
			if (pEnd == nullptr) {
				m_patterns.insert(new Pattern(pWord));
				return;
			}
			*pEnd = 0;
			m_patterns.insert(new Pattern(pWord));
			p = ltrimpw(pEnd + 1);
		}
	}
}
/////////////////////////////////////////////////////////////////////////////////////////
void QSMainDlg::AddColumn(int idx, ColumnItem *pCol)
{
	LV_COLUMN lvc = {};
	lvc.mask = LVCF_TEXT | LVCF_WIDTH;
	lvc.pszText = TranslateW(pCol->title);
	lvc.cx = pCol->width;
	m_grid.InsertColumn(idx, &lvc);
	HDITEM hdi;
	hdi.mask = HDI_FORMAT;
	hdi.fmt = HDF_LEFT | HDF_STRING | HDF_CHECKBOX | (pCol->bFilter ? HDF_CHECKED : 0);
	SendMessage(m_grid.GetHeader(), HDM_SETITEM, idx, LPARAM(&hdi));
}
void QSMainDlg::AddContactToList(MCONTACT hContact, CRowItem *pRow)
{
	LV_ITEM li = {};
	li.mask = LVIF_PARAM;
	li.iItem = 100000;
	li.lParam = LPARAM(pRow);
	li.iItem = m_grid.InsertItem(&li);
	li.iImage = 0;
	li.iSubItem = 0;
	for (int i = 0; i < g_plugin.m_columns.getCount(); i++) {
		auto &col = g_plugin.m_columns[i];
		if (!col.bEnabled)
			continue;
		// Client icons preprocess
		li.pszText = pRow->pValues[i].text;
		li.mask = LVIF_TEXT + col.HasImage(li.pszText);
		if (col.isAccount)
			li.iImage = Clist_GetContactIcon(hContact);
		m_grid.SetItem(&li);
		li.iSubItem++;
	}
}
void QSMainDlg::AdvancedFilter()
{
	m_grid.SetDraw(false);
	for (auto &it : m_rows) {
		bool bShow = (szFilterProto == nullptr) || !mir_strcmp(szFilterProto, it->szProto);
		if (bShow && !bShowOffline && it->status == ID_STATUS_OFFLINE)
			bShow = false;
		if (it->bPattern) {
			if (bShow) {
				if (!it->bActive)
					ProcessLine(it, false);
			}
			else {
				it->bActive = false;
				m_grid.DeleteItem(FindItem(it));
			}
		}
	}
	m_grid.SetDraw(true);
	InvalidateRect(m_grid.GetHwnd(), 0, false);
	Sort();
	UpdateSB();
}
void QSMainDlg::ChangeStatusPicture(CRowItem *pRow, MCONTACT, LPARAM lParam)
{
	int idx = FindItem(pRow);
	if (idx == -1)
		return;
	LV_ITEM li = {};
	li.iItem = idx;
	li.mask = LVIF_IMAGE;
	li.iImage = lParam;
	m_grid.SetItem(&li);
}
void QSMainDlg::CopyMultiLines()
{
	CMStringW buf;
	int i = 0;
	for (auto &it : g_plugin.m_columns) {
		if (it->bEnabled) {
			it->width = m_grid.GetColumnWidth(i);
			if (it->width >= 10)
				buf.AppendFormat(L"%s\t", it->title);
		}
		i++;
	}
	buf.Append(L"\r\n");
	int nRows = m_grid.GetItemCount();
	int nSelected = m_grid.GetSelectedCount();
	for (int j = 0; j < nRows; j++) {
		if (nSelected > 1 && !m_grid.GetItemState(j, LVIS_SELECTED))
			continue;
		auto *pRow = GetRow(j);
		
		i = 0;
		for (auto &it : g_plugin.m_columns) {
			if (it->bEnabled && it->width >= 10)
				buf.AppendFormat(L"%s\t", pRow->pValues[i].getText());
			i++;
		}
		buf.Append(L"\r\n");
	}
	Utils_ClipboardCopy(MClipUnicode(buf));
}
void QSMainDlg::DeleteByList()
{
	if (IDOK != MessageBoxW(0, TranslateT("Do you really want to delete selected contacts?"), TranslateT("Warning"), MB_OKCANCEL + MB_ICONWARNING))
		return;
	m_grid.SetDraw(false);
	for (int i = m_grid.GetItemCount() - 1; i >= 0; i--)
		if (m_grid.GetItemState(i, LVIS_SELECTED))
			db_delete_contact(GetRow(i)->hContact);
	m_grid.SetDraw(true);
}
void QSMainDlg::DeleteOneContact(MCONTACT hContact)
{
	if (ServiceExists(MS_CLIST_DELETECONTACT))
		CallService(MS_CLIST_DELETECONTACT, hContact, 0);
	else
		db_delete_contact(hContact);
}
wchar_t* QSMainDlg::DoMeta(MCONTACT hContact)
{
	for (auto &it : m_rows) {
		if (it->hContact != hContact)
			continue;
		if (it->bIsMeta) {
			if (it->wparam == 0)
				it->wparam = ++hLastMeta;
		}
		else if (it->bIsSub)
			it->lparam = FindMeta(db_mc_getMeta(hContact), it->wparam);
		if (it->wparam > 0) {
			CMStringW tmp(FORMAT, L"[%d]", int(it->wparam));
			if (it->lparam > 0)
				tmp.AppendFormat(L" %d", int(it->lparam));
			return tmp.Detach();
		}
		break;
	}
	return nullptr;
}
void QSMainDlg::DrawSB()
{
	CStatusBarItem global(0, 0);
	for (auto &it : m_sbdata) {
		global.found += it->found;
		global.liston += it->liston;
		global.online += it->online;
		global.total += it->total;
	}
	CMStringW buf(FORMAT, TranslateT("%i users found (%i) Online: %i"), global.found, m_rows.getCount(), global.online);
	
	RECT rc;
	HDC hdc = GetDC(hwndStatusBar);
	DrawTextW(hdc, buf, buf.GetLength(), &rc, DT_CALCRECT);
	ReleaseDC(hwndStatusBar, hdc);
	int all = rc.right - rc.left, i = 1;
	mir_ptr parts((int*)mir_alloc(sizeof(int) * (m_sbdata.getCount()+2)));
	parts[0] = all;
	for (auto &it : m_sbdata) {
		UNREFERENCED_PARAMETER(it);
		all += 55;
		parts[i++] = all;
	}
	parts[i] = -1;
	SendMessageW(hwndStatusBar, SB_SETPARTS, m_sbdata.getCount() + 2, LPARAM(parts.get()));
	SendMessageW(hwndStatusBar, SB_SETTEXTW, 0, LPARAM(buf.c_str()));
	i = 1;
	for (auto &it : m_sbdata) {
		HICON hIcon;
		wchar_t c, *pc;
		if (it->bAccDel) {
			c = '!';
			pc = TranslateT("deleted");
			hIcon = Skin_LoadProtoIcon(it->szProto, ID_STATUS_OFFLINE);
		}
		else if (it->bAccOff) {
			c = '?';
			pc = TranslateT("disabled");
			hIcon = Skin_LoadProtoIcon(it->szProto, ID_STATUS_OFFLINE);
		}
		else {
			c = ' ';
			pc = TranslateT("active");
			hIcon = Skin_LoadProtoIcon(it->szProto, ID_STATUS_ONLINE);
		}
		SendMessageW(hwndStatusBar, SB_SETICON, i, (LPARAM)hIcon);
		buf.Format(L"%c %d", c, it->found);
		SendMessageW(hwndStatusBar, SB_SETTEXTW, i, LPARAM(buf.c_str()));
		auto *pa = Proto_GetAccount(it->szProto);
		buf.Format(L"%s (%s): %d (%d); %s %d (%d))", pa->tszAccountName, pc, it->found, it->total, TranslateT("Online"), it->liston, it->online);
		SendMessageW(hwndStatusBar, SB_SETTIPTEXTW, i, LPARAM(buf.c_str()));
		i++;
	}
}
void QSMainDlg::FillGrid()
{
	m_grid.SetDraw(false);
	for (auto &it: m_rows)
		ProcessLine(it);
	m_grid.SetDraw(true);
	InvalidateRect(m_grid.GetHwnd(), 0, FALSE);
	Sort();
	UpdateSB();
	AdvancedFilter();
	m_grid.SetCurSel(0);
}
void QSMainDlg::FillProtoCombo()
{
	cmbProto.ResetContent();
	cmbProto.AddString(TranslateT("All"));
	for (auto &it : Accounts())
		cmbProto.AddString(it->tszAccountName, (LPARAM)it);
	cmbProto.SetCurSel(0);
}
int QSMainDlg::FindItem(CRowItem *pRow)
{
	if (pRow == nullptr)
		return -1;
	LV_FINDINFO fi = {};
	fi.flags = LVFI_PARAM;
	fi.lParam = LPARAM(pRow);
	return m_grid.FindItem(-1, &fi);
}
int QSMainDlg::FindMeta(MCONTACT hMeta, WPARAM &metaNum)
{
	for (auto &it : m_rows) {
		if (it->hContact != hMeta)
			continue;
		// new meta
		if (it->wparam == 0) {
			it->wparam = ++hLastMeta;
			it->lparam = 0;
		}
		metaNum = it->wparam;
		it->lparam++;
		return it->lparam;
	}
	return 0;
}
CRowItem* QSMainDlg::FindRow(MCONTACT hContact)
{
	for (auto &it : m_rows)
		if (it->hContact == hContact)
			return it;
	return nullptr;
}
MCONTACT QSMainDlg::GetFocusedContact()
{
	int idx = m_grid.GetSelectionMark();
	if (idx == -1)
		return -1;
	
	INT_PTR data = m_grid.GetItemData(idx);
	return (data == -1) ? -1 : ((CRowItem *)data)->hContact;
}
int QSMainDlg::GetLVSubItem(int x, int y)
{
	LV_HITTESTINFO info = {};
	info.pt.x = x;
	info.pt.y = y;
	ScreenToClient(m_grid.GetHwnd(), &info.pt);
	if (m_grid.SubItemHitTest(&info) == -1)
		return -1;
	return (info.flags & LVHT_ONITEM) ? info.iSubItem : -1;
}
void QSMainDlg::PrepareTable(bool bReset)
{
	m_grid.DeleteAllItems();
	HDITEM hdi = {};
	hdi.mask = HDI_FORMAT;
	int old = tableColumns;
	tableColumns = 0;
	LV_COLUMN lvc = {};
	lvc.mask = LVCF_TEXT | LVCF_WIDTH;
	for (auto &it : g_plugin.m_columns) {
		if (it->bEnabled)
			AddColumn(tableColumns++, it);
			
		it->SetSpecialColumns();
	}
	if (bReset)
		for (int i = old - 1; i >= tableColumns; i--)
			m_grid.DeleteColumn(i);
}
bool QSMainDlg::PrepareToFill()
{
	if (g_plugin.m_columns.getCount() == 0)
		return false;
	for (auto &it : g_plugin.m_columns)
		if (it->bEnabled)
			it->bInit = true;
	hLastMeta = 0;
	m_rows.destroy();
	for (auto &hContact : Contacts())
		m_rows.insert(new CRowItem(hContact, this));
	return m_rows.getCount() != 0;
}
void QSMainDlg::ProcessLine(CRowItem *pRow, bool test)
{
	if (pRow->bDeleted)
		return;
	if (test)
		pRow->bPattern = CheckPattern(pRow);
	if (pRow->bPattern) {
		if (!pRow->bActive) {
			if ((g_plugin.m_flags & QSO_SHOWOFFLINE) || pRow->status != ID_STATUS_OFFLINE) {
				// check for proto in combo
				if (!szFilterProto || !mir_strcmp(szFilterProto, pRow->szProto)) {
					pRow->bActive = true;
					AddContactToList(pRow->hContact, pRow);
				}
			}
		}
	}
	else if (pRow->bActive) {
		pRow->bActive = false;
		m_grid.DeleteItem(FindItem(pRow));
	}
}
void QSMainDlg::SaveColumnOrder()
{
	int idx = 0, col = 0;
	for (auto &it : g_plugin.m_columns) {
		if (it->bEnabled) {
			it->width = m_grid.GetColumnWidth(col++);
			g_plugin.SaveColumn(idx, *it);
		}
		idx++;
	}
}
void QSMainDlg::ShowContactMsgDlg(MCONTACT hContact)
{
	if (hContact) {
		Clist_ContactDoubleClicked(hContact);
		if (g_plugin.m_flags & QSO_AUTOCLOSE)
			Close();
	}
}
/////////////////////////////////////////////////////////////////////////////////////////
// contact menu
static INT_PTR ColChangeFunc(void *pThis, WPARAM hContact, LPARAM, LPARAM param)
{
	((QSMainDlg *)pThis)->ChangeCellValue(hContact, (int)param);
	return 0;
}
void QSMainDlg::ChangeCellValue(MCONTACT hContact, int col)
{
	auto &pCol = g_plugin.m_columns[col];
	auto *pRow = FindRow(hContact);
	if (pRow == nullptr)
		return;
	const char *szModule = pCol.module;
	if (szModule == nullptr)
		szModule = pRow->szProto;
	auto &pVal = pRow->pValues[col];
	CMStringW wszTitle(FORMAT, TranslateT("Editing of column %s"), pCol.title);
	ENTER_STRING es = {};
	es.szModuleName = MODULENAME;
	es.caption = TranslateT("Enter new cell value");
	es.ptszInitVal = pVal.text;
	if (!EnterString(&es))
		return;
	replaceStrW(pVal.text, es.ptszResult);
	if (pCol.datatype != QSTS_STRING)
		pVal.data = _wtoi(pVal.text);
	switch (pCol.datatype) {
	case QSTS_BYTE:
		db_set_b(hContact, szModule, pCol.setting, pVal.data);
		break;
	case QSTS_WORD:
		db_set_w(hContact, szModule, pCol.setting, pVal.data);
		break;
	case QSTS_DWORD:
	case QSTS_SIGNED:
	case QSTS_HEXNUM:
		db_set_dw(hContact, szModule, pCol.setting, pVal.data);
		break;
	case QSTS_STRING:
		db_set_ws(hContact, szModule, pCol.setting, pVal.text);
		break;
	}
	UpdateLVCell(FindItem(pRow), col, pVal.text);
}
void QSMainDlg::ShowContactMenu(MCONTACT hContact, int col)
{
	if (hContact == 0)
		return;
	HANDLE srvhandle = 0;
	bool bDoit = false;
	if (col >= 0) {
		if ((col = ListViewToColumn(col)) == -1)
			return;
		auto &pCol = g_plugin.m_columns[col];
		if (pCol.setting_type == QST_SETTING && pCol.datatype != QSTS_TIMESTAMP) {
			bDoit = true;
			srvhandle = CreateServiceFunctionObjParam("QS/Dummy", &ColChangeFunc, this, col);
			if (mnuhandle == nullptr) {
				CMenuItem mi(&g_plugin);
				SET_UID(mi, 0xD384A798, 0x5D4C, 0x48B4, 0xB3, 0xE2, 0x30, 0x04, 0x6E, 0xD6, 0xF4, 0x81);
				mi.name.a = LPGEN("Change setting through QS");
				mi.pszService = "QS/Dummy";
				mnuhandle = Menu_AddContactMenuItem(&mi);
			}
			else Menu_ModifyItem(mnuhandle, 0, INVALID_HANDLE_VALUE, 0);
		}
	}
	POINT pt;
	GetCursorPos(&pt);
	HMENU hMenu = Menu_BuildContactMenu(hContact);
	if (hMenu) {
		int iCmd = ::TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_grid.GetHwnd(), 0);
		if (iCmd) {
			if (Clist_MenuProcessCommand(iCmd, MPCF_CONTACTMENU, hContact)) {
				if (g_plugin.m_flags & QSO_AUTOCLOSE)
					CloseSrWindow();
			}
		}
		::DestroyMenu(hMenu);
	}
	if (srvhandle)
		DestroyServiceFunction(srvhandle);
	if (bDoit)
		Menu_ShowItem(mnuhandle, false);
}
/////////////////////////////////////////////////////////////////////////////////////////
// muptiple selection popup menu
static HMENU MakeContainerMenu()
{
	HMENU hMenu = CreatePopupMenu();
	for (int i = 0;; i++) {
		char setting[10];
		_itoa_s(i, setting, 10);
		ptrW wszName(db_get_wsa(0, "TAB_ContainersW", setting));
		if (wszName != nullptr)
			AppendMenuW(hMenu, MF_STRING, 300 + i, wszName);
		else
			break;
	}
	
	return hMenu;
}
void QSMainDlg::ShowMultiPopup(int cnt)
{
	HMENU hMenu = CreatePopupMenu();
	AppendMenuW(hMenu, MF_DISABLED + MF_STRING, 0, CMStringW(FORMAT, TranslateT("Selected %d contacts"), cnt));
	AppendMenuW(hMenu, MF_SEPARATOR, 0, 0);
	AppendMenuW(hMenu, MF_STRING, 101, TranslateT("&Delete"));
	AppendMenuW(hMenu, MF_STRING, 102, TranslateT("&Copy"));
	AppendMenuW(hMenu, MF_STRING, 103, TranslateT("C&onvert to Meta"));
	HMENU cntmenu = MakeContainerMenu();
	AppendMenuW(hMenu, MF_POPUP, UINT_PTR(cntmenu), TranslateT("Attach to &Tab container"));
	if (HMENU grpmenu = Clist_GroupBuildMenu(400))
		AppendMenuW(hMenu, MF_POPUP, UINT_PTR(grpmenu), TranslateT("&Move to Group"));
	POINT pt;
	GetCursorPos(&pt);
	int iRes = TrackPopupMenu(hMenu, TPM_RETURNCMD+TPM_NONOTIFY, pt.x, pt.y, 0, m_hwnd, 0);
	switch (iRes) {
	case 101:
		DeleteByList();
		break;
	case 102:
		CopyMultiLines();
		break;
	case 103:
		ConvertToMeta();
		break;
	}
	
	if (iRes >= 300 && iRes <= 399) {
		wchar_t buf[100];
		if (iRes == 300) // default container, just delete setting
			buf[0] = 0;
		else
			GetMenuStringW(cntmenu, iRes, buf, _countof(buf), MF_BYCOMMAND);
		MoveToContainer(buf);
	}
	else if (iRes >= 400 && iRes <= 499)
		MoveToGroup(Clist_GroupGetName(iRes - 400));
}
void QSMainDlg::ConvertToMeta()
{
	MCONTACT hMeta = 0;
	
	int nCount = m_grid.GetItemCount();
	for (int i = 0; i < nCount; i++) {
		if (!m_grid.GetItemState(i, LVIS_SELECTED))
			continue;
		auto *pRow = GetRow(i);
		if (MCONTACT tmp = db_mc_getMeta(pRow->hContact)) {
			if (hMeta == 0)
				hMeta = tmp;
			else if (hMeta != tmp) {
				MessageBoxW(m_hwnd, TranslateT("Some of selected contacts in different metacontacts already"), L"Quick Search", MB_ICONERROR);
				return;
			}
		}
	}
	if (hMeta != 0)
		if (IDYES != MessageBoxW(0, TranslateT("One or more contacts already belong to the same metacontact. Try to convert anyway?"), L"Quick Search", MB_YESNO + MB_ICONWARNING))
			return;
	for (int i = 0; i < nCount; i++) {
		if (!m_grid.GetItemState(i, LVIS_SELECTED))
			continue;
		auto *pRow = GetRow(i);
		if (hMeta)
			db_mc_addToMeta(pRow->hContact, hMeta);
		else
			db_mc_convertToMeta(pRow->hContact);
	}
}
void QSMainDlg::MoveToContainer(const wchar_t *pwszName)
{
	int grcol = -1;
	for (auto &it : g_plugin.m_columns) {
		if (it->isContainer) {
			if (it->bEnabled)
				grcol = g_plugin.m_columns.indexOf(&it);
			else
				it->bInit = false;
		}
	}
	int nCount = m_grid.GetItemCount();
	for (int i = 0; i < nCount; i++) {
		if (!m_grid.GetItemState(i, LVIS_SELECTED))
			continue;
		auto *pRow = GetRow(i);
		if (*pwszName == 0)
			db_unset(pRow->hContact, "Tab_SRMsg", "containerW");
		else
			db_set_ws(pRow->hContact, "Tab_SRMsg", "containerW", pwszName);
		if (grcol != -1) {
			auto &pVal = pRow->pValues[grcol];
			replaceStrW(pVal.text, (*pwszName) ? pwszName : nullptr);
			UpdateLVCell(i, grcol, pwszName);
		}
	}
}
void QSMainDlg::MoveToGroup(const wchar_t *pwszName)
{
	int grcol = -1;
	for (auto &it : g_plugin.m_columns) {
		if (it->isGroup) {
			if (it->bEnabled)
				grcol = g_plugin.m_columns.indexOf(&it);
			else
				it->bInit = false;
		}
	}
	int nCount = m_grid.GetItemCount();
	for (int i = 0; i < nCount; i++) {
		if (!m_grid.GetItemState(i, LVIS_SELECTED))
			continue;
		auto *pRow = GetRow(i);
		Clist_SetGroup(pRow->hContact, pwszName);
		if (grcol != -1) {
			auto &pVal = pRow->pValues[grcol];
			replaceStrW(pVal.text, pwszName);
			UpdateLVCell(i, grcol, pwszName);
		}
	}
}
/////////////////////////////////////////////////////////////////////////////////////////
// grid sorting
static int CALLBACK CompareItem(LPARAM l1, LPARAM l2, LPARAM type)
{
	bool typ1, typ2;
	UINT_PTR i1, i2;
	int result = 0;
	CRowItem *r1 = (CRowItem *)l1, *r2 = (CRowItem *)l2;
	if (type == StatusSort) {
		i1 = r1->status, i2 = r2->status;
		if (i1 == ID_STATUS_OFFLINE) i1 += 64;
		if (i2 == ID_STATUS_OFFLINE) i2 += 64;
		typ1 = typ2 = false;
	}
	else {
		auto &res1 = r1->pValues[type], &res2 = r2->pValues[type];
		i1 = res1.data, i2 = res2.data;
		typ1 = i1 == -1; typ2 = i1 == -1;
		if (typ1 && typ2) { // two strings
			if (res1.text == 0 && res2.text == 0)
				result = 0;
			else if (res2.text == 0)
				result = 1;
			else if (res1.text == 0)
				result = -1;
			else
				result = lstrcmpiW(res1.text, res2.text);
		}
		else if (typ1 || typ2) // string & num
			result = (typ1) ? 1 : -1;
	}
	if (!typ1 && !typ2) { // not strings
		if (i1 > i2)
			result = 1;
		else if (i1 < i2)
			result = -1;
		else
			result = 0;
	}
	if (g_plugin.m_flags & QSO_SORTASC)
		result = -result;
	return result;
}
void QSMainDlg::Sort()
{
	if (g_plugin.m_sortOrder >= tableColumns)
		g_plugin.m_sortOrder = StatusSort;
	m_grid.SortItems(&CompareItem, ListViewToColumn(g_plugin.m_sortOrder));
	if (g_plugin.m_sortOrder != StatusSort && (g_plugin.m_flags & QSO_SORTBYSTATUS))
		m_grid.SortItems(&CompareItem, StatusSort);
}
void QSMainDlg::UpdateLVCell(int item, int column, const wchar_t *pwszText)
{
	auto &pCol = g_plugin.m_columns[column];
	auto *pRow = GetRow(item);
	if (pwszText == nullptr) {
		auto &pVal = pRow->pValues[column];
		replaceStrW(pVal.text, 0);
		pVal.LoadOneItem(pRow->hContact, pCol, this);
		pwszText = pVal.text;
	}
	m_grid.SetItemText(item, ColumnToListView(column), pwszText);
	
	if (pCol.bFilter)
		ProcessLine(pRow, true);
	if (g_plugin.m_sortOrder == column)
		Sort();
}
void QSMainDlg::UpdateSB()
{
	m_sbdata.destroy();
	for (auto &it : m_rows) {
		if (it->szProto == nullptr)
			continue;
		CStatusBarItem tmp(it->szProto, it->flags);
		auto *pItem = m_sbdata.find(&tmp);
		if (pItem == nullptr)
			m_sbdata.insert(pItem = new CStatusBarItem(it->szProto, it->flags));
		pItem->total++;
		if (it->bActive)
			pItem->found++;
		if (it->status != ID_STATUS_OFFLINE) {
			pItem->online++;
			if (it->bActive)
				pItem->liston++;
		}
	}
	DrawSB();
}