[PATCH v4 5/7] comctl32/listview: Send LVN_ODSTATECHANGED notification.

Zhiyi Zhang zzhang at codeweavers.com
Thu Jun 30 04:41:05 CDT 2022



On 6/29/22 05:16, Angelo Haller wrote:
> From: Angelo Haller <angelo at szanni.org>
>
> Send LVN_ODSTATECHANGED notification on selection change for
> listviews when LVS_OWNERDATA is set.
>
> Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52534
> Signed-off-by: Angelo Haller <angelo at szanni.org>
>
> ---
> v3: Merge nFirst & nLast definition into a single line.
>     Add function call guard due to preceding patch changes.
> ---
>  dlls/comctl32/listview.c       | 16 +++++++++++-----
>  dlls/comctl32/tests/listview.c |  6 +++---
>  2 files changed, 14 insertions(+), 8 deletions(-)
>
> diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
> index 5ba1924cbd7..0243d9a84ce 100644
> --- a/dlls/comctl32/listview.c
> +++ b/dlls/comctl32/listview.c
> @@ -3604,6 +3604,7 @@ static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
>   */
>  static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
>  {
> +    INT nFirst = -1, nLast = -1;
>      RANGES selection;
>      DWORD old_mask;
>      LVITEMW item;
> @@ -3655,21 +3656,26 @@ static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
>  	iterator_destroy(&i);
>      }
>  
> -    /* disable per item notifications on LVS_OWNERDATA style
> -       FIXME: single LVN_ODSTATECHANGED should be used */
> +    /* Disable per item notifications on LVS_OWNERDATA style */
>      old_mask = infoPtr->notify_mask & NOTIFY_MASK_ITEM_CHANGE;
>      if (infoPtr->dwStyle & LVS_OWNERDATA)
>          infoPtr->notify_mask &= ~NOTIFY_MASK_ITEM_CHANGE;
>  
>      LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
>  
> -
>      iterator_rangesitems(&i, selection);
> -    while(iterator_next(&i))
> -	LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
> +    while(iterator_next(&i)) {
> +        if (nFirst == -1)
> +            nFirst = i.nItem;
> +        nLast = i.nItem;
> +        LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
> +    }
Hi Angelo,

This looks like a separate change than what you describe in the subject. Please put it in a separate patch.

Other than the above. There are some typos in 1/7 but I took care of it. I made some changes to the patch series overall
and attached them in the mail attachments. Mostly some style fix. You can make your changes on top of them.

Then there are still some message sequences in test_ownerdata_multiselect() that don't pass. It looks like one issue for
the rest of the todo_wines and they're probably existing failures. But could you fix them as well?

Thanks for your work. It's much better than the last revision.

Best Regards,
Zhiyi


>      /* this will also destroy the selection */
>      iterator_destroy(&i);
>  
> +    if (infoPtr->dwStyle & LVS_OWNERDATA)
> +        LISTVIEW_SetOwnerDataState(infoPtr, nFirst, nLast, &item);
> +
>      infoPtr->notify_mask |= old_mask;
>      LISTVIEW_SetItemFocus(infoPtr, nItem);
>  }
> diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c
> index 7243a1858cd..d8a27ea18f4 100644
> --- a/dlls/comctl32/tests/listview.c
> +++ b/dlls/comctl32/tests/listview.c
> @@ -3605,7 +3605,7 @@ static void test_ownerdata_multiselect(void)
>      expect(0, res);
>      ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>                  ownerdata_multiselect_select_0_to_1_odstatechanged_seq,
> -                "ownerdata multiselect: select multiple via SHIFT+DOWN", TRUE);
> +                "ownerdata multiselect: select multiple via SHIFT+DOWN", FALSE);
>      res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
>      expect(0, res);
>      res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
> @@ -3633,7 +3633,7 @@ static void test_ownerdata_multiselect(void)
>      expect(0, res);
>      ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>                  ownerdata_multiselect_select_0_to_1_odstatechanged_seq,
> -                "ownerdata multiselect: select multiple via SHIFT+CONTROL+DOWN", TRUE);
> +                "ownerdata multiselect: select multiple via SHIFT+CONTROL+DOWN", FALSE);
>      res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
>      expect(0, res);
>      res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
> @@ -3676,7 +3676,7 @@ static void test_ownerdata_multiselect(void)
>      expect(0, res);
>      ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>                  ownerdata_multiselect_select_0_to_2_odstatechanged_seq,
> -                "ownerdata multiselect: select multiple after skip via SHIFT+CONTROL+DOWN", TRUE);
> +                "ownerdata multiselect: select multiple after skip via SHIFT+CONTROL+DOWN", FALSE);
>      res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
>      expect(0, res);
>      res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
-------------- next part --------------
From d9f8b8d5ee9a412fbd35d36de66913c362543f9e Mon Sep 17 00:00:00 2001
From: Angelo Haller <angelo at szanni.org>
Date: Tue, 28 Jun 2022 16:16:07 -0500
Subject: [PATCH 1/6] comctl32/listview: Send one deselect all items
 notification for LVS_OWNERDATA listviews.
To: wine-devel at winehq.org

Send one deselect all items notification on selection change for LVS_OWNERDATA listviews instead
of notifying about each individual item change.

Signed-off-by: Angelo Haller <angelo at szanni.org>
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/listview.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
index ab328b3e798..3761a61286e 100644
--- a/dlls/comctl32/listview.c
+++ b/dlls/comctl32/listview.c
@@ -3406,7 +3406,14 @@ static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
 
     lvItem.state = 0;
     lvItem.stateMask = LVIS_SELECTED;
-    
+
+    /* Only send one deselect all (-1) notification for LVS_OWNERDATA style */
+    if (infoPtr->dwStyle & LVS_OWNERDATA)
+    {
+        LISTVIEW_SetItemState(infoPtr, -1, &lvItem);
+        return TRUE;
+    }
+
     /* need to clone the DPA because callbacks can change it */
     if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
     iterator_rangesitems(&i, ranges_diff(clone, toSkip));
-- 
2.34.1

-------------- next part --------------
From ce248bfe84a5f06281022266b020535b144c3688 Mon Sep 17 00:00:00 2001
From: Angelo Haller <angelo at szanni.org>
Date: Tue, 28 Jun 2022 16:16:08 -0500
Subject: [PATCH 2/6] comctl32/listview: Move sending LVN_ODSTATECHANGED
 notifications to a function.
To: wine-devel at winehq.org

Signed-off-by: Angelo Haller <angelo at szanni.org>
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/listview.c | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
index 3761a61286e..a12b19b8083 100644
--- a/dlls/comctl32/listview.c
+++ b/dlls/comctl32/listview.c
@@ -438,6 +438,7 @@ static INT LISTVIEW_GetStringWidthT(const LISTVIEW_INFO *, LPCWSTR, BOOL);
 static BOOL LISTVIEW_KeySelection(LISTVIEW_INFO *, INT, BOOL);
 static UINT LISTVIEW_GetItemState(const LISTVIEW_INFO *, INT, UINT);
 static BOOL LISTVIEW_SetItemState(LISTVIEW_INFO *, INT, const LVITEMW *);
+static VOID LISTVIEW_SetOwnerDataState(LISTVIEW_INFO *, INT, INT, const LVITEMW *);
 static LRESULT LISTVIEW_VScroll(LISTVIEW_INFO *, INT, INT);
 static LRESULT LISTVIEW_HScroll(LISTVIEW_INFO *, INT, INT);
 static BOOL LISTVIEW_EnsureVisible(LISTVIEW_INFO *, INT, BOOL);
@@ -3565,7 +3566,6 @@ static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
     INT nFirst = min(infoPtr->nSelectionMark, nItem);
     INT nLast = max(infoPtr->nSelectionMark, nItem);
     HWND hwndSelf = infoPtr->hwndSelf;
-    NMLVODSTATECHANGE nmlv;
     DWORD old_mask;
     LVITEMW item;
     INT i;
@@ -3587,13 +3587,8 @@ static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
     for (i = nFirst; i <= nLast; i++)
 	LISTVIEW_SetItemState(infoPtr,i,&item);
 
-    ZeroMemory(&nmlv, sizeof(nmlv));
-    nmlv.iFrom = nFirst;
-    nmlv.iTo = nLast;
-    nmlv.uOldState = 0;
-    nmlv.uNewState = item.state;
+    LISTVIEW_SetOwnerDataState(infoPtr, nFirst, nLast, &item);
 
-    notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
     if (!IsWindow(hwndSelf))
         return FALSE;
     infoPtr->notify_mask |= old_mask;
@@ -9023,6 +9018,22 @@ static BOOL LISTVIEW_SetItemPosition(LISTVIEW_INFO *infoPtr, INT nItem, const PO
     return LISTVIEW_MoveIconTo(infoPtr, nItem, &Pt, FALSE);
 }
 
+/* Make sure to also disable per item notifications via the notification mask. */
+static VOID LISTVIEW_SetOwnerDataState(LISTVIEW_INFO *infoPtr, INT nFirst, INT nLast, const LVITEMW *item)
+{
+    NMLVODSTATECHANGE nmlv;
+
+    if (!item) return;
+
+    ZeroMemory(&nmlv, sizeof(nmlv));
+    nmlv.iFrom = nFirst;
+    nmlv.iTo = nLast;
+    nmlv.uOldState = 0;
+    nmlv.uNewState = item->state;
+
+    notify_hdr(infoPtr, LVN_ODSTATECHANGED, (LPNMHDR)&nmlv);
+}
+
 /***
  * DESCRIPTION:
  * Sets the state of one or many items.
-- 
2.34.1

-------------- next part --------------
From a40dcea5fce9471d599c93dba274068a71c16a3f Mon Sep 17 00:00:00 2001
From: Angelo Haller <angelo at szanni.org>
Date: Tue, 28 Jun 2022 16:16:09 -0500
Subject: [PATCH 3/6] comctl32/listview: Send LVN_ODSTATECHANGED only for
 LVS_OWNERDATA listviews.
To: wine-devel at winehq.org

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=53123
Signed-off-by: Angelo Haller <angelo at szanni.org>
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/listview.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
index a12b19b8083..bf22a90e987 100644
--- a/dlls/comctl32/listview.c
+++ b/dlls/comctl32/listview.c
@@ -3587,7 +3587,8 @@ static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
     for (i = nFirst; i <= nLast; i++)
 	LISTVIEW_SetItemState(infoPtr,i,&item);
 
-    LISTVIEW_SetOwnerDataState(infoPtr, nFirst, nLast, &item);
+    if (infoPtr->dwStyle & LVS_OWNERDATA)
+        LISTVIEW_SetOwnerDataState(infoPtr, nFirst, nLast, &item);
 
     if (!IsWindow(hwndSelf))
         return FALSE;
-- 
2.34.1

-------------- next part --------------
From 2d341e08f7a2387e89adef98c7dbaaa123e8f4bd Mon Sep 17 00:00:00 2001
From: Angelo Haller <angelo at szanni.org>
Date: Tue, 28 Jun 2022 16:16:10 -0500
Subject: [PATCH 4/6] comctl32/listview: Send LVN_ODSTATECHANGED notification
 for LVS_OWNERDATA listview on selection changes.
To: wine-devel at winehq.org

Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=52534
Signed-off-by: Angelo Haller <angelo at szanni.org>
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/listview.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
index bf22a90e987..db04affa56c 100644
--- a/dlls/comctl32/listview.c
+++ b/dlls/comctl32/listview.c
@@ -3610,6 +3610,7 @@ static BOOL LISTVIEW_AddGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
  */
 static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
 {
+    INT nFirst = -1, nLast = -1;
     RANGES selection;
     DWORD old_mask;
     LVITEMW item;
@@ -3661,21 +3662,27 @@ static void LISTVIEW_SetGroupSelection(LISTVIEW_INFO *infoPtr, INT nItem)
 	iterator_destroy(&i);
     }
 
-    /* disable per item notifications on LVS_OWNERDATA style
-       FIXME: single LVN_ODSTATECHANGED should be used */
+    /* Disable per item notifications on LVS_OWNERDATA style */
     old_mask = infoPtr->notify_mask & NOTIFY_MASK_ITEM_CHANGE;
     if (infoPtr->dwStyle & LVS_OWNERDATA)
         infoPtr->notify_mask &= ~NOTIFY_MASK_ITEM_CHANGE;
 
     LISTVIEW_DeselectAllSkipItems(infoPtr, selection);
 
-
     iterator_rangesitems(&i, selection);
     while(iterator_next(&i))
-	LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
+    {
+        if (nFirst == -1)
+            nFirst = i.nItem;
+        nLast = i.nItem;
+        LISTVIEW_SetItemState(infoPtr, i.nItem, &item);
+    }
     /* this will also destroy the selection */
     iterator_destroy(&i);
 
+    if (infoPtr->dwStyle & LVS_OWNERDATA)
+        LISTVIEW_SetOwnerDataState(infoPtr, nFirst, nLast, &item);
+
     infoPtr->notify_mask |= old_mask;
     LISTVIEW_SetItemFocus(infoPtr, nItem);
 }
-- 
2.34.1

-------------- next part --------------
From b2b4b6939067b08924c59b74b1ea1a753b6f24b6 Mon Sep 17 00:00:00 2001
From: Angelo Haller <angelo at szanni.org>
Date: Tue, 28 Jun 2022 16:16:11 -0500
Subject: [PATCH 5/6] comctl32/listview: Don't send LVN_ODSTATECHANGED for
 empty ranges.
To: wine-devel at winehq.org

Signed-off-by: Angelo Haller <angelo at szanni.org>
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/listview.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
index db04affa56c..c5eb2d78cf2 100644
--- a/dlls/comctl32/listview.c
+++ b/dlls/comctl32/listview.c
@@ -9031,6 +9031,7 @@ static VOID LISTVIEW_SetOwnerDataState(LISTVIEW_INFO *infoPtr, INT nFirst, INT n
 {
     NMLVODSTATECHANGE nmlv;
 
+    if (nFirst == nLast) return;
     if (!item) return;
 
     ZeroMemory(&nmlv, sizeof(nmlv));
-- 
2.34.1

-------------- next part --------------
From acf71795c51a519e8cb56c924d62cc4098fe8242 Mon Sep 17 00:00:00 2001
From: Angelo Haller <angelo at szanni.org>
Date: Tue, 28 Jun 2022 16:16:06 -0500
Subject: [PATCH 6/6] comctl32/tests: Add more ownerdata listview tests.
To: wine-devel at winehq.org

Signed-off-by: Angelo Haller <angelo at szanni.org>
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/comctl32/tests/listview.c | 260 +++++++++++++++++++++++++++++++--
 1 file changed, 244 insertions(+), 16 deletions(-)

diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c
index ed5222a5ee8..4032671d4ea 100644
--- a/dlls/comctl32/tests/listview.c
+++ b/dlls/comctl32/tests/listview.c
@@ -256,11 +256,77 @@ static const struct message ownerdata_deselect_all_parent_seq[] = {
     { 0 }
 };
 
-static const struct message ownerdata_multiselect_odstatechanged_seq[] = {
-    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
+static const struct message ownerdata_multiselect_select_0_to_1_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
     { WM_NOTIFY, sent|id, 0, 0, LVN_ODSTATECHANGED },
-    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
-    { WM_NOTIFY, sent|id, 0, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 0, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 1, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_select_0_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 0, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_select_0_modkey_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 0, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 0, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_move_0_to_1_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, 0, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 1, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_select_0_to_2_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id, 0, 0, LVN_ODSTATECHANGED },
+    { WM_NOTIFY, sent|id|wparam, 1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 2, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_select_3_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 2, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 3, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_select_3_modkey_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 3, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 2, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 3, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_select_3_to_2_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id, 0, 0, LVN_ODSTATECHANGED },
+    { WM_NOTIFY, sent|id|wparam, 3, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 2, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_move_3_to_2_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, 3, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 2, 0, LVN_ITEMCHANGED },
+    { 0 }
+};
+
+static const struct message ownerdata_multiselect_select_3_to_1_odstatechanged_seq[] = {
+    { WM_NOTIFY, sent|id|wparam, -1, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id, 0, 0, LVN_ODSTATECHANGED },
+    { WM_NOTIFY, sent|id|wparam, 2, 0, LVN_ITEMCHANGED },
+    { WM_NOTIFY, sent|id|wparam, 1, 0, LVN_ITEMCHANGED },
     { 0 }
 };
 
@@ -3567,43 +3633,205 @@ static void test_ownerdata_multiselect(void)
 
     res = SendMessageA(hwnd, LVM_SETSELECTIONMARK, 0, 0);
     expect(0, res);
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* First down then up */
+    /* Select multiple items via SHIFT+DOWN */
+    hold_key(VK_SHIFT);
+
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_0_to_1_odstatechanged_seq,
+                "ownerdata multiselect: select multiple via SHIFT+DOWN", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(2, res);
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Select one item via SHIFT+UP */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_0_modkey_odstatechanged_seq,
+                "ownerdata multiselect: select one via SHIFT+UP", TRUE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_UP, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(1, res);
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Select multiple items via SHIFT+CONTROL+DOWN */
+    hold_key(VK_CONTROL);
+
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_0_to_1_odstatechanged_seq,
+                "ownerdata multiselect: select multiple via SHIFT+CONTROL+DOWN", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(2, res);
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Select one item via SHIFT+CONTROL+UP */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_0_modkey_odstatechanged_seq,
+                "ownerdata multiselect: select one via SHIFT+CONTROL+UP", TRUE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_UP, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(1, res);
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Keep selection but move cursor via CONTROL+DOWN */
+    release_key(VK_SHIFT);
+
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_move_0_to_1_odstatechanged_seq,
+                "ownerdata multiselect: keep selection but move cursor via CONTROL+DOWN", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(1, res);
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Select multiple via SHIFT+CONTROL+DOWN after moving cursor over an item without selecting */
+    hold_key(VK_SHIFT);
+
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_0_to_2_odstatechanged_seq,
+                "ownerdata multiselect: select multiple after skip via SHIFT+CONTROL+DOWN", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(3, res);
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Deselect all items, select item 3 via DOWN */
+    release_key(VK_CONTROL);
+    release_key(VK_SHIFT);
+
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_3_odstatechanged_seq,
+                "ownerdata multiselect: deselect all, select item 3 via DOWN", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(1, res);
 
     hold_key(VK_SHIFT);
 
     flush_sequences(sequences, NUM_MSG_SEQUENCES);
 
-    res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
+    /* First up then down */
+    /* Select multiple items via SHIFT+UP */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
     expect(0, res);
-
     ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
-                ownerdata_multiselect_odstatechanged_seq,
-                "ownerdata select multiple notification", TRUE);
-
-    res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
+                ownerdata_multiselect_select_3_to_2_odstatechanged_seq,
+                "ownerdata multiselect: select multiple via SHIFT+UP", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_UP, 0);
     expect(0, res);
-
     res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
     expect(2, res);
 
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Select one item via SHIFT+DOWN */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_3_modkey_odstatechanged_seq,
+                "ownerdata multiselect: select one via SHIFT+DOWN", TRUE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(1, res);
+
     hold_key(VK_CONTROL);
 
     flush_sequences(sequences, NUM_MSG_SEQUENCES);
 
+    /* Select multiple items via SHIFT+CONTROL+UP */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_3_to_2_odstatechanged_seq,
+                "ownerdata multiselect: select multiple via SHIFT+CONTROL+UP", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_UP, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(2, res);
+
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Select one item via SHIFT+CONTROL+DOWN */
     res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
     expect(0, res);
-
     ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
-                ownerdata_multiselect_odstatechanged_seq,
-                "ownerdata select multiple notification", TRUE);
-
+                ownerdata_multiselect_select_3_modkey_odstatechanged_seq,
+                "ownerdata multiselect: select one via SHIFT+CONTROL+DOWN", TRUE);
     res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
     expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(1, res);
+
+    release_key(VK_SHIFT);
+
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Keep selection but move cursor via CONTROL+UP */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_move_3_to_2_odstatechanged_seq,
+                "ownerdata multiselect: keep selection but move cursor via CONTROL+UP ", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_UP, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(1, res);
+
+    hold_key(VK_SHIFT);
+
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Select multiple via SHIFT+CONTROL+DOWN after moving cursor over an item without selecting */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_3_to_1_odstatechanged_seq,
+                "ownerdata multiselect: select multiple after skip via SHIFT+CONTROL+UP", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_UP, 0);
+    expect(0, res);
+    res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
+    expect(3, res);
 
     release_key(VK_CONTROL);
     release_key(VK_SHIFT);
 
+    flush_sequences(sequences, NUM_MSG_SEQUENCES);
+
+    /* Deselect all items, select item 3 via UP */
+    res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
+    expect(0, res);
+    ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
+                ownerdata_multiselect_select_0_odstatechanged_seq,
+                "ownerdata multiselect: deselect all, select item 0 via UP", FALSE);
+    res = SendMessageA(hwnd, WM_KEYUP, VK_UP, 0);
+    expect(0, res);
     res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
-    expect(3, res);
+    expect(1, res);
 
     DestroyWindow(hwnd);
 }
-- 
2.34.1



More information about the wine-devel mailing list