widl: Fix a couple of uninitialized variable compiler warnings.
[wine] / dlls / comctl32 / draglist.c
index 46364c8..9ecade5 100644 (file)
@@ -2,6 +2,7 @@
  * 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)
 {
@@ -64,63 +292,50 @@ 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
-
-
-