1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: atklistener.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_vcl.hxx"
34 #include <com/sun/star/accessibility/TextSegment.hpp>
35 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
36 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
37 #include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
38 #include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
39 #include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
41 #include "atklistener.hxx"
42 #include "atkwrapper.hxx"
44 #include <rtl/ref.hxx>
47 using namespace com::sun::star
;
50 #define CSTRING_FROM_ANY(i) rtl::OUStringToOString( i.get< rtl::OUString >(), RTL_TEXTENCODING_UTF8 ).getStr()
52 AtkListener::AtkListener( AtkObjectWrapper
* pWrapper
) : mpWrapper( pWrapper
)
56 g_object_ref( mpWrapper
);
57 updateChildList( mpWrapper
->mpContext
);
61 AtkListener::~AtkListener()
64 g_object_unref( mpWrapper
);
67 /*****************************************************************************/
69 AtkStateType
mapState( const uno::Any
&rAny
)
71 sal_Int16 nState
= accessibility::AccessibleStateType::INVALID
;
73 return mapAtkState( nState
);
76 /*****************************************************************************/
78 // XEventListener implementation
79 void AtkListener::disposing( const lang::EventObject
& ) throw (uno::RuntimeException
)
83 AtkObject
*atk_obj
= ATK_OBJECT( mpWrapper
);
85 // Release all interface references to avoid shutdown problems with
87 atk_object_wrapper_dispose( mpWrapper
);
89 // This is an equivalent to a state change to DEFUNC(T).
90 atk_object_notify_state_change( atk_obj
, ATK_STATE_DEFUNCT
, TRUE
);
92 if( atk_get_focus_object() == atk_obj
)
93 atk_focus_tracker_notify( NULL
);
95 // Release the wrapper object so that it can vanish ..
96 g_object_unref( mpWrapper
);
101 /*****************************************************************************/
103 static AtkObject
*getObjFromAny( const uno::Any
&rAny
)
105 uno::Reference
< accessibility::XAccessible
> xAccessible
;
106 rAny
>>= xAccessible
;
107 return xAccessible
.is() ? atk_object_wrapper_ref( xAccessible
) : NULL
;
110 /*****************************************************************************/
112 // Updates the child list held to provide the old IndexInParent on children_changed::remove
113 void AtkListener::updateChildList(accessibility::XAccessibleContext
* pContext
)
115 m_aChildList
.clear();
117 uno::Reference
< accessibility::XAccessibleStateSet
> xStateSet
= pContext
->getAccessibleStateSet();
119 && !xStateSet
->contains(accessibility::AccessibleStateType::DEFUNC
)
120 && !xStateSet
->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS
) )
122 sal_Int32 nChildren
= pContext
->getAccessibleChildCount();
123 m_aChildList
.resize(nChildren
);
124 for(sal_Int32 n
= 0; n
< nChildren
; n
++)
126 m_aChildList
[n
] = pContext
->getAccessibleChild(n
);
127 OSL_ASSERT(m_aChildList
[n
].is());
132 /*****************************************************************************/
134 void AtkListener::handleChildAdded(
135 const uno::Reference
< accessibility::XAccessibleContext
>& rxParent
,
136 const uno::Reference
< accessibility::XAccessible
>& rxAccessible
)
138 AtkObject
* pChild
= atk_object_wrapper_ref( rxAccessible
);
142 updateChildList(rxParent
.get());
144 atk_object_wrapper_add_child( mpWrapper
, pChild
,
145 atk_object_get_index_in_parent( pChild
));
147 g_object_unref( pChild
);
151 /*****************************************************************************/
153 void AtkListener::handleChildRemoved(
154 const uno::Reference
< accessibility::XAccessibleContext
>& rxParent
,
155 const uno::Reference
< accessibility::XAccessible
>& rxChild
)
157 sal_Int32 nIndex
= -1;
159 // Locate the child in the children list
160 size_t n
, nmax
= m_aChildList
.size();
161 for( n
= 0; n
< nmax
; ++n
)
163 if( rxChild
== m_aChildList
[n
] )
170 // FIXME: two problems here:
171 // a) we get child-removed events for objects that are no real childs
172 // in the accessibility hierarchy or have been removed before due to
173 // some child removing batch.
174 // b) spi_atk_bridge_signal_listener ignores the given parameters
175 // for children_changed events and always asks the parent for the
176 // 0. child, which breaks somehow on vanishing list boxes.
177 // Ignoring "remove" events for objects not in the m_aChildList
181 updateChildList(rxParent
.get());
183 AtkObject
* pChild
= atk_object_wrapper_ref( rxChild
, false );
186 atk_object_wrapper_remove_child( mpWrapper
, pChild
, nIndex
);
187 g_object_unref( pChild
);
192 /*****************************************************************************/
194 void AtkListener::handleInvalidateChildren(
195 const uno::Reference
< accessibility::XAccessibleContext
>& rxParent
)
197 // Send notifications for all previous children
198 size_t n
= m_aChildList
.size();
201 if( m_aChildList
[n
].is() )
203 AtkObject
* pChild
= atk_object_wrapper_ref( m_aChildList
[n
], false );
206 atk_object_wrapper_remove_child( mpWrapper
, pChild
, n
);
207 g_object_unref( pChild
);
212 updateChildList(rxParent
.get());
214 // Send notifications for all new children
215 size_t nmax
= m_aChildList
.size();
216 for( n
= 0; n
< nmax
; ++n
)
218 if( m_aChildList
[n
].is() )
220 AtkObject
* pChild
= atk_object_wrapper_ref( m_aChildList
[n
] );
224 atk_object_wrapper_add_child( mpWrapper
, pChild
, n
);
225 g_object_unref( pChild
);
231 /*****************************************************************************/
233 static uno::Reference
< accessibility::XAccessibleContext
>
234 getAccessibleContextFromSource( const uno::Reference
< uno::XInterface
>& rxSource
)
236 uno::Reference
< accessibility::XAccessibleContext
> xContext(rxSource
, uno::UNO_QUERY
);
237 if( ! xContext
.is() )
239 g_warning( "ERROR: Event source does not implement XAccessibleContext" );
241 // Second try - query for XAccessible, which should give us access to
242 // XAccessibleContext.
243 uno::Reference
< accessibility::XAccessible
> xAccessible(rxSource
, uno::UNO_QUERY
);
244 if( xAccessible
.is() )
245 xContext
= xAccessible
->getAccessibleContext();
251 /*****************************************************************************/
253 // XAccessibleEventListener
254 void AtkListener::notifyEvent( const accessibility::AccessibleEventObject
& aEvent
) throw( uno::RuntimeException
)
259 AtkObject
*atk_obj
= ATK_OBJECT( mpWrapper
);
261 switch( aEvent
.EventId
)
263 // AtkObject signals:
265 case accessibility::AccessibleEventId::CHILD
:
267 uno::Reference
< accessibility::XAccessibleContext
> xParent
;
268 uno::Reference
< accessibility::XAccessible
> xChild
;
270 xParent
= getAccessibleContextFromSource(aEvent
.Source
);
271 g_return_if_fail( xParent
.is() );
273 if( aEvent
.OldValue
>>= xChild
)
274 handleChildRemoved(xParent
, xChild
);
276 if( aEvent
.NewValue
>>= xChild
)
277 handleChildAdded(xParent
, xChild
);
281 case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN
:
283 uno::Reference
< accessibility::XAccessibleContext
> xParent
;
285 xParent
= getAccessibleContextFromSource(aEvent
.Source
);
286 g_return_if_fail( xParent
.is() );
288 handleInvalidateChildren(xParent
);
292 case accessibility::AccessibleEventId::NAME_CHANGED
:
295 if( aEvent
.NewValue
>>= aName
)
297 atk_object_set_name(atk_obj
,
298 rtl::OUStringToOString(aName
, RTL_TEXTENCODING_UTF8
).getStr());
303 case accessibility::AccessibleEventId::DESCRIPTION_CHANGED
:
305 rtl::OUString aDescription
;
306 if( aEvent
.NewValue
>>= aDescription
)
308 atk_object_set_description(atk_obj
,
309 rtl::OUStringToOString(aDescription
, RTL_TEXTENCODING_UTF8
).getStr());
314 case accessibility::AccessibleEventId::STATE_CHANGED
:
316 AtkStateType eOldState
= mapState( aEvent
.OldValue
);
317 AtkStateType eNewState
= mapState( aEvent
.NewValue
);
319 gboolean bState
= eNewState
!= ATK_STATE_INVALID
;
320 AtkStateType eRealState
= bState
? eNewState
: eOldState
;
322 atk_object_notify_state_change( atk_obj
, eRealState
, bState
);
326 case accessibility::AccessibleEventId::BOUNDRECT_CHANGED
:
328 #ifdef HAS_ATKRECTANGLE
329 if( ATK_IS_COMPONENT( atk_obj
) )
333 atk_component_get_extents( ATK_COMPONENT( atk_obj
),
340 g_signal_emit_by_name( atk_obj
, "bounds_changed", &rect
);
343 g_warning( "bounds_changed event for object not implementing AtkComponent\n");
348 case accessibility::AccessibleEventId::VISIBLE_DATA_CHANGED
:
349 g_signal_emit_by_name( atk_obj
, "visible-data-changed" );
352 case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED
:
354 AtkObject
*pChild
= getObjFromAny( aEvent
.NewValue
);
357 g_signal_emit_by_name( atk_obj
, "active-descendant-changed", pChild
);
358 g_object_unref( pChild
);
363 // --> OD 2009-05-26 #i92103#
364 case accessibility::AccessibleEventId::LISTBOX_ENTRY_EXPANDED
:
366 AtkObject
*pChild
= getObjFromAny( aEvent
.NewValue
);
369 AtkStateType eExpandedState
= ATK_STATE_EXPANDED
;
370 atk_object_notify_state_change( pChild
, eExpandedState
, true );
371 g_object_unref( pChild
);
376 case accessibility::AccessibleEventId::LISTBOX_ENTRY_COLLAPSED
:
378 AtkObject
*pChild
= getObjFromAny( aEvent
.NewValue
);
381 AtkStateType eExpandedState
= ATK_STATE_EXPANDED
;
382 atk_object_notify_state_change( pChild
, eExpandedState
, false );
383 g_object_unref( pChild
);
389 // AtkAction signals ...
390 case accessibility::AccessibleEventId::ACTION_CHANGED
:
391 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-actions");
395 case accessibility::AccessibleEventId::CARET_CHANGED
:
398 aEvent
.NewValue
>>= nPos
;
399 g_signal_emit_by_name( atk_obj
, "text_caret_moved", nPos
);
402 case accessibility::AccessibleEventId::TEXT_CHANGED
:
404 // TESTME: and remove this comment:
405 // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
406 accessibility::TextSegment aDeletedText
;
407 accessibility::TextSegment aInsertedText
;
409 // TODO: when GNOME starts to send "update" kind of events, change
410 // we need to re-think this implementation as well
411 if( aEvent
.OldValue
>>= aDeletedText
)
413 /* Remember the text segment here to be able to return removed text in get_text().
414 * This is clearly a hack to be used until appropriate API exists in atk to pass
415 * the string value directly or we find a compelling reason to start caching the
416 * UTF-8 converted strings in the atk wrapper object.
419 g_object_set_data( G_OBJECT(atk_obj
), "ooo::text_changed::delete", &aDeletedText
);
421 g_signal_emit_by_name( atk_obj
, "text_changed::delete",
422 (gint
) aDeletedText
.SegmentStart
,
423 (gint
)( aDeletedText
.SegmentEnd
- aDeletedText
.SegmentStart
) );
425 g_object_steal_data( G_OBJECT(atk_obj
), "ooo::text_changed::delete" );
428 if( aEvent
.NewValue
>>= aInsertedText
)
429 g_signal_emit_by_name( atk_obj
, "text_changed::insert",
430 (gint
) aInsertedText
.SegmentStart
,
431 (gint
)( aInsertedText
.SegmentEnd
- aInsertedText
.SegmentStart
) );
435 case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED
:
437 g_signal_emit_by_name( atk_obj
, "text-selection-changed" );
441 case accessibility::AccessibleEventId::TEXT_ATTRIBUTE_CHANGED
:
442 g_signal_emit_by_name( atk_obj
, "text-attributes-changed" );
446 case accessibility::AccessibleEventId::VALUE_CHANGED
:
447 g_object_notify( G_OBJECT( atk_obj
), "accessible-value" );
450 case accessibility::AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED
:
451 case accessibility::AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED
:
452 case accessibility::AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED
:
453 case accessibility::AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED
:
454 case accessibility::AccessibleEventId::LABEL_FOR_RELATION_CHANGED
:
455 case accessibility::AccessibleEventId::LABELED_BY_RELATION_CHANGED
:
456 case accessibility::AccessibleEventId::MEMBER_OF_RELATION_CHANGED
:
457 case accessibility::AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED
:
458 // FIXME: ask Bill how Atk copes with this little lot ...
462 case accessibility::AccessibleEventId::TABLE_MODEL_CHANGED
:
464 accessibility::AccessibleTableModelChange aChange
;
465 aEvent
.NewValue
>>= aChange
;
467 sal_Int32 nRowsChanged
= aChange
.LastRow
- aChange
.FirstRow
+ 1;
468 sal_Int32 nColumnsChanged
= aChange
.LastColumn
- aChange
.FirstColumn
+ 1;
470 static const struct {
475 { NULL
, NULL
}, // dummy
476 { "row_inserted", "column_inserted" }, // INSERT = 1
477 { "row_deleted", "column_deleted" } // DELETE = 2
479 switch( aChange
.Type
)
481 case accessibility::AccessibleTableModelChangeType::INSERT
:
482 case accessibility::AccessibleTableModelChangeType::DELETE
:
483 if( nRowsChanged
> 0 )
484 g_signal_emit_by_name( G_OBJECT( atk_obj
),
485 aSignalNames
[aChange
.Type
].row
,
486 aChange
.FirstRow
, nRowsChanged
);
487 if( nColumnsChanged
> 0 )
488 g_signal_emit_by_name( G_OBJECT( atk_obj
),
489 aSignalNames
[aChange
.Type
].col
,
490 aChange
.FirstColumn
, nColumnsChanged
);
493 case accessibility::AccessibleTableModelChangeType::UPDATE
:
494 // This is not really a model change, is it ?
497 g_warning( "TESTME: unusual table model change %d\n", aChange
.Type
);
500 g_signal_emit_by_name( G_OBJECT( atk_obj
), "model-changed" );
504 case accessibility::AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED
:
505 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-table-column-header");
508 case accessibility::AccessibleEventId::TABLE_CAPTION_CHANGED
:
509 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-table-caption");
512 case accessibility::AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED
:
513 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-table-column-description");
516 case accessibility::AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED
:
517 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-table-row-description");
520 case accessibility::AccessibleEventId::TABLE_ROW_HEADER_CHANGED
:
521 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-table-row-header");
524 case accessibility::AccessibleEventId::TABLE_SUMMARY_CHANGED
:
525 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-table-summary");
528 case accessibility::AccessibleEventId::SELECTION_CHANGED
:
529 g_signal_emit_by_name( G_OBJECT( atk_obj
), "selection_changed");
532 case accessibility::AccessibleEventId::HYPERTEXT_CHANGED
:
533 g_signal_emit_by_name( G_OBJECT( atk_obj
), "property_change::accessible-hypertext-offset");
537 g_warning( "Unknown event notification %d", aEvent
.EventId
);