Wine-Devel
By thread
wine-devel@list.winehq.org
By month
Messages by month
- ----- 2026 -----
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2002 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2001 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
June 2022
- 68 participants
- 3274 messages
Re: [PATCH 0/1] MR229: comctl32: Always paint SS_BITMAP static control background with WM_CTLCOLORSTATIC brush.
by Zhiyi Zhang (@zhiyi)
Zhiyi Zhang (@zhiyi) commented about dlls/comctl32/static.c:
> {
> HDC hMemDC;
> HBITMAP hBitmap, oldbitmap;
> + RECT rcClient;
> +
> + GetClientRect(hwnd, &rcClient);
> + FillRect( hdc, &rcClient, hbrush );
Inconsistent style. I know the code in STATIC_PaintBitmapfn() uses both styles and it's a mess. But please keep the new code in a consistent style. In this case, either both functions use spaces or both don't. Otherwise, the MR looks good to me.
--
https://gitlab.winehq.org/wine/wine/-/merge_requests/229#note_2007
June 14, 2022
[PATCH v4] comctl32/listview: Add partial support for LVM_SETBKIMAGE.
by Zhiyi Zhang
From: Dmitry Timoshkov <dmitry(a)baikal.ru>
Signed-off-by: Zhiyi Zhang <zzhang(a)codeweavers.com>
---
v2: Formatting fixes, add some tests.
v3: Add v6 tests, make v6 behaviour the default one.
v4: Supersede 238455. I was wrong and LVM_SETBKIMAGE should repaint the control. Also the background bitmap
should be painted in LISTVIEW_EraseBkgnd() when the background color is CLR_NONE.
dlls/comctl32/listview.c | 90 ++++++++++++++++++++++++++++++----
dlls/comctl32/tests/listview.c | 78 +++++++++++++++++++++++++++++
2 files changed, 158 insertions(+), 10 deletions(-)
diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
index 730bf4aaddd..ab328b3e798 100644
--- a/dlls/comctl32/listview.c
+++ b/dlls/comctl32/listview.c
@@ -91,7 +91,7 @@
*
* Messages:
* -- LVM_ENABLEGROUPVIEW
- * -- LVM_GETBKIMAGE, LVM_SETBKIMAGE
+ * -- LVM_GETBKIMAGE
* -- LVM_GETGROUPINFO, LVM_SETGROUPINFO
* -- LVM_GETGROUPMETRICS, LVM_SETGROUPMETRICS
* -- LVM_GETINSERTMARK, LVM_SETINSERTMARK
@@ -297,6 +297,7 @@ typedef struct tagLISTVIEW_INFO
COLORREF clrBk;
COLORREF clrText;
COLORREF clrTextBk;
+ HBITMAP hBkBitmap;
/* font */
HFONT hDefaultFont;
@@ -4551,6 +4552,21 @@ static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
return nItem;
}
+static void LISTVIEW_DrawBackgroundBitmap(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
+{
+ HDC mem_hdc;
+
+ if (!infoPtr->hBkBitmap)
+ return;
+
+ TRACE("(hdc=%p, lprcBox=%s, hBkBitmap=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBitmap);
+
+ mem_hdc = CreateCompatibleDC(hdc);
+ SelectObject(mem_hdc, infoPtr->hBkBitmap);
+ BitBlt(hdc, lprcBox->left, lprcBox->top, lprcBox->right - lprcBox->left,
+ lprcBox->bottom - lprcBox->top, mem_hdc, lprcBox->left, lprcBox->top, SRCCOPY);
+ DeleteDC(mem_hdc);
+}
/***
* DESCRIPTION:
@@ -4565,13 +4581,17 @@ static INT LISTVIEW_GetTopIndex(const LISTVIEW_INFO *infoPtr)
* Success: TRUE
* Failure: FALSE
*/
-static inline BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
+static BOOL LISTVIEW_FillBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc, const RECT *lprcBox)
{
- if (!infoPtr->hBkBrush) return FALSE;
+ if (infoPtr->hBkBrush)
+ {
+ TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
- TRACE("(hdc=%p, lprcBox=%s, hBkBrush=%p)\n", hdc, wine_dbgstr_rect(lprcBox), infoPtr->hBkBrush);
+ FillRect(hdc, lprcBox, infoPtr->hBkBrush);
+ }
- return FillRect(hdc, lprcBox, infoPtr->hBkBrush);
+ LISTVIEW_DrawBackgroundBitmap(infoPtr, hdc, lprcBox);
+ return TRUE;
}
/* Draw main item or subitem */
@@ -8028,7 +8048,53 @@ static BOOL LISTVIEW_SetBkColor(LISTVIEW_INFO *infoPtr, COLORREF color)
return TRUE;
}
-/* LISTVIEW_SetBkImage */
+static BOOL LISTVIEW_SetBkImage(LISTVIEW_INFO *infoPtr, const LVBKIMAGEW *image, BOOL isW)
+{
+ TRACE("%08lx, %p, %p, %u, %d, %d\n", image->ulFlags, image->hbm, image->pszImage,
+ image->cchImageMax, image->xOffsetPercent, image->yOffsetPercent);
+
+ if (image->ulFlags & ~LVBKIF_SOURCE_MASK)
+ FIXME("unsupported flags %08lx\n", image->ulFlags & ~LVBKIF_SOURCE_MASK);
+
+ if (image->xOffsetPercent || image->yOffsetPercent)
+ FIXME("unsupported offset %d,%d\n", image->xOffsetPercent, image->yOffsetPercent);
+
+ switch (image->ulFlags & LVBKIF_SOURCE_MASK)
+ {
+ case LVBKIF_SOURCE_NONE:
+ if (infoPtr->hBkBitmap)
+ {
+ DeleteObject(infoPtr->hBkBitmap);
+ infoPtr->hBkBitmap = NULL;
+ }
+ InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
+ break;
+
+ case LVBKIF_SOURCE_HBITMAP:
+ {
+ BITMAP bm;
+
+ if (infoPtr->hBkBitmap)
+ {
+ DeleteObject(infoPtr->hBkBitmap);
+ infoPtr->hBkBitmap = NULL;
+ }
+ InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
+ if (GetObjectW(image->hbm, sizeof(bm), &bm) == sizeof(bm))
+ {
+ infoPtr->hBkBitmap = image->hbm;
+ return TRUE;
+ }
+ break;
+ }
+
+ case LVBKIF_SOURCE_URL:
+ FIXME("LVBKIF_SOURCE_URL: %s\n", isW ? debugstr_w(image->pszImage) : debugstr_a((LPCSTR)image->pszImage));
+ break;
+ }
+
+ return FALSE;
+}
/*** Helper for {Insert,Set}ColumnT *only* */
static void column_fill_hditem(const LISTVIEW_INFO *infoPtr, HDITEMW *lphdi, INT nColumn,
@@ -9641,10 +9707,11 @@ static inline BOOL LISTVIEW_EraseBkgnd(const LISTVIEW_INFO *infoPtr, HDC hdc)
if (infoPtr->clrBk == CLR_NONE)
{
if (infoPtr->dwLvExStyle & LVS_EX_TRANSPARENTBKGND)
- return SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT,
- (WPARAM)hdc, PRF_ERASEBKGND);
+ SendMessageW(infoPtr->hwndNotify, WM_PRINTCLIENT, (WPARAM)hdc, PRF_ERASEBKGND);
else
- return SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
+ SendMessageW(infoPtr->hwndNotify, WM_ERASEBKGND, (WPARAM)hdc, 0);
+ LISTVIEW_DrawBackgroundBitmap(infoPtr, hdc, &rc);
+ return TRUE;
}
/* for double buffered controls we need to do this during refresh */
@@ -10406,6 +10473,7 @@ static LRESULT LISTVIEW_NCDestroy(LISTVIEW_INFO *infoPtr)
infoPtr->hFont = 0;
if (infoPtr->hDefaultFont) DeleteObject(infoPtr->hDefaultFont);
if (infoPtr->clrBk != CLR_NONE) DeleteObject(infoPtr->hBkBrush);
+ if (infoPtr->hBkBitmap) DeleteObject(infoPtr->hBkBitmap);
SetWindowLongPtrW(infoPtr->hwndSelf, 0, 0);
@@ -11554,7 +11622,9 @@ LISTVIEW_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
case LVM_SETBKCOLOR:
return LISTVIEW_SetBkColor(infoPtr, (COLORREF)lParam);
- /* case LVM_SETBKIMAGE: */
+ case LVM_SETBKIMAGEA:
+ case LVM_SETBKIMAGEW:
+ return LISTVIEW_SetBkImage(infoPtr, (LVBKIMAGEW *)lParam, uMsg == LVM_SETBKIMAGEW);
case LVM_SETCALLBACKMASK:
infoPtr->uCallbackMask = (UINT)wParam;
diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c
index 6ac7f53137d..ed5222a5ee8 100644
--- a/dlls/comctl32/tests/listview.c
+++ b/dlls/comctl32/tests/listview.c
@@ -23,6 +23,7 @@
#include <stdio.h>
#include <windows.h>
#include <commctrl.h>
+#include <objbase.h>
#include "wine/test.h"
#include "v6util.h"
@@ -6924,6 +6925,81 @@ static void test_LVM_GETNEXTITEMINDEX(void)
DestroyWindow(hwnd);
}
+static void test_LVM_SETBKIMAGE(BOOL is_v6)
+{
+ LVBKIMAGEA image;
+ HBITMAP hbmp;
+ BITMAP bm;
+ HWND hwnd;
+ int ret;
+
+ CoInitialize(NULL);
+
+ hbmp = CreateBitmap(32, 32, 1, 1, NULL);
+ hwnd = create_listview_control(LVS_REPORT);
+
+ image.ulFlags = LVBKIF_SOURCE_NONE;
+ image.hbm = 0;
+ image.pszImage = NULL;
+ image.cchImageMax = 0;
+ image.xOffsetPercent = 0;
+ image.yOffsetPercent = 0;
+ ret = SendMessageA(hwnd, LVM_SETBKIMAGEA, 0, (LPARAM)&image);
+ ok(!ret, "got %d\n", ret);
+
+ ret = GetObjectA(hbmp, sizeof(bm), &bm);
+ ok(ret == sizeof(bm), "got %d\n", ret);
+
+ image.ulFlags = LVBKIF_SOURCE_HBITMAP;
+ image.hbm = hbmp;
+ ret = SendMessageA(hwnd, LVM_SETBKIMAGEA, 0, (LPARAM)&image);
+ if (is_v6)
+ ok(ret, "got %d\n", ret);
+ else
+ todo_wine ok(!ret, "got %d\n", ret);
+
+ ret = GetObjectA(hbmp, sizeof(bm), &bm);
+ ok(ret == sizeof(bm), "got %d\n", ret);
+
+ image.ulFlags = LVBKIF_SOURCE_NONE;
+ image.hbm = 0;
+ ret = SendMessageA(hwnd, LVM_SETBKIMAGEA, 0, (LPARAM)&image);
+ ok(!ret, "got %d\n", ret);
+
+ ret = GetObjectA(hbmp, sizeof(bm), &bm);
+ ok(!ret, "got %d\n", ret);
+
+ hbmp = CreateBitmap(32, 32, 1, 1, NULL);
+
+ image.ulFlags = LVBKIF_SOURCE_HBITMAP;
+ image.hbm = hbmp;
+ ret = SendMessageA(hwnd, LVM_SETBKIMAGEA, 0, (LPARAM)&image);
+ if (is_v6)
+ ok(ret, "got %d\n", ret);
+ else
+ todo_wine ok(!ret, "got %d\n", ret);
+
+ ret = GetObjectA(hbmp, sizeof(bm), &bm);
+ ok(ret == sizeof(bm), "got %d\n", ret);
+
+ image.ulFlags = LVBKIF_SOURCE_HBITMAP;
+ image.hbm = hbmp;
+ ret = SendMessageA(hwnd, LVM_SETBKIMAGEA, 0, (LPARAM)&image);
+ ok(!ret, "got %d\n", ret);
+
+ ret = GetObjectA(hbmp, sizeof(bm), &bm);
+ ok(!ret, "got %d\n", ret);
+
+ image.ulFlags = LVBKIF_SOURCE_NONE;
+ image.hbm = 0;
+ ret = SendMessageA(hwnd, LVM_SETBKIMAGEA, 0, (LPARAM)&image);
+ ok(!ret, "got %d\n", ret);
+
+ DestroyWindow(hwnd);
+
+ CoUninitialize();
+}
+
START_TEST(listview)
{
ULONG_PTR ctx_cookie;
@@ -6987,6 +7063,7 @@ START_TEST(listview)
test_LVN_ENDLABELEDIT();
test_LVM_GETCOUNTPERPAGE();
test_item_state_change();
+ test_LVM_SETBKIMAGE(FALSE);
if (!load_v6_module(&ctx_cookie, &hCtx))
{
@@ -7034,6 +7111,7 @@ START_TEST(listview)
test_item_state_change();
test_selected_column();
test_LVM_GETNEXTITEMINDEX();
+ test_LVM_SETBKIMAGE(TRUE);
unload_v6_module(ctx_cookie, hCtx);
--
2.34.1
June 14, 2022
Re: [PATCH v2 resend 6/6] comctl32/listview: Fix deselect on LVS_OWNERDATA.
by Zhiyi Zhang
On 6/11/22 23:34, Angelo Haller wrote:
> On 11/06/2022 02.55, Zhiyi Zhang wrote:
>>
>> On 6/11/22 02:18, Angelo Haller wrote:
>>> On 10/06/2022 03.13, Zhiyi Zhang wrote:
>>>> On 5/26/22 04:00, Angelo Haller wrote:
>>>>> From: Angelo Haller <angelo(a)szanni.org>
>>>>>
>>>>> Send one "deselect all items" notification on selection change for
>>>>> LVS_OWNERDATA listviews instead of notifying about each individual
>>>>> item change.
>>>>>
>>>>> Enable LVS_OWNERDATA multi select tests for wine.
>>>>>
>>>>> Signed-off-by: Angelo Haller <angelo(a)szanni.org>
>>>>> ---
>>>>> dlls/comctl32/listview.c | 6 ++++++
>>>>> dlls/comctl32/tests/listview.c | 8 ++++----
>>>>> 2 files changed, 10 insertions(+), 4 deletions(-)
>>>>>
>>>>> diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
>>>>> index bb394974906..a71e34b99d9 100644
>>>>> --- a/dlls/comctl32/listview.c
>>>>> +++ b/dlls/comctl32/listview.c
>>>>> @@ -3406,6 +3406,12 @@ static BOOL LISTVIEW_DeselectAllSkipItems(LISTVIEW_INFO *infoPtr, RANGES toSkip)
>>>>> lvItem.state = 0;
>>>>> lvItem.stateMask = LVIS_SELECTED;
>>>>> +
>>>>> + /* notify deselect of all items (-1) on LVS_OWNERDATA style */
>>>>> + if (infoPtr->dwStyle & LVS_OWNERDATA) {
>>>>> + LISTVIEW_SetItemState(infoPtr, -1, &lvItem);
>>>>> + return TRUE;
>>>>> + }
>>>> Please add a separate test for this. Select and then deselect all items instead of mixing it with other tests.
>>> Maybe the comment needs to be better. Deselect all (-1) is the ONLY signal ever sent on ANY deselection,
>>> be it one or multiple items (for LVS_OWNERDATA).
>>> This is how win32 implements it, hence there is no way of making this a separate test.
>>>
>>> Maybe the line should read:
>>>
>>> /* Always send one deselect all (-1) notification for LVS_OWNERDATA style instead of
>>> * informing individual items of deselection */
>>>
>>> And I am still looking into the other remarks. Some might need reordering as I won't be able to
>>> enable the tests without this last patch.
>>>
>>> Thanks for the feedback so far!
>> I was thinking adding a test that selects items, and them maybe send a mouse click somewhere
>> in the blank space of the listview control to deselect them all to show that only a -1 notification is sent.
>> But it may be difficult to get the tests work reliably. If you can't make it work, I guess mixing it with
>> other tests is okay.
>
> We do not need mouse clicks. See my reply to your comment in 1/6. The deselect of multiple items can be triggered by selecting multiple holding shift, releasing shift and moving the cursor again. This deselects multiple. The code testing for this is already in 1/6.
Using the keyboard VK_DOWN to deselect simultaneously selects another item. That's why I think it's mixing the results.
You should be able to emulate a mouse click on the blank listview client area to deselect all items, without generating
extra messages. This way we can see only the essential messages are sent and it's clearer.
>
> My comment was with regards to enabling the tests individually. I might be able to do so by reordering and spitting some of the patches.
>
>>
>>
>>>>> /* need to clone the DPA because callbacks can change it */
>>>>> if (!(clone = ranges_clone(infoPtr->selectionRanges))) return FALSE;
>>>>> diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c
>>>>> index 78b3e3ae069..066858ac7e8 100644
>>>>> --- a/dlls/comctl32/tests/listview.c
>>>>> +++ b/dlls/comctl32/tests/listview.c
>>>>> @@ -3598,7 +3598,7 @@ static void test_ownerdata_multiselect(void)
>>>>> ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> ownerdata_multiselect_select_0_to_1_seq,
>>>>> - "ownerdata multiselect: select multiple via SHIFT", TRUE);
>>>>> + "ownerdata multiselect: select multiple via SHIFT", FALSE);
>>>>> res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
>>>>> expect(0, res);
>>>>> @@ -3615,7 +3615,7 @@ static void test_ownerdata_multiselect(void)
>>>>> ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> ownerdata_multiselect_select_0_to_2_seq,
>>>>> - "ownerdata multiselect: select multiple via SHIFT+CONTROL", TRUE);
>>>>> + "ownerdata multiselect: select multiple via SHIFT+CONTROL", FALSE);
>>>>> res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
>>>>> expect(0, res);
>>>>> @@ -3633,7 +3633,7 @@ static void test_ownerdata_multiselect(void)
>>>>> ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> ownerdata_multiselect_deselect_all_select_3_seq,
>>>>> - "ownerdata multiselect: deselect all, select item 3", TRUE);
>>>>> + "ownerdata multiselect: deselect all, select item 3", FALSE);
>>>>> res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
>>>>> expect(1, res);
>>>>> @@ -3645,7 +3645,7 @@ static void test_ownerdata_multiselect(void)
>>>>> ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> ownerdata_multiselect_deselect_3_select_2_seq,
>>>>> - "ownerdata multiselect: deselect item 3, select item 2", TRUE);
>>>>> + "ownerdata multiselect: deselect item 3, select item 2", FALSE);;
>>>> Extra ;
>>>>
>>>>
>>>> Thanks,
>>>> Zhiyi
>>>>
>>>>
>>>>> DestroyWindow(hwnd);
>>>>> }
>>>
>
June 14, 2022
Re: [PATCH v2 resend 3/6] comctl32/listview: Send LVN_ODSTATECHANGED only for virtual lists.
by Zhiyi Zhang
On 6/11/22 23:30, Angelo Haller wrote:
> On 11/06/2022 03.02, Zhiyi Zhang wrote:
>>
>> On 6/11/22 07:56, Angelo Haller wrote:
>>> On 10/06/2022 03.13, Zhiyi Zhang wrote:
>>>> On 5/26/22 04:00, Angelo Haller wrote:
>>>>> From: Angelo Haller <angelo(a)szanni.org>
>>>>>
>>>>> The LVN_ODSTATECHANGED notification should only be sent to lists
>>>>> that have LVS_OWNERDATA set.
>>>>>
>>>>> Signed-off-by: Angelo Haller <angelo(a)szanni.org>
>>>>> ---
>>>>> dlls/comctl32/listview.c | 1 +
>>>>> 1 file changed, 1 insertion(+)
>>>>>
>>>>> diff --git a/dlls/comctl32/listview.c b/dlls/comctl32/listview.c
>>>>> index 72ade724313..318df0a4093 100644
>>>>> --- a/dlls/comctl32/listview.c
>>>>> +++ b/dlls/comctl32/listview.c
>>>>> @@ -8946,6 +8946,7 @@ static VOID LISTVIEW_SetOwnerDataState(LISTVIEW_INFO *infoPtr, INT nFirst, INT n
>>>>> {
>>>>> NMLVODSTATECHANGE nmlv;
>>>>> + if (!(infoPtr->dwStyle & LVS_OWNERDATA)) return;
>>>> Make sense. It will be better if you can add a simple test before this patch and remove the todo_wines after the fix.
>>> Is this strictly necessary? The call site has special handling for:
>>> infoPtr->dwStyle & LVS_OWNERDATA
>>>
>>> We could guard the call to the function at the call site, if that is preferred.
>>>
>>> Apart from that I am not sure how to write a test for this. This bug is triggered by creating a non ownerdata list and selecting multiple entries holding shift+ctrl and clicking with the mouse. This will send an LVN_ODSTATECHANGED notification where it is not supposed to (as it is not an ownerdata list).
>> You can write a test that demonstrate the exact same thing. Create a non onwerdata listview control and test the message sequence doesn't contain LVN_ODSTATECHANGED, which should have a todo_wine
>> because Wine is currently broken in this case. Then you fix it in the next patch and removes the todo_wine.
>>
>> As for the mouse emulation, could you do it using only the keyboard? Such as sending VK_DOWN while holding Shift and Ctrl?
>>
> This is exactly what I was trying to communicate. I can NOT trigger the erroneous sending of LVN_ODSTATECHANGED via the keyboard.
Ok, it's fine to leave the tests.
>
> The bug is in LISTVIEW_AddGroupSelection which gets called from LISTVIEW_LButtonDown.
>
> Patch 2/6 moves the offending code to a new function LISTVIEW_SetOwnerdataState().
>
> This is why I asked about opening a bug report with an contrived application that crashes with wine but not with windows.
>
>>> I have not found any test examples with mouse emulation. Maybe I missed something.
>>>
>>> Or should I open a separate bug report for this line?
>>>
>>>>> if (!item) return;
>>>>> ZeroMemory(&nmlv, sizeof(nmlv));
>>>
>
June 14, 2022
Re: [PATCH v2 resend 1/6] comctl32/tests: Expand ownerdata listview tests.
by Zhiyi Zhang
On 6/11/22 23:26, Angelo Haller wrote:
> On 11/06/2022 02.57, Zhiyi Zhang wrote:
>>
>> On 6/11/22 02:33, Angelo Haller wrote:
>>> On 10/06/2022 03.12, Zhiyi Zhang wrote:
>>>> On 5/26/22 04:00, Angelo Haller wrote:
>>>>> From: Angelo Haller <angelo(a)szanni.org>
>>>>>
>>>>> Add more test cases to ownderdata listviews.
>>>>> Check LVN_ITEMCHANGED IDs.
>>>>>
>>>>> Signed-off-by: Angelo Haller <angelo(a)szanni.org>
>>>>> ---
>>>>> dlls/comctl32/tests/listview.c | 59 +++++++++++++++++++++++++++++-----
>>>>> 1 file changed, 51 insertions(+), 8 deletions(-)
>>>>>
>>>>> diff --git a/dlls/comctl32/tests/listview.c b/dlls/comctl32/tests/listview.c
>>>>> index 6ac7f53137d..78b3e3ae069 100644
>>>>> --- a/dlls/comctl32/tests/listview.c
>>>>> +++ b/dlls/comctl32/tests/listview.c
>>>>> @@ -255,11 +255,33 @@ 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_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_to_2_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_deselect_all_select_3_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_deselect_3_select_2_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 },
>>>>> { 0 }
>>>>> };
>>>>> @@ -3575,8 +3597,8 @@ static void test_ownerdata_multiselect(void)
>>>>> expect(0, res);
>>>>> ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> - ownerdata_multiselect_odstatechanged_seq,
>>>>> - "ownerdata select multiple notification", TRUE);
>>>>> + ownerdata_multiselect_select_0_to_1_seq,
>>>>> + "ownerdata multiselect: select multiple via SHIFT", TRUE);
>>>>> res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
>>>>> expect(0, res);
>>>>> @@ -3592,8 +3614,8 @@ static void test_ownerdata_multiselect(void)
>>>>> expect(0, res);
>>>>> ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> - ownerdata_multiselect_odstatechanged_seq,
>>>>> - "ownerdata select multiple notification", TRUE);
>>>>> + ownerdata_multiselect_select_0_to_2_seq,
>>>>> + "ownerdata multiselect: select multiple via SHIFT+CONTROL", TRUE);
>>>>> res = SendMessageA(hwnd, WM_KEYUP, VK_DOWN, 0);
>>>>> expect(0, res);
>>>>> @@ -3604,6 +3626,27 @@ static void test_ownerdata_multiselect(void)
>>>>> res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
>>>>> expect(3, res);
>>>>> + flush_sequences(sequences, NUM_MSG_SEQUENCES);
>>>>> +
>>>>> + res = SendMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
>>>>> + expect(0, res);
>>>>> +
>>>>> + ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> + ownerdata_multiselect_deselect_all_select_3_seq,
>>>>> + "ownerdata multiselect: deselect all, select item 3", TRUE);
>>>>> +
>>>>> + res = SendMessageA(hwnd, LVM_GETSELECTEDCOUNT, 0, 0);
>>>>> + expect(1, res);
>>>> Hi Angelo,
>>>>
>>>> Thanks for looking into this. There are a few things can be improved for this series.
>>>>
>>>> Please complete the keyboard sequence by sending WM_KEYUP for VK_DOWN and
>>>> WM_KEYUP for VK_UP.
>>> I'll add the WM_KEYUP events for sure. Not sure how that slipped.
>>>> Also please add tests for pressing VK_UP while holding Shift after setting pressing VK_DOWN while holding Shift.
>>>> Same for holding both Shift and Control And add checks for NMLVODSTATECHANGE member values. For example,
>>>> I don't think uOldState should always be 0.
>>> Happy to add more tests for VK_UP.
>>>
>>> With regards to the uOldState: I have not found an instance where it is not 0. Which kind of makes sense, at least according to the underlying logic.
>>>
>>> Windows seems to only use LVN_ODSTATECHANGED to set a new state (selection of multiple items). For un-setting (uOldState != 0) Windows will instead send an all items changed (LVN_ITEMCHANGED = -1) signal instead. That is at least what I have observed in my testing.
>> I see. Please add that tests as well.
>
> That test is in this exact patch. This is why I added ownerdata_multiselect_deselect_all_select_3_seq. It checks that on deslection of multiple items NO LVN_ODSTATECHANGED is send but instead LVN_ITEMCHANGED = -1.
>
> Or did i misunderstand?
ownerdata_multiselect_deselect_all_select_3_seq is tested with Shift key released. It would be helpful to see the
deselect message sequence while holding Shift key.
>
>>
>>>>> +
>>>>> + flush_sequences(sequences, NUM_MSG_SEQUENCES);
>>>>> +
>>>>> + res = SendMessageA(hwnd, WM_KEYDOWN, VK_UP, 0);
>>>>> + expect(0, res);
>>>>> +
>>>>> + ok_sequence(sequences, PARENT_ODSTATECHANGED_SEQ_INDEX,
>>>>> + ownerdata_multiselect_deselect_3_select_2_seq,
>>>>> + "ownerdata multiselect: deselect item 3, select item 2", TRUE);
>>>>> +
>>>>> DestroyWindow(hwnd);
>>>>> }
>>>>>
>>>
>
June 14, 2022
Re: Questions about the GitLab patch review process
by Alex Henrie
On Fri, Jun 10, 2022 at 10:13 AM Rémi Bernon <rbernon(a)codeweavers.com> wrote:
>
> On 6/10/22 17:21, Alex Henrie wrote:
> > Dear developers,
> >
> > I have a couple of questions about how patch reviews on GitLab are
> > intended to work:
> >
> > * Can I request a review from anyone?
> >
> > * Does requesting a review improve the patch's chances of being accepted?
> >
> > Thanks for your help.
> >
> > -Alex
> >
>
> Imho yes to both, especially to the second question.
>
> For the first question, it's probably best to request review from the
> maintainer of a module. Sometimes there isn't any and looking for
> someone knowledgeable is best.
>
> If there is a maintainer, they will probably be assigned by Alexandre
> anyway at some point (but it saves some effort if you do).
Thanks for the advice. I agree that requesting a review from the
person or people listed in the MAINTAINERS file would be best, but my
interests tend to lie in places that don't have a specific maintainer.
I will try to find knowledgeable contributors to review my GitLab
patches, and I hope you're right that it will speed the process along.
-Alex
June 14, 2022
How to identify the primary battery (for Wine)
by Alex Henrie
Hi Sebastian,
I've been working to improve battery status reporting for applications
running on Wine on Linux. However, we Wine developers are not sure
what the best way is to determine which battery is the computer's
primary battery. Could you take a look at
https://gitlab.winehq.org/wine/wine/-/merge_requests/134 and leave a
comment there or on the wine-devel mailing list?
Thanks in advance,
-Alex
June 14, 2022
[PATCH 3/3] uiautomationcore: Implement NavigateDirection_{Previous/Next}Sibling for MSAA providers.
by Connor McAdams
From: Connor McAdams <cmcadams(a)codeweavers.com>
Signed-off-by: Connor McAdams <cmcadams(a)codeweavers.com>
---
dlls/uiautomationcore/tests/uiautomation.c | 132 +++++++++----------
dlls/uiautomationcore/uia_provider.c | 140 +++++++++++++++++++--
2 files changed, 195 insertions(+), 77 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c
index da19d3668fa..15d7b4e784b 100644
--- a/dlls/uiautomationcore/tests/uiautomation.c
+++ b/dlls/uiautomationcore/tests/uiautomation.c
@@ -1112,13 +1112,13 @@ static void test_uia_prov_from_acc_navigation(void)
elfrag2 = (void *)0xdeadbeef;
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
elfrag2 = (void *)0xdeadbeef;
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_PreviousSibling, &elfrag2);
ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
/*
@@ -1149,18 +1149,16 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT(Accessible_get_accChild);
SET_EXPECT(Accessible_get_accState);
hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_NextSibling, &elfrag3);
- todo_wine ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag3, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED(Accessible_get_accChildCount);
- todo_wine CHECK_CALLED(Accessible_get_accChild);
- todo_wine CHECK_CALLED(Accessible_get_accState);
- if (elfrag3)
- {
- check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
- IRawElementProviderFragment_Release(elfrag3);
- ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
- }
+ ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag3, "elfrag2 == NULL\n");
+ CHECK_CALLED(Accessible_get_accChildCount);
+ CHECK_CALLED(Accessible_get_accChild);
+ CHECK_CALLED(Accessible_get_accState);
+ check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
+
+ IRawElementProviderFragment_Release(elfrag3);
+ ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
IRawElementProviderFragment_Release(elfrag2);
ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
@@ -1206,19 +1204,16 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT(Accessible_get_accChild);
SET_EXPECT(Accessible_get_accState);
hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_PreviousSibling, &elfrag3);
- todo_wine ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag3, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED(Accessible_get_accChildCount);
- todo_wine CHECK_CALLED(Accessible_get_accChild);
- todo_wine CHECK_CALLED(Accessible_get_accState);
- if (elfrag3)
- {
- check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
- IRawElementProviderFragment_Release(elfrag3);
- ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
- }
+ ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag3, "elfrag2 == NULL\n");
+ CHECK_CALLED(Accessible_get_accChildCount);
+ CHECK_CALLED(Accessible_get_accChild);
+ CHECK_CALLED(Accessible_get_accState);
+ check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
+ IRawElementProviderFragment_Release(elfrag3);
+ ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
IRawElementProviderFragment_Release(elfrag2);
ok(Accessible_child2.ref == 1, "Unexpected refcnt %ld\n", Accessible_child2.ref);
ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
@@ -1338,21 +1333,21 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT(Accessible_child2_accLocation);
SET_EXPECT(Accessible_child2_get_accName);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 5);
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 4);
- todo_wine CHECK_CALLED(Accessible_child_get_accParent);
- todo_wine CHECK_CALLED(Accessible_child_get_accRole);
- todo_wine CHECK_CALLED(Accessible_child_get_accState);
- todo_wine CHECK_CALLED(Accessible_child_get_accChildCount);
- todo_wine CHECK_CALLED(Accessible_child_accLocation);
- todo_wine CHECK_CALLED(Accessible_child_get_accName);
- todo_wine CHECK_CALLED(Accessible_child2_get_accRole);
- todo_wine CHECK_CALLED(Accessible_child2_get_accState);
- todo_wine CHECK_CALLED(Accessible_child2_get_accChildCount);
- todo_wine CHECK_CALLED(Accessible_child2_accLocation);
- todo_wine CHECK_CALLED(Accessible_child2_get_accName);
+ CHECK_CALLED_MULTI(Accessible_get_accChildCount, 5);
+ CHECK_CALLED_MULTI(Accessible_get_accChild, 4);
+ CHECK_CALLED(Accessible_child_get_accParent);
+ CHECK_CALLED(Accessible_child_get_accRole);
+ CHECK_CALLED(Accessible_child_get_accState);
+ CHECK_CALLED(Accessible_child_get_accChildCount);
+ CHECK_CALLED(Accessible_child_accLocation);
+ CHECK_CALLED(Accessible_child_get_accName);
+ CHECK_CALLED(Accessible_child2_get_accRole);
+ CHECK_CALLED(Accessible_child2_get_accState);
+ CHECK_CALLED(Accessible_child2_get_accChildCount);
+ CHECK_CALLED(Accessible_child2_accLocation);
+ CHECK_CALLED(Accessible_child2_get_accName);
/* Now they have a role mismatch, we can determine our position. */
set_accessible_props(&Accessible_child2, ROLE_SYSTEM_DOCUMENT, STATE_SYSTEM_FOCUSABLE, 1,
@@ -1369,27 +1364,27 @@ static void test_uia_prov_from_acc_navigation(void)
* Even though we didn't get a new fragment, now that we know our
* position, a reference is added to the parent IAccessible.
*/
- todo_wine ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 6);
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 5);
- todo_wine CHECK_CALLED(Accessible_get_accState);
- todo_wine CHECK_CALLED(Accessible_child_get_accParent);
- todo_wine CHECK_CALLED(Accessible_child_get_accRole);
- todo_wine CHECK_CALLED(Accessible_child2_get_accRole);
+ CHECK_CALLED_MULTI(Accessible_get_accChildCount, 6);
+ CHECK_CALLED_MULTI(Accessible_get_accChild, 5);
+ CHECK_CALLED(Accessible_get_accState);
+ CHECK_CALLED(Accessible_child_get_accParent);
+ CHECK_CALLED(Accessible_child_get_accRole);
+ CHECK_CALLED(Accessible_child2_get_accRole);
/* Now that we know our position, no extra nav work. */
SET_EXPECT(Accessible_get_accChildCount);
SET_EXPECT(Accessible_get_accChild);
SET_EXPECT(Accessible_get_accState);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
- todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED(Accessible_get_accChildCount);
- todo_wine CHECK_CALLED(Accessible_get_accChild);
- todo_wine CHECK_CALLED(Accessible_get_accState);
+ ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag2, "elfrag2 == NULL\n");
+ CHECK_CALLED(Accessible_get_accChildCount);
+ CHECK_CALLED(Accessible_get_accChild);
+ CHECK_CALLED(Accessible_get_accState);
if (elfrag2)
{
check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
@@ -1449,23 +1444,20 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT(Accessible_child_accNavigate);
SET_EXPECT(Accessible_child_get_accParent);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_NextSibling, &elfrag2);
- todo_wine ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref);
- todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED(Accessible_get_accChildCount);
- todo_wine CHECK_CALLED(Accessible_get_accChild);
- todo_wine CHECK_CALLED(Accessible_child_get_accState);
- todo_wine CHECK_CALLED(Accessible_child_accNavigate);
- todo_wine CHECK_CALLED(Accessible_child_get_accParent);
- if (elfrag2)
- {
- check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF);
- IRawElementProviderFragment_Release(elfrag2);
- ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
- ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
- }
+ ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref);
+ ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag2, "elfrag2 == NULL\n");
+ CHECK_CALLED(Accessible_get_accChildCount);
+ CHECK_CALLED(Accessible_get_accChild);
+ CHECK_CALLED(Accessible_child_get_accState);
+ CHECK_CALLED(Accessible_child_accNavigate);
+ CHECK_CALLED(Accessible_child_get_accParent);
+ check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF);
+ IRawElementProviderFragment_Release(elfrag2);
+ ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
+ ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
IRawElementProviderFragment_Release(elfrag);
IRawElementProviderSimple_Release(elprov);
ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c
index 891cb486161..f72628885bd 100644
--- a/dlls/uiautomationcore/uia_provider.c
+++ b/dlls/uiautomationcore/uia_provider.c
@@ -223,7 +223,7 @@ static HRESULT msaa_acc_get_parent(IAccessible *acc, IAccessible **parent)
#define DIR_FORWARD 0
#define DIR_REVERSE 1
static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG direction,
- IAccessible **child, LONG *child_id, LONG *end_pos)
+ IAccessible **child, LONG *child_id, LONG *end_pos, BOOL check_visible)
{
LONG child_count, cur_pos;
IDispatch *disp;
@@ -246,7 +246,7 @@ static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG di
if (hr == S_FALSE)
{
- if (!msaa_check_acc_state(acc, cid, STATE_SYSTEM_INVISIBLE))
+ if (!check_visible || !msaa_check_acc_state(acc, cid, STATE_SYSTEM_INVISIBLE))
{
*child = acc;
*child_id = *end_pos = cur_pos;
@@ -263,7 +263,7 @@ static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG di
break;
variant_init_i4(&cid, CHILDID_SELF);
- if (!msaa_check_acc_state(acc_child, cid, STATE_SYSTEM_INVISIBLE))
+ if (!check_visible || !msaa_check_acc_state(acc_child, cid, STATE_SYSTEM_INVISIBLE))
{
*child = acc_child;
*child_id = CHILDID_SELF;
@@ -286,6 +286,83 @@ static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG di
return hr;
}
+static HRESULT msaa_acc_get_child_pos(IAccessible *acc, IAccessible **out_parent, LONG *out_pos)
+{
+ IAccessible *child, *parent, *match, **children;
+ LONG child_count, child_id, end_pos, match_pos;
+ HRESULT hr;
+ int i;
+
+ *out_parent = NULL;
+ *out_pos = 0;
+ hr = msaa_acc_get_parent(acc, &parent);
+ if (FAILED(hr) || !parent)
+ return hr;
+
+ hr = IAccessible_get_accChildCount(parent, &child_count);
+ if (FAILED(hr) || !child_count)
+ {
+ IAccessible_Release(parent);
+ return hr;
+ }
+
+ children = heap_alloc_zero(sizeof(*children) * child_count);
+ if (!children)
+ return E_OUTOFMEMORY;
+
+ match = NULL;
+ for (i = 0; i < child_count; i++)
+ {
+ hr = msaa_acc_get_next_child(parent, i + 1, DIR_FORWARD, &child, &child_id, &end_pos, FALSE);
+ if (FAILED(hr) || !child)
+ goto exit;
+
+ if (child != parent)
+ children[i] = child;
+ }
+
+ for (i = 0; i < child_count; i++)
+ {
+ if (!children[i])
+ continue;
+
+ if (msaa_acc_compare(acc, children[i]))
+ {
+ if (!match)
+ {
+ match = children[i];
+ match_pos = i + 1;
+ }
+ /* Can't have more than one IAccessible match. */
+ else
+ {
+ match = NULL;
+ match_pos = 0;
+ break;
+ }
+ }
+ }
+
+exit:
+ if (match)
+ {
+ *out_parent = parent;
+ *out_pos = match_pos;
+ }
+ else
+ IAccessible_Release(parent);
+
+ for (i = 0; i < child_count; i++)
+ {
+ if (children[i])
+ IAccessible_Release(children[i]);
+ }
+
+ heap_free(children);
+
+ return hr;
+}
+
static LONG msaa_role_to_uia_control_type(LONG role)
{
switch (role)
@@ -647,9 +724,11 @@ static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface,
break;
if (direction == NavigateDirection_FirstChild)
- hr = msaa_acc_get_next_child(msaa_prov->acc, 1, DIR_FORWARD, &acc, &child_id, &end_pos);
+ hr = msaa_acc_get_next_child(msaa_prov->acc, 1, DIR_FORWARD, &acc, &child_id,
+ &end_pos, TRUE);
else
- hr = msaa_acc_get_next_child(msaa_prov->acc, child_count, DIR_REVERSE, &acc, &child_id, &end_pos);
+ hr = msaa_acc_get_next_child(msaa_prov->acc, child_count, DIR_REVERSE, &acc, &child_id,
+ &end_pos, TRUE);
if (FAILED(hr) || !acc)
break;
@@ -675,8 +754,55 @@ static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface,
case NavigateDirection_NextSibling:
case NavigateDirection_PreviousSibling:
- FIXME("Unimplemented NavigateDirection %d\n", direction);
- return E_NOTIMPL;
+ if (msaa_check_root_acc(msaa_prov))
+ break;
+
+ if (!msaa_prov->parent)
+ {
+ if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
+ {
+ msaa_prov->parent = msaa_prov->acc;
+ IAccessible_AddRef(msaa_prov->acc);
+ msaa_prov->child_pos = V_I4(&msaa_prov->cid);
+ }
+ else
+ {
+ hr = msaa_acc_get_child_pos(msaa_prov->acc, &acc, &child_id);
+ if (FAILED(hr) || !acc)
+ break;
+ msaa_prov->parent = acc;
+ msaa_prov->child_pos = child_id;
+ }
+ }
+
+ if (direction == NavigateDirection_NextSibling)
+ hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos + 1, DIR_FORWARD,
+ &acc, &child_id, &end_pos, TRUE);
+ else
+ hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos - 1, DIR_REVERSE,
+ &acc, &child_id, &end_pos, TRUE);
+
+ if (FAILED(hr) || !acc)
+ break;
+
+ hr = UiaProviderFromIAccessible(acc, child_id, 0, &elprov);
+ if (SUCCEEDED(hr))
+ {
+ struct msaa_provider *prov = impl_from_msaa_provider(elprov);
+
+ *ret_val = &prov->IRawElementProviderFragment_iface;
+ prov->parent = msaa_prov->parent;
+ IAccessible_AddRef(msaa_prov->parent);
+ if (acc != msaa_prov->acc)
+ prov->child_pos = end_pos;
+ else
+ prov->child_pos = child_id;
+ }
+
+ if (acc != msaa_prov->parent)
+ IAccessible_Release(acc);
+
+ break;
default:
FIXME("Invalid NavigateDirection %d\n", direction);
--
GitLab
https://gitlab.winehq.org/wine/wine/-/merge_requests/241
June 14, 2022
[PATCH 2/3] uiautomationcore: Implement NavigateDirection_{First/Last}Child for MSAA Providers.
by Connor McAdams
From: Connor McAdams <cmcadams(a)codeweavers.com>
Signed-off-by: Connor McAdams <cmcadams(a)codeweavers.com>
---
dlls/uiautomationcore/tests/uiautomation.c | 155 ++++++++++-----------
dlls/uiautomationcore/uia_provider.c | 108 +++++++++++++-
2 files changed, 181 insertions(+), 82 deletions(-)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c
index 10d91d04d81..da19d3668fa 100644
--- a/dlls/uiautomationcore/tests/uiautomation.c
+++ b/dlls/uiautomationcore/tests/uiautomation.c
@@ -1134,37 +1134,36 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT(Accessible_child_accNavigate);
SET_EXPECT(Accessible_child_get_accParent);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
- todo_wine ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref);
- todo_wine ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(Accessible_child.ref == 2, "Unexpected refcnt %ld\n", Accessible_child.ref);
+ ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag2, "elfrag2 == NULL\n");
+ CHECK_CALLED_MULTI(Accessible_get_accChildCount, 3);
+ CHECK_CALLED_MULTI(Accessible_get_accChild, 2);
+ CHECK_CALLED(Accessible_child_get_accState);
+ CHECK_CALLED(Accessible_child_accNavigate);
+ CHECK_CALLED(Accessible_child_get_accParent);
+
+ check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF);
+ SET_EXPECT(Accessible_get_accChildCount);
+ SET_EXPECT(Accessible_get_accChild);
+ SET_EXPECT(Accessible_get_accState);
+ hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_NextSibling, &elfrag3);
+ todo_wine ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 3);
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 2);
- todo_wine CHECK_CALLED(Accessible_child_get_accState);
- todo_wine CHECK_CALLED(Accessible_child_accNavigate);
- todo_wine CHECK_CALLED(Accessible_child_get_accParent);
- if (elfrag2)
+ todo_wine ok(!!elfrag3, "elfrag2 == NULL\n");
+ todo_wine CHECK_CALLED(Accessible_get_accChildCount);
+ todo_wine CHECK_CALLED(Accessible_get_accChild);
+ todo_wine CHECK_CALLED(Accessible_get_accState);
+ if (elfrag3)
{
- check_fragment_acc(elfrag2, &Accessible_child.IAccessible_iface, CHILDID_SELF);
-
- SET_EXPECT(Accessible_get_accChildCount);
- SET_EXPECT(Accessible_get_accChild);
- SET_EXPECT(Accessible_get_accState);
- hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_NextSibling, &elfrag3);
- ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- ok(!!elfrag3, "elfrag2 == NULL\n");
- CHECK_CALLED(Accessible_get_accChildCount);
- CHECK_CALLED(Accessible_get_accChild);
- CHECK_CALLED(Accessible_get_accState);
check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
-
IRawElementProviderFragment_Release(elfrag3);
ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
- IRawElementProviderFragment_Release(elfrag2);
- ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
- ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
}
+ IRawElementProviderFragment_Release(elfrag2);
+ ok(Accessible_child.ref == 1, "Unexpected refcnt %ld\n", Accessible_child.ref);
+ ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
/* Retrieve childid 3 as first child now that Accessible_child is invisible. */
set_accessible_props(&Accessible_child, 0, STATE_SYSTEM_INVISIBLE, 0, NULL, 0, 0, 0, 0);
@@ -1173,19 +1172,16 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT_MULTI(Accessible_get_accState, 2);
SET_EXPECT(Accessible_child_get_accState);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
- todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 4);
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 3);
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accState, 2);
- todo_wine CHECK_CALLED(Accessible_child_get_accState);
- if (elfrag2)
- {
- check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
- IRawElementProviderFragment_Release(elfrag2);
- ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
- }
+ ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag2, "elfrag2 == NULL\n");
+ CHECK_CALLED_MULTI(Accessible_get_accChildCount, 4);
+ CHECK_CALLED_MULTI(Accessible_get_accChild, 3);
+ CHECK_CALLED_MULTI(Accessible_get_accState, 2);
+ CHECK_CALLED(Accessible_child_get_accState);
+ check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
+ IRawElementProviderFragment_Release(elfrag2);
+ ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
/* Retrieve childid 4 (Accessible_child2) as last child. */
set_accessible_props(&Accessible_child2, 0, STATE_SYSTEM_FOCUSABLE, 0, NULL, 0, 0, 0, 0);
@@ -1195,38 +1191,38 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT(Accessible_child2_accNavigate);
SET_EXPECT(Accessible_child2_get_accParent);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
- todo_wine ok(Accessible_child2.ref == 2, "Unexpected refcnt %ld\n", Accessible_child2.ref);
- todo_wine ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(Accessible_child2.ref == 2, "Unexpected refcnt %ld\n", Accessible_child2.ref);
+ ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag2, "elfrag2 == NULL\n");
+ CHECK_CALLED_MULTI(Accessible_get_accChildCount, 2);
+ CHECK_CALLED(Accessible_get_accChild);
+ CHECK_CALLED(Accessible_child2_get_accState);
+ CHECK_CALLED(Accessible_child2_accNavigate);
+ CHECK_CALLED(Accessible_child2_get_accParent);
+
+ check_fragment_acc(elfrag2, &Accessible_child2.IAccessible_iface, CHILDID_SELF);
+ SET_EXPECT(Accessible_get_accChildCount);
+ SET_EXPECT(Accessible_get_accChild);
+ SET_EXPECT(Accessible_get_accState);
+ hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_PreviousSibling, &elfrag3);
+ todo_wine ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 2);
+ todo_wine ok(!!elfrag3, "elfrag2 == NULL\n");
+ todo_wine CHECK_CALLED(Accessible_get_accChildCount);
todo_wine CHECK_CALLED(Accessible_get_accChild);
- todo_wine CHECK_CALLED(Accessible_child2_get_accState);
- todo_wine CHECK_CALLED(Accessible_child2_accNavigate);
- todo_wine CHECK_CALLED(Accessible_child2_get_accParent);
- if (elfrag2)
+ todo_wine CHECK_CALLED(Accessible_get_accState);
+ if (elfrag3)
{
- check_fragment_acc(elfrag2, &Accessible_child2.IAccessible_iface, CHILDID_SELF);
-
- SET_EXPECT(Accessible_get_accChildCount);
- SET_EXPECT(Accessible_get_accChild);
- SET_EXPECT(Accessible_get_accState);
- hr = IRawElementProviderFragment_Navigate(elfrag2, NavigateDirection_PreviousSibling, &elfrag3);
- ok(Accessible.ref == 5, "Unexpected refcnt %ld\n", Accessible.ref);
- ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- ok(!!elfrag3, "elfrag2 == NULL\n");
- CHECK_CALLED(Accessible_get_accChildCount);
- CHECK_CALLED(Accessible_get_accChild);
- CHECK_CALLED(Accessible_get_accState);
check_fragment_acc(elfrag3, &Accessible.IAccessible_iface, 3);
-
IRawElementProviderFragment_Release(elfrag3);
ok(Accessible.ref == 3, "Unexpected refcnt %ld\n", Accessible.ref);
- IRawElementProviderFragment_Release(elfrag2);
- ok(Accessible_child2.ref == 1, "Unexpected refcnt %ld\n", Accessible_child2.ref);
- ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
}
+ IRawElementProviderFragment_Release(elfrag2);
+ ok(Accessible_child2.ref == 1, "Unexpected refcnt %ld\n", Accessible_child2.ref);
+ ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
+
/* Retrieve childid 3 as last child, now that Accessible_child2 is STATE_SYSTEM_INVISIBLE. */
set_accessible_props(&Accessible_child2, 0, STATE_SYSTEM_INVISIBLE, 0, NULL, 0, 0, 0, 0);
SET_EXPECT_MULTI(Accessible_get_accChildCount, 3);
@@ -1234,19 +1230,16 @@ static void test_uia_prov_from_acc_navigation(void)
SET_EXPECT(Accessible_get_accState);
SET_EXPECT(Accessible_child2_get_accState);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
- todo_wine ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
- todo_wine ok(!!elfrag2, "elfrag2 == NULL\n");
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChildCount, 3);
- todo_wine CHECK_CALLED_MULTI(Accessible_get_accChild, 2);
- todo_wine CHECK_CALLED(Accessible_get_accState);
- todo_wine CHECK_CALLED(Accessible_child2_get_accState);
- if (elfrag2)
- {
- check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
- IRawElementProviderFragment_Release(elfrag2);
- ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
- }
+ ok(Accessible.ref == 4, "Unexpected refcnt %ld\n", Accessible.ref);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(!!elfrag2, "elfrag2 == NULL\n");
+ CHECK_CALLED_MULTI(Accessible_get_accChildCount, 3);
+ CHECK_CALLED_MULTI(Accessible_get_accChild, 2);
+ CHECK_CALLED(Accessible_get_accState);
+ CHECK_CALLED(Accessible_child2_get_accState);
+ check_fragment_acc(elfrag2, &Accessible.IAccessible_iface, 3);
+ IRawElementProviderFragment_Release(elfrag2);
+ ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
IRawElementProviderFragment_Release(elfrag);
IRawElementProviderSimple_Release(elprov);
@@ -1309,15 +1302,15 @@ static void test_uia_prov_from_acc_navigation(void)
/* ChildCount of 0, do nothing for First/Last child.*/
SET_EXPECT(Accessible_child_get_accChildCount);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
- todo_wine CHECK_CALLED(Accessible_child_get_accChildCount);
+ CHECK_CALLED(Accessible_child_get_accChildCount);
SET_EXPECT(Accessible_child_get_accChildCount);
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
- todo_wine CHECK_CALLED(Accessible_child_get_accChildCount);
+ CHECK_CALLED(Accessible_child_get_accChildCount);
/*
* In the case of sibling navigation on an IAccessible that wasn't
@@ -1438,12 +1431,12 @@ static void test_uia_prov_from_acc_navigation(void)
*/
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_FirstChild, &elfrag2);
ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
hr = IRawElementProviderFragment_Navigate(elfrag, NavigateDirection_LastChild, &elfrag2);
ok(Accessible.ref == 2, "Unexpected refcnt %ld\n", Accessible.ref);
- todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!elfrag2, "elfrag2 != NULL\n");
/*
diff --git a/dlls/uiautomationcore/uia_provider.c b/dlls/uiautomationcore/uia_provider.c
index 1dfe447331e..891cb486161 100644
--- a/dlls/uiautomationcore/uia_provider.c
+++ b/dlls/uiautomationcore/uia_provider.c
@@ -220,6 +220,72 @@ static HRESULT msaa_acc_get_parent(IAccessible *acc, IAccessible **parent)
return hr;
}
+#define DIR_FORWARD 0
+#define DIR_REVERSE 1
+static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG direction,
+ IAccessible **child, LONG *child_id, LONG *end_pos)
+{
+ LONG child_count, cur_pos;
+ IDispatch *disp;
+ VARIANT cid;
+ HRESULT hr;
+
+ *child = NULL;
+ *child_id = 0;
+ cur_pos = start_pos;
+ while (1)
+ {
+ hr = IAccessible_get_accChildCount(acc, &child_count);
+ if (FAILED(hr) || (cur_pos > child_count))
+ break;
+
+ variant_init_i4(&cid, cur_pos);
+ hr = IAccessible_get_accChild(acc, cid, &disp);
+ if (FAILED(hr))
+ break;
+
+ if (hr == S_FALSE)
+ {
+ if (!msaa_check_acc_state(acc, cid, STATE_SYSTEM_INVISIBLE))
+ {
+ *child = acc;
+ *child_id = *end_pos = cur_pos;
+ return S_OK;
+ }
+ }
+ else
+ {
+ IAccessible *acc_child = NULL;
+
+ hr = IDispatch_QueryInterface(disp, &IID_IAccessible, (void **)&acc_child);
+ IDispatch_Release(disp);
+ if (FAILED(hr))
+ break;
+
+ variant_init_i4(&cid, CHILDID_SELF);
+ if (!msaa_check_acc_state(acc_child, cid, STATE_SYSTEM_INVISIBLE))
+ {
+ *child = acc_child;
+ *child_id = CHILDID_SELF;
+ *end_pos = cur_pos;
+ return S_OK;
+ }
+
+ IAccessible_Release(acc_child);
+ }
+
+ if (direction == DIR_FORWARD)
+ cur_pos++;
+ else
+ cur_pos--;
+
+ if ((cur_pos > child_count) || (cur_pos <= 0))
+ break;
+ }
+
+ return hr;
+}
+
static LONG msaa_role_to_uia_control_type(LONG role)
{
switch (role)
@@ -315,6 +381,9 @@ struct msaa_provider {
BOOL root_acc_check_ran;
BOOL is_root_acc;
+
+ IAccessible *parent;
+ INT child_pos;
};
static BOOL msaa_check_root_acc(struct msaa_provider *msaa_prov)
@@ -326,7 +395,7 @@ static BOOL msaa_check_root_acc(struct msaa_provider *msaa_prov)
return msaa_prov->is_root_acc;
msaa_prov->root_acc_check_ran = TRUE;
- if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
+ if (V_I4(&msaa_prov->cid) != CHILDID_SELF || msaa_prov->parent)
return FALSE;
hr = AccessibleObjectFromWindow(msaa_prov->hwnd, OBJID_CLIENT, &IID_IAccessible, (void **)&acc);
@@ -383,6 +452,8 @@ ULONG WINAPI msaa_provider_Release(IRawElementProviderSimple *iface)
if (!refcount)
{
IAccessible_Release(msaa_prov->acc);
+ if (msaa_prov->parent)
+ IAccessible_Release(msaa_prov->parent);
heap_free(msaa_prov);
}
@@ -531,6 +602,7 @@ static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface,
enum NavigateDirection direction, IRawElementProviderFragment **ret_val)
{
struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
+ LONG child_count, child_id, end_pos;
IRawElementProviderSimple *elprov;
IAccessible *acc;
HRESULT hr;
@@ -567,6 +639,40 @@ static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface,
case NavigateDirection_FirstChild:
case NavigateDirection_LastChild:
+ if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
+ break;
+
+ hr = IAccessible_get_accChildCount(msaa_prov->acc, &child_count);
+ if (FAILED(hr) || !child_count)
+ break;
+
+ if (direction == NavigateDirection_FirstChild)
+ hr = msaa_acc_get_next_child(msaa_prov->acc, 1, DIR_FORWARD, &acc, &child_id, &end_pos);
+ else
+ hr = msaa_acc_get_next_child(msaa_prov->acc, child_count, DIR_REVERSE, &acc, &child_id, &end_pos);
+
+ if (FAILED(hr) || !acc)
+ break;
+
+ hr = UiaProviderFromIAccessible(acc, child_id, 0, &elprov);
+ if (SUCCEEDED(hr))
+ {
+ struct msaa_provider *prov = impl_from_msaa_provider(elprov);
+
+ *ret_val = &prov->IRawElementProviderFragment_iface;
+ prov->parent = msaa_prov->acc;
+ IAccessible_AddRef(msaa_prov->acc);
+ if (acc != msaa_prov->acc)
+ prov->child_pos = end_pos;
+ else
+ prov->child_pos = child_id;
+ }
+
+ if (acc != msaa_prov->acc)
+ IAccessible_Release(acc);
+
+ break;
+
case NavigateDirection_NextSibling:
case NavigateDirection_PreviousSibling:
FIXME("Unimplemented NavigateDirection %d\n", direction);
--
GitLab
https://gitlab.winehq.org/wine/wine/-/merge_requests/241
June 14, 2022
[PATCH 1/3] uiautomationcore/tests: Release interfaces returned from get_HostRawElementProvider.
by Connor McAdams
From: Connor McAdams <cmcadams(a)codeweavers.com>
Signed-off-by: Connor McAdams <cmcadams(a)codeweavers.com>
---
dlls/uiautomationcore/tests/uiautomation.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/dlls/uiautomationcore/tests/uiautomation.c b/dlls/uiautomationcore/tests/uiautomation.c
index 885e520bfd3..10d91d04d81 100644
--- a/dlls/uiautomationcore/tests/uiautomation.c
+++ b/dlls/uiautomationcore/tests/uiautomation.c
@@ -2092,12 +2092,14 @@ static void test_UiaProviderFromIAccessible(void)
CHECK_CALLED(Accessible2_get_accName);
todo_wine CHECK_CALLED(Accessible2_QI_IAccIdentity);
todo_wine CHECK_CALLED(Accessible2_get_accParent);
+ IRawElementProviderSimple_Release(elprov2);
elprov2 = (void *)0xdeadbeef;
acc_client = NULL;
hr = IRawElementProviderSimple_get_HostRawElementProvider(elprov, &elprov2);
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
ok(!!elprov2, "elprov == NULL, elprov %p\n", elprov2);
+ IRawElementProviderSimple_Release(elprov2);
IRawElementProviderSimple_Release(elprov);
ok(Accessible.ref == 1, "Unexpected refcnt %ld\n", Accessible.ref);
--
GitLab
https://gitlab.winehq.org/wine/wine/-/merge_requests/241
June 14, 2022