* Drag List control
*
* Copyright 1999 Eric Kohl
+ * Copyright 2004 Robert Shearman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* NOTES
- * This is just a dummy control. An author is needed! Any volunteers?
- * I will only improve this control once in a while.
- * Eric <ekohl@abo.rhein-zeitung.de>
*
- * TODO:
- * - Everything.
+ * This code was audited for completeness against the documented features
+ * of Comctl32.dll version 6.0 on Mar. 10, 2004, by Robert Shearman.
+ *
+ * Unless otherwise noted, we believe this code to be complete, as per
+ * the specification mentioned above.
+ * If you discover missing features or bugs please note them below.
+ *
*/
+#include <stdarg.h>
+
+#include "windef.h"
+#include "winbase.h"
+#include "wingdi.h"
+#include "winuser.h"
+#include "winnls.h"
#include "commctrl.h"
+#include "comctl32.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(commctrl);
+#define DRAGLIST_SUBCLASSID 0
+#define DRAGLIST_SCROLLPERIOD 200
+#define DRAGLIST_TIMERID 666
+
+/* properties relating to IDI_DRAGICON */
+#define DRAGICON_HOTSPOT_X 17
+#define DRAGICON_HOTSPOT_Y 7
+#define DRAGICON_HEIGHT 32
+
+/* internal Wine specific data for the drag list control */
+typedef struct _DRAGLISTDATA
+{
+ /* are we currently in dragging mode? */
+ BOOL dragging;
+
+ /* cursor to use as determined by DL_DRAGGING notification.
+ * NOTE: as we use LoadCursor we don't have to use DeleteCursor
+ * when we are finished with it */
+ HCURSOR cursor;
+
+ /* optimisation so that we don't have to load the cursor
+ * all of the time whilst dragging */
+ LRESULT last_dragging_response;
+ /* prevents flicker with drawing drag arrow */
+ RECT last_drag_icon_rect;
+} DRAGLISTDATA;
+
+UINT uDragListMessage = 0; /* registered window message code */
static DWORD dwLastScrollTime = 0;
+static HICON hDragArrow = NULL;
+
+/***********************************************************************
+ * DragList_Notify (internal)
+ *
+ * Sends notification messages to the parent control. Note that it
+ * does not use WM_NOTIFY like the rest of the controls, but a registered
+ * window message.
+ */
+static LRESULT DragList_Notify(HWND hwndLB, UINT uNotification)
+{
+ DRAGLISTINFO dli;
+ dli.hWnd = hwndLB;
+ dli.uNotification = uNotification;
+ GetCursorPos(&dli.ptCursor);
+ return SendMessageW(GetParent(hwndLB), uDragListMessage, GetDlgCtrlID(hwndLB), (LPARAM)&dli);
+}
+
+/* cleans up after dragging */
+static void DragList_EndDrag(HWND hwnd, DRAGLISTDATA * data)
+{
+ KillTimer(hwnd, DRAGLIST_TIMERID);
+ ReleaseCapture();
+ /* clear any drag insert icon present */
+ InvalidateRect(GetParent(hwnd), &data->last_drag_icon_rect, TRUE);
+ /* clear data for next use */
+ memset(data, 0, sizeof(*data));
+}
+
+/***********************************************************************
+ * DragList_SubclassWindowProc (internal)
+ *
+ * Handles certain messages to enable dragging for the ListBox and forwards
+ * the rest to the ListBox.
+ */
+static LRESULT CALLBACK
+DragList_SubclassWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
+{
+ DRAGLISTDATA * data = (DRAGLISTDATA*)dwRefData;
+ switch (uMsg)
+ {
+ case WM_LBUTTONDOWN:
+ SetFocus(hwnd);
+ data->dragging = DragList_Notify(hwnd, DL_BEGINDRAG);
+ if (data->dragging)
+ {
+ SetCapture(hwnd);
+ SetTimer(hwnd, DRAGLIST_TIMERID, DRAGLIST_SCROLLPERIOD, NULL);
+ }
+ /* note that we don't absorb this message to let the list box
+ * do its thing (normally selecting an item) */
+ break;
+
+ case WM_KEYDOWN:
+ case WM_RBUTTONDOWN:
+ /* user cancelled drag by either right clicking or
+ * by pressing the escape key */
+ if ((data->dragging) &&
+ ((uMsg == WM_RBUTTONDOWN) || (wParam == VK_ESCAPE)))
+ {
+ /* clean up and absorb message */
+ DragList_EndDrag(hwnd, data);
+ DragList_Notify(hwnd, DL_CANCELDRAG);
+ return 0;
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ case WM_TIMER:
+ if (data->dragging)
+ {
+ LRESULT cursor = DragList_Notify(hwnd, DL_DRAGGING);
+ /* optimisation so that we don't have to load the cursor
+ * all of the time whilst dragging */
+ if (data->last_dragging_response != cursor)
+ {
+ switch (cursor)
+ {
+ case DL_STOPCURSOR:
+ data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_NO);
+ SetCursor(data->cursor);
+ break;
+ case DL_COPYCURSOR:
+ data->cursor = LoadCursorW(COMCTL32_hModule, (LPCWSTR)IDC_COPY);
+ SetCursor(data->cursor);
+ break;
+ case DL_MOVECURSOR:
+ data->cursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
+ SetCursor(data->cursor);
+ break;
+ }
+ data->last_dragging_response = cursor;
+ }
+ /* don't pass this message on to List Box */
+ return 0;
+ }
+ break;
+
+ case WM_LBUTTONUP:
+ if (data->dragging)
+ {
+ DragList_EndDrag(hwnd, data);
+ DragList_Notify(hwnd, DL_DROPPED);
+ }
+ break;
+
+ case WM_GETDLGCODE:
+ /* tell dialog boxes that we want to receive WM_KEYDOWN events
+ * for keys like VK_ESCAPE */
+ if (data->dragging)
+ return DLGC_WANTALLKEYS;
+ break;
+ case WM_NCDESTROY:
+ RemoveWindowSubclass(hwnd, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID);
+ Free(data);
+ break;
+ }
+ return DefSubclassProc(hwnd, uMsg, wParam, lParam);
+}
/***********************************************************************
* MakeDragList (COMCTL32.13)
+ *
+ * Makes a normal ListBox into a DragList by subclassing it.
+ *
+ * RETURNS
+ * Success: Non-zero
+ * Failure: Zero
*/
BOOL WINAPI MakeDragList (HWND hwndLB)
{
- FIXME("(0x%x)\n", hwndLB);
+ DRAGLISTDATA *data = Alloc(sizeof(DRAGLISTDATA));
+
+ TRACE("(%p)\n", hwndLB);
+ if (!uDragListMessage)
+ uDragListMessage = RegisterWindowMessageW(DRAGLISTMSGSTRINGW);
- return FALSE;
+ return SetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR)data);
}
/***********************************************************************
* DrawInsert (COMCTL32.15)
+ *
+ * Draws insert arrow by the side of the ListBox item in the parent window.
+ *
+ * RETURNS
+ * Nothing.
*/
VOID WINAPI DrawInsert (HWND hwndParent, HWND hwndLB, INT nItem)
{
- FIXME("(0x%x 0x%x %d)\n", hwndParent, hwndLB, nItem);
+ RECT rcItem, rcListBox, rcDragIcon;
+ HDC hdc;
+ DRAGLISTDATA * data;
+
+ TRACE("(%p %p %d)\n", hwndParent, hwndLB, nItem);
+
+ if (!hDragArrow)
+ hDragArrow = LoadIconW(COMCTL32_hModule, (LPCWSTR)IDI_DRAGARROW);
+
+ if (LB_ERR == SendMessageW(hwndLB, LB_GETITEMRECT, nItem, (LPARAM)&rcItem))
+ return;
+
+ if (!GetWindowRect(hwndLB, &rcListBox))
+ return;
+
+ /* convert item rect to parent co-ordinates */
+ if (!MapWindowPoints(hwndLB, hwndParent, (LPPOINT)&rcItem, 2))
+ return;
+
+ /* convert list box rect to parent co-ordinates */
+ if (!MapWindowPoints(HWND_DESKTOP, hwndParent, (LPPOINT)&rcListBox, 2))
+ return;
+
+ rcDragIcon.left = rcListBox.left - DRAGICON_HOTSPOT_X;
+ rcDragIcon.top = rcItem.top - DRAGICON_HOTSPOT_Y;
+ rcDragIcon.right = rcListBox.left;
+ rcDragIcon.bottom = rcDragIcon.top + DRAGICON_HEIGHT;
+
+ if (!GetWindowSubclass(hwndLB, DragList_SubclassWindowProc, DRAGLIST_SUBCLASSID, (DWORD_PTR*)&data))
+ return;
+
+ if (nItem < 0)
+ SetRectEmpty(&rcDragIcon);
+
+ /* prevent flicker by only redrawing when necessary */
+ if (!EqualRect(&rcDragIcon, &data->last_drag_icon_rect))
+ {
+ /* get rid of any previous inserts drawn */
+ RedrawWindow(hwndParent, &data->last_drag_icon_rect, NULL,
+ RDW_INTERNALPAINT | RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
+ CopyRect(&data->last_drag_icon_rect, &rcDragIcon);
+ if (nItem >= 0)
+ {
+ hdc = GetDC(hwndParent);
+
+ DrawIcon(hdc, rcDragIcon.left, rcDragIcon.top, hDragArrow);
+
+ ReleaseDC(hwndParent, hdc);
+ }
+ }
}
/***********************************************************************
* LBItemFromPt (COMCTL32.14)
+ *
+ * Gets the index of the ListBox item under the specified point,
+ * scrolling if bAutoScroll is TRUE and pt is outside of the ListBox.
+ *
+ * RETURNS
+ * The ListBox item ID if pt is over a list item or -1 otherwise.
*/
INT WINAPI LBItemFromPt (HWND hwndLB, POINT pt, BOOL bAutoScroll)
{
INT nIndex;
DWORD dwScrollTime;
- FIXME("(0x%x %ld x %ld %s)\n",
- hwndLB, pt.x, pt.y, bAutoScroll ? "TRUE" : "FALSE");
+ TRACE("(%p %d x %d %s)\n",
+ hwndLB, pt.x, pt.y, bAutoScroll ? "TRUE" : "FALSE");
ScreenToClient (hwndLB, &pt);
GetClientRect (hwndLB, &rcClient);
- nIndex = (INT)SendMessageA (hwndLB, LB_GETTOPINDEX, 0, 0);
+ nIndex = (INT)SendMessageW (hwndLB, LB_GETTOPINDEX, 0, 0);
if (PtInRect (&rcClient, pt))
{
- /* point is inside -- get the item index */
- while (TRUE)
- {
- if (SendMessageA (hwndLB, LB_GETITEMRECT, nIndex, (LPARAM)&rcClient) == LB_ERR)
- return -1;
+ /* point is inside -- get the item index */
+ while (TRUE)
+ {
+ if (SendMessageW (hwndLB, LB_GETITEMRECT, nIndex, (LPARAM)&rcClient) == LB_ERR)
+ return -1;
- if (PtInRect (&rcClient, pt))
- return nIndex;
+ if (PtInRect (&rcClient, pt))
+ return nIndex;
- nIndex++;
- }
+ nIndex++;
+ }
}
else
{
- /* point is outside */
- if (!bAutoScroll)
- return -1;
+ /* point is outside */
+ if (!bAutoScroll)
+ return -1;
- if ((pt.x > rcClient.right) || (pt.x < rcClient.left))
- return -1;
+ if ((pt.x > rcClient.right) || (pt.x < rcClient.left))
+ return -1;
- if (pt.y < 0)
- nIndex--;
- else
- nIndex++;
+ if (pt.y < 0)
+ nIndex--;
+ else
+ nIndex++;
- dwScrollTime = GetTickCount ();
+ dwScrollTime = GetTickCount ();
- if ((dwScrollTime - dwLastScrollTime) < 200)
- return -1;
+ if ((dwScrollTime - dwLastScrollTime) < DRAGLIST_SCROLLPERIOD)
+ return -1;
- dwLastScrollTime = dwScrollTime;
+ dwLastScrollTime = dwScrollTime;
- SendMessageA (hwndLB, LB_SETTOPINDEX, (WPARAM)nIndex, 0);
+ SendMessageW (hwndLB, LB_SETTOPINDEX, (WPARAM)nIndex, 0);
}
return -1;
}
-
-
-#if 0
-static LRESULT CALLBACK
-DRAGLIST_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
-{
-
- return FALSE;
-}
-#endif
-
-
-