bump product version to 6.3.0.0.beta1
[LibreOffice.git] / reportdesign / source / core / sdr / UndoEnv.cxx
blobc55bf8b5210f857352ed69aa7e63435a5cc1df98
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/debug.hxx>
42 #include <tools/diagnose_ex.h>
43 #include <vcl/svapp.hxx>
44 #include <dbaccess/dbsubcomponentcontroller.hxx>
45 #include <svx/unoshape.hxx>
46 #include <osl/mutex.hxx>
48 namespace rptui
50 using namespace ::com::sun::star;
51 using namespace uno;
52 using namespace lang;
53 using namespace script;
54 using namespace beans;
55 using namespace awt;
56 using namespace util;
57 using namespace container;
58 using namespace report;
61 struct PropertyInfo
63 bool const bIsReadonlyOrTransient;
65 explicit PropertyInfo( const bool i_bIsTransientOrReadOnly )
66 :bIsReadonlyOrTransient( i_bIsTransientOrReadOnly )
71 typedef std::unordered_map< OUString, PropertyInfo > PropertiesInfo;
73 struct ObjectInfo
75 PropertiesInfo aProperties;
76 Reference< XPropertySet > xPropertyIntrospection;
78 ObjectInfo()
79 :aProperties()
80 ,xPropertyIntrospection()
85 typedef ::std::map< Reference< XPropertySet >, ObjectInfo > PropertySetInfoCache;
88 class OXUndoEnvironmentImpl
90 public:
91 OReportModel& m_rModel;
92 PropertySetInfoCache m_aPropertySetCache;
93 FormatNormalizer m_aFormatNormalizer;
94 ConditionUpdater m_aConditionUpdater;
95 ::osl::Mutex m_aMutex;
96 ::std::vector< uno::Reference< container::XChild> > m_aSections;
97 Reference< XIntrospection > m_xIntrospection;
98 oslInterlockedCount m_nLocks;
99 bool m_bReadOnly;
100 bool m_bIsUndo;
102 explicit OXUndoEnvironmentImpl(OReportModel& _rModel);
103 OXUndoEnvironmentImpl(const OXUndoEnvironmentImpl&) = delete;
104 OXUndoEnvironmentImpl& operator=(const OXUndoEnvironmentImpl&) = delete;
107 OXUndoEnvironmentImpl::OXUndoEnvironmentImpl(OReportModel& _rModel) : m_rModel(_rModel)
108 ,m_aFormatNormalizer( _rModel )
109 ,m_aConditionUpdater()
110 ,m_nLocks(0)
111 ,m_bReadOnly(false)
112 ,m_bIsUndo(false)
117 OXUndoEnvironment::OXUndoEnvironment(OReportModel& _rModel)
118 :m_pImpl(new OXUndoEnvironmentImpl(_rModel) )
120 StartListening(m_pImpl->m_rModel);
124 OXUndoEnvironment::~OXUndoEnvironment()
128 void OXUndoEnvironment::Lock()
130 OSL_ENSURE(m_refCount,"Illegal call to dead object!");
131 osl_atomic_increment( &m_pImpl->m_nLocks );
133 void OXUndoEnvironment::UnLock()
135 OSL_ENSURE(m_refCount,"Illegal call to dead object!");
137 osl_atomic_decrement( &m_pImpl->m_nLocks );
139 bool OXUndoEnvironment::IsLocked() const { return m_pImpl->m_nLocks != 0; }
141 void OXUndoEnvironment::RemoveSection(OReportPage const * _pPage)
143 if ( _pPage )
145 Reference< XInterface > xSection(_pPage->getSection());
146 if ( xSection.is() )
147 RemoveElement( xSection );
151 void OXUndoEnvironment::Clear(const Accessor& /*_r*/)
153 OUndoEnvLock aLock(*this);
155 m_pImpl->m_aPropertySetCache.clear();
157 sal_uInt16 nCount = m_pImpl->m_rModel.GetPageCount();
158 sal_uInt16 i;
159 for (i = 0; i < nCount; i++)
161 OReportPage* pPage = dynamic_cast<OReportPage*>( m_pImpl->m_rModel.GetPage(i) );
162 RemoveSection(pPage);
165 nCount = m_pImpl->m_rModel.GetMasterPageCount();
166 for (i = 0; i < nCount; i++)
168 OReportPage* pPage = dynamic_cast<OReportPage*>( m_pImpl->m_rModel.GetMasterPage(i) );
169 RemoveSection(pPage);
172 m_pImpl->m_aSections.clear();
174 if (IsListening(m_pImpl->m_rModel))
175 EndListening(m_pImpl->m_rModel);
179 void OXUndoEnvironment::ModeChanged()
181 m_pImpl->m_bReadOnly = !m_pImpl->m_bReadOnly;
183 if (!m_pImpl->m_bReadOnly)
184 StartListening(m_pImpl->m_rModel);
185 else
186 EndListening(m_pImpl->m_rModel);
190 void OXUndoEnvironment::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
192 if (rHint.GetId() == SfxHintId::ModeChanged )
193 ModeChanged();
196 // XEventListener
198 void SAL_CALL OXUndoEnvironment::disposing(const EventObject& e)
200 // check if it's an object we have cached information about
201 Reference< XPropertySet > xSourceSet(e.Source, UNO_QUERY);
202 if ( xSourceSet.is() )
204 uno::Reference< report::XSection> xSection(xSourceSet,uno::UNO_QUERY);
205 if ( xSection.is() )
206 RemoveSection(xSection);
207 else
208 RemoveElement(xSourceSet);
212 // XPropertyChangeListener
214 void SAL_CALL OXUndoEnvironment::propertyChange( const PropertyChangeEvent& _rEvent )
217 ::osl::ClearableMutexGuard aGuard( m_pImpl->m_aMutex );
219 if ( IsLocked() )
220 return;
222 Reference< XPropertySet > xSet( _rEvent.Source, UNO_QUERY );
223 if (!xSet.is())
224 return;
226 dbaui::DBSubComponentController* pController = m_pImpl->m_rModel.getController();
227 if ( !pController )
228 return;
230 // no Undo for transient and readonly props.
231 // let's see if we know something about the set
232 PropertySetInfoCache::iterator objectPos = m_pImpl->m_aPropertySetCache.find(xSet);
233 if (objectPos == m_pImpl->m_aPropertySetCache.end())
235 objectPos = m_pImpl->m_aPropertySetCache.emplace( xSet, ObjectInfo() ).first;
236 DBG_ASSERT(objectPos != m_pImpl->m_aPropertySetCache.end(), "OXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
238 if ( objectPos == m_pImpl->m_aPropertySetCache.end() )
239 return;
241 // now we have access to the cached info about the set
242 // let's see what we know about the property
243 ObjectInfo& rObjectInfo = objectPos->second;
244 PropertiesInfo::const_iterator aPropertyPos = rObjectInfo.aProperties.find( _rEvent.PropertyName );
245 if ( aPropertyPos == rObjectInfo.aProperties.end() )
246 { // nothing 'til now ... have to change this ....
247 // the attributes
248 Reference< XPropertySetInfo > xPSI( xSet->getPropertySetInfo(), UNO_SET_THROW );
249 sal_Int32 nPropertyAttributes = 0;
252 if ( xPSI->hasPropertyByName( _rEvent.PropertyName ) )
254 nPropertyAttributes = xPSI->getPropertyByName( _rEvent.PropertyName ).Attributes;
256 else
258 // it's perfectly valid for a component to notify a change in a property which it doesn't have - as long
259 // as it has an attribute with this name
260 if ( !rObjectInfo.xPropertyIntrospection.is() )
262 if ( !m_pImpl->m_xIntrospection.is() )
264 m_pImpl->m_xIntrospection = theIntrospection::get( m_pImpl->m_rModel.getController()->getORB() );
266 Reference< XIntrospectionAccess > xIntrospection(
267 m_pImpl->m_xIntrospection->inspect( makeAny( _rEvent.Source ) ),
268 UNO_SET_THROW
270 rObjectInfo.xPropertyIntrospection.set( xIntrospection->queryAdapter( cppu::UnoType<XPropertySet>::get() ), UNO_QUERY_THROW );
272 if ( rObjectInfo.xPropertyIntrospection.is() )
274 xPSI.set( rObjectInfo.xPropertyIntrospection->getPropertySetInfo(), UNO_SET_THROW );
275 nPropertyAttributes = xPSI->getPropertyByName( _rEvent.PropertyName ).Attributes;
279 catch( const Exception& )
281 DBG_UNHANDLED_EXCEPTION("reportdesign");
283 const bool bTransReadOnly =
284 ( ( nPropertyAttributes & PropertyAttribute::READONLY ) != 0 )
285 || ( ( nPropertyAttributes & PropertyAttribute::TRANSIENT ) != 0 );
287 // insert the new entry
288 aPropertyPos = rObjectInfo.aProperties.emplace(
289 _rEvent.PropertyName,
290 PropertyInfo( bTransReadOnly )
291 ).first;
292 DBG_ASSERT(aPropertyPos != rObjectInfo.aProperties.end(), "OXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
295 implSetModified();
297 // now we have access to the cached info about the property affected
298 // and are able to decide whether or not we need an undo action
300 // no UNDO for transient/readonly properties
301 if ( aPropertyPos->second.bIsReadonlyOrTransient )
302 return;
304 // give components with sub responsibilities a chance
305 m_pImpl->m_aFormatNormalizer.notifyPropertyChange( _rEvent );
306 m_pImpl->m_aConditionUpdater.notifyPropertyChange( _rEvent );
308 aGuard.clear();
309 // TODO: this is a potential race condition: two threads here could in theory
310 // add their undo actions out-of-order
312 SolarMutexGuard aSolarGuard;
313 std::unique_ptr<ORptUndoPropertyAction> pUndo;
316 uno::Reference< report::XSection> xSection( xSet, uno::UNO_QUERY );
317 if ( xSection.is() )
319 uno::Reference< report::XGroup> xGroup = xSection->getGroup();
320 if ( xGroup.is() )
321 pUndo.reset(new OUndoPropertyGroupSectionAction( m_pImpl->m_rModel, _rEvent, OGroupHelper::getMemberFunction( xSection ), xGroup ));
322 else
323 pUndo.reset(new OUndoPropertyReportSectionAction( m_pImpl->m_rModel, _rEvent, OReportHelper::getMemberFunction( xSection ), xSection->getReportDefinition() ));
326 catch(const Exception&)
328 DBG_UNHANDLED_EXCEPTION("reportdesign");
331 if ( pUndo == nullptr )
332 pUndo.reset(new ORptUndoPropertyAction( m_pImpl->m_rModel, _rEvent ));
334 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction( std::move(pUndo) );
335 pController->InvalidateAll();
338 ::std::vector< uno::Reference< container::XChild> >::const_iterator OXUndoEnvironment::getSection(const Reference<container::XChild>& _xContainer) const
340 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = m_pImpl->m_aSections.end();
341 if ( _xContainer.is() )
343 aFind = ::std::find(m_pImpl->m_aSections.begin(),m_pImpl->m_aSections.end(),_xContainer);
345 if ( aFind == m_pImpl->m_aSections.end() )
347 Reference<container::XChild> xParent(_xContainer->getParent(),uno::UNO_QUERY);
348 aFind = getSection(xParent);
351 return aFind;
353 // XContainerListener
355 void SAL_CALL OXUndoEnvironment::elementInserted(const ContainerEvent& evt)
357 SolarMutexGuard aSolarGuard;
358 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
360 // new listener object
361 Reference< uno::XInterface > xIface( evt.Element, UNO_QUERY );
362 if ( !IsLocked() )
364 Reference< report::XReportComponent > xReportComponent( xIface, UNO_QUERY );
365 if ( xReportComponent.is() )
367 Reference< report::XSection > xContainer(evt.Source,uno::UNO_QUERY);
369 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = getSection(xContainer.get());
371 if ( aFind != m_pImpl->m_aSections.end() )
373 OUndoEnvLock aLock(*this);
376 OReportPage* pPage = m_pImpl->m_rModel.getPage(uno::Reference< report::XSection>(*aFind,uno::UNO_QUERY));
377 OSL_ENSURE(pPage,"No page could be found for section!");
378 if ( pPage )
379 pPage->insertObject(xReportComponent);
381 catch(uno::Exception&)
383 DBG_UNHANDLED_EXCEPTION("reportdesign");
388 else
390 uno::Reference< report::XFunctions> xContainer(evt.Source,uno::UNO_QUERY);
391 if ( xContainer.is() )
393 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction(
394 std::make_unique<OUndoContainerAction>( m_pImpl->m_rModel, rptui::Inserted, xContainer.get(),
395 xIface, RID_STR_UNDO_ADDFUNCTION ) );
400 AddElement(xIface);
402 implSetModified();
406 void OXUndoEnvironment::implSetModified()
408 m_pImpl->m_rModel.SetModified( true );
412 void SAL_CALL OXUndoEnvironment::elementReplaced(const ContainerEvent& evt)
414 SolarMutexGuard aSolarGuard;
415 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
417 Reference< XInterface > xIface(evt.ReplacedElement,uno::UNO_QUERY);
418 OSL_ENSURE(xIface.is(), "OXUndoEnvironment::elementReplaced: invalid container notification!");
419 RemoveElement(xIface);
421 xIface.set(evt.Element,uno::UNO_QUERY);
422 AddElement(xIface);
424 implSetModified();
428 void SAL_CALL OXUndoEnvironment::elementRemoved(const ContainerEvent& evt)
430 SolarMutexGuard aSolarGuard;
431 ::osl::MutexGuard aGuard( m_pImpl->m_aMutex );
433 Reference< uno::XInterface > xIface( evt.Element, UNO_QUERY );
434 if ( !IsLocked() )
436 Reference< report::XSection > xContainer(evt.Source,uno::UNO_QUERY);
437 ::std::vector< uno::Reference< container::XChild> >::const_iterator aFind = getSection(xContainer.get());
439 Reference< report::XReportComponent > xReportComponent( xIface, UNO_QUERY );
440 if ( aFind != m_pImpl->m_aSections.end() && xReportComponent.is() )
442 OXUndoEnvironment::OUndoEnvLock aLock(*this);
445 OReportPage* pPage = m_pImpl->m_rModel.getPage(uno::Reference< report::XSection >( *aFind, uno::UNO_QUERY_THROW ) );
446 OSL_ENSURE( pPage, "OXUndoEnvironment::elementRemoved: no page for the section!" );
447 if ( pPage )
448 pPage->removeSdrObject(xReportComponent);
450 catch(const uno::Exception&)
452 DBG_UNHANDLED_EXCEPTION("reportdesign");
455 else
457 uno::Reference< report::XFunctions> xFunctions(evt.Source,uno::UNO_QUERY);
458 if ( xFunctions.is() )
460 m_pImpl->m_rModel.GetSdrUndoManager()->AddUndoAction( std::make_unique<OUndoContainerAction>(
461 m_pImpl->m_rModel, rptui::Removed, xFunctions.get(), xIface, RID_STR_UNDO_ADDFUNCTION ) );
466 if ( xIface.is() )
467 RemoveElement(xIface);
469 implSetModified();
473 void SAL_CALL OXUndoEnvironment::modified( const EventObject& /*aEvent*/ )
475 implSetModified();
479 void OXUndoEnvironment::AddSection(const Reference< report::XSection > & _xSection)
481 OUndoEnvLock aLock(*this);
484 uno::Reference<container::XChild> xChild = _xSection.get();
485 m_pImpl->m_aSections.push_back(xChild);
486 Reference< XInterface > xInt(_xSection);
487 AddElement(xInt);
489 catch(const uno::Exception&)
491 DBG_UNHANDLED_EXCEPTION("reportdesign");
496 void OXUndoEnvironment::RemoveSection(const Reference< report::XSection > & _xSection)
498 OUndoEnvLock aLock(*this);
501 uno::Reference<container::XChild> xChild(_xSection.get());
502 m_pImpl->m_aSections.erase(::std::remove(m_pImpl->m_aSections.begin(),m_pImpl->m_aSections.end(),
503 xChild), m_pImpl->m_aSections.end());
504 Reference< XInterface > xInt(_xSection);
505 RemoveElement(xInt);
507 catch(uno::Exception&){}
511 void OXUndoEnvironment::switchListening( const Reference< XIndexAccess >& _rxContainer, bool _bStartListening )
513 OSL_PRECOND( _rxContainer.is(), "OXUndoEnvironment::switchListening: invalid container!" );
514 if ( !_rxContainer.is() )
515 return;
519 // also handle all children of this element
520 Reference< XInterface > xInterface;
521 sal_Int32 nCount = _rxContainer->getCount();
522 for(sal_Int32 i = 0;i != nCount;++i)
524 xInterface.set(_rxContainer->getByIndex( i ),uno::UNO_QUERY);
525 if ( _bStartListening )
526 AddElement( xInterface );
527 else
528 RemoveElement( xInterface );
531 // be notified of any changes in the container elements
532 Reference< XContainer > xSimpleContainer( _rxContainer, UNO_QUERY );
533 if ( xSimpleContainer.is() )
535 if ( _bStartListening )
536 xSimpleContainer->addContainerListener( this );
537 else
538 xSimpleContainer->removeContainerListener( this );
541 catch( const Exception& )
543 DBG_UNHANDLED_EXCEPTION("reportdesign");
548 void OXUndoEnvironment::switchListening( const Reference< XInterface >& _rxObject, bool _bStartListening )
550 OSL_PRECOND( _rxObject.is(), "OXUndoEnvironment::switchListening: how should I listen at a NULL object?" );
554 if ( !m_pImpl->m_bReadOnly )
556 Reference< XPropertySet > xProps( _rxObject, UNO_QUERY );
557 if ( xProps.is() )
559 if ( _bStartListening )
560 xProps->addPropertyChangeListener( OUString(), this );
561 else
562 xProps->removePropertyChangeListener( OUString(), this );
566 Reference< XModifyBroadcaster > xBroadcaster( _rxObject, UNO_QUERY );
567 if ( xBroadcaster.is() )
569 if ( _bStartListening )
570 xBroadcaster->addModifyListener( this );
571 else
572 xBroadcaster->removeModifyListener( this );
575 catch( const Exception& )
581 void OXUndoEnvironment::AddElement(const Reference< XInterface >& _rxElement )
583 if ( !IsLocked() )
584 m_pImpl->m_aFormatNormalizer.notifyElementInserted( _rxElement );
586 // if it's a container, start listening at all elements
587 Reference< XIndexAccess > xContainer( _rxElement, UNO_QUERY );
588 if ( xContainer.is() )
589 switchListening( xContainer, true );
591 switchListening( _rxElement, true );
595 void OXUndoEnvironment::RemoveElement(const Reference< XInterface >& _rxElement)
597 uno::Reference<beans::XPropertySet> xProp(_rxElement,uno::UNO_QUERY);
598 if (!m_pImpl->m_aPropertySetCache.empty())
599 m_pImpl->m_aPropertySetCache.erase(xProp);
600 switchListening( _rxElement, false );
602 Reference< XIndexAccess > xContainer( _rxElement, UNO_QUERY );
603 if ( xContainer.is() )
604 switchListening( xContainer, false );
607 void OXUndoEnvironment::SetUndoMode(bool _bUndo)
609 m_pImpl->m_bIsUndo = _bUndo;
612 bool OXUndoEnvironment::IsUndoMode() const
614 return m_pImpl->m_bIsUndo;
617 } // rptui
620 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */