lok: vcl: fix multiple floatwin removal case more robustly.
[LibreOffice.git] / reportdesign / source / core / sdr / UndoEnv.cxx
blob5d6b46cff3a674630b3d109da2d0fe02a0b4ee26
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 <strings.hxx>
24 #include <rptui_slotid.hrc>
25 #include <RptDef.hxx>
26 #include <RptObject.hxx>
27 #include <RptPage.hxx>
28 #include <strings.hrc>
29 #include <RptModel.hxx>
31 #include <com/sun/star/container/XChild.hpp>
32 #include <com/sun/star/container/XNameContainer.hpp>
33 #include <com/sun/star/beans/theIntrospection.hpp>
34 #include <com/sun/star/beans/PropertyAttribute.hpp>
35 #include <com/sun/star/util/XModifyBroadcaster.hpp>
36 #include <com/sun/star/beans/XIntrospectionAccess.hpp>
37 #include <com/sun/star/beans/XIntrospection.hpp>
39 #include <connectivity/dbtools.hxx>
40 #include <svl/hint.hxx>
41 #include <tools/diagnose_ex.h>
42 #include <comphelper/stl_types.hxx>
43 #include <vcl/svapp.hxx>
44 #include <dbaccess/dbsubcomponentcontroller.hxx>
45 #include <svx/unoshape.hxx>
46 #include <osl/mutex.hxx>
47 #include <o3tl/make_unique.hxx>
49 namespace rptui
51 using namespace ::com::sun::star;
52 using namespace uno;
53 using namespace lang;
54 using namespace script;
55 using namespace beans;
56 using namespace awt;
57 using namespace util;
58 using namespace container;
59 using namespace report;
62 struct PropertyInfo
64 bool const bIsReadonlyOrTransient;
66 explicit PropertyInfo( const bool i_bIsTransientOrReadOnly )
67 :bIsReadonlyOrTransient( i_bIsTransientOrReadOnly )
72 typedef std::unordered_map< OUString, PropertyInfo > PropertiesInfo;
74 struct ObjectInfo
76 PropertiesInfo aProperties;
77 Reference< XPropertySet > xPropertyIntrospection;
79 ObjectInfo()
80 :aProperties()
81 ,xPropertyIntrospection()
86 typedef ::std::map< Reference< XPropertySet >, ObjectInfo, ::comphelper::OInterfaceCompare< XPropertySet > > PropertySetInfoCache;
89 class OXUndoEnvironmentImpl
91 public:
92 OReportModel& m_rModel;
93 PropertySetInfoCache m_aPropertySetCache;
94 FormatNormalizer m_aFormatNormalizer;
95 ConditionUpdater m_aConditionUpdater;
96 ::osl::Mutex m_aMutex;
97 ::std::vector< uno::Reference< container::XChild> > m_aSections;
98 Reference< XIntrospection > m_xIntrospection;
99 oslInterlockedCount m_nLocks;
100 bool m_bReadOnly;
101 bool m_bIsUndo;
103 explicit OXUndoEnvironmentImpl(OReportModel& _rModel);
104 OXUndoEnvironmentImpl(const OXUndoEnvironmentImpl&) = delete;
105 OXUndoEnvironmentImpl& operator=(const OXUndoEnvironmentImpl&) = delete;
108 OXUndoEnvironmentImpl::OXUndoEnvironmentImpl(OReportModel& _rModel) : m_rModel(_rModel)
109 ,m_aFormatNormalizer( _rModel )
110 ,m_aConditionUpdater()
111 ,m_nLocks(0)
112 ,m_bReadOnly(false)
113 ,m_bIsUndo(false)
118 OXUndoEnvironment::OXUndoEnvironment(OReportModel& _rModel)
119 :m_pImpl(new OXUndoEnvironmentImpl(_rModel) )
121 StartListening(m_pImpl->m_rModel);
125 OXUndoEnvironment::~OXUndoEnvironment()
129 void OXUndoEnvironment::Lock()
131 OSL_ENSURE(m_refCount,"Illegal call to dead object!");
132 osl_atomic_increment( &m_pImpl->m_nLocks );
134 void OXUndoEnvironment::UnLock()
136 OSL_ENSURE(m_refCount,"Illegal call to dead object!");
138 osl_atomic_decrement( &m_pImpl->m_nLocks );
140 bool OXUndoEnvironment::IsLocked() const { return m_pImpl->m_nLocks != 0; }
142 void OXUndoEnvironment::RemoveSection(OReportPage const * _pPage)
144 if ( _pPage )
146 Reference< XInterface > xSection(_pPage->getSection());
147 if ( xSection.is() )
148 RemoveElement( xSection );
152 void OXUndoEnvironment::Clear(const Accessor& /*_r*/)
154 OUndoEnvLock aLock(*this);
156 m_pImpl->m_aPropertySetCache.clear();
158 sal_uInt16 nCount = m_pImpl->m_rModel.GetPageCount();
159 sal_uInt16 i;
160 for (i = 0; i < nCount; i++)
162 OReportPage* pPage = dynamic_cast<OReportPage*>( m_pImpl->m_rModel.GetPage(i) );
163 RemoveSection(pPage);
166 nCount = m_pImpl->m_rModel.GetMasterPageCount();
167 for (i = 0; i < nCount; i++)
169 OReportPage* pPage = dynamic_cast<OReportPage*>( m_pImpl->m_rModel.GetMasterPage(i) );
170 RemoveSection(pPage);
173 m_pImpl->m_aSections.clear();
175 if (IsListening(m_pImpl->m_rModel))
176 EndListening(m_pImpl->m_rModel);
180 void OXUndoEnvironment::ModeChanged()
182 m_pImpl->m_bReadOnly = !m_pImpl->m_bReadOnly;
184 if (!m_pImpl->m_bReadOnly)
185 StartListening(m_pImpl->m_rModel);
186 else
187 EndListening(m_pImpl->m_rModel);
191 void OXUndoEnvironment::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
193 if (rHint.GetId() == SfxHintId::ModeChanged )
194 ModeChanged();
197 // XEventListener
199 void SAL_CALL OXUndoEnvironment::disposing(const EventObject& e)
201 // check if it's an object we have cached information about
202 Reference< XPropertySet > xSourceSet(e.Source, UNO_QUERY);
203 if ( xSourceSet.is() )
205 uno::Reference< report::XSection> xSection(xSourceSet,uno::UNO_QUERY);
206 if ( xSection.is() )
207 RemoveSection(xSection);
208 else
209 RemoveElement(xSourceSet);
213 // XPropertyChangeListener
215 void SAL_CALL OXUndoEnvironment::propertyChange( const PropertyChangeEvent& _rEvent )
218 ::osl::ClearableMutexGuard aGuard( m_pImpl->m_aMutex );
220 if ( IsLocked() )
221 return;
223 Reference< XPropertySet > xSet( _rEvent.Source, UNO_QUERY );
224 if (!xSet.is())
225 return;
227 dbaui::DBSubComponentController* pController = m_pImpl->m_rModel.getController();
228 if ( !pController )
229 return;
231 // no Undo for transient and readonly props.
232 // let's see if we know something about the set
233 PropertySetInfoCache::iterator objectPos = m_pImpl->m_aPropertySetCache.find(xSet);
234 if (objectPos == m_pImpl->m_aPropertySetCache.end())
236 objectPos = m_pImpl->m_aPropertySetCache.emplace( xSet, ObjectInfo() ).first;
237 DBG_ASSERT(objectPos != m_pImpl->m_aPropertySetCache.end(), "OXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
239 if ( objectPos == m_pImpl->m_aPropertySetCache.end() )
240 return;
242 // now we have access to the cached info about the set
243 // let's see what we know about the property
244 ObjectInfo& rObjectInfo = objectPos->second;
245 PropertiesInfo::const_iterator aPropertyPos = rObjectInfo.aProperties.find( _rEvent.PropertyName );
246 if ( aPropertyPos == rObjectInfo.aProperties.end() )
247 { // nothing 'til now ... have to change this ....
248 // the attributes
249 Reference< XPropertySetInfo > xPSI( xSet->getPropertySetInfo(), UNO_SET_THROW );
250 sal_Int32 nPropertyAttributes = 0;
253 if ( xPSI->hasPropertyByName( _rEvent.PropertyName ) )
255 nPropertyAttributes = xPSI->getPropertyByName( _rEvent.PropertyName ).Attributes;
257 else
259 // it's perfectly valid for a component to notify a change in a property which it doesn't have - as long
260 // as it has an attribute with this name
261 if ( !rObjectInfo.xPropertyIntrospection.is() )
263 if ( !m_pImpl->m_xIntrospection.is() )
265 m_pImpl->m_xIntrospection = theIntrospection::get( m_pImpl->m_rModel.getController()->getORB() );
267 Reference< XIntrospectionAccess > xIntrospection(
268 m_pImpl->m_xIntrospection->inspect( makeAny( _rEvent.Source ) ),
269 UNO_SET_THROW
271 rObjectInfo.xPropertyIntrospection.set( xIntrospection->queryAdapter( cppu::UnoType<XPropertySet>::get() ), UNO_QUERY_THROW );
273 if ( rObjectInfo.xPropertyIntrospection.is() )
275 xPSI.set( rObjectInfo.xPropertyIntrospection->getPropertySetInfo(), UNO_SET_THROW );
276 nPropertyAttributes = xPSI->getPropertyByName( _rEvent.PropertyName ).Attributes;
280 catch( const Exception& )
282 DBG_UNHANDLED_EXCEPTION("reportdesign");
284 const bool bTransReadOnly =
285 ( ( nPropertyAttributes & PropertyAttribute::READONLY ) != 0 )
286 || ( ( nPropertyAttributes & PropertyAttribute::TRANSIENT ) != 0 );
288 // insert the new entry
289 aPropertyPos = rObjectInfo.aProperties.emplace(
290 _rEvent.PropertyName,
291 PropertyInfo( bTransReadOnly )
292 ).first;
293 DBG_ASSERT(aPropertyPos != rObjectInfo.aProperties.end(), "OXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
296 implSetModified();
298 // now we have access to the cached info about the property affected
299 // and are able to decide whether or not we need an undo action
301 // no UNDO for transient/readonly properties
302 if ( aPropertyPos->second.bIsReadonlyOrTransient )
303 return;
305 // give components with sub responsibilities a chance
306 m_pImpl->m_aFormatNormalizer.notifyPropertyChange( _rEvent );
307 m_pImpl->m_aConditionUpdater.notifyPropertyChange( _rEvent );
309 aGuard.clear();
310 // TODO: this is a potential race condition: two threads here could in theory
311 // add their undo actions out-of-order
313 SolarMutexGuard aSolarGuard;
314 std::unique_ptr<ORptUndoPropertyAction> pUndo;
317 uno::Reference< report::XSection> xSection( xSet, uno::UNO_QUERY );
318 if ( xSection.is() )
320 uno::Reference< report::XGroup> xGroup = xSection->getGroup();
321 if ( xGroup.is() )
322 pUndo.reset(new OUndoPropertyGroupSectionAction( m_pImpl->m_rModel, _rEvent, OGroupHelper::getMemberFunction( xSection ), xGroup ));
323 else
324 pUndo.reset(new OUndoPropertyReportSectionAction( m_pImpl->m_rModel, _rEvent, OReportHelper::getMemberFunction( xSection ), xSection->getReportDefinition() ));
327 catch(const Exception&)
329 DBG_UNHANDLED_EXCEPTION("reportdesign");
332 if ( pUndo == nullptr )
333 pUndo.reset(new ORptUndoPropertyAction( m_pImpl->m_rModel, _rEvent ));
335 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction( std::move(pUndo) );
336 pController->InvalidateAll();
339 ::std::vector< uno::Reference< container::XChild> >::const_iterator OXUndoEnvironment::getSection(const Reference<container::XChild>& _xContainer) const
341 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = m_pImpl->m_aSections.end();
342 if ( _xContainer.is() )
344 aFind = ::std::find(m_pImpl->m_aSections.begin(),m_pImpl->m_aSections.end(),_xContainer);
346 if ( aFind == m_pImpl->m_aSections.end() )
348 Reference<container::XChild> xParent(_xContainer->getParent(),uno::UNO_QUERY);
349 aFind = getSection(xParent);
352 return aFind;
354 // XContainerListener
356 void SAL_CALL OXUndoEnvironment::elementInserted(const ContainerEvent& evt)
358 SolarMutexGuard aSolarGuard;
359 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
361 // new listener object
362 Reference< uno::XInterface > xIface( evt.Element, UNO_QUERY );
363 if ( !IsLocked() )
365 Reference< report::XReportComponent > xReportComponent( xIface, UNO_QUERY );
366 if ( xReportComponent.is() )
368 Reference< report::XSection > xContainer(evt.Source,uno::UNO_QUERY);
370 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = getSection(xContainer.get());
372 if ( aFind != m_pImpl->m_aSections.end() )
374 OUndoEnvLock aLock(*this);
377 OReportPage* pPage = m_pImpl->m_rModel.getPage(uno::Reference< report::XSection>(*aFind,uno::UNO_QUERY));
378 OSL_ENSURE(pPage,"No page could be found for section!");
379 if ( pPage )
380 pPage->insertObject(xReportComponent);
382 catch(uno::Exception&)
384 DBG_UNHANDLED_EXCEPTION("reportdesign");
389 else
391 uno::Reference< report::XFunctions> xContainer(evt.Source,uno::UNO_QUERY);
392 if ( xContainer.is() )
394 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction(
395 o3tl::make_unique<OUndoContainerAction>( m_pImpl->m_rModel, rptui::Inserted, xContainer.get(),
396 xIface, RID_STR_UNDO_ADDFUNCTION ) );
401 AddElement(xIface);
403 implSetModified();
407 void OXUndoEnvironment::implSetModified()
409 m_pImpl->m_rModel.SetModified( true );
413 void SAL_CALL OXUndoEnvironment::elementReplaced(const ContainerEvent& evt)
415 SolarMutexGuard aSolarGuard;
416 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
418 Reference< XInterface > xIface(evt.ReplacedElement,uno::UNO_QUERY);
419 OSL_ENSURE(xIface.is(), "OXUndoEnvironment::elementReplaced: invalid container notification!");
420 RemoveElement(xIface);
422 xIface.set(evt.Element,uno::UNO_QUERY);
423 AddElement(xIface);
425 implSetModified();
429 void SAL_CALL OXUndoEnvironment::elementRemoved(const ContainerEvent& evt)
431 SolarMutexGuard aSolarGuard;
432 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
434 Reference< uno::XInterface > xIface( evt.Element, UNO_QUERY );
435 if ( !IsLocked() )
437 Reference< report::XSection > xContainer(evt.Source,uno::UNO_QUERY);
438 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = getSection(xContainer.get());
440 Reference< report::XReportComponent > xReportComponent( xIface, UNO_QUERY );
441 if ( aFind != m_pImpl->m_aSections.end() && xReportComponent.is() )
443 OXUndoEnvironment::OUndoEnvLock aLock(*this);
446 OReportPage* pPage = m_pImpl->m_rModel.getPage(uno::Reference< report::XSection >( *aFind, uno::UNO_QUERY_THROW ) );
447 OSL_ENSURE( pPage, "OXUndoEnvironment::elementRemoved: no page for the section!" );
448 if ( pPage )
449 pPage->removeSdrObject(xReportComponent);
451 catch(const uno::Exception&)
453 DBG_UNHANDLED_EXCEPTION("reportdesign");
456 else
458 uno::Reference< report::XFunctions> xFunctions(evt.Source,uno::UNO_QUERY);
459 if ( xFunctions.is() )
461 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction( o3tl::make_unique<OUndoContainerAction>(
462 m_pImpl->m_rModel, rptui::Removed, xFunctions.get(), xIface, RID_STR_UNDO_ADDFUNCTION ) );
467 if ( xIface.is() )
468 RemoveElement(xIface);
470 implSetModified();
474 void SAL_CALL OXUndoEnvironment::modified( const EventObject& /*aEvent*/ )
476 implSetModified();
480 void OXUndoEnvironment::AddSection(const Reference< report::XSection > & _xSection)
482 OUndoEnvLock aLock(*this);
485 uno::Reference<container::XChild> xChild = _xSection.get();
486 m_pImpl->m_aSections.push_back(xChild);
487 Reference< XInterface > xInt(_xSection);
488 AddElement(xInt);
490 catch(const uno::Exception&)
492 DBG_UNHANDLED_EXCEPTION("reportdesign");
497 void OXUndoEnvironment::RemoveSection(const Reference< report::XSection > & _xSection)
499 OUndoEnvLock aLock(*this);
502 uno::Reference<container::XChild> xChild(_xSection.get());
503 m_pImpl->m_aSections.erase(::std::remove(m_pImpl->m_aSections.begin(),m_pImpl->m_aSections.end(),
504 xChild), m_pImpl->m_aSections.end());
505 Reference< XInterface > xInt(_xSection);
506 RemoveElement(xInt);
508 catch(uno::Exception&){}
512 void OXUndoEnvironment::switchListening( const Reference< XIndexAccess >& _rxContainer, bool _bStartListening )
514 OSL_PRECOND( _rxContainer.is(), "OXUndoEnvironment::switchListening: invalid container!" );
515 if ( !_rxContainer.is() )
516 return;
520 // also handle all children of this element
521 Reference< XInterface > xInterface;
522 sal_Int32 nCount = _rxContainer->getCount();
523 for(sal_Int32 i = 0;i != nCount;++i)
525 xInterface.set(_rxContainer->getByIndex( i ),uno::UNO_QUERY);
526 if ( _bStartListening )
527 AddElement( xInterface );
528 else
529 RemoveElement( xInterface );
532 // be notified of any changes in the container elements
533 Reference< XContainer > xSimpleContainer( _rxContainer, UNO_QUERY );
534 if ( xSimpleContainer.is() )
536 if ( _bStartListening )
537 xSimpleContainer->addContainerListener( this );
538 else
539 xSimpleContainer->removeContainerListener( this );
542 catch( const Exception& )
544 DBG_UNHANDLED_EXCEPTION("reportdesign");
549 void OXUndoEnvironment::switchListening( const Reference< XInterface >& _rxObject, bool _bStartListening )
551 OSL_PRECOND( _rxObject.is(), "OXUndoEnvironment::switchListening: how should I listen at a NULL object?" );
555 if ( !m_pImpl->m_bReadOnly )
557 Reference< XPropertySet > xProps( _rxObject, UNO_QUERY );
558 if ( xProps.is() )
560 if ( _bStartListening )
561 xProps->addPropertyChangeListener( OUString(), this );
562 else
563 xProps->removePropertyChangeListener( OUString(), this );
567 Reference< XModifyBroadcaster > xBroadcaster( _rxObject, UNO_QUERY );
568 if ( xBroadcaster.is() )
570 if ( _bStartListening )
571 xBroadcaster->addModifyListener( this );
572 else
573 xBroadcaster->removeModifyListener( this );
576 catch( const Exception& )
582 void OXUndoEnvironment::AddElement(const Reference< XInterface >& _rxElement )
584 if ( !IsLocked() )
585 m_pImpl->m_aFormatNormalizer.notifyElementInserted( _rxElement );
587 // if it's a container, start listening at all elements
588 Reference< XIndexAccess > xContainer( _rxElement, UNO_QUERY );
589 if ( xContainer.is() )
590 switchListening( xContainer, true );
592 switchListening( _rxElement, true );
596 void OXUndoEnvironment::RemoveElement(const Reference< XInterface >& _rxElement)
598 uno::Reference<beans::XPropertySet> xProp(_rxElement,uno::UNO_QUERY);
599 if (!m_pImpl->m_aPropertySetCache.empty())
600 m_pImpl->m_aPropertySetCache.erase(xProp);
601 switchListening( _rxElement, false );
603 Reference< XIndexAccess > xContainer( _rxElement, UNO_QUERY );
604 if ( xContainer.is() )
605 switchListening( xContainer, false );
608 void OXUndoEnvironment::SetUndoMode(bool _bUndo)
610 m_pImpl->m_bIsUndo = _bUndo;
613 bool OXUndoEnvironment::IsUndoMode() const
615 return m_pImpl->m_bIsUndo;
618 } // rptui
621 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */