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 <vcl/svapp.hxx>
21 #include <vcl/window.hxx>
22 #include <vcl/toolbox.hxx>
23 #include <vcl/menu.hxx>
24 
25 #include <osx/a11yfocustracker.hxx>
26 
27 #include "documentfocuslistener.hxx"
28 
29 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
30 #include <com/sun/star/accessibility/XAccessibleSelection.hpp>
31 #include <com/sun/star/accessibility/XAccessibleStateSet.hpp>
32 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
33 #include <com/sun/star/accessibility/AccessibleRole.hpp>
34 
35 using namespace ::com::sun::star::accessibility;
36 using namespace ::com::sun::star::uno;
37 
38 static vcl::Window *
getWindow(const::VclSimpleEvent * pEvent)39 getWindow(const ::VclSimpleEvent *pEvent)
40 {
41     return static_cast< const ::VclWindowEvent *> (pEvent)->GetWindow();
42 }
43 
44 // callback function for Application::addEventListener
45 
WindowEventHandler(void * pThis,VclSimpleEvent & rEvent)46 void AquaA11yFocusTracker::WindowEventHandler(void * pThis, VclSimpleEvent& rEvent)
47 {
48     AquaA11yFocusTracker *pFocusTracker = static_cast<AquaA11yFocusTracker *>(
49         pThis);
50     switch (rEvent.GetId())
51     {
52     case VclEventId::WindowPaint:
53         pFocusTracker-> toolbox_open_floater( getWindow(&rEvent) );
54         break;
55     case VclEventId::WindowGetFocus:
56         pFocusTracker->window_got_focus( getWindow(&rEvent) );
57         break;
58     case VclEventId::ObjectDying:
59         pFocusTracker->m_aDocumentWindowList.erase( getWindow(&rEvent) );
60         [[fallthrough]];
61     case VclEventId::ToolboxHighlightOff:
62         pFocusTracker->toolbox_highlight_off( getWindow(&rEvent) );
63         break;
64     case VclEventId::ToolboxHighlight:
65         pFocusTracker->toolbox_highlight_on( getWindow(&rEvent) );
66         break;
67     case VclEventId::TabpageActivate:
68         pFocusTracker->tabpage_activated( getWindow(&rEvent) );
69         break;
70     case VclEventId::MenuHighlight:
71         // Inspired by code in WindowEventHandler in
72         // vcl/unx/gtk/a11y/atkutil.cxx, find out what kind of event
73         // it is to avoid blindly using a static_cast and crash,
74         // fdo#47275.
75         if( const VclMenuEvent* pMenuEvent = dynamic_cast < const VclMenuEvent* > (&rEvent) )
76         {
77             pFocusTracker->menu_highlighted( pMenuEvent );
78         }
79         break;
80     default:
81         break;
82     }
83 }
84 
AquaA11yFocusTracker()85 AquaA11yFocusTracker::AquaA11yFocusTracker() :
86     m_aWindowEventLink(this, WindowEventHandler),
87     m_xDocumentFocusListener(new DocumentFocusListener(*this))
88 {
89     Application::AddEventListener(m_aWindowEventLink);
90     window_got_focus(Application::GetFocusWindow());
91 }
92 
~AquaA11yFocusTracker()93 AquaA11yFocusTracker::~AquaA11yFocusTracker() {}
94 
setFocusedObject(const Reference<XAccessible> & xAccessible)95 void AquaA11yFocusTracker::setFocusedObject(const Reference< XAccessible >& xAccessible)
96 {
97     if( xAccessible != m_xFocusedObject )
98     {
99         m_xFocusedObject = xAccessible;
100 
101         if( m_aFocusListener.is() )
102             m_aFocusListener->focusedObjectChanged(xAccessible);
103     }
104 }
105 
notify_toolbox_item_focus(ToolBox * pToolBox)106 void AquaA11yFocusTracker::notify_toolbox_item_focus(ToolBox *pToolBox)
107 {
108     Reference< XAccessible > xAccessible( pToolBox->GetAccessible() );
109 
110     if( xAccessible.is() )
111     {
112         Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext());
113 
114         if( xContext.is() )
115         {
116             ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
117             if( nPos != ToolBox::ITEM_NOTFOUND )
118                 setFocusedObject( xContext->getAccessibleChild( nPos ) );
119                     //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32!
120         }
121     }
122 }
123 
toolbox_open_floater(vcl::Window * pWindow)124 void AquaA11yFocusTracker::toolbox_open_floater(vcl::Window *pWindow)
125 {
126     bool bToolboxFound = false;
127     bool bFloatingWindowFound = false;
128     vcl::Window * pFloatingWindow = nullptr;
129     while ( pWindow != nullptr ) {
130         if ( pWindow->GetType() == WindowType::TOOLBOX ) {
131             bToolboxFound = true;
132         } else if ( pWindow->GetType() == WindowType::FLOATINGWINDOW ) {
133             bFloatingWindowFound = true;
134             pFloatingWindow = pWindow;
135         }
136         pWindow = pWindow->GetParent();
137     }
138     if ( bToolboxFound && bFloatingWindowFound ) {
139         Reference < XAccessible > rxAccessible = pFloatingWindow -> GetAccessible();
140         if ( ! rxAccessible.is() ) {
141             return;
142         }
143         Reference < XAccessibleContext > rxContext = rxAccessible -> getAccessibleContext();
144         if ( ! rxContext.is() ) {
145             return;
146         }
147         if ( rxContext -> getAccessibleChildCount() > 0 ) {
148             Reference < XAccessible > rxAccessibleChild = rxContext -> getAccessibleChild( 0 );
149             if ( ! rxAccessibleChild.is() ) {
150                 return;
151             }
152             setFocusedObject ( rxAccessibleChild );
153         }
154     }
155 }
156 
toolbox_highlight_on(vcl::Window * pWindow)157 void AquaA11yFocusTracker::toolbox_highlight_on(vcl::Window *pWindow)
158 {
159     // Make sure either the toolbox or its parent toolbox has the focus
160     if ( ! pWindow->HasFocus() )
161     {
162         ToolBox* pToolBoxParent = dynamic_cast< ToolBox * >( pWindow->GetParent() );
163         if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
164             return;
165     }
166 
167     notify_toolbox_item_focus(static_cast <ToolBox *> (pWindow));
168 }
169 
toolbox_highlight_off(vcl::Window const * pWindow)170 void AquaA11yFocusTracker::toolbox_highlight_off(vcl::Window const *pWindow)
171 {
172     ToolBox* pToolBoxParent = dynamic_cast< ToolBox * >( pWindow->GetParent() );
173 
174     // Notify when leaving sub toolboxes
175     if( pToolBoxParent && pToolBoxParent->HasFocus() )
176         notify_toolbox_item_focus( pToolBoxParent );
177 }
178 
tabpage_activated(vcl::Window * pWindow)179 void AquaA11yFocusTracker::tabpage_activated(vcl::Window *pWindow)
180 {
181     Reference< XAccessible > xAccessible( pWindow->GetAccessible() );
182 
183     if( xAccessible.is() )
184     {
185         Reference< XAccessibleSelection > xSelection(xAccessible->getAccessibleContext(), UNO_QUERY);
186 
187         if( xSelection.is() )
188             setFocusedObject( xSelection->getSelectedAccessibleChild(0) );
189     }
190 }
191 
menu_highlighted(const VclMenuEvent * pEvent)192 void AquaA11yFocusTracker::menu_highlighted(const VclMenuEvent *pEvent)
193 {
194     Menu * pMenu = pEvent->GetMenu();
195 
196     if( pMenu )
197     {
198         Reference< XAccessible > xAccessible( pMenu->GetAccessible() );
199 
200         if( xAccessible.is() )
201             setFocusedObject( xAccessible );
202     }
203 }
204 
window_got_focus(vcl::Window * pWindow)205 void AquaA11yFocusTracker::window_got_focus(vcl::Window *pWindow)
206 {
207     // The menu bar is handled through VclEventId::MenuHighlightED
208     if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
209         return;
210 
211     // ToolBoxes are handled through VclEventId::ToolboxHighlight
212     if( pWindow->GetType() == WindowType::TOOLBOX )
213         return;
214 
215     if( pWindow->GetType() == WindowType::TABCONTROL )
216     {
217         tabpage_activated( pWindow );
218         return;
219     }
220 
221     Reference< XAccessible > xAccessible(pWindow->GetAccessible());
222 
223     if( ! xAccessible.is() )
224         return;
225 
226     Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext();
227 
228     if( ! xContext.is() )
229         return;
230 
231     Reference< XAccessibleStateSet > xStateSet = xContext->getAccessibleStateSet();
232 
233     if( ! xStateSet.is() )
234         return;
235 
236 /* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
237  * need to add listeners to the children instead of re-using the tabpage stuff
238  */
239     if( xStateSet->contains(AccessibleStateType::FOCUSED) && (pWindow->GetType() != WindowType::TREELISTBOX) )
240     {
241         setFocusedObject( xAccessible );
242     }
243     else
244     {
245         if( m_aDocumentWindowList.insert(pWindow).second )
246             m_xDocumentFocusListener->attachRecursive(xAccessible, xContext, xStateSet);
247     }
248 }
249 
250 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
251