/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
using ::com::sun::star::sdb::XColumn; using ::com::sun::star::awt::XControl; using ::com::sun::star::awt::TabController; using ::com::sun::star::awt::XToolkit; using ::com::sun::star::awt::XWindowPeer; using ::com::sun::star::form::XGrid; using ::com::sun::star::beans::XPropertySet; using ::com::sun::star::uno::UNO_SET_THROW; using ::com::sun::star::uno::UNO_QUERY_THROW; using ::com::sun::star::container::XIndexAccess; using ::com::sun::star::uno::Exception; using ::com::sun::star::uno::XInterface; using ::com::sun::star::uno::UNO_QUERY; using ::com::sun::star::uno::Sequence; using ::com::sun::star::uno::Reference; using ::com::sun::star::beans::XPropertySetInfo; using ::com::sun::star::beans::PropertyValue; using ::com::sun::star::lang::IndexOutOfBoundsException; using ::com::sun::star::sdb::XInteractionSupplyParameters; using ::com::sun::star::awt::XTextComponent; using ::com::sun::star::awt::XTextListener; using ::com::sun::star::uno::Any; using ::com::sun::star::frame::XDispatch; using ::com::sun::star::lang::XMultiServiceFactory; using ::com::sun::star::uno::Type; using ::com::sun::star::lang::IllegalArgumentException; using ::com::sun::star::sdbc::XConnection; using ::com::sun::star::sdbc::XRowSet; using ::com::sun::star::sdbc::XDatabaseMetaData; using ::com::sun::star::util::XNumberFormatsSupplier; using ::com::sun::star::util::NumberFormatter; using ::com::sun::star::util::XNumberFormatter; using ::com::sun::star::sdbcx::XColumnsSupplier; using ::com::sun::star::container::XNameAccess; using ::com::sun::star::lang::EventObject; using ::com::sun::star::beans::Property; using ::com::sun::star::container::XEnumeration; using ::com::sun::star::form::XFormComponent; using ::com::sun::star::form::runtime::XFormOperations; using ::com::sun::star::form::runtime::FilterEvent; using ::com::sun::star::form::runtime::XFilterControllerListener; using ::com::sun::star::awt::XControlContainer; using ::com::sun::star::container::XIdentifierReplace; using ::com::sun::star::form::XFormControllerListener; using ::com::sun::star::awt::XWindow; using ::com::sun::star::sdbc::XResultSet; using ::com::sun::star::awt::XControlModel; using ::com::sun::star::awt::XTabControllerModel; using ::com::sun::star::beans::PropertyChangeEvent; using ::com::sun::star::form::validation::XValidatableFormComponent; using ::com::sun::star::form::XLoadable; using ::com::sun::star::form::XBoundControl; using ::com::sun::star::beans::XPropertyChangeListener; using ::com::sun::star::awt::TextEvent; using ::com::sun::star::form::XBoundComponent; using ::com::sun::star::awt::XCheckBox; using ::com::sun::star::awt::XComboBox; using ::com::sun::star::awt::XListBox; using ::com::sun::star::awt::ItemEvent; using ::com::sun::star::util::XModifyListener; using ::com::sun::star::form::XReset; using ::com::sun::star::frame::XDispatchProviderInterception; using ::com::sun::star::form::XGridControl; using ::com::sun::star::awt::XVclWindowPeer; using ::com::sun::star::form::validation::XValidator; using ::com::sun::star::awt::FocusEvent; using ::com::sun::star::sdb::SQLContext; using ::com::sun::star::container::XChild; using ::com::sun::star::form::TabulatorCycle_RECORDS; using ::com::sun::star::container::ContainerEvent; using ::com::sun::star::lang::DisposedException; using ::com::sun::star::lang::Locale; using ::com::sun::star::lang::NoSupportException; using ::com::sun::star::sdb::RowChangeEvent; using ::com::sun::star::frame::XStatusListener; using ::com::sun::star::frame::XDispatchProviderInterceptor; using ::com::sun::star::sdb::SQLErrorEvent; using ::com::sun::star::form::DatabaseParameterEvent; using ::com::sun::star::sdb::ParametersRequest; using ::com::sun::star::task::XInteractionRequest; using ::com::sun::star::util::URL; using ::com::sun::star::frame::FeatureStateEvent; using ::com::sun::star::form::runtime::XFormControllerContext; using ::com::sun::star::task::InteractionHandler; using ::com::sun::star::task::XInteractionHandler; using ::com::sun::star::form::runtime::FormOperations; using ::com::sun::star::container::XContainer; using ::com::sun::star::sdbc::SQLWarning;
struct ColumnInfo
{ // information about the column itself
Reference< XColumn > xColumn;
sal_Int32 nNullable; bool bAutoIncrement; bool bReadOnly;
OUString sName;
// information about the control(s) bound to this column
/// the first control which is bound to the given column, and which requires input
Reference< XControl > xFirstControlWithInputRequired; /** the first grid control which contains a column which is bound to the given database column, and requires input
*/
Reference< XGrid > xFirstGridWithInputRequiredColumn; /** if xFirstControlWithInputRequired is a grid control, then nRequiredGridColumn specifies the position of the grid column which is actually bound
*/
sal_Int32 nRequiredGridColumn;
void ColumnInfoCache::initializeControls( const Sequence< Reference< XControl > >& _rControls )
{ try
{ // for every of our known columns, find the controls which are bound to this column for (auto& rCol : m_aColumns)
{
OSL_ENSURE( !rCol.xFirstControlWithInputRequired.is() && !rCol.xFirstGridWithInputRequiredColumn.is()
&& ( rCol.nRequiredGridColumn == -1 ), "ColumnInfoCache::initializeControls: called me twice?" );
if ( !lcl_isBoundTo( xGridColumnModel, xNormColumn )
|| !lcl_isInputRequired( xGridColumnModel )
) continue; // with next grid column
break;
}
if ( gridCol < gridColCount )
{ // found a grid column which is bound to the given
rCol.xFirstGridWithInputRequiredColumn = std::move(xGrid);
rCol.nRequiredGridColumn = gridCol; break;
}
continue; // with next control
}
if ( !xModelPSI->hasPropertyByName( FM_PROP_BOUNDFIELD )
|| !lcl_isBoundTo( xModel, xNormColumn )
|| !lcl_isInputRequired( xModel )
) continue; // with next control
break;
}
if ( pControl == pControlEnd ) // did not find a control which is bound to this particular column, and for which the input is required continue; // with next DB column
struct UpdateAllListeners
{ booloperator()( const Reference< XDispatch >& _rxDispatcher ) const
{ static_cast< svx::OSingleFeatureDispatcher* >( _rxDispatcher.get() )->updateAllListeners(); // the return is a dummy only so we can use this struct in a lambda expression returntrue;
}
};
}
IMPL_LINK_NOARG( FormController, OnInvalidateFeatures, Timer*, void )
{
::osl::MutexGuard aGuard( m_aMutex ); for (constauto& rFeature : m_aInvalidFeatures)
{
DispatcherContainer::const_iterator aDispatcherPos = m_aFeatureDispatchers.find( rFeature ); if ( aDispatcherPos != m_aFeatureDispatchers.end() )
{ // TODO: for the real and actual listener notifications, we should release // our mutex
UpdateAllListeners( )( aDispatcherPos->second );
}
}
}
Sequence< OUString> SAL_CALL FormController::getSupportedServiceNames()
{ // service names which are supported only, but cannot be used to created an // instance at a service factory static constexpr OUString aNonCreatableServiceNames[] { u"com.sun.star.form.FormControllerDispatcher"_ustr };
// services which can be used to created an instance at a service factory
Sequence< OUString > aCreatableServiceNames( getSupportedServiceNames_Static() ); return ::comphelper::concatSequences( aCreatableServiceNames, aNonCreatableServiceNames );
}
// reset the text for all controls
::std::for_each( m_aFilterComponents.begin(), m_aFilterComponents.end(), ResetComponentText() );
if ( m_aFilterRows.empty() ) // nothing to do anymore return;
if ( m_nCurrentFilterPosition < 0 ) return;
// set the text for all filters
OSL_ENSURE( m_aFilterRows.size() > o3tl::make_unsigned(m_nCurrentFilterPosition), "FormController::impl_setTextOnAllFilter_throw: m_nCurrentFilterPosition too big" );
if ( ( Term < 0 ) || ( Term >= getDisjunctiveTerms() ) ) throw IndexOutOfBoundsException( OUString(), *this );
// if the to-be-deleted row is our current row, we need to shift if ( Term == m_nCurrentFilterPosition )
{ if ( m_nCurrentFilterPosition < sal_Int32( m_aFilterRows.size() - 1 ) )
++m_nCurrentFilterPosition; else
--m_nCurrentFilterPosition;
}
// if we're still active, simulate a "deactivated" event if ( m_xActiveControl.is() )
m_aActivateListeners.notifyEach( &XFormControllerListener::formDeactivated, aEvt );
// clean up our children for (constauto& rpChild : m_aChildren)
{ // search the position of the model within the form
Reference< XFormComponent > xForm(rpChild->getModel(), UNO_QUERY);
sal_uInt32 nPos = m_xModelAsIndex->getCount();
Reference< XFormComponent > xTemp; for( ; nPos; )
{
if (bAutoFields)
{ // as we don't want new controls to be attached to the scripting environment // we change attach flags
m_bAttachEvents = false; for (sal_Int32 i = nControls; i > 0;)
{
Reference< XControl > xControl = pControls[--i]; if (xControl.is())
{
Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet))
{ // does the model use a bound field ?
Reference< XPropertySet > xField;
xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
// is it an autofield? if ( xField.is()
&& ::comphelper::hasProperty( FM_PROP_AUTOINCREMENT, xField )
&& ::comphelper::getBOOL( xField->getPropertyValue( FM_PROP_AUTOINCREMENT ) )
)
{
replaceControl( xControl, new FmXAutoControl() );
}
}
}
}
m_bAttachEvents = true;
} else
{
m_bDetachEvents = false; for (sal_Int32 i = nControls; i > 0;)
{
Reference< XControl > xControl = pControls[--i]; if (xControl.is())
{
Reference< XPropertySet > xSet(xControl->getModel(), UNO_QUERY); if (xSet.is() && ::comphelper::hasProperty(FM_PROP_BOUNDFIELD, xSet))
{ // does the model use a bound field ?
Reference< XPropertySet > xField;
xSet->getPropertyValue(FM_PROP_BOUNDFIELD) >>= xField;
// do we have a new filter if (!aText.isEmpty())
rRow[xText] = aText; else
{ // do we have the control in the row
FmFilterRow::iterator iter = rRow.find(xText); // erase the entry out of the row if (iter != rRow.end())
rRow.erase(iter);
}
try
{ if ( _rEvent.Source != m_xActiveControl )
{ // let this control grab the focus // (this case may happen if somebody moves the scroll wheel of the mouse over a control // which does not have the focus) // 85511 - 29.05.2001 - frank.schoenheit@germany.sun.com
// also, it happens when an image control gets a new image by double-clicking it // #i88458# / 2009-01-12 / frank.schoenheit@sun.com
Reference< XWindow > xControlWindow( _rEvent.Source, UNO_QUERY_THROW );
xControlWindow->setFocus();
}
} catch( const Exception& )
{
DBG_UNHANDLED_EXCEPTION("svx");
}
impl_onModify();
}
void FormController::impl_checkDisposed_throw() const
{ if ( impl_isDisposed_nofail() ) throw DisposedException( OUString(), *const_cast< FormController* >( this ) );
}
bool FormController::determineLockState() const
{
OSL_ENSURE( !impl_isDisposed_nofail(), "FormController: already disposed!" ); // a.) in filter mode we are always locked // b.) if we have no valid model or our model (a result set) is not alive -> we're locked // c.) if we are inserting everything is OK and we are not locked // d.) if are not updatable or on invalid position
Reference< XResultSet > xResultSet(m_xModelAsIndex, UNO_QUERY); if (m_bFiltering || !xResultSet.is() || !isRowSetAlive(xResultSet)) returntrue; else return !(m_bCanInsert && m_bCurrentRecordNew)
&& (xResultSet->isBeforeFirst() || xResultSet->isAfterLast() || xResultSet->rowDeleted() || !m_bCanUpdate);
}
Reference< XControl > xControl(e.Source, UNO_QUERY); if (m_bDBConnection)
{ // do we need to keep the locking of the commit // we hold the lock as long as the control differs from the current // otherwise we disabled the lock
m_bCommitLock = m_bCommitLock && xControl.get() != m_xCurrentControl.get(); if (m_bCommitLock) return;
// when do we have to commit a value to form or a filter // a.) if the current value is modified // b.) there must be a current control // c.) and it must be different from the new focus owning control or // d.) the focus is moving around (so we have only one control)
if ( ( m_bModified || m_bFiltering )
&& m_xCurrentControl.is()
&& ( ( xControl.get() != m_xCurrentControl.get() )
|| ( ( e.FocusFlags & FocusChangeReason::AROUND )
&& ( m_bCycle || m_bFiltering )
)
)
)
{ // check the old control if the content is ok #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
Reference< XBoundControl > xLockingTest(m_xCurrentControl, UNO_QUERY); bool bControlIsLocked = xLockingTest.is() && xLockingTest->getLock();
assert(!bControlIsLocked && "FormController::Gained: I'm modified and the current control is locked ? How this ?"); // normally, a locked control should not be modified, so probably my bModified must // have been set from a different context, which I would not understand ... #endif
DBG_ASSERT(m_xCurrentControl.is(), "no CurrentControl set"); // first the control ask if it supports the IFace
Reference< XBoundComponent > xBound(m_xCurrentControl, UNO_QUERY); if (!xBound.is() && m_xCurrentControl.is())
xBound.set(m_xCurrentControl->getModel(), UNO_QUERY);
// lock if we lose the focus during commit
m_bCommitLock = true;
// commit unsuccessful, reset focus if (xBound.is() && !xBound->commit())
{ // the commit failed and we don't commit again until the current control // which couldn't be commit gains the focus again
Reference< XWindow > xWindow(m_xCurrentControl, UNO_QUERY); if (xWindow.is())
xWindow->setFocus(); return;
} else
{
m_bModified = false;
m_bCommitLock = false;
}
}
if (!m_bFiltering && m_bCycle && (e.FocusFlags & FocusChangeReason::AROUND) && m_xCurrentControl.is())
{
OSL_ENSURE( m_xFormOperations.is(), "FormController::focusGained: hmm?" ); // should have been created in setModel try
{ if ( e.FocusFlags & FocusChangeReason::FORWARD )
{ if ( m_xFormOperations.is() && m_xFormOperations->isEnabled( FormFeature::MoveToNext ) )
m_xFormOperations->execute( FormFeature::MoveToNext );
} else// backward
{ if ( m_xFormOperations.is() && m_xFormOperations->isEnabled( FormFeature::MoveToPrevious ) )
m_xFormOperations->execute( FormFeature::MoveToPrevious );
}
} catch ( const Exception& )
{ // don't handle this any further. That's an ... admissible error.
DBG_UNHANDLED_EXCEPTION("svx");
}
}
}
// still one and the same control if ( ( m_xActiveControl == xControl )
&& ( xControl == m_xCurrentControl )
)
{
DBG_ASSERT(m_xCurrentControl.is(), "No CurrentControl selected"); return;
}
// invalidate all features which depend on the currently focused control if ( m_bDBConnection && !m_bFiltering )
implInvalidateCurrentControlDependentFeatures();
if ( !m_xCurrentControl.is() ) return;
// control gets focus, then possibly in the visible range
Reference< XFormControllerContext > xContext( m_xFormControllerContext );
Reference< XControl > xCurrentControl( m_xCurrentControl );
aGuard.clear(); // <-- SYNCHRONIZED
if ( xContext.is() )
xContext->makeVisible( xCurrentControl );
}
Reference< XWindowPeer > xNext(e.NextFocus, UNO_QUERY); // if focus hasn't passed to some other window, e.g. focus in a welded item, don't deactivate if (!xNext) return;
Reference< XControl > xNextControl = isInList(xNext); if (!xNextControl.is())
{
m_xActiveControl = nullptr;
m_aDeactivationEvent.Call();
}
}
void SAL_CALL FormController::mousePressed( const awt::MouseEvent& /*_rEvent*/ )
{ // not interested in
}
void SAL_CALL FormController::mouseReleased( const awt::MouseEvent& /*_rEvent*/ )
{ // not interested in
}
try
{ // disconnect from the old model if (m_xModelAsIndex.is())
{ if (m_bDBConnection)
{ // we are currently working on the model
EventObject aEvt(m_xModelAsIndex);
unloaded(aEvt);
}
Reference< XLoadable > xForm(m_xModelAsIndex, UNO_QUERY); if (xForm.is())
xForm->removeLoadListener(this);
Reference< XSQLErrorBroadcaster > xBroadcaster(m_xModelAsIndex, UNO_QUERY); if (xBroadcaster.is())
xBroadcaster->removeSQLErrorListener(this);
Reference< XDatabaseParameterBroadcaster > xParamBroadcaster(m_xModelAsIndex, UNO_QUERY); if (xParamBroadcaster.is())
xParamBroadcaster->removeParameterListener(this);
}
disposeAllFeaturesAndDispatchers();
if ( m_xFormOperations.is() )
m_xFormOperations->dispose();
m_xFormOperations.clear();
// set the new model wait for the load event if (m_xTabController.is())
m_xTabController->setModel(Model);
m_xModelAsIndex.set(Model, UNO_QUERY);
m_xModelAsManager.set(Model, UNO_QUERY);
// only if both ifaces exit, the controller will work successful if (!m_xModelAsIndex.is() || !m_xModelAsManager.is())
{
m_xModelAsManager = nullptr;
m_xModelAsIndex = nullptr;
}
if (m_xModelAsIndex.is())
{ // re-create m_xFormOperations
m_xFormOperations = FormOperations::createWithFormController( m_xComponentContext, this );
m_xFormOperations->setFeatureInvalidation( this );
// adding load and ui interaction listeners
Reference< XLoadable > xForm(Model, UNO_QUERY); if (xForm.is())
xForm->addLoadListener(this);
Reference< XSQLErrorBroadcaster > xBroadcaster(Model, UNO_QUERY); if (xBroadcaster.is())
xBroadcaster->addSQLErrorListener(this);
Reference< XDatabaseParameterBroadcaster > xParamBroadcaster(Model, UNO_QUERY); if (xParamBroadcaster.is())
xParamBroadcaster->addParameterListener(this);
// well, is the database already loaded? // then we have to simulate a load event
Reference< XLoadable > xCursor(m_xModelAsIndex, UNO_QUERY); if (xCursor.is() && xCursor->isLoaded())
{
EventObject aEvt(xCursor);
loaded(aEvt);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.