/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::lang; using namespace ::com::sun::star::i18n; using ::com::sun::star::awt::FontDescriptor; #define UNOCONTROL_STREAMVERSION short(2) static void lcl_ImplMergeFontProperty( FontDescriptor& rFD, sal_uInt16 nPropId, const Any& rValue ) { // some props are defined with other types than the matching FontDescriptor members have // (e.g. FontWidth, FontSlant) // 78474 - 09/19/2000 - FS float nExtractFloat = 0; sal_Int16 nExtractShort = 0; switch ( nPropId ) { case BASEPROPERTY_FONTDESCRIPTORPART_NAME: rValue >>= rFD.Name; break; case BASEPROPERTY_FONTDESCRIPTORPART_STYLENAME: rValue >>= rFD.StyleName; break; case BASEPROPERTY_FONTDESCRIPTORPART_FAMILY: rValue >>= rFD.Family; break; case BASEPROPERTY_FONTDESCRIPTORPART_CHARSET: rValue >>= rFD.CharSet; break; case BASEPROPERTY_FONTDESCRIPTORPART_HEIGHT: rValue >>= nExtractFloat; rFD.Height = static_cast(nExtractFloat); break; case BASEPROPERTY_FONTDESCRIPTORPART_WEIGHT: rValue >>= rFD.Weight; break; case BASEPROPERTY_FONTDESCRIPTORPART_SLANT: if ( rValue >>= nExtractShort ) rFD.Slant = static_cast(nExtractShort); else rValue >>= rFD.Slant; break; case BASEPROPERTY_FONTDESCRIPTORPART_UNDERLINE: rValue >>= rFD.Underline; break; case BASEPROPERTY_FONTDESCRIPTORPART_STRIKEOUT: rValue >>= rFD.Strikeout; break; case BASEPROPERTY_FONTDESCRIPTORPART_WIDTH: rValue >>= rFD.Width; break; case BASEPROPERTY_FONTDESCRIPTORPART_PITCH: rValue >>= rFD.Pitch; break; case BASEPROPERTY_FONTDESCRIPTORPART_CHARWIDTH: rValue >>= rFD.CharacterWidth; break; case BASEPROPERTY_FONTDESCRIPTORPART_ORIENTATION: rValue >>= rFD.Orientation; break; case BASEPROPERTY_FONTDESCRIPTORPART_KERNING: rValue >>= rFD.Kerning; break; case BASEPROPERTY_FONTDESCRIPTORPART_WORDLINEMODE: rValue >>= rFD.WordLineMode; break; case BASEPROPERTY_FONTDESCRIPTORPART_TYPE: rValue >>= rFD.Type; break; default: OSL_FAIL( "FontProperty?!" ); } } UnoControlModel::UnoControlModel( const Reference< XComponentContext >& rxContext ) :UnoControlModel_Base() ,maDisposeListeners( *this ) ,m_xContext( rxContext ) { // Insert properties from Model into table, // only existing properties are valid, even if they're VOID } UnoControlModel::UnoControlModel( const UnoControlModel& rModel ) : UnoControlModel_Base(), OPropertySetHelper() , maData( rModel.maData ) , maDisposeListeners( *this ) , m_xContext( rModel.m_xContext ) { } css::uno::Sequence UnoControlModel::ImplGetPropertyIds() const { sal_uInt32 nIDs = maData.size(); css::uno::Sequence aIDs( nIDs ); sal_Int32* pIDs = aIDs.getArray(); sal_uInt32 n = 0; for ( const auto& rData : maData ) pIDs[n++] = rData.first; return aIDs; } bool UnoControlModel::ImplHasProperty( sal_uInt16 nPropId ) const { if ( ( nPropId >= BASEPROPERTY_FONTDESCRIPTORPART_START ) && ( nPropId <= BASEPROPERTY_FONTDESCRIPTORPART_END ) ) nPropId = BASEPROPERTY_FONTDESCRIPTOR; return maData.find( nPropId ) != maData.end(); } css::uno::Any UnoControlModel::ImplGetDefaultValue( sal_uInt16 nPropId ) const { css::uno::Any aDefault; if ( (nPropId == BASEPROPERTY_FONTDESCRIPTOR) || ( (nPropId >= BASEPROPERTY_FONTDESCRIPTORPART_START) && (nPropId <= BASEPROPERTY_FONTDESCRIPTORPART_END) ) ) { EmptyFontDescriptor aFD; switch ( nPropId ) { case BASEPROPERTY_FONTDESCRIPTOR: aDefault <<= aFD; break; case BASEPROPERTY_FONTDESCRIPTORPART_NAME: aDefault <<= aFD.Name; break; case BASEPROPERTY_FONTDESCRIPTORPART_STYLENAME: aDefault <<= aFD.StyleName; break; case BASEPROPERTY_FONTDESCRIPTORPART_FAMILY: aDefault <<= aFD.Family; break; case BASEPROPERTY_FONTDESCRIPTORPART_CHARSET: aDefault <<= aFD.CharSet; break; case BASEPROPERTY_FONTDESCRIPTORPART_HEIGHT: aDefault <<= static_cast(aFD.Height); break; case BASEPROPERTY_FONTDESCRIPTORPART_WEIGHT: aDefault <<= aFD.Weight; break; case BASEPROPERTY_FONTDESCRIPTORPART_SLANT: aDefault <<= static_cast(aFD.Slant); break; case BASEPROPERTY_FONTDESCRIPTORPART_UNDERLINE: aDefault <<= aFD.Underline; break; case BASEPROPERTY_FONTDESCRIPTORPART_STRIKEOUT: aDefault <<= aFD.Strikeout; break; case BASEPROPERTY_FONTDESCRIPTORPART_WIDTH: aDefault <<= aFD.Width; break; case BASEPROPERTY_FONTDESCRIPTORPART_PITCH: aDefault <<= aFD.Pitch; break; case BASEPROPERTY_FONTDESCRIPTORPART_CHARWIDTH: aDefault <<= aFD.CharacterWidth; break; case BASEPROPERTY_FONTDESCRIPTORPART_ORIENTATION: aDefault <<= aFD.Orientation; break; case BASEPROPERTY_FONTDESCRIPTORPART_KERNING: aDefault <<= aFD.Kerning; break; case BASEPROPERTY_FONTDESCRIPTORPART_WORDLINEMODE: aDefault <<= aFD.WordLineMode; break; case BASEPROPERTY_FONTDESCRIPTORPART_TYPE: aDefault <<= aFD.Type; break; default: OSL_FAIL( "FontProperty?!" ); } } else { switch ( nPropId ) { case BASEPROPERTY_GRAPHIC: aDefault <<= Reference< graphic::XGraphic >(); break; case BASEPROPERTY_REFERENCE_DEVICE: aDefault <<= Reference< awt::XDevice >(); break; case BASEPROPERTY_ITEM_SEPARATOR_POS: case BASEPROPERTY_VERTICALALIGN: case BASEPROPERTY_BORDERCOLOR: case BASEPROPERTY_SYMBOL_COLOR: case BASEPROPERTY_TABSTOP: case BASEPROPERTY_TEXTCOLOR: case BASEPROPERTY_TEXTLINECOLOR: case BASEPROPERTY_DATE: case BASEPROPERTY_DATESHOWCENTURY: case BASEPROPERTY_TIME: case BASEPROPERTY_VALUE_DOUBLE: case BASEPROPERTY_PROGRESSVALUE: case BASEPROPERTY_SCROLLVALUE: case BASEPROPERTY_VISIBLESIZE: case BASEPROPERTY_BACKGROUNDCOLOR: case BASEPROPERTY_FILLCOLOR: case BASEPROPERTY_HIGHLIGHT_COLOR: case BASEPROPERTY_HIGHLIGHT_TEXT_COLOR: break; // Void case BASEPROPERTY_FONTRELIEF: case BASEPROPERTY_FONTEMPHASISMARK: case BASEPROPERTY_MAXTEXTLEN: case BASEPROPERTY_STATE: case BASEPROPERTY_EXTDATEFORMAT: case BASEPROPERTY_EXTTIMEFORMAT: case BASEPROPERTY_ECHOCHAR: aDefault <<= sal_Int16(0); break; case BASEPROPERTY_BORDER: aDefault <<= sal_Int16(1); break; case BASEPROPERTY_DECIMALACCURACY: aDefault <<= sal_Int16(2); break; case BASEPROPERTY_LINECOUNT: aDefault <<= sal_Int16(5); break; case BASEPROPERTY_ALIGN: aDefault <<= sal_Int16(PROPERTY_ALIGN_LEFT); break; case BASEPROPERTY_IMAGEALIGN: aDefault <<= sal_Int16(1) /*ImageAlign::Top*/; break; case BASEPROPERTY_IMAGEPOSITION: aDefault <<= sal_Int16(12) /*ImagePosition::Centered*/; break; case BASEPROPERTY_PUSHBUTTONTYPE: aDefault <<= sal_Int16(0) /*PushButtonType::STANDARD*/; break; case BASEPROPERTY_MOUSE_WHEEL_BEHAVIOUR:aDefault <<= sal_Int16(awt::MouseWheelBehavior::SCROLL_FOCUS_ONLY); break; case BASEPROPERTY_DATEMAX: aDefault <<= util::Date( 31, 12, 2200 ); break; case BASEPROPERTY_DATEMIN: aDefault <<= util::Date( 1, 1, 1900 ); break; case BASEPROPERTY_TIMEMAX: aDefault <<= util::Time(0, 0, 59, 23, false); break; case BASEPROPERTY_TIMEMIN: aDefault <<= util::Time(); break; case BASEPROPERTY_VALUEMAX_DOUBLE: aDefault <<= double(1000000); break; case BASEPROPERTY_VALUEMIN_DOUBLE: aDefault <<= double(-1000000); break; case BASEPROPERTY_VALUESTEP_DOUBLE: aDefault <<= double(1); break; case BASEPROPERTY_PROGRESSVALUE_MAX: aDefault <<= sal_Int32(100); break; case BASEPROPERTY_PROGRESSVALUE_MIN: aDefault <<= sal_Int32(0); break; case BASEPROPERTY_SCROLLVALUE_MAX: aDefault <<= sal_Int32(100); break; case BASEPROPERTY_SCROLLVALUE_MIN: aDefault <<= sal_Int32(0); break; case BASEPROPERTY_LINEINCREMENT: aDefault <<= sal_Int32(1); break; case BASEPROPERTY_BLOCKINCREMENT: aDefault <<= sal_Int32(10); break; case BASEPROPERTY_ORIENTATION: aDefault <<= sal_Int32(0); break; case BASEPROPERTY_SPINVALUE: aDefault <<= sal_Int32(0); break; case BASEPROPERTY_SPININCREMENT: aDefault <<= sal_Int32(1); break; case BASEPROPERTY_SPINVALUE_MIN: aDefault <<= sal_Int32(0); break; case BASEPROPERTY_SPINVALUE_MAX: aDefault <<= sal_Int32(100); break; case BASEPROPERTY_REPEAT_DELAY: aDefault <<= sal_Int32(50); break; // 50 milliseconds case BASEPROPERTY_DEFAULTCONTROL: aDefault <<= const_cast(this)->getServiceName(); break; case BASEPROPERTY_AUTOHSCROLL: case BASEPROPERTY_AUTOVSCROLL: case BASEPROPERTY_MOVEABLE: case BASEPROPERTY_CLOSEABLE: case BASEPROPERTY_SIZEABLE: case BASEPROPERTY_HSCROLL: case BASEPROPERTY_DEFAULTBUTTON: case BASEPROPERTY_MULTILINE: case BASEPROPERTY_MULTISELECTION: case BASEPROPERTY_TRISTATE: case BASEPROPERTY_DROPDOWN: case BASEPROPERTY_SPIN: case BASEPROPERTY_READONLY: case BASEPROPERTY_VSCROLL: case BASEPROPERTY_NUMSHOWTHOUSANDSEP: case BASEPROPERTY_STRICTFORMAT: case BASEPROPERTY_REPEAT: case BASEPROPERTY_PAINTTRANSPARENT: case BASEPROPERTY_DESKTOP_AS_PARENT: case BASEPROPERTY_HARDLINEBREAKS: case BASEPROPERTY_NOLABEL: aDefault <<= false; break; case BASEPROPERTY_MULTISELECTION_SIMPLEMODE: case BASEPROPERTY_HIDEINACTIVESELECTION: case BASEPROPERTY_ENFORCE_FORMAT: case BASEPROPERTY_AUTOCOMPLETE: case BASEPROPERTY_SCALEIMAGE: case BASEPROPERTY_ENABLED: case BASEPROPERTY_PRINTABLE: case BASEPROPERTY_ENABLEVISIBLE: case BASEPROPERTY_DECORATION: aDefault <<= true; break; case BASEPROPERTY_GROUPNAME: case BASEPROPERTY_HELPTEXT: case BASEPROPERTY_HELPURL: case BASEPROPERTY_IMAGEURL: case BASEPROPERTY_DIALOGSOURCEURL: case BASEPROPERTY_EDITMASK: case BASEPROPERTY_LITERALMASK: case BASEPROPERTY_LABEL: case BASEPROPERTY_TITLE: case BASEPROPERTY_TEXT: aDefault <<= OUString(); break; case BASEPROPERTY_WRITING_MODE: case BASEPROPERTY_CONTEXT_WRITING_MODE: aDefault <<= text::WritingMode2::CONTEXT; break; case BASEPROPERTY_STRINGITEMLIST: { css::uno::Sequence< OUString> aStringSeq; aDefault <<= aStringSeq; } break; case BASEPROPERTY_TYPEDITEMLIST: { css::uno::Sequence< css::uno::Any > aAnySeq; aDefault <<= aAnySeq; } break; case BASEPROPERTY_SELECTEDITEMS: { css::uno::Sequence aINT16Seq; aDefault <<= aINT16Seq; } break; case BASEPROPERTY_CURRENCYSYMBOL: { OUString sDefaultCurrency( utl::ConfigManager::getDefaultCurrency() ); // extract the bank symbol sal_Int32 nSepPos = sDefaultCurrency.indexOf( '-' ); OUString sBankSymbol; if ( nSepPos >= 0 ) { sBankSymbol = sDefaultCurrency.copy( 0, nSepPos ); sDefaultCurrency = sDefaultCurrency.copy( nSepPos + 1 ); } // the remaining is the locale LocaleDataWrapper aLocaleInfo( m_xContext, LanguageTag(sDefaultCurrency) ); if ( sBankSymbol.isEmpty() ) sBankSymbol = aLocaleInfo.getCurrBankSymbol(); // look for the currency entry (for this language) which has the given bank symbol const Sequence< Currency2 > aAllCurrencies = aLocaleInfo.getAllCurrencies(); OUString sCurrencySymbol = aLocaleInfo.getCurrSymbol(); if ( sBankSymbol.isEmpty() ) { DBG_ASSERT( aAllCurrencies.hasElements(), "UnoControlModel::ImplGetDefaultValue: no currencies at all!" ); if ( aAllCurrencies.hasElements() ) { sBankSymbol = aAllCurrencies[0].BankSymbol; sCurrencySymbol = aAllCurrencies[0].Symbol; } } if ( !sBankSymbol.isEmpty() ) { bool bLegacy = false; bool bFound = false; for ( const Currency2& rCurrency : aAllCurrencies ) if ( rCurrency.BankSymbol == sBankSymbol ) { sCurrencySymbol = rCurrency.Symbol; if ( rCurrency.LegacyOnly ) bLegacy = true; else { bFound = true; break; } } DBG_ASSERT( bLegacy || bFound, "UnoControlModel::ImplGetDefaultValue: did not find the given bank symbol!" ); } aDefault <<= sCurrencySymbol; } break; default: OSL_FAIL( "ImplGetDefaultValue - unknown Property" ); } } return aDefault; } void UnoControlModel::ImplRegisterProperty( sal_uInt16 nPropId, const css::uno::Any& rDefault ) { maData[ nPropId ] = rDefault; } void UnoControlModel::ImplRegisterProperty( sal_uInt16 nPropId ) { ImplRegisterProperty( nPropId, ImplGetDefaultValue( nPropId ) ); if ( nPropId == BASEPROPERTY_FONTDESCRIPTOR ) { // some properties are not included in the FontDescriptor, but every time // when we have a FontDescriptor we want to have these properties too. // => Easier to register the here, instead everywhere where I register the FontDescriptor... ImplRegisterProperty( BASEPROPERTY_TEXTCOLOR ); ImplRegisterProperty( BASEPROPERTY_TEXTLINECOLOR ); ImplRegisterProperty( BASEPROPERTY_FONTRELIEF ); ImplRegisterProperty( BASEPROPERTY_FONTEMPHASISMARK ); } } void UnoControlModel::ImplRegisterProperties( const std::vector< sal_uInt16 > &rIds ) { for (const auto& rId : rIds) { if( !ImplHasProperty( rId ) ) ImplRegisterProperty( rId, ImplGetDefaultValue( rId ) ); } } // css::uno::XInterface css::uno::Any UnoControlModel::queryAggregation( const css::uno::Type & rType ) { Any aRet = UnoControlModel_Base::queryAggregation( rType ); if ( !aRet.hasValue() ) aRet = ::comphelper::OPropertySetHelper::queryInterface( rType ); return aRet; } // XInterface IMPLEMENT_FORWARD_REFCOUNT( UnoControlModel, UnoControlModel_Base ) // css::lang::XTypeProvider IMPLEMENT_FORWARD_XTYPEPROVIDER2( UnoControlModel, UnoControlModel_Base, ::comphelper::OPropertySetHelper ) uno::Reference< util::XCloneable > UnoControlModel::createClone() { rtl::Reference pClone = Clone(); return pClone; } // css::lang::XComponent void UnoControlModel::dispose( ) { std::unique_lock aGuard( m_aMutex ); css::lang::EventObject aEvt; aEvt.Source = static_cast(static_cast(this)); maDisposeListeners.disposeAndClear( aGuard, aEvt ); // let the property set helper notify our property listeners OPropertySetHelper::disposing(aGuard); } void UnoControlModel::addEventListener( const css::uno::Reference< css::lang::XEventListener >& rxListener ) { std::unique_lock aGuard( m_aMutex ); maDisposeListeners.addInterface( rxListener ); } void UnoControlModel::removeEventListener( const css::uno::Reference< css::lang::XEventListener >& rxListener ) { std::unique_lock aGuard( m_aMutex ); maDisposeListeners.removeInterface( rxListener ); } // css::beans::XPropertyState css::beans::PropertyState UnoControlModel::getPropertyState( const OUString& PropertyName ) { std::unique_lock aGuard( m_aMutex ); return getPropertyStateImpl(aGuard, PropertyName); } css::beans::PropertyState UnoControlModel::getPropertyStateImpl( std::unique_lock& rGuard, const OUString& PropertyName ) { sal_uInt16 nPropId = GetPropertyId( PropertyName ); css::uno::Any aValue = getPropertyValueImpl( rGuard, PropertyName ); css::uno::Any aDefault = ImplGetDefaultValue( nPropId ); return CompareProperties( aValue, aDefault ) ? css::beans::PropertyState_DEFAULT_VALUE : css::beans::PropertyState_DIRECT_VALUE; } css::uno::Sequence< css::beans::PropertyState > UnoControlModel::getPropertyStates( const css::uno::Sequence< OUString >& PropertyNames ) { std::unique_lock aGuard( m_aMutex ); sal_Int32 nNames = PropertyNames.getLength(); css::uno::Sequence< css::beans::PropertyState > aStates( nNames ); std::transform(PropertyNames.begin(), PropertyNames.end(), aStates.getArray(), [this, &aGuard](const OUString& rName) -> css::beans::PropertyState { return getPropertyStateImpl(aGuard, rName); }); return aStates; } void UnoControlModel::setPropertyToDefault( const OUString& PropertyName ) { Any aDefaultValue; { std::unique_lock aGuard( m_aMutex ); aDefaultValue = ImplGetDefaultValue( GetPropertyId( PropertyName ) ); } setPropertyValue( PropertyName, aDefaultValue ); } css::uno::Any UnoControlModel::getPropertyDefault( const OUString& rPropertyName ) { std::unique_lock aGuard( m_aMutex ); return ImplGetDefaultValue( GetPropertyId( rPropertyName ) ); } // css::io::XPersistObjec OUString UnoControlModel::getServiceName( ) { OSL_FAIL( "ServiceName of UnoControlModel ?!" ); return OUString(); } void UnoControlModel::write( const css::uno::Reference< css::io::XObjectOutputStream >& OutStream ) { std::unique_lock aGuard( m_aMutex ); css::uno::Reference< css::io::XMarkableStream > xMark( OutStream, css::uno::UNO_QUERY ); DBG_ASSERT( xMark.is(), "write: no css::io::XMarkableStream!" ); OutStream->writeShort( UNOCONTROL_STREAMVERSION ); o3tl::sorted_vector aProps; for (const auto& rData : maData) { if ( ( ( GetPropertyAttribs( rData.first ) & css::beans::PropertyAttribute::TRANSIENT ) == 0 ) && ( getPropertyStateImpl( aGuard, GetPropertyName( rData.first ) ) != css::beans::PropertyState_DEFAULT_VALUE ) ) { aProps.insert( rData.first ); } } sal_uInt32 nProps = aProps.size(); // Save FontProperty always in the old format (due to missing distinction // between 5.0 and 5.1) OutStream->writeLong( ( aProps.find( BASEPROPERTY_FONTDESCRIPTOR ) != aProps.end() ) ? ( nProps + 3 ) : nProps ); for ( const auto& rProp : aProps ) { sal_Int32 nPropDataBeginMark = xMark->createMark(); OutStream->writeLong( 0 ); // DataLen const css::uno::Any* pProp = &(maData[rProp]); OutStream->writeShort( rProp ); bool bVoid = pProp->getValueTypeClass() == css::uno::TypeClass_VOID; OutStream->writeBoolean( bVoid ); if ( !bVoid ) { const css::uno::Any& rValue = *pProp; const css::uno::Type& rType = rValue.getValueType(); if ( rType == cppu::UnoType< bool >::get() ) { bool b = false; rValue >>= b; OutStream->writeBoolean( b ); } else if ( rType == ::cppu::UnoType< OUString >::get() ) { OUString aUString; rValue >>= aUString; OutStream->writeUTF( aUString ); } else if ( rType == ::cppu::UnoType< ::cppu::UnoUnsignedShortType >::get() ) { sal_uInt16 n = 0; rValue >>= n; OutStream->writeShort( n ); } else if ( rType == cppu::UnoType::get() ) { sal_Int16 n = 0; rValue >>= n; OutStream->writeShort( n ); } else if ( rType == cppu::UnoType::get() ) { sal_uInt32 n = 0; rValue >>= n; OutStream->writeLong( n ); } else if ( rType == cppu::UnoType::get() ) { sal_Int32 n = 0; rValue >>= n; OutStream->writeLong( n ); } else if ( rType == cppu::UnoType::get() ) { double n = 0; rValue >>= n; OutStream->writeDouble( n ); } else if ( rType == cppu::UnoType< css::awt::FontDescriptor >::get() ) { css::awt::FontDescriptor aFD; rValue >>= aFD; OutStream->writeUTF( aFD.Name ); OutStream->writeShort( aFD.Height ); OutStream->writeShort( aFD.Width ); OutStream->writeUTF( aFD.StyleName ); OutStream->writeShort( aFD.Family ); OutStream->writeShort( aFD.CharSet ); OutStream->writeShort( aFD.Pitch ); OutStream->writeDouble( aFD.CharacterWidth ); OutStream->writeDouble( aFD.Weight ); OutStream->writeShort( sal::static_int_cast< sal_Int16 >(aFD.Slant) ); OutStream->writeShort( aFD.Underline ); OutStream->writeShort( aFD.Strikeout ); OutStream->writeDouble( aFD.Orientation ); OutStream->writeBoolean( aFD.Kerning ); OutStream->writeBoolean( aFD.WordLineMode ); OutStream->writeShort( aFD.Type ); } else if ( rType == cppu::UnoType::get() ) { css::util::Date d; rValue >>= d; OutStream->writeLong(d.Day + 100 * d.Month + 10000 * d.Year); // YYYYMMDD } else if ( rType == cppu::UnoType::get() ) { css::util::Time t; rValue >>= t; OutStream->writeLong( t.NanoSeconds / 1000000 + 100 * t.Seconds + 10000 * t.Minutes + 1000000 * t.Hours); // HHMMSShh } else if ( rType == cppu::UnoType< css::uno::Sequence< OUString> >::get() ) { css::uno::Sequence< OUString> aSeq; rValue >>= aSeq; tools::Long nEntries = aSeq.getLength(); OutStream->writeLong( nEntries ); for (const auto& rVal : aSeq) OutStream->writeUTF( rVal ); } else if ( rType == cppu::UnoType< cppu::UnoSequenceType >::get() ) { css::uno::Sequence aSeq; rValue >>= aSeq; tools::Long nEntries = aSeq.getLength(); OutStream->writeLong( nEntries ); for (const auto nVal : aSeq) OutStream->writeShort( nVal ); } else if ( rType == cppu::UnoType< css::uno::Sequence >::get() ) { css::uno::Sequence aSeq; rValue >>= aSeq; tools::Long nEntries = aSeq.getLength(); OutStream->writeLong( nEntries ); for (const auto nVal : aSeq) OutStream->writeShort( nVal ); } else if ( rType.getTypeClass() == TypeClass_ENUM ) { sal_Int32 nAsInt = 0; ::cppu::enum2int( nAsInt, rValue ); OutStream->writeLong( nAsInt ); } #if OSL_DEBUG_LEVEL > 0 else { SAL_WARN( "toolkit", "UnoControlModel::write: don't know how to handle a property of type '" << rType.getTypeName() << "'.\n(Currently handling property '" << GetPropertyName( rProp ) << "'.)"); } #endif } sal_Int32 nPropDataLen = xMark->offsetToMark( nPropDataBeginMark ); xMark->jumpToMark( nPropDataBeginMark ); OutStream->writeLong( nPropDataLen ); xMark->jumpToFurthest(); xMark->deleteMark(nPropDataBeginMark); } if ( aProps.find( BASEPROPERTY_FONTDESCRIPTOR ) == aProps.end() ) return; const css::uno::Any* pProp = &maData[ BASEPROPERTY_FONTDESCRIPTOR ]; // Until 5.0 export arrives, write old format... css::awt::FontDescriptor aFD; (*pProp) >>= aFD; for ( sal_uInt16 n = BASEPROPERTY_FONT_TYPE; n <= BASEPROPERTY_FONT_ATTRIBS; n++ ) { sal_Int32 nPropDataBeginMark = xMark->createMark(); OutStream->writeLong( 0 ); // DataLen OutStream->writeShort( n ); // PropId OutStream->writeBoolean( false ); // Void if ( n == BASEPROPERTY_FONT_TYPE ) { OutStream->writeUTF( aFD.Name ); OutStream->writeUTF( aFD.StyleName ); OutStream->writeShort( aFD.Family ); OutStream->writeShort( aFD.CharSet ); OutStream->writeShort( aFD.Pitch ); } else if ( n == BASEPROPERTY_FONT_SIZE ) { OutStream->writeLong( aFD.Width ); OutStream->writeLong( aFD.Height ); OutStream->writeShort( sal::static_int_cast< sal_Int16 >( vcl::unohelper::ConvertFontWidth(aFD.CharacterWidth)) ); } else if ( n == BASEPROPERTY_FONT_ATTRIBS ) { OutStream->writeShort( sal::static_int_cast< sal_Int16 >( vcl::unohelper::ConvertFontWeight(aFD.Weight)) ); OutStream->writeShort( sal::static_int_cast< sal_Int16 >(aFD.Slant) ); OutStream->writeShort( aFD.Underline ); OutStream->writeShort( aFD.Strikeout ); OutStream->writeShort( static_cast(aFD.Orientation * 10) ); OutStream->writeBoolean( aFD.Kerning ); OutStream->writeBoolean( aFD.WordLineMode ); } else { OSL_FAIL( "Property?!" ); } sal_Int32 nPropDataLen = xMark->offsetToMark( nPropDataBeginMark ); xMark->jumpToMark( nPropDataBeginMark ); OutStream->writeLong( nPropDataLen ); xMark->jumpToFurthest(); xMark->deleteMark(nPropDataBeginMark); } } void UnoControlModel::read( const css::uno::Reference< css::io::XObjectInputStream >& InStream ) { std::unique_lock aGuard( m_aMutex ); css::uno::Reference< css::io::XMarkableStream > xMark( InStream, css::uno::UNO_QUERY ); DBG_ASSERT( xMark.is(), "read: no css::io::XMarkableStream!" ); short nVersion = InStream->readShort(); sal_uInt32 nProps = static_cast(InStream->readLong()); css::uno::Sequence< OUString> aProps( nProps ); css::uno::Sequence< css::uno::Any> aValues( nProps ); bool bInvalidEntries = false; // Unfortunately, there's no mark for the whole block, thus only properties may be changed. // No data for the model may be added following the properties // Used for import of old parts in css::awt::FontDescriptor std::unique_ptr pFD; for ( sal_uInt32 i = 0; i < nProps; i++ ) { sal_Int32 nPropDataBeginMark = xMark->createMark(); sal_Int32 nPropDataLen = InStream->readLong(); sal_uInt16 nPropId = static_cast(InStream->readShort()); css::uno::Any aValue; bool bIsVoid = InStream->readBoolean(); if ( !bIsVoid ) { if ( maData.find( nPropId ) != maData.end() ) { const css::uno::Type* pType = GetPropertyType( nPropId ); if ( *pType == cppu::UnoType::get() ) { bool b = InStream->readBoolean(); aValue <<= b; } else if ( *pType == cppu::UnoType::get() ) { OUString aUTF = InStream->readUTF(); aValue <<= aUTF; } else if ( *pType == ::cppu::UnoType< ::cppu::UnoUnsignedShortType >::get() ) { sal_uInt16 n = InStream->readShort(); aValue <<= n; } else if ( *pType == cppu::UnoType::get() ) { sal_Int16 n = InStream->readShort(); aValue <<= n; } else if ( *pType == cppu::UnoType::get() ) { sal_uInt32 n = InStream->readLong(); aValue <<= n; } else if ( *pType == cppu::UnoType::get() ) { sal_Int32 n = InStream->readLong(); aValue <<= n; } else if ( *pType == cppu::UnoType::get() ) { double n = InStream->readDouble(); aValue <<= n; } else if ( *pType == cppu::UnoType< css::awt::FontDescriptor >::get() ) { css::awt::FontDescriptor aFD; aFD.Name = InStream->readUTF(); aFD.Height = InStream->readShort(); aFD.Width = InStream->readShort(); aFD.StyleName = InStream->readUTF(); aFD.Family = InStream->readShort(); aFD.CharSet = InStream->readShort(); aFD.Pitch = InStream->readShort(); aFD.CharacterWidth = static_cast(InStream->readDouble()); aFD.Weight = static_cast(InStream->readDouble()); aFD.Slant = static_cast(InStream->readShort()); aFD.Underline = InStream->readShort(); aFD.Strikeout = InStream->readShort(); aFD.Orientation = static_cast(InStream->readDouble()); aFD.Kerning = InStream->readBoolean() != 0; aFD.WordLineMode = InStream->readBoolean() != 0; aFD.Type = InStream->readShort(); aValue <<= aFD; } else if ( *pType == cppu::UnoType::get() ) { sal_Int32 n = InStream->readLong(); // YYYYMMDD aValue <<= css::util::Date( n % 100, (n / 100) % 100, n / 10000); } else if ( *pType == cppu::UnoType::get() ) { sal_Int32 n = InStream->readLong(); // HHMMSShh aValue <<= css::util::Time( (n % 100) * 1000000, (n / 100) % 100, (n / 10000) % 100, n / 1000000, false); } else if ( *pType == cppu::UnoType< css::uno::Sequence< OUString> >::get() ) { tools::Long nEntries = InStream->readLong(); css::uno::Sequence< OUString> aSeq( nEntries ); for ( tools::Long n = 0; n < nEntries; n++ ) aSeq.getArray()[n] = InStream->readUTF(); aValue <<= aSeq; } else if ( *pType == cppu::UnoType< cppu::UnoSequenceType >::get() ) { tools::Long nEntries = InStream->readLong(); css::uno::Sequence aSeq( nEntries ); for ( tools::Long n = 0; n < nEntries; n++ ) aSeq.getArray()[n] = static_cast(InStream->readShort()); aValue <<= aSeq; } else if ( *pType == cppu::UnoType< css::uno::Sequence >::get() ) { tools::Long nEntries = InStream->readLong(); css::uno::Sequence aSeq( nEntries ); for ( tools::Long n = 0; n < nEntries; n++ ) aSeq.getArray()[n] = InStream->readShort(); aValue <<= aSeq; } else if ( pType->getTypeClass() == TypeClass_ENUM ) { sal_Int32 nAsInt = InStream->readLong(); aValue = ::cppu::int2enum( nAsInt, *pType ); } else { SAL_WARN( "toolkit", "UnoControlModel::read: don't know how to handle a property of type '" << pType->getTypeName() << "'.\n(Currently handling property '" << GetPropertyName( nPropId ) << "'.)"); } } else { // Old trash from 5.0 if ( nPropId == BASEPROPERTY_FONT_TYPE ) { // Redundant information for older versions // is skipped by MarkableStream if ( nVersion < 2 ) { if ( !pFD ) { pFD.reset(new css::awt::FontDescriptor); auto it = maData.find( BASEPROPERTY_FONTDESCRIPTOR ); if ( it != maData.end() ) // due to defaults... it->second >>= *pFD; } pFD->Name = InStream->readUTF(); pFD->StyleName = InStream->readUTF(); pFD->Family = InStream->readShort(); pFD->CharSet = InStream->readShort(); pFD->Pitch = InStream->readShort(); } } else if ( nPropId == BASEPROPERTY_FONT_SIZE ) { if ( nVersion < 2 ) { if ( !pFD ) { pFD.reset(new css::awt::FontDescriptor); auto it = maData.find(BASEPROPERTY_FONTDESCRIPTOR); if ( it != maData.end() ) // due to defaults... it->second >>= *pFD; } pFD->Width = static_cast(InStream->readLong()); pFD->Height = static_cast(InStream->readLong()); InStream->readShort(); // ignore css::awt::FontWidth - it was // misspelled and is no longer needed pFD->CharacterWidth = css::awt::FontWidth::DONTKNOW; } } else if ( nPropId == BASEPROPERTY_FONT_ATTRIBS ) { if ( nVersion < 2 ) { if ( !pFD ) { pFD.reset(new css::awt::FontDescriptor); auto it = maData.find(BASEPROPERTY_FONTDESCRIPTOR); if ( it != maData.end() ) // due to defaults... it->second >>= *pFD; } pFD->Weight = vcl::unohelper::ConvertFontWeight(static_cast(InStream->readShort())); pFD->Slant = static_cast(InStream->readShort()); pFD->Underline = InStream->readShort(); pFD->Strikeout = InStream->readShort(); pFD->Orientation = static_cast(static_cast(InStream->readShort())) / 10; pFD->Kerning = InStream->readBoolean() != 0; pFD->WordLineMode = InStream->readBoolean() != 0; } } else { OSL_FAIL( "read: unknown Property!" ); } } } else // bVoid { if ( nPropId == BASEPROPERTY_FONTDESCRIPTOR ) { EmptyFontDescriptor aFD; aValue <<= aFD; } } if ( maData.find( nPropId ) != maData.end() ) { aProps.getArray()[i] = GetPropertyName( nPropId ); aValues.getArray()[i] = std::move(aValue); } else { bInvalidEntries = true; } // Skip rest of input if there is more data in stream than this version can handle xMark->jumpToMark( nPropDataBeginMark ); InStream->skipBytes( nPropDataLen ); xMark->deleteMark(nPropDataBeginMark); } if ( bInvalidEntries ) { for ( sal_Int32 i = 0; i < aProps.getLength(); i++ ) { if ( aProps.getConstArray()[i].isEmpty() ) { ::comphelper::removeElementAt( aProps, i ); ::comphelper::removeElementAt( aValues, i ); i--; } } } try { setPropertyValuesImpl( aGuard, aProps, aValues ); } catch ( const Exception& ) { DBG_UNHANDLED_EXCEPTION("toolkit.controls"); } if ( pFD ) { css::uno::Any aValue; aValue <<= *pFD; setFastPropertyValueImpl( aGuard, BASEPROPERTY_FONTDESCRIPTOR, aValue ); } } // css::lang::XServiceInfo OUString UnoControlModel::getImplementationName( ) { OSL_FAIL( "This method should be overridden!" ); return OUString(); } sal_Bool UnoControlModel::supportsService( const OUString& rServiceName ) { return cppu::supportsService(this, rServiceName); } css::uno::Sequence< OUString > UnoControlModel::getSupportedServiceNames( ) { return { u"com.sun.star.awt.UnoControlModel"_ustr }; } bool UnoControlModel::convertFastPropertyValue( std::unique_lock& rGuard, Any & rConvertedValue, Any & rOldValue, sal_Int32 nPropId, const Any& rValue ) { bool bVoid = rValue.getValueTypeClass() == css::uno::TypeClass_VOID; if ( bVoid ) { rConvertedValue.clear(); } else { const css::uno::Type* pDestType = GetPropertyType( static_cast(nPropId) ); if ( pDestType->getTypeClass() == TypeClass_ANY ) { rConvertedValue = rValue; } else { if ( pDestType->equals( rValue.getValueType() ) ) { rConvertedValue = rValue; } else { bool bConverted = false; // 13.03.2001 - 84923 - frank.schoenheit@germany.sun.com switch (pDestType->getTypeClass()) { case TypeClass_DOUBLE: { // try as double double nAsDouble = 0; bConverted = ( rValue >>= nAsDouble ); if ( bConverted ) rConvertedValue <<= nAsDouble; else { // try as integer sal_Int32 nAsInteger = 0; bConverted = ( rValue >>= nAsInteger ); if ( bConverted ) rConvertedValue <<= static_cast(nAsInteger); } } break; case TypeClass_SHORT: { sal_Int16 n; bConverted = ( rValue >>= n ); if ( bConverted ) rConvertedValue <<= n; } break; case TypeClass_UNSIGNED_SHORT: { sal_uInt16 n; bConverted = ( rValue >>= n ); if ( bConverted ) rConvertedValue <<= n; } break; case TypeClass_LONG: { sal_Int32 n; bConverted = ( rValue >>= n ); if ( bConverted ) rConvertedValue <<= n; } break; case TypeClass_UNSIGNED_LONG: { sal_uInt32 n; bConverted = ( rValue >>= n ); if ( bConverted ) rConvertedValue <<= n; } break; case TypeClass_INTERFACE: { if ( rValue.getValueTypeClass() == TypeClass_INTERFACE ) { Reference< XInterface > xPure( rValue, UNO_QUERY ); if ( xPure.is() ) rConvertedValue = xPure->queryInterface( *pDestType ); else rConvertedValue.setValue( nullptr, *pDestType ); bConverted = true; } } break; case TypeClass_ENUM: { sal_Int32 nValue = 0; bConverted = ( rValue >>= nValue ); if ( bConverted ) rConvertedValue = ::cppu::int2enum( nValue, *pDestType ); } break; default: ; // avoid compiler warning } if (!bConverted) { throw css::lang::IllegalArgumentException( "Unable to convert the given value for the property " + GetPropertyName( static_cast(nPropId) ) + ".\nExpected type: " + pDestType->getTypeName() + "\nFound type: " + rValue.getValueTypeName(), static_cast< css::beans::XPropertySet* >(this), 1); } } } } // the current value getFastPropertyValue( rGuard, rOldValue, nPropId ); return !CompareProperties( rConvertedValue, rOldValue ); } void UnoControlModel::setFastPropertyValue_NoBroadcast( std::unique_lock& /*rGuard*/, sal_Int32 nPropId, const css::uno::Any& rValue ) { // Missing: the fake solo properties of the FontDescriptor ImplPropertyTable::const_iterator it = maData.find( nPropId ); const css::uno::Any* pProp = it == maData.end() ? nullptr : &(it->second); ENSURE_OR_RETURN_VOID( pProp, "UnoControlModel::setFastPropertyValue_NoBroadcast: invalid property id!" ); DBG_ASSERT( ( rValue.getValueTypeClass() != css::uno::TypeClass_VOID ) || ( GetPropertyAttribs( static_cast(nPropId) ) & css::beans::PropertyAttribute::MAYBEVOID ), "Property should not be VOID!" ); maData[ nPropId ] = rValue; } void UnoControlModel::getFastPropertyValue( std::unique_lock& /*rGuard*/, css::uno::Any& rValue, sal_Int32 nPropId ) const { ImplPropertyTable::const_iterator it = maData.find( nPropId ); const css::uno::Any* pProp = it == maData.end() ? nullptr : &(it->second); if ( pProp ) rValue = *pProp; else if ( ( nPropId >= BASEPROPERTY_FONTDESCRIPTORPART_START ) && ( nPropId <= BASEPROPERTY_FONTDESCRIPTORPART_END ) ) { const auto iter = maData.find( BASEPROPERTY_FONTDESCRIPTOR ); assert(iter != maData.end()); pProp = &(iter->second); css::awt::FontDescriptor aFD; (*pProp) >>= aFD; switch ( nPropId ) { case BASEPROPERTY_FONTDESCRIPTORPART_NAME: rValue <<= aFD.Name; break; case BASEPROPERTY_FONTDESCRIPTORPART_STYLENAME: rValue <<= aFD.StyleName; break; case BASEPROPERTY_FONTDESCRIPTORPART_FAMILY: rValue <<= aFD.Family; break; case BASEPROPERTY_FONTDESCRIPTORPART_CHARSET: rValue <<= aFD.CharSet; break; case BASEPROPERTY_FONTDESCRIPTORPART_HEIGHT: rValue <<= static_cast(aFD.Height); break; case BASEPROPERTY_FONTDESCRIPTORPART_WEIGHT: rValue <<= aFD.Weight; break; case BASEPROPERTY_FONTDESCRIPTORPART_SLANT: rValue <<= static_cast(aFD.Slant); break; case BASEPROPERTY_FONTDESCRIPTORPART_UNDERLINE: rValue <<= aFD.Underline; break; case BASEPROPERTY_FONTDESCRIPTORPART_STRIKEOUT: rValue <<= aFD.Strikeout; break; case BASEPROPERTY_FONTDESCRIPTORPART_WIDTH: rValue <<= aFD.Width; break; case BASEPROPERTY_FONTDESCRIPTORPART_PITCH: rValue <<= aFD.Pitch; break; case BASEPROPERTY_FONTDESCRIPTORPART_CHARWIDTH: rValue <<= aFD.CharacterWidth; break; case BASEPROPERTY_FONTDESCRIPTORPART_ORIENTATION: rValue <<= aFD.Orientation; break; case BASEPROPERTY_FONTDESCRIPTORPART_KERNING: rValue <<= aFD.Kerning; break; case BASEPROPERTY_FONTDESCRIPTORPART_WORDLINEMODE: rValue <<= aFD.WordLineMode; break; case BASEPROPERTY_FONTDESCRIPTORPART_TYPE: rValue <<= aFD.Type; break; default: OSL_FAIL( "FontProperty?!" ); } } else { OSL_FAIL( "getFastPropertyValue - invalid Property!" ); } } // css::beans::XFastPropertySet void UnoControlModel::setFastPropertyValueImpl( std::unique_lock& rGuard, sal_Int32 nPropId, const css::uno::Any& rValue ) { if ( ( nPropId >= BASEPROPERTY_FONTDESCRIPTORPART_START ) && ( nPropId <= BASEPROPERTY_FONTDESCRIPTORPART_END ) ) { Any aOldSingleValue; getFastPropertyValue( rGuard, aOldSingleValue, BASEPROPERTY_FONTDESCRIPTORPART_START ); css::uno::Any* pProp = &maData[ BASEPROPERTY_FONTDESCRIPTOR ]; FontDescriptor aFontDescriptor; (*pProp) >>= aFontDescriptor; lcl_ImplMergeFontProperty(aFontDescriptor, static_cast(nPropId), rValue); Any aNewValue; aNewValue <<= aFontDescriptor; sal_Int32 nDescriptorId = BASEPROPERTY_FONTDESCRIPTOR; // also, we need fire a propertyChange event for the single property, since with // the above line, only an event for the FontDescriptor property will be fired Any aNewSingleValue; getFastPropertyValue( rGuard, aNewSingleValue, BASEPROPERTY_FONTDESCRIPTORPART_START ); setFastPropertyValues( rGuard, 1, &nDescriptorId, &aNewValue, 1 ); fire( rGuard, &nPropId, &aNewSingleValue, &aOldSingleValue, 1, false ); } else setFastPropertyValues( rGuard, 1, &nPropId, &rValue, 1 ); } // css::beans::XMultiPropertySet css::uno::Reference< css::beans::XPropertySetInfo > UnoControlModel::getPropertySetInfo( ) { OSL_FAIL( "UnoControlModel::getPropertySetInfo() not possible!" ); return css::uno::Reference< css::beans::XPropertySetInfo >(); } void UnoControlModel::setPropertyValues( const css::uno::Sequence< OUString >& rPropertyNames, const css::uno::Sequence< css::uno::Any >& Values ) { std::unique_lock aGuard( m_aMutex ); setPropertyValuesImpl(aGuard, rPropertyNames, Values); } void UnoControlModel::setPropertyValuesImpl( std::unique_lock& rGuard, const css::uno::Sequence< OUString >& rPropertyNames, const css::uno::Sequence< css::uno::Any >& Values ) { sal_Int32 nProps = rPropertyNames.getLength(); if (nProps != Values.getLength()) throw css::lang::IllegalArgumentException(u"lengths do not match"_ustr, getXWeak(), -1); // sal_Int32* pHandles = new sal_Int32[nProps]; // don't do this - it leaks in case of an exception Sequence< sal_Int32 > aHandles( nProps ); sal_Int32* pHandles = aHandles.getArray(); // may need to change the order in the sequence, for this we need a non-const value sequence uno::Sequence< uno::Any > aValues( Values ); uno::Any* pValues = aValues.getArray(); sal_Int32 nValidHandles = getInfoHelper().fillHandles( pHandles, rPropertyNames ); if ( !nValidHandles ) return; // if somebody sets properties which are single aspects of a font descriptor, // remove them, and build a font descriptor instead std::unique_ptr< awt::FontDescriptor > pFD; for ( sal_Int32 n = 0; n < nProps; ++n ) { if ( ( pHandles[n] >= BASEPROPERTY_FONTDESCRIPTORPART_START ) && ( pHandles[n] <= BASEPROPERTY_FONTDESCRIPTORPART_END ) ) { if (!pFD) { css::uno::Any* pProp = &maData[ BASEPROPERTY_FONTDESCRIPTOR ]; pFD.reset( new awt::FontDescriptor ); (*pProp) >>= *pFD; } lcl_ImplMergeFontProperty( *pFD, static_cast(pHandles[n]), pValues[n] ); pHandles[n] = -1; nValidHandles--; } } if ( nValidHandles ) { ImplNormalizePropertySequence( nProps, pHandles, pValues, &nValidHandles ); setFastPropertyValues( rGuard, nProps, pHandles, pValues, nValidHandles ); } // Don't merge FD property into array, as it is sorted if (pFD) { css::uno::Any aValue; aValue <<= *pFD; sal_Int32 nHandle = BASEPROPERTY_FONTDESCRIPTOR; setFastPropertyValues( rGuard, 1, &nHandle, &aValue, 1 ); } } void UnoControlModel::ImplNormalizePropertySequence( const sal_Int32, sal_Int32*, uno::Any*, sal_Int32* ) const { // nothing to do here } void UnoControlModel::ImplEnsureHandleOrder( const sal_Int32 _nCount, sal_Int32* _pHandles, uno::Any* _pValues, sal_Int32 _nFirstHandle, sal_Int32 _nSecondHandle ) { for ( sal_Int32 i=0; i < _nCount; ++_pHandles, ++_pValues, ++i ) { if ( _nSecondHandle == *_pHandles ) { sal_Int32* pLaterHandles = _pHandles + 1; uno::Any* pLaterValues = _pValues + 1; for ( sal_Int32 j = i + 1; j < _nCount; ++j, ++pLaterHandles, ++pLaterValues ) { if ( _nFirstHandle == *pLaterHandles ) { // indeed it is -> exchange the both places in the sequences std::swap(*_pHandles, *pLaterHandles); std::swap(*_pValues, *pLaterValues); break; // this will leave the inner loop, and continue with the outer loop. // Note that this means we will encounter the _nSecondHandle handle, again, once we reached // (in the outer loop) the place where we just put it. } } } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */