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