1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20 #include <AccessibleSmElementsControl.hxx>
21 #include <AccessibleSmElement.hxx>
22 #include <ElementsDockingWindow.hxx>
23 #include <smmod.hxx>
24
25 #include <comphelper/accessiblewrapper.hxx>
26 #include <comphelper/processfactory.hxx>
27 #include <comphelper/types.hxx>
28 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
29 #include <com/sun/star/accessibility/AccessibleRole.hpp>
30 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
31 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
32 #include <cppuhelper/supportsservice.hxx>
33 #include <cppuhelper/typeprovider.hxx>
34 #include <toolkit/helper/convert.hxx>
35 #include <unotools/accessiblerelationsethelper.hxx>
36 #include <unotools/accessiblestatesethelper.hxx>
37 #include <vcl/svapp.hxx>
38 #include <vcl/vclevent.hxx>
39
40 using namespace ::com::sun::star;
41 using namespace ::com::sun::star::accessibility;
42 using OContextEntryGuard = ::comphelper::OContextEntryGuard;
43 using OExternalLockGuard = ::comphelper::OExternalLockGuard;
44
45 // AccessibleSmElementsControl
46
AccessibleSmElementsControl(SmElementsControl & rControl)47 AccessibleSmElementsControl::AccessibleSmElementsControl(SmElementsControl& rControl)
48 : m_pControl(&rControl)
49 {
50 }
51
~AccessibleSmElementsControl()52 AccessibleSmElementsControl::~AccessibleSmElementsControl() {}
53
UpdateFocus(sal_uInt16 nPos)54 void AccessibleSmElementsControl::UpdateFocus(sal_uInt16 nPos)
55 {
56 const bool bSetFocus = (nPos == SAL_MAX_UINT16);
57
58 // submit events only if the widget has the focus to avoid sending events due to mouse move
59 if (!m_pControl || (bSetFocus && !m_pControl->HasFocus()))
60 return;
61
62 if (bSetFocus)
63 nPos = m_pControl->itemHighlighted() - m_pControl->itemOffset();
64
65 if (nPos < m_aAccessibleChildren.size())
66 {
67 const auto& rxChild = m_aAccessibleChildren[nPos];
68 if (rxChild.is())
69 rxChild->SetFocus(bSetFocus);
70 }
71 }
72
ReleaseAllItems()73 void AccessibleSmElementsControl::ReleaseAllItems()
74 {
75 if (m_aAccessibleChildren.empty())
76 return;
77
78 m_aAccessibleChildren.clear();
79
80 // The original toolbox accessibility code uses individual NAME_CHANGED
81 // events in a loop. We can't do this, because on each remove event the
82 // list of known children is rebuild. But since we rely on the child
83 // count of the SmElementsControl, we'll always have no or all items.
84 // In the latter case this would automatically recreate all items!
85 assert(m_pControl && m_pControl->itemCount() == 0);
86 NotifyAccessibleEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN, uno::Any(), uno::Any());
87 }
88
AddAllItems()89 void AccessibleSmElementsControl::AddAllItems()
90 {
91 assert(m_pControl);
92 if (!m_pControl)
93 return;
94
95 uno::Any aNewName(getAccessibleName());
96 NotifyAccessibleEvent(AccessibleEventId::NAME_CHANGED, uno::Any(), aNewName);
97
98 // register the new items
99 sal_uInt16 nCount = getAccessibleChildCount();
100 for (sal_uInt16 i = 0; i < nCount; ++i)
101 {
102 uno::Any aNewValue;
103 aNewValue <<= getAccessibleChild(static_cast<sal_Int32>(i));
104 NotifyAccessibleEvent(AccessibleEventId::CHILD, uno::Any(), aNewValue);
105 }
106 }
107
IMPLEMENT_FORWARD_XINTERFACE2(AccessibleSmElementsControl,comphelper::OAccessibleComponentHelper,AccessibleSmElementsControl_BASE)108 IMPLEMENT_FORWARD_XINTERFACE2(AccessibleSmElementsControl, comphelper::OAccessibleComponentHelper,
109 AccessibleSmElementsControl_BASE)
110
111 IMPLEMENT_FORWARD_XTYPEPROVIDER2(AccessibleSmElementsControl,
112 comphelper::OAccessibleComponentHelper,
113 AccessibleSmElementsControl_BASE)
114
115 // XAccessible
116 uno::Reference<XAccessibleContext> AccessibleSmElementsControl::getAccessibleContext()
117 {
118 return this;
119 }
120
121 // XComponent
disposing()122 void AccessibleSmElementsControl::disposing()
123 {
124 comphelper::OAccessibleComponentHelper::disposing();
125 m_aAccessibleChildren.clear();
126 }
127
grabFocus()128 void AccessibleSmElementsControl::grabFocus()
129 {
130 SolarMutexGuard aGuard;
131 if (!m_pControl)
132 throw uno::RuntimeException();
133
134 m_pControl->GrabFocus();
135 }
136
getForeground()137 sal_Int32 AccessibleSmElementsControl::getForeground()
138 {
139 SolarMutexGuard aGuard;
140
141 if (!m_pControl)
142 throw uno::RuntimeException();
143 return static_cast<sal_Int32>(m_pControl->GetTextColor());
144 }
145
getBackground()146 sal_Int32 AccessibleSmElementsControl::getBackground()
147 {
148 SolarMutexGuard aGuard;
149
150 if (!m_pControl)
151 throw uno::RuntimeException();
152 Color nCol = m_pControl->GetControlBackground();
153 return static_cast<sal_Int32>(nCol);
154 }
155
156 // XServiceInfo
getImplementationName()157 OUString AccessibleSmElementsControl::getImplementationName()
158 {
159 return "com.sun.star.comp.toolkit.AccessibleSmElementsControl";
160 }
161
supportsService(const OUString & rServiceName)162 sal_Bool AccessibleSmElementsControl::supportsService(const OUString& rServiceName)
163 {
164 return cppu::supportsService(this, rServiceName);
165 }
166
getSupportedServiceNames()167 uno::Sequence<OUString> AccessibleSmElementsControl::getSupportedServiceNames()
168 {
169 return { "com.sun.star.accessibility.AccessibleContext",
170 "com.sun.star.accessibility.AccessibleComponent",
171 "com.sun.star.accessibility.AccessibleSelection",
172 "com.sun.star.accessibility.AccessibleSmElementsControl" };
173 }
174
175 // XAccessibleContext
getAccessibleChildCount()176 sal_Int32 AccessibleSmElementsControl::getAccessibleChildCount()
177 {
178 comphelper::OExternalLockGuard aGuard(this);
179 sal_Int32 nCount = 0;
180 if (m_pControl)
181 {
182 nCount = m_pControl->itemCount();
183 if (m_aAccessibleChildren.size() != sal_uInt16(nCount))
184 m_aAccessibleChildren.resize(nCount);
185 if (m_pControl->scrollbarAccessible().is())
186 nCount++;
187 }
188 return nCount;
189 }
190
getAccessibleChild(sal_Int32 i)191 uno::Reference<XAccessible> AccessibleSmElementsControl::getAccessibleChild(sal_Int32 i)
192 {
193 comphelper::OExternalLockGuard aGuard(this);
194
195 if (i < 0 || i >= getAccessibleChildCount())
196 throw lang::IndexOutOfBoundsException();
197
198 // first child may be the scrollbar
199 sal_uInt16 c(i);
200 uno::Reference<XAccessible> xScrollbar = m_pControl->scrollbarAccessible();
201 if (xScrollbar.is())
202 {
203 if (c == 0)
204 return xScrollbar;
205 c--;
206 }
207
208 assert(c < m_aAccessibleChildren.size());
209 rtl::Reference<AccessibleSmElement> xChild = m_aAccessibleChildren[c];
210 const sal_uInt16 nItemId = m_pControl->itemOffset() + c;
211 if (xChild.is() && xChild->itemId() != nItemId)
212 xChild.clear();
213 if (!xChild.is())
214 {
215 sal_uInt16 nHighlightItemId = m_pControl->itemHighlighted();
216 AccessibleSmElement* pChild = new AccessibleSmElement(m_pControl, nItemId, i);
217 if (pChild->itemId() == nHighlightItemId)
218 pChild->SetFocus(true);
219 m_aAccessibleChildren[c] = pChild;
220 xChild = pChild;
221 }
222 return xChild.get();
223 }
224
getAccessibleParent()225 uno::Reference<XAccessible> AccessibleSmElementsControl::getAccessibleParent()
226 {
227 SolarMutexGuard aGuard;
228 if (!m_pControl)
229 throw uno::RuntimeException();
230
231 vcl::Window* pAccParent = m_pControl->GetAccessibleParentWindow();
232 assert(pAccParent);
233 return pAccParent ? pAccParent->GetAccessible() : uno::Reference<XAccessible>();
234 }
235
236 uno::Reference<XAccessible>
getAccessibleAtPoint(const awt::Point & rPoint)237 AccessibleSmElementsControl::getAccessibleAtPoint(const awt::Point& rPoint)
238 {
239 comphelper::OExternalLockGuard aGuard(this);
240
241 uno::Reference<XAccessible> xAccessible;
242 if (m_pControl)
243 {
244 sal_uInt16 nPos = m_pControl->itemAtPos(VCLPoint(rPoint));
245 nPos -= m_pControl->itemOffset();
246 if (nPos <= m_aAccessibleChildren.size())
247 xAccessible = getAccessibleChild(nPos);
248 }
249 return xAccessible;
250 }
251
getAccessibleRole()252 sal_Int16 AccessibleSmElementsControl::getAccessibleRole() { return AccessibleRole::SCROLL_PANE; }
253
getAccessibleDescription()254 OUString AccessibleSmElementsControl::getAccessibleDescription() { return OUString(); }
255
getAccessibleName()256 OUString AccessibleSmElementsControl::getAccessibleName()
257 {
258 SolarMutexGuard aGuard;
259 OUString aName;
260 if (m_pControl)
261 aName = SmResId(m_pControl->elementSetId().getStr());
262 return aName;
263 }
264
265 // XAccessibleSelection
selectAccessibleChild(sal_Int32 nChildIndex)266 void AccessibleSmElementsControl::selectAccessibleChild(sal_Int32 nChildIndex)
267 {
268 OExternalLockGuard aGuard(this);
269
270 if ((!m_pControl) || nChildIndex < 0
271 || static_cast<size_t>(nChildIndex) >= m_aAccessibleChildren.size())
272 throw lang::IndexOutOfBoundsException();
273
274 m_pControl->setItemHighlighted(nChildIndex);
275 }
276
isAccessibleChildSelected(sal_Int32 nChildIndex)277 sal_Bool AccessibleSmElementsControl::isAccessibleChildSelected(sal_Int32 nChildIndex)
278 {
279 OExternalLockGuard aGuard(this);
280 if ((!m_pControl) || nChildIndex < 0
281 || static_cast<size_t>(nChildIndex) >= m_aAccessibleChildren.size())
282 throw lang::IndexOutOfBoundsException();
283
284 return (m_pControl->itemHighlighted() == nChildIndex);
285 }
286
clearAccessibleSelection()287 void AccessibleSmElementsControl::clearAccessibleSelection()
288 {
289 OExternalLockGuard aGuard(this);
290 if (m_pControl)
291 m_pControl->setItemHighlighted(SAL_MAX_UINT16);
292 }
293
selectAllAccessibleChildren()294 void AccessibleSmElementsControl::selectAllAccessibleChildren()
295 {
296 // intentionally empty
297 }
298
getSelectedAccessibleChildCount()299 sal_Int32 AccessibleSmElementsControl::getSelectedAccessibleChildCount()
300 {
301 OExternalLockGuard aGuard(this);
302
303 sal_Int32 nRet = 0;
304 if (m_pControl
305 && (m_pControl->itemHighlighted() - m_pControl->itemOffset()) < getAccessibleChildCount())
306 nRet = 1;
307 return nRet;
308 }
309
310 uno::Reference<XAccessible>
getSelectedAccessibleChild(sal_Int32 nSelectedChildIndex)311 AccessibleSmElementsControl::getSelectedAccessibleChild(sal_Int32 nSelectedChildIndex)
312 {
313 OExternalLockGuard aGuard(this);
314 if (nSelectedChildIndex != 0 || !m_pControl)
315 throw lang::IndexOutOfBoundsException();
316 return getAccessibleChild(m_pControl->itemHighlighted() - m_pControl->itemOffset());
317 }
318
deselectAccessibleChild(sal_Int32 nChildIndex)319 void AccessibleSmElementsControl::deselectAccessibleChild(sal_Int32 nChildIndex)
320 {
321 OExternalLockGuard aGuard(this);
322 if (nChildIndex != 0 || nChildIndex >= getAccessibleChildCount())
323 throw lang::IndexOutOfBoundsException();
324 clearAccessibleSelection(); // there can be just one selected child
325 }
326
327 // XAccessibleComponent
lcl_GetLocationOnScreen(vcl::Window const * m_pControl)328 static awt::Point lcl_GetLocationOnScreen(vcl::Window const* m_pControl)
329 {
330 awt::Point aPos;
331 if (m_pControl)
332 {
333 tools::Rectangle aRect = m_pControl->GetWindowExtentsRelative(nullptr);
334 aPos.X = aRect.Left();
335 aPos.Y = aRect.Top();
336 }
337 return aPos;
338 }
339
lcl_GetBounds(vcl::Window const * m_pControl)340 static awt::Rectangle lcl_GetBounds(vcl::Window const* m_pControl)
341 {
342 // !! see VCLXAccessibleComponent::implGetBounds()
343
344 //! the coordinates returned are relative to the parent window !
345 //! Thus the top-left point may be different from (0, 0) !
346
347 awt::Rectangle aBounds;
348 if (m_pControl)
349 {
350 tools::Rectangle aRect = m_pControl->GetWindowExtentsRelative(nullptr);
351 aBounds.X = aRect.Left();
352 aBounds.Y = aRect.Top();
353 aBounds.Width = aRect.GetWidth();
354 aBounds.Height = aRect.GetHeight();
355
356 vcl::Window* pParent = m_pControl->GetAccessibleParentWindow();
357 if (pParent)
358 {
359 tools::Rectangle aParentRect = pParent->GetWindowExtentsRelative(nullptr);
360 awt::Point aParentScreenLoc(aParentRect.Left(), aParentRect.Top());
361 aBounds.X -= aParentScreenLoc.X;
362 aBounds.Y -= aParentScreenLoc.Y;
363 }
364 }
365 return aBounds;
366 }
367
TestControl()368 void AccessibleSmElementsControl::TestControl()
369 {
370 if (!m_pControl)
371 throw uno::RuntimeException();
372 assert(m_pControl->GetParent()->GetAccessible() == getAccessibleParent());
373 }
374
implGetBounds()375 awt::Rectangle AccessibleSmElementsControl::implGetBounds()
376 {
377 SolarMutexGuard aGuard;
378 TestControl();
379 return lcl_GetBounds(m_pControl);
380 }
381
getBounds()382 awt::Rectangle AccessibleSmElementsControl::getBounds() { return implGetBounds(); }
383
containsPoint(const awt::Point & aPoint)384 sal_Bool AccessibleSmElementsControl::containsPoint(const awt::Point& aPoint)
385 {
386 SolarMutexGuard aGuard;
387 TestControl();
388 Size aSz(m_pControl->GetSizePixel());
389 return aPoint.X >= 0 && aPoint.Y >= 0 && aPoint.X < aSz.Width() && aPoint.Y < aSz.Height();
390 }
391
getLocation()392 awt::Point AccessibleSmElementsControl::getLocation()
393 {
394 SolarMutexGuard aGuard;
395 TestControl();
396 awt::Rectangle aRect(lcl_GetBounds(m_pControl));
397 return awt::Point(aRect.X, aRect.Y);
398 }
399
getLocationOnScreen()400 awt::Point AccessibleSmElementsControl::getLocationOnScreen()
401 {
402 SolarMutexGuard aGuard;
403 TestControl();
404 return lcl_GetLocationOnScreen(m_pControl);
405 }
406
getSize()407 awt::Size AccessibleSmElementsControl::getSize()
408 {
409 SolarMutexGuard aGuard;
410 TestControl();
411
412 Size aSz(m_pControl->GetSizePixel());
413 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
414 awt::Rectangle aRect(lcl_GetBounds(m_pControl));
415 Size aSz2(aRect.Width, aRect.Height);
416 assert(aSz == aSz2 && "mismatch in width");
417 #endif
418 return awt::Size(aSz.Width(), aSz.Height());
419 }
420
getAccessibleRelationSet()421 uno::Reference<XAccessibleRelationSet> AccessibleSmElementsControl::getAccessibleRelationSet()
422 {
423 uno::Reference<XAccessibleRelationSet> xRelSet = new utl::AccessibleRelationSetHelper();
424 return xRelSet; // empty relation set
425 }
426
getAccessibleStateSet()427 uno::Reference<XAccessibleStateSet> AccessibleSmElementsControl::getAccessibleStateSet()
428 {
429 SolarMutexGuard aGuard;
430 ::utl::AccessibleStateSetHelper* pStateSet = new ::utl::AccessibleStateSetHelper;
431
432 uno::Reference<XAccessibleStateSet> xStateSet(pStateSet);
433
434 if (!m_pControl)
435 pStateSet->AddState(AccessibleStateType::DEFUNC);
436 else
437 {
438 pStateSet->AddState(AccessibleStateType::ENABLED);
439 pStateSet->AddState(AccessibleStateType::FOCUSABLE);
440 if (m_pControl->HasFocus())
441 pStateSet->AddState(AccessibleStateType::FOCUSED);
442 if (m_pControl->IsActive())
443 pStateSet->AddState(AccessibleStateType::ACTIVE);
444 if (m_pControl->IsVisible())
445 pStateSet->AddState(AccessibleStateType::SHOWING);
446 if (m_pControl->IsReallyVisible())
447 pStateSet->AddState(AccessibleStateType::VISIBLE);
448 if (COL_TRANSPARENT != m_pControl->GetBackground().GetColor())
449 pStateSet->AddState(AccessibleStateType::OPAQUE);
450 }
451
452 return xStateSet;
453 }
454
455 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
456