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