summaryrefslogtreecommitdiff
path: root/src/modules/clist
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/clist')
-rw-r--r--src/modules/clist/Docking.cpp392
-rw-r--r--src/modules/clist/clc.cpp1350
-rw-r--r--src/modules/clist/clc.h248
-rw-r--r--src/modules/clist/clcfiledrop.cpp278
-rw-r--r--src/modules/clist/clcidents.cpp201
-rw-r--r--src/modules/clist/clcitems.cpp707
-rw-r--r--src/modules/clist/clcmsgs.cpp472
-rw-r--r--src/modules/clist/clcutils.cpp879
-rw-r--r--src/modules/clist/clistcore.cpp224
-rw-r--r--src/modules/clist/clistevents.cpp441
-rw-r--r--src/modules/clist/clistmenus.cpp1443
-rw-r--r--src/modules/clist/clistmod.cpp569
-rw-r--r--src/modules/clist/clistsettings.cpp331
-rw-r--r--src/modules/clist/clisttray.cpp980
-rw-r--r--src/modules/clist/clui.cpp1136
-rw-r--r--src/modules/clist/cluiservices.cpp221
-rw-r--r--src/modules/clist/contact.cpp193
-rw-r--r--src/modules/clist/genmenu.cpp1294
-rw-r--r--src/modules/clist/genmenu.h146
-rw-r--r--src/modules/clist/genmenuopt.cpp870
-rw-r--r--src/modules/clist/groups.cpp577
-rw-r--r--src/modules/clist/keyboard.cpp173
-rw-r--r--src/modules/clist/movetogroup.cpp160
-rw-r--r--src/modules/clist/protocolorder.cpp351
24 files changed, 13636 insertions, 0 deletions
diff --git a/src/modules/clist/Docking.cpp b/src/modules/clist/Docking.cpp
new file mode 100644
index 0000000000..8dd61cdc37
--- /dev/null
+++ b/src/modules/clist/Docking.cpp
@@ -0,0 +1,392 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+#define WM_DOCKCALLBACK (WM_USER+121)
+#define EDGESENSITIVITY 3
+
+#define DOCKED_NONE 0
+#define DOCKED_LEFT 1
+#define DOCKED_RIGHT 2
+
+static char docked;
+static POINT dockPos;
+
+static void Docking_GetMonitorRectFromPoint(LPPOINT pt, LPRECT rc)
+{
+ if (MyMonitorFromPoint)
+ {
+ MONITORINFO monitorInfo;
+ HMONITOR hMonitor = MyMonitorFromPoint(*pt, MONITOR_DEFAULTTONEAREST); // always returns a valid value
+ monitorInfo.cbSize = sizeof(monitorInfo);
+
+ if (MyGetMonitorInfo(hMonitor, &monitorInfo))
+ {
+ *rc = monitorInfo.rcMonitor;
+ return;
+ }
+ }
+
+ // "generic" win95/NT support, also serves as failsafe
+ rc->left = 0;
+ rc->top = 0;
+ rc->bottom = GetSystemMetrics(SM_CYSCREEN);
+ rc->right = GetSystemMetrics(SM_CXSCREEN);
+}
+
+static void Docking_RectToDock(LPRECT rc)
+{
+ rc->right += dockPos.x - rc->left;
+ rc->left = dockPos.x;
+ rc->bottom += dockPos.y - rc->top;
+ rc->top = dockPos.y;
+}
+
+static void Docking_PosCommand(HWND hwnd, LPRECT rc, bool query)
+{
+ APPBARDATA abd = {0};
+
+ abd.cbSize = sizeof(abd);
+ abd.hWnd = hwnd;
+ abd.uEdge = docked == DOCKED_LEFT ? ABE_LEFT : ABE_RIGHT;
+ abd.rc = *rc;
+ SHAppBarMessage(query ? ABM_QUERYPOS : ABM_SETPOS, &abd);
+ *rc = abd.rc;
+}
+
+static UINT_PTR Docking_Command(HWND hwnd, int cmd)
+{
+ APPBARDATA abd = {0};
+
+ abd.cbSize = sizeof(abd);
+ abd.hWnd = hwnd;
+ abd.uCallbackMessage = WM_DOCKCALLBACK;
+ return SHAppBarMessage(cmd, &abd);
+}
+
+static void Docking_AdjustPosition(HWND hwnd, LPRECT rcDisplay, LPRECT rc, bool query, bool move)
+{
+ int cx = rc->right - rc->left;
+
+ rc->top = rcDisplay->top;
+ rc->bottom = rcDisplay->bottom;
+ if (docked == DOCKED_LEFT)
+ {
+ rc->right = rcDisplay->left + (rc->right - rc->left);
+ rc->left = rcDisplay->left;
+ }
+ else
+ {
+ rc->left = rcDisplay->right - (rc->right - rc->left);
+ rc->right = rcDisplay->right;
+ }
+ Docking_PosCommand(hwnd, rc, true);
+
+ if (docked == DOCKED_LEFT)
+ rc->right = rc->left + cx;
+ else
+ rc->left = rc->right - cx;
+
+ if (!query)
+ {
+ Docking_PosCommand(hwnd, rc, false);
+ dockPos = *(LPPOINT)rc;
+ }
+
+ if (move)
+ {
+ MoveWindow(hwnd, rc->left, rc->top, rc->right - rc->left,
+ rc->bottom - rc->top, TRUE);
+ }
+}
+
+static void Docking_SetSize(HWND hwnd, LPRECT rc, bool query, bool move)
+{
+ RECT rcMonitor;
+ Docking_GetMonitorRectFromPoint(
+ docked == DOCKED_LEFT && !query ? (LPPOINT)&rc->right : (LPPOINT)rc, &rcMonitor);
+ Docking_AdjustPosition(hwnd, &rcMonitor, rc, query, move);
+}
+
+static bool Docking_IsWindowVisible(HWND hwnd)
+{
+ LONG style = GetWindowLong(hwnd, GWL_STYLE);
+ return style & WS_VISIBLE && !(style & WS_MINIMIZE);
+}
+
+INT_PTR Docking_IsDocked(WPARAM, LPARAM)
+{
+ return docked;
+}
+
+int fnDocking_ProcessWindowMessage(WPARAM wParam, LPARAM lParam)
+{
+ static int draggingTitle;
+ MSG *msg = (MSG *) wParam;
+
+ if (msg->message == WM_DESTROY)
+ {
+ if (docked)
+ {
+ DBWriteContactSettingByte(NULL, "CList", "Docked", (BYTE) docked);
+ DBWriteContactSettingDword(NULL, "CList", "DockX", (DWORD) dockPos.x);
+ DBWriteContactSettingDword(NULL, "CList", "DockY", (DWORD) dockPos.y);
+ }
+ else
+ {
+ DBDeleteContactSetting(NULL, "CList", "Docked");
+ DBDeleteContactSetting(NULL, "CList", "DockX");
+ DBDeleteContactSetting(NULL, "CList", "DockY");
+ }
+ }
+
+ if (!docked && msg->message != WM_CREATE && msg->message != WM_MOVING)
+ return 0;
+
+ switch (msg->message)
+ {
+ case WM_CREATE:
+ draggingTitle = 0;
+ docked = DBGetContactSettingByte(NULL, "CLUI", "DockToSides", 1) ?
+ (char) DBGetContactSettingByte(NULL, "CList", "Docked", 0) : 0;
+ dockPos.x = (int) DBGetContactSettingDword(NULL, "CList", "DockX", 0);
+ dockPos.y = (int) DBGetContactSettingDword(NULL, "CList", "DockY", 0);
+ break;
+
+ case WM_ACTIVATE:
+ Docking_Command(msg->hwnd, ABM_ACTIVATE);
+ break;
+
+ case WM_WINDOWPOSCHANGING:
+ {
+ LPWINDOWPOS wp = (LPWINDOWPOS)msg->lParam;
+
+ bool vis = Docking_IsWindowVisible(msg->hwnd);
+ if (wp->flags & SWP_SHOWWINDOW)
+ vis = !IsIconic(msg->hwnd);
+ if (wp->flags & SWP_HIDEWINDOW)
+ vis = false;
+
+ if (vis)
+ {
+ if (!(wp->flags & (SWP_NOMOVE | SWP_NOSIZE)))
+ {
+ bool addbar = Docking_Command(msg->hwnd, ABM_NEW) != 0;
+
+ RECT rc = {0};
+ GetWindowRect(msg->hwnd, &rc);
+
+ int cx = rc.right - rc.left;
+ if (!(wp->flags & SWP_NOMOVE)) { rc.left = wp->x; rc.top = wp->y; }
+
+ if (addbar)
+ Docking_RectToDock(&rc);
+
+ if (!(wp->flags & SWP_NOSIZE))
+ {
+ rc.right = rc.left + wp->cx;
+ rc.bottom = rc.top + wp->cy;
+ addbar |= (cx != wp->cx);
+ }
+
+ Docking_SetSize(msg->hwnd, &rc, !addbar, false);
+
+ if (!(wp->flags & SWP_NOMOVE)) { wp->x = rc.left; wp->y = rc.top; }
+ if (!(wp->flags & SWP_NOSIZE)) wp->cy = rc.bottom - rc.top;
+
+ *((LRESULT *) lParam) = TRUE;
+ return TRUE;
+ }
+ else
+ {
+ if ((wp->flags & SWP_SHOWWINDOW) && Docking_Command(msg->hwnd, ABM_NEW))
+ {
+ RECT rc = {0};
+ GetWindowRect(msg->hwnd, &rc);
+ Docking_RectToDock(&rc);
+
+ Docking_SetSize(msg->hwnd, &rc, false, false);
+
+ wp->x = rc.left;
+ wp->y = rc.top;
+ wp->cy = rc.bottom - rc.top;
+ wp->cx = rc.right - rc.left;
+ wp->flags &= ~(SWP_NOSIZE | SWP_NOMOVE);
+ }
+ }
+ }
+ break;
+ }
+
+ case WM_WINDOWPOSCHANGED:
+ {
+ LPWINDOWPOS wp = (LPWINDOWPOS)msg->lParam;
+ bool vis = Docking_IsWindowVisible(msg->hwnd);
+ if (wp->flags & SWP_SHOWWINDOW)
+ vis = !IsIconic(msg->hwnd);
+ if (wp->flags & SWP_HIDEWINDOW)
+ vis = false;
+
+ if (!vis)
+ Docking_Command(msg->hwnd, ABM_REMOVE);
+ else
+ Docking_Command(msg->hwnd, ABM_WINDOWPOSCHANGED);
+ break;
+ }
+
+ case WM_DISPLAYCHANGE:
+ if (Docking_IsWindowVisible(msg->hwnd))
+ {
+ RECT rc = {0};
+ GetWindowRect(msg->hwnd, &rc);
+ Docking_RectToDock(&rc);
+ Docking_SetSize(msg->hwnd, &rc, false, true);
+ }
+ break;
+
+ case WM_MOVING:
+ if (!docked)
+ {
+ RECT rcMonitor;
+ POINT ptCursor;
+
+ // stop early
+ if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
+ return 0;
+
+ // GetMessagePos() is no good, position is always unsigned
+// GetCursorPos(&ptCursor);
+ DWORD pos = GetMessagePos();
+ ptCursor.x = GET_X_LPARAM(pos);
+ ptCursor.y = GET_Y_LPARAM(pos);
+ Docking_GetMonitorRectFromPoint(&ptCursor, &rcMonitor);
+
+ if (((ptCursor.x < rcMonitor.left + EDGESENSITIVITY) ||
+ (ptCursor.x >= rcMonitor.right - EDGESENSITIVITY)) &&
+ DBGetContactSettingByte(NULL, "CLUI", "DockToSides", 1))
+ {
+ docked = (ptCursor.x < rcMonitor.left + EDGESENSITIVITY) ? DOCKED_LEFT : DOCKED_RIGHT;
+ PostMessage(msg->hwnd, WM_LBUTTONUP, 0, MAKELPARAM(ptCursor.x, ptCursor.y));
+
+ Docking_Command(msg->hwnd, ABM_NEW);
+ Docking_AdjustPosition(msg->hwnd, &rcMonitor, (LPRECT)msg->lParam, false, true);
+
+ *((LRESULT *) lParam) = TRUE;
+ return TRUE;
+ }
+ }
+ break;
+
+ case WM_NCHITTEST:
+ switch (DefWindowProc(msg->hwnd, WM_NCHITTEST, msg->wParam, msg->lParam))
+ {
+ case HTSIZE: case HTTOP: case HTTOPLEFT: case HTTOPRIGHT:
+ case HTBOTTOM: case HTBOTTOMRIGHT: case HTBOTTOMLEFT:
+ *((LRESULT *) lParam) = HTCLIENT;
+ return TRUE;
+
+ case HTLEFT:
+ if (docked == DOCKED_LEFT)
+ {
+ *((LRESULT *) lParam) = HTCLIENT;
+ return TRUE;
+ }
+ break;
+
+ case HTRIGHT:
+ if (docked == DOCKED_RIGHT)
+ {
+ *((LRESULT *) lParam) = HTCLIENT;
+ return TRUE;
+ }
+ break;
+ }
+ break;
+
+ case WM_SYSCOMMAND:
+ if ((msg->wParam & 0xFFF0) != SC_MOVE)
+ return 0;
+
+ SetActiveWindow(msg->hwnd);
+ SetCapture(msg->hwnd);
+ draggingTitle = 1;
+ *((LRESULT *) lParam) = 0;
+ return 1;
+
+ case WM_MOUSEMOVE:
+ if (draggingTitle)
+ {
+ RECT rc;
+ POINT pt;
+ GetClientRect(msg->hwnd, &rc);
+ if ((docked == DOCKED_LEFT && (short) LOWORD(msg->lParam) > rc.right) ||
+ (docked == DOCKED_RIGHT && (short) LOWORD(msg->lParam) < 0))
+ {
+ ReleaseCapture();
+ draggingTitle = 0;
+ docked = 0;
+ GetCursorPos(&pt);
+ PostMessage(msg->hwnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(pt.x, pt.y));
+ SetWindowPos(msg->hwnd, 0, pt.x - rc.right / 2,
+ pt.y - GetSystemMetrics(SM_CYFRAME) - GetSystemMetrics(SM_CYSMCAPTION) / 2,
+ DBGetContactSettingDword(NULL, "CList", "Width", 0),
+ DBGetContactSettingDword(NULL, "CList", "Height", 0),
+ SWP_NOZORDER);
+ Docking_Command(msg->hwnd, ABM_REMOVE);
+ }
+ return 1;
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (draggingTitle)
+ {
+ ReleaseCapture();
+ draggingTitle = 0;
+ }
+ break;
+
+ case WM_DOCKCALLBACK:
+ switch (msg->wParam)
+ {
+ case ABN_WINDOWARRANGE:
+ ShowWindow(msg->hwnd, msg->lParam ? SW_HIDE : SW_SHOW);
+ break;
+
+ case ABN_POSCHANGED:
+ {
+ RECT rc = {0};
+ GetWindowRect(msg->hwnd, &rc);
+ Docking_SetSize(msg->hwnd, &rc, false, true);
+ }
+ break;
+ }
+ return 1;
+
+ case WM_DESTROY:
+ Docking_Command(msg->hwnd, ABM_REMOVE);
+ break;
+ }
+ return 0;
+}
diff --git a/src/modules/clist/clc.cpp b/src/modules/clist/clc.cpp
new file mode 100644
index 0000000000..1994706992
--- /dev/null
+++ b/src/modules/clist/clc.cpp
@@ -0,0 +1,1350 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+int InitGenMenu( void );
+int UnitGenMenu( void );
+
+void InitCustomMenus( void );
+void UninitCustomMenus( void );
+
+void MTG_OnmodulesLoad( void );
+
+static BOOL bModuleInitialized = FALSE;
+static HANDLE hClcWindowList;
+static HANDLE hShowInfoTipEvent;
+HANDLE hHideInfoTipEvent;
+static HANDLE hAckHook;
+static HANDLE hClcSettingsChanged;
+
+int g_IconWidth, g_IconHeight;
+
+void FreeDisplayNameCache(void);
+
+void fnClcBroadcast( int msg, WPARAM wParam, LPARAM lParam )
+{
+ WindowList_Broadcast(hClcWindowList, msg, wParam, lParam);
+}
+
+void fnClcOptionsChanged(void)
+{
+ cli.pfnClcBroadcast( INTM_RELOADOPTIONS, 0, 0);
+}
+
+HMENU fnBuildGroupPopupMenu( struct ClcGroup* group )
+{
+ HMENU hMenu = LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT));
+ HMENU hGroupMenu = GetSubMenu(hMenu, 2);
+ RemoveMenu(hMenu, 2, MF_BYPOSITION);
+ DestroyMenu(hMenu);
+ CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) hGroupMenu, 0);
+
+ CheckMenuItem(hGroupMenu, POPUP_GROUPHIDEOFFLINE, group->hideOffline ? MF_CHECKED : MF_UNCHECKED);
+ return hGroupMenu;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// standard CLC services
+
+static int ClcSettingChanged(WPARAM wParam, LPARAM lParam)
+{
+ char *szProto;
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam;
+ if ( (HANDLE)wParam != NULL && !strcmp(cws->szModule, "CList")) {
+ if (!strcmp(cws->szSetting, "MyHandle")) {
+ cli.pfnInvalidateDisplayNameCacheEntry((HANDLE) wParam);
+ cli.pfnClcBroadcast( INTM_NAMECHANGED, wParam, lParam);
+ }
+ else if (!strcmp(cws->szSetting, "Group"))
+ cli.pfnClcBroadcast( INTM_GROUPCHANGED, wParam, lParam);
+ else if (!strcmp(cws->szSetting, "Hidden"))
+ cli.pfnClcBroadcast( INTM_HIDDENCHANGED, wParam, lParam);
+ else if (!strcmp(cws->szSetting, "NotOnList"))
+ cli.pfnClcBroadcast( INTM_NOTONLISTCHANGED, wParam, lParam);
+ else if (!strcmp(cws->szSetting, "Status"))
+ cli.pfnClcBroadcast( INTM_INVALIDATE, 0, 0);
+ else if (!strcmp(cws->szSetting, "NameOrder"))
+ cli.pfnClcBroadcast( INTM_NAMEORDERCHANGED, 0, 0);
+ }
+ else if (!strcmp(cws->szModule, "CListGroups")) {
+ cli.pfnClcBroadcast( INTM_GROUPSCHANGED, wParam, lParam);
+ }
+ else {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ if (szProto != NULL && (HANDLE) wParam != NULL) {
+ char *id = NULL;
+ if (!strcmp(cws->szModule, "Protocol") && !strcmp(cws->szSetting, "p")) {
+ cli.pfnClcBroadcast( INTM_PROTOCHANGED, wParam, lParam);
+ }
+ // something is being written to a protocol module
+ if (!strcmp(szProto, cws->szModule)) {
+ // was a unique setting key written?
+ id = (char *) CallProtoService(szProto, PS_GETCAPS, PFLAG_UNIQUEIDSETTING, 0);
+ if ((INT_PTR) id != CALLSERVICE_NOTFOUND && id != NULL && !strcmp(id, cws->szSetting)) {
+ cli.pfnClcBroadcast( INTM_PROTOCHANGED, wParam, lParam);
+ }
+ }
+ }
+ if (szProto == NULL || strcmp(szProto, cws->szModule))
+ return 0;
+ if (!strcmp(cws->szSetting, "Nick") || !strcmp(cws->szSetting, "FirstName") || !strcmp(cws->szSetting, "e-mail")
+ || !strcmp(cws->szSetting, "LastName") || !strcmp(cws->szSetting, "UIN"))
+ cli.pfnClcBroadcast( INTM_NAMECHANGED, wParam, lParam);
+ else if (!strcmp(cws->szSetting, "ApparentMode"))
+ cli.pfnClcBroadcast( INTM_APPARENTMODECHANGED, wParam, lParam);
+ else if (!strcmp(cws->szSetting, "IdleTS"))
+ cli.pfnClcBroadcast( INTM_IDLECHANGED, wParam, lParam);
+ }
+ return 0;
+}
+
+static int ClcAccountsChanged(WPARAM, LPARAM)
+{
+ int i, cnt;
+ for (i = 0, cnt = 0; i < accounts.getCount(); ++i)
+ if (Proto_IsAccountEnabled(accounts[i])) ++cnt;
+
+ cli.hClcProtoCount = cnt;
+ cli.clcProto = (ClcProtoStatus *) mir_realloc(cli.clcProto, sizeof(ClcProtoStatus) * cli.hClcProtoCount);
+
+ for (i = 0, cnt = 0; i < accounts.getCount(); ++i) {
+ if (Proto_IsAccountEnabled(accounts[i])) {
+ cli.clcProto[cnt].szProto = accounts[i]->szModuleName;
+ cli.clcProto[cnt].dwStatus = CallProtoService(accounts[i]->szModuleName, PS_GETSTATUS, 0, 0);
+ ++cnt;
+ }
+ }
+ return 0;
+}
+
+static int ClcModulesLoaded(WPARAM, LPARAM)
+{
+ ClcAccountsChanged(0, 0);
+ MTG_OnmodulesLoad();
+ return 0;
+}
+
+static int ClcProtoAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA *) lParam;
+ int i;
+
+ if (ack->type == ACKTYPE_STATUS) {
+ WindowList_BroadcastAsync(hClcWindowList,INTM_INVALIDATE,0,0);
+ if (ack->result == ACKRESULT_SUCCESS) {
+ for (i = 0; i < cli.hClcProtoCount; i++) {
+ if (!lstrcmpA(cli.clcProto[i].szProto, ack->szModule)) {
+ cli.clcProto[i].dwStatus = (WORD) ack->lParam;
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int ClcContactAdded(WPARAM wParam, LPARAM lParam)
+{
+ WindowList_BroadcastAsync(hClcWindowList,INTM_CONTACTADDED,wParam,lParam);
+ return 0;
+}
+
+static int ClcContactDeleted(WPARAM wParam, LPARAM lParam)
+{
+ WindowList_BroadcastAsync(hClcWindowList,INTM_CONTACTDELETED,wParam,lParam);
+ return 0;
+}
+
+static int ClcContactIconChanged(WPARAM wParam, LPARAM lParam)
+{
+ WindowList_BroadcastAsync(hClcWindowList,INTM_ICONCHANGED,wParam,lParam);
+ return 0;
+}
+
+static int ClcIconsChanged(WPARAM, LPARAM)
+{
+ WindowList_BroadcastAsync(hClcWindowList,INTM_INVALIDATE,0,0);
+ return 0;
+}
+
+static INT_PTR SetInfoTipHoverTime(WPARAM wParam, LPARAM)
+{
+ DBWriteContactSettingWord(NULL, "CLC", "InfoTipHoverTime", (WORD) wParam);
+ cli.pfnClcBroadcast( INTM_SETINFOTIPHOVERTIME, wParam, 0);
+ return 0;
+}
+
+static INT_PTR GetInfoTipHoverTime(WPARAM, LPARAM)
+{
+ return DBGetContactSettingWord(NULL, "CLC", "InfoTipHoverTime", 750);
+}
+
+static void SortClcByTimer( HWND hwnd )
+{
+ KillTimer( hwnd, TIMERID_DELAYEDRESORTCLC );
+ SetTimer( hwnd, TIMERID_DELAYEDRESORTCLC, 200, NULL );
+}
+
+int LoadCLCModule(void)
+{
+ bModuleInitialized = TRUE;
+
+ g_IconWidth = GetSystemMetrics(SM_CXSMICON);
+ g_IconHeight = GetSystemMetrics(SM_CYSMICON);
+
+ hClcWindowList = (HANDLE) CallService(MS_UTILS_ALLOCWINDOWLIST, 0, 0);
+ hShowInfoTipEvent = CreateHookableEvent(ME_CLC_SHOWINFOTIP);
+ hHideInfoTipEvent = CreateHookableEvent(ME_CLC_HIDEINFOTIP);
+ CreateServiceFunction(MS_CLC_SETINFOTIPHOVERTIME, SetInfoTipHoverTime);
+ CreateServiceFunction(MS_CLC_GETINFOTIPHOVERTIME, GetInfoTipHoverTime);
+
+ InitFileDropping();
+
+ HookEvent(ME_SYSTEM_MODULESLOADED, ClcModulesLoaded);
+ HookEvent(ME_PROTO_ACCLISTCHANGED, ClcAccountsChanged);
+ hClcSettingsChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ClcSettingChanged);
+ HookEvent(ME_DB_CONTACT_ADDED, ClcContactAdded);
+ HookEvent(ME_DB_CONTACT_DELETED, ClcContactDeleted);
+ HookEvent(ME_CLIST_CONTACTICONCHANGED, ClcContactIconChanged);
+ HookEvent(ME_SKIN_ICONSCHANGED, ClcIconsChanged);
+ hAckHook = (HANDLE) HookEvent(ME_PROTO_ACK, ClcProtoAck);
+
+ InitCustomMenus();
+ return 0;
+}
+
+void UnloadClcModule()
+{
+ if ( !bModuleInitialized ) return;
+
+ UnhookEvent(hAckHook);
+ UnhookEvent(hClcSettingsChanged);
+
+ mir_free(cli.clcProto);
+
+ FreeDisplayNameCache();
+
+ UninitCustomMenus();
+ UnitGenMenu();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// default contact list control window procedure
+
+LRESULT CALLBACK fnContactListControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ struct ClcData *dat;
+
+ dat = (struct ClcData *) GetWindowLongPtr(hwnd, 0);
+ if (msg >= CLM_FIRST && msg < CLM_LAST)
+ return cli.pfnProcessExternalMessages(hwnd, dat, msg, wParam, lParam);
+
+ switch (msg) {
+ case WM_CREATE:
+ WindowList_Add(hClcWindowList, hwnd, NULL);
+ cli.pfnRegisterFileDropping(hwnd);
+ if ( dat == NULL ) {
+ dat = (struct ClcData *) mir_calloc(sizeof(struct ClcData));
+ SetWindowLongPtr(hwnd, 0, (LONG_PTR) dat);
+ }
+ {
+ int i;
+ for (i = 0; i <= FONTID_MAX; i++)
+ dat->fontInfo[i].changed = 1;
+ }
+ dat->selection = -1;
+ dat->iconXSpace = 20;
+ dat->checkboxSize = 13;
+ dat->dragAutoScrollHeight = 30;
+ dat->iDragItem = -1;
+ dat->iInsertionMark = -1;
+ dat->insertionMarkHitHeight = 5;
+ dat->iHotTrack = -1;
+ dat->infoTipTimeout = DBGetContactSettingWord(NULL, "CLC", "InfoTipHoverTime", 750);
+ dat->extraColumnSpacing = 20;
+ dat->list.cl.increment = 30;
+ dat->needsResort = 1;
+ cli.pfnLoadClcOptions(hwnd, dat);
+ if (!IsWindowVisible(hwnd))
+ SetTimer(hwnd,TIMERID_REBUILDAFTER,10,NULL);
+ else
+ {
+ cli.pfnRebuildEntireList(hwnd,dat);
+ NMCLISTCONTROL nm;
+ nm.hdr.code = CLN_LISTREBUILT;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+ }
+ break;
+ case INTM_SCROLLBARCHANGED:
+ if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CONTACTLIST) {
+ if (dat->noVScrollbar)
+ ShowScrollBar(hwnd, SB_VERT, FALSE);
+ else
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ }
+ break;
+
+ case INTM_RELOADOPTIONS:
+ cli.pfnLoadClcOptions(hwnd, dat);
+ cli.pfnSaveStateAndRebuildList(hwnd, dat);
+ break;
+ case WM_THEMECHANGED:
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+ case WM_SIZE:
+ cli.pfnEndRename(hwnd, dat, 1);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ { // creating imagelist containing blue line for highlight
+ HBITMAP hBmp, hBmpMask, hoBmp, hoMaskBmp;
+ HDC hdc,hdcMem;
+ RECT rc;
+ int depth;
+ HBRUSH hBrush;
+
+ GetClientRect(hwnd, &rc);
+ if (rc.right == 0)
+ break;
+ rc.bottom = dat->rowHeight;
+ hdc = GetDC(hwnd);
+ depth = GetDeviceCaps(hdc, BITSPIXEL);
+ if (depth < 16)
+ depth = 16;
+ hBmp = CreateBitmap(rc.right, rc.bottom, 1, depth, NULL);
+ hBmpMask = CreateBitmap(rc.right, rc.bottom, 1, 1, NULL);
+ hdcMem = CreateCompatibleDC(hdc);
+ hoBmp = (HBITMAP) SelectObject(hdcMem, hBmp);
+ hBrush = CreateSolidBrush(dat->useWindowsColours ? GetSysColor(COLOR_HIGHLIGHT) : dat->selBkColour);
+ FillRect(hdcMem, &rc, hBrush);
+ DeleteObject(hBrush);
+
+ hoMaskBmp = ( HBITMAP )SelectObject(hdcMem, hBmpMask);
+ FillRect(hdcMem, &rc, ( HBRUSH )GetStockObject(BLACK_BRUSH));
+ SelectObject(hdcMem, hoMaskBmp);
+ SelectObject(hdcMem, hoBmp);
+ DeleteDC(hdcMem);
+ ReleaseDC(hwnd, hdc);
+ if (dat->himlHighlight)
+ ImageList_Destroy(dat->himlHighlight);
+ dat->himlHighlight = ImageList_Create(rc.right, rc.bottom, (IsWinVerXPPlus()? ILC_COLOR32 : ILC_COLOR16) | ILC_MASK, 1, 1);
+ ImageList_Add(dat->himlHighlight, hBmp, hBmpMask);
+ DeleteObject(hBmpMask);
+ DeleteObject(hBmp);
+ }
+ break;
+
+ case WM_SYSCOLORCHANGE:
+ SendMessage(hwnd, WM_SIZE, 0, 0);
+ break;
+
+ case WM_GETDLGCODE:
+ if (lParam) {
+ MSG *msg = (MSG *) lParam;
+ if (msg->message == WM_KEYDOWN) {
+ if (msg->wParam == VK_TAB)
+ return 0;
+ if (msg->wParam == VK_ESCAPE && dat->hwndRenameEdit == NULL && dat->szQuickSearch[0] == 0)
+ return 0;
+ }
+ if (msg->message == WM_CHAR) {
+ if (msg->wParam == '\t')
+ return 0;
+ if (msg->wParam == 27 && dat->hwndRenameEdit == NULL && dat->szQuickSearch[0] == 0)
+ return 0;
+ }
+ }
+ return DLGC_WANTMESSAGE;
+
+ case WM_KILLFOCUS:
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ case WM_SETFOCUS:
+ case WM_ENABLE:
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case WM_GETFONT:
+ return (LRESULT) dat->fontInfo[FONTID_CONTACTS].hFont;
+
+ case INTM_GROUPSCHANGED:
+ {
+ DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *) lParam;
+ if (dbcws->value.type == DBVT_ASCIIZ || dbcws->value.type == DBVT_UTF8) {
+ int groupId = atoi(dbcws->szSetting) + 1;
+ struct ClcContact *contact;
+ struct ClcGroup *group;
+ TCHAR szFullName[512];
+ int i, nameLen, eq;
+ //check name of group and ignore message if just being expanded/collapsed
+ if (cli.pfnFindItem(hwnd, dat, (HANDLE) (groupId | HCONTACT_ISGROUP), &contact, &group, NULL)) {
+ lstrcpy(szFullName, contact->szText);
+ while (group->parent) {
+ for (i = 0; i < group->parent->cl.count; i++)
+ if (group->parent->cl.items[i]->group == group)
+ break;
+ if (i == group->parent->cl.count) {
+ szFullName[0] = '\0';
+ break;
+ }
+ group = group->parent;
+ nameLen = lstrlen(group->cl.items[i]->szText);
+ if (lstrlen(szFullName) + 1 + nameLen > SIZEOF(szFullName)) {
+ szFullName[0] = '\0';
+ break;
+ }
+ memmove(szFullName + 1 + nameLen, szFullName, sizeof( TCHAR )*( lstrlen(szFullName) + 1));
+ memcpy(szFullName, group->cl.items[i]->szText, sizeof( TCHAR )*nameLen);
+ szFullName[nameLen] = '\\';
+ }
+
+ if ( dbcws->value.type == DBVT_ASCIIZ ) {
+ #if defined( UNICODE )
+ WCHAR* wszGrpName = mir_a2u(dbcws->value.pszVal+1);
+ eq = !lstrcmp( szFullName, wszGrpName );
+ mir_free( wszGrpName );
+ #else
+ eq = !lstrcmp( szFullName, dbcws->value.pszVal+1 );
+ #endif
+ }
+ else {
+ char* szGrpName = NEWSTR_ALLOCA(dbcws->value.pszVal+1);
+ #if defined( UNICODE )
+ WCHAR* wszGrpName;
+ Utf8Decode(szGrpName, &wszGrpName );
+ eq = !lstrcmp( szFullName, wszGrpName );
+ mir_free( wszGrpName );
+ #else
+ Utf8Decode(szGrpName, NULL);
+ eq = !lstrcmp( szFullName, szGrpName );
+ #endif
+ }
+ if ( eq && (contact->group->hideOffline != 0) == ((dbcws->value.pszVal[0] & GROUPF_HIDEOFFLINE) != 0))
+ break; //only expanded has changed: no action reqd
+ }
+ }
+ cli.pfnSaveStateAndRebuildList(hwnd, dat);
+ break;
+ }
+ case INTM_NAMEORDERCHANGED:
+ PostMessage(hwnd, CLM_AUTOREBUILD, 0, 0);
+ break;
+
+ case INTM_CONTACTADDED:
+ cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 1);
+ cli.pfnNotifyNewContact(hwnd, (HANDLE) wParam);
+ SortClcByTimer(hwnd);
+ break;
+
+ case INTM_CONTACTDELETED:
+ cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam);
+ SortClcByTimer(hwnd);
+ break;
+
+ case INTM_HIDDENCHANGED:
+ {
+ DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *) lParam;
+ if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_SHOWHIDDEN)
+ break;
+ if (dbcws->value.type == DBVT_DELETED || dbcws->value.bVal == 0) {
+ if (cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, NULL, NULL, NULL))
+ break;
+ cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 1);
+ cli.pfnNotifyNewContact(hwnd, (HANDLE) wParam);
+ }
+ else cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam);
+
+ dat->needsResort = 1;
+ SortClcByTimer(hwnd);
+ break;
+ }
+ case INTM_GROUPCHANGED:
+ {
+ struct ClcContact *contact;
+ BYTE iExtraImage[MAXEXTRACOLUMNS];
+ BYTE flags = 0;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ memset(iExtraImage, 0xFF, SIZEOF(iExtraImage));
+ else {
+ CopyMemory(iExtraImage, contact->iExtraImage, SIZEOF(iExtraImage));
+ flags = contact->flags;
+ }
+ cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam);
+ if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_SHOWHIDDEN || !DBGetContactSettingByte((HANDLE) wParam, "CList", "Hidden", 0)) {
+ NMCLISTCONTROL nm;
+ cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 1);
+ if (cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL)) {
+ CopyMemory(contact->iExtraImage, iExtraImage, SIZEOF(iExtraImage));
+ if(flags & CONTACTF_CHECKED)
+ contact->flags |= CONTACTF_CHECKED;
+ }
+ nm.hdr.code = CLN_CONTACTMOVED;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = (HANDLE) wParam;
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+ dat->needsResort = 1;
+ }
+ SetTimer(hwnd,TIMERID_REBUILDAFTER,1,NULL);
+ break;
+ }
+ case INTM_ICONCHANGED:
+ {
+ struct ClcContact *contact = NULL;
+ struct ClcGroup *group = NULL;
+ int recalcScrollBar = 0, shouldShow;
+ WORD status;
+ char *szProto;
+ HANDLE hSelItem = NULL;
+ struct ClcContact *selcontact = NULL;
+
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ if (szProto == NULL)
+ status = ID_STATUS_OFFLINE;
+ else
+ status = DBGetContactSettingWord((HANDLE) wParam, szProto, "Status", ID_STATUS_OFFLINE);
+
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ shouldShow = (style & CLS_SHOWHIDDEN || !DBGetContactSettingByte((HANDLE) wParam, "CList", "Hidden", 0))
+ && (!cli.pfnIsHiddenMode(dat, status)
+ || CallService(MS_CLIST_GETCONTACTICON, wParam, 0) != lParam); // this means an offline msg is flashing, so the contact should be shown
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, &group, NULL)) {
+ if (shouldShow && CallService(MS_DB_CONTACT_IS, wParam, 0)) {
+ if (dat->selection >= 0 && cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) != -1)
+ hSelItem = cli.pfnContactToHItem(selcontact);
+ cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, (style & CLS_CONTACTLIST) == 0, 0);
+ recalcScrollBar = 1;
+ cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL);
+ if (contact) {
+ contact->iImage = (WORD) lParam;
+ cli.pfnNotifyNewContact(hwnd, (HANDLE) wParam);
+ dat->needsResort = 1;
+ } }
+ }
+ else { // item in list already
+ if (contact->iImage == (WORD) lParam)
+ break;
+ if (!shouldShow && !(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) {
+ if (dat->selection >= 0 && cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) != -1)
+ hSelItem = cli.pfnContactToHItem(selcontact);
+ cli.pfnRemoveItemFromGroup(hwnd, group, contact, (style & CLS_CONTACTLIST) == 0);
+ recalcScrollBar = 1;
+ }
+ else {
+ contact->iImage = (WORD) lParam;
+ if (!cli.pfnIsHiddenMode(dat, status))
+ contact->flags |= CONTACTF_ONLINE;
+ else
+ contact->flags &= ~CONTACTF_ONLINE;
+ }
+ dat->needsResort = 1;
+ }
+ if (hSelItem) {
+ struct ClcGroup *selgroup;
+ if (cli.pfnFindItem(hwnd, dat, hSelItem, &selcontact, &selgroup, NULL))
+ dat->selection = cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf(( SortedList* )&selgroup->cl, selcontact));
+ else
+ dat->selection = -1;
+ }
+ SortClcByTimer(hwnd);
+ break;
+ }
+ case INTM_NAMECHANGED:
+ {
+ struct ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ break;
+
+ lstrcpyn(contact->szText, cli.pfnGetContactDisplayName((HANDLE)wParam,0), SIZEOF(contact->szText));
+ dat->needsResort = 1;
+ SortClcByTimer(hwnd);
+ break;
+ }
+ case INTM_PROTOCHANGED:
+ {
+ struct ClcContact *contact = NULL;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ break;
+ contact->proto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ cli.pfnInvalidateDisplayNameCacheEntry((HANDLE)wParam);
+ lstrcpyn(contact->szText, cli.pfnGetContactDisplayName((HANDLE)wParam,0), SIZEOF(contact->szText));
+ SortClcByTimer(hwnd);
+ break;
+ }
+ case INTM_NOTONLISTCHANGED:
+ {
+ DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING *) lParam;
+ struct ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ break;
+ if (contact->type != CLCIT_CONTACT)
+ break;
+ if (dbcws->value.type == DBVT_DELETED || dbcws->value.bVal == 0)
+ contact->flags &= ~CONTACTF_NOTONLIST;
+ else
+ contact->flags |= CONTACTF_NOTONLIST;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+ }
+ case INTM_INVALIDATE:
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case INTM_APPARENTMODECHANGED:
+ {
+ WORD apparentMode;
+ char *szProto;
+ struct ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ break;
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ if (szProto == NULL)
+ break;
+ apparentMode = DBGetContactSettingWord((HANDLE) wParam, szProto, "ApparentMode", 0);
+ contact->flags &= ~(CONTACTF_INVISTO | CONTACTF_VISTO);
+ if (apparentMode == ID_STATUS_OFFLINE)
+ contact->flags |= CONTACTF_INVISTO;
+ else if (apparentMode == ID_STATUS_ONLINE)
+ contact->flags |= CONTACTF_VISTO;
+ else if (apparentMode)
+ contact->flags |= CONTACTF_VISTO | CONTACTF_INVISTO;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+ }
+ case INTM_SETINFOTIPHOVERTIME:
+ dat->infoTipTimeout = wParam;
+ break;
+
+ case INTM_IDLECHANGED:
+ {
+ char *szProto;
+ struct ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ break;
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ if (szProto == NULL)
+ break;
+ contact->flags &= ~CONTACTF_IDLE;
+ if (DBGetContactSettingDword((HANDLE) wParam, szProto, "IdleTS", 0)) {
+ contact->flags |= CONTACTF_IDLE;
+ }
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+ }
+ case WM_PRINTCLIENT:
+ cli.pfnPaintClc(hwnd, dat, (HDC) wParam, NULL);
+ break;
+
+ case WM_NCPAINT:
+ if (wParam == 1)
+ break;
+ {
+ POINT ptTopLeft = { 0, 0 };
+ HRGN hClientRgn;
+ ClientToScreen(hwnd, &ptTopLeft);
+ hClientRgn = CreateRectRgn(0, 0, 1, 1);
+ CombineRgn(hClientRgn, (HRGN) wParam, NULL, RGN_COPY);
+ OffsetRgn(hClientRgn, -ptTopLeft.x, -ptTopLeft.y);
+ InvalidateRgn(hwnd, hClientRgn, FALSE);
+ DeleteObject(hClientRgn);
+ UpdateWindow(hwnd);
+ }
+ break;
+
+ case WM_PAINT:
+ {
+ HDC hdc;
+ PAINTSTRUCT ps;
+ hdc = BeginPaint(hwnd, &ps);
+ /* we get so many cli.pfnInvalidateRect()'s that there is no point painting,
+ Windows in theory shouldn't queue up WM_PAINTs in this case but it does so
+ we'll just ignore them */
+ if (IsWindowVisible(hwnd))
+ cli.pfnPaintClc(hwnd, dat, hdc, &ps.rcPaint);
+ EndPaint(hwnd, &ps);
+ break;
+ }
+ case WM_VSCROLL:
+ {
+ int desty;
+ RECT clRect;
+ int noSmooth = 0;
+
+ cli.pfnEndRename(hwnd, dat, 1);
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ desty = dat->yScroll;
+ GetClientRect(hwnd, &clRect);
+ switch (LOWORD(wParam)) {
+ case SB_LINEUP: desty -= dat->rowHeight; break;
+ case SB_LINEDOWN: desty += dat->rowHeight; break;
+ case SB_PAGEUP: desty -= clRect.bottom - dat->rowHeight; break;
+ case SB_PAGEDOWN: desty += clRect.bottom - dat->rowHeight; break;
+ case SB_BOTTOM: desty = 0x7FFFFFFF; break;
+ case SB_TOP: desty = 0; break;
+ case SB_THUMBTRACK: desty = HIWORD(wParam); noSmooth = 1; break; //noone has more than 4000 contacts, right?
+ default: return 0;
+ }
+ cli.pfnScrollTo(hwnd, dat, desty, noSmooth);
+ break;
+ }
+ case WM_MOUSEWHEEL:
+ {
+ UINT scrollLines;
+ cli.pfnEndRename(hwnd, dat, 1);
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ if (!SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, FALSE))
+ scrollLines = 3;
+ cli.pfnScrollTo(hwnd, dat, dat->yScroll - (short) HIWORD(wParam) * dat->rowHeight * (signed) scrollLines / WHEEL_DELTA, 0);
+ return 0;
+ }
+ case WM_KEYDOWN:
+ {
+ int selMoved = 0;
+ int changeGroupExpand = 0;
+ int pageSize;
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ if (CallService(MS_CLIST_MENUPROCESSHOTKEY, wParam, MPCF_CONTACTMENU))
+ break;
+ {
+ RECT clRect;
+ GetClientRect(hwnd, &clRect);
+ pageSize = clRect.bottom / dat->rowHeight;
+ }
+ switch (wParam) {
+ case VK_DOWN: dat->selection++; selMoved = 1; break;
+ case VK_UP: dat->selection--; selMoved = 1; break;
+ case VK_PRIOR: dat->selection -= pageSize; selMoved = 1; break;
+ case VK_NEXT: dat->selection += pageSize; selMoved = 1; break;
+ case VK_HOME: dat->selection = 0; selMoved = 1; break;
+ case VK_END: dat->selection = cli.pfnGetGroupContentsCount(&dat->list, 1) - 1; selMoved = 1; break;
+ case VK_LEFT: changeGroupExpand = 1; break;
+ case VK_RIGHT: changeGroupExpand = 2; break;
+ case VK_RETURN: cli.pfnDoSelectionDefaultAction(hwnd, dat); return 0;
+ case VK_F2: cli.pfnBeginRenameSelection(hwnd, dat); return 0;
+ case VK_DELETE: cli.pfnDeleteFromContactList(hwnd, dat); return 0;
+ default:
+ {
+ NMKEY nmkey;
+ nmkey.hdr.hwndFrom = hwnd;
+ nmkey.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nmkey.hdr.code = NM_KEYDOWN;
+ nmkey.nVKey = wParam;
+ nmkey.uFlags = HIWORD(lParam);
+ if (SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nmkey))
+ return 0;
+ }
+ }
+ if (changeGroupExpand) {
+ int hit;
+ struct ClcContact *contact;
+ struct ClcGroup *group;
+ dat->szQuickSearch[0] = 0;
+ hit = cli.pfnGetRowByIndex(dat, dat->selection, &contact, &group);
+ if (hit != -1) {
+ if (changeGroupExpand == 1 && contact->type == CLCIT_CONTACT) {
+ if (group == &dat->list)
+ return 0;
+ dat->selection = cli.pfnGetRowsPriorTo(&dat->list, group, -1);
+ selMoved = 1;
+ }
+ else {
+ if (contact->type == CLCIT_GROUP)
+ cli.pfnSetGroupExpand(hwnd, dat, contact->group, changeGroupExpand == 2);
+ return 0;
+ }
+ }
+ else
+ return 0;
+ }
+ if (selMoved) {
+ dat->szQuickSearch[0] = 0;
+ if (dat->selection >= cli.pfnGetGroupContentsCount(&dat->list, 1))
+ dat->selection = cli.pfnGetGroupContentsCount(&dat->list, 1) - 1;
+ if (dat->selection < 0)
+ dat->selection = 0;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0);
+ UpdateWindow(hwnd);
+ return 0;
+ }
+ break;
+ }
+ case WM_CHAR:
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ if (wParam == 27) //escape
+ dat->szQuickSearch[0] = 0;
+ else if (wParam == '\b' && dat->szQuickSearch[0])
+ dat->szQuickSearch[lstrlen(dat->szQuickSearch) - 1] = '\0';
+ else if (wParam < ' ')
+ break;
+ else if (wParam == ' ' && dat->szQuickSearch[0] == '\0' && GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CHECKBOXES) {
+ struct ClcContact *contact;
+ NMCLISTCONTROL nm;
+ if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1)
+ break;
+ if (contact->type != CLCIT_CONTACT)
+ break;
+ contact->flags ^= CONTACTF_CHECKED;
+ if (contact->type == CLCIT_GROUP)
+ cli.pfnSetGroupChildCheckboxes(contact->group, contact->flags & CONTACTF_CHECKED);
+ cli.pfnRecalculateGroupCheckboxes(hwnd, dat);
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ nm.hdr.code = CLN_CHECKCHANGED;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags);
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+ }
+ else {
+ TCHAR szNew[2];
+ szNew[0] = (TCHAR) wParam;
+ szNew[1] = '\0';
+ if (lstrlen(dat->szQuickSearch) >= SIZEOF(dat->szQuickSearch) - 1) {
+ MessageBeep(MB_OK);
+ break;
+ }
+ _tcscat(dat->szQuickSearch, szNew);
+ }
+ if (dat->szQuickSearch[0]) {
+ int index;
+ index = cli.pfnFindRowByText(hwnd, dat, dat->szQuickSearch, 1);
+ if (index != -1)
+ dat->selection = index;
+ else {
+ MessageBeep(MB_OK);
+ dat->szQuickSearch[ lstrlen(dat->szQuickSearch) - 1] = '\0';
+ }
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0);
+ }
+ else
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case WM_SYSKEYDOWN:
+ cli.pfnEndRename(hwnd, dat, 1);
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ dat->iHotTrack = -1;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ ReleaseCapture();
+ if (wParam == VK_F10 && GetKeyState(VK_SHIFT) & 0x8000)
+ break;
+ SendMessage(GetParent(hwnd), msg, wParam, lParam);
+ return 0;
+
+ case WM_TIMER:
+ switch( wParam ) {
+ case TIMERID_RENAME:
+ cli.pfnBeginRenameSelection(hwnd, dat);
+ break;
+ case TIMERID_DRAGAUTOSCROLL:
+ cli.pfnScrollTo(hwnd, dat, dat->yScroll + dat->dragAutoScrolling * dat->rowHeight * 2, 0);
+ break;
+ case TIMERID_INFOTIP:
+ { CLCINFOTIP it;
+ struct ClcContact *contact;
+ int hit;
+ RECT clRect;
+ POINT ptClientOffset = { 0 };
+
+ KillTimer(hwnd, wParam);
+ GetCursorPos(&it.ptCursor);
+ ScreenToClient(hwnd, &it.ptCursor);
+ if (it.ptCursor.x != dat->ptInfoTip.x || it.ptCursor.y != dat->ptInfoTip.y)
+ break;
+ GetClientRect(hwnd, &clRect);
+ it.rcItem.left = 0;
+ it.rcItem.right = clRect.right;
+ hit = cli.pfnHitTest(hwnd, dat, it.ptCursor.x, it.ptCursor.y, &contact, NULL, NULL);
+ if (hit == -1)
+ break;
+ if (contact->type != CLCIT_GROUP && contact->type != CLCIT_CONTACT)
+ break;
+ ClientToScreen(hwnd, &it.ptCursor);
+ ClientToScreen(hwnd, &ptClientOffset);
+ it.isTreeFocused = GetFocus() == hwnd;
+ it.rcItem.top = cli.pfnGetRowTopY(dat, hit) - dat->yScroll;
+ it.rcItem.bottom = it.rcItem.top + cli.pfnGetRowHeight(dat, hit);
+ OffsetRect(&it.rcItem, ptClientOffset.x, ptClientOffset.y);
+ it.isGroup = contact->type == CLCIT_GROUP;
+ it.hItem = contact->type == CLCIT_GROUP ? (HANDLE) contact->groupId : contact->hContact;
+ it.cbSize = sizeof(it);
+ dat->hInfoTipItem = cli.pfnContactToHItem(contact);
+ NotifyEventHooks(hShowInfoTipEvent, 0, (LPARAM) & it);
+ break;
+ }
+ case TIMERID_REBUILDAFTER:
+ KillTimer(hwnd,TIMERID_REBUILDAFTER);
+ cli.pfnInvalidateRect(hwnd,NULL,FALSE);
+ cli.pfnSaveStateAndRebuildList(hwnd,dat);
+ break;
+
+ case TIMERID_DELAYEDRESORTCLC:
+ KillTimer(hwnd,TIMERID_DELAYEDRESORTCLC);
+ cli.pfnInvalidateRect(hwnd,NULL,FALSE);
+ cli.pfnSortCLC(hwnd,dat,1);
+ cli.pfnRecalcScrollBar(hwnd,dat);
+ break;
+ }
+ break;
+
+ case WM_MBUTTONDOWN:
+ case WM_LBUTTONDOWN:
+ {
+ struct ClcContact *contact;
+ struct ClcGroup *group;
+ int hit;
+ DWORD hitFlags;
+
+ if (GetFocus() != hwnd)
+ SetFocus(hwnd);
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ cli.pfnEndRename(hwnd, dat, 1);
+ dat->ptDragStart.x = (short) LOWORD(lParam);
+ dat->ptDragStart.y = (short) HIWORD(lParam);
+ dat->szQuickSearch[0] = 0;
+ hit = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), &contact, &group, &hitFlags);
+ if (hit != -1) {
+ if (hit == dat->selection && hitFlags & CLCHT_ONITEMLABEL && dat->exStyle & CLS_EX_EDITLABELS) {
+ SetCapture(hwnd);
+ dat->iDragItem = dat->selection;
+ dat->dragStage = DRAGSTAGE_NOTMOVED | DRAGSTAGEF_MAYBERENAME;
+ dat->dragAutoScrolling = 0;
+ break;
+ } }
+
+ if (hit != -1 && contact->type == CLCIT_GROUP)
+ if (hitFlags & CLCHT_ONITEMICON) {
+ struct ClcGroup *selgroup;
+ struct ClcContact *selcontact;
+ dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, &selgroup);
+ cli.pfnSetGroupExpand(hwnd, dat, contact->group, -1);
+ if (dat->selection != -1) {
+ dat->selection =
+ cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf((SortedList*)&selgroup->cl,selcontact));
+ if (dat->selection == -1)
+ dat->selection = cli.pfnGetRowsPriorTo(&dat->list, contact->group, -1);
+ }
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ UpdateWindow(hwnd);
+ break;
+ }
+ if (hit != -1 && hitFlags & CLCHT_ONITEMCHECK) {
+ NMCLISTCONTROL nm;
+ contact->flags ^= CONTACTF_CHECKED;
+ if (contact->type == CLCIT_GROUP)
+ cli.pfnSetGroupChildCheckboxes(contact->group, contact->flags & CONTACTF_CHECKED);
+ cli.pfnRecalculateGroupCheckboxes(hwnd, dat);
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ nm.hdr.code = CLN_CHECKCHANGED;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags);
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+ }
+ if (!(hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMLABEL | CLCHT_ONITEMCHECK))) {
+ NMCLISTCONTROL nm;
+ nm.hdr.code = NM_CLICK;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ if (hit == -1)
+ nm.hItem = NULL;
+ else
+ nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags);
+ nm.iColumn = hitFlags & CLCHT_ONITEMEXTRA ? HIBYTE(HIWORD(hitFlags)) : -1;
+ nm.pt = dat->ptDragStart;
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+ }
+ if (hitFlags & (CLCHT_ONITEMCHECK | CLCHT_ONITEMEXTRA))
+ break;
+ dat->selection = hit;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ if (dat->selection != -1)
+ cli.pfnEnsureVisible(hwnd, dat, hit, 0);
+ UpdateWindow(hwnd);
+ if (dat->selection != -1 && (contact->type == CLCIT_CONTACT || contact->type == CLCIT_GROUP)
+ && !(hitFlags & (CLCHT_ONITEMEXTRA | CLCHT_ONITEMCHECK))) {
+ SetCapture(hwnd);
+ dat->iDragItem = dat->selection;
+ dat->dragStage = DRAGSTAGE_NOTMOVED;
+ dat->dragAutoScrolling = 0;
+ }
+ break;
+ }
+ case WM_MOUSEMOVE:
+ if (dat->iDragItem == -1) {
+ int iOldHotTrack = dat->iHotTrack;
+ if (dat->hwndRenameEdit != NULL)
+ break;
+ if (GetKeyState(VK_MENU) & 0x8000 || GetKeyState(VK_F10) & 0x8000)
+ break;
+ dat->iHotTrack = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), NULL, NULL, NULL);
+ if (iOldHotTrack != dat->iHotTrack) {
+ if (iOldHotTrack == -1)
+ SetCapture(hwnd);
+ else if (dat->iHotTrack == -1)
+ ReleaseCapture();
+ if (dat->exStyle & CLS_EX_TRACKSELECT) {
+ cli.pfnInvalidateItem(hwnd, dat, iOldHotTrack);
+ cli.pfnInvalidateItem(hwnd, dat, dat->iHotTrack);
+ }
+ cli.pfnHideInfoTip(hwnd, dat);
+ }
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ if (wParam == 0 && dat->hInfoTipItem == NULL) {
+ dat->ptInfoTip.x = (short) LOWORD(lParam);
+ dat->ptInfoTip.y = (short) HIWORD(lParam);
+ SetTimer(hwnd, TIMERID_INFOTIP, dat->infoTipTimeout, NULL);
+ }
+ break;
+ }
+ if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_NOTMOVED && !(dat->exStyle & CLS_EX_DISABLEDRAGDROP)) {
+ if (abs((short) LOWORD(lParam) - dat->ptDragStart.x) >= GetSystemMetrics(SM_CXDRAG)
+ || abs((short) HIWORD(lParam) - dat->ptDragStart.y) >= GetSystemMetrics(SM_CYDRAG))
+ dat->dragStage = (dat->dragStage & ~DRAGSTAGEM_STAGE) | DRAGSTAGE_ACTIVE;
+ }
+ if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_ACTIVE) {
+ HCURSOR hNewCursor;
+ RECT clRect;
+ POINT pt;
+ int target;
+
+ GetClientRect(hwnd, &clRect);
+ pt.x = (short) LOWORD(lParam);
+ pt.y = (short) HIWORD(lParam);
+ hNewCursor = LoadCursor(NULL, IDC_NO);
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ if (dat->dragAutoScrolling) {
+ KillTimer(hwnd, TIMERID_DRAGAUTOSCROLL);
+ dat->dragAutoScrolling = 0;
+ }
+ target = cli.pfnGetDropTargetInformation(hwnd, dat, pt);
+ if (dat->dragStage & DRAGSTAGEF_OUTSIDE && target != DROPTARGET_OUTSIDE) {
+ NMCLISTCONTROL nm;
+ struct ClcContact *contact;
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL);
+ nm.hdr.code = CLN_DRAGSTOP;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags);
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+ dat->dragStage &= ~DRAGSTAGEF_OUTSIDE;
+ }
+ switch (target) {
+ case DROPTARGET_ONSELF:
+ case DROPTARGET_ONCONTACT:
+ break;
+ case DROPTARGET_ONGROUP:
+ hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER));
+ break;
+ case DROPTARGET_INSERTION:
+ hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER));
+ break;
+ case DROPTARGET_OUTSIDE:
+ {
+ NMCLISTCONTROL nm;
+ struct ClcContact *contact;
+
+ if (pt.x >= 0 && pt.x < clRect.right
+ && ((pt.y < 0 && pt.y > -dat->dragAutoScrollHeight)
+ || (pt.y >= clRect.bottom && pt.y < clRect.bottom + dat->dragAutoScrollHeight))) {
+ if (!dat->dragAutoScrolling) {
+ if (pt.y < 0)
+ dat->dragAutoScrolling = -1;
+ else
+ dat->dragAutoScrolling = 1;
+ SetTimer(hwnd, TIMERID_DRAGAUTOSCROLL, dat->scrollTime, NULL);
+ }
+ SendMessage(hwnd, WM_TIMER, TIMERID_DRAGAUTOSCROLL, 0);
+ }
+
+ dat->dragStage |= DRAGSTAGEF_OUTSIDE;
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL);
+ nm.hdr.code = CLN_DRAGGING;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags);
+ nm.pt = pt;
+ if (SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm))
+ return 0;
+ break;
+ }
+ default:
+ {
+ struct ClcGroup *group;
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, NULL, &group);
+ if (group->parent)
+ hNewCursor = LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER));
+ break;
+ }
+ }
+ SetCursor(hNewCursor);
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (dat->iDragItem == -1)
+ break;
+ SetCursor((HCURSOR) GetClassLongPtr(hwnd, GCLP_HCURSOR));
+ if (dat->exStyle & CLS_EX_TRACKSELECT) {
+ dat->iHotTrack = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), NULL, NULL, NULL);
+ if (dat->iHotTrack == -1)
+ ReleaseCapture();
+ }
+ else ReleaseCapture();
+ KillTimer(hwnd, TIMERID_DRAGAUTOSCROLL);
+ if (dat->dragStage == (DRAGSTAGE_NOTMOVED | DRAGSTAGEF_MAYBERENAME))
+ SetTimer(hwnd, TIMERID_RENAME, GetDoubleClickTime(), NULL);
+ else if ((dat->dragStage & DRAGSTAGEM_STAGE) == DRAGSTAGE_ACTIVE) {
+ POINT pt;
+ int target;
+
+ pt.x = (short) LOWORD(lParam);
+ pt.y = (short) HIWORD(lParam);
+ target = cli.pfnGetDropTargetInformation(hwnd, dat, pt);
+ switch (target) {
+ case DROPTARGET_ONSELF:
+ break;
+ case DROPTARGET_ONCONTACT:
+ break;
+ case DROPTARGET_ONGROUP:
+ {
+ struct ClcContact *contactn, *contacto;
+ cli.pfnGetRowByIndex(dat, dat->selection, &contactn, NULL);
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contacto, NULL);
+ if (contacto->type == CLCIT_CONTACT) //dropee is a contact
+ CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)contacto->hContact, contactn->groupId);
+ else if (contacto->type == CLCIT_GROUP) { //dropee is a group
+ TCHAR szNewName[120];
+ TCHAR* szGroup = cli.pfnGetGroupName(contactn->groupId, NULL);
+ mir_sntprintf(szNewName, SIZEOF(szNewName), _T("%s\\%s"), szGroup, contacto->szText);
+ cli.pfnRenameGroup( contacto->groupId, szNewName );
+ }
+ break;
+ }
+ case DROPTARGET_INSERTION:
+ {
+ struct ClcContact *contact, *destcontact;
+ struct ClcGroup *destgroup;
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL);
+ if (cli.pfnGetRowByIndex(dat, dat->iInsertionMark, &destcontact, &destgroup) == -1 || destgroup != contact->group->parent)
+ CallService(MS_CLIST_GROUPMOVEBEFORE, contact->groupId, 0);
+ else {
+ if (destcontact->type == CLCIT_GROUP)
+ destgroup = destcontact->group;
+ else
+ destgroup = destgroup;
+ CallService(MS_CLIST_GROUPMOVEBEFORE, contact->groupId, destgroup->groupId);
+ }
+ break;
+ }
+ case DROPTARGET_OUTSIDE:
+ {
+ NMCLISTCONTROL nm;
+ struct ClcContact *contact;
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, NULL);
+ nm.hdr.code = CLN_DROPPED;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = cli.pfnContactToItemHandle(contact, &nm.flags);
+ nm.pt = pt;
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+ break;
+ }
+ default:
+ {
+ struct ClcGroup *group;
+ struct ClcContact *contact;
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &contact, &group);
+ if (group->parent) { //move to root
+ if (contact->type == CLCIT_CONTACT) //dropee is a contact
+ CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)contact->hContact, 0);
+ else if (contact->type == CLCIT_GROUP) { //dropee is a group
+ TCHAR szNewName[120];
+ lstrcpyn(szNewName, contact->szText, SIZEOF(szNewName));
+ cli.pfnRenameGroup( contact->groupId, szNewName );
+ } } } } }
+
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ dat->iDragItem = -1;
+ dat->iInsertionMark = -1;
+ break;
+
+ case WM_LBUTTONDBLCLK:
+ {
+ struct ClcContact *contact;
+ DWORD hitFlags;
+ ReleaseCapture();
+ dat->iHotTrack = -1;
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_RENAME);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ dat->szQuickSearch[0] = 0;
+ dat->selection = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), &contact, NULL, &hitFlags);
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ if (dat->selection != -1)
+ cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0);
+ if (!(hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMLABEL)))
+ break;
+ UpdateWindow(hwnd);
+ cli.pfnDoSelectionDefaultAction(hwnd, dat);
+ break;
+ }
+ case WM_CONTEXTMENU:
+ {
+ struct ClcContact *contact;
+ HMENU hMenu = NULL;
+ POINT pt;
+ DWORD hitFlags;
+
+ cli.pfnEndRename(hwnd, dat, 1);
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_RENAME);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ if (GetFocus() != hwnd)
+ SetFocus(hwnd);
+ dat->iHotTrack = -1;
+ dat->szQuickSearch[0] = 0;
+ pt.x = (short) LOWORD(lParam);
+ pt.y = (short) HIWORD(lParam);
+ if (pt.x == -1 && pt.y == -1) {
+ dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL);
+ if (dat->selection != -1)
+ cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0);
+ pt.x = dat->iconXSpace + 15;
+ pt.y = cli.pfnGetRowTopY(dat, dat->selection) - dat->yScroll + (int)(cli.pfnGetRowHeight(dat, dat->selection) * .7);
+ hitFlags = dat->selection == -1 ? CLCHT_NOWHERE : CLCHT_ONITEMLABEL;
+ }
+ else {
+ ScreenToClient(hwnd, &pt);
+ dat->selection = cli.pfnHitTest(hwnd, dat, pt.x, pt.y, &contact, NULL, &hitFlags);
+ }
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ if (dat->selection != -1)
+ cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0);
+ UpdateWindow(hwnd);
+
+ if (dat->selection != -1 && hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMCHECK | CLCHT_ONITEMLABEL)) {
+ if (contact->type == CLCIT_GROUP) {
+ hMenu = cli.pfnBuildGroupPopupMenu(contact->group);
+ ClientToScreen(hwnd, &pt);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
+ DestroyMenu(hMenu);
+ return 0;
+ }
+ else if (contact->type == CLCIT_CONTACT)
+ hMenu = (HMENU) CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM) contact->hContact, 0);
+ }
+ else {
+ //call parent for new group/hide offline menu
+ SendMessage(GetParent(hwnd), WM_CONTEXTMENU, wParam, lParam);
+ }
+ if (hMenu != NULL) {
+ ClientToScreen(hwnd, &pt);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
+ DestroyMenu(hMenu);
+ }
+ return 0;
+ }
+ case WM_MEASUREITEM:
+ return CallService(MS_CLIST_MENUMEASUREITEM, wParam, lParam);
+
+ case WM_DRAWITEM:
+ return CallService(MS_CLIST_MENUDRAWITEM, wParam, lParam);
+
+ case WM_COMMAND:
+ {
+ struct ClcContact *contact;
+ int hit = cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL);
+ if (hit == -1)
+ break;
+ if (contact->type == CLCIT_CONTACT)
+ if (CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_CONTACTMENU), (LPARAM) contact->hContact))
+ break;
+ switch (LOWORD(wParam)) {
+ case POPUP_NEWSUBGROUP:
+ if (contact->type != CLCIT_GROUP)
+ break;
+ SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~CLS_HIDEEMPTYGROUPS);
+ CallService(MS_CLIST_GROUPCREATE, contact->groupId, 0);
+ break;
+ case POPUP_RENAMEGROUP:
+ cli.pfnBeginRenameSelection(hwnd, dat);
+ break;
+ case POPUP_DELETEGROUP:
+ if (contact->type != CLCIT_GROUP)
+ break;
+ CallService(MS_CLIST_GROUPDELETE, contact->groupId, 0);
+ break;
+ case POPUP_GROUPHIDEOFFLINE:
+ if (contact->type != CLCIT_GROUP)
+ break;
+ CallService(MS_CLIST_GROUPSETFLAGS, contact->groupId,
+ MAKELPARAM(contact->group->hideOffline ? 0 : GROUPF_HIDEOFFLINE, GROUPF_HIDEOFFLINE));
+ break;
+ }
+ break;
+ }
+ case WM_DESTROY:
+ cli.pfnHideInfoTip(hwnd, dat);
+ {
+ int i;
+ for (i = 0; i <= FONTID_MAX; i++)
+ if (!dat->fontInfo[i].changed)
+ DeleteObject(dat->fontInfo[i].hFont);
+ }
+ if (dat->himlHighlight)
+ ImageList_Destroy(dat->himlHighlight);
+ if (dat->hwndRenameEdit)
+ DestroyWindow(dat->hwndRenameEdit);
+ if (!dat->bkChanged && dat->hBmpBackground)
+ DeleteObject(dat->hBmpBackground);
+ cli.pfnFreeGroup(&dat->list);
+ mir_free(dat);
+ cli.pfnUnregisterFileDropping(hwnd);
+ WindowList_Remove(hClcWindowList, hwnd);
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+}
diff --git a/src/modules/clist/clc.h b/src/modules/clist/clc.h
new file mode 100644
index 0000000000..0bbf197b44
--- /dev/null
+++ b/src/modules/clist/clc.h
@@ -0,0 +1,248 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+struct ClcContact {
+ BYTE type;
+ BYTE flags;
+ union {
+ struct {
+ WORD iImage;
+ HANDLE hContact;
+ };
+ struct {
+ WORD groupId;
+ struct ClcGroup *group;
+ };
+ };
+ BYTE iExtraImage[MAXEXTRACOLUMNS];
+ TCHAR szText[120-MAXEXTRACOLUMNS];
+ char * proto; // MS_PROTO_GETBASEPROTO
+};
+
+struct ClcData {
+ struct ClcGroup list;
+ int rowHeight;
+ int yScroll;
+ int selection;
+ struct ClcFontInfo fontInfo[FONTID_MAX + 1];
+ int scrollTime;
+ HIMAGELIST himlHighlight;
+ int groupIndent;
+ TCHAR szQuickSearch[128];
+ int iconXSpace;
+ HWND hwndRenameEdit;
+ COLORREF bkColour, selBkColour, selTextColour, hotTextColour, quickSearchColour;
+ int iDragItem, iInsertionMark;
+ int dragStage;
+ POINT ptDragStart;
+ int dragAutoScrolling;
+ int dragAutoScrollHeight;
+ int leftMargin;
+ int insertionMarkHitHeight;
+ HBITMAP hBmpBackground;
+ int backgroundBmpUse, bkChanged;
+ int iHotTrack;
+ int gammaCorrection;
+ DWORD greyoutFlags; //see m_clc.h
+ DWORD offlineModes;
+ DWORD exStyle;
+ POINT ptInfoTip;
+ int infoTipTimeout;
+ HANDLE hInfoTipItem;
+ HIMAGELIST himlExtraColumns;
+ int extraColumnsCount;
+ int extraColumnSpacing;
+ int checkboxSize;
+ int showSelAlways;
+ int showIdle;
+ int noVScrollbar;
+ int useWindowsColours;
+ int needsResort;
+};
+
+/* clc.c */
+extern int g_IconWidth, g_IconHeight;
+
+void fnClcOptionsChanged( void );
+void fnClcBroadcast( int msg, WPARAM wParam, LPARAM lParam );
+HMENU fnBuildGroupPopupMenu( struct ClcGroup* group );
+
+LRESULT CALLBACK fnContactListControlWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+/* clcidents.c */
+int fnGetRowsPriorTo( struct ClcGroup *group, struct ClcGroup *subgroup, int contactIndex );
+int fnFindItem( HWND hwnd, struct ClcData *dat, HANDLE hItem, struct ClcContact **contact, struct ClcGroup **subgroup, int *isVisible );
+int fnGetRowByIndex( struct ClcData *dat, int testindex, struct ClcContact **contact, struct ClcGroup **subgroup );
+HANDLE fnContactToHItem( struct ClcContact* contact );
+HANDLE fnContactToItemHandle( struct ClcContact * contact, DWORD * nmFlags );
+
+/* clcitems.c */
+struct ClcGroup* fnAddGroup( HWND hwnd, struct ClcData *dat, const TCHAR *szName, DWORD flags, int groupId, int calcTotalMembers );
+struct ClcGroup* fnRemoveItemFromGroup(HWND hwnd, struct ClcGroup *group, struct ClcContact *contact, int updateTotalCount);
+
+void fnFreeContact( struct ClcContact *p );
+void fnFreeGroup( struct ClcGroup *group );
+int fnAddInfoItemToGroup(struct ClcGroup *group, int flags, const TCHAR *pszText);
+int fnAddItemToGroup( struct ClcGroup *group,int iAboveItem );
+void fnAddContactToTree( HWND hwnd, struct ClcData *dat, HANDLE hContact, int updateTotalCount, int checkHideOffline);
+int fnAddContactToGroup( struct ClcData *dat, struct ClcGroup *group, HANDLE hContact);
+void fnDeleteItemFromTree( HWND hwnd, HANDLE hItem );
+void fnRebuildEntireList( HWND hwnd, struct ClcData *dat );
+int fnGetGroupContentsCount( struct ClcGroup *group, int visibleOnly );
+void fnSortCLC( HWND hwnd, struct ClcData *dat, int useInsertionSort );
+void fnSaveStateAndRebuildList(HWND hwnd, struct ClcData *dat);
+
+/* clcmsgs.c */
+LRESULT fnProcessExternalMessages(HWND hwnd, struct ClcData *dat, UINT msg, WPARAM wParam, LPARAM lParam );
+
+/* clcutils.c */
+char* fnGetGroupCountsText(struct ClcData *dat, struct ClcContact *contact );
+int fnHitTest( HWND hwnd, struct ClcData *dat, int testx, int testy, struct ClcContact **contact, struct ClcGroup **group, DWORD * flags );
+void fnScrollTo( HWND hwnd, struct ClcData *dat, int desty, int noSmooth );
+void fnEnsureVisible(HWND hwnd, struct ClcData *dat, int iItem, int partialOk );
+void fnRecalcScrollBar( HWND hwnd, struct ClcData *dat );
+void fnSetGroupExpand( HWND hwnd, struct ClcData *dat, struct ClcGroup *group, int newState );
+void fnDoSelectionDefaultAction( HWND hwnd, struct ClcData *dat );
+int fnFindRowByText(HWND hwnd, struct ClcData *dat, const TCHAR *text, int prefixOk );
+void fnEndRename(HWND hwnd, struct ClcData *dat, int save );
+void fnDeleteFromContactList( HWND hwnd, struct ClcData *dat );
+void fnBeginRenameSelection( HWND hwnd, struct ClcData *dat );
+void fnCalcEipPosition( struct ClcData *dat, struct ClcContact *contact, struct ClcGroup *group, POINT *result);
+int fnGetDropTargetInformation( HWND hwnd, struct ClcData *dat, POINT pt );
+int fnClcStatusToPf2( int status );
+int fnIsHiddenMode( struct ClcData *dat, int status );
+void fnHideInfoTip( HWND hwnd, struct ClcData *dat );
+void fnNotifyNewContact( HWND hwnd, HANDLE hContact );
+DWORD fnGetDefaultExStyle( void );
+void fnGetSetting( int i, LOGFONT* lf, COLORREF* colour );
+void fnGetDefaultFontSetting(int i, LOGFONT* lf, COLORREF* colour);
+void fnGetFontSetting( int i, LOGFONT* lf, COLORREF* colour );
+void fnLoadClcOptions( HWND hwnd, struct ClcData *dat );
+void fnRecalculateGroupCheckboxes( HWND hwnd, struct ClcData *dat );
+void fnSetGroupChildCheckboxes( struct ClcGroup *group, int checked );
+void fnInvalidateItem( HWND hwnd, struct ClcData *dat, int iItem );
+
+int fnGetRowBottomY(struct ClcData *dat, int item);
+int fnGetRowHeight(struct ClcData *dat, int item);
+int fnGetRowTopY(struct ClcData *dat, int item);
+int fnGetRowTotalHeight(struct ClcData *dat);
+int fnRowHitTest(struct ClcData *dat, int y);
+
+/* clcopts.c */
+int ClcOptInit(WPARAM wParam,LPARAM lParam);
+DWORD GetDefaultExStyle(void);
+void GetFontSetting(int i,LOGFONTA *lf,COLORREF *colour);
+
+/* clistmenus.c */
+HGENMENU fnGetProtocolMenu( const char* );
+int fnGetProtocolVisibility( const char* accName );
+
+int fnGetAccountIndexByPos(int Pos);
+int fnGetProtoIndexByPos(PROTOCOLDESCRIPTOR ** proto, int protoCnt, int Pos);
+void RebuildMenuOrder( void );
+
+INT_PTR MenuProcessCommand(WPARAM wParam, LPARAM lParam);
+
+/* clistsettings.c */
+TCHAR* fnGetContactDisplayName( HANDLE hContact, int mode );
+void fnGetDefaultFontSetting( int i, LOGFONT* lf, COLORREF * colour);
+void fnInvalidateDisplayNameCacheEntry( HANDLE hContact );
+
+ClcCacheEntryBase* fnGetCacheEntry( HANDLE hContact );
+ClcCacheEntryBase* fnCreateCacheItem ( HANDLE hContact );
+void fnCheckCacheItem( ClcCacheEntryBase* p );
+void fnFreeCacheItem( ClcCacheEntryBase* p );
+
+/* clcfiledrop.c */
+void InitFileDropping(void);
+
+void fnRegisterFileDropping ( HWND hwnd );
+void fnUnregisterFileDropping ( HWND hwnd );
+
+/* clistevents.c */
+struct CListEvent* fnAddEvent( CLISTEVENT *cle );
+CLISTEVENT* fnGetEvent( HANDLE hContact, int idx );
+
+struct CListEvent* fnCreateEvent( void );
+void fnFreeEvent( struct CListEvent* p );
+
+int fnEventsProcessContactDoubleClick( HANDLE hContact );
+int fnEventsProcessTrayDoubleClick( int );
+int fnGetImlIconIndex(HICON hIcon);
+int fnRemoveEvent( HANDLE hContact, HANDLE dbEvent );
+
+/* clistmod.c */
+int fnIconFromStatusMode(const char *szProto, int status, HANDLE hContact);
+int fnShowHide( WPARAM wParam, LPARAM lParam );
+HICON fnGetIconFromStatusMode( HANDLE hContact, const char *szProto, int status );
+TCHAR* fnGetStatusModeDescription( int wParam, int lParam);
+int fnGetWindowVisibleState(HWND hWnd, int iStepX, int iStepY);
+
+/* clisttray.c */
+void fnInitTray( void );
+void fnUninitTray( void );
+void fnLockTray( void );
+void fnUnlockTray( void );
+int fnCListTrayNotify(MIRANDASYSTRAYNOTIFY *msn);
+int fnTrayIconAdd(HWND hwnd, const char *szProto, const char *szIconProto, int status);
+int fnTrayIconDestroy( HWND hwnd );
+void fnTrayIconIconsChanged ( void );
+int fnTrayIconInit( HWND hwnd );
+TCHAR* fnTrayIconMakeTooltip( const TCHAR *szPrefix, const char *szProto );
+int fnTrayIconPauseAutoHide ( WPARAM wParam, LPARAM lParam );
+INT_PTR fnTrayIconProcessMessage ( WPARAM wParam, LPARAM lParam );
+void fnTrayIconRemove(HWND hwnd, const char *szProto);
+int fnTrayIconSetBaseInfo(HICON hIcon, const char *szPreferredProto);
+void fnTrayIconSetToBase ( char *szPreferredProto );
+void fnTrayIconTaskbarCreated( HWND hwnd );
+int fnTrayIconUpdate( HICON hNewIcon, const TCHAR *szNewTip, const char *szPreferredProto, int isBase );
+void fnTrayIconUpdateBase ( const char *szChangedProto );
+void fnTrayIconUpdateWithImageList ( int iImage, const TCHAR *szNewTip, char *szPreferredProto );
+
+VOID CALLBACK fnTrayCycleTimerProc(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime);
+
+/* clui.c */
+LRESULT CALLBACK fnContactListWndProc ( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
+void fnLoadCluiGlobalOpts( void );
+void fnCluiProtocolStatusChanged(int,const char*);
+void fnDrawMenuItem(DRAWITEMSTRUCT *dis, HICON hIcon, HICON eventIcon);
+
+/* contact.c */
+void fnChangeContactIcon ( HANDLE hContact, int iIcon, int add );
+void fnLoadContactTree ( void );
+int fnCompareContacts ( const struct ClcContact *contact1, const struct ClcContact *contact2);
+void fnSortContacts ( void );
+int fnSetHideOffline ( WPARAM wParam, LPARAM lParam );
+
+/* docking.c */
+int fnDocking_ProcessWindowMessage ( WPARAM wParam, LPARAM lParam );
+
+/* group.c */
+TCHAR* fnGetGroupName ( int idx, DWORD* pdwFlags );
+int fnRenameGroup ( int groupID, TCHAR* newName );
+
+/* keyboard.c */
+int fnHotKeysRegister ( HWND hwnd );
+void fnHotKeysUnregister ( HWND hwnd );
+int fnHotKeysProcess ( HWND hwnd, WPARAM wParam, LPARAM lParam );
+int fnHotkeysProcessMessage ( WPARAM wParam, LPARAM lParam );
diff --git a/src/modules/clist/clcfiledrop.cpp b/src/modules/clist/clcfiledrop.cpp
new file mode 100644
index 0000000000..aae299d7b8
--- /dev/null
+++ b/src/modules/clist/clcfiledrop.cpp
@@ -0,0 +1,278 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+#include <shlobj.h>
+
+struct CDropTarget : IDropTarget
+{
+ LONG refCount;
+ IDropTargetHelper *pDropTargetHelper;
+
+ ULONG STDMETHODCALLTYPE AddRef(void);
+ ULONG STDMETHODCALLTYPE Release(void);
+ HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject);
+
+ HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
+ HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
+ HRESULT STDMETHODCALLTYPE DragLeave(void);
+ HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect);
+}
+static dropTarget;
+
+static HWND hwndCurrentDrag = NULL;
+static int originalSelection;
+
+HRESULT CDropTarget::QueryInterface(REFIID riid, LPVOID * ppvObj)
+{
+ if (riid == IID_IDropTarget) {
+ *ppvObj = this;
+ AddRef();
+ return S_OK;
+ }
+ *ppvObj = NULL;
+ return E_NOINTERFACE;
+}
+
+ULONG CDropTarget::AddRef(void)
+{
+ return InterlockedIncrement(&refCount);
+}
+
+ULONG CDropTarget::Release(void)
+{
+ if (refCount == 1) {
+ if (pDropTargetHelper)
+ pDropTargetHelper->Release();
+ }
+ return InterlockedDecrement(&refCount);
+}
+
+static HANDLE HContactFromPoint(HWND hwnd, struct ClcData *dat, int x, int y, int *hitLine)
+{
+ int hit;
+ struct ClcContact *contact;
+ DWORD hitFlags;
+ char *szProto;
+ DWORD protoCaps;
+
+ hit = cli.pfnHitTest(hwnd, dat, x, y, &contact, NULL, &hitFlags);
+ if (hit == -1 || !(hitFlags & (CLCHT_ONITEMLABEL | CLCHT_ONITEMICON)) || contact->type != CLCIT_CONTACT)
+ return NULL;
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) contact->hContact, 0);
+ if (szProto == NULL)
+ return NULL;
+ protoCaps = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (!(protoCaps & PF1_FILESEND))
+ return NULL;
+ if (ID_STATUS_OFFLINE == DBGetContactSettingWord(contact->hContact, szProto, "Status", ID_STATUS_OFFLINE))
+ return NULL;
+ if (hitLine)
+ *hitLine = hit;
+ return contact->hContact;
+}
+
+HRESULT CDropTarget::DragOver(DWORD /*grfKeyState*/, POINTL pt, DWORD * pdwEffect)
+{
+ POINT shortPt;
+ struct ClcData *dat;
+ RECT clRect;
+ int hit;
+ HANDLE hContact;
+
+ if (pDropTargetHelper && hwndCurrentDrag)
+ pDropTargetHelper->DragOver((POINT*)&pt, *pdwEffect);
+
+ *pdwEffect = 0;
+ if (hwndCurrentDrag == NULL) {
+ *pdwEffect = DROPEFFECT_NONE;
+ return S_OK;
+ }
+ CallService(MS_CLIST_PAUSEAUTOHIDE, 0, 0);
+ dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);
+ shortPt.x = pt.x;
+ shortPt.y = pt.y;
+ ScreenToClient(hwndCurrentDrag, &shortPt);
+ GetClientRect(hwndCurrentDrag, &clRect);
+
+ if (shortPt.y < dat->dragAutoScrollHeight || shortPt.y >= clRect.bottom - dat->dragAutoScrollHeight) {
+ *pdwEffect |= DROPEFFECT_SCROLL;
+ cli.pfnScrollTo(hwndCurrentDrag, dat, dat->yScroll + (shortPt.y < dat->dragAutoScrollHeight ? -1 : 1) * dat->rowHeight * 2, 0);
+ }
+ hContact = HContactFromPoint(hwndCurrentDrag, dat, shortPt.x, shortPt.y, &hit);
+ if (hContact == NULL) {
+ hit = -1;
+ *pdwEffect |= DROPEFFECT_NONE;
+ }
+ else
+ *pdwEffect |= DROPEFFECT_COPY;
+
+ if (dat->selection != hit) {
+ dat->selection = hit;
+ cli.pfnInvalidateRect(hwndCurrentDrag, NULL, FALSE);
+ if (pDropTargetHelper) pDropTargetHelper->Show(FALSE);
+ UpdateWindow(hwndCurrentDrag);
+ if (pDropTargetHelper) pDropTargetHelper->Show(TRUE);
+ }
+
+ return S_OK;
+}
+
+HRESULT CDropTarget::DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect)
+{
+ HWND hwnd;
+ TCHAR szWindowClass[64];
+ POINT shortPt;
+
+ shortPt.x = pt.x;
+ shortPt.y = pt.y;
+ hwnd = WindowFromPoint(shortPt);
+ GetClassName(hwnd, szWindowClass, SIZEOF(szWindowClass));
+ if (!lstrcmp(szWindowClass, CLISTCONTROL_CLASS)) {
+ struct ClcData *dat;
+ hwndCurrentDrag = hwnd;
+ dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);
+ originalSelection = dat->selection;
+ dat->showSelAlways = 1;
+ }
+ if (pDropTargetHelper && hwndCurrentDrag)
+ pDropTargetHelper->DragEnter(hwndCurrentDrag, pDataObj, (POINT*)&pt, *pdwEffect);
+ return DragOver(grfKeyState, pt, pdwEffect);
+}
+
+HRESULT CDropTarget::DragLeave(void)
+{
+ if (hwndCurrentDrag) {
+ struct ClcData *dat;
+ if (pDropTargetHelper)
+ pDropTargetHelper->DragLeave();
+ dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);
+ dat->showSelAlways = 0;
+ dat->selection = originalSelection;
+ cli.pfnInvalidateRect(hwndCurrentDrag, NULL, FALSE);
+ }
+ hwndCurrentDrag = NULL;
+ return S_OK;
+}
+
+static void AddToFileList(TCHAR ***pppFiles, int *totalCount, const TCHAR *szFilename)
+{
+ *pppFiles = (TCHAR **) mir_realloc(*pppFiles, (++*totalCount + 1) * sizeof(TCHAR *));
+ (*pppFiles)[*totalCount] = NULL;
+ (*pppFiles)[*totalCount - 1] = mir_tstrdup(szFilename);
+ if (GetFileAttributes(szFilename) & FILE_ATTRIBUTE_DIRECTORY) {
+ WIN32_FIND_DATA fd;
+ HANDLE hFind;
+ TCHAR szPath[MAX_PATH];
+ lstrcpy(szPath, szFilename);
+ lstrcat(szPath, _T("\\*"));
+ if (hFind = FindFirstFile(szPath, &fd)) {
+ do {
+ if (!lstrcmp(fd.cFileName, _T(".")) || !lstrcmp(fd.cFileName, _T("..")))
+ continue;
+ lstrcpy(szPath, szFilename);
+ lstrcat(szPath, _T("\\"));
+ lstrcat(szPath, fd.cFileName);
+ AddToFileList(pppFiles, totalCount, szPath);
+ } while (FindNextFile(hFind, &fd));
+ FindClose(hFind);
+ }
+ }
+}
+
+HRESULT CDropTarget::Drop(IDataObject * pDataObj, DWORD /*fKeyState*/, POINTL pt, DWORD * pdwEffect)
+{
+ FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
+ STGMEDIUM stg;
+ HDROP hDrop;
+ POINT shortPt;
+ struct ClcData *dat;
+ HANDLE hContact;
+
+ if (pDropTargetHelper && hwndCurrentDrag)
+ pDropTargetHelper->Drop(pDataObj, (POINT*)&pt, *pdwEffect);
+
+ *pdwEffect = DROPEFFECT_NONE;
+ if (hwndCurrentDrag == NULL || S_OK != pDataObj->GetData(&fe, &stg))
+ return S_OK;
+ hDrop = (HDROP) stg.hGlobal;
+ dat = (struct ClcData *) GetWindowLongPtr(hwndCurrentDrag, 0);
+
+ shortPt.x = pt.x;
+ shortPt.y = pt.y;
+ ScreenToClient(hwndCurrentDrag, &shortPt);
+ hContact = HContactFromPoint(hwndCurrentDrag, dat, shortPt.x, shortPt.y, NULL);
+ if (hContact != NULL) {
+ TCHAR **ppFiles = NULL;
+ TCHAR szFilename[MAX_PATH];
+ int fileCount, totalCount = 0, i;
+
+ fileCount = DragQueryFile(hDrop, -1, NULL, 0);
+ ppFiles = NULL;
+ for (i = 0; i < fileCount; i++) {
+ DragQueryFile(hDrop, i, szFilename, SIZEOF(szFilename));
+ AddToFileList(&ppFiles, &totalCount, szFilename);
+ }
+
+ if (!CallService(MS_FILE_SENDSPECIFICFILEST, (WPARAM) hContact, (LPARAM) ppFiles))
+ *pdwEffect = DROPEFFECT_COPY;
+
+ for (i = 0; ppFiles[i]; i++)
+ mir_free(ppFiles[i]);
+ mir_free(ppFiles);
+ }
+
+ if (stg.pUnkForRelease)
+ stg.pUnkForRelease->Release();
+ else
+ GlobalFree(stg.hGlobal);
+
+ DragLeave();
+ return S_OK;
+}
+
+static VOID CALLBACK CreateDropTargetHelperTimerProc(HWND hwnd, UINT, UINT_PTR idEvent, DWORD)
+{
+ KillTimer(hwnd, idEvent);
+ //This is a ludicrously slow function (~200ms) so we delay load it a bit.
+ if (S_OK != CoCreateInstance(CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER,
+ IID_IDropTargetHelper, (LPVOID*)&dropTarget.pDropTargetHelper))
+ dropTarget.pDropTargetHelper = NULL;
+}
+
+void InitFileDropping(void)
+{
+ // Disabled as this function loads tons of dlls for no apparenet reason
+ // we will se what the reaction will be
+// SetTimer(NULL, 1, 1000, CreateDropTargetHelperTimerProc);
+}
+
+void fnRegisterFileDropping(HWND hwnd)
+{
+ RegisterDragDrop(hwnd, (IDropTarget *) & dropTarget);
+}
+
+void fnUnregisterFileDropping(HWND hwnd)
+{
+ RevokeDragDrop(hwnd);
+}
diff --git a/src/modules/clist/clcidents.cpp b/src/modules/clist/clcidents.cpp
new file mode 100644
index 0000000000..7cb18760fe
--- /dev/null
+++ b/src/modules/clist/clcidents.cpp
@@ -0,0 +1,201 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+/* the CLC uses 3 different ways to identify elements in its list, this file
+contains routines to convert between them.
+
+1) struct ClcContact/struct ClcGroup pair. Only ever used within the duration
+ of a single operation, but used at some point in nearly everything
+2) index integer. The 0-based number of the item from the top. Only visible
+ items are counted (ie not closed groups). Used for saving selection and drag
+ highlight
+3) hItem handle. Either the hContact or (hGroup|HCONTACT_ISGROUP). Used
+ exclusively externally
+
+1->2: GetRowsPriorTo()
+1->3: ContactToHItem()
+3->1: FindItem()
+2->1: GetRowByIndex()
+*/
+
+int fnGetRowsPriorTo(struct ClcGroup *group, struct ClcGroup *subgroup, int contactIndex)
+{
+ int count = 0;
+
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ group = group->parent;
+ if (group == NULL)
+ break;
+ group->scanIndex++;
+ continue;
+ }
+ if (group == subgroup && contactIndex == group->scanIndex)
+ return count;
+ count++;
+ if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ if (group->cl.items[group->scanIndex]->group == subgroup && contactIndex == -1)
+ return count - 1;
+ if (group->cl.items[group->scanIndex]->group->expanded) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ continue;
+ }
+ }
+ group->scanIndex++;
+ }
+ return -1;
+}
+
+int fnFindItem(HWND hwnd, struct ClcData *dat, HANDLE hItem, struct ClcContact **contact, struct ClcGroup **subgroup, int *isVisible)
+{
+ int index = 0;
+ int nowVisible = 1;
+ struct ClcGroup *group = &dat->list;
+
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ struct ClcGroup *tgroup;
+ group = group->parent;
+ if (group == NULL)
+ break;
+ nowVisible = 1;
+ for (tgroup = group; tgroup; tgroup = tgroup->parent)
+ if (!group->expanded) {
+ nowVisible = 0;
+ break;
+ }
+ group->scanIndex++;
+ continue;
+ }
+ if (nowVisible)
+ index++;
+ if ((IsHContactGroup(hItem) && group->cl.items[group->scanIndex]->type == CLCIT_GROUP
+ && ((unsigned) hItem & ~HCONTACT_ISGROUP) == group->cl.items[group->scanIndex]->groupId) || (IsHContactContact(hItem)
+ && group->cl.items[group->scanIndex]->type == CLCIT_CONTACT
+ && group->cl.items[group->scanIndex]->hContact == hItem) || (IsHContactInfo(hItem)
+ && group->cl.items[group->scanIndex]->type == CLCIT_INFO
+ && group->cl.items[group->scanIndex]->hContact == (HANDLE) ((UINT_PTR)hItem & ~HCONTACT_ISINFO)))
+ {
+ if (isVisible) {
+ if (!nowVisible)
+ *isVisible = 0;
+ else {
+ int posY = cli.pfnGetRowTopY(dat, index+1);
+ if (posY < dat->yScroll)
+ *isVisible = 0;
+ else {
+ RECT clRect;
+ GetClientRect(hwnd, &clRect);
+ if (posY >= dat->yScroll + clRect.bottom)
+ *isVisible = 0;
+ else
+ *isVisible = 1;
+ }
+ }
+ }
+ if (contact)
+ *contact = group->cl.items[group->scanIndex];
+ if (subgroup)
+ *subgroup = group;
+ return 1;
+ }
+ if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ nowVisible &= group->expanded;
+ continue;
+ }
+ group->scanIndex++;
+ }
+ return 0;
+}
+
+int fnGetRowByIndex(struct ClcData *dat, int testindex, struct ClcContact **contact, struct ClcGroup **subgroup)
+{
+ int index = 0;
+ struct ClcGroup *group = &dat->list;
+
+ if (testindex<0)
+ return (-1);
+
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ group = group->parent;
+ if (group == NULL)
+ break;
+ group->scanIndex++;
+ continue;
+ }
+ if (testindex == index) {
+ if (contact)
+ *contact = group->cl.items[group->scanIndex];
+ if (subgroup)
+ *subgroup = group;
+ return index;
+ }
+ index++;
+ if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP && group->cl.items[group->scanIndex]->group->expanded) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ continue;
+ }
+ group->scanIndex++;
+ }
+ return -1;
+}
+
+HANDLE fnContactToHItem(struct ClcContact * contact)
+{
+ switch (contact->type) {
+ case CLCIT_CONTACT:
+ return contact->hContact;
+ case CLCIT_GROUP:
+ return (HANDLE) (contact->groupId | HCONTACT_ISGROUP);
+ case CLCIT_INFO:
+ return (HANDLE) ((UINT_PTR) contact->hContact | HCONTACT_ISINFO);
+ }
+ return NULL;
+}
+
+HANDLE fnContactToItemHandle(struct ClcContact * contact, DWORD * nmFlags)
+{
+ switch (contact->type) {
+ case CLCIT_CONTACT:
+ return contact->hContact;
+ case CLCIT_GROUP:
+ if (nmFlags)
+ *nmFlags |= CLNF_ISGROUP;
+ return (HANDLE) contact->groupId;
+ case CLCIT_INFO:
+ if (nmFlags)
+ *nmFlags |= CLNF_ISINFO;
+ return (HANDLE) ((UINT_PTR) contact->hContact | HCONTACT_ISINFO);
+ }
+ return NULL;
+}
diff --git a/src/modules/clist/clcitems.cpp b/src/modules/clist/clcitems.cpp
new file mode 100644
index 0000000000..cc338d5ec9
--- /dev/null
+++ b/src/modules/clist/clcitems.cpp
@@ -0,0 +1,707 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+//routines for managing adding/removal of items in the list, including sorting
+
+int fnAddItemToGroup(struct ClcGroup *group, int iAboveItem)
+{
+ struct ClcContact* newItem = cli.pfnCreateClcContact();
+ newItem->type = CLCIT_DIVIDER;
+ newItem->flags = 0;
+ newItem->szText[0] = '\0';
+ memset( newItem->iExtraImage, 0xFF, SIZEOF(newItem->iExtraImage));
+
+ List_Insert(( SortedList* )&group->cl, newItem, iAboveItem );
+ return iAboveItem;
+}
+
+struct ClcGroup* fnAddGroup(HWND hwnd, struct ClcData *dat, const TCHAR *szName, DWORD flags, int groupId, int calcTotalMembers)
+{
+ TCHAR *pBackslash, *pNextField, szThisField[ SIZEOF(dat->list.cl.items[0]->szText) ];
+ struct ClcGroup *group = &dat->list;
+ int i, compareResult;
+
+ dat->needsResort = 1;
+ if (!(GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_USEGROUPS))
+ return &dat->list;
+
+ pNextField = ( TCHAR* )szName;
+ do {
+ pBackslash = _tcschr(pNextField, '\\');
+ if (pBackslash == NULL) {
+ lstrcpyn(szThisField, pNextField, SIZEOF(szThisField));
+ pNextField = NULL;
+ }
+ else {
+ lstrcpyn(szThisField, pNextField, min( SIZEOF(szThisField), pBackslash - pNextField + 1 ));
+ pNextField = pBackslash + 1;
+ }
+ compareResult = 1;
+ for (i = 0; i < group->cl.count; i++) {
+ if (group->cl.items[i]->type == CLCIT_CONTACT)
+ break;
+ if (group->cl.items[i]->type != CLCIT_GROUP)
+ continue;
+ compareResult = lstrcmp(szThisField, group->cl.items[i]->szText);
+ if (compareResult == 0) {
+ if (pNextField == NULL && flags != (DWORD) - 1) {
+ group->cl.items[i]->groupId = (WORD) groupId;
+ group = group->cl.items[i]->group;
+ group->expanded = (flags & GROUPF_EXPANDED) != 0;
+ group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0;
+ group->groupId = groupId;
+ }
+ else
+ group = group->cl.items[i]->group;
+ break;
+ }
+ if (pNextField == NULL && group->cl.items[i]->groupId == 0)
+ break;
+ if (!(dat->exStyle & CLS_EX_SORTGROUPSALPHA) && groupId && group->cl.items[i]->groupId > groupId)
+ break;
+ }
+ if (compareResult) {
+ if (groupId == 0)
+ return NULL;
+ i = cli.pfnAddItemToGroup(group, i);
+ group->cl.items[i]->type = CLCIT_GROUP;
+ lstrcpyn(group->cl.items[i]->szText, szThisField, SIZEOF( group->cl.items[i]->szText ));
+ group->cl.items[i]->groupId = (WORD) (pNextField ? 0 : groupId);
+ group->cl.items[i]->group = (struct ClcGroup *) mir_alloc(sizeof(struct ClcGroup));
+ group->cl.items[i]->group->parent = group;
+ group = group->cl.items[i]->group;
+ memset( &group->cl, 0, sizeof( group->cl ));
+ group->cl.increment = 10;
+ if (flags == (DWORD) - 1 || pNextField != NULL) {
+ group->expanded = 0;
+ group->hideOffline = 0;
+ }
+ else {
+ group->expanded = (flags & GROUPF_EXPANDED) != 0;
+ group->hideOffline = (flags & GROUPF_HIDEOFFLINE) != 0;
+ }
+ group->groupId = pNextField ? 0 : groupId;
+ group->totalMembers = 0;
+ if (flags != (DWORD) - 1 && pNextField == NULL && calcTotalMembers) {
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact) {
+ ClcCacheEntryBase* cache = cli.pfnGetCacheEntry( hContact );
+ if ( !lstrcmp( cache->group, szName) && (style & CLS_SHOWHIDDEN || !cache->isHidden ))
+ group->totalMembers++;
+
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ }
+ }
+ }
+ } while (pNextField);
+ return group;
+}
+
+void fnFreeContact(struct ClcContact* p)
+{
+ if (p->type == CLCIT_GROUP) {
+ cli.pfnFreeGroup(p->group);
+ mir_free(p->group);
+} }
+
+void fnFreeGroup(struct ClcGroup *group)
+{
+ int i;
+ for (i = 0; i < group->cl.count; i++) {
+ cli.pfnFreeContact(group->cl.items[i]);
+ mir_free(group->cl.items[i]);
+ }
+ if (group->cl.items)
+ mir_free(group->cl.items);
+ group->cl.limit = group->cl.count = 0;
+ group->cl.items = NULL;
+}
+
+static int iInfoItemUniqueHandle = 0;
+int fnAddInfoItemToGroup(struct ClcGroup *group, int flags, const TCHAR *pszText)
+{
+ int i = 0;
+
+ if (flags & CLCIIF_BELOWCONTACTS)
+ i = group->cl.count;
+ else if (flags & CLCIIF_BELOWGROUPS) {
+ for (; i < group->cl.count; i++)
+ if (group->cl.items[i]->type == CLCIT_CONTACT)
+ break;
+ }
+ else
+ for (; i < group->cl.count; i++)
+ if (group->cl.items[i]->type != CLCIT_INFO)
+ break;
+ i = cli.pfnAddItemToGroup(group, i);
+ iInfoItemUniqueHandle = (iInfoItemUniqueHandle + 1) & 0xFFFF;
+ if (iInfoItemUniqueHandle == 0)
+ ++iInfoItemUniqueHandle;
+ group->cl.items[i]->type = CLCIT_INFO;
+ group->cl.items[i]->flags = (BYTE) flags;
+ group->cl.items[i]->hContact = (HANDLE)++ iInfoItemUniqueHandle;
+ lstrcpyn(group->cl.items[i]->szText, pszText, SIZEOF( group->cl.items[i]->szText ));
+ return i;
+}
+
+int fnAddContactToGroup(struct ClcData *dat, struct ClcGroup *group, HANDLE hContact)
+{
+ char *szProto;
+ WORD apparentMode;
+ DWORD idleMode;
+
+ int i, index = -1;
+
+ dat->needsResort = 1;
+ for (i = group->cl.count - 1; i >= 0; i--) {
+ if (group->cl.items[i]->hContact == hContact )
+ return i;
+
+ if ( index == -1 )
+ if (group->cl.items[i]->type != CLCIT_INFO || !(group->cl.items[i]->flags & CLCIIF_BELOWCONTACTS))
+ index = i;
+ }
+
+ i = cli.pfnAddItemToGroup(group, index + 1);
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ group->cl.items[i]->type = CLCIT_CONTACT;
+ group->cl.items[i]->iImage = CallService(MS_CLIST_GETCONTACTICON, (WPARAM) hContact, 0);
+ group->cl.items[i]->hContact = hContact;
+ group->cl.items[i]->proto = szProto;
+ if (szProto != NULL && !cli.pfnIsHiddenMode(dat, DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE)))
+ group->cl.items[i]->flags |= CONTACTF_ONLINE;
+ apparentMode = szProto != NULL ? DBGetContactSettingWord(hContact, szProto, "ApparentMode", 0) : 0;
+ if (apparentMode == ID_STATUS_OFFLINE)
+ group->cl.items[i]->flags |= CONTACTF_INVISTO;
+ else if (apparentMode == ID_STATUS_ONLINE)
+ group->cl.items[i]->flags |= CONTACTF_VISTO;
+ else if (apparentMode)
+ group->cl.items[i]->flags |= CONTACTF_VISTO | CONTACTF_INVISTO;
+ if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0))
+ group->cl.items[i]->flags |= CONTACTF_NOTONLIST;
+ idleMode = szProto != NULL ? DBGetContactSettingDword(hContact, szProto, "IdleTS", 0) : 0;
+ if (idleMode)
+ group->cl.items[i]->flags |= CONTACTF_IDLE;
+ lstrcpyn(group->cl.items[i]->szText, cli.pfnGetContactDisplayName(hContact,0), SIZEOF(group->cl.items[i]->szText));
+
+ { ClcCacheEntryBase* p = cli.pfnGetCacheEntry(hContact);
+ if ( p != NULL ) {
+ if ( p->group ) mir_free( p->group );
+ p->group = NULL;
+ } }
+
+ return i;
+}
+
+void fnAddContactToTree(HWND hwnd, struct ClcData *dat, HANDLE hContact, int updateTotalCount, int checkHideOffline)
+{
+ struct ClcGroup *group;
+ DBVARIANT dbv;
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ WORD status = ID_STATUS_OFFLINE;
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+
+ dat->needsResort = 1;
+ if (style & CLS_NOHIDEOFFLINE)
+ checkHideOffline = 0;
+ if (checkHideOffline)
+ if (szProto != NULL)
+ status = DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE);
+
+ if ( DBGetContactSettingTString(hContact, "CList", "Group", &dbv))
+ group = &dat->list;
+ else {
+ group = cli.pfnAddGroup(hwnd, dat, dbv.ptszVal, (DWORD) - 1, 0, 0);
+ if (group == NULL) {
+ int i, len;
+ DWORD groupFlags;
+ TCHAR *szGroupName;
+ if (!(style & CLS_HIDEEMPTYGROUPS)) {
+ mir_free(dbv.ptszVal);
+ return;
+ }
+ if (checkHideOffline && cli.pfnIsHiddenMode(dat, status)) {
+ for (i = 1;; i++) {
+ szGroupName = cli.pfnGetGroupName(i, &groupFlags);
+ if (szGroupName == NULL) {
+ mir_free(dbv.ptszVal);
+ return;
+ } //never happens
+ if (!lstrcmp(szGroupName, dbv.ptszVal))
+ break;
+ }
+ if (groupFlags & GROUPF_HIDEOFFLINE) {
+ mir_free(dbv.ptszVal);
+ return;
+ }
+ }
+ for (i = 1;; i++) {
+ szGroupName = cli.pfnGetGroupName(i, &groupFlags);
+ if (szGroupName == NULL) {
+ mir_free(dbv.ptszVal);
+ return;
+ } //never happens
+ if (!lstrcmp(szGroupName, dbv.ptszVal))
+ break;
+ len = lstrlen(szGroupName);
+ if (!_tcsncmp(szGroupName, dbv.ptszVal, len) && dbv.ptszVal[len] == '\\')
+ cli.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 1);
+ }
+ group = cli.pfnAddGroup(hwnd, dat, dbv.ptszVal, groupFlags, i, 1);
+ }
+ mir_free(dbv.ptszVal);
+ }
+ if (checkHideOffline) {
+ if (cli.pfnIsHiddenMode(dat, status) && (style & CLS_HIDEOFFLINE || group->hideOffline)) {
+ if (updateTotalCount)
+ group->totalMembers++;
+ return;
+ }
+ }
+ cli.pfnAddContactToGroup(dat, group, hContact);
+ if (updateTotalCount)
+ group->totalMembers++;
+}
+
+struct ClcGroup* fnRemoveItemFromGroup(HWND hwnd, struct ClcGroup *group, struct ClcContact *contact, int updateTotalCount)
+{
+ int iContact;
+ if (( iContact = List_IndexOf(( SortedList* )&group->cl, contact )) == -1 )
+ return group;
+
+ if (updateTotalCount && contact->type == CLCIT_CONTACT)
+ group->totalMembers--;
+
+ { ClcCacheEntryBase* p = cli.pfnGetCacheEntry(contact->hContact);
+ if ( p != NULL ) {
+ if ( p->group ) mir_free( p->group );
+ p->group = NULL;
+ } }
+
+ cli.pfnFreeContact( group->cl.items[iContact] );
+ mir_free( group->cl.items[iContact] );
+ List_Remove(( SortedList* )&group->cl, iContact );
+
+ if ((GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_HIDEEMPTYGROUPS) && group->cl.count == 0) {
+ int i;
+ if (group->parent == NULL)
+ return group;
+ for (i = 0; i < group->parent->cl.count; i++)
+ if (group->parent->cl.items[i]->type == CLCIT_GROUP && group->parent->cl.items[i]->groupId == group->groupId)
+ break;
+ if (i == group->parent->cl.count)
+ return group; //never happens
+ return cli.pfnRemoveItemFromGroup(hwnd, group->parent, group->parent->cl.items[i], 0);
+ }
+ return group;
+}
+
+void fnDeleteItemFromTree(HWND hwnd, HANDLE hItem)
+{
+ struct ClcContact *contact;
+ struct ClcGroup *group;
+ struct ClcData *dat = (struct ClcData *) GetWindowLongPtr(hwnd, 0);
+
+ dat->needsResort = 1;
+ if (!cli.pfnFindItem(hwnd, dat, hItem, &contact, &group, NULL)) {
+ DBVARIANT dbv;
+ int i, nameOffset;
+ if (!IsHContactContact(hItem))
+ return;
+ if (DBGetContactSettingTString(hItem, "CList", "Group", &dbv))
+ return;
+
+ //decrease member counts of all parent groups too
+ group = &dat->list;
+ nameOffset = 0;
+ for (i = 0;; i++) {
+ if (group->scanIndex == group->cl.count)
+ break;
+ if (group->cl.items[i]->type == CLCIT_GROUP) {
+ int len = lstrlen(group->cl.items[i]->szText);
+ if (!_tcsncmp(group->cl.items[i]->szText, dbv.ptszVal + nameOffset, len) &&
+ (dbv.ptszVal[nameOffset + len] == '\\' || dbv.ptszVal[nameOffset + len] == '\0')) {
+ group->totalMembers--;
+ if (dbv.ptszVal[nameOffset + len] == '\0')
+ break;
+ }
+ }
+ }
+ mir_free(dbv.ptszVal);
+ }
+ else
+ cli.pfnRemoveItemFromGroup(hwnd, group, contact, 1);
+}
+
+void fnRebuildEntireList(HWND hwnd, struct ClcData *dat)
+{
+ char *szProto;
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ HANDLE hContact;
+ struct ClcGroup *group;
+ DBVARIANT dbv;
+
+ dat->list.expanded = 1;
+ dat->list.hideOffline = DBGetContactSettingByte(NULL, "CLC", "HideOfflineRoot", 0) && style&CLS_USEGROUPS;
+ dat->list.cl.count = dat->list.cl.limit = 0;
+ dat->selection = -1;
+ {
+ int i;
+ TCHAR *szGroupName;
+ DWORD groupFlags;
+
+ for (i = 1;; i++) {
+ szGroupName = cli.pfnGetGroupName(i, &groupFlags);
+ if (szGroupName == NULL)
+ break;
+ cli.pfnAddGroup(hwnd, dat, szGroupName, groupFlags, i, 0);
+ }
+ }
+
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact) {
+ if (style & CLS_SHOWHIDDEN || !DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) {
+ if (DBGetContactSettingTString(hContact, "CList", "Group", &dbv))
+ group = &dat->list;
+ else {
+ group = cli.pfnAddGroup(hwnd, dat, dbv.ptszVal, (DWORD) - 1, 0, 0);
+ if (group == NULL && style & CLS_SHOWHIDDEN) group = &dat->list;
+ mir_free(dbv.ptszVal);
+ }
+
+ if (group != NULL) {
+ group->totalMembers++;
+ if (!(style & CLS_NOHIDEOFFLINE) && (style & CLS_HIDEOFFLINE || group->hideOffline)) {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto == NULL) {
+ if (!cli.pfnIsHiddenMode(dat, ID_STATUS_OFFLINE))
+ cli.pfnAddContactToGroup(dat, group, hContact);
+ }
+ else if (!cli.pfnIsHiddenMode(dat, DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE)))
+ cli.pfnAddContactToGroup(dat, group, hContact);
+ }
+ else cli.pfnAddContactToGroup(dat, group, hContact);
+ }
+ }
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ }
+
+ if (style & CLS_HIDEEMPTYGROUPS) {
+ group = &dat->list;
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ group = group->parent;
+ if (group == NULL)
+ break;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ if (group->cl.items[group->scanIndex]->group->cl.count == 0) {
+ group = cli.pfnRemoveItemFromGroup(hwnd, group, group->cl.items[group->scanIndex], 0);
+ }
+ else {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ }
+ continue;
+ }
+ group->scanIndex++;
+ }
+ }
+
+ cli.pfnSortCLC(hwnd, dat, 0);
+}
+
+int fnGetGroupContentsCount(struct ClcGroup *group, int visibleOnly)
+{
+ int count = group->cl.count;
+ struct ClcGroup *topgroup = group;
+
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ if (group == topgroup)
+ break;
+ group = group->parent;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP && (!visibleOnly || group->cl.items[group->scanIndex]->group->expanded)) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ count += group->cl.count;
+ continue;
+ }
+ group->scanIndex++;
+ }
+ return count;
+}
+
+static int __cdecl GroupSortProc(const void* p1, const void* p2)
+{
+ ClcContact **contact1 = ( ClcContact** )p1, **contact2 = ( ClcContact** )p2;
+
+ return lstrcmpi(contact1[0]->szText, contact2[0]->szText);
+}
+
+static int __cdecl ContactSortProc(const void* p1, const void* p2)
+{
+ ClcContact **contact1 = ( ClcContact** )p1, **contact2 = ( ClcContact** )p2;
+
+ int result = cli.pfnCompareContacts( contact1[0], contact2[0] );
+ if (result)
+ return result;
+ //nothing to distinguish them, so make sure they stay in the same order
+ return (int)((INT_PTR) contact2[0]->hContact - (INT_PTR) contact1[0]->hContact);
+}
+
+static void InsertionSort(struct ClcContact **pContactArray, int nArray, int (*CompareProc) (const void *, const void *))
+{
+ int i, j;
+ struct ClcContact* testElement;
+
+ for (i = 1; i < nArray; i++) {
+ if (CompareProc(&pContactArray[i - 1], &pContactArray[i]) > 0) {
+ testElement = pContactArray[i];
+ for (j = i - 2; j >= 0; j--)
+ if (CompareProc(&pContactArray[j], &testElement) <= 0)
+ break;
+ j++;
+ memmove(&pContactArray[j + 1], &pContactArray[j], sizeof(void*) * (i - j));
+ pContactArray[j] = testElement;
+} } }
+
+static void SortGroup(struct ClcData *dat, struct ClcGroup *group, int useInsertionSort)
+{
+ int i, sortCount;
+
+ for (i = group->cl.count - 1; i >= 0; i--) {
+ if (group->cl.items[i]->type == CLCIT_DIVIDER) {
+ mir_free( group->cl.items[i] );
+ List_Remove(( SortedList* )&group->cl, i );
+ } }
+
+ for (i = 0; i < group->cl.count; i++)
+ if (group->cl.items[i]->type != CLCIT_INFO)
+ break;
+ if (i > group->cl.count - 2)
+ return;
+ if (group->cl.items[i]->type == CLCIT_GROUP) {
+ if (dat->exStyle & CLS_EX_SORTGROUPSALPHA) {
+ for (sortCount = 0; i + sortCount < group->cl.count; sortCount++)
+ if (group->cl.items[i + sortCount]->type != CLCIT_GROUP)
+ break;
+ qsort(group->cl.items + i, sortCount, sizeof(void*), GroupSortProc);
+ i = i + sortCount;
+ }
+ for (; i < group->cl.count; i++)
+ if (group->cl.items[i]->type == CLCIT_CONTACT)
+ break;
+ if (group->cl.count - i < 2)
+ return;
+ }
+ for (sortCount = 0; i + sortCount < group->cl.count; sortCount++)
+ if (group->cl.items[i + sortCount]->type != CLCIT_CONTACT)
+ break;
+ if (useInsertionSort)
+ InsertionSort(group->cl.items + i, sortCount, ContactSortProc);
+ else
+ qsort(group->cl.items + i, sortCount, sizeof(void*), ContactSortProc);
+ if (dat->exStyle & CLS_EX_DIVIDERONOFF) {
+ int prevContactOnline = 0;
+ for (i = 0; i < group->cl.count; i++) {
+ if (group->cl.items[i]->type != CLCIT_CONTACT)
+ continue;
+ if (group->cl.items[i]->flags & CONTACTF_ONLINE)
+ prevContactOnline = 1;
+ else {
+ if (prevContactOnline) {
+ i = cli.pfnAddItemToGroup(group, i);
+ group->cl.items[i]->type = CLCIT_DIVIDER;
+ lstrcpy(group->cl.items[i]->szText, TranslateT("Offline"));
+ }
+ break;
+} } } }
+
+void fnSortCLC(HWND hwnd, struct ClcData *dat, int useInsertionSort)
+{
+ struct ClcContact *selcontact;
+ struct ClcGroup *group = &dat->list, *selgroup;
+ HANDLE hSelItem;
+
+ if ( dat->needsResort ) {
+ if (cli.pfnGetRowByIndex(dat, dat->selection, &selcontact, NULL) == -1)
+ hSelItem = NULL;
+ else
+ hSelItem = cli.pfnContactToHItem(selcontact);
+ group->scanIndex = 0;
+ SortGroup(dat, group, useInsertionSort);
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ group = group->parent;
+ if (group == NULL)
+ break;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ SortGroup(dat, group, useInsertionSort);
+ continue;
+ }
+ group->scanIndex++;
+ }
+ if (hSelItem)
+ if (cli.pfnFindItem(hwnd, dat, hSelItem, &selcontact, &selgroup, NULL))
+ dat->selection = cli.pfnGetRowsPriorTo(&dat->list, selgroup, List_IndexOf((SortedList*)&selgroup->cl,selcontact));
+
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ }
+ dat->needsResort = 0;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+}
+
+struct SavedContactState_t
+{
+ HANDLE hContact;
+ BYTE iExtraImage[MAXEXTRACOLUMNS];
+ int checked;
+};
+
+struct SavedGroupState_t
+{
+ int groupId, expanded;
+};
+
+struct SavedInfoState_t
+{
+ int parentId;
+ ClcContact contact;
+};
+
+void fnSaveStateAndRebuildList(HWND hwnd, struct ClcData *dat)
+{
+ NMCLISTCONTROL nm;
+ int i, j;
+ ClcGroup *group;
+ ClcContact *contact;
+
+ cli.pfnHideInfoTip(hwnd, dat);
+ KillTimer(hwnd, TIMERID_INFOTIP);
+ KillTimer(hwnd, TIMERID_RENAME);
+ cli.pfnEndRename(hwnd, dat, 1);
+
+ OBJLIST<SavedContactState_t> saveContact( 10, HandleKeySortT );
+ OBJLIST<SavedGroupState_t> saveGroup( 100, NumericKeySortT );
+ OBJLIST<SavedInfoState_t> saveInfo( 10, NumericKeySortT );
+
+ dat->needsResort = 1;
+ group = &dat->list;
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ group = group->parent;
+ if (group == NULL)
+ break;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+
+ SavedGroupState_t* p = new SavedGroupState_t;
+ p->groupId = group->groupId;
+ p->expanded = group->expanded;
+ saveGroup.insert( p );
+ continue;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_CONTACT) {
+ SavedContactState_t* p = new SavedContactState_t;
+ p->hContact = group->cl.items[group->scanIndex]->hContact;
+ CopyMemory( p->iExtraImage, group->cl.items[group->scanIndex]->iExtraImage,
+ sizeof(group->cl.items[group->scanIndex]->iExtraImage));
+ p->checked = group->cl.items[group->scanIndex]->flags & CONTACTF_CHECKED;
+ saveContact.insert( p );
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_INFO) {
+ SavedInfoState_t* p = new SavedInfoState_t;
+ p->parentId = (group->parent == NULL) ? -1 : group->groupId;
+ p->contact = *group->cl.items[group->scanIndex];
+ saveInfo.insert( p );
+ }
+ group->scanIndex++;
+ }
+
+ cli.pfnFreeGroup(&dat->list);
+ cli.pfnRebuildEntireList(hwnd, dat);
+
+ group = &dat->list;
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ group = group->parent;
+ if (group == NULL)
+ break;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+
+ SavedGroupState_t tmp, *p;
+ tmp.groupId = group->groupId;
+ if (( p = saveGroup.find( &tmp )) != NULL )
+ group->expanded = p->expanded;
+ continue;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_CONTACT) {
+ SavedContactState_t tmp, *p;
+ tmp.hContact = group->cl.items[group->scanIndex]->hContact;
+ if (( p = saveContact.find( &tmp )) != NULL ) {
+ CopyMemory(group->cl.items[group->scanIndex]->iExtraImage, p->iExtraImage,
+ SIZEOF(group->cl.items[group->scanIndex]->iExtraImage));
+ if (p->checked)
+ group->cl.items[group->scanIndex]->flags |= CONTACTF_CHECKED;
+ } }
+
+ group->scanIndex++;
+ }
+
+ for (i = 0; i < saveInfo.getCount(); i++) {
+ if (saveInfo[i].parentId == -1)
+ group = &dat->list;
+ else {
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) (saveInfo[i].parentId | HCONTACT_ISGROUP), &contact, NULL, NULL))
+ continue;
+ group = contact->group;
+ }
+ j = cli.pfnAddInfoItemToGroup(group, saveInfo[i].contact.flags, _T(""));
+ *group->cl.items[j] = saveInfo[i].contact;
+ }
+
+ cli.pfnRecalculateGroupCheckboxes(hwnd, dat);
+
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ nm.hdr.code = CLN_LISTREBUILT;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+}
diff --git a/src/modules/clist/clcmsgs.cpp b/src/modules/clist/clcmsgs.cpp
new file mode 100644
index 0000000000..a46777939d
--- /dev/null
+++ b/src/modules/clist/clcmsgs.cpp
@@ -0,0 +1,472 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+//processing of all the CLM_ messages incoming
+
+LRESULT fnProcessExternalMessages(HWND hwnd, struct ClcData *dat, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case CLM_ADDCONTACT:
+ cli.pfnAddContactToTree(hwnd, dat, (HANDLE) wParam, 1, 0);
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ cli.pfnSortCLC(hwnd, dat, 1);
+ break;
+
+ case CLM_ADDGROUP:
+ {
+ DWORD groupFlags;
+ TCHAR *szName = cli.pfnGetGroupName(wParam, &groupFlags);
+ if (szName == NULL)
+ break;
+ cli.pfnAddGroup(hwnd, dat, szName, groupFlags, wParam, 0);
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ break;
+ }
+
+ case CLM_ADDINFOITEMA:
+ case CLM_ADDINFOITEMW:
+ {
+ int i;
+ ClcContact *groupContact;
+ ClcGroup *group;
+ CLCINFOITEM *cii = (CLCINFOITEM *) lParam;
+ if (cii==NULL || cii->cbSize != sizeof(CLCINFOITEM))
+ return (LRESULT) (HANDLE) NULL;
+ if (cii->hParentGroup == NULL)
+ group = &dat->list;
+ else {
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) ((UINT_PTR) cii->hParentGroup | HCONTACT_ISGROUP), &groupContact, NULL, NULL))
+ return (LRESULT) (HANDLE) NULL;
+ group = groupContact->group;
+ }
+ #if defined( _UNICODE )
+ if ( msg == CLM_ADDINFOITEMA )
+ { WCHAR* wszText = mir_a2u(( char* )cii->pszText );
+ i = cli.pfnAddInfoItemToGroup(group, cii->flags, wszText);
+ mir_free( wszText );
+ }
+ else i = cli.pfnAddInfoItemToGroup(group, cii->flags, cii->pszText);
+ #else
+ i = cli.pfnAddInfoItemToGroup(group, cii->flags, cii->pszText);
+ #endif
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ return (LRESULT) group->cl.items[i]->hContact | HCONTACT_ISINFO;
+ }
+
+ case CLM_AUTOREBUILD:
+ KillTimer(hwnd,TIMERID_REBUILDAFTER);
+ cli.pfnSaveStateAndRebuildList(hwnd, dat);
+ break;
+
+ case CLM_DELETEITEM:
+ cli.pfnDeleteItemFromTree(hwnd, (HANDLE) wParam);
+ cli.pfnSortCLC(hwnd, dat, 1);
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ break;
+
+ case CLM_EDITLABEL:
+ SendMessage(hwnd, CLM_SELECTITEM, wParam, 0);
+ cli.pfnBeginRenameSelection(hwnd, dat);
+ break;
+
+ case CLM_ENDEDITLABELNOW:
+ cli.pfnEndRename(hwnd, dat, wParam);
+ break;
+
+ case CLM_ENSUREVISIBLE:
+ {
+ ClcContact *contact;
+ ClcGroup *group, *tgroup;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, &group, NULL))
+ break;
+ for (tgroup = group; tgroup; tgroup = tgroup->parent)
+ cli.pfnSetGroupExpand(hwnd, dat, tgroup, 1);
+ cli.pfnEnsureVisible(hwnd, dat, cli.pfnGetRowsPriorTo(&dat->list, group, List_IndexOf((SortedList*)&group->cl,contact)), 0);
+ break;
+ }
+
+ case CLM_EXPAND:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ break;
+ if (contact->type != CLCIT_GROUP)
+ break;
+ cli.pfnSetGroupExpand(hwnd, dat, contact->group, lParam);
+ break;
+ }
+
+ case CLM_FINDCONTACT:
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, NULL, NULL, NULL))
+ return (LRESULT) (HANDLE) NULL;
+ return wParam;
+
+ case CLM_FINDGROUP:
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) (wParam | HCONTACT_ISGROUP), NULL, NULL, NULL))
+ return (LRESULT) (HANDLE) NULL;
+ return wParam | HCONTACT_ISGROUP;
+
+ case CLM_GETBKCOLOR:
+ return dat->bkColour;
+
+ case CLM_GETCHECKMARK:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ return 0;
+ return (contact->flags & CONTACTF_CHECKED) != 0;
+ }
+
+ case CLM_GETCOUNT:
+ return cli.pfnGetGroupContentsCount(&dat->list, 0);
+
+ case CLM_GETEDITCONTROL:
+ return (LRESULT) dat->hwndRenameEdit;
+
+ case CLM_GETEXPAND:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ return CLE_INVALID;
+ if (contact->type != CLCIT_GROUP)
+ return CLE_INVALID;
+ return contact->group->expanded;
+ }
+
+ case CLM_GETEXTRACOLUMNS:
+ return dat->extraColumnsCount;
+
+ case CLM_GETEXTRAIMAGE:
+ {
+ ClcContact *contact;
+ if (LOWORD(lParam) >= dat->extraColumnsCount)
+ return 0xFF;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ return 0xFF;
+ return contact->iExtraImage[LOWORD(lParam)];
+ }
+
+ case CLM_GETEXTRAIMAGELIST:
+ return (LRESULT) dat->himlExtraColumns;
+
+ case CLM_GETFONT:
+ if (wParam < 0 || wParam > FONTID_MAX)
+ return 0;
+ return (LRESULT) dat->fontInfo[wParam].hFont;
+
+ case CLM_GETHIDEOFFLINEROOT:
+ return DBGetContactSettingByte(NULL, "CLC", "HideOfflineRoot", 0);
+
+ case CLM_GETINDENT:
+ return dat->groupIndent;
+
+ case CLM_GETISEARCHSTRING:
+ lstrcpy(( TCHAR* ) lParam, dat->szQuickSearch);
+ return lstrlen(dat->szQuickSearch);
+
+ case CLM_GETITEMTEXT:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ return 0;
+ lstrcpy(( TCHAR* ) lParam, contact->szText);
+ return lstrlen(contact->szText);
+ }
+
+ case CLM_GETITEMTYPE:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ return CLCIT_INVALID;
+ return contact->type;
+ }
+
+ case CLM_GETLEFTMARGIN:
+ return dat->leftMargin;
+
+ case CLM_GETNEXTITEM:
+ {
+ if (wParam == CLGN_ROOT) {
+ if (dat->list.cl.count)
+ return (LRESULT) cli.pfnContactToHItem(dat->list.cl.items[0]);
+ return NULL;
+ }
+
+ ClcContact *contact;
+ ClcGroup *group;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) lParam, &contact, &group, NULL))
+ return NULL;
+
+ int i = List_IndexOf((SortedList*)&group->cl,contact);
+ switch (wParam) {
+ case CLGN_CHILD:
+ if (contact->type != CLCIT_GROUP)
+ return (LRESULT) (HANDLE) NULL;
+ group = contact->group;
+ if (group->cl.count == 0)
+ return NULL;
+ return (LRESULT) cli.pfnContactToHItem(group->cl.items[0]);
+
+ case CLGN_PARENT:
+ return group->groupId | HCONTACT_ISGROUP;
+
+ case CLGN_NEXT:
+ do {
+ if (++i >= group->cl.count)
+ return NULL;
+ }
+ while (group->cl.items[i]->type == CLCIT_DIVIDER);
+ return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]);
+
+ case CLGN_PREVIOUS:
+ do {
+ if (--i < 0)
+ return NULL;
+ }
+ while (group->cl.items[i]->type == CLCIT_DIVIDER);
+ return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]);
+
+ case CLGN_NEXTCONTACT:
+ for (i++; i < group->cl.count; i++)
+ if (group->cl.items[i]->type == CLCIT_CONTACT)
+ break;
+ if (i >= group->cl.count)
+ return NULL;
+ return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]);
+
+ case CLGN_PREVIOUSCONTACT:
+ if (i >= group->cl.count)
+ return NULL;
+ for (i--; i >= 0; i--)
+ if (group->cl.items[i]->type == CLCIT_CONTACT)
+ break;
+ if (i < 0)
+ return NULL;
+ return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]);
+
+ case CLGN_NEXTGROUP:
+ for (i++; i < group->cl.count; i++)
+ if (group->cl.items[i]->type == CLCIT_GROUP)
+ break;
+ if (i >= group->cl.count)
+ return NULL;
+ return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]);
+
+ case CLGN_PREVIOUSGROUP:
+ if (i >= group->cl.count)
+ return NULL;
+ for (i--; i >= 0; i--)
+ if (group->cl.items[i]->type == CLCIT_GROUP)
+ break;
+ if (i < 0)
+ return NULL;
+ return (LRESULT) cli.pfnContactToHItem(group->cl.items[i]);
+ }
+ return NULL;
+ }
+
+ case CLM_GETSCROLLTIME:
+ return dat->scrollTime;
+
+ case CLM_GETSELECTION:
+ {
+ ClcContact *contact;
+ if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1)
+ return NULL;
+ return (LRESULT) cli.pfnContactToHItem(contact);
+ }
+
+ case CLM_GETTEXTCOLOR:
+ if (wParam < 0 || wParam > FONTID_MAX)
+ return 0;
+ return (LRESULT) dat->fontInfo[wParam].colour;
+
+ case CLM_HITTEST:
+ {
+ ClcContact *contact;
+ DWORD hitFlags;
+ int hit;
+ hit = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), &contact, NULL, &hitFlags);
+ if (wParam)
+ *(PDWORD) wParam = hitFlags;
+ if (hit == -1)
+ return NULL;
+ return (LRESULT) cli.pfnContactToHItem(contact);
+ }
+
+ case CLM_SELECTITEM:
+ {
+ ClcContact *contact;
+ ClcGroup *group, *tgroup;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, &group, NULL))
+ break;
+ for (tgroup = group; tgroup; tgroup = tgroup->parent)
+ cli.pfnSetGroupExpand(hwnd, dat, tgroup, 1);
+ dat->selection = cli.pfnGetRowsPriorTo(&dat->list, group, List_IndexOf((SortedList*)&group->cl,contact));
+ cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0);
+ break;
+ }
+
+ case CLM_SETBKBITMAP:
+ if (!dat->bkChanged && dat->hBmpBackground) {
+ DeleteObject(dat->hBmpBackground);
+ dat->hBmpBackground = NULL;
+ }
+ dat->hBmpBackground = (HBITMAP) lParam;
+ dat->backgroundBmpUse = wParam;
+ dat->bkChanged = 1;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETBKCOLOR:
+ if (!dat->bkChanged && dat->hBmpBackground) {
+ DeleteObject(dat->hBmpBackground);
+ dat->hBmpBackground = NULL;
+ }
+ dat->bkColour = wParam;
+ dat->bkChanged = 1;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETCHECKMARK:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ return 0;
+ if (lParam)
+ contact->flags |= CONTACTF_CHECKED;
+ else
+ contact->flags &= ~CONTACTF_CHECKED;
+ cli.pfnRecalculateGroupCheckboxes(hwnd, dat);
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+ }
+
+ case CLM_SETEXTRACOLUMNS:
+ if (wParam > MAXEXTRACOLUMNS)
+ return 0;
+ dat->extraColumnsCount = wParam;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETEXTRAIMAGE:
+ {
+ ClcContact *contact;
+ if (LOWORD(lParam) >= dat->extraColumnsCount)
+ return 0;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ return 0;
+ contact->iExtraImage[LOWORD(lParam)] = (BYTE) HIWORD(lParam);
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+ }
+
+ case CLM_SETEXTRAIMAGELIST:
+ dat->himlExtraColumns = (HIMAGELIST) lParam;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETFONT:
+ if (HIWORD(lParam) < 0 || HIWORD(lParam) > FONTID_MAX)
+ return 0;
+ dat->fontInfo[HIWORD(lParam)].hFont = (HFONT) wParam;
+ dat->fontInfo[HIWORD(lParam)].changed = 1;
+ {
+ SIZE fontSize;
+ HDC hdc = GetDC(hwnd);
+ SelectObject(hdc, (HFONT) wParam);
+ GetTextExtentPoint32A(hdc, "x", 1, &fontSize);
+ dat->fontInfo[HIWORD(lParam)].fontHeight = fontSize.cy;
+ if (dat->rowHeight < fontSize.cy + 2)
+ dat->rowHeight = fontSize.cy + 2;
+ ReleaseDC(hwnd, hdc);
+ }
+ if (LOWORD(lParam))
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETGREYOUTFLAGS:
+ dat->greyoutFlags = wParam;
+ break;
+
+ case CLM_SETHIDEEMPTYGROUPS:
+ if (wParam)
+ SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | CLS_HIDEEMPTYGROUPS);
+ else
+ SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~CLS_HIDEEMPTYGROUPS);
+ SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0);
+ break;
+
+ case CLM_SETHIDEOFFLINEROOT:
+ DBWriteContactSettingByte(NULL, "CLC", "HideOfflineRoot", (BYTE) wParam);
+ SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0);
+ break;
+
+ case CLM_SETINDENT:
+ dat->groupIndent = wParam;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETITEMTEXT:
+ {
+ ClcContact *contact;
+ if (!cli.pfnFindItem(hwnd, dat, (HANDLE) wParam, &contact, NULL, NULL))
+ break;
+ lstrcpyn(contact->szText, ( TCHAR* )lParam, SIZEOF( contact->szText ));
+ cli.pfnSortCLC(hwnd, dat, 1);
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+ }
+
+ case CLM_SETLEFTMARGIN:
+ dat->leftMargin = wParam;
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ break;
+
+ case CLM_SETOFFLINEMODES:
+ dat->offlineModes = wParam;
+ SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0);
+ break;
+
+ case CLM_SETSCROLLTIME:
+ dat->scrollTime = wParam;
+ break;
+
+ case CLM_SETTEXTCOLOR:
+ if (wParam < 0 || wParam > FONTID_MAX)
+ break;
+ dat->fontInfo[wParam].colour = lParam;
+ break;
+
+ case CLM_SETUSEGROUPS:
+ if (wParam)
+ SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | CLS_USEGROUPS);
+ else
+ SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) & ~CLS_USEGROUPS);
+ SendMessage(hwnd, CLM_AUTOREBUILD, 0, 0);
+ break;
+ }
+ return 0;
+}
diff --git a/src/modules/clist/clcutils.cpp b/src/modules/clist/clcutils.cpp
new file mode 100644
index 0000000000..3205066e2f
--- /dev/null
+++ b/src/modules/clist/clcutils.cpp
@@ -0,0 +1,879 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+//loads of stuff that didn't really fit anywhere else
+
+extern HANDLE hHideInfoTipEvent;
+
+char* fnGetGroupCountsText(struct ClcData *dat, struct ClcContact *contact)
+{
+ static char szName[32];
+ int onlineCount, totalCount;
+ struct ClcGroup *group, *topgroup;
+
+ if (contact->type != CLCIT_GROUP || !(dat->exStyle & CLS_EX_SHOWGROUPCOUNTS))
+ return "";
+
+ group = topgroup = contact->group;
+ onlineCount = 0;
+ totalCount = group->totalMembers;
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ if (group == topgroup)
+ break;
+ group = group->parent;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ totalCount += group->totalMembers;
+ continue;
+ }
+ else if (group->cl.items[group->scanIndex]->type == CLCIT_CONTACT)
+ if (group->cl.items[group->scanIndex]->flags & CONTACTF_ONLINE)
+ onlineCount++;
+ group->scanIndex++;
+ }
+ if (onlineCount == 0 && dat->exStyle & CLS_EX_HIDECOUNTSWHENEMPTY)
+ return "";
+ mir_snprintf(szName, SIZEOF(szName), "(%u/%u)", onlineCount, totalCount);
+ return szName;
+}
+
+int fnHitTest(HWND hwnd, struct ClcData *dat, int testx, int testy, struct ClcContact **contact, struct ClcGroup **group, DWORD * flags)
+{
+ ClcContact *hitcontact = NULL;
+ ClcGroup *hitgroup = NULL;
+ int hit, indent, width, i;
+ int checkboxWidth;
+ SIZE textSize;
+ HDC hdc;
+ RECT clRect;
+ HFONT hFont;
+ DWORD style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ POINT pt;
+
+ if ( flags )
+ *flags = 0;
+
+ pt.x = testx;
+ pt.y = testy;
+ ClientToScreen(hwnd, &pt);
+
+ HWND hwndParent = hwnd, hwndTemp;
+ do
+ {
+ hwndTemp = hwndParent;
+ hwndParent = (HWND)GetWindowLongPtr(hwndTemp, GWLP_HWNDPARENT);
+
+ POINT pt1 = pt;
+ ScreenToClient(hwndParent, &pt1);
+ HWND h = ChildWindowFromPointEx(hwndParent ? hwndParent : GetDesktopWindow(),
+ pt1, CWP_SKIPINVISIBLE | CWP_SKIPTRANSPARENT);
+ if (h != hwndTemp)
+ {
+ if (!hwndParent || !(GetWindowLong(hwndTemp, GWL_STYLE) & BS_GROUPBOX))
+ return -1;
+ }
+ }
+ while (hwndParent);
+
+ GetClientRect(hwnd, &clRect);
+ if ( testx < 0 || testy < 0 || testy >= clRect.bottom || testx >= clRect.right ) {
+ if ( flags ) {
+ if (testx < 0)
+ *flags |= CLCHT_TOLEFT;
+ else if (testx >= clRect.right)
+ *flags |= CLCHT_TORIGHT;
+ if (testy < 0)
+ *flags |= CLCHT_ABOVE;
+ else if (testy >= clRect.bottom)
+ *flags |= CLCHT_BELOW;
+ }
+ return -1;
+ }
+ if (testx < dat->leftMargin) {
+ if (flags)
+ *flags |= CLCHT_INLEFTMARGIN | CLCHT_NOWHERE;
+ return -1;
+ }
+ hit = cli.pfnRowHitTest(dat, dat->yScroll + testy);
+ if ( hit != -1 )
+ hit = cli.pfnGetRowByIndex(dat, hit, &hitcontact, &hitgroup);
+ if (hit == -1) {
+ if (flags)
+ *flags |= CLCHT_NOWHERE | CLCHT_BELOWITEMS;
+ return -1;
+ }
+ if (contact)
+ *contact = hitcontact;
+ if (group)
+ *group = hitgroup;
+ for (indent = 0; hitgroup->parent; indent++, hitgroup = hitgroup->parent);
+ if (testx < dat->leftMargin + indent * dat->groupIndent) {
+ if (flags)
+ *flags |= CLCHT_ONITEMINDENT;
+ return hit;
+ }
+ checkboxWidth = 0;
+ if (style & CLS_CHECKBOXES && hitcontact->type == CLCIT_CONTACT)
+ checkboxWidth = dat->checkboxSize + 2;
+ if (style & CLS_GROUPCHECKBOXES && hitcontact->type == CLCIT_GROUP)
+ checkboxWidth = dat->checkboxSize + 2;
+ if (hitcontact->type == CLCIT_INFO && hitcontact->flags & CLCIIF_CHECKBOX)
+ checkboxWidth = dat->checkboxSize + 2;
+ if (testx < dat->leftMargin + indent * dat->groupIndent + checkboxWidth) {
+ if (flags)
+ *flags |= CLCHT_ONITEMCHECK;
+ return hit;
+ }
+ if (testx < dat->leftMargin + indent * dat->groupIndent + checkboxWidth + dat->iconXSpace) {
+ if (flags)
+ *flags |= CLCHT_ONITEMICON;
+ return hit;
+ }
+
+ for (i = 0; i < dat->extraColumnsCount; i++) {
+ if (hitcontact->iExtraImage[i] == 0xFF)
+ continue;
+ if (testx >= clRect.right - dat->extraColumnSpacing * (dat->extraColumnsCount - i) &&
+ testx < clRect.right - dat->extraColumnSpacing * (dat->extraColumnsCount - i) + g_IconWidth ) {
+ if (flags)
+ *flags |= CLCHT_ONITEMEXTRA | (i << 24);
+ return hit;
+ }
+ }
+ hdc = GetDC(hwnd);
+ if (hitcontact->type == CLCIT_GROUP)
+ hFont = ( HFONT )SelectObject(hdc, dat->fontInfo[FONTID_GROUPS].hFont);
+ else
+ hFont = ( HFONT )SelectObject(hdc, dat->fontInfo[FONTID_CONTACTS].hFont);
+ GetTextExtentPoint32(hdc, hitcontact->szText, lstrlen(hitcontact->szText), &textSize);
+ width = textSize.cx;
+ if (hitcontact->type == CLCIT_GROUP) {
+ char *szCounts;
+ szCounts = cli.pfnGetGroupCountsText(dat, hitcontact);
+ if (szCounts[0]) {
+ GetTextExtentPoint32A(hdc, " ", 1, &textSize);
+ width += textSize.cx;
+ SelectObject(hdc, dat->fontInfo[FONTID_GROUPCOUNTS].hFont);
+ GetTextExtentPoint32A(hdc, szCounts, lstrlenA(szCounts), &textSize);
+ width += textSize.cx;
+ }
+ }
+ SelectObject(hdc, hFont);
+ ReleaseDC(hwnd, hdc);
+ if (testx < dat->leftMargin + indent * dat->groupIndent + checkboxWidth + dat->iconXSpace + width + 4) {
+ if (flags)
+ *flags |= CLCHT_ONITEMLABEL;
+ return hit;
+ }
+ if (flags)
+ *flags |= CLCHT_NOWHERE;
+ return -1;
+}
+
+void fnScrollTo(HWND hwnd, struct ClcData *dat, int desty, int noSmooth)
+{
+ DWORD startTick, nowTick;
+ int oldy = dat->yScroll;
+ RECT clRect, rcInvalidate;
+ int maxy, previousy;
+
+ if (dat->iHotTrack != -1 && dat->yScroll != desty) {
+ cli.pfnInvalidateItem(hwnd, dat, dat->iHotTrack);
+ dat->iHotTrack = -1;
+ ReleaseCapture();
+ }
+ GetClientRect(hwnd, &clRect);
+ rcInvalidate = clRect;
+ maxy = cli.pfnGetRowTotalHeight(dat) - clRect.bottom;
+ if (desty > maxy)
+ desty = maxy;
+ if (desty < 0)
+ desty = 0;
+ if (abs(desty - dat->yScroll) < 4)
+ noSmooth = 1;
+ if (!noSmooth && dat->exStyle & CLS_EX_NOSMOOTHSCROLLING)
+ noSmooth = 1;
+ previousy = dat->yScroll;
+ if (!noSmooth) {
+ startTick = GetTickCount();
+ for (;;) {
+ nowTick = GetTickCount();
+ if (nowTick >= startTick + dat->scrollTime)
+ break;
+ dat->yScroll = oldy + (desty - oldy) * (int) (nowTick - startTick) / dat->scrollTime;
+ if (dat->backgroundBmpUse & CLBF_SCROLL || dat->hBmpBackground == NULL)
+ ScrollWindowEx(hwnd, 0, previousy - dat->yScroll, NULL, NULL, NULL, NULL, SW_INVALIDATE);
+ else
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ previousy = dat->yScroll;
+ SetScrollPos(hwnd, SB_VERT, dat->yScroll, TRUE);
+ UpdateWindow(hwnd);
+ }
+ }
+ dat->yScroll = desty;
+ if (dat->backgroundBmpUse & CLBF_SCROLL || dat->hBmpBackground == NULL)
+ ScrollWindowEx(hwnd, 0, previousy - dat->yScroll, NULL, NULL, NULL, NULL, SW_INVALIDATE);
+ else
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ SetScrollPos(hwnd, SB_VERT, dat->yScroll, TRUE);
+}
+
+void fnEnsureVisible(HWND hwnd, struct ClcData *dat, int iItem, int partialOk)
+{
+ int itemy = cli.pfnGetRowTopY(dat, iItem), itemh = cli.pfnGetRowHeight(dat, iItem), newY = 0;
+ int moved = 0;
+ RECT clRect;
+
+ GetClientRect(hwnd, &clRect);
+ if (partialOk) {
+ if (itemy + itemh - 1 < dat->yScroll) {
+ newY = itemy;
+ moved = 1;
+ }
+ else if (itemy >= dat->yScroll + clRect.bottom) {
+ newY = itemy - clRect.bottom + itemh;
+ moved = 1;
+ }
+ }
+ else {
+ if (itemy < dat->yScroll) {
+ newY = itemy;
+ moved = 1;
+ }
+ else if (itemy >= dat->yScroll + clRect.bottom - itemh) {
+ newY = itemy - clRect.bottom + itemh;
+ moved = 1;
+ }
+ }
+ if (moved)
+ cli.pfnScrollTo(hwnd, dat, newY, 0);
+}
+
+void fnRecalcScrollBar(HWND hwnd, struct ClcData *dat)
+{
+ SCROLLINFO si = { 0 };
+ RECT clRect;
+ NMCLISTCONTROL nm;
+
+ GetClientRect(hwnd, &clRect);
+ si.cbSize = sizeof(si);
+ si.fMask = SIF_ALL;
+ si.nMin = 0;
+ si.nMax = cli.pfnGetRowTotalHeight(dat)-1;
+ si.nPage = clRect.bottom;
+ si.nPos = dat->yScroll;
+
+ if (GetWindowLongPtr(hwnd, GWL_STYLE) & CLS_CONTACTLIST) {
+ if (dat->noVScrollbar == 0)
+ SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
+ }
+ else SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
+
+ cli.pfnScrollTo(hwnd, dat, dat->yScroll, 1);
+ nm.hdr.code = CLN_LISTSIZECHANGE;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.pt.y = si.nMax;
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+}
+
+void fnSetGroupExpand(HWND hwnd, struct ClcData *dat, struct ClcGroup *group, int newState)
+{
+ int contentCount;
+ int groupy;
+ int newY, posY;
+ RECT clRect;
+ NMCLISTCONTROL nm;
+
+ if (newState == -1)
+ group->expanded ^= 1;
+ else {
+ if (group->expanded == (newState != 0))
+ return;
+ group->expanded = newState != 0;
+ }
+ cli.pfnInvalidateRect(hwnd, NULL, FALSE);
+ contentCount = cli.pfnGetGroupContentsCount(group, 1);
+ groupy = cli.pfnGetRowsPriorTo(&dat->list, group, -1);
+ if (dat->selection > groupy && dat->selection < groupy + contentCount)
+ dat->selection = groupy;
+ GetClientRect(hwnd, &clRect);
+ newY = dat->yScroll;
+ posY = cli.pfnGetRowBottomY(dat, groupy + contentCount);
+ if (posY >= newY + clRect.bottom)
+ newY = posY - clRect.bottom;
+ posY = cli.pfnGetRowTopY(dat, groupy);
+ if (newY > posY)
+ newY = posY;
+ cli.pfnRecalcScrollBar(hwnd, dat);
+ if (group->expanded)
+ cli.pfnScrollTo(hwnd, dat, newY, 0);
+ nm.hdr.code = CLN_EXPANDED;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.hItem = (HANDLE) group->groupId;
+ nm.action = group->expanded;
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+}
+
+void fnDoSelectionDefaultAction(HWND hwnd, struct ClcData *dat)
+{
+ struct ClcContact *contact;
+
+ if (dat->selection == -1)
+ return;
+ dat->szQuickSearch[0] = 0;
+ if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1)
+ return;
+ if (contact->type == CLCIT_GROUP)
+ cli.pfnSetGroupExpand(hwnd, dat, contact->group, -1);
+ if (contact->type == CLCIT_CONTACT)
+ CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM) contact->hContact, 0);
+}
+
+int fnFindRowByText(HWND hwnd, struct ClcData *dat, const TCHAR *text, int prefixOk)
+{
+ struct ClcGroup *group = &dat->list;
+ int testlen = lstrlen(text);
+
+ group->scanIndex = 0;
+ for (;;) {
+ if (group->scanIndex == group->cl.count) {
+ group = group->parent;
+ if (group == NULL)
+ break;
+ group->scanIndex++;
+ continue;
+ }
+ if (group->cl.items[group->scanIndex]->type != CLCIT_DIVIDER) {
+ if ((prefixOk && !_tcsnicmp(text, group->cl.items[group->scanIndex]->szText, testlen)) ||
+ (!prefixOk && !lstrcmpi(text, group->cl.items[group->scanIndex]->szText))) {
+ struct ClcGroup *contactGroup = group;
+ int contactScanIndex = group->scanIndex;
+ for (; group; group = group->parent)
+ cli.pfnSetGroupExpand(hwnd, dat, group, 1);
+ return cli.pfnGetRowsPriorTo(&dat->list, contactGroup, contactScanIndex);
+ }
+ if (group->cl.items[group->scanIndex]->type == CLCIT_GROUP) {
+ if (!(dat->exStyle & CLS_EX_QUICKSEARCHVISONLY) || group->cl.items[group->scanIndex]->group->expanded) {
+ group = group->cl.items[group->scanIndex]->group;
+ group->scanIndex = 0;
+ continue;
+ }
+ }
+ }
+ group->scanIndex++;
+ }
+ return -1;
+}
+
+void fnEndRename(HWND, struct ClcData *dat, int save)
+{
+ HWND hwndEdit = dat->hwndRenameEdit;
+ if (hwndEdit == NULL)
+ return;
+
+ dat->hwndRenameEdit = NULL;
+ if (save) {
+ TCHAR text[120]; text[0] = 0;
+ GetWindowText(hwndEdit, text, SIZEOF(text));
+
+ ClcContact *contact;
+ if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) != -1) {
+ if (lstrcmp(contact->szText, text) && !_tcsstr(text, _T("\\"))) {
+ if (contact->type == CLCIT_GROUP) {
+ if (contact->group->parent && contact->group->parent->parent) {
+ TCHAR szFullName[256];
+ mir_sntprintf(szFullName, SIZEOF(szFullName), _T("%s\\%s"),
+ cli.pfnGetGroupName(contact->group->parent->groupId, NULL), text);
+ cli.pfnRenameGroup(contact->groupId, szFullName);
+ }
+ else
+ cli.pfnRenameGroup(contact->groupId, text);
+ }
+ else if (contact->type == CLCIT_CONTACT) {
+ cli.pfnInvalidateDisplayNameCacheEntry(contact->hContact);
+ TCHAR* otherName = cli.pfnGetContactDisplayName(contact->hContact, GCDNF_NOMYHANDLE);
+ if (!text[0] || !lstrcmp(otherName, text))
+ DBDeleteContactSetting(contact->hContact, "CList", "MyHandle");
+ else
+ DBWriteContactSettingTString(contact->hContact, "CList", "MyHandle", text);
+ mir_free(otherName);
+ }
+ }
+ }
+ }
+ DestroyWindow(hwndEdit);
+}
+
+void fnDeleteFromContactList(HWND hwnd, struct ClcData *dat)
+{
+ struct ClcContact *contact;
+ if (dat->selection == -1)
+ return;
+ dat->szQuickSearch[0] = 0;
+ if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1)
+ return;
+ switch (contact->type) {
+ case CLCIT_GROUP:
+ CallService(MS_CLIST_GROUPDELETE, (WPARAM)contact->groupId, 0);
+ break;
+ case CLCIT_CONTACT:
+ CallService("CList/DeleteContactCommand", (WPARAM)contact->hContact, (LPARAM)hwnd );
+ break;
+} }
+
+static WNDPROC OldRenameEditWndProc;
+static LRESULT CALLBACK RenameEditSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_KEYDOWN:
+ switch (wParam) {
+ case VK_RETURN:
+ cli.pfnEndRename(GetParent(hwnd), (struct ClcData *) GetWindowLongPtr(GetParent(hwnd), 0), 1);
+ return 0;
+ case VK_ESCAPE:
+ cli.pfnEndRename(GetParent(hwnd), (struct ClcData *) GetWindowLongPtr(GetParent(hwnd), 0), 0);
+ return 0;
+ }
+ break;
+ case WM_GETDLGCODE:
+ if (lParam) {
+ MSG *msg = (MSG *) lParam;
+ if (msg->message == WM_KEYDOWN && msg->wParam == VK_TAB)
+ return 0;
+ if (msg->message == WM_CHAR && msg->wParam == '\t')
+ return 0;
+ }
+ return DLGC_WANTMESSAGE;
+ case WM_KILLFOCUS:
+ cli.pfnEndRename(GetParent(hwnd), (struct ClcData *) GetWindowLongPtr(GetParent(hwnd), 0), 1);
+ return 0;
+ }
+ return CallWindowProc(OldRenameEditWndProc, hwnd, msg, wParam, lParam);
+}
+
+void fnBeginRenameSelection(HWND hwnd, struct ClcData *dat)
+{
+ struct ClcContact *contact;
+ struct ClcGroup *group;
+ RECT clRect;
+ POINT pt;
+ int h;
+
+ KillTimer(hwnd, TIMERID_RENAME);
+ ReleaseCapture();
+ dat->iHotTrack = -1;
+ dat->selection = cli.pfnGetRowByIndex(dat, dat->selection, &contact, &group);
+ if (dat->selection == -1)
+ return;
+ if (contact->type != CLCIT_CONTACT && contact->type != CLCIT_GROUP)
+ return;
+ GetClientRect(hwnd, &clRect);
+ cli.pfnCalcEipPosition( dat, contact, group, &pt );
+ h = cli.pfnGetRowHeight(dat, dat->selection);
+ dat->hwndRenameEdit = CreateWindow( _T("EDIT"), contact->szText, WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, pt.x, pt.y, clRect.right - pt.x, h, hwnd, NULL, cli.hInst, NULL);
+ OldRenameEditWndProc = (WNDPROC) SetWindowLongPtr(dat->hwndRenameEdit, GWLP_WNDPROC, (LONG_PTR) RenameEditSubclassProc);
+ SendMessage(dat->hwndRenameEdit, WM_SETFONT, (WPARAM) (contact->type == CLCIT_GROUP ? dat->fontInfo[FONTID_GROUPS].hFont : dat->fontInfo[FONTID_CONTACTS].hFont), 0);
+ SendMessage(dat->hwndRenameEdit, EM_SETMARGINS, EC_LEFTMARGIN | EC_RIGHTMARGIN | EC_USEFONTINFO, 0);
+ SendMessage(dat->hwndRenameEdit, EM_SETSEL, 0, (LPARAM) (-1));
+ ShowWindow(dat->hwndRenameEdit, SW_SHOW);
+ SetFocus(dat->hwndRenameEdit);
+}
+
+void fnCalcEipPosition( struct ClcData *dat, struct ClcContact *, struct ClcGroup *group, POINT *result)
+{
+ int indent;
+ for (indent = 0; group->parent; indent++, group = group->parent);
+ result->x = indent * dat->groupIndent + dat->iconXSpace - 2;
+ result->y = cli.pfnGetRowTopY(dat, dat->selection) - dat->yScroll;
+}
+
+int fnGetDropTargetInformation(HWND hwnd, struct ClcData *dat, POINT pt)
+{
+ RECT clRect;
+ int hit;
+ struct ClcContact *contact, *movecontact;
+ struct ClcGroup *group, *movegroup;
+ DWORD hitFlags;
+
+ GetClientRect(hwnd, &clRect);
+ dat->selection = dat->iDragItem;
+ dat->iInsertionMark = -1;
+ if (!PtInRect(&clRect, pt))
+ return DROPTARGET_OUTSIDE;
+
+ hit = cli.pfnHitTest(hwnd, dat, pt.x, pt.y, &contact, &group, &hitFlags);
+ cli.pfnGetRowByIndex(dat, dat->iDragItem, &movecontact, &movegroup);
+ if (hit == dat->iDragItem)
+ return DROPTARGET_ONSELF;
+ if (hit == -1 || hitFlags & CLCHT_ONITEMEXTRA)
+ return DROPTARGET_ONNOTHING;
+
+ if (movecontact->type == CLCIT_GROUP) {
+ struct ClcContact *bottomcontact = NULL, *topcontact = NULL;
+ struct ClcGroup *topgroup = NULL;
+ int topItem = -1, bottomItem = -1;
+ int ok = 0;
+ if (pt.y + dat->yScroll < cli.pfnGetRowTopY(dat, hit) + dat->insertionMarkHitHeight) {
+ //could be insertion mark (above)
+ topItem = hit - 1;
+ bottomItem = hit;
+ bottomcontact = contact;
+ topItem = cli.pfnGetRowByIndex(dat, topItem, &topcontact, &topgroup);
+ ok = 1;
+ }
+ if (pt.y + dat->yScroll >= cli.pfnGetRowBottomY(dat, hit+1) - dat->insertionMarkHitHeight) {
+ //could be insertion mark (below)
+ topItem = hit;
+ bottomItem = hit + 1;
+ topcontact = contact;
+ topgroup = group;
+ bottomItem = cli.pfnGetRowByIndex(dat, bottomItem, &bottomcontact, NULL);
+ ok = 1;
+ }
+ if (ok) {
+ ok = 0;
+ if (bottomItem == -1 || bottomcontact->type != CLCIT_GROUP) { //need to special-case moving to end
+ if (topItem != dat->iDragItem) {
+ for (; topgroup; topgroup = topgroup->parent) {
+ if (topgroup == movecontact->group)
+ break;
+ if (topgroup == movecontact->group->parent) {
+ ok = 1;
+ break;
+ }
+ }
+ if (ok)
+ bottomItem = topItem + 1;
+ }
+ }
+ else if (bottomItem != dat->iDragItem && bottomcontact->type == CLCIT_GROUP && bottomcontact->group->parent == movecontact->group->parent) {
+ if (bottomcontact != movecontact + 1)
+ ok = 1;
+ }
+ if (ok) {
+ dat->iInsertionMark = bottomItem;
+ dat->selection = -1;
+ return DROPTARGET_INSERTION;
+ }
+ }
+ }
+ if (contact->type == CLCIT_GROUP) {
+ if (dat->iInsertionMark == -1) {
+ if (movecontact->type == CLCIT_GROUP) { //check not moving onto its own subgroup
+ for (; group; group = group->parent)
+ if (group == movecontact->group)
+ return DROPTARGET_ONSELF;
+ }
+ dat->selection = hit;
+ return DROPTARGET_ONGROUP;
+ }
+ }
+ return DROPTARGET_ONCONTACT;
+}
+
+int fnClcStatusToPf2(int status)
+{
+ switch(status) {
+ case ID_STATUS_ONLINE: return PF2_ONLINE;
+ case ID_STATUS_AWAY: return PF2_SHORTAWAY;
+ case ID_STATUS_DND: return PF2_HEAVYDND;
+ case ID_STATUS_NA: return PF2_LONGAWAY;
+ case ID_STATUS_OCCUPIED: return PF2_LIGHTDND;
+ case ID_STATUS_FREECHAT: return PF2_FREECHAT;
+ case ID_STATUS_INVISIBLE: return PF2_INVISIBLE;
+ case ID_STATUS_ONTHEPHONE: return PF2_ONTHEPHONE;
+ case ID_STATUS_OUTTOLUNCH: return PF2_OUTTOLUNCH;
+ case ID_STATUS_OFFLINE: return MODEF_OFFLINE;
+ }
+ return 0;
+}
+
+int fnIsHiddenMode(struct ClcData *dat, int status)
+{
+ return dat->offlineModes & cli.pfnClcStatusToPf2(status);
+}
+
+void fnHideInfoTip(HWND, struct ClcData *dat)
+{
+ CLCINFOTIP it = { 0 };
+
+ if (dat->hInfoTipItem == NULL)
+ return;
+ it.isGroup = IsHContactGroup(dat->hInfoTipItem);
+ it.hItem = (HANDLE) ((UINT_PTR) dat->hInfoTipItem & ~HCONTACT_ISGROUP);
+ it.cbSize = sizeof(it);
+ dat->hInfoTipItem = NULL;
+ NotifyEventHooks(hHideInfoTipEvent, 0, (LPARAM) & it);
+}
+
+void fnNotifyNewContact(HWND hwnd, HANDLE hContact)
+{
+ NMCLISTCONTROL nm;
+ nm.hdr.code = CLN_NEWCONTACT;
+ nm.hdr.hwndFrom = hwnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hwnd);
+ nm.flags = 0;
+ nm.hItem = hContact;
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & nm);
+}
+
+DWORD fnGetDefaultExStyle(void)
+{
+ BOOL param;
+ DWORD ret = CLCDEFAULT_EXSTYLE;
+ if (SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &param, FALSE) && !param)
+ ret |= CLS_EX_NOSMOOTHSCROLLING;
+ if (SystemParametersInfo(SPI_GETHOTTRACKING, 0, &param, FALSE) && !param)
+ ret &= ~CLS_EX_TRACKSELECT;
+ return ret;
+}
+
+#define DBFONTF_BOLD 1
+#define DBFONTF_ITALIC 2
+#define DBFONTF_UNDERLINE 4
+
+void fnGetDefaultFontSetting(int i, LOGFONT* lf, COLORREF* colour)
+{
+ SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), lf, FALSE);
+ *colour = GetSysColor(COLOR_WINDOWTEXT);
+ lf->lfHeight = 8;
+ switch (i) {
+ case FONTID_GROUPS:
+ lf->lfWeight = FW_BOLD;
+ break;
+ case FONTID_GROUPCOUNTS:
+ *colour = GetSysColor(COLOR_3DSHADOW);
+ break;
+ case FONTID_OFFINVIS:
+ case FONTID_INVIS:
+ lf->lfItalic = !lf->lfItalic;
+ break;
+ case FONTID_DIVIDERS:
+ break;
+ case FONTID_NOTONLIST:
+ *colour = GetSysColor(COLOR_3DSHADOW);
+ break;
+} }
+
+void fnGetFontSetting(int i, LOGFONT* lf, COLORREF* colour)
+{
+ DBVARIANT dbv;
+ char idstr[20];
+ BYTE style;
+
+ cli.pfnGetDefaultFontSetting(i, lf, colour);
+ mir_snprintf(idstr, SIZEOF(idstr), "Font%dName", i);
+ if ( !DBGetContactSettingTString(NULL, "CLC", idstr, &dbv )) {
+ lstrcpy(lf->lfFaceName, dbv.ptszVal);
+ mir_free(dbv.pszVal);
+ }
+ mir_snprintf(idstr, SIZEOF(idstr), "Font%dCol", i);
+ *colour = DBGetContactSettingDword(NULL, "CLC", idstr, *colour);
+ mir_snprintf(idstr, SIZEOF(idstr), "Font%dSize", i);
+ lf->lfHeight = (char) DBGetContactSettingByte(NULL, "CLC", idstr, lf->lfHeight);
+ mir_snprintf(idstr, SIZEOF(idstr), "Font%dSty", i);
+ style = (BYTE) DBGetContactSettingByte(NULL, "CLC", idstr, (lf->lfWeight == FW_NORMAL ? 0 : DBFONTF_BOLD) | (lf->lfItalic ? DBFONTF_ITALIC : 0) | (lf->lfUnderline ? DBFONTF_UNDERLINE : 0));
+ lf->lfWidth = lf->lfEscapement = lf->lfOrientation = 0;
+ lf->lfWeight = style & DBFONTF_BOLD ? FW_BOLD : FW_NORMAL;
+ lf->lfItalic = (style & DBFONTF_ITALIC) != 0;
+ lf->lfUnderline = (style & DBFONTF_UNDERLINE) != 0;
+ lf->lfStrikeOut = 0;
+ mir_snprintf(idstr, SIZEOF(idstr), "Font%dSet", i);
+ lf->lfCharSet = DBGetContactSettingByte(NULL, "CLC", idstr, lf->lfCharSet);
+ lf->lfOutPrecision = OUT_DEFAULT_PRECIS;
+ lf->lfClipPrecision = CLIP_DEFAULT_PRECIS;
+ lf->lfQuality = DEFAULT_QUALITY;
+ lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE;
+}
+
+void fnLoadClcOptions(HWND hwnd, struct ClcData *dat)
+{
+ dat->rowHeight = DBGetContactSettingByte(NULL, "CLC", "RowHeight", CLCDEFAULT_ROWHEIGHT);
+ {
+ int i;
+ LOGFONT lf;
+ SIZE fontSize;
+
+ HDC hdc = GetDC(hwnd);
+ for (i = 0; i <= FONTID_MAX; i++)
+ {
+ if (!dat->fontInfo[i].changed)
+ DeleteObject(dat->fontInfo[i].hFont);
+
+ cli.pfnGetFontSetting(i, &lf, &dat->fontInfo[i].colour);
+ lf.lfHeight = -MulDiv(lf.lfHeight, GetDeviceCaps(hdc, LOGPIXELSY), 72);
+
+ dat->fontInfo[i].hFont = CreateFontIndirect(&lf);
+ dat->fontInfo[i].changed = 0;
+
+ HFONT holdfont = (HFONT)SelectObject(hdc,dat->fontInfo[i].hFont);
+ GetTextExtentPoint32(hdc, _T("x"), 1, &fontSize);
+ SelectObject(hdc, holdfont);
+
+ dat->fontInfo[i].fontHeight = fontSize.cy;
+ }
+ ReleaseDC(hwnd, hdc);
+ }
+ dat->leftMargin = DBGetContactSettingByte(NULL, "CLC", "LeftMargin", CLCDEFAULT_LEFTMARGIN);
+ dat->exStyle = DBGetContactSettingDword(NULL, "CLC", "ExStyle", cli.pfnGetDefaultExStyle());
+ dat->scrollTime = DBGetContactSettingWord(NULL, "CLC", "ScrollTime", CLCDEFAULT_SCROLLTIME);
+ dat->groupIndent = DBGetContactSettingByte(NULL, "CLC", "GroupIndent", CLCDEFAULT_GROUPINDENT);
+ dat->gammaCorrection = DBGetContactSettingByte(NULL, "CLC", "GammaCorrect", CLCDEFAULT_GAMMACORRECT);
+ dat->showIdle = DBGetContactSettingByte(NULL, "CLC", "ShowIdle", CLCDEFAULT_SHOWIDLE);
+ dat->noVScrollbar = DBGetContactSettingByte(NULL, "CLC", "NoVScrollBar", 0);
+ SendMessage(hwnd, INTM_SCROLLBARCHANGED, 0, 0);
+ if (!dat->bkChanged) {
+ DBVARIANT dbv;
+ dat->bkColour = DBGetContactSettingDword(NULL, "CLC", "BkColour", CLCDEFAULT_BKCOLOUR);
+ if (dat->hBmpBackground) {
+ DeleteObject(dat->hBmpBackground);
+ dat->hBmpBackground = NULL;
+ }
+ if (DBGetContactSettingByte(NULL, "CLC", "UseBitmap", CLCDEFAULT_USEBITMAP)) {
+ if (!DBGetContactSettingString(NULL, "CLC", "BkBitmap", &dbv)) {
+ dat->hBmpBackground = (HBITMAP) CallService(MS_UTILS_LOADBITMAP, 0, (LPARAM) dbv.pszVal);
+ mir_free(dbv.pszVal);
+ }
+ }
+ dat->backgroundBmpUse = DBGetContactSettingWord(NULL, "CLC", "BkBmpUse", CLCDEFAULT_BKBMPUSE);
+ }
+ dat->greyoutFlags = DBGetContactSettingDword(NULL, "CLC", "GreyoutFlags", CLCDEFAULT_GREYOUTFLAGS);
+ dat->offlineModes = DBGetContactSettingDword(NULL, "CLC", "OfflineModes", CLCDEFAULT_OFFLINEMODES);
+ dat->selBkColour = DBGetContactSettingDword(NULL, "CLC", "SelBkColour", CLCDEFAULT_SELBKCOLOUR);
+ dat->selTextColour = DBGetContactSettingDword(NULL, "CLC", "SelTextColour", CLCDEFAULT_SELTEXTCOLOUR);
+ dat->hotTextColour = DBGetContactSettingDword(NULL, "CLC", "HotTextColour", CLCDEFAULT_HOTTEXTCOLOUR);
+ dat->quickSearchColour = DBGetContactSettingDword(NULL, "CLC", "QuickSearchColour", CLCDEFAULT_QUICKSEARCHCOLOUR);
+ dat->useWindowsColours = DBGetContactSettingByte(NULL, "CLC", "UseWinColours", CLCDEFAULT_USEWINDOWSCOLOURS);
+ {
+ NMHDR hdr;
+ hdr.code = CLN_OPTIONSCHANGED;
+ hdr.hwndFrom = hwnd;
+ hdr.idFrom = GetDlgCtrlID(hwnd);
+ SendMessage(GetParent(hwnd), WM_NOTIFY, 0, (LPARAM) & hdr);
+ }
+ SendMessage(hwnd, WM_SIZE, 0, 0);
+}
+
+#define GSIF_HASMEMBERS 0x80000000
+#define GSIF_ALLCHECKED 0x40000000
+#define GSIF_INDEXMASK 0x3FFFFFFF
+void fnRecalculateGroupCheckboxes(HWND, struct ClcData *dat)
+{
+ struct ClcGroup *group;
+ int check;
+
+ group = &dat->list;
+ group->scanIndex = GSIF_ALLCHECKED;
+ for (;;) {
+ if ((group->scanIndex & GSIF_INDEXMASK) == group->cl.count) {
+ check = (group->scanIndex & (GSIF_HASMEMBERS | GSIF_ALLCHECKED)) == (GSIF_HASMEMBERS | GSIF_ALLCHECKED);
+ if (group->parent == NULL)
+ break;
+ group->parent->scanIndex |= group->scanIndex & GSIF_HASMEMBERS;
+ group = group->parent;
+ if (check)
+ group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->flags |= CONTACTF_CHECKED;
+ else {
+ group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->flags &= ~CONTACTF_CHECKED;
+ group->scanIndex &= ~GSIF_ALLCHECKED;
+ }
+ }
+ else if (group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->type == CLCIT_GROUP) {
+ group = group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->group;
+ group->scanIndex = GSIF_ALLCHECKED;
+ continue;
+ }
+ else if (group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->type == CLCIT_CONTACT) {
+ group->scanIndex |= GSIF_HASMEMBERS;
+ if (!(group->cl.items[(group->scanIndex & GSIF_INDEXMASK)]->flags & CONTACTF_CHECKED))
+ group->scanIndex &= ~GSIF_ALLCHECKED;
+ }
+ group->scanIndex++;
+ }
+}
+
+void fnSetGroupChildCheckboxes(struct ClcGroup *group, int checked)
+{
+ int i;
+
+ for (i = 0; i < group->cl.count; i++) {
+ if (group->cl.items[i]->type == CLCIT_GROUP) {
+ cli.pfnSetGroupChildCheckboxes(group->cl.items[i]->group, checked);
+ if (checked)
+ group->cl.items[i]->flags |= CONTACTF_CHECKED;
+ else
+ group->cl.items[i]->flags &= ~CONTACTF_CHECKED;
+ }
+ else if (group->cl.items[i]->type == CLCIT_CONTACT) {
+ if (checked)
+ group->cl.items[i]->flags |= CONTACTF_CHECKED;
+ else
+ group->cl.items[i]->flags &= ~CONTACTF_CHECKED;
+ }
+ }
+}
+
+void fnInvalidateItem(HWND hwnd, struct ClcData *dat, int iItem)
+{
+ RECT rc;
+ if ( iItem == -1 )
+ return;
+
+ GetClientRect(hwnd, &rc);
+ rc.top = cli.pfnGetRowTopY(dat, iItem) - dat->yScroll;
+ rc.bottom = rc.top + cli.pfnGetRowHeight(dat, iItem);
+ cli.pfnInvalidateRect(hwnd, &rc, FALSE);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// row coord functions
+
+int fnGetRowTopY(struct ClcData *dat, int item)
+{ return item * dat->rowHeight;
+}
+
+int fnGetRowBottomY(struct ClcData *dat, int item)
+{ return (item+1) * dat->rowHeight;
+}
+
+int fnGetRowTotalHeight(struct ClcData *dat)
+{ return dat->rowHeight * cli.pfnGetGroupContentsCount(&dat->list, 1);
+}
+
+int fnGetRowHeight(struct ClcData *dat, int)
+{ return dat->rowHeight;
+}
+
+int fnRowHitTest(struct ClcData *dat, int y)
+{ if (!dat->rowHeight)
+ return y;
+ return y / dat->rowHeight;
+}
diff --git a/src/modules/clist/clistcore.cpp b/src/modules/clist/clistcore.cpp
new file mode 100644
index 0000000000..2fe4a288e9
--- /dev/null
+++ b/src/modules/clist/clistcore.cpp
@@ -0,0 +1,224 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#include "commonheaders.h"
+#include "clc.h"
+#include "genmenu.h"
+
+CLIST_INTERFACE cli = { 0 };
+
+static TCHAR szTip[MAX_TIP_SIZE+1];
+
+int LoadContactListModule2( void );
+int LoadCLCModule( void );
+void BuildProtoMenus( void );
+
+static int interfaceInited = 0;
+
+static void fnPaintClc( HWND, ClcData*, HDC, RECT* )
+{
+}
+
+static struct ClcContact* fnCreateClcContact( void )
+{
+ return ( struct ClcContact* )mir_calloc( sizeof( struct ClcContact ));
+}
+
+static BOOL fnInvalidateRect( HWND hwnd, CONST RECT* lpRect,BOOL bErase )
+{
+ return InvalidateRect( hwnd, lpRect, bErase );
+}
+
+static void fnOnCreateClc( void )
+{
+}
+
+static void fnReloadProtoMenus( void )
+{
+ RebuildMenuOrder();
+ if (DBGetContactSettingByte(NULL, "CList", "MoveProtoMenus", FALSE))
+ BuildProtoMenus();
+ cli.pfnCluiProtocolStatusChanged(0,0);
+}
+
+static INT_PTR srvRetrieveInterface( WPARAM, LPARAM lParam )
+{
+ int rc;
+
+ if ( interfaceInited == 0 ) {
+ cli.version = 6;
+ cli.bDisplayLocked = TRUE;
+
+ cli.pfnClcOptionsChanged = fnClcOptionsChanged;
+ cli.pfnClcBroadcast = fnClcBroadcast;
+ cli.pfnContactListControlWndProc = fnContactListControlWndProc;
+ cli.pfnBuildGroupPopupMenu = fnBuildGroupPopupMenu;
+
+ cli.pfnRegisterFileDropping = fnRegisterFileDropping;
+ cli.pfnUnregisterFileDropping = fnUnregisterFileDropping;
+
+ cli.pfnGetRowsPriorTo = fnGetRowsPriorTo;
+ cli.pfnFindItem = fnFindItem;
+ cli.pfnGetRowByIndex = fnGetRowByIndex;
+ cli.pfnContactToHItem = fnContactToHItem;
+ cli.pfnContactToItemHandle = fnContactToItemHandle;
+
+ cli.pfnAddGroup = fnAddGroup;
+ cli.pfnAddItemToGroup = fnAddItemToGroup;
+ cli.pfnCreateClcContact = fnCreateClcContact;
+ cli.pfnRemoveItemFromGroup = fnRemoveItemFromGroup;
+ cli.pfnFreeContact = fnFreeContact;
+ cli.pfnFreeGroup = fnFreeGroup;
+ cli.pfnAddInfoItemToGroup = fnAddInfoItemToGroup;
+ cli.pfnAddContactToGroup = fnAddContactToGroup;
+ cli.pfnAddContactToTree = fnAddContactToTree;
+ cli.pfnDeleteItemFromTree = fnDeleteItemFromTree;
+ cli.pfnRebuildEntireList = fnRebuildEntireList;
+ cli.pfnGetGroupContentsCount = fnGetGroupContentsCount;
+ cli.pfnSortCLC = fnSortCLC;
+ cli.pfnSaveStateAndRebuildList = fnSaveStateAndRebuildList;
+
+ cli.pfnProcessExternalMessages = fnProcessExternalMessages;
+
+ cli.pfnPaintClc = fnPaintClc;
+
+ cli.pfnGetGroupCountsText = fnGetGroupCountsText;
+ cli.pfnHitTest = fnHitTest;
+ cli.pfnScrollTo = fnScrollTo;
+ cli.pfnEnsureVisible = fnEnsureVisible;
+ cli.pfnRecalcScrollBar = fnRecalcScrollBar;
+ cli.pfnSetGroupExpand = fnSetGroupExpand;
+ cli.pfnDoSelectionDefaultAction = fnDoSelectionDefaultAction;
+ cli.pfnFindRowByText = fnFindRowByText;
+ cli.pfnEndRename = fnEndRename;
+ cli.pfnDeleteFromContactList = fnDeleteFromContactList;
+ cli.pfnBeginRenameSelection = fnBeginRenameSelection;
+ cli.pfnCalcEipPosition = fnCalcEipPosition;
+ cli.pfnGetDropTargetInformation = fnGetDropTargetInformation;
+ cli.pfnClcStatusToPf2 = fnClcStatusToPf2;
+ cli.pfnIsHiddenMode = fnIsHiddenMode;
+ cli.pfnHideInfoTip = fnHideInfoTip;
+ cli.pfnNotifyNewContact = fnNotifyNewContact;
+ cli.pfnGetDefaultExStyle = fnGetDefaultExStyle;
+ cli.pfnGetDefaultFontSetting = fnGetDefaultFontSetting;
+ cli.pfnGetFontSetting = fnGetFontSetting;
+ cli.pfnLoadClcOptions = fnLoadClcOptions;
+ cli.pfnRecalculateGroupCheckboxes = fnRecalculateGroupCheckboxes;
+ cli.pfnSetGroupChildCheckboxes = fnSetGroupChildCheckboxes;
+ cli.pfnInvalidateItem = fnInvalidateItem;
+ cli.pfnGetRowBottomY = fnGetRowBottomY;
+ cli.pfnGetRowHeight = fnGetRowHeight;
+ cli.pfnGetRowTopY = fnGetRowTopY;
+ cli.pfnGetRowTotalHeight = fnGetRowTotalHeight;
+ cli.pfnRowHitTest = fnRowHitTest;
+
+ cli.pfnAddEvent = fnAddEvent;
+ cli.pfnCreateEvent = fnCreateEvent;
+ cli.pfnEventsProcessContactDoubleClick = fnEventsProcessContactDoubleClick;
+ cli.pfnEventsProcessTrayDoubleClick = fnEventsProcessTrayDoubleClick;
+ cli.pfnFreeEvent = fnFreeEvent;
+ cli.pfnGetEvent = fnGetEvent;
+ cli.pfnGetImlIconIndex = fnGetImlIconIndex;
+ cli.pfnRemoveEvent = fnRemoveEvent;
+
+ cli.pfnGetContactDisplayName = fnGetContactDisplayName;
+ cli.pfnInvalidateDisplayNameCacheEntry = fnInvalidateDisplayNameCacheEntry;
+ cli.pfnCreateCacheItem = fnCreateCacheItem;
+ cli.pfnCheckCacheItem = fnCheckCacheItem;
+ cli.pfnFreeCacheItem = fnFreeCacheItem;
+ cli.pfnGetCacheEntry = fnGetCacheEntry;
+
+ cli.szTip = szTip;
+ cli.pfnInitTray = fnInitTray;
+ cli.pfnUninitTray = fnUninitTray;
+ cli.pfnLockTray = fnLockTray;
+ cli.pfnUnlockTray = fnUnlockTray;
+
+ cli.pfnTrayCycleTimerProc = fnTrayCycleTimerProc;
+ cli.pfnTrayIconAdd = fnTrayIconAdd;
+ cli.pfnTrayIconDestroy = fnTrayIconDestroy;
+ cli.pfnTrayIconIconsChanged = fnTrayIconIconsChanged;
+ cli.pfnTrayIconInit = fnTrayIconInit;
+ cli.pfnTrayIconMakeTooltip = fnTrayIconMakeTooltip;
+ cli.pfnTrayIconPauseAutoHide = fnTrayIconPauseAutoHide;
+ cli.pfnTrayIconProcessMessage = fnTrayIconProcessMessage;
+ cli.pfnTrayIconRemove = fnTrayIconRemove;
+ cli.pfnTrayIconSetBaseInfo = fnTrayIconSetBaseInfo;
+ cli.pfnTrayIconSetToBase = fnTrayIconSetToBase;
+ cli.pfnTrayIconTaskbarCreated = fnTrayIconTaskbarCreated;
+ cli.pfnTrayIconUpdate = fnTrayIconUpdate;
+ cli.pfnTrayIconUpdateBase = fnTrayIconUpdateBase;
+ cli.pfnTrayIconUpdateWithImageList = fnTrayIconUpdateWithImageList;
+ cli.pfnCListTrayNotify = fnCListTrayNotify;
+
+ cli.pfnContactListWndProc = fnContactListWndProc;
+ cli.pfnLoadCluiGlobalOpts = fnLoadCluiGlobalOpts;
+ cli.pfnCluiProtocolStatusChanged = fnCluiProtocolStatusChanged;
+ cli.pfnDrawMenuItem = fnDrawMenuItem;
+ cli.pfnInvalidateRect = fnInvalidateRect;
+ cli.pfnOnCreateClc = fnOnCreateClc;
+
+ cli.pfnChangeContactIcon = fnChangeContactIcon;
+ cli.pfnLoadContactTree = fnLoadContactTree;
+ cli.pfnCompareContacts = fnCompareContacts;
+ cli.pfnSortContacts = fnSortContacts;
+ cli.pfnSetHideOffline = fnSetHideOffline;
+
+ cli.pfnDocking_ProcessWindowMessage = fnDocking_ProcessWindowMessage;
+
+ cli.pfnGetIconFromStatusMode = fnGetIconFromStatusMode;
+ cli.pfnGetWindowVisibleState = fnGetWindowVisibleState;
+ cli.pfnIconFromStatusMode = fnIconFromStatusMode;
+ cli.pfnShowHide = fnShowHide;
+ cli.pfnGetStatusModeDescription = fnGetStatusModeDescription;
+
+ cli.pfnGetGroupName = fnGetGroupName;
+ cli.pfnRenameGroup = fnRenameGroup;
+
+ cli.pfnHotKeysRegister = fnHotKeysRegister;
+ cli.pfnHotKeysUnregister = fnHotKeysUnregister;
+ cli.pfnHotKeysProcess = fnHotKeysProcess;
+ cli.pfnHotkeysProcessMessage = fnHotkeysProcessMessage;
+
+ cli.pfnGetProtocolVisibility = fnGetProtocolVisibility;
+ cli.pfnGetProtoIndexByPos = fnGetProtoIndexByPos;
+ cli.pfnReloadProtoMenus = fnReloadProtoMenus;
+ cli.pfnGetAccountIndexByPos = fnGetAccountIndexByPos;
+ cli.pfnGetProtocolMenu = fnGetProtocolMenu;
+
+ cli.hInst = ( HMODULE )lParam;
+
+ rc = LoadContactListModule2();
+ if (rc == 0)
+ rc = LoadCLCModule();
+ interfaceInited = 1;
+ }
+
+ return ( LPARAM )&cli;
+}
+
+int LoadContactListModule()
+{
+ CreateServiceFunction( MS_CLIST_RETRIEVE_INTERFACE, srvRetrieveInterface );
+ return 0;
+}
diff --git a/src/modules/clist/clistevents.cpp b/src/modules/clist/clistevents.cpp
new file mode 100644
index 0000000000..20f5995c4e
--- /dev/null
+++ b/src/modules/clist/clistevents.cpp
@@ -0,0 +1,441 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+struct CListEvent
+{
+ int imlIconIndex;
+ int flashesDone;
+ CLISTEVENT cle;
+};
+
+struct CListImlIcon
+{
+ int index;
+ HICON hIcon;
+};
+static struct CListImlIcon *imlIcon;
+static int imlIconCount;
+
+extern HIMAGELIST hCListImages;
+
+static UINT_PTR flashTimerId;
+static int iconsOn;
+static int disableTrayFlash;
+static int disableIconFlash;
+
+int fnGetImlIconIndex(HICON hIcon)
+{
+ int i;
+
+ for (i = 0; i < imlIconCount; i++) {
+ if (imlIcon[i].hIcon == hIcon)
+ return imlIcon[i].index;
+ }
+ imlIcon = (struct CListImlIcon *) mir_realloc(imlIcon, sizeof(struct CListImlIcon) * (imlIconCount + 1));
+ imlIconCount++;
+ imlIcon[i].hIcon = hIcon;
+ imlIcon[i].index = ImageList_AddIcon(hCListImages, hIcon);
+ return imlIcon[i].index;
+}
+
+static char * GetEventProtocol(int idx)
+{
+ if (cli.events.count && idx>=0 && idx<cli.events.count)
+ {
+ char *szProto;
+ if (cli.events.items[idx]->cle.hContact == NULL)
+ {
+ if (cli.events.items[idx]->cle.flags&CLEF_PROTOCOLGLOBAL)
+ szProto = cli.events.items[idx]->cle.lpszProtocol;
+ else
+ szProto = NULL;
+ }
+ else
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) cli.events.items[idx]->cle.hContact, 0);
+ return szProto;
+ }
+ return NULL;
+}
+
+static void ShowOneEventInTray(int idx)
+{
+ cli.pfnTrayIconUpdateWithImageList((iconsOn || disableTrayFlash) ? cli.events.items[idx]->imlIconIndex : 0, cli.events.items[idx]->cle.ptszTooltip, GetEventProtocol(idx));
+}
+
+static void ShowEventsInTray()
+{
+ int i;
+ char ** pTrayProtos;
+ char nTrayProtoCnt;
+ int nTrayCnt=cli.trayIconCount;
+ if (!cli.events.count || !nTrayCnt) return;
+ if (cli.events.count ==1 || nTrayCnt == 1)
+ {
+ ShowOneEventInTray(0); //for only one icon in tray show topmost event
+ return;
+ }
+
+ // in case if we have several icons in tray and several events with different protocols
+ // lets use several icon to show events from protocols in different icons
+ cli.pfnLockTray();
+ pTrayProtos = (char**)_alloca(sizeof(char*)*cli.trayIconCount);
+ nTrayProtoCnt=0;
+ for (i=0; i<cli.trayIconCount; i++)
+ {
+ if (cli.trayIcon[i].id == 0 || !cli.trayIcon[i].szProto) continue;
+ pTrayProtos[nTrayProtoCnt++]=cli.trayIcon[i].szProto;
+ }
+ for (i=0; i<cli.events.count; i++)
+ {
+ char * iEventProto=GetEventProtocol(i);
+ {
+ int j;
+ for (j=0; j<nTrayProtoCnt; j++)
+ if ( iEventProto && pTrayProtos[j] && !lstrcmpA(pTrayProtos[j],iEventProto))
+ break;
+ if ( j>=nTrayProtoCnt ) j=0; //event was not found so assume first icon
+ if ( pTrayProtos[j] ) //if not already set
+ ShowOneEventInTray(i); //show it
+ pTrayProtos[j]=NULL; //and clear slot
+ }
+ }
+ cli.pfnUnlockTray();
+}
+
+static VOID CALLBACK IconFlashTimer(HWND, UINT, UINT_PTR idEvent, DWORD)
+{
+ int i, j;
+ ShowEventsInTray();
+ for (i = 0; i < cli.events.count; i++) {
+ for (j = 0; j < i; j++)
+ if (cli.events.items[j]->cle.hContact == cli.events.items[i]->cle.hContact)
+ break;
+ if (j >= i)
+ cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact, iconsOn || disableIconFlash ? cli.events.items[i]->imlIconIndex : 0, 0);
+ //decrease eflashes in any case - no need to collect all events
+ if (cli.events.items[i]->cle.flags & CLEF_ONLYAFEW) {
+ if (0 >= --cli.events.items[i]->flashesDone)
+ cli.pfnRemoveEvent( cli.events.items[i]->cle.hContact, cli.events.items[i]->cle.hDbEvent);
+ } }
+
+ if (cli.events.count == 0) {
+ KillTimer(NULL, idEvent);
+ cli.pfnTrayIconSetToBase( NULL );
+ }
+
+ iconsOn = !iconsOn;
+}
+
+struct CListEvent* fnAddEvent( CLISTEVENT *cle )
+{
+ int i;
+ struct CListEvent* p;
+
+ if (cle==NULL || cle->cbSize != sizeof(CLISTEVENT))
+ return NULL;
+
+ if (cle->flags & CLEF_URGENT) {
+ for (i = 0; i < cli.events.count; i++)
+ if (!(cli.events.items[i]->cle.flags & CLEF_URGENT))
+ break;
+ }
+ else i = cli.events.count;
+
+ if (( p = ( struct CListEvent* )cli.pfnCreateEvent()) == NULL )
+ return NULL;
+
+ List_Insert(( SortedList* )&cli.events, p, i );
+ p->cle = *cle;
+ p->imlIconIndex = fnGetImlIconIndex(cli.events.items[i]->cle.hIcon);
+ p->flashesDone = 12;
+ p->cle.pszService = mir_strdup(cli.events.items[i]->cle.pszService);
+ #if defined( _UNICODE )
+ if (p->cle.flags & CLEF_UNICODE)
+ p->cle.ptszTooltip = mir_tstrdup((TCHAR*)p->cle.ptszTooltip);
+ else
+ p->cle.ptszTooltip = mir_a2u((char*)p->cle.pszTooltip); //if no flag defined it handled as unicode
+ #else
+ p->cle.ptszTooltip = mir_tstrdup(p->cle.ptszTooltip);
+ #endif
+ if (cli.events.count == 1) {
+ char *szProto;
+ if (cle->hContact == NULL)
+ {
+ if (cle->flags&CLEF_PROTOCOLGLOBAL)
+ szProto = cle->lpszProtocol;
+ else
+ szProto=NULL;
+ }
+ else
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)cle->hContact, 0);
+ iconsOn = 1;
+ flashTimerId = SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "IconFlashTime", 550), IconFlashTimer);
+ cli.pfnTrayIconUpdateWithImageList( p->imlIconIndex, p->cle.ptszTooltip, szProto);
+ }
+ cli.pfnChangeContactIcon(cle->hContact, p->imlIconIndex, 1);
+ cli.pfnSortContacts();
+ return p;
+}
+
+// Removes an event from the contact list's queue
+// Returns 0 if the event was successfully removed, or nonzero if the event was not found
+int fnRemoveEvent( HANDLE hContact, HANDLE dbEvent )
+{
+ int i;
+ char *szProto;
+ int nSameProto=0;
+
+ // Find the event that should be removed
+ for (i = 0; i < cli.events.count; i++)
+ if ((cli.events.items[i]->cle.hContact == hContact) && (cli.events.items[i]->cle.hDbEvent == dbEvent))
+ break;
+
+ // Event was not found
+ if (i == cli.events.count)
+ return 1;
+
+ // Update contact's icon
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
+ cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact,
+ CallService(MS_CLIST_GETCONTACTICON, (WPARAM)cli.events.items[i]->cle.hContact, 1),
+ 0);
+
+ // Free any memory allocated to the event
+ cli.pfnFreeEvent( cli.events.items[i] );
+ List_Remove(( SortedList* )&cli.events, i );
+ {
+ //count same protocoled events
+ char * szEventProto;
+ for (i = 0; i < cli.events.count; i++)
+ {
+ if (cli.events.items[i]->cle.hContact)
+ szEventProto=(char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)(cli.events.items[i]->cle.hContact), 0);
+ else if (cli.events.items[i]->cle.flags&CLEF_PROTOCOLGLOBAL)
+ szEventProto=(char *) cli.events.items[i]->cle.lpszProtocol;
+ else
+ szEventProto = NULL;
+ if (szEventProto && szProto && !lstrcmpA(szEventProto,szProto))
+ nSameProto++;
+
+ }
+
+ }
+ if (cli.events.count == 0 || nSameProto == 0) {
+ if (cli.events.count == 0)
+ KillTimer(NULL, flashTimerId);
+ cli.pfnTrayIconSetToBase( hContact == NULL ? NULL : szProto);
+ }
+ else {
+ if (cli.events.items[0]->cle.hContact == NULL)
+ szProto = NULL;
+ else
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) cli.events.items[0]->cle.hContact, 0);
+ cli.pfnTrayIconUpdateWithImageList(iconsOn ? cli.events.items[0]->imlIconIndex : 0, cli.events.items[0]->cle.ptszTooltip, szProto);
+ }
+
+ return 0;
+}
+
+CLISTEVENT* fnGetEvent( HANDLE hContact, int idx )
+{
+ if ( hContact == INVALID_HANDLE_VALUE) {
+ if (idx >= cli.events.count)
+ return NULL;
+ return &cli.events.items[idx]->cle;
+ }
+
+ for (int i = 0; i < cli.events.count; i++)
+ if (cli.events.items[i]->cle.hContact == hContact)
+ if (idx-- == 0)
+ return &cli.events.items[i]->cle;
+ return NULL;
+}
+
+int fnEventsProcessContactDoubleClick(HANDLE hContact)
+{
+ for (int i = 0; i < cli.events.count; i++) {
+ if (cli.events.items[i]->cle.hContact == hContact) {
+ HANDLE hDbEvent = cli.events.items[i]->cle.hDbEvent;
+ CallService(cli.events.items[i]->cle.pszService, (WPARAM) (HWND) NULL, (LPARAM) & cli.events.items[i]->cle);
+ cli.pfnRemoveEvent(hContact, hDbEvent);
+ return 0;
+ } }
+
+ return 1;
+}
+
+int fnEventsProcessTrayDoubleClick(int index)
+{
+ BOOL click_in_first_icon=FALSE;
+ if (cli.events.count) {
+ HANDLE hContact, hDbEvent;
+ int eventIndex=0;
+ cli.pfnLockTray();
+ if (cli.trayIconCount>1 && index>0) {
+ int i;
+ char * szProto=NULL;
+ for (i=0; i<cli.trayIconCount; i++)
+ if (cli.trayIcon[i].id==index) {
+ szProto=cli.trayIcon[i].szProto;
+ if (i==0) click_in_first_icon=TRUE;
+ break;
+ }
+ if (szProto) {
+ for(i=0; i<cli.events.count; i++) {
+ char * eventProto=NULL;
+ if (cli.events.items[i]->cle.hContact)
+ eventProto=(char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)cli.events.items[i]->cle.hContact, 0);
+ if (!eventProto)
+ eventProto=cli.events.items[i]->cle.lpszProtocol;
+
+ if (!eventProto || !_strcmpi(eventProto, szProto)) {
+ eventIndex=i;
+ break;
+ } }
+
+ if (i==cli.events.count) { //EventNotFound
+ //lets process backward try to find first event without desired proto in tray
+ int j;
+ if (click_in_first_icon)
+ for(i=0; i<cli.events.count; i++) {
+ char * eventProto=NULL;
+ if (cli.events.items[i]->cle.hContact)
+ eventProto=(char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)cli.events.items[i]->cle.hContact, 0);
+ if (!eventProto)
+ eventProto=cli.events.items[i]->cle.lpszProtocol;
+ if (eventProto) {
+ for (j=0; j<cli.trayIconCount; j++)
+ if (cli.trayIcon[j].szProto && !_strcmpi(eventProto, cli.trayIcon[j].szProto))
+ break;
+
+ if (j==cli.trayIconCount) {
+ eventIndex=i;
+ break;
+ } } }
+ if (i==cli.events.count) { //not found
+ cli.pfnUnlockTray();
+ return 1; //continue processing to show contact list
+ } } } }
+
+ cli.pfnUnlockTray();
+ hContact = cli.events.items[eventIndex]->cle.hContact;
+ hDbEvent = cli.events.items[eventIndex]->cle.hDbEvent;
+ //if (!ServiceExists(cli.events.items[eventIndex]->cle.pszService))
+ // ; may be better to show send msg?
+ CallService(cli.events.items[eventIndex]->cle.pszService, (WPARAM) NULL, (LPARAM) & cli.events.items[eventIndex]->cle);
+ cli.pfnRemoveEvent(hContact, hDbEvent);
+ return 0;
+ }
+ return 1;
+}
+
+static int RemoveEventsForContact(WPARAM wParam, LPARAM)
+{
+ int j, hit;
+
+ /*
+ the for(;;) loop is used here since the cli.events.count can not be relied upon to take us
+ thru the cli.events.items[] array without suffering from shortsightedness about how many unseen
+ events remain, e.g. three events, we remove the first, we're left with 2, the event
+ loop exits at 2 and we never see the real new 2.
+ */
+
+ for (; cli.events.count > 0;) {
+ for (hit = 0, j = 0; j < cli.events.count; j++) {
+ if (cli.events.items[j]->cle.hContact == (HANDLE) wParam) {
+ cli.pfnRemoveEvent((HANDLE)wParam, cli.events.items[j]->cle.hDbEvent);
+ hit = 1;
+ }
+ }
+ if (j == cli.events.count && hit == 0)
+ return 0; /* got to the end of the array and didnt remove anything */
+ }
+
+ return 0;
+}
+
+static int CListEventSettingsChanged(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE) wParam;
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam;
+ if (hContact == NULL && cws && cws->szModule && cws->szSetting && strcmp(cws->szModule, "CList") == 0) {
+ if (strcmp(cws->szSetting, "DisableTrayFlash") == 0)
+ disableTrayFlash = (int) cws->value.bVal;
+ else if (strcmp(cws->szSetting, "NoIconBlink") == 0)
+ disableIconFlash = (int) cws->value.bVal;
+ }
+ return 0;
+}
+
+/***************************************************************************************/
+
+INT_PTR AddEventSyncStub(WPARAM wParam, LPARAM lParam) { return CallServiceSync(MS_CLIST_ADDEVENT"_SYNC",wParam, lParam); }
+INT_PTR AddEventStub(WPARAM, LPARAM lParam) { return cli.pfnAddEvent((CLISTEVENT*)lParam ) == NULL; }
+INT_PTR RemoveEventStub(WPARAM wParam, LPARAM lParam) { return cli.pfnRemoveEvent((HANDLE)wParam,(HANDLE)lParam ); }
+INT_PTR GetEventStub(WPARAM wParam, LPARAM lParam) { return (INT_PTR)cli.pfnGetEvent((HANDLE)wParam,(int)lParam); }
+
+int InitCListEvents(void)
+{
+ memset( &cli.events, 0, sizeof(cli.events));
+ cli.events.increment = 10;
+
+ disableTrayFlash = DBGetContactSettingByte(NULL, "CList", "DisableTrayFlash", 0);
+ disableIconFlash = DBGetContactSettingByte(NULL, "CList", "NoIconBlink", 0);
+ CreateServiceFunction(MS_CLIST_ADDEVENT, AddEventSyncStub); //need to be called through sync to keep flash timer workable
+ CreateServiceFunction(MS_CLIST_ADDEVENT"_SYNC", AddEventStub);
+ CreateServiceFunction(MS_CLIST_REMOVEEVENT, RemoveEventStub);
+ CreateServiceFunction(MS_CLIST_GETEVENT, GetEventStub);
+ HookEvent(ME_DB_CONTACT_DELETED, RemoveEventsForContact);
+ HookEvent(ME_DB_CONTACT_SETTINGCHANGED, CListEventSettingsChanged);
+ return 0;
+}
+
+struct CListEvent* fnCreateEvent( void )
+{
+ return (struct CListEvent*)mir_calloc( sizeof(struct CListEvent));
+}
+
+void fnFreeEvent( struct CListEvent* p )
+{
+ if ( p->cle.pszService )
+ mir_free( p->cle.pszService );
+ if ( p->cle.pszTooltip )
+ mir_free( p->cle.pszTooltip );
+ mir_free( p );
+}
+
+void UninitCListEvents(void)
+{
+ int i;
+
+ if (cli.events.count) KillTimer(NULL, flashTimerId);
+
+ for (i = 0; i < cli.events.count; i++)
+ cli.pfnFreeEvent(( struct CListEvent* )cli.events.items[i] );
+ List_Destroy(( SortedList* )&cli.events );
+
+ if ( imlIcon != NULL )
+ mir_free( imlIcon );
+}
diff --git a/src/modules/clist/clistmenus.cpp b/src/modules/clist/clistmenus.cpp
new file mode 100644
index 0000000000..6c2a4f7a3f
--- /dev/null
+++ b/src/modules/clist/clistmenus.cpp
@@ -0,0 +1,1443 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#pragma hdrstop
+
+#include "m_hotkeys.h"
+
+#include "clc.h"
+#include "genmenu.h"
+
+#define MS_CLIST_HKSTATUS "Clist/HK/SetStatus"
+
+#define FIRSTCUSTOMMENUITEMID 30000
+#define MENU_CUSTOMITEMMAIN 0x80000000
+//#define MENU_CUSTOMITEMCONTEXT 0x40000000
+//#define MENU_CUSTOMITEMFRAME 0x20000000
+
+typedef struct {
+ WORD id;
+ int iconId;
+ CLISTMENUITEM mi;
+}
+ CListIntMenuItem,*lpCListIntMenuItem;
+
+//new menu sys
+HANDLE hMainMenuObject = 0;
+HANDLE hContactMenuObject = 0;
+HANDLE hStatusMenuObject = 0;
+int UnloadMoveToGroup(void);
+
+int statustopos(int status);
+void Proto_SetStatus(const char* szProto, unsigned status);
+
+bool prochotkey;
+
+HANDLE hPreBuildMainMenuEvent, hStatusModeChangeEvent, hPreBuildContactMenuEvent;
+
+static HANDLE hAckHook;
+
+static HMENU hMainMenu,hStatusMenu = 0;
+static const int statusModeList[ MAX_STATUS_COUNT ] =
+{
+ ID_STATUS_OFFLINE, ID_STATUS_ONLINE, ID_STATUS_AWAY, ID_STATUS_NA, ID_STATUS_OCCUPIED,
+ ID_STATUS_DND, ID_STATUS_FREECHAT, ID_STATUS_INVISIBLE, ID_STATUS_ONTHEPHONE, ID_STATUS_OUTTOLUNCH
+};
+
+static const int skinIconStatusList[ MAX_STATUS_COUNT ] =
+{
+ SKINICON_STATUS_OFFLINE, SKINICON_STATUS_ONLINE, SKINICON_STATUS_AWAY, SKINICON_STATUS_NA, SKINICON_STATUS_OCCUPIED,
+ SKINICON_STATUS_DND, SKINICON_STATUS_FREE4CHAT, SKINICON_STATUS_INVISIBLE, SKINICON_STATUS_ONTHEPHONE, SKINICON_STATUS_OUTTOLUNCH
+};
+
+static const int statusModePf2List[ MAX_STATUS_COUNT ] =
+{
+ 0xFFFFFFFF, PF2_ONLINE, PF2_SHORTAWAY, PF2_LONGAWAY, PF2_LIGHTDND,
+ PF2_HEAVYDND, PF2_FREECHAT, PF2_INVISIBLE, PF2_ONTHEPHONE, PF2_OUTTOLUNCH
+};
+
+static INT_PTR statusHotkeys[ MAX_STATUS_COUNT ];
+
+PMO_IntMenuItem* hStatusMainMenuHandles;
+int hStatusMainMenuHandlesCnt;
+
+typedef struct
+{
+ int protoindex;
+ int protostatus[ MAX_STATUS_COUNT ];
+ PMO_IntMenuItem menuhandle[ MAX_STATUS_COUNT ];
+}
+ tStatusMenuHandles,*lpStatusMenuHandles;
+
+lpStatusMenuHandles hStatusMenuHandles;
+int hStatusMenuHandlesCnt;
+
+//mainmenu exec param(ownerdata)
+typedef struct
+{
+ char *szServiceName;
+ TCHAR *szMenuName;
+ int Param1;
+}
+ MainMenuExecParam,*lpMainMenuExecParam;
+
+//contactmenu exec param(ownerdata)
+//also used in checkservice
+typedef struct
+{
+ char *szServiceName;
+ char *pszContactOwner;//for check proc
+ int param;
+}
+ ContactMenuExecParam,*lpContactMenuExecParam;
+
+typedef struct
+{
+ char *szProto;
+ int isOnList;
+ int isOnline;
+}
+ BuildContactParam;
+
+typedef struct
+{
+ char *proto; //This is unique protoname
+ int protoindex;
+ int status;
+
+ BOOL custom;
+ char *svc;
+ HANDLE hMenuItem;
+}
+ StatusMenuExecParam,*lpStatusMenuExecParam;
+
+typedef struct _MenuItemHandles
+{
+ HMENU OwnerMenu;
+ int position;
+}
+ MenuItemData;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// service functions
+
+void FreeMenuProtos( void )
+{
+ int i;
+
+ if ( cli.menuProtos ) {
+ for ( i=0; i < cli.menuProtoCount; i++ )
+ if ( cli.menuProtos[i].szProto )
+ mir_free(cli.menuProtos[i].szProto);
+
+ mir_free( cli.menuProtos );
+ cli.menuProtos = NULL;
+ }
+ cli.menuProtoCount = 0;
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+int GetAverageMode(int* pNetProtoCount = NULL)
+{
+ int netProtoCount = 0;
+ int averageMode = 0;
+
+ for ( int i=0; i < accounts.getCount(); i++ ) {
+ PROTOACCOUNT* pa = accounts[i];
+ if ( cli.pfnGetProtocolVisibility( pa->szModuleName ) == 0 )
+ continue;
+
+ netProtoCount++;
+
+ if ( averageMode == 0 )
+ averageMode = CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0 );
+ else if ( averageMode > 0 && averageMode != CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0 )) {
+ averageMode = -1;
+ if (pNetProtoCount == NULL) break;
+ }
+ }
+
+ if (pNetProtoCount) *pNetProtoCount = netProtoCount;
+ return averageMode;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MAIN MENU
+
+/*
+wparam=handle to the menu item returned by MS_CLIST_ADDCONTACTMENUITEM
+return 0 on success.
+*/
+
+static INT_PTR RemoveMainMenuItem(WPARAM wParam, LPARAM)
+{
+ CallService(MO_REMOVEMENUITEM,wParam,0);
+ return 0;
+}
+
+static INT_PTR BuildMainMenu(WPARAM, LPARAM)
+{
+ ListParam param = { 0 };
+ param.MenuObjectHandle = hMainMenuObject;
+
+ NotifyEventHooks(hPreBuildMainMenuEvent,(WPARAM)0,(LPARAM)0);
+
+ CallService(MO_BUILDMENU,(WPARAM)hMainMenu,(LPARAM)&param);
+ DrawMenuBar((HWND)CallService("CLUI/GetHwnd",(WPARAM)0,(LPARAM)0));
+ return (INT_PTR)hMainMenu;
+}
+
+static INT_PTR AddMainMenuItem(WPARAM, LPARAM lParam)
+{
+ CLISTMENUITEM* mi = ( CLISTMENUITEM* )lParam;
+ if ( mi->cbSize != sizeof( CLISTMENUITEM ))
+ return NULL;
+
+ TMO_MenuItem tmi = { 0 };
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = mi->flags;
+ tmi.hIcon = mi->hIcon;
+ tmi.hotKey = mi->hotKey;
+ tmi.ptszName = mi->ptszName;
+ tmi.position = mi->position;
+
+ //pszPopupName for new system mean root level
+ //pszPopupName for old system mean that exists popup
+ tmi.root = ( HGENMENU )mi->pszPopupName;
+ {
+ lpMainMenuExecParam mmep;
+ mmep = ( lpMainMenuExecParam )mir_alloc( sizeof( MainMenuExecParam ));
+ if ( mmep == NULL )
+ return 0;
+
+ //we need just one parametr.
+ mmep->szServiceName = mir_strdup(mi->pszService);
+ mmep->Param1 = mi->popupPosition;
+ mmep->szMenuName = tmi.ptszName;
+ tmi.ownerdata=mmep;
+ }
+
+ PMO_IntMenuItem pimi = MO_AddNewMenuItem( hMainMenuObject, &tmi );
+
+ char* name;
+ bool needFree = false;
+
+ if (mi->pszService)
+ name = mi->pszService;
+ else if (mi->flags & CMIF_UNICODE) {
+ name = mir_t2a( mi->ptszName );
+ needFree = true;
+ }
+ else
+ name = mi->pszName;
+
+ MO_SetOptionsMenuItem( pimi, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )name );
+ if (needFree) mir_free(name);
+
+ return ( INT_PTR )pimi;
+}
+
+int MainMenuCheckService(WPARAM, LPARAM)
+{
+ return 0;
+}
+
+//called with:
+//wparam - ownerdata
+//lparam - lparam from winproc
+INT_PTR MainMenuExecService(WPARAM wParam, LPARAM lParam)
+{
+ lpMainMenuExecParam mmep = ( lpMainMenuExecParam )wParam;
+ if ( mmep != NULL ) {
+ // bug in help.c,it used wparam as parent window handle without reason.
+ if ( !lstrcmpA(mmep->szServiceName,"Help/AboutCommand"))
+ mmep->Param1 = 0;
+
+ CallService(mmep->szServiceName,mmep->Param1,lParam);
+ }
+ return 1;
+}
+
+INT_PTR FreeOwnerDataMainMenu(WPARAM, LPARAM lParam)
+{
+ lpMainMenuExecParam mmep = ( lpMainMenuExecParam )lParam;
+ if ( mmep != NULL ) {
+ FreeAndNil(( void** )&mmep->szServiceName);
+ FreeAndNil(( void** )&mmep);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CONTACT MENU
+
+static INT_PTR RemoveContactMenuItem(WPARAM wParam, LPARAM)
+{
+ CallService(MO_REMOVEMENUITEM,wParam,0);
+ return 0;
+}
+
+static INT_PTR AddContactMenuItem(WPARAM, LPARAM lParam)
+{
+ CLISTMENUITEM *mi=(CLISTMENUITEM*)lParam;
+ if ( mi->cbSize != sizeof( CLISTMENUITEM ))
+ return 0;
+
+ TMO_MenuItem tmi = { 0 };
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = mi->flags;
+ tmi.hIcon = mi->hIcon;
+ tmi.hotKey = mi->hotKey;
+ tmi.position = mi->position;
+ tmi.ptszName = mi->ptszName;
+ tmi.root = ( HGENMENU )mi->pszPopupName;
+
+ if ( !( mi->flags & CMIF_ROOTHANDLE )) {
+ //old system
+ tmi.flags |= CMIF_ROOTHANDLE;
+ tmi.root = NULL;
+ }
+
+ //owner data
+ lpContactMenuExecParam cmep = ( lpContactMenuExecParam )mir_calloc(sizeof(ContactMenuExecParam));
+ cmep->szServiceName = mir_strdup( mi->pszService );
+ if ( mi->pszContactOwner != NULL )
+ cmep->pszContactOwner = mir_strdup( mi->pszContactOwner );
+ cmep->param = mi->popupPosition;
+ tmi.ownerdata = cmep;
+
+ //may be need to change how UniqueName is formed?
+ PMO_IntMenuItem menuHandle = MO_AddNewMenuItem( hContactMenuObject, &tmi );
+ char buf[ 256 ];
+ if (mi->pszService)
+ mir_snprintf( buf, SIZEOF(buf), "%s/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", (mi->pszService) ? mi->pszService : "" );
+ else if (mi->ptszName)
+ {
+ if (tmi.flags&CMIF_UNICODE)
+ {
+ char * temp = mir_t2a(mi->ptszName);
+ mir_snprintf( buf, SIZEOF(buf), "%s/NoService/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", temp );
+ mir_free(temp);
+ }
+ else
+ mir_snprintf( buf, SIZEOF(buf), "%s/NoService/%s", (mi->pszContactOwner) ? mi->pszContactOwner : "", mi->ptszName );
+ }
+ else buf[0]='\0';
+ if (buf[0]) MO_SetOptionsMenuItem( menuHandle, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf );
+ return ( INT_PTR )menuHandle;
+}
+
+static INT_PTR BuildContactMenu(WPARAM wParam, LPARAM)
+{
+ HANDLE hContact = ( HANDLE )wParam;
+ NotifyEventHooks(hPreBuildContactMenuEvent,(WPARAM)hContact,0);
+
+ char *szProto=(char*)CallService(MS_PROTO_GETCONTACTBASEPROTO,(WPARAM)hContact,0);
+
+ BuildContactParam bcp;
+ bcp.szProto = szProto;
+ bcp.isOnList = ( DBGetContactSettingByte(hContact,"CList","NotOnList",0) == 0 );
+ bcp.isOnline = ( szProto != NULL && ID_STATUS_OFFLINE != DBGetContactSettingWord(hContact,szProto,"Status",ID_STATUS_OFFLINE));
+
+ ListParam param = { 0 };
+ param.MenuObjectHandle = hContactMenuObject;
+ param.wParam = (WPARAM)&bcp;
+
+ HMENU hMenu = CreatePopupMenu();
+ CallService(MO_BUILDMENU,(WPARAM)hMenu,(LPARAM)&param);
+
+ return (INT_PTR)hMenu;
+}
+
+//called with:
+//wparam - ownerdata
+//lparam - lparam from winproc
+INT_PTR ContactMenuExecService(WPARAM wParam,LPARAM lParam)
+{
+ if (wParam!=0) {
+ lpContactMenuExecParam cmep=(lpContactMenuExecParam)wParam;
+ //call with wParam=(WPARAM)(HANDLE)hContact,lparam=popupposition
+ CallService(cmep->szServiceName,lParam,cmep->param);
+ }
+ return 0;
+}
+
+//true - ok,false ignore
+INT_PTR ContactMenuCheckService(WPARAM wParam,LPARAM)
+{
+ PCheckProcParam pcpp = ( PCheckProcParam )wParam;
+ BuildContactParam *bcp=NULL;
+ lpContactMenuExecParam cmep=NULL;
+ TMO_MenuItem mi;
+
+ if ( pcpp == NULL )
+ return FALSE;
+
+ bcp = ( BuildContactParam* )pcpp->wParam;
+ if ( bcp == NULL )
+ return FALSE;
+
+ cmep = ( lpContactMenuExecParam )pcpp->MenuItemOwnerData;
+ if ( cmep == NULL ) //this is root...build it
+ return TRUE;
+
+ if ( cmep->pszContactOwner != NULL ) {
+ if ( bcp->szProto == NULL ) return FALSE;
+ if ( strcmp( cmep->pszContactOwner, bcp->szProto )) return FALSE;
+ }
+ if ( MO_GetMenuItem(( WPARAM )pcpp->MenuItemHandle, ( LPARAM )&mi ) == 0 ) {
+ if ( mi.flags & CMIF_HIDDEN ) return FALSE;
+ if ( mi.flags & CMIF_NOTONLIST && bcp->isOnList ) return FALSE;
+ if ( mi.flags & CMIF_NOTOFFLIST && !bcp->isOnList ) return FALSE;
+ if ( mi.flags & CMIF_NOTONLINE && bcp->isOnline ) return FALSE;
+ if ( mi.flags & CMIF_NOTOFFLINE && !bcp->isOnline ) return FALSE;
+ }
+ return TRUE;
+}
+
+INT_PTR FreeOwnerDataContactMenu (WPARAM, LPARAM lParam)
+{
+ lpContactMenuExecParam cmep = ( lpContactMenuExecParam )lParam;
+ if ( cmep != NULL ) {
+ FreeAndNil(( void** )&cmep->szServiceName);
+ FreeAndNil(( void** )&cmep->pszContactOwner);
+ FreeAndNil(( void** )&cmep);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// STATUS MENU
+
+BOOL FindMenuHandleByGlobalID(HMENU hMenu, PMO_IntMenuItem id, MenuItemData* itdat)
+{
+ int i;
+ PMO_IntMenuItem pimi;
+ MENUITEMINFO mii={0};
+ BOOL inSub=FALSE;
+ if (!itdat)
+ return FALSE;
+
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ mii.fMask = MIIM_SUBMENU | MIIM_DATA;
+ for ( i = GetMenuItemCount( hMenu )-1; i >= 0; i-- ) {
+ GetMenuItemInfo(hMenu,i,TRUE,&mii);
+ if ( mii.fType == MFT_SEPARATOR )
+ continue;
+ if ( mii.hSubMenu )
+ inSub = FindMenuHandleByGlobalID(mii.hSubMenu, id, itdat);
+ if ( inSub )
+ return inSub;
+
+ pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData );
+ if ( pimi != NULL ) {
+ if ( pimi == id ) {
+ itdat->OwnerMenu = hMenu;
+ itdat->position = i;
+ return TRUE;
+ } } }
+
+ return FALSE;
+}
+
+INT_PTR StatusMenuCheckService(WPARAM wParam, LPARAM)
+{
+ PCheckProcParam pcpp = ( PCheckProcParam )wParam;
+ if ( !pcpp )
+ return TRUE;
+
+ PMO_IntMenuItem timi = MO_GetIntMenuItem( pcpp->MenuItemHandle );
+ if ( !timi )
+ return TRUE;
+
+ StatusMenuExecParam *smep = ( StatusMenuExecParam* )pcpp->MenuItemOwnerData;
+ if (smep && !smep->status && smep->custom )
+ {
+ if (wildcmp(smep->svc, "*XStatus*"))
+ {
+ int XStatus = CallProtoService(smep->proto, "/GetXStatus", 0, 0);
+ char buf[255];
+ mir_snprintf( buf, sizeof(buf), "*XStatus%d", XStatus );
+
+ bool check = wildcmp(smep->svc, buf);
+ bool reset = wildcmp(smep->svc, "*XStatus0");
+
+ if (check)
+ timi->mi.flags |= CMIF_CHECKED;
+ else
+ timi->mi.flags &= ~CMIF_CHECKED;
+
+ if ( reset || check )
+ {
+ PMO_IntMenuItem timiParent = MO_GetIntMenuItem( timi->mi.root );
+ if (timiParent)
+ {
+ CLISTMENUITEM mi2 = {0};
+ mi2.cbSize = sizeof(mi2);
+ mi2.flags = CMIM_NAME | CMIF_TCHAR;
+ mi2.ptszName = timi->mi.hIcon ? timi->mi.ptszName : TranslateT("Custom status");
+
+ timiParent = MO_GetIntMenuItem( timi->mi.root );
+
+ MenuItemData it = {0};
+
+ if (FindMenuHandleByGlobalID(hStatusMenu, timiParent, &it))
+ {
+ MENUITEMINFO mi ={0};
+ TCHAR d[100];
+ GetMenuString(it.OwnerMenu, it.position, d, SIZEOF(d), MF_BYPOSITION);
+
+ if (!IsWinVer98Plus())
+ {
+ mi.cbSize = MENUITEMINFO_V4_SIZE;
+ mi.fMask = MIIM_TYPE | MIIM_STATE;
+ mi.fType = MFT_STRING;
+ }
+ else
+ {
+ mi.cbSize = sizeof( mi );
+ mi.fMask = MIIM_STRING | MIIM_STATE;
+ if ( timi->iconId != -1 )
+ {
+ mi.fMask |= MIIM_BITMAP;
+ if (IsWinVerVistaPlus() && isThemeActive()) {
+ if (timi->hBmp == NULL)
+ timi->hBmp = ConvertIconToBitmap(NULL, timi->parent->m_hMenuIcons, timi->iconId);
+ mi.hbmpItem = timi->hBmp;
+ }
+ else
+ mi.hbmpItem = HBMMENU_CALLBACK;
+ }
+ }
+
+ mi.fState |= (check && !reset ? MFS_CHECKED : MFS_UNCHECKED );
+ mi.dwTypeData = mi2.ptszName;
+ SetMenuItemInfo(it.OwnerMenu, it.position, TRUE, &mi);
+ }
+
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)timi->mi.root, (LPARAM)&mi2);
+ timiParent->iconId = timi->iconId;
+ if (timiParent->hBmp) DeleteObject(timiParent->hBmp);
+ timiParent->hBmp = NULL;
+ } } }
+ }
+ else if ( smep && smep->status && !smep->custom ) {
+ int curProtoStatus = ( smep->proto ) ? CallProtoService(smep->proto,PS_GETSTATUS,0,0) : GetAverageMode();
+ if ( smep->status == curProtoStatus )
+ timi->mi.flags |= CMIF_CHECKED;
+ else
+ timi->mi.flags &= ~CMIF_CHECKED;
+ }
+ else if (( !smep || smep->proto ) && timi->mi.pszName ) {
+ int curProtoStatus=0;
+ BOOL IconNeedDestroy=FALSE;
+ char* prot;
+ if (smep)
+ prot = smep->proto;
+ else
+ {
+ #ifdef UNICODE
+ char *prn=mir_u2a(timi->mi.ptszName);
+ prot = NEWSTR_ALLOCA( prn );
+ if (prn) mir_free(prn);
+ #else
+ prot = timi->mi.ptszName;
+ #endif
+ }
+ if ( Proto_GetAccount( prot ) == NULL )
+ return TRUE;
+
+ if (( curProtoStatus = CallProtoService(prot,PS_GETSTATUS,0,0)) == CALLSERVICE_NOTFOUND )
+ curProtoStatus = 0;
+
+ if ( curProtoStatus >= ID_STATUS_OFFLINE && curProtoStatus < ID_STATUS_IDLE )
+ timi->mi.hIcon = LoadSkinProtoIcon(prot,curProtoStatus);
+ else {
+ timi->mi.hIcon=(HICON)CallProtoService(prot,PS_LOADICON,PLI_PROTOCOL|PLIF_SMALL,0);
+ if ( timi->mi.hIcon == (HICON)CALLSERVICE_NOTFOUND )
+ timi->mi.hIcon = NULL;
+ else
+ IconNeedDestroy = TRUE;
+ }
+
+ if (timi->mi.hIcon) {
+ timi->mi.flags |= CMIM_ICON;
+ MO_ModifyMenuItem( timi, &timi->mi );
+ if ( IconNeedDestroy ) {
+ DestroyIcon( timi->mi.hIcon );
+ timi->mi.hIcon = NULL;
+ }
+ else IconLib_ReleaseIcon(timi->mi.hIcon,0);
+ } }
+
+ return TRUE;
+}
+
+INT_PTR StatusMenuExecService(WPARAM wParam, LPARAM)
+{
+ lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )wParam;
+ if ( smep != NULL ) {
+ if ( smep->custom ) {
+ if (smep->svc && *smep->svc)
+ CallService(smep->svc, 0, (LPARAM)smep->hMenuItem);
+ }
+ else {
+ if ( smep->status == 0 && smep->protoindex !=0 && smep->proto != NULL ) {
+ PMO_IntMenuItem pimi;
+ char *prot = smep->proto;
+ char szHumanName[64]={0};
+ PROTOACCOUNT * acc = Proto_GetAccount( smep->proto );
+ int i=(DBGetContactSettingByte(NULL,prot,"LockMainStatus",0)?0:1);
+ DBWriteContactSettingByte(NULL,prot,"LockMainStatus",(BYTE)i);
+
+ CallProtoService( smep->proto, PS_GETNAME, (WPARAM)SIZEOF(szHumanName), (LPARAM)szHumanName );
+ pimi = MO_GetIntMenuItem(( HGENMENU )smep->protoindex );
+ PMO_IntMenuItem root = (PMO_IntMenuItem)pimi->mi.root;
+ mir_free( pimi->mi.pszName );
+ mir_free( root->mi.pszName );
+ if ( i ) {
+ TCHAR buf[256];
+ pimi->mi.flags|=CMIF_CHECKED;
+ if ( cli.bDisplayLocked ) {
+ mir_sntprintf(buf,SIZEOF(buf),TranslateT("%s (locked)"),acc->tszAccountName);
+ pimi->mi.ptszName = mir_tstrdup( buf );
+ root->mi.ptszName = mir_tstrdup( buf );
+ }
+ else {
+ pimi->mi.ptszName = mir_tstrdup( acc->tszAccountName );
+ root->mi.ptszName = mir_tstrdup( acc->tszAccountName );
+ }
+ }
+ else {
+ pimi->mi.ptszName = mir_tstrdup( acc->tszAccountName );
+ root->mi.ptszName = mir_tstrdup( acc->tszAccountName );
+ pimi->mi.flags &= ~CMIF_CHECKED;
+ }
+ if ( cli.hwndStatus )
+ InvalidateRect( cli.hwndStatus, NULL, TRUE );
+ }
+ else if ( smep->proto != NULL ) {
+ Proto_SetStatus(smep->proto, smep->status);
+ NotifyEventHooks(hStatusModeChangeEvent, smep->status, (LPARAM)smep->proto);
+ }
+ else {
+ int MenusProtoCount = 0;
+
+ for( int i=0; i < accounts.getCount(); i++ )
+ if ( cli.pfnGetProtocolVisibility( accounts[i]->szModuleName ))
+ MenusProtoCount++;
+
+ cli.currentDesiredStatusMode = smep->status;
+
+ for ( int j=0; j < accounts.getCount(); j++ ) {
+ PROTOACCOUNT* pa = accounts[j];
+ if ( !Proto_IsAccountEnabled( pa ))
+ continue;
+ if ( MenusProtoCount > 1 && Proto_IsAccountLocked( pa ))
+ continue;
+
+ Proto_SetStatus(pa->szModuleName, cli.currentDesiredStatusMode);
+ }
+ NotifyEventHooks( hStatusModeChangeEvent, cli.currentDesiredStatusMode, 0 );
+ DBWriteContactSettingWord( NULL, "CList", "Status", ( WORD )cli.currentDesiredStatusMode );
+ return 1;
+ } } }
+
+ return 0;
+}
+
+INT_PTR FreeOwnerDataStatusMenu(WPARAM, LPARAM lParam)
+{
+ lpStatusMenuExecParam smep = (lpStatusMenuExecParam)lParam;
+ if ( smep != NULL ) {
+ FreeAndNil(( void** )&smep->proto);
+ FreeAndNil(( void** )&smep->svc);
+ FreeAndNil(( void** )&smep);
+ }
+
+ return (0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Other menu functions
+
+//wparam MenuItemHandle
+static INT_PTR ModifyCustomMenuItem(WPARAM wParam,LPARAM lParam)
+{
+ CLISTMENUITEM *mi=(CLISTMENUITEM*)lParam;
+ TMO_MenuItem tmi;
+
+ if ( lParam == 0 )
+ return -1;
+ if ( mi->cbSize != sizeof( CLISTMENUITEM ))
+ return 1;
+
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = mi->flags;
+ tmi.hIcon = mi->hIcon;
+ tmi.hotKey = mi->hotKey;
+ tmi.ptszName = mi->ptszName;
+ return MO_ModifyMenuItem(( PMO_IntMenuItem )wParam, &tmi );
+}
+
+INT_PTR MenuProcessCommand(WPARAM wParam,LPARAM lParam)
+{
+ WORD cmd = LOWORD(wParam);
+
+ if ( HIWORD(wParam) & MPCF_MAINMENU ) {
+ int hst = LOWORD( wParam );
+ if ( hst >= ID_STATUS_OFFLINE && hst <= ID_STATUS_OUTTOLUNCH ) {
+ int pos = statustopos( hst );
+ if ( pos != -1 && hStatusMainMenuHandles != NULL )
+ return MO_ProcessCommand( hStatusMainMenuHandles[ pos ], lParam );
+ } }
+
+ if ( !( cmd >= CLISTMENUIDMIN && cmd <= CLISTMENUIDMAX ))
+ return 0; // DO NOT process ids outside from clist menu id range v0.7.0.27+
+
+ //process old menu sys
+ if ( HIWORD(wParam) & MPCF_CONTACTMENU )
+ return MO_ProcessCommandBySubMenuIdent( (int)hContactMenuObject, LOWORD(wParam), lParam );
+
+ //unknown old menu
+ return MO_ProcessCommandByMenuIdent( LOWORD(wParam), lParam );
+}
+
+BOOL FindMenuHanleByGlobalID(HMENU hMenu, PMO_IntMenuItem id, MenuItemData* itdat)
+{
+ int i;
+ PMO_IntMenuItem pimi;
+ MENUITEMINFO mii = {0};
+ BOOL inSub=FALSE;
+
+ if ( !itdat )
+ return FALSE;
+
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ mii.fMask = MIIM_SUBMENU | MIIM_DATA;
+ for ( i = GetMenuItemCount( hMenu )-1; i >= 0; i-- ) {
+ GetMenuItemInfo( hMenu, i, TRUE, &mii );
+ if ( mii.fType == MFT_SEPARATOR )
+ continue;
+
+ if ( mii.hSubMenu )
+ inSub = FindMenuHanleByGlobalID( mii.hSubMenu, id, itdat );
+ if (inSub)
+ return inSub;
+
+ pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData);
+ if ( pimi != NULL ) {
+ if ( pimi == id ) {
+ itdat->OwnerMenu = hMenu;
+ itdat->position = i;
+ return TRUE;
+ } } }
+
+ return FALSE;
+}
+
+static INT_PTR MenuProcessHotkey(WPARAM vKey, LPARAM)
+{
+ prochotkey = true;
+
+ bool res =
+ MO_ProcessHotKeys( hStatusMenuObject, vKey ) ||
+ MO_ProcessHotKeys( hMainMenuObject, vKey );
+
+ prochotkey = false;
+
+ return res;
+}
+
+static int MenuIconsChanged(WPARAM, LPARAM)
+{
+ //just rebuild menu
+ RebuildMenuOrder();
+ cli.pfnCluiProtocolStatusChanged(0,0);
+ return 0;
+}
+
+static INT_PTR MeasureMenuItem(WPARAM, LPARAM lParam)
+{
+ return MO_MeasureMenuItem(( LPMEASUREITEMSTRUCT )lParam );
+}
+
+static INT_PTR DrawMenuItem(WPARAM, LPARAM lParam)
+{
+ return MO_DrawMenuItem(( LPDRAWITEMSTRUCT )lParam );
+}
+
+int RecursiveDeleteMenu(HMENU hMenu)
+{
+ int cnt = GetMenuItemCount(hMenu);
+ for ( int i=0; i < cnt; i++ ) {
+ HMENU submenu = GetSubMenu(hMenu, 0);
+ if (submenu) DestroyMenu(submenu);
+ DeleteMenu(hMenu, 0, MF_BYPOSITION);
+ }
+ return 0;
+}
+
+static INT_PTR MenuGetMain(WPARAM, LPARAM)
+{
+ RecursiveDeleteMenu(hMainMenu);
+ BuildMainMenu(0,0);
+ return (INT_PTR)hMainMenu;
+}
+
+static INT_PTR BuildStatusMenu(WPARAM, LPARAM)
+{
+ ListParam param = { 0 };
+ param.MenuObjectHandle = hStatusMenuObject;
+
+ RecursiveDeleteMenu(hStatusMenu);
+ CallService(MO_BUILDMENU,(WPARAM)hStatusMenu,(LPARAM)&param);
+ return (INT_PTR)hStatusMenu;
+}
+
+static INT_PTR SetStatusMode(WPARAM wParam, LPARAM)
+{
+ prochotkey = true;
+ MenuProcessCommand(MAKEWPARAM(LOWORD(wParam), MPCF_MAINMENU), 0);
+ prochotkey = false;
+ return 0;
+}
+
+int fnGetProtocolVisibility(const char* accName)
+{
+ if ( accName ) {
+ PROTOACCOUNT* pa = Proto_GetAccount( accName );
+ return pa && pa->bIsVisible && Proto_IsAccountEnabled( pa ) &&
+ pa->ppro && (pa->ppro->GetCaps( PFLAGNUM_2, 0 ) & ~pa->ppro->GetCaps( PFLAGNUM_5, 0 ));
+ }
+
+ return FALSE;
+}
+
+int fnGetProtoIndexByPos(PROTOCOLDESCRIPTOR ** proto, int protoCnt, int Pos)
+{
+ int p;
+ char buf[10];
+ DBVARIANT dbv;
+
+ _itoa( Pos, buf, 10 );
+ if ( !DBGetContactSetting( NULL, "Protocols", buf, &dbv )) {
+ for ( p=0; p < protoCnt; p++ ) {
+ if ( lstrcmpA( proto[p]->szName, dbv.pszVal ) == 0 ) {
+ DBFreeVariant( &dbv );
+ return p;
+ } }
+
+ DBFreeVariant( &dbv );
+ }
+
+ return -1;
+}
+
+int fnGetAccountIndexByPos(int Pos)
+{
+ int i;
+ for ( i=0; i < accounts.getCount(); i++ )
+ if ( accounts[i]->iOrder == Pos )
+ return i;
+
+ return -1;
+}
+
+void RebuildMenuOrder( void )
+{
+ int i,j,s;
+ DWORD flags;
+
+ BYTE bHideStatusMenu = DBGetContactSettingByte( NULL, "CLUI", "DontHideStatusMenu", 0 ); // cool perversion, though
+
+ //clear statusmenu
+ RecursiveDeleteMenu(hStatusMenu);
+
+ //status menu
+ if ( hStatusMenuObject != 0 ) {
+ CallService(MO_REMOVEMENUOBJECT,(WPARAM)hStatusMenuObject,0);
+ mir_free( hStatusMainMenuHandles );
+ mir_free( hStatusMenuHandles );
+ }
+
+ TMenuParam tmp = { 0 };
+ tmp.cbSize = sizeof(tmp);
+ tmp.ExecService = "StatusMenuExecService";
+ tmp.CheckService = "StatusMenuCheckService";
+ tmp.name = "StatusMenu";
+
+ hStatusMenuObject=(HANDLE)CallService(MO_CREATENEWMENUOBJECT,(WPARAM)0,(LPARAM)&tmp);
+ MO_SetOptionsMenuObject( hStatusMenuObject, OPT_MENUOBJECT_SET_FREE_SERVICE, (INT_PTR)"CLISTMENUS/FreeOwnerDataStatusMenu" );
+
+ hStatusMainMenuHandles = ( PMO_IntMenuItem* )mir_calloc( SIZEOF(statusModeList) * sizeof( PMO_IntMenuItem* ));
+ hStatusMainMenuHandlesCnt = SIZEOF(statusModeList);
+
+ hStatusMenuHandles = ( tStatusMenuHandles* )mir_calloc(sizeof(tStatusMenuHandles)*accounts.getCount());
+ hStatusMenuHandlesCnt = accounts.getCount();
+
+ FreeMenuProtos();
+
+ for ( s=0; s < accounts.getCount(); s++ ) {
+ i = cli.pfnGetAccountIndexByPos( s );
+ if ( i == -1 )
+ continue;
+
+ PROTOACCOUNT* pa = accounts[i];
+ int pos = 0;
+ if ( !bHideStatusMenu && !cli.pfnGetProtocolVisibility( pa->szModuleName ))
+ continue;
+
+ flags = pa->ppro->GetCaps( PFLAGNUM_2, 0 ) & ~pa->ppro->GetCaps( PFLAGNUM_5, 0 );
+ int j;
+ HICON ic;
+ TCHAR tbuf[256];
+
+ //adding root
+ TMO_MenuItem tmi = { 0 };
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = CMIF_TCHAR | CMIF_ROOTHANDLE | CMIF_KEEPUNTRANSLATED;
+ tmi.position = pos++;
+ tmi.hIcon = ic = (HICON)CallProtoService( pa->szModuleName, PS_LOADICON, PLI_PROTOCOL | PLIF_SMALL, 0 );
+
+ if ( Proto_IsAccountLocked( pa ) && cli.bDisplayLocked ) {
+ mir_sntprintf( tbuf, SIZEOF(tbuf), TranslateT("%s (locked)"), pa->tszAccountName );
+ tmi.ptszName = tbuf;
+ }
+ else tmi.ptszName = pa->tszAccountName;
+
+ {
+ //owner data
+ lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_calloc( sizeof( StatusMenuExecParam ));
+ smep->proto = mir_strdup(pa->szModuleName);
+ tmi.ownerdata = smep;
+ }
+ PMO_IntMenuItem rootmenu = MO_AddNewMenuItem( hStatusMenuObject, &tmi );
+
+ memset(&tmi,0,sizeof(tmi));
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = CMIF_TCHAR | CMIF_ROOTHANDLE | CMIF_KEEPUNTRANSLATED;
+ tmi.root = rootmenu;
+ tmi.position = pos++;
+ tmi.hIcon = ic;
+ {
+ //owner data
+ lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_alloc( sizeof( StatusMenuExecParam ));
+ memset( smep, 0, sizeof( *smep ));
+ smep->proto = mir_strdup(pa->szModuleName);
+ tmi.ownerdata = smep;
+ }
+
+ if ( Proto_IsAccountLocked( pa ))
+ tmi.flags |= CMIF_CHECKED;
+
+ if (( tmi.flags & CMIF_CHECKED ) && cli.bDisplayLocked ) {
+ mir_sntprintf( tbuf, SIZEOF(tbuf), TranslateT("%s (locked)"), pa->tszAccountName );
+ tmi.ptszName = tbuf;
+ }
+ else tmi.ptszName = pa->tszAccountName;
+
+ PMO_IntMenuItem menuHandle = MO_AddNewMenuItem( hStatusMenuObject, &tmi );
+ ((lpStatusMenuExecParam)tmi.ownerdata)->protoindex = ( int )menuHandle;
+ MO_ModifyMenuItem( menuHandle, &tmi );
+
+ cli.menuProtos=(MenuProto*)mir_realloc(cli.menuProtos, sizeof(MenuProto)*(cli.menuProtoCount+1));
+ memset(&(cli.menuProtos[cli.menuProtoCount]),0,sizeof(MenuProto));
+ cli.menuProtos[cli.menuProtoCount].pMenu = rootmenu;
+ cli.menuProtos[cli.menuProtoCount].szProto = mir_strdup(pa->szModuleName);
+
+ cli.menuProtoCount++;
+ {
+ char buf[256];
+ mir_snprintf( buf, SIZEOF(buf), "RootProtocolIcon_%s", pa->szModuleName );
+ MO_SetOptionsMenuItem( menuHandle, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf );
+ }
+ DestroyIcon(ic);
+ pos += 500000;
+
+ for ( j=0; j < SIZEOF(statusModeList); j++ ) {
+ if ( !( flags & statusModePf2List[j] ))
+ continue;
+
+ //adding
+ memset( &tmi, 0, sizeof( tmi ));
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR;
+ if ( statusModeList[j] == ID_STATUS_OFFLINE )
+ tmi.flags |= CMIF_CHECKED;
+ tmi.root = rootmenu;
+ tmi.position = pos++;
+ tmi.ptszName = cli.pfnGetStatusModeDescription( statusModeList[j], GSMDF_UNTRANSLATED );
+ tmi.hIcon = LoadSkinProtoIcon( pa->szModuleName, statusModeList[j] );
+ {
+ //owner data
+ lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_calloc( sizeof( StatusMenuExecParam ));
+ smep->custom = FALSE;
+ smep->status = statusModeList[j];
+ smep->protoindex = i;
+ smep->proto = mir_strdup(pa->szModuleName);
+ tmi.ownerdata = smep;
+ }
+
+ hStatusMenuHandles[i].protoindex = i;
+ hStatusMenuHandles[i].protostatus[j] = statusModeList[j];
+ hStatusMenuHandles[i].menuhandle[j] = MO_AddNewMenuItem( hStatusMenuObject, &tmi );
+ {
+ char buf[ 256 ];
+ mir_snprintf(buf, SIZEOF(buf), "ProtocolIcon_%s_%s",pa->szModuleName,tmi.pszName);
+ MO_SetOptionsMenuItem( hStatusMenuHandles[i].menuhandle[j], OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf );
+ }
+ IconLib_ReleaseIcon(tmi.hIcon,0);
+ } }
+
+ NotifyEventHooks(cli.hPreBuildStatusMenuEvent, 0, 0);
+ int pos = 200000;
+
+ //add to root menu
+ for ( j=0; j < SIZEOF(statusModeList); j++ ) {
+ for ( i=0; i < accounts.getCount(); i++ ) {
+ PROTOACCOUNT* pa = accounts[i];
+ if ( !bHideStatusMenu && !cli.pfnGetProtocolVisibility( pa->szModuleName ))
+ continue;
+
+ flags = pa->ppro->GetCaps(PFLAGNUM_2, 0) & ~pa->ppro->GetCaps(PFLAGNUM_5, 0);
+ if ( !( flags & statusModePf2List[j] ))
+ continue;
+
+ TMO_MenuItem tmi = { 0 };
+ tmi.cbSize = sizeof( tmi );
+ tmi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR;
+ if ( statusModeList[j] == ID_STATUS_OFFLINE )
+ tmi.flags |= CMIF_CHECKED;
+
+ tmi.hIcon = LoadSkinIcon( skinIconStatusList[j] );
+ tmi.position = pos++;
+ tmi.hotKey = MAKELPARAM(MOD_CONTROL,'0'+j);
+ {
+ //owner data
+ lpStatusMenuExecParam smep = ( lpStatusMenuExecParam )mir_alloc( sizeof( StatusMenuExecParam ));
+ smep->custom = FALSE;
+ smep->status = statusModeList[j];
+ smep->proto = NULL;
+ smep->svc = NULL;
+ tmi.ownerdata = smep;
+ }
+ {
+ TCHAR buf[ 256 ], hotkeyName[ 100 ];
+ WORD hotKey = GetHotkeyValue( statusHotkeys[j] );
+ HotkeyToName( hotkeyName, SIZEOF(hotkeyName), HIBYTE(hotKey), LOBYTE(hotKey));
+ mir_sntprintf( buf, SIZEOF( buf ), TranslateT("%s\t%s"),
+ cli.pfnGetStatusModeDescription( statusModeList[j], 0 ), hotkeyName );
+ tmi.ptszName = buf;
+ tmi.hotKey = MAKELONG(HIBYTE(hotKey), LOBYTE(hotKey));
+ hStatusMainMenuHandles[j] = MO_AddNewMenuItem( hStatusMenuObject, &tmi );
+ }
+ {
+ char buf[ 256 ];
+ mir_snprintf( buf, sizeof( buf ), "Root2ProtocolIcon_%s_%s", pa->szModuleName, tmi.pszName );
+ MO_SetOptionsMenuItem( hStatusMainMenuHandles[j], OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf );
+ }
+ IconLib_ReleaseIcon( tmi.hIcon, 0 );
+ break;
+ } }
+
+ BuildStatusMenu(0,0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int sttRebuildHotkeys( WPARAM, LPARAM )
+{
+ TMO_MenuItem tmi = { 0 };
+ tmi.cbSize = sizeof( tmi );
+ tmi.flags = CMIM_HOTKEY | CMIM_NAME | CMIF_TCHAR;
+
+ for ( int j=0; j < SIZEOF(statusModeList); j++ ) {
+ TCHAR buf[ 256 ], hotkeyName[ 100 ];
+ WORD hotKey = GetHotkeyValue( statusHotkeys[j] );
+ HotkeyToName( hotkeyName, SIZEOF(hotkeyName), HIBYTE(hotKey), LOBYTE(hotKey));
+ mir_sntprintf( buf, SIZEOF( buf ), TranslateT("%s\t%s"),
+ cli.pfnGetStatusModeDescription( statusModeList[j], 0 ), hotkeyName );
+ tmi.ptszName = buf;
+ tmi.hotKey = MAKELONG(HIBYTE(hotKey), LOBYTE(hotKey));
+ MO_ModifyMenuItem( hStatusMainMenuHandles[j], &tmi );
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int statustopos(int status)
+{
+ int j;
+ for ( j = 0; j < SIZEOF(statusModeList); j++ )
+ if ( status == statusModeList[j] )
+ return j;
+
+ return -1;
+}
+
+static int MenuProtoAck(WPARAM, LPARAM lParam)
+{
+ int i;
+ ACKDATA* ack=(ACKDATA*)lParam;
+ int overallStatus;
+ TMO_MenuItem tmi;
+
+ if ( ack->type != ACKTYPE_STATUS ) return 0;
+ if ( ack->result != ACKRESULT_SUCCESS ) return 0;
+ if ( hStatusMainMenuHandles == NULL ) return 0;
+
+ if ( cli.pfnGetProtocolVisibility( ack->szModule ) == 0 ) return 0;
+
+ overallStatus = GetAverageMode();
+
+ memset(&tmi,0,sizeof(tmi));
+ tmi.cbSize=sizeof(tmi);
+ if (overallStatus >= ID_STATUS_OFFLINE) {
+ int pos = statustopos(cli.currentStatusMenuItem);
+ if (pos==-1) pos=0;
+ { // reset all current possible checked statuses
+ int pos2;
+ for (pos2=0; pos2<hStatusMainMenuHandlesCnt; pos2++)
+ {
+ if (pos2>=0 && pos2 < hStatusMainMenuHandlesCnt)
+ {
+ tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE;
+ MO_ModifyMenuItem( hStatusMainMenuHandles[pos2], &tmi );
+ } } }
+
+ cli.currentStatusMenuItem=overallStatus;
+ pos = statustopos(cli.currentStatusMenuItem);
+ if (pos>=0 && pos < hStatusMainMenuHandlesCnt) {
+ tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE | CMIF_CHECKED;
+ MO_ModifyMenuItem( hStatusMainMenuHandles[pos], &tmi );
+ }
+// cli.currentDesiredStatusMode = cli.currentStatusMenuItem;
+ }
+ else {
+ int pos = statustopos( cli.currentStatusMenuItem );
+ if ( pos == -1 ) pos=0;
+ if ( pos >= 0 && pos < hStatusMainMenuHandlesCnt ) {
+ tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE;
+ MO_ModifyMenuItem( hStatusMainMenuHandles[pos], &tmi );
+ }
+ //SetMenuDefaultItem(hStatusMenu,-1,FALSE);
+ cli.currentStatusMenuItem=0;
+ }
+
+ for ( i=0; i < accounts.getCount(); i++ ) {
+ if ( !lstrcmpA( accounts[i]->szModuleName, ack->szModule )) {
+ //hProcess is previous mode, lParam is new mode
+ if ((( int )ack->hProcess >= ID_STATUS_OFFLINE || ( int )ack->hProcess == 0 ) && ( int )ack->hProcess < ID_STATUS_OFFLINE + SIZEOF(statusModeList)) {
+ int pos = statustopos(( int )ack->hProcess);
+ if ( pos == -1 )
+ pos = 0;
+ for ( pos = 0; pos < SIZEOF(statusModeList); pos++ ) {
+ tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE;
+ MO_ModifyMenuItem( hStatusMenuHandles[i].menuhandle[pos], &tmi );
+ } }
+
+ if ( ack->lParam >= ID_STATUS_OFFLINE && ack->lParam < ID_STATUS_OFFLINE + SIZEOF(statusModeList)) {
+ int pos = statustopos(( int )ack->lParam );
+ if ( pos >= 0 && pos < SIZEOF(statusModeList)) {
+ tmi.flags = CMIM_FLAGS | CMIF_ROOTHANDLE | CMIF_CHECKED;
+ MO_ModifyMenuItem( hStatusMenuHandles[i].menuhandle[pos], &tmi );
+ } }
+ break;
+ } }
+
+ //BuildStatusMenu(0,0);
+ return 0;
+}
+
+static MenuProto* FindProtocolMenu( const char* proto )
+{
+ for (int i=0; i < cli.menuProtoCount; i++)
+ if ( cli.menuProtos[i].pMenu && !lstrcmpiA( cli.menuProtos[i].szProto, proto ))
+ return &cli.menuProtos[i];
+
+ if ( cli.menuProtoCount == 1 )
+ if ( !lstrcmpiA( cli.menuProtos[0].szProto, proto ))
+ return &cli.menuProtos[0];
+
+ return NULL;
+}
+
+HGENMENU fnGetProtocolMenu( const char* proto )
+{
+ MenuProto* mp = FindProtocolMenu( proto );
+ if ( mp )
+ return mp->pMenu;
+
+ return NULL;
+}
+
+static INT_PTR AddStatusMenuItem(WPARAM wParam,LPARAM lParam)
+{
+ CLISTMENUITEM *mi = ( CLISTMENUITEM* )lParam;
+ if ( mi->cbSize != sizeof( CLISTMENUITEM ))
+ return 0;
+
+ PMO_IntMenuItem pRoot = NULL;
+ lpStatusMenuExecParam smep = NULL;
+
+ TMO_MenuItem tmi = { 0 };
+ tmi.cbSize = sizeof(tmi);
+ tmi.hIcon = mi->hIcon;
+ tmi.hotKey = mi->hotKey;
+ tmi.position = mi->position;
+ tmi.pszName = mi->pszName;
+ tmi.flags = mi->flags;
+ tmi.root = mi->hParentMenu;
+
+ // for new style menus the pszPopupName contains the root menu handle
+ if ( mi->flags & CMIF_ROOTHANDLE )
+ pRoot = MO_GetIntMenuItem( mi->hParentMenu );
+
+ // for old style menus the pszPopupName really means the popup name
+ else {
+ MenuProto* mp = FindProtocolMenu( mi->pszContactOwner );
+ if ( mp && mi->pszPopupName ) {
+ if ( mp->pMenu ) {
+ #if defined _UNICODE
+ TCHAR* ptszName = ( mi->flags & CMIF_UNICODE ) ? mir_tstrdup(mi->ptszPopupName) : mir_a2t(mi->pszPopupName);
+ pRoot = MO_RecursiveWalkMenu( mp->pMenu->submenu.first, FindRoot, ptszName );
+ mir_free( ptszName );
+ #else
+ pRoot = MO_RecursiveWalkMenu( mp->pMenu->submenu.first, FindRoot, mi->pszPopupName );
+ #endif
+ }
+ if ( pRoot == NULL ) {
+ TMO_MenuItem tmi = { 0 };
+ tmi.cbSize = sizeof(tmi);
+ tmi.flags = (mi->flags & CMIF_UNICODE) | CMIF_ROOTHANDLE;
+ tmi.position = 1001;
+ tmi.root = mp->pMenu;
+ tmi.hIcon = NULL;
+ tmi.pszName = mi->pszPopupName;
+ pRoot = MO_AddNewMenuItem( hStatusMenuObject, &tmi );
+ }
+
+ tmi.flags |= CMIF_ROOTHANDLE;
+ tmi.root = pRoot;
+ } }
+
+ if (wParam) {
+ int * res=(int*)wParam;
+ *res = ( int )pRoot;
+ }
+
+ //owner data
+ if ( mi->pszService ) {
+ smep = ( lpStatusMenuExecParam )mir_calloc(sizeof(StatusMenuExecParam));
+ smep->custom = TRUE;
+ smep->svc=mir_strdup(mi->pszService);
+ {
+ char *buf=mir_strdup(mi->pszService);
+ int i=0;
+ while(buf[i]!='\0' && buf[i]!='/') i++;
+ buf[i]='\0';
+ smep->proto=mir_strdup(buf);
+ mir_free(buf);
+ }
+ tmi.ownerdata = smep;
+ }
+ PMO_IntMenuItem menuHandle = MO_AddNewMenuItem( hStatusMenuObject, &tmi );
+ if ( smep )
+ smep->hMenuItem = menuHandle;
+
+ char buf[MAX_PATH+64];
+ #if defined( _UNICODE )
+ {
+ char* p = ( pRoot ) ? mir_t2a( pRoot->mi.ptszName ) : NULL;
+ mir_snprintf( buf, SIZEOF(buf), "%s/%s", ( p ) ? p : "", mi->pszService ? mi->pszService : "" );
+ mir_free( p );
+ }
+ #else
+ mir_snprintf( buf, SIZEOF(buf), "%s/%s", pRoot ? pRoot->mi.ptszName : _T(""), mi->pszService ? mi->pszService : "" );
+ #endif
+ MO_SetOptionsMenuItem( menuHandle, OPT_MENUITEMSETUNIQNAME, ( INT_PTR )buf );
+
+ return ( INT_PTR )menuHandle;
+}
+
+static INT_PTR HotkeySetStatus(WPARAM wParam,LPARAM lParam)
+{
+ return SetStatusMode( lParam, 0 );
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// PROTOCOL MENU
+
+static INT_PTR AddProtoMenuItem(WPARAM wParam,LPARAM lParam)
+{
+ if ( DBGetContactSettingByte( NULL, "CList", "MoveProtoMenus", FALSE ))
+ return AddStatusMenuItem( wParam, lParam );
+
+ return AddMainMenuItem( wParam, lParam );
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void InitCustomMenus(void)
+{
+ CreateServiceFunction("MainMenuExecService",MainMenuExecService);
+
+ CreateServiceFunction("ContactMenuExecService",ContactMenuExecService);
+ CreateServiceFunction("ContactMenuCheckService",ContactMenuCheckService);
+
+ CreateServiceFunction("StatusMenuExecService",StatusMenuExecService);
+ CreateServiceFunction("StatusMenuCheckService",StatusMenuCheckService);
+
+ //free services
+ CreateServiceFunction("CLISTMENUS/FreeOwnerDataMainMenu",FreeOwnerDataMainMenu);
+ CreateServiceFunction("CLISTMENUS/FreeOwnerDataContactMenu",FreeOwnerDataContactMenu);
+ CreateServiceFunction("CLISTMENUS/FreeOwnerDataStatusMenu",FreeOwnerDataStatusMenu);
+
+ CreateServiceFunction(MS_CLIST_SETSTATUSMODE, SetStatusMode);
+
+ CreateServiceFunction(MS_CLIST_ADDMAINMENUITEM,AddMainMenuItem);
+ CreateServiceFunction(MS_CLIST_ADDSTATUSMENUITEM,AddStatusMenuItem);
+ CreateServiceFunction(MS_CLIST_MENUGETMAIN,MenuGetMain);
+ CreateServiceFunction(MS_CLIST_REMOVEMAINMENUITEM,RemoveMainMenuItem);
+ CreateServiceFunction(MS_CLIST_MENUBUILDMAIN,BuildMainMenu);
+
+ CreateServiceFunction(MS_CLIST_ADDCONTACTMENUITEM,AddContactMenuItem);
+ CreateServiceFunction(MS_CLIST_MENUBUILDCONTACT,BuildContactMenu);
+ CreateServiceFunction(MS_CLIST_REMOVECONTACTMENUITEM,RemoveContactMenuItem);
+
+ CreateServiceFunction(MS_CLIST_MODIFYMENUITEM,ModifyCustomMenuItem);
+ CreateServiceFunction(MS_CLIST_MENUMEASUREITEM,MeasureMenuItem);
+ CreateServiceFunction(MS_CLIST_MENUDRAWITEM,DrawMenuItem);
+
+ CreateServiceFunction(MS_CLIST_MENUGETSTATUS,BuildStatusMenu);
+ CreateServiceFunction(MS_CLIST_MENUPROCESSCOMMAND,MenuProcessCommand);
+ CreateServiceFunction(MS_CLIST_MENUPROCESSHOTKEY,MenuProcessHotkey);
+
+ CreateServiceFunction(MS_CLIST_ADDPROTOMENUITEM,AddProtoMenuItem);
+
+ hPreBuildContactMenuEvent=CreateHookableEvent(ME_CLIST_PREBUILDCONTACTMENU);
+ hPreBuildMainMenuEvent=CreateHookableEvent(ME_CLIST_PREBUILDMAINMENU);
+ cli.hPreBuildStatusMenuEvent=CreateHookableEvent(ME_CLIST_PREBUILDSTATUSMENU);
+ hStatusModeChangeEvent = CreateHookableEvent( ME_CLIST_STATUSMODECHANGE );
+
+ hAckHook=(HANDLE)HookEvent(ME_PROTO_ACK,MenuProtoAck);
+
+ hMainMenu = CreatePopupMenu();
+ hStatusMenu = CreatePopupMenu();
+
+ hStatusMainMenuHandles=NULL;
+ hStatusMainMenuHandlesCnt=0;
+
+ hStatusMenuHandles=NULL;
+ hStatusMenuHandlesCnt=0;
+
+ //new menu sys
+ InitGenMenu();
+
+ //main menu
+ {
+ TMenuParam tmp = { 0 };
+ tmp.cbSize=sizeof(tmp);
+ tmp.CheckService=NULL;
+ tmp.ExecService="MainMenuExecService";
+ tmp.name="MainMenu";
+ hMainMenuObject=(HANDLE)CallService(MO_CREATENEWMENUOBJECT,(WPARAM)0,(LPARAM)&tmp);
+ }
+
+ MO_SetOptionsMenuObject( hMainMenuObject, OPT_USERDEFINEDITEMS, TRUE );
+ MO_SetOptionsMenuObject( hMainMenuObject, OPT_MENUOBJECT_SET_FREE_SERVICE, (INT_PTR)"CLISTMENUS/FreeOwnerDataMainMenu" );
+
+ //contact menu
+ {
+ TMenuParam tmp = { 0 };
+ tmp.cbSize=sizeof(tmp);
+ tmp.CheckService="ContactMenuCheckService";
+ tmp.ExecService="ContactMenuExecService";
+ tmp.name="ContactMenu";
+ hContactMenuObject=(HANDLE)CallService(MO_CREATENEWMENUOBJECT,(WPARAM)0,(LPARAM)&tmp);
+ }
+
+ MO_SetOptionsMenuObject( hContactMenuObject, OPT_USERDEFINEDITEMS, TRUE );
+ MO_SetOptionsMenuObject( hContactMenuObject, OPT_MENUOBJECT_SET_FREE_SERVICE, (INT_PTR)"CLISTMENUS/FreeOwnerDataContactMenu" );
+
+ // initialize hotkeys
+ CreateServiceFunction(MS_CLIST_HKSTATUS, HotkeySetStatus);
+
+ HOTKEYDESC hkd = { 0 };
+ hkd.cbSize = sizeof( hkd );
+ hkd.ptszSection = _T("Status");
+ hkd.dwFlags = HKD_TCHAR;
+ for ( int i = 0; i < SIZEOF(statusHotkeys); i++ ) {
+ char szName[30];
+ mir_snprintf( szName, SIZEOF(szName), "StatusHotKey_%d", i );
+ hkd.pszName = szName;
+ hkd.lParam = statusModeList[i];
+ hkd.ptszDescription = fnGetStatusModeDescription( hkd.lParam, 0 );
+ hkd.DefHotKey = HOTKEYCODE( HOTKEYF_CONTROL, '0'+i ) | HKF_MIRANDA_LOCAL;
+ hkd.pszService = MS_CLIST_HKSTATUS;
+ statusHotkeys[i] = CallService( MS_HOTKEY_REGISTER, 0, LPARAM( &hkd ));
+ }
+
+ HookEvent( ME_HOTKEYS_CHANGED, sttRebuildHotkeys );
+
+ // add exit command to menu
+ {
+ CLISTMENUITEM mi = { 0 };
+ mi.cbSize = sizeof( mi );
+ mi.position = 0x7fffffff;
+ mi.flags = CMIF_ICONFROMICOLIB;
+ mi.pszService = "CloseAction";
+ mi.pszName = LPGEN("E&xit");
+ mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_EXIT );
+ AddMainMenuItem( 0, ( LPARAM )&mi );
+ }
+
+ cli.currentStatusMenuItem=ID_STATUS_OFFLINE;
+ cli.currentDesiredStatusMode=ID_STATUS_OFFLINE;
+
+ if ( IsWinVer98Plus() )
+ HookEvent(ME_SKIN_ICONSCHANGED, MenuIconsChanged );
+}
+
+void UninitCustomMenus(void)
+{
+ mir_free(hStatusMainMenuHandles);
+ hStatusMainMenuHandles = NULL;
+
+ mir_free( hStatusMenuHandles );
+ hStatusMenuHandles = NULL;
+
+ if ( hMainMenuObject ) CallService( MO_REMOVEMENUOBJECT, (WPARAM)hMainMenuObject, 0 );
+ if ( hStatusMenuObject ) CallService( MO_REMOVEMENUOBJECT, (WPARAM)hMainMenuObject, 0 );
+
+ UnloadMoveToGroup();
+ FreeMenuProtos();
+
+ DestroyMenu(hMainMenu);
+ DestroyMenu(hStatusMenu);
+ UnhookEvent(hAckHook);
+}
diff --git a/src/modules/clist/clistmod.cpp b/src/modules/clist/clistmod.cpp
new file mode 100644
index 0000000000..7de958342b
--- /dev/null
+++ b/src/modules/clist/clistmod.cpp
@@ -0,0 +1,569 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+INT_PTR AddMainMenuItem(WPARAM wParam, LPARAM lParam);
+INT_PTR AddContactMenuItem(WPARAM wParam, LPARAM lParam);
+INT_PTR ContactChangeGroup(WPARAM wParam, LPARAM lParam);
+int InitCListEvents(void);
+void UninitCListEvents(void);
+int ContactSettingChanged(WPARAM wParam, LPARAM lParam);
+int ContactAdded(WPARAM wParam, LPARAM lParam);
+int ContactDeleted(WPARAM wParam, LPARAM lParam);
+INT_PTR GetContactDisplayName(WPARAM wParam, LPARAM lParam);
+INT_PTR InvalidateDisplayName(WPARAM wParam, LPARAM lParam);
+int InitGroupServices(void);
+INT_PTR Docking_IsDocked(WPARAM wParam, LPARAM lParam);
+void InitDisplayNameCache(void);
+void FreeDisplayNameCache(void);
+int LoadCLUIModule(void);
+int InitClistHotKeys(void);
+
+HANDLE hContactDoubleClicked, hContactIconChangedEvent;
+HIMAGELIST hCListImages;
+BOOL(WINAPI * MySetProcessWorkingSetSize) (HANDLE, SIZE_T, SIZE_T);
+
+extern BYTE nameOrder[];
+
+struct ProtoIconIndex
+{
+ char *szProto;
+ int iIconBase;
+};
+
+OBJLIST<ProtoIconIndex> protoIconIndex(5);
+
+static HANDLE hProtoAckHook;
+static HANDLE hContactSettingChanged;
+
+TCHAR* fnGetStatusModeDescription( int mode, int flags )
+{
+ static TCHAR szMode[64];
+ TCHAR* descr;
+ int noPrefixReqd = 0;
+ switch (mode) {
+ case ID_STATUS_OFFLINE:
+ descr = _T("Offline");
+ noPrefixReqd = 1;
+ break;
+ case ID_STATUS_CONNECTING:
+ descr = _T("Connecting");
+ noPrefixReqd = 1;
+ break;
+ case ID_STATUS_ONLINE:
+ descr = _T("Online");
+ noPrefixReqd = 1;
+ break;
+ case ID_STATUS_AWAY:
+ descr = _T("Away");
+ break;
+ case ID_STATUS_DND:
+ descr = _T("DND");
+ break;
+ case ID_STATUS_NA:
+ descr = _T("NA");
+ break;
+ case ID_STATUS_OCCUPIED:
+ descr = _T("Occupied");
+ break;
+ case ID_STATUS_FREECHAT:
+ descr = _T("Free for chat");
+ break;
+ case ID_STATUS_INVISIBLE:
+ descr = _T("Invisible");
+ break;
+ case ID_STATUS_OUTTOLUNCH:
+ descr = _T("Out to lunch");
+ break;
+ case ID_STATUS_ONTHEPHONE:
+ descr = _T("On the phone");
+ break;
+ case ID_STATUS_IDLE:
+ descr = _T("Idle");
+ break;
+ default:
+ if (mode > ID_STATUS_CONNECTING && mode < ID_STATUS_CONNECTING + MAX_CONNECT_RETRIES) {
+ const TCHAR* connFmt = _T("Connecting (attempt %d)");
+ mir_sntprintf(szMode, SIZEOF(szMode), (flags&GSMDF_UNTRANSLATED)?connFmt:TranslateTS(connFmt), mode - ID_STATUS_CONNECTING + 1);
+ return szMode;
+ }
+ return NULL;
+ }
+ if (noPrefixReqd || !(flags & GSMDF_PREFIXONLINE))
+ return ( flags & GSMDF_UNTRANSLATED ) ? descr : TranslateTS( descr );
+
+ lstrcpy( szMode, TranslateT( "Online" ));
+ lstrcat( szMode, _T(": "));
+ lstrcat( szMode, ( flags & GSMDF_UNTRANSLATED ) ? descr : TranslateTS( descr ));
+ return szMode;
+}
+
+static INT_PTR GetStatusModeDescription(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR* buf1 = cli.pfnGetStatusModeDescription( wParam, lParam );
+
+ #ifdef UNICODE
+ if ( !( lParam & GSMDF_TCHAR ))
+ {
+ static char szMode[64];
+ char *buf2 = mir_u2a(buf1);
+ mir_snprintf(szMode, SIZEOF(szMode), "%s", buf2);
+ mir_free(buf2);
+ return (INT_PTR)szMode;
+ }
+ #endif
+
+ return (INT_PTR)buf1;
+}
+
+static int ProtocolAck(WPARAM, LPARAM lParam)
+{
+ ACKDATA *ack = (ACKDATA *) lParam;
+
+ if (ack->type != ACKTYPE_STATUS)
+ return 0;
+ CallService(MS_CLUI_PROTOCOLSTATUSCHANGED, ack->lParam, (LPARAM) ack->szModule);
+
+ if ((int) ack->hProcess < ID_STATUS_ONLINE && ack->lParam >= ID_STATUS_ONLINE) {
+ DWORD caps;
+ caps = (DWORD) CallProtoService(ack->szModule, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (caps & PF1_SERVERCLIST) {
+ HANDLE hContact;
+ char *szProto;
+
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact) {
+ szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto != NULL && !strcmp(szProto, ack->szModule))
+ if (DBGetContactSettingByte(hContact, "CList", "Delete", 0))
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM) hContact, 0);
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ } } }
+
+ cli.pfnTrayIconUpdateBase(ack->szModule);
+ return 0;
+}
+
+HICON fnGetIconFromStatusMode( HANDLE hContact, const char *szProto, int status )
+{
+ return ImageList_GetIcon( hCListImages, cli.pfnIconFromStatusMode( szProto, status, hContact ), ILD_NORMAL);
+}
+
+int fnIconFromStatusMode(const char *szProto, int status, HANDLE )
+{
+ int index, i;
+
+ for ( index = 0; index < SIZEOF(statusModeList); index++ )
+ if ( status == statusModeList[index] )
+ break;
+
+ if ( index == SIZEOF(statusModeList))
+ index = 0;
+ if (szProto == NULL)
+ return index + 1;
+ for ( i = 0; i < protoIconIndex.getCount(); i++ ) {
+ if (strcmp(szProto, protoIconIndex[i].szProto) == 0)
+ return protoIconIndex[i].iIconBase + index;
+ }
+ return 1;
+}
+
+static INT_PTR GetContactIcon(WPARAM wParam, LPARAM)
+{
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ HANDLE hContact = (HANDLE)wParam;
+
+ return cli.pfnIconFromStatusMode(szProto,
+ szProto == NULL ? ID_STATUS_OFFLINE : DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE), hContact);
+}
+
+static void AddProtoIconIndex( PROTOACCOUNT* pa )
+{
+ ProtoIconIndex *pii = new ProtoIconIndex;
+ pii->szProto = pa->szModuleName;
+ for (int i = 0; i < SIZEOF(statusModeList); i++) {
+ int iImg = ImageList_AddIcon_ProtoIconLibLoaded(hCListImages, pa->szModuleName, statusModeList[i] );
+ if (i == 0)
+ pii->iIconBase = iImg;
+ }
+ protoIconIndex.insert(pii);
+}
+
+static void RemoveProtoIconIndex( PROTOACCOUNT* pa )
+{
+ for (int i = 0; i < protoIconIndex.getCount(); i++)
+ if (strcmp(protoIconIndex[i].szProto, pa->szModuleName) == 0) {
+ protoIconIndex.remove(i);
+ break;
+ }
+}
+
+static int ContactListModulesLoaded(WPARAM, LPARAM)
+{
+ if ( !ServiceExists( MS_DB_CONTACT_GETSETTING_STR )) {
+ MessageBox( NULL, TranslateT( "This plugin requires db3x plugin version 0.5.1.0 or later" ), _T("CList"), MB_OK );
+ return 1;
+ }
+
+ RebuildMenuOrder();
+ for (int i = 0; i < accounts.getCount(); i++)
+ AddProtoIconIndex( accounts[i] );
+
+ cli.pfnLoadContactTree();
+
+ LoadCLUIModule();
+
+ InitClistHotKeys();
+
+ return 0;
+}
+
+static int ContactListAccountsChanged( WPARAM eventCode, LPARAM lParam )
+{
+ switch (eventCode)
+ {
+ case PRAC_ADDED:
+ AddProtoIconIndex(( PROTOACCOUNT* )lParam );
+ break;
+
+ case PRAC_REMOVED:
+ RemoveProtoIconIndex(( PROTOACCOUNT* )lParam );
+ break;
+ }
+ cli.pfnReloadProtoMenus();
+ cli.pfnTrayIconIconsChanged();
+ cli.pfnClcBroadcast( INTM_RELOADOPTIONS, 0, 0 );
+ cli.pfnClcBroadcast( INTM_INVALIDATE, 0, 0 );
+ return 0;
+}
+
+static INT_PTR ContactDoubleClicked(WPARAM wParam, LPARAM)
+{
+ // Try to process event myself
+ if ( cli.pfnEventsProcessContactDoubleClick(( HANDLE )wParam ) == 0 )
+ return 0;
+
+ // Allow third-party plugins to process a dblclick
+ if ( NotifyEventHooks( hContactDoubleClicked, wParam, 0 ))
+ return 0;
+
+ // Otherwise try to execute the default action
+ TryProcessDoubleClick(( HANDLE )wParam );
+ return 0;
+}
+
+static INT_PTR GetIconsImageList(WPARAM, LPARAM)
+{
+ return (INT_PTR)hCListImages;
+}
+
+static INT_PTR ContactFilesDropped(WPARAM wParam, LPARAM lParam)
+{
+ CallService(MS_FILE_SENDSPECIFICFILES, wParam, lParam);
+ return 0;
+}
+
+static int CListIconsChanged(WPARAM, LPARAM)
+{
+ int i, j;
+
+ for (i = 0; i < SIZEOF(statusModeList); i++)
+ ImageList_ReplaceIcon_IconLibLoaded(hCListImages, i + 1, LoadSkinIcon( skinIconStatusList[i] ));
+ ImageList_ReplaceIcon_IconLibLoaded(hCListImages, IMAGE_GROUPOPEN, LoadSkinIcon( SKINICON_OTHER_GROUPOPEN ));
+ ImageList_ReplaceIcon_IconLibLoaded(hCListImages, IMAGE_GROUPSHUT, LoadSkinIcon( SKINICON_OTHER_GROUPSHUT ));
+ for (i = 0; i < protoIconIndex.getCount(); i++)
+ for (j = 0; j < SIZEOF(statusModeList); j++)
+ ImageList_ReplaceIcon_IconLibLoaded(hCListImages, protoIconIndex[i].iIconBase + j, LoadSkinProtoIcon(protoIconIndex[i].szProto, statusModeList[j] ));
+ cli.pfnTrayIconIconsChanged();
+ cli.pfnInvalidateRect( cli.hwndContactList, NULL, TRUE);
+ return 0;
+}
+
+/*
+Begin of Hrk's code for bug
+*/
+#define GWVS_HIDDEN 1
+#define GWVS_VISIBLE 2
+#define GWVS_COVERED 3
+#define GWVS_PARTIALLY_COVERED 4
+
+int fnGetWindowVisibleState(HWND hWnd, int iStepX, int iStepY)
+{
+ RECT rc, rcWin, rcWorkArea;
+ POINT pt;
+ register int i, j, width, height, iCountedDots = 0, iNotCoveredDots = 0;
+ BOOL bPartiallyCovered = FALSE;
+ HWND hAux = 0;
+
+ if (hWnd == NULL) {
+ SetLastError(0x00000006); //Wrong handle
+ return -1;
+ }
+ //Some defaults now. The routine is designed for thin and tall windows.
+ if (iStepX <= 0)
+ iStepX = 4;
+ if (iStepY <= 0)
+ iStepY = 16;
+
+ if (IsIconic(hWnd) || !IsWindowVisible(hWnd))
+ return GWVS_HIDDEN;
+ else
+ {
+ if (CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0))
+ return GWVS_VISIBLE;
+
+ GetWindowRect(hWnd, &rcWin);
+
+ SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, FALSE);
+ if (MyMonitorFromWindow)
+ {
+ HMONITOR hMon = MyMonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO mi;
+ mi.cbSize = sizeof(mi);
+ if (MyGetMonitorInfo(hMon, &mi))
+ rcWorkArea = mi.rcWork;
+ }
+
+ IntersectRect(&rc, &rcWin, &rcWorkArea);
+
+ width = rc.right - rc.left;
+ height = rc.bottom - rc.top;
+
+ for (i = rc.top; i < rc.bottom; i += (height / iStepY)) {
+ pt.y = i;
+ for (j = rc.left; j < rc.right; j += (width / iStepX)) {
+ pt.x = j;
+ hAux = WindowFromPoint(pt);
+ while (GetParent(hAux) != NULL)
+ hAux = GetParent(hAux);
+ if (hAux != hWnd && hAux != NULL) //There's another window!
+ bPartiallyCovered = TRUE;
+ else
+ iNotCoveredDots++; //Let's count the not covered dots.
+ iCountedDots++; //Let's keep track of how many dots we checked.
+ }
+ }
+ if (iNotCoveredDots == iCountedDots) //Every dot was not covered: the window is visible.
+ return GWVS_VISIBLE;
+ else if (iNotCoveredDots == 0) //They're all covered!
+ return GWVS_COVERED;
+ else //There are dots which are visible, but they are not as many as the ones we counted: it's partially covered.
+ return GWVS_PARTIALLY_COVERED;
+ }
+}
+
+int fnShowHide(WPARAM, LPARAM)
+{
+ BOOL bShow = FALSE;
+
+ int iVisibleState = cli.pfnGetWindowVisibleState(cli.hwndContactList, 0, 0);
+
+ //bShow is FALSE when we enter the switch.
+ switch (iVisibleState) {
+ case GWVS_PARTIALLY_COVERED:
+ //If we don't want to bring it to top, we can use a simple break. This goes against readability ;-) but the comment explains it.
+ if (!DBGetContactSettingByte(NULL, "CList", "BringToFront", SETTING_BRINGTOFRONT_DEFAULT))
+ break;
+ case GWVS_COVERED: //Fall through (and we're already falling)
+ case GWVS_HIDDEN:
+ bShow = TRUE;
+ break;
+ case GWVS_VISIBLE: //This is not needed, but goes for readability.
+ bShow = FALSE;
+ break;
+ case -1: //We can't get here, both cli.hwndContactList and iStepX and iStepY are right.
+ return 0;
+ }
+ if (bShow == TRUE) {
+ RECT rcWindow;
+
+ ShowWindow(cli.hwndContactList, SW_RESTORE);
+ if (!DBGetContactSettingByte(NULL, "CList", "OnTop", SETTING_ONTOP_DEFAULT))
+ SetWindowPos(cli.hwndContactList, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+ else
+ SetWindowPos(cli.hwndContactList, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+
+ SetForegroundWindow(cli.hwndContactList);
+ DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_NORMAL);
+
+ //this forces the window onto the visible screen
+ GetWindowRect(cli.hwndContactList, &rcWindow);
+ if (Utils_AssertInsideScreen(&rcWindow) == 1)
+ {
+ MoveWindow(cli.hwndContactList, rcWindow.left, rcWindow.top,
+ rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top, TRUE);
+ }
+ }
+ else { //It needs to be hidden
+ if (DBGetContactSettingByte(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) ||
+ DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT))
+ {
+ ShowWindow(cli.hwndContactList, SW_HIDE);
+ DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_HIDDEN);
+ }
+ else
+ {
+ ShowWindow(cli.hwndContactList, SW_MINIMIZE);
+ DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_MINIMIZED);
+ }
+
+ if (MySetProcessWorkingSetSize != NULL && DBGetContactSettingByte(NULL, "CList", "DisableWorkingSet", 1))
+ MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// old evil code. hopefully it will be deleted soon, cause nobody uses it now
+
+#define SAFESTRING(a) a?a:""
+
+int GetStatusModeOrdering(int statusMode);
+extern int sortByStatus, sortByProto;
+
+static INT_PTR CompareContacts( WPARAM wParam, LPARAM lParam )
+{
+ HANDLE a = (HANDLE) wParam, b = (HANDLE) lParam;
+ TCHAR namea[128], *nameb;
+ int statusa, statusb;
+ char *szProto1, *szProto2;
+ int rc;
+
+ szProto1 = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) a, 0);
+ szProto2 = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) b, 0);
+ statusa = DBGetContactSettingWord((HANDLE) a, SAFESTRING(szProto1), "Status", ID_STATUS_OFFLINE);
+ statusb = DBGetContactSettingWord((HANDLE) b, SAFESTRING(szProto2), "Status", ID_STATUS_OFFLINE);
+
+ if (sortByProto) {
+ /* deal with statuses, online contacts have to go above offline */
+ if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) {
+ return 2 * (statusa == ID_STATUS_OFFLINE) - 1;
+ }
+ /* both are online, now check protocols */
+ rc = strcmp(SAFESTRING(szProto1), SAFESTRING(szProto2)); /* strcmp() doesn't like NULL so feed in "" as needed */
+ if (rc != 0 && (szProto1 != NULL && szProto2 != NULL))
+ return rc;
+ /* protocols are the same, order by display name */
+ }
+
+ if (sortByStatus) {
+ int ordera, orderb;
+ ordera = GetStatusModeOrdering(statusa);
+ orderb = GetStatusModeOrdering(statusb);
+ if (ordera != orderb)
+ return ordera - orderb;
+ }
+ else {
+ //one is offline: offline goes below online
+ if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) {
+ return 2 * (statusa == ID_STATUS_OFFLINE) - 1;
+ }
+ }
+
+ nameb = cli.pfnGetContactDisplayName( a, 0);
+ _tcsncpy(namea, nameb, SIZEOF(namea));
+ namea[ SIZEOF(namea)-1 ] = 0;
+ nameb = cli.pfnGetContactDisplayName( b, 0);
+
+ //otherwise just compare names
+ return _tcsicmp(namea, nameb);
+}
+
+/***************************************************************************************/
+
+static INT_PTR TrayIconProcessMessageStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnTrayIconProcessMessage( wParam, lParam ); }
+static INT_PTR TrayIconPauseAutoHideStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnTrayIconPauseAutoHide( wParam, lParam ); }
+static INT_PTR ShowHideStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnShowHide( wParam, lParam ); }
+static INT_PTR SetHideOfflineStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnSetHideOffline( wParam, lParam ); }
+static INT_PTR Docking_ProcessWindowMessageStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnDocking_ProcessWindowMessage( wParam, lParam ); }
+static INT_PTR HotkeysProcessMessageStub( WPARAM wParam, LPARAM lParam ) { return cli.pfnHotkeysProcessMessage( wParam, lParam ); }
+
+int LoadContactListModule2(void)
+{
+ HookEvent(ME_SYSTEM_MODULESLOADED, ContactListModulesLoaded);
+ HookEvent(ME_PROTO_ACCLISTCHANGED, ContactListAccountsChanged);
+ hContactSettingChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, ContactSettingChanged);
+ HookEvent(ME_DB_CONTACT_ADDED, ContactAdded);
+ HookEvent(ME_DB_CONTACT_DELETED, ContactDeleted);
+ hProtoAckHook = (HANDLE) HookEvent(ME_PROTO_ACK, ProtocolAck);
+ hContactDoubleClicked = CreateHookableEvent(ME_CLIST_DOUBLECLICKED);
+ hContactIconChangedEvent = CreateHookableEvent(ME_CLIST_CONTACTICONCHANGED);
+ CreateServiceFunction(MS_CLIST_CONTACTDOUBLECLICKED, ContactDoubleClicked);
+ CreateServiceFunction(MS_CLIST_CONTACTFILESDROPPED, ContactFilesDropped);
+ CreateServiceFunction(MS_CLIST_GETSTATUSMODEDESCRIPTION, GetStatusModeDescription);
+ CreateServiceFunction(MS_CLIST_GETCONTACTDISPLAYNAME, GetContactDisplayName);
+ CreateServiceFunction(MS_CLIST_INVALIDATEDISPLAYNAME, InvalidateDisplayName);
+ CreateServiceFunction(MS_CLIST_TRAYICONPROCESSMESSAGE, TrayIconProcessMessageStub );
+ CreateServiceFunction(MS_CLIST_PAUSEAUTOHIDE, TrayIconPauseAutoHideStub);
+ CreateServiceFunction(MS_CLIST_CONTACTSCOMPARE, CompareContacts);
+ CreateServiceFunction(MS_CLIST_CONTACTCHANGEGROUP, ContactChangeGroup);
+ CreateServiceFunction(MS_CLIST_SHOWHIDE, ShowHideStub);
+ CreateServiceFunction(MS_CLIST_SETHIDEOFFLINE, SetHideOfflineStub);
+ CreateServiceFunction(MS_CLIST_DOCKINGPROCESSMESSAGE, Docking_ProcessWindowMessageStub);
+ CreateServiceFunction(MS_CLIST_DOCKINGISDOCKED, Docking_IsDocked);
+ CreateServiceFunction(MS_CLIST_HOTKEYSPROCESSMESSAGE, HotkeysProcessMessageStub);
+ CreateServiceFunction(MS_CLIST_GETCONTACTICON, GetContactIcon);
+ MySetProcessWorkingSetSize = (BOOL(WINAPI *) (HANDLE, SIZE_T, SIZE_T)) GetProcAddress(GetModuleHandleA("kernel32"), "SetProcessWorkingSetSize");
+ InitDisplayNameCache();
+ InitCListEvents();
+ InitGroupServices();
+ cli.pfnInitTray();
+
+ hCListImages = ImageList_Create(16, 16, ILC_MASK | (IsWinVerXPPlus()? ILC_COLOR32 : ILC_COLOR16), 13, 0);
+ HookEvent(ME_SKIN_ICONSCHANGED, CListIconsChanged);
+ CreateServiceFunction(MS_CLIST_GETICONSIMAGELIST, GetIconsImageList);
+
+ ImageList_AddIcon_NotShared(hCListImages, MAKEINTRESOURCE(IDI_BLANK));
+
+ {
+ int i;
+ //now all core skin icons are loaded via icon lib. so lets release them
+ for (i = 0; i < SIZEOF(statusModeList); i++)
+ ImageList_AddIcon_IconLibLoaded(hCListImages, skinIconStatusList[i] );
+ }
+
+ //see IMAGE_GROUP... in clist.h if you add more images above here
+ ImageList_AddIcon_IconLibLoaded(hCListImages, SKINICON_OTHER_GROUPOPEN );
+ ImageList_AddIcon_IconLibLoaded(hCListImages, SKINICON_OTHER_GROUPSHUT );
+ return 0;
+}
+
+void UnloadContactListModule()
+{
+ if ( hCListImages ) {
+ //remove transitory contacts
+ HANDLE hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact != NULL) {
+ HANDLE hNext = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0))
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM) hContact, 0);
+ hContact = hNext;
+ }
+ ImageList_Destroy(hCListImages);
+ UnhookEvent(hProtoAckHook);
+ UninitCListEvents();
+ protoIconIndex.destroy();
+ DestroyHookableEvent(hContactDoubleClicked);
+ UnhookEvent(hContactSettingChanged);
+} }
diff --git a/src/modules/clist/clistsettings.cpp b/src/modules/clist/clistsettings.cpp
new file mode 100644
index 0000000000..df92386ded
--- /dev/null
+++ b/src/modules/clist/clistsettings.cpp
@@ -0,0 +1,331 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+SortedList* clistCache = NULL;
+
+static int compareContacts( ClcCacheEntryBase* p1, ClcCacheEntryBase* p2 )
+{
+ return ( char* )p1->hContact - ( char* )p2->hContact;
+}
+
+void InitDisplayNameCache(void)
+{
+ clistCache = List_Create( 0, 50 );
+ clistCache->sortFunc = ( FSortFunc )compareContacts;
+}
+
+void FreeDisplayNameCache(void)
+{
+ if ( clistCache != NULL ) {
+ int i;
+ for ( i = 0; i < clistCache->realCount; i++) {
+ cli.pfnFreeCacheItem(( ClcCacheEntryBase* )clistCache->items[i] );
+ mir_free( clistCache->items[i] );
+ }
+
+ List_Destroy( clistCache );
+ mir_free(clistCache);
+ clistCache = NULL;
+} }
+
+// default handlers for the cache item creation and destruction
+
+ClcCacheEntryBase* fnCreateCacheItem( HANDLE hContact )
+{
+ ClcCacheEntryBase* p = ( ClcCacheEntryBase* )mir_calloc( sizeof( ClcCacheEntryBase ));
+ if ( p == NULL )
+ return NULL;
+
+ p->hContact = hContact;
+ return p;
+}
+
+void fnCheckCacheItem( ClcCacheEntryBase* p )
+{
+ DBVARIANT dbv;
+ if ( p->group == NULL ) {
+ if ( !DBGetContactSettingTString( p->hContact, "CList", "Group", &dbv )) {
+ p->group = mir_tstrdup( dbv.ptszVal );
+ mir_free( dbv.ptszVal );
+ }
+ else p->group = mir_tstrdup( _T("") );
+ }
+
+ if ( p->isHidden == -1 )
+ p->isHidden = DBGetContactSettingByte( p->hContact, "CList", "Hidden", 0 );
+}
+
+void fnFreeCacheItem( ClcCacheEntryBase* p )
+{
+ if ( p->name ) { mir_free( p->name ); p->name = NULL; }
+ #if defined( _UNICODE )
+ if ( p->szName ) { mir_free( p->szName); p->szName = NULL; }
+ #endif
+ if ( p->group ) { mir_free( p->group ); p->group = NULL; }
+ p->isHidden = -1;
+}
+
+ClcCacheEntryBase* fnGetCacheEntry(HANDLE hContact)
+{
+ ClcCacheEntryBase* p;
+ int idx;
+ if ( !List_GetIndex( clistCache, &hContact, &idx )) {
+ if (( p = cli.pfnCreateCacheItem( hContact )) != NULL ) {
+ List_Insert( clistCache, p, idx );
+ cli.pfnInvalidateDisplayNameCacheEntry( p );
+ }
+ }
+ else p = ( ClcCacheEntryBase* )clistCache->items[idx];
+
+ cli.pfnCheckCacheItem( p );
+ return p;
+}
+
+void fnInvalidateDisplayNameCacheEntry(HANDLE hContact)
+{
+ if (hContact == INVALID_HANDLE_VALUE) {
+ FreeDisplayNameCache();
+ InitDisplayNameCache();
+ SendMessage(cli.hwndContactTree, CLM_AUTOREBUILD, 0, 0);
+ }
+ else {
+ int idx;
+ if ( List_GetIndex( clistCache, &hContact, &idx ))
+ cli.pfnFreeCacheItem(( ClcCacheEntryBase* )clistCache->items[idx] );
+} }
+
+TCHAR* fnGetContactDisplayName( HANDLE hContact, int mode )
+{
+ CONTACTINFO ci;
+ TCHAR *buffer;
+ ClcCacheEntryBase* cacheEntry = NULL;
+
+ if ( mode & GCDNF_NOCACHE )
+ mode &= ~GCDNF_NOCACHE;
+ else if ( mode != GCDNF_NOMYHANDLE) {
+ cacheEntry = cli.pfnGetCacheEntry( hContact );
+ if ( cacheEntry->name )
+ return cacheEntry->name;
+ }
+ ZeroMemory(&ci, sizeof(ci));
+ ci.cbSize = sizeof(ci);
+ ci.hContact = hContact;
+ if (ci.hContact == NULL)
+ ci.szProto = "ICQ";
+ ci.dwFlag = ((mode == GCDNF_NOMYHANDLE) ? CNF_DISPLAYNC : CNF_DISPLAY) | CNF_TCHAR;
+ if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
+ if (ci.type == CNFT_ASCIIZ) {
+ if (cacheEntry == NULL)
+ return ci.pszVal;
+
+ cacheEntry->name = ci.pszVal;
+ #if defined( _UNICODE )
+ cacheEntry->szName = mir_u2a( ci.pszVal );
+ #endif
+ return ci.pszVal;
+ }
+
+ if (ci.type == CNFT_DWORD) {
+ if (cacheEntry == NULL) {
+ buffer = (TCHAR*) mir_alloc(15 * sizeof( TCHAR ));
+ _ltot(ci.dVal, buffer, 10 );
+ return buffer;
+ }
+ else {
+ buffer = (TCHAR*) mir_alloc(15 * sizeof( TCHAR ));
+ _ltot(ci.dVal, buffer, 10 );
+ cacheEntry->name = buffer;
+ #if defined( _UNICODE )
+ cacheEntry->szName = mir_u2a( buffer );
+ #endif
+ return buffer;
+ } } }
+
+ CallContactService(hContact, PSS_GETINFO, SGIF_MINIMAL, 0);
+ buffer = TranslateT("(Unknown Contact)");
+ return ( cacheEntry == NULL ) ? mir_tstrdup( buffer ) : buffer;
+}
+
+INT_PTR GetContactDisplayName(WPARAM wParam, LPARAM lParam)
+{
+ CONTACTINFO ci;
+ ClcCacheEntryBase* cacheEntry = NULL;
+ char *buffer;
+ HANDLE hContact = (HANDLE)wParam;
+
+ if ( lParam & GCDNF_UNICODE )
+ return ( INT_PTR )cli.pfnGetContactDisplayName(hContact, lParam & ~GCDNF_UNICODE );
+
+ if ((int) lParam != GCDNF_NOMYHANDLE) {
+ cacheEntry = cli.pfnGetCacheEntry(hContact);
+ #if defined( _UNICODE )
+ if ( cacheEntry->szName )
+ return (INT_PTR)cacheEntry->szName;
+ #else
+ if ( cacheEntry->name )
+ return (INT_PTR)cacheEntry->name;
+ #endif
+ }
+ ZeroMemory(&ci, sizeof(ci));
+ ci.cbSize = sizeof(ci);
+ ci.hContact = hContact;
+ if (ci.hContact == NULL)
+ ci.szProto = "ICQ";
+ ci.dwFlag = ((lParam == GCDNF_NOMYHANDLE) ? CNF_DISPLAYNC : CNF_DISPLAY) | CNF_TCHAR;
+ if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM) & ci)) {
+ if (ci.type == CNFT_ASCIIZ) {
+ if (cacheEntry == NULL) {
+ #if defined( _UNICODE )
+ buffer = mir_u2a( ci.pszVal );
+ mir_free(ci.pszVal);
+ #else
+ buffer = ci.pszVal;
+ #endif
+ return (INT_PTR) buffer;
+ }
+ else {
+ cacheEntry->name = ci.pszVal;
+ #if defined( _UNICODE )
+ cacheEntry->szName = mir_u2a( ci.pszVal );
+ return (INT_PTR)cacheEntry->szName;
+ #else
+ return (INT_PTR)cacheEntry->name;
+ #endif
+ }
+ }
+ if (ci.type == CNFT_DWORD) {
+ if (cacheEntry == NULL) {
+ buffer = ( char* )mir_alloc(15);
+ _ltoa(ci.dVal, buffer, 10 );
+ return (INT_PTR) buffer;
+ }
+ else {
+ buffer = ( char* )mir_alloc(15);
+ _ltoa(ci.dVal, buffer, 10 );
+ #if defined( _UNICODE )
+ cacheEntry->szName = buffer;
+ cacheEntry->name = mir_a2u( buffer );
+ #else
+ cacheEntry->name = buffer;
+ #endif
+ return (INT_PTR) buffer;
+ } } }
+
+ CallContactService(hContact, PSS_GETINFO, SGIF_MINIMAL, 0);
+ buffer = Translate("(Unknown Contact)");
+ return (INT_PTR) buffer;
+}
+
+INT_PTR InvalidateDisplayName(WPARAM wParam, LPARAM)
+{
+ cli.pfnInvalidateDisplayNameCacheEntry((HANDLE)wParam);
+ return 0;
+}
+
+int ContactAdded(WPARAM wParam, LPARAM)
+{
+ cli.pfnChangeContactIcon((HANDLE)wParam, cli.pfnIconFromStatusMode((char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0), ID_STATUS_OFFLINE, NULL), 1);
+ cli.pfnSortContacts();
+ return 0;
+}
+
+int ContactDeleted(WPARAM wParam, LPARAM)
+{
+ CallService(MS_CLUI_CONTACTDELETED, wParam, 0);
+ return 0;
+}
+
+int ContactSettingChanged(WPARAM wParam, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam;
+ DBVARIANT dbv;
+ HANDLE hContact = (HANDLE)wParam;
+
+ // Early exit
+ if ( hContact == NULL)
+ return 0;
+
+ dbv.pszVal = NULL;
+ if (!DBGetContactSetting(hContact, "Protocol", "p", &dbv)) {
+ if (!strcmp(cws->szModule, dbv.pszVal)) {
+ cli.pfnInvalidateDisplayNameCacheEntry(hContact);
+ if (!strcmp(cws->szSetting, "UIN") || !strcmp(cws->szSetting, "Nick") || !strcmp(cws->szSetting, "FirstName")
+ || !strcmp(cws->szSetting, "LastName") || !strcmp(cws->szSetting, "e-mail")) {
+ CallService(MS_CLUI_CONTACTRENAMED, wParam, 0);
+ }
+ else if (!strcmp(cws->szSetting, "Status")) {
+ if (!DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) {
+ if (DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT)) {
+ // User's state is changing, and we are hideOffline-ing
+ if (cws->value.wVal == ID_STATUS_OFFLINE) {
+ cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(cws->szModule, cws->value.wVal, hContact), 0);
+ CallService(MS_CLUI_CONTACTDELETED, wParam, 0);
+ mir_free(dbv.pszVal);
+ return 0;
+ }
+ cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(cws->szModule, cws->value.wVal, hContact), 1);
+ }
+ cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(cws->szModule, cws->value.wVal, hContact), 0);
+ }
+ }
+ else {
+ mir_free(dbv.pszVal);
+ return 0;
+ }
+ cli.pfnSortContacts();
+ } }
+
+ if (!strcmp(cws->szModule, "CList")) {
+ if (!strcmp(cws->szSetting, "Hidden")) {
+ if (cws->value.type == DBVT_DELETED || cws->value.bVal == 0) {
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode(szProto, szProto == NULL ? ID_STATUS_OFFLINE : DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE), hContact), 1);
+ }
+ else
+ CallService(MS_CLUI_CONTACTDELETED, wParam, 0);
+ }
+ if (!strcmp(cws->szSetting, "MyHandle"))
+ cli.pfnInvalidateDisplayNameCacheEntry(hContact);
+ }
+
+ if (!strcmp(cws->szModule, "Protocol")) {
+ if (!strcmp(cws->szSetting, "p")) {
+ char *szProto;
+ if (cws->value.type == DBVT_DELETED)
+ szProto = NULL;
+ else
+ szProto = cws->value.pszVal;
+ cli.pfnChangeContactIcon(hContact,
+ cli.pfnIconFromStatusMode(szProto,
+ szProto == NULL ? ID_STATUS_OFFLINE : DBGetContactSettingWord(hContact, szProto, "Status",
+ ID_STATUS_OFFLINE), hContact), 0);
+ } }
+
+ // Clean up
+ if (dbv.pszVal)
+ mir_free(dbv.pszVal);
+
+ return 0;
+}
diff --git a/src/modules/clist/clisttray.cpp b/src/modules/clist/clisttray.cpp
new file mode 100644
index 0000000000..2292020c48
--- /dev/null
+++ b/src/modules/clist/clisttray.cpp
@@ -0,0 +1,980 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+#define TOOLTIP_TOLERANCE 5
+
+extern HIMAGELIST hCListImages;
+extern BOOL(WINAPI * MySetProcessWorkingSetSize) (HANDLE, SIZE_T, SIZE_T);
+
+int GetAverageMode(int* pNetProtoCount = NULL);
+
+static UINT WM_TASKBARCREATED;
+static UINT WM_TASKBARBUTTONCREATED;
+static BOOL mToolTipTrayTips = FALSE;
+static UINT_PTR cycleTimerId = 0;
+static int cycleStep = 0;
+static UINT_PTR RefreshTimerId=0; /////by FYR
+static CRITICAL_SECTION trayLockCS;
+
+// don't move to win2k.h, need new and old versions to work on 9x/2000/XP
+#define NIF_STATE 0x00000008
+#define NIF_INFO 0x00000010
+
+#define lock cli.pfnLockTray()
+#define ulock cli.pfnUnlockTray()
+
+#define initcheck if(!fTrayInited) return
+
+static BOOL fTrayInited=FALSE;
+
+static TCHAR* sttGetXStatus( const char* szProto )
+{
+ TCHAR* result = NULL;
+
+ if ( CallProtoService( szProto, PS_GETSTATUS, 0, 0 ) > ID_STATUS_OFFLINE ) {
+ char str[MAXMODULELABELLENGTH];
+ mir_snprintf( str, sizeof(str), "%s/GetXStatus", szProto );
+ if ( ServiceExists( str )) {
+ char* dbTitle = "XStatusName";
+ char* dbTitle2 = NULL;
+ int xstatus = CallProtoService( szProto, "/GetXStatus", ( WPARAM )&dbTitle, ( LPARAM )&dbTitle2 );
+ if ( dbTitle && xstatus ) {
+ DBVARIANT dbv={0};
+ if ( !DBGetContactSettingTString(NULL, szProto, dbTitle, &dbv )) {
+ if ( dbv.ptszVal[0] != 0 )
+ result = mir_tstrdup(dbv.ptszVal);
+ DBFreeVariant(&dbv);
+ } } } }
+
+ return result;
+}
+
+static HICON lastTaskBarIcon;
+static void SetTaskBarIcon(const HICON hIcon, const TCHAR *szNewTip)
+{
+ if (pTaskbarInterface)
+ {
+ wchar_t *szTip = mir_t2u(szNewTip);
+ pTaskbarInterface->SetOverlayIcon(cli.hwndContactList, hIcon, szTip);
+ mir_free(szTip);
+ lastTaskBarIcon = hIcon;
+ }
+}
+
+TCHAR* fnTrayIconMakeTooltip( const TCHAR *szPrefix, const char *szProto )
+{
+ TCHAR *szStatus, *szSeparator;
+ TCHAR *ProtoXStatus=NULL;
+ int t;
+ PROTOACCOUNT* pa;
+ initcheck NULL;
+ lock;
+ if ( !mToolTipTrayTips )
+ szSeparator = (IsWinVerMEPlus()) ? szSeparator = _T("\n") : _T(" | ");
+ else
+ szSeparator = _T("\n");
+
+ if (szProto == NULL) {
+ if (accounts.getCount() == 0) {
+ ulock;
+ return NULL;
+ }
+ if (accounts.getCount() == 1) {
+ ulock;
+ return cli.pfnTrayIconMakeTooltip(szPrefix, accounts[0]->szModuleName);
+ }
+
+ if (szPrefix && szPrefix[0]) {
+ lstrcpyn(cli.szTip, szPrefix, MAX_TIP_SIZE);
+ if (!DBGetContactSettingByte(NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT))
+ { ulock; return cli.szTip; }
+ }
+ else cli.szTip[0] = '\0';
+ cli.szTip[ MAX_TIP_SIZE-1 ] = '\0';
+
+ for ( t = 0; t < accounts.getCount(); t++ ) {
+ int i = cli.pfnGetAccountIndexByPos( t );
+ if ( i == -1 )
+ continue;
+
+ pa = accounts[i];
+ if ( !cli.pfnGetProtocolVisibility( pa->szModuleName ))
+ continue;
+
+ szStatus = cli.pfnGetStatusModeDescription( CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0), 0);
+ if ( !szStatus )
+ continue;
+
+ ProtoXStatus = sttGetXStatus( pa->szModuleName );
+
+ if ( mToolTipTrayTips ) {
+ TCHAR tipline[256];
+ mir_sntprintf(tipline, SIZEOF(tipline), _T("<b>%-12.12s</b>\t%s"), pa->tszAccountName, szStatus);
+ if ( cli.szTip[0] )
+ _tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip));
+ _tcsncat(cli.szTip, tipline, MAX_TIP_SIZE - _tcslen(cli.szTip));
+ if (ProtoXStatus) {
+ mir_sntprintf(tipline, SIZEOF(tipline), _T("%-24.24s\n"), ProtoXStatus);
+ if ( cli.szTip[0] )
+ _tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip));
+ _tcsncat(cli.szTip, tipline, MAX_TIP_SIZE - _tcslen(cli.szTip));
+ }
+ }
+ else {
+ if (cli.szTip[0])
+ _tcsncat(cli.szTip, szSeparator, MAX_TIP_SIZE - _tcslen(cli.szTip));
+
+ _tcsncat(cli.szTip, pa->tszAccountName, MAX_TIP_SIZE - _tcslen(cli.szTip));
+ _tcsncat(cli.szTip, _T(" "), MAX_TIP_SIZE - _tcslen(cli.szTip));
+ _tcsncat(cli.szTip, szStatus, MAX_TIP_SIZE - _tcslen(cli.szTip));
+ }
+ mir_free( ProtoXStatus );
+ }
+ }
+ else {
+ if (( pa = Proto_GetAccount( szProto )) != NULL ) {
+ ProtoXStatus = sttGetXStatus( szProto );
+ szStatus = cli.pfnGetStatusModeDescription(CallProtoService(szProto, PS_GETSTATUS, 0, 0), 0);
+ if ( szPrefix && szPrefix[0] ) {
+ if ( DBGetContactSettingByte( NULL, "CList", "AlwaysStatus", SETTING_ALWAYSSTATUS_DEFAULT )) {
+ if ( mToolTipTrayTips ) {
+ if ( ProtoXStatus )
+ mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s<b>%-12.12s</b>\t%s%s%-24.24s"), szPrefix, szSeparator, pa->tszAccountName, szStatus,szSeparator,ProtoXStatus);
+ else
+ mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s<b>%-12.12s</b>\t%s"), szPrefix, szSeparator, pa->tszAccountName, szStatus);
+ }
+ else mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s%s %s"), szPrefix, szSeparator, pa->tszAccountName, szStatus);
+ }
+ else lstrcpyn(cli.szTip, szPrefix, MAX_TIP_SIZE);
+ }
+ else {
+ if ( mToolTipTrayTips ) {
+ if ( ProtoXStatus )
+ mir_sntprintf( cli.szTip, MAX_TIP_SIZE, _T("<b>%-12.12s</b>\t%s\n%-24.24s"), pa->tszAccountName, szStatus,ProtoXStatus);
+ else
+ mir_sntprintf( cli.szTip, MAX_TIP_SIZE, _T("<b>%-12.12s</b>\t%s"), pa->tszAccountName, szStatus);
+ }
+ else mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s %s"), pa->tszAccountName, szStatus);
+ }
+ mir_free(ProtoXStatus);
+ } }
+
+ ulock;
+ return cli.szTip;
+}
+
+int fnTrayIconAdd(HWND hwnd, const char *szProto, const char *szIconProto, int status)
+{
+ NOTIFYICONDATA nid = { 0 };
+ int i;
+ initcheck 0;
+ lock;
+ for (i = 0; i < cli.trayIconCount; i++)
+ if (cli.trayIcon[i].id == 0)
+ break;
+
+ cli.trayIcon[i].id = TRAYICON_ID_BASE + i;
+ cli.trayIcon[i].szProto = (char *) szProto;
+ cli.trayIcon[i].hBaseIcon = cli.pfnGetIconFromStatusMode( NULL, szIconProto ? szIconProto : cli.trayIcon[i].szProto, status );
+
+ nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
+ nid.hWnd = hwnd;
+ nid.uID = cli.trayIcon[i].id;
+ nid.uFlags = mToolTipTrayTips ? NIF_ICON | NIF_MESSAGE : NIF_ICON | NIF_MESSAGE | NIF_TIP;
+ nid.uCallbackMessage = TIM_CALLBACK;
+ nid.hIcon = cli.trayIcon[i].hBaseIcon;
+
+ if (cli.shellVersion >= 5)
+ nid.uFlags |= NIF_INFO;
+
+ cli.pfnTrayIconMakeTooltip( NULL, cli.trayIcon[i].szProto );
+ if ( !mToolTipTrayTips )
+ lstrcpyn( nid.szTip, cli.szTip, SIZEOF( nid.szTip ));
+ cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip );
+
+ Shell_NotifyIcon(NIM_ADD, &nid);
+ cli.trayIcon[i].isBase = 1;
+
+ if (cli.trayIconCount == 1)
+ SetTaskBarIcon(cli.trayIcon[0].hBaseIcon, cli.szTip);
+
+ ulock; return i;
+}
+
+void fnTrayIconRemove(HWND hwnd, const char *szProto)
+{
+ int i;
+ initcheck;
+ lock;
+ for ( i = 0; i < cli.trayIconCount; i++ ) {
+ struct trayIconInfo_t* pii = &cli.trayIcon[i];
+ if ( pii->id != 0 && !lstrcmpA( szProto, pii->szProto )) {
+ NOTIFYICONDATA nid = { 0 };
+ nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
+ nid.hWnd = hwnd;
+ nid.uID = pii->id;
+ Shell_NotifyIcon(NIM_DELETE, &nid);
+
+ DestroyIcon(pii->hBaseIcon);
+ mir_free(pii->ptszToolTip); pii->ptszToolTip = NULL;
+ pii->id = 0;
+ break;
+ } }
+
+ if (cli.trayIconCount == 1)
+ SetTaskBarIcon(NULL, NULL);
+
+ ulock;
+}
+
+int fnTrayIconInit(HWND hwnd)
+{
+ int netProtoCount = 0;
+ initcheck 0;
+ lock;
+
+ int averageMode = GetAverageMode(&netProtoCount);
+ mToolTipTrayTips = ServiceExists("mToolTip/ShowTip") != 0;
+
+ if ( cli.cycleTimerId ) {
+ KillTimer(NULL, cli.cycleTimerId);
+ cli.cycleTimerId = 0;
+ }
+
+ cli.trayIconCount = 1;
+
+ if (netProtoCount)
+ {
+ cli.trayIcon = (trayIconInfo_t *) mir_calloc(sizeof(trayIconInfo_t) * accounts.getCount());
+
+ int trayIconSetting = DBGetContactSettingByte(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT);
+
+ if (trayIconSetting == SETTING_TRAYICON_SINGLE)
+ {
+ DBVARIANT dbv = { DBVT_DELETED };
+ char *szProto;
+ if (!DBGetContactSettingString(NULL, "CList", "PrimaryStatus", &dbv)
+ && (averageMode < 0 || DBGetContactSettingByte(NULL, "CList", "AlwaysPrimary", 0) ))
+ szProto = dbv.pszVal;
+ else
+ szProto = NULL;
+
+ cli.pfnTrayIconAdd(hwnd, NULL, szProto, szProto ? CallProtoService(szProto, PS_GETSTATUS, 0, 0) : CallService(MS_CLIST_GETSTATUSMODE, 0, 0));
+ DBFreeVariant(&dbv);
+ }
+ else if (trayIconSetting == SETTING_TRAYICON_MULTI &&
+ (averageMode < 0 || DBGetContactSettingByte(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT )))
+ {
+ cli.trayIconCount = netProtoCount;
+ for (int i = 0; i < accounts.getCount(); ++i)
+ {
+ int j = cli.pfnGetAccountIndexByPos(i);
+ if (j >= 0)
+ {
+ PROTOACCOUNT* pa = accounts[j];
+ if (cli.pfnGetProtocolVisibility(pa->szModuleName))
+ cli.pfnTrayIconAdd(hwnd, pa->szModuleName, NULL, CallProtoService(pa->szModuleName, PS_GETSTATUS, 0, 0));
+ }
+ }
+ }
+ else
+ {
+ cli.pfnTrayIconAdd(hwnd, NULL, NULL, averageMode);
+
+ if (trayIconSetting == SETTING_TRAYICON_CYCLE && averageMode < 0)
+ cli.cycleTimerId = SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 1000, cli.pfnTrayCycleTimerProc);
+ }
+ }
+ else
+ {
+ cli.trayIcon = (trayIconInfo_t *) mir_calloc(sizeof(trayIconInfo_t));
+ cli.pfnTrayIconAdd(hwnd, NULL, NULL, CallService(MS_CLIST_GETSTATUSMODE, 0, 0));
+ }
+
+ ulock;
+ return 0;
+}
+
+int fnTrayIconDestroy(HWND hwnd)
+{
+ NOTIFYICONDATA nid = { 0 };
+ int i;
+ initcheck 0;
+ lock;
+
+ if (cli.trayIconCount == 1)
+ SetTaskBarIcon(NULL, NULL);
+
+ nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
+ nid.hWnd = hwnd;
+ for ( i = 0; i < cli.trayIconCount; i++ ) {
+ if ( cli.trayIcon[i].id == 0 )
+ continue;
+ nid.uID = cli.trayIcon[i].id;
+ Shell_NotifyIcon( NIM_DELETE, &nid );
+ DestroyIcon( cli.trayIcon[i].hBaseIcon );
+ mir_free( cli.trayIcon[i].ptszToolTip );
+ }
+ mir_free(cli.trayIcon);
+ cli.trayIcon = NULL;
+ cli.trayIconCount = 0;
+
+ ulock;
+ return 0;
+}
+
+//called when Explorer crashes and the taskbar is remade
+void fnTrayIconTaskbarCreated(HWND hwnd)
+{
+ initcheck;
+ cli.pfnTrayIconDestroy(hwnd);
+ cli.pfnTrayIconInit(hwnd);
+}
+
+static VOID CALLBACK RefreshTimerProc(HWND, UINT, UINT_PTR, DWORD)
+{
+ int i;
+ if ( RefreshTimerId ) {
+ KillTimer(NULL,RefreshTimerId);
+ RefreshTimerId=0;
+ }
+ for (i=0; i < accounts.getCount(); i++) {
+ cli.pfnTrayIconUpdateBase( accounts[i]->szModuleName );
+ }
+}
+
+int fnTrayIconUpdate(HICON hNewIcon, const TCHAR *szNewTip, const char *szPreferredProto, int isBase)
+{
+ NOTIFYICONDATA nid = { 0 };
+ int i;
+
+ initcheck -1;
+ lock;
+ nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATA_V1_SIZE;
+ nid.hWnd = cli.hwndContactList;
+ nid.uFlags = mToolTipTrayTips ? NIF_ICON : NIF_ICON | NIF_TIP;
+ nid.hIcon = hNewIcon;
+ if (!hNewIcon)
+ { ulock; return -1; }
+
+ for (i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+ if (lstrcmpA(cli.trayIcon[i].szProto, szPreferredProto))
+ continue;
+
+ nid.uID = cli.trayIcon[i].id;
+ cli.pfnTrayIconMakeTooltip(szNewTip, cli.trayIcon[i].szProto);
+ mir_free( cli.trayIcon[i].ptszToolTip );
+ cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip );
+ if (!mToolTipTrayTips)
+ lstrcpyn(nid.szTip, cli.szTip, SIZEOF(nid.szTip));
+ Shell_NotifyIcon(NIM_MODIFY, &nid);
+
+ if (cli.trayIconCount == 1)
+ SetTaskBarIcon(hNewIcon, cli.szTip);
+ else
+ SetTaskBarIcon(NULL, NULL);
+
+ cli.trayIcon[i].isBase = isBase;
+ { ulock; return i; }
+ }
+
+ //if there wasn't a suitable icon, change all the icons
+ {
+ for (i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+ nid.uID = cli.trayIcon[i].id;
+
+ cli.pfnTrayIconMakeTooltip(szNewTip, cli.trayIcon[i].szProto);
+ mir_free( cli.trayIcon[i].ptszToolTip );
+ cli.trayIcon[i].ptszToolTip = mir_tstrdup( cli.szTip );
+ if(!mToolTipTrayTips)
+ lstrcpyn(nid.szTip, cli.szTip, SIZEOF(nid.szTip));
+ Shell_NotifyIcon(NIM_MODIFY, &nid);
+
+ if (cli.trayIconCount == 1)
+ SetTaskBarIcon(hNewIcon, cli.szTip);
+ else
+ SetTaskBarIcon(NULL, NULL);
+
+ cli.trayIcon[i].isBase = isBase;
+ if (DBGetContactSettingByte(NULL,"CList","TrayIcon",SETTING_TRAYICON_DEFAULT) == SETTING_TRAYICON_MULTI)
+ {
+ DWORD time1=DBGetContactSettingWord(NULL,"CList","CycleTime",SETTING_CYCLETIME_DEFAULT)*200;
+ DWORD time2=DBGetContactSettingWord(NULL,"CList","IconFlashTime",550)+1000;
+ DWORD time=max(max(2000,time1),time2);
+ if(RefreshTimerId) {KillTimer(NULL,RefreshTimerId); RefreshTimerId=0;}
+ RefreshTimerId=SetTimer(NULL,0,time,RefreshTimerProc); // if unknown base was changed - than show preffered proto icon for 2 sec and reset it to original one after timeout
+ }
+ { ulock; return i; }
+ }
+ }
+ { ulock; return -1; }
+}
+
+int fnTrayIconSetBaseInfo(HICON hIcon, const char *szPreferredProto)
+{
+ int i;
+ initcheck -1;
+ lock;
+ if (szPreferredProto)
+ {
+ for (i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+ if (lstrcmpA(cli.trayIcon[i].szProto, szPreferredProto))
+ continue;
+
+ DestroyIcon(cli.trayIcon[i].hBaseIcon);
+ cli.trayIcon[i].hBaseIcon = hIcon;
+ ulock; return i;
+ }
+ if ((cli.pfnGetProtocolVisibility(szPreferredProto))
+ && (GetAverageMode()==-1)
+ && (DBGetContactSettingByte(NULL,"CList","TrayIcon",SETTING_TRAYICON_DEFAULT)==SETTING_TRAYICON_MULTI)
+ && !(DBGetContactSettingByte(NULL,"CList","AlwaysMulti",SETTING_ALWAYSMULTI_DEFAULT)))
+ goto LBL_Error;
+ }
+
+ //if there wasn't a specific icon, there will only be one suitable
+ for (i = 0; i < cli.trayIconCount; i++) {
+ if (cli.trayIcon[i].id == 0)
+ continue;
+
+ DestroyIcon(cli.trayIcon[i].hBaseIcon);
+ cli.trayIcon[i].hBaseIcon = hIcon;
+ ulock; return i;
+ }
+
+LBL_Error:
+ DestroyIcon(hIcon);
+ ulock; return -1;
+}
+
+void fnTrayIconUpdateWithImageList(int iImage, const TCHAR *szNewTip, char *szPreferredProto)
+{
+ HICON hIcon = ImageList_GetIcon(hCListImages, iImage, ILD_NORMAL);
+ cli.pfnTrayIconUpdate(hIcon, szNewTip, szPreferredProto, 0);
+ DestroyIcon(hIcon);
+}
+
+VOID CALLBACK fnTrayCycleTimerProc(HWND, UINT, UINT_PTR, DWORD)
+{
+ initcheck;
+ lock;
+
+ int i;
+ for (i = accounts.getCount() + 1; --i;) {
+ cycleStep = (cycleStep + 1) % accounts.getCount();
+ if ( cli.pfnGetProtocolVisibility( accounts[cycleStep]->szModuleName ))
+ break;
+ }
+
+ if (i)
+ {
+ DestroyIcon(cli.trayIcon[0].hBaseIcon);
+ cli.trayIcon[0].hBaseIcon = cli.pfnGetIconFromStatusMode(NULL, accounts[cycleStep]->szModuleName,
+ CallProtoService( accounts[cycleStep]->szModuleName, PS_GETSTATUS, 0, 0 ));
+ if (cli.trayIcon[0].isBase)
+ cli.pfnTrayIconUpdate(cli.trayIcon[0].hBaseIcon, NULL, NULL, 1);
+ }
+
+ ulock;
+}
+
+void fnTrayIconUpdateBase(const char *szChangedProto)
+{
+ if ( !cli.pfnGetProtocolVisibility( szChangedProto )) return;
+
+ int i, netProtoCount, changed = -1;
+ HWND hwnd = cli.hwndContactList;
+ initcheck;
+ lock;
+ int averageMode = GetAverageMode(&netProtoCount);
+
+ if (cli.cycleTimerId) {
+ KillTimer(NULL, cli.cycleTimerId);
+ cli.cycleTimerId = 0;
+ }
+
+ for (i = 0; i < accounts.getCount(); i++) {
+ if (!lstrcmpA(szChangedProto, accounts[i]->szModuleName ))
+ cycleStep = i - 1;
+ }
+
+ if (netProtoCount > 0)
+ {
+ int trayIconSetting = DBGetContactSettingByte(NULL, "CList", "TrayIcon", SETTING_TRAYICON_DEFAULT);
+
+ if (averageMode > 0) {
+ if (trayIconSetting == SETTING_TRAYICON_MULTI) {
+ if (DBGetContactSettingByte(NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT))
+ //changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode((char*)szChangedProto, NULL, averageMode), (char*)szChangedProto);
+ changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto );
+ else if (cli.trayIcon && cli.trayIcon[0].szProto != NULL) {
+ cli.pfnTrayIconDestroy(hwnd);
+ cli.pfnTrayIconInit(hwnd);
+ }
+ else
+ changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL );
+ }
+ else
+ changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode(NULL, NULL, averageMode), NULL);
+ }
+ else {
+ switch (trayIconSetting) {
+ case SETTING_TRAYICON_SINGLE:
+ {
+ DBVARIANT dbv = { DBVT_DELETED };
+ char *szProto;
+ if (DBGetContactSettingString(NULL, "CList", "PrimaryStatus", &dbv))
+ szProto = NULL;
+ else
+ szProto = dbv.pszVal;
+ changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szProto, szProto ? CallProtoService(szProto, PS_GETSTATUS, 0,0) : CallService(MS_CLIST_GETSTATUSMODE, 0, 0)), szProto );
+ DBFreeVariant(&dbv);
+ break;
+ }
+ case SETTING_TRAYICON_CYCLE:
+ cli.cycleTimerId =
+ SetTimer(NULL, 0, DBGetContactSettingWord(NULL, "CList", "CycleTime", SETTING_CYCLETIME_DEFAULT) * 1000, cli.pfnTrayCycleTimerProc);
+ changed =
+ cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon
+ (hCListImages, cli.pfnIconFromStatusMode(szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0), NULL),
+ ILD_NORMAL), NULL);
+ break;
+ case SETTING_TRAYICON_MULTI:
+ if (!cli.trayIcon) {
+ cli.pfnTrayIconRemove(NULL, NULL);
+ }
+ else if ((cli.trayIconCount > 1 || netProtoCount == 1) || DBGetContactSettingByte( NULL, "CList", "AlwaysMulti", SETTING_ALWAYSMULTI_DEFAULT ))
+ changed = cli.pfnTrayIconSetBaseInfo( cli.pfnGetIconFromStatusMode( NULL, szChangedProto, CallProtoService(szChangedProto, PS_GETSTATUS, 0, 0)), (char*)szChangedProto );
+ else {
+ cli.pfnTrayIconDestroy(hwnd);
+ cli.pfnTrayIconInit(hwnd);
+ }
+ break;
+ }
+ }
+ }
+ else
+ changed = cli.pfnTrayIconSetBaseInfo(ImageList_GetIcon(hCListImages, cli.pfnIconFromStatusMode(NULL, averageMode, NULL), ILD_NORMAL), NULL);
+
+ if (changed != -1 && cli.trayIcon[changed].isBase)
+ cli.pfnTrayIconUpdate(cli.trayIcon[changed].hBaseIcon, NULL, cli.trayIcon[changed].szProto, 1);
+ ulock;
+}
+
+void fnTrayIconSetToBase(char *szPreferredProto)
+{
+ int i;
+ initcheck;
+ lock;
+
+ for (i = 0; i < cli.trayIconCount; i++) {
+ if ( cli.trayIcon[i].id == 0 )
+ continue;
+ if ( lstrcmpA( cli.trayIcon[i].szProto, szPreferredProto ))
+ continue;
+ cli.pfnTrayIconUpdate( cli.trayIcon[i].hBaseIcon, NULL, szPreferredProto, 1);
+ ulock; return;
+ }
+
+ //if there wasn't a specific icon, there will only be one suitable
+ for ( i = 0; i < cli.trayIconCount; i++) {
+ if ( cli.trayIcon[i].id == 0 )
+ continue;
+ cli.pfnTrayIconUpdate( cli.trayIcon[i].hBaseIcon, NULL, szPreferredProto, 1);
+ ulock; return;
+ }
+ ulock; return;
+}
+
+void fnTrayIconIconsChanged(void)
+{
+ initcheck;
+ lock;
+ cli.pfnTrayIconDestroy(cli.hwndContactList);
+ cli.pfnTrayIconInit(cli.hwndContactList);
+ ulock;
+}
+
+static UINT_PTR autoHideTimerId;
+static VOID CALLBACK TrayIconAutoHideTimer(HWND hwnd, UINT, UINT_PTR idEvent, DWORD)
+{
+ HWND hwndClui;
+ initcheck;
+ lock;
+ KillTimer(hwnd, idEvent);
+ hwndClui = cli.hwndContactList;
+ if (GetActiveWindow() != hwndClui) {
+ ShowWindow(hwndClui, SW_HIDE);
+ if (MySetProcessWorkingSetSize != NULL)
+ MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
+ }
+ ulock; return;
+}
+
+int fnTrayIconPauseAutoHide(WPARAM, LPARAM)
+{
+ initcheck 0;
+ lock;
+ if (DBGetContactSettingByte(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) {
+ if ( GetActiveWindow() != cli.hwndContactList ) {
+ KillTimer(NULL, autoHideTimerId);
+ autoHideTimerId = SetTimer(NULL, 0, 1000 * DBGetContactSettingWord(NULL, "CList", "HideTime", SETTING_HIDETIME_DEFAULT), TrayIconAutoHideTimer);
+ }
+ }
+ ulock; return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// processes tray icon's messages
+
+static BYTE s_LastHoverIconID = 0;
+static BOOL g_trayTooltipActive = FALSE;
+static POINT tray_hover_pos = {0};
+
+static void CALLBACK TrayHideToolTipTimerProc(HWND hwnd, UINT, UINT_PTR, DWORD)
+{
+ if ( g_trayTooltipActive ) {
+ POINT pt;
+ GetCursorPos(&pt);
+ if ( abs(pt.x - tray_hover_pos.x) > TOOLTIP_TOLERANCE || abs(pt.y - tray_hover_pos.y) > TOOLTIP_TOLERANCE ) {
+ CallService("mToolTip/HideTip", 0, 0);
+ g_trayTooltipActive = FALSE;
+ KillTimer( hwnd, TIMERID_TRAYHOVER_2 );
+ }
+ }
+ else KillTimer( hwnd, TIMERID_TRAYHOVER_2 );
+}
+
+static void CALLBACK TrayToolTipTimerProc(HWND hwnd, UINT, UINT_PTR id, DWORD)
+{
+ if ( !g_trayTooltipActive && !cli.bTrayMenuOnScreen ) {
+ CLCINFOTIP ti = {0};
+ POINT pt;
+ GetCursorPos( &pt );
+ if ( abs(pt.x - tray_hover_pos.x) <= TOOLTIP_TOLERANCE && abs(pt.y - tray_hover_pos.y) <= TOOLTIP_TOLERANCE ) {
+ TCHAR* szTipCur = cli.szTip;
+ {
+ int n = s_LastHoverIconID-100;
+ if ( n >= 0 && n < cli.trayIconCount )
+ szTipCur = cli.trayIcon[n].ptszToolTip;
+ }
+ ti.rcItem.left = pt.x - 10;
+ ti.rcItem.right = pt.x + 10;
+ ti.rcItem.top = pt.y - 10;
+ ti.rcItem.bottom = pt.y + 10;
+ ti.cbSize = sizeof( ti );
+ ti.isTreeFocused = GetFocus() == cli.hwndContactList ? 1 : 0;
+ #if defined( _UNICODE )
+ if (CallService( "mToolTip/ShowTipW", (WPARAM)szTipCur, (LPARAM)&ti ) == CALLSERVICE_NOTFOUND)
+ {
+ char* p = mir_u2a( szTipCur );
+ CallService( "mToolTip/ShowTip", (WPARAM)p, (LPARAM)&ti );
+ mir_free( p );
+ }
+ #else
+ CallService( "mToolTip/ShowTip", (WPARAM)szTipCur, (LPARAM)&ti );
+ #endif
+ GetCursorPos( &tray_hover_pos );
+ SetTimer( cli.hwndContactList, TIMERID_TRAYHOVER_2, 600, TrayHideToolTipTimerProc );
+ g_trayTooltipActive = TRUE;
+ } }
+
+ KillTimer(hwnd, id);
+}
+
+INT_PTR fnTrayIconProcessMessage(WPARAM wParam, LPARAM lParam)
+{
+ MSG *msg = (MSG *) wParam;
+ switch (msg->message) {
+ case WM_CREATE: {
+ WM_TASKBARCREATED = RegisterWindowMessage( _T("TaskbarCreated"));
+ WM_TASKBARBUTTONCREATED = RegisterWindowMessage( _T("TaskbarButtonCreated"));
+ PostMessage(msg->hwnd, TIM_CREATE, 0, 0);
+ break;
+ }
+ case TIM_CREATE:
+ cli.pfnTrayIconInit(msg->hwnd);
+ break;
+
+ case WM_ACTIVATE:
+ if (DBGetContactSettingByte(NULL, "CList", "AutoHide", SETTING_AUTOHIDE_DEFAULT)) {
+ if (LOWORD(msg->wParam) == WA_INACTIVE)
+ autoHideTimerId = SetTimer(NULL, 0, 1000 * DBGetContactSettingWord(NULL, "CList", "HideTime", SETTING_HIDETIME_DEFAULT), TrayIconAutoHideTimer);
+ else
+ KillTimer(NULL, autoHideTimerId);
+ }
+ break;
+
+ case WM_DESTROY:
+ cli.pfnTrayIconDestroy(msg->hwnd);
+ cli.pfnUninitTray();
+ break;
+
+ case TIM_CALLBACK:
+ if ( msg->lParam == WM_RBUTTONDOWN || msg->lParam == WM_LBUTTONDOWN || msg->lParam == WM_RBUTTONDOWN && g_trayTooltipActive ) {
+ CallService("mToolTip/HideTip", 0, 0);
+ g_trayTooltipActive = FALSE;
+ }
+
+ if ( msg->lParam == WM_MBUTTONUP )
+ cli.pfnShowHide(0, 0);
+ else if (msg->lParam == (DBGetContactSettingByte(NULL, "CList", "Tray1Click", SETTING_TRAY1CLICK_DEFAULT) ? WM_LBUTTONUP : WM_LBUTTONDBLCLK)) {
+ if ((GetAsyncKeyState(VK_CONTROL) & 0x8000))
+ {
+ POINT pt;
+ HMENU hMenu = (HMENU)CallService(MS_CLIST_MENUGETSTATUS, 0, 0);
+
+ for (int i = 0; i < cli.trayIconCount; ++i)
+ {
+ if ((unsigned)cli.trayIcon[i].id == msg->wParam)
+ {
+ if (!cli.trayIcon[i].szProto) break;
+
+ int ind = 0;
+ for (int j = 0; j < accounts.getCount(); ++j)
+ {
+ int k = cli.pfnGetAccountIndexByPos(j);
+ if (k >= 0)
+ {
+ if (!strcmp(cli.trayIcon[i].szProto, accounts[k]->szModuleName))
+ {
+ HMENU hm = GetSubMenu(hMenu, ind);
+ if (hm) hMenu = hm;
+ break;
+ }
+
+ if (cli.pfnGetProtocolVisibility(accounts[k]->szModuleName))
+ ++ind;
+ }
+ }
+ break;
+ }
+ }
+
+ SetForegroundWindow(msg->hwnd);
+ SetFocus(msg->hwnd);
+ GetCursorPos(&pt);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, msg->hwnd, NULL);
+ }
+ else if (cli.pfnEventsProcessTrayDoubleClick(msg->wParam))
+ cli.pfnShowHide(0, 0);
+ }
+ else if (msg->lParam == WM_RBUTTONUP) {
+ MENUITEMINFO mi;
+ POINT pt;
+ HMENU hMainMenu = LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT));
+ HMENU hMenu = GetSubMenu(hMainMenu, 0);
+ CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) hMenu, 0);
+
+ ZeroMemory(&mi, sizeof(mi));
+ mi.cbSize = MENUITEMINFO_V4_SIZE;
+ mi.fMask = MIIM_SUBMENU | MIIM_TYPE;
+ mi.fType = MFT_STRING;
+ mi.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETMAIN, 0, 0);
+ mi.dwTypeData = TranslateT("&Main Menu");
+ InsertMenuItem(hMenu, 1, TRUE, &mi);
+ mi.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0);
+ mi.dwTypeData = TranslateT("&Status");
+ InsertMenuItem(hMenu, 2, TRUE, &mi);
+ SetMenuDefaultItem(hMenu, ID_TRAY_HIDE, FALSE);
+
+ SetForegroundWindow(msg->hwnd);
+ SetFocus(msg->hwnd);
+ GetCursorPos(&pt);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, msg->hwnd, NULL);
+
+ RemoveMenu(hMenu, 1, MF_BYPOSITION);
+ RemoveMenu(hMenu, 1, MF_BYPOSITION);
+ DestroyMenu(hMainMenu);
+ }
+ else if ( msg->lParam == WM_MOUSEMOVE ) {
+ s_LastHoverIconID = msg->wParam;
+ if ( g_trayTooltipActive ) {
+ POINT pt;
+ GetCursorPos( &pt );
+ if ( abs(pt.x - tray_hover_pos.x) > TOOLTIP_TOLERANCE || abs(pt.y - tray_hover_pos.y) > TOOLTIP_TOLERANCE ) {
+ CallService("mToolTip/HideTip", 0, 0);
+ g_trayTooltipActive = FALSE;
+ ReleaseCapture();
+ }
+ }
+ else {
+ GetCursorPos(&tray_hover_pos);
+ SetTimer(cli.hwndContactList, TIMERID_TRAYHOVER, 600, TrayToolTipTimerProc);
+ }
+ break;
+ }
+
+ *((LRESULT *) lParam) = 0;
+ return TRUE;
+
+ default:
+ if (msg->message == WM_TASKBARCREATED) {
+ cli.pfnTrayIconTaskbarCreated(msg->hwnd);
+ *((LRESULT *) lParam) = 0;
+ return TRUE;
+ }
+ else if (msg->message == WM_TASKBARBUTTONCREATED) {
+ SetTaskBarIcon(lastTaskBarIcon, NULL);
+ *((LRESULT *) lParam) = 0;
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// processes tray icon's notifications
+
+int fnCListTrayNotify( MIRANDASYSTRAYNOTIFY* msn )
+{
+ UINT iconId = 0;
+
+ if ( msn == NULL )
+ return 1;
+
+ if ( msn->cbSize != sizeof(MIRANDASYSTRAYNOTIFY) || msn->szInfo == NULL || msn->szInfoTitle == NULL )
+ return 1;
+
+ if ( cli.trayIcon == NULL )
+ return 2;
+
+ if ( msn->szProto ) {
+ int j;
+ for ( j = 0; j < cli.trayIconCount; j++ ) {
+ if ( cli.trayIcon[j].szProto != NULL ) {
+ if ( !strcmp( msn->szProto, cli.trayIcon[j].szProto )) {
+ iconId = cli.trayIcon[j].id;
+ break;
+ }
+ }
+ else if ( cli.trayIcon[j].isBase ) {
+ iconId = cli.trayIcon[j].id;
+ break;
+ } }
+ }
+ else iconId = cli.trayIcon[0].id;
+
+#if defined(_UNICODE)
+ if ( msn->dwInfoFlags & NIIF_INTERN_UNICODE ) {
+ NOTIFYICONDATAW nid = {0};
+ nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATAW_V1_SIZE;
+ nid.hWnd = cli.hwndContactList;
+ nid.uID = iconId;
+ nid.uFlags = NIF_INFO;
+ lstrcpynW( nid.szInfo, msn->tszInfo, SIZEOF( nid.szInfo ));
+ lstrcpynW( nid.szInfoTitle, msn->tszInfoTitle, SIZEOF( nid.szInfoTitle ));
+ nid.szInfo[ SIZEOF(nid.szInfo)-1 ] = 0;
+ nid.szInfoTitle[ SIZEOF(nid.szInfoTitle)-1 ] = 0;
+ nid.uTimeout = msn->uTimeout;
+ nid.dwInfoFlags = (msn->dwInfoFlags & ~NIIF_INTERN_UNICODE);
+ return Shell_NotifyIconW( NIM_MODIFY, &nid ) == 0;
+ }
+ else
+#endif
+ {
+ NOTIFYICONDATAA nid = { 0 };
+ nid.cbSize = ( cli.shellVersion >= 5 ) ? sizeof(nid) : NOTIFYICONDATAA_V1_SIZE;
+ nid.hWnd = cli.hwndContactList;
+ nid.uID = iconId;
+ nid.uFlags = NIF_INFO;
+ lstrcpynA( nid.szInfo, msn->szInfo, sizeof( nid.szInfo ));
+ lstrcpynA( nid.szInfoTitle, msn->szInfoTitle, sizeof( nid.szInfoTitle ));
+ nid.uTimeout = msn->uTimeout;
+ nid.dwInfoFlags = msn->dwInfoFlags;
+ return Shell_NotifyIconA( NIM_MODIFY, &nid ) == 0;
+} }
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+typedef struct _DllVersionInfo
+{
+ DWORD cbSize;
+ DWORD dwMajorVersion; // Major version
+ DWORD dwMinorVersion; // Minor version
+ DWORD dwBuildNumber; // Build number
+ DWORD dwPlatformID; // DLLVER_PLATFORM_*
+}
+ DLLVERSIONINFO;
+
+typedef HRESULT(CALLBACK * DLLGETVERSIONPROC) (DLLVERSIONINFO *);
+
+static DLLVERSIONINFO dviShell;
+
+static INT_PTR pfnCListTrayNotifyStub(WPARAM, LPARAM lParam )
+{ return cli.pfnCListTrayNotify(( MIRANDASYSTRAYNOTIFY* )lParam );
+}
+
+void fnInitTray( void )
+{
+ HMODULE hLib = GetModuleHandleA("shell32");
+ if ( hLib ) {
+ DLLGETVERSIONPROC proc;
+ dviShell.cbSize = sizeof(dviShell);
+ proc = ( DLLGETVERSIONPROC )GetProcAddress( hLib, "DllGetVersion" );
+ if (proc) {
+ proc( &dviShell );
+ cli.shellVersion = dviShell.dwMajorVersion;
+ }
+ FreeLibrary(hLib);
+ }
+ InitializeCriticalSection(&trayLockCS);
+ if ( cli.shellVersion >= 5 )
+ CreateServiceFunction(MS_CLIST_SYSTRAY_NOTIFY, pfnCListTrayNotifyStub );
+ fTrayInited=TRUE;
+}
+
+void fnUninitTray( void )
+{
+ fTrayInited=FALSE;
+ DeleteCriticalSection( &trayLockCS );
+}
+void fnLockTray( void )
+{
+// return; //stub to be removed
+ initcheck;
+ EnterCriticalSection( &trayLockCS );
+}
+
+void fnUnlockTray( void )
+{
+// return; //stub to be removed
+ initcheck;
+#ifdef _DEBUG
+ if (trayLockCS.RecursionCount==0) DebugBreak(); //try to unlock already
+#endif
+ LeaveCriticalSection( &trayLockCS );
+}
+
+#undef lock
+#undef ulock
+#undef initcheck
diff --git a/src/modules/clist/clui.cpp b/src/modules/clist/clui.cpp
new file mode 100644
index 0000000000..13ebf74a21
--- /dev/null
+++ b/src/modules/clist/clui.cpp
@@ -0,0 +1,1136 @@
+/*
+
+ Miranda IM: the free IM client for Microsoft* Windows*
+
+ Copyright 2000-2010 Miranda ICQ/IM project,
+ all portions of this codebase are copyrighted to the people
+ listed in contributors.txt.
+
+ 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; either version 2
+ of the License, or (at your option) any later version.
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "../database/profilemanager.h"
+#include "clc.h"
+
+#define TM_AUTOALPHA 1
+#define MENU_MIRANDAMENU 0xFFFF1234
+
+extern BOOL(WINAPI * MySetProcessWorkingSetSize) (HANDLE, SIZE_T, SIZE_T);
+
+static HMODULE hUserDll;
+static HANDLE hContactDraggingEvent, hContactDroppedEvent, hContactDragStopEvent;
+static int transparentFocus = 1;
+UINT uMsgProcessProfile;
+
+#define M_RESTORESTATUS (WM_USER+7)
+
+void LoadCluiServices();
+
+typedef struct {
+ int showsbar;
+ int showgrip;
+ int transparent;
+ int alpha;
+}
+ CluiOpts;
+
+static CluiOpts cluiopt = {0};
+
+void fnLoadCluiGlobalOpts()
+{
+ cluiopt.showsbar = DBGetContactSettingByte(NULL, "CLUI", "ShowSBar", 1);
+ cluiopt.showgrip = DBGetContactSettingByte(NULL, "CLUI", "ShowGrip", 1);
+ cluiopt.transparent = DBGetContactSettingByte(NULL,"CList","Transparent",SETTING_TRANSPARENT_DEFAULT);
+ cluiopt.alpha = DBGetContactSettingByte(NULL, "CList", "Alpha", SETTING_ALPHA_DEFAULT);
+}
+
+static int CluiModulesLoaded(WPARAM, LPARAM)
+{
+ if (cli.hMenuMain) {
+ MENUITEMINFO mii = { 0 };
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ mii.fMask = MIIM_SUBMENU;
+ mii.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETMAIN, 0, 0);
+ SetMenuItemInfo(cli.hMenuMain, 0, TRUE, &mii);
+ mii.hSubMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0);
+ SetMenuItemInfo(cli.hMenuMain, 1, TRUE, &mii);
+ }
+ return 0;
+}
+
+// Disconnect all protocols.
+// Happens on shutdown and standby.
+static void DisconnectAll()
+{
+ int nProto;
+ for (nProto = 0; nProto < accounts.getCount(); nProto++)
+ CallProtoService( accounts[nProto]->szModuleName, PS_SETSTATUS, ID_STATUS_OFFLINE, 0);
+}
+
+static int CluiIconsChanged(WPARAM, LPARAM)
+{
+ DrawMenuBar(cli.hwndContactList);
+ return 0;
+}
+
+static HANDLE hRenameMenuItem;
+
+static int MenuItem_PreBuild(WPARAM, LPARAM)
+{
+ TCHAR cls[128];
+ HANDLE hItem;
+ HWND hwndClist = GetFocus();
+ CLISTMENUITEM mi;
+
+ ZeroMemory(&mi, sizeof(mi));
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIM_FLAGS;
+ GetClassName(hwndClist, cls, SIZEOF(cls));
+ hwndClist = (!lstrcmp(CLISTCONTROL_CLASS, cls)) ? hwndClist : cli.hwndContactList;
+ hItem = (HANDLE) SendMessage(hwndClist, CLM_GETSELECTION, 0, 0);
+ if (!hItem) {
+ mi.flags = CMIM_FLAGS | CMIF_HIDDEN;
+ }
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM) hRenameMenuItem, (LPARAM) & mi);
+ return 0;
+}
+
+static INT_PTR MenuItem_RenameContact(WPARAM, LPARAM)
+{
+ TCHAR cls[128];
+ HANDLE hItem;
+ HWND hwndClist = GetFocus();
+ GetClassName(hwndClist, cls, SIZEOF(cls));
+ // worst case scenario, the rename is sent to the main contact list
+ hwndClist = (!lstrcmp(CLISTCONTROL_CLASS, cls)) ? hwndClist : cli.hwndContactList;
+ hItem = (HANDLE) SendMessage(hwndClist, CLM_GETSELECTION, 0, 0);
+ if (hItem) {
+ SetFocus(hwndClist);
+ SendMessage(hwndClist, CLM_EDITLABEL, (WPARAM) hItem, 0);
+ }
+ return 0;
+}
+
+static INT_PTR CALLBACK AskForConfirmationDlgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hWnd);
+ {
+ LOGFONT lf;
+ HFONT hFont;
+
+ hFont = (HFONT) SendDlgItemMessage(hWnd, IDYES, WM_GETFONT, 0, 0);
+ GetObject(hFont, sizeof(lf), &lf);
+ lf.lfWeight = FW_BOLD;
+ SendDlgItemMessage(hWnd, IDC_TOPLINE, WM_SETFONT, (WPARAM) CreateFontIndirect(&lf), 0);
+ }
+ {
+ TCHAR szFormat[256];
+ TCHAR szFinal[256];
+
+ GetDlgItemText(hWnd, IDC_TOPLINE, szFormat, SIZEOF(szFormat));
+ mir_sntprintf(szFinal, SIZEOF(szFinal), szFormat, cli.pfnGetContactDisplayName((HANDLE)lParam, 0));
+ SetDlgItemText(hWnd, IDC_TOPLINE, szFinal);
+ }
+ SetFocus(GetDlgItem(hWnd, IDNO));
+ SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDYES:
+ if (IsDlgButtonChecked(hWnd, IDC_HIDE)) {
+ EndDialog(hWnd, IDC_HIDE);
+ break;
+ }
+ //fall through
+ case IDCANCEL:
+ case IDNO:
+ EndDialog(hWnd, LOWORD(wParam));
+ break;
+ }
+ break;
+
+ case WM_CLOSE:
+ SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(IDNO, BN_CLICKED), 0);
+ break;
+
+ case WM_DESTROY:
+ DeleteObject((HFONT) SendDlgItemMessage(hWnd, IDC_TOPLINE, WM_GETFONT, 0, 0));
+ break;
+ }
+
+ return FALSE;
+}
+
+static INT_PTR MenuItem_DeleteContact(WPARAM wParam, LPARAM lParam)
+{
+ //see notes about deleting contacts on PF1_SERVERCLIST servers in m_protosvc.h
+ UINT_PTR action;
+
+ if (DBGetContactSettingByte(NULL, "CList", "ConfirmDelete", SETTING_CONFIRMDELETE_DEFAULT) &&
+ !(GetKeyState(VK_SHIFT)&0x8000) )
+ // Ask user for confirmation, and if the contact should be archived (hidden, not deleted)
+ action = DialogBoxParam(hMirandaInst, MAKEINTRESOURCE(IDD_DELETECONTACT), (HWND) lParam, AskForConfirmationDlgProc, wParam);
+ else
+ action = IDYES;
+
+ switch (action) {
+
+ // Delete contact
+ case IDYES:
+ {
+ char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
+ if (szProto != NULL) {
+ // Check if protocol uses server side lists
+ DWORD caps;
+
+ caps = (DWORD) CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0);
+ if (caps & PF1_SERVERCLIST) {
+ int status;
+
+ status = CallProtoService(szProto, PS_GETSTATUS, 0, 0);
+ if (status == ID_STATUS_OFFLINE || (status >= ID_STATUS_CONNECTING && status < ID_STATUS_CONNECTING + MAX_CONNECT_RETRIES)) {
+ // Set a flag so we remember to delete the contact when the protocol goes online the next time
+ DBWriteContactSettingByte((HANDLE) wParam, "CList", "Delete", 1);
+ MessageBox( NULL,
+ TranslateT("This contact is on an instant messaging system which stores its contact list on a central server. The contact will be removed from the server and from your contact list when you next connect to that network."),
+ TranslateT("Delete Contact"), MB_OK);
+ return 0;
+ } } }
+
+ CallService(MS_DB_CONTACT_DELETE, wParam, 0);
+ }
+ break;
+
+ // Archive contact
+ case IDC_HIDE:
+ DBWriteContactSettingByte((HANDLE) wParam, "CList", "Hidden", 1);
+ break;
+ }
+
+ return 0;
+}
+
+static INT_PTR MenuItem_AddContactToList(WPARAM wParam, LPARAM)
+{
+ ADDCONTACTSTRUCT acs = { 0 };
+
+ acs.handle = (HANDLE) wParam;
+ acs.handleType = HANDLE_CONTACT;
+ acs.szProto = "";
+
+ CallService(MS_ADDCONTACT_SHOW, (WPARAM) NULL, (LPARAM) & acs);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// this is the smallest available window procedure
+
+#ifndef CS_DROPSHADOW
+#define CS_DROPSHADOW 0x00020000
+#endif
+
+LRESULT CALLBACK ContactListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT result;
+ MSG m;
+ m.hwnd=hwnd;
+ m.message=msg;
+ m.wParam=wParam;
+ m.lParam=lParam;
+ if ( cli.pfnDocking_ProcessWindowMessage(( WPARAM )&m, ( LPARAM )&result ))
+ return result;
+ if ( cli.pfnTrayIconProcessMessage(( WPARAM )&m, ( LPARAM )&result ))
+ return result;
+ if ( cli.pfnHotkeysProcessMessage(( WPARAM )&m, ( LPARAM )&result ))
+ return result;
+
+ return cli.pfnContactListWndProc( hwnd, msg, wParam, lParam );
+}
+
+int LoadCLUIModule(void)
+{
+ DBVARIANT dbv;
+ TCHAR titleText[256];
+
+ uMsgProcessProfile = RegisterWindowMessage( _T("Miranda::ProcessProfile"));
+ cli.pfnLoadCluiGlobalOpts();
+
+ HookEvent(ME_SYSTEM_MODULESLOADED, CluiModulesLoaded);
+ HookEvent(ME_SKIN_ICONSCHANGED, CluiIconsChanged);
+
+ hContactDraggingEvent = CreateHookableEvent(ME_CLUI_CONTACTDRAGGING);
+ hContactDroppedEvent = CreateHookableEvent(ME_CLUI_CONTACTDROPPED);
+ hContactDragStopEvent = CreateHookableEvent(ME_CLUI_CONTACTDRAGSTOP);
+ LoadCluiServices();
+
+ WNDCLASSEX wndclass;
+ wndclass.cbSize = sizeof(wndclass);
+ wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_GLOBALCLASS;
+ wndclass.lpfnWndProc = cli.pfnContactListControlWndProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = sizeof(void *);
+ wndclass.hInstance = cli.hInst;
+ wndclass.hIcon = NULL;
+ wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wndclass.hbrBackground = NULL;
+ wndclass.lpszMenuName = NULL;
+ wndclass.lpszClassName = CLISTCONTROL_CLASS;
+ wndclass.hIconSm = NULL;
+ RegisterClassEx(&wndclass);
+
+ wndclass.style = CS_HREDRAW | CS_VREDRAW | ((IsWinVerXPPlus() &&
+ DBGetContactSettingByte(NULL, "CList", "WindowShadow", 0) == 1) ? CS_DROPSHADOW : 0);
+ wndclass.lpfnWndProc = ContactListWndProc;
+ wndclass.cbClsExtra = 0;
+ wndclass.cbWndExtra = 0;
+ wndclass.hInstance = cli.hInst;
+ wndclass.hIcon = LoadSkinIcon(SKINICON_OTHER_MIRANDA, true);
+ wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wndclass.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1);
+ wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_CLISTMENU);
+ wndclass.lpszClassName = _T(MIRANDACLASS);
+ wndclass.hIconSm = LoadSkinIcon(SKINICON_OTHER_MIRANDA);
+ RegisterClassEx(&wndclass);
+
+ if (DBGetContactSettingTString(NULL, "CList", "TitleText", &dbv))
+ lstrcpyn(titleText, _T(MIRANDANAME), SIZEOF( titleText ));
+ else {
+ lstrcpyn(titleText, dbv.ptszVal, SIZEOF(titleText));
+ DBFreeVariant(&dbv);
+ }
+
+ RECT pos;
+ pos.left = (int) DBGetContactSettingDword(NULL, "CList", "x", 700);
+ pos.top = (int) DBGetContactSettingDword(NULL, "CList", "y", 221);
+ pos.right = pos.left + (int) DBGetContactSettingDword(NULL, "CList", "Width", 108);
+ pos.bottom = pos.top + (int) DBGetContactSettingDword(NULL, "CList", "Height", 310);
+
+ Utils_AssertInsideScreen(&pos);
+
+ cli.hwndContactList = CreateWindowEx(
+ (DBGetContactSettingByte(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) ? WS_EX_TOOLWINDOW : WS_EX_APPWINDOW),
+ _T(MIRANDACLASS),
+ titleText,
+ WS_POPUPWINDOW | WS_THICKFRAME | WS_CLIPCHILDREN |
+ (DBGetContactSettingByte(NULL, "CLUI", "ShowCaption", SETTING_SHOWCAPTION_DEFAULT) ? WS_CAPTION | WS_SYSMENU |
+ (DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT) ? 0 : WS_MINIMIZEBOX) : 0),
+ pos.left, pos.top, pos.right - pos.left, pos.bottom - pos.top,
+ NULL, NULL, cli.hInst, NULL);
+
+ if (DBGetContactSettingByte(NULL, "CList", "OnDesktop", 0)) {
+ HWND hProgMan = FindWindow(_T("Progman"), NULL);
+ if (IsWindow(hProgMan))
+ SetParent(cli.hwndContactList, hProgMan);
+ }
+
+ cli.pfnOnCreateClc();
+
+ PostMessage(cli.hwndContactList, M_RESTORESTATUS, 0, 0);
+
+ {
+ int state = DBGetContactSettingByte(NULL, "CList", "State", SETTING_STATE_NORMAL);
+ cli.hMenuMain = GetMenu(cli.hwndContactList);
+ if (!DBGetContactSettingByte(NULL, "CLUI", "ShowMainMenu", SETTING_SHOWMAINMENU_DEFAULT))
+ SetMenu(cli.hwndContactList, NULL);
+ if (state == SETTING_STATE_NORMAL)
+ ShowWindow(cli.hwndContactList, SW_SHOW);
+ else if (state == SETTING_STATE_MINIMIZED)
+ ShowWindow(cli.hwndContactList, SW_SHOWMINIMIZED);
+ SetWindowPos(cli.hwndContactList,
+ DBGetContactSettingByte(NULL, "CList", "OnTop", SETTING_ONTOP_DEFAULT) ? HWND_TOPMOST : HWND_NOTOPMOST,
+ 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
+ }
+ {
+ CLISTMENUITEM mi = { 0 };
+ mi.cbSize = sizeof(mi);
+
+ CreateServiceFunction("CList/DeleteContactCommand", MenuItem_DeleteContact);
+ mi.position = 2000070000;
+ mi.flags = CMIF_ICONFROMICOLIB;
+ mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_DELETE );
+ mi.pszContactOwner = NULL; //on every contact
+ mi.pszName = LPGEN("De&lete");
+ mi.pszService = "CList/DeleteContactCommand";
+ CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM) & mi);
+
+ CreateServiceFunction("CList/RenameContactCommand", MenuItem_RenameContact);
+ mi.position = 2000050000;
+ mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_RENAME );
+ mi.pszContactOwner = NULL; //on every contact
+ mi.pszName = LPGEN("&Rename");
+ mi.pszService = "CList/RenameContactCommand";
+ hRenameMenuItem = (HANDLE) CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM) & mi);
+
+ CreateServiceFunction("CList/AddToListContactCommand", MenuItem_AddContactToList);
+ mi.position = -2050000000;
+ mi.flags |= CMIF_NOTONLIST;
+ mi.icolibItem = GetSkinIconHandle( SKINICON_OTHER_ADDCONTACT );
+ mi.pszName = LPGEN("&Add permanently to list");
+ mi.pszService = "CList/AddToListContactCommand";
+ CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM) & mi);
+
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, MenuItem_PreBuild);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// default contact list window procedure
+
+void fnDrawMenuItem(DRAWITEMSTRUCT *dis, HICON hIcon, HICON eventIcon)
+{
+ if (!IsWinVerXPPlus()) {
+ FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_MENU));
+ if (dis->itemState & ODS_HOTLIGHT)
+ DrawEdge(dis->hDC, &dis->rcItem, BDR_RAISEDINNER, BF_RECT);
+ else if (dis->itemState & ODS_SELECTED)
+ DrawEdge(dis->hDC, &dis->rcItem, BDR_SUNKENOUTER, BF_RECT);
+ if (eventIcon != 0) {
+ DrawState(dis->hDC, NULL, NULL, (LPARAM) eventIcon, 0, 2, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, 4 + g_IconWidth, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ }
+ else DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, (dis->rcItem.right + dis->rcItem.left - g_IconWidth) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ }
+ else {
+ HBRUSH hBr;
+ BOOL bfm = FALSE;
+ SystemParametersInfo(SPI_GETFLATMENU, 0, &bfm, 0);
+ if (bfm) {
+ /* flat menus: fill with COLOR_MENUHILIGHT and outline with COLOR_HIGHLIGHT, otherwise use COLOR_MENUBAR */
+ if (dis->itemState & ODS_SELECTED || dis->itemState & ODS_HOTLIGHT) {
+ /* selected or hot lighted, no difference */
+ hBr = GetSysColorBrush(COLOR_MENUHILIGHT);
+ FillRect(dis->hDC, &dis->rcItem, hBr);
+ DeleteObject(hBr);
+ /* draw the frame */
+ hBr = GetSysColorBrush(COLOR_HIGHLIGHT);
+ FrameRect(dis->hDC, &dis->rcItem, hBr);
+ DeleteObject(hBr);
+ } else {
+ /* flush the DC with the menu bar colour (only supported on XP) and then draw the icon */
+ hBr = GetSysColorBrush(COLOR_MENUBAR);
+ FillRect(dis->hDC, &dis->rcItem, hBr);
+ DeleteObject(hBr);
+ } //if
+ /* draw the icon */
+ if (eventIcon != 0) {
+ DrawState(dis->hDC, NULL, NULL, (LPARAM) eventIcon, 0, 2, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, 4 + g_IconWidth, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ }
+ else DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, (dis->rcItem.right + dis->rcItem.left - g_IconWidth) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ }
+ else {
+ /* non-flat menus, flush the DC with a normal menu colour */
+ FillRect(dis->hDC, &dis->rcItem, GetSysColorBrush(COLOR_MENU));
+ if (dis->itemState & ODS_HOTLIGHT)
+ DrawEdge(dis->hDC, &dis->rcItem, BDR_RAISEDINNER, BF_RECT);
+ else if (dis->itemState & ODS_SELECTED)
+ DrawEdge(dis->hDC, &dis->rcItem, BDR_SUNKENOUTER, BF_RECT);
+
+ if (eventIcon != 0) {
+ DrawState(dis->hDC, NULL, NULL, (LPARAM) eventIcon, 0, 2, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, 4 + g_IconWidth, (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ }
+ else DrawState(dis->hDC, NULL, NULL, (LPARAM) hIcon, 0, (dis->rcItem.right + dis->rcItem.left - g_IconWidth) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), (dis->rcItem.bottom + dis->rcItem.top - g_IconHeight) / 2 + (dis->itemState & ODS_SELECTED ? 1 : 0), 0, 0, DST_ICON | (dis->itemState & ODS_INACTIVE ? DSS_DISABLED : DSS_NORMAL));
+ } }
+
+ DestroyIcon(hIcon);
+ return;
+}
+
+#define M_CREATECLC (WM_USER+1)
+LRESULT CALLBACK fnContactListWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ if (msg == uMsgProcessProfile)
+ {
+ TCHAR profile[MAX_PATH];
+ int rc;
+ // wParam = (ATOM)hProfileAtom, lParam = 0
+ if (GlobalGetAtomName((ATOM) wParam, profile, SIZEOF(profile)))
+ {
+ TCHAR *pfd = Utils_ReplaceVarsT(_T("%miranda_userdata%\\%miranda_profilename%.dat"));
+ rc = lstrcmpi(profile, pfd) == 0;
+ mir_free(pfd);
+ ReplyMessage(rc);
+ if (rc) {
+ ShowWindow(hwnd, SW_RESTORE);
+ ShowWindow(hwnd, SW_SHOW);
+ SetForegroundWindow(hwnd);
+ SetFocus(hwnd);
+ }
+ }
+ return 0;
+ }
+
+ switch (msg) {
+ case WM_NCCREATE:
+ {
+ MENUITEMINFO mii = { 0 };
+/*
+ if (IsWinVerVistaPlus() && isThemeActive())
+ {
+ HICON hIcon = LoadSkinnedIcon(SKINICON_OTHER_MAINMENU);
+ HBITMAP hBmp = ConvertIconToBitmap(hIcon, NULL, 0);
+ IconLib_ReleaseIcon(hIcon, NULL);
+
+ mii.cbSize = sizeof(mii);
+ mii.fMask = MIIM_BITMAP | MIIM_STRING | MIIM_DATA;
+ mii.hbmpItem = hBmp;
+ }
+ else
+*/
+ {
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ mii.fMask = MIIM_TYPE | MIIM_DATA;
+ mii.dwItemData = MENU_MIRANDAMENU;
+ mii.fType = MFT_OWNERDRAW;
+ }
+ SetMenuItemInfo(GetMenu(hwnd), 0, TRUE, &mii);
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+ case WM_CREATE:
+ CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) GetMenu(hwnd), 0);
+ DrawMenuBar(hwnd);
+
+ //create the status wnd
+ {
+ int flags = WS_CHILD | CCS_BOTTOM;
+ flags |= cluiopt.showsbar ? WS_VISIBLE : 0;
+ flags |= cluiopt.showgrip ? SBARS_SIZEGRIP : 0;
+ cli.hwndStatus = CreateWindow(STATUSCLASSNAME, NULL, flags, 0, 0, 0, 0, hwnd, NULL, cli.hInst, NULL);
+ }
+ cli.pfnCluiProtocolStatusChanged(0, 0);
+
+ //delay creation of CLC so that it can get the status icons right the first time (needs protocol modules loaded)
+ PostMessage(hwnd, M_CREATECLC, 0, 0);
+
+ if (cluiopt.transparent) {
+ SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);
+ if (setLayeredWindowAttributes)
+ setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA);
+ }
+ transparentFocus = 1;
+ return FALSE;
+
+ case M_CREATECLC:
+ cli.hwndContactTree = CreateWindow( CLISTCONTROL_CLASS, _T(""),
+ WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN
+ | CLS_CONTACTLIST
+ | (DBGetContactSettingByte(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT) ? CLS_USEGROUPS : 0)
+ | (DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) ? CLS_HIDEOFFLINE : 0)
+ | (DBGetContactSettingByte(NULL, "CList", "HideEmptyGroups", SETTING_HIDEEMPTYGROUPS_DEFAULT) ?
+ CLS_HIDEEMPTYGROUPS : 0), 0, 0, 0, 0, hwnd, NULL, cli.hInst, NULL);
+ SendMessage(hwnd, WM_SIZE, 0, 0);
+ break;
+
+ case M_RESTORESTATUS:
+ #ifndef _DEBUG
+ {
+ int nStatus = DBGetContactSettingWord(NULL, "CList", "Status", ID_STATUS_OFFLINE);
+ if (nStatus != ID_STATUS_OFFLINE) CallService(MS_CLIST_SETSTATUSMODE, nStatus, 0);
+ }
+ #endif
+ break;
+
+ // Power management
+ case WM_POWERBROADCAST:
+ switch ((DWORD) wParam) {
+ case PBT_APMSUSPEND:
+ // Computer is suspending, disconnect all protocols
+ DisconnectAll();
+ break;
+
+ case PBT_APMRESUMEAUTOMATIC:
+ case PBT_APMRESUMESUSPEND:
+ // Computer is resuming, restore all protocols
+ PostMessage(hwnd, M_RESTORESTATUS, 0, 0);
+ break;
+ }
+ break;
+
+ case WM_SYSCOLORCHANGE:
+ SendMessage(cli.hwndContactTree, msg, wParam, lParam);
+ SendMessage(cli.hwndStatus, msg, wParam, lParam);
+ // XXX: only works with 4.71 with 95, IE4.
+ SendMessage(cli.hwndStatus, SB_SETBKCOLOR, 0, GetSysColor(COLOR_3DFACE));
+ break;
+
+ case WM_SIZE:
+ if (IsZoomed(hwnd))
+ ShowWindow(hwnd, SW_SHOWNORMAL);
+ {
+ RECT rect, rcStatus;
+ GetClientRect(hwnd, &rect);
+ if (cluiopt.showsbar) {
+ SetWindowPos(cli.hwndStatus, NULL, 0, rect.bottom - 20, rect.right - rect.left, 20, SWP_NOZORDER);
+ GetWindowRect(cli.hwndStatus, &rcStatus);
+ cli.pfnCluiProtocolStatusChanged(0, 0);
+ }
+ else
+ rcStatus.top = rcStatus.bottom = 0;
+ SetWindowPos(cli.hwndContactTree, NULL, 0, 0, rect.right, rect.bottom - (rcStatus.bottom - rcStatus.top), SWP_NOZORDER);
+ }
+ if (wParam == SIZE_MINIMIZED)
+ {
+ if ((GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) ||
+ DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT))
+ {
+ ShowWindow(hwnd, SW_HIDE);
+ DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_HIDDEN);
+ }
+ else
+ DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_MINIMIZED);
+
+ if (MySetProcessWorkingSetSize != NULL && DBGetContactSettingByte(NULL, "CList", "DisableWorkingSet", 1))
+ MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
+ }
+ // drop thru
+ case WM_MOVE:
+ if (!IsIconic(hwnd)) {
+ RECT rc;
+ GetWindowRect(hwnd, &rc);
+
+ if (!CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) { //if docked, dont remember pos (except for width)
+ DBWriteContactSettingDword(NULL, "CList", "Height", (DWORD) (rc.bottom - rc.top));
+ DBWriteContactSettingDword(NULL, "CList", "x", (DWORD) rc.left);
+ DBWriteContactSettingDword(NULL, "CList", "y", (DWORD) rc.top);
+ }
+ DBWriteContactSettingDword(NULL, "CList", "Width", (DWORD) (rc.right - rc.left));
+ }
+ return FALSE;
+
+ case WM_SETFOCUS:
+ SetFocus(cli.hwndContactTree);
+ return 0;
+
+ case WM_ACTIVATE:
+ if (wParam == WA_INACTIVE) {
+ if ((HWND) wParam != hwnd)
+ if (cluiopt.transparent)
+ if (transparentFocus)
+ SetTimer(hwnd, TM_AUTOALPHA, 250, NULL);
+ }
+ else {
+ if (cluiopt.transparent) {
+ KillTimer(hwnd, TM_AUTOALPHA);
+ if (setLayeredWindowAttributes)
+ setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA);
+ transparentFocus = 1;
+ }
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+
+ case WM_SETCURSOR:
+ if(cluiopt.transparent) {
+ if (!transparentFocus && GetForegroundWindow()!=hwnd && setLayeredWindowAttributes) {
+ setLayeredWindowAttributes(hwnd, RGB(0,0,0), (BYTE)cluiopt.alpha, LWA_ALPHA);
+ transparentFocus=1;
+ SetTimer(hwnd, TM_AUTOALPHA,250,NULL);
+ }
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+
+ case WM_NCHITTEST:
+ {
+ LRESULT result;
+ result = DefWindowProc(hwnd, WM_NCHITTEST, wParam, lParam);
+ if (result == HTSIZE || result == HTTOP || result == HTTOPLEFT || result == HTTOPRIGHT ||
+ result == HTBOTTOM || result == HTBOTTOMRIGHT || result == HTBOTTOMLEFT)
+ if (DBGetContactSettingByte(NULL, "CLUI", "AutoSize", 0))
+ return HTCLIENT;
+ return result;
+ }
+
+ case WM_TIMER:
+ if ((int) wParam == TM_AUTOALPHA) {
+ int inwnd;
+
+ if (GetForegroundWindow() == hwnd) {
+ KillTimer(hwnd, TM_AUTOALPHA);
+ inwnd = 1;
+ }
+ else {
+ POINT pt;
+ HWND hwndPt;
+ pt.x = (short) LOWORD(GetMessagePos());
+ pt.y = (short) HIWORD(GetMessagePos());
+ hwndPt = WindowFromPoint(pt);
+ inwnd = (hwndPt == hwnd || GetParent(hwndPt) == hwnd);
+ }
+ if (inwnd != transparentFocus && setLayeredWindowAttributes) { //change
+ transparentFocus = inwnd;
+ if (transparentFocus)
+ setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) cluiopt.alpha, LWA_ALPHA);
+ else
+ setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) DBGetContactSettingByte(NULL, "CList", "AutoAlpha", SETTING_AUTOALPHA_DEFAULT), LWA_ALPHA);
+ }
+ if (!transparentFocus)
+ KillTimer(hwnd, TM_AUTOALPHA);
+ }
+ return TRUE;
+
+ case WM_SHOWWINDOW:
+ {
+ static int noRecurse = 0;
+ if (lParam)
+ break;
+ if (noRecurse)
+ break;
+ if (!DBGetContactSettingByte(NULL, "CLUI", "FadeInOut", 0) || !IsWinVer2000Plus())
+ break;
+ if (GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED) {
+ DWORD thisTick, startTick;
+ int sourceAlpha, destAlpha;
+ if (wParam) {
+ sourceAlpha = 0;
+ destAlpha = (BYTE) cluiopt.alpha;
+ setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LWA_ALPHA);
+ noRecurse = 1;
+ ShowWindow(hwnd, SW_SHOW);
+ noRecurse = 0;
+ }
+ else {
+ sourceAlpha = (BYTE) cluiopt.alpha;
+ destAlpha = 0;
+ }
+ for (startTick = GetTickCount();;) {
+ thisTick = GetTickCount();
+ if (thisTick >= startTick + 200)
+ break;
+ setLayeredWindowAttributes(hwnd, RGB(0, 0, 0),
+ (BYTE) (sourceAlpha + (destAlpha - sourceAlpha) * (int) (thisTick - startTick) / 200), LWA_ALPHA);
+ }
+ setLayeredWindowAttributes(hwnd, RGB(0, 0, 0), (BYTE) destAlpha, LWA_ALPHA);
+ }
+ else {
+ if (wParam)
+ SetForegroundWindow(hwnd);
+ animateWindow(hwnd, 200, AW_BLEND | (wParam ? 0 : AW_HIDE));
+ SetWindowPos(cli.hwndContactTree, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
+ }
+ break;
+ }
+ case WM_MENURBUTTONUP: /* this API is so badly documented at MSDN!! */
+ {
+ UINT id = 0;
+
+ id = GetMenuItemID((HMENU) lParam, LOWORD(wParam)); /* LOWORD(wParam) contains the menu pos in its parent menu */
+ if (id != (-1))
+ SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(id, 0), 0);
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+ case WM_SYSCOMMAND:
+ switch (wParam)
+ {
+ case SC_MAXIMIZE:
+ return 0;
+
+ case SC_MINIMIZE:
+ case SC_CLOSE:
+ if ((GetWindowLongPtr(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) ||
+ DBGetContactSettingByte(NULL, "CList", "Min2Tray", SETTING_MIN2TRAY_DEFAULT))
+ {
+ ShowWindow(hwnd, SW_HIDE);
+ DBWriteContactSettingByte(NULL, "CList", "State", SETTING_STATE_HIDDEN);
+
+ if (MySetProcessWorkingSetSize != NULL && DBGetContactSettingByte(NULL, "CList", "DisableWorkingSet", 1))
+ MySetProcessWorkingSetSize(GetCurrentProcess(), -1, -1);
+
+ return 0;
+ }
+ else if (wParam == SC_CLOSE)
+ wParam = SC_MINIMIZE;
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+
+ case WM_COMMAND:
+ if (CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(LOWORD(wParam), MPCF_MAINMENU), (LPARAM) (HANDLE) NULL))
+ break;
+ switch (LOWORD(wParam)) {
+ case ID_TRAY_EXIT:
+ case ID_ICQ_EXIT:
+ if (CallService(MS_SYSTEM_OKTOEXIT, 0, 0))
+ DestroyWindow(hwnd);
+ break;
+ case ID_TRAY_HIDE:
+ CallService(MS_CLIST_SHOWHIDE, 0, 0);
+ break;
+ case POPUP_NEWGROUP:
+ SendMessage(cli.hwndContactTree, CLM_SETHIDEEMPTYGROUPS, 0, 0);
+ CallService(MS_CLIST_GROUPCREATE, 0, 0);
+ break;
+ case POPUP_HIDEOFFLINE:
+ CallService(MS_CLIST_SETHIDEOFFLINE, (WPARAM) (-1), 0);
+ break;
+ case POPUP_HIDEOFFLINEROOT:
+ SendMessage(cli.hwndContactTree, CLM_SETHIDEOFFLINEROOT, !SendMessage(cli.hwndContactTree, CLM_GETHIDEOFFLINEROOT, 0, 0), 0);
+ break;
+ case POPUP_HIDEEMPTYGROUPS:
+ {
+ int newVal = !(GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEEMPTYGROUPS);
+ DBWriteContactSettingByte(NULL, "CList", "HideEmptyGroups", (BYTE) newVal);
+ SendMessage(cli.hwndContactTree, CLM_SETHIDEEMPTYGROUPS, newVal, 0);
+ break;
+ }
+ case POPUP_DISABLEGROUPS:
+ {
+ int newVal = !(GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_USEGROUPS);
+ DBWriteContactSettingByte(NULL, "CList", "UseGroups", (BYTE) newVal);
+ SendMessage(cli.hwndContactTree, CLM_SETUSEGROUPS, newVal, 0);
+ break;
+ }
+ case POPUP_HIDEMIRANDA:
+ {
+ CallService(MS_CLIST_SHOWHIDE, 0, 0);
+ break;
+ } }
+ return FALSE;
+ case WM_KEYDOWN:
+ CallService(MS_CLIST_MENUPROCESSHOTKEY, wParam, MPCF_MAINMENU | MPCF_CONTACTMENU);
+ break;
+
+ case WM_GETMINMAXINFO:
+ DefWindowProc(hwnd, msg, wParam, lParam);
+ ((LPMINMAXINFO) lParam)->ptMinTrackSize.x = 16 + GetSystemMetrics(SM_CXHTHUMB);
+ ((LPMINMAXINFO) lParam)->ptMinTrackSize.y = 16;
+ return 0;
+
+ case WM_SETTINGCHANGE:
+ if (wParam == SPI_SETWORKAREA && (GetWindowLong(hwnd, GWL_STYLE) & (WS_VISIBLE | WS_MINIMIZE)) == WS_VISIBLE &&
+ !CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0))
+ {
+ RECT rc;
+ GetWindowRect(hwnd, &rc);
+ if (Utils_AssertInsideScreen(&rc) == 1)
+ MoveWindow(hwnd, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, TRUE);
+ }
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+
+ case WM_DISPLAYCHANGE:
+ DefWindowProc(hwnd, msg, wParam, lParam);
+ SendMessage(cli.hwndContactTree, WM_SIZE, 0, 0); //forces it to send a cln_listsizechanged
+ break;
+
+ //MSG FROM CHILD CONTROL
+ case WM_NOTIFY:
+ if (((LPNMHDR) lParam)->hwndFrom == cli.hwndContactTree) {
+ switch (((LPNMHDR) lParam)->code) {
+ case CLN_EXPANDED:
+ {
+ NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam;
+ CallService(MS_CLIST_GROUPSETEXPANDED, (WPARAM) nmc->hItem, nmc->action);
+ return FALSE;
+ }
+ case CLN_DRAGGING:
+ {
+ NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam;
+ ClientToScreen(hwnd, &nmc->pt);
+ if (!(nmc->flags & CLNF_ISGROUP))
+ if (NotifyEventHooks(hContactDraggingEvent, (WPARAM) nmc->hItem, MAKELPARAM(nmc->pt.x, nmc->pt.y))) {
+ SetCursor(LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)));
+ return TRUE;
+ }
+ break;
+ }
+ case CLN_DRAGSTOP:
+ {
+ NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam;
+ if (!(nmc->flags & CLNF_ISGROUP))
+ NotifyEventHooks(hContactDragStopEvent, (WPARAM) nmc->hItem, 0);
+ break;
+ }
+ case CLN_DROPPED:
+ {
+ NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam;
+ ClientToScreen(hwnd, &nmc->pt);
+ if (!(nmc->flags & CLNF_ISGROUP))
+ if (NotifyEventHooks(hContactDroppedEvent, (WPARAM) nmc->hItem, MAKELPARAM(nmc->pt.x, nmc->pt.y))) {
+ SetCursor(LoadCursor(cli.hInst, MAKEINTRESOURCE(IDC_DROPUSER)));
+ return TRUE;
+ }
+ break;
+ }
+ case NM_KEYDOWN:
+ {
+ NMKEY *nmkey = (NMKEY *) lParam;
+ return CallService(MS_CLIST_MENUPROCESSHOTKEY, nmkey->nVKey, MPCF_MAINMENU | MPCF_CONTACTMENU);
+ }
+ case CLN_LISTSIZECHANGE:
+ {
+ NMCLISTCONTROL *nmc = (NMCLISTCONTROL *) lParam;
+ RECT rcWindow, rcTree, rcWorkArea;
+ int maxHeight, newHeight;
+
+ if (!DBGetContactSettingByte(NULL, "CLUI", "AutoSize", 0))
+ break;
+ if (CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0))
+ break;
+ maxHeight = DBGetContactSettingByte(NULL, "CLUI", "MaxSizeHeight", 75);
+ GetWindowRect(hwnd, &rcWindow);
+ GetWindowRect(cli.hwndContactTree, &rcTree);
+
+ SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, FALSE);
+ if (MyMonitorFromWindow)
+ {
+ HMONITOR hMon = MyMonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO mi;
+ mi.cbSize = sizeof(mi);
+ if (MyGetMonitorInfo(hMon, &mi))
+ rcWorkArea = mi.rcWork;
+ }
+
+ newHeight = max(nmc->pt.y, 9) + 1 + (rcWindow.bottom - rcWindow.top) - (rcTree.bottom - rcTree.top);
+ if (newHeight > (rcWorkArea.bottom - rcWorkArea.top) * maxHeight / 100)
+ newHeight = (rcWorkArea.bottom - rcWorkArea.top) * maxHeight / 100;
+ if (DBGetContactSettingByte(NULL, "CLUI", "AutoSizeUpward", 0)) {
+ rcWindow.top = rcWindow.bottom - newHeight;
+ if (rcWindow.top < rcWorkArea.top)
+ rcWindow.top = rcWorkArea.top;
+ }
+ else {
+ rcWindow.bottom = rcWindow.top + newHeight;
+ if (rcWindow.bottom > rcWorkArea.bottom)
+ rcWindow.bottom = rcWorkArea.bottom;
+ }
+ SetWindowPos(hwnd, 0, rcWindow.left, rcWindow.top, rcWindow.right - rcWindow.left, rcWindow.bottom - rcWindow.top,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+ break;
+ }
+ case NM_CLICK:
+ {
+ NMCLISTCONTROL *nm = (NMCLISTCONTROL *) lParam;
+ DWORD hitFlags;
+
+ if (SendMessage(cli.hwndContactTree, CLM_HITTEST, (WPARAM) & hitFlags, MAKELPARAM(nm->pt.x, nm->pt.y)))
+ break;
+ if ((hitFlags & (CLCHT_NOWHERE | CLCHT_INLEFTMARGIN | CLCHT_BELOWITEMS)) == 0)
+ break;
+ if (DBGetContactSettingByte(NULL, "CLUI", "ClientAreaDrag", SETTING_CLIENTDRAG_DEFAULT)) {
+ POINT pt;
+ pt = nm->pt;
+ ClientToScreen(cli.hwndContactTree, &pt);
+ return SendMessage(hwnd, WM_SYSCOMMAND, SC_MOVE | HTCAPTION, MAKELPARAM(pt.x, pt.y));
+ }
+ break;
+ } }
+ }
+ else if (((LPNMHDR) lParam)->hwndFrom == cli.hwndStatus) {
+ if (((LPNMHDR) lParam)->code == NM_CLICK)
+ {
+ unsigned int nParts, nPanel;
+ NMMOUSE *nm = (NMMOUSE *) lParam;
+ HMENU hMenu;
+ RECT rc;
+ POINT pt;
+
+ hMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0);
+ nParts = SendMessage(cli.hwndStatus, SB_GETPARTS, 0, 0);
+ if (nm->dwItemSpec == 0xFFFFFFFE) {
+ nPanel = nParts - 1;
+ SendMessage(cli.hwndStatus, SB_GETRECT, nPanel, (LPARAM) & rc);
+ if (nm->pt.x < rc.left)
+ return FALSE;
+ }
+ else nPanel = nm->dwItemSpec;
+
+ if (nParts > 0)
+ {
+ unsigned int cpnl = 0;
+ int mcnt = GetMenuItemCount(hMenu);
+ for (int i=0; i<mcnt; ++i) {
+ HMENU hMenus = GetSubMenu(hMenu, i);
+ if (hMenus && cpnl++ == nPanel) {
+ hMenu = hMenus;
+ break;
+ }
+ }
+ }
+ SendMessage(cli.hwndStatus, SB_GETRECT, nPanel, (LPARAM) & rc);
+ pt.x = rc.left;
+ pt.y = rc.top;
+ ClientToScreen(cli.hwndStatus, &pt);
+ TrackPopupMenu(hMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, NULL);
+ } }
+ return FALSE;
+
+ case WM_MENUSELECT:
+ if(lParam && (HMENU)lParam == cli.hMenuMain) {
+ int pos = LOWORD(wParam);
+ POINT pt;
+ GetCursorPos(&pt);
+ if ((pos == 0 || pos == 1) && (HIWORD(wParam) & MF_POPUP) &&
+ (!(HIWORD(wParam) & MF_MOUSESELECT) || MenuItemFromPoint(hwnd, cli.hMenuMain, pt) != -1)) {
+ MENUITEMINFO mii = { 0 };
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ mii.fMask = MIIM_SUBMENU;
+ mii.hSubMenu = (HMENU)CallService((pos == 0) ? MS_CLIST_MENUGETMAIN : MS_CLIST_MENUGETSTATUS, 0, 0);
+ SetMenuItemInfo(cli.hMenuMain, pos, TRUE, &mii);
+ } }
+ break;
+
+ case WM_CONTEXTMENU:
+ {
+ RECT rc;
+ POINT pt;
+
+ pt.x = (short) LOWORD(lParam);
+ pt.y = (short) HIWORD(lParam);
+ // x/y might be -1 if it was generated by a kb click
+ GetWindowRect(cli.hwndContactTree, &rc);
+ if (pt.x == -1 && pt.y == -1) {
+ // all this is done in screen-coords!
+ GetCursorPos(&pt);
+ // the mouse isnt near the window, so put it in the middle of the window
+ if (!PtInRect(&rc, pt)) {
+ pt.x = rc.left + (rc.right - rc.left) / 2;
+ pt.y = rc.top + (rc.bottom - rc.top) / 2;
+ }
+ }
+ if (PtInRect(&rc, pt)) {
+ HMENU hMenu;
+ hMenu = GetSubMenu(LoadMenu(cli.hInst, MAKEINTRESOURCE(IDR_CONTEXT)), 1);
+ CallService(MS_LANGPACK_TRANSLATEMENU, (WPARAM) hMenu, 0);
+ CheckMenuItem(hMenu, POPUP_HIDEOFFLINE,
+ DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(hMenu, POPUP_HIDEOFFLINEROOT, SendMessage(cli.hwndContactTree, CLM_GETHIDEOFFLINEROOT, 0, 0) ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(hMenu, POPUP_HIDEEMPTYGROUPS,
+ GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEEMPTYGROUPS ? MF_CHECKED : MF_UNCHECKED);
+ CheckMenuItem(hMenu, POPUP_DISABLEGROUPS, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_USEGROUPS ? MF_UNCHECKED : MF_CHECKED);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
+ DestroyMenu(hMenu);
+ return 0;
+ }
+ GetWindowRect(cli.hwndStatus, &rc);
+ if (PtInRect(&rc, pt)) {
+ HMENU hMenu;
+ if (DBGetContactSettingByte(NULL, "CLUI", "SBarRightClk", 0))
+ hMenu = (HMENU) CallService(MS_CLIST_MENUGETMAIN, 0, 0);
+ else
+ hMenu = (HMENU) CallService(MS_CLIST_MENUGETSTATUS, 0, 0);
+ TrackPopupMenu(hMenu, TPM_TOPALIGN | TPM_LEFTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
+ return 0;
+ } }
+ break;
+
+ case WM_MEASUREITEM:
+ if (((LPMEASUREITEMSTRUCT) lParam)->itemData == MENU_MIRANDAMENU) {
+ ((LPMEASUREITEMSTRUCT) lParam)->itemWidth = g_IconWidth * 4 / 3;
+ ((LPMEASUREITEMSTRUCT) lParam)->itemHeight = 0;
+ return TRUE;
+ }
+ return CallService(MS_CLIST_MENUMEASUREITEM, wParam, lParam);
+ case WM_DRAWITEM:
+ {
+ LPDRAWITEMSTRUCT dis = (LPDRAWITEMSTRUCT) lParam;
+ if (dis->hwndItem == cli.hwndStatus) {
+ char *szProto = (char *) dis->itemData;
+ if (szProto == NULL) return 0;
+ int status, x;
+ SIZE textSize;
+ BYTE showOpts = DBGetContactSettingByte(NULL, "CLUI", "SBarShow", 1);
+ status = CallProtoService(szProto, PS_GETSTATUS, 0, 0);
+ SetBkMode(dis->hDC, TRANSPARENT);
+ x = dis->rcItem.left;
+ if (showOpts & 1) {
+ HICON hIcon = LoadSkinProtoIcon(szProto, status);
+ DrawIconEx(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - g_IconHeight) >> 1, hIcon,
+ g_IconWidth, g_IconHeight, 0, NULL, DI_NORMAL);
+ IconLib_ReleaseIcon(hIcon,0);
+ if ( Proto_IsAccountLocked( Proto_GetAccount( szProto ))) {
+ hIcon = LoadSkinnedIcon(SKINICON_OTHER_STATUS_LOCKED);
+ if (hIcon != NULL) {
+ DrawIconEx(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - g_IconHeight) >> 1, hIcon,
+ g_IconWidth, g_IconHeight, 0, NULL, DI_NORMAL);
+ IconLib_ReleaseIcon(hIcon,0);
+ }
+
+ }
+ x += g_IconWidth + 2;
+ }
+ else
+ x += 2;
+ if (showOpts & 2) {
+ PROTOACCOUNT* pa;
+ TCHAR tszName[64];
+ if (( pa = Proto_GetAccount( szProto )) != NULL )
+ mir_sntprintf( tszName, SIZEOF(tszName), _T("%s "), pa->tszAccountName );
+ else
+ tszName[0] = 0;
+
+ GetTextExtentPoint32(dis->hDC, tszName, lstrlen(tszName), &textSize);
+ TextOut(dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - textSize.cy) >> 1, tszName, lstrlen(tszName));
+ x += textSize.cx;
+ }
+ if (showOpts & 4) {
+ TCHAR* szStatus = cli.pfnGetStatusModeDescription( status, 0 );
+ if ( !szStatus )
+ szStatus = _T("");
+ GetTextExtentPoint32( dis->hDC, szStatus, lstrlen(szStatus), &textSize );
+ TextOut( dis->hDC, x, (dis->rcItem.top + dis->rcItem.bottom - textSize.cy ) >> 1, szStatus, lstrlen( szStatus ));
+ }
+ }
+ else if (dis->CtlType == ODT_MENU) {
+ if (dis->itemData == MENU_MIRANDAMENU) {
+ HICON hIcon = LoadSkinnedIcon(SKINICON_OTHER_MAINMENU);
+ fnDrawMenuItem(dis, CopyIcon(hIcon), NULL);
+ IconLib_ReleaseIcon(hIcon, NULL);
+ return TRUE;
+ }
+ return CallService(MS_CLIST_MENUDRAWITEM, wParam, lParam);
+ } }
+ return 0;
+
+ case WM_CLOSE:
+ if (CallService(MS_SYSTEM_OKTOEXIT, 0, 0))
+ DestroyWindow(hwnd);
+ return FALSE;
+
+ case WM_DESTROY:
+ if (!IsIconic(hwnd)) {
+ RECT rc;
+ GetWindowRect(hwnd, &rc);
+
+ if (!CallService(MS_CLIST_DOCKINGISDOCKED, 0, 0)) { //if docked, dont remember pos (except for width)
+ DBWriteContactSettingDword(NULL, "CList", "Height", (DWORD) (rc.bottom - rc.top));
+ DBWriteContactSettingDword(NULL, "CList", "x", (DWORD) rc.left);
+ DBWriteContactSettingDword(NULL, "CList", "y", (DWORD) rc.top);
+ }
+ DBWriteContactSettingDword(NULL, "CList", "Width", (DWORD) (rc.right - rc.left));
+ }
+
+ RemoveMenu(cli.hMenuMain, 0, MF_BYPOSITION);
+ RemoveMenu(cli.hMenuMain, 0, MF_BYPOSITION);
+
+ if ( cli.hwndStatus ) {
+ DestroyWindow( cli.hwndStatus );
+ cli.hwndStatus = NULL;
+ }
+
+ // Disconnect all protocols
+ DisconnectAll();
+
+ ShowWindow(hwnd, SW_HIDE);
+ DestroyWindow(cli.hwndContactTree);
+ FreeLibrary(hUserDll);
+ PostQuitMessage(0);
+
+ default:
+ return DefWindowProc(hwnd, msg, wParam, lParam);
+ }
+
+ return TRUE;
+}
diff --git a/src/modules/clist/cluiservices.cpp b/src/modules/clist/cluiservices.cpp
new file mode 100644
index 0000000000..c6b6efda71
--- /dev/null
+++ b/src/modules/clist/cluiservices.cpp
@@ -0,0 +1,221 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+static INT_PTR GetHwnd(WPARAM, LPARAM)
+{
+ return (INT_PTR)cli.hwndContactList;
+}
+
+static INT_PTR GetHwndTree(WPARAM, LPARAM)
+{
+ return (INT_PTR)cli.hwndContactTree;
+}
+
+static INT_PTR CluiProtocolStatusChanged(WPARAM wParam, LPARAM lParam)
+{
+ cli.pfnCluiProtocolStatusChanged( wParam, (const char*)lParam );
+ return 0;
+}
+
+INT_PTR SortList(WPARAM, LPARAM)
+{
+ //unnecessary: CLC does this automatically
+ return 0;
+}
+
+static INT_PTR GroupAdded(WPARAM wParam, LPARAM lParam)
+{
+ //CLC does this automatically unless it's a new group
+ if (lParam) {
+ HANDLE hItem;
+ TCHAR szFocusClass[64];
+ HWND hwndFocus = GetFocus();
+
+ GetClassName(hwndFocus, szFocusClass, SIZEOF(szFocusClass));
+ if (!lstrcmp(szFocusClass, CLISTCONTROL_CLASS)) {
+ hItem = (HANDLE) SendMessage(hwndFocus, CLM_FINDGROUP, wParam, 0);
+ if (hItem)
+ SendMessage(hwndFocus, CLM_EDITLABEL, (WPARAM) hItem, 0);
+ }
+ }
+ return 0;
+}
+
+static INT_PTR ContactSetIcon(WPARAM, LPARAM)
+{
+ //unnecessary: CLC does this automatically
+ return 0;
+}
+
+static INT_PTR ContactDeleted(WPARAM, LPARAM)
+{
+ //unnecessary: CLC does this automatically
+ return 0;
+}
+
+static INT_PTR ContactAdded(WPARAM, LPARAM)
+{
+ //unnecessary: CLC does this automatically
+ return 0;
+}
+
+static INT_PTR ListBeginRebuild(WPARAM, LPARAM)
+{
+ //unnecessary: CLC does this automatically
+ return 0;
+}
+
+static INT_PTR ListEndRebuild(WPARAM, LPARAM)
+{
+ int rebuild = 0;
+ //CLC does this automatically, but we need to force it if hideoffline or hideempty has changed
+ if ((DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEOFFLINE) == 0)) {
+ if (DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT))
+ SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) | CLS_HIDEOFFLINE);
+ else
+ SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & ~CLS_HIDEOFFLINE);
+ rebuild = 1;
+ }
+ if ((DBGetContactSettingByte(NULL, "CList", "HideEmptyGroups", SETTING_HIDEEMPTYGROUPS_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_HIDEEMPTYGROUPS) == 0)) {
+ if (DBGetContactSettingByte(NULL, "CList", "HideEmptyGroups", SETTING_HIDEEMPTYGROUPS_DEFAULT))
+ SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) | CLS_HIDEEMPTYGROUPS);
+ else
+ SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & ~CLS_HIDEEMPTYGROUPS);
+ rebuild = 1;
+ }
+ if ((DBGetContactSettingByte(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT) == 0) != ((GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & CLS_USEGROUPS) == 0)) {
+ if (DBGetContactSettingByte(NULL, "CList", "UseGroups", SETTING_USEGROUPS_DEFAULT))
+ SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) | CLS_USEGROUPS);
+ else
+ SetWindowLongPtr(cli.hwndContactTree, GWL_STYLE, GetWindowLongPtr(cli.hwndContactTree, GWL_STYLE) & ~CLS_USEGROUPS);
+ rebuild = 1;
+ }
+ if (rebuild)
+ SendMessage(cli.hwndContactTree, CLM_AUTOREBUILD, 0, 0);
+ return 0;
+}
+
+static INT_PTR ContactRenamed(WPARAM, LPARAM)
+{
+ //unnecessary: CLC does this automatically
+ return 0;
+}
+
+static INT_PTR GetCaps(WPARAM wParam, LPARAM)
+{
+ switch (wParam) {
+ case CLUICAPS_FLAGS1:
+ return CLUIF_HIDEEMPTYGROUPS | CLUIF_DISABLEGROUPS | CLUIF_HASONTOPOPTION | CLUIF_HASAUTOHIDEOPTION;
+ }
+ return 0;
+}
+
+void LoadCluiServices(void)
+{
+ CreateServiceFunction(MS_CLUI_GETHWND, GetHwnd);
+ CreateServiceFunction(MS_CLUI_GETHWNDTREE,GetHwndTree);
+ CreateServiceFunction(MS_CLUI_PROTOCOLSTATUSCHANGED, CluiProtocolStatusChanged);
+ CreateServiceFunction(MS_CLUI_GROUPADDED, GroupAdded);
+ CreateServiceFunction(MS_CLUI_CONTACTSETICON, ContactSetIcon);
+ CreateServiceFunction(MS_CLUI_CONTACTADDED, ContactAdded);
+ CreateServiceFunction(MS_CLUI_CONTACTDELETED, ContactDeleted);
+ CreateServiceFunction(MS_CLUI_CONTACTRENAMED, ContactRenamed);
+ CreateServiceFunction(MS_CLUI_LISTBEGINREBUILD, ListBeginRebuild);
+ CreateServiceFunction(MS_CLUI_LISTENDREBUILD, ListEndRebuild);
+ CreateServiceFunction(MS_CLUI_SORTLIST, SortList);
+ CreateServiceFunction(MS_CLUI_GETCAPS, GetCaps);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// default protocol status notification handler
+
+void fnCluiProtocolStatusChanged(int, const char* )
+{
+ int i, *partWidths;
+ int borders[3];
+ int flags = 0;
+
+ if ( cli.menuProtoCount == 0 ) {
+ SendMessage(cli.hwndStatus, SB_SETPARTS, 0, 0);
+ SendMessage( cli.hwndStatus, SB_SETTEXT, SBT_OWNERDRAW, 0 );
+ return;
+ }
+
+ SendMessage(cli.hwndStatus, SB_GETBORDERS, 0, ( LPARAM )&borders);
+
+ partWidths = ( int* )alloca( cli.menuProtoCount * sizeof( int ));
+ if ( DBGetContactSettingByte( NULL, "CLUI", "EqualSections", 0 )) {
+ RECT rc;
+ GetClientRect( cli.hwndStatus, &rc );
+ rc.right -= borders[0] * 2 + ( DBGetContactSettingByte( NULL, "CLUI", "ShowGrip", 1 ) ? GetSystemMetrics( SM_CXVSCROLL ) : 0 );
+ for ( i = 0; i < cli.menuProtoCount; i++ )
+ partWidths[ i ] = ( i+1 ) * rc.right / cli.menuProtoCount - (borders[2] >> 1);
+ }
+ else {
+ HDC hdc;
+ SIZE textSize;
+ BYTE showOpts = DBGetContactSettingByte(NULL, "CLUI", "SBarShow", 1);
+
+ hdc = GetDC(NULL);
+ SelectObject(hdc, (HFONT) SendMessage(cli.hwndStatus, WM_GETFONT, 0, 0));
+ for ( i = 0; i < cli.menuProtoCount; i++ ) { //count down since built in ones tend to go at the end
+ int x = 2;
+ if ( showOpts & 1 )
+ x += g_IconWidth;
+ if ( showOpts & 2 ) {
+ TCHAR tszName[64];
+ PROTOACCOUNT* pa = Proto_GetAccount( cli.menuProtos[i].szProto );
+ if ( pa )
+ mir_sntprintf( tszName, SIZEOF(tszName), _T("%s "), pa->tszAccountName );
+ else
+ tszName[0] = 0;
+
+ if ( showOpts & 4 && lstrlen(tszName) < SIZEOF(tszName)-1 )
+ lstrcat( tszName, _T(" "));
+ GetTextExtentPoint32(hdc, tszName, lstrlen(tszName), &textSize);
+ x += textSize.cx;
+ x += GetSystemMetrics(SM_CXBORDER) * 4; // The SB panel doesnt allocate enough room
+ }
+ if ( showOpts & 4 ) {
+ TCHAR* modeDescr = cli.pfnGetStatusModeDescription( CallProtoService( cli.menuProtos[i].szProto, PS_GETSTATUS, 0, 0), 0 );
+ GetTextExtentPoint32(hdc, modeDescr, lstrlen(modeDescr), &textSize);
+ x += textSize.cx;
+ x += GetSystemMetrics(SM_CXBORDER) * 4; // The SB panel doesnt allocate enough room
+ }
+ partWidths[ i ] = ( i ? partWidths[ i-1] : 0 ) + x + 2;
+ }
+ ReleaseDC(NULL, hdc);
+ }
+
+ partWidths[ cli.menuProtoCount-1 ] = -1;
+ SendMessage(cli.hwndStatus, SB_SETMINHEIGHT, g_IconHeight, 0);
+ SendMessage(cli.hwndStatus, SB_SETPARTS, cli.menuProtoCount, ( LPARAM )partWidths);
+ flags = SBT_OWNERDRAW;
+ if ( DBGetContactSettingByte( NULL, "CLUI", "SBarBevel", 1 ) == 0 )
+ flags |= SBT_NOBORDERS;
+ for ( i = 0; i < cli.menuProtoCount; i++ ) {
+ SendMessage( cli.hwndStatus, SB_SETTEXT, i | flags, ( LPARAM )cli.menuProtos[i].szProto );
+ }
+}
diff --git a/src/modules/clist/contact.cpp b/src/modules/clist/contact.cpp
new file mode 100644
index 0000000000..f996dd11ac
--- /dev/null
+++ b/src/modules/clist/contact.cpp
@@ -0,0 +1,193 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+extern HANDLE hContactIconChangedEvent;
+extern HANDLE hGroupChangeEvent;
+
+int sortByStatus;
+int sortByProto;
+
+static const struct {
+ int status,order;
+} statusModeOrder[]={
+ {ID_STATUS_OFFLINE,500},
+ {ID_STATUS_ONLINE,10},
+ {ID_STATUS_AWAY,200},
+ {ID_STATUS_DND,110},
+ {ID_STATUS_NA,450},
+ {ID_STATUS_OCCUPIED,100},
+ {ID_STATUS_FREECHAT,0},
+ {ID_STATUS_INVISIBLE,20},
+ {ID_STATUS_ONTHEPHONE,150},
+ {ID_STATUS_OUTTOLUNCH,425}};
+
+static int GetContactStatus(HANDLE hContact)
+{
+ char* szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto == NULL)
+ return ID_STATUS_OFFLINE;
+ return DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE);
+}
+
+void fnChangeContactIcon(HANDLE hContact, int iIcon, int add)
+{
+ CallService(add ? MS_CLUI_CONTACTADDED : MS_CLUI_CONTACTSETICON, (WPARAM) hContact, iIcon);
+ NotifyEventHooks(hContactIconChangedEvent, (WPARAM) hContact, iIcon);
+}
+
+int GetStatusModeOrdering(int statusMode)
+{
+ int i;
+ for (i = 0; i < SIZEOF(statusModeOrder); i++)
+ if (statusModeOrder[i].status == statusMode)
+ return statusModeOrder[i].order;
+ return 1000;
+}
+
+void fnLoadContactTree(void)
+{
+ HANDLE hContact;
+ int i, status, hideOffline;
+
+ CallService(MS_CLUI_LISTBEGINREBUILD, 0, 0);
+ for (i = 1;; i++) {
+ if ( cli.pfnGetGroupName(i, NULL) == NULL)
+ break;
+ CallService(MS_CLUI_GROUPADDED, i, 0);
+ }
+
+ hideOffline = DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT);
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ while (hContact != NULL) {
+ status = GetContactStatus(hContact);
+ if ((!hideOffline || status != ID_STATUS_OFFLINE) && !DBGetContactSettingByte(hContact, "CList", "Hidden", 0))
+ cli.pfnChangeContactIcon(hContact, cli.pfnIconFromStatusMode((char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0), status, hContact), 1);
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0);
+ }
+ sortByStatus = DBGetContactSettingByte(NULL, "CList", "SortByStatus", SETTING_SORTBYSTATUS_DEFAULT);
+ sortByProto = DBGetContactSettingByte(NULL, "CList", "SortByProto", SETTING_SORTBYPROTO_DEFAULT);
+ CallService(MS_CLUI_SORTLIST, 0, 0);
+ CallService(MS_CLUI_LISTENDREBUILD, 0, 0);
+}
+
+int fnCompareContacts(const struct ClcContact* c1, const struct ClcContact* c2)
+{
+ HANDLE a = c1->hContact, b = c2->hContact;
+ TCHAR namea[128], *nameb;
+ int statusa, statusb;
+ int rc;
+
+ statusa = DBGetContactSettingWord((HANDLE) a, c1->proto, "Status", ID_STATUS_OFFLINE);
+ statusb = DBGetContactSettingWord((HANDLE) b, c2->proto, "Status", ID_STATUS_OFFLINE);
+
+ if (sortByProto) {
+ /* deal with statuses, online contacts have to go above offline */
+ if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) {
+ return 2 * (statusa == ID_STATUS_OFFLINE) - 1;
+ }
+ /* both are online, now check protocols */
+ rc = lstrcmpA( c1->proto, c2->proto);
+ if (rc != 0 && (c1->proto != NULL && c2->proto != NULL))
+ return rc;
+ /* protocols are the same, order by display name */
+ }
+
+ if (sortByStatus) {
+ int ordera, orderb;
+ ordera = GetStatusModeOrdering(statusa);
+ orderb = GetStatusModeOrdering(statusb);
+ if (ordera != orderb)
+ return ordera - orderb;
+ }
+ else {
+ //one is offline: offline goes below online
+ if ((statusa == ID_STATUS_OFFLINE) != (statusb == ID_STATUS_OFFLINE)) {
+ return 2 * (statusa == ID_STATUS_OFFLINE) - 1;
+ }
+ }
+
+ nameb = cli.pfnGetContactDisplayName( a, 0);
+ _tcsncpy(namea, nameb, SIZEOF(namea));
+ namea[ SIZEOF(namea)-1 ] = 0;
+ nameb = cli.pfnGetContactDisplayName( b, 0);
+
+ //otherwise just compare names
+ return _tcsicmp(namea, nameb);
+}
+
+static UINT_PTR resortTimerId = 0;
+static VOID CALLBACK SortContactsTimer(HWND, UINT, UINT_PTR, DWORD)
+{
+ KillTimer(NULL, resortTimerId);
+ resortTimerId = 0;
+ CallService(MS_CLUI_SORTLIST, 0, 0);
+}
+
+void fnSortContacts(void)
+{
+ //avoid doing lots of resorts in quick succession
+ sortByStatus = DBGetContactSettingByte(NULL, "CList", "SortByStatus", SETTING_SORTBYSTATUS_DEFAULT);
+ sortByProto = DBGetContactSettingByte(NULL, "CList", "SortByProto", SETTING_SORTBYPROTO_DEFAULT);
+ if (resortTimerId)
+ KillTimer(NULL, resortTimerId);
+ // setting this to a higher delay causes shutdown waits.
+ resortTimerId = SetTimer(NULL, 0, 500, SortContactsTimer);
+}
+
+INT_PTR ContactChangeGroup(WPARAM wParam, LPARAM lParam)
+{
+ CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, NULL };
+
+ CallService(MS_CLUI_CONTACTDELETED, wParam, 0);
+ if ((HANDLE) lParam == NULL)
+ DBDeleteContactSetting((HANDLE) wParam, "CList", "Group");
+ else {
+ grpChg.pszNewName = cli.pfnGetGroupName(lParam, NULL);
+ DBWriteContactSettingTString((HANDLE) wParam, "CList", "Group", grpChg.pszNewName);
+ }
+ CallService(MS_CLUI_CONTACTADDED, wParam,
+ cli.pfnIconFromStatusMode((char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0), GetContactStatus((HANDLE) wParam), (HANDLE) wParam));
+
+ NotifyEventHooks(hGroupChangeEvent, wParam, (LPARAM)&grpChg);
+ return 0;
+}
+
+int fnSetHideOffline(WPARAM wParam, LPARAM)
+{
+ switch(( int )wParam ) {
+ case 0:
+ DBWriteContactSettingByte(NULL, "CList", "HideOffline", 0);
+ break;
+ case 1:
+ DBWriteContactSettingByte(NULL, "CList", "HideOffline", 1);
+ break;
+ case -1:
+ DBWriteContactSettingByte(NULL, "CList", "HideOffline",
+ (BYTE) ! DBGetContactSettingByte(NULL, "CList", "HideOffline", SETTING_HIDEOFFLINE_DEFAULT));
+ break;
+ }
+ cli.pfnLoadContactTree();
+ return 0;
+}
diff --git a/src/modules/clist/genmenu.cpp b/src/modules/clist/genmenu.cpp
new file mode 100644
index 0000000000..92ec6d3edc
--- /dev/null
+++ b/src/modules/clist/genmenu.cpp
@@ -0,0 +1,1294 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "genmenu.h"
+
+static bool bIsGenMenuInited;
+bool bIconsDisabled;
+static CRITICAL_SECTION csMenuHook;
+
+static int NextObjectId = 0x100, NextObjectMenuItemId = CLISTMENUIDMIN;
+
+#if defined( _DEBUG )
+static void DumpMenuItem( TMO_IntMenuItem* pParent, int level = 0 )
+{
+ char temp[ 30 ];
+ memset( temp, '\t', level );
+ temp[ level ] = 0;
+
+ for ( PMO_IntMenuItem pimi = pParent; pimi != NULL; pimi = pimi->next ) {
+ Netlib_Logf( NULL, "%sMenu item %08p [%08p]: %S", temp, pimi, pimi->mi.root, pimi->mi.ptszName );
+
+ PMO_IntMenuItem submenu = pimi->submenu.first;
+ if ( submenu )
+ DumpMenuItem( submenu, level+1 );
+} }
+
+#endif
+
+static int CompareMenus( const TIntMenuObject* p1, const TIntMenuObject* p2 )
+{
+ return lstrcmpA( p1->Name, p2->Name );
+}
+
+LIST<TIntMenuObject> g_menus( 10, CompareMenus );
+
+void FreeAndNil( void **p )
+{
+ if ( p == NULL )
+ return;
+
+ if ( *p != NULL ) {
+ mir_free( *p );
+ *p = NULL;
+} }
+
+int GetMenuObjbyId( const int id )
+{
+ for ( int i=0; i < g_menus.getCount(); i++ )
+ if ( g_menus[i]->id == id )
+ return i;
+
+ return -1;
+}
+
+PMO_IntMenuItem MO_RecursiveWalkMenu( PMO_IntMenuItem parent, pfnWalkFunc func, void* param )
+{
+ if ( parent == NULL )
+ return FALSE;
+
+ PMO_IntMenuItem pnext;
+ for ( PMO_IntMenuItem pimi = parent; pimi != NULL; pimi = pnext ) {
+ PMO_IntMenuItem submenu = pimi->submenu.first;
+ pnext = pimi->next;
+ if ( func( pimi, param )) // it can destroy the menu item
+ return pimi;
+
+ if ( submenu ) {
+ PMO_IntMenuItem res = MO_RecursiveWalkMenu( submenu, func, param );
+ if ( res )
+ return res;
+ }
+ }
+
+ return FALSE;
+}
+
+//wparam=0
+//lparam=LPMEASUREITEMSTRUCT
+int MO_MeasureMenuItem( LPMEASUREITEMSTRUCT mis )
+{
+ // prevent win9x from ugly menus displaying when there is no icon
+ mis->itemWidth = 0;
+ mis->itemHeight = 0;
+
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ if ( mis == NULL )
+ return FALSE;
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )mis->itemData );
+ if ( pimi == NULL )
+ return FALSE;
+
+ if ( pimi->iconId == -1 )
+ return FALSE;
+
+ mis->itemWidth = max(0,GetSystemMetrics(SM_CXSMICON)-GetSystemMetrics(SM_CXMENUCHECK)+4);
+ mis->itemHeight = GetSystemMetrics(SM_CYSMICON)+2;
+ return TRUE;
+}
+
+//wparam=0
+//lparam=LPDRAWITEMSTRUCT
+int MO_DrawMenuItem( LPDRAWITEMSTRUCT dis )
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ if ( dis == NULL )
+ return FALSE;
+
+ EnterCriticalSection( &csMenuHook );
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )dis->itemData );
+ if ( pimi == NULL || pimi->iconId == -1 ) {
+ LeaveCriticalSection( &csMenuHook );
+ return FALSE;
+ }
+
+ int y = (dis->rcItem.bottom - dis->rcItem.top - GetSystemMetrics(SM_CYSMICON))/2+1;
+ if ( dis->itemState & ODS_SELECTED ) {
+ if ( dis->itemState & ODS_CHECKED ) {
+ RECT rc;
+ rc.left = 2; rc.right = GetSystemMetrics(SM_CXSMICON)+2;
+ rc.top = y; rc.bottom = rc.top+GetSystemMetrics(SM_CYSMICON)+2;
+ FillRect(dis->hDC, &rc, GetSysColorBrush( COLOR_HIGHLIGHT ));
+ ImageList_DrawEx( pimi->parent->m_hMenuIcons, pimi->iconId, dis->hDC, 2, y, 0, 0, CLR_NONE, CLR_DEFAULT, ILD_SELECTED );
+ }
+ else ImageList_DrawEx( pimi->parent->m_hMenuIcons, pimi->iconId, dis->hDC, 2, y, 0, 0, CLR_NONE, CLR_DEFAULT, ILD_FOCUS );
+ }
+ else {
+ if ( dis->itemState & ODS_CHECKED) {
+ RECT rc;
+ rc.left = 0; rc.right = GetSystemMetrics(SM_CXSMICON)+4;
+ rc.top = y-2; rc.bottom = rc.top + GetSystemMetrics(SM_CYSMICON)+4;
+ DrawEdge(dis->hDC,&rc,BDR_SUNKENOUTER,BF_RECT);
+ InflateRect(&rc,-1,-1);
+ COLORREF menuCol = GetSysColor(COLOR_MENU);
+ COLORREF hiliteCol = GetSysColor(COLOR_3DHIGHLIGHT);
+ HBRUSH hBrush = CreateSolidBrush(RGB((GetRValue(menuCol)+GetRValue(hiliteCol))/2,(GetGValue(menuCol)+GetGValue(hiliteCol))/2,(GetBValue(menuCol)+GetBValue(hiliteCol))/2));
+ FillRect(dis->hDC,&rc,GetSysColorBrush(COLOR_MENU));
+ DeleteObject(hBrush);
+ ImageList_DrawEx(pimi->parent->m_hMenuIcons,pimi->iconId,dis->hDC,2,y,0,0,CLR_NONE,GetSysColor(COLOR_MENU),ILD_BLEND50);
+ }
+ else ImageList_DrawEx(pimi->parent->m_hMenuIcons,pimi->iconId,dis->hDC,2,y,0,0,CLR_NONE,CLR_NONE,ILD_NORMAL);
+ }
+ LeaveCriticalSection( &csMenuHook );
+ return TRUE;
+}
+
+int MO_RemoveAllObjects()
+{
+ int i;
+ for ( i=0; i < g_menus.getCount(); i++ )
+ delete g_menus[i];
+
+ g_menus.destroy();
+ return 0;
+}
+
+//wparam=MenuObjectHandle
+INT_PTR MO_RemoveMenuObject(WPARAM wParam, LPARAM)
+{
+ int objidx;
+
+ if ( !bIsGenMenuInited) return -1;
+ EnterCriticalSection( &csMenuHook );
+
+ objidx = GetMenuObjbyId(( int )wParam );
+ if ( objidx == -1 ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ delete g_menus[ objidx ];
+ g_menus.remove( objidx );
+ LeaveCriticalSection( &csMenuHook );
+ return 0;
+}
+
+//wparam=MenuObjectHandle
+//lparam=vKey
+INT_PTR MO_ProcessHotKeys( HANDLE menuHandle, INT_PTR vKey )
+{
+ if ( !bIsGenMenuInited)
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+
+ int objidx = GetMenuObjbyId( (int)menuHandle );
+ if ( objidx == -1 ) {
+ LeaveCriticalSection( &csMenuHook );
+ return FALSE;
+ }
+
+ for ( PMO_IntMenuItem pimi = g_menus[objidx]->m_items.first; pimi != NULL; pimi = pimi->next ) {
+ if ( pimi->mi.hotKey == 0 ) continue;
+ if ( HIWORD(pimi->mi.hotKey) != vKey) continue;
+ if ( !(LOWORD(pimi->mi.hotKey) & MOD_ALT ) != !( GetKeyState( VK_MENU ) & 0x8000)) continue;
+ if ( !(LOWORD(pimi->mi.hotKey) & MOD_CONTROL ) != !( GetKeyState( VK_CONTROL ) & 0x8000)) continue;
+ if ( !(LOWORD(pimi->mi.hotKey) & MOD_SHIFT ) != !( GetKeyState( VK_SHIFT ) & 0x8000)) continue;
+
+ MO_ProcessCommand( pimi, 0 );
+ LeaveCriticalSection( &csMenuHook );
+ return TRUE;
+ }
+
+ LeaveCriticalSection( &csMenuHook );
+ return FALSE;
+}
+
+INT_PTR MO_GetProtoRootMenu(WPARAM wParam,LPARAM lParam)
+{
+ char* szProto = ( char* )wParam;
+ if ( szProto == NULL )
+ return 0;
+
+ if ( DBGetContactSettingByte( NULL, "CList", "MoveProtoMenus", FALSE ))
+ return ( INT_PTR )cli.pfnGetProtocolMenu( szProto );
+
+ int objidx = GetMenuObjbyId(( int )hMainMenuObject );
+ if ( objidx == -1 )
+ return NULL;
+
+ EnterCriticalSection( &csMenuHook );
+
+ TIntMenuObject* pmo = g_menus[objidx];
+ PMO_IntMenuItem p;
+ for ( p = pmo->m_items.first; p != NULL; p = p->next )
+ if ( !lstrcmpA( p->UniqName, szProto ))
+ break;
+
+ LeaveCriticalSection( &csMenuHook );
+ return ( INT_PTR )p;
+}
+
+//wparam=MenuItemHandle
+//lparam=PMO_MenuItem
+INT_PTR MO_GetMenuItem(WPARAM wParam,LPARAM lParam)
+{
+ PMO_MenuItem mi = (PMO_MenuItem)lParam;
+ if ( !bIsGenMenuInited || mi == NULL )
+ return -1;
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam);
+ EnterCriticalSection( &csMenuHook );
+ if ( !pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ *mi = pimi->mi;
+ LeaveCriticalSection( &csMenuHook );
+ return 0;
+}
+
+static int FindDefaultItem( PMO_IntMenuItem pimi, void* )
+{
+ if ( pimi->mi.flags & ( CMIF_GRAYED | CMIF_HIDDEN ))
+ return FALSE;
+
+ return ( pimi->mi.flags & CMIF_DEFAULT ) ? TRUE : FALSE;
+}
+
+INT_PTR MO_GetDefaultMenuItem(WPARAM wParam, LPARAM)
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam);
+ EnterCriticalSection( &csMenuHook );
+ if ( pimi )
+ pimi = MO_RecursiveWalkMenu( pimi, FindDefaultItem, NULL );
+
+ LeaveCriticalSection( &csMenuHook );
+ return ( INT_PTR )pimi;
+}
+
+//wparam MenuItemHandle
+//lparam PMO_MenuItem
+int MO_ModifyMenuItem( PMO_IntMenuItem menuHandle, PMO_MenuItem pmi )
+{
+ int oldflags;
+
+ if ( !bIsGenMenuInited || pmi == NULL || pmi->cbSize != sizeof( TMO_MenuItem ))
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )menuHandle );
+ if ( !pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ if ( pmi->flags & CMIM_NAME ) {
+ FreeAndNil(( void** )&pimi->mi.pszName );
+#if defined( _UNICODE )
+ if ( pmi->flags & CMIF_UNICODE )
+ pimi->mi.ptszName = mir_tstrdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : TranslateTS( pmi->ptszName ));
+ else {
+ if ( pmi->flags & CMIF_KEEPUNTRANSLATED ) {
+ int len = lstrlenA( pmi->pszName );
+ pimi->mi.ptszName = ( TCHAR* )mir_alloc( sizeof( TCHAR )*( len+1 ));
+ MultiByteToWideChar( CP_ACP, 0, pmi->pszName, -1, pimi->mi.ptszName, len+1 );
+ pimi->mi.ptszName[ len ] = 0;
+ }
+ else pimi->mi.ptszName = LangPackPcharToTchar( pmi->pszName );
+ }
+#else
+ pimi->mi.ptszName = mir_strdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : Translate( pmi->ptszName ));
+#endif
+ }
+ if ( pmi->flags & CMIM_FLAGS ) {
+ oldflags = pimi->mi.flags & ( CMIF_ROOTHANDLE | CMIF_ICONFROMICOLIB );
+ pimi->mi.flags = (pmi->flags & ~CMIM_ALL) | oldflags;
+ }
+ if ( (pmi->flags & CMIM_ICON) && !bIconsDisabled ) {
+ if ( pimi->mi.flags & CMIF_ICONFROMICOLIB ) {
+ HICON hIcon = IcoLib_GetIconByHandle( pmi->hIcolibItem, false );
+ if ( hIcon != NULL ) {
+ pimi->hIcolibItem = pmi->hIcolibItem;
+ pimi->iconId = ImageList_ReplaceIcon( pimi->parent->m_hMenuIcons, pimi->iconId, hIcon );
+ IconLib_ReleaseIcon( hIcon, 0 );
+ }
+ else pimi->iconId = -1, pimi->hIcolibItem = NULL;
+ }
+ else {
+ pimi->mi.hIcon = pmi->hIcon;
+ if ( pmi->hIcon != NULL )
+ pimi->iconId = ImageList_ReplaceIcon( pimi->parent->m_hMenuIcons, pimi->iconId, pmi->hIcon );
+ else
+ pimi->iconId = -1; //fixme, should remove old icon & shuffle all iconIds
+ }
+ if (pimi->hBmp) DeleteObject(pimi->hBmp); pimi->hBmp = NULL;
+ }
+
+ if ( pmi->flags & CMIM_HOTKEY )
+ pimi->mi.hotKey = pmi->hotKey;
+
+ LeaveCriticalSection( &csMenuHook );
+ return 0;
+}
+
+//wparam MenuItemHandle
+//return ownerdata useful to free ownerdata before delete menu item,
+//NULL on error.
+INT_PTR MO_MenuItemGetOwnerData(WPARAM wParam, LPARAM)
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam );
+ if ( !pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ INT_PTR res = ( INT_PTR )pimi->mi.ownerdata;
+ LeaveCriticalSection( &csMenuHook );
+ return res;
+}
+
+PMO_IntMenuItem MO_GetIntMenuItem(HGENMENU wParam)
+{
+ PMO_IntMenuItem result = ( PMO_IntMenuItem )wParam;
+ if ( result == NULL || wParam == (HGENMENU)0xffff1234 || wParam == HGENMENU_ROOT)
+ return NULL;
+
+ __try
+ {
+ if ( result->signature != MENUITEM_SIGNATURE )
+ result = NULL;
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER )
+ {
+ result = NULL;
+ }
+
+ return result;
+}
+
+//LOWORD(wparam) menuident
+
+static int FindMenuByCommand( PMO_IntMenuItem pimi, void* pCommand )
+{
+ return ( pimi->iCommand == (int)pCommand );
+}
+
+int MO_ProcessCommandBySubMenuIdent(int menuID, int command, LPARAM lParam)
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+
+ int objidx = GetMenuObjbyId( menuID );
+ if ( objidx == -1 ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ PMO_IntMenuItem pimi = MO_RecursiveWalkMenu( g_menus[objidx]->m_items.first, FindMenuByCommand, ( void* )command );
+ if ( pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return MO_ProcessCommand( pimi, lParam );
+ }
+
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+}
+
+INT_PTR MO_ProcessCommandByMenuIdent(WPARAM wParam,LPARAM lParam)
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+
+ for ( int i=0; i < g_menus.getCount(); i++ ) {
+ PMO_IntMenuItem pimi = MO_RecursiveWalkMenu( g_menus[i]->m_items.first, FindMenuByCommand, ( void* )wParam );
+ if ( pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return MO_ProcessCommand( pimi, lParam );
+ } }
+
+ LeaveCriticalSection( &csMenuHook );
+ return FALSE;
+}
+
+int MO_ProcessCommand( PMO_IntMenuItem aHandle, LPARAM lParam )
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem( aHandle );
+ if ( !pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ char *srvname = pimi->parent->ExecService;
+ void *ownerdata = pimi->mi.ownerdata;
+ LeaveCriticalSection( &csMenuHook );
+ CallService( srvname, ( WPARAM )ownerdata, lParam );
+ return 1;
+}
+
+int MO_SetOptionsMenuItem( PMO_IntMenuItem aHandle, int setting, INT_PTR value )
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem( aHandle );
+ if ( !pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ int res = -1;
+ __try
+ {
+ res = 1;
+ if ( setting == OPT_MENUITEMSETUNIQNAME ) {
+ mir_free( pimi->UniqName );
+ pimi->UniqName = mir_strdup(( char* )value );
+ }
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER ) {}
+
+ LeaveCriticalSection( &csMenuHook );
+ return res;
+}
+
+int MO_SetOptionsMenuObject( HANDLE handle, int setting, INT_PTR value )
+{
+ int pimoidx;
+ int res = 0;
+
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+ __try
+ {
+ pimoidx = GetMenuObjbyId( (int)handle );
+ res = pimoidx != -1;
+ if ( res ) {
+ TIntMenuObject* pmo = g_menus[pimoidx];
+
+ switch ( setting ) {
+ case OPT_MENUOBJECT_SET_ONADD_SERVICE:
+ FreeAndNil(( void** )&pmo->onAddService );
+ pmo->onAddService = mir_strdup(( char* )value );
+ break;
+
+ case OPT_MENUOBJECT_SET_FREE_SERVICE:
+ FreeAndNil(( void** )&pmo->FreeService );
+ pmo->FreeService = mir_strdup(( char* )value );
+ break;
+
+ case OPT_MENUOBJECT_SET_CHECK_SERVICE:
+ FreeAndNil(( void** )&pmo->CheckService );
+ pmo->CheckService = mir_strdup(( char* )value);
+ break;
+
+ case OPT_USERDEFINEDITEMS:
+ pmo->m_bUseUserDefinedItems = ( BOOL )value;
+ break;
+ }
+ }
+ }
+ __except( EXCEPTION_EXECUTE_HANDLER ) {}
+
+ LeaveCriticalSection( &csMenuHook );
+ return res;
+}
+
+//wparam=0;
+//lparam=PMenuParam;
+//result=MenuObjectHandle
+INT_PTR MO_CreateNewMenuObject(WPARAM, LPARAM lParam)
+{
+ PMenuParam pmp = ( PMenuParam )lParam;
+ if ( !bIsGenMenuInited || pmp == NULL )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+ TIntMenuObject* p = new TIntMenuObject();
+ p->id = NextObjectId++;
+ p->Name = mir_strdup( pmp->name );
+ p->CheckService = mir_strdup( pmp->CheckService );
+ p->ExecService = mir_strdup( pmp->ExecService );
+ p->m_hMenuIcons = ImageList_Create( GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
+ (IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16) | ILC_MASK, 15, 100 );
+ g_menus.insert(p);
+
+ LeaveCriticalSection( &csMenuHook );
+ return p->id;
+}
+
+//wparam=MenuItemHandle
+//lparam=0
+
+static int FreeMenuItem( TMO_IntMenuItem* pimi, void* )
+{
+ pimi->parent->freeItem( pimi );
+ return FALSE;
+}
+
+static int FindParent( TMO_IntMenuItem* pimi, void* p )
+{
+ return pimi->next == p;
+}
+
+INT_PTR MO_RemoveMenuItem(WPARAM wParam, LPARAM)
+{
+ EnterCriticalSection( &csMenuHook );
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )wParam );
+ if ( !pimi ) {
+ LeaveCriticalSection( &csMenuHook );
+ return -1;
+ }
+
+ if ( pimi->submenu.first ) {
+ MO_RecursiveWalkMenu( pimi->submenu.first, FreeMenuItem, NULL );
+ pimi->submenu.first = NULL;
+ }
+
+ PMO_IntMenuItem prev = MO_RecursiveWalkMenu( pimi->owner->first, FindParent, pimi );
+ if ( prev )
+ prev->next = pimi->next;
+ if ( pimi->owner->first == pimi )
+ pimi->owner->first = pimi->next;
+ if ( pimi->owner->last == pimi )
+ pimi->owner->last = prev;
+
+ pimi->signature = 0; // invalidate all future calls to that object
+ pimi->parent->freeItem( pimi );
+
+ LeaveCriticalSection( &csMenuHook );
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// we presume that this function is being called inside csMenuHook only
+
+static int PackMenuItems( PMO_IntMenuItem pimi, void* )
+{
+ pimi->iCommand = NextObjectMenuItemId++;
+ return FALSE;
+}
+
+static int GetNextObjectMenuItemId()
+{
+ // if menu commands are exausted, pack the menu array
+ if ( NextObjectMenuItemId >= CLISTMENUIDMAX ) {
+ NextObjectMenuItemId = CLISTMENUIDMIN;
+ for ( int i=0; i < g_menus.getCount(); i++ )
+ MO_RecursiveWalkMenu( g_menus[i]->m_items.first, PackMenuItems, NULL );
+ }
+
+ return NextObjectMenuItemId++;
+}
+
+//wparam=MenuObjectHandle
+//lparam=PMO_MenuItem
+//return MenuItemHandle
+PMO_IntMenuItem MO_AddNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi )
+{
+ if ( !bIsGenMenuInited || pmi == NULL || pmi->cbSize != sizeof( TMO_MenuItem ))
+ return NULL;
+
+ //old mode
+ if ( !( pmi->flags & CMIF_ROOTHANDLE ))
+ return MO_AddOldNewMenuItem( menuobjecthandle, pmi );
+
+ EnterCriticalSection( &csMenuHook );
+ int objidx = GetMenuObjbyId( (int)menuobjecthandle );
+ if ( objidx == -1 ) {
+ LeaveCriticalSection( &csMenuHook );
+ return NULL;
+ }
+
+ TIntMenuObject* pmo = g_menus[objidx];
+
+ TMO_IntMenuItem* p = ( TMO_IntMenuItem* )mir_calloc( sizeof( TMO_IntMenuItem ));
+ p->parent = pmo;
+ p->signature = MENUITEM_SIGNATURE;
+ p->iCommand = GetNextObjectMenuItemId();
+ p->mi = *pmi;
+ p->iconId = -1;
+ p->OverrideShow = TRUE;
+ p->originalPosition = pmi->position;
+ #if defined( _UNICODE )
+ if ( pmi->flags & CMIF_UNICODE )
+ p->mi.ptszName = mir_tstrdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : TranslateTS( pmi->ptszName ));
+ else {
+ if ( pmi->flags & CMIF_KEEPUNTRANSLATED )
+ p->mi.ptszName = mir_a2u(pmi->pszName);
+ else
+ p->mi.ptszName = LangPackPcharToTchar( pmi->pszName );
+ }
+ #else
+ p->mi.ptszName = mir_strdup(( pmi->flags & CMIF_KEEPUNTRANSLATED ) ? pmi->ptszName : Translate( pmi->ptszName ));
+ #endif
+
+ if ( pmi->hIcon != NULL && !bIconsDisabled ) {
+ if ( pmi->flags & CMIF_ICONFROMICOLIB ) {
+ HICON hIcon = IcoLib_GetIconByHandle( pmi->hIcolibItem, false );
+ p->iconId = ImageList_AddIcon( pmo->m_hMenuIcons, hIcon );
+ p->hIcolibItem = pmi->hIcolibItem;
+ IconLib_ReleaseIcon( hIcon, 0 );
+ }
+ else {
+ HANDLE hIcolibItem = IcoLib_IsManaged( pmi->hIcon );
+ if ( hIcolibItem ) {
+ p->iconId = ImageList_AddIcon( pmo->m_hMenuIcons, pmi->hIcon );
+ p->hIcolibItem = hIcolibItem;
+ }
+ else p->iconId = ImageList_AddIcon( pmo->m_hMenuIcons, pmi->hIcon );
+ } }
+
+ if ( p->mi.root == HGENMENU_ROOT )
+ p->mi.root = NULL;
+
+ PMO_IntMenuItem pRoot = ( p->mi.root != NULL ) ? MO_GetIntMenuItem( p->mi.root ) : NULL;
+ if ( pRoot )
+ p->owner = &pRoot->submenu;
+ else
+ p->owner = &pmo->m_items;
+
+ if ( !p->owner->first )
+ p->owner->first = p;
+ if ( p->owner->last )
+ p->owner->last->next = p;
+ p->owner->last = p;
+
+ LeaveCriticalSection( &csMenuHook );
+ return p;
+}
+
+//wparam=MenuObjectHandle
+//lparam=PMO_MenuItem
+
+int FindRoot( PMO_IntMenuItem pimi, void* param )
+{
+ if ( pimi->mi.pszName != NULL )
+ if ( pimi->submenu.first && !_tcscmp( pimi->mi.ptszName, ( TCHAR* )param ))
+ return TRUE;
+
+ return FALSE;
+}
+
+PMO_IntMenuItem MO_AddOldNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi )
+{
+ if ( !bIsGenMenuInited || pmi == NULL )
+ return NULL;
+
+ int objidx = GetMenuObjbyId( (int)menuobjecthandle );
+ if ( objidx == -1 )
+ return NULL;
+
+ if ( pmi->cbSize != sizeof( TMO_MenuItem ))
+ return NULL;
+
+ if ( pmi->flags & CMIF_ROOTHANDLE )
+ return NULL;
+
+ //is item with popup or not
+ if ( pmi->root == 0 ) {
+ //yes,this without popup
+ pmi->root = NULL; //first level
+ }
+ else { // no,search for needed root and create it if need
+ TCHAR* tszRoot;
+#if defined( _UNICODE )
+ if ( pmi->flags & CMIF_UNICODE )
+ tszRoot = mir_tstrdup(TranslateTS(( TCHAR* )pmi->root ));
+ else
+ tszRoot = LangPackPcharToTchar(( char* )pmi->root );
+#else
+ tszRoot = mir_tstrdup(TranslateTS(( TCHAR* )pmi->root ));
+#endif
+
+ PMO_IntMenuItem oldroot = MO_RecursiveWalkMenu( g_menus[objidx]->m_items.first, FindRoot, tszRoot );
+ mir_free( tszRoot );
+
+ if ( oldroot == NULL ) {
+ //not found,creating root
+ TMO_MenuItem tmi = { 0 };
+ tmi = *pmi;
+ tmi.flags |= CMIF_ROOTHANDLE;
+ tmi.ownerdata = 0;
+ tmi.root = NULL;
+ //copy pszPopupName
+ tmi.ptszName = ( TCHAR* )pmi->root;
+ if (( oldroot = MO_AddNewMenuItem( menuobjecthandle, &tmi )) != NULL )
+ MO_SetOptionsMenuItem( oldroot, OPT_MENUITEMSETUNIQNAME, (INT_PTR)pmi->root );
+ }
+ pmi->root = oldroot;
+
+ //popup will be created in next commands
+ }
+ pmi->flags |= CMIF_ROOTHANDLE;
+ //add popup(root allready exists)
+ return MO_AddNewMenuItem( menuobjecthandle, pmi );
+}
+
+static int WhereToPlace( HMENU hMenu, PMO_MenuItem mi )
+{
+ MENUITEMINFO mii = { 0 };
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ mii.fMask = MIIM_SUBMENU | MIIM_DATA;
+ for ( int i=GetMenuItemCount( hMenu )-1; i >= 0; i-- ) {
+ GetMenuItemInfo( hMenu, i, TRUE, &mii );
+ if ( mii.fType != MFT_SEPARATOR ) {
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData);
+ if ( pimi != NULL )
+ if ( pimi->mi.position <= mi->position )
+ return i+1;
+ } }
+
+ return 0;
+}
+
+static void InsertMenuItemWithSeparators(HMENU hMenu, int uItem, MENUITEMINFO *lpmii)
+{
+ int needSeparator = 0;
+ MENUITEMINFO mii;
+
+ PMO_IntMenuItem pimi = MO_GetIntMenuItem(( HGENMENU )lpmii->dwItemData );
+ if ( pimi == NULL )
+ return;
+
+ int thisItemPosition = pimi->mi.position;
+
+ ZeroMemory( &mii, sizeof( mii ));
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ //check for separator before
+ if ( uItem ) {
+ mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE;
+ GetMenuItemInfo( hMenu, uItem-1, TRUE, &mii );
+ pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData );
+ if ( pimi != NULL ) {
+ if ( mii.fType == MFT_SEPARATOR )
+ needSeparator = 0;
+ else
+ needSeparator = ( pimi->mi.position / SEPARATORPOSITIONINTERVAL ) != thisItemPosition / SEPARATORPOSITIONINTERVAL;
+ }
+ if ( needSeparator) {
+ //but might be supposed to be after the next one instead
+ mii.fType = 0;
+ if ( uItem < GetMenuItemCount( hMenu )) {
+ mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE;
+ GetMenuItemInfo( hMenu, uItem, TRUE, &mii );
+ }
+ if ( mii.fType != MFT_SEPARATOR) {
+ mii.fMask = MIIM_TYPE;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem( hMenu, uItem, TRUE, &mii );
+ }
+ uItem++;
+ } }
+
+ //check for separator after
+ if ( uItem < GetMenuItemCount( hMenu )) {
+ mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE;
+ mii.cch = 0;
+ GetMenuItemInfo( hMenu, uItem, TRUE, &mii );
+ pimi = MO_GetIntMenuItem(( HGENMENU )mii.dwItemData );
+ if ( pimi != NULL ) {
+ if ( mii.fType == MFT_SEPARATOR )
+ needSeparator=0;
+ else
+ needSeparator = pimi->mi.position / SEPARATORPOSITIONINTERVAL != thisItemPosition / SEPARATORPOSITIONINTERVAL;
+ }
+ if ( needSeparator) {
+ mii.fMask = MIIM_TYPE;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem( hMenu, uItem, TRUE, &mii );
+ } }
+
+ if ( uItem == GetMenuItemCount( hMenu )-1 ) {
+ TCHAR str[32];
+ mii.fMask = MIIM_SUBMENU | MIIM_DATA | MIIM_TYPE;
+ mii.dwTypeData = str;
+ mii.cch = SIZEOF( str );
+ GetMenuItemInfo( hMenu, uItem, TRUE, &mii );
+ }
+
+ // create local copy *lpmii so we can change some flags
+ MENUITEMINFO mii_copy = *lpmii;
+ lpmii = &mii_copy;
+
+ if (( GetMenuItemCount( hMenu ) % 35 ) == 33 /* will be 34 after addition :) */ && pimi != NULL )
+ if ( pimi->mi.root != NULL ) {
+ if ( !( lpmii->fMask & MIIM_FTYPE ))
+ lpmii->fType = 0;
+ lpmii->fMask |= MIIM_FTYPE;
+ lpmii->fType |= MFT_MENUBARBREAK;
+ }
+
+ InsertMenuItem( hMenu, uItem, TRUE, lpmii );
+}
+
+//wparam started hMenu
+//lparam ListParam*
+//result hMenu
+INT_PTR MO_BuildMenu(WPARAM wParam,LPARAM lParam)
+{
+ if ( !bIsGenMenuInited )
+ return -1;
+
+ EnterCriticalSection( &csMenuHook );
+
+ ListParam *lp = ( ListParam* )lParam;
+ int pimoidx = GetMenuObjbyId( (int)lp->MenuObjectHandle );
+ if ( pimoidx == -1 ) {
+ LeaveCriticalSection( &csMenuHook );
+ return 0;
+ }
+
+ #if defined( _DEBUG )
+ // DumpMenuItem( g_menus[pimoidx]->m_items.first );
+ #endif
+
+ INT_PTR res = (INT_PTR)BuildRecursiveMenu(( HMENU )wParam, g_menus[pimoidx]->m_items.first, ( ListParam* )lParam );
+ LeaveCriticalSection( &csMenuHook );
+ return res;
+}
+
+#ifdef _DEBUG
+#define PUTPOSITIONSONMENU
+#endif
+
+void GetMenuItemName( PMO_IntMenuItem pMenuItem, char* pszDest, size_t cbDestSize )
+{
+ if ( pMenuItem->UniqName )
+ mir_snprintf( pszDest, cbDestSize, "{%s}", pMenuItem->UniqName );
+ else if (pMenuItem->mi.flags & CMIF_UNICODE) {
+ char* name = mir_t2a( pMenuItem->mi.ptszName );
+ mir_snprintf( pszDest, cbDestSize, "{%s}", name );
+ mir_free(name);
+ }
+ else
+ mir_snprintf( pszDest, cbDestSize, "{%s}", pMenuItem->mi.pszName );
+}
+
+HMENU BuildRecursiveMenu(HMENU hMenu, PMO_IntMenuItem pRootMenu, ListParam *param)
+{
+ if ( param == NULL || pRootMenu == NULL )
+ return NULL;
+
+ TIntMenuObject* pmo = pRootMenu->parent;
+
+ int rootlevel = ( param->rootlevel == -1 ) ? 0 : param->rootlevel;
+
+ ListParam localparam = *param;
+
+ while ( rootlevel == 0 && GetMenuItemCount( hMenu ) > 0 )
+ DeleteMenu( hMenu, 0, MF_BYPOSITION );
+
+ for ( PMO_IntMenuItem pmi = pRootMenu; pmi != NULL; pmi = pmi->next ) {
+ PMO_MenuItem mi = &pmi->mi;
+ if ( mi->cbSize != sizeof( TMO_MenuItem ))
+ continue;
+
+ if ( mi->flags & CMIF_HIDDEN )
+ continue;
+
+ if ( pmo->CheckService != NULL ) {
+ TCheckProcParam CheckParam;
+ CheckParam.lParam = param->lParam;
+ CheckParam.wParam = param->wParam;
+ CheckParam.MenuItemOwnerData = mi->ownerdata;
+ CheckParam.MenuItemHandle = pmi;
+ if ( CallService( pmo->CheckService, ( WPARAM )&CheckParam, 0 ) == FALSE )
+ continue;
+ }
+
+ /**************************************/
+ if ( rootlevel == 0 && mi->root == NULL && pmo->m_bUseUserDefinedItems ) {
+ char DBString[256];
+ DBVARIANT dbv = { 0 };
+ int pos;
+ char MenuNameItems[256];
+ mir_snprintf(MenuNameItems, SIZEOF(MenuNameItems), "%s_Items", pmo->Name);
+
+ char menuItemName[256];
+ GetMenuItemName( pmi, menuItemName, sizeof( menuItemName ));
+
+ // check if it visible
+ mir_snprintf( DBString, SIZEOF(DBString), "%s_visible", menuItemName );
+ if ( DBGetContactSettingByte( NULL, MenuNameItems, DBString, -1 ) == -1 )
+ DBWriteContactSettingByte( NULL, MenuNameItems, DBString, 1 );
+
+ pmi->OverrideShow = TRUE;
+ if ( !DBGetContactSettingByte( NULL, MenuNameItems, DBString, 1 )) {
+ pmi->OverrideShow = FALSE;
+ continue; // find out what value to return if not getting added
+ }
+
+ // mi.pszName
+ mir_snprintf( DBString, SIZEOF(DBString), "%s_name", menuItemName );
+ if ( !DBGetContactSettingTString( NULL, MenuNameItems, DBString, &dbv )) {
+ if ( _tcslen( dbv.ptszVal ) > 0 ) {
+ if ( pmi->CustomName ) mir_free( pmi->CustomName );
+ pmi->CustomName = mir_tstrdup( dbv.ptszVal );
+ }
+ DBFreeVariant( &dbv );
+ }
+
+ mir_snprintf( DBString, SIZEOF(DBString), "%s_pos", menuItemName );
+ if (( pos = DBGetContactSettingDword( NULL, MenuNameItems, DBString, -1 )) == -1 ) {
+ DBWriteContactSettingDword( NULL, MenuNameItems, DBString, mi->position );
+ if ( pmi->submenu.first )
+ mi->position = 0;
+ }
+ else mi->position = pos;
+ }
+
+ /**************************************/
+
+ if ( rootlevel != (int)pmi->mi.root )
+ continue;
+
+ MENUITEMINFO mii = { 0 };
+ mii.dwItemData = ( LPARAM )pmi;
+
+ int i = WhereToPlace( hMenu, mi );
+
+ if ( !IsWinVer98Plus()) {
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID;
+ mii.fType = MFT_STRING;
+ }
+ else {
+ mii.cbSize = sizeof( mii );
+ mii.fMask = MIIM_DATA | MIIM_ID | MIIM_STRING;
+ if ( pmi->iconId != -1 ) {
+ mii.fMask |= MIIM_BITMAP;
+ if (IsWinVerVistaPlus() && isThemeActive()) {
+ if (pmi->hBmp == NULL)
+ pmi->hBmp = ConvertIconToBitmap(NULL, pmi->parent->m_hMenuIcons, pmi->iconId);
+ mii.hbmpItem = pmi->hBmp;
+ }
+ else
+ mii.hbmpItem = HBMMENU_CALLBACK;
+ }
+ }
+
+ mii.fMask |= MIIM_STATE;
+ mii.fState = (( pmi->mi.flags & CMIF_GRAYED ) ? MFS_GRAYED : MFS_ENABLED );
+ mii.fState |= (( pmi->mi.flags & CMIF_CHECKED) ? MFS_CHECKED : MFS_UNCHECKED );
+ if ( pmi->mi.flags & CMIF_DEFAULT ) mii.fState |= MFS_DEFAULT;
+
+ mii.dwTypeData = ( pmi->CustomName ) ? pmi->CustomName : mi->ptszName;
+
+ // it's a submenu
+ if ( pmi->submenu.first ) {
+ mii.fMask |= MIIM_SUBMENU;
+ mii.hSubMenu = CreatePopupMenu();
+
+ #ifdef PUTPOSITIONSONMENU
+ if ( GetKeyState(VK_CONTROL) & 0x8000) {
+ TCHAR str[256];
+ mir_sntprintf( str, SIZEOF(str), _T( "%s (%d,id %x)" ), mi->pszName, mi->position, mii.dwItemData );
+ mii.dwTypeData = str;
+ }
+ #endif
+
+ InsertMenuItemWithSeparators( hMenu, i, &mii);
+ localparam.rootlevel = LPARAM( pmi );
+ BuildRecursiveMenu( mii.hSubMenu, pmi->submenu.first, &localparam );
+ }
+ else {
+ mii.wID = pmi->iCommand;
+
+ #ifdef PUTPOSITIONSONMENU
+ if ( GetKeyState(VK_CONTROL) & 0x8000) {
+ TCHAR str[256];
+ mir_sntprintf( str, SIZEOF(str), _T("%s (%d,id %x)"), mi->pszName, mi->position, mii.dwItemData );
+ mii.dwTypeData = str;
+ }
+ #endif
+
+ if ( pmo->onAddService != NULL )
+ if ( CallService( pmo->onAddService, ( WPARAM )&mii, ( LPARAM )pmi ) == FALSE )
+ continue;
+
+ InsertMenuItemWithSeparators( hMenu, i, &mii );
+ } }
+
+ return hMenu;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// iconlib in menu
+
+static int MO_ReloadIcon( PMO_IntMenuItem pmi, void* )
+{
+ if ( pmi->hIcolibItem ) {
+ HICON newIcon = IcoLib_GetIconByHandle( pmi->hIcolibItem, false );
+ if ( newIcon )
+ ImageList_ReplaceIcon( pmi->parent->m_hMenuIcons, pmi->iconId, newIcon );
+
+ IconLib_ReleaseIcon(newIcon,0);
+ }
+
+ return FALSE;
+}
+
+int OnIconLibChanges(WPARAM, LPARAM)
+{
+ EnterCriticalSection( &csMenuHook );
+ for ( int mo=0; mo < g_menus.getCount(); mo++ )
+ if ( (int)hStatusMenuObject != g_menus[mo]->id ) //skip status menu
+ MO_RecursiveWalkMenu( g_menus[mo]->m_items.first, MO_ReloadIcon, 0 );
+
+ LeaveCriticalSection( &csMenuHook );
+
+ cli.pfnReloadProtoMenus();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+//
+
+static int MO_RegisterIcon( PMO_IntMenuItem pmi, void* )
+{
+ char *uname, *descr;
+ uname = pmi->UniqName;
+ if ( uname == NULL )
+ #ifdef UNICODE
+ uname = mir_u2a(pmi->CustomName);
+ descr = mir_u2a(pmi->mi.ptszName);
+ #else
+ uname = pmi->CustomName;
+ descr = pmi->mi.pszName;
+ #endif
+
+ if ( !uname && !descr )
+ return FALSE;
+
+ if ( !pmi->hIcolibItem ) {
+ HICON hIcon = ImageList_GetIcon( pmi->parent->m_hMenuIcons, pmi->iconId, 0 );
+ char* buf = NEWSTR_ALLOCA( descr );
+
+ char sectionName[256], iconame[256];
+ mir_snprintf( sectionName, sizeof(sectionName), "Menu Icons/%s", pmi->parent->Name );
+
+ // remove '&'
+ char* start = buf;
+ while ( start ) {
+ if (( start = strchr( start, '&' )) == NULL )
+ break;
+
+ memmove(start,start+1,strlen(start+1)+1);
+ if (*start!='\0') start++;
+ else break;
+ }
+
+ mir_snprintf(iconame, sizeof(iconame), "genmenu_%s_%s", pmi->parent->Name, uname && *uname ? uname : descr);
+
+ SKINICONDESC sid={0};
+ sid.cbSize = sizeof(sid);
+ sid.cx = 16;
+ sid.cy = 16;
+ sid.pszSection = sectionName;
+ sid.pszName = iconame;
+ sid.pszDefaultFile = NULL;
+ sid.pszDescription = buf;
+ sid.hDefaultIcon = hIcon;
+ pmi->hIcolibItem = ( HANDLE )CallService(MS_SKIN2_ADDICON, 0, (LPARAM)&sid);
+
+ Safe_DestroyIcon( hIcon );
+ if ( hIcon = ( HICON )CallService( MS_SKIN2_GETICON, 0, (LPARAM)iconame )) {
+ ImageList_ReplaceIcon( pmi->parent->m_hMenuIcons, pmi->iconId, hIcon );
+ IconLib_ReleaseIcon( hIcon, 0 );
+ } }
+
+ #ifdef UNICODE
+ if ( !pmi->UniqName )
+ mir_free( uname );
+ mir_free( descr );
+ #endif
+
+ return FALSE;
+}
+
+int RegisterAllIconsInIconLib()
+{
+ //register all icons
+ for ( int mo=0; mo < g_menus.getCount(); mo++ ) {
+ if ( (int)hStatusMenuObject == g_menus[mo]->id ) //skip status menu
+ continue;
+
+ MO_RecursiveWalkMenu( g_menus[mo]->m_items.first, MO_RegisterIcon, 0 );
+ }
+
+ return 0;
+}
+
+int TryProcessDoubleClick( HANDLE hContact )
+{
+ int iMenuID = GetMenuObjbyId( (int)hContactMenuObject );
+ if ( iMenuID != -1 ) {
+ NotifyEventHooks(hPreBuildContactMenuEvent,(WPARAM)hContact,0);
+
+ PMO_IntMenuItem pimi = ( PMO_IntMenuItem )MO_GetDefaultMenuItem(( WPARAM )g_menus[ iMenuID ]->m_items.first, 0 );
+ if ( pimi != NULL ) {
+ MO_ProcessCommand( pimi, ( LPARAM )hContact );
+ return 0;
+ } }
+
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Static services
+
+int posttimerid;
+
+static VOID CALLBACK PostRegisterIcons( HWND, UINT, UINT_PTR, DWORD )
+{
+ KillTimer( 0, posttimerid );
+ RegisterAllIconsInIconLib();
+}
+
+static int OnModulesLoaded(WPARAM, LPARAM)
+{
+ posttimerid = SetTimer(( HWND )NULL, 0, 5, ( TIMERPROC )PostRegisterIcons );
+ HookEvent(ME_SKIN2_ICONSCHANGED,OnIconLibChanges);
+ return 0;
+}
+
+static INT_PTR SRVMO_SetOptionsMenuObject( WPARAM, LPARAM lParam)
+{
+ lpOptParam lpop = ( lpOptParam )lParam;
+ if ( lpop == NULL )
+ return 0;
+
+ return MO_SetOptionsMenuObject( lpop->Handle, lpop->Setting, lpop->Value );
+}
+
+static INT_PTR SRVMO_SetOptionsMenuItem( WPARAM, LPARAM lParam)
+{
+ lpOptParam lpop = ( lpOptParam )lParam;
+ if ( lpop == NULL )
+ return 0;
+
+ return MO_SetOptionsMenuItem(( PMO_IntMenuItem )lpop->Handle, lpop->Setting, lpop->Value );
+}
+
+int InitGenMenu()
+{
+ InitializeCriticalSection( &csMenuHook );
+ CreateServiceFunction( MO_BUILDMENU, MO_BuildMenu );
+
+ CreateServiceFunction( MO_PROCESSCOMMAND, ( MIRANDASERVICE )MO_ProcessCommand );
+ CreateServiceFunction( MO_CREATENEWMENUOBJECT, MO_CreateNewMenuObject );
+ CreateServiceFunction( MO_REMOVEMENUITEM, MO_RemoveMenuItem );
+ CreateServiceFunction( MO_ADDNEWMENUITEM, ( MIRANDASERVICE )MO_AddNewMenuItem );
+ CreateServiceFunction( MO_MENUITEMGETOWNERDATA, MO_MenuItemGetOwnerData );
+ CreateServiceFunction( MO_MODIFYMENUITEM, ( MIRANDASERVICE )MO_ModifyMenuItem );
+ CreateServiceFunction( MO_GETMENUITEM, MO_GetMenuItem );
+ CreateServiceFunction( MO_GETDEFAULTMENUITEM, MO_GetDefaultMenuItem );
+ CreateServiceFunction( MO_PROCESSCOMMANDBYMENUIDENT, MO_ProcessCommandByMenuIdent );
+ CreateServiceFunction( MO_PROCESSHOTKEYS, ( MIRANDASERVICE )MO_ProcessHotKeys );
+ CreateServiceFunction( MO_REMOVEMENUOBJECT, MO_RemoveMenuObject );
+ CreateServiceFunction( MO_GETPROTOROOTMENU, MO_GetProtoRootMenu );
+
+ CreateServiceFunction( MO_SETOPTIONSMENUOBJECT, SRVMO_SetOptionsMenuObject );
+ CreateServiceFunction( MO_SETOPTIONSMENUITEM, SRVMO_SetOptionsMenuItem );
+
+ bIconsDisabled = DBGetContactSettingByte(NULL, "CList", "DisableMenuIcons", 0) != 0;
+
+ EnterCriticalSection( &csMenuHook );
+ bIsGenMenuInited = true;
+ LeaveCriticalSection( &csMenuHook );
+
+ HookEvent( ME_SYSTEM_MODULESLOADED, OnModulesLoaded );
+ HookEvent( ME_OPT_INITIALISE, GenMenuOptInit );
+ return 0;
+}
+
+int UnitGenMenu()
+{
+ if ( bIsGenMenuInited ) {
+ EnterCriticalSection( &csMenuHook );
+ MO_RemoveAllObjects();
+ bIsGenMenuInited=false;
+
+ LeaveCriticalSection( &csMenuHook );
+ DeleteCriticalSection(&csMenuHook);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+TIntMenuObject::TIntMenuObject()
+{
+}
+
+TIntMenuObject::~TIntMenuObject()
+{
+ MO_RecursiveWalkMenu( m_items.first, FreeMenuItem, NULL );
+
+ FreeAndNil(( void** )&FreeService );
+ FreeAndNil(( void** )&onAddService );
+ FreeAndNil(( void** )&CheckService );
+ FreeAndNil(( void** )&ExecService );
+ FreeAndNil(( void** )&Name );
+
+ ImageList_Destroy(m_hMenuIcons);
+}
+
+void TIntMenuObject::freeItem( TMO_IntMenuItem* p )
+{
+ if ( FreeService )
+ CallService( FreeService, ( WPARAM )p, ( LPARAM )p->mi.ownerdata );
+
+ FreeAndNil(( void** )&p->mi.pszName );
+ FreeAndNil(( void** )&p->UniqName );
+ FreeAndNil(( void** )&p->CustomName );
+ if ( p->hBmp ) DeleteObject( p->hBmp );
+ mir_free( p );
+}
diff --git a/src/modules/clist/genmenu.h b/src/modules/clist/genmenu.h
new file mode 100644
index 0000000000..63c665b940
--- /dev/null
+++ b/src/modules/clist/genmenu.h
@@ -0,0 +1,146 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#ifndef GENMENU_H
+#define GENMENU_H
+//general menu object module
+#include "m_genmenu.h"
+
+/* genmenu structs */
+
+#define MENUITEM_SIGNATURE 0xDEADBEEF
+
+typedef struct
+{
+ struct _tagIntMenuItem *first, // first element of submenu, or NULL
+ *last; // last element of submenu, or NULL
+}
+ TMO_LinkedList;
+
+typedef struct _tagIntMenuItem
+{
+ DWORD signature;
+ int iCommand;
+ int iconId; // icon index in the section's image list
+ TMO_MenuItem mi; // user-defined data
+ BOOL OverrideShow;
+ char* UniqName; // unique name
+ TCHAR* CustomName;
+ HANDLE hIcolibItem; // handle of iconlib item
+ HBITMAP hBmp;
+ int originalPosition;
+
+ struct _tagIntMenuItem *next; // next item in list
+ struct TIntMenuObject *parent;
+ TMO_LinkedList *owner;
+ TMO_LinkedList submenu;
+}
+ TMO_IntMenuItem,*PMO_IntMenuItem;
+
+struct TIntMenuObject
+{
+ TIntMenuObject();
+ ~TIntMenuObject();
+
+ __inline void* operator new( size_t size )
+ { return mir_calloc( size );
+ }
+ __inline void operator delete( void* p )
+ { mir_free( p );
+ }
+
+ char* Name;
+ int id;
+
+ //ExecService
+ //LPARAM lParam;//owner data
+ //WPARAM wParam;//allways lparam from winproc
+ char *ExecService;
+
+ //CheckService called when building menu
+ //return false to skip item.
+ //LPARAM lParam;//0
+ //WPARAM wParam;//CheckParam
+ char *CheckService;//analog to check_proc
+
+ //LPARAM lParam;//ownerdata
+ //WPARAM wParam;//menuitemhandle
+ char *FreeService;//callback service used to free ownerdata for menuitems
+
+ //LPARAM lParam;//MENUITEMINFO filled with all needed data
+ //WPARAM wParam;//menuitemhandle
+ char *onAddService;//called just before add MENUITEMINFO to hMenu
+
+ TMO_LinkedList m_items;
+ HIMAGELIST m_hMenuIcons;
+ BOOL m_bUseUserDefinedItems;
+
+ void freeItem( TMO_IntMenuItem* );
+};
+
+extern LIST<TIntMenuObject> g_menus;
+
+#define SEPARATORPOSITIONINTERVAL 100000
+
+//internal usage
+HMENU BuildRecursiveMenu(HMENU hMenu, PMO_IntMenuItem, ListParam *param);
+void GetMenuItemName( PMO_IntMenuItem pMenuItem, char* pszDest, size_t cbDestSize );
+
+PMO_IntMenuItem MO_GetIntMenuItem( HGENMENU );
+
+PMO_IntMenuItem MO_AddNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi );
+PMO_IntMenuItem MO_AddOldNewMenuItem( HANDLE menuobjecthandle, PMO_MenuItem pmi );
+
+int MO_DrawMenuItem( LPDRAWITEMSTRUCT dis );
+int MO_MeasureMenuItem( LPMEASUREITEMSTRUCT mis );
+int MO_ModifyMenuItem( PMO_IntMenuItem menuHandle, PMO_MenuItem pmiparam );
+int MO_ProcessCommand( PMO_IntMenuItem pimi, LPARAM lParam );
+INT_PTR MO_ProcessHotKeys( HANDLE menuHandle, INT_PTR vKey );
+int MO_SetOptionsMenuItem( PMO_IntMenuItem menuobjecthandle, int setting, INT_PTR value );
+int MO_SetOptionsMenuObject( HANDLE menuobjecthandle, int setting, INT_PTR value );
+
+INT_PTR MO_ProcessCommandByMenuIdent(WPARAM wParam,LPARAM lParam);
+int MO_ProcessCommandBySubMenuIdent(int menuID, int command, LPARAM lParam);
+
+// function returns TRUE if the walk should be immediately stopped
+typedef int ( *pfnWalkFunc )( PMO_IntMenuItem, void* );
+
+// returns the item, on which pfnWalkFunc returned TRUE
+PMO_IntMenuItem MO_RecursiveWalkMenu( PMO_IntMenuItem, pfnWalkFunc, void* );
+
+//general stuff
+int InitGenMenu();
+int UnitGenMenu();
+
+int FindRoot( PMO_IntMenuItem pimi, void* param );
+
+TMO_IntMenuItem * GetMenuItemByGlobalID(int globalMenuID);
+BOOL FindMenuHanleByGlobalID(HMENU hMenu, int globalID, struct _MenuItemHandles * dat); //GenMenu.c
+
+int GenMenuOptInit(WPARAM wParam, LPARAM lParam);
+int GetMenuObjbyId(const int id);
+int GetMenuItembyId(const int objpos,const int id);
+INT_PTR MO_GetMenuItem(WPARAM wParam,LPARAM lParam);
+void FreeAndNil(void **p);
+static int RemoveFromList(int pos,void **lpList,int *ListElemCount,int ElemSize);
+static int RemoveFromList(int pos,void **lpList,int *ListElemCount,int ElemSize);
+#endif
diff --git a/src/modules/clist/genmenuopt.cpp b/src/modules/clist/genmenuopt.cpp
new file mode 100644
index 0000000000..7fefbf8dcb
--- /dev/null
+++ b/src/modules/clist/genmenuopt.cpp
@@ -0,0 +1,870 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "genmenu.h"
+
+#define STR_SEPARATOR _T("-----------------------------------")
+
+extern bool bIconsDisabled;
+extern int DefaultImageListColorDepth;
+long handleCustomDraw(HWND hWndTreeView, LPNMTVCUSTOMDRAW pNMTVCD);
+void RebuildProtoMenus( int );
+
+struct OrderData
+{
+ int dragging;
+ HTREEITEM hDragItem;
+ int iInitMenuValue;
+};
+
+typedef struct tagMenuItemOptData
+{
+ TCHAR* name;
+ TCHAR* defname;
+ char* uniqname;
+
+ int pos;
+ boolean show;
+ DWORD isSelected;
+ int id;
+
+ PMO_IntMenuItem pimi;
+}
+ MenuItemOptData,*lpMenuItemOptData;
+
+static BOOL GetCurrentMenuObjectID(HWND hwndDlg, int* result)
+{
+ TVITEM tvi;
+ HWND hTree = GetDlgItem(hwndDlg, IDC_MENUOBJECTS);
+ HTREEITEM hti = TreeView_GetSelection(hTree);
+ if ( hti == NULL )
+ return FALSE;
+
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hti;
+ TreeView_GetItem(hTree, &tvi);
+ *result = (int)tvi.lParam;
+ return TRUE;
+}
+
+static int SaveTree(HWND hwndDlg)
+{
+ TVITEM tvi;
+ int count;
+ TCHAR idstr[100];
+ char menuItemName[256], DBString[256], MenuNameItems[256];
+ int menupos;
+ int MenuObjectId, runtimepos;
+ TIntMenuObject* pimo;
+ MenuItemOptData* iod;
+ HWND hTree = GetDlgItem( hwndDlg, IDC_MENUITEMS );
+
+ if ( !GetCurrentMenuObjectID( hwndDlg, &MenuObjectId ))
+ return 0;
+
+ tvi.hItem = TreeView_GetRoot( hTree );
+ tvi.cchTextMax = 99;
+ tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_HANDLE;
+ tvi.pszText = idstr;
+ count = 0;
+
+ menupos = GetMenuObjbyId( MenuObjectId );
+ if ( menupos == -1 )
+ return -1;
+
+ pimo = g_menus[menupos];
+
+ mir_snprintf( MenuNameItems, sizeof(MenuNameItems), "%s_Items", pimo->Name);
+ runtimepos = 100;
+
+ while ( tvi.hItem != NULL ) {
+ TreeView_GetItem( hTree, &tvi );
+ iod = ( MenuItemOptData* )tvi.lParam;
+ if ( iod->pimi ) {
+ GetMenuItemName( iod->pimi, menuItemName, sizeof( menuItemName ));
+
+ mir_snprintf( DBString, SIZEOF(DBString), "%s_visible", menuItemName );
+ DBWriteContactSettingByte( NULL, MenuNameItems, DBString, iod->show );
+
+ mir_snprintf( DBString, SIZEOF(DBString), "%s_pos", menuItemName );
+ DBWriteContactSettingDword( NULL, MenuNameItems, DBString, runtimepos );
+
+ mir_snprintf( DBString, SIZEOF(DBString), "%s_name", menuItemName );
+ if ( lstrcmp( iod->name, iod->defname ) != 0 )
+ DBWriteContactSettingTString( NULL, MenuNameItems, DBString, iod->name );
+ else
+ DBDeleteContactSetting( NULL, MenuNameItems, DBString );
+
+ runtimepos += 100;
+ }
+
+ if ( iod->name && !_tcscmp( iod->name, STR_SEPARATOR) && iod->show )
+ runtimepos += SEPARATORPOSITIONINTERVAL;
+
+ tvi.hItem = TreeView_GetNextSibling( hTree, tvi.hItem );
+ count++;
+ }
+ return 1;
+}
+
+static int BuildMenuObjectsTree(HWND hwndDlg)
+{
+ TVINSERTSTRUCT tvis;
+ HWND hTree = GetDlgItem(hwndDlg,IDC_MENUOBJECTS);
+ int i;
+
+ tvis.hParent = NULL;
+ tvis.hInsertAfter = TVI_LAST;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ TreeView_DeleteAllItems( hTree );
+ if ( g_menus.getCount() == 0 )
+ return FALSE;
+
+ for ( i=0; i < g_menus.getCount(); i++ ) {
+ if ( g_menus[i]->id == (int)hStatusMenuObject || !g_menus[i]->m_bUseUserDefinedItems )
+ continue;
+
+ tvis.item.lParam = ( LPARAM )g_menus[i]->id;
+ tvis.item.pszText = LangPackPcharToTchar( g_menus[i]->Name );
+ tvis.item.iImage = tvis.item.iSelectedImage = TRUE;
+ TreeView_InsertItem( hTree, &tvis );
+ mir_free( tvis.item.pszText );
+ }
+ return 1;
+}
+
+static int sortfunc(const void *a,const void *b)
+{
+ lpMenuItemOptData *sd1,*sd2;
+ sd1=(lpMenuItemOptData *)a;
+ sd2=(lpMenuItemOptData *)b;
+ if ((*sd1)->pos > (*sd2)->pos)
+ return 1;
+
+ if ((*sd1)->pos < (*sd2)->pos)
+ return -1;
+
+ return 0;
+}
+
+static int InsertSeparator(HWND hwndDlg)
+{
+ MenuItemOptData *PD;
+ TVINSERTSTRUCT tvis = {0};
+ TVITEM tvi = {0};
+ HTREEITEM hti = {0};
+ HWND hMenuTree = GetDlgItem( hwndDlg, IDC_MENUITEMS );
+
+ hti = TreeView_GetSelection( hMenuTree );
+ if ( hti == NULL )
+ return 1;
+
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT;
+ tvi.hItem = hti;
+ if ( TreeView_GetItem( hMenuTree, &tvi) == FALSE )
+ return 1;
+
+ PD = ( MenuItemOptData* )mir_alloc( sizeof( MenuItemOptData ));
+ ZeroMemory( PD, sizeof( MenuItemOptData ));
+ PD->id = -1;
+ PD->name = mir_tstrdup( STR_SEPARATOR );
+ PD->show = TRUE;
+ PD->pos = ((MenuItemOptData *)tvi.lParam)->pos-1;
+
+ tvis.item.lParam = (LPARAM)(PD);
+ tvis.item.pszText = PD->name;
+ tvis.item.iImage = tvis.item.iSelectedImage = PD->show;
+ tvis.hParent = NULL;
+ tvis.hInsertAfter = hti;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ TreeView_InsertItem( hMenuTree, &tvis );
+ return 1;
+}
+
+static void FreeTreeData( HWND hwndDlg )
+{
+ HTREEITEM hItem = TreeView_GetRoot(GetDlgItem(hwndDlg,IDC_MENUITEMS));
+ while ( hItem != NULL ) {
+ TVITEM tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ TreeView_GetItem( GetDlgItem( hwndDlg, IDC_MENUITEMS ), &tvi );
+ { MenuItemOptData* O = (MenuItemOptData *)tvi.lParam;
+ if ( O->name ) mir_free( O->name );
+ if ( O->defname ) mir_free( O->defname );
+ if ( O->uniqname ) mir_free( O->uniqname );
+ mir_free( O );
+ }
+
+ tvi.lParam = 0;
+ TreeView_SetItem(GetDlgItem(hwndDlg,IDC_MENUITEMS), &tvi);
+
+ hItem = TreeView_GetNextSibling(GetDlgItem(hwndDlg,IDC_MENUITEMS), hItem);
+} }
+
+static int BuildTree(HWND hwndDlg,int MenuObjectId, BOOL bReread)
+{
+ char menuItemName[256],MenuNameItems[256];
+ char buf[256];
+
+ FreeTreeData( hwndDlg );
+ TreeView_DeleteAllItems(GetDlgItem(hwndDlg,IDC_MENUITEMS));
+
+ int menupos = GetMenuObjbyId( MenuObjectId );
+ if ( menupos == -1 )
+ return FALSE;
+
+ TIntMenuObject* pimo = g_menus[menupos];
+ if ( pimo->m_items.first == NULL )
+ return FALSE;
+
+ mir_snprintf( MenuNameItems, sizeof(MenuNameItems), "%s_Items", pimo->Name );
+
+ int count = 0;
+ {
+ for ( PMO_IntMenuItem p = pimo->m_items.first; p != NULL; p = p->next )
+ if ( p->mi.root == ( HGENMENU )-1 || p->mi.root == NULL )
+ count++;
+ }
+
+ lpMenuItemOptData *PDar = ( lpMenuItemOptData* )mir_alloc( sizeof( lpMenuItemOptData )*count );
+
+ count = 0;
+ {
+ for ( PMO_IntMenuItem p = pimo->m_items.first; p != NULL; p = p->next ) {
+ if ( p->mi.root != ( HGENMENU )-1 && p->mi.root != NULL )
+ continue;
+
+ MenuItemOptData *PD = ( MenuItemOptData* )mir_calloc( sizeof( MenuItemOptData ));
+ GetMenuItemName( p, menuItemName, sizeof( menuItemName ));
+ {
+ DBVARIANT dbv;
+ mir_snprintf(buf, SIZEOF(buf), "%s_name", menuItemName);
+
+ if ( !DBGetContactSettingTString( NULL, MenuNameItems, buf, &dbv )) {
+ PD->name = mir_tstrdup( dbv.ptszVal );
+ DBFreeVariant( &dbv );
+ }
+ else PD->name = mir_tstrdup( p->mi.ptszName );
+ }
+
+ PD->pimi = p;
+ PD->defname = mir_tstrdup( p->mi.ptszName );
+
+ mir_snprintf( buf, SIZEOF(buf), "%s_visible", menuItemName );
+ PD->show = DBGetContactSettingByte( NULL, MenuNameItems, buf, 1 );
+
+ if ( bReread ) {
+ mir_snprintf( buf, SIZEOF(buf), "%s_pos", menuItemName );
+ PD->pos = DBGetContactSettingDword( NULL, MenuNameItems, buf, 1 );
+ }
+ else PD->pos = ( PD->pimi ) ? PD->pimi->originalPosition : 0;
+
+ PD->id = p->iCommand;
+
+ if ( p->UniqName )
+ PD->uniqname = mir_strdup( p->UniqName );
+
+ PDar[ count ] = PD;
+ count++;
+ } }
+
+ qsort( PDar, count, sizeof( lpMenuItemOptData ), sortfunc );
+
+ SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, WM_SETREDRAW, FALSE, 0);
+ int lastpos = 0;
+ bool first = TRUE;
+
+ TVINSERTSTRUCT tvis;
+ tvis.hParent = NULL;
+ tvis.hInsertAfter = TVI_LAST;
+ tvis.item.mask = TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ for ( int i=0; i < count; i++ ) {
+ if ( PDar[i]->pos - lastpos >= SEPARATORPOSITIONINTERVAL ) {
+ MenuItemOptData *PD = ( MenuItemOptData* )mir_calloc( sizeof( MenuItemOptData ));
+ PD->id = -1;
+ PD->name = mir_tstrdup( STR_SEPARATOR );
+ PD->pos = PDar[i]->pos - 1;
+ PD->show = TRUE;
+
+ tvis.item.lParam = ( LPARAM )PD;
+ tvis.item.pszText = PD->name;
+ tvis.item.iImage = tvis.item.iSelectedImage = PD->show;
+ SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, TVM_INSERTITEM, 0, (LPARAM)&tvis);
+ }
+
+ tvis.item.lParam = ( LPARAM )PDar[i];
+ tvis.item.pszText = PDar[i]->name;
+ tvis.item.iImage = tvis.item.iSelectedImage = PDar[i]->show;
+
+ HTREEITEM hti = (HTREEITEM)SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, TVM_INSERTITEM, 0, (LPARAM)&tvis);
+ if ( first ) {
+ TreeView_SelectItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),hti);
+ first=FALSE;
+ }
+
+ lastpos = PDar[i]->pos;
+ }
+
+ SendDlgItemMessage( hwndDlg, IDC_MENUITEMS, WM_SETREDRAW, TRUE, 0 );
+ mir_free( PDar );
+ ShowWindow( GetDlgItem( hwndDlg, IDC_NOTSUPPORTWARNING ),( pimo->m_bUseUserDefinedItems ) ? SW_HIDE : SW_SHOW );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_MENUITEMS ), pimo->m_bUseUserDefinedItems );
+ EnableWindow( GetDlgItem( hwndDlg, IDC_INSERTSEPARATOR ), pimo->m_bUseUserDefinedItems );
+ return 1;
+}
+
+static void RebuildCurrent( HWND hwndDlg )
+{
+ int MenuObjectID;
+ if ( GetCurrentMenuObjectID( hwndDlg, &MenuObjectID ))
+ BuildTree( hwndDlg, MenuObjectID, TRUE );
+}
+
+static void ResetMenuItems( HWND hwndDlg )
+{
+ int MenuObjectID;
+ if ( GetCurrentMenuObjectID( hwndDlg, &MenuObjectID ))
+ BuildTree( hwndDlg, MenuObjectID, FALSE );
+}
+
+static HTREEITEM MoveItemAbove(HWND hTreeWnd, HTREEITEM hItem, HTREEITEM hInsertAfter)
+{
+ TVITEM tvi = { 0 };
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ if ( !SendMessage(hTreeWnd, TVM_GETITEM, 0, ( LPARAM )&tvi ))
+ return NULL;
+ if ( hItem && hInsertAfter ) {
+ TVINSERTSTRUCT tvis;
+ TCHAR name[128];
+ if ( hItem == hInsertAfter )
+ return hItem;
+
+ tvis.item.mask = TVIF_HANDLE | TVIF_PARAM | TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ tvis.item.stateMask = 0xFFFFFFFF;
+ tvis.item.pszText = name;
+ tvis.item.cchTextMax = sizeof( name );
+ tvis.item.hItem = hItem;
+ tvis.item.iImage = tvis.item.iSelectedImage = (( MenuItemOptData* )tvi.lParam)->show;
+ if(!SendMessage(hTreeWnd, TVM_GETITEM, 0, (LPARAM)&tvis.item))
+ return NULL;
+ if (!TreeView_DeleteItem(hTreeWnd,hItem))
+ return NULL;
+ tvis.hParent=NULL;
+ tvis.hInsertAfter=hInsertAfter;
+ return TreeView_InsertItem(hTreeWnd, &tvis);
+ }
+ return NULL;
+}
+
+WNDPROC MyOldWindowProc=NULL;
+
+LRESULT CALLBACK LBTNDOWNProc(HWND hwnd,UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ if (uMsg==WM_LBUTTONDOWN && !(GetKeyState(VK_CONTROL)&0x8000)) {
+
+ TVHITTESTINFO hti;
+ hti.pt.x=(short)LOWORD(lParam);
+ hti.pt.y=(short)HIWORD(lParam);
+ // ClientToScreen(hwndDlg,&hti.pt);
+ // ScreenToClient(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti.pt);
+ TreeView_HitTest(hwnd,&hti);
+ if (hti.flags&TVHT_ONITEMLABEL) {
+ /// LabelClicked Set/unset selection
+ TVITEM tvi;
+ HWND tvw=hwnd;
+ tvi.mask=TVIF_HANDLE|TVIF_PARAM;
+ tvi.hItem=hti.hItem;
+ TreeView_GetItem( tvw, &tvi );
+
+ if (!((MenuItemOptData *)tvi.lParam)->isSelected) { /* is not Selected*/
+ // reset all selection except current
+ HTREEITEM hit;
+ hit=TreeView_GetRoot(tvw);
+ if (hit)
+ do {
+ TVITEM tvi={0};
+ tvi.mask=TVIF_HANDLE|TVIF_PARAM;
+ tvi.hItem=hit;
+ TreeView_GetItem(tvw, &tvi);
+
+ if (hti.hItem!=hit)
+ ((MenuItemOptData *)tvi.lParam)->isSelected=0;
+ else
+ ((MenuItemOptData *)tvi.lParam)->isSelected=1;
+ TreeView_SetItem(tvw, &tvi);
+ }
+ while (hit=TreeView_GetNextSibling(tvw,hit));
+ } } }
+
+ return CallWindowProc(MyOldWindowProc,hwnd,uMsg,wParam,lParam);
+}
+
+static INT_PTR CALLBACK GenMenuOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ struct OrderData *dat = (struct OrderData*)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_USERDATA);
+ LPNMHDR hdr;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ dat=(struct OrderData*)mir_alloc(sizeof(struct OrderData));
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_USERDATA,(LONG_PTR)dat);
+ dat->dragging = 0;
+ dat->iInitMenuValue = DBGetContactSettingByte( NULL, "CList", "MoveProtoMenus", FALSE );
+ MyOldWindowProc = (WNDPROC)GetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_WNDPROC);
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_MENUITEMS),GWLP_WNDPROC,(LONG_PTR)&LBTNDOWNProc);
+ {
+ HIMAGELIST himlCheckBoxes;
+ himlCheckBoxes=ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON),
+ (IsWinVerXPPlus() ? ILC_COLOR32 : ILC_COLOR16) | ILC_MASK, 2, 2);
+
+ ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_NOTICK);
+ ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_TICK);
+
+ TreeView_SetImageList(GetDlgItem(hwndDlg,IDC_MENUOBJECTS),himlCheckBoxes,TVSIL_NORMAL);
+ TreeView_SetImageList(GetDlgItem(hwndDlg,IDC_MENUITEMS),himlCheckBoxes,TVSIL_NORMAL);
+ }
+ CheckDlgButton(hwndDlg, dat->iInitMenuValue ? IDC_RADIO2 : IDC_RADIO1, TRUE );
+ CheckDlgButton(hwndDlg, IDC_DISABLEMENUICONS, bIconsDisabled );
+ BuildMenuObjectsTree(hwndDlg);
+ return TRUE;
+
+ case WM_COMMAND:
+ if ( HIWORD(wParam) == BN_CLICKED || HIWORD( wParam ) == BN_DBLCLK ) {
+ switch ( LOWORD( wParam )) {
+ case IDC_INSERTSEPARATOR:
+ InsertSeparator(hwndDlg);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+
+ case IDC_RESETMENU:
+ ResetMenuItems( hwndDlg );
+ SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 );
+ break;
+
+ case IDC_DISABLEMENUICONS:
+ case IDC_RADIO1:
+ case IDC_RADIO2:
+ SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 );
+ break;
+
+ case IDC_GENMENU_DEFAULT:
+ {
+ TVITEM tvi;
+ HTREEITEM hti;
+ MenuItemOptData *iod;
+
+ hti=TreeView_GetSelection(GetDlgItem(hwndDlg,IDC_MENUITEMS));
+ if (hti==NULL)
+ break;
+
+ tvi.mask=TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
+ tvi.hItem=hti;
+ TreeView_GetItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),&tvi);
+ iod = ( MenuItemOptData * )tvi.lParam;
+
+ if ( iod->name && _tcsstr( iod->name, STR_SEPARATOR ))
+ break;
+
+ if (iod->name)
+ mir_free(iod->name);
+ iod->name = mir_tstrdup( iod->defname );
+
+ SaveTree(hwndDlg);
+ RebuildCurrent(hwndDlg);
+ SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 );
+ }
+ break;
+
+ case IDC_GENMENU_SET:
+ {
+ TVITEM tvi;
+ TCHAR buf[256];
+ MenuItemOptData *iod;
+
+ HTREEITEM hti = TreeView_GetSelection( GetDlgItem( hwndDlg,IDC_MENUITEMS ));
+ if ( hti == NULL )
+ break;
+
+ tvi.mask = TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
+ tvi.hItem = hti;
+ SendDlgItemMessage(hwndDlg, IDC_MENUITEMS, TVM_GETITEM, 0, (LPARAM)&tvi);
+ iod = ( MenuItemOptData * )tvi.lParam;
+
+ if ( iod->name && _tcsstr(iod->name, STR_SEPARATOR ))
+ break;
+
+ ZeroMemory(buf,sizeof( buf ));
+ GetDlgItemText( hwndDlg, IDC_GENMENU_CUSTOMNAME, buf, SIZEOF( buf ));
+ if (iod->name)
+ mir_free(iod->name);
+
+ iod->name = mir_tstrdup(buf);
+
+ SaveTree(hwndDlg);
+ RebuildCurrent(hwndDlg);
+ SendMessage( GetParent( hwndDlg ), PSM_CHANGED, 0, 0 );
+ }
+ break;
+ } }
+ break;
+
+ case WM_NOTIFY:
+ hdr = (LPNMHDR)lParam;
+ switch( hdr->idFrom ) {
+ case 0:
+ if (hdr->code == PSN_APPLY ) {
+ bIconsDisabled = IsDlgButtonChecked(hwndDlg, IDC_DISABLEMENUICONS) != 0;
+ DBWriteContactSettingByte(NULL, "CList", "DisableMenuIcons", bIconsDisabled);
+ SaveTree(hwndDlg);
+ int iNewMenuValue = IsDlgButtonChecked(hwndDlg, IDC_RADIO1) ? 0 : 1;
+ if ( iNewMenuValue != dat->iInitMenuValue ) {
+ RebuildProtoMenus( iNewMenuValue );
+ dat->iInitMenuValue = iNewMenuValue;
+ }
+ RebuildCurrent(hwndDlg);
+ }
+ break;
+
+ case IDC_MENUOBJECTS:
+ if (hdr->code == TVN_SELCHANGEDA )
+ RebuildCurrent( hwndDlg );
+ break;
+
+ case IDC_MENUITEMS:
+ switch (hdr->code) {
+ case NM_CUSTOMDRAW:
+ {
+ int i= handleCustomDraw(GetDlgItem(hwndDlg,IDC_MENUITEMS),(LPNMTVCUSTOMDRAW) lParam);
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, i);
+ return TRUE;
+ }
+
+ case TVN_BEGINDRAGA:
+ SetCapture(hwndDlg);
+ dat->dragging=1;
+ dat->hDragItem=((LPNMTREEVIEW)lParam)->itemNew.hItem;
+ TreeView_SelectItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),dat->hDragItem);
+ break;
+
+ case NM_CLICK:
+ {
+ TVHITTESTINFO hti;
+ hti.pt.x=(short)LOWORD(GetMessagePos());
+ hti.pt.y=(short)HIWORD(GetMessagePos());
+ ScreenToClient(hdr->hwndFrom,&hti.pt);
+ if (TreeView_HitTest(hdr->hwndFrom,&hti)) {
+ if (hti.flags&TVHT_ONITEMICON) {
+ TVITEM tvi;
+ tvi.mask=TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
+ tvi.hItem=hti.hItem;
+ TreeView_GetItem(hdr->hwndFrom,&tvi);
+
+ tvi.iImage=tvi.iSelectedImage=!tvi.iImage;
+ ((MenuItemOptData *)tvi.lParam)->show=tvi.iImage;
+ TreeView_SetItem(hdr->hwndFrom,&tvi);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+
+ //all changes take effect in runtime
+ //ShowWindow(GetDlgItem(hwndDlg,IDC_BUTTONORDERTREEWARNING),SW_SHOW);
+ }
+ /*--------MultiSelection----------*/
+ if (hti.flags&TVHT_ONITEMLABEL) {
+ /// LabelClicked Set/unset selection
+ TVITEM tvi;
+ HWND tvw=hdr->hwndFrom;
+ tvi.mask=TVIF_HANDLE|TVIF_PARAM;
+ tvi.hItem=hti.hItem;
+ TreeView_GetItem(tvw,&tvi);
+ if (GetKeyState(VK_CONTROL)&0x8000) {
+ if (((MenuItemOptData *)tvi.lParam)->isSelected)
+ ((MenuItemOptData *)tvi.lParam)->isSelected=0;
+ else
+ ((MenuItemOptData *)tvi.lParam)->isSelected=1; //current selection order++.
+ TreeView_SetItem(tvw,&tvi);
+ }
+ else if (GetKeyState(VK_SHIFT)&0x8000) {
+ ; // shifted click
+ }
+ else {
+ // reset all selection except current
+ HTREEITEM hit;
+ hit=TreeView_GetRoot(tvw);
+ if (hit)
+ do {
+ TVITEM tvi={0};
+ tvi.mask=TVIF_HANDLE|TVIF_PARAM;
+ tvi.hItem=hit;
+ TreeView_GetItem(tvw,&tvi);
+
+ if (hti.hItem!=hit)
+ ((MenuItemOptData *)tvi.lParam)->isSelected=0;
+ else
+ ((MenuItemOptData *)tvi.lParam)->isSelected=1;
+ TreeView_SetItem(tvw,&tvi);
+ }
+ while (hit=TreeView_GetNextSibling(tvw,hit));
+ } } }
+ break;
+ }
+ case TVN_SELCHANGING:
+ {
+ LPNMTREEVIEW pn;
+ pn = (LPNMTREEVIEW) lParam;
+ //((MenuItemOptData *)(pn->itemNew.lParam))->isSelected=1;
+ /*if (pn->action==NotKeyPressed)
+ {
+ remove all selection
+ }
+ */
+ }
+ case TVN_SELCHANGEDA:
+ {
+ TVITEM tvi;
+ HTREEITEM hti;
+ MenuItemOptData *iod;
+
+ SetDlgItemTextA(hwndDlg,IDC_GENMENU_CUSTOMNAME,"");
+ SetDlgItemTextA(hwndDlg,IDC_GENMENU_SERVICE,"");
+
+ EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_CUSTOMNAME),FALSE);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_DEFAULT),FALSE);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_SET),FALSE);
+
+ hti=TreeView_GetSelection(GetDlgItem(hwndDlg,IDC_MENUITEMS));
+ if (hti==NULL)
+ break;
+
+ tvi.mask=TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
+ tvi.hItem=hti;
+ TreeView_GetItem(GetDlgItem(hwndDlg,IDC_MENUITEMS),&tvi);
+
+ if ( tvi.lParam == 0 )
+ break;
+
+ iod = ( MenuItemOptData * )tvi.lParam;
+
+ if ( iod->name && _tcsstr(iod->name, STR_SEPARATOR))
+ break;
+
+ SetDlgItemText(hwndDlg,IDC_GENMENU_CUSTOMNAME,iod->name);
+
+ if (iod->pimi->submenu.first == NULL && iod->uniqname)
+ SetDlgItemTextA(hwndDlg, IDC_GENMENU_SERVICE, iod->uniqname);
+
+ EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_DEFAULT), lstrcmp(iod->name, iod->defname) != 0);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_SET),TRUE);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_GENMENU_CUSTOMNAME),TRUE);
+ break;
+ }
+ break;
+ } }
+ break;
+
+ case WM_MOUSEMOVE:
+ if (!dat||!dat->dragging) break;
+ {
+ TVHITTESTINFO hti;
+
+ hti.pt.x=(short)LOWORD(lParam);
+ hti.pt.y=(short)HIWORD(lParam);
+ ClientToScreen(hwndDlg,&hti.pt);
+ ScreenToClient(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti.pt);
+ TreeView_HitTest(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti);
+ if (hti.flags&(TVHT_ONITEM|TVHT_ONITEMRIGHT)) {
+ HTREEITEM it = hti.hItem;
+ hti.pt.y -= TreeView_GetItemHeight(GetDlgItem(hwndDlg,IDC_MENUITEMS))/2;
+ TreeView_HitTest(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti);
+ if (!(hti.flags&TVHT_ABOVE))
+ TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),hti.hItem,1);
+ else
+ TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),it,0);
+ }
+ else {
+ if (hti.flags&TVHT_ABOVE) SendDlgItemMessage(hwndDlg,IDC_MENUITEMS,WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);
+ if (hti.flags&TVHT_BELOW) SendDlgItemMessage(hwndDlg,IDC_MENUITEMS,WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);
+ TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),NULL,0);
+ } }
+ break;
+
+ case WM_LBUTTONUP:
+ if (!dat->dragging)
+ break;
+
+ TreeView_SetInsertMark(GetDlgItem(hwndDlg,IDC_MENUITEMS),NULL,0);
+ dat->dragging=0;
+ ReleaseCapture();
+ {
+ TVHITTESTINFO hti;
+ hti.pt.x=(short)LOWORD(lParam);
+ hti.pt.y=(short)HIWORD(lParam);
+ ClientToScreen(hwndDlg,&hti.pt);
+ ScreenToClient(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti.pt);
+ hti.pt.y-=TreeView_GetItemHeight(GetDlgItem(hwndDlg,IDC_MENUITEMS))/2;
+ TreeView_HitTest(GetDlgItem(hwndDlg,IDC_MENUITEMS),&hti);
+ if (hti.flags&TVHT_ABOVE) hti.hItem=TVI_FIRST;
+ if (dat->hDragItem==hti.hItem) break;
+ dat->hDragItem=NULL;
+ if (hti.flags&(TVHT_ONITEM|TVHT_ONITEMRIGHT)||(hti.hItem==TVI_FIRST)) {
+ HWND tvw;
+ HTREEITEM * pSIT;
+ HTREEITEM FirstItem=NULL;
+ UINT uITCnt,uSic ;
+ tvw=GetDlgItem(hwndDlg,IDC_MENUITEMS);
+ uITCnt=TreeView_GetCount(tvw);
+ uSic=0;
+ if (uITCnt) {
+ pSIT=(HTREEITEM *)mir_alloc(sizeof(HTREEITEM)*uITCnt);
+ if (pSIT) {
+ HTREEITEM hit;
+ hit=TreeView_GetRoot(tvw);
+ if (hit)
+ do {
+ TVITEM tvi={0};
+ tvi.mask=TVIF_HANDLE|TVIF_PARAM;
+ tvi.hItem=hit;
+ TreeView_GetItem(tvw,&tvi);
+ if (((MenuItemOptData *)tvi.lParam)->isSelected) {
+ pSIT[uSic]=tvi.hItem;
+
+ uSic++;
+ }
+ }while (hit=TreeView_GetNextSibling(tvw,hit));
+ // Proceed moving
+ {
+ UINT i;
+ HTREEITEM insertAfter;
+ insertAfter=hti.hItem;
+ for (i=0; i<uSic; i++) {
+ if (insertAfter) insertAfter=MoveItemAbove(tvw,pSIT[i],insertAfter);
+ else break;
+ if (!i) FirstItem=insertAfter;
+ } }
+ // free pointers...
+ mir_free(pSIT);
+ } }
+
+ if (FirstItem) TreeView_SelectItem(tvw,FirstItem);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ SaveTree(hwndDlg);
+ } }
+ break;
+
+ case WM_DESTROY:
+ if ( dat )
+ mir_free( dat );
+
+ ImageList_Destroy(TreeView_SetImageList(GetDlgItem(hwndDlg,IDC_MENUOBJECTS),NULL,TVSIL_NORMAL));
+ FreeTreeData( hwndDlg );
+ break;
+
+ }
+ return FALSE;
+}
+
+long handleCustomDraw(HWND hWndTreeView, LPNMTVCUSTOMDRAW pNMTVCD)
+{
+ if ( pNMTVCD == NULL )
+ return -1;
+
+ switch ( pNMTVCD->nmcd.dwDrawStage ) {
+ case CDDS_PREPAINT:
+ return CDRF_NOTIFYITEMDRAW;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ HTREEITEM hItem = (HTREEITEM) pNMTVCD->nmcd.dwItemSpec;
+ TCHAR buf[255];
+ TVITEM tvi = {0};
+ int k=0;
+ tvi.mask = TVIF_HANDLE |TVIF_PARAM|TVIS_SELECTED|TVIF_TEXT|TVIF_IMAGE;
+ tvi.stateMask=TVIS_SELECTED;
+ tvi.hItem = hItem;
+ tvi.pszText=(LPTSTR)(&buf);
+ tvi.cchTextMax=254;
+ TreeView_GetItem(hWndTreeView, &tvi);
+ if (((MenuItemOptData *)tvi.lParam)->isSelected) {
+ pNMTVCD->clrTextBk = GetSysColor(COLOR_HIGHLIGHT);
+ pNMTVCD->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
+ }
+ else {
+ pNMTVCD->clrTextBk = GetSysColor(COLOR_WINDOW);
+ pNMTVCD->clrText = GetSysColor(COLOR_WINDOWTEXT);
+ }
+
+ /* At this point, you can change the background colors for the item
+ and any subitems and return CDRF_NEWFONT. If the list-view control
+ is in report mode, you can simply return CDRF_NOTIFYSUBITEMREDRAW
+ to customize the item's subitems individually */
+ if ( tvi.iImage == -1 ) {
+ HBRUSH br;
+ SIZE sz;
+ RECT rc;
+ k=1;
+
+ GetTextExtentPoint32(pNMTVCD->nmcd.hdc,tvi.pszText,lstrlen(tvi.pszText),&sz);
+
+ if (sz.cx+3>pNMTVCD->nmcd.rc.right-pNMTVCD->nmcd.rc.left) rc=pNMTVCD->nmcd.rc;
+ else SetRect(&rc,pNMTVCD->nmcd.rc.left,pNMTVCD->nmcd.rc.top,pNMTVCD->nmcd.rc.left+sz.cx+3,pNMTVCD->nmcd.rc.bottom);
+
+ br=CreateSolidBrush(pNMTVCD->clrTextBk);
+ SetTextColor(pNMTVCD->nmcd.hdc,pNMTVCD->clrText);
+ SetBkColor(pNMTVCD->nmcd.hdc,pNMTVCD->clrTextBk);
+ FillRect(pNMTVCD->nmcd.hdc,&rc,br);
+ DeleteObject(br);
+ DrawText(pNMTVCD->nmcd.hdc,tvi.pszText,lstrlen(tvi.pszText),&pNMTVCD->nmcd.rc,DT_LEFT|DT_VCENTER|DT_NOPREFIX);
+ }
+
+ return CDRF_NEWFONT|(k?CDRF_SKIPDEFAULT:0);
+ }
+ }
+ return 0;
+}
+
+INT_PTR CALLBACK ProtocolOrderOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+
+int GenMenuOptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { 0 };
+ odp.cbSize=sizeof(odp);
+ odp.hInstance = hMirandaInst;
+ odp.pszGroup = LPGEN("Customize");
+
+ odp.position = -1000000000;
+ odp.pszTemplate = MAKEINTRESOURCEA( IDD_OPT_GENMENU );
+ odp.pszTitle = LPGEN("Menus");
+ odp.pfnDlgProc = GenMenuOpts;
+ odp.flags = ODPF_BOLDGROUPS;
+ CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp );
+
+ odp.position = -10000000;
+ odp.groupPosition = 1000000;
+ odp.pszTemplate = MAKEINTRESOURCEA( IDD_OPT_PROTOCOLORDER );
+ odp.pszTitle = LPGEN("Accounts");
+ odp.pfnDlgProc = ProtocolOrderOpts;
+ odp.flags = ODPF_BOLDGROUPS|ODPF_EXPERTONLY;
+ CallService( MS_OPT_ADDPAGE, wParam, ( LPARAM )&odp );
+ return 0;
+}
diff --git a/src/modules/clist/groups.cpp b/src/modules/clist/groups.cpp
new file mode 100644
index 0000000000..0693c25036
--- /dev/null
+++ b/src/modules/clist/groups.cpp
@@ -0,0 +1,577 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+
+HANDLE hGroupChangeEvent;
+
+static INT_PTR RenameGroup(WPARAM wParam, LPARAM lParam);
+static INT_PTR MoveGroupBefore(WPARAM wParam, LPARAM lParam);
+
+static int CountGroups(void)
+{
+ DBVARIANT dbv;
+ int i;
+ char str[33];
+
+ for (i = 0;; i++) {
+ _itoa(i, str, 10);
+ if (DBGetContactSetting(NULL, "CListGroups", str, &dbv))
+ break;
+ DBFreeVariant(&dbv);
+ }
+ return i;
+}
+
+static int GroupNameExists(const TCHAR *name, int skipGroup)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+ int i;
+
+ for (i = 0;; i++) {
+ if (i == skipGroup)
+ continue;
+ _itoa(i, idstr, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv))
+ break;
+ if (!_tcscmp(dbv.ptszVal + 1, name)) {
+ DBFreeVariant(&dbv);
+ return i+1;
+ }
+ DBFreeVariant(&dbv);
+ }
+ return 0;
+}
+
+static INT_PTR CreateGroup(WPARAM wParam, LPARAM lParam)
+{
+ int newId = CountGroups();
+ TCHAR newBaseName[127], newName[128];
+ char str[33];
+ int i;
+ DBVARIANT dbv;
+
+ const TCHAR* grpName = lParam ? (TCHAR*)lParam : TranslateT("New Group");
+ if (wParam) {
+ _itoa(wParam - 1, str, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv))
+ return 0;
+
+ mir_sntprintf( newBaseName, SIZEOF(newBaseName), _T("%s\\%s"), dbv.ptszVal + 1, grpName );
+ mir_free(dbv.pszVal);
+ }
+ else lstrcpyn( newBaseName, grpName, SIZEOF( newBaseName ));
+
+ _itoa(newId, str, 10);
+ lstrcpyn( newName + 1, newBaseName, SIZEOF(newName) - 1);
+ if (lParam) {
+ i = GroupNameExists(newBaseName, -1);
+ if (i) newId = i - 1;
+ i = !i;
+ }
+ else {
+ i = 1;
+ while (GroupNameExists(newName + 1, -1))
+ mir_sntprintf( newName + 1, SIZEOF(newName) - 1, _T("%s (%d)"), newBaseName, ++i );
+ }
+ if (i) {
+ const CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, newName };
+
+ newName[0] = 1 | GROUPF_EXPANDED; //1 is required so we never get '\0'
+ DBWriteContactSettingTString(NULL, "CListGroups", str, newName);
+ CallService(MS_CLUI_GROUPADDED, newId + 1, 1);
+
+ NotifyEventHooks(hGroupChangeEvent, 0, (LPARAM)&grpChg);
+ }
+
+ return newId + 1;
+}
+
+static INT_PTR GetGroupName2(WPARAM wParam, LPARAM lParam)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+ static char name[128];
+
+ _itoa(wParam - 1, idstr, 10);
+ if (DBGetContactSettingString(NULL, "CListGroups", idstr, &dbv))
+ return (INT_PTR) (char *) NULL;
+ lstrcpynA(name, dbv.pszVal + 1, SIZEOF(name));
+ if ((DWORD *) lParam != NULL)
+ *(DWORD *) lParam = dbv.pszVal[0];
+ DBFreeVariant(&dbv);
+ return (INT_PTR) name;
+}
+
+TCHAR* fnGetGroupName( int idx, DWORD* pdwFlags )
+{
+ char idstr[33];
+ DBVARIANT dbv;
+ static TCHAR name[128];
+
+ _itoa( idx-1, idstr, 10);
+ if (DBGetContactSettingTString( NULL, "CListGroups", idstr, &dbv ))
+ return NULL;
+
+ lstrcpyn( name, dbv.ptszVal + 1, SIZEOF( name ));
+ if ( pdwFlags != NULL )
+ *pdwFlags = dbv.ptszVal[0];
+ DBFreeVariant( &dbv );
+ return name;
+}
+
+static INT_PTR GetGroupName(WPARAM wParam, LPARAM lParam)
+{
+ INT_PTR ret;
+ ret = GetGroupName2(wParam, lParam);
+ if ((int *) lParam)
+ *(int *) lParam = 0 != (*(int *) lParam & GROUPF_EXPANDED);
+ return ret;
+}
+
+static INT_PTR DeleteGroup(WPARAM wParam, LPARAM)
+{
+ int i;
+ char str[33];
+ DBVARIANT dbv;
+ HANDLE hContact;
+ TCHAR name[256], szNewParent[256], *pszLastBackslash;
+
+ //get the name
+ _itoa(wParam - 1, str, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv))
+ return 1;
+ lstrcpyn(name, dbv.ptszVal + 1, SIZEOF(name));
+ DBFreeVariant(&dbv);
+ if (DBGetContactSettingByte(NULL, "CList", "ConfirmDelete", SETTING_CONFIRMDELETE_DEFAULT))
+ {
+ TCHAR szQuestion[256+100];
+ mir_sntprintf( szQuestion, SIZEOF(szQuestion), TranslateT("Are you sure you want to delete group '%s'? This operation can not be undone."), name );
+ if (MessageBox(cli.hwndContactList, szQuestion, TranslateT("Delete Group"), MB_YESNO|MB_ICONQUESTION)==IDNO)
+ return 1;
+ }
+ SetCursor(LoadCursor(NULL, IDC_WAIT));
+ //must remove setting from all child contacts too
+ //children are demoted to the next group up, not deleted.
+ lstrcpy(szNewParent, name);
+ pszLastBackslash = _tcsrchr(szNewParent, '\\');
+ if (pszLastBackslash)
+ pszLastBackslash[0] = '\0';
+ else
+ szNewParent[0] = '\0';
+
+ CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), NULL, NULL };
+
+ for (hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ hContact ;
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0))
+ {
+ if (DBGetContactSettingTString(hContact, "CList", "Group", &dbv))
+ continue;
+
+ if (_tcscmp(dbv.ptszVal, name))
+ {
+ DBFreeVariant(&dbv);
+ continue;
+ }
+ DBFreeVariant(&dbv);
+
+ if (szNewParent[0])
+ {
+ DBWriteContactSettingTString(hContact, "CList", "Group", szNewParent);
+ grpChg.pszNewName = szNewParent;
+ }
+ else
+ {
+ DBDeleteContactSetting(hContact, "CList", "Group");
+ grpChg.pszNewName = NULL;
+ }
+ NotifyEventHooks(hGroupChangeEvent, (WPARAM)hContact, (LPARAM)&grpChg);
+ }
+ //shuffle list of groups up to fill gap
+ for (i = wParam - 1;; i++) {
+ _itoa(i + 1, str, 10);
+ if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv))
+ break;
+ _itoa(i, str, 10);
+ DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ _itoa(i, str, 10);
+ DBDeleteContactSetting(NULL, "CListGroups", str);
+ //rename subgroups
+ {
+ TCHAR szNewName[256];
+ int len;
+
+ len = lstrlen(name);
+ for (i = 0;; i++) {
+ _itoa(i, str, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv))
+ break;
+ if (!_tcsncmp(dbv.ptszVal + 1, name, len) && dbv.pszVal[len + 1] == '\\' && _tcschr(dbv.ptszVal + len + 2, '\\') == NULL) {
+ if (szNewParent[0])
+ mir_sntprintf(szNewName, SIZEOF(szNewName), _T("%s\\%s"), szNewParent, dbv.ptszVal + len + 2);
+ else
+ lstrcpyn(szNewName, dbv.ptszVal + len + 2, SIZEOF(szNewName));
+ cli.pfnRenameGroup(i + 1, szNewName);
+ }
+ DBFreeVariant(&dbv);
+ }
+ }
+ SetCursor(LoadCursor(NULL, IDC_ARROW));
+ cli.pfnLoadContactTree();
+
+ {
+ const CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), name, NULL };
+ NotifyEventHooks(hGroupChangeEvent, 0, (LPARAM)&grpChg);
+ }
+ return 0;
+}
+
+static int RenameGroupWithMove(int groupId, const TCHAR *szName, int move)
+{
+ char idstr[33];
+ TCHAR str[256], oldName[256];
+ DBVARIANT dbv;
+ HANDLE hContact;
+
+ if (GroupNameExists(szName, groupId)) {
+ MessageBox(NULL, TranslateT("You already have a group with that name. Please enter a unique name for the group."), TranslateT("Rename Group"), MB_OK);
+ return 1;
+ }
+
+ //do the change
+ _itoa(groupId, idstr, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv))
+ return 1;
+ str[0] = dbv.pszVal[0] & 0x7F;
+ lstrcpyn(oldName, dbv.ptszVal + 1, SIZEOF(oldName));
+ DBFreeVariant(&dbv);
+ lstrcpyn(str + 1, szName, SIZEOF(str) - 1);
+ DBWriteContactSettingTString(NULL, "CListGroups", idstr, str);
+
+ //must rename setting in all child contacts too
+ hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0);
+ do {
+ ClcCacheEntryBase* cache = cli.pfnGetCacheEntry( hContact );
+ if ( !lstrcmp(cache->group, oldName)) {
+ DBWriteContactSettingTString(hContact, "CList", "Group", szName);
+ mir_free(cache->group);
+ cache->group = 0;
+ cli.pfnCheckCacheItem(cache);
+ }
+ }
+ while ((hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0)) != NULL);
+
+ //rename subgroups
+ {
+ TCHAR szNewName[256];
+ int len, i;
+
+ len = lstrlen(oldName);
+ for (i = 0;; i++) {
+ if (i == groupId)
+ continue;
+ _itoa(i, idstr, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv))
+ break;
+ if ( !_tcsncmp(dbv.ptszVal + 1, oldName, len) && dbv.ptszVal[len + 1] == '\\' && _tcschr(dbv.ptszVal + len + 2, '\\') == NULL) {
+ mir_sntprintf( szNewName, SIZEOF(szNewName), _T("%s\\%s"), szName, dbv.ptszVal + len + 2 );
+ RenameGroupWithMove(i, szNewName, 0); //luckily, child groups will never need reordering
+ }
+ DBFreeVariant(&dbv);
+ }
+ }
+
+ //finally must make sure it's after any parent items
+ if (move) {
+ TCHAR *pszLastBackslash;
+ int i;
+
+ lstrcpyn(str, szName, SIZEOF(str));
+ pszLastBackslash = _tcsrchr(str, '\\');
+ if (pszLastBackslash != NULL) {
+ *pszLastBackslash = '\0';
+ for (i = 0;; i++) {
+ _itoa(i, idstr, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv))
+ break;
+ if (!lstrcmp(dbv.ptszVal + 1, str)) {
+ if (i < groupId)
+ break; //is OK
+ MoveGroupBefore(groupId + 1, i + 2);
+ break;
+ }
+ DBFreeVariant(&dbv);
+ }
+ }
+ }
+ {
+ const CLISTGROUPCHANGE grpChg = { sizeof(CLISTGROUPCHANGE), oldName, (TCHAR*)szName };
+ NotifyEventHooks(hGroupChangeEvent, 0, (LPARAM)&grpChg);
+ }
+ return 0;
+}
+
+int fnRenameGroup( int groupID, TCHAR* newName )
+{
+ return -1 != RenameGroupWithMove( groupID-1, newName, 1);
+}
+
+static INT_PTR RenameGroup(WPARAM wParam, LPARAM lParam)
+{
+ #if defined( _UNICODE )
+ WCHAR* temp = mir_a2u(( char* )lParam );
+ int result = ( -1 != RenameGroupWithMove(wParam - 1, temp, 1));
+ mir_free( temp );
+ return result;
+ #else
+ return -1 != RenameGroupWithMove(wParam - 1, (TCHAR*) lParam, 1);
+ #endif
+}
+
+static INT_PTR SetGroupExpandedState(WPARAM wParam, LPARAM lParam)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+
+ _itoa(wParam - 1, idstr, 10);
+ if (DBGetContactSettingStringUtf(NULL, "CListGroups", idstr, &dbv))
+ return 1;
+ if (lParam)
+ dbv.pszVal[0] |= GROUPF_EXPANDED;
+ else
+ dbv.pszVal[0] = dbv.pszVal[0] & ~GROUPF_EXPANDED;
+ DBWriteContactSettingStringUtf(NULL, "CListGroups", idstr, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ return 0;
+}
+
+static INT_PTR SetGroupFlags(WPARAM wParam, LPARAM lParam)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+ int flags, oldval, newval;
+
+ _itoa(wParam - 1, idstr, 10);
+ if (DBGetContactSettingStringUtf(NULL, "CListGroups", idstr, &dbv))
+ return 1;
+ flags = LOWORD(lParam) & HIWORD(lParam);
+ oldval = dbv.pszVal[0];
+ newval = dbv.pszVal[0] = ((oldval & ~HIWORD(lParam)) | flags) & 0x7f;
+ DBWriteContactSettingStringUtf(NULL, "CListGroups", idstr, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ if ((oldval & GROUPF_HIDEOFFLINE) != (newval & GROUPF_HIDEOFFLINE))
+ cli.pfnLoadContactTree();
+ return 0;
+}
+
+static INT_PTR MoveGroupBefore(WPARAM wParam, LPARAM lParam)
+{
+ int i, shuffleFrom, shuffleTo, shuffleDir;
+ char str[33];
+ TCHAR *szMoveName;
+ DBVARIANT dbv;
+
+ if (wParam == 0 || (LPARAM) wParam == lParam)
+ return 0;
+ _itoa(wParam - 1, str, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", str, &dbv))
+ return 0;
+ szMoveName = dbv.ptszVal;
+ //shuffle list of groups up to fill gap
+ if (lParam == 0) {
+ shuffleFrom = wParam - 1;
+ shuffleTo = -1;
+ shuffleDir = -1;
+ }
+ else {
+ if ((LPARAM) wParam < lParam) {
+ shuffleFrom = wParam - 1;
+ shuffleTo = lParam - 2;
+ shuffleDir = -1;
+ }
+ else {
+ shuffleFrom = wParam - 1;
+ shuffleTo = lParam - 1;
+ shuffleDir = 1;
+ }
+ }
+ if (shuffleDir == -1) {
+ for (i = shuffleFrom; i != shuffleTo; i++) {
+ _itoa(i + 1, str, 10);
+ if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv)) {
+ shuffleTo = i;
+ break;
+ }
+ _itoa(i, str, 10);
+ DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ }
+ else {
+ for (i = shuffleFrom; i != shuffleTo; i--) {
+ _itoa(i - 1, str, 10);
+ if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv)) {
+ mir_free(szMoveName);
+ return 1;
+ } //never happens
+ _itoa(i, str, 10);
+ DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ }
+ _itoa(shuffleTo, str, 10);
+ DBWriteContactSettingTString(NULL, "CListGroups", str, szMoveName);
+ mir_free(szMoveName);
+ return shuffleTo + 1;
+}
+
+static INT_PTR BuildGroupMenu(WPARAM, LPARAM)
+{
+ char idstr[33];
+ DBVARIANT dbv;
+ int groupId;
+ HMENU hRootMenu, hThisMenu;
+ int nextMenuId = 100;
+ TCHAR *pBackslash, *pNextField, szThisField[128], szThisMenuItem[128];
+ int menuId, compareResult, menuItemCount;
+ MENUITEMINFO mii = { 0 };
+
+ if (DBGetContactSettingStringUtf(NULL, "CListGroups", "0", &dbv))
+ return (INT_PTR) (HMENU) NULL;
+ DBFreeVariant(&dbv);
+ hRootMenu = CreateMenu();
+ for (groupId = 0;; groupId++) {
+ _itoa(groupId, idstr, 10);
+ if (DBGetContactSettingTString(NULL, "CListGroups", idstr, &dbv))
+ break;
+
+ pNextField = dbv.ptszVal + 1;
+ hThisMenu = hRootMenu;
+ mii.cbSize = MENUITEMINFO_V4_SIZE;
+ do {
+ pBackslash = _tcschr(pNextField, '\\');
+ if (pBackslash == NULL) {
+ lstrcpyn(szThisField, pNextField, SIZEOF(szThisField));
+ pNextField = NULL;
+ }
+ else {
+ lstrcpyn(szThisField, pNextField, min( SIZEOF(szThisField), pBackslash - pNextField + 1));
+ pNextField = pBackslash + 1;
+ }
+ compareResult = 1;
+ menuItemCount = GetMenuItemCount(hThisMenu);
+ for (menuId = 0; menuId < menuItemCount; menuId++) {
+ mii.fMask = MIIM_TYPE | MIIM_SUBMENU | MIIM_DATA;
+ mii.cch = SIZEOF(szThisMenuItem);
+ mii.dwTypeData = szThisMenuItem;
+ GetMenuItemInfo(hThisMenu, menuId, TRUE, &mii);
+ compareResult = lstrcmp(szThisField, szThisMenuItem);
+ if (compareResult == 0) {
+ if (pNextField == NULL) {
+ mii.fMask = MIIM_DATA;
+ mii.dwItemData = groupId + 1;
+ SetMenuItemInfo(hThisMenu, menuId, TRUE, &mii);
+ }
+ else {
+ if (mii.hSubMenu == NULL) {
+ mii.fMask = MIIM_SUBMENU;
+ mii.hSubMenu = CreateMenu();
+ SetMenuItemInfo(hThisMenu, menuId, TRUE, &mii);
+ mii.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID;
+ //dwItemData doesn't change
+ mii.fType = MFT_STRING;
+ mii.dwTypeData = TranslateT("This group");
+ mii.wID = nextMenuId++;
+ InsertMenuItem(mii.hSubMenu, 0, TRUE, &mii);
+ mii.fMask = MIIM_TYPE;
+ mii.fType = MFT_SEPARATOR;
+ InsertMenuItem(mii.hSubMenu, 1, TRUE, &mii);
+ }
+ hThisMenu = mii.hSubMenu;
+ }
+ break;
+ }
+ if ((int) mii.dwItemData - 1 > groupId)
+ break;
+ }
+ if (compareResult) {
+ mii.fMask = MIIM_TYPE | MIIM_ID;
+ mii.wID = nextMenuId++;
+ mii.dwTypeData = szThisField;
+ mii.fType = MFT_STRING;
+ if (pNextField) {
+ mii.fMask |= MIIM_SUBMENU;
+ mii.hSubMenu = CreateMenu();
+ }
+ else {
+ mii.fMask |= MIIM_DATA;
+ mii.dwItemData = groupId + 1;
+ }
+ InsertMenuItem(hThisMenu, menuId, TRUE, &mii);
+ if (pNextField) {
+ hThisMenu = mii.hSubMenu;
+ }
+ }
+ } while (pNextField);
+
+ DBFreeVariant(&dbv);
+ }
+ return (INT_PTR) hRootMenu;
+}
+
+int InitGroupServices(void)
+{
+ for (int i = 0; ; i++)
+ {
+ char str[32];
+ _itoa(i, str, 10);
+
+ DBVARIANT dbv;
+ if (DBGetContactSettingStringUtf(NULL, "CListGroups", str, &dbv))
+ break;
+ if (dbv.pszVal[0] & 0x80)
+ {
+ dbv.pszVal[0] &= 0x7f;
+ DBWriteContactSettingStringUtf(NULL, "CListGroups", str, dbv.pszVal);
+ }
+ DBFreeVariant(&dbv);
+ }
+
+ CreateServiceFunction(MS_CLIST_GROUPCREATE, CreateGroup);
+ CreateServiceFunction(MS_CLIST_GROUPDELETE, DeleteGroup);
+ CreateServiceFunction(MS_CLIST_GROUPRENAME, RenameGroup);
+ CreateServiceFunction(MS_CLIST_GROUPGETNAME, GetGroupName);
+ CreateServiceFunction(MS_CLIST_GROUPGETNAME2, GetGroupName2);
+ CreateServiceFunction(MS_CLIST_GROUPSETEXPANDED, SetGroupExpandedState);
+ CreateServiceFunction(MS_CLIST_GROUPSETFLAGS, SetGroupFlags);
+ CreateServiceFunction(MS_CLIST_GROUPMOVEBEFORE, MoveGroupBefore);
+ CreateServiceFunction(MS_CLIST_GROUPBUILDMENU, BuildGroupMenu);
+
+ hGroupChangeEvent = CreateHookableEvent( ME_CLIST_GROUPCHANGE );
+
+ return 0;
+}
diff --git a/src/modules/clist/keyboard.cpp b/src/modules/clist/keyboard.cpp
new file mode 100644
index 0000000000..2c9cdce69c
--- /dev/null
+++ b/src/modules/clist/keyboard.cpp
@@ -0,0 +1,173 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2009 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+#include "clc.h"
+#include <m_hotkeys.h>
+
+static INT_PTR hkHideShow(WPARAM, LPARAM)
+{
+ cli.pfnShowHide(0,0);
+ return 0;
+}
+/*
+INT_PTR hkSearch(WPARAM wParam,LPARAM lParam)
+{
+ DBVARIANT dbv={0};
+ if(!DBGetContactSettingString(NULL,"CList","SearchUrl",&dbv)) {
+ CallService(MS_UTILS_OPENURL,DBGetContactSettingByte(NULL,"CList","HKSearchNewWnd",0),(LPARAM)dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ return 0;
+}
+*/
+static INT_PTR hkRead(WPARAM, LPARAM)
+{
+ if(cli.pfnEventsProcessTrayDoubleClick(0)==0) return TRUE;
+ SetForegroundWindow(cli.hwndContactList);
+ SetFocus(cli.hwndContactList);
+ return 0;
+}
+
+static INT_PTR hkOpts(WPARAM, LPARAM)
+{
+ CallService("Options/OptionsCommand",0, 0);
+ return 0;
+}
+/*
+static INT_PTR hkCloseMiranda(WPARAM wParam,LPARAM lParam)
+{
+ CallService("CloseAction", 0, 0);
+ return 0;
+}
+
+INT_PTR hkRestoreStatus(WPARAM wParam,LPARAM lParam)
+{
+ int nStatus = DBGetContactSettingWord(NULL, "CList", "Status", ID_STATUS_OFFLINE);
+ CallService(MS_CLIST_SETSTATUSMODE, nStatus, 0);
+ return 0;
+}
+
+static INT_PTR hkAllOffline(WPARAM, LPARAM)
+{
+ CallService(MS_CLIST_SETSTATUSMODE, ID_STATUS_OFFLINE, 0);
+ return 0;
+}
+*/
+int InitClistHotKeys(void)
+{
+ HOTKEYDESC shk = {0};
+
+ CreateServiceFunction("CLIST/HK/SHOWHIDE",hkHideShow);
+ CreateServiceFunction("CLIST/HK/Opts",hkOpts);
+ CreateServiceFunction("CLIST/HK/Read",hkRead);
+// CreateServiceFunction("CLIST/HK/CloseMiranda",hkCloseMiranda);
+// CreateServiceFunction("CLIST/HK/RestoreStatus",hkRestoreStatus);
+// CreateServiceFunction("CLIST/HK/AllOffline",hkAllOffline);
+
+ shk.cbSize=sizeof(shk);
+ shk.pszDescription="Show Hide Contact List";
+ shk.pszName="ShowHide";
+ shk.pszSection="Main";
+ shk.pszService="CLIST/HK/SHOWHIDE";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'A');
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+
+ shk.pszDescription="Read Message";
+ shk.pszName="ReadMessage";
+ shk.pszSection="Main";
+ shk.pszService="CLIST/HK/Read";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'I');
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+/*
+ shk.pszDescription="Search in site";
+ shk.pszName="SearchInWeb";
+ shk.pszSection="Main";
+ shk.pszService="CLIST/HK/Search";
+ shk.DefHotKey=846;
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+*/
+ shk.pszDescription = "Open Options Page";
+ shk.pszName = "ShowOptions";
+ shk.pszSection = "Main";
+ shk.pszService = "CLIST/HK/Opts";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'O') | HKF_MIRANDA_LOCAL;
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+
+ shk.pszDescription = "Open Logging Options";
+ shk.pszName = "ShowLogOptions";
+ shk.pszSection = "Main";
+ shk.pszService = "Netlib/Log/Win";
+ shk.DefHotKey = 0;
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+
+ shk.pszDescription="Open Find User Dialog";
+ shk.pszName="FindUsers";
+ shk.pszSection="Main";
+ shk.pszService="FindAdd/FindAddCommand";
+ shk.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, 'F') | HKF_MIRANDA_LOCAL;
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+
+/*
+ shk.pszDescription="Close Miranda";
+ shk.pszName="CloseMiranda";
+ shk.pszSection="Main";
+ shk.pszService="CLIST/HK/CloseMiranda";
+ shk.DefHotKey=0;
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+
+ shk.pszDescription="Restore last status";
+ shk.pszName="RestoreLastStatus";
+ shk.pszSection="Status";
+ shk.pszService="CLIST/HK/RestoreStatus";
+ shk.DefHotKey=0;
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+
+ shk.pszDescription="Set All Offline";
+ shk.pszName="AllOffline";
+ shk.pszSection="Status";
+ shk.pszService="CLIST/HK/AllOffline";
+ shk.DefHotKey=0;
+ CallService(MS_HOTKEY_REGISTER,0,(LPARAM)&shk);
+*/
+ return 0;
+}
+
+
+int fnHotKeysRegister(HWND)
+{
+ return 0;
+}
+
+void fnHotKeysUnregister(HWND)
+{
+}
+
+int fnHotKeysProcess(HWND, WPARAM, LPARAM)
+{
+ return TRUE;
+}
+
+int fnHotkeysProcessMessage(WPARAM, LPARAM)
+{
+ return FALSE;
+}
diff --git a/src/modules/clist/movetogroup.cpp b/src/modules/clist/movetogroup.cpp
new file mode 100644
index 0000000000..9924bef2aa
--- /dev/null
+++ b/src/modules/clist/movetogroup.cpp
@@ -0,0 +1,160 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+#include "commonheaders.h"
+
+HANDLE hOnCntMenuBuild;
+HGENMENU hMoveToGroupItem=0, hPriorityItem = 0, hFloatingItem = 0;
+
+LIST<HANDLE> lphGroupsItems(5);
+
+//service
+//wparam - hcontact
+//lparam .popupposition from CLISTMENUITEM
+
+#define MTG_MOVE "MoveToGroup/Move"
+
+struct GroupItemSort
+{
+ TCHAR* name;
+ int position;
+
+ GroupItemSort(TCHAR* pname, int pos)
+ : name(mir_tstrdup(pname)), position(pos) {}
+
+ ~GroupItemSort() { mir_free(name); }
+
+ static int compare(const GroupItemSort* d1, const GroupItemSort* d2)
+ { return _tcscoll(d1->name, d2->name); }
+};
+
+static TCHAR* PrepareGroupName( TCHAR* str )
+{
+ TCHAR* p = _tcschr( str, '&' ), *d;
+ if ( p == NULL )
+ return mir_tstrdup( str );
+
+ d = p = ( TCHAR* )mir_alloc( sizeof( TCHAR )*( 2*_tcslen( str )+1 ));
+ while ( *str ) {
+ if ( *str == '&' )
+ *d++ = '&';
+ *d++ = *str++;
+ }
+
+ *d++ = 0;
+ return p;
+}
+
+static void AddGroupItem(HGENMENU hRoot, TCHAR* name, int pos, WPARAM param, bool checked)
+{
+ CLISTMENUITEM mi = { 0 };
+ mi.cbSize = sizeof(mi);
+ mi.hParentMenu = hRoot;
+ mi.popupPosition = param; // param to pszService - only with CMIF_CHILDPOPUP !!!!!!
+ mi.position = pos;
+ mi.ptszName = PrepareGroupName( name );
+ mi.flags = CMIF_ROOTHANDLE | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED;
+ if ( checked )
+ mi.flags |= CMIF_CHECKED;
+ mi.pszService = MTG_MOVE;
+ HANDLE result = ( HANDLE )CallService(MS_CLIST_ADDCONTACTMENUITEM, param, (LPARAM)&mi);
+ mir_free( mi.ptszName );
+
+ lphGroupsItems.insert((HANDLE*)result);
+}
+
+static int OnContactMenuBuild(WPARAM wParam,LPARAM)
+{
+ int i;
+ OBJLIST<GroupItemSort> groups(10, GroupItemSort::compare);
+
+ if (!hMoveToGroupItem)
+ {
+ CLISTMENUITEM mi = {0};
+
+ mi.cbSize = sizeof(mi);
+ mi.position = 100000;
+ mi.pszName = LPGEN("&Move to Group");
+ mi.flags = CMIF_ROOTHANDLE | CMIF_ICONFROMICOLIB;
+ mi.icolibItem = GetSkinIconHandle(SKINICON_OTHER_GROUP);
+
+ hMoveToGroupItem = (HGENMENU)CallService(MS_CLIST_ADDCONTACTMENUITEM, 0, (LPARAM)&mi);
+ }
+
+ for (i = 0; i < lphGroupsItems.getCount(); i++)
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)lphGroupsItems[i], 0);
+ lphGroupsItems.destroy();
+
+ TCHAR *szContactGroup = DBGetStringT((HANDLE)wParam, "CList", "Group");
+
+ int pos = 1000;
+
+ AddGroupItem(hMoveToGroupItem, TranslateT("<Root Group>"), pos, -1, !szContactGroup);
+
+ pos += 100000; // Separator
+
+ for (i = 0; ; ++i)
+ {
+ char intname[20];
+ _itoa(i, intname, 10);
+
+ DBVARIANT dbv;
+ if (DBGetContactSettingTString(NULL, "CListGroups", intname, &dbv))
+ break;
+
+ if (dbv.ptszVal[0])
+ groups.insert(new GroupItemSort(dbv.ptszVal + 1, i + 1));
+
+ mir_free(dbv.ptszVal);
+ }
+
+ for (i = 0; i < groups.getCount(); ++i)
+ {
+ bool checked = szContactGroup && !_tcscmp(szContactGroup, groups[i].name);
+ AddGroupItem(hMoveToGroupItem, groups[i].name, ++pos, groups[i].position, checked);
+ }
+
+ groups.destroy();
+ mir_free(szContactGroup);
+
+ return 0;
+}
+
+static INT_PTR MTG_DOMOVE(WPARAM wParam,LPARAM lParam)
+{
+ CallService(MS_CLIST_CONTACTCHANGEGROUP, wParam, lParam < 0 ? 0 : lParam);
+ return 0;
+}
+
+void MTG_OnmodulesLoad()
+{
+ hOnCntMenuBuild=HookEvent(ME_CLIST_PREBUILDCONTACTMENU,OnContactMenuBuild);
+ CreateServiceFunction(MTG_MOVE,MTG_DOMOVE);
+}
+
+int UnloadMoveToGroup(void)
+{
+ UnhookEvent(hOnCntMenuBuild);
+ lphGroupsItems.destroy();
+
+ return 0;
+}
diff --git a/src/modules/clist/protocolorder.cpp b/src/modules/clist/protocolorder.cpp
new file mode 100644
index 0000000000..ff77babfec
--- /dev/null
+++ b/src/modules/clist/protocolorder.cpp
@@ -0,0 +1,351 @@
+/*
+
+Miranda IM: the free IM client for Microsoft* Windows*
+
+Copyright 2000-2010 Miranda ICQ/IM project,
+all portions of this codebase are copyrighted to the people
+listed in contributors.txt.
+
+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; either version 2
+of the License, or (at your option) any later version.
+
+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, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+// options dialog for protocol order and visibility
+// written by daniel vijge
+// gpl license ect...
+
+#include "commonheaders.h"
+#include "clc.h"
+
+typedef struct tagProtocolData
+{
+ char *RealName;
+ int protopos;
+ int show, enabled;
+}
+ ProtocolData;
+
+struct ProtocolOrderData
+{
+ int dragging;
+ HTREEITEM hDragItem;
+};
+
+typedef struct {
+ char* protoName;
+ int visible;
+}
+ tempProtoItem;
+
+int isProtoSuitable( PROTO_INTERFACE* ppi )
+{
+ if ( ppi == NULL )
+ return TRUE;
+
+ return ppi->GetCaps( PFLAGNUM_2, 0 ) & ~ppi->GetCaps( PFLAGNUM_5, 0 );
+}
+
+bool CheckProtocolOrder(void)
+{
+ bool changed = false;
+ int i, id = 0;
+
+ for (;;)
+ {
+ // Find account with this id
+ for (i = 0; i < accounts.getCount(); i++)
+ if (accounts[i]->iOrder == id) break;
+
+ // Account with id not found
+ if (i == accounts.getCount())
+ {
+ // Check if this is skipped id, if it is decrement all other ids
+ bool found = false;
+ for (i = 0; i < accounts.getCount(); i++)
+ {
+ if (accounts[i]->iOrder < 1000000 && accounts[i]->iOrder > id)
+ {
+ --accounts[i]->iOrder;
+ found = true;
+ }
+ }
+ if (found) changed = true;
+ else break;
+ }
+ else
+ ++id;
+ }
+
+ if (id < accounts.getCount())
+ {
+ // Remove huge ids
+ for (i = 0; i < accounts.getCount(); i++)
+ {
+ if (accounts[i]->iOrder >= 1000000)
+ accounts[i]->iOrder = id++;
+ }
+ changed = true;
+ }
+
+ if (id < accounts.getCount())
+ {
+ // Remove duplicate ids
+ for (i = 0; i < accounts.getCount(); i++)
+ {
+ bool found = false;
+ for (int j = 0; j < accounts.getCount(); j++)
+ {
+ if (accounts[j]->iOrder == i)
+ {
+ if (found) accounts[j]->iOrder = id++;
+ else found = true;
+ }
+ }
+ }
+ changed = true;
+ }
+
+ return changed;
+}
+
+
+int FillTree(HWND hwnd)
+{
+ ProtocolData *PD;
+ int i;
+ PROTOACCOUNT* pa;
+
+ TVINSERTSTRUCT tvis;
+ tvis.hParent = NULL;
+ tvis.hInsertAfter = TVI_LAST;
+ tvis.item.mask = TVIF_PARAM|TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
+
+ TreeView_DeleteAllItems(hwnd);
+ if ( accounts.getCount() == 0 )
+ return FALSE;
+
+ for ( i = 0; i < accounts.getCount(); i++ ) {
+ int idx = cli.pfnGetAccountIndexByPos( i );
+ if ( idx == -1 )
+ continue;
+
+ pa = accounts[idx];
+
+ PD = ( ProtocolData* )mir_alloc( sizeof( ProtocolData ));
+ PD->RealName = pa->szModuleName;
+ PD->protopos = pa->iOrder;
+ PD->enabled = Proto_IsAccountEnabled( pa ) && isProtoSuitable( pa->ppro );
+ PD->show = PD->enabled ? pa->bIsVisible : 100;
+
+ tvis.item.lParam = ( LPARAM )PD;
+ tvis.item.pszText = pa->tszAccountName;
+ tvis.item.iImage = tvis.item.iSelectedImage = PD->show;
+ TreeView_InsertItem( hwnd, &tvis );
+ }
+
+ return 0;
+}
+
+INT_PTR CALLBACK ProtocolOrderOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ HWND hwndProtoOrder = GetDlgItem(hwndDlg, IDC_PROTOCOLORDER);
+ struct ProtocolOrderData *dat = (ProtocolOrderData*)GetWindowLongPtr(hwndProtoOrder, GWLP_USERDATA);
+
+ switch (msg)
+ {
+ case WM_DESTROY:
+ ImageList_Destroy(TreeView_GetImageList(hwndProtoOrder, TVSIL_NORMAL));
+ mir_free( dat );
+ break;
+
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ dat = (ProtocolOrderData*)mir_calloc(sizeof(ProtocolOrderData));
+ SetWindowLongPtr(hwndProtoOrder, GWLP_USERDATA, (LONG_PTR)dat);
+ dat->dragging=0;
+
+ SetWindowLong(hwndProtoOrder, GWL_STYLE, GetWindowLong(hwndProtoOrder, GWL_STYLE) | TVS_NOHSCROLL);
+ {
+ HIMAGELIST himlCheckBoxes = ImageList_Create( GetSystemMetrics( SM_CXSMICON ), GetSystemMetrics( SM_CYSMICON ), ILC_COLOR32|ILC_MASK, 2, 2 );
+ ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_NOTICK);
+ ImageList_AddIcon_IconLibLoaded(himlCheckBoxes, SKINICON_OTHER_TICK);
+ TreeView_SetImageList(hwndProtoOrder, himlCheckBoxes, TVSIL_NORMAL);
+ }
+
+ FillTree(hwndProtoOrder);
+ return TRUE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_RESETPROTOCOLDATA && HIWORD(wParam) == BN_CLICKED)
+ {
+ for ( int i = 0; i < accounts.getCount(); i++ )
+ accounts[i]->iOrder = i;
+
+ FillTree(hwndProtoOrder);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);
+ }
+ break;
+
+ case WM_NOTIFY:
+ switch(((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ if (((LPNMHDR)lParam)->code == PSN_APPLY ) {
+ int count = 0;
+
+ TVITEM tvi;
+ tvi.hItem = TreeView_GetRoot(hwndProtoOrder);
+ tvi.cchTextMax = 32;
+ tvi.mask = TVIF_PARAM | TVIF_HANDLE;
+
+ while ( tvi.hItem != NULL ) {
+ TreeView_GetItem(hwndProtoOrder, &tvi);
+
+ if (tvi.lParam!=0) {
+ ProtocolData* ppd = ( ProtocolData* )tvi.lParam;
+ PROTOACCOUNT* pa = Proto_GetAccount( ppd->RealName );
+ if ( pa != NULL ) {
+ pa->iOrder = count++;
+ if ( ppd->enabled )
+ pa->bIsVisible = ppd->show;
+ }
+ }
+
+ tvi.hItem = TreeView_GetNextSibling(hwndProtoOrder, tvi.hItem );
+ }
+
+ WriteDbAccounts();
+ cli.pfnReloadProtoMenus();
+ cli.pfnTrayIconIconsChanged();
+ cli.pfnClcBroadcast( INTM_RELOADOPTIONS, 0, 0 );
+ cli.pfnClcBroadcast( INTM_INVALIDATE, 0, 0 );
+ }
+ break;
+
+ case IDC_PROTOCOLORDER:
+ switch (((LPNMHDR)lParam)->code) {
+ case TVN_DELETEITEMA:
+ {
+ NMTREEVIEWA * pnmtv = (NMTREEVIEWA *) lParam;
+ if (pnmtv && pnmtv->itemOld.lParam)
+ mir_free((ProtocolData*)pnmtv->itemOld.lParam);
+ }
+ break;
+
+ case TVN_BEGINDRAGA:
+ SetCapture(hwndDlg);
+ dat->dragging=1;
+ dat->hDragItem=((LPNMTREEVIEW)lParam)->itemNew.hItem;
+ TreeView_SelectItem(hwndProtoOrder, dat->hDragItem);
+ break;
+
+ case NM_CLICK:
+ {
+ TVHITTESTINFO hti;
+ hti.pt.x=(short)LOWORD(GetMessagePos());
+ hti.pt.y=(short)HIWORD(GetMessagePos());
+ ScreenToClient(((LPNMHDR)lParam)->hwndFrom,&hti.pt);
+ if ( TreeView_HitTest(((LPNMHDR)lParam)->hwndFrom, &hti )) {
+ if ( hti.flags & TVHT_ONITEMICON ) {
+ TVITEMA tvi;
+ tvi.mask = TVIF_HANDLE|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
+ tvi.hItem = hti.hItem;
+ TreeView_GetItem(((LPNMHDR)lParam)->hwndFrom,&tvi);
+
+ ProtocolData *pData = ( ProtocolData* )tvi.lParam;
+ if ( pData->enabled ) {
+ tvi.iImage = tvi.iSelectedImage = !tvi.iImage;
+ pData->show = tvi.iImage;
+ TreeView_SetItem(((LPNMHDR)lParam)->hwndFrom,&tvi);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);
+ } } } } }
+ break;
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ if ( dat->dragging ) {
+ TVHITTESTINFO hti;
+ hti.pt.x=(short)LOWORD(lParam);
+ hti.pt.y=(short)HIWORD(lParam);
+ ClientToScreen(hwndDlg, &hti.pt);
+ ScreenToClient(hwndProtoOrder, &hti.pt);
+ TreeView_HitTest(hwndProtoOrder, &hti);
+ if ( hti.flags & (TVHT_ONITEM|TVHT_ONITEMRIGHT ))
+ {
+ HTREEITEM it = hti.hItem;
+ hti.pt.y -= TreeView_GetItemHeight(hwndProtoOrder) / 2;
+ TreeView_HitTest(hwndProtoOrder, &hti);
+ if ( !( hti.flags & TVHT_ABOVE ))
+ TreeView_SetInsertMark(hwndProtoOrder, hti.hItem, 1);
+ else
+ TreeView_SetInsertMark(hwndProtoOrder, it, 0);
+ }
+ else {
+ if (hti.flags&TVHT_ABOVE) SendMessage(hwndProtoOrder, WM_VSCROLL, MAKEWPARAM(SB_LINEUP, 0), 0);
+ if (hti.flags&TVHT_BELOW) SendMessage(hwndProtoOrder, WM_VSCROLL, MAKEWPARAM(SB_LINEDOWN, 0), 0);
+ TreeView_SetInsertMark(hwndProtoOrder, NULL, 0);
+ } }
+ break;
+
+ case WM_LBUTTONUP:
+ if ( dat->dragging ) {
+ TVHITTESTINFO hti;
+ TVITEM tvi;
+
+ TreeView_SetInsertMark(hwndProtoOrder, NULL, 0);
+ dat->dragging = 0;
+ ReleaseCapture();
+
+ hti.pt.x = (short)LOWORD(lParam);
+ hti.pt.y = (short)HIWORD(lParam);
+ ClientToScreen(hwndDlg, &hti.pt);
+ ScreenToClient(hwndProtoOrder, &hti.pt);
+ hti.pt.y -= TreeView_GetItemHeight(hwndProtoOrder) / 2;
+ TreeView_HitTest(hwndProtoOrder, &hti);
+ if (dat->hDragItem == hti.hItem) break;
+ if (hti.flags & TVHT_ABOVE) hti.hItem = TVI_FIRST;
+ tvi.mask = TVIF_HANDLE|TVIF_PARAM;
+ tvi.hItem = dat->hDragItem;
+ TreeView_GetItem(hwndProtoOrder, &tvi);
+ if ( hti.flags & (TVHT_ONITEM | TVHT_ONITEMRIGHT) || (hti.hItem == TVI_FIRST))
+ {
+ TVINSERTSTRUCT tvis;
+ TCHAR name[128];
+ ProtocolData * lpOldData;
+ tvis.item.mask = TVIF_HANDLE|TVIF_PARAM|TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
+ tvis.item.stateMask = 0xFFFFFFFF;
+ tvis.item.pszText = name;
+ tvis.item.cchTextMax = SIZEOF(name);
+ tvis.item.hItem = dat->hDragItem;
+ tvis.item.iImage = tvis.item.iSelectedImage = ((ProtocolData *)tvi.lParam)->show;
+ TreeView_GetItem(hwndProtoOrder, &tvis.item);
+
+ //the pointed lParam will be freed inside TVN_DELETEITEM
+ //so lets substitute it with 0
+ lpOldData=(ProtocolData *)tvis.item.lParam;
+ tvis.item.lParam=0;
+ TreeView_SetItem(hwndProtoOrder, &tvis.item);
+ tvis.item.lParam=(LPARAM)lpOldData;
+
+ //now current item contain lParam=0 we can delete it. the memory will be kept.
+ TreeView_DeleteItem(hwndProtoOrder, dat->hDragItem);
+ tvis.hParent = NULL;
+ tvis.hInsertAfter = hti.hItem;
+ TreeView_SelectItem(hwndProtoOrder, TreeView_InsertItem(hwndProtoOrder, &tvis));
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);
+ } }
+ break;
+ }
+ return FALSE;
+}