From 48540940b6c28bb4378abfeb500ec45a625b37b6 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Tue, 15 May 2012 10:38:20 +0000 Subject: initial commit git-svn-id: http://svn.miranda-ng.org/main/trunk@2 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- src/modules/clist/Docking.cpp | 392 ++++++++++ src/modules/clist/clc.cpp | 1350 ++++++++++++++++++++++++++++++++ src/modules/clist/clc.h | 248 ++++++ src/modules/clist/clcfiledrop.cpp | 278 +++++++ src/modules/clist/clcidents.cpp | 201 +++++ src/modules/clist/clcitems.cpp | 707 +++++++++++++++++ src/modules/clist/clcmsgs.cpp | 472 ++++++++++++ src/modules/clist/clcutils.cpp | 879 +++++++++++++++++++++ src/modules/clist/clistcore.cpp | 224 ++++++ src/modules/clist/clistevents.cpp | 441 +++++++++++ src/modules/clist/clistmenus.cpp | 1443 +++++++++++++++++++++++++++++++++++ src/modules/clist/clistmod.cpp | 569 ++++++++++++++ src/modules/clist/clistsettings.cpp | 331 ++++++++ src/modules/clist/clisttray.cpp | 980 ++++++++++++++++++++++++ src/modules/clist/clui.cpp | 1136 +++++++++++++++++++++++++++ src/modules/clist/cluiservices.cpp | 221 ++++++ src/modules/clist/contact.cpp | 193 +++++ src/modules/clist/genmenu.cpp | 1294 +++++++++++++++++++++++++++++++ src/modules/clist/genmenu.h | 146 ++++ src/modules/clist/genmenuopt.cpp | 870 +++++++++++++++++++++ src/modules/clist/groups.cpp | 577 ++++++++++++++ src/modules/clist/keyboard.cpp | 173 +++++ src/modules/clist/movetogroup.cpp | 160 ++++ src/modules/clist/protocolorder.cpp | 351 +++++++++ 24 files changed, 13636 insertions(+) create mode 100644 src/modules/clist/Docking.cpp create mode 100644 src/modules/clist/clc.cpp create mode 100644 src/modules/clist/clc.h create mode 100644 src/modules/clist/clcfiledrop.cpp create mode 100644 src/modules/clist/clcidents.cpp create mode 100644 src/modules/clist/clcitems.cpp create mode 100644 src/modules/clist/clcmsgs.cpp create mode 100644 src/modules/clist/clcutils.cpp create mode 100644 src/modules/clist/clistcore.cpp create mode 100644 src/modules/clist/clistevents.cpp create mode 100644 src/modules/clist/clistmenus.cpp create mode 100644 src/modules/clist/clistmod.cpp create mode 100644 src/modules/clist/clistsettings.cpp create mode 100644 src/modules/clist/clisttray.cpp create mode 100644 src/modules/clist/clui.cpp create mode 100644 src/modules/clist/cluiservices.cpp create mode 100644 src/modules/clist/contact.cpp create mode 100644 src/modules/clist/genmenu.cpp create mode 100644 src/modules/clist/genmenu.h create mode 100644 src/modules/clist/genmenuopt.cpp create mode 100644 src/modules/clist/groups.cpp create mode 100644 src/modules/clist/keyboard.cpp create mode 100644 src/modules/clist/movetogroup.cpp create mode 100644 src/modules/clist/protocolorder.cpp (limited to 'src/modules/clist') 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 + +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 saveContact( 10, HandleKeySortT ); + OBJLIST saveGroup( 100, NumericKeySortT ); + OBJLIST 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, ¶m, FALSE) && !param) + ret |= CLS_EX_NOSMOOTHSCROLLING; + if (SystemParametersInfo(SPI_GETHOTTRACKING, 0, ¶m, 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 && idxcle.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=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; icle.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; icle.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; jcle.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)¶m); + 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)¶m); + + 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)¶m); + 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=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(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("%-12.12s\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%-12.12s\t%s%s%-24.24s"), szPrefix, szSeparator, pa->tszAccountName, szStatus,szSeparator,ProtoXStatus); + else + mir_sntprintf(cli.szTip, MAX_TIP_SIZE, _T("%s%s%-12.12s\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("%-12.12s\t%s\n%-24.24s"), pa->tszAccountName, szStatus,ProtoXStatus); + else + mir_sntprintf( cli.szTip, MAX_TIP_SIZE, _T("%-12.12s\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; iitemData == 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 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 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; inmcd.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 + +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 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 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(""), 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; +} -- cgit v1.2.3