1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
21 #define _LINUX_SOURCE_COMPAT
22 #include <sys/timer.h>
23 #undef _LINUX_SOURCE_COMPAT
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"
49 using namespace ::com::sun::star
;
53 struct theNextFocusObject
:
54 public rtl::Static
< uno::WeakReference
< accessibility::XAccessible
>, theNextFocusObject
>
59 static guint focus_notify_handler
= 0;
61 /*****************************************************************************/
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 ..
79 SAL_WNODEPRECATED_DECLARATIONS_PUSH
80 atk_focus_tracker_notify(atk_obj
);
81 SAL_WNODEPRECATED_DECLARATIONS_POP
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() )
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
);
119 /*****************************************************************************/
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
;
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
);
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
)
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
) );
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
);
234 case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN
:
235 SAL_INFO("vcl.a11y", "Invalidate all children called");
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() )
257 uno::Reference
< accessibility::XAccessibleContext
> xContext(aEvent
.Source
, uno::UNO_QUERY
);
261 uno::Reference
< accessibility::XAccessible
> xParent( xContext
->getAccessibleParent() );
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();
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();
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())
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
)
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
) );
333 attachRecursive(xChild
);
338 /*****************************************************************************/
340 void DocumentFocusListener::detachRecursive(
341 const uno::Reference
< accessibility::XAccessible
>& xAccessible
344 uno::Reference
< accessibility::XAccessibleContext
> xContext
=
345 xAccessible
->getAccessibleContext();
348 detachRecursive(xContext
);
351 /*****************************************************************************/
353 void DocumentFocusListener::detachRecursive(
354 const uno::Reference
< accessibility::XAccessibleContext
>& xContext
357 uno::Reference
< accessibility::XAccessibleStateSet
> xStateSet
=
358 xContext
->getAccessibleStateSet();
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
) )
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
) );
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() )
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() )
426 uno::Reference
< accessibility::XAccessibleContext
> xContext
=
427 xAccessible
->getAccessibleContext();
429 if( ! xContext
.is() )
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() )
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
,
470 uno::Reference
< accessibility::XAccessible
> xChild(xContext
->getAccessibleChild(index
));
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 /*****************************************************************************/
501 ~WindowList() { assert(list
.empty()); };
502 // needs to be empty already on DeInitVCL, but at least check it's empty
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());
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
)
535 // ToolBoxes are handled through VclEventId::ToolboxHighlight
536 if( pWindow
->GetType() == WindowType::TOOLBOX
)
539 if( pWindow
->GetType() == WindowType::TABCONTROL
)
541 handle_tabpage_activated( pWindow
);
545 uno::Reference
< accessibility::XAccessible
> xAccessible
=
546 pWindow
->GetAccessible();
548 if( ! xAccessible
.is() )
551 uno::Reference
< accessibility::XAccessibleContext
> xContext
=
552 xAccessible
->getAccessibleContext();
554 if( ! xContext
.is() )
557 uno::Reference
< accessibility::XAccessibleStateSet
> xStateSet
=
558 xContext
->getAccessibleStateSet();
560 if( ! xStateSet
.is() )
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
);
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() );
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
:
625 case VclEventId::WindowHide
:
627 case VclEventId::WindowClose
:
629 case VclEventId::WindowGetFocus
:
630 handle_get_focus(static_cast< ::VclWindowEvent
const * >(&rEvent
));
632 case VclEventId::WindowLoseFocus
:
634 case VclEventId::WindowMinimize
:
636 case VclEventId::WindowNormalize
:
638 case VclEventId::WindowKeyInput
:
639 case VclEventId::WindowKeyUp
:
640 case VclEventId::WindowCommand
:
641 case VclEventId::WindowMouseMove
:
644 case VclEventId::MenuHighlight
:
645 if (const VclMenuEvent
* pMenuEvent
= dynamic_cast<const VclMenuEvent
*>(&rEvent
))
647 handle_menu_highlighted(pMenuEvent
);
651 case VclEventId::ToolboxHighlight
:
652 handle_toolbox_highlight(static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow());
655 case VclEventId::ToolboxButtonStateChanged
:
656 handle_toolbox_buttonchange(static_cast< ::VclWindowEvent
const * >(&rEvent
));
659 case VclEventId::ObjectDying
:
660 g_aWindowList
.list
.erase( static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow() );
662 case VclEventId::ToolboxHighlightOff
:
663 handle_toolbox_highlightoff(static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow());
666 case VclEventId::TabpageActivate
:
667 handle_tabpage_activated(static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow());
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());
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()
697 Application::AddEventListener( g_aEventListenerLink
);
702 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */