summaryrefslogtreecommitdiff
path: root/plugins/NewAwaySysMod/ContactList.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/NewAwaySysMod/ContactList.cpp')
-rw-r--r--plugins/NewAwaySysMod/ContactList.cpp905
1 files changed, 905 insertions, 0 deletions
diff --git a/plugins/NewAwaySysMod/ContactList.cpp b/plugins/NewAwaySysMod/ContactList.cpp
new file mode 100644
index 0000000000..70b26d1d57
--- /dev/null
+++ b/plugins/NewAwaySysMod/ContactList.cpp
@@ -0,0 +1,905 @@
+/*
+ New Away System - plugin for Miranda IM
+ Copyright (c) 2005-2007 Chervov Dmitry
+
+ 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 "Common.h"
+#include "ContactList.h"
+#include "Properties.h"
+
+#define INTM_CONTACTDELETED (WM_USER + 1)
+#define INTM_ICONCHANGED (WM_USER + 2)
+#define INTM_INVALIDATE (WM_USER + 3)
+
+#define HCONTACT_ISGROUP 0x80000000
+#define HCONTACT_ISINFO 0xFFFF0000
+#define IsHContactInfo(h) (((unsigned)(h) & HCONTACT_ISINFO) == HCONTACT_ISINFO)
+#define IsHContactGroup(h) (!IsHContactInfo(h) && ((unsigned)(h) & HCONTACT_ISGROUP))
+#define IsHContactContact(h) (((unsigned)(h) & HCONTACT_ISGROUP) == 0)
+
+#define EXTRAICON_XSTEP (GetSystemMetrics(SM_CXSMICON) + 1)
+
+static HANDLE hCLWindowList;
+
+
+static int CLContactDeleted(WPARAM wParam, LPARAM lParam)
+{
+ WindowList_Broadcast(hCLWindowList, INTM_CONTACTDELETED, wParam, lParam);
+ return 0;
+}
+
+static int CLContactIconChanged(WPARAM wParam, LPARAM lParam)
+{
+ WindowList_Broadcast(hCLWindowList, INTM_ICONCHANGED, wParam, lParam);
+ return 0;
+}
+
+static int CLIconsChanged(WPARAM wParam, LPARAM lParam)
+{
+ WindowList_Broadcast(hCLWindowList, INTM_INVALIDATE, 0, 0);
+ return 0;
+}
+
+void LoadCListModule()
+{
+ hCLWindowList = (HANDLE)CallService(MS_UTILS_ALLOCWINDOWLIST, 0, 0);
+ HookEvent(ME_DB_CONTACT_DELETED, CLContactDeleted);
+ HookEvent(ME_CLIST_CONTACTICONCHANGED, CLContactIconChanged);
+ HookEvent(ME_SKIN_ICONSCHANGED, CLIconsChanged);
+}
+
+
+static LRESULT CALLBACK ParentSubclassProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
+{
+ CCList *dat = CWndUserData(hWnd).GetCList();
+ switch (Msg)
+ {
+ case WM_NOTIFY:
+ {
+ LPNMHDR pnmh = (LPNMHDR)lParam;
+ if (pnmh->hwndFrom == dat->hTreeView)
+ {
+ switch (pnmh->code)
+ {
+ case TVN_ITEMEXPANDED: // just set an appropriate group image
+ {
+ LPNMTREEVIEW pnmtv = (LPNMTREEVIEW)lParam;
+ TVITEM tvItem;
+ tvItem.hItem = pnmtv->itemNew.hItem;
+ tvItem.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ tvItem.iImage = tvItem.iSelectedImage = (pnmtv->itemNew.state & TVIS_EXPANDED) ? IMAGE_GROUPOPEN : IMAGE_GROUPSHUT;
+ TreeView_SetItem(dat->hTreeView, &tvItem);
+ } break;
+ case TVN_SELCHANGED:
+ {
+ LPNMTREEVIEW pnmtv = (LPNMTREEVIEW)lParam;
+ TREEITEMARRAY OldSelection = dat->SelectedItems;
+ int I;
+ for (I = 0; I < dat->SelectedItems.GetSize(); I++)
+ {
+ if (dat->SelectedItems[I] != pnmtv->itemNew.hItem)
+ {
+ TreeView_SetItemState(dat->hTreeView, dat->SelectedItems[I], 0, TVIS_SELECTED);
+ }
+ }
+ dat->SelectedItems.RemoveAll();
+ if (pnmtv->itemNew.hItem)
+ {
+ dat->SelectedItems.AddElem(pnmtv->itemNew.hItem);
+ dat->SelectGroups(pnmtv->itemNew.hItem, true);
+ }
+ NMCLIST nm;
+ nm.hdr.code = MCLN_SELCHANGED;
+ nm.hdr.hwndFrom = dat->hTreeView;
+ nm.hdr.idFrom = GetDlgCtrlID(dat->hTreeView);
+ nm.OldSelection = &OldSelection;
+ nm.NewSelection = &dat->SelectedItems;
+ SendMessage(hWnd, WM_NOTIFY, 0, (LPARAM)&nm);
+ } break;
+ case TVN_DELETEITEM:
+ {
+ if (dat->Items.GetSize()) // if Items size = 0, then this TVN_DELETEITEM came after WM_DESTROY, so there is no need to do anything
+ {
+ LPNMTREEVIEW pnmtv = (LPNMTREEVIEW)lParam;
+ TREEITEMARRAY OldSelection = dat->SelectedItems;
+ int Index = dat->SelectedItems.Find(pnmtv->itemOld.hItem);
+ if (Index != -1)
+ {
+ dat->SelectedItems.RemoveElem(Index);
+ }
+ // find an item to pass to SelectGroups()
+ HTREEITEM hItem = TreeView_GetNextSibling(dat->hTreeView, pnmtv->itemOld.hItem);
+ if (!hItem)
+ {
+ hItem = TreeView_GetPrevSibling(dat->hTreeView, pnmtv->itemOld.hItem);
+ if (!hItem)
+ {
+ hItem = TreeView_GetParent(dat->hTreeView, pnmtv->itemOld.hItem);
+ }
+ }
+ if (hItem) // if it wasn't one of the root items
+ {
+ dat->SelectGroups(hItem, dat->SelectedItems.Find(hItem) != -1);
+ }
+ NMCLIST nm;
+ nm.hdr.code = MCLN_SELCHANGED;
+ nm.hdr.hwndFrom = dat->hTreeView;
+ nm.hdr.idFrom = GetDlgCtrlID(dat->hTreeView);
+ nm.OldSelection = &OldSelection;
+ nm.NewSelection = &dat->SelectedItems;
+ SendMessage(hWnd, WM_NOTIFY, 0, (LPARAM)&nm);
+ dat->Items[pnmtv->itemOld.lParam].hContact = INVALID_HANDLE_VALUE;
+ }
+ } break;
+ case NM_CUSTOMDRAW:
+ {
+ LPNMTVCUSTOMDRAW lpNMCD = (LPNMTVCUSTOMDRAW)lParam;
+ switch (lpNMCD->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT: // the control is about to start painting
+ {
+ return CDRF_NOTIFYITEMDRAW; // instruct the control to return information when it draws items
+ } break;
+ case CDDS_ITEMPREPAINT:
+ {
+ return CDRF_NOTIFYPOSTPAINT;
+ } break;
+ case CDDS_ITEMPOSTPAINT:
+ {
+ RECT rc;
+ if (TreeView_GetItemRect(dat->hTreeView, (HTREEITEM)lpNMCD->nmcd.dwItemSpec, &rc, false))
+ {
+ int I;
+ for (I = 0; I < MAXEXTRAICONS; I++)
+ {
+ BYTE nIndex = dat->Items[lpNMCD->nmcd.lItemlParam].ExtraIcons[I];
+ if (nIndex != CLC_EXTRAICON_EMPTY)
+ {
+ ImageList_DrawEx(dat->ExtraImageList, nIndex, lpNMCD->nmcd.hdc, rc.right - EXTRAICON_XSTEP * (I + 1), rc.top, 0, 0, /*GetSysColor(COLOR_WINDOW)*/CLR_NONE, CLR_NONE, ILD_NORMAL);
+ }
+ }
+ }
+ } break;
+ }
+ } break;
+ }
+ }
+ }
+ }
+ return CallWindowProc(dat->OrigParentProc, hWnd, Msg, wParam, lParam);
+}
+
+
+static LRESULT CALLBACK ContactListSubclassProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
+{
+ CCList *dat = CWndUserData(GetParent(hWnd)).GetCList();
+ switch (Msg)
+ {
+ case INTM_CONTACTDELETED: // wParam = (HANDLE)hContact
+ {
+ HTREEITEM hItem = dat->FindContact((HANDLE)wParam);
+ if (hItem)
+ {
+ TreeView_DeleteItem(hWnd, hItem);
+ }
+ } break;
+ case INTM_ICONCHANGED: // wParam = (HANDLE)hContact, lParam = IconID
+ {
+ TVITEM tvi;
+ tvi.hItem = dat->FindContact((HANDLE)wParam);
+ if (tvi.hItem)
+ {
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ tvi.iImage = tvi.iSelectedImage = lParam;
+ TreeView_SetItem(hWnd, &tvi);
+ dat->SortContacts();
+ InvalidateRect(hWnd, NULL, false);
+ }
+ } break;
+ case INTM_INVALIDATE:
+ {
+ InvalidateRect(hWnd, NULL, true);
+ } break;
+ case WM_RBUTTONDOWN:
+ {
+ SetFocus(hWnd);
+ TVHITTESTINFO hitTest;
+ hitTest.pt.x = (short)LOWORD(lParam);
+ hitTest.pt.y = (short)HIWORD(lParam);
+ TreeView_HitTest(hWnd, &hitTest);
+ if (hitTest.hItem && hitTest.flags & TVHT_ONITEM)
+ {
+ TreeView_SelectItem(hWnd, hitTest.hItem);
+ }
+ return DefWindowProc(hWnd, Msg, wParam, lParam);
+ } break;
+ case WM_LBUTTONDOWN:
+ {
+ POINT pt = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
+ DWORD hitFlags;
+ HTREEITEM hItem = dat->HitTest(&pt, &hitFlags);
+ if (!hItem)
+ {
+ break;
+ }
+ if (hitFlags & MCLCHT_ONITEMICON)
+ {
+ if (TreeView_GetChild(hWnd, hItem)) // if it's a group, then toggle its state
+ {
+ NMTREEVIEW nmtv;
+ nmtv.hdr.hwndFrom = hWnd;
+ nmtv.hdr.idFrom = GetDlgCtrlID(hWnd);
+ nmtv.hdr.code = TVN_ITEMEXPANDING;
+ nmtv.action = TVE_TOGGLE;
+ nmtv.itemNew.hItem = hItem;
+ nmtv.itemNew.mask = TVIF_HANDLE | TVIF_STATE | TVIF_PARAM;
+ TreeView_GetItem(hWnd, &nmtv.itemNew);
+ nmtv.ptDrag = pt;
+ if (SendMessage(GetParent(hWnd), WM_NOTIFY, 0, (LPARAM)&nmtv))
+ {
+ return 0;
+ }
+ HTREEITEM hOldSelItem = TreeView_GetSelection(hWnd);
+ TreeView_Expand(hWnd, hItem, TVE_TOGGLE);
+ HTREEITEM hNewSelItem = TreeView_GetSelection(hWnd);
+ if (hNewSelItem != hOldSelItem)
+ {
+ TreeView_SetItemState(hWnd, hOldSelItem, (dat->SelectedItems.Find(hOldSelItem) == -1) ? 0 : TVIS_SELECTED, TVIS_SELECTED);
+ TreeView_SetItemState(hWnd, hNewSelItem, (dat->SelectedItems.Find(hNewSelItem) == -1) ? 0 : TVIS_SELECTED, TVIS_SELECTED);
+ }
+ nmtv.hdr.code = TVN_ITEMEXPANDED;
+ TreeView_GetItem(hWnd, &nmtv.itemNew);
+ SendMessage(GetParent(hWnd), WM_NOTIFY, 0, (LPARAM)&nmtv);
+ return 0;
+ }
+ }
+ if (hitFlags & MCLCHT_ONITEM)
+ {
+ if (wParam & MK_CONTROL)
+ {
+ SetFocus(hWnd);
+ TREEITEMARRAY OldSelection = dat->SelectedItems;
+ int nIndex = dat->SelectedItems.Find(hItem);
+ if (nIndex == -1)
+ {
+ TreeView_SetItemState(hWnd, hItem, TVIS_SELECTED, TVIS_SELECTED);
+ dat->SelectedItems.AddElem(hItem);
+ } else
+ {
+ TreeView_SetItemState(hWnd, hItem, 0, TVIS_SELECTED);
+ dat->SelectedItems.RemoveElem(nIndex);
+ }
+ dat->SelectGroups(hItem, nIndex == -1);
+ NMCLIST nm;
+ nm.hdr.code = MCLN_SELCHANGED;
+ nm.hdr.hwndFrom = hWnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hWnd);
+ nm.OldSelection = &OldSelection;
+ nm.NewSelection = &dat->SelectedItems;
+ SendMessage(GetParent(hWnd), WM_NOTIFY, 0, (LPARAM)&nm);
+ return 0;
+ } else
+ {
+ if (hItem == TreeView_GetSelection(hWnd) && (dat->SelectedItems.GetSize() != 1 || (dat->SelectedItems.GetSize() == 1 && dat->SelectedItems[0] != hItem))) // if it was a click on the selected item and there's need to do something in this case, then send SELCHANGED notification by ourselves, as the tree control doesn't do anything
+ {
+ TreeView_SetItemState(hWnd, hItem, TVIS_SELECTED, TVIS_SELECTED);
+ NMTREEVIEW nm = {0};
+ nm.hdr.code = TVN_SELCHANGED;
+ nm.hdr.hwndFrom = hWnd;
+ nm.hdr.idFrom = GetDlgCtrlID(hWnd);
+ nm.itemOld.hItem = TreeView_GetSelection(hWnd);
+ nm.itemOld.mask = TVIF_HANDLE | TVIF_STATE | TVIF_PARAM;
+ TreeView_GetItem(hWnd, &nm.itemOld);
+ nm.itemNew = nm.itemOld;
+ SendMessage(GetParent(hWnd), WM_NOTIFY, 0, (LPARAM)&nm);
+ }
+ }
+ }
+ } break;
+ case WM_SETFOCUS:
+ case WM_KILLFOCUS:
+ {
+ int I;
+ for (I = 0; I < dat->SelectedItems.GetSize(); I++)
+ {
+ RECT rc;
+ if (TreeView_GetItemRect(hWnd, dat->SelectedItems[I], &rc, false))
+ {
+ InvalidateRect(hWnd, &rc, false);
+ }
+ }
+ } break;
+ case WM_SIZE:
+ case WM_HSCROLL:
+ {
+ InvalidateRect(hWnd, NULL, false);
+ } break;
+ case WM_MEASUREITEM:
+ {
+ if (!wParam) // if the message was sent by a menu
+ {
+ return CallService(MS_CLIST_MENUMEASUREITEM, wParam, lParam);
+ }
+ } break;
+ case WM_DRAWITEM:
+ {
+ if (!wParam) // if the message was sent by a menu
+ {
+ return CallService(MS_CLIST_MENUDRAWITEM, wParam, lParam);
+ }
+ } break;
+ case WM_CONTEXTMENU:
+ {
+ POINT pt;
+ pt.x = (short)LOWORD(lParam);
+ pt.y = (short)HIWORD(lParam);
+ HTREEITEM hItem = NULL;
+ if (pt.x == -1 && pt.y == -1)
+ {
+ if (dat->SelectedItems.GetSize() == 1)
+ {
+ hItem = dat->SelectedItems[0];
+ TreeView_EnsureVisible(hWnd, hItem);
+ RECT rc;
+ TreeView_GetItemRect(hWnd, hItem, &rc, true);
+ pt.x = rc.left;
+ pt.y = rc.bottom;
+ }
+ } else
+ {
+ DWORD hitFlags;
+ ScreenToClient(hWnd, &pt);
+ hItem = dat->HitTest(&pt, &hitFlags);
+ if (!(hitFlags & MCLCHT_ONITEM))
+ {
+ hItem = NULL;
+ }
+ }
+ if (hItem)
+ {
+ HANDLE hContact = dat->GetItemData(hItem).hContact;
+ if (IsHContactContact(hContact))
+ {
+ HMENU hMenu = (HMENU)CallService(MS_CLIST_MENUBUILDCONTACT, (WPARAM)hContact, 0);
+ if (hMenu)
+ {
+ ClientToScreen(hWnd, &pt);
+ CallService(MS_CLIST_MENUPROCESSCOMMAND, MAKEWPARAM(TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, 0, hWnd, NULL), MPCF_CONTACTMENU), (LPARAM)hContact);
+ DestroyMenu(hMenu);
+ return 0;
+ }
+ }
+ }
+ } break;
+ case WM_DESTROY:
+ {
+ if (dat->ExtraImageList)
+ {
+ ImageList_Destroy(dat->ExtraImageList);
+ }
+ dat->SelectedItems.RemoveAll();
+ dat->Items.RemoveAll();
+ } break;
+ }
+ return CallWindowProc(dat->OrigTreeViewProc, hWnd, Msg, wParam, lParam);
+}
+
+
+CCList::CCList(HWND hTreeView): hTreeView(hTreeView), ExtraImageList(NULL)
+{
+ CWndUserData(GetParent(hTreeView)).SetCList(this);
+ OrigTreeViewProc = (WNDPROC)SetWindowLongPtr(hTreeView, GWLP_WNDPROC, (LONG_PTR)ContactListSubclassProc);
+ OrigParentProc = (WNDPROC)SetWindowLongPtr(GetParent(hTreeView), GWLP_WNDPROC, (LONG_PTR)ParentSubclassProc);
+ TreeView_SetImageList(hTreeView, CallService(MS_CLIST_GETICONSIMAGELIST, 0, 0), TVSIL_NORMAL);
+ WindowList_Add(hCLWindowList, hTreeView, NULL);
+ TreeView_SetIndent(hTreeView, 5); // doesn't set it less than the initial value on my system, and I guess it's because of icons... but who knows - maybe it will work somewhere
+}
+
+
+CCList::~CCList()
+{
+ WindowList_Remove(hCLWindowList, hTreeView);
+ _ASSERT(GetWindowLongPtr(GetParent(hTreeView), GWLP_WNDPROC) == (LONG_PTR)ParentSubclassProc); // we won't allow anyone to change our WNDPROC. otherwise we're not sure that we're setting the right WNDPROC back
+ SetWindowLongPtr(hTreeView, GWLP_WNDPROC, (LONG_PTR)OrigTreeViewProc);
+ SetWindowLongPtr(GetParent(hTreeView), GWLP_WNDPROC, (LONG_PTR)OrigParentProc);
+ CWndUserData(GetParent(hTreeView)).SetCList(NULL);
+}
+
+
+HTREEITEM CCList::AddContact(HANDLE hContact)
+// adds a new contact if it doesn't exist yet; returns its hItem
+{
+ _ASSERT(IsHContactContact(hContact));
+ HTREEITEM hContactItem = FindContact(hContact);
+ if (hContactItem)
+ {
+ return hContactItem;
+ }
+ TVINSERTSTRUCT tvIns;
+ ZeroMemory(&tvIns, sizeof(tvIns));
+ tvIns.hParent = AddGroup(DBGetContactSettingString(hContact, "CList", "Group", _T("")));
+/* if (!tvIns.hParent)
+ {
+ return NULL;
+ }*/ // <- place hidden contacts in the root anyway, as otherwise we won't see icq contacts that are hidden beneath metacontacts; TODO: show metacontacts as groups??
+ tvIns.item.pszText = (TCHAR*)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR);
+ tvIns.hInsertAfter = TVI_ROOT;
+ tvIns.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
+ tvIns.item.iImage = tvIns.item.iSelectedImage = CallService(MS_CLIST_GETCONTACTICON, (WPARAM)hContact, 0);
+ tvIns.item.lParam = Items.AddElem(CCLItemData(hContact));
+ return TreeView_InsertItem(hTreeView, &tvIns);
+}
+
+
+typedef struct
+{
+ HANDLE hGroup;
+ TCString GroupName;
+} sGroupEnumData;
+
+int GroupEnum(const char *szSetting, LPARAM lParam)
+{
+ sGroupEnumData *GroupEnumData = (sGroupEnumData*)lParam;
+ TCString GroupName = DBGetContactSettingString(NULL, "CListGroups", szSetting, _T(" "));
+ if (!lstrcmp(GroupEnumData->GroupName, &GroupName[1]))
+ {
+ GroupEnumData->hGroup = (HANDLE)(atol(szSetting) | HCONTACT_ISGROUP);
+ }
+ return 0;
+}
+
+HTREEITEM CCList::AddGroup(TCString GroupName)
+// adds a new group if it doesn't exist yet; returns its hItem
+{
+ if (GroupName == _T(""))
+ {
+ return TVI_ROOT;
+ }
+ sGroupEnumData GroupEnumData;
+ GroupEnumData.GroupName = GroupName;
+ GroupEnumData.hGroup = NULL;
+ DBCONTACTENUMSETTINGS dbEnum;
+ ZeroMemory(&dbEnum, sizeof(dbEnum));
+ dbEnum.lParam = (LPARAM)&GroupEnumData;
+ dbEnum.pfnEnumProc = GroupEnum;
+ dbEnum.szModule = "CListGroups";
+ CallService(MS_DB_CONTACT_ENUMSETTINGS, NULL, (LPARAM)&dbEnum);
+ if (!GroupEnumData.hGroup) // means there is no such group in the groups list
+ {
+ return NULL;
+ }
+ HTREEITEM hGroupItem = FindContact(GroupEnumData.hGroup);
+ if (hGroupItem)
+ {
+ return hGroupItem; // exists already, just return its handle
+ }
+ TVINSERTSTRUCT tvIns = {0};
+ tvIns.hParent = TVI_ROOT;
+ tvIns.item.pszText = _tcsrchr(GroupName, '\\');
+ if (tvIns.item.pszText)
+ {
+ TCString ParentGroupName(_T(""));
+ tvIns.hParent = AddGroup(ParentGroupName.DiffCat(GroupName, tvIns.item.pszText));
+ tvIns.item.pszText++;
+ } else
+ {
+ tvIns.item.pszText = GroupName;
+ }
+ tvIns.hInsertAfter = TVI_ROOT;
+ tvIns.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM;
+ tvIns.item.state = tvIns.item.stateMask = TVIS_BOLD | TVIS_EXPANDED;
+ tvIns.item.iImage = tvIns.item.iSelectedImage = IMAGE_GROUPOPEN;
+ tvIns.item.lParam = Items.AddElem(CCLItemData(GroupEnumData.hGroup));
+ return TreeView_InsertItem(hTreeView, &tvIns);
+}
+
+
+HTREEITEM CCList::AddInfo(TCString Title, HTREEITEM hParent, HTREEITEM hInsertAfter, LPARAM lParam, HICON hIcon)
+{
+ TVINSERTSTRUCT tvi = {0};
+ tvi.item.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM;
+ tvi.item.pszText = Title;
+ tvi.hParent = hParent;
+ tvi.hInsertAfter = hInsertAfter;
+ tvi.item.lParam = Items.AddElem(CCLItemData());
+ Items[tvi.item.lParam].lParam = lParam;
+ tvi.item.state = tvi.item.stateMask = TVIS_BOLD | TVIS_EXPANDED;
+ if (hIcon)
+ {
+ HIMAGELIST iml = TreeView_GetImageList(hTreeView, TVSIL_NORMAL);
+ tvi.item.mask |= TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ tvi.item.iImage = tvi.item.iSelectedImage = ImageList_AddIcon(iml, hIcon); // we don't check for duplicate icons, but I think that's ok, judging that the check will require some pretty significant amount of additional coding
+ TreeView_SetImageList(hTreeView, iml, TVSIL_NORMAL);
+ }
+ return TreeView_InsertItem(hTreeView, &tvi);
+}
+
+
+void CCList::SetInfoIcon(HTREEITEM hItem, HICON hIcon)
+{
+ _ASSERT(hItem && hIcon && GetItemType(hItem) == MCLCIT_INFO);
+ TVITEM tvi = {0};
+ tvi.mask = TVIF_HANDLE | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
+ tvi.hItem = hItem;
+ HIMAGELIST iml = TreeView_GetImageList(hTreeView, TVSIL_NORMAL);
+ tvi.iImage = tvi.iSelectedImage = ImageList_AddIcon(iml, hIcon); // again, we don't check for duplicate icons
+ TreeView_SetImageList(hTreeView, iml, TVSIL_NORMAL);
+ TreeView_SetItem(hTreeView, &tvi);
+}
+
+
+static int CALLBACK CompareItemsCallback(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
+{
+ CCList *dat = (CCList*)lParamSort;
+ if (IsHContactInfo(dat->Items[lParam1].hContact)) // Info items precede all other items
+ {
+ return (IsHContactInfo(dat->Items[lParam2].hContact)) ? 0 : -1;
+ } else if (IsHContactInfo(dat->Items[lParam2].hContact))
+ {
+ return 1;
+ }
+ if (IsHContactGroup(dat->Items[lParam1].hContact)) // groups precede contacts
+ {
+ if (IsHContactGroup(dat->Items[lParam2].hContact))
+ {
+ return (unsigned)dat->Items[lParam1].hContact - (unsigned)dat->Items[lParam2].hContact;
+ } else
+ {
+ return -1;
+ }
+ } else if (IsHContactGroup(dat->Items[lParam2].hContact))
+ {
+ return 1;
+ }
+ return CallService(MS_CLIST_CONTACTSCOMPARE, (WPARAM)dat->Items[lParam1].hContact, (LPARAM)dat->Items[lParam2].hContact);
+}
+
+void CCList::SortContacts()
+{
+ TVSORTCB tvSort;
+ ZeroMemory(&tvSort, sizeof(tvSort));
+ tvSort.lpfnCompare = CompareItemsCallback;
+ tvSort.hParent = TVI_ROOT;
+ tvSort.lParam = (LPARAM)this;
+ while (tvSort.hParent)
+ {
+ TreeView_SortChildrenCB(hTreeView, &tvSort, 0);
+ tvSort.hParent = GetNextItem(MCLGN_NEXT | MCLGN_GROUP | MCLGN_MULTILEVEL, tvSort.hParent);
+ }
+}
+
+
+int CCList::GetExtraImage(HTREEITEM hItem, int iColumn) // returns iImage, or CLC_EXTRAICON_EMPTY
+{
+ _ASSERT(iColumn < MAXEXTRAICONS);
+ return GetItemData(hItem).ExtraIcons[iColumn];
+}
+
+
+void CCList::SetExtraImage(HTREEITEM hItem, int iColumn, int iImage) // set iImage to CLC_EXTRAICON_EMPTY to reset image
+{
+ _ASSERT(iColumn < MAXEXTRAICONS);
+ GetItemData(hItem).ExtraIcons[iColumn] = iImage;
+ RECT rc;
+ if (TreeView_GetItemRect(hTreeView, hItem, &rc, false))
+ {
+ InvalidateRect(hTreeView, &rc, true);
+ }
+}
+
+
+void CCList::SetExtraImageList(HIMAGELIST hImgList)
+{
+ ExtraImageList = hImgList;
+ InvalidateRect(hTreeView, NULL, false);
+}
+
+
+int CCList::GetItemType(HTREEITEM hItem) // returns a MCLCIT_ (see below)
+{
+ HANDLE hContact = GetItemData(hItem).hContact;
+ return (IsHContactInfo(hContact)) ? MCLCIT_INFO : ((IsHContactGroup(hContact)) ? MCLCIT_GROUP : MCLCIT_CONTACT);
+}
+
+
+DWORD CCList::GetItemTypeAsCLGNFlag(HTREEITEM hItem)
+{
+ HANDLE hContact = GetItemData(hItem).hContact;
+ return (IsHContactInfo(hContact)) ? MCLGN_INFO : ((IsHContactGroup(hContact)) ? MCLGN_GROUP : MCLGN_CONTACT);
+}
+
+
+HTREEITEM CCList::GetNextItem(DWORD Flags, HTREEITEM hItem)
+{
+ switch (Flags & ~(MCLGN_MULTILEVEL | MCLGN_NOTCHILD | MCLGN_ANY))
+ {
+ case MCLGN_ROOT:
+ {
+ return TreeView_GetRoot(hTreeView);
+ } break;
+ case MCLGN_LAST:
+ {
+ HTREEITEM hNextItem = TVI_ROOT;
+ do
+ {
+ hItem = hNextItem;
+ hNextItem = TreeView_GetLastChild(hTreeView, hNextItem);
+ } while (hNextItem);
+ return (hItem == TVI_ROOT) ? NULL : hItem;
+ } break;
+ case MCLGN_CHILD:
+ {
+ return TreeView_GetChild(hTreeView, hItem);
+ } break;
+ case MCLGN_LASTCHILD:
+ {
+ return TreeView_GetLastChild(hTreeView, hItem);
+ } break;
+ case MCLGN_PARENT:
+ {
+ return TreeView_GetParent(hTreeView, hItem);
+ } break;
+ case MCLGN_NEXT:
+ {
+ do
+ {
+ if (Flags & MCLGN_MULTILEVEL)
+ {
+ HTREEITEM hNextItem = NULL;
+ if ((Flags & MCLGN_NOTCHILD) != MCLGN_NOTCHILD)
+ {
+ hNextItem = TreeView_GetChild(hTreeView, hItem);
+ }
+ if (!hNextItem)
+ {
+ hNextItem = TreeView_GetNextSibling(hTreeView, hItem);
+ while (!hNextItem) // move back until we find next sibling of the item or one of its parents
+ {
+ hItem = TreeView_GetParent(hTreeView, hItem);
+ if (!hItem) // means it was the root, there are no items left.
+ {
+ break; // returns NULL as the next item
+ }
+ hNextItem = TreeView_GetNextSibling(hTreeView, hItem);
+ }
+ }
+ hItem = hNextItem;
+ } else
+ {
+ hItem = TreeView_GetNextSibling(hTreeView, hItem);
+ }
+ Flags &= ~(MCLGN_NOTCHILD & ~MCLGN_MULTILEVEL); // clear MCLGN_NOTCHILD flag
+ } while (hItem && !(Flags & GetItemTypeAsCLGNFlag(hItem)));
+ return hItem;
+ } break;
+ case MCLGN_PREV:
+ {
+ do
+ {
+ if (Flags & MCLGN_MULTILEVEL)
+ {
+ HTREEITEM hNextItem = TreeView_GetPrevSibling(hTreeView, hItem);
+ if (hNextItem)
+ {
+ if ((Flags & MCLGN_NOTCHILD) != MCLGN_NOTCHILD)
+ {
+ while (hNextItem)
+ {
+ hItem = hNextItem;
+ hNextItem = TreeView_GetLastChild(hTreeView, hItem);
+ }
+ } else
+ {
+ hItem = hNextItem;
+ }
+ } else
+ {
+ hItem = TreeView_GetParent(hTreeView, hItem);
+ }
+ } else
+ {
+ hItem = TreeView_GetPrevSibling(hTreeView, hItem);
+ }
+ Flags &= ~(MCLGN_NOTCHILD & ~MCLGN_MULTILEVEL); // clear MCLGN_NOTCHILD flag
+ } while (hItem && !(Flags & GetItemTypeAsCLGNFlag(hItem)));
+ return hItem;
+ } break;
+ default:
+ {
+ _ASSERT(0);
+ } break;
+ }
+ return NULL;
+}
+
+
+HANDLE CCList::GethContact(HTREEITEM hItem) // returns hContact, hGroup or hInfo
+{
+ HANDLE hContact = GetItemData(hItem).hContact;
+ if (IsHContactContact(hContact))
+ {
+ return hContact;
+ } else if (IsHContactGroup(hContact))
+ {
+ return (HANDLE)((int)hContact & ~HCONTACT_ISGROUP);
+ } else
+ {
+ return (HANDLE)((int)hContact & ~HCONTACT_ISINFO);
+ }
+}
+
+
+HTREEITEM CCList::HitTest(LPPOINT pt, PDWORD hitFlags) // pt is relative to control; returns hItem or NULL
+{
+ TVHITTESTINFO hti;
+ hti.pt = *pt;
+ TreeView_HitTest(hTreeView, &hti);
+ *hitFlags = 0;
+ if (hti.flags & TVHT_ABOVE)
+ {
+ *hitFlags |= MCLCHT_ABOVE;
+ }
+ if (hti.flags & TVHT_BELOW)
+ {
+ *hitFlags |= MCLCHT_BELOW;
+ }
+ if (hti.flags & TVHT_TOLEFT)
+ {
+ *hitFlags |= MCLCHT_TOLEFT;
+ }
+ if (hti.flags & TVHT_TORIGHT)
+ {
+ *hitFlags |= MCLCHT_TORIGHT;
+ }
+ if (hti.flags & TVHT_NOWHERE)
+ {
+ *hitFlags |= MCLCHT_NOWHERE;
+ }
+ if (hti.flags & TVHT_ONITEMINDENT)
+ {
+ *hitFlags |= MCLCHT_ONITEMINDENT;
+ }
+ if (hti.flags & (TVHT_ONITEMICON | TVHT_ONITEMSTATEICON))
+ {
+ *hitFlags |= MCLCHT_ONITEMICON;
+ }
+ if (hti.flags & TVHT_ONITEMLABEL)
+ {
+ *hitFlags |= MCLCHT_ONITEMLABEL;
+ }
+ if (hti.flags & TVHT_ONITEMRIGHT)
+ {
+ *hitFlags |= MCLCHT_ONITEMRIGHT;
+ }
+ if (hti.flags & (TVHT_ONITEMINDENT | TVHT_ONITEM | TVHT_ONITEMRIGHT))
+ {
+ // extraicon tests
+ RECT rc;
+ if (TreeView_GetItemRect(hTreeView, hti.hItem, &rc, false))
+ {
+ int nIndex = (rc.right - pt->x - 1) / EXTRAICON_XSTEP;
+ if (nIndex >= 0 && nIndex < MAXEXTRAICONS && GetItemData(hti.hItem).ExtraIcons[nIndex] != CLC_EXTRAICON_EMPTY)
+ {
+ *hitFlags |= MCLCHT_ONITEMEXTRA | (nIndex << 24);
+ }
+ }
+ }
+ return hti.hItem;
+}
+
+
+int CCList::Array_SetItemState(HTREEITEM hItem, bool bSelected)
+{
+ _ASSERT(hItem);
+ int nIndex = SelectedItems.Find(hItem);
+ if (nIndex == -1 && bSelected)
+ {
+ return SelectedItems.AddElem(hItem);
+ } else if (nIndex != -1 && !bSelected)
+ {
+ SelectedItems.RemoveElem(nIndex);
+ return -1;
+ }
+ return nIndex;
+}
+
+
+CCLItemData& CCList::GetItemData(HTREEITEM hItem)
+{
+ _ASSERT(hItem && hItem != INVALID_HANDLE_VALUE);
+ TVITEM tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = hItem;
+ int Res = TreeView_GetItem(hTreeView, &tvi);
+ _ASSERT(Res);
+ return Items[tvi.lParam];
+}
+
+
+HTREEITEM CCList::TreeView_GetLastChild(HWND hTreeView, HTREEITEM hItem)
+{
+ HTREEITEM hPrevItem = TreeView_GetChild(hTreeView, hItem);
+ hItem = hPrevItem;
+ while (hItem) // find last sibling
+ {
+ hPrevItem = hItem;
+ hItem = TreeView_GetNextSibling(hTreeView, hPrevItem);
+ }
+ return hPrevItem;
+}
+
+
+HTREEITEM CCList::FindContact(HANDLE hContact)
+{
+ TVITEM tvi;
+ tvi.mask = TVIF_HANDLE | TVIF_PARAM;
+ tvi.hItem = TreeView_GetRoot(hTreeView);
+ while (tvi.hItem)
+ {
+ TreeView_GetItem(hTreeView, &tvi);
+ if (Items[tvi.lParam].hContact == hContact)
+ {
+ return tvi.hItem;
+ }
+ tvi.hItem = GetNextItem(MCLGN_NEXT | MCLGN_ANY | MCLGN_MULTILEVEL, tvi.hItem);
+ }
+ return NULL;
+}
+
+
+void CCList::SelectGroups(HTREEITEM hCurItem, bool bSelected)
+{
+// select/deselect all child items
+ HTREEITEM hItem = TreeView_GetChild(hTreeView, hCurItem);
+ HTREEITEM hLimitItem = GetNextItem(MCLGN_NEXT | MCLGN_ANY | MCLGN_NOTCHILD, hCurItem);
+ while (hItem && hItem != hLimitItem)
+ {
+ TreeView_SetItemState(hTreeView, hItem, bSelected ? TVIS_SELECTED : 0, TVIS_SELECTED);
+ Array_SetItemState(hItem, bSelected);
+ hItem = GetNextItem(MCLGN_NEXT | MCLGN_ANY | MCLGN_MULTILEVEL, hItem);
+ }
+// select/deselect all parent groups
+ hCurItem = TreeView_GetParent(hTreeView, hCurItem);
+ if (bSelected)
+ {
+ while (hCurItem) // select until we'll find an unselected item or until we'll reach the root
+ {
+ hItem = TreeView_GetChild(hTreeView, hCurItem);
+ while (hItem) // walk through all siblings
+ {
+ if (!(TreeView_GetItemState(hTreeView, hItem, TVIS_SELECTED) & TVIS_SELECTED))
+ {
+ break;
+ }
+ hItem = TreeView_GetNextSibling(hTreeView, hItem);
+ }
+ if (hItem) // means there was at least one unselected item
+ {
+ break;
+ }
+ TreeView_SetItemState(hTreeView, hCurItem, TVIS_SELECTED, TVIS_SELECTED);
+ Array_SetItemState(hCurItem, true);
+ hCurItem = TreeView_GetParent(hTreeView, hCurItem);
+ }
+ }
+ while (hCurItem) // and deselect all remaining parent groups
+ {
+ TreeView_SetItemState(hTreeView, hCurItem, 0, TVIS_SELECTED);
+ Array_SetItemState(hCurItem, false);
+ hCurItem = TreeView_GetParent(hTreeView, hCurItem);
+ }
+}