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 #include <sal/config.h> 21 22 #include <cstdlib> 23 #include <memory> 24 #include <utility> 25 #include <algorithm> 26 #include <osl/mutex.hxx> 27 #include <sal/log.hxx> 28 #include <com/sun/star/uno/Any.hxx> 29 #include <com/sun/star/uno/Reference.hxx> 30 #include <com/sun/star/awt/Point.hpp> 31 #include <com/sun/star/awt/Rectangle.hpp> 32 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp> 33 #include <com/sun/star/accessibility/AccessibleEventId.hpp> 34 #include <com/sun/star/accessibility/XAccessible.hpp> 35 #include <com/sun/star/accessibility/XAccessibleContext.hpp> 36 #include <com/sun/star/accessibility/XAccessibleComponent.hpp> 37 #include <com/sun/star/accessibility/AccessibleStateType.hpp> 38 #include <comphelper/accessibleeventnotifier.hxx> 39 #include <vcl/svapp.hxx> 40 #include <vcl/textdata.hxx> 41 #include <vcl/unohelp.hxx> 42 43 44 // Project-local header 45 46 47 #include "AccessibleTextEventQueue.hxx" 48 #include <svx/AccessibleTextHelper.hxx> 49 50 #include <editeng/unoedhlp.hxx> 51 #include <editeng/unoedprx.hxx> 52 #include <editeng/AccessibleParaManager.hxx> 53 #include <editeng/AccessibleEditableTextPara.hxx> 54 #include <svx/svdmodel.hxx> 55 #include <svx/svdpntv.hxx> 56 #include <cell.hxx> 57 #include "../table/accessiblecell.hxx" 58 #include <editeng/editdata.hxx> 59 #include <tools/debug.hxx> 60 #include <tools/diagnose_ex.h> 61 62 using namespace ::com::sun::star; 63 using namespace ::com::sun::star::accessibility; 64 65 namespace accessibility 66 { 67 68 // AccessibleTextHelper_Impl declaration 69 70 template < typename first_type, typename second_type > makeSortedPair(first_type first,second_type second)71 static ::std::pair< first_type, second_type > makeSortedPair( first_type first, 72 second_type second ) 73 { 74 if( first > second ) 75 return ::std::make_pair( second, first ); 76 else 77 return ::std::make_pair( first, second ); 78 } 79 80 class AccessibleTextHelper_Impl : public SfxListener 81 { 82 public: 83 typedef ::std::vector< sal_Int16 > VectorOfStates; 84 85 // receive pointer to our frontend class and view window 86 AccessibleTextHelper_Impl(); 87 virtual ~AccessibleTextHelper_Impl() override; 88 89 // XAccessibleContext child handling methods 90 sal_Int32 getAccessibleChildCount() const; 91 uno::Reference< XAccessible > getAccessibleChild( sal_Int32 i ); 92 93 // XAccessibleEventBroadcaster child related methods 94 void addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ); 95 void removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ); 96 97 // XAccessibleComponent child related methods 98 uno::Reference< XAccessible > getAccessibleAtPoint( const awt::Point& aPoint ); 99 100 SvxEditSourceAdapter& GetEditSource() const; 101 102 void SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource ); 103 SetEventSource(const uno::Reference<XAccessible> & rInterface)104 void SetEventSource( const uno::Reference< XAccessible >& rInterface ) 105 { 106 mxFrontEnd = rInterface; 107 } 108 109 void SetOffset( const Point& ); GetOffset() const110 Point GetOffset() const 111 { 112 ::osl::MutexGuard aGuard( maMutex ); Point aPoint( maOffset ); 113 return aPoint; 114 } 115 116 void SetStartIndex( sal_Int32 nOffset ); GetStartIndex() const117 sal_Int32 GetStartIndex() const 118 { 119 // Strictly correct only with locked solar mutex, // but 120 // here we rely on the fact that sal_Int32 access is 121 // atomic 122 return mnStartIndex; 123 } 124 125 void SetAdditionalChildStates( const VectorOfStates& rChildStates ); 126 127 void Dispose(); 128 129 // do NOT hold object mutex when calling this! Danger of deadlock 130 void FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue = uno::Any(), const uno::Any& rOldValue = uno::Any() ) const; 131 void FireEvent( const AccessibleEventObject& rEvent ) const; 132 133 void SetFocus( bool bHaveFocus ); HaveFocus()134 bool HaveFocus() 135 { 136 // No locking of solar mutex here, since we rely on the fact 137 // that sal_Bool access is atomic 138 return mbThisHasFocus; 139 } 140 void SetChildFocus( sal_Int32 nChild, bool bHaveFocus ); 141 void SetShapeFocus( bool bHaveFocus ); 142 void ChangeChildFocus( sal_Int32 nNewChild ); 143 144 #ifdef DBG_UTIL 145 void CheckInvariants() const; 146 #endif 147 148 // checks all children for visibility, throws away invisible ones 149 void UpdateVisibleChildren( bool bBroadcastEvents=true ); 150 151 // check all children for changes in position and size 152 void UpdateBoundRect(); 153 154 // calls SetSelection on the forwarder and updates maLastSelection 155 // cache. 156 void UpdateSelection(); 157 158 private: 159 160 // Process event queue 161 void ProcessQueue(); 162 163 // syntactic sugar for FireEvent GotPropertyEvent(const uno::Any & rNewValue,const sal_Int16 nEventId) const164 void GotPropertyEvent( const uno::Any& rNewValue, const sal_Int16 nEventId ) const { FireEvent( nEventId, rNewValue ); } 165 166 // shutdown usage of current edit source on myself and the children. 167 void ShutdownEditSource(); 168 169 void ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast ); 170 171 virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override; 172 getNotifierClientId() const173 int getNotifierClientId() const { return mnNotifierClientId; } 174 175 // lock solar mutex before 176 SvxTextForwarder& GetTextForwarder() const; 177 // lock solar mutex before 178 SvxViewForwarder& GetViewForwarder() const; 179 // lock solar mutex before 180 SvxEditViewForwarder& GetEditViewForwarder() const; 181 182 // are we in edit mode? 183 bool IsActive() const; 184 185 // our frontend class (the one implementing the actual 186 // interface). That's not necessarily the one containing the impl 187 // pointer! 188 uno::Reference< XAccessible > mxFrontEnd; 189 190 // a wrapper for the text forwarders (guarded by solar mutex) 191 mutable SvxEditSourceAdapter maEditSource; 192 193 // store last selection (to correctly report selection changes, guarded by solar mutex) 194 ESelection maLastSelection; 195 196 // cache range of visible children (guarded by solar mutex) 197 sal_Int32 mnFirstVisibleChild; 198 sal_Int32 mnLastVisibleChild; 199 200 // offset to add to all our children (unguarded, relying on 201 // the fact that sal_Int32 access is atomic) 202 sal_Int32 mnStartIndex; 203 204 // the object handling our children (guarded by solar mutex) 205 ::accessibility::AccessibleParaManager maParaManager; 206 207 // Queued events from Notify() (guarded by solar mutex) 208 AccessibleTextEventQueue maEventQueue; 209 210 // spin lock to prevent notify in notify (guarded by solar mutex) 211 bool mbInNotify; 212 213 // whether the object or its children has the focus set (guarded by solar mutex) 214 bool mbGroupHasFocus; 215 216 // whether we (this object) has the focus set (guarded by solar mutex) 217 bool mbThisHasFocus; 218 219 mutable ::osl::Mutex maMutex; 220 221 /// our current offset to the containing shape/cell (guarded by maMutex) 222 Point maOffset; 223 224 /// client Id from AccessibleEventNotifier 225 int mnNotifierClientId; 226 }; 227 AccessibleTextHelper_Impl()228 AccessibleTextHelper_Impl::AccessibleTextHelper_Impl() : 229 maLastSelection( EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND,EE_PARA_NOT_FOUND,EE_INDEX_NOT_FOUND ), 230 mnFirstVisibleChild( -1 ), 231 mnLastVisibleChild( -2 ), 232 mnStartIndex( 0 ), 233 mbInNotify( false ), 234 mbGroupHasFocus( false ), 235 mbThisHasFocus( false ), 236 maOffset(0,0), 237 // well, that's strictly exception safe, though not really 238 // robust. We rely on the fact that this member is constructed 239 // last, and that the constructor body is empty, thus no 240 // chance for exceptions once the Id is fetched. Nevertheless, 241 // normally should employ RAII here... 242 mnNotifierClientId(::comphelper::AccessibleEventNotifier::registerClient()) 243 { 244 SAL_INFO("svx", "received ID: " << mnNotifierClientId ); 245 } 246 ~AccessibleTextHelper_Impl()247 AccessibleTextHelper_Impl::~AccessibleTextHelper_Impl() 248 { 249 SolarMutexGuard aGuard; 250 251 try 252 { 253 // call Dispose here, too, since we've some resources not 254 // automatically freed otherwise 255 Dispose(); 256 } 257 catch( const uno::Exception& ) {} 258 } 259 GetTextForwarder() const260 SvxTextForwarder& AccessibleTextHelper_Impl::GetTextForwarder() const 261 { 262 if( !maEditSource.IsValid() ) 263 throw uno::RuntimeException("Unknown edit source", mxFrontEnd); 264 265 SvxTextForwarder* pTextForwarder = maEditSource.GetTextForwarder(); 266 267 if( !pTextForwarder ) 268 throw uno::RuntimeException("Unable to fetch text forwarder, model might be dead", mxFrontEnd); 269 270 if( !pTextForwarder->IsValid() ) 271 throw uno::RuntimeException("Text forwarder is invalid, model might be dead", mxFrontEnd); 272 273 return *pTextForwarder; 274 } 275 GetViewForwarder() const276 SvxViewForwarder& AccessibleTextHelper_Impl::GetViewForwarder() const 277 { 278 if( !maEditSource.IsValid() ) 279 throw uno::RuntimeException("Unknown edit source", mxFrontEnd); 280 281 SvxViewForwarder* pViewForwarder = maEditSource.GetViewForwarder(); 282 283 if( !pViewForwarder ) 284 throw uno::RuntimeException("Unable to fetch view forwarder, model might be dead", mxFrontEnd); 285 286 if( !pViewForwarder->IsValid() ) 287 throw uno::RuntimeException("View forwarder is invalid, model might be dead", mxFrontEnd); 288 289 return *pViewForwarder; 290 } 291 GetEditViewForwarder() const292 SvxEditViewForwarder& AccessibleTextHelper_Impl::GetEditViewForwarder() const 293 { 294 if( !maEditSource.IsValid() ) 295 throw uno::RuntimeException("Unknown edit source", mxFrontEnd); 296 297 SvxEditViewForwarder* pViewForwarder = maEditSource.GetEditViewForwarder(); 298 299 if( !pViewForwarder ) 300 { 301 throw uno::RuntimeException("No edit view forwarder, object not in edit mode", mxFrontEnd); 302 } 303 304 if( !pViewForwarder->IsValid() ) 305 { 306 throw uno::RuntimeException("View forwarder is invalid, object not in edit mode", mxFrontEnd); 307 } 308 309 return *pViewForwarder; 310 } 311 GetEditSource() const312 SvxEditSourceAdapter& AccessibleTextHelper_Impl::GetEditSource() const 313 { 314 if( !maEditSource.IsValid() ) 315 throw uno::RuntimeException("AccessibleTextHelper_Impl::GetEditSource: no edit source", mxFrontEnd ); 316 return maEditSource; 317 } 318 319 namespace { 320 321 // functor for sending child events (no stand-alone function, they are maybe not inlined) 322 class AccessibleTextHelper_OffsetChildIndex 323 { 324 public: AccessibleTextHelper_OffsetChildIndex(sal_Int32 nDifference)325 explicit AccessibleTextHelper_OffsetChildIndex( sal_Int32 nDifference ) : mnDifference(nDifference) {} operator ()(::accessibility::AccessibleEditableTextPara & rPara)326 void operator()( ::accessibility::AccessibleEditableTextPara& rPara ) 327 { 328 rPara.SetIndexInParent( rPara.GetIndexInParent() + mnDifference ); 329 } 330 331 private: 332 const sal_Int32 mnDifference; 333 }; 334 335 } 336 SetStartIndex(sal_Int32 nOffset)337 void AccessibleTextHelper_Impl::SetStartIndex( sal_Int32 nOffset ) 338 { 339 sal_Int32 nOldOffset( mnStartIndex ); 340 341 mnStartIndex = nOffset; 342 343 if( nOldOffset != nOffset ) 344 { 345 // update children 346 AccessibleTextHelper_OffsetChildIndex aFunctor( nOffset - nOldOffset ); 347 348 ::std::for_each( maParaManager.begin(), maParaManager.end(), 349 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_OffsetChildIndex > (aFunctor) ); 350 } 351 } 352 SetAdditionalChildStates(const VectorOfStates & rChildStates)353 void AccessibleTextHelper_Impl::SetAdditionalChildStates( const VectorOfStates& rChildStates ) 354 { 355 maParaManager.SetAdditionalChildStates( rChildStates ); 356 } 357 SetChildFocus(sal_Int32 nChild,bool bHaveFocus)358 void AccessibleTextHelper_Impl::SetChildFocus( sal_Int32 nChild, bool bHaveFocus ) 359 { 360 if( bHaveFocus ) 361 { 362 if( mbThisHasFocus ) 363 SetShapeFocus( false ); 364 365 maParaManager.SetFocus( nChild ); 366 367 // we just received the focus, also send caret event then 368 UpdateSelection(); 369 370 SAL_INFO("svx", "Paragraph " << nChild << " received focus"); 371 } 372 else 373 { 374 maParaManager.SetFocus( -1 ); 375 376 SAL_INFO("svx", "Paragraph " << nChild << " lost focus"); 377 378 if( mbGroupHasFocus ) 379 SetShapeFocus( true ); 380 } 381 } 382 ChangeChildFocus(sal_Int32 nNewChild)383 void AccessibleTextHelper_Impl::ChangeChildFocus( sal_Int32 nNewChild ) 384 { 385 if( mbThisHasFocus ) 386 SetShapeFocus( false ); 387 388 mbGroupHasFocus = true; 389 maParaManager.SetFocus( nNewChild ); 390 391 SAL_INFO("svx", "Paragraph " << nNewChild << " received focus"); 392 } 393 SetShapeFocus(bool bHaveFocus)394 void AccessibleTextHelper_Impl::SetShapeFocus( bool bHaveFocus ) 395 { 396 bool bOldFocus( mbThisHasFocus ); 397 398 mbThisHasFocus = bHaveFocus; 399 400 if( bOldFocus == bHaveFocus ) 401 return; 402 403 if( bHaveFocus ) 404 { 405 if( mxFrontEnd.is() ) 406 { 407 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() ); 408 if ( !pAccessibleCell ) 409 GotPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED ); 410 else // the focus event on cell should be fired on table directly 411 { 412 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable(); 413 if (pAccTable) 414 pAccTable->SetStateDirectly(AccessibleStateType::FOCUSED); 415 } 416 } 417 SAL_INFO("svx", "Parent object received focus" ); 418 } 419 else 420 { 421 // The focus state should be reset directly on table. 422 //LostPropertyEvent( uno::makeAny(AccessibleStateType::FOCUSED), AccessibleEventId::STATE_CHANGED ); 423 if( mxFrontEnd.is() ) 424 { 425 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() ); 426 if ( !pAccessibleCell ) 427 FireEvent( AccessibleEventId::STATE_CHANGED, uno::Any(), uno::makeAny(AccessibleStateType::FOCUSED) ); 428 else 429 { 430 AccessibleTableShape* pAccTable = pAccessibleCell->GetParentTable(); 431 if (pAccTable) 432 pAccTable->ResetStateDirectly(AccessibleStateType::FOCUSED); 433 } 434 } 435 SAL_INFO("svx", "Parent object lost focus" ); 436 } 437 } 438 SetFocus(bool bHaveFocus)439 void AccessibleTextHelper_Impl::SetFocus( bool bHaveFocus ) 440 { 441 bool bOldFocus( mbGroupHasFocus ); 442 443 mbGroupHasFocus = bHaveFocus; 444 445 if( IsActive() ) 446 { 447 try 448 { 449 // find the one with the cursor and get/set focus accordingly 450 ESelection aSelection; 451 if( GetEditViewForwarder().GetSelection( aSelection ) ) 452 SetChildFocus( aSelection.nEndPara, bHaveFocus ); 453 } 454 catch( const uno::Exception& ) {} 455 } 456 else if( bOldFocus != bHaveFocus ) 457 { 458 SetShapeFocus( bHaveFocus ); 459 } 460 461 SAL_INFO("svx", "focus changed, Object " << this << ", state: " << (bHaveFocus ? "focused" : "not focused") ); 462 } 463 IsActive() const464 bool AccessibleTextHelper_Impl::IsActive() const 465 { 466 try 467 { 468 SvxEditSource& rEditSource = GetEditSource(); 469 SvxEditViewForwarder* pViewForwarder = rEditSource.GetEditViewForwarder(); 470 471 if( !pViewForwarder ) 472 return false; 473 474 if( mxFrontEnd.is() ) 475 { 476 AccessibleCell* pAccessibleCell = dynamic_cast< AccessibleCell* > ( mxFrontEnd.get() ); 477 if ( pAccessibleCell ) 478 { 479 sdr::table::CellRef xCell = pAccessibleCell->getCellRef(); 480 if ( xCell.is() ) 481 return xCell->IsActiveCell(); 482 } 483 } 484 return pViewForwarder->IsValid(); 485 } 486 catch( const uno::RuntimeException& ) 487 { 488 return false; 489 } 490 } 491 UpdateSelection()492 void AccessibleTextHelper_Impl::UpdateSelection() 493 { 494 try 495 { 496 ESelection aSelection; 497 if( GetEditViewForwarder().GetSelection( aSelection ) ) 498 { 499 if( maLastSelection != aSelection && 500 aSelection.nEndPara < maParaManager.GetNum() ) 501 { 502 // #103998# Not that important, changed from assertion to trace 503 if( mbThisHasFocus ) 504 { 505 SAL_INFO("svx", "Parent has focus!"); 506 } 507 508 sal_Int32 nMaxValidParaIndex( GetTextForwarder().GetParagraphCount() - 1 ); 509 510 // notify all affected paragraphs (TODO: may be suboptimal, 511 // since some paragraphs might stay selected) 512 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND ) 513 { 514 // Did the caret move from one paragraph to another? 515 // #100530# no caret events if not focused. 516 if( mbGroupHasFocus && 517 maLastSelection.nEndPara != aSelection.nEndPara ) 518 { 519 if( maLastSelection.nEndPara < maParaManager.GetNum() ) 520 { 521 maParaManager.FireEvent( ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ), 522 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex )+1, 523 AccessibleEventId::CARET_CHANGED, 524 uno::makeAny(static_cast<sal_Int32>(-1)), 525 uno::makeAny(maLastSelection.nEndPos) ); 526 } 527 528 ChangeChildFocus( aSelection.nEndPara ); 529 530 SAL_INFO( 531 "svx", 532 "focus changed, Object: " << this 533 << ", Paragraph: " << aSelection.nEndPara 534 << ", Last paragraph: " 535 << maLastSelection.nEndPara); 536 } 537 } 538 539 // #100530# no caret events if not focused. 540 if( mbGroupHasFocus ) 541 { 542 uno::Any aOldCursor; 543 544 // #i13705# The old cursor can only contain valid 545 // values if it's the same paragraph! 546 if( maLastSelection.nStartPara != EE_PARA_NOT_FOUND && 547 maLastSelection.nEndPara == aSelection.nEndPara ) 548 { 549 aOldCursor <<= maLastSelection.nEndPos; 550 } 551 else 552 { 553 aOldCursor <<= static_cast<sal_Int32>(-1); 554 } 555 556 maParaManager.FireEvent( aSelection.nEndPara, 557 aSelection.nEndPara+1, 558 AccessibleEventId::CARET_CHANGED, 559 uno::makeAny(aSelection.nEndPos), 560 aOldCursor ); 561 } 562 563 SAL_INFO( 564 "svx", 565 "caret changed, Object: " << this << ", New pos: " 566 << aSelection.nEndPos << ", Old pos: " 567 << maLastSelection.nEndPos << ", New para: " 568 << aSelection.nEndPara << ", Old para: " 569 << maLastSelection.nEndPara); 570 571 // #108947# Sort new range before calling FireEvent 572 ::std::pair<sal_Int32, sal_Int32> sortedSelection( 573 makeSortedPair(::std::min( aSelection.nStartPara, nMaxValidParaIndex ), 574 ::std::min( aSelection.nEndPara, nMaxValidParaIndex ) ) ); 575 576 // #108947# Sort last range before calling FireEvent 577 ::std::pair<sal_Int32, sal_Int32> sortedLastSelection( 578 makeSortedPair(::std::min( maLastSelection.nStartPara, nMaxValidParaIndex ), 579 ::std::min( maLastSelection.nEndPara, nMaxValidParaIndex ) ) ); 580 581 // event TEXT_SELECTION_CHANGED has to be submitted. (#i27299#) 582 const sal_Int16 nTextSelChgEventId = 583 AccessibleEventId::TEXT_SELECTION_CHANGED; 584 // #107037# notify selection change 585 if( maLastSelection.nStartPara == EE_PARA_NOT_FOUND ) 586 { 587 // last selection is undefined 588 // use method <ESelection::HasRange()> (#i27299#) 589 if ( aSelection.HasRange() ) 590 { 591 // selection was undefined, now is on 592 maParaManager.FireEvent( sortedSelection.first, 593 sortedSelection.second+1, 594 nTextSelChgEventId ); 595 } 596 } 597 else 598 { 599 // last selection is valid 600 // use method <ESelection::HasRange()> (#i27299#) 601 if ( maLastSelection.HasRange() && 602 !aSelection.HasRange() ) 603 { 604 // selection was on, now is empty 605 maParaManager.FireEvent( sortedLastSelection.first, 606 sortedLastSelection.second+1, 607 nTextSelChgEventId ); 608 } 609 // use method <ESelection::HasRange()> (#i27299#) 610 else if( !maLastSelection.HasRange() && 611 aSelection.HasRange() ) 612 { 613 // selection was empty, now is on 614 maParaManager.FireEvent( sortedSelection.first, 615 sortedSelection.second+1, 616 nTextSelChgEventId ); 617 } 618 // no event TEXT_SELECTION_CHANGED event, if new and 619 // last selection are empty. (#i27299#) 620 else if ( maLastSelection.HasRange() && 621 aSelection.HasRange() ) 622 { 623 // use sorted last and new selection 624 ESelection aTmpLastSel( maLastSelection ); 625 aTmpLastSel.Adjust(); 626 ESelection aTmpSel( aSelection ); 627 aTmpSel.Adjust(); 628 // first submit event for new and changed selection 629 sal_Int32 nPara = aTmpSel.nStartPara; 630 for ( ; nPara <= aTmpSel.nEndPara; ++nPara ) 631 { 632 if ( nPara < aTmpLastSel.nStartPara || 633 nPara > aTmpLastSel.nEndPara ) 634 { 635 // new selection on paragraph <nPara> 636 maParaManager.FireEvent( nPara, 637 nTextSelChgEventId ); 638 } 639 else 640 { 641 // check for changed selection on paragraph <nPara> 642 const sal_Int32 nParaStartPos = 643 nPara == aTmpSel.nStartPara 644 ? aTmpSel.nStartPos : 0; 645 const sal_Int32 nParaEndPos = 646 nPara == aTmpSel.nEndPara 647 ? aTmpSel.nEndPos : -1; 648 const sal_Int32 nLastParaStartPos = 649 nPara == aTmpLastSel.nStartPara 650 ? aTmpLastSel.nStartPos : 0; 651 const sal_Int32 nLastParaEndPos = 652 nPara == aTmpLastSel.nEndPara 653 ? aTmpLastSel.nEndPos : -1; 654 if ( nParaStartPos != nLastParaStartPos || 655 nParaEndPos != nLastParaEndPos ) 656 { 657 maParaManager.FireEvent( 658 nPara, nTextSelChgEventId ); 659 } 660 } 661 } 662 // second submit event for 'old' selections 663 nPara = aTmpLastSel.nStartPara; 664 for ( ; nPara <= aTmpLastSel.nEndPara; ++nPara ) 665 { 666 if ( nPara < aTmpSel.nStartPara || 667 nPara > aTmpSel.nEndPara ) 668 { 669 maParaManager.FireEvent( nPara, 670 nTextSelChgEventId ); 671 } 672 } 673 } 674 } 675 676 maLastSelection = aSelection; 677 } 678 } 679 } 680 // no selection? no update actions 681 catch( const uno::RuntimeException& ) {} 682 } 683 ShutdownEditSource()684 void AccessibleTextHelper_Impl::ShutdownEditSource() 685 { 686 // This should only be called with solar mutex locked, i.e. from the main office thread 687 688 // This here is somewhat clumsy: As soon as our children have 689 // a NULL EditSource (maParaManager.SetEditSource()), they 690 // enter the disposed state and cannot be reanimated. Thus, it 691 // is unavoidable and a hard requirement to let go and create 692 // from scratch each and every child. 693 694 // invalidate children 695 maParaManager.Dispose(); 696 maParaManager.SetNum(0); 697 698 // lost all children 699 if( mxFrontEnd.is() ) 700 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN); 701 702 // quit listen on stale edit source 703 if( maEditSource.IsValid() ) 704 EndListening( maEditSource.GetBroadcaster() ); 705 706 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() ); 707 } 708 SetEditSource(::std::unique_ptr<SvxEditSource> && pEditSource)709 void AccessibleTextHelper_Impl::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource ) 710 { 711 // This should only be called with solar mutex locked, i.e. from the main office thread 712 713 // shutdown old edit source 714 ShutdownEditSource(); 715 716 // set new edit source 717 maEditSource.SetEditSource( std::move(pEditSource) ); 718 719 // init child vector to the current child count 720 if( maEditSource.IsValid() ) 721 { 722 maParaManager.SetNum( GetTextForwarder().GetParagraphCount() ); 723 724 // listen on new edit source 725 StartListening( maEditSource.GetBroadcaster() ); 726 727 UpdateVisibleChildren(); 728 } 729 } 730 SetOffset(const Point & rPoint)731 void AccessibleTextHelper_Impl::SetOffset( const Point& rPoint ) 732 { 733 // guard against non-atomic access to maOffset data structure 734 { 735 ::osl::MutexGuard aGuard( maMutex ); 736 maOffset = rPoint; 737 } 738 739 maParaManager.SetEEOffset( rPoint ); 740 741 // in all cases, check visibility afterwards. 742 UpdateVisibleChildren(); 743 UpdateBoundRect(); 744 } 745 UpdateVisibleChildren(bool bBroadcastEvents)746 void AccessibleTextHelper_Impl::UpdateVisibleChildren( bool bBroadcastEvents ) 747 { 748 try 749 { 750 SvxTextForwarder& rCacheTF = GetTextForwarder(); 751 sal_Int32 nParas=rCacheTF.GetParagraphCount(); 752 753 // GetTextForwarder might have replaced everything, update 754 // paragraph count in case it's outdated 755 maParaManager.SetNum( nParas ); 756 757 mnFirstVisibleChild = -1; 758 mnLastVisibleChild = -2; 759 760 for( sal_Int32 nCurrPara=0; nCurrPara<nParas; ++nCurrPara ) 761 { 762 if (nCurrPara == 0) 763 mnFirstVisibleChild = nCurrPara; 764 mnLastVisibleChild = nCurrPara; 765 if (mxFrontEnd.is() && bBroadcastEvents) 766 { 767 // child not yet created? 768 ::accessibility::AccessibleParaManager::WeakChild aChild( maParaManager.GetChild(nCurrPara) ); 769 if( aChild.second.Width == 0 && 770 aChild.second.Height == 0 ) 771 { 772 GotPropertyEvent( uno::makeAny( maParaManager.CreateChild( nCurrPara - mnFirstVisibleChild, 773 mxFrontEnd, GetEditSource(), nCurrPara ).first ), 774 AccessibleEventId::CHILD ); 775 } 776 } 777 } 778 } 779 catch( const uno::Exception& ) 780 { 781 OSL_FAIL("AccessibleTextHelper_Impl::UpdateVisibleChildren error while determining visible children"); 782 783 // something failed - currently no children 784 mnFirstVisibleChild = -1; 785 mnLastVisibleChild = -2; 786 maParaManager.SetNum(0); 787 788 // lost all children 789 if( bBroadcastEvents ) 790 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN); 791 } 792 } 793 794 namespace { 795 796 // functor for checking changes in paragraph bounding boxes (no stand-alone function, maybe not inlined) 797 class AccessibleTextHelper_UpdateChildBounds 798 { 799 public: AccessibleTextHelper_UpdateChildBounds()800 explicit AccessibleTextHelper_UpdateChildBounds() {} operator ()(const::accessibility::AccessibleParaManager::WeakChild & rChild)801 ::accessibility::AccessibleParaManager::WeakChild operator()( const ::accessibility::AccessibleParaManager::WeakChild& rChild ) 802 { 803 // retrieve hard reference from weak one 804 auto aHardRef( rChild.first.get() ); 805 806 if( aHardRef.is() ) 807 { 808 awt::Rectangle aNewRect = aHardRef->getBounds(); 809 const awt::Rectangle& aOldRect = rChild.second; 810 811 if( aNewRect.X != aOldRect.X || 812 aNewRect.Y != aOldRect.Y || 813 aNewRect.Width != aOldRect.Width || 814 aNewRect.Height != aOldRect.Height ) 815 { 816 // visible data changed 817 aHardRef->FireEvent( AccessibleEventId::BOUNDRECT_CHANGED ); 818 819 // update internal bounds 820 return ::accessibility::AccessibleParaManager::WeakChild( rChild.first, aNewRect ); 821 } 822 } 823 824 // identity transform 825 return rChild; 826 } 827 }; 828 829 } 830 UpdateBoundRect()831 void AccessibleTextHelper_Impl::UpdateBoundRect() 832 { 833 // send BOUNDRECT_CHANGED to affected children 834 AccessibleTextHelper_UpdateChildBounds aFunctor; 835 ::std::transform( maParaManager.begin(), maParaManager.end(), maParaManager.begin(), aFunctor ); 836 } 837 838 #ifdef DBG_UTIL CheckInvariants() const839 void AccessibleTextHelper_Impl::CheckInvariants() const 840 { 841 if( mnFirstVisibleChild >= 0 && 842 mnFirstVisibleChild > mnLastVisibleChild ) 843 { 844 OSL_FAIL( "AccessibleTextHelper: range invalid" ); 845 } 846 } 847 #endif 848 849 namespace { 850 851 // functor for sending child events (no stand-alone function, they are maybe not inlined) 852 class AccessibleTextHelper_LostChildEvent 853 { 854 public: AccessibleTextHelper_LostChildEvent(AccessibleTextHelper_Impl & rImpl)855 explicit AccessibleTextHelper_LostChildEvent( AccessibleTextHelper_Impl& rImpl ) : mrImpl(rImpl) {} operator ()(const::accessibility::AccessibleParaManager::WeakChild & rPara)856 void operator()( const ::accessibility::AccessibleParaManager::WeakChild& rPara ) 857 { 858 // retrieve hard reference from weak one 859 auto aHardRef( rPara.first.get() ); 860 861 if( aHardRef.is() ) 862 mrImpl.FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny<css::uno::Reference<css::accessibility::XAccessible>>(aHardRef) ); 863 } 864 865 private: 866 AccessibleTextHelper_Impl& mrImpl; 867 }; 868 869 } 870 ParagraphsMoved(sal_Int32 nFirst,sal_Int32 nMiddle,sal_Int32 nLast)871 void AccessibleTextHelper_Impl::ParagraphsMoved( sal_Int32 nFirst, sal_Int32 nMiddle, sal_Int32 nLast ) 872 { 873 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount(); 874 875 /* rotate paragraphs 876 * ================= 877 * 878 * Three cases: 879 * 880 * 1. 881 * ... nParagraph ... nParam1 ... nParam2 ... 882 * |______________[xxxxxxxxxxx] 883 * becomes 884 * [xxxxxxxxxxx]|______________ 885 * 886 * tail is 0 887 * 888 * 2. 889 * ... nParam1 ... nParagraph ... nParam2 ... 890 * [xxxxxxxxxxx|xxxxxxxxxxxxxx]____________ 891 * becomes 892 * ____________[xxxxxxxxxxx|xxxxxxxxxxxxxx] 893 * 894 * tail is nParagraph - nParam1 895 * 896 * 3. 897 * ... nParam1 ... nParam2 ... nParagraph ... 898 * [xxxxxxxxxxx]___________|____________ 899 * becomes 900 * ___________|____________[xxxxxxxxxxx] 901 * 902 * tail is nParam2 - nParam1 903 */ 904 905 // sort nParagraph, nParam1 and nParam2 in ascending order, calc range 906 if( nMiddle < nFirst ) 907 { 908 ::std::swap(nFirst, nMiddle); 909 } 910 else if( nMiddle < nLast ) 911 { 912 nLast = nLast + nMiddle - nFirst; 913 } 914 else 915 { 916 ::std::swap(nMiddle, nLast); 917 nLast = nLast + nMiddle - nFirst; 918 } 919 920 if( !(nFirst < nParas && nMiddle < nParas && nLast < nParas) ) 921 return; 922 923 // since we have no "paragraph index 924 // changed" event on UAA, remove 925 // [first,last] and insert again later (in 926 // UpdateVisibleChildren) 927 928 // maParaManager.Rotate( nFirst, nMiddle, nLast ); 929 930 // send CHILD_EVENT to affected children 931 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin(); 932 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin; 933 934 ::std::advance( begin, nFirst ); 935 ::std::advance( end, nLast+1 ); 936 937 // TODO: maybe optimize here in the following way. If the 938 // number of removed children exceeds a certain threshold, 939 // use InvalidateFlags::Children 940 AccessibleTextHelper_LostChildEvent aFunctor( *this ); 941 942 ::std::for_each( begin, end, aFunctor ); 943 944 maParaManager.Release(nFirst, nLast+1); 945 // should be no need for UpdateBoundRect, since all affected children are cleared. 946 } 947 948 namespace { 949 950 // functor for sending child events (no stand-alone function, they are maybe not inlined) 951 class AccessibleTextHelper_ChildrenTextChanged 952 { 953 public: operator ()(::accessibility::AccessibleEditableTextPara & rPara)954 void operator()( ::accessibility::AccessibleEditableTextPara& rPara ) 955 { 956 rPara.TextChanged(); 957 } 958 }; 959 960 /** functor processing queue events 961 962 Reacts on SfxHintId::TextParaInserted/REMOVED events and stores 963 their content 964 */ 965 class AccessibleTextHelper_QueueFunctor 966 { 967 public: AccessibleTextHelper_QueueFunctor()968 AccessibleTextHelper_QueueFunctor() : 969 mnParasChanged( 0 ), 970 mnParaIndex(-1), 971 mnHintId(SfxHintId::NONE) 972 {} operator ()(const SfxHint * pEvent)973 void operator()( const SfxHint* pEvent ) 974 { 975 if( !pEvent || mnParasChanged == -1 ) 976 return; 977 978 // determine hint type 979 const TextHint* pTextHint = dynamic_cast<const TextHint*>( pEvent ); 980 const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( pEvent ); 981 982 if( !(!pEditSourceHint && pTextHint && 983 (pTextHint->GetId() == SfxHintId::TextParaInserted || 984 pTextHint->GetId() == SfxHintId::TextParaRemoved )) ) 985 return; 986 987 if( pTextHint->GetValue() == EE_PARA_ALL ) 988 { 989 mnParasChanged = -1; 990 } 991 else 992 { 993 mnHintId = pTextHint->GetId(); 994 mnParaIndex = pTextHint->GetValue(); 995 ++mnParasChanged; 996 } 997 } 998 999 /** Query number of paragraphs changed during queue processing. 1000 1001 @return number of changed paragraphs, -1 for 1002 "every paragraph changed" 1003 */ GetNumberOfParasChanged() const1004 sal_Int32 GetNumberOfParasChanged() const { return mnParasChanged; } 1005 /** Query index of last added/removed paragraph 1006 1007 @return index of lastly added paragraphs, -1 for none 1008 added so far. 1009 */ GetParaIndex() const1010 sal_Int32 GetParaIndex() const { return mnParaIndex; } 1011 /** Query hint id of last interesting event 1012 1013 @return hint id of last interesting event (REMOVED/INSERTED). 1014 */ GetHintId() const1015 SfxHintId GetHintId() const { return mnHintId; } 1016 1017 private: 1018 /** number of paragraphs changed during queue processing. -1 for 1019 "every paragraph changed" 1020 */ 1021 sal_Int32 mnParasChanged; 1022 /// index of paragraph added/removed last 1023 sal_Int32 mnParaIndex; 1024 /// TextHint ID (removed/inserted) of last interesting event 1025 SfxHintId mnHintId; 1026 }; 1027 1028 } 1029 ProcessQueue()1030 void AccessibleTextHelper_Impl::ProcessQueue() 1031 { 1032 // inspect queue for paragraph insert/remove events. If there 1033 // is exactly _one_ of those in the queue, and the number of 1034 // paragraphs has changed by exactly one, use that event to 1035 // determine a priori which paragraph was added/removed. This 1036 // is necessary, since I must sync right here with the 1037 // EditEngine state (number of paragraphs etc.), since I'm 1038 // potentially sending listener events right away. 1039 AccessibleTextHelper_QueueFunctor aFunctor; 1040 maEventQueue.ForEach( aFunctor ); 1041 1042 const sal_Int32 nNewParas( GetTextForwarder().GetParagraphCount() ); 1043 const sal_Int32 nCurrParas( maParaManager.GetNum() ); 1044 1045 // whether every paragraph already is updated (no need to 1046 // repeat that later on, e.g. for PARA_MOVED events) 1047 bool bEverythingUpdated( false ); 1048 1049 if( std::abs( nNewParas - nCurrParas ) == 1 && 1050 aFunctor.GetNumberOfParasChanged() == 1 ) 1051 { 1052 // #103483# Exactly one paragraph added/removed. This is 1053 // the normal case, optimize event handling here. 1054 1055 if( aFunctor.GetHintId() == SfxHintId::TextParaInserted ) 1056 { 1057 // update num of paras 1058 maParaManager.SetNum( nNewParas ); 1059 1060 // release everything from the insertion position until the end 1061 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas); 1062 1063 // TODO: Clarify whether this behaviour _really_ saves 1064 // anybody anything! 1065 // update children, _don't_ broadcast 1066 UpdateVisibleChildren( false ); 1067 UpdateBoundRect(); 1068 1069 // send insert event 1070 // #109864# Enforce creation of this paragraph 1071 try 1072 { 1073 GotPropertyEvent( uno::makeAny( getAccessibleChild( aFunctor.GetParaIndex() - 1074 mnFirstVisibleChild + GetStartIndex() ) ), 1075 AccessibleEventId::CHILD ); 1076 } 1077 catch( const uno::Exception& ) 1078 { 1079 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue: could not create new paragraph"); 1080 } 1081 } 1082 else if( aFunctor.GetHintId() == SfxHintId::TextParaRemoved ) 1083 { 1084 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator begin = maParaManager.begin(); 1085 ::std::advance( begin, aFunctor.GetParaIndex() ); 1086 ::accessibility::AccessibleParaManager::VectorOfChildren::const_iterator end = begin; 1087 ::std::advance( end, 1 ); 1088 1089 // #i61812# remember para to be removed for later notification 1090 // AFTER the new state is applied (that after the para got removed) 1091 ::uno::Reference< XAccessible > xPara(begin->first.get()); 1092 1093 // release everything from the remove position until the end 1094 maParaManager.Release(aFunctor.GetParaIndex(), nCurrParas); 1095 1096 // update num of paras 1097 maParaManager.SetNum( nNewParas ); 1098 1099 // TODO: Clarify whether this behaviour _really_ saves 1100 // anybody anything! 1101 // update children, _don't_ broadcast 1102 UpdateVisibleChildren( false ); 1103 UpdateBoundRect(); 1104 1105 // #i61812# notification for removed para 1106 if (xPara.is()) 1107 FireEvent(AccessibleEventId::CHILD, uno::Any(), uno::makeAny( xPara) ); 1108 } 1109 #ifdef DBG_UTIL 1110 else 1111 OSL_FAIL("AccessibleTextHelper_Impl::ProcessQueue() invalid hint id"); 1112 #endif 1113 } 1114 else if( nNewParas != nCurrParas ) 1115 { 1116 // release all paras 1117 maParaManager.Release(0, nCurrParas); 1118 1119 // update num of paras 1120 maParaManager.SetNum( nNewParas ); 1121 1122 // #109864# create from scratch, don't broadcast 1123 UpdateVisibleChildren( false ); 1124 UpdateBoundRect(); 1125 1126 // number of paragraphs somehow changed - but we have no 1127 // chance determining how. Thus, throw away everything and 1128 // create from scratch. 1129 // (child events should be broadcast after the changes are done...) 1130 FireEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN); 1131 1132 // no need for further updates later on 1133 bEverythingUpdated = true; 1134 } 1135 1136 while( !maEventQueue.IsEmpty() ) 1137 { 1138 ::std::unique_ptr< SfxHint > pHint( maEventQueue.PopFront() ); 1139 if (pHint) 1140 { 1141 const SfxHint& rHint = *pHint; 1142 1143 // Note, if you add events here, you need to update the AccessibleTextEventQueue::Append 1144 // code, because only the events we process here, are actually queued there. 1145 1146 try 1147 { 1148 1149 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) 1150 { 1151 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint ); 1152 1153 switch( pSdrHint->GetKind() ) 1154 { 1155 case SdrHintKind::BeginEdit: 1156 { 1157 if(!IsActive()) 1158 { 1159 break; 1160 } 1161 // change children state 1162 maParaManager.SetActive(); 1163 1164 // per definition, edit mode text has the focus 1165 SetFocus( true ); 1166 break; 1167 } 1168 1169 case SdrHintKind::EndEdit: 1170 { 1171 // focused child now loses focus 1172 ESelection aSelection; 1173 if( GetEditViewForwarder().GetSelection( aSelection ) ) 1174 SetChildFocus( aSelection.nEndPara, false ); 1175 1176 // change children state 1177 maParaManager.SetActive( false ); 1178 1179 maLastSelection = ESelection( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND, 1180 EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND); 1181 break; 1182 } 1183 default: 1184 break; 1185 } 1186 } 1187 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) ) 1188 { 1189 switch( pEditSourceHint->GetId() ) 1190 { 1191 case SfxHintId::EditSourceParasMoved: 1192 { 1193 DBG_ASSERT( pEditSourceHint->GetStartValue() < GetTextForwarder().GetParagraphCount() && 1194 pEditSourceHint->GetEndValue() < GetTextForwarder().GetParagraphCount(), 1195 "AccessibleTextHelper_Impl::NotifyHdl: Invalid notification"); 1196 1197 if( !bEverythingUpdated ) 1198 { 1199 ParagraphsMoved(pEditSourceHint->GetStartValue(), 1200 pEditSourceHint->GetValue(), 1201 pEditSourceHint->GetEndValue()); 1202 1203 // in all cases, check visibility afterwards. 1204 UpdateVisibleChildren(); 1205 } 1206 break; 1207 } 1208 1209 case SfxHintId::EditSourceSelectionChanged: 1210 // notify listeners 1211 try 1212 { 1213 UpdateSelection(); 1214 } 1215 // maybe we're not in edit mode (this is not an error) 1216 catch( const uno::Exception& ) {} 1217 break; 1218 default: break; 1219 } 1220 } 1221 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) ) 1222 { 1223 const sal_Int32 nParas = GetTextForwarder().GetParagraphCount(); 1224 1225 switch( pTextHint->GetId() ) 1226 { 1227 case SfxHintId::TextModified: 1228 { 1229 // notify listeners 1230 sal_Int32 nPara( pTextHint->GetValue() ); 1231 1232 // #108900# Delegate change event to children 1233 AccessibleTextHelper_ChildrenTextChanged aNotifyChildrenFunctor; 1234 1235 if( nPara == EE_PARA_ALL ) 1236 { 1237 // #108900# Call every child 1238 ::std::for_each( maParaManager.begin(), maParaManager.end(), 1239 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) ); 1240 } 1241 else 1242 if( nPara < nParas ) 1243 { 1244 // #108900# Call child at index nPara 1245 ::std::for_each( maParaManager.begin()+nPara, maParaManager.begin()+nPara+1, 1246 AccessibleParaManager::WeakChildAdapter< AccessibleTextHelper_ChildrenTextChanged > (aNotifyChildrenFunctor) ); 1247 } 1248 break; 1249 } 1250 1251 case SfxHintId::TextParaInserted: 1252 // already happened above 1253 break; 1254 1255 case SfxHintId::TextParaRemoved: 1256 // already happened above 1257 break; 1258 1259 case SfxHintId::TextHeightChanged: 1260 // visibility changed, done below 1261 break; 1262 1263 case SfxHintId::TextViewScrolled: 1264 // visibility changed, done below 1265 break; 1266 default: break; 1267 } 1268 1269 // in all cases, check visibility afterwards. 1270 UpdateVisibleChildren(); 1271 UpdateBoundRect(); 1272 } 1273 else if ( dynamic_cast<const SvxViewChangedHint*>( &rHint ) ) 1274 { 1275 // just check visibility 1276 UpdateVisibleChildren(); 1277 UpdateBoundRect(); 1278 } 1279 // it's VITAL to keep the SfxSimpleHint last! It's the base of some classes above! 1280 else if( rHint.GetId() == SfxHintId::Dying) 1281 { 1282 // edit source is dying under us, become defunc then 1283 try 1284 { 1285 // make edit source inaccessible 1286 // Note: cannot destroy it here, since we're called from there! 1287 ShutdownEditSource(); 1288 } 1289 catch( const uno::Exception& ) {} 1290 } 1291 } 1292 catch( const uno::Exception& ) 1293 { 1294 DBG_UNHANDLED_EXCEPTION("svx"); 1295 } 1296 } 1297 } 1298 } 1299 Notify(SfxBroadcaster &,const SfxHint & rHint)1300 void AccessibleTextHelper_Impl::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint ) 1301 { 1302 // precondition: solar mutex locked 1303 DBG_TESTSOLARMUTEX(); 1304 1305 // precondition: not in a recursion 1306 if( mbInNotify ) 1307 return; 1308 1309 mbInNotify = true; 1310 1311 try 1312 { 1313 // Process notification event, arranged in order of likelihood of 1314 // occurrence to avoid unnecessary dynamic_cast. Note that 1315 // SvxEditSourceHint is derived from TextHint, so has to be checked 1316 // before that. 1317 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) 1318 { 1319 const SdrHint* pSdrHint = static_cast< const SdrHint* >( &rHint ); 1320 // process drawing layer events right away, if not 1321 // within an open EE notification frame. Otherwise, 1322 // event processing would be delayed until next EE 1323 // notification sequence. 1324 maEventQueue.Append( *pSdrHint ); 1325 } 1326 else if( const SvxViewChangedHint* pViewHint = dynamic_cast<const SvxViewChangedHint*>( &rHint ) ) 1327 { 1328 // process visibility right away, if not within an 1329 // open EE notification frame. Otherwise, event 1330 // processing would be delayed until next EE 1331 // notification sequence. 1332 maEventQueue.Append( *pViewHint ); 1333 } 1334 else if( const SvxEditSourceHint* pEditSourceHint = dynamic_cast<const SvxEditSourceHint*>( &rHint ) ) 1335 { 1336 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#) 1337 maEventQueue.Append( *pEditSourceHint ); 1338 } 1339 else if( const TextHint* pTextHint = dynamic_cast<const TextHint*>( &rHint ) ) 1340 { 1341 // EditEngine should emit TEXT_SELECTION_CHANGED events (#i27299#) 1342 if(pTextHint->GetId() == SfxHintId::TextProcessNotifications) 1343 ProcessQueue(); 1344 else 1345 maEventQueue.Append( *pTextHint ); 1346 } 1347 // it's VITAL to keep the SfxHint last! It's the base of the classes above! 1348 else if( rHint.GetId() == SfxHintId::Dying ) 1349 { 1350 // handle this event _at once_, because after that, objects are invalid 1351 // edit source is dying under us, become defunc then 1352 maEventQueue.Clear(); 1353 try 1354 { 1355 // make edit source inaccessible 1356 // Note: cannot destroy it here, since we're called from there! 1357 ShutdownEditSource(); 1358 } 1359 catch( const uno::Exception& ) {} 1360 } 1361 } 1362 catch( const uno::Exception& ) 1363 { 1364 DBG_UNHANDLED_EXCEPTION("svx"); 1365 mbInNotify = false; 1366 } 1367 1368 mbInNotify = false; 1369 } 1370 Dispose()1371 void AccessibleTextHelper_Impl::Dispose() 1372 { 1373 if( getNotifierClientId() != -1 ) 1374 { 1375 try 1376 { 1377 // #106234# Unregister from EventNotifier 1378 ::comphelper::AccessibleEventNotifier::revokeClient( getNotifierClientId() ); 1379 SAL_INFO("svx", "disposed ID: " << mnNotifierClientId ); 1380 } 1381 catch( const uno::Exception& ) {} 1382 1383 mnNotifierClientId = -1; 1384 } 1385 1386 try 1387 { 1388 // dispose children 1389 maParaManager.Dispose(); 1390 } 1391 catch( const uno::Exception& ) {} 1392 1393 // quit listen on stale edit source 1394 if( maEditSource.IsValid() ) 1395 EndListening( maEditSource.GetBroadcaster() ); 1396 1397 // clear references 1398 maEditSource.SetEditSource( ::std::unique_ptr< SvxEditSource >() ); 1399 mxFrontEnd = nullptr; 1400 } 1401 FireEvent(const sal_Int16 nEventId,const uno::Any & rNewValue,const uno::Any & rOldValue) const1402 void AccessibleTextHelper_Impl::FireEvent( const sal_Int16 nEventId, const uno::Any& rNewValue, const uno::Any& rOldValue ) const 1403 { 1404 // -- object locked -- 1405 AccessibleEventObject aEvent; 1406 { 1407 osl::MutexGuard aGuard(maMutex); 1408 1409 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper::FireEvent: no event source set"); 1410 1411 if (mxFrontEnd.is()) 1412 aEvent = AccessibleEventObject(mxFrontEnd->getAccessibleContext(), nEventId, 1413 rNewValue, rOldValue); 1414 else 1415 aEvent = AccessibleEventObject(uno::Reference<uno::XInterface>(), nEventId, 1416 rNewValue, rOldValue); 1417 1418 // no locking necessary, FireEvent internally copies listeners 1419 // if someone removes/adds in between Further locking, 1420 // actually, might lead to deadlocks, since we're calling out 1421 // of this object 1422 } 1423 // -- until here -- 1424 1425 FireEvent(aEvent); 1426 } 1427 FireEvent(const AccessibleEventObject & rEvent) const1428 void AccessibleTextHelper_Impl::FireEvent( const AccessibleEventObject& rEvent ) const 1429 { 1430 // #102261# Call global queue for focus events 1431 if( rEvent.EventId == AccessibleStateType::FOCUSED ) 1432 vcl::unohelper::NotifyAccessibleStateEventGlobally( rEvent ); 1433 1434 // #106234# Delegate to EventNotifier 1435 ::comphelper::AccessibleEventNotifier::addEvent( getNotifierClientId(), 1436 rEvent ); 1437 } 1438 1439 // XAccessibleContext getAccessibleChildCount() const1440 sal_Int32 AccessibleTextHelper_Impl::getAccessibleChildCount() const 1441 { 1442 return mnLastVisibleChild - mnFirstVisibleChild + 1; 1443 } 1444 getAccessibleChild(sal_Int32 i)1445 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleChild( sal_Int32 i ) 1446 { 1447 i -= GetStartIndex(); 1448 1449 if( 0 > i || i >= getAccessibleChildCount() || 1450 GetTextForwarder().GetParagraphCount() <= i ) 1451 { 1452 throw lang::IndexOutOfBoundsException("Invalid child index", mxFrontEnd); 1453 } 1454 1455 DBG_ASSERT(mxFrontEnd.is(), "AccessibleTextHelper_Impl::UpdateVisibleChildren: no frontend set"); 1456 1457 if( mxFrontEnd.is() ) 1458 return maParaManager.CreateChild( i, mxFrontEnd, GetEditSource(), mnFirstVisibleChild + i ).first; 1459 else 1460 return nullptr; 1461 } 1462 addAccessibleEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1463 void AccessibleTextHelper_Impl::addAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) 1464 { 1465 if( getNotifierClientId() != -1 ) 1466 ::comphelper::AccessibleEventNotifier::addEventListener( getNotifierClientId(), xListener ); 1467 } 1468 removeAccessibleEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1469 void AccessibleTextHelper_Impl::removeAccessibleEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) 1470 { 1471 if( getNotifierClientId() == -1 ) 1472 return; 1473 1474 const sal_Int32 nListenerCount = ::comphelper::AccessibleEventNotifier::removeEventListener( getNotifierClientId(), xListener ); 1475 if ( !nListenerCount ) 1476 { 1477 // no listeners anymore 1478 // -> revoke ourself. This may lead to the notifier thread dying (if we were the last client), 1479 // and at least to us not firing any events anymore, in case somebody calls 1480 // NotifyAccessibleEvent, again 1481 ::comphelper::AccessibleEventNotifier::TClientId nId( getNotifierClientId() ); 1482 mnNotifierClientId = -1; 1483 ::comphelper::AccessibleEventNotifier::revokeClient( nId ); 1484 } 1485 } 1486 getAccessibleAtPoint(const awt::Point & _aPoint)1487 uno::Reference< XAccessible > AccessibleTextHelper_Impl::getAccessibleAtPoint( const awt::Point& _aPoint ) 1488 { 1489 // make given position relative 1490 if( !mxFrontEnd.is() ) 1491 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd ); 1492 1493 uno::Reference< XAccessibleContext > xFrontEndContext = mxFrontEnd->getAccessibleContext(); 1494 1495 if( !xFrontEndContext.is() ) 1496 throw uno::RuntimeException("AccessibleTextHelper_Impl::getAccessibleAt: frontend invalid", mxFrontEnd ); 1497 1498 uno::Reference< XAccessibleComponent > xFrontEndComponent( xFrontEndContext, uno::UNO_QUERY_THROW ); 1499 1500 // #103862# No longer need to make given position relative 1501 Point aPoint( _aPoint.X, _aPoint.Y ); 1502 1503 // respect EditEngine offset to surrounding shape/cell 1504 aPoint -= GetOffset(); 1505 1506 // convert to EditEngine coordinate system 1507 SvxTextForwarder& rCacheTF = GetTextForwarder(); 1508 Point aLogPoint( GetViewForwarder().PixelToLogic( aPoint, rCacheTF.GetMapMode() ) ); 1509 1510 // iterate over all visible children (including those not yet created) 1511 sal_Int32 nChild; 1512 for( nChild=mnFirstVisibleChild; nChild <= mnLastVisibleChild; ++nChild ) 1513 { 1514 DBG_ASSERT(nChild >= 0, 1515 "AccessibleTextHelper_Impl::getAccessibleAt: index value overflow"); 1516 1517 tools::Rectangle aParaBounds( rCacheTF.GetParaBounds( nChild ) ); 1518 1519 if( aParaBounds.IsInside( aLogPoint ) ) 1520 return getAccessibleChild( nChild - mnFirstVisibleChild + GetStartIndex() ); 1521 } 1522 1523 // found none 1524 return nullptr; 1525 } 1526 1527 1528 // AccessibleTextHelper implementation (simply forwards to impl) 1529 AccessibleTextHelper(::std::unique_ptr<SvxEditSource> && pEditSource)1530 AccessibleTextHelper::AccessibleTextHelper( ::std::unique_ptr< SvxEditSource > && pEditSource ) : 1531 mpImpl( new AccessibleTextHelper_Impl() ) 1532 { 1533 SolarMutexGuard aGuard; 1534 1535 SetEditSource( std::move(pEditSource) ); 1536 } 1537 ~AccessibleTextHelper()1538 AccessibleTextHelper::~AccessibleTextHelper() 1539 { 1540 } 1541 GetEditSource() const1542 const SvxEditSource& AccessibleTextHelper::GetEditSource() const 1543 { 1544 #ifdef DBG_UTIL 1545 mpImpl->CheckInvariants(); 1546 1547 const SvxEditSource& aEditSource = mpImpl->GetEditSource(); 1548 1549 mpImpl->CheckInvariants(); 1550 1551 return aEditSource; 1552 #else 1553 return mpImpl->GetEditSource(); 1554 #endif 1555 } 1556 SetEditSource(::std::unique_ptr<SvxEditSource> && pEditSource)1557 void AccessibleTextHelper::SetEditSource( ::std::unique_ptr< SvxEditSource > && pEditSource ) 1558 { 1559 #ifdef DBG_UTIL 1560 // precondition: solar mutex locked 1561 DBG_TESTSOLARMUTEX(); 1562 1563 mpImpl->CheckInvariants(); 1564 #endif 1565 1566 mpImpl->SetEditSource( std::move(pEditSource) ); 1567 1568 #ifdef DBG_UTIL 1569 mpImpl->CheckInvariants(); 1570 #endif 1571 } 1572 SetEventSource(const uno::Reference<XAccessible> & rInterface)1573 void AccessibleTextHelper::SetEventSource( const uno::Reference< XAccessible >& rInterface ) 1574 { 1575 #ifdef DBG_UTIL 1576 mpImpl->CheckInvariants(); 1577 #endif 1578 1579 mpImpl->SetEventSource( rInterface ); 1580 1581 #ifdef DBG_UTIL 1582 mpImpl->CheckInvariants(); 1583 #endif 1584 } 1585 SetFocus(bool bHaveFocus)1586 void AccessibleTextHelper::SetFocus( bool bHaveFocus ) 1587 { 1588 #ifdef DBG_UTIL 1589 // precondition: solar mutex locked 1590 DBG_TESTSOLARMUTEX(); 1591 1592 mpImpl->CheckInvariants(); 1593 #endif 1594 1595 mpImpl->SetFocus( bHaveFocus ); 1596 1597 #ifdef DBG_UTIL 1598 mpImpl->CheckInvariants(); 1599 #endif 1600 } 1601 HaveFocus()1602 bool AccessibleTextHelper::HaveFocus() 1603 { 1604 #ifdef DBG_UTIL 1605 mpImpl->CheckInvariants(); 1606 1607 bool bRet( mpImpl->HaveFocus() ); 1608 1609 mpImpl->CheckInvariants(); 1610 1611 return bRet; 1612 #else 1613 return mpImpl->HaveFocus(); 1614 #endif 1615 } 1616 SetOffset(const Point & rPoint)1617 void AccessibleTextHelper::SetOffset( const Point& rPoint ) 1618 { 1619 #ifdef DBG_UTIL 1620 // precondition: solar mutex locked 1621 DBG_TESTSOLARMUTEX(); 1622 1623 mpImpl->CheckInvariants(); 1624 #endif 1625 1626 mpImpl->SetOffset( rPoint ); 1627 1628 #ifdef DBG_UTIL 1629 mpImpl->CheckInvariants(); 1630 #endif 1631 } 1632 SetStartIndex(sal_Int32 nOffset)1633 void AccessibleTextHelper::SetStartIndex( sal_Int32 nOffset ) 1634 { 1635 #ifdef DBG_UTIL 1636 // precondition: solar mutex locked 1637 DBG_TESTSOLARMUTEX(); 1638 1639 mpImpl->CheckInvariants(); 1640 #endif 1641 1642 mpImpl->SetStartIndex( nOffset ); 1643 1644 #ifdef DBG_UTIL 1645 mpImpl->CheckInvariants(); 1646 #endif 1647 } 1648 GetStartIndex() const1649 sal_Int32 AccessibleTextHelper::GetStartIndex() const 1650 { 1651 #ifdef DBG_UTIL 1652 mpImpl->CheckInvariants(); 1653 1654 sal_Int32 nOffset = mpImpl->GetStartIndex(); 1655 1656 mpImpl->CheckInvariants(); 1657 1658 return nOffset; 1659 #else 1660 return mpImpl->GetStartIndex(); 1661 #endif 1662 } 1663 SetAdditionalChildStates(const VectorOfStates & rChildStates)1664 void AccessibleTextHelper::SetAdditionalChildStates( const VectorOfStates& rChildStates ) 1665 { 1666 mpImpl->SetAdditionalChildStates( rChildStates ); 1667 } 1668 UpdateChildren()1669 void AccessibleTextHelper::UpdateChildren() 1670 { 1671 #ifdef DBG_UTIL 1672 // precondition: solar mutex locked 1673 DBG_TESTSOLARMUTEX(); 1674 1675 mpImpl->CheckInvariants(); 1676 #endif 1677 1678 mpImpl->UpdateVisibleChildren(); 1679 mpImpl->UpdateBoundRect(); 1680 1681 mpImpl->UpdateSelection(); 1682 1683 #ifdef DBG_UTIL 1684 mpImpl->CheckInvariants(); 1685 #endif 1686 } 1687 Dispose()1688 void AccessibleTextHelper::Dispose() 1689 { 1690 // As Dispose calls ShutdownEditSource, which in turn 1691 // deregisters as listener on the edit source, have to lock 1692 // here 1693 SolarMutexGuard aGuard; 1694 1695 #ifdef DBG_UTIL 1696 mpImpl->CheckInvariants(); 1697 #endif 1698 1699 mpImpl->Dispose(); 1700 1701 #ifdef DBG_UTIL 1702 mpImpl->CheckInvariants(); 1703 #endif 1704 } 1705 1706 // XAccessibleContext GetChildCount() const1707 sal_Int32 AccessibleTextHelper::GetChildCount() const 1708 { 1709 SolarMutexGuard aGuard; 1710 1711 #ifdef DBG_UTIL 1712 mpImpl->CheckInvariants(); 1713 1714 sal_Int32 nRet = mpImpl->getAccessibleChildCount(); 1715 1716 mpImpl->CheckInvariants(); 1717 1718 return nRet; 1719 #else 1720 return mpImpl->getAccessibleChildCount(); 1721 #endif 1722 } 1723 GetChild(sal_Int32 i)1724 uno::Reference< XAccessible > AccessibleTextHelper::GetChild( sal_Int32 i ) 1725 { 1726 SolarMutexGuard aGuard; 1727 1728 #ifdef DBG_UTIL 1729 mpImpl->CheckInvariants(); 1730 1731 uno::Reference< XAccessible > xRet = mpImpl->getAccessibleChild( i ); 1732 1733 mpImpl->CheckInvariants(); 1734 1735 return xRet; 1736 #else 1737 return mpImpl->getAccessibleChild( i ); 1738 #endif 1739 } 1740 AddEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1741 void AccessibleTextHelper::AddEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) 1742 { 1743 #ifdef DBG_UTIL 1744 mpImpl->CheckInvariants(); 1745 1746 mpImpl->addAccessibleEventListener( xListener ); 1747 1748 mpImpl->CheckInvariants(); 1749 #else 1750 mpImpl->addAccessibleEventListener( xListener ); 1751 #endif 1752 } 1753 RemoveEventListener(const uno::Reference<XAccessibleEventListener> & xListener)1754 void AccessibleTextHelper::RemoveEventListener( const uno::Reference< XAccessibleEventListener >& xListener ) 1755 { 1756 #ifdef DBG_UTIL 1757 mpImpl->CheckInvariants(); 1758 1759 mpImpl->removeAccessibleEventListener( xListener ); 1760 1761 mpImpl->CheckInvariants(); 1762 #else 1763 mpImpl->removeAccessibleEventListener( xListener ); 1764 #endif 1765 } 1766 1767 // XAccessibleComponent GetAt(const awt::Point & aPoint)1768 uno::Reference< XAccessible > AccessibleTextHelper::GetAt( const awt::Point& aPoint ) 1769 { 1770 SolarMutexGuard aGuard; 1771 1772 #ifdef DBG_UTIL 1773 mpImpl->CheckInvariants(); 1774 1775 uno::Reference< XAccessible > xChild = mpImpl->getAccessibleAtPoint( aPoint ); 1776 1777 mpImpl->CheckInvariants(); 1778 1779 return xChild; 1780 #else 1781 return mpImpl->getAccessibleAtPoint( aPoint ); 1782 #endif 1783 } 1784 1785 } // end of namespace accessibility 1786 1787 1788 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 1789