nss: upgrade to release 3.73
[LibreOffice.git] / vcl / unx / gtk3 / a11y / gtk3atkutil.cxx
blobf253eef8254f0ae0f5b9286fc1ee8eca35dc1e89
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 #ifdef AIX
21 #define _LINUX_SOURCE_COMPAT
22 #include <sys/timer.h>
23 #undef _LINUX_SOURCE_COMPAT
24 #endif
26 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
27 #include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
28 #include <com/sun/star/accessibility/XAccessibleSelection.hpp>
29 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
30 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
31 #include <com/sun/star/accessibility/XAccessibleText.hpp>
32 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
33 #include <cppuhelper/implbase.hxx>
34 #include <cppuhelper/weakref.hxx>
35 #include <sal/log.hxx>
37 #include <vcl/svapp.hxx>
38 #include <vcl/window.hxx>
39 #include <vcl/menu.hxx>
40 #include <vcl/toolbox.hxx>
42 #include <unx/gtk/gtkdata.hxx>
43 #include "atkwrapper.hxx"
44 #include "atkutil.hxx"
46 #include <cassert>
47 #include <set>
49 using namespace ::com::sun::star;
51 namespace
53 struct theNextFocusObject :
54 public rtl::Static< uno::WeakReference< accessibility::XAccessible >, theNextFocusObject>
59 static guint focus_notify_handler = 0;
61 /*****************************************************************************/
63 extern "C" {
65 static gboolean
66 atk_wrapper_focus_idle_handler (gpointer data)
68 SolarMutexGuard aGuard;
70 focus_notify_handler = 0;
72 uno::Reference< accessibility::XAccessible > xAccessible = theNextFocusObject::get();
73 if( xAccessible.get() == static_cast < accessibility::XAccessible * > (data) )
75 AtkObject *atk_obj = xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : nullptr;
76 // Gail does not notify focus changes to NULL, so do we ..
77 if( atk_obj )
79 SAL_WNODEPRECATED_DECLARATIONS_PUSH
80 atk_focus_tracker_notify(atk_obj);
81 SAL_WNODEPRECATED_DECLARATIONS_POP
82 // #i93269#
83 // emit text_caret_moved event for <XAccessibleText> object,
84 // if cursor is inside the <XAccessibleText> object.
85 // also emit state-changed:focused event under the same condition.
87 AtkObjectWrapper* wrapper_obj = ATK_OBJECT_WRAPPER (atk_obj);
88 if( wrapper_obj && !wrapper_obj->mpText.is() )
90 wrapper_obj->mpText.set(wrapper_obj->mpContext, css::uno::UNO_QUERY);
91 if ( wrapper_obj->mpText.is() )
93 gint caretPos = -1;
95 try {
96 caretPos = wrapper_obj->mpText->getCaretPosition();
98 catch(const uno::Exception&) {
99 g_warning( "Exception in getCaretPosition()" );
102 if ( caretPos != -1 )
104 atk_object_notify_state_change( atk_obj, ATK_STATE_FOCUSED, true );
105 g_signal_emit_by_name( atk_obj, "text_caret_moved", caretPos );
110 g_object_unref(atk_obj);
114 return false;
117 } // extern "C"
119 /*****************************************************************************/
121 static void
122 atk_wrapper_focus_tracker_notify_when_idle( const uno::Reference< accessibility::XAccessible > &xAccessible )
124 if( focus_notify_handler )
125 g_source_remove(focus_notify_handler);
127 theNextFocusObject::get() = xAccessible;
129 focus_notify_handler = g_idle_add (atk_wrapper_focus_idle_handler, xAccessible.get());
132 /*****************************************************************************/
134 class DocumentFocusListener :
135 public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener >
138 o3tl::sorted_vector< uno::Reference< uno::XInterface > > m_aRefList;
140 public:
141 /// @throws lang::IndexOutOfBoundsException
142 /// @throws uno::RuntimeException
143 void attachRecursive(
144 const uno::Reference< accessibility::XAccessible >& xAccessible
147 /// @throws lang::IndexOutOfBoundsException
148 /// @throws uno::RuntimeException
149 void attachRecursive(
150 const uno::Reference< accessibility::XAccessible >& xAccessible,
151 const uno::Reference< accessibility::XAccessibleContext >& xContext
154 /// @throws lang::IndexOutOfBoundsException
155 /// @throws uno::RuntimeException
156 void attachRecursive(
157 const uno::Reference< accessibility::XAccessible >& xAccessible,
158 const uno::Reference< accessibility::XAccessibleContext >& xContext,
159 const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
162 /// @throws lang::IndexOutOfBoundsException
163 /// @throws uno::RuntimeException
164 void detachRecursive(
165 const uno::Reference< accessibility::XAccessible >& xAccessible
168 /// @throws lang::IndexOutOfBoundsException
169 /// @throws uno::RuntimeException
170 void detachRecursive(
171 const uno::Reference< accessibility::XAccessibleContext >& xContext
174 /// @throws lang::IndexOutOfBoundsException
175 /// @throws uno::RuntimeException
176 void detachRecursive(
177 const uno::Reference< accessibility::XAccessibleContext >& xContext,
178 const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
181 /// @throws lang::IndexOutOfBoundsException
182 /// @throws uno::RuntimeException
183 static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent );
185 // XEventListener
186 virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;
188 // XAccessibleEventListener
189 virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override;
192 /*****************************************************************************/
194 void DocumentFocusListener::disposing( const lang::EventObject& aEvent )
197 // Unref the object here, but do not remove as listener since the object
198 // might no longer be in a state that safely allows this.
199 if( aEvent.Source.is() )
200 m_aRefList.erase(aEvent.Source);
204 /*****************************************************************************/
206 void DocumentFocusListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent )
208 try {
209 switch( aEvent.EventId )
211 case accessibility::AccessibleEventId::STATE_CHANGED:
213 sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
214 aEvent.NewValue >>= nState;
216 if( accessibility::AccessibleStateType::FOCUSED == nState )
217 atk_wrapper_focus_tracker_notify_when_idle( getAccessible(aEvent) );
219 break;
222 case accessibility::AccessibleEventId::CHILD:
224 uno::Reference< accessibility::XAccessible > xChild;
225 if( (aEvent.OldValue >>= xChild) && xChild.is() )
226 detachRecursive(xChild);
228 if( (aEvent.NewValue >>= xChild) && xChild.is() )
229 attachRecursive(xChild);
231 break;
234 case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
235 SAL_INFO("vcl.a11y", "Invalidate all children called");
236 break;
238 default:
239 break;
242 catch( const lang::IndexOutOfBoundsException& )
244 g_warning("Focused object has invalid index in parent");
248 /*****************************************************************************/
250 uno::Reference< accessibility::XAccessible > DocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
252 uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);
254 if( xAccessible.is() )
255 return xAccessible;
257 uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);
259 if( xContext.is() )
261 uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
262 if( xParent.is() )
264 uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
265 if( xParentContext.is() )
267 return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
272 return uno::Reference< accessibility::XAccessible >();
275 /*****************************************************************************/
277 void DocumentFocusListener::attachRecursive(
278 const uno::Reference< accessibility::XAccessible >& xAccessible
281 uno::Reference< accessibility::XAccessibleContext > xContext =
282 xAccessible->getAccessibleContext();
284 if( xContext.is() )
285 attachRecursive(xAccessible, xContext);
288 /*****************************************************************************/
290 void DocumentFocusListener::attachRecursive(
291 const uno::Reference< accessibility::XAccessible >& xAccessible,
292 const uno::Reference< accessibility::XAccessibleContext >& xContext
295 uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
296 xContext->getAccessibleStateSet();
298 if( xStateSet.is() )
299 attachRecursive(xAccessible, xContext, xStateSet);
302 /*****************************************************************************/
304 void DocumentFocusListener::attachRecursive(
305 const uno::Reference< accessibility::XAccessible >& xAccessible,
306 const uno::Reference< accessibility::XAccessibleContext >& xContext,
307 const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
310 if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED ) )
311 atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
313 uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
315 if (!xBroadcaster.is())
316 return;
318 // If not already done, add the broadcaster to the list and attach as listener.
319 const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
320 if( !m_aRefList.insert(xInterface).second )
321 return;
323 xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
325 if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
327 sal_Int32 n, nmax = xContext->getAccessibleChildCount();
328 for( n = 0; n < nmax; n++ )
330 uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
332 if( xChild.is() )
333 attachRecursive(xChild);
338 /*****************************************************************************/
340 void DocumentFocusListener::detachRecursive(
341 const uno::Reference< accessibility::XAccessible >& xAccessible
344 uno::Reference< accessibility::XAccessibleContext > xContext =
345 xAccessible->getAccessibleContext();
347 if( xContext.is() )
348 detachRecursive(xContext);
351 /*****************************************************************************/
353 void DocumentFocusListener::detachRecursive(
354 const uno::Reference< accessibility::XAccessibleContext >& xContext
357 uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
358 xContext->getAccessibleStateSet();
360 if( xStateSet.is() )
361 detachRecursive(xContext, xStateSet);
364 /*****************************************************************************/
366 void DocumentFocusListener::detachRecursive(
367 const uno::Reference< accessibility::XAccessibleContext >& xContext,
368 const uno::Reference< accessibility::XAccessibleStateSet >& xStateSet
371 uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
373 if( !xBroadcaster.is() || 0 >= m_aRefList.erase(xBroadcaster) )
374 return;
376 xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
378 if( ! xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS ) )
380 sal_Int32 n, nmax = xContext->getAccessibleChildCount();
381 for( n = 0; n < nmax; n++ )
383 uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
385 if( xChild.is() )
386 detachRecursive(xChild);
391 /*****************************************************************************/
394 * page tabs in gtk are widgets, so we need to simulate focus events for those
397 static void handle_tabpage_activated(vcl::Window *pWindow)
399 uno::Reference< accessibility::XAccessible > xAccessible =
400 pWindow->GetAccessible();
402 if( ! xAccessible.is() )
403 return;
405 uno::Reference< accessibility::XAccessibleSelection > xSelection(
406 xAccessible->getAccessibleContext(), uno::UNO_QUERY);
408 if( xSelection.is() )
409 atk_wrapper_focus_tracker_notify_when_idle( xSelection->getSelectedAccessibleChild(0) );
412 /*****************************************************************************/
415 * toolbar items in gtk are widgets, so we need to simulate focus events for those
418 static void notify_toolbox_item_focus(ToolBox *pToolBox)
420 uno::Reference< accessibility::XAccessible > xAccessible =
421 pToolBox->GetAccessible();
423 if( ! xAccessible.is() )
424 return;
426 uno::Reference< accessibility::XAccessibleContext > xContext =
427 xAccessible->getAccessibleContext();
429 if( ! xContext.is() )
430 return;
432 ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
433 if( nPos != ToolBox::ITEM_NOTFOUND )
434 atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
435 //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32
438 static void handle_toolbox_highlight(vcl::Window *pWindow)
440 ToolBox *pToolBox = static_cast <ToolBox *> (pWindow);
442 // Make sure either the toolbox or its parent toolbox has the focus
443 if ( ! pToolBox->HasFocus() )
445 ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pToolBox->GetParent() );
446 if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
447 return;
450 notify_toolbox_item_focus(pToolBox);
453 static void handle_toolbox_highlightoff(vcl::Window const *pWindow)
455 ToolBox* pToolBoxParent = dynamic_cast< ToolBox* >( pWindow->GetParent() );
457 // Notify when leaving sub toolboxes
458 if( pToolBoxParent && pToolBoxParent->HasFocus() )
459 notify_toolbox_item_focus( pToolBoxParent );
462 /*****************************************************************************/
464 static void create_wrapper_for_child(
465 const uno::Reference< accessibility::XAccessibleContext >& xContext,
466 sal_Int32 index)
468 if( xContext.is() )
470 uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(index));
471 if( xChild.is() )
473 // create the wrapper object - it will survive the unref unless it is a transient object
474 g_object_unref( atk_object_wrapper_ref( xChild ) );
479 /*****************************************************************************/
481 static void handle_toolbox_buttonchange(VclWindowEvent const *pEvent)
483 vcl::Window* pWindow = pEvent->GetWindow();
484 sal_Int32 index = static_cast<sal_Int32>(reinterpret_cast<sal_IntPtr>(pEvent->GetData()));
486 if( pWindow && pWindow->IsReallyVisible() )
488 uno::Reference< accessibility::XAccessible > xAccessible(pWindow->GetAccessible());
489 if( xAccessible.is() )
491 create_wrapper_for_child(xAccessible->getAccessibleContext(), index);
496 /*****************************************************************************/
498 namespace {
500 struct WindowList {
501 ~WindowList() { assert(list.empty()); };
502 // needs to be empty already on DeInitVCL, but at least check it's empty
503 // on exit
505 std::set< VclPtr<vcl::Window> > list;
508 WindowList g_aWindowList;
512 DocumentFocusListener & GtkSalData::GetDocumentFocusListener()
514 if (!m_pDocumentFocusListener)
516 m_pDocumentFocusListener = new DocumentFocusListener;
517 m_xDocumentFocusListener.set(m_pDocumentFocusListener);
519 return *m_pDocumentFocusListener;
522 static void handle_get_focus(::VclWindowEvent const * pEvent)
524 GtkSalData *const pSalData(GetGtkSalData());
525 assert(pSalData);
527 DocumentFocusListener & rDocumentFocusListener(pSalData->GetDocumentFocusListener());
529 vcl::Window *pWindow = pEvent->GetWindow();
531 // The menu bar is handled through VclEventId::MenuHighlightED
532 if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
533 return;
535 // ToolBoxes are handled through VclEventId::ToolboxHighlight
536 if( pWindow->GetType() == WindowType::TOOLBOX )
537 return;
539 if( pWindow->GetType() == WindowType::TABCONTROL )
541 handle_tabpage_activated( pWindow );
542 return;
545 uno::Reference< accessibility::XAccessible > xAccessible =
546 pWindow->GetAccessible();
548 if( ! xAccessible.is() )
549 return;
551 uno::Reference< accessibility::XAccessibleContext > xContext =
552 xAccessible->getAccessibleContext();
554 if( ! xContext.is() )
555 return;
557 uno::Reference< accessibility::XAccessibleStateSet > xStateSet =
558 xContext->getAccessibleStateSet();
560 if( ! xStateSet.is() )
561 return;
563 /* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
564 * need to add listeners to the children instead of re-using the tabpage stuff
566 if( xStateSet->contains(accessibility::AccessibleStateType::FOCUSED) &&
567 ( pWindow->GetType() != WindowType::TREELISTBOX ) )
569 atk_wrapper_focus_tracker_notify_when_idle( xAccessible );
571 else
573 if( g_aWindowList.list.insert(pWindow).second )
577 rDocumentFocusListener.attachRecursive(xAccessible, xContext, xStateSet);
579 catch (const uno::Exception&)
581 g_warning( "Exception caught processing focus events" );
587 /*****************************************************************************/
589 static void handle_menu_highlighted(::VclMenuEvent const * pEvent)
593 Menu* pMenu = pEvent->GetMenu();
594 sal_uInt16 nPos = pEvent->GetItemPos();
596 if( pMenu && nPos != 0xFFFF)
598 uno::Reference< accessibility::XAccessible > xAccessible ( pMenu->GetAccessible() );
600 if( xAccessible.is() )
602 uno::Reference< accessibility::XAccessibleContext > xContext ( xAccessible->getAccessibleContext() );
604 if( xContext.is() )
605 atk_wrapper_focus_tracker_notify_when_idle( xContext->getAccessibleChild( nPos ) );
609 catch (const uno::Exception&)
611 g_warning( "Exception caught processing menu highlight events" );
615 /*****************************************************************************/
617 static void WindowEventHandler(void *, VclSimpleEvent& rEvent)
621 switch (rEvent.GetId())
623 case VclEventId::WindowShow:
624 break;
625 case VclEventId::WindowHide:
626 break;
627 case VclEventId::WindowClose:
628 break;
629 case VclEventId::WindowGetFocus:
630 handle_get_focus(static_cast< ::VclWindowEvent const * >(&rEvent));
631 break;
632 case VclEventId::WindowLoseFocus:
633 break;
634 case VclEventId::WindowMinimize:
635 break;
636 case VclEventId::WindowNormalize:
637 break;
638 case VclEventId::WindowKeyInput:
639 case VclEventId::WindowKeyUp:
640 case VclEventId::WindowCommand:
641 case VclEventId::WindowMouseMove:
642 break;
644 case VclEventId::MenuHighlight:
645 if (const VclMenuEvent* pMenuEvent = dynamic_cast<const VclMenuEvent*>(&rEvent))
647 handle_menu_highlighted(pMenuEvent);
649 break;
651 case VclEventId::ToolboxHighlight:
652 handle_toolbox_highlight(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
653 break;
655 case VclEventId::ToolboxButtonStateChanged:
656 handle_toolbox_buttonchange(static_cast< ::VclWindowEvent const * >(&rEvent));
657 break;
659 case VclEventId::ObjectDying:
660 g_aWindowList.list.erase( static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow() );
661 [[fallthrough]];
662 case VclEventId::ToolboxHighlightOff:
663 handle_toolbox_highlightoff(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
664 break;
666 case VclEventId::TabpageActivate:
667 handle_tabpage_activated(static_cast< ::VclWindowEvent const * >(&rEvent)->GetWindow());
668 break;
670 case VclEventId::ComboboxSetText:
671 // This looks quite strange to me. Stumbled over this when fixing #i104290#.
672 // This kicked in when leaving the combobox in the toolbar, after that the events worked.
673 // 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.
674 // Fix for #i104290# was done in toolkit/source/awt/vclxaccessiblecomponent, FOCUSED state for compound controls in general.
675 // create_wrapper_for_children(static_cast< ::VclWindowEvent const * >(pEvent)->GetWindow());
676 break;
678 default:
679 break;
682 catch (const lang::IndexOutOfBoundsException&)
684 g_warning("Focused object has invalid index in parent");
688 static Link<VclSimpleEvent&,void> g_aEventListenerLink( nullptr, WindowEventHandler );
690 /*****************************************************************************/
692 void ooo_atk_util_ensure_event_listener()
694 static bool bInited;
695 if (!bInited)
697 Application::AddEventListener( g_aEventListenerLink );
698 bInited = true;
702 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */