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 .
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"
42 using namespace ::com::sun::star
;
46 uno::WeakReference
< accessibility::XAccessible
> theNextFocusObject
;
49 static guint focus_notify_handler
= 0;
51 /*****************************************************************************/
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 ..
69 SAL_WNODEPRECATED_DECLARATIONS_PUSH
70 atk_focus_tracker_notify(atk_obj
);
71 SAL_WNODEPRECATED_DECLARATIONS_POP
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() )
86 caretPos
= wrapper_obj
->mpText
->getCaretPosition();
88 catch(const uno::Exception
&) {
89 g_warning( "Exception in getCaretPosition()" );
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
);
109 /*****************************************************************************/
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
)
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
) );
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
);
164 case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN
:
166 if (uno::Reference
< accessibility::XAccessible
> xAcc
= getAccessible(aEvent
))
167 detachRecursive(xAcc
);
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() )
190 uno::Reference
< accessibility::XAccessibleContext
> xContext(aEvent
.Source
, uno::UNO_QUERY
);
194 uno::Reference
< accessibility::XAccessible
> xParent( xContext
->getAccessibleParent() );
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();
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
,
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())
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
)
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
) );
263 attachRecursive(xChild
);
268 /*****************************************************************************/
270 void DocumentFocusListener::detachRecursive(
271 const uno::Reference
< accessibility::XAccessible
>& xAccessible
274 uno::Reference
< accessibility::XAccessibleContext
> xContext
=
275 xAccessible
->getAccessibleContext();
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
,
299 uno::Reference
< accessibility::XAccessibleEventBroadcaster
> xBroadcaster(xContext
, uno::UNO_QUERY
);
301 if( !xBroadcaster
.is() || 0 >= m_aRefList
.erase(xBroadcaster
) )
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
) );
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() )
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() )
354 uno::Reference
< accessibility::XAccessibleContext
> xContext
=
355 xAccessible
->getAccessibleContext();
357 if( ! xContext
.is() )
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() )
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
,
397 uno::Reference
< accessibility::XAccessible
> xChild(xContext
->getAccessibleChild(index
));
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 /*****************************************************************************/
428 ~WindowList() { assert(list
.empty()); };
429 // needs to be empty already on DeInitVCL, but at least check it's empty
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();
444 xDFL
= new DocumentFocusListener
;
445 m_xDocumentFocusListener
= xDFL
.get();
450 static void handle_get_focus(::VclWindowEvent
const * pEvent
)
452 GtkSalData
*const pSalData(GetGtkSalData());
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
)
463 // ToolBoxes are handled through VclEventId::ToolboxHighlight
464 if( pWindow
->GetType() == WindowType::TOOLBOX
)
467 if( pWindow
->GetType() == WindowType::TABCONTROL
)
469 handle_tabpage_activated( pWindow
);
473 uno::Reference
< accessibility::XAccessible
> xAccessible
=
474 pWindow
->GetAccessible();
476 if( ! xAccessible
.is() )
479 uno::Reference
< accessibility::XAccessibleContext
> xContext
=
480 xAccessible
->getAccessibleContext();
482 if( ! xContext
.is() )
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
);
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() );
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
:
549 case VclEventId::WindowHide
:
551 case VclEventId::WindowClose
:
553 case VclEventId::WindowGetFocus
:
554 handle_get_focus(static_cast< ::VclWindowEvent
const * >(&rEvent
));
556 case VclEventId::WindowLoseFocus
:
558 case VclEventId::WindowMinimize
:
560 case VclEventId::WindowNormalize
:
562 case VclEventId::WindowKeyInput
:
563 case VclEventId::WindowKeyUp
:
564 case VclEventId::WindowCommand
:
565 case VclEventId::WindowMouseMove
:
568 case VclEventId::MenuHighlight
:
569 if (const VclMenuEvent
* pMenuEvent
= dynamic_cast<const VclMenuEvent
*>(&rEvent
))
571 handle_menu_highlighted(pMenuEvent
);
575 case VclEventId::ToolboxHighlight
:
576 handle_toolbox_highlight(static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow());
579 case VclEventId::ToolboxButtonStateChanged
:
580 handle_toolbox_buttonchange(static_cast< ::VclWindowEvent
const * >(&rEvent
));
583 case VclEventId::ObjectDying
:
584 g_aWindowList
.list
.erase( static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow() );
586 case VclEventId::ToolboxHighlightOff
:
587 handle_toolbox_highlightoff(static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow());
590 case VclEventId::TabpageActivate
:
591 handle_tabpage_activated(static_cast< ::VclWindowEvent
const * >(&rEvent
)->GetWindow());
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());
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()
621 Application::AddEventListener( g_aEventListenerLink
);
626 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */