bump product version to 5.0.4.1
[LibreOffice.git] / svx / source / form / fmundo.cxx
blobcbd32ba352a501082a6bbc196097b7a0f4146363
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.hrc"
30 #include "svx/fmresids.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>
46 #include "svx/fmtools.hxx"
47 #include <svl/macitem.hxx>
48 #include <tools/diagnose_ex.h>
49 #include <sfx2/objsh.hxx>
50 #include <sfx2/docfile.hxx>
51 #include <sfx2/app.hxx>
52 #include <sfx2/sfx.hrc>
53 #include <sfx2/event.hxx>
54 #include <osl/mutex.hxx>
55 #include <comphelper/property.hxx>
56 #include <comphelper/uno3.hxx>
57 #include <connectivity/dbtools.hxx>
59 using namespace ::com::sun::star::uno;
60 using namespace ::com::sun::star::awt;
61 using namespace ::com::sun::star::beans;
62 using namespace ::com::sun::star::container;
63 using namespace ::com::sun::star::script;
64 using namespace ::com::sun::star::lang;
65 using namespace ::com::sun::star::form;
66 using namespace ::com::sun::star::util;
67 using namespace ::com::sun::star::reflection;
68 using namespace ::com::sun::star::form::binding;
69 using namespace ::com::sun::star::sdbc;
70 using namespace ::svxform;
71 using namespace ::dbtools;
74 #include <com/sun/star/script/XScriptListener.hpp>
75 #include <comphelper/processfactory.hxx>
76 #include <cppuhelper/implbase1.hxx>
78 typedef cppu::WeakImplHelper1< XScriptListener > ScriptEventListener_BASE;
79 class ScriptEventListenerWrapper : public ScriptEventListener_BASE
81 public:
82 ScriptEventListenerWrapper( FmFormModel& _rModel) throw ( RuntimeException )
83 :m_rModel( _rModel )
84 ,m_attemptedListenerCreation( false )
88 // XEventListener
89 virtual void SAL_CALL disposing(const EventObject& ) throw( RuntimeException, std::exception ) SAL_OVERRIDE {}
91 // XScriptListener
92 virtual void SAL_CALL firing(const ScriptEvent& evt) throw(RuntimeException, std::exception) SAL_OVERRIDE
94 attemptListenerCreation();
95 if ( m_vbaListener.is() )
97 m_vbaListener->firing( evt );
101 virtual Any SAL_CALL approveFiring(const ScriptEvent& evt) throw( com::sun::star::reflection::InvocationTargetException, RuntimeException, std::exception) SAL_OVERRIDE
103 attemptListenerCreation();
104 if ( m_vbaListener.is() )
106 return m_vbaListener->approveFiring( evt );
108 return Any();
111 private:
112 void attemptListenerCreation()
114 if ( m_attemptedListenerCreation )
115 return;
116 m_attemptedListenerCreation = true;
120 css::uno::Reference<css::uno::XComponentContext> context(
121 comphelper::getProcessComponentContext());
122 Reference< XScriptListener > const xScriptListener(
123 context->getServiceManager()->createInstanceWithContext(
124 "ooo.vba.EventListener", context),
125 UNO_QUERY_THROW);
126 Reference< XPropertySet > const xListenerProps( xScriptListener, UNO_QUERY_THROW );
127 // SfxObjectShellRef is good here since the model controls the lifetime of the shell
128 SfxObjectShellRef const xObjectShell = m_rModel.GetObjectShell();
129 ENSURE_OR_THROW( xObjectShell.Is(), "no object shell!" );
130 xListenerProps->setPropertyValue("Model", makeAny( xObjectShell->GetModel() ) );
132 m_vbaListener = xScriptListener;
134 catch( Exception const & )
136 DBG_UNHANDLED_EXCEPTION();
139 FmFormModel& m_rModel;
140 Reference< XScriptListener > m_vbaListener;
141 bool m_attemptedListenerCreation;
147 // some helper structs for caching property infos
149 struct PropertyInfo
151 bool bIsTransientOrReadOnly : 1; // the property is transient or read-only, thus we need no undo action for it
152 bool bIsValueProperty : 1; // the property is the special value property, thus it may be handled
153 // as if it's transient or persistent
156 struct PropertySetInfo
158 typedef std::map<OUString, PropertyInfo> AllProperties;
160 AllProperties aProps; // all properties of this set which we know so far
161 bool bHasEmptyControlSource; // sal_True -> the set has a DataField property, and the current value is an empty string
162 // sal_False -> the set has _no_ such property or its value isn't empty
165 typedef std::map<Reference< XPropertySet >, PropertySetInfo> PropertySetInfoCache;
169 OUString static_STR_UNDO_PROPERTY;
172 FmXUndoEnvironment::FmXUndoEnvironment(FmFormModel& _rModel)
173 :rModel( _rModel )
174 ,m_pPropertySetCache( NULL )
175 ,m_pScriptingEnv( ::svxform::createDefaultFormScriptingEnvironment( _rModel ) )
176 ,m_Locks( 0 )
177 ,bReadOnly( false )
178 ,m_bDisposed( false )
182 m_vbaListener = new ScriptEventListenerWrapper( _rModel );
184 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);
200 void FmXUndoEnvironment::dispose()
202 OSL_ENSURE( !m_bDisposed, "FmXUndoEnvironment::dispose: disposed twice?" );
203 if ( !m_bDisposed )
204 return;
206 Lock();
208 sal_uInt16 nCount = rModel.GetPageCount();
209 sal_uInt16 i;
210 for (i = 0; i < nCount; i++)
212 FmFormPage* pPage = PTR_CAST( FmFormPage, rModel.GetPage(i) );
213 if ( pPage )
215 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
216 if ( xForms.is() )
217 RemoveElement( xForms );
221 nCount = rModel.GetMasterPageCount();
222 for (i = 0; i < nCount; i++)
224 FmFormPage* pPage = PTR_CAST( FmFormPage, rModel.GetMasterPage(i) );
225 if ( pPage )
227 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
228 if ( xForms.is() )
229 RemoveElement( xForms );
233 UnLock();
235 OSL_PRECOND( rModel.GetObjectShell(), "FmXUndoEnvironment::dispose: no object shell anymore!" );
236 if ( rModel.GetObjectShell() )
237 EndListening( *rModel.GetObjectShell() );
239 if ( IsListening( rModel ) )
240 EndListening( rModel );
242 m_pScriptingEnv->dispose();
244 m_bDisposed = true;
248 void FmXUndoEnvironment::ModeChanged()
250 OSL_PRECOND( rModel.GetObjectShell(), "FmXUndoEnvironment::ModeChanged: no object shell anymore!" );
251 if ( !rModel.GetObjectShell() )
252 return;
254 if (bReadOnly != (rModel.GetObjectShell()->IsReadOnly() || rModel.GetObjectShell()->IsReadOnlyUI()))
256 bReadOnly = !bReadOnly;
258 sal_uInt16 nCount = rModel.GetPageCount();
259 sal_uInt16 i;
260 for (i = 0; i < nCount; i++)
262 FmFormPage* pPage = PTR_CAST( FmFormPage, rModel.GetPage(i) );
263 if ( pPage )
265 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
266 if ( xForms.is() )
267 TogglePropertyListening( xForms );
271 nCount = rModel.GetMasterPageCount();
272 for (i = 0; i < nCount; i++)
274 FmFormPage* pPage = PTR_CAST( FmFormPage, rModel.GetMasterPage(i) );
275 if ( pPage )
277 Reference< css::form::XForms > xForms = pPage->GetForms( false ).get();
278 if ( xForms.is() )
279 TogglePropertyListening( xForms );
283 if (!bReadOnly)
284 StartListening(rModel);
285 else
286 EndListening(rModel);
291 void FmXUndoEnvironment::Notify( SfxBroadcaster& /*rBC*/, const SfxHint& rHint )
293 const SdrHint* pSdrHint = dynamic_cast<const SdrHint*>(&rHint);
294 if (pSdrHint)
296 switch( pSdrHint->GetKind() )
298 case HINT_OBJINSERTED:
300 SdrObject* pSdrObj = const_cast<SdrObject*>(pSdrHint->GetObject());
301 Inserted( pSdrObj );
302 } break;
303 case HINT_OBJREMOVED:
305 SdrObject* pSdrObj = const_cast<SdrObject*>(pSdrHint->GetObject());
306 Removed( pSdrObj );
308 break;
309 default:
310 break;
313 else if (dynamic_cast<const SfxSimpleHint*>(&rHint))
315 switch ( static_cast<const SfxSimpleHint*>(&rHint)->GetId() )
317 case SFX_HINT_DYING:
318 dispose();
319 rModel.SetObjectShell( NULL );
320 break;
321 case SFX_HINT_MODECHANGED:
322 ModeChanged();
323 break;
326 else if (dynamic_cast<const SfxEventHint*>(&rHint))
328 switch ( static_cast<const SfxEventHint*>(&rHint)->GetEventId() )
330 case SFX_EVENT_CREATEDOC:
331 case SFX_EVENT_OPENDOC:
332 ModeChanged();
333 break;
340 void FmXUndoEnvironment::Inserted(SdrObject* pObj)
342 if (pObj->GetObjInventor() == FmFormInventor)
344 FmFormObj* pFormObj = PTR_CAST(FmFormObj, pObj);
345 Inserted( pFormObj );
347 else if (pObj->IsGroupObject())
349 SdrObjListIter aIter(*pObj->GetSubList());
350 while ( aIter.IsMore() )
351 Inserted( aIter.Next() );
356 namespace
358 bool lcl_searchElement(const Reference< XIndexAccess>& xCont, const Reference< XInterface >& xElement)
360 if (!xCont.is() || !xElement.is())
361 return false;
363 sal_Int32 nCount = xCont->getCount();
364 Reference< XInterface > xComp;
365 for (sal_Int32 i = 0; i < nCount; i++)
369 xCont->getByIndex(i) >>= xComp;
370 if (xComp.is())
372 if ( xElement == xComp )
373 return true;
374 else
376 Reference< XIndexAccess> xCont2(xComp, UNO_QUERY);
377 if (xCont2.is() && lcl_searchElement(xCont2, xElement))
378 return true;
382 catch(const Exception&)
384 DBG_UNHANDLED_EXCEPTION();
387 return false;
392 void FmXUndoEnvironment::Inserted(FmFormObj* pObj)
394 DBG_ASSERT( pObj, "FmXUndoEnvironment::Inserted: invalid object!" );
395 if ( !pObj )
396 return;
398 // ist das Control noch einer Form zugeordnet
399 Reference< XInterface > xModel(pObj->GetUnoControlModel(), UNO_QUERY);
400 Reference< XFormComponent > xContent(xModel, UNO_QUERY);
401 if (xContent.is() && pObj->GetPage())
403 // if the component doesn't belong to a form, yet, find one to insert into
404 if (!xContent->getParent().is())
408 Reference< XIndexContainer > xObjectParent = pObj->GetOriginalParent();
410 FmFormPage& rPage = dynamic_cast< FmFormPage& >( *pObj->GetPage() );
411 Reference< XIndexAccess > xForms( rPage.GetForms(), UNO_QUERY_THROW );
413 Reference< XIndexContainer > xNewParent;
414 Reference< XForm > xForm;
415 sal_Int32 nPos = -1;
416 if ( lcl_searchElement( xForms, xObjectParent ) )
418 // the form which was the parent of the object when it was removed is still
419 // part of the form component hierarchy of the current page
420 xNewParent = xObjectParent;
421 xForm.set( xNewParent, UNO_QUERY_THROW );
422 nPos = ::std::min( pObj->GetOriginalIndex(), xNewParent->getCount() );
424 else
426 xForm.set( rPage.GetImpl().findPlaceInFormComponentHierarchy( xContent ), UNO_SET_THROW );
427 xNewParent.set( xForm, UNO_QUERY_THROW );
428 nPos = xNewParent->getCount();
431 FmFormPageImpl::setUniqueName( xContent, xForm );
432 xNewParent->insertByIndex( nPos, makeAny( xContent ) );
434 Reference< XEventAttacherManager > xManager( xNewParent, UNO_QUERY_THROW );
435 xManager->registerScriptEvents( nPos, pObj->GetOriginalEvents() );
437 catch( const Exception& )
439 DBG_UNHANDLED_EXCEPTION();
443 // FormObject zuruecksetzen
444 pObj->ClearObjEnv();
449 void FmXUndoEnvironment::Removed(SdrObject* pObj)
451 if ( pObj->IsVirtualObj() )
452 // for virtual objects, we've already been notified of the removal of the master
453 // object, which is sufficient here
454 return;
456 if (pObj->GetObjInventor() == FmFormInventor)
458 FmFormObj* pFormObj = PTR_CAST(FmFormObj, pObj);
459 Removed(pFormObj);
461 else if (pObj->IsGroupObject())
463 SdrObjListIter aIter(*pObj->GetSubList());
464 while ( aIter.IsMore() )
465 Removed( aIter.Next() );
470 void FmXUndoEnvironment::Removed(FmFormObj* pObj)
472 DBG_ASSERT( pObj, "FmXUndoEnvironment::Removed: invalid object!" );
473 if ( !pObj )
474 return;
476 // ist das Control noch einer Form zugeordnet
477 Reference< XFormComponent > xContent(pObj->GetUnoControlModel(), UNO_QUERY);
478 if (xContent.is())
480 // das Object wird aus einer Liste herausgenommen
481 // existiert ein Vater wird das Object beim beim Vater entfernt und
482 // am FormObject gemerkt!
484 // wird das Object wieder eingefuegt und ein Parent existiert, so wird dieser
485 // Parent wiederum gesetzt
486 Reference< XIndexContainer > xForm(xContent->getParent(), UNO_QUERY);
487 if (xForm.is())
489 Reference< XIndexAccess > xIndexAccess((XIndexContainer*)xForm.get());
490 // Feststellen an welcher Position sich das Kind befunden hat
491 const sal_Int32 nPos = getElementPos(xIndexAccess, xContent);
492 if (nPos >= 0)
494 Sequence< ScriptEventDescriptor > aEvts;
495 Reference< XEventAttacherManager > xManager(xForm, UNO_QUERY);
496 if (xManager.is())
497 aEvts = xManager->getScriptEvents(nPos);
501 pObj->SetObjEnv(xForm, nPos, aEvts);
502 xForm->removeByIndex(nPos);
504 catch(Exception&)
506 DBG_UNHANDLED_EXCEPTION();
514 // XEventListener
516 void SAL_CALL FmXUndoEnvironment::disposing(const EventObject& e) throw( RuntimeException, std::exception )
518 // check if it's an object we have cached information about
519 if (m_pPropertySetCache)
521 Reference< XPropertySet > xSourceSet(e.Source, UNO_QUERY);
522 if (xSourceSet.is())
524 PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache);
525 PropertySetInfoCache::iterator aSetPos = pCache->find(xSourceSet);
526 if (aSetPos != pCache->end())
527 pCache->erase(aSetPos);
532 // XPropertyChangeListener
534 void SAL_CALL FmXUndoEnvironment::propertyChange(const PropertyChangeEvent& evt) throw(::com::sun::star::uno::RuntimeException, std::exception)
536 ::osl::ClearableMutexGuard aGuard( m_aMutex );
538 if (!IsLocked())
540 Reference< XPropertySet > xSet(evt.Source, UNO_QUERY);
541 if (!xSet.is())
542 return;
544 // if it's a "default value" property of a control model, set the according "value" property
545 static const OUString pDefaultValueProperties[] = {
546 OUString(FM_PROP_DEFAULT_TEXT), OUString(FM_PROP_DEFAULTCHECKED), OUString(FM_PROP_DEFAULT_DATE), OUString(FM_PROP_DEFAULT_TIME),
547 OUString(FM_PROP_DEFAULT_VALUE), OUString(FM_PROP_DEFAULT_SELECT_SEQ), OUString(FM_PROP_EFFECTIVE_DEFAULT)
549 const OUString aValueProperties[] = {
550 OUString(FM_PROP_TEXT), OUString(FM_PROP_STATE), OUString(FM_PROP_DATE), OUString(FM_PROP_TIME),
551 OUString(FM_PROP_VALUE), OUString(FM_PROP_SELECT_SEQ), OUString(FM_PROP_EFFECTIVE_VALUE)
553 sal_Int32 nDefaultValueProps = sizeof(pDefaultValueProperties)/sizeof(pDefaultValueProperties[0]);
554 OSL_ENSURE(sizeof(aValueProperties)/sizeof(aValueProperties[0]) == nDefaultValueProps,
555 "FmXUndoEnvironment::propertyChange: inconsistence!");
556 for (sal_Int32 i=0; i<nDefaultValueProps; ++i)
558 if (evt.PropertyName == pDefaultValueProperties[i])
562 xSet->setPropertyValue(aValueProperties[i], evt.NewValue);
564 catch(const Exception&)
566 OSL_FAIL("FmXUndoEnvironment::propertyChange: could not adjust the value property!");
571 // no Undo for transient and readonly props. But unfortunately "transient" is not only that the
572 // "transient" flag is set for the property in question, instead it is somewhat more complex
573 // Transience criterions are:
574 // - the "transient" flag is set for the property
575 // - OR the control has a non-empty COntrolSource property, i.e. is intended to be bound
576 // to a database column. Note that it doesn't matter here whether the control actually
577 // *is* bound to a column
578 // - OR the control is bound to an external value via XBindableValue/XValueBinding
579 // which does not have a "ExternalData" property being <TRUE/>
581 if (!m_pPropertySetCache)
582 m_pPropertySetCache = new PropertySetInfoCache;
583 PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache);
585 // let's see if we know something about the set
586 PropertySetInfoCache::iterator aSetPos = pCache->find(xSet);
587 if (aSetPos == pCache->end())
589 PropertySetInfo aNewEntry;
590 if (!::comphelper::hasProperty(FM_PROP_CONTROLSOURCE, xSet))
592 aNewEntry.bHasEmptyControlSource = false;
594 else
598 Any aCurrentControlSource = xSet->getPropertyValue(FM_PROP_CONTROLSOURCE);
599 aNewEntry.bHasEmptyControlSource = !aCurrentControlSource.hasValue() || ::comphelper::getString(aCurrentControlSource).isEmpty();
601 catch(const Exception&)
603 DBG_UNHANDLED_EXCEPTION();
606 aSetPos = pCache->insert(PropertySetInfoCache::value_type(xSet,aNewEntry)).first;
607 DBG_ASSERT(aSetPos != pCache->end(), "FmXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
609 else
610 { // is it the DataField property ?
611 if (evt.PropertyName == FM_PROP_CONTROLSOURCE)
613 aSetPos->second.bHasEmptyControlSource = !evt.NewValue.hasValue() || ::comphelper::getString(evt.NewValue).isEmpty();
617 // now we have access to the cached info about the set
618 // let's see what we know about the property
619 PropertySetInfo::AllProperties& rPropInfos = aSetPos->second.aProps;
620 PropertySetInfo::AllProperties::iterator aPropertyPos = rPropInfos.find(evt.PropertyName);
621 if (aPropertyPos == rPropInfos.end())
622 { // nothing 'til now ... have to change this ....
623 PropertyInfo aNewEntry;
625 // the attributes
626 sal_Int32 nAttributes = xSet->getPropertySetInfo()->getPropertyByName(evt.PropertyName).Attributes;
627 aNewEntry.bIsTransientOrReadOnly = ((nAttributes & PropertyAttribute::READONLY) != 0) || ((nAttributes & PropertyAttribute::TRANSIENT) != 0);
629 // check if it is the special "DataFieldProperty"
630 aNewEntry.bIsValueProperty = false;
633 if (::comphelper::hasProperty(FM_PROP_CONTROLSOURCEPROPERTY, xSet))
635 Any aControlSourceProperty = xSet->getPropertyValue(FM_PROP_CONTROLSOURCEPROPERTY);
636 OUString sControlSourceProperty;
637 aControlSourceProperty >>= sControlSourceProperty;
639 aNewEntry.bIsValueProperty = (sControlSourceProperty.equals(evt.PropertyName));
642 catch(const Exception&)
644 DBG_UNHANDLED_EXCEPTION();
647 // insert the new entry
648 aPropertyPos = rPropInfos.insert(PropertySetInfo::AllProperties::value_type(evt.PropertyName,aNewEntry)).first;
649 DBG_ASSERT(aPropertyPos != rPropInfos.end(), "FmXUndoEnvironment::propertyChange : just inserted it ... why it's not there ?");
652 // now we have access to the cached info about the property affected
653 // and are able to decide whether or not we need an undo action
655 bool bAddUndoAction = rModel.IsUndoEnabled();
656 // no UNDO for transient/readonly properties
657 if ( bAddUndoAction && aPropertyPos->second.bIsTransientOrReadOnly )
658 bAddUndoAction = false;
660 if ( bAddUndoAction && aPropertyPos->second.bIsValueProperty )
662 // no UNDO when the "value" property changes, but the ControlSource is non-empty
663 // (in this case the control is intended to be bound to a database column)
664 if ( !aSetPos->second.bHasEmptyControlSource )
665 bAddUndoAction = false;
667 // no UNDO if the control is currently bound to an external value
668 if ( bAddUndoAction )
670 Reference< XBindableValue > xBindable( evt.Source, UNO_QUERY );
671 Reference< XValueBinding > xBinding;
672 if ( xBindable.is() )
673 xBinding = xBindable->getValueBinding();
675 Reference< XPropertySet > xBindingProps;
676 Reference< XPropertySetInfo > xBindingPropsPSI;
677 if ( xBindable.is() )
678 xBindingProps.set( xBinding, UNO_QUERY );
679 if ( xBindingProps.is() )
680 xBindingPropsPSI = xBindingProps->getPropertySetInfo();
681 // TODO: we should cache all those things, else this might be too expensive.
682 // However, this requires we're notified of changes in the value binding
684 static const char s_sExternalData[] = "ExternalData";
685 if ( xBindingPropsPSI.is() && xBindingPropsPSI->hasPropertyByName( s_sExternalData ) )
687 bool bExternalData = true;
688 OSL_VERIFY( xBindingProps->getPropertyValue( s_sExternalData ) >>= bExternalData );
689 bAddUndoAction = !bExternalData;
691 else
692 bAddUndoAction = !xBinding.is();
696 if ( bAddUndoAction && ( evt.PropertyName == FM_PROP_STRINGITEMLIST ) )
698 Reference< XListEntrySink > xSink( evt.Source, UNO_QUERY );
699 if ( xSink.is() && xSink->getListEntrySource().is() )
700 // #i41029# / 2005-01-31 / frank.schoenheit@sun.com
701 bAddUndoAction = false;
704 if ( bAddUndoAction )
706 aGuard.clear();
707 // TODO: this is a potential race condition: two threads here could in theory
708 // add their undo actions out-of-order
710 SolarMutexGuard aSolarGuard;
711 rModel.AddUndo(new FmUndoPropertyAction(rModel, evt));
714 else
716 // if it's the DataField property we may have to adjust our cache
717 if (m_pPropertySetCache && evt.PropertyName == FM_PROP_CONTROLSOURCE)
719 Reference< XPropertySet > xSet(evt.Source, UNO_QUERY);
720 PropertySetInfoCache* pCache = static_cast<PropertySetInfoCache*>(m_pPropertySetCache);
721 PropertySetInfo& rSetInfo = (*pCache)[xSet];
722 rSetInfo.bHasEmptyControlSource = !evt.NewValue.hasValue() || ::comphelper::getString(evt.NewValue).isEmpty();
727 // XContainerListener
729 void SAL_CALL FmXUndoEnvironment::elementInserted(const ContainerEvent& evt) throw(::com::sun::star::uno::RuntimeException, std::exception)
731 SolarMutexGuard aSolarGuard;
732 ::osl::MutexGuard aGuard( m_aMutex );
734 // neues Object zum lauschen
735 Reference< XInterface > xIface;
736 evt.Element >>= xIface;
737 OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementInserted: invalid container notification!");
738 AddElement(xIface);
740 implSetModified();
744 void FmXUndoEnvironment::implSetModified()
746 if ( !IsLocked() && rModel.GetObjectShell() )
748 rModel.GetObjectShell()->SetModified( true );
753 void SAL_CALL FmXUndoEnvironment::elementReplaced(const ContainerEvent& evt) throw(::com::sun::star::uno::RuntimeException, std::exception)
755 SolarMutexGuard aSolarGuard;
756 ::osl::MutexGuard aGuard( m_aMutex );
758 Reference< XInterface > xIface;
759 evt.ReplacedElement >>= xIface;
760 OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementReplaced: invalid container notification!");
761 RemoveElement(xIface);
763 evt.Element >>= xIface;
764 AddElement(xIface);
766 implSetModified();
770 void SAL_CALL FmXUndoEnvironment::elementRemoved(const ContainerEvent& evt) throw(::com::sun::star::uno::RuntimeException, std::exception)
772 SolarMutexGuard aSolarGuard;
773 ::osl::MutexGuard aGuard( m_aMutex );
775 Reference< XInterface > xIface( evt.Element, UNO_QUERY );
776 OSL_ENSURE(xIface.is(), "FmXUndoEnvironment::elementRemoved: invalid container notification!");
777 RemoveElement(xIface);
779 implSetModified();
783 void SAL_CALL FmXUndoEnvironment::modified( const EventObject& /*aEvent*/ ) throw (RuntimeException, std::exception)
785 implSetModified();
789 void FmXUndoEnvironment::AddForms(const Reference< XNameContainer > & rForms)
791 Lock();
792 AddElement(Reference<XInterface>( rForms, UNO_QUERY ));
793 UnLock();
797 void FmXUndoEnvironment::RemoveForms(const Reference< XNameContainer > & rForms)
799 Lock();
800 RemoveElement(Reference<XInterface>( rForms, UNO_QUERY ));
801 UnLock();
805 void FmXUndoEnvironment::TogglePropertyListening(const Reference< XInterface > & Element)
807 // am Container horchen
808 Reference< XIndexContainer > xContainer(Element, UNO_QUERY);
809 if (xContainer.is())
811 sal_uInt32 nCount = xContainer->getCount();
812 Reference< XInterface > xIface;
813 for (sal_uInt32 i = 0; i < nCount; i++)
815 xContainer->getByIndex(i) >>= xIface;
816 TogglePropertyListening(xIface);
820 Reference< XPropertySet > xSet(Element, UNO_QUERY);
821 if (xSet.is())
823 if (!bReadOnly)
824 xSet->addPropertyChangeListener( OUString(), this );
825 else
826 xSet->removePropertyChangeListener( OUString(), this );
832 void FmXUndoEnvironment::switchListening( const Reference< XIndexContainer >& _rxContainer, bool _bStartListening )
834 OSL_PRECOND( _rxContainer.is(), "FmXUndoEnvironment::switchListening: invalid container!" );
835 if ( !_rxContainer.is() )
836 return;
840 // if it's an EventAttacherManager, then we need to listen for
841 // script events
842 Reference< XEventAttacherManager > xManager( _rxContainer, UNO_QUERY );
843 if ( xManager.is() )
845 if ( _bStartListening )
847 m_pScriptingEnv->registerEventAttacherManager( xManager );
848 if ( m_vbaListener.is() )
849 xManager->addScriptListener( m_vbaListener );
851 else
853 m_pScriptingEnv->revokeEventAttacherManager( xManager );
854 if ( m_vbaListener.is() )
855 xManager->removeScriptListener( m_vbaListener );
859 // also handle all children of this element
860 sal_uInt32 nCount = _rxContainer->getCount();
861 Reference< XInterface > xInterface;
862 for ( sal_uInt32 i = 0; i < nCount; ++i )
864 _rxContainer->getByIndex( i ) >>= xInterface;
865 if ( _bStartListening )
866 AddElement( xInterface );
867 else
868 RemoveElement( xInterface );
871 // be notified of any changes in the container elements
872 Reference< XContainer > xSimpleContainer( _rxContainer, UNO_QUERY );
873 OSL_ENSURE( xSimpleContainer.is(), "FmXUndoEnvironment::switchListening: how are we expected to be notified of changes in the container?" );
874 if ( xSimpleContainer.is() )
876 if ( _bStartListening )
877 xSimpleContainer->addContainerListener( this );
878 else
879 xSimpleContainer->removeContainerListener( this );
882 catch( const Exception& )
884 OSL_FAIL( "FmXUndoEnvironment::switchListening: caught an exception!" );
889 void FmXUndoEnvironment::switchListening( const Reference< XInterface >& _rxObject, bool _bStartListening )
891 OSL_PRECOND( _rxObject.is(), "FmXUndoEnvironment::switchListening: how should I listen at a NULL object?" );
895 if ( !bReadOnly )
897 Reference< XPropertySet > xProps( _rxObject, UNO_QUERY );
898 if ( xProps.is() )
900 if ( _bStartListening )
901 xProps->addPropertyChangeListener( OUString(), this );
902 else
903 xProps->removePropertyChangeListener( OUString(), this );
907 Reference< XModifyBroadcaster > xBroadcaster( _rxObject, UNO_QUERY );
908 if ( xBroadcaster.is() )
910 if ( _bStartListening )
911 xBroadcaster->addModifyListener( this );
912 else
913 xBroadcaster->removeModifyListener( this );
916 catch( const Exception& )
918 OSL_FAIL( "FmXUndoEnvironment::switchListening: caught an exception!" );
923 void FmXUndoEnvironment::AddElement(const Reference< XInterface >& _rxElement )
925 OSL_ENSURE( !m_bDisposed, "FmXUndoEnvironment::AddElement: not when I'm already disposed!" );
927 // am Container horchen
928 Reference< XIndexContainer > xContainer( _rxElement, UNO_QUERY );
929 if ( xContainer.is() )
930 switchListening( xContainer, true );
932 switchListening( _rxElement, true );
936 void FmXUndoEnvironment::RemoveElement(const Reference< XInterface >& _rxElement)
938 if ( m_bDisposed )
939 return;
941 switchListening( _rxElement, false );
943 if (!bReadOnly)
945 // reset the ActiveConnection if the form is to be removed. This will (should) free the resources
946 // associated with this connection
947 // 86299 - 05/02/2001 - frank.schoenheit@germany.sun.com
948 Reference< XForm > xForm( _rxElement, UNO_QUERY );
949 Reference< XPropertySet > xFormProperties( xForm, UNO_QUERY );
950 if ( xFormProperties.is() )
952 Reference< XConnection > xDummy;
953 if ( !isEmbeddedInDatabase( _rxElement, xDummy ) )
954 // (if there is a connection in the context of the component, setting
955 // a new connection would be vetoed, anyway)
956 // #i34196#
957 xFormProperties->setPropertyValue( FM_PROP_ACTIVE_CONNECTION, Any() );
961 Reference< XIndexContainer > xContainer( _rxElement, UNO_QUERY );
962 if ( xContainer.is() )
963 switchListening( xContainer, false );
968 FmUndoPropertyAction::FmUndoPropertyAction(FmFormModel& rNewMod, const PropertyChangeEvent& evt)
969 :SdrUndoAction(rNewMod)
970 ,xObj(evt.Source, UNO_QUERY)
971 ,aPropertyName(evt.PropertyName)
972 ,aNewValue(evt.NewValue)
973 ,aOldValue(evt.OldValue)
975 if (rNewMod.GetObjectShell())
976 rNewMod.GetObjectShell()->SetModified(true);
977 if(static_STR_UNDO_PROPERTY.isEmpty())
978 static_STR_UNDO_PROPERTY = SVX_RESSTR(RID_STR_UNDO_PROPERTY);
983 void FmUndoPropertyAction::Undo()
985 FmXUndoEnvironment& rEnv = static_cast<FmFormModel&>(rMod).GetUndoEnv();
987 if (xObj.is() && !rEnv.IsLocked())
989 rEnv.Lock();
992 xObj->setPropertyValue( aPropertyName, aOldValue );
994 catch( const Exception& )
996 OSL_FAIL( "FmUndoPropertyAction::Undo: caught an exception!" );
998 rEnv.UnLock();
1003 void FmUndoPropertyAction::Redo()
1005 FmXUndoEnvironment& rEnv = static_cast<FmFormModel&>(rMod).GetUndoEnv();
1007 if (xObj.is() && !rEnv.IsLocked())
1009 rEnv.Lock();
1012 xObj->setPropertyValue( aPropertyName, aNewValue );
1014 catch( const Exception& )
1016 OSL_FAIL( "FmUndoPropertyAction::Redo: caught an exception!" );
1018 rEnv.UnLock();
1023 OUString FmUndoPropertyAction::GetComment() const
1025 OUString aStr(static_STR_UNDO_PROPERTY);
1027 aStr = aStr.replaceFirst( "#", aPropertyName );
1028 return aStr;
1033 FmUndoContainerAction::FmUndoContainerAction(FmFormModel& _rMod,
1034 Action _eAction,
1035 const Reference< XIndexContainer > & xCont,
1036 const Reference< XInterface > & xElem,
1037 sal_Int32 nIdx)
1038 :SdrUndoAction( _rMod )
1039 ,m_xContainer( xCont )
1040 ,m_nIndex( nIdx )
1041 ,m_eAction( _eAction )
1043 OSL_ENSURE( nIdx >= 0, "FmUndoContainerAction::FmUndoContainerAction: invalid index!" );
1044 // some old code suggested this could be a valid argument. However, this code was
1045 // buggy, and it *seemed* that nobody used it - so it was removed.
1047 if ( xCont.is() && xElem.is() )
1049 // normalize
1050 m_xElement.set(xElem, css::uno::UNO_QUERY);
1051 if ( m_eAction == Removed )
1053 if (m_nIndex >= 0)
1055 Reference< XEventAttacherManager > xManager( xCont, UNO_QUERY );
1056 if ( xManager.is() )
1057 m_aEvents = xManager->getScriptEvents(m_nIndex);
1059 else
1060 m_xElement = NULL;
1062 // we now own the element
1063 m_xOwnElement = m_xElement;
1069 FmUndoContainerAction::~FmUndoContainerAction()
1071 // if we own the object ....
1072 DisposeElement( m_xOwnElement );
1077 void FmUndoContainerAction::DisposeElement( const Reference< XInterface > & xElem )
1079 Reference< XComponent > xComp( xElem, UNO_QUERY );
1080 if ( xComp.is() )
1082 // and the object does not have a parent
1083 Reference< XChild > xChild( xElem, UNO_QUERY );
1084 if ( xChild.is() && !xChild->getParent().is() )
1085 // -> dispose it
1086 xComp->dispose();
1091 void FmUndoContainerAction::implReInsert( )
1093 if ( m_xContainer->getCount() >= m_nIndex )
1095 // insert the element
1096 Any aVal;
1097 if ( m_xContainer->getElementType() == cppu::UnoType<XFormComponent>::get() )
1099 aVal <<= Reference< XFormComponent >( m_xElement, UNO_QUERY );
1101 else
1103 aVal <<= Reference< XForm >( m_xElement, UNO_QUERY );
1105 m_xContainer->insertByIndex( m_nIndex, aVal );
1107 OSL_ENSURE( getElementPos( m_xContainer.get(), m_xElement ) == m_nIndex, "FmUndoContainerAction::implReInsert: insertion did not work!" );
1109 // register the events
1110 Reference< XEventAttacherManager > xManager( m_xContainer, UNO_QUERY );
1111 if ( xManager.is() )
1112 xManager->registerScriptEvents( m_nIndex, m_aEvents );
1114 // we don't own the object anymore
1115 m_xOwnElement = NULL;
1120 void FmUndoContainerAction::implReRemove( )
1122 Reference< XInterface > xElement;
1123 if ( ( m_nIndex >= 0 ) && ( m_nIndex < m_xContainer->getCount() ) )
1124 m_xContainer->getByIndex( m_nIndex ) >>= xElement;
1126 if ( xElement != m_xElement )
1128 // the indexes in the container changed. Okay, so go the long way and
1129 // manually determine the index
1130 m_nIndex = getElementPos( m_xContainer.get(), m_xElement );
1131 if ( m_nIndex != -1 )
1132 xElement = m_xElement;
1135 OSL_ENSURE( xElement == m_xElement, "FmUndoContainerAction::implReRemove: cannot find the element which I'm responsible for!" );
1136 if ( xElement == m_xElement )
1138 Reference< XEventAttacherManager > xManager( m_xContainer, UNO_QUERY );
1139 if ( xManager.is() )
1140 m_aEvents = xManager->getScriptEvents( m_nIndex );
1141 m_xContainer->removeByIndex( m_nIndex );
1142 // from now on, we own this object
1143 m_xOwnElement = m_xElement;
1148 void FmUndoContainerAction::Undo()
1150 FmXUndoEnvironment& rEnv = static_cast< FmFormModel& >( rMod ).GetUndoEnv();
1152 if ( m_xContainer.is() && !rEnv.IsLocked() && m_xElement.is() )
1154 rEnv.Lock();
1157 switch ( m_eAction )
1159 case Inserted:
1160 implReRemove();
1161 break;
1163 case Removed:
1164 implReInsert();
1165 break;
1168 catch( const Exception& )
1170 OSL_FAIL( "FmUndoContainerAction::Undo: caught an exception!" );
1172 rEnv.UnLock();
1177 void FmUndoContainerAction::Redo()
1179 FmXUndoEnvironment& rEnv = static_cast< FmFormModel& >( rMod ).GetUndoEnv();
1180 if ( m_xContainer.is() && !rEnv.IsLocked() && m_xElement.is() )
1182 rEnv.Lock();
1185 switch ( m_eAction )
1187 case Inserted:
1188 implReInsert();
1189 break;
1191 case Removed:
1192 implReRemove();
1193 break;
1196 catch( const Exception& )
1198 OSL_FAIL( "FmUndoContainerAction::Redo: caught an exception!" );
1200 rEnv.UnLock();
1205 FmUndoModelReplaceAction::FmUndoModelReplaceAction(FmFormModel& _rMod, SdrUnoObj* _pObject, const Reference< XControlModel > & _xReplaced)
1206 :SdrUndoAction(_rMod)
1207 ,m_xReplaced(_xReplaced)
1208 ,m_pObject(_pObject)
1213 FmUndoModelReplaceAction::~FmUndoModelReplaceAction()
1215 // dispose our element if nobody else is responsible for
1216 DisposeElement(m_xReplaced);
1221 void FmUndoModelReplaceAction::DisposeElement( const ::com::sun::star::uno::Reference< ::com::sun::star::awt::XControlModel>& xReplaced )
1223 Reference< XComponent > xComp(xReplaced, UNO_QUERY);
1224 if (xComp.is())
1226 Reference< XChild > xChild(xReplaced, UNO_QUERY);
1227 if (!xChild.is() || !xChild->getParent().is())
1228 xComp->dispose();
1233 void FmUndoModelReplaceAction::Undo()
1237 Reference< XControlModel > xCurrentModel( m_pObject->GetUnoControlModel() );
1239 // replace the model within the parent
1240 Reference< XChild > xCurrentAsChild( xCurrentModel, UNO_QUERY );
1241 Reference< XNameContainer > xCurrentsParent;
1242 if ( xCurrentAsChild.is() )
1243 xCurrentsParent.set(xCurrentAsChild->getParent(), css::uno::UNO_QUERY);
1244 DBG_ASSERT( xCurrentsParent.is(), "FmUndoModelReplaceAction::Undo: invalid current model!" );
1246 if ( xCurrentsParent.is() )
1248 // the form container works with FormComponents
1249 Reference< XFormComponent > xComponent( m_xReplaced, UNO_QUERY );
1250 DBG_ASSERT( xComponent.is(), "FmUndoModelReplaceAction::Undo: the new model is no form component !" );
1252 Reference< XPropertySet > xCurrentAsSet( xCurrentModel, UNO_QUERY );
1253 DBG_ASSERT( ::comphelper::hasProperty(FM_PROP_NAME, xCurrentAsSet ), "FmUndoModelReplaceAction::Undo : one of the models is invalid !");
1255 OUString sName;
1256 xCurrentAsSet->getPropertyValue( FM_PROP_NAME ) >>= sName;
1257 xCurrentsParent->replaceByName( sName, makeAny( xComponent ) );
1259 m_pObject->SetUnoControlModel(m_xReplaced);
1260 m_pObject->SetChanged();
1262 m_xReplaced = xCurrentModel;
1265 catch(Exception&)
1267 OSL_FAIL("FmUndoModelReplaceAction::Undo : could not replace the model !");
1272 OUString FmUndoModelReplaceAction::GetComment() const
1274 return SVX_RESSTR(RID_STR_UNDO_MODEL_REPLACE);
1277 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */