bump product version to 7.6.3.2-android
[LibreOffice.git] / vcl / unx / gtk3 / a11y / atkutil.cxx
blobfc15351374fdd5eb95c6c5382eb67cfd4ab7e0a4
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
21 #include <com/sun/star/accessibility/XAccessibleSelection.hpp>
22 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
23 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
24 #include <com/sun/star/accessibility/XAccessibleText.hpp>
25 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
26 #include <cppuhelper/implbase.hxx>
27 #include <cppuhelper/weakref.hxx>
28 #include <sal/log.hxx>
30 #include <vcl/svapp.hxx>
31 #include <vcl/window.hxx>
32 #include <vcl/menu.hxx>
33 #include <vcl/toolbox.hxx>
35 #include <unx/gtk/gtkdata.hxx>
36 #include "atkwrapper.hxx"
37 #include "atkutil.hxx"
39 #include <cassert>
40 #include <set>
42 using namespace ::com::sun::star;
44 namespace
46 uno::WeakReference< accessibility::XAccessible > theNextFocusObject;
49 static guint focus_notify_handler = 0;
51 /*****************************************************************************/
53 extern "C" {
55 static gboolean
56 atk_wrapper_focus_idle_handler (gpointer data)
58 SolarMutexGuard aGuard;
60 focus_notify_handler = 0;
62 uno::Reference< accessibility::XAccessible > xAccessible = theNextFocusObject;
63 if( xAccessible.get() == static_cast < accessibility::XAccessible * > (data) )
65 AtkObject *atk_obj = xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
66 // Gail does not notify focus changes to NULL, so do we ..
67 if( atk_obj )
69 SAL_WNODEPRECATED_DECLARATIONS_PUSH
70 atk_focus_tracker_notify(atk_obj);
71 SAL_WNODEPRECATED_DECLARATIONS_POP
72 // #i93269#
73 // emit text_caret_moved event for <XAccessibleText> object,
74 // if cursor is inside the <XAccessibleText> object.
75 // also emit state-changed:focused event under the same condition.
77 AtkObjectWrapper* wrapper_obj = ATK_OBJECT_WRAPPER (atk_obj);
78 if( wrapper_obj && !wrapper_obj->mpText.is() )
80 wrapper_obj->mpText.set(wrapper_obj->mpContext, css::uno::UNO_QUERY);
81 if ( wrapper_obj->mpText.is() )
83 gint caretPos = -1;
85 try {
86 caretPos = wrapper_obj->mpText->getCaretPosition();
88 catch(const uno::Exception&) {
89 g_warning( "Exception in getCaretPosition()" );
92 if ( caretPos != -1 )
94 atk_object_notify_state_change( atk_obj, ATK_STATE_FOCUSED, true );
95 g_signal_emit_by_name( atk_obj, "text_caret_moved", caretPos );
100 g_object_unref(atk_obj);
104 return false;
107 } // extern "C"
109 /*****************************************************************************/
111 static void
112 atk_wrapper_focus_tracker_notify_when_idle( const uno::Reference< accessibility::XAccessible > &xAccessible )
114 if( focus_notify_handler )
115 g_source_remove(focus_notify_handler);
117 theNextFocusObject = xAccessible;
119 focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
122 /*****************************************************************************/
124 void DocumentFocusListener::disposing( const lang::EventObject& aEvent )
127 // Unref the object here, but do not remove as listener since the object
128 // might no longer be in a state that safely allows this.
129 if( aEvent.Source.is() )
130 m_aRefList.erase(aEvent.Source);
134 /*****************************************************************************/
136 void DocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
138 try {
139 switch( aEvent.EventId )
141 case accessibility::AccessibleEventId::STATE_CHANGED:
143 sal_Int64 nState = accessibility::AccessibleStateType::INVALID;
144 aEvent.NewValue >>= nState;
146 if( accessibility::AccessibleStateType::FOCUSED == nState )
147 atk_wrapper_focus_tracker_notify_when_idle( getAccessible(aEvent) );
149 break;
152 case accessibility::AccessibleEventId::CHILD:
154 uno::Reference< accessibility::XAccessible > xChild;
155 if( (aEvent.OldValue >>= xChild) && xChild.is() )
156 detachRecursive(xChild);
158 if( (aEvent.NewValue >>= xChild) && xChild.is() )
159 attachRecursive(xChild);
161 break;
164 case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
166 if (uno::Reference< accessibility::XAccessible > xAcc = getAccessible(aEvent))
167 detachRecursive(xAcc);
168 break;
171 default:
172 break;
175 catch( const lang::IndexOutOfBoundsException& )
177 g_warning("DocumentFocusListener: Focused object has invalid index in parent");
181 /*****************************************************************************/
183 uno::Reference< accessibility::XAccessible > DocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
185 uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);
187 if( xAccessible.is() )
188 return xAccessible;
190 uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);
192 if( xContext.is() )
194 uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
195 if( xParent.is() )
197 uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
198 if( xParentContext.is() )
200 return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
205 return uno::Reference< accessibility::XAccessible >();
208 /*****************************************************************************/
210 void DocumentFocusListener::attachRecursive(
211 const uno::Reference< accessibility::XAccessible >& xAccessible
214 uno::Reference< accessibility::XAccessibleContext > xContext =
215 xAccessible->getAccessibleContext();
217 if( xContext.is() )
218 attachRecursive(xAccessible, xContext);
221 /*****************************************************************************/
223 void DocumentFocusListener::attachRecursive(
224 const uno::Reference< accessibility::XAccessible >& xAccessible,
225 const uno::Reference< accessibility::XAccessibleContext >& xContext
228 sal_Int64 nStateSet = xContext->getAccessibleStateSet();
229 attachRecursive(xAccessible, xContext, nStateSet);
232 /*****************************************************************************/
234 void DocumentFocusListener::attachRecursive(
235 const uno::Reference< accessibility::XAccessible >& xAccessible,
236 const uno::Reference< accessibility::XAccessibleContext >& xContext,
237 sal_Int64 nStateSet
240 if( nStateSet & accessibility::AccessibleStateType::FOCUSED )
241 atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
243 uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
245 if (!xBroadcaster.is())
246 return;
248 // If not already done, add the broadcaster to the list and attach as listener.
249 const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
250 if( !m_aRefList.insert(xInterface).second )
251 return;
253 xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
255 if( ! (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
257 sal_Int64 n, nmax = xContext->getAccessibleChildCount();
258 for( n = 0; n < nmax; n++ )
260 uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
262 if( xChild.is() )
263 attachRecursive(xChild);
268 /*****************************************************************************/
270 void DocumentFocusListener::detachRecursive(
271 const uno::Reference< accessibility::XAccessible >& xAccessible
274 uno::Reference< accessibility::XAccessibleContext > xContext =
275 xAccessible->getAccessibleContext();
277 if( xContext.is() )
278 detachRecursive(xContext);
281 /*****************************************************************************/
283 void DocumentFocusListener::detachRecursive(
284 const uno::Reference< accessibility::XAccessibleContext >& xContext
287 sal_Int64 nStateSet = xContext->getAccessibleStateSet();
289 detachRecursive(xContext, nStateSet);
292 /*****************************************************************************/
294 void DocumentFocusListener::detachRecursive(
295 const uno::Reference< accessibility::XAccessibleContext >& xContext,
296 sal_Int64 nStateSet
299 uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
301 if( !xBroadcaster.is() || 0 >= m_aRefList.erase(xBroadcaster) )
302 return;
304 xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
306 if( ! (nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
308 sal_Int64 n, nmax = xContext->getAccessibleChildCount();
309 for( n = 0; n < nmax; n++ )
311 uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
313 if( xChild.is() )
314 detachRecursive(xChild);
319 /*****************************************************************************/
322 * page tabs in gtk are widgets, so we need to simulate focus events for those
325 static void handle_tabpage_activated(vcl::Window *pWindow)
327 uno::Reference< accessibility::XAccessible > xAccessible =
328 pWindow->GetAccessible();
330 if( ! xAccessible.is() )
331 return;
333 uno::Reference< accessibility::XAccessibleSelection > xSelection(
334 xAccessible->getAccessibleContext(), uno::UNO_QUERY);
336 if( xSelection.is() )
337 atk_wrapper_focus_tracker_notify_when_idle( xSelection->getSelectedAccessibleChild(0) );
340 /*****************************************************************************/
343 * toolbar items in gtk are widgets, so we need to simulate focus events for those
346 static void notify_toolbox_item_focus(ToolBox *pToolBox)
348 uno::Reference< accessibility::XAccessible > xAccessible =
349 pToolBox->GetAccessible();
351 if( ! xAccessible.is() )
352 return;
354 uno::Reference< accessibility::XAccessibleContext > xContext =
355 xAccessible->getAccessibleContext();
357 if( ! xContext.is() )
358 return;
360 ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
361 if( nPos != ToolBox::ITEM_NOTFOUND )
362 atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
365 static void handle_toolbox_highlight(vcl::Window *pWindow)
367 ToolBox *pToolBox = static_cast <ToolBox *> (pWindow);
369 // Make sure either the toolbox or its parent toolbox has the focus
370 if ( ! pToolBox->HasFocus() )
372 ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pToolBox->GetParent() );
373 if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
374 return;
377 notify_toolbox_item_focus(pToolBox);
380 static void handle_toolbox_highlightoff(vcl::Window const *pWindow)
382 ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pWindow->GetParent() );
384 // Notify when leaving sub toolboxes
385 if( pToolBoxParent && pToolBoxParent->HasFocus() )
386 notify_toolbox_item_focus( pToolBoxParent );
389 /*****************************************************************************/
391 static void create_wrapper_for_child(
392 const uno::Reference< accessibility::XAccessibleContext >& xContext,
393 sal_Int32 index)
395 if( xContext.is() )
397 uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(index));
398 if( xChild.is() )
400 // create the wrapper object - it will survive the unref unless it is a transient object
401 g_object_unref( atk_object_wrapper_ref( xChild ) );
406 /*****************************************************************************/
408 static void handle_toolbox_buttonchange(VclWindowEvent const *pEvent)
410 vcl::Window* pWindow = pEvent->GetWindow();
411 sal_Int32 index = static_cast<sal_Int32>(reinterpret_cast<sal_IntPtr>(pEvent->GetData()));
413 if( pWindow && pWindow->IsReallyVisible() )
415 uno::Reference< accessibility::XAccessible > xAccessible(pWindow->GetAccessible());
416 if( xAccessible.is() )
418 create_wrapper_for_child(xAccessible->getAccessibleContext(), index);
423 /*****************************************************************************/
425 namespace {
427 struct WindowList {
428 ~WindowList() { assert(list.empty()); };
429 // needs to be empty already on DeInitVCL, but at least check it's empty
430 // on exit
432 std::set< VclPtr<vcl::Window> > list;
435 WindowList g_aWindowList;
439 rtl::Reference<DocumentFocusListener> GtkSalData::GetDocumentFocusListener()
441 rtl::Reference<DocumentFocusListener> xDFL = m_xDocumentFocusListener.get();
442 if (!xDFL)
444 xDFL = new DocumentFocusListener;
445 m_xDocumentFocusListener = xDFL.get();
447 return xDFL;
450 static void handle_get_focus(::VclWindowEvent const * pEvent)
452 GtkSalData *const pSalData(GetGtkSalData());
453 assert(pSalData);
455 rtl::Reference<DocumentFocusListener> xDocumentFocusListener(pSalData->GetDocumentFocusListener());
457 vcl::Window *pWindow = pEvent->GetWindow();
459 // The menu bar is handled through VclEventId::MenuHighlightED
460 if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
461 return;
463 // ToolBoxes are handled through VclEventId::ToolboxHighlight
464 if( pWindow->GetType() == WindowType::TOOLBOX )
465 return;
467 if( pWindow->GetType() == WindowType::TABCONTROL )
469 handle_tabpage_activated( pWindow );
470 return;
473 uno::Reference< accessibility::XAccessible > xAccessible =
474 pWindow->GetAccessible();
476 if( ! xAccessible.is() )
477 return;
479 uno::Reference< accessibility::XAccessibleContext > xContext =
480 xAccessible->getAccessibleContext();
482 if( ! xContext.is() )
483 return;
485 sal_Int64 nStateSet = xContext->getAccessibleStateSet();
487 /* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
488 * need to add listeners to the children instead of re-using the tabpage stuff
490 if( (nStateSet & accessibility::AccessibleStateType::FOCUSED) &&
491 ( pWindow->GetType() != WindowType::TREELISTBOX ) )
493 atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
495 else
497 if( g_aWindowList.list.insert(pWindow).second )
501 xDocumentFocusListener->attachRecursive(xAccessible, xContext, nStateSet);
503 catch (const uno::Exception&)
505 g_warning( "Exception caught processing focus events" );
511 /*****************************************************************************/
513 static void handle_menu_highlighted(::VclMenuEvent const * pEvent)
517 Menu* pMenu = pEvent->GetMenu();
518 sal_uInt16 nPos = pEvent->GetItemPos();
520 if( pMenu && nPos != 0xFFFF)
522 uno::Reference< accessibility::XAccessible > xAccessible ( pMenu->GetAccessible() );
524 if( xAccessible.is() )
526 uno::Reference< accessibility::XAccessibleContext > xContext ( xAccessible->getAccessibleContext() );
528 if( xContext.is() )
529 atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
533 catch (const uno::Exception&)
535 g_warning( "Exception caught processing menu highlight events" );
539 /*****************************************************************************/
541 static void WindowEventHandler(void *, VclSimpleEvent& rEvent)
545 switch (rEvent.GetId())
547 case VclEventId::WindowShow:
548 break;
549 case VclEventId::WindowHide:
550 break;
551 case VclEventId::WindowClose:
552 break;
553 case VclEventId::WindowGetFocus:
554 handle_get_focus(static_cast< ::VclWindowEvent const * >(&rEvent));
555 break;
556 case VclEventId::WindowLoseFocus:
557 break;
558 case VclEventId::WindowMinimize:
559 break;
560 case VclEventId::WindowNormalize:
561 break;
562 case VclEventId::WindowKeyInput:
563 case VclEventId::WindowKeyUp:
564 case VclEventId::WindowCommand:
565 case VclEventId::WindowMouseMove:
566 break;
568 case VclEventId::MenuHighlight:
569 if (const VclMenuEvent* pMenuEvent = dynamic_cast<const VclMenuEvent*>(&rEvent))
571 handle_menu_highlighted(pMenuEvent);
573 break;
575 case VclEventId::ToolboxHighlight:
576 handle_toolbox_highlight(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
577 break;
579 case VclEventId::ToolboxButtonStateChanged:
580 handle_toolbox_buttonchange(static_cast< ::VclWindowEvent const * >(&rEvent));
581 break;
583 case VclEventId::ObjectDying:
584 g_aWindowList.list.erase( static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow() );
585 [[fallthrough]];
586 case VclEventId::ToolboxHighlightOff:
587 handle_toolbox_highlightoff(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
588 break;
590 case VclEventId::TabpageActivate:
591 handle_tabpage_activated(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
592 break;
594 case VclEventId::ComboboxSetText:
595 // This looks quite strange to me. Stumbled over this when fixing #i104290#.
596 // This kicked in when leaving the combobox in the toolbar, after that the events worked.
597 // I guess this was a try to work around missing combobox events, which didn't do the full job, and shouldn't be necessary anymore.
598 // Fix for #i104290# was done in toolkit/source/awt/vclxaccessiblecomponent, FOCUSED state for compound controls in general.
599 // create_wrapper_for_children(static_cast< ::VclWindowEvent const * >(pEvent)->GetWindow());
600 break;
602 default:
603 break;
606 catch (const lang::IndexOutOfBoundsException&)
608 g_warning("WindowEventHandler: Focused object has invalid index in parent");
612 static Link<VclSimpleEvent&,void> g_aEventListenerLink( nullptr, WindowEventHandler );
614 /*****************************************************************************/
616 void ooo_atk_util_ensure_event_listener()
618 static bool bInited;
619 if (!bInited)
621 Application::AddEventListener( g_aEventListenerLink );
622 bInited = true;
626 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */