tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / reportdesign / source / core / sdr / UndoEnv.cxx
blob649703a34f802cb49e5af9d701c494e3c1d1a770
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
19 #include <UndoActions.hxx>
20 #include <UndoEnv.hxx>
21 #include "formatnormalizer.hxx"
22 #include <conditionupdater.hxx>
23 #include <RptPage.hxx>
24 #include <strings.hrc>
25 #include <RptModel.hxx>
27 #include <com/sun/star/container/XChild.hpp>
28 #include <com/sun/star/beans/theIntrospection.hpp>
29 #include <com/sun/star/beans/PropertyAttribute.hpp>
30 #include <com/sun/star/util/XModifyBroadcaster.hpp>
31 #include <com/sun/star/beans/XIntrospectionAccess.hpp>
32 #include <com/sun/star/beans/XIntrospection.hpp>
34 #include <svl/hint.hxx>
35 #include <tools/debug.hxx>
36 #include <comphelper/diagnose_ex.hxx>
37 #include <vcl/svapp.hxx>
38 #include <dbaccess/dbsubcomponentcontroller.hxx>
39 #include <osl/mutex.hxx>
41 #include <unordered_map>
43 namespace rptui
45 using namespace ::com::sun::star;
46 using namespace uno;
47 using namespace lang;
48 using namespace beans;
49 using namespace util;
50 using namespace container;
51 using namespace report;
53 namespace {
55 struct PropertyInfo
57 bool bIsReadonlyOrTransient;
59 explicit PropertyInfo( const bool i_bIsTransientOrReadOnly )
60 :bIsReadonlyOrTransient( i_bIsTransientOrReadOnly )
67 typedef std::unordered_map< OUString, PropertyInfo > PropertiesInfo;
69 namespace {
71 struct ObjectInfo
73 PropertiesInfo aProperties;
74 Reference< XPropertySet > xPropertyIntrospection;
76 ObjectInfo()
83 typedef ::std::map< Reference< XPropertySet >, ObjectInfo > PropertySetInfoCache;
86 class OXUndoEnvironmentImpl
88 public:
89 OReportModel& m_rModel;
90 PropertySetInfoCache m_aPropertySetCache;
91 FormatNormalizer m_aFormatNormalizer;
92 ConditionUpdater m_aConditionUpdater;
93 ::osl::Mutex m_aMutex;
94 ::std::vector< uno::Reference< container::XChild> > m_aSections;
95 Reference< XIntrospection > m_xIntrospection;
96 oslInterlockedCount m_nLocks;
97 bool m_bReadOnly;
98 bool m_bIsUndo;
100 explicit OXUndoEnvironmentImpl(OReportModel& _rModel);
101 OXUndoEnvironmentImpl(const OXUndoEnvironmentImpl&) = delete;
102 OXUndoEnvironmentImpl& operator=(const OXUndoEnvironmentImpl&) = delete;
105 OXUndoEnvironmentImpl::OXUndoEnvironmentImpl(OReportModel& _rModel) : m_rModel(_rModel)
106 ,m_aFormatNormalizer( _rModel )
107 ,m_nLocks(0)
108 ,m_bReadOnly(false)
109 ,m_bIsUndo(false)
114 OXUndoEnvironment::OXUndoEnvironment(OReportModel& _rModel)
115 :m_pImpl(new OXUndoEnvironmentImpl(_rModel) )
117 StartListening(m_pImpl->m_rModel);
121 OXUndoEnvironment::~OXUndoEnvironment()
125 void OXUndoEnvironment::Lock()
127 OSL_ENSURE(m_refCount,"Illegal call to dead object!");
128 osl_atomic_increment( &m_pImpl->m_nLocks );
130 void OXUndoEnvironment::UnLock()
132 OSL_ENSURE(m_refCount,"Illegal call to dead object!");
134 osl_atomic_decrement( &m_pImpl->m_nLocks );
136 bool OXUndoEnvironment::IsLocked() const { return m_pImpl->m_nLocks != 0; }
138 void OXUndoEnvironment::RemoveSection(OReportPage const * _pPage)
140 if ( _pPage )
142 Reference< XInterface > xSection(_pPage->getSection());
143 if ( xSection.is() )
144 RemoveElement( xSection );
148 void OXUndoEnvironment::Clear(const Accessor& /*_r*/)
150 OUndoEnvLock aLock(*this);
152 m_pImpl->m_aPropertySetCache.clear();
154 sal_uInt16 nCount = m_pImpl->m_rModel.GetPageCount();
155 sal_uInt16 i;
156 for (i = 0; i < nCount; i++)
158 OReportPage* pPage = dynamic_cast<OReportPage*>( m_pImpl->m_rModel.GetPage(i) );
159 RemoveSection(pPage);
162 nCount = m_pImpl->m_rModel.GetMasterPageCount();
163 for (i = 0; i < nCount; i++)
165 OReportPage* pPage = dynamic_cast<OReportPage*>( m_pImpl->m_rModel.GetMasterPage(i) );
166 RemoveSection(pPage);
169 m_pImpl->m_aSections.clear();
171 if (IsListening(m_pImpl->m_rModel))
172 EndListening(m_pImpl->m_rModel);
176 void OXUndoEnvironment::ModeChanged()
178 m_pImpl->m_bReadOnly = !m_pImpl->m_bReadOnly;
180 if (!m_pImpl->m_bReadOnly)
181 StartListening(m_pImpl->m_rModel);
182 else
183 EndListening(m_pImpl->m_rModel);
187 void OXUndoEnvironment::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
189 if (rHint.GetId() == SfxHintId::ModeChanged )
190 ModeChanged();
193 // XEventListener
195 void SAL_CALL OXUndoEnvironment::disposing(const EventObject& e)
197 // check if it's an object we have cached information about
198 Reference< XPropertySet > xSourceSet(e.Source, UNO_QUERY);
199 if ( xSourceSet.is() )
201 uno::Reference< report::XSection> xSection(xSourceSet,uno::UNO_QUERY);
202 if ( xSection.is() )
203 RemoveSection(xSection);
204 else
205 RemoveElement(xSourceSet);
209 // XPropertyChangeListener
211 void SAL_CALL OXUndoEnvironment::propertyChange( const PropertyChangeEvent& _rEvent )
214 ::osl::ClearableMutexGuard aGuard( m_pImpl->m_aMutex );
216 if ( IsLocked() )
217 return;
219 Reference< XPropertySet > xSet( _rEvent.Source, UNO_QUERY );
220 if (!xSet.is())
221 return;
223 dbaui::DBSubComponentController* pController = m_pImpl->m_rModel.getController();
224 if ( !pController )
225 return;
227 // no Undo for transient and readonly props.
228 // let's see if we know something about the set
229 PropertySetInfoCache::iterator objectPos = m_pImpl->m_aPropertySetCache.find(xSet);
230 if (objectPos == m_pImpl->m_aPropertySetCache.end())
232 objectPos = m_pImpl->m_aPropertySetCache.emplace( xSet, ObjectInfo() ).first;
233 DBG_ASSERT(objectPos != m_pImpl->m_aPropertySetCache.end(), "OXUndoEnvironment::propertyChange : just inserted it... why it's not there?");
235 if ( objectPos == m_pImpl->m_aPropertySetCache.end() )
236 return;
238 // now we have access to the cached info about the set
239 // let's see what we know about the property
240 ObjectInfo& rObjectInfo = objectPos->second;
241 PropertiesInfo::const_iterator aPropertyPos = rObjectInfo.aProperties.find( _rEvent.PropertyName );
242 if ( aPropertyPos == rObjectInfo.aProperties.end() )
243 { // nothing 'til now... have to change this...
244 // the attributes
245 Reference< XPropertySetInfo > xPSI( xSet->getPropertySetInfo(), UNO_SET_THROW );
246 sal_Int32 nPropertyAttributes = 0;
249 if ( xPSI->hasPropertyByName( _rEvent.PropertyName ) )
251 nPropertyAttributes = xPSI->getPropertyByName( _rEvent.PropertyName ).Attributes;
253 else
255 // it's perfectly valid for a component to notify a change in a property which it doesn't have - as long
256 // as it has an attribute with this name
257 if ( !rObjectInfo.xPropertyIntrospection.is() )
259 if ( !m_pImpl->m_xIntrospection.is() )
261 m_pImpl->m_xIntrospection = theIntrospection::get( m_pImpl->m_rModel.getController()->getORB() );
263 Reference< XIntrospectionAccess > xIntrospection(
264 m_pImpl->m_xIntrospection->inspect( Any( _rEvent.Source ) ),
265 UNO_SET_THROW
267 rObjectInfo.xPropertyIntrospection.set( xIntrospection->queryAdapter( cppu::UnoType<XPropertySet>::get() ), UNO_QUERY_THROW );
269 if ( rObjectInfo.xPropertyIntrospection.is() )
271 xPSI.set( rObjectInfo.xPropertyIntrospection->getPropertySetInfo(), UNO_SET_THROW );
272 nPropertyAttributes = xPSI->getPropertyByName( _rEvent.PropertyName ).Attributes;
276 catch( const Exception& )
278 DBG_UNHANDLED_EXCEPTION("reportdesign");
280 const bool bTransReadOnly =
281 ( ( nPropertyAttributes & PropertyAttribute::READONLY ) != 0 )
282 || ( ( nPropertyAttributes & PropertyAttribute::TRANSIENT ) != 0 );
284 // insert the new entry
285 aPropertyPos = rObjectInfo.aProperties.emplace(
286 _rEvent.PropertyName,
287 PropertyInfo( bTransReadOnly )
288 ).first;
289 DBG_ASSERT(aPropertyPos != rObjectInfo.aProperties.end(), "OXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
292 implSetModified();
294 // now we have access to the cached info about the property affected
295 // and are able to decide whether or not we need an undo action
297 // no UNDO for transient/readonly properties
298 if ( aPropertyPos->second.bIsReadonlyOrTransient )
299 return;
301 // give components with sub responsibilities a chance
302 m_pImpl->m_aFormatNormalizer.notifyPropertyChange( _rEvent );
303 m_pImpl->m_aConditionUpdater.notifyPropertyChange( _rEvent );
305 aGuard.clear();
306 // TODO: this is a potential race condition: two threads here could in theory
307 // add their undo actions out-of-order
309 SolarMutexGuard aSolarGuard;
310 std::unique_ptr<ORptUndoPropertyAction> pUndo;
313 uno::Reference< report::XSection> xSection( xSet, uno::UNO_QUERY );
314 if ( xSection.is() )
316 uno::Reference< report::XGroup> xGroup = xSection->getGroup();
317 if ( xGroup.is() )
318 pUndo.reset(new OUndoPropertyGroupSectionAction( m_pImpl->m_rModel, _rEvent, OGroupHelper::getMemberFunction( xSection ), xGroup ));
319 else
320 pUndo.reset(new OUndoPropertyReportSectionAction( m_pImpl->m_rModel, _rEvent, OReportHelper::getMemberFunction( xSection ), xSection->getReportDefinition() ));
323 catch(const Exception&)
325 DBG_UNHANDLED_EXCEPTION("reportdesign");
328 if ( pUndo == nullptr )
329 pUndo.reset(new ORptUndoPropertyAction( m_pImpl->m_rModel, _rEvent ));
331 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction( std::move(pUndo) );
332 pController->InvalidateAll();
335 ::std::vector< uno::Reference< container::XChild> >::const_iterator OXUndoEnvironment::getSection(const Reference<container::XChild>& _xContainer) const
337 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = m_pImpl->m_aSections.end();
338 if ( _xContainer.is() )
340 aFind = ::std::find(m_pImpl->m_aSections.begin(),m_pImpl->m_aSections.end(),_xContainer);
342 if ( aFind == m_pImpl->m_aSections.end() )
344 Reference<container::XChild> xParent(_xContainer->getParent(),uno::UNO_QUERY);
345 aFind = getSection(xParent);
348 return aFind;
350 // XContainerListener
352 void SAL_CALL OXUndoEnvironment::elementInserted(const ContainerEvent& evt)
354 SolarMutexGuard aSolarGuard;
355 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
357 // new listener object
358 Reference< uno::XInterface > xIface( evt.Element, UNO_QUERY );
359 if ( !IsLocked() )
361 Reference< report::XReportComponent > xReportComponent( xIface, UNO_QUERY );
362 if ( xReportComponent.is() )
364 Reference< report::XSection > xContainer(evt.Source,uno::UNO_QUERY);
366 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = getSection(xContainer);
368 if ( aFind != m_pImpl->m_aSections.end() )
370 OUndoEnvLock aLock(*this);
373 OReportPage* pPage = m_pImpl->m_rModel.getPage(uno::Reference< report::XSection>(*aFind,uno::UNO_QUERY));
374 OSL_ENSURE(pPage,"No page could be found for section!");
375 if ( pPage )
376 pPage->insertObject(xReportComponent);
378 catch(uno::Exception&)
380 DBG_UNHANDLED_EXCEPTION("reportdesign");
385 else
387 uno::Reference< report::XFunctions> xContainer(evt.Source,uno::UNO_QUERY);
388 if ( xContainer.is() )
390 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction(
391 std::make_unique<OUndoContainerAction>( m_pImpl->m_rModel, rptui::Inserted, xContainer.get(),
392 xIface, RID_STR_UNDO_ADDFUNCTION ) );
397 AddElement(xIface);
399 implSetModified();
403 void OXUndoEnvironment::implSetModified()
405 m_pImpl->m_rModel.SetModified( true );
409 void SAL_CALL OXUndoEnvironment::elementReplaced(const ContainerEvent& evt)
411 SolarMutexGuard aSolarGuard;
412 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
414 Reference< XInterface > xIface(evt.ReplacedElement,uno::UNO_QUERY);
415 OSL_ENSURE(xIface.is(), "OXUndoEnvironment::elementReplaced: invalid container notification!");
416 RemoveElement(xIface);
418 xIface.set(evt.Element,uno::UNO_QUERY);
419 AddElement(xIface);
421 implSetModified();
425 void SAL_CALL OXUndoEnvironment::elementRemoved(const ContainerEvent& evt)
427 SolarMutexGuard aSolarGuard;
428 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
430 Reference< uno::XInterface > xIface( evt.Element, UNO_QUERY );
431 if ( !IsLocked() )
433 Reference< report::XSection > xContainer(evt.Source,uno::UNO_QUERY);
434 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = getSection(xContainer);
436 Reference< report::XReportComponent > xReportComponent( xIface, UNO_QUERY );
437 if ( aFind != m_pImpl->m_aSections.end() && xReportComponent.is() )
439 OXUndoEnvironment::OUndoEnvLock aLock(*this);
442 OReportPage* pPage = m_pImpl->m_rModel.getPage(uno::Reference< report::XSection >( *aFind, uno::UNO_QUERY_THROW ) );
443 OSL_ENSURE( pPage, "OXUndoEnvironment::elementRemoved: no page for the section!" );
444 if ( pPage )
445 pPage->removeSdrObject(xReportComponent);
447 catch(const uno::Exception&)
449 DBG_UNHANDLED_EXCEPTION("reportdesign");
452 else
454 uno::Reference< report::XFunctions> xFunctions(evt.Source,uno::UNO_QUERY);
455 if ( xFunctions.is() )
457 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction( std::make_unique<OUndoContainerAction>(
458 m_pImpl->m_rModel, rptui::Removed, xFunctions.get(), xIface, RID_STR_UNDO_ADDFUNCTION ) );
463 if ( xIface.is() )
464 RemoveElement(xIface);
466 implSetModified();
470 void SAL_CALL OXUndoEnvironment::modified( const EventObject& /*aEvent*/ )
472 implSetModified();
476 void OXUndoEnvironment::AddSection(const Reference< report::XSection > & _xSection)
478 OUndoEnvLock aLock(*this);
481 uno::Reference<container::XChild> xChild = _xSection;
482 m_pImpl->m_aSections.push_back(xChild);
483 AddElement(_xSection);
485 catch(const uno::Exception&)
487 DBG_UNHANDLED_EXCEPTION("reportdesign");
492 void OXUndoEnvironment::RemoveSection(const Reference< report::XSection > & _xSection)
494 OUndoEnvLock aLock(*this);
497 uno::Reference<container::XChild> xChild(_xSection);
498 std::erase(m_pImpl->m_aSections, xChild);
499 RemoveElement(_xSection);
501 catch(uno::Exception&){}
505 void OXUndoEnvironment::switchListening( const Reference< XIndexAccess >& _rxContainer, bool _bStartListening )
507 OSL_PRECOND( _rxContainer.is(), "OXUndoEnvironment::switchListening: invalid container!" );
508 if ( !_rxContainer.is() )
509 return;
513 // also handle all children of this element
514 Reference< XInterface > xInterface;
515 sal_Int32 nCount = _rxContainer->getCount();
516 for(sal_Int32 i = 0;i != nCount;++i)
518 xInterface.set(_rxContainer->getByIndex( i ),uno::UNO_QUERY);
519 if ( _bStartListening )
520 AddElement( xInterface );
521 else
522 RemoveElement( xInterface );
525 // be notified of any changes in the container elements
526 Reference< XContainer > xSimpleContainer( _rxContainer, UNO_QUERY );
527 if ( xSimpleContainer.is() )
529 if ( _bStartListening )
530 xSimpleContainer->addContainerListener( this );
531 else
532 xSimpleContainer->removeContainerListener( this );
535 catch( const Exception& )
537 DBG_UNHANDLED_EXCEPTION("reportdesign");
542 void OXUndoEnvironment::switchListening( const Reference< XInterface >& _rxObject, bool _bStartListening )
544 OSL_PRECOND( _rxObject.is(), "OXUndoEnvironment::switchListening: how should I listen at a NULL object?" );
548 if ( !m_pImpl->m_bReadOnly )
550 Reference< XPropertySet > xProps( _rxObject, UNO_QUERY );
551 if ( xProps.is() )
553 if ( _bStartListening )
554 xProps->addPropertyChangeListener( OUString(), this );
555 else
556 xProps->removePropertyChangeListener( OUString(), this );
560 Reference< XModifyBroadcaster > xBroadcaster( _rxObject, UNO_QUERY );
561 if ( xBroadcaster.is() )
563 if ( _bStartListening )
564 xBroadcaster->addModifyListener( this );
565 else
566 xBroadcaster->removeModifyListener( this );
569 catch( const Exception& )
575 void OXUndoEnvironment::AddElement(const Reference< XInterface >& _rxElement )
577 if ( !IsLocked() )
578 m_pImpl->m_aFormatNormalizer.notifyElementInserted( _rxElement );
580 // if it's a container, start listening at all elements
581 Reference< XIndexAccess > xContainer( _rxElement, UNO_QUERY );
582 if ( xContainer.is() )
583 switchListening( xContainer, true );
585 switchListening( _rxElement, true );
589 void OXUndoEnvironment::RemoveElement(const Reference< XInterface >& _rxElement)
591 uno::Reference<beans::XPropertySet> xProp(_rxElement,uno::UNO_QUERY);
592 if (!m_pImpl->m_aPropertySetCache.empty())
593 m_pImpl->m_aPropertySetCache.erase(xProp);
594 switchListening( _rxElement, false );
596 Reference< XIndexAccess > xContainer( _rxElement, UNO_QUERY );
597 if ( xContainer.is() )
598 switchListening( xContainer, false );
601 void OXUndoEnvironment::SetUndoMode(bool _bUndo)
603 m_pImpl->m_bIsUndo = _bUndo;
606 bool OXUndoEnvironment::IsUndoMode() const
608 return m_pImpl->m_bIsUndo;
611 } // rptui
614 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */