diff options
Diffstat (limited to 'embeddedobj/source/msole/ownview.cxx')
-rw-r--r-- | embeddedobj/source/msole/ownview.cxx | 665 |
1 files changed, 665 insertions, 0 deletions
diff --git a/embeddedobj/source/msole/ownview.cxx b/embeddedobj/source/msole/ownview.cxx new file mode 100644 index 000000000000..92f233dd2a29 --- /dev/null +++ b/embeddedobj/source/msole/ownview.cxx @@ -0,0 +1,665 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_embeddedobj.hxx" +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XComponentLoader.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/embed/XClassifiedObject.hpp> +#include <com/sun/star/io/XStream.hpp> +#include <com/sun/star/io/XInputStream.hpp> +#include <com/sun/star/io/XOutputStream.hpp> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/task/XInteractionHandler.hpp> +#include <com/sun/star/ucb/XSimpleFileAccess.hpp> +#include <com/sun/star/util/XCloseable.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +#include <com/sun/star/document/XEventBroadcaster.hpp> +#include <com/sun/star/document/XEventListener.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <cppuhelper/implbase1.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/mimeconfighelper.hxx> + +#include "ownview.hxx" + + +using namespace ::com::sun::star; +using namespace ::comphelper; + +::rtl::OUString GetNewTempFileURL_Impl( const uno::Reference< lang::XMultiServiceFactory >& xFactory ) throw( io::IOException ); +::rtl::OUString GetNewFilledTempFile_Impl( const uno::Reference< io::XInputStream >& xInStream, const uno::Reference< lang::XMultiServiceFactory >& xFactory ) throw( io::IOException ); +sal_Bool KillFile_Impl( const ::rtl::OUString& aURL, const uno::Reference< lang::XMultiServiceFactory >& xFactory ); +uno::Reference< io::XStream > TryToGetAcceptableFormat_Impl( const uno::Reference< io::XStream >& xStream, const uno::Reference< lang::XMultiServiceFactory >& xFactory ) throw ( uno::Exception ); + +//======================================================== +// Dummy interaction handler +//======================================================== +//-------------------------------------------------------- +class DummyHandler_Impl : public ::cppu::WeakImplHelper1< task::XInteractionHandler > +{ +public: + DummyHandler_Impl() {} + ~DummyHandler_Impl(); + + virtual void SAL_CALL handle( const uno::Reference< task::XInteractionRequest >& xRequest ) + throw( uno::RuntimeException ); +}; + +//-------------------------------------------------------- +DummyHandler_Impl::~DummyHandler_Impl() +{ +} + +//-------------------------------------------------------- +void SAL_CALL DummyHandler_Impl::handle( const uno::Reference< task::XInteractionRequest >& ) + throw( uno::RuntimeException ) +{ + return; +} + +//======================================================== +// Object viewer +//======================================================== +//-------------------------------------------------------- +OwnView_Impl::OwnView_Impl( const uno::Reference< lang::XMultiServiceFactory >& xFactory, + const uno::Reference< io::XInputStream >& xInputStream ) +: m_xFactory( xFactory ) +, m_bBusy( sal_False ) +, m_bUseNative( sal_False ) +{ + if ( !xFactory.is() || !xInputStream.is() ) + throw uno::RuntimeException(); + + m_aTempFileURL = GetNewFilledTempFile_Impl( xInputStream, m_xFactory ); +} + +//-------------------------------------------------------- +OwnView_Impl::~OwnView_Impl() +{ + try { + KillFile_Impl( m_aTempFileURL, m_xFactory ); + } catch( uno::Exception& ) {} + + try { + if ( m_aNativeTempURL.getLength() ) + KillFile_Impl( m_aNativeTempURL, m_xFactory ); + } catch( uno::Exception& ) {} +} + +//-------------------------------------------------------- +sal_Bool OwnView_Impl::CreateModelFromURL( const ::rtl::OUString& aFileURL ) +{ + sal_Bool bResult = sal_False; + + if ( aFileURL.getLength() ) + { + try { + uno::Reference < frame::XComponentLoader > xDocumentLoader( + m_xFactory->createInstance ( + ::rtl::OUString::createFromAscii( "com.sun.star.frame.Desktop" ) ), + uno::UNO_QUERY ); + + if ( xDocumentLoader.is() ) + { + uno::Sequence< beans::PropertyValue > aArgs( m_aFilterName.getLength() ? 5 : 4 ); + + aArgs[0].Name = ::rtl::OUString::createFromAscii( "URL" ); + aArgs[0].Value <<= aFileURL; + + aArgs[1].Name = ::rtl::OUString::createFromAscii( "ReadOnly" ); + aArgs[1].Value <<= sal_True; + + aArgs[2].Name = ::rtl::OUString::createFromAscii( "InteractionHandler" ); + aArgs[2].Value <<= uno::Reference< task::XInteractionHandler >( + static_cast< ::cppu::OWeakObject* >( new DummyHandler_Impl() ), uno::UNO_QUERY ); + + aArgs[3].Name = ::rtl::OUString::createFromAscii( "DontEdit" ); + aArgs[3].Value <<= sal_True; + + if ( m_aFilterName.getLength() ) + { + aArgs[4].Name = ::rtl::OUString::createFromAscii( "FilterName" ); + aArgs[4].Value <<= m_aFilterName; + } + + uno::Reference< frame::XModel > xModel( xDocumentLoader->loadComponentFromURL( + aFileURL, + ::rtl::OUString::createFromAscii( "_blank" ), + 0, + aArgs ), + uno::UNO_QUERY ); + + if ( xModel.is() ) + { + uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY ); + if ( xBroadCaster.is() ) + xBroadCaster->addEventListener( uno::Reference< document::XEventListener >( + static_cast< ::cppu::OWeakObject* >( this ), + uno::UNO_QUERY ) ); + + uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY ); + if ( xCloseable.is() ) + { + xCloseable->addCloseListener( uno::Reference< util::XCloseListener >( + static_cast< ::cppu::OWeakObject* >( this ), + uno::UNO_QUERY ) ); + + ::osl::MutexGuard aGuard( m_aMutex ); + m_xModel = xModel; + bResult = sal_True; + } + } + } + } + catch( uno::Exception& ) + { + } + } + + return bResult; +} + +//-------------------------------------------------------- +sal_Bool OwnView_Impl::CreateModel( sal_Bool bUseNative ) +{ + sal_Bool bResult = sal_False; + + try { + bResult = CreateModelFromURL( bUseNative ? m_aNativeTempURL : m_aTempFileURL ); + } + catch( uno::Exception& ) + { + } + + return bResult; +} + +//-------------------------------------------------------- +::rtl::OUString OwnView_Impl::GetFilterNameFromExtentionAndInStream( + const ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory >& xFactory, + const ::rtl::OUString& aNameWithExtention, + const uno::Reference< io::XInputStream >& xInputStream ) +{ + if ( !xInputStream.is() ) + throw uno::RuntimeException(); + + uno::Reference< document::XTypeDetection > xTypeDetection( + xFactory->createInstance( ::rtl::OUString::createFromAscii( "com.sun.star.document.TypeDetection" ) ), + uno::UNO_QUERY_THROW ); + + ::rtl::OUString aTypeName; + + if ( aNameWithExtention.getLength() ) + { + ::rtl::OUString aURLToAnalyze = + ( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "file:///" ) ) + aNameWithExtention ); + aTypeName = xTypeDetection->queryTypeByURL( aURLToAnalyze ); + } + + uno::Sequence< beans::PropertyValue > aArgs( aTypeName.getLength() ? 3 : 2 ); + aArgs[0].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "URL" ) ); + aArgs[0].Value <<= ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "private:stream" ) ); + aArgs[1].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "InputStream" ) ); + aArgs[1].Value <<= xInputStream; + if ( aTypeName.getLength() ) + { + aArgs[2].Name = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "TypeName" ) ); + aArgs[2].Value <<= aTypeName; + } + + aTypeName = xTypeDetection->queryTypeByDescriptor( aArgs, sal_True ); + + ::rtl::OUString aFilterName; + for ( sal_Int32 nInd = 0; nInd < aArgs.getLength(); nInd++ ) + if ( aArgs[nInd].Name.equalsAscii( "FilterName" ) ) + aArgs[nInd].Value >>= aFilterName; + + if ( !aFilterName.getLength() && aTypeName.getLength() ) + { + // get the default filter name for the type + uno::Reference< container::XNameAccess > xNameAccess( xTypeDetection, uno::UNO_QUERY_THROW ); + uno::Sequence< beans::PropertyValue > aTypes; + + if ( xNameAccess.is() && ( xNameAccess->getByName( aTypeName ) >>= aTypes ) ) + { + for ( sal_Int32 nInd = 0; nInd < aTypes.getLength(); nInd++ ) + { + if ( aTypes[nInd].Name.equalsAscii( "PreferredFilter" ) && ( aTypes[nInd].Value >>= aFilterName ) ) + { + aTypes[nInd].Value >>= aFilterName; + break; + } + } + } + } + + return aFilterName; +} + +//-------------------------------------------------------- +sal_Bool OwnView_Impl::ReadContentsAndGenerateTempFile( const uno::Reference< io::XInputStream >& xInStream, + sal_Bool bParseHeader ) +{ + uno::Reference< io::XSeekable > xSeekable( xInStream, uno::UNO_QUERY_THROW ); + xSeekable->seek( 0 ); + + // create m_aNativeTempURL + ::rtl::OUString aNativeTempURL; + uno::Reference < beans::XPropertySet > xNativeTempFile( + m_xFactory->createInstance( ::rtl::OUString::createFromAscii( "com.sun.star.io.TempFile" ) ), + uno::UNO_QUERY_THROW ); + uno::Reference < io::XStream > xNativeTempStream( xNativeTempFile, uno::UNO_QUERY_THROW ); + uno::Reference < io::XOutputStream > xNativeOutTemp = xNativeTempStream->getOutputStream(); + uno::Reference < io::XInputStream > xNativeInTemp = xNativeTempStream->getInputStream(); + if ( !xNativeOutTemp.is() || !xNativeInTemp.is() ) + throw uno::RuntimeException(); + + try { + xNativeTempFile->setPropertyValue( ::rtl::OUString::createFromAscii( "RemoveFile" ), uno::makeAny( sal_False ) ); + uno::Any aUrl = xNativeTempFile->getPropertyValue( ::rtl::OUString::createFromAscii( "Uri" ) ); + aUrl >>= aNativeTempURL; + } + catch ( uno::Exception& ) + { + } + + sal_Bool bFailed = sal_False; + ::rtl::OUString aFileSuffix; + + if ( bParseHeader ) + { + uno::Sequence< sal_Int8 > aReadSeq( 4 ); + // read the complete size of the Object Package + if ( xInStream->readBytes( aReadSeq, 4 ) != 4 ) + return sal_False; +/* + sal_uInt32 nLength = (sal_uInt8)aReadSeq[0] + + (sal_uInt8)aReadSeq[1] * 0x100 + + (sal_uInt8)aReadSeq[2] * 0x10000 + + (sal_uInt8)aReadSeq[3] * 0x1000000; +*/ + // read the first header ( have no idea what does this header mean ) + if ( xInStream->readBytes( aReadSeq, 2 ) != 2 || aReadSeq[0] != 2 || aReadSeq[1] != 0 ) + return sal_False; + + // read file name + // only extension is interesting so only subset of symbols is accepted + do + { + if ( xInStream->readBytes( aReadSeq, 1 ) != 1 ) + return sal_False; + + if ( + (aReadSeq[0] >= '0' && aReadSeq[0] <= '9') || + (aReadSeq[0] >= 'a' && aReadSeq[0] <= 'z') || + (aReadSeq[0] >= 'A' && aReadSeq[0] <= 'Z') || + aReadSeq[0] == '.' + ) + { + aFileSuffix += ::rtl::OUString::valueOf( (sal_Unicode) aReadSeq[0] ); + } + + } while( aReadSeq[0] ); + + // skip url + do + { + if ( xInStream->readBytes( aReadSeq, 1 ) != 1 ) + return sal_False; + } while( aReadSeq[0] ); + + // check the next header + if ( xInStream->readBytes( aReadSeq, 4 ) != 4 + || aReadSeq[0] || aReadSeq[1] || aReadSeq[2] != 3 || aReadSeq[3] ) + return sal_False; + + // get the size of the next entry + if ( xInStream->readBytes( aReadSeq, 4 ) != 4 ) + return sal_False; + + sal_uInt32 nUrlSize = (sal_uInt8)aReadSeq[0] + + (sal_uInt8)aReadSeq[1] * 0x100 + + (sal_uInt8)aReadSeq[2] * 0x10000 + + (sal_uInt8)aReadSeq[3] * 0x1000000; + sal_Int64 nTargetPos = xSeekable->getPosition() + nUrlSize; + + xSeekable->seek( nTargetPos ); + + // get the size of stored data + if ( xInStream->readBytes( aReadSeq, 4 ) != 4 ) + return sal_False; + + sal_uInt32 nDataSize = (sal_uInt8)aReadSeq[0] + + (sal_uInt8)aReadSeq[1] * 0x100 + + (sal_uInt8)aReadSeq[2] * 0x10000 + + (sal_uInt8)aReadSeq[3] * 0x1000000; + + aReadSeq.realloc( 32000 ); + sal_uInt32 nRead = 0; + while ( nRead < nDataSize ) + { + sal_uInt32 nToRead = ( nDataSize - nRead > 32000 ) ? 32000 : nDataSize - nRead; + sal_uInt32 nLocalRead = xInStream->readBytes( aReadSeq, nToRead ); + + + if ( !nLocalRead ) + { + bFailed = sal_True; + break; + } + else if ( nLocalRead == 32000 ) + xNativeOutTemp->writeBytes( aReadSeq ); + else + { + uno::Sequence< sal_Int8 > aToWrite( aReadSeq ); + aToWrite.realloc( nLocalRead ); + xNativeOutTemp->writeBytes( aToWrite ); + } + + nRead += nLocalRead; + } + } + else + { + uno::Sequence< sal_Int8 > aData( 8 ); + if ( xInStream->readBytes( aData, 8 ) == 8 + && aData[0] == -1 && aData[1] == -1 && aData[2] == -1 && aData[3] == -1 + && ( aData[4] == 2 || aData[4] == 3 ) && aData[5] == 0 && aData[6] == 0 && aData[7] == 0 ) + { + // the header has to be removed + xSeekable->seek( 40 ); + } + else + { + // the usual Ole10Native format + xSeekable->seek( 4 ); + } + + ::comphelper::OStorageHelper::CopyInputToOutput( xInStream, xNativeOutTemp ); + } + + xNativeOutTemp->closeOutput(); + + // The temporary native file is created, now the filter must be detected + if ( !bFailed ) + { + m_aFilterName = GetFilterNameFromExtentionAndInStream( m_xFactory, aFileSuffix, xNativeInTemp ); + m_aNativeTempURL = aNativeTempURL; + } + + return !bFailed; +} + +//-------------------------------------------------------- +void OwnView_Impl::CreateNative() +{ + if ( m_aNativeTempURL.getLength() ) + return; + + try + { + uno::Reference < ucb::XSimpleFileAccess > xAccess( + m_xFactory->createInstance ( + ::rtl::OUString::createFromAscii( "com.sun.star.ucb.SimpleFileAccess" ) ), + uno::UNO_QUERY_THROW ); + + uno::Reference< io::XInputStream > xInStream = xAccess->openFileRead( m_aTempFileURL ); + if ( !xInStream.is() ) + throw uno::RuntimeException(); + + uno::Sequence< uno::Any > aArgs( 1 ); + aArgs[0] <<= xInStream; + uno::Reference< container::XNameAccess > xNameAccess( + m_xFactory->createInstanceWithArguments( + ::rtl::OUString::createFromAscii( "com.sun.star.embed.OLESimpleStorage" ), + aArgs ), + uno::UNO_QUERY_THROW ); + + ::rtl::OUString aSubStreamName = ::rtl::OUString::createFromAscii( "\1Ole10Native" ); + uno::Reference< embed::XClassifiedObject > xStor( xNameAccess, uno::UNO_QUERY_THROW ); + uno::Sequence< sal_Int8 > aStorClassID = xStor->getClassID(); + + if ( xNameAccess->hasByName( aSubStreamName ) ) + { + sal_uInt8 aClassID[] = + { 0x00, 0x03, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 }; + uno::Sequence< sal_Int8 > aPackageClassID( (sal_Int8*)aClassID, 16 ); + + uno::Reference< io::XStream > xSubStream; + xNameAccess->getByName( aSubStreamName ) >>= xSubStream; + if ( xSubStream.is() ) + { + sal_Bool bOk = sal_False; + + if ( MimeConfigurationHelper::ClassIDsEqual( aPackageClassID, aStorClassID ) ) + { + // the storage represents Object Package + + bOk = ReadContentsAndGenerateTempFile( xSubStream->getInputStream(), sal_True ); + + if ( !bOk && m_aNativeTempURL.getLength() ) + { + KillFile_Impl( m_aNativeTempURL, m_xFactory ); + m_aNativeTempURL = ::rtl::OUString(); + } + } + + if ( !bOk ) + { + bOk = ReadContentsAndGenerateTempFile( xSubStream->getInputStream(), sal_False ); + + if ( !bOk && m_aNativeTempURL.getLength() ) + { + KillFile_Impl( m_aNativeTempURL, m_xFactory ); + m_aNativeTempURL = ::rtl::OUString(); + } + } + } + } + else + { + // TODO/LATER: No native stream, needs a new solution + } + } + catch( uno::Exception& ) + {} +} + +//-------------------------------------------------------- +sal_Bool OwnView_Impl::Open() +{ + sal_Bool bResult = sal_False; + + uno::Reference< frame::XModel > xExistingModel; + + { + ::osl::MutexGuard aGuard( m_aMutex ); + xExistingModel = m_xModel; + if ( m_bBusy ) + return sal_False; + + m_bBusy = sal_True; + } + + if ( xExistingModel.is() ) + { + try { + uno::Reference< frame::XController > xController = xExistingModel->getCurrentController(); + if ( xController.is() ) + { + uno::Reference< frame::XFrame > xFrame = xController->getFrame(); + if ( xFrame.is() ) + { + xFrame->activate(); + uno::Reference<awt::XTopWindow> xTopWindow( xFrame->getContainerWindow(), uno::UNO_QUERY ); + if(xTopWindow.is()) + xTopWindow->toFront(); + + bResult = sal_True; + } + } + } + catch( uno::Exception& ) + { + } + } + else + { + bResult = CreateModel( m_bUseNative ); + + if ( !bResult && !m_bUseNative ) + { + // the original storage can not be recognized + if ( !m_aNativeTempURL.getLength() ) + { + // create a temporary file for the native representation if there is no + CreateNative(); + } + + if ( m_aNativeTempURL.getLength() ) + { + bResult = CreateModel( sal_True ); + if ( bResult ) + m_bUseNative = sal_True; + } + } + } + + m_bBusy = sal_False; + + return bResult; +} + +//-------------------------------------------------------- +void OwnView_Impl::Close() +{ + uno::Reference< frame::XModel > xModel; + + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !m_xModel.is() ) + return; + xModel = m_xModel; + m_xModel = uno::Reference< frame::XModel >(); + + if ( m_bBusy ) + return; + + m_bBusy = sal_True; + } + + try { + uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY ); + if ( xBroadCaster.is() ) + xBroadCaster->removeEventListener( uno::Reference< document::XEventListener >( + static_cast< ::cppu::OWeakObject* >( this ), + uno::UNO_QUERY ) ); + + uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY ); + if ( xCloseable.is() ) + { + xCloseable->removeCloseListener( uno::Reference< util::XCloseListener >( + static_cast< ::cppu::OWeakObject* >( this ), + uno::UNO_QUERY ) ); + xCloseable->close( sal_True ); + } + } + catch( uno::Exception& ) + {} + + m_bBusy = sal_False; +} + +//-------------------------------------------------------- +void SAL_CALL OwnView_Impl::notifyEvent( const document::EventObject& aEvent ) + throw ( uno::RuntimeException ) +{ + + uno::Reference< frame::XModel > xModel; + + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( aEvent.Source == m_xModel && aEvent.EventName.equalsAscii( "OnSaveAsDone" ) ) + { + // SaveAs operation took place, so just forget the model and deregister listeners + xModel = m_xModel; + m_xModel = uno::Reference< frame::XModel >(); + } + } + + if ( xModel.is() ) + { + try { + uno::Reference< document::XEventBroadcaster > xBroadCaster( xModel, uno::UNO_QUERY ); + if ( xBroadCaster.is() ) + xBroadCaster->removeEventListener( uno::Reference< document::XEventListener >( + static_cast< ::cppu::OWeakObject* >( this ), + uno::UNO_QUERY ) ); + + uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY ); + if ( xCloseable.is() ) + xCloseable->removeCloseListener( uno::Reference< util::XCloseListener >( + static_cast< ::cppu::OWeakObject* >( this ), + uno::UNO_QUERY ) ); + } + catch( uno::Exception& ) + {} + } +} + +//-------------------------------------------------------- +void SAL_CALL OwnView_Impl::queryClosing( const lang::EventObject&, sal_Bool ) + throw ( util::CloseVetoException, + uno::RuntimeException ) +{ +} + +//-------------------------------------------------------- +void SAL_CALL OwnView_Impl::notifyClosing( const lang::EventObject& Source ) + throw ( uno::RuntimeException ) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if ( Source.Source == m_xModel ) + m_xModel = uno::Reference< frame::XModel >(); +} + +//-------------------------------------------------------- +void SAL_CALL OwnView_Impl::disposing( const lang::EventObject& Source ) + throw (uno::RuntimeException) +{ + ::osl::MutexGuard aGuard( m_aMutex ); + if ( Source.Source == m_xModel ) + m_xModel = uno::Reference< frame::XModel >(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |