/* -*- 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 .
*/ #include"FormattedField.hxx" #include <services.hxx> #include <property.hxx> #include <propertybaghelper.hxx> #include <comphelper/property.hxx> #include <comphelper/sequence.hxx> #include <comphelper/numbers.hxx> #include <comphelper/types.hxx> #include <connectivity/dbtools.hxx> #include <connectivity/dbconversion.hxx> #include <o3tl/any.hxx> #include <svl/numformat.hxx> #include <svl/numuno.hxx> #include <vcl/keycodes.hxx> #include <vcl/svapp.hxx> #include <vcl/settings.hxx> #include <tools/debug.hxx> #include <i18nlangtag/languagetag.hxx> #include <com/sun/star/beans/PropertyAttribute.hpp> #include <com/sun/star/sdbc/DataType.hpp> #include <com/sun/star/util/NumberFormat.hpp> #include <com/sun/star/util/Date.hpp> #include <com/sun/star/util/Time.hpp> #include <com/sun/star/awt/MouseEvent.hpp> #include <com/sun/star/form/XSubmit.hpp> #include <com/sun/star/awt/XWindow.hpp> #include <com/sun/star/form/FormComponentType.hpp> #include <com/sun/star/util/XNumberFormatTypes.hpp> #include <com/sun/star/form/XForm.hpp> #include <com/sun/star/container/XIndexAccess.hpp> #include <osl/mutex.hxx> // needed as long as we use the SolarMutex #include <comphelper/streamsection.hxx> #include <cppuhelper/weakref.hxx> #include <unotools/desktopterminationobserver.hxx> #include <vector> #include <algorithm>
void OFormattedModel::describeAggregateProperties( Sequence< Property >& _rAggregateProps ) const
{
OEditBaseModel::describeAggregateProperties( _rAggregateProps ); // TreatAsNumeric is not transient: we want to attach it to the UI // This is necessary to make EffectiveDefault (which may be text or a number) meaningful
ModifyPropertyAttributes(_rAggregateProps, PROPERTY_TREATASNUMERIC, 0, PropertyAttribute::TRANSIENT); // Same for FormatKey // (though the paragraph above for the TreatAsNumeric does not hold anymore - we do not have an UI for this. // But we have for the format key ...)
ModifyPropertyAttributes(_rAggregateProps, PROPERTY_FORMATKEY, 0, PropertyAttribute::TRANSIENT);
RemoveProperty(_rAggregateProps, PROPERTY_STRICTFORMAT); // no strict format property for formatted fields: it does not make sense, 'cause // there is no general way to decide which characters/sub strings are allowed during the input of an // arbitrary formatted control
}
void OFormattedModel::setPropertyToDefaultByHandle(sal_Int32 nHandle)
{ if (nHandle == PROPERTY_ID_FORMATSSUPPLIER)
{
Reference<XNumberFormatsSupplier> xSupplier = calcDefaultFormatsSupplier();
DBG_ASSERT(m_xAggregateSet.is(), "OFormattedModel::setPropertyToDefaultByHandle(FORMATSSUPPLIER) : have no aggregate !"); if (m_xAggregateSet.is())
m_xAggregateSet->setPropertyValue(PROPERTY_FORMATSSUPPLIER, Any(xSupplier));
} else
OEditBaseModel::setPropertyToDefaultByHandle(nHandle);
}
void OFormattedModel::_propertyChanged( const css::beans::PropertyChangeEvent& evt )
{ // TODO: check how this works with external bindings
OSL_ENSURE( evt.Source == m_xAggregateSet, "OFormattedModel::_propertyChanged: where did this come from?" ); if ( evt.Source != m_xAggregateSet ) return;
if ( evt.PropertyName == PROPERTY_FORMATKEY )
{ if ( evt.NewValue.getValueTypeClass() == TypeClass_LONG )
{ try
{
::osl::MutexGuard aGuard( m_aMutex );
Reference<XNumberFormatsSupplier> xSupplier( calcFormatsSupplier() );
m_nKeyType = getNumberFormatType(xSupplier->getNumberFormats(), getINT32( evt.NewValue ) ); // as m_aSaveValue (which is used by commitControlValueToDbColumn) is format dependent we have // to recalc it, which is done by translateDbColumnToControlValue if ( m_xColumn.is() && m_xAggregateFastSet.is() && !m_xCursor->isBeforeFirst() && !m_xCursor->isAfterLast())
{
setControlValue( translateDbColumnToControlValue(), eOther );
} // if we're connected to an external value binding, then re-calculate the type // used to exchange the value - it depends on the format, too if ( hasExternalValueBinding() )
{
calculateExternalValueType();
}
} catch(const Exception&)
{
}
} return;
} if ( evt.PropertyName == PROPERTY_FORMATSSUPPLIER )
{
updateFormatterNullDate(); return;
}
OBoundControlModel::_propertyChanged( evt );
}
void OFormattedModel::updateFormatterNullDate()
{ // calc the current NULL date
Reference< XNumberFormatsSupplier > xSupplier( calcFormatsSupplier() ); if ( xSupplier.is() )
xSupplier->getNumberFormatSettings()->getPropertyValue(u"NullDate"_ustr) >>= m_aNullDate;
}
Reference< XNumberFormatsSupplier > OFormattedModel::calcFormatsSupplier() const
{
Reference<XNumberFormatsSupplier> xSupplier;
DBG_ASSERT(m_xAggregateSet.is(), "OFormattedModel::calcFormatsSupplier : have no aggregate !"); // Does my aggregate model have a FormatSupplier? if( m_xAggregateSet.is() )
m_xAggregateSet->getPropertyValue(PROPERTY_FORMATSSUPPLIER) >>= xSupplier; if (!xSupplier.is()) // check if my parent form has a supplier
xSupplier = calcFormFormatsSupplier(); if (!xSupplier.is())
xSupplier = calcDefaultFormatsSupplier();
DBG_ASSERT(xSupplier.is(), "OFormattedModel::calcFormatsSupplier : no supplier !"); // We should have one by now return xSupplier;
}
Reference<XNumberFormatsSupplier> OFormattedModel::calcFormFormatsSupplier() const
{
Reference<XChild> xMe(const_cast<OFormattedModel*>(this)); // By this we make sure that we get the right object even when aggregating
DBG_ASSERT(xMe.is(), "OFormattedModel::calcFormFormatsSupplier : I should have a content interface !"); // Iterate through until we reach a StartForm (starting with an own Parent)
Reference<XChild> xParent(xMe->getParent(), UNO_QUERY);
Reference<XForm> xNextParentForm(xParent, UNO_QUERY); while (!xNextParentForm.is() && xParent.is())
{
xParent.set(xParent->getParent(), css::uno::UNO_QUERY);
xNextParentForm.set(xParent, css::uno::UNO_QUERY);
} if (!xNextParentForm.is())
{
OSL_FAIL("OFormattedModel::calcFormFormatsSupplier : have no ancestor which is a form !"); return nullptr;
} // The FormatSupplier of my ancestor (if it has one)
Reference< XRowSet > xRowSet( xNextParentForm, UNO_QUERY );
Reference< XNumberFormatsSupplier > xSupplier; if (xRowSet.is())
xSupplier = getNumberFormats( getConnection(xRowSet), true, getContext() ); return xSupplier;
}
// XBoundComponent void OFormattedModel::loaded(const EventObject& rEvent)
{ // HACK: our onConnectedDbColumn accesses our NumberFormatter which locks the solar mutex (as it doesn't have // an own one). To prevent deadlocks with other threads which may request a property from us in an // UI-triggered action (e.g. a tooltip) we lock the solar mutex _here_ before our base class locks // its own mutex (which is used for property requests) // alternative a): we use two mutexes, one which is passed to the OPropertysetHelper and used for // property requests and one for our own code. This would need a lot of code rewriting // alternative b): The NumberFormatter has to be really threadsafe (with an own mutex), which is // the only "clean" solution for me.
SolarMutexGuard aGuard;
OEditBaseModel::loaded(rEvent);
}
void OFormattedModel::onConnectedDbColumn( const Reference< XInterface >& _rxForm )
{
m_xOriginalFormatter = nullptr; // get some properties of the field
Reference<XPropertySet> xField = getField();
sal_Int32 nFormatKey = 0;
DBG_ASSERT(m_xAggregateSet.is(), "OFormattedModel::onConnectedDbColumn : have no aggregate !"); if (m_xAggregateSet.is())
{ // all the following doesn't make any sense if we have no aggregate ...
Any aSupplier = m_xAggregateSet->getPropertyValue(PROPERTY_FORMATSSUPPLIER);
DBG_ASSERT( aSupplier.hasValue(), "OFormattedModel::onConnectedDbColumn : invalid property value !" ); // This should've been set to the correct value in the ctor or in the read
Any aFmtKey = m_xAggregateSet->getPropertyValue(PROPERTY_FORMATKEY); if ( !(aFmtKey >>= nFormatKey ) )
{ // nobody gave us a format to use. So we examine the field we're bound to for a // format key, and use it ourself, too
sal_Int32 nType = DataType::VARCHAR; if (xField.is())
{
aFmtKey = xField->getPropertyValue(PROPERTY_FORMATKEY);
xField->getPropertyValue(PROPERTY_FIELDTYPE) >>= nType ;
}
Reference<XNumberFormatsSupplier> xSupplier = calcFormFormatsSupplier();
DBG_ASSERT(xSupplier.is(), "OFormattedModel::onConnectedDbColumn : bound to a field but no parent with a formatter ? how this ?"); if (xSupplier.is())
{
m_bOriginalNumeric = getBOOL(getPropertyValue(PROPERTY_TREATASNUMERIC)); if (!aFmtKey.hasValue())
{ // we aren't bound to a field (or this field's format is invalid) // -> determine the standard text (or numeric) format of the supplier
Reference<XNumberFormatTypes> xTypes(xSupplier->getNumberFormats(), UNO_QUERY); if (xTypes.is())
{
Locale aApplicationLocale = Application::GetSettings().GetUILanguageTag().getLocale(); if (m_bOriginalNumeric)
aFmtKey <<= xTypes->getStandardFormat(NumberFormat::NUMBER, aApplicationLocale); else
aFmtKey <<= xTypes->getStandardFormat(NumberFormat::TEXT, aApplicationLocale);
}
}
aSupplier >>= m_xOriginalFormatter;
m_xAggregateSet->setPropertyValue(PROPERTY_FORMATSSUPPLIER, Any(xSupplier));
m_xAggregateSet->setPropertyValue(PROPERTY_FORMATKEY, aFmtKey); // Adapt the NumericFalg to my bound field if (xField.is())
{
m_bNumeric = false; switch (nType)
{ case DataType::BIT: case DataType::BOOLEAN: case DataType::TINYINT: case DataType::SMALLINT: case DataType::INTEGER: case DataType::BIGINT: case DataType::FLOAT: case DataType::REAL: case DataType::DOUBLE: case DataType::NUMERIC: case DataType::DECIMAL: case DataType::DATE: case DataType::TIME: case DataType::TIMESTAMP:
m_bNumeric = true; break;
}
} else
m_bNumeric = m_bOriginalNumeric;
setPropertyValue(PROPERTY_TREATASNUMERIC, Any(m_bNumeric));
OSL_VERIFY( aFmtKey >>= nFormatKey );
}
}
}
Reference<XNumberFormatsSupplier> xSupplier = calcFormatsSupplier();
m_bNumeric = getBOOL( getPropertyValue( PROPERTY_TREATASNUMERIC ) );
m_nKeyType = getNumberFormatType( xSupplier->getNumberFormats(), nFormatKey );
xSupplier->getNumberFormatSettings()->getPropertyValue(u"NullDate"_ustr) >>= m_aNullDate;
OEditBaseModel::onConnectedDbColumn( _rxForm );
}
void OFormattedModel::onDisconnectedDbColumn()
{
OEditBaseModel::onDisconnectedDbColumn(); if (m_xOriginalFormatter.is())
{ // Our aggregated model does not hold any Format information
m_xAggregateSet->setPropertyValue(PROPERTY_FORMATSSUPPLIER, Any(m_xOriginalFormatter));
m_xAggregateSet->setPropertyValue(PROPERTY_FORMATKEY, Any());
setPropertyValue(PROPERTY_TREATASNUMERIC, Any(m_bOriginalNumeric));
m_xOriginalFormatter = nullptr;
}
m_nKeyType = NumberFormat::UNDEFINED;
m_aNullDate = DBTypeConversion::getStandardDate();
}
void OFormattedModel::write(const Reference<XObjectOutputStream>& _rxOutStream)
{
OEditBaseModel::write(_rxOutStream);
_rxOutStream->writeShort(0x0003);
DBG_ASSERT(m_xAggregateSet.is(), "OFormattedModel::write : have no aggregate !"); // Bring my Format (may be void) to a persistent Format. // The Supplier together with the Key is already persistent, but that doesn't mean // we have to save the Supplier (which would be quite some overhead)
Reference<XNumberFormatsSupplier> xSupplier;
Any aFmtKey; bool bVoidKey = true; if (m_xAggregateSet.is())
{
Any aSupplier = m_xAggregateSet->getPropertyValue(PROPERTY_FORMATSSUPPLIER); if (aSupplier.getValueTypeClass() != TypeClass_VOID)
{
OSL_VERIFY( aSupplier >>= xSupplier );
}
aFmtKey = m_xAggregateSet->getPropertyValue(PROPERTY_FORMATKEY);
bVoidKey = (!xSupplier.is() || !aFmtKey.hasValue()) || (isLoaded() && m_xOriginalFormatter.is()); // (no Format and/or Key) OR (loaded and faked Formatter)
}
_rxOutStream->writeBoolean(!bVoidKey); if (!bVoidKey)
{ // Create persistent values from the FormatKey and the Formatter
Any aKey = m_xAggregateSet->getPropertyValue(PROPERTY_FORMATKEY);
sal_Int32 nKey = aKey.hasValue() ? getINT32(aKey) : 0;
Reference<XNumberFormats> xFormats = xSupplier->getNumberFormats();
OUString sFormatDescription;
LanguageType eFormatLanguage = LANGUAGE_DONTKNOW; static constexpr OUString s_aLocaleProp = u"Locale"_ustr;
Reference<css::beans::XPropertySet> xFormat = xFormats->getByKey(nKey); if (hasProperty(s_aLocaleProp, xFormat))
{
Any aLocale = xFormat->getPropertyValue(s_aLocaleProp);
DBG_ASSERT(aLocale.has<Locale>(), "OFormattedModel::write : invalid language property !"); if (auto pLocale = o3tl::tryAccess<Locale>(aLocale))
{
eFormatLanguage = LanguageTag::convertToLanguageType( *pLocale, false);
}
} static constexpr OUString s_aFormatStringProp = u"FormatString"_ustr; if (hasProperty(s_aFormatStringProp, xFormat))
xFormat->getPropertyValue(s_aFormatStringProp) >>= sFormatDescription;
_rxOutStream->writeUTF(sFormatDescription);
_rxOutStream->writeLong(static_cast<sal_uInt16>(eFormatLanguage));
} // version 2 : write the properties common to all OEditBaseModels
writeCommonEditProperties(_rxOutStream); // version 3 : write the effective value property of the aggregate // Due to a bug within the UnoControlFormattedFieldModel implementation (our default aggregate) // this props value isn't correctly read and this can't be corrected without being incompatible. // so we have our own handling. // and to be a little bit more compatible we make the following section skippable
{
OStreamSection aDownCompat(_rxOutStream); // a sub version within the skippable block
_rxOutStream->writeShort(0x0000); // version 0: the effective value of the aggregate
Any aEffectiveValue; if (m_xAggregateSet.is())
{ try { aEffectiveValue = m_xAggregateSet->getPropertyValue(PROPERTY_EFFECTIVE_VALUE); } catch(const Exception&) { }
}
{
OStreamSection aDownCompat2(_rxOutStream); switch (aEffectiveValue.getValueTypeClass())
{ case TypeClass_STRING:
_rxOutStream->writeShort(0x0000);
_rxOutStream->writeUTF(::comphelper::getString(aEffectiveValue)); break; case TypeClass_DOUBLE:
_rxOutStream->writeShort(0x0001);
_rxOutStream->writeDouble(::comphelper::getDouble(aEffectiveValue)); break; default: // void and all unknown states
DBG_ASSERT(!aEffectiveValue.hasValue(), "FmXFormattedModel::write : unknown property value type !");
_rxOutStream->writeShort(0x0002); break;
}
}
}
}
void OFormattedModel::read(const Reference<XObjectInputStream>& _rxInStream)
{
OEditBaseModel::read(_rxInStream);
sal_uInt16 nVersion = _rxInStream->readShort();
Reference<XNumberFormatsSupplier> xSupplier;
sal_Int32 nKey = -1; switch (nVersion)
{ case 0x0001 : case 0x0002 : case 0x0003 :
{ bool bNonVoidKey = _rxInStream->readBoolean(); if (bNonVoidKey)
{ // read string and language...
OUString sFormatDescription = _rxInStream->readUTF();
LanguageType eDescriptionLanguage(_rxInStream->readLong()); // and let a formatter roll dice based on that to create a key...
xSupplier = calcFormatsSupplier(); // calcFormatsSupplier first takes the one from the model, then one from the starform, then a new one...
Reference<XNumberFormats> xFormats = xSupplier->getNumberFormats(); if (xFormats.is())
{
Locale aDescriptionLanguage( LanguageTag::convertToLocale(eDescriptionLanguage));
nKey = xFormats->queryKey(sFormatDescription, aDescriptionLanguage, false); if (nKey == sal_Int32(-1))
{ // does not yet exist in my formatter...
nKey = xFormats->addNew(sFormatDescription, aDescriptionLanguage);
}
}
} if ((nVersion == 0x0002) || (nVersion == 0x0003))
readCommonEditProperties(_rxInStream); if (nVersion == 0x0003)
{ // since version 3 there is a "skippable" block at this position
OStreamSection aDownCompat(_rxInStream);
_rxInStream->readShort(); // sub-version // version 0 and higher: the "effective value" property
Any aEffectiveValue;
{
OStreamSection aDownCompat2(_rxInStream); switch (_rxInStream->readShort())
{ case 0: // String
aEffectiveValue <<= _rxInStream->readUTF(); break; case 1: // double
aEffectiveValue <<= _rxInStream->readDouble(); break; case 2: break; case 3:
OSL_FAIL("FmXFormattedModel::read : unknown effective value type!");
}
} // this property is only to be set if we have no control source: in all other cases the base class made a // reset after it's read and this set the effective value to a default value if ( m_xAggregateSet.is() && getControlSource().isEmpty() )
{ try
{
m_xAggregateSet->setPropertyValue(PROPERTY_EFFECTIVE_VALUE, aEffectiveValue);
} catch(const Exception&)
{
}
}
}
} break; default :
OSL_FAIL("OFormattedModel::read : unknown version !"); // then the format of the aggregated set stay like it was during creation: void
defaultCommonEditProperties(); break;
} if ((nKey != -1) && m_xAggregateSet.is())
{
m_xAggregateSet->setPropertyValue(PROPERTY_FORMATSSUPPLIER, Any(xSupplier));
m_xAggregateSet->setPropertyValue(PROPERTY_FORMATKEY, Any(nKey));
} else
{
setPropertyToDefault(PROPERTY_FORMATSSUPPLIER);
setPropertyToDefault(PROPERTY_FORMATKEY);
}
}
sal_uInt16 OFormattedModel::getPersistenceFlags() const
{ return (OEditBaseModel::getPersistenceFlags() & ~PF_HANDLE_COMMON_PROPS); // a) we do our own call to writeCommonEditProperties
}
bool OFormattedModel::commitControlValueToDbColumn( bool/*_bPostReset*/ )
{
Any aControlValue( m_xAggregateFastSet->getFastPropertyValue( getValuePropertyAggHandle() ) ); if ( aControlValue == m_aSaveValue ) returntrue;
Any OFormattedModel::translateControlValueToExternalValue( ) const
{
OSL_PRECOND( hasExternalValueBinding(), "OFormattedModel::translateControlValueToExternalValue: precondition not met!" );
Any aControlValue( getControlValue() ); if ( !aControlValue.hasValue() ) return aControlValue;
Any aExternalValue; // translate into the external value type
Type aExternalValueType( getExternalValueType() ); switch ( aExternalValueType.getTypeClass() )
{ case TypeClass_STRING:
{
OUString sString; if ( aControlValue >>= sString )
{
aExternalValue <<= sString; break;
}
[[fallthrough]];
} case TypeClass_BOOLEAN:
{ double fValue = 0;
OSL_VERIFY( aControlValue >>= fValue ); // if this asserts ... well, the somebody set the TreatAsNumeric property to false, // and the control value is a string. This implies some weird misconfiguration // of the FormattedModel, so we won't care for it for the moment.
aExternalValue <<= fValue != 0.0;
} break; default:
{ double fValue = 0;
OSL_VERIFY( aControlValue >>= fValue ); // if this asserts ... well, the somebody set the TreatAsNumeric property to false, // and the control value is a string. This implies some weird misconfiguration // of the FormattedModel, so we won't care for it for the moment. if ( aExternalValueType.equals( cppu::UnoType< css::util::Date >::get() ) )
{
aExternalValue <<= DBTypeConversion::toDate( fValue, m_aNullDate );
} elseif ( aExternalValueType.equals( cppu::UnoType< css::util::Time >::get() ) )
{
aExternalValue <<= DBTypeConversion::toTime( fValue );
} elseif ( aExternalValueType.equals( cppu::UnoType< css::util::DateTime >::get() ) )
{
aExternalValue <<= DBTypeConversion::toDateTime( fValue, m_aNullDate );
} else
{
OSL_ENSURE( aExternalValueType.equals( cppu::UnoType< double >::get() ), "OFormattedModel::translateControlValueToExternalValue: don't know how to translate this type!" );
aExternalValue <<= fValue;
}
} break;
} return aExternalValue;
}
Any OFormattedModel::translateDbColumnToControlValue()
{ if ( m_bNumeric )
m_aSaveValue <<= DBTypeConversion::getValue( m_xColumn, m_aNullDate ); // #100056# OJ else
m_aSaveValue <<= m_xColumn->getString(); if ( m_xColumn->wasNull() )
m_aSaveValue.clear(); return m_aSaveValue;
}
Sequence< Type > OFormattedModel::getSupportedBindingTypes()
{
::std::vector< Type > aTypes; switch ( m_nKeyType & ~NumberFormat::DEFINED )
{ case NumberFormat::DATE:
aTypes.push_back(cppu::UnoType< css::util::Date >::get() ); break; case NumberFormat::TIME:
aTypes.push_back(cppu::UnoType< css::util::Time >::get() ); break; case NumberFormat::DATETIME:
aTypes.push_back(cppu::UnoType< css::util::DateTime >::get() ); break; case NumberFormat::TEXT:
aTypes.push_back(cppu::UnoType< OUString >::get() ); break; case NumberFormat::LOGICAL:
aTypes.push_back(cppu::UnoType< sal_Bool >::get() ); break;
}
aTypes.push_back( cppu::UnoType< double >::get() ); return comphelper::containerToSequence(aTypes);
}
Any OFormattedModel::getDefaultForReset() const
{ return m_xAggregateSet->getPropertyValue( PROPERTY_EFFECTIVE_DEFAULT );
}
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.