xref: /trunk/main/vcl/unx/gtk/a11y/atklistener.cxx (revision 9f62ea84)
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