summaryrefslogtreecommitdiff
path: root/plugins/QuickSearch
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2021-05-13 11:45:07 +0300
committerGeorge Hazan <ghazan@miranda.im>2021-05-13 11:45:07 +0300
commitb9abdafff215858880890ec5aaacd5ea9bb880f1 (patch)
treedfa5b0494b1698cbef370e0a35862e7037ea90d7 /plugins/QuickSearch
parent1aecfbf8295912ccfc26bdbd9d745aed96966e66 (diff)
welcome back to C++, QuickSearch
Diffstat (limited to 'plugins/QuickSearch')
-rw-r--r--plugins/QuickSearch/quicksearch.vcxproj47
-rw-r--r--plugins/QuickSearch/quicksearch.vcxproj.filters49
-rw-r--r--plugins/QuickSearch/res/default.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/delete.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/down.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/female.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/male.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/new.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/qs.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/reload.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/resource.rc182
-rw-r--r--plugins/QuickSearch/res/up.icobin0 -> 1150 bytes
-rw-r--r--plugins/QuickSearch/res/version.rc9
-rw-r--r--plugins/QuickSearch/src/frame.cpp181
-rw-r--r--plugins/QuickSearch/src/main.cpp190
-rw-r--r--plugins/QuickSearch/src/options.cpp519
-rw-r--r--plugins/QuickSearch/src/resource.h67
-rw-r--r--plugins/QuickSearch/src/stdafx.cxx18
-rw-r--r--plugins/QuickSearch/src/stdafx.h417
-rw-r--r--plugins/QuickSearch/src/utils.cpp554
-rw-r--r--plugins/QuickSearch/src/version.h13
-rw-r--r--plugins/QuickSearch/src/window.cpp777
-rw-r--r--plugins/QuickSearch/src/window_misc.cpp809
-rw-r--r--plugins/QuickSearch/src/window_row.cpp222
24 files changed, 4054 insertions, 0 deletions
diff --git a/plugins/QuickSearch/quicksearch.vcxproj b/plugins/QuickSearch/quicksearch.vcxproj
new file mode 100644
index 0000000000..c61516a7d4
--- /dev/null
+++ b/plugins/QuickSearch/quicksearch.vcxproj
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectName>QuickSearch</ProjectName>
+ <ProjectGuid>{3C3EAFC2-01FB-4BA8-8E47-42E0969C0D75}</ProjectGuid>
+ </PropertyGroup>
+ <ImportGroup Label="PropertySheets">
+ <Import Project="$(ProjectDir)..\..\build\vc.common\plugin.props" />
+ </ImportGroup>
+ <ItemGroup>
+ <ClCompile Include="src\frame.cpp" />
+ <ClCompile Include="src\options.cpp" />
+ <ClCompile Include="src\main.cpp" />
+ <ClCompile Include="src\stdafx.cxx">
+ <PrecompiledHeader>Create</PrecompiledHeader>
+ </ClCompile>
+ <ClCompile Include="src\utils.cpp" />
+ <ClCompile Include="src\window.cpp" />
+ <ClCompile Include="src\window_misc.cpp" />
+ <ClCompile Include="src\window_row.cpp" />
+ <ClInclude Include="src\resource.h" />
+ <ClInclude Include="src\stdafx.h" />
+ <ClInclude Include="src\version.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="res\resource.rc" />
+ <ResourceCompile Include="res\version.rc" />
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/plugins/QuickSearch/quicksearch.vcxproj.filters b/plugins/QuickSearch/quicksearch.vcxproj.filters
new file mode 100644
index 0000000000..d03459b649
--- /dev/null
+++ b/plugins/QuickSearch/quicksearch.vcxproj.filters
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" />
+ <ItemGroup>
+ <ClCompile Include="src\options.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\stdafx.cxx">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\frame.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\window.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\utils.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\window_misc.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="src\window_row.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="src\resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\stdafx.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="src\version.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="res\version.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ <ResourceCompile Include="res\resource.rc">
+ <Filter>Resource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/plugins/QuickSearch/res/default.ico b/plugins/QuickSearch/res/default.ico
new file mode 100644
index 0000000000..59707dab07
--- /dev/null
+++ b/plugins/QuickSearch/res/default.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/delete.ico b/plugins/QuickSearch/res/delete.ico
new file mode 100644
index 0000000000..c2ab44c6eb
--- /dev/null
+++ b/plugins/QuickSearch/res/delete.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/down.ico b/plugins/QuickSearch/res/down.ico
new file mode 100644
index 0000000000..79ec3929df
--- /dev/null
+++ b/plugins/QuickSearch/res/down.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/female.ico b/plugins/QuickSearch/res/female.ico
new file mode 100644
index 0000000000..581ff41d3b
--- /dev/null
+++ b/plugins/QuickSearch/res/female.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/male.ico b/plugins/QuickSearch/res/male.ico
new file mode 100644
index 0000000000..b638465f43
--- /dev/null
+++ b/plugins/QuickSearch/res/male.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/new.ico b/plugins/QuickSearch/res/new.ico
new file mode 100644
index 0000000000..ece3037034
--- /dev/null
+++ b/plugins/QuickSearch/res/new.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/qs.ico b/plugins/QuickSearch/res/qs.ico
new file mode 100644
index 0000000000..f8c5526cfb
--- /dev/null
+++ b/plugins/QuickSearch/res/qs.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/reload.ico b/plugins/QuickSearch/res/reload.ico
new file mode 100644
index 0000000000..feadf04bf2
--- /dev/null
+++ b/plugins/QuickSearch/res/reload.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/resource.rc b/plugins/QuickSearch/res/resource.rc
new file mode 100644
index 0000000000..e1de82739e
--- /dev/null
+++ b/plugins/QuickSearch/res/resource.rc
@@ -0,0 +1,182 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "..\src\resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// Neutral resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NEU)
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+#pragma code_page(1251)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_FRAME DIALOGEX 0, 0, 114, 16
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "MS Shell Dlg", 0, 0, 0x1
+BEGIN
+ PUSHBUTTON "*",IDC_FRAME_OPEN,2,2,12,12,BS_ICON
+ EDITTEXT IDC_FRAME_EDIT,16,2,96,12
+END
+
+IDD_OPTIONS DIALOGEX 0, 0, 314, 250
+STYLE DS_SETFONT | WS_CHILD
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "Ms Shell Dlg", 0, 0, 0x1
+BEGIN
+ CONTROL "Reload",IDC_RELOAD,"MButtonClass",WS_TABSTOP,2,148,16,16,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "New",IDC_NEW,"MButtonClass",WS_TABSTOP,32,148,16,16,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "Up",IDC_UP,"MButtonClass",WS_TABSTOP,62,148,16,16,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "Down",IDC_DN,"MButtonClass",WS_TABSTOP,80,148,16,16,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "Delete",IDC_DELETE,"MButtonClass",WS_TABSTOP,112,148,16,16,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "Default",IDC_DEFAULT,"MButtonClass",WS_TABSTOP,142,148,16,16,WS_EX_NOACTIVATE | 0x10000000L
+ CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,0,2,158,144
+ PUSHBUTTON ">>",IDC_B_RESIZE,160,2,10,246,BS_FLAT
+ CTEXT "Settings",IDC_S_COLSETTING,172,2,139,12,SS_CENTERIMAGE
+ CONTROL "",IDC_S_LINE,"Static",SS_ETCHEDHORZ,172,14,139,2
+ LTEXT "Title:",IDC_S_TITLE,174,16,137,12,SS_CENTERIMAGE
+ EDITTEXT IDC_E_TITLE,172,28,139,14,ES_AUTOHSCROLL
+ LTEXT "Type:",IDC_S_VARTYPE,174,44,137,12,SS_CENTERIMAGE
+ COMBOBOX IDC_C_VARTYPE,172,56,139,80,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Data type:",IDC_S_DATATYPE,174,72,137,12,SS_CENTERIMAGE
+ COMBOBOX IDC_C_DATATYPE,172,84,139,80,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP
+ LTEXT "Module:",IDC_S_MODULE,174,104,137,12,SS_CENTERIMAGE
+ EDITTEXT IDC_E_MODULE,172,116,139,14,ES_AUTOHSCROLL
+ LTEXT "Setting:",IDC_S_SETTING,174,132,137,12,SS_CENTERIMAGE
+ EDITTEXT IDC_E_SETTING,172,144,139,14,ES_AUTOHSCROLL
+ LTEXT "InfoType:",IDC_S_CNFTYPE,174,72,137,12,SS_CENTERIMAGE
+ COMBOBOX IDC_C_CNFTYPE,172,84,139,80,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP
+ EDITTEXT IDC_E_SCRIPT,172,72,139,14,ES_AUTOHSCROLL
+ COMBOBOX IDC_C_OTHER,172,72,139,150,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP
+ PUSHBUTTON "Save",IDC_SETITEM,259,234,52,14
+ CONTROL "Tool Window Style",IDC_CH_USETOOLSTYLE,"Button",BS_AUTOCHECKBOX | BS_FLAT | WS_TABSTOP,3,176,152,12
+ CONTROL "Draw Grid",IDC_CH_DRAWGRID,"Button",BS_AUTOCHECKBOX | BS_FLAT | WS_TABSTOP,3,188,152,12
+ CONTROL "Save search pattern",IDC_CH_SAVEPATTERN,"Button",BS_AUTOCHECKBOX | BS_FLAT | WS_TABSTOP,3,200,152,12
+ CONTROL "Auto Close mode",IDC_CH_AUTOCLOSE,"Button",BS_AUTOCHECKBOX | BS_FLAT | WS_TABSTOP,3,212,152,12
+ CONTROL "Sort by Status",IDC_CH_SORTSTATUS,"Button",BS_AUTOCHECKBOX | BS_FLAT | WS_TABSTOP,3,224,152,12
+ CONTROL "Show Client Icons",IDC_CH_CLIENTICONS,"Button",BS_AUTOCHECKBOX | BS_FLAT | WS_TABSTOP,3,236,152,12
+ GROUPBOX "Additional Options",IDC_CH_GROUP,0,168,158,82
+END
+
+IDD_MAIN DIALOGEX 40, 40, 520, 240
+STYLE DS_SETFONT | DS_FIXEDSYS | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
+EXSTYLE WS_EX_CONTROLPARENT
+FONT 8, "Ms Shell Dlg", 0, 0, 0x1
+BEGIN
+ EDITTEXT IDC_E_SEARCHTEXT,180,8,276,14
+ PUSHBUTTON "Close",IDCANCEL,462,0,52,14
+ PUSHBUTTON "Refresh",IDC_REFRESH,462,16,52,14
+ CONTROL "Show Offline contacts",IDC_CH_SHOWOFFLINE,"Button",BS_AUTOCHECKBOX | BS_VCENTER | BS_MULTILINE | BS_FLAT | WS_TABSTOP,2,0,96,20
+ CONTROL "Colorize",IDC_CH_COLORIZE,"Button",BS_AUTOCHECKBOX | BS_VCENTER | BS_MULTILINE | BS_FLAT | WS_TABSTOP,2,20,96,10
+ COMBOBOX IDC_CB_PROTOCOLS,100,8,76,80,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP
+ CONTROL "",IDC_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOLABELWRAP | WS_BORDER | WS_TABSTOP,2,32,516,194
+ CONTROL "",IDC_STATUSBAR,"msctls_statusbar32",0x903,0,226,520,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_DEFAULT ICON "default.ico"
+
+IDI_DELETE ICON "delete.ico"
+
+IDI_DOWN ICON "down.ico"
+
+IDI_FEMALE ICON "female.ico"
+
+IDI_MALE ICON "male.ico"
+
+IDI_NEW ICON "new.ico"
+
+IDI_QS ICON "qs.ico"
+
+IDI_RELOAD ICON "reload.ico"
+
+IDI_UP ICON "up.ico"
+
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "..\\src\\resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_OPTIONS, DIALOG
+ BEGIN
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// AFX_DIALOG_LAYOUT
+//
+
+IDD_OPTIONS AFX_DIALOG_LAYOUT
+BEGIN
+ 0
+END
+
+#endif // Neutral resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/plugins/QuickSearch/res/up.ico b/plugins/QuickSearch/res/up.ico
new file mode 100644
index 0000000000..6efc4d7257
--- /dev/null
+++ b/plugins/QuickSearch/res/up.ico
Binary files differ
diff --git a/plugins/QuickSearch/res/version.rc b/plugins/QuickSearch/res/version.rc
new file mode 100644
index 0000000000..5a5ddd63ed
--- /dev/null
+++ b/plugins/QuickSearch/res/version.rc
@@ -0,0 +1,9 @@
+// Microsoft Visual C++ generated resource script.
+//
+#ifdef APSTUDIO_INVOKED
+#error this file is not editable by Microsoft Visual C++
+#endif //APSTUDIO_INVOKED
+
+#include "..\src\version.h"
+
+#include "..\..\build\Version.rc"
diff --git a/plugins/QuickSearch/src/frame.cpp b/plugins/QuickSearch/src/frame.cpp
new file mode 100644
index 0000000000..2674ebcdb2
--- /dev/null
+++ b/plugins/QuickSearch/src/frame.cpp
@@ -0,0 +1,181 @@
+/*
+Copyright (C) 2012-21 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+#define colorName "Frame background"
+
+static HWND hwndFrame = nullptr;
+static int frameId = -1;
+static WNDPROC OldEditProc;
+static CMStringW wszPattern;
+static HBRUSH hBrush;
+static COLORREF frm_bkg;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// frame window procedure
+
+static INT_PTR CALLBACK NewEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_CHAR:
+ if (wParam == 27) { // Escape
+ SendMessage(hwnd, WM_SETTEXT, 0, 0);
+ return 0;
+ }
+ if (wParam == 13) { // Enter
+ CallService(QS_SHOWSERVICE, WPARAM(wszPattern.c_str()), 0);
+ return 0;
+ }
+ }
+ return CallWindowProc(OldEditProc, hwnd, msg, wParam, lParam);
+}
+
+static int Resizer(HWND, LPARAM, UTILRESIZECONTROL *urc)
+{
+ switch (urc->wId) {
+ case IDC_FRAME_OPEN: return RD_ANCHORX_LEFT | RD_ANCHORY_CENTRE;
+ case IDC_FRAME_EDIT: return RD_ANCHORX_WIDTH | RD_ANCHORY_CENTRE;
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK FrameWndProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ RECT rc;
+ HWND hwndTooltip;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ OldEditProc = (WNDPROC)SetWindowLongPtrW(GetDlgItem(hwndDlg, IDC_FRAME_EDIT), GWLP_WNDPROC, LPARAM(&NewEditProc));
+
+ hwndTooltip = CreateWindowW(TOOLTIPS_CLASS, nullptr, TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndDlg, 0, g_plugin.getInst(), 0);
+ {
+ TOOLINFOW ti = {};
+ ti.cbSize = sizeof(ti);
+ ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS;
+ ti.hwnd = hwndDlg;
+ ti.hinst = g_plugin.getInst();
+ ti.uId = (UINT_PTR)GetDlgItem(hwndDlg, IDC_FRAME_OPEN);
+ ti.lpszText = TranslateT("Open QS window");
+ SendMessageW(hwndTooltip, TTM_ADDTOOLW, 0, LPARAM(&ti));
+ }
+ SendMessage(GetDlgItem(hwndDlg, IDC_FRAME_OPEN), BM_SETIMAGE, IMAGE_ICON, (LPARAM)g_plugin.getIcon(IDI_QS));
+ return TRUE;
+
+ case WM_DESTROY:
+ DeleteObject(hBrush);
+ hBrush = nullptr;
+ break;
+
+ case WM_SIZE:
+ Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), MAKEINTRESOURCEA(IDD_FRAME), Resizer);
+ break;
+
+ case WM_ERASEBKGND:
+ GetClientRect(hwndDlg, &rc);
+ FillRect(HDC(wParam), &rc, hBrush);
+ return TRUE;
+
+ case WM_COMMAND:
+ if (HIWORD(wParam) == EN_CHANGE) {
+ wchar_t str[100];
+ int length = GetDlgItemTextW(hwndDlg, IDC_FRAME_EDIT, str, _countof(str));
+ if (length > 0) {
+ CharLowerW(str);
+ wszPattern = str;
+ }
+ else wszPattern.Empty();
+ }
+ else if (wParam == IDC_FRAME_OPEN) {
+ CallService(QS_SHOWSERVICE, WPARAM(wszPattern.c_str()), 0);
+ }
+ break;
+ }
+
+ return DefWindowProc(hwndDlg, msg, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int OnChangeColors(WPARAM, LPARAM)
+{
+ frm_bkg = Colour_Get("QuickSearch", colorName);
+
+ DeleteObject(hBrush);
+ hBrush = CreateSolidBrush(frm_bkg);
+ return 0;
+}
+
+void CreateFrame(HWND hwndParent)
+{
+ if (!ServiceExists(MS_CLIST_FRAMES_ADDFRAME))
+ return;
+
+ if (hwndParent)
+ hwndParent = g_clistApi.hwndContactList;
+
+ if (hwndFrame == nullptr)
+ hwndFrame = CreateDialogW(g_plugin.getInst(), MAKEINTRESOURCE(IDD_FRAME), hwndParent, FrameWndProc);
+
+ if (hwndFrame == nullptr)
+ return;
+
+ RECT tr;
+ GetWindowRect(hwndFrame, &tr);
+
+ CLISTFrame Frame = { sizeof(Frame) };
+ Frame.hWnd = hwndFrame;
+ Frame.hIcon = g_plugin.getIcon(IDI_QS);
+ Frame.align = alTop;
+ Frame.height = tr.bottom - tr.top + 2;
+ Frame.Flags = F_NOBORDER | F_UNICODE;
+ Frame.szName.w = TranslateT("Quick search");
+
+ frameId = CallService(MS_CLIST_FRAMES_ADDFRAME, WPARAM(&Frame), LPARAM(&g_plugin));
+ if (frameId <= 0)
+ return;
+
+ CallService(MS_CLIST_FRAMES_UPDATEFRAME, frameId, FU_FMPOS);
+
+ HWND wnd = g_clistApi.hwndContactList;
+ DWORD tmp = SendMessage(wnd, CLM_GETEXSTYLE, 0, 0);
+ SendMessage(wnd, CLM_SETEXSTYLE, tmp | CLS_EX_SHOWSELALWAYS, 0);
+
+ ColourID cid;
+ strncpy_s(cid.group, MODULENAME, _TRUNCATE);
+ strncpy_s(cid.dbSettingsGroup, MODULENAME, _TRUNCATE);
+ strncpy_s(cid.name, colorName, _TRUNCATE);
+ strncpy_s(cid.setting, "frame_back", _TRUNCATE);
+ cid.defcolour = GetSysColor(COLOR_3DFACE);
+ cid.order = 0;
+ g_plugin.addColor(&cid);
+
+ HookEvent(ME_COLOUR_RELOAD, &OnChangeColors);
+ OnChangeColors(0,0);
+}
+
+void DestroyFrame()
+{
+ if (frameId >= 0) {
+ CallService(MS_CLIST_FRAMES_REMOVEFRAME, frameId, 0);
+ frameId = -1;
+ }
+
+ DestroyWindow(hwndFrame);
+ hwndFrame = nullptr;
+}
diff --git a/plugins/QuickSearch/src/main.cpp b/plugins/QuickSearch/src/main.cpp
new file mode 100644
index 0000000000..fef90535c5
--- /dev/null
+++ b/plugins/QuickSearch/src/main.cpp
@@ -0,0 +1,190 @@
+/*
+Copyright (C) 2012-21 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+CMPlugin g_plugin;
+
+HANDLE hTTBButton;
+
+bool g_bVarsInstalled, g_bTipperInstalled, g_bFingerInstalled;
+
+int OnOptInit(WPARAM, LPARAM);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+PLUGININFOEX pluginInfoEx = {
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ // {49BD9F2A-3111-4EB9-87E3-71E69CD97F7C}
+ {0x49bd9f2a, 0x3111, 0x4eb9, {0x87, 0xe3, 0x71, 0xe6, 0x9c, 0xd9, 0x7f, 0x7c}}
+};
+
+CMPlugin::CMPlugin() :
+ PLUGIN<CMPlugin>(MODULENAME, pluginInfoEx),
+ m_columns(1)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int OnTTBLoaded(WPARAM, LPARAM)
+{
+ TTBButton ttb = {};
+ ttb.dwFlags = TTBBF_VISIBLE;
+ ttb.pszService = QS_SHOWSERVICE;
+ ttb.hIconHandleDn = ttb.hIconHandleUp = g_plugin.getIconHandle(IDI_QS);
+ ttb.name = MODULENAME;
+ ttb.pszTooltipUp = ttb.pszTooltipDn = LPGEN("Quick Search");
+ hTTBButton = g_plugin.addTTB(&ttb);
+ return 0;
+}
+
+static INT_PTR OpenSearchWindow(WPARAM wParam, LPARAM)
+{
+ OpenSrWindow((wchar_t *)wParam);
+ return 0;
+}
+
+static int OnCheckPlugins(WPARAM, LPARAM)
+{
+ g_bVarsInstalled = ServiceExists(MS_VARS_FORMATSTRING);
+ g_bTipperInstalled = ServiceExists(MS_TIPPER_SHOWTIPW);
+ g_bFingerInstalled = ServiceExists(MS_FP_GETCLIENTICONW);
+
+ return 0;
+}
+
+static int OnModulesLoaded(WPARAM, LPARAM)
+{
+ HookEvent(ME_TTB_MODULELOADED, OnTTBLoaded);
+
+ CreateServiceFunction(QS_SHOWSERVICE, OpenSearchWindow);
+
+ // add menu item
+ CMenuItem mi(&g_plugin);
+ SET_UID(mi, 0x98C2A92A, 0xD93D, 0x43E8, 0x91, 0xC3, 0x3B, 0xB6, 0xBE, 0x43, 0x44, 0xF0);
+ mi.name.a = "Quick Search";
+ mi.position = 500050000;
+ mi.pszService = QS_SHOWSERVICE;
+ mi.hIcolibItem = g_plugin.getIconHandle(IDI_QS);
+ Menu_AddMainMenuItem(&mi);
+
+ // register hotkey
+ HOTKEYDESC hkd = {};
+ hkd.pszName = "QS_Global";
+ hkd.szDescription.a = "QuickSearch window hotkey";
+ hkd.szSection.a = "QuickSearch";
+ hkd.pszService = QS_SHOWSERVICE;
+ hkd.DefHotKey = HOTKEYCODE(HOTKEYF_ALT, VK_F3);
+ g_plugin.addHotkey(&hkd);
+
+ CreateFrame(0);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static IconItem iconList[] =
+{
+ { "Quick Search", "QS", IDI_QS },
+ { "New Column", "New", IDI_NEW },
+ { "Column Up", "Up", IDI_UP },
+ { "Column Down", "Down", IDI_DOWN },
+ { "Delete Column", "Delete", IDI_DELETE },
+ { "Default", "Default", IDI_DEFAULT},
+ { "Reload", "Reload", IDI_RELOAD },
+ { "Male", "Male", IDI_MALE },
+ { "Female", "Female", IDI_FEMALE },
+};
+
+struct
+{
+ COLORREF defValue;
+ const char *szSetting, *szDescr;
+}
+static sttColors[color_max] = {
+ { 0x00FFFFFF, "back_norm", LPGEN("Normal background") },
+ { 0x00000000, "fore_norm", LPGEN("Normal foreground") },
+ { 0x00EBE6DE, "back_odd" , LPGEN("Odd background") },
+ { 0x00000000, "fore_odd" , LPGEN("Odd foreground") },
+ { 0x008080FF, "back_dis" , LPGEN("Disabled account background") },
+ { 0x00000000, "fore_dis" , LPGEN("Disabled account foreground") },
+ { 0x008000FF, "back_del" , LPGEN("Deleted account background") },
+ { 0x00000000, "fore_del" , LPGEN("Deleted account foreground") },
+ { 0x0080FFFF, "back_hid" , LPGEN("Hidden contact background") },
+ { 0x00000000, "fore_hid" , LPGEN("Hidden contact foreground") },
+ { 0x00BAE699, "back_meta", LPGEN("Metacontact background") },
+ { 0x00000000, "fore_meta", LPGEN("Metacontact foreground") },
+ { 0x00B3CCC1, "back_sub" , LPGEN("Subcontact background") },
+ { 0x00000000, "fore_sub" , LPGEN("Subcontact foreground") },
+};
+
+static int OnColorReload(WPARAM, LPARAM)
+{
+ for (int i = 0; i < color_max; i++)
+ g_plugin.m_colors[i] = Colour_Get(MODULENAME, sttColors[i].szSetting);
+ return 0;
+}
+
+int CMPlugin::Load()
+{
+ g_plugin.registerIcon(MODULENAME, iconList);
+
+ ColourID colourid = {};
+ strncpy_s(colourid.group, MODULENAME, _TRUNCATE);
+ strncpy_s(colourid.dbSettingsGroup, MODULENAME, _TRUNCATE);
+
+ for (auto &it : sttColors) {
+ strncpy_s(colourid.name, it.szDescr, _TRUNCATE);
+ strncpy_s(colourid.setting, it.szSetting, _TRUNCATE);
+ colourid.defcolour = it.defValue;
+ colourid.order = int(&it - sttColors);
+ g_plugin.addColor(&colourid);
+ }
+ OnColorReload(0, 0);
+ OnCheckPlugins(0, 0);
+
+ HookEvent(ME_COLOUR_RELOAD, OnColorReload);
+ HookEvent(ME_OPT_INITIALISE, OnOptInit);
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+ HookEvent(ME_SYSTEM_MODULELOAD, OnCheckPlugins);
+ HookEvent(ME_SYSTEM_MODULEUNLOAD, OnCheckPlugins);
+
+ LoadColumns(m_columns);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPlugin::Unload()
+{
+ if (hTTBButton) {
+ CallService(MS_TTB_REMOVEBUTTON, (WPARAM)hTTBButton, 0);
+ hTTBButton = 0;
+ }
+
+ DestroyFrame();
+ CloseSrWindow();
+ return 0;
+}
diff --git a/plugins/QuickSearch/src/options.cpp b/plugins/QuickSearch/src/options.cpp
new file mode 100644
index 0000000000..c8b7320dbf
--- /dev/null
+++ b/plugins/QuickSearch/src/options.cpp
@@ -0,0 +1,519 @@
+/*
+Copyright (C) 2012-21 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+class COptionsDlg : public CDlgBase
+{
+ OBJLIST<ColumnItem> m_columns;
+
+ void AddColumn(int idx, ColumnItem *pCol)
+ {
+ LVITEM lvi = {};
+ lvi.mask = LVIF_PARAM;
+ lvi.iItem = idx;
+ lvi.lParam = LPARAM(pCol);
+ m_list.InsertItem(&lvi);
+ }
+
+ void CheckDirection(int iItem)
+ {
+ btnUp.Enable(iItem > 0);
+ btnDown.Enable(iItem < m_list.GetItemCount()-1);
+ }
+
+ void ClearScreen()
+ {
+ // setting
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_DATATYPE), SW_HIDE);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_DATATYPE), SW_HIDE);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_MODULE), SW_HIDE);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_E_MODULE), SW_HIDE);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_SETTING), SW_HIDE);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_E_SETTING), SW_HIDE);
+
+ // contact info
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_CNFTYPE), SW_HIDE);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_CNFTYPE), SW_HIDE);
+
+ // others
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_OTHER), SW_HIDE);
+ }
+
+ void DisplayCurInfo(const ColumnItem *pCol)
+ {
+ ClearScreen();
+ SetupScreen(pCol->setting_type);
+
+ editTitle.SetText(pCol->title);
+ cmbVarType.SelectData(pCol->setting_type);
+
+ switch (pCol->setting_type) {
+ case QST_SETTING:
+ cmbDataType.SelectData(pCol->datatype);
+ editModule.SetTextA(pCol->module);
+ editSetting.SetTextA(pCol->setting);
+ break;
+
+ case QST_SCRIPT:
+ SetDlgItemTextW(m_hwnd, IDC_E_SCRIPT, pCol->script);
+ break;
+
+ case QST_CONTACTINFO:
+ cmbCnfType.SelectData(pCol->cnftype);
+ break;
+
+ case QST_OTHER:
+ cmbOther.SelectData(pCol->other);
+ break;
+ }
+ }
+
+ void FillTableLine(int item, ColumnItem *pColumn)
+ {
+ m_list.SetItemText(item, 1, pColumn->title);
+
+ switch (pColumn->setting_type) {
+ case QST_SETTING:
+ m_list.SetItemText(item, 2, _A2T(pColumn->module));
+ m_list.SetItemText(item, 3, _A2T(pColumn->setting));
+ break;
+
+ case QST_SCRIPT:
+ m_list.SetItemText(item, 2, TranslateT("Script"));
+ break;
+
+ case QST_SERVICE:
+ m_list.SetItemText(item, 2, TranslateT("Service"));
+ m_list.SetItemText(item, 3, _A2T(pColumn->svc.service));
+ break;
+
+ case QST_CONTACTINFO:
+ m_list.SetItemText(item, 2, TranslateT("Contact info"));
+ m_list.SetItemText(item, 3, cnf2str(pColumn->cnftype));
+ break;
+
+ case QST_OTHER:
+ m_list.SetItemText(item, 2, TranslateT("Other"));
+ if (pColumn->other == QSTO_METACONTACT)
+ m_list.SetItemText(item, 3, TranslateT("Metacontact"));
+ break;
+ }
+ }
+
+ void InitScreen()
+ {
+ // setting
+ cmbDataType.SetCurSel(0);
+ editModule.SetText(L"");
+ editSetting.SetText(L"");
+
+ // contact info
+ cmbCnfType.SetCurSel(0);
+
+ // others
+ cmbOther.SetCurSel(0);
+ }
+
+ void ResizeControl(int id, int width)
+ {
+ HWND hwnd = GetDlgItem(m_hwnd, id);
+ RECT rc;
+ ::GetWindowRect(hwnd, &rc);
+ ::SetWindowPos(hwnd, 0, 0, 0, width, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
+ }
+
+ void SetupScreen(int type)
+ {
+ if (!IsWindowVisible(GetDlgItem(m_hwnd, IDC_E_TITLE)))
+ return;
+
+ // setting
+ int cmd = (type == QST_SETTING) ? SW_SHOW : SW_HIDE;
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_DATATYPE), cmd);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_DATATYPE), cmd);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_MODULE), cmd);
+ editModule.Show(cmd == SW_SHOW);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_SETTING), cmd);
+ editSetting.Show(cmd == SW_SHOW);
+
+ // contact info
+ cmd = (type == QST_CONTACTINFO) ? SW_SHOW : SW_HIDE;
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_CNFTYPE), cmd);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_CNFTYPE), cmd);
+
+ // script
+ cmd = (type == QST_SCRIPT) ? SW_SHOW : SW_HIDE;
+ ShowWindow(GetDlgItem(m_hwnd, IDC_E_SCRIPT), cmd);
+
+ // others
+ cmd = (type == QST_OTHER) ? SW_SHOW : SW_HIDE;
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_OTHER), cmd);
+ }
+
+ void UpdateList()
+ {
+ m_list.DeleteAllItems();
+
+ int cnt = 0;
+ for (auto &it : m_columns) {
+ AddColumn(cnt, it);
+ FillTableLine(cnt, it);
+ m_list.SetCheckState(cnt, it->bEnabled);
+ cnt++;
+ }
+
+ m_list.SetCurSel(0);
+ }
+
+ CCtrlEdit editTitle, editModule, editSetting;
+ CCtrlCheck chkSortStatus, chkAutoClose, chkUseToolstyle, chkDrawGrid, chkSavePattern, chkClientIcons;
+ CCtrlCombo cmbVarType, cmbDataType, cmbOther, cmbCnfType;
+ CCtrlListView m_list;
+ CCtrlButton btnSave, btnResize;
+ CCtrlMButton btnNew, btnUp, btnDown, btnDelete, btnDefault, btnReload;
+
+public:
+ COptionsDlg() :
+ CDlgBase(g_plugin, IDD_OPTIONS),
+ m_columns(1),
+ m_list(this, IDC_LIST),
+ btnSave(this, IDC_SETITEM),
+ btnResize(this, IDC_B_RESIZE),
+ btnUp(this, IDC_UP, g_plugin.getIcon(IDI_UP), LPGEN("Up")),
+ btnNew(this, IDC_NEW, g_plugin.getIcon(IDI_NEW), LPGEN("New")),
+ btnDown(this, IDC_DN, g_plugin.getIcon(IDI_DOWN), LPGEN("Down")),
+ btnDelete(this, IDC_DELETE, g_plugin.getIcon(IDI_DELETE), LPGEN("Delete")),
+ btnReload(this, IDC_RELOAD, g_plugin.getIcon(IDI_RELOAD), LPGEN("Reload")),
+ btnDefault(this, IDC_DEFAULT, g_plugin.getIcon(IDI_DEFAULT), LPGEN("Default")),
+ editTitle(this, IDC_E_TITLE),
+ editModule(this, IDC_E_MODULE),
+ editSetting(this, IDC_E_SETTING),
+ cmbOther(this, IDC_C_OTHER),
+ cmbCnfType(this, IDC_C_CNFTYPE),
+ cmbVarType(this, IDC_C_VARTYPE),
+ cmbDataType(this, IDC_C_DATATYPE),
+ chkDrawGrid(this, IDC_CH_DRAWGRID),
+ chkAutoClose(this, IDC_CH_AUTOCLOSE),
+ chkSortStatus(this, IDC_CH_SORTSTATUS),
+ chkSavePattern(this, IDC_CH_SAVEPATTERN),
+ chkClientIcons(this, IDC_CH_CLIENTICONS),
+ chkUseToolstyle(this, IDC_CH_USETOOLSTYLE)
+ {
+ m_list.OnItemChanged = Callback(this, &COptionsDlg::onItemChanged_List);
+
+ btnUp.OnClick = Callback(this, &COptionsDlg::onClick_Up);
+ btnNew.OnClick = Callback(this, &COptionsDlg::onClick_New);
+ btnDown.OnClick = Callback(this, &COptionsDlg::onClick_Down);
+ btnSave.OnClick = Callback(this, &COptionsDlg::onClick_Save);
+ btnDelete.OnClick = Callback(this, &COptionsDlg::onClick_Delete);
+ btnReload.OnClick = Callback(this, &COptionsDlg::onClick_Reload);
+ btnResize.OnClick = Callback(this, &COptionsDlg::onClick_Resize);
+ btnDefault.OnClick = Callback(this, &COptionsDlg::onClick_Default);
+
+ cmbVarType.OnSelChanged = Callback(this, &COptionsDlg::onSelChanged_Var);
+ }
+
+ bool OnInitDialog() override
+ {
+ editTitle.SetSilent(); editModule.SetSilent(); editSetting.SetSilent();
+ cmbOther.SetSilent(); cmbCnfType.SetSilent(); cmbVarType.SetSilent(); cmbDataType.SetSilent();
+
+ m_list.SetExtendedListViewStyle(m_list.GetExtendedListViewStyle() | LVS_EX_FULLROWSELECT | LVS_EX_CHECKBOXES);
+ m_list.AddColumn(0, L"#", 20);
+ m_list.AddColumn(1, TranslateT("Title"), g_plugin.getWord("col1"));
+ m_list.AddColumn(2, TranslateT("Module/InfoType"), g_plugin.getWord("col2"));
+ m_list.AddColumn(3, TranslateT("Setting"), g_plugin.getWord("col3"));
+
+ cmbVarType.AddString(TranslateT("DB setting"), QST_SETTING);
+ cmbVarType.AddString(TranslateT("Script"), QST_SCRIPT);
+ cmbVarType.AddString(TranslateT("Contact info"), QST_CONTACTINFO);
+ cmbVarType.AddString(TranslateT("Other"), QST_OTHER);
+ cmbVarType.SetCurSel(0);
+
+ cmbDataType.AddString(TranslateT("Byte"), QSTS_BYTE);
+ cmbDataType.AddString(TranslateT("Word"), QSTS_WORD);
+ cmbDataType.AddString(TranslateT("Dword"), QSTS_DWORD);
+ cmbDataType.AddString(TranslateT("Signed"), QSTS_SIGNED);
+ cmbDataType.AddString(TranslateT("As hex"), QSTS_HEXNUM);
+ cmbDataType.AddString(TranslateT("String"), QSTS_STRING);
+ cmbDataType.AddString(TranslateT("TimeStamp"), QSTS_TIMESTAMP);
+ cmbDataType.SetCurSel(0);
+
+ cmbOther.AddString(TranslateT("Last seen"), QSTO_LASTSEEN);
+ cmbOther.AddString(TranslateT("Last event"), QSTO_LASTEVENT);
+ cmbOther.AddString(TranslateT("Metacontact"), QSTO_METACONTACT);
+ cmbOther.AddString(TranslateT("Event count"), QSTO_EVENTCOUNT);
+ cmbOther.AddString(TranslateT("Display name"), QSTO_DISPLAYNAME);
+ cmbOther.AddString(TranslateT("Account name"), QSTO_ACCOUNT);
+ cmbOther.SetCurSel(0);
+
+ for (int i = CNF_FIRSTNAME; i < CNF_MAX; i++)
+ cmbCnfType.AddString(cnf2str(i), i);
+ cmbCnfType.SetCurSel(0);
+
+ chkDrawGrid.SetState((g_plugin.m_flags & QSO_DRAWGRID) != 0);
+ chkAutoClose.SetState((g_plugin.m_flags & QSO_AUTOCLOSE) != 0);
+ chkSortStatus.SetState((g_plugin.m_flags & QSO_SORTBYSTATUS) != 0);
+ chkClientIcons.SetState((g_plugin.m_flags & QSO_CLIENTICONS) != 0);
+ chkSavePattern.SetState((g_plugin.m_flags & QSO_SAVEPATTERN) != 0);
+ chkUseToolstyle.SetState((g_plugin.m_flags & QSO_TOOLSTYLE) != 0);
+
+ // make local copy of column descriptions
+ for (auto &it : g_plugin.m_columns)
+ m_columns.insert(new ColumnItem(*it));
+
+ UpdateList();
+ onClick_Resize(0);
+ if (m_columns.getCount())
+ DisplayCurInfo(&m_columns[0]);
+ return true;
+ }
+
+ bool OnApply() override
+ {
+ // checkboxes
+ g_plugin.m_flags &= ~QSO_MAINOPTIONS;
+ if (chkDrawGrid.IsChecked()) g_plugin.m_flags |= QSO_DRAWGRID;
+ if (chkAutoClose.IsChecked()) g_plugin.m_flags |= QSO_AUTOCLOSE;
+ if (chkSortStatus.IsChecked()) g_plugin.m_flags |= QSO_SORTBYSTATUS;
+ if (chkClientIcons.IsChecked()) g_plugin.m_flags |= QSO_CLIENTICONS;
+ if (chkSavePattern.IsChecked()) g_plugin.m_flags |= QSO_SAVEPATTERN;
+ if (chkUseToolstyle.IsChecked()) g_plugin.m_flags |= QSO_TOOLSTYLE;
+
+ int tmpbool = CloseSrWindow(false);
+
+ g_plugin.m_columns.destroy();
+ int nCount = m_list.GetItemCount();
+ for (int i = 0; i < nCount; i++) {
+ auto *pCol = (ColumnItem *)m_list.GetItemData(i);
+ pCol->bEnabled = m_list.GetCheckState(i) != 0;
+ g_plugin.m_columns.insert(new ColumnItem(*pCol));
+ }
+
+ g_plugin.SaveOptions();
+
+ if (tmpbool)
+ OpenSrWindow(0);
+ return true;
+ }
+
+ void OnDestroy() override
+ {
+ m_columns.destroy();
+
+ g_plugin.setWord("col1", m_list.GetColumnWidth(1));
+ g_plugin.setWord("col2", m_list.GetColumnWidth(2));
+ g_plugin.setWord("col3", m_list.GetColumnWidth(3));
+ }
+
+ void onClick_New(CCtrlButton *)
+ {
+ int idx = m_list.GetSelectionMark()+1;
+ auto *pNew = new ColumnItem(TranslateT("New column"));
+ m_columns.insert(pNew);
+
+ AddColumn(idx, pNew);
+ m_list.EnsureVisible(idx, FALSE);
+ m_list.SetCurSel(idx);
+ InitScreen();
+ CheckDirection(idx);
+ btnDelete.Enable();
+ NotifyChange();
+ }
+
+ void onClick_Delete(CCtrlButton *)
+ {
+ int idx = m_list.GetSelectionMark();
+ auto *pCol = (ColumnItem *)m_list.GetItemData(idx);
+
+ m_list.DeleteItem(idx);
+ m_columns.remove(pCol);
+
+ int nCount = m_list.GetItemCount();
+ if (nCount == 0) {
+ m_list.Disable();
+ InitScreen();
+ }
+ else {
+ if (nCount == idx)
+ idx--;
+ m_list.SetCurSel(idx);
+ }
+ CheckDirection(idx);
+ NotifyChange();
+ }
+
+ void onClick_Up(CCtrlButton *)
+ {
+ int idx = m_list.GetSelectionMark();
+ if (idx > 0) {
+ CheckDirection(m_list.MoveItem(idx, -1));
+ NotifyChange();
+ }
+ }
+
+ void onClick_Down(CCtrlButton *)
+ {
+ int idx = m_list.GetSelectionMark();
+ if (idx < m_list.GetItemCount() - 1) {
+ CheckDirection(m_list.MoveItem(idx, 1));
+ NotifyChange();
+ }
+ }
+
+ void onClick_Reload(CCtrlButton *)
+ {
+ g_plugin.LoadColumns(m_columns);
+ UpdateList();
+ }
+
+ void onClick_Default(CCtrlButton *)
+ {
+ LoadDefaultColumns(m_columns);
+ UpdateList();
+ NotifyChange();
+ }
+
+ void onClick_Save(CCtrlButton *)
+ {
+ if (m_list.GetItemCount() == 0) {
+ AddColumn(0, new ColumnItem(TranslateT("New column")));
+ m_list.SetCurSel(0);
+ btnDelete.Enable();
+ }
+
+ int idx = m_list.GetSelectionMark();
+ auto *pCol = (ColumnItem *)m_list.GetItemData(idx);
+ pCol->dwFlags = 0;
+ if (m_list.GetCheckState(idx))
+ pCol->bEnabled = pCol->bFilter = true;
+ pCol->setting_type = cmbVarType.GetItemData(cmbVarType.GetCurSel());
+ replaceStrW(pCol->title, editTitle.GetText());
+
+ switch (pCol->setting_type) {
+ case QST_SETTING:
+ pCol->datatype = cmbDataType.GetItemData(cmbDataType.GetCurSel());
+ pCol->module = mir_u2a(ptrW(editModule.GetText()));
+ pCol->setting = mir_u2a(ptrW(editSetting.GetText()));
+ break;
+
+ case QST_CONTACTINFO:
+ pCol->cnftype = cmbCnfType.GetItemData(cmbCnfType.GetCurSel());
+ break;
+
+ case QST_OTHER:
+ pCol->other = cmbCnfType.GetItemData(cmbCnfType.GetCurSel());
+ break;
+ }
+
+ FillTableLine(idx, pCol);
+ NotifyChange();
+ }
+
+ void onClick_Resize(CCtrlButton *)
+ {
+ wchar_t *pcw;
+ int dx, rside;
+
+ HWND wSeparator = GetDlgItem(m_hwnd,IDC_B_RESIZE);
+ RECT rc, rc1;
+ GetClientRect(m_hwnd, &rc);
+ GetWindowRect(wSeparator, &rc1);
+
+ POINT pt = { rc1.left, 0 };
+ ScreenToClient(m_hwnd, &pt);
+ if (pt.x < (rc.right - 50)) {
+ rside = SW_HIDE;
+ dx = rc.right - (rc1.right - rc1.left) - 4;
+ pcw = L"<";
+ }
+ else {
+ rside = SW_SHOW;
+
+ GetWindowRect(GetDlgItem(m_hwnd, IDC_S_COLSETTING), &rc);
+ pt.x = rc.left;
+ pt.y = 0;
+ ScreenToClient(m_hwnd, &pt);
+ dx = pt.x - (rc1.right - rc1.left) - 4;
+ pcw = L">";
+ }
+
+ SendMessageW(wSeparator, WM_SETTEXT, 0, LPARAM(pcw));
+
+ // move separator button
+ SetWindowPos(wSeparator,0,dx+2,2,0,0,SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);
+
+ // resize left side controls
+ ResizeControl(IDC_LIST, dx);
+ ResizeControl(IDC_CH_GROUP, dx);
+
+ ResizeControl(IDC_CH_USETOOLSTYLE, dx - 8);
+ ResizeControl(IDC_CH_DRAWGRID, dx - 8);
+ ResizeControl(IDC_CH_SAVEPATTERN, dx - 8);
+ ResizeControl(IDC_CH_AUTOCLOSE, dx - 8);
+ ResizeControl(IDC_CH_SORTSTATUS, dx - 8);
+ ResizeControl(IDC_CH_CLIENTICONS, dx - 8);
+
+ // show/hide setting block (ugly, i know!)
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_COLSETTING), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_LINE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_TITLE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_E_TITLE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_E_SCRIPT), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_E_MODULE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_E_SETTING), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_S_VARTYPE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_VARTYPE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_OTHER), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_CNFTYPE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_C_DATATYPE), rside);
+ ShowWindow(GetDlgItem(m_hwnd, IDC_SETITEM), rside);
+
+ ClearScreen();
+ if (rside == SW_SHOW)
+ SetupScreen(cmbVarType.GetItemData(cmbVarType.GetCurSel()));
+ }
+
+ void onItemChanged_List(CCtrlListView::TEventInfo *ev)
+ {
+ auto *nmlv = ev->nmlv;
+ // we got new focus
+ if ((nmlv->uOldState & LVNI_FOCUSED) < (nmlv->uNewState & LVNI_FOCUSED)) {
+ CheckDirection(nmlv->iItem);
+ InitScreen();
+ DisplayCurInfo((ColumnItem*)nmlv->lParam);
+ }
+ }
+
+ void onSelChanged_Var(CCtrlCombo *pCombo)
+ {
+ SetupScreen(pCombo->GetItemData(pCombo->GetCurSel()));
+ }
+};
+
+int OnOptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.szGroup.a = LPGEN("Contacts");
+ odp.szTitle.a = LPGEN("Quick Search");
+ odp.position = 900003000;
+ odp.pDialog = new COptionsDlg();
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/plugins/QuickSearch/src/resource.h b/plugins/QuickSearch/src/resource.h
new file mode 100644
index 0000000000..8058dc6038
--- /dev/null
+++ b/plugins/QuickSearch/src/resource.h
@@ -0,0 +1,67 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by w:\miranda-ng\plugins\QuickSearch\res\resource.rc
+//
+#define IDI_DEFAULT 101
+#define IDI_DELETE 102
+#define IDI_DOWN 103
+#define IDI_FEMALE 104
+#define IDI_MALE 105
+#define IDI_NEW 106
+#define IDI_QS 107
+#define IDI_RELOAD 108
+#define IDI_UP 109
+#define IDD_MAIN 110
+#define IDD_OPTIONS 111
+#define IDD_FRAME 112
+#define IDC_FRAME_OPEN 1001
+#define IDC_FRAME_EDIT 1002
+#define IDC_RELOAD 1003
+#define IDC_NEW 1004
+#define IDC_UP 1005
+#define IDC_DN 1006
+#define IDC_DELETE 1007
+#define IDC_DEFAULT 1008
+#define IDC_LIST 1009
+#define IDC_B_RESIZE 1010
+#define IDC_S_COLSETTING 1011
+#define IDC_S_LINE 1012
+#define IDC_S_TITLE 1013
+#define IDC_E_TITLE 1014
+#define IDC_S_VARTYPE 1015
+#define IDC_C_VARTYPE 1016
+#define IDC_S_DATATYPE 1017
+#define IDC_C_DATATYPE 1018
+#define IDC_S_MODULE 1019
+#define IDC_E_MODULE 1020
+#define IDC_S_SETTING 1021
+#define IDC_E_SETTING 1022
+#define IDC_S_CNFTYPE 1023
+#define IDC_C_CNFTYPE 1024
+#define IDC_E_SCRIPT 1025
+#define IDC_C_OTHER 1026
+#define IDC_SETITEM 1027
+#define IDC_CH_USETOOLSTYLE 1028
+#define IDC_CH_DRAWGRID 1029
+#define IDC_CH_SAVEPATTERN 1030
+#define IDC_CH_AUTOCLOSE 1031
+#define IDC_CH_SORTSTATUS 1032
+#define IDC_CH_CLIENTICONS 1033
+#define IDC_CH_GROUP 1034
+#define IDC_E_SEARCHTEXT 1035
+#define IDC_REFRESH 1036
+#define IDC_CH_SHOWOFFLINE 1037
+#define IDC_CB_PROTOCOLS 1038
+#define IDC_STATUSBAR 1039
+#define IDC_CH_COLORIZE 1040
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 107
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1032
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/plugins/QuickSearch/src/stdafx.cxx b/plugins/QuickSearch/src/stdafx.cxx
new file mode 100644
index 0000000000..20c49240b6
--- /dev/null
+++ b/plugins/QuickSearch/src/stdafx.cxx
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2012-21 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h" \ No newline at end of file
diff --git a/plugins/QuickSearch/src/stdafx.h b/plugins/QuickSearch/src/stdafx.h
new file mode 100644
index 0000000000..591ac22596
--- /dev/null
+++ b/plugins/QuickSearch/src/stdafx.h
@@ -0,0 +1,417 @@
+#include <windows.h>
+#include <CommCtrl.h>
+#include <stdio.h>
+#include <malloc.h>
+
+#include <newpluginapi.h>
+#include <m_clistint.h>
+#include <m_cluiframes.h>
+#include <m_contacts.h>
+#include <m_database.h>
+#include <m_findadd.h>
+#include <m_fingerprint.h>
+#include <m_fontservice.h>
+#include <m_hotkeys.h>
+#include <m_genmenu.h>
+#include <m_gui.h>
+#include <m_langpack.h>
+#include <m_metacontacts.h>
+#include <m_netlib.h>
+#include <m_options.h>
+#include <m_popup.h>
+#include <m_protocols.h>
+#include <m_protosvc.h>
+#include <m_skin.h>
+#include <m_timezones.h>
+#include <m_tipper.h>
+#include <m_toptoolbar.h>
+#include <m_utils.h>
+#include <m_variables.h>
+#include <m_xstatus.h>
+
+#include "resource.h"
+#include "version.h"
+
+#define MODULENAME "QuickSearch"
+#define QS_SHOWSERVICE "QuickSearch/Show"
+
+#define StatusSort 1000
+
+#define QSTS_BYTE 0
+#define QSTS_WORD 1
+#define QSTS_DWORD 2
+#define QSTS_STRING 3
+#define QSTS_IP 4
+#define QSTS_TIMESTAMP 5
+#define QSTS_SIGNED 6
+#define QSTS_HEXNUM 7
+
+// must be non-zero for empty-column checking
+#define QST_SETTING 100
+#define QST_SCRIPT 1
+#define QST_SERVICE 2
+#define QST_CONTACTINFO 3
+#define QST_OTHER 200
+
+#define QSTO_LASTSEEN 0
+#define QSTO_LASTEVENT 1
+#define QSTO_METACONTACT 2
+#define QSTO_EVENTCOUNT 3
+#define QSTO_DISPLAYNAME 4
+#define QSTO_ACCOUNT 5
+
+#define QSO_SORTBYSTATUS 0x00000001 // Sort by status
+#define QSO_DRAWGRID 0x00000002 // Draw listview grid
+#define QSO_TOOLSTYLE 0x00000004 // QS window tool style
+#define QSO_SAVEPATTERN 0x00000008 // Save filter pattern
+#define QSO_AUTOCLOSE 0x00000010 // Close QS window after action
+#define QSO_CLIENTICONS 0x00000020 // Show client icons (fingerprint)
+#define QSO_MAINOPTIONS 0x0000FFFF // mask for common options
+
+// QS window options
+#define QSO_STAYONTOP 0x00010000 // Stay QS window on top
+#define QSO_SHOWOFFLINE 0x00020000 // Show offline contacts
+#define QSO_COLORIZE 0x00040000 // Colorize lines
+#define QSO_SORTASC 0x00080000 // Sort column ascending
+
+struct ServiceDescr
+{
+ char *service;
+ LPARAM wParam, lParam;
+ DWORD wFlags, lFlags, flags;
+};
+
+struct ColumnItem : public MZeroedObject
+{
+ ColumnItem(const wchar_t *pszTitle, int _width = 64, int _setting_type = QST_SETTING);
+ ColumnItem(const ColumnItem &src);
+ ~ColumnItem();
+
+ void SetSpecialColumns();
+
+ wchar_t *title;
+ uint16_t setting_type = 0; // QST_* constants
+ uint16_t width = 0;
+
+ union
+ {
+ uint32_t dwFlags = 0;
+ struct {
+ bool bEnabled : 1;
+ bool bInit : 1;
+ bool bFilter : 1;
+ bool isXstatus : 1;
+ bool isGender : 1;
+ bool isClient : 1;
+ bool isGroup : 1;
+ bool isContainer : 1;
+ };
+ };
+
+
+ union {
+ // 0: db setting
+ struct {
+ uint16_t datatype; // QSTS_* constants
+ char *module;
+ char *setting;
+ };
+
+ // 1: script
+ wchar_t *script;
+
+ // 2: service
+ ServiceDescr svc;
+
+ // 3: contact info
+ int cnftype; // CNF_* constants
+
+ // 4: other
+ int other; // QSTO_* constants
+ };
+};
+
+struct ContactIntoColumn : public ColumnItem
+{
+ ContactIntoColumn(const wchar_t *pwszTitle, int width, int _type) :
+ ColumnItem(pwszTitle, width, QST_CONTACTINFO)
+ {
+ cnftype = _type;
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+#define so_mbottom "mbottom"
+#define so_mright "mright"
+#define so_mtop "mtop"
+#define so_mleft "mleft"
+
+#define so_columnsort "columnsort"
+#define so_flags "flags"
+
+#define so_numcolumns "numcolumns"
+#define so_item "item"
+
+#define so_title "title"
+#define so_width "width"
+#define so_flags "flags"
+#define so_setting_type "setting_type"
+
+#define so_cnftype "cnftype"
+
+#define so_datatype "datatype"
+#define so_module "module"
+#define so_setting "setting"
+
+#define so_script "script"
+
+#define so_service "service"
+#define so_restype "flags"
+#define so_wparam "wparam"
+#define so_lparam "lparam"
+
+#define so_other "other"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+enum ColorCode
+{
+ bkg_norm, fgr_norm,
+ bkg_odd, fgr_odd,
+ bkg_dis, fgr_dis,
+ bkg_del, fgr_del,
+ bkg_hid, fgr_hid,
+ bkg_meta, fgr_meta,
+ bkg_sub, fgr_sub,
+ color_max
+};
+
+struct CMPlugin : public PLUGIN<CMPlugin>
+{
+ CMPlugin();
+
+ COLORREF m_colors[color_max];
+ RECT m_rect;
+ OBJLIST<ColumnItem> m_columns;
+ int m_sortOrder;
+ DWORD m_flags; // QSO_* constants
+
+ void LoadOptWnd();
+ void SaveOptWnd();
+
+ void LoadColumns(OBJLIST<ColumnItem>&);
+ void LoadColumn(int n, ColumnItem &col);
+ void LoadParamValue(char *buf, DWORD &flags, LPARAM &value);
+
+ void SaveOptions();
+ void SaveColumn(int n, const ColumnItem &col);
+ void SaveParamValue(char *buf, DWORD flags, LPARAM value);
+
+ int Load() override;
+ int Unload() override;
+};
+
+extern bool g_bVarsInstalled, g_bTipperInstalled, g_bFingerInstalled;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// frame.cpp
+
+void CreateFrame(HWND);
+void DestroyFrame();
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// utils.cpp
+
+int ColumnToListView(int col);
+int ListViewToColumn(int col);
+void LoadDefaultColumns(OBJLIST<ColumnItem> &dst);
+
+void SnapToScreen(RECT &rc);
+
+const wchar_t* cnf2str(int);
+
+wchar_t* BuildLastSeenTime(DWORD timestamp);
+DWORD BuildLastSeenTimeInt(MCONTACT hContact, const char *szModule);
+wchar_t* TimeToStrW(DWORD timestamp);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// windows.cpp
+
+struct QSFlags
+{
+ union {
+ DWORD flags = 0;
+ struct {
+ bool bInList : 1; // in constant list
+ bool bActive : 1; // contact in listview
+ bool bDeleted : 1; // contact deleted
+ bool bPattern : 1; // pattern check passed
+ bool bAccDel : 1; // account deleted
+ bool bAccOff : 1; // account disabled
+ bool bIsMeta : 1; // contact is metacontact
+ bool bIsSub : 1; // contact is part of metacontact
+ };
+ };
+};
+
+struct CRowItem : public QSFlags
+{
+ CRowItem(MCONTACT _1, class QSMainDlg *pDlg);
+ ~CRowItem();
+
+ MCONTACT hContact;
+ const char *szProto;
+
+ int status = ID_STATUS_OFFLINE;
+ WPARAM wparam = 0;
+ LPARAM lparam = 0;
+
+ void GetCellColor(int idx, COLORREF &clrBack, COLORREF &clrText);
+
+ struct Val
+ {
+ wchar_t *text = nullptr;
+ UINT_PTR data = UINT_PTR(-1);
+
+ void LoadOneItem(MCONTACT hContact, const ColumnItem &pCol, class QSMainDlg *pDlg);
+ }
+ *pValues;
+};
+
+// status bar data
+struct CStatusBarItem : public QSFlags
+{
+ CStatusBarItem(const char *_proto, int _flags) :
+ szProto(_proto)
+ {
+ flags = _flags;
+ }
+
+ const char *szProto;
+
+ int total = 0;
+ int found = 0;
+ int online = 0;
+ int liston = 0;
+};
+
+class QSMainDlg : public CDlgBase
+{
+ friend struct CRowItem;
+
+ OBJLIST<CRowItem> m_rows;
+ OBJLIST<CStatusBarItem> m_sbdata;
+
+ UI_MESSAGE_MAP(QSMainDlg, CDlgBase);
+ UI_MESSAGE(WM_SYSCOMMAND, OnSysCommand);
+ UI_MESSAGE(WM_MOUSEMOVE, OnMouseMove);
+ UI_MESSAGE(WM_KEYDOWN, OnKeydown);
+ UI_MESSAGE_MAP_END();
+
+ // internal methods
+ void AddColumn(int idx, ColumnItem *pCol);
+ void AddContactToList(MCONTACT hContact, CRowItem *pRow);
+ void AdvancedFilter();
+ void ConvertToMeta();
+ void CopyMultiLines();
+ void DeleteByList();
+ void DeleteOneContact(MCONTACT hContact);
+ wchar_t* DoMeta(MCONTACT hContact);
+ void FillGrid();
+ void FillProtoCombo();
+ int FindItem(CRowItem *pRow);
+ int FindMeta(MCONTACT hMeta, WPARAM &metaNum);
+ CRowItem* FindRow(MCONTACT hContact);
+ MCONTACT GetFocusedContact();
+ int GetLVSubItem(int x, int y);
+ void MoveToContainer(const wchar_t *pwszName);
+ void MoveToGroup(const wchar_t *pwszName);
+ void PrepareTable(bool bReset = false);
+ void ProcessLine(CRowItem *pRow, bool test = true);
+ void SaveColumnOrder();
+ void ShowContactMenu(MCONTACT, int);
+ void ShowContactMsgDlg(MCONTACT hContact);
+ void ShowMultiPopup(int);
+ void Sort();
+ void UpdateLVCell(int item, int column, const wchar_t *pwszText = nullptr);
+
+ __forceinline CRowItem* GetRow(int i) {
+ return (CRowItem *)m_grid.GetItemData(i);
+ }
+
+ // status bar
+ HWND hwndStatusBar = 0, HintWnd = 0;
+ void DrawSB();
+ void UpdateSB();
+
+ // internal data
+ int hLastMeta;
+ bool TTShowed = false;
+ int tableColumns = 0;
+ HICON hIconF = 0, hIconM = 0;
+ HANDLE hAdd, hDelete, hChange;
+ HGENMENU mnuhandle = 0;
+
+ // patterns
+ wchar_t *pattern = nullptr; // edit field text
+ wchar_t *patstr = nullptr; // edit field text
+
+ struct
+ {
+ wchar_t *str;
+ bool res;
+ }
+ static patterns[8];
+
+ int numpattern = 0;
+
+ bool bShowOffline;
+ char *szFilterProto = nullptr;
+
+ bool CheckPattern(CRowItem *);
+ void MakePattern();
+
+ // controls
+ CTimer m_hover;
+ CCtrlEdit edtFilter;
+ CCtrlCheck chkShowOffline, chkColorize;
+ CCtrlCombo cmbProto;
+ CCtrlButton btnRefresh;
+ CCtrlListView m_grid;
+
+public:
+ QSMainDlg(const wchar_t *pattern);
+
+ bool OnInitDialog() override;
+ void OnDestroy() override;
+ int Resizer(UTILRESIZECONTROL *urc) override;
+
+ void ChangeCellValue(MCONTACT hContact, int col);
+ bool PrepareToFill();
+
+ INT_PTR OnSysCommand(UINT, WPARAM, LPARAM);
+ INT_PTR OnMouseMove(UINT, WPARAM, LPARAM);
+ INT_PTR OnKeydown(UINT, WPARAM, LPARAM);
+
+ INT_PTR NewEditProc(UINT, WPARAM, LPARAM);
+ INT_PTR NewLVProc(UINT, WPARAM, LPARAM);
+
+ void onBuildMenu_Grid(CContextMenuPos *pos);
+ void onColumnClick_Grid(CCtrlListView::TEventInfo *ev);
+ void onCustomDraw_Grid(CCtrlListView::TEventInfo *ev);
+ void onDblClick_Grid(CCtrlListView::TEventInfo *ev);
+
+ void onSelChange_Proto(CCtrlCombo *);
+
+ void onChange_Filter(CCtrlEdit *);
+ void onChange_ShowOffline(CCtrlCheck *);
+ void onChange_Colorize(CCtrlCheck *);
+
+ void onClick_Refresh(CCtrlButton *);
+
+ void onTimer_Hover(CTimer *);
+};
+
+int CloseSrWindow(bool = true);
+int OpenSrWindow(const wchar_t *pwszPattern);
diff --git a/plugins/QuickSearch/src/utils.cpp b/plugins/QuickSearch/src/utils.cpp
new file mode 100644
index 0000000000..d8a2795d01
--- /dev/null
+++ b/plugins/QuickSearch/src/utils.cpp
@@ -0,0 +1,554 @@
+/*
+Copyright (C) 2012-21 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+#define ACF_TYPE_NUMBER 0x00 // Parameter is number
+#define ACF_TYPE_STRING 0x01 // Parameter is ANSI String
+#define ACF_TYPE_UNICODE 0x02 // Parameter is Unicode string
+#define ACF_TYPE_STRUCT 0x03 // Parameter is (result is in) structure
+#define ACF_TYPE_PARAM 0x08 // Parameter is Call parameter
+#define ACF_TYPE_CURRENT 0x09 // Parameter is ignored, used current user handle from current message window
+#define ACF_TYPE_RESULT 0x0A // Parameter is previous action result
+#define ACF_TYPE_MASK 0x0F // parameter/result type mask
+
+ColumnItem::ColumnItem(const wchar_t *pwszTitle, int _width, int _setting_type) :
+ title(mir_wstrdup(pwszTitle)),
+ width(_width),
+ setting_type(_setting_type)
+{
+ bEnabled = true;
+}
+
+ColumnItem::ColumnItem(const ColumnItem &src)
+{
+ memcpy(this, &src, sizeof(ColumnItem));
+
+ title = mir_wstrdup(title);
+
+ switch (setting_type) {
+ case QST_SETTING:
+ module = mir_strdup(module);
+ setting = mir_strdup(setting);
+ break;
+
+ case QST_SCRIPT:
+ script = mir_wstrdup(script);
+ break;
+
+ case QST_SERVICE:
+ svc.service = mir_strdup(svc.service);
+ switch (svc.wFlags) {
+ case ACF_TYPE_NUMBER:
+ case ACF_TYPE_STRING:
+ case ACF_TYPE_UNICODE:
+ svc.wParam = (WPARAM)mir_wstrdup((wchar_t *)svc.wParam);
+ break;
+ case ACF_TYPE_STRUCT:
+ svc.wParam = (WPARAM)mir_strdup((char *)svc.wParam);
+ break;
+ }
+
+ switch (svc.lFlags) {
+ case ACF_TYPE_NUMBER:
+ case ACF_TYPE_STRING:
+ case ACF_TYPE_UNICODE:
+ svc.lParam = (WPARAM)mir_wstrdup((wchar_t *)svc.lParam);
+ break;
+ case ACF_TYPE_STRUCT:
+ svc.lParam = (WPARAM)mir_strdup((char *)svc.lParam);
+ break;
+ }
+ break;
+ }
+}
+
+ColumnItem::~ColumnItem()
+{
+ mir_free(title);
+
+ switch (setting_type) {
+ case QST_SETTING:
+ mir_free(module);
+ mir_free(setting);
+ break;
+
+ case QST_SCRIPT:
+ mir_free(script);
+ break;
+
+ case QST_SERVICE:
+ mir_free(svc.service);
+ switch (svc.wFlags) {
+ case ACF_TYPE_NUMBER:
+ case ACF_TYPE_STRING:
+ case ACF_TYPE_UNICODE:
+ mir_free((wchar_t *)svc.wParam);
+ break;
+ case ACF_TYPE_STRUCT:
+ mir_free((char *)svc.wParam);
+ break;
+ }
+
+ switch (svc.lFlags) {
+ case ACF_TYPE_NUMBER:
+ case ACF_TYPE_STRING:
+ case ACF_TYPE_UNICODE:
+ mir_free((wchar_t *)svc.lParam);
+ break;
+ case ACF_TYPE_STRUCT:
+ mir_free((char *)svc.lParam);
+ break;
+ }
+ break;
+ }
+}
+
+void ColumnItem::SetSpecialColumns()
+{
+ if (setting_type == QST_SETTING) {
+ if (datatype == QSTS_STRING && !mir_strcmp(module, "CList") && !mir_strcmp(setting, "Group"))
+ isGroup = true;
+
+ else if (datatype == QSTS_STRING && !mir_strcmp(module, "Tab_SRMsg") && !mir_strcmp(setting, "containerW"))
+ isContainer = true;
+
+ else if (datatype == QSTS_BYTE && !mir_strcmpi(setting, "XStatusId"))
+ isXstatus = true;
+
+ else if (datatype == QSTS_STRING && !mir_strcmp(setting, "MirVer") && g_bFingerInstalled)
+ isClient = true;
+ }
+ else if (setting_type == QST_CONTACTINFO && cnftype == CNF_GENDER)
+ isGender = true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// column functions
+
+int ListViewToColumn(int col)
+{
+ for (auto &it : g_plugin.m_columns) {
+ if (!it->bEnabled)
+ continue;
+
+ if (col-- <= 0)
+ return g_plugin.m_columns.indexOf(&it);
+ }
+ return -1;
+}
+
+int ColumnToListView(int col)
+{
+ int res = -1;
+ for (auto &it : g_plugin.m_columns) {
+ if (it->bEnabled)
+ res++;
+
+ if (col-- <= 0)
+ break;
+ }
+ return res;
+}
+
+void LoadDefaultColumns(OBJLIST<ColumnItem> &dst)
+{
+ dst.destroy();
+
+ auto *pNew = new ColumnItem(TranslateT("Account"), 82, QST_OTHER);
+ pNew->other = QSTO_ACCOUNT;
+ dst.insert(pNew);
+
+ dst.insert(new ContactIntoColumn(TranslateT("Gender"), 20, CNF_GENDER));
+
+ pNew = new ContactIntoColumn(TranslateT("UserID"), 80, CNF_UNIQUEID);
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ContactIntoColumn(TranslateT("Nickname"), 76, QST_OTHER);
+ pNew->bFilter = true;
+ pNew->other = QSTO_DISPLAYNAME;
+
+ pNew = new ContactIntoColumn(TranslateT("First name"), 68, CNF_FIRSTNAME);
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ContactIntoColumn(TranslateT("Last name"), 66, CNF_LASTNAME);
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Group"), 80, QST_SETTING);
+ pNew->datatype = QSTS_STRING;
+ pNew->module = mir_strdup("CList");
+ pNew->setting = mir_strdup("Group");
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Container"), 80, QST_SETTING);
+ pNew->datatype = QSTS_STRING;
+ pNew->module = mir_strdup("Tab_SRMsg");
+ pNew->setting = mir_strdup("containerW");
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ContactIntoColumn(TranslateT("Email"), 116, CNF_EMAIL);
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Client ID"), 60, QST_SETTING);
+ pNew->datatype = QSTS_STRING;
+ pNew->setting = mir_strdup("MirVer");
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Last seen"), 116, QST_OTHER);
+ pNew->other = QSTO_LASTSEEN;
+ pNew->dwFlags = 0;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Last event"), 100, QST_OTHER);
+ pNew->other = QSTO_LASTEVENT;
+ pNew->dwFlags = 0;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Online since"), 100, QST_SETTING);
+ pNew->datatype = QSTS_TIMESTAMP;
+ pNew->setting = mir_strdup("LogonTS");
+ pNew->bFilter = true;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Metacontact"), 50, QST_OTHER);
+ pNew->other = QSTO_METACONTACT;
+ pNew->dwFlags = 0;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Event count"), 50, QST_OTHER);
+ pNew->other = QSTO_EVENTCOUNT;
+ pNew->dwFlags = 0;
+ dst.insert(pNew);
+
+ pNew = new ColumnItem(TranslateT("Contact add time"), 80, QST_SETTING);
+ pNew->datatype = QSTS_TIMESTAMP;
+ pNew->module = mir_strdup("UserInfo");
+ pNew->setting = mir_strdup("ContactAddTime");
+ dst.insert(pNew);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// window options
+
+void CMPlugin::LoadOptWnd()
+{
+ m_rect.bottom = getDword(so_mbottom);
+ m_rect.right = getDword(so_mright);
+ m_rect.left = getDword(so_mleft);
+ m_rect.top = getDword(so_mtop);
+
+ m_flags = getDword(so_flags);
+ m_sortOrder = getDword(so_columnsort);
+}
+
+void CMPlugin::SaveOptWnd()
+{
+ setDword(so_mbottom, m_rect.bottom);
+ setDword(so_mright, m_rect.right);
+ setDword(so_mleft, m_rect.left);
+ setDword(so_mtop, m_rect.top);
+
+ setDword(so_flags, m_flags);
+ setDword(so_columnsort, m_sortOrder);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// load options
+
+void CMPlugin::LoadColumns(OBJLIST<ColumnItem> &dst)
+{
+ m_flags = getDword(so_flags);
+ int numCols = getWord(so_numcolumns);
+
+ for (int i = 0; i < numCols; i++) {
+ auto *pNew = new ColumnItem(nullptr);
+ LoadColumn(i, *pNew);
+ dst.insert(pNew);
+ }
+}
+
+void CMPlugin::LoadColumn(int n, ColumnItem &col)
+{
+ char buf[127];
+ int offset = mir_snprintf(buf, "%s%d_", so_item, n);
+
+ strcpy(buf + offset, so_title); col.title = getWStringA(buf);
+ strcpy(buf + offset, so_setting_type); col.setting_type = getWord(buf);
+ strcpy(buf + offset, so_flags); col.dwFlags = getWord(buf);
+ strcpy(buf + offset, so_width); col.width = getWord(buf);
+
+ switch (col.setting_type) {
+ case QST_SETTING:
+ strcpy(buf + offset, so_datatype); col.datatype = getWord(buf);
+ strcpy(buf + offset, so_module); col.module = getStringA(buf);
+ strcpy(buf + offset, so_setting); col.setting = getStringA(buf);
+ break;
+
+ case QST_SCRIPT:
+ strcpy(buf + offset, so_script); col.script = getWStringA(buf);
+ break;
+
+ case QST_CONTACTINFO:
+ strcpy(buf + offset, so_cnftype); col.cnftype = getWord(buf);
+ break;
+
+ case QST_SERVICE:
+ offset = mir_snprintf(buf, "%s%d/service/", so_item, n);
+ strcpy(buf + offset, so_service); col.svc.service = getStringA(buf);
+ strcpy(buf + offset, so_restype); col.svc.flags = getDword(buf);
+ if (!mir_strcmp(col.svc.service, "Proto/GetContactBaseAccount")) {
+ col.setting_type = QST_OTHER;
+ col.other = QSTO_ACCOUNT;
+ break;
+ }
+
+ strcpy(buf + offset, so_wparam);
+ LoadParamValue(buf, col.svc.wFlags, col.svc.wParam);
+
+ strcpy(buf + offset, so_lparam);
+ LoadParamValue(buf, col.svc.lFlags, col.svc.lParam);
+ break;
+
+ case QST_OTHER:
+ strcpy(buf + offset, so_other); col.other = getWord(buf);
+ break;
+ }
+}
+
+void CMPlugin::LoadParamValue(char *buf, DWORD &dwFlags, LPARAM &dwWalue)
+{
+ char *pEnd = buf + strlen(buf);
+ strcpy(pEnd, "flags"); dwFlags = getDword(buf);
+
+ strcpy(pEnd, "value");
+ switch (dwFlags) {
+ case ACF_TYPE_NUMBER:
+ case ACF_TYPE_STRING:
+ case ACF_TYPE_UNICODE:
+ dwWalue = LPARAM(getWStringA(buf));
+ break;
+
+ case ACF_TYPE_STRUCT:
+ dwWalue = LPARAM(getStringA(buf));
+ break;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// save options
+
+static int ListSettings(const char *szSetting, void *param)
+{
+ if (!memcmp(szSetting, so_item, 4)) {
+ auto *pList = (LIST<char>*)param;
+ pList->insert(mir_strdup(szSetting));
+ }
+ return 0;
+}
+
+void CMPlugin::SaveOptions()
+{
+ // remove old settings
+ LIST<char> settings(30);
+ db_enum_settings(0, ListSettings, MODULENAME, &settings);
+
+ for (auto &it : settings) {
+ delSetting(it);
+ mir_free(it);
+ }
+
+ // write new settings
+ setDword(so_flags, m_flags);
+ setWord(so_numcolumns, m_columns.getCount());
+
+ int i = 0;
+ for (auto &it : m_columns)
+ SaveColumn(i++, *it);
+}
+
+void CMPlugin::SaveColumn(int n, const ColumnItem &col)
+{
+ char buf[127];
+ int offset = mir_snprintf(buf, "%s%d_", so_item, n);
+
+ strcpy(buf + offset, so_title); setWString(buf, col.title);
+ strcpy(buf + offset, so_setting_type); setWord(buf, col.setting_type);
+ strcpy(buf + offset, so_flags); setWord(buf, col.dwFlags);
+ strcpy(buf + offset, so_width); setWord(buf, col.width);
+
+ switch (col.setting_type) {
+ case QST_SETTING:
+ strcpy(buf + offset, so_datatype); setWord(buf, col.datatype);
+ strcpy(buf + offset, so_module); setString(buf, col.module);
+ strcpy(buf + offset, so_setting); setString(buf, col.setting);
+ break;
+
+ case QST_SCRIPT:
+ strcpy(buf + offset, so_script); setWString(buf, col.script);
+ break;
+
+ case QST_CONTACTINFO:
+ strcpy(buf + offset, so_cnftype); setWord(buf, col.cnftype);
+ break;
+
+ case QST_SERVICE:
+ offset = mir_snprintf(buf, "%s%d/service/", so_item, n);
+ strcpy(buf + offset, so_service); setString(buf, col.svc.service);
+ strcpy(buf + offset, so_restype); setDword(buf, col.svc.flags);
+
+ strcpy(buf + offset, so_wparam);
+ SaveParamValue(buf, col.svc.wFlags, col.svc.wParam);
+
+ strcpy(buf + offset, so_lparam);
+ SaveParamValue(buf, col.svc.lFlags, col.svc.lParam);
+ break;
+
+ case QST_OTHER:
+ strcpy(buf + offset, so_other); setWord(buf, col.other);
+ break;
+ }
+}
+
+void CMPlugin::SaveParamValue(char *buf, DWORD flags, LPARAM value)
+{
+ char *pEnd = buf + strlen(buf);
+ strcpy(pEnd, "flags"); setDword(buf, flags);
+
+ strcpy(pEnd, "value");
+ switch (flags) {
+ case ACF_TYPE_NUMBER:
+ case ACF_TYPE_STRING:
+ case ACF_TYPE_UNICODE:
+ setWString(buf, (wchar_t *)value);
+ break;
+
+ case ACF_TYPE_STRUCT:
+ setString(buf, (char *)value);
+ break;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+const wchar_t* cnf2str(int cnf)
+{
+ switch (cnf) {
+ case CNF_FIRSTNAME: return TranslateT("FIRSTNAME");
+ case CNF_LASTNAME: return TranslateT("LASTNAME");
+ case CNF_NICK: return TranslateT("NICK");
+ case CNF_CUSTOMNICK: return TranslateT("CUSTOMNICK");
+ case CNF_EMAIL: return TranslateT("EMAIL");
+ case CNF_CITY: return TranslateT("CITY");
+ case CNF_STATE: return TranslateT("STATE");
+ case CNF_COUNTRY: return TranslateT("COUNTRY");
+ case CNF_PHONE: return TranslateT("PHONE");
+ case CNF_HOMEPAGE: return TranslateT("HOMEPAGE");
+ case CNF_ABOUT: return TranslateT("ABOUT");
+ case CNF_GENDER: return TranslateT("GENDER");
+ case CNF_AGE: return TranslateT("AGE");
+ case CNF_FIRSTLAST: return TranslateT("FIRSTLAST");
+ case CNF_UNIQUEID: return TranslateT("UNIQUEID");
+ case CNF_FAX: return TranslateT("FAX");
+ case CNF_CELLULAR: return TranslateT("CELLULAR");
+ case CNF_TIMEZONE: return TranslateT("TIMEZONE");
+ case CNF_MYNOTES: return TranslateT("MYNOTES");
+ case CNF_BIRTHDAY: return TranslateT("BIRTHDAY");
+ case CNF_BIRTHMONTH: return TranslateT("BIRTHMONTH");
+ case CNF_BIRTHYEAR: return TranslateT("BIRTHYEAR");
+ case CNF_STREET: return TranslateT("STREET");
+ case CNF_ZIP: return TranslateT("ZIP");
+ case CNF_LANGUAGE1: return TranslateT("LANGUAGE1");
+ case CNF_LANGUAGE2: return TranslateT("LANGUAGE2");
+ case CNF_LANGUAGE3: return TranslateT("LANGUAGE3");
+ case CNF_CONAME: return TranslateT("CONAME");
+ case CNF_CODEPT: return TranslateT("CODEPT");
+ case CNF_COPOSITION: return TranslateT("COPOSITION");
+ case CNF_COSTREET: return TranslateT("COSTREET");
+ case CNF_COCITY: return TranslateT("COCITY");
+ case CNF_COSTATE: return TranslateT("COSTATE");
+ case CNF_COZIP: return TranslateT("COZIP");
+ case CNF_COCOUNTRY: return TranslateT("COCOUNTRY");
+ case CNF_COHOMEPAGE: return TranslateT("COHOMEPAGE");
+ case CNF_DISPLAYUID: return TranslateT("DISPLAYUID");
+ }
+ return L"";
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// formatters
+
+wchar_t* BuildLastSeenTime(DWORD ts)
+{
+ int year = ts / (60 * 24 * 31 * 356);
+ if (year == 0)
+ return nullptr;
+
+ year += 1980; ts = ts % (60 * 24 * 31 * 356);
+
+ int month = ts / (60 * 24 * 31); ts = ts % (60 * 24 * 31);
+ int day = ts / (60 * 24); ts = ts % (60 * 24);
+ int hours = ts / 60;
+ int mins = ts % 60;
+
+ return CMStringW(FORMAT, L"%02d.%02d.%04d - %02d:%02d", year, month, day, hours, mins).Detach();
+}
+
+DWORD BuildLastSeenTimeInt(MCONTACT hContact, const char *szModule)
+{
+ int year = db_get_w(hContact, szModule, "Year");
+ if (year == 0)
+ return 0;
+
+ int day = db_get_w(hContact, szModule, "Day");
+ int month = db_get_w(hContact, szModule, "Month");
+ int hours = db_get_w(hContact, szModule, "Hours");
+ int minutes = db_get_w(hContact, szModule, "Minutes");
+
+ return ((((year - 1980) * 356 + month) * 31 + day) * 24 + hours) * 60 + minutes;
+}
+
+wchar_t* TimeToStrW(DWORD timestamp)
+{
+ wchar_t buf[63];
+ TimeZone_ToStringW(timestamp, L"d - t", buf, _countof(buf));
+ return mir_wstrdup(buf);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void SnapToScreen(RECT &rc)
+{
+ int left = GetSystemMetrics(SM_XVIRTUALSCREEN);
+ int top = GetSystemMetrics(SM_YVIRTUALSCREEN);
+ int right = GetSystemMetrics(SM_CXVIRTUALSCREEN) + left;
+ int bottom = GetSystemMetrics(SM_CYVIRTUALSCREEN) + top;
+ if (rc.right > right)
+ rc.right = right;
+ if (rc.bottom > bottom)
+ rc.bottom = bottom;
+ if (rc.left < left)
+ rc.left = left;
+ if (rc.top < top)
+ rc.top = top;
+}
diff --git a/plugins/QuickSearch/src/version.h b/plugins/QuickSearch/src/version.h
new file mode 100644
index 0000000000..f9508cadc2
--- /dev/null
+++ b/plugins/QuickSearch/src/version.h
@@ -0,0 +1,13 @@
+#define __MAJOR_VERSION 1
+#define __MINOR_VERSION 5
+#define __RELEASE_NUM 0
+#define __BUILD_NUM 1
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "Quick Search"
+#define __FILENAME "QuickSearch.dll"
+#define __DESCRIPTION "This plugin allows you to quick search for nickname, firstname, lastname, email, uin in your contact list."
+#define __AUTHOR "Bethoven, Awkward"
+#define __AUTHORWEB "https://miranda-ng.org/p/QuickSearch/"
+#define __COPYRIGHT "© 2004-05 Bethoven; 2006-13 Awkward; 2014-21 Miranda NG team"
diff --git a/plugins/QuickSearch/src/window.cpp b/plugins/QuickSearch/src/window.cpp
new file mode 100644
index 0000000000..63d4a652a8
--- /dev/null
+++ b/plugins/QuickSearch/src/window.cpp
@@ -0,0 +1,777 @@
+/*
+Copyright (C) 2012-21 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+#define IDM_STAYONTOP (WM_USER+1)
+
+static QSMainDlg *g_pDlg = 0;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int OnContactAdded(WPARAM, LPARAM)
+{
+ return 0;
+}
+
+static int OnContactDeleted(WPARAM, LPARAM)
+{
+ return 0;
+}
+
+static int OnStatusChanged(WPARAM, LPARAM)
+{
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// edit control window procedure
+
+static LRESULT CALLBACK sttNewEditProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ auto *pDlg = (QSMainDlg *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
+ if (pDlg)
+ if (INT_PTR res = pDlg->NewEditProc(msg, wParam, lParam))
+ return res;
+
+ return mir_callNextSubclass(hwnd, sttNewEditProc, msg, wParam, lParam);
+}
+
+INT_PTR QSMainDlg::NewEditProc(UINT msg, WPARAM wParam, LPARAM)
+{
+ switch (msg) {
+ case WM_CHAR:
+ if (wParam == 27) // Escape
+ PostMessage(m_hwnd, WM_COMMAND, IDCANCEL, 0);
+ break;
+
+ case WM_KEYUP:
+ if (wParam == VK_RETURN) {
+ if (m_grid.GetSelectedCount() == 1)
+ ShowContactMsgDlg(GetFocusedContact());
+ return 0;
+ }
+ break;
+
+ case WM_KEYDOWN:
+ int count = m_grid.GetItemCount();
+ int current = m_grid.GetNextItem(-1, LVNI_FOCUSED);
+ int next = -1;
+ if (count > 0) {
+ switch (wParam) {
+ case VK_UP:
+ if (current > 0)
+ next = current - 1;
+ break;
+
+ case VK_DOWN:
+ if (current < count - 1)
+ next = current + 1;
+ break;
+
+ case VK_F5:
+ onClick_Refresh(0);
+ return 0;
+
+ case VK_NEXT:
+ case VK_PRIOR:
+ int perpage = m_grid.GetCountPerPage();
+ if (wParam == VK_NEXT)
+ next = min(current + perpage, count);
+ else
+ next = max(current - perpage, 0);
+ break;
+ }
+ }
+
+ if (next >= 0) {
+ m_grid.SetItemState(-1, 0, LVIS_SELECTED);
+ m_grid.SetCurSel(next);
+ m_grid.EnsureVisible(next, FALSE);
+ }
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// list header window procedure
+
+static void MakeColumnMenu()
+{}
+
+static LRESULT CALLBACK sttNewLVHProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_RBUTTONUP:
+ return 0;
+
+ case WM_RBUTTONDOWN:
+ MakeColumnMenu();
+ break;
+ }
+
+ return mir_callNextSubclass(hwnd, sttNewLVHProc, msg, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// grid list window procedure
+
+static int OldHSubItem = 0, OldHItem = 0;
+
+static LRESULT CALLBACK sttNewLVProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ auto *pDlg = (QSMainDlg *)GetWindowLongPtrW(hwnd, GWLP_USERDATA);
+ if (pDlg)
+ if (INT_PTR res = pDlg->NewLVProc(msg, wParam, lParam))
+ return res;
+
+ return mir_callNextSubclass(hwnd, sttNewLVProc, msg, wParam, lParam);
+}
+
+INT_PTR QSMainDlg::NewLVProc(UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LV_HITTESTINFO pinfo;
+
+ switch (msg) {
+ case WM_CHAR:
+ switch (wParam) { // ESC
+ case 27:
+ Close();
+ break;
+
+ case 1: // Cltr+A
+ m_grid.SetItemState(-1, LVIS_SELECTED, LVIS_SELECTED);
+ break;
+
+ case 3: // Ctrl-C
+ CopyMultiLines();
+ break;
+
+ case 8: // backspace
+ if (pattern != nullptr) {
+ size_t len = mir_wstrlen(pattern);
+ pattern[len - 1] = 0;
+ edtFilter.SetText(pattern);
+ }
+ break;
+ }
+
+ if (wParam >= 32 && wParam <= 127) { // letters
+ CMStringW buf;
+ if (pattern)
+ buf = pattern;
+ buf.AppendChar(wParam);
+ edtFilter.SetText(buf);
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ pinfo.pt.x = LOWORD(lParam);
+ pinfo.pt.y = HIWORD(lParam);
+ pinfo.flags = 0;
+ if (m_grid.SubItemHitTest(&pinfo) == -1)
+ break;
+
+ if ((pinfo.flags & LVHT_ONITEM) && (pinfo.iItem != OldHItem || pinfo.iSubItem != OldHSubItem)) {
+ OldHSubItem = pinfo.iSubItem;
+ OldHItem = pinfo.iItem;
+
+ if (g_bTipperInstalled) {
+ if (TTShowed) {
+ TTShowed = false;
+ Tipper_Hide();
+ }
+ m_hover.Stop();
+
+ if (OldHSubItem == 0)
+ m_hover.Start(450);
+ }
+
+ TOOLINFOW ti = {};
+ ti.cbSize = sizeof(ti);
+ ti.uFlags = TTF_SUBCLASS + TTF_IDISHWND;
+ ti.hwnd = m_hwnd;
+ ti.uId = LPARAM(m_hwnd);
+
+ int num = ListViewToColumn(OldHSubItem);
+ auto &pCol = g_plugin.m_columns[num];
+ if (pCol.isXstatus || pCol.isGender) {
+ auto *pRow = GetRow(OldHItem);
+ if (pCol.isGender) {
+ switch (pRow->pValues[num].data) {
+ case 'M': ti.lpszText = TranslateT("Male"); break;
+ case 'F': ti.lpszText = TranslateT("Female"); break;
+ default: ti.lpszText = TranslateT("Unknown"); break;
+ }
+ }
+ else {
+ wchar_t buf[256];
+ mir_wstrncpy(buf, pRow->pValues[num].text, _countof(buf));
+ int iStatus = _wtoi(buf);
+
+ CUSTOM_STATUS ics = {};
+ ics.cbSize = sizeof(ics);
+ ics.status = &iStatus;
+ ics.flags = CSSF_DEFAULT_NAME | CSSF_MASK_NAME | CSSF_UNICODE;
+ ics.pwszName = buf;
+ CallProtoService(pRow->szProto, PS_GETCUSTOMSTATUSEX);
+ ti.lpszText = TranslateW(buf);
+ }
+ }
+
+ SendMessageW(HintWnd, TTM_SETTOOLINFOW, 0, LPARAM(&ti));
+ }
+ break;
+
+ case WM_KEYUP:
+ switch (wParam) {
+ case VK_RETURN:
+ if (m_grid.GetSelectedCount() == 1)
+ ShowContactMsgDlg(GetFocusedContact());
+ break;
+
+ case VK_INSERT:
+ CallService(MS_FINDADD_FINDADD, 0, 0);
+ break;
+
+ case VK_DELETE:
+ lParam = m_grid.GetSelectedCount();
+ if (lParam > 1)
+ DeleteByList();
+ else if (lParam == 1)
+ DeleteOneContact(GetFocusedContact());
+ break;
+
+ case VK_F5:
+ onClick_Refresh(0);
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->code == HDN_ITEMSTATEICONCLICK) {
+ NMHEADER *pdhr = (NMHEADER *)lParam;
+ if ((pdhr->pitem->mask & HDI_FORMAT) && (pdhr->pitem->fmt & HDF_CHECKBOX)) {
+ int i = ListViewToColumn(pdhr->iItem);
+ auto &pCol = g_plugin.m_columns[i];
+
+ if (pdhr->pitem->fmt & HDF_CHECKED) {
+ pCol.bFilter = false;
+ pdhr->pitem->fmt &= ~HDF_CHECKED;
+ }
+ else {
+ pCol.bFilter = true;
+ pdhr->pitem->fmt |= HDF_CHECKED;
+ }
+
+ SendMessage(pdhr->hdr.hwndFrom, HDM_SETITEM, pdhr->iItem, LPARAM(pdhr->pitem));
+ FillGrid();
+ }
+ }
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// QSMainDlg class implementation
+
+static int CompareSb(const CStatusBarItem *p1, const CStatusBarItem *p2)
+{
+ if (p1->bAccDel != p2->bAccDel)
+ return (p1->bAccDel) ? 1 : -1;
+
+ if (p1->bAccOff != p2->bAccOff)
+ return (p1->bAccOff) ? 1 : -1;
+
+ return mir_strcmp(p1->szProto, p2->szProto);
+}
+
+QSMainDlg::QSMainDlg(const wchar_t *pwszPattern) :
+ CDlgBase(g_plugin, IDD_MAIN),
+ m_rows(50),
+ m_sbdata(10, CompareSb),
+ m_grid(this, IDC_LIST),
+ m_hover(this, 10),
+ cmbProto(this, IDC_CB_PROTOCOLS),
+ edtFilter(this, IDC_E_SEARCHTEXT),
+ btnRefresh(this, IDC_REFRESH),
+ chkColorize(this, IDC_CH_COLORIZE),
+ chkShowOffline(this, IDC_CH_SHOWOFFLINE)
+{
+ SetMinSize(300, 160);
+
+ if (pwszPattern) {
+ pattern = mir_wstrdup(pwszPattern);
+ CharLowerW(pattern);
+ }
+ else if (g_plugin.m_flags & QSO_SAVEPATTERN)
+ pattern = g_plugin.getWStringA("pattern");
+ else
+ pattern = nullptr;
+
+ m_hover.OnEvent = Callback(this, &QSMainDlg::onTimer_Hover);
+
+ m_grid.OnBuildMenu = Callback(this, &QSMainDlg::onBuildMenu_Grid);
+ m_grid.OnColumnClick = Callback(this, &QSMainDlg::onColumnClick_Grid);
+ m_grid.OnCustomDraw = Callback(this, &QSMainDlg::onCustomDraw_Grid);
+
+ btnRefresh.OnClick = Callback(this, &QSMainDlg::onClick_Refresh);
+
+ cmbProto.OnSelChanged = Callback(this, &QSMainDlg::onSelChange_Proto);
+
+ edtFilter.OnChange = Callback(this, &QSMainDlg::onChange_Filter);
+ chkColorize.OnChange = Callback(this, &QSMainDlg::onChange_Colorize);
+ chkShowOffline.OnChange = Callback(this, &QSMainDlg::onChange_ShowOffline);
+}
+
+bool QSMainDlg::OnInitDialog()
+{
+ g_pDlg = this;
+ mnuhandle = 0;
+
+ SetCaption(TranslateT("Quick Search"));
+
+ hwndStatusBar = GetDlgItem(m_hwnd, IDC_STATUSBAR);
+
+ HMENU smenu = GetSystemMenu(m_hwnd, false);
+ InsertMenu(smenu, 5, MF_BYPOSITION | MF_SEPARATOR, 0, nullptr);
+ InsertMenuW(smenu, 6, MF_BYPOSITION | MF_STRING, IDM_STAYONTOP, TranslateT("Stay on Top"));
+
+ if (g_plugin.m_flags & QSO_STAYONTOP) {
+ CheckMenuItem(smenu, IDM_STAYONTOP, MF_BYCOMMAND | MF_CHECKED);
+ SetWindowPos(m_hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+ }
+
+ CheckDlgButton(m_hwnd, IDC_CH_SHOWOFFLINE, (g_plugin.m_flags & QSO_SHOWOFFLINE) != 0);
+
+ szFilterProto = nullptr; // display all protocols
+ if (g_plugin.m_flags & QSO_SHOWOFFLINE)
+ bShowOffline = true;
+
+ CheckDlgButton(m_hwnd, IDC_CH_COLORIZE, (g_plugin.m_flags & QSO_COLORIZE) != 0);
+
+ // Window
+ INT_PTR tmp = GetWindowLongPtrW(m_hwnd, GWL_EXSTYLE);
+ if (g_plugin.m_flags & QSO_TOOLSTYLE)
+ tmp |= WS_EX_TOOLWINDOW;
+ else
+ tmp &= ~WS_EX_TOOLWINDOW;
+ SetWindowLongPtrW(m_hwnd, GWL_EXSTYLE, tmp);
+
+ SendMessage(m_hwnd, WM_SETICON, ICON_SMALL, (LPARAM)g_plugin.getIcon(IDI_QS));
+
+ // ListView
+ m_grid.SetImageList(Clist_GetImageList(), LVSIL_SMALL);
+
+ tmp = LVS_EX_FULLROWSELECT | LVS_EX_SUBITEMIMAGES | LVS_EX_HEADERDRAGDROP |
+ LVS_EX_LABELTIP | LVS_EX_DOUBLEBUFFER;
+ if (g_plugin.m_flags & QSO_DRAWGRID)
+ tmp |= LVS_EX_GRIDLINES;
+ m_grid.SetExtendedListViewStyle(tmp);
+
+ // ListView header
+ HWND header = m_grid.GetHeader();
+ SetWindowLongPtrW(header, GWL_STYLE, GetWindowLongPtrW(header, GWL_STYLE) | HDS_CHECKBOXES);
+
+ mir_subclassWindow(edtFilter.GetHwnd(), &sttNewEditProc);
+
+ SetWindowLongPtrW(m_grid.GetHeader(), GWLP_USERDATA, LPARAM(this));
+ mir_subclassWindow(m_grid.GetHeader(), &sttNewLVHProc);
+
+ SetWindowLongPtrW(m_grid.GetHwnd(), GWLP_USERDATA, LPARAM(this));
+ mir_subclassWindow(m_grid.GetHwnd(), &sttNewLVProc);
+
+ FillProtoCombo();
+
+ PrepareTable();
+
+ if (pattern != nullptr)
+ SetDlgItemTextW(m_hwnd, IDC_E_SEARCHTEXT, pattern);
+ else {
+ SetDlgItemTextW(m_hwnd, IDC_E_SEARCHTEXT, L"");
+ FillGrid();
+ }
+
+ // Show sorting column
+ HDITEM hdi = {};
+ hdi.mask = HDI_FORMAT;
+ SendMessageW(header, HDM_GETITEM, g_plugin.m_sortOrder, LPARAM(&hdi));
+ if (g_plugin.m_flags & QSO_SORTASC)
+ hdi.fmt |= HDF_SORTUP;
+ else
+ hdi.fmt |= HDF_SORTDOWN;
+ SendMessageW(header, HDM_SETITEM, g_plugin.m_sortOrder, LPARAM(&hdi));
+
+ RECT rc = g_plugin.m_rect;
+ ::SnapToScreen(rc);
+ ::MoveWindow(m_hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, false);
+
+ HintWnd = CreateWindowExW(0, TOOLTIPS_CLASS, nullptr, 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hwnd, 0, g_plugin.getInst(), 0);
+
+ TOOLINFOW ti;
+ ti.cbSize = sizeof(ti);
+ ti.uFlags = TTF_SUBCLASS + TTF_IDISHWND;
+ ti.hwnd = m_hwnd;
+ ti.uId = UINT_PTR(m_grid.GetHwnd());
+ SendMessageW(HintWnd, TTM_ADDTOOLW, 0, LPARAM(&ti));
+
+ hAdd = HookEvent(ME_DB_CONTACT_ADDED, &OnContactAdded);
+ hDelete = HookEvent(ME_DB_CONTACT_DELETED, &OnContactDeleted);
+ hChange = HookEvent(ME_CLIST_CONTACTICONCHANGED, &OnStatusChanged);
+ return true;
+}
+
+void QSMainDlg::OnDestroy()
+{
+ if (mnuhandle)
+ Menu_RemoveItem(mnuhandle);
+
+ UnhookEvent(hAdd);
+ UnhookEvent(hDelete);
+ UnhookEvent(hChange);
+
+ g_pDlg = nullptr;
+
+ RECT rc;
+ GetWindowRect(m_hwnd, &rc);
+ CopyRect(&g_plugin.m_rect, &rc);
+
+ // save column width/order
+ SaveColumnOrder();
+
+ g_plugin.SaveOptWnd();
+
+ m_grid.SetImageList(0, LVSIL_SMALL);
+
+ if (g_plugin.m_flags & QSO_SAVEPATTERN)
+ g_plugin.setWString("pattern", pattern);
+
+ mir_free(patstr);
+ mir_free(pattern);
+
+ m_rows.destroy();
+}
+
+int QSMainDlg::Resizer(UTILRESIZECONTROL *urc)
+{
+ switch (urc->wId) {
+ case IDCANCEL:
+ case IDC_REFRESH:
+ return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP;
+
+ case IDC_E_SEARCHTEXT:
+ return RD_ANCHORX_WIDTH | RD_ANCHORY_TOP;
+
+ case IDC_LIST:
+ return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT;
+
+ case IDC_STATUSBAR:
+ return RD_ANCHORX_WIDTH | RD_ANCHORY_BOTTOM;
+ }
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP;
+}
+
+INT_PTR QSMainDlg::OnSysCommand(UINT, WPARAM wParam, LPARAM)
+{
+ if (wParam == IDM_STAYONTOP) {
+ int h; HWND w;
+ if (g_plugin.m_flags & QSO_STAYONTOP) {
+ h = MF_BYCOMMAND | MF_UNCHECKED;
+ w = HWND_NOTOPMOST;
+ }
+ else {
+ h = MF_BYCOMMAND | MF_CHECKED;
+ w = HWND_TOPMOST;
+ }
+ CheckMenuItem(GetSystemMenu(m_hwnd, false), IDM_STAYONTOP, h);
+ SetWindowPos(m_hwnd, w, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+ g_plugin.m_flags ^= QSO_STAYONTOP;
+ }
+ return 0;
+}
+
+INT_PTR QSMainDlg::OnMouseMove(UINT, WPARAM, LPARAM lParam)
+{
+ if (g_bTipperInstalled) {
+ RECT rc;
+ GetWindowRect(m_grid.GetHwnd(), &rc);
+
+ POINT pt = { LOWORD(lParam), HIWORD(lParam) };
+ ClientToScreen(m_hwnd, &pt);
+ if (!PtInRect(&rc, pt)) {
+ if (TTShowed) {
+ TTShowed = false;
+ CallService(MS_TIPPER_HIDETIP, 0, 0);
+ }
+ }
+
+ m_hover.Stop();
+ }
+ return 0;
+}
+
+INT_PTR QSMainDlg::OnKeydown(UINT, WPARAM wParam, LPARAM)
+{
+ if (wParam == VK_F5)
+ PostMessage(m_hwnd, WM_COMMAND, IDC_REFRESH, 0);
+ return 0;
+}
+
+void QSMainDlg::onBuildMenu_Grid(CContextMenuPos *pos)
+{
+ int w = m_grid.GetSelectedCount();
+ if (w > 1)
+ ShowMultiPopup(w);
+ else
+ ShowContactMenu(GetFocusedContact(), GetLVSubItem(pos->pt.x, pos->pt.y));
+}
+
+void QSMainDlg::onSelChange_Proto(CCtrlCombo *)
+{
+ LPARAM lParam = cmbProto.GetItemData(cmbProto.GetCurSel());
+ if (lParam == -1 || lParam == 0)
+ szFilterProto = nullptr;
+ else
+ szFilterProto = ((PROTOACCOUNT *)lParam)->szModuleName;
+
+ AdvancedFilter();
+}
+
+void QSMainDlg::onChange_Filter(CCtrlEdit *)
+{
+ if (!m_bInitialized)
+ return;
+
+ wchar_t buf[256];
+ GetDlgItemTextW(m_hwnd, IDC_E_SEARCHTEXT, buf, _countof(buf));
+ CharLowerW(buf);
+ replaceStrW(pattern, (buf[0]) ? buf : nullptr);
+ FillGrid();
+}
+
+void QSMainDlg::onChange_ShowOffline(CCtrlCheck *)
+{
+ if (chkShowOffline.IsChecked()) {
+ g_plugin.m_flags |= QSO_SHOWOFFLINE;
+ bShowOffline = true;
+ }
+ else {
+ g_plugin.m_flags &= ~QSO_SHOWOFFLINE;
+ bShowOffline = false;
+ }
+
+ AdvancedFilter();
+}
+
+void QSMainDlg::onChange_Colorize(CCtrlCheck *)
+{
+ if (IsDlgButtonChecked(m_hwnd, IDC_CH_COLORIZE))
+ g_plugin.m_flags &= ~QSO_COLORIZE;
+ else
+ g_plugin.m_flags |= QSO_COLORIZE;
+ RedrawWindow(m_grid.GetHwnd(), nullptr, 0, RDW_INVALIDATE);
+}
+
+void QSMainDlg::onClick_Refresh(CCtrlButton *)
+{
+ m_rows.destroy();
+ PrepareToFill();
+ PrepareTable(true);
+ FillGrid();
+}
+
+void QSMainDlg::onColumnClick_Grid(CCtrlListView::TEventInfo *ev)
+{
+ HWND header = m_grid.GetHeader();
+
+ // clear sort mark
+ HDITEM hdi = {};
+ hdi.mask = HDI_FORMAT;
+ SendMessage(header, HDM_GETITEM, g_plugin.m_sortOrder, LPARAM(&hdi));
+ hdi.fmt &= ~(HDF_SORTDOWN | HDF_SORTUP);
+ SendMessage(header, HDM_SETITEM, g_plugin.m_sortOrder, LPARAM(&hdi));
+
+ if (g_plugin.m_sortOrder != ev->nmlv->iSubItem) {
+ g_plugin.m_flags |= QSO_SORTASC;
+ g_plugin.m_sortOrder = ev->nmlv->iSubItem;
+ }
+ else g_plugin.m_flags ^= QSO_SORTASC;;
+
+ // set new sort mark
+ SendMessage(header, HDM_GETITEM, g_plugin.m_sortOrder, LPARAM(&hdi));
+ if ((g_plugin.m_flags & QSO_SORTASC) == 0)
+ hdi.fmt |= HDF_SORTDOWN;
+ else
+ hdi.fmt &= ~HDF_SORTUP;
+ SendMessage(header, HDM_SETITEM, g_plugin.m_sortOrder, LPARAM(&hdi));
+
+ Sort();
+}
+
+void QSMainDlg::onDblClick_Grid(CCtrlListView::TEventInfo*)
+{
+ ShowContactMsgDlg(GetFocusedContact());
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void QSMainDlg::onCustomDraw_Grid(CCtrlListView::TEventInfo *ev)
+{
+ LPNMLVCUSTOMDRAW lplvcd = ev->nmcd;
+ HICON h;
+ RECT rc;
+ auto *pRow = (CRowItem *)lplvcd->nmcd.lItemlParam;
+
+ int result = CDRF_DODEFAULT;
+ switch (lplvcd->nmcd.dwDrawStage) {
+ case CDDS_PREPAINT:
+ result = CDRF_NOTIFYITEMDRAW;
+ break;
+
+ case CDDS_ITEMPREPAINT:
+ pRow->GetCellColor(lplvcd->nmcd.dwItemSpec, lplvcd->clrTextBk, lplvcd->clrText);
+ result = CDRF_NOTIFYSUBITEMDRAW;
+ break;
+
+ case CDDS_SUBITEM + CDDS_ITEMPREPAINT:
+ pRow->GetCellColor(lplvcd->nmcd.dwItemSpec, lplvcd->clrTextBk, lplvcd->clrText);
+ {
+ int sub = ListViewToColumn(lplvcd->iSubItem);
+ auto *pCol = &g_plugin.m_columns[sub];
+ if (pCol == nullptr)
+ break;
+
+ if (pCol->isGender) {
+ m_grid.GetSubItemRect(lplvcd->nmcd.dwItemSpec, lplvcd->iSubItem, LVIR_ICON, &rc);
+
+ switch (pRow->pValues[sub].data) {
+ case 'F': h = g_plugin.getIcon(IDI_FEMALE); break;
+ case 'M': h = g_plugin.getIcon(IDI_MALE); break;
+ default: h = 0;
+ }
+
+ if (h)
+ DrawIconEx(lplvcd->nmcd.hdc, rc.left + 1, rc.top, h, 16, 16, 0, 0, DI_NORMAL);
+ result = CDRF_SKIPDEFAULT;
+ }
+ else if (pCol->isXstatus) {
+ int j = _wtoi(pRow->pValues[sub].text);
+ if (j > 0 && ProtoServiceExists(pRow->szProto, PS_GETCUSTOMSTATUSICON)) {
+ h = (HICON)CallProtoService(pRow->szProto, PS_GETCUSTOMSTATUSICON, j, LR_SHARED);
+ m_grid.GetSubItemRect(lplvcd->nmcd.dwItemSpec, lplvcd->iSubItem, LVIR_ICON, &rc);
+ DrawIconEx(lplvcd->nmcd.hdc, rc.left + 1, rc.top, h, 16, 16, 0, 0, DI_NORMAL);
+ }
+ result = CDRF_SKIPDEFAULT;
+ }
+ else if ((g_plugin.m_flags & QSO_CLIENTICONS) && pCol->isClient)
+ result = CDRF_NOTIFYPOSTPAINT;
+ }
+ break;
+
+ case CDDS_SUBITEM + CDDS_ITEMPOSTPAINT:
+ {
+ int sub = ListViewToColumn(lplvcd->iSubItem);
+ auto *pCol = &g_plugin.m_columns[sub];
+ if (pCol == nullptr)
+ break;
+
+ if (pCol->isClient) {
+ auto *MirVerW = pRow->pValues[sub].text;
+ if (MirVerW && *MirVerW && g_bFingerInstalled) {
+ h = Finger_GetClientIcon(MirVerW, FALSE);
+ m_grid.GetSubItemRect(lplvcd->nmcd.dwItemSpec, lplvcd->iSubItem, LVIR_ICON, &rc);
+ DrawIconEx(lplvcd->nmcd.hdc, rc.left + 1, rc.top, h, 16, 16, 0, 0, DI_NORMAL);
+ DestroyIcon(h);
+ }
+ }
+ result = CDRF_SKIPDEFAULT;
+ }
+ break;
+ }
+
+ SetWindowLongPtrW(m_hwnd, DWLP_MSGRESULT, result);
+}
+
+void QSMainDlg::onTimer_Hover(CTimer *pTimer)
+{
+ pTimer->Stop();
+
+ if (GetForegroundWindow() != m_hwnd)
+ return;
+
+ auto *pRow = GetRow(OldHItem);
+ if (pRow == 0)
+ return;
+
+ POINT pt;
+ GetCursorPos(&pt);
+
+ RECT rcItem;
+ m_grid.GetItemRect(OldHItem, &rcItem, 0);
+ ScreenToClient(m_grid.GetHwnd(), &pt);
+ if (!PtInRect(&rcItem, pt))
+ return;
+
+ CLCINFOTIP info = {};
+ info.cbSize = sizeof(info);
+ info.hItem = HANDLE(pRow->hContact);
+ Tipper_ShowTip(0, &info);
+ TTShowed = true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CloseSrWindow(bool)
+{
+ if (!g_pDlg)
+ return false;
+
+ g_pDlg->Close();
+ return true;
+}
+
+int OpenSrWindow(const wchar_t *pwszPattern)
+{
+ if (g_pDlg) {
+ WINDOWPLACEMENT wp;
+ wp.length = sizeof(wp);
+ GetWindowPlacement(g_pDlg->GetHwnd(), &wp);
+ if (wp.showCmd == SW_SHOWMINIMIZED)
+ g_pDlg->Show(SW_RESTORE);
+ SetForegroundWindow(g_pDlg->GetHwnd());
+ return true;
+ }
+
+ int count = 0;
+ for (auto &it : g_plugin.m_columns)
+ if (it->bEnabled)
+ count++;
+
+ // no even one visible column
+ if (count == 0)
+ return true;
+
+ g_plugin.LoadOptWnd();
+
+ auto *pDlg = new QSMainDlg(pwszPattern);
+ if (pDlg->PrepareToFill())
+ pDlg->Create();
+ else
+ delete pDlg;
+
+ return true;
+}
diff --git a/plugins/QuickSearch/src/window_misc.cpp b/plugins/QuickSearch/src/window_misc.cpp
new file mode 100644
index 0000000000..4e79febb1f
--- /dev/null
+++ b/plugins/QuickSearch/src/window_misc.cpp
@@ -0,0 +1,809 @@
+/*
+Copyright (C) 2012-21 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// patterns
+
+bool QSMainDlg::CheckPattern(CRowItem *)
+{
+ return true;
+}
+
+void QSMainDlg::MakePattern()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void QSMainDlg::AddColumn(int idx, ColumnItem *pCol)
+{
+ LV_COLUMN lvcol = {};
+ lvcol.mask = LVCF_TEXT | LVCF_WIDTH;
+ lvcol.pszText = TranslateW(pCol->title);
+ lvcol.cx = pCol->width;
+ m_grid.InsertColumn(idx, &lvcol);
+
+ HDITEM hdi;
+ hdi.mask = HDI_FORMAT;
+ if (pCol->bFilter)
+ hdi.fmt = HDF_LEFT | HDF_STRING | HDF_CHECKBOX | HDF_CHECKED;
+ else
+ hdi.fmt = HDF_LEFT | HDF_STRING | HDF_CHECKBOX;
+ SendMessage(m_grid.GetHeader(), HDM_SETITEM, idx, LPARAM(&hdi));
+}
+
+void QSMainDlg::AddContactToList(MCONTACT hContact, CRowItem *pRow)
+{
+ LV_ITEMW li = {};
+ li.mask = LVIF_IMAGE | LVIF_PARAM;
+ li.iItem = 100000;
+ li.iImage = Clist_GetContactIcon(hContact);
+ 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;
+ if ((col.isClient && (g_plugin.m_flags & QSO_CLIENTICONS) && li.pszText != 0) || col.isXstatus || col.isGender)
+ li.mask |= LVIF_IMAGE;
+ m_grid.SetItem(&li);
+ li.iSubItem++;
+ }
+}
+
+void QSMainDlg::AdvancedFilter()
+{
+ m_grid.SendMsg(WM_SETREDRAW, FALSE, 0);
+
+ 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.SendMsg(WM_SETREDRAW, TRUE, 0);
+ InvalidateRect(m_grid.GetHwnd(), 0, false);
+
+ Sort();
+ UpdateSB();
+}
+
+void QSMainDlg::CopyMultiLines()
+{
+ CMStringW buf;
+
+ int i = 0;
+ for (auto &it : g_plugin.m_columns) {
+ it->width = m_grid.GetColumnWidth(i++);
+ if (it->width >= 10)
+ buf.AppendFormat(L"%s\t", it->title);
+ }
+ buf.Append(L"\r\n");
+
+ int nRows = m_grid.GetItemCount();
+ for (int j = 0; j < nRows; j++) {
+ auto *pRow = GetRow(j);
+
+ i = 0;
+ for (auto &it : g_plugin.m_columns) {
+ if (it->width >= 10)
+ buf.AppendFormat(L"%s\t", pRow->pValues[i].text);
+ i++;
+ }
+ buf.Append(L"\r\n");
+ }
+
+ if (OpenClipboard(m_hwnd)) {
+ EmptyClipboard();
+ if (HGLOBAL hData = GlobalAlloc(GMEM_MOVEABLE, (buf.GetLength() + 1) * sizeof(wchar_t))) {
+ mir_wstrcpy((wchar_t *)GlobalLock(hData), buf);
+ GlobalUnlock(hData);
+ SetClipboardData(CF_UNICODETEXT, hData);
+ }
+ CloseClipboard();
+ }
+}
+
+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.SendMsg(WM_SETREDRAW, FALSE, 0);
+
+ 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.SendMsg(WM_SETREDRAW, TRUE, 0);
+}
+
+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<int> 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.SendMsg(WM_SETREDRAW, FALSE, 0);
+
+ MakePattern();
+
+ for (auto &it: m_rows)
+ ProcessLine(it);
+
+ m_grid.SendMsg(WM_SETREDRAW, TRUE, 0);
+ 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 + tableColumns - 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) {
+ wchar_t buf[100];
+ if (iRes == 400) // default container, just delete setting
+ buf[0] = 0;
+ else
+ GetMenuStringW(cntmenu, iRes, buf, _countof(buf), MF_BYCOMMAND);
+
+ MoveToGroup(buf);
+ }
+}
+
+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();
+}
diff --git a/plugins/QuickSearch/src/window_row.cpp b/plugins/QuickSearch/src/window_row.cpp
new file mode 100644
index 0000000000..16427be551
--- /dev/null
+++ b/plugins/QuickSearch/src/window_row.cpp
@@ -0,0 +1,222 @@
+/*
+Copyright (C) 2012-21 Miranda NG team (https://miranda-ng.org)
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation version 2
+of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h"
+
+CRowItem::CRowItem(MCONTACT _1, QSMainDlg *pDlg) :
+ hContact(_1)
+{
+ auto *pa = Proto_GetContactAccount(hContact);
+ if (pa != nullptr) {
+ szProto = pa->szModuleName;
+ if (!pa->IsEnabled())
+ bAccOff = true;
+
+ if (db_mc_isMeta(hContact))
+ bIsMeta = true;
+ else if (db_mc_isSub(hContact))
+ bIsSub = true;
+ }
+ else {
+ szProto = nullptr;
+ bAccDel = true;
+ }
+
+ if (bAccDel || bAccOff)
+ status = ID_STATUS_OFFLINE;
+ else
+ status = Contact_GetStatus(hContact);
+
+ if (int nCount = g_plugin.m_columns.getCount()) {
+ pValues = new Val[nCount];
+ for (int i = 0; i < nCount; i++)
+ pValues[i].LoadOneItem(hContact, g_plugin.m_columns[i], pDlg);
+ }
+ else pValues = nullptr;
+}
+
+CRowItem::~CRowItem()
+{
+ delete[] pValues;
+}
+
+void CRowItem::GetCellColor(int idx, COLORREF &clrBack, COLORREF &clrText)
+{
+ if (g_plugin.m_flags & QSO_COLORIZE) {
+ if (bAccDel) {
+ clrBack = g_plugin.m_colors[bkg_del];
+ clrText = g_plugin.m_colors[fgr_del];
+ return;
+ }
+ if (bAccOff) {
+ clrBack = g_plugin.m_colors[bkg_dis];
+ clrText = g_plugin.m_colors[fgr_dis];
+ return;
+ }
+ if (bIsMeta) {
+ clrBack = g_plugin.m_colors[bkg_meta];
+ clrText = g_plugin.m_colors[fgr_meta];
+ return;
+ }
+ if (bIsSub) {
+ clrBack = g_plugin.m_colors[bkg_sub];
+ clrText = g_plugin.m_colors[fgr_sub];
+ return;
+ }
+ if (bInList) {
+ clrBack = g_plugin.m_colors[bkg_hid];
+ clrText = g_plugin.m_colors[fgr_hid];
+ return;
+ }
+ }
+
+ if ((g_plugin.m_flags & QSO_DRAWGRID) == 0 && idx % 2 == 1) {
+ clrBack = g_plugin.m_colors[bkg_odd];
+ clrText = g_plugin.m_colors[fgr_odd];
+ }
+ else {
+ clrBack = g_plugin.m_colors[bkg_norm];
+ clrText = g_plugin.m_colors[fgr_norm];
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static wchar_t* int2strw(DWORD num)
+{
+ wchar_t buf[64];
+ _itow_s(num, buf, 10);
+ return mir_wstrdup(buf);
+}
+
+static wchar_t* hex2strw(DWORD num)
+{
+ wchar_t buf[64];
+ _itow_s(num, buf, 16);
+ return mir_wstrdup(buf);
+}
+
+void CRowItem::Val::LoadOneItem(MCONTACT hContact, const ColumnItem &pCol, QSMainDlg *pDlg)
+{
+ data = UINT_PTR(-1);
+ replaceStrW(text, nullptr);
+
+ switch (pCol.setting_type) {
+ case QST_SCRIPT:
+ {
+ VARSW vars(pCol.script);
+ if (g_bVarsInstalled)
+ text = variables_parse(vars, 0, hContact);
+ else
+ text = vars.detach();
+ }
+ break;
+
+ case QST_SERVICE:
+ // !!!!!!!!!!!!!!!!!!! not implemented
+ break;
+
+ case QST_CONTACTINFO:
+ text = Contact_GetInfo(pCol.cnftype, hContact);
+ break;
+
+ case QST_OTHER:
+ switch (pCol.other) {
+ case QSTO_ACCOUNT:
+ if (auto *pa = Proto_GetContactAccount(hContact))
+ text = mir_wstrdup(pa->tszAccountName);
+ break;
+
+ case QSTO_LASTSEEN:
+ data = BuildLastSeenTimeInt(hContact, "SeenModule");
+ text = BuildLastSeenTime(data);
+ break;
+
+ case QSTO_DISPLAYNAME:
+ text = mir_wstrdup(Clist_GetContactDisplayName(hContact, 0));
+ break;
+
+ case QSTO_LASTEVENT:
+ if (MEVENT hDbEvent = db_event_last(hContact)) {
+ DBEVENTINFO dbei = {};
+ db_event_get(hDbEvent, &dbei);
+ data = dbei.timestamp;
+ text = TimeToStrW(data);
+ }
+ else text = 0;
+ break;
+
+ case QSTO_METACONTACT:
+ text = pDlg->DoMeta(hContact);
+ break;
+
+ case QSTO_EVENTCOUNT:
+ data = db_event_count(hContact);
+ text = int2strw(data);
+ break;
+ }
+ break;
+
+ case QST_SETTING:
+ auto *szNodule = pCol.module;
+ if (!mir_strlen(szNodule))
+ szNodule = Proto_GetBaseAccountName(hContact);
+
+ switch (pCol.datatype) {
+ case QSTS_STRING:
+ text = db_get_wsa(hContact, szNodule, pCol.setting);
+ break;
+
+ case QSTS_BYTE:
+ data = db_get_b(hContact, szNodule, pCol.setting);
+ text = int2strw(data);
+ break;
+
+ case QSTS_WORD:
+ data = db_get_w(hContact, szNodule, pCol.setting);
+ text = int2strw(data);
+ break;
+
+ case QSTS_DWORD:
+ if (pCol.setting == nullptr) {
+ data = hContact;
+ text = hex2strw(data);
+ }
+ else {
+ data = db_get_dw(hContact, szNodule, pCol.setting);
+ text = int2strw(data);
+ }
+ break;
+
+ case QSTS_SIGNED:
+ data = db_get_dw(hContact, szNodule, pCol.setting);
+ text = int2strw(data);
+ break;
+
+ case QSTS_HEXNUM:
+ data = db_get_dw(hContact, szNodule, pCol.setting);
+ text = hex2strw(data);
+ break;
+
+ case QSTS_TIMESTAMP:
+ data = db_get_dw(hContact, szNodule, pCol.setting);
+ if (data != 0)
+ text = TimeToStrW(data);
+ break;
+ }
+ }
+}