uiautomationcore: Add stub IProxyProviderWinEventHandler implementation for MSAA providers.
Signed-off-by: Connor McAdams <cmcadams@codeweavers.com>
This commit is contained in:
parent
c7e976f819
commit
c22bd318b3
2 changed files with 251 additions and 3 deletions
|
@ -394,6 +394,9 @@ DEFINE_EXPECT(uia_event_callback);
|
||||||
DEFINE_EXPECT(uia_com_event_callback);
|
DEFINE_EXPECT(uia_com_event_callback);
|
||||||
DEFINE_EXPECT(winproc_GETOBJECT_UiaRoot);
|
DEFINE_EXPECT(winproc_GETOBJECT_UiaRoot);
|
||||||
DEFINE_EXPECT(child_winproc_GETOBJECT_UiaRoot);
|
DEFINE_EXPECT(child_winproc_GETOBJECT_UiaRoot);
|
||||||
|
DEFINE_EXPECT(ProxyEventSink_AddAutomationPropertyChangedEvent);
|
||||||
|
DEFINE_EXPECT(ProxyEventSink_AddAutomationEvent);
|
||||||
|
DEFINE_EXPECT(ProxyEventSink_AddStructureChangedEvent);
|
||||||
|
|
||||||
static BOOL check_variant_i4(VARIANT *v, int val)
|
static BOOL check_variant_i4(VARIANT *v, int val)
|
||||||
{
|
{
|
||||||
|
@ -3389,6 +3392,207 @@ static void test_uia_reserved_value_ifaces(void)
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static struct ProxyEventSink
|
||||||
|
{
|
||||||
|
IProxyProviderWinEventSink IProxyProviderWinEventSink_iface;
|
||||||
|
LONG ref;
|
||||||
|
|
||||||
|
IRawElementProviderSimple *event_elprov;
|
||||||
|
int event_id;
|
||||||
|
|
||||||
|
IRawElementProviderSimple *prop_change_elprov;
|
||||||
|
int prop_change_prop_id;
|
||||||
|
VARIANT prop_change_value;
|
||||||
|
|
||||||
|
IRawElementProviderSimple *structure_change_elprov;
|
||||||
|
int structure_change_type;
|
||||||
|
SAFEARRAY *structure_change_rt_id;
|
||||||
|
} ProxyEventSink;
|
||||||
|
|
||||||
|
static void proxy_event_sink_clear(void)
|
||||||
|
{
|
||||||
|
if (ProxyEventSink.event_elprov)
|
||||||
|
IRawElementProviderSimple_Release(ProxyEventSink.event_elprov);
|
||||||
|
ProxyEventSink.event_elprov = NULL;
|
||||||
|
ProxyEventSink.event_id = 0;
|
||||||
|
|
||||||
|
if (ProxyEventSink.prop_change_elprov)
|
||||||
|
IRawElementProviderSimple_Release(ProxyEventSink.prop_change_elprov);
|
||||||
|
ProxyEventSink.prop_change_elprov = NULL;
|
||||||
|
ProxyEventSink.prop_change_prop_id = 0;
|
||||||
|
VariantClear(&ProxyEventSink.prop_change_value);
|
||||||
|
|
||||||
|
if (ProxyEventSink.structure_change_elprov)
|
||||||
|
IRawElementProviderSimple_Release(ProxyEventSink.structure_change_elprov);
|
||||||
|
ProxyEventSink.structure_change_elprov = NULL;
|
||||||
|
ProxyEventSink.structure_change_type = 0;
|
||||||
|
SafeArrayDestroy(ProxyEventSink.structure_change_rt_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct ProxyEventSink *impl_from_ProxyEventSink(IProxyProviderWinEventSink *iface)
|
||||||
|
{
|
||||||
|
return CONTAINING_RECORD(iface, struct ProxyEventSink, IProxyProviderWinEventSink_iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT WINAPI ProxyEventSink_QueryInterface(IProxyProviderWinEventSink *iface, REFIID riid, void **obj)
|
||||||
|
{
|
||||||
|
*obj = NULL;
|
||||||
|
if (IsEqualIID(riid, &IID_IProxyProviderWinEventSink) || IsEqualIID(riid, &IID_IUnknown))
|
||||||
|
*obj = iface;
|
||||||
|
else
|
||||||
|
return E_NOINTERFACE;
|
||||||
|
|
||||||
|
IProxyProviderWinEventSink_AddRef(iface);
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ULONG WINAPI ProxyEventSink_AddRef(IProxyProviderWinEventSink *iface)
|
||||||
|
{
|
||||||
|
struct ProxyEventSink *This = impl_from_ProxyEventSink(iface);
|
||||||
|
return InterlockedIncrement(&This->ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ULONG WINAPI ProxyEventSink_Release(IProxyProviderWinEventSink *iface)
|
||||||
|
{
|
||||||
|
struct ProxyEventSink *This = impl_from_ProxyEventSink(iface);
|
||||||
|
return InterlockedDecrement(&This->ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT WINAPI ProxyEventSink_AddAutomationPropertyChangedEvent(IProxyProviderWinEventSink *iface,
|
||||||
|
IRawElementProviderSimple *elprov, PROPERTYID prop_id, VARIANT new_value)
|
||||||
|
{
|
||||||
|
struct ProxyEventSink *This = impl_from_ProxyEventSink(iface);
|
||||||
|
|
||||||
|
CHECK_EXPECT(ProxyEventSink_AddAutomationPropertyChangedEvent);
|
||||||
|
This->prop_change_elprov = elprov;
|
||||||
|
if (elprov)
|
||||||
|
IRawElementProviderSimple_AddRef(elprov);
|
||||||
|
|
||||||
|
This->prop_change_prop_id = prop_id;
|
||||||
|
VariantCopy(&This->prop_change_value, &new_value);
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT WINAPI ProxyEventSink_AddAutomationEvent(IProxyProviderWinEventSink *iface,
|
||||||
|
IRawElementProviderSimple *elprov, EVENTID event_id)
|
||||||
|
{
|
||||||
|
struct ProxyEventSink *This = impl_from_ProxyEventSink(iface);
|
||||||
|
|
||||||
|
CHECK_EXPECT(ProxyEventSink_AddAutomationEvent);
|
||||||
|
This->event_elprov = elprov;
|
||||||
|
if (elprov)
|
||||||
|
IRawElementProviderSimple_AddRef(elprov);
|
||||||
|
This->event_id = event_id;
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT WINAPI ProxyEventSink_AddStructureChangedEvent(IProxyProviderWinEventSink *iface,
|
||||||
|
IRawElementProviderSimple *elprov, enum StructureChangeType structure_change_type, SAFEARRAY *runtime_id)
|
||||||
|
{
|
||||||
|
struct ProxyEventSink *This = impl_from_ProxyEventSink(iface);
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
CHECK_EXPECT(ProxyEventSink_AddStructureChangedEvent);
|
||||||
|
This->structure_change_elprov = elprov;
|
||||||
|
if (elprov)
|
||||||
|
IRawElementProviderSimple_AddRef(elprov);
|
||||||
|
This->structure_change_type = structure_change_type;
|
||||||
|
|
||||||
|
hr = SafeArrayCopy(runtime_id, &This->structure_change_rt_id);
|
||||||
|
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const IProxyProviderWinEventSinkVtbl ProxyEventSinkVtbl = {
|
||||||
|
ProxyEventSink_QueryInterface,
|
||||||
|
ProxyEventSink_AddRef,
|
||||||
|
ProxyEventSink_Release,
|
||||||
|
ProxyEventSink_AddAutomationPropertyChangedEvent,
|
||||||
|
ProxyEventSink_AddAutomationEvent,
|
||||||
|
ProxyEventSink_AddStructureChangedEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct ProxyEventSink ProxyEventSink =
|
||||||
|
{
|
||||||
|
{ &ProxyEventSinkVtbl },
|
||||||
|
1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void set_accessible_props(struct Accessible *acc, INT role, INT state,
|
||||||
|
LONG child_count, LPCWSTR name, LONG left, LONG top, LONG width, LONG height);
|
||||||
|
static void set_accessible_ia2_props(struct Accessible *acc, BOOL enable_ia2, LONG unique_id);
|
||||||
|
static void test_uia_prov_from_acc_winevent_handler(HWND hwnd)
|
||||||
|
{
|
||||||
|
IProxyProviderWinEventHandler *handler;
|
||||||
|
IRawElementProviderSimple *elprov;
|
||||||
|
HRESULT hr;
|
||||||
|
|
||||||
|
set_accessible_props(&Accessible, ROLE_SYSTEM_DOCUMENT, 0, 0, L"acc_name", 0, 0, 0, 0);
|
||||||
|
Accessible.ow_hwnd = hwnd;
|
||||||
|
hr = pUiaProviderFromIAccessible(&Accessible.IAccessible_iface, CHILDID_SELF, UIA_PFIA_DEFAULT, &elprov);
|
||||||
|
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
|
||||||
|
|
||||||
|
hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IProxyProviderWinEventHandler, (void **)&handler);
|
||||||
|
ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
|
||||||
|
ok(!!handler, "Handler == NULL\n");
|
||||||
|
|
||||||
|
/* EVENT_SYSTEM_ALERT */
|
||||||
|
SET_EXPECT(ProxyEventSink_AddAutomationEvent);
|
||||||
|
hr = IProxyProviderWinEventHandler_RespondToWinEvent(handler, EVENT_SYSTEM_ALERT, hwnd, OBJID_CLIENT, CHILDID_SELF,
|
||||||
|
&ProxyEventSink.IProxyProviderWinEventSink_iface);
|
||||||
|
todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
|
||||||
|
todo_wine CHECK_CALLED(ProxyEventSink_AddAutomationEvent);
|
||||||
|
todo_wine ok(ProxyEventSink.event_id == UIA_SystemAlertEventId, "Unexpected event_id %d\n", ProxyEventSink.event_id);
|
||||||
|
todo_wine ok(ProxyEventSink.event_elprov == elprov, "Unexpected event_elprov %p\n", ProxyEventSink.event_elprov);
|
||||||
|
proxy_event_sink_clear();
|
||||||
|
|
||||||
|
/* EVENT_OBJECT_FOCUS is not handled. */
|
||||||
|
hr = IProxyProviderWinEventHandler_RespondToWinEvent(handler, EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, CHILDID_SELF,
|
||||||
|
&ProxyEventSink.IProxyProviderWinEventSink_iface);
|
||||||
|
todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
|
||||||
|
|
||||||
|
/* EVENT_OBJECT_NAMECHANGE. */
|
||||||
|
SET_EXPECT(ProxyEventSink_AddAutomationPropertyChangedEvent);
|
||||||
|
SET_ACC_METHOD_EXPECT(&Accessible, get_accName);
|
||||||
|
hr = IProxyProviderWinEventHandler_RespondToWinEvent(handler, EVENT_OBJECT_NAMECHANGE, hwnd, OBJID_CLIENT, CHILDID_SELF,
|
||||||
|
&ProxyEventSink.IProxyProviderWinEventSink_iface);
|
||||||
|
todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
|
||||||
|
todo_wine ok(ProxyEventSink.prop_change_elprov == elprov, "Unexpected prop_change_elprov %p\n", ProxyEventSink.prop_change_elprov);
|
||||||
|
todo_wine ok(ProxyEventSink.prop_change_prop_id == UIA_NamePropertyId, "Unexpected prop_change_prop_id %d\n",
|
||||||
|
ProxyEventSink.prop_change_prop_id);
|
||||||
|
todo_wine ok(V_VT(&ProxyEventSink.prop_change_value) == VT_BSTR, "Unexpected prop_change_value vt %d\n",
|
||||||
|
V_VT(&ProxyEventSink.prop_change_value));
|
||||||
|
if (V_VT(&ProxyEventSink.prop_change_value) == VT_BSTR)
|
||||||
|
ok(!lstrcmpW(V_BSTR(&ProxyEventSink.prop_change_value), Accessible.name), "Unexpected BSTR %s\n",
|
||||||
|
wine_dbgstr_w(V_BSTR(&ProxyEventSink.prop_change_value)));
|
||||||
|
todo_wine CHECK_CALLED(ProxyEventSink_AddAutomationPropertyChangedEvent);
|
||||||
|
todo_wine CHECK_ACC_METHOD_CALLED(&Accessible, get_accName);
|
||||||
|
proxy_event_sink_clear();
|
||||||
|
|
||||||
|
/* EVENT_OBJECT_REORDER. */
|
||||||
|
acc_client = &Accessible.IAccessible_iface;
|
||||||
|
SET_EXPECT(winproc_GETOBJECT_CLIENT);
|
||||||
|
SET_EXPECT(ProxyEventSink_AddStructureChangedEvent);
|
||||||
|
hr = IProxyProviderWinEventHandler_RespondToWinEvent(handler, EVENT_OBJECT_REORDER, hwnd, OBJID_CLIENT, CHILDID_SELF,
|
||||||
|
&ProxyEventSink.IProxyProviderWinEventSink_iface);
|
||||||
|
todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr);
|
||||||
|
todo_wine ok(ProxyEventSink.structure_change_elprov == elprov, "Unexpected structure_change_elprov %p\n",
|
||||||
|
ProxyEventSink.structure_change_elprov);
|
||||||
|
todo_wine ok(ProxyEventSink.structure_change_type == StructureChangeType_ChildrenInvalidated, "Unexpected structure_change_type %d\n",
|
||||||
|
ProxyEventSink.structure_change_type);
|
||||||
|
ok(!ProxyEventSink.structure_change_rt_id, "structure_change_rt_id != NULL\n");
|
||||||
|
todo_wine CHECK_CALLED(winproc_GETOBJECT_CLIENT);
|
||||||
|
todo_wine CHECK_CALLED(ProxyEventSink_AddStructureChangedEvent);
|
||||||
|
proxy_event_sink_clear();
|
||||||
|
|
||||||
|
acc_client = NULL;
|
||||||
|
IProxyProviderWinEventHandler_Release(handler);
|
||||||
|
IRawElementProviderSimple_Release(elprov);
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_GUID(SID_AccFromDAWrapper, 0x33f139ee, 0xe509, 0x47f7, 0xbf,0x39, 0x83,0x76,0x44,0xf7,0x45,0x76);
|
DEFINE_GUID(SID_AccFromDAWrapper, 0x33f139ee, 0xe509, 0x47f7, 0xbf,0x39, 0x83,0x76,0x44,0xf7,0x45,0x76);
|
||||||
static IAccessible *msaa_acc_da_unwrap(IAccessible *acc)
|
static IAccessible *msaa_acc_da_unwrap(IAccessible *acc)
|
||||||
{
|
{
|
||||||
|
@ -3467,9 +3671,6 @@ static void check_msaa_prov_host_elem_prov_(IUnknown *elem, BOOL exp_host_prov,
|
||||||
IRawElementProviderSimple_Release(elprov);
|
IRawElementProviderSimple_Release(elprov);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void set_accessible_props(struct Accessible *acc, INT role, INT state,
|
|
||||||
LONG child_count, LPCWSTR name, LONG left, LONG top, LONG width, LONG height);
|
|
||||||
static void set_accessible_ia2_props(struct Accessible *acc, BOOL enable_ia2, LONG unique_id);
|
|
||||||
static void test_uia_prov_from_acc_fragment_root(HWND hwnd)
|
static void test_uia_prov_from_acc_fragment_root(HWND hwnd)
|
||||||
{
|
{
|
||||||
IRawElementProviderFragmentRoot *elroot, *elroot2;
|
IRawElementProviderFragmentRoot *elroot, *elroot2;
|
||||||
|
@ -5506,6 +5707,7 @@ static void test_UiaProviderFromIAccessible(void)
|
||||||
test_uia_prov_from_acc_navigation();
|
test_uia_prov_from_acc_navigation();
|
||||||
test_uia_prov_from_acc_ia2();
|
test_uia_prov_from_acc_ia2();
|
||||||
test_uia_prov_from_acc_fragment_root(hwnd);
|
test_uia_prov_from_acc_fragment_root(hwnd);
|
||||||
|
test_uia_prov_from_acc_winevent_handler(hwnd);
|
||||||
|
|
||||||
CoUninitialize();
|
CoUninitialize();
|
||||||
DestroyWindow(hwnd);
|
DestroyWindow(hwnd);
|
||||||
|
|
|
@ -530,6 +530,7 @@ struct msaa_provider {
|
||||||
IRawElementProviderFragment IRawElementProviderFragment_iface;
|
IRawElementProviderFragment IRawElementProviderFragment_iface;
|
||||||
IRawElementProviderFragmentRoot IRawElementProviderFragmentRoot_iface;
|
IRawElementProviderFragmentRoot IRawElementProviderFragmentRoot_iface;
|
||||||
ILegacyIAccessibleProvider ILegacyIAccessibleProvider_iface;
|
ILegacyIAccessibleProvider ILegacyIAccessibleProvider_iface;
|
||||||
|
IProxyProviderWinEventHandler IProxyProviderWinEventHandler_iface;
|
||||||
LONG refcount;
|
LONG refcount;
|
||||||
|
|
||||||
IAccessible *acc;
|
IAccessible *acc;
|
||||||
|
@ -586,6 +587,8 @@ HRESULT WINAPI msaa_provider_QueryInterface(IRawElementProviderSimple *iface, RE
|
||||||
*ppv = &msaa_prov->IRawElementProviderFragmentRoot_iface;
|
*ppv = &msaa_prov->IRawElementProviderFragmentRoot_iface;
|
||||||
else if (IsEqualIID(riid, &IID_ILegacyIAccessibleProvider))
|
else if (IsEqualIID(riid, &IID_ILegacyIAccessibleProvider))
|
||||||
*ppv = &msaa_prov->ILegacyIAccessibleProvider_iface;
|
*ppv = &msaa_prov->ILegacyIAccessibleProvider_iface;
|
||||||
|
else if (IsEqualIID(riid, &IID_IProxyProviderWinEventHandler))
|
||||||
|
*ppv = &msaa_prov->IProxyProviderWinEventHandler_iface;
|
||||||
else
|
else
|
||||||
return E_NOINTERFACE;
|
return E_NOINTERFACE;
|
||||||
|
|
||||||
|
@ -1369,6 +1372,48 @@ static const ILegacyIAccessibleProviderVtbl msaa_acc_provider_vtbl = {
|
||||||
msaa_acc_provider_get_DefaultAction,
|
msaa_acc_provider_get_DefaultAction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IProxyProviderWinEventHandler interface for UiaProviderFromIAccessible
|
||||||
|
* providers.
|
||||||
|
*/
|
||||||
|
static inline struct msaa_provider *impl_from_msaa_winevent_handler(IProxyProviderWinEventHandler *iface)
|
||||||
|
{
|
||||||
|
return CONTAINING_RECORD(iface, struct msaa_provider, IProxyProviderWinEventHandler_iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT WINAPI msaa_winevent_handler_QueryInterface(IProxyProviderWinEventHandler *iface, REFIID riid,
|
||||||
|
void **ppv)
|
||||||
|
{
|
||||||
|
struct msaa_provider *msaa_prov = impl_from_msaa_winevent_handler(iface);
|
||||||
|
return IRawElementProviderSimple_QueryInterface(&msaa_prov->IRawElementProviderSimple_iface, riid, ppv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ULONG WINAPI msaa_winevent_handler_AddRef(IProxyProviderWinEventHandler *iface)
|
||||||
|
{
|
||||||
|
struct msaa_provider *msaa_prov = impl_from_msaa_winevent_handler(iface);
|
||||||
|
return IRawElementProviderSimple_AddRef(&msaa_prov->IRawElementProviderSimple_iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ULONG WINAPI msaa_winevent_handler_Release(IProxyProviderWinEventHandler *iface)
|
||||||
|
{
|
||||||
|
struct msaa_provider *msaa_prov = impl_from_msaa_winevent_handler(iface);
|
||||||
|
return IRawElementProviderSimple_Release(&msaa_prov->IRawElementProviderSimple_iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HRESULT WINAPI msaa_winevent_handler_RespondToWinEvent(IProxyProviderWinEventHandler *iface, DWORD event_id,
|
||||||
|
HWND hwnd, LONG objid, LONG cid, IProxyProviderWinEventSink *event_sink)
|
||||||
|
{
|
||||||
|
FIXME("%p, %ld, %p, %ld, %ld, %p: stub\n", iface, event_id, hwnd, objid, cid, event_sink);
|
||||||
|
return E_NOTIMPL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const IProxyProviderWinEventHandlerVtbl msaa_winevent_handler_vtbl = {
|
||||||
|
msaa_winevent_handler_QueryInterface,
|
||||||
|
msaa_winevent_handler_AddRef,
|
||||||
|
msaa_winevent_handler_Release,
|
||||||
|
msaa_winevent_handler_RespondToWinEvent,
|
||||||
|
};
|
||||||
|
|
||||||
HRESULT create_msaa_provider(IAccessible *acc, LONG child_id, HWND hwnd, BOOL known_root_acc,
|
HRESULT create_msaa_provider(IAccessible *acc, LONG child_id, HWND hwnd, BOOL known_root_acc,
|
||||||
IRawElementProviderSimple **elprov)
|
IRawElementProviderSimple **elprov)
|
||||||
{
|
{
|
||||||
|
@ -1381,6 +1426,7 @@ HRESULT create_msaa_provider(IAccessible *acc, LONG child_id, HWND hwnd, BOOL kn
|
||||||
msaa_prov->IRawElementProviderFragment_iface.lpVtbl = &msaa_fragment_vtbl;
|
msaa_prov->IRawElementProviderFragment_iface.lpVtbl = &msaa_fragment_vtbl;
|
||||||
msaa_prov->IRawElementProviderFragmentRoot_iface.lpVtbl = &msaa_fragment_root_vtbl;
|
msaa_prov->IRawElementProviderFragmentRoot_iface.lpVtbl = &msaa_fragment_root_vtbl;
|
||||||
msaa_prov->ILegacyIAccessibleProvider_iface.lpVtbl = &msaa_acc_provider_vtbl;
|
msaa_prov->ILegacyIAccessibleProvider_iface.lpVtbl = &msaa_acc_provider_vtbl;
|
||||||
|
msaa_prov->IProxyProviderWinEventHandler_iface.lpVtbl = &msaa_winevent_handler_vtbl;
|
||||||
msaa_prov->refcount = 1;
|
msaa_prov->refcount = 1;
|
||||||
variant_init_i4(&msaa_prov->cid, child_id);
|
variant_init_i4(&msaa_prov->cid, child_id);
|
||||||
msaa_prov->acc = acc;
|
msaa_prov->acc = acc;
|
||||||
|
|
Loading…
Add table
Reference in a new issue