Bump version to 6.4-15
[LibreOffice.git] / svx / source / form / fmundo.cxx
blob7b632521b4651ddcd0274426a40d05fae4b2b829
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 .
20 #include <sal/config.h>
22 #include <map>
24 #include <sal/macros.h>
25 #include <fmundo.hxx>
26 #include <fmpgeimp.hxx>
27 #include <svx/svditer.hxx>
28 #include <fmobj.hxx>
29 #include <fmprop.hxx>
30 #include <svx/strings.hrc>
31 #include <svx/fmglob.hxx>
32 #include <svx/dialmgr.hxx>
33 #include <svx/fmmodel.hxx>
34 #include <svx/fmpage.hxx>
36 #include <com/sun/star/util/XModifyBroadcaster.hpp>
37 #include <com/sun/star/beans/PropertyAttribute.hpp>
38 #include <com/sun/star/container/XContainer.hpp>
39 #include <com/sun/star/container/XContainerListener.hpp>
40 #include <com/sun/star/script/XEventAttacherManager.hpp>
41 #include <com/sun/star/form/binding/XBindableValue.hpp>
42 #include <com/sun/star/form/binding/XListEntrySink.hpp>
43 #include <com/sun/star/reflection/XInterfaceMethodTypeDescription.hpp>
44 #include <com/sun/star/sdbc/XConnection.hpp>
45 #include <com/sun/star/uno/XComponentContext.hpp>
47 #include <svx/fmtools.hxx>
48 #include <svl/macitem.hxx>
49 #include <tools/debug.hxx>
50 #include <tools/diagnose_ex.h>
51 #include <sfx2/objsh.hxx>
52 #include <sfx2/docfile.hxx>
53 #include <sfx2/app.hxx>
54 #include <sfx2/event.hxx>
55 #include <osl/mutex.hxx>
56 #include <comphelper/property.hxx>
57 #include <comphelper/types.hxx>
58 #include <connectivity/dbtools.hxx>
59 #include <vcl/svapp.hxx>
61 using namespace ::com::sun::star::uno;
62 using namespace ::com::sun::star::awt;
63 using namespace ::com::sun::star::beans;
64 using namespace ::com::sun::star::container;
65 using namespace ::com::sun::star::script;
66 using namespace ::com::sun::star::lang;
67 using namespace ::com::sun::star::form;
68 using namespace ::com::sun::star::util;
69 using namespace ::com::sun::star::reflection;
70 using namespace ::com::sun::star::form::binding;
71 using namespace ::com::sun::star::sdbc;
72 using namespace ::svxform;
73 using namespace ::dbtools;
76 #include <com/sun/star/script/XScriptListener.hpp>
77 #include <comphelper/processfactory.hxx>
78 #include <cppuhelper/implbase.hxx>
80 class ScriptEventListenerWrapper : public cppu::WeakImplHelper< XScriptListener >
82 public:
83 /// @throws css::uno::RuntimeException
84 explicit ScriptEventListenerWrapper( FmFormModel& _rModel)
85 :m_rModel( _rModel )
86 ,m_attemptedListenerCreation( false )
90 // XEventListener
91 virtual void SAL_CALL disposing(const EventObject& ) override {}
93 // XScriptListener
94 virtual void SAL_CALL firing(const ScriptEvent& evt) override
96 attemptListenerCreation();
97 if ( m_vbaListener.is() )
99 m_vbaListener->firing( evt );
103 virtual Any SAL_CALL approveFiring(const ScriptEvent& evt) override
105 attemptListenerCreation();
106 if ( m_vbaListener.is() )
108 return m_vbaListener->approveFiring( evt );
110 return Any();
113 private:
114 void attemptListenerCreation()
116 if ( m_attemptedListenerCreation )
117 return;
118 m_attemptedListenerCreation = true;
122 css::uno::Reference<css::uno::XComponentContext> context(
123 comphelper::getProcessComponentContext());
124 Reference< XScriptListener > const xScriptListener(
125 context->getServiceManager()->createInstanceWithContext(
126 "ooo.vba.EventListener", context),
127 UNO_QUERY_THROW);
128 Reference< XPropertySet > const xListenerProps( xScriptListener, UNO_QUERY_THROW );
129 // SfxObjectShellRef is good here since the model controls the lifetime of the shell
130 SfxObjectShellRef const xObjectShell = m_rModel.GetObjectShell();
131 ENSURE_OR_THROW( xObjectShell.is(), "no object shell!" );
132 xListenerProps->setPropertyValue("Model", makeAny( xObjectShell->GetModel() ) );
134 m_vbaListener = xScriptListener;
136 catch( Exception const & )
138 DBG_UNHANDLED_EXCEPTION("svx");
141 FmFormModel& m_rModel;
142 Reference< XScriptListener > m_vbaListener;
143 bool m_attemptedListenerCreation;
149 // some helper structs for caching property infos
151 struct PropertyInfo
153 bool bIsTransientOrReadOnly : 1; // the property is transient or read-only, thus we need no undo action for it
154 bool bIsValueProperty : 1; // the property is the special value property, thus it may be handled
155 // as if it's transient or persistent
158 struct PropertySetInfo
160 typedef std::map<OUString, PropertyInfo> AllProperties;
162 AllProperties aProps; // all properties of this set which we know so far
163 bool bHasEmptyControlSource; // sal_True -> the set has a DataField property, and the current value is an empty string
164 // sal_False -> the set has _no_ such property or its value isn't empty
167 typedef std::map<Reference< XPropertySet >, PropertySetInfo> PropertySetInfoCache;
170 static OUString static_STR_UNDO_PROPERTY;
173 FmXUndoEnvironment::FmXUndoEnvironment(FmFormModel& _rModel)
174 :rModel( _rModel )
175 ,m_pPropertySetCache( nullptr )
176 ,m_pScriptingEnv( ::svxform::createDefaultFormScriptingEnvironment( _rModel ) )
177 ,m_Locks( 0 )
178 ,bReadOnly( false )
179 ,m_bDisposed( false )
183 m_vbaListener = new ScriptEventListenerWrapper( _rModel );
185 catch( Exception& )
190 FmXUndoEnvironment::~FmXUndoEnvironment()
192 if ( !m_bDisposed ) // i120746, call FormScriptingEnvironment::dispose to avoid memory leak
193 m_pScriptingEnv->dispose();
195 if (m_pPropertySetCache)
196 delete static_cast<PropertySetInfoCache*>(m_pPropertySetCache);
199 void FmXUndoEnvironment::dispose()
201 OSL_ENSURE( !m_bDisposed, "FmXUndoEnvironment::dispose: disposed twice?" );
202 if ( !m_bDisposed )
203 return;
205 Lock();
207 sal_uInt16 nCount = rModel.GetPageCount();
208 sal_uInt16 i;
209 for (i = 0; i < nCount; i++)
211 FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetPage(i) );
212 if ( pPage )
214 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
215 if ( xForms.is() )
216 RemoveElement( xForms );
220 nCount = rModel.GetMasterPageCount();
221 for (i = 0; i < nCount; i++)
223 FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetMasterPage(i) );
224 if ( pPage )
226 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
227 if ( xForms.is() )
228 RemoveElement( xForms );
232 UnLock();
234 OSL_PRECOND( rModel.GetObjectShell(), "FmXUndoEnvironment::dispose: no object shell anymore!" );
235 if ( rModel.GetObjectShell() )
236 EndListening( *rModel.GetObjectShell() );
238 if ( IsListening( rModel ) )
239 EndListening( rModel );
241 m_pScriptingEnv->dispose();
243 m_bDisposed = true;
247 void FmXUndoEnvironment::ModeChanged()
249 OSL_PRECOND( rModel.GetObjectShell(), "FmXUndoEnvironment::ModeChanged: no object shell anymore!" );
250 if ( !rModel.GetObjectShell() )
251 return;
253 if (bReadOnly != (rModel.GetObjectShell()->IsReadOnly() || rModel.GetObjectShell()->IsReadOnlyUI()))
255 bReadOnly = !bReadOnly;
257 sal_uInt16 nCount = rModel.GetPageCount();
258 sal_uInt16 i;
259 for (i = 0; i < nCount; i++)
261 FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetPage(i) );
262 if ( pPage )
264 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
265 if ( xForms.is() )
266 TogglePropertyListening( xForms );
270 nCount = rModel.GetMasterPageCount();
271 for (i = 0; i < nCount; i++)
273 FmFormPage* pPage = dynamic_cast<FmFormPage*>( rModel.GetMasterPage(i) );
274 if ( pPage )
276 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
277 if ( xForms.is() )
278 TogglePropertyListening( xForms );
282 if (!bReadOnly)
283 StartListening(rModel);
284 else
285 EndListening(rModel);
290 void FmXUndoEnvironment::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
292 if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
294 const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint);
295 switch (pSdrHint->GetKind())
297 case SdrHintKind::ObjectInserted:
299 SdrObject* pSdrObj = const_cast<SdrObject*>(pSdrHint->GetObject());
300 Inserted( pSdrObj );
301 } break;
302 case SdrHintKind::ObjectRemoved:
304 SdrObject* pSdrObj = const_cast<SdrObject*>(pSdrHint->GetObject());
305 Removed( pSdrObj );
307 break;
308 default:
309 break;
312 else if (rHint.GetId() != SfxHintId::NONE)
314 switch (rHint.GetId())
316 case SfxHintId::Dying:
317 dispose();
318 rModel.SetObjectShell( nullptr );
319 break;
320 case SfxHintId::ModeChanged:
321 ModeChanged();
322 break;
323 default: break;
326 else if (const SfxEventHint* pEventHint = dynamic_cast<const SfxEventHint*>(&rHint))
328 switch (pEventHint->GetEventId())
330 case SfxEventHintId::CreateDoc:
331 case SfxEventHintId::OpenDoc:
332 ModeChanged();
333 break;
334 default: break;
339 void FmXUndoEnvironment::Inserted(SdrObject* pObj)
341 if (pObj->GetObjInventor() == SdrInventor::FmForm)
343 FmFormObj* pFormObj = dynamic_cast<FmFormObj*>( pObj );
344 Inserted( pFormObj );
346 else if (pObj->IsGroupObject())
348 SdrObjListIter aIter(pObj->GetSubList());
349 while ( aIter.IsMore() )
350 Inserted( aIter.Next() );
355 namespace
357 bool lcl_searchElement(const Reference< XIndexAccess>& xCont, const Reference< XInterface >& xElement)
359 if (!xCont.is() || !xElement.is())
360 return false;
362 sal_Int32 nCount = xCont->getCount();
363 Reference< XInterface > xComp;
364 for (sal_Int32 i = 0; i < nCount; i++)
368 xCont->getByIndex(i) >>= xComp;
369 if (xComp.is())
371 if ( xElement == xComp )
372 return true;
373 else
375 Reference< XIndexAccess> xCont2(xComp, UNO_QUERY);
376 if (xCont2.is() && lcl_searchElement(xCont2, xElement))
377 return true;
381 catch(const Exception&)
383 DBG_UNHANDLED_EXCEPTION("svx");
386 return false;
391 void FmXUndoEnvironment::Inserted(FmFormObj* pObj)
393 DBG_ASSERT( pObj, "FmXUndoEnvironment::Inserted: invalid object!" );
394 if ( !pObj )
395 return;
397 // is the control still assigned to a form
398 Reference< XInterface > xModel(pObj->GetUnoControlModel(), UNO_QUERY);
399 Reference< XFormComponent > xContent(xModel, UNO_QUERY);
400 if (xContent.is() && pObj->getSdrPageFromSdrObject())
402 // if the component doesn't belong to a form, yet, find one to insert into
403 if (!xContent->getParent().is())
407 const Reference< XIndexContainer >& xObjectParent = pObj->GetOriginalParent();
409 FmFormPage& rPage(dynamic_cast< FmFormPage& >( *pObj->getSdrPageFromSdrObject()));
410 Reference< XIndexAccess > xForms( rPage.GetForms(), UNO_QUERY_THROW );
412 Reference< XIndexContainer > xNewParent;
413 Reference< XForm > xForm;
414 sal_Int32 nPos = -1;
415 if ( lcl_searchElement( xForms, xObjectParent ) )
417 // the form which was the parent of the object when it was removed is still
418 // part of the form component hierarchy of the current page
419 xNewParent = xObjectParent;
420 xForm.set( xNewParent, UNO_QUERY_THROW );
421 nPos = ::std::min( pObj->GetOriginalIndex(), xNewParent->getCount() );
423 else
425 xForm.set( rPage.GetImpl().findPlaceInFormComponentHierarchy( xContent ), UNO_SET_THROW );
426 xNewParent.set( xForm, UNO_QUERY_THROW );
427 nPos = xNewParent->getCount();
430 FmFormPageImpl::setUniqueName( xContent, xForm );
431 xNewParent->insertByIndex( nPos, makeAny( xContent ) );
433 Reference< XEventAttacherManager > xManager( xNewParent, UNO_QUERY_THROW );
434 xManager->registerScriptEvents( nPos, pObj->GetOriginalEvents() );
436 catch( const Exception& )
438 DBG_UNHANDLED_EXCEPTION("svx");
442 // reset FormObject
443 pObj->ClearObjEnv();
448 void FmXUndoEnvironment::Removed(SdrObject* pObj)
450 if ( pObj->IsVirtualObj() )
451 // for virtual objects, we've already been notified of the removal of the master
452 // object, which is sufficient here
453 return;
455 if (pObj->GetObjInventor() == SdrInventor::FmForm)
457 FmFormObj* pFormObj = dynamic_cast<FmFormObj*>( pObj );
458 Removed(pFormObj);
460 else if (pObj->IsGroupObject())
462 SdrObjListIter aIter(pObj->GetSubList());
463 while ( aIter.IsMore() )
464 Removed( aIter.Next() );
469 void FmXUndoEnvironment::Removed(FmFormObj* pObj)
471 DBG_ASSERT( pObj, "FmXUndoEnvironment::Removed: invalid object!" );
472 if ( !pObj )
473 return;
475 // is the control still assigned to a form
476 Reference< XFormComponent > xContent(pObj->GetUnoControlModel(), UNO_QUERY);
477 if (xContent.is())
479 // The object is taken out of a list.
480 // If a father exists, the object is removed at the father and
481 // noted at the FormObject!
483 // If the object is reinserted and a parent exists, this parent is set though.
484 Reference< XIndexContainer > xForm(xContent->getParent(), UNO_QUERY);
485 if (xForm.is())
487 Reference< XIndexAccess > xIndexAccess(xForm.get());
488 // determine which position the child was at
489 const sal_Int32 nPos = getElementPos(xIndexAccess, xContent);
490 if (nPos >= 0)
492 Sequence< ScriptEventDescriptor > aEvts;
493 Reference< XEventAttacherManager > xManager(xForm, UNO_QUERY);
494 if (xManager.is())
495 aEvts = xManager->getScriptEvents(nPos);
499 pObj->SetObjEnv(xForm, nPos, aEvts);
500 xForm->removeByIndex(nPos);
502 catch(Exception&)
504 DBG_UNHANDLED_EXCEPTION("svx");
512 // XEventListener
514 void SAL_CALL FmXUndoEnvironment::disposing(const EventObject& e)
516 // check if it's an object we have cached information about
517 if (m_pPropertySetCache)
519 Reference< XPropertySet > xSourceSet(e.Source, UNO_QUERY);
520 if (xSourceSet.is())
522 PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache);
523 PropertySetInfoCache::iterator aSetPos = pCache->find(xSourceSet);
524 if (aSetPos != pCache->end())
525 pCache->erase(aSetPos);
530 // XPropertyChangeListener
532 void SAL_CALL FmXUndoEnvironment::propertyChange(const PropertyChangeEvent& evt)
534 ::osl::ClearableMutexGuard aGuard( m_aMutex );
536 if (!IsLocked())
538 Reference< XPropertySet > xSet(evt.Source, UNO_QUERY);
539 if (!xSet.is())
540 return;
542 // if it's a "default value" property of a control model, set the according "value" property
543 static const OUStringLiteral pDefaultValueProperties[] = {
544 FM_PROP_DEFAULT_TEXT, FM_PROP_DEFAULTCHECKED, FM_PROP_DEFAULT_DATE, FM_PROP_DEFAULT_TIME,
545 FM_PROP_DEFAULT_VALUE, FM_PROP_DEFAULT_SELECT_SEQ, FM_PROP_EFFECTIVE_DEFAULT
547 static const OUStringLiteral aValueProperties[] = {
548 FM_PROP_TEXT, FM_PROP_STATE, FM_PROP_DATE, FM_PROP_TIME,
549 FM_PROP_VALUE, FM_PROP_SELECT_SEQ, FM_PROP_EFFECTIVE_VALUE
551 sal_Int32 nDefaultValueProps = SAL_N_ELEMENTS(pDefaultValueProperties);
552 OSL_ENSURE(SAL_N_ELEMENTS(aValueProperties) == nDefaultValueProps,
553 "FmXUndoEnvironment::propertyChange: inconsistence!");
554 for (sal_Int32 i=0; i<nDefaultValueProps; ++i)
556 if (evt.PropertyName == pDefaultValueProperties[i])
560 xSet->setPropertyValue(aValueProperties[i], evt.NewValue);
562 catch(const Exception&)
564 OSL_FAIL("FmXUndoEnvironment::propertyChange: could not adjust the value property!");
569 // no Undo for transient and readonly props. But unfortunately "transient" is not only that the
570 // "transient" flag is set for the property in question, instead it is somewhat more complex
571 // Transience criterions are:
572 // - the "transient" flag is set for the property
573 // - OR the control has a non-empty COntrolSource property, i.e. is intended to be bound
574 // to a database column. Note that it doesn't matter here whether the control actually
575 // *is* bound to a column
576 // - OR the control is bound to an external value via XBindableValue/XValueBinding
577 // which does not have a "ExternalData" property being <TRUE/>
579 if (!m_pPropertySetCache)
580 m_pPropertySetCache = new PropertySetInfoCache;
581 PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache);
583 // let's see if we know something about the set
584 PropertySetInfoCache::iterator aSetPos = pCache->find(xSet);
585 if (aSetPos == pCache->end())
587 PropertySetInfo aNewEntry;
588 if (!::comphelper::hasProperty(FM_PROP_CONTROLSOURCE, xSet))
590 aNewEntry.bHasEmptyControlSource = false;
592 else
596 Any aCurrentControlSource = xSet->getPropertyValue(FM_PROP_CONTROLSOURCE);
597 aNewEntry.bHasEmptyControlSource = !aCurrentControlSource.hasValue() || ::comphelper::getString(aCurrentControlSource).isEmpty();
599 catch(const Exception&)
601 DBG_UNHANDLED_EXCEPTION("svx");
604 aSetPos = pCache->emplace(xSet,aNewEntry).first;
605 DBG_ASSERT(aSetPos != pCache->end(), "FmXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
607 else
608 { // is it the DataField property ?
609 if (evt.PropertyName == FM_PROP_CONTROLSOURCE)
611 aSetPos->second.bHasEmptyControlSource = !evt.NewValue.hasValue() || ::comphelper::getString(evt.NewValue).isEmpty();
615 // now we have access to the cached info about the set
616 // let's see what we know about the property
617 PropertySetInfo::AllProperties& rPropInfos = aSetPos->second.aProps;
618 PropertySetInfo::AllProperties::iterator aPropertyPos = rPropInfos.find(evt.PropertyName);
619 if (aPropertyPos == rPropInfos.end())
620 { // nothing 'til now ... have to change this...
621 PropertyInfo aNewEntry;
623 // the attributes
624 sal_Int32 nAttributes = xSet->getPropertySetInfo()->getPropertyByName(evt.PropertyName).Attributes;
625 aNewEntry.bIsTransientOrReadOnly = ((nAttributes & PropertyAttribute::READONLY) != 0) || ((nAttributes & PropertyAttribute::TRANSIENT) != 0);
627 // check if it is the special "DataFieldProperty"
628 aNewEntry.bIsValueProperty = false;
631 if (::comphelper::hasProperty(FM_PROP_CONTROLSOURCEPROPERTY, xSet))
633 Any aControlSourceProperty = xSet->getPropertyValue(FM_PROP_CONTROLSOURCEPROPERTY);
634 OUString sControlSourceProperty;
635 aControlSourceProperty >>= sControlSourceProperty;
637 aNewEntry.bIsValueProperty = (sControlSourceProperty == evt.PropertyName);
640 catch(const Exception&)
642 DBG_UNHANDLED_EXCEPTION("svx");
645 // insert the new entry
646 aPropertyPos = rPropInfos.emplace(evt.PropertyName,aNewEntry).first;
647 DBG_ASSERT(aPropertyPos != rPropInfos.end(), "FmXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
650 // now we have access to the cached info about the property affected
651 // and are able to decide whether or not we need an undo action
653 bool bAddUndoAction = rModel.IsUndoEnabled();
654 // no UNDO for transient/readonly properties
655 if ( bAddUndoAction && aPropertyPos->second.bIsTransientOrReadOnly )
656 bAddUndoAction = false;
658 if ( bAddUndoAction && aPropertyPos->second.bIsValueProperty )
660 // no UNDO when the "value" property changes, but the ControlSource is non-empty
661 // (in this case the control is intended to be bound to a database column)
662 if ( !aSetPos->second.bHasEmptyControlSource )
663 bAddUndoAction = false;
665 // no UNDO if the control is currently bound to an external value
666 if ( bAddUndoAction )
668 Reference< XBindableValue > xBindable( evt.Source, UNO_QUERY );
669 Reference< XValueBinding > xBinding;
670 if ( xBindable.is() )
671 xBinding = xBindable->getValueBinding();
673 Reference< XPropertySet > xBindingProps;
674 Reference< XPropertySetInfo > xBindingPropsPSI;
675 if ( xBindable.is() )
676 xBindingProps.set( xBinding, UNO_QUERY );
677 if ( xBindingProps.is() )
678 xBindingPropsPSI = xBindingProps->getPropertySetInfo();
679 // TODO: we should cache all those things, else this might be too expensive.
680 // However, this requires we're notified of changes in the value binding
682 static const char s_sExternalData[] = "ExternalData";
683 if ( xBindingPropsPSI.is() && xBindingPropsPSI->hasPropertyByName( s_sExternalData ) )
685 bool bExternalData = true;
686 OSL_VERIFY( xBindingProps->getPropertyValue( s_sExternalData ) >>= bExternalData );
687 bAddUndoAction = !bExternalData;
689 else
690 bAddUndoAction = !xBinding.is();
694 if ( bAddUndoAction && ( evt.PropertyName == FM_PROP_STRINGITEMLIST ) )
696 Reference< XListEntrySink > xSink( evt.Source, UNO_QUERY );
697 if ( xSink.is() && xSink->getListEntrySource().is() )
698 // #i41029# / 2005-01-31 / frank.schoenheit@sun.com
699 bAddUndoAction = false;
702 if ( bAddUndoAction )
704 aGuard.clear();
705 // TODO: this is a potential race condition: two threads here could in theory
706 // add their undo actions out-of-order
708 SolarMutexGuard aSolarGuard;
709 rModel.AddUndo(std::make_unique<FmUndoPropertyAction>(rModel, evt));
712 else
714 // if it's the DataField property we may have to adjust our cache
715 if (m_pPropertySetCache && evt.PropertyName == FM_PROP_CONTROLSOURCE)
717 Reference< XPropertySet > xSet(evt.Source, UNO_QUERY);
718 PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache);
719 PropertySetInfo& rSetInfo = (*pCache)[xSet];
720 rSetInfo.bHasEmptyControlSource = !evt.NewValue.hasValue() || ::comphelper::getString(evt.NewValue).isEmpty();
725 // XContainerListener
727 void SAL_CALL FmXUndoEnvironment::elementInserted(const ContainerEvent& evt)
729 SolarMutexGuard aSolarGuard;
730 ::osl::MutexGuard aGuard( m_aMutex );
732 // new object for listening
733 Reference< XInterface > xIface;
734 evt.Element >>= xIface;
735 OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementInserted: invalid container notification!");
736 AddElement(xIface);
738 implSetModified();
742 void FmXUndoEnvironment::implSetModified()
744 if ( !IsLocked() && rModel.GetObjectShell() )
746 rModel.GetObjectShell()->SetModified();
751 void SAL_CALL FmXUndoEnvironment::elementReplaced(const ContainerEvent& evt)
753 SolarMutexGuard aSolarGuard;
754 ::osl::MutexGuard aGuard( m_aMutex );
756 Reference< XInterface > xIface;
757 evt.ReplacedElement >>= xIface;
758 OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementReplaced: invalid container notification!");
759 RemoveElement(xIface);
761 evt.Element >>= xIface;
762 AddElement(xIface);
764 implSetModified();
768 void SAL_CALL FmXUndoEnvironment::elementRemoved(const ContainerEvent& evt)
770 SolarMutexGuard aSolarGuard;
771 ::osl::MutexGuard aGuard( m_aMutex );
773 Reference< XInterface > xIface( evt.Element, UNO_QUERY );
774 OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementRemoved: invalid container notification!");
775 RemoveElement(xIface);
777 implSetModified();
781 void SAL_CALL FmXUndoEnvironment::modified( const EventObject& /*aEvent*/ )
783 implSetModified();
787 void FmXUndoEnvironment::AddForms(const Reference< XNameContainer > & rForms)
789 Lock();
790 AddElement(Reference<XInterface>( rForms, UNO_QUERY ));
791 UnLock();
795 void FmXUndoEnvironment::RemoveForms(const Reference< XNameContainer > & rForms)
797 Lock();
798 RemoveElement(Reference<XInterface>( rForms, UNO_QUERY ));
799 UnLock();
803 void FmXUndoEnvironment::TogglePropertyListening(const Reference< XInterface > & Element)
805 // listen at the container
806 Reference< XIndexContainer > xContainer(Element, UNO_QUERY);
807 if (xContainer.is())
809 sal_uInt32 nCount = xContainer->getCount();
810 Reference< XInterface > xIface;
811 for (sal_uInt32 i = 0; i < nCount; i++)
813 xContainer->getByIndex(i) >>= xIface;
814 TogglePropertyListening(xIface);
818 Reference< XPropertySet > xSet(Element, UNO_QUERY);
819 if (xSet.is())
821 if (!bReadOnly)
822 xSet->addPropertyChangeListener( OUString(), this );
823 else
824 xSet->removePropertyChangeListener( OUString(), this );
829 void FmXUndoEnvironment::switchListening( const Reference< XIndexContainer >& _rxContainer, bool _bStartListening )
831 OSL_PRECOND( _rxContainer.is(), "FmXUndoEnvironment::switchListening: invalid container!" );
832 if ( !_rxContainer.is() )
833 return;
837 // if it's an EventAttacherManager, then we need to listen for
838 // script events
839 Reference< XEventAttacherManager > xManager( _rxContainer, UNO_QUERY );
840 if ( xManager.is() )
842 if ( _bStartListening )
844 m_pScriptingEnv->registerEventAttacherManager( xManager );
845 if ( m_vbaListener.is() )
846 xManager->addScriptListener( m_vbaListener );
848 else
850 m_pScriptingEnv->revokeEventAttacherManager( xManager );
851 if ( m_vbaListener.is() )
852 xManager->removeScriptListener( m_vbaListener );
856 // also handle all children of this element
857 sal_uInt32 nCount = _rxContainer->getCount();
858 Reference< XInterface > xInterface;
859 for ( sal_uInt32 i = 0; i < nCount; ++i )
861 _rxContainer->getByIndex( i ) >>= xInterface;
862 if ( _bStartListening )
863 AddElement( xInterface );
864 else
865 RemoveElement( xInterface );
868 // be notified of any changes in the container elements
869 Reference< XContainer > xSimpleContainer( _rxContainer, UNO_QUERY );
870 OSL_ENSURE( xSimpleContainer.is(), "FmXUndoEnvironment::switchListening: how are we expected to be notified of changes in the container?" );
871 if ( xSimpleContainer.is() )
873 if ( _bStartListening )
874 xSimpleContainer->addContainerListener( this );
875 else
876 xSimpleContainer->removeContainerListener( this );
879 catch( const Exception& )
881 OSL_FAIL( "FmXUndoEnvironment::switchListening: caught an exception!" );
886 void FmXUndoEnvironment::switchListening( const Reference< XInterface >& _rxObject, bool _bStartListening )
888 OSL_PRECOND( _rxObject.is(), "FmXUndoEnvironment::switchListening: how should I listen at a NULL object?" );
892 if ( !bReadOnly )
894 Reference< XPropertySet > xProps( _rxObject, UNO_QUERY );
895 if ( xProps.is() )
897 if ( _bStartListening )
898 xProps->addPropertyChangeListener( OUString(), this );
899 else
900 xProps->removePropertyChangeListener( OUString(), this );
904 Reference< XModifyBroadcaster > xBroadcaster( _rxObject, UNO_QUERY );
905 if ( xBroadcaster.is() )
907 if ( _bStartListening )
908 xBroadcaster->addModifyListener( this );
909 else
910 xBroadcaster->removeModifyListener( this );
913 catch( const Exception& )
915 OSL_FAIL( "FmXUndoEnvironment::switchListening: caught an exception!" );
920 void FmXUndoEnvironment::AddElement(const Reference< XInterface >& _rxElement )
922 OSL_ENSURE( !m_bDisposed, "FmXUndoEnvironment::AddElement: not when I'm already disposed!" );
924 // listen at the container
925 Reference< XIndexContainer > xContainer( _rxElement, UNO_QUERY );
926 if ( xContainer.is() )
927 switchListening( xContainer, true );
929 switchListening( _rxElement, true );
933 void FmXUndoEnvironment::RemoveElement(const Reference< XInterface >& _rxElement)
935 if ( m_bDisposed )
936 return;
938 switchListening( _rxElement, false );
940 if (!bReadOnly)
942 // reset the ActiveConnection if the form is to be removed. This will (should) free the resources
943 // associated with this connection
944 // 86299 - 05/02/2001 - frank.schoenheit@germany.sun.com
945 Reference< XForm > xForm( _rxElement, UNO_QUERY );
946 Reference< XPropertySet > xFormProperties( xForm, UNO_QUERY );
947 if ( xFormProperties.is() )
949 Reference< XConnection > xDummy;
950 if ( !isEmbeddedInDatabase( _rxElement, xDummy ) )
951 // (if there is a connection in the context of the component, setting
952 // a new connection would be vetoed, anyway)
953 // #i34196#
954 xFormProperties->setPropertyValue( FM_PROP_ACTIVE_CONNECTION, Any() );
958 Reference< XIndexContainer > xContainer( _rxElement, UNO_QUERY );
959 if ( xContainer.is() )
960 switchListening( xContainer, false );
964 FmUndoPropertyAction::FmUndoPropertyAction(FmFormModel& rNewMod, const PropertyChangeEvent& evt)
965 :SdrUndoAction(rNewMod)
966 ,xObj(evt.Source, UNO_QUERY)
967 ,aPropertyName(evt.PropertyName)
968 ,aNewValue(evt.NewValue)
969 ,aOldValue(evt.OldValue)
971 if (rNewMod.GetObjectShell())
972 rNewMod.GetObjectShell()->SetModified();
973 if(static_STR_UNDO_PROPERTY.isEmpty())
974 static_STR_UNDO_PROPERTY = SvxResId(RID_STR_UNDO_PROPERTY);
978 void FmUndoPropertyAction::Undo()
980 FmXUndoEnvironment& rEnv = static_cast<FmFormModel&>(rMod).GetUndoEnv();
982 if (xObj.is() && !rEnv.IsLocked())
984 rEnv.Lock();
987 xObj->setPropertyValue( aPropertyName, aOldValue );
989 catch( const Exception& )
991 OSL_FAIL( "FmUndoPropertyAction::Undo: caught an exception!" );
993 rEnv.UnLock();
998 void FmUndoPropertyAction::Redo()
1000 FmXUndoEnvironment& rEnv = static_cast<FmFormModel&>(rMod).GetUndoEnv();
1002 if (xObj.is() && !rEnv.IsLocked())
1004 rEnv.Lock();
1007 xObj->setPropertyValue( aPropertyName, aNewValue );
1009 catch( const Exception& )
1011 OSL_FAIL( "FmUndoPropertyAction::Redo: caught an exception!" );
1013 rEnv.UnLock();
1018 OUString FmUndoPropertyAction::GetComment() const
1020 OUString aStr = static_STR_UNDO_PROPERTY.replaceFirst( "#", aPropertyName );
1021 return aStr;
1025 FmUndoContainerAction::FmUndoContainerAction(FmFormModel& _rMod,
1026 Action _eAction,
1027 const Reference< XIndexContainer > & xCont,
1028 const Reference< XInterface > & xElem,
1029 sal_Int32 nIdx)
1030 :SdrUndoAction( _rMod )
1031 ,m_xContainer( xCont )
1032 ,m_nIndex( nIdx )
1033 ,m_eAction( _eAction )
1035 OSL_ENSURE( nIdx >= 0, "FmUndoContainerAction::FmUndoContainerAction: invalid index!" );
1036 // some old code suggested this could be a valid argument. However, this code was
1037 // buggy, and it *seemed* that nobody used it - so it was removed.
1039 if ( xCont.is() && xElem.is() )
1041 // normalize
1042 m_xElement = xElem;
1043 if ( m_eAction == Removed )
1045 if (m_nIndex >= 0)
1047 Reference< XEventAttacherManager > xManager( xCont, UNO_QUERY );
1048 if ( xManager.is() )
1049 m_aEvents = xManager->getScriptEvents(m_nIndex);
1051 else
1052 m_xElement = nullptr;
1054 // we now own the element
1055 m_xOwnElement = m_xElement;
1061 FmUndoContainerAction::~FmUndoContainerAction()
1063 // if we own the object...
1064 DisposeElement( m_xOwnElement );
1068 void FmUndoContainerAction::DisposeElement( const Reference< XInterface > & xElem )
1070 Reference< XComponent > xComp( xElem, UNO_QUERY );
1071 if ( xComp.is() )
1073 // and the object does not have a parent
1074 Reference< XChild > xChild( xElem, UNO_QUERY );
1075 if ( xChild.is() && !xChild->getParent().is() )
1076 // -> dispose it
1077 xComp->dispose();
1082 void FmUndoContainerAction::implReInsert( )
1084 if ( m_xContainer->getCount() >= m_nIndex )
1086 // insert the element
1087 Any aVal;
1088 if ( m_xContainer->getElementType() == cppu::UnoType<XFormComponent>::get() )
1090 aVal <<= Reference< XFormComponent >( m_xElement, UNO_QUERY );
1092 else
1094 aVal <<= Reference< XForm >( m_xElement, UNO_QUERY );
1096 m_xContainer->insertByIndex( m_nIndex, aVal );
1098 OSL_ENSURE( getElementPos( m_xContainer.get(), m_xElement ) == m_nIndex, "FmUndoContainerAction::implReInsert: insertion did not work!" );
1100 // register the events
1101 Reference< XEventAttacherManager > xManager( m_xContainer, UNO_QUERY );
1102 if ( xManager.is() )
1103 xManager->registerScriptEvents( m_nIndex, m_aEvents );
1105 // we don't own the object anymore
1106 m_xOwnElement = nullptr;
1111 void FmUndoContainerAction::implReRemove( )
1113 Reference< XInterface > xElement;
1114 if ( ( m_nIndex >= 0 ) && ( m_nIndex < m_xContainer->getCount() ) )
1115 m_xContainer->getByIndex( m_nIndex ) >>= xElement;
1117 if ( xElement != m_xElement )
1119 // the indexes in the container changed. Okay, so go the long way and
1120 // manually determine the index
1121 m_nIndex = getElementPos( m_xContainer.get(), m_xElement );
1122 if ( m_nIndex != -1 )
1123 xElement = m_xElement;
1126 OSL_ENSURE( xElement == m_xElement, "FmUndoContainerAction::implReRemove: cannot find the element which I'm responsible for!" );
1127 if ( xElement == m_xElement )
1129 Reference< XEventAttacherManager > xManager( m_xContainer, UNO_QUERY );
1130 if ( xManager.is() )
1131 m_aEvents = xManager->getScriptEvents( m_nIndex );
1132 m_xContainer->removeByIndex( m_nIndex );
1133 // from now on, we own this object
1134 m_xOwnElement = m_xElement;
1139 void FmUndoContainerAction::Undo()
1141 FmXUndoEnvironment& rEnv = static_cast< FmFormModel& >( rMod ).GetUndoEnv();
1143 if ( m_xContainer.is() && !rEnv.IsLocked() && m_xElement.is() )
1145 rEnv.Lock();
1148 switch ( m_eAction )
1150 case Inserted:
1151 implReRemove();
1152 break;
1154 case Removed:
1155 implReInsert();
1156 break;
1159 catch( const Exception& )
1161 OSL_FAIL( "FmUndoContainerAction::Undo: caught an exception!" );
1163 rEnv.UnLock();
1168 void FmUndoContainerAction::Redo()
1170 FmXUndoEnvironment& rEnv = static_cast< FmFormModel& >( rMod ).GetUndoEnv();
1171 if ( m_xContainer.is() && !rEnv.IsLocked() && m_xElement.is() )
1173 rEnv.Lock();
1176 switch ( m_eAction )
1178 case Inserted:
1179 implReInsert();
1180 break;
1182 case Removed:
1183 implReRemove();
1184 break;
1187 catch( const Exception& )
1189 OSL_FAIL( "FmUndoContainerAction::Redo: caught an exception!" );
1191 rEnv.UnLock();
1196 FmUndoModelReplaceAction::FmUndoModelReplaceAction(FmFormModel& _rMod, SdrUnoObj* _pObject, const Reference< XControlModel > & _xReplaced)
1197 :SdrUndoAction(_rMod)
1198 ,m_xReplaced(_xReplaced)
1199 ,m_pObject(_pObject)
1204 FmUndoModelReplaceAction::~FmUndoModelReplaceAction()
1206 // dispose our element if nobody else is responsible for
1207 DisposeElement(m_xReplaced);
1211 void FmUndoModelReplaceAction::DisposeElement( const css::uno::Reference< css::awt::XControlModel>& xReplaced )
1213 Reference< XComponent > xComp(xReplaced, UNO_QUERY);
1214 if (xComp.is())
1216 Reference< XChild > xChild(xReplaced, UNO_QUERY);
1217 if (!xChild.is() || !xChild->getParent().is())
1218 xComp->dispose();
1223 void FmUndoModelReplaceAction::Undo()
1227 Reference< XControlModel > xCurrentModel( m_pObject->GetUnoControlModel() );
1229 // replace the model within the parent
1230 Reference< XChild > xCurrentAsChild( xCurrentModel, UNO_QUERY );
1231 Reference< XNameContainer > xCurrentsParent;
1232 if ( xCurrentAsChild.is() )
1233 xCurrentsParent.set(xCurrentAsChild->getParent(), css::uno::UNO_QUERY);
1234 DBG_ASSERT( xCurrentsParent.is(), "FmUndoModelReplaceAction::Undo: invalid current model!" );
1236 if ( xCurrentsParent.is() )
1238 // the form container works with FormComponents
1239 Reference< XFormComponent > xComponent( m_xReplaced, UNO_QUERY );
1240 DBG_ASSERT( xComponent.is(), "FmUndoModelReplaceAction::Undo: the new model is no form component !" );
1242 Reference< XPropertySet > xCurrentAsSet( xCurrentModel, UNO_QUERY );
1243 DBG_ASSERT( ::comphelper::hasProperty(FM_PROP_NAME, xCurrentAsSet ), "FmUndoModelReplaceAction::Undo : one of the models is invalid !");
1245 OUString sName;
1246 xCurrentAsSet->getPropertyValue( FM_PROP_NAME ) >>= sName;
1247 xCurrentsParent->replaceByName( sName, makeAny( xComponent ) );
1249 m_pObject->SetUnoControlModel(m_xReplaced);
1250 m_pObject->SetChanged();
1252 m_xReplaced = xCurrentModel;
1255 catch(Exception&)
1257 OSL_FAIL("FmUndoModelReplaceAction::Undo : could not replace the model !");
1262 OUString FmUndoModelReplaceAction::GetComment() const
1264 return SvxResId(RID_STR_UNDO_MODEL_REPLACE);
1267 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */