1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_vcl.hxx"
26
27 #include <com/sun/star/accessibility/TextSegment.hpp>
28 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
29 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
30 #include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
31 #include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
32 #include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
33
34 #include "atklistener.hxx"
35 #include "atkwrapper.hxx"
36
37 #include <rtl/ref.hxx>
38 #include <stdio.h>
39
40 using namespace com::sun::star;
41
42
43 #define CSTRING_FROM_ANY(i) rtl::OUStringToOString( i.get< rtl::OUString >(), RTL_TEXTENCODING_UTF8 ).getStr()
44
AtkListener(AtkObjectWrapper * pWrapper)45 AtkListener::AtkListener( AtkObjectWrapper* pWrapper ) : mpWrapper( pWrapper )
46 {
47 if( mpWrapper )
48 {
49 g_object_ref( mpWrapper );
50 updateChildList( mpWrapper->mpContext );
51 }
52 }
53
~AtkListener()54 AtkListener::~AtkListener()
55 {
56 if( mpWrapper )
57 g_object_unref( mpWrapper );
58 }
59
60 /*****************************************************************************/
61
mapState(const uno::Any & rAny)62 AtkStateType mapState( const uno::Any &rAny )
63 {
64 sal_Int16 nState = accessibility::AccessibleStateType::INVALID;
65 rAny >>= nState;
66 return mapAtkState( nState );
67 }
68
69 /*****************************************************************************/
70
71 // XEventListener implementation
disposing(const lang::EventObject &)72 void AtkListener::disposing( const lang::EventObject& ) throw (uno::RuntimeException)
73 {
74 if( mpWrapper )
75 {
76 AtkObject *atk_obj = ATK_OBJECT( mpWrapper );
77
78 // Release all interface references to avoid shutdown problems with
79 // global mutex
80 atk_object_wrapper_dispose( mpWrapper );
81
82 // This is an equivalent to a state change to DEFUNC(T).
83 atk_object_notify_state_change( atk_obj, ATK_STATE_DEFUNCT, TRUE );
84
85 if( atk_get_focus_object() == atk_obj )
86 atk_focus_tracker_notify( NULL );
87
88 // Release the wrapper object so that it can vanish ..
89 g_object_unref( mpWrapper );
90 mpWrapper = NULL;
91 }
92 }
93
94 /*****************************************************************************/
95
getObjFromAny(const uno::Any & rAny)96 static AtkObject *getObjFromAny( const uno::Any &rAny )
97 {
98 uno::Reference< accessibility::XAccessible > xAccessible;
99 rAny >>= xAccessible;
100 return xAccessible.is() ? atk_object_wrapper_ref( xAccessible ) : NULL;
101 }
102
103 /*****************************************************************************/
104
105 // Updates the child list held to provide the old IndexInParent on children_changed::remove
updateChildList(accessibility::XAccessibleContext * pContext)106 void AtkListener::updateChildList(accessibility::XAccessibleContext* pContext)
107 {
108 m_aChildList.clear();
109
110 uno::Reference< accessibility::XAccessibleStateSet > xStateSet = pContext->getAccessibleStateSet();
111 if( xStateSet.is()
112 && !xStateSet->contains(accessibility::AccessibleStateType::DEFUNC)
113 && !xStateSet->contains(accessibility::AccessibleStateType::MANAGES_DESCENDANTS) )
114 {
115 sal_Int32 nChildren = pContext->getAccessibleChildCount();
116 m_aChildList.resize(nChildren);
117 for(sal_Int32 n = 0; n < nChildren; n++)
118 {
119 m_aChildList[n] = pContext->getAccessibleChild(n);
120 OSL_ASSERT(m_aChildList[n].is());
121 }
122 }
123 }
124
125 /*****************************************************************************/
126
handleChildAdded(const uno::Reference<accessibility::XAccessibleContext> & rxParent,const uno::Reference<accessibility::XAccessible> & rxAccessible)127 void AtkListener::handleChildAdded(
128 const uno::Reference< accessibility::XAccessibleContext >& rxParent,
129 const uno::Reference< accessibility::XAccessible>& rxAccessible)
130 {
131 AtkObject * pChild = atk_object_wrapper_ref( rxAccessible );
132
133 if( pChild )
134 {
135 updateChildList(rxParent.get());
136
137 atk_object_wrapper_add_child( mpWrapper, pChild,
138 atk_object_get_index_in_parent( pChild ));
139
140 g_object_unref( pChild );
141 }
142 }
143
144 /*****************************************************************************/
145
handleChildRemoved(const uno::Reference<accessibility::XAccessibleContext> & rxParent,const uno::Reference<accessibility::XAccessible> & rxChild)146 void AtkListener::handleChildRemoved(
147 const uno::Reference< accessibility::XAccessibleContext >& rxParent,
148 const uno::Reference< accessibility::XAccessible>& rxChild)
149 {
150 sal_Int32 nIndex = -1;
151
152 // Locate the child in the children list
153 size_t n, nmax = m_aChildList.size();
154 for( n = 0; n < nmax; ++n )
155 {
156 if( rxChild == m_aChildList[n] )
157 {
158 nIndex = n;
159 break;
160 }
161 }
162
163 // FIXME: two problems here:
164 // a) we get child-removed events for objects that are no real childs
165 // in the accessibility hierarchy or have been removed before due to
166 // some child removing batch.
167 // b) spi_atk_bridge_signal_listener ignores the given parameters
168 // for children_changed events and always asks the parent for the
169 // 0. child, which breaks somehow on vanishing list boxes.
170 // Ignoring "remove" events for objects not in the m_aChildList
171 // for now.
172 if( nIndex >= 0 )
173 {
174 updateChildList(rxParent.get());
175
176 AtkObject * pChild = atk_object_wrapper_ref( rxChild, false );
177 if( pChild )
178 {
179 atk_object_wrapper_remove_child( mpWrapper, pChild, nIndex );
180 g_object_unref( pChild );
181 }
182 }
183 }
184
185 /*****************************************************************************/
186
handleInvalidateChildren(const uno::Reference<accessibility::XAccessibleContext> & rxParent)187 void AtkListener::handleInvalidateChildren(
188 const uno::Reference< accessibility::XAccessibleContext >& rxParent)
189 {
190 // Send notifications for all previous children
191 size_t n = m_aChildList.size();
192 while( n-- > 0 )
193 {
194 if( m_aChildList[n].is() )
195 {
196 AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n], false );
197 if( pChild )
198 {
199 atk_object_wrapper_remove_child( mpWrapper, pChild, n );
200 g_object_unref( pChild );
201 }
202 }
203 }
204
205 updateChildList(rxParent.get());
206
207 // Send notifications for all new children
208 size_t nmax = m_aChildList.size();
209 for( n = 0; n < nmax; ++n )
210 {
211 if( m_aChildList[n].is() )
212 {
213 AtkObject * pChild = atk_object_wrapper_ref( m_aChildList[n] );
214
215 if( pChild )
216 {
217 atk_object_wrapper_add_child( mpWrapper, pChild, n );
218 g_object_unref( pChild );
219 }
220 }
221 }
222 }
223
224 /*****************************************************************************/
225
226 static uno::Reference< accessibility::XAccessibleContext >
getAccessibleContextFromSource(const uno::Reference<uno::XInterface> & rxSource)227 getAccessibleContextFromSource( const uno::Reference< uno::XInterface >& rxSource )
228 {
229 uno::Reference< accessibility::XAccessibleContext > xContext(rxSource, uno::UNO_QUERY);
230 if( ! xContext.is() )
231 {
232 g_warning( "ERROR: Event source does not implement XAccessibleContext" );
233
234 // Second try - query for XAccessible, which should give us access to
235 // XAccessibleContext.
236 uno::Reference< accessibility::XAccessible > xAccessible(rxSource, uno::UNO_QUERY);
237 if( xAccessible.is() )
238 xContext = xAccessible->getAccessibleContext();
239 }
240
241 return xContext;
242 }
243
244 /*****************************************************************************/
245
246 // XAccessibleEventListener
notifyEvent(const accessibility::AccessibleEventObject & aEvent)247 void AtkListener::notifyEvent( const accessibility::AccessibleEventObject& aEvent ) throw( uno::RuntimeException )
248 {
249 if( !mpWrapper )
250 return;
251
252 AtkObject *atk_obj = ATK_OBJECT( mpWrapper );
253
254 switch( aEvent.EventId )
255 {
256 // AtkObject signals:
257 // Hierarchy signals
258 case accessibility::AccessibleEventId::CHILD:
259 {
260 uno::Reference< accessibility::XAccessibleContext > xParent;
261 uno::Reference< accessibility::XAccessible > xChild;
262
263 xParent = getAccessibleContextFromSource(aEvent.Source);
264 g_return_if_fail( xParent.is() );
265
266 if( aEvent.OldValue >>= xChild )
267 handleChildRemoved(xParent, xChild);
268
269 if( aEvent.NewValue >>= xChild )
270 handleChildAdded(xParent, xChild);
271 }
272 break;
273
274 case accessibility::AccessibleEventId::INVALIDATE_ALL_CHILDREN:
275 {
276 uno::Reference< accessibility::XAccessibleContext > xParent;
277
278 xParent = getAccessibleContextFromSource(aEvent.Source);
279 g_return_if_fail( xParent.is() );
280
281 handleInvalidateChildren(xParent);
282 }
283 break;
284
285 case accessibility::AccessibleEventId::NAME_CHANGED:
286 {
287 rtl::OUString aName;
288 if( aEvent.NewValue >>= aName )
289 {
290 atk_object_set_name(atk_obj,
291 rtl::OUStringToOString(aName, RTL_TEXTENCODING_UTF8).getStr());
292 }
293 }
294 break;
295
296 case accessibility::AccessibleEventId::DESCRIPTION_CHANGED:
297 {
298 rtl::OUString aDescription;
299 if( aEvent.NewValue >>= aDescription )
300 {
301 atk_object_set_description(atk_obj,
302 rtl::OUStringToOString(aDescription, RTL_TEXTENCODING_UTF8).getStr());
303 }
304 }
305 break;
306
307 case accessibility::AccessibleEventId::STATE_CHANGED:
308 {
309 AtkStateType eOldState = mapState( aEvent.OldValue );
310 AtkStateType eNewState = mapState( aEvent.NewValue );
311
312 gboolean bState = eNewState != ATK_STATE_INVALID;
313 AtkStateType eRealState = bState ? eNewState : eOldState;
314
315 atk_object_notify_state_change( atk_obj, eRealState, bState );
316 break;
317 }
318
319 case accessibility::AccessibleEventId::BOUNDRECT_CHANGED:
320
321 #ifdef HAS_ATKRECTANGLE
322 if( ATK_IS_COMPONENT( atk_obj ) )
323 {
324 AtkRectangle rect;
325
326 atk_component_get_extents( ATK_COMPONENT( atk_obj ),
327 &rect.x,
328 &rect.y,
329 &rect.width,
330 &rect.height,
331 ATK_XY_SCREEN );
332
333 g_signal_emit_by_name( atk_obj, "bounds_changed", &rect );
334 }
335 else
336 g_warning( "bounds_changed event for object not implementing AtkComponent\n");
337 #endif
338
339 break;
340
341 case accessibility::AccessibleEventId::VISIBLE_DATA_CHANGED:
342 g_signal_emit_by_name( atk_obj, "visible-data-changed" );
343 break;
344
345 case accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
346 {
347 AtkObject *pChild = getObjFromAny( aEvent.NewValue );
348 if( pChild )
349 {
350 g_signal_emit_by_name( atk_obj, "active-descendant-changed", pChild );
351 g_object_unref( pChild );
352 }
353 break;
354 }
355
356 // --> OD 2009-05-26 #i92103#
357 case accessibility::AccessibleEventId::LISTBOX_ENTRY_EXPANDED:
358 {
359 AtkObject *pChild = getObjFromAny( aEvent.NewValue );
360 if( pChild )
361 {
362 AtkStateType eExpandedState = ATK_STATE_EXPANDED;
363 atk_object_notify_state_change( pChild, eExpandedState, true );
364 g_object_unref( pChild );
365 }
366 break;
367 }
368
369 case accessibility::AccessibleEventId::LISTBOX_ENTRY_COLLAPSED:
370 {
371 AtkObject *pChild = getObjFromAny( aEvent.NewValue );
372 if( pChild )
373 {
374 AtkStateType eExpandedState = ATK_STATE_EXPANDED;
375 atk_object_notify_state_change( pChild, eExpandedState, false );
376 g_object_unref( pChild );
377 }
378 break;
379 }
380 // <--
381
382 // AtkAction signals ...
383 case accessibility::AccessibleEventId::ACTION_CHANGED:
384 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-actions");
385 break;
386
387 // AtkText
388 case accessibility::AccessibleEventId::CARET_CHANGED:
389 {
390 sal_Int32 nPos=0;
391 aEvent.NewValue >>= nPos;
392 g_signal_emit_by_name( atk_obj, "text_caret_moved", nPos );
393 break;
394 }
395 case accessibility::AccessibleEventId::TEXT_CHANGED:
396 {
397 // TESTME: and remove this comment:
398 // cf. comphelper/source/misc/accessibletexthelper.cxx (implInitTextChangedEvent)
399 accessibility::TextSegment aDeletedText;
400 accessibility::TextSegment aInsertedText;
401
402 // TODO: when GNOME starts to send "update" kind of events, change
403 // we need to re-think this implementation as well
404 if( aEvent.OldValue >>= aDeletedText )
405 {
406 /* Remember the text segment here to be able to return removed text in get_text().
407 * This is clearly a hack to be used until appropriate API exists in atk to pass
408 * the string value directly or we find a compelling reason to start caching the
409 * UTF-8 converted strings in the atk wrapper object.
410 */
411
412 g_object_set_data( G_OBJECT(atk_obj), "ooo::text_changed::delete", &aDeletedText);
413
414 g_signal_emit_by_name( atk_obj, "text_changed::delete",
415 (gint) aDeletedText.SegmentStart,
416 (gint)( aDeletedText.SegmentEnd - aDeletedText.SegmentStart ) );
417
418 g_object_steal_data( G_OBJECT(atk_obj), "ooo::text_changed::delete" );
419 }
420
421 if( aEvent.NewValue >>= aInsertedText )
422 g_signal_emit_by_name( atk_obj, "text_changed::insert",
423 (gint) aInsertedText.SegmentStart,
424 (gint)( aInsertedText.SegmentEnd - aInsertedText.SegmentStart ) );
425 break;
426 }
427
428 case accessibility::AccessibleEventId::TEXT_SELECTION_CHANGED:
429 {
430 g_signal_emit_by_name( atk_obj, "text-selection-changed" );
431 break;
432 }
433
434 case accessibility::AccessibleEventId::TEXT_ATTRIBUTE_CHANGED:
435 g_signal_emit_by_name( atk_obj, "text-attributes-changed" );
436 break;
437
438 // AtkValue
439 case accessibility::AccessibleEventId::VALUE_CHANGED:
440 g_object_notify( G_OBJECT( atk_obj ), "accessible-value" );
441 break;
442
443 case accessibility::AccessibleEventId::CONTENT_FLOWS_FROM_RELATION_CHANGED:
444 case accessibility::AccessibleEventId::CONTENT_FLOWS_TO_RELATION_CHANGED:
445 case accessibility::AccessibleEventId::CONTROLLED_BY_RELATION_CHANGED:
446 case accessibility::AccessibleEventId::CONTROLLER_FOR_RELATION_CHANGED:
447 case accessibility::AccessibleEventId::LABEL_FOR_RELATION_CHANGED:
448 case accessibility::AccessibleEventId::LABELED_BY_RELATION_CHANGED:
449 case accessibility::AccessibleEventId::MEMBER_OF_RELATION_CHANGED:
450 case accessibility::AccessibleEventId::SUB_WINDOW_OF_RELATION_CHANGED:
451 // FIXME: ask Bill how Atk copes with this little lot ...
452 break;
453
454 // AtkTable
455 case accessibility::AccessibleEventId::TABLE_MODEL_CHANGED:
456 {
457 accessibility::AccessibleTableModelChange aChange;
458 aEvent.NewValue >>= aChange;
459
460 sal_Int32 nRowsChanged = aChange.LastRow - aChange.FirstRow + 1;
461 sal_Int32 nColumnsChanged = aChange.LastColumn - aChange.FirstColumn + 1;
462
463 static const struct {
464 const char *row;
465 const char *col;
466 } aSignalNames[] =
467 {
468 { NULL, NULL }, // dummy
469 { "row_inserted", "column_inserted" }, // INSERT = 1
470 { "row_deleted", "column_deleted" } // DELETE = 2
471 };
472 switch( aChange.Type )
473 {
474 case accessibility::AccessibleTableModelChangeType::INSERT:
475 case accessibility::AccessibleTableModelChangeType::DELETE:
476 if( nRowsChanged > 0 )
477 g_signal_emit_by_name( G_OBJECT( atk_obj ),
478 aSignalNames[aChange.Type].row,
479 aChange.FirstRow, nRowsChanged );
480 if( nColumnsChanged > 0 )
481 g_signal_emit_by_name( G_OBJECT( atk_obj ),
482 aSignalNames[aChange.Type].col,
483 aChange.FirstColumn, nColumnsChanged );
484 break;
485
486 case accessibility::AccessibleTableModelChangeType::UPDATE:
487 // This is not really a model change, is it ?
488 break;
489 default:
490 g_warning( "TESTME: unusual table model change %d\n", aChange.Type );
491 break;
492 }
493 g_signal_emit_by_name( G_OBJECT( atk_obj ), "model-changed" );
494 break;
495 }
496
497 case accessibility::AccessibleEventId::TABLE_COLUMN_HEADER_CHANGED:
498 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-column-header");
499 break;
500
501 case accessibility::AccessibleEventId::TABLE_CAPTION_CHANGED:
502 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-caption");
503 break;
504
505 case accessibility::AccessibleEventId::TABLE_COLUMN_DESCRIPTION_CHANGED:
506 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-column-description");
507 break;
508
509 case accessibility::AccessibleEventId::TABLE_ROW_DESCRIPTION_CHANGED:
510 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-description");
511 break;
512
513 case accessibility::AccessibleEventId::TABLE_ROW_HEADER_CHANGED:
514 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-row-header");
515 break;
516
517 case accessibility::AccessibleEventId::TABLE_SUMMARY_CHANGED:
518 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-table-summary");
519 break;
520
521 case accessibility::AccessibleEventId::SELECTION_CHANGED:
522 g_signal_emit_by_name( G_OBJECT( atk_obj ), "selection_changed");
523 break;
524
525 case accessibility::AccessibleEventId::HYPERTEXT_CHANGED:
526 g_signal_emit_by_name( G_OBJECT( atk_obj ), "property_change::accessible-hypertext-offset");
527 break;
528
529 default:
530 g_warning( "Unknown event notification %d", aEvent.EventId );
531 break;
532 }
533 }
534