diff options
Diffstat (limited to 'sfx2/source/appl/appdde.cxx')
-rw-r--r-- | sfx2/source/appl/appdde.cxx | 570 |
1 files changed, 570 insertions, 0 deletions
diff --git a/sfx2/source/appl/appdde.cxx b/sfx2/source/appl/appdde.cxx new file mode 100644 index 000000000..d725d050f --- /dev/null +++ b/sfx2/source/appl/appdde.cxx @@ -0,0 +1,570 @@ +/* -*- 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 <sal/config.h> + +#include <string_view> + +#include <config_features.h> +#include <rtl/character.hxx> +#include <rtl/malformeduriexception.hxx> +#include <rtl/uri.hxx> +#include <sot/exchange.hxx> +#include <svl/eitem.hxx> +#include <basic/sbstar.hxx> +#include <svl/stritem.hxx> +#include <svl/svdde.hxx> +#include <sfx2/lnkbase.hxx> +#include <sfx2/linkmgr.hxx> + +#include <tools/debug.hxx> +#include <tools/urlobj.hxx> +#include <tools/diagnose_ex.h> +#include <unotools/pathoptions.hxx> +#include <vcl/svapp.hxx> + +#include <sfx2/app.hxx> +#include <appdata.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxsids.hrc> +#include <sfx2/docfile.hxx> +#include <ucbhelper/content.hxx> +#include <comphelper/processfactory.hxx> + +#if defined(_WIN32) + +static OUString SfxDdeServiceName_Impl( const OUString& sIn ) +{ + OUStringBuffer sReturn(sIn.getLength()); + + for ( sal_uInt16 n = sIn.getLength(); n; --n ) + { + sal_Unicode cChar = sIn[n-1]; + if (rtl::isAsciiAlphanumeric(cChar)) + sReturn.append(cChar); + } + + return sReturn.makeStringAndClear(); +} + +namespace { + +class ImplDdeService : public DdeService +{ +public: + explicit ImplDdeService( const OUString& rNm ) + : DdeService( rNm ) + {} + virtual bool MakeTopic( const OUString& ); + + virtual OUString Topics(); + + virtual bool SysTopicExecute( const OUString* pStr ); +}; + + bool lcl_IsDocument( std::u16string_view rContent ) + { + using namespace com::sun::star; + + bool bRet = false; + INetURLObject aObj( rContent ); + DBG_ASSERT( aObj.GetProtocol() != INetProtocol::NotValid, "Invalid URL!" ); + + try + { + ::ucbhelper::Content aCnt( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ), uno::Reference< ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() ); + bRet = aCnt.isDocument(); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sfx.appl", "" ); + } + + return bRet; + } +} + +bool ImplDdeService::MakeTopic( const OUString& rNm ) +{ + // Workaround for Event after Main() under OS/2 + // happens when exiting starts the App again + if ( !Application::IsInExecute() ) + return false; + + // The Topic rNm is sought, do we have it? + // First only loop over the ObjectShells to find those + // with the specific name: + bool bRet = false; + OUString sNm( rNm.toAsciiLowerCase() ); + SfxObjectShell* pShell = SfxObjectShell::GetFirst(); + while( pShell ) + { + OUString sTmp( pShell->GetTitle(SFX_TITLE_FULLNAME) ); + if( sNm == sTmp.toAsciiLowerCase() ) + { + SfxGetpApp()->AddDdeTopic( pShell ); + bRet = true; + break; + } + pShell = SfxObjectShell::GetNext( *pShell ); + } + + if( !bRet ) + { + bool abs; + OUString url; + try { + url = rtl::Uri::convertRelToAbs(SvtPathOptions().GetWorkPath(), rNm); + abs = true; + } catch (rtl::MalformedUriException &) { + abs = false; + } + if ( abs && lcl_IsDocument( url ) ) + { + // File exists? then try to load it: + SfxStringItem aName( SID_FILE_NAME, url ); + SfxBoolItem aNewView(SID_OPEN_NEW_VIEW, true); + + SfxBoolItem aSilent(SID_SILENT, true); + SfxDispatcher* pDispatcher = SfxGetpApp()->GetDispatcher_Impl(); + const SfxPoolItem* pRet = pDispatcher->ExecuteList(SID_OPENDOC, + SfxCallMode::SYNCHRON, + { &aName, &aNewView, &aSilent }); + + if( auto const item = dynamic_cast< const SfxViewFrameItem *>( pRet ); + item && + item->GetFrame() && + nullptr != ( pShell = item->GetFrame()->GetObjectShell() ) ) + { + SfxGetpApp()->AddDdeTopic( pShell ); + bRet = true; + } + } + } + return bRet; +} + +OUString ImplDdeService::Topics() +{ + OUString sRet; + if( GetSysTopic() ) + sRet += GetSysTopic()->GetName(); + + SfxObjectShell* pShell = SfxObjectShell::GetFirst(); + while( pShell ) + { + if( SfxViewFrame::GetFirst( pShell ) ) + { + if( !sRet.isEmpty() ) + sRet += "\t"; + sRet += pShell->GetTitle(SFX_TITLE_FULLNAME); + } + pShell = SfxObjectShell::GetNext( *pShell ); + } + if( !sRet.isEmpty() ) + sRet += "\r\n"; + return sRet; +} + +bool ImplDdeService::SysTopicExecute( const OUString* pStr ) +{ + return SfxApplication::DdeExecute( *pStr ); +} +#endif + +class SfxDdeDocTopic_Impl : public DdeTopic +{ +#if defined(_WIN32) +public: + SfxObjectShell* pSh; + DdeData aData; + css::uno::Sequence< sal_Int8 > aSeq; + + explicit SfxDdeDocTopic_Impl( SfxObjectShell* pShell ) + : DdeTopic( pShell->GetTitle(SFX_TITLE_FULLNAME) ), pSh( pShell ) + {} + + virtual DdeData* Get( SotClipboardFormatId ) override; + virtual bool Put( const DdeData* ) override; + virtual bool Execute( const OUString* ) override; + virtual bool StartAdviseLoop() override; + virtual bool MakeItem( const OUString& rItem ) override; +#endif +}; + + +#if defined(_WIN32) + +namespace { + +/* [Description] + + Checks if 'rCmd' of the event 'rEvent' is (without '(') and then assemble + this data into a <ApplicationEvent>, which is then executed through + <Application::AppEvent()>. If 'rCmd' is the given event 'rEvent', then + TRUE is returned, otherwise FALSE. + + [Example] + + rCmd = "Open(\"d:\doc\doc.sdw\")" + rEvent = "Open" +*/ +bool SfxAppEvent_Impl( const OUString& rCmd, std::u16string_view rEvent, + ApplicationEvent::Type eType ) +{ + OUString sEvent(OUString::Concat(rEvent) + "("); + if (rCmd.startsWithIgnoreAsciiCase(sEvent)) + { + sal_Int32 start = sEvent.getLength(); + if ( rCmd.getLength() - start >= 2 ) + { + // Transform into the ApplicationEvent Format + //TODO: I /assume/ that rCmd should match the syntax of + // <http://msdn.microsoft.com/en-us/library/ms648995.aspx> + // "WM_DDE_EXECUTE message" but does not (handle commands enclosed + // in [...]; handle commas separating multiple arguments; handle + // double "", ((, )), [[, ]] in quoted arguments); see also the mail + // thread starting at <http://lists.freedesktop.org/archives/ + // libreoffice/2013-July/054779.html> "DDE on Windows." + std::vector<OUString> aData; + for ( sal_Int32 n = start; n < rCmd.getLength() - 1; ) + { + // Resiliently read arguments either starting with " and + // spanning to the next " (if any; TODO: do we need to undo any + // escaping within the string?) or with neither " nor SPC and + // spanning to the next SPC (if any; TODO: is this from not + // wrapped in "..." relevant? it would have been parsed by the + // original code even if that was only by accident, so I left it + // in), with runs of SPCs treated like single ones: + switch ( rCmd[n] ) + { + case '"': + { + sal_Int32 i = rCmd.indexOf('"', ++n); + if (i < 0 || i > rCmd.getLength() - 1) { + i = rCmd.getLength() - 1; + } + aData.push_back(rCmd.copy(n, i - n)); + n = i + 1; + break; + } + case ' ': + ++n; + break; + default: + { + sal_Int32 i = rCmd.indexOf(' ', n); + if (i < 0 || i > rCmd.getLength() - 1) { + i = rCmd.getLength() - 1; + } + aData.push_back(rCmd.copy(n, i - n)); + n = i + 1; + break; + } + } + } + + GetpApp()->AppEvent( ApplicationEvent(eType, std::move(aData)) ); + return true; + } + } + + return false; +} + +} + +/* Description] + + This method can be overridden by application developers, to receive + DDE-commands directed to their SfxApplication subclass. + + The base implementation understands the API functionality of the + relevant SfxApplication subclass in BASIC syntax. Return values can + not be transferred, unfortunately. +*/ +bool SfxApplication::DdeExecute( const OUString& rCmd ) // Expressed in our BASIC-Syntax +{ + // Print or Open-Event? + if ( !( SfxAppEvent_Impl( rCmd, u"Print", ApplicationEvent::Type::Print ) || + SfxAppEvent_Impl( rCmd, u"Open", ApplicationEvent::Type::Open ) ) ) + { + // all others are BASIC + StarBASIC* pBasic = GetBasic(); + DBG_ASSERT( pBasic, "Where is the Basic???" ); + SbxVariable* pRet = pBasic->Execute( rCmd ); + if( !pRet ) + { + SbxBase::ResetError(); + return false; + } + } + return true; +} + +/* [Description] + + This method can be overridden by application developers, to receive + DDE-commands directed to the their SfxApplication subclass. + + The base implementation does nothing and returns 0. +*/ +bool SfxObjectShell::DdeExecute( const OUString& rCmd ) // Expressed in our BASIC-Syntax +{ +#if !HAVE_FEATURE_SCRIPTING + (void) rCmd; +#else + StarBASIC* pBasic = GetBasic(); + DBG_ASSERT( pBasic, "Where is the Basic???" ) ; + SbxVariable* pRet = pBasic->Execute( rCmd ); + if( !pRet ) + { + SbxBase::ResetError(); + return false; + } +#endif + return true; +} + +/* [Description] + + This method can be overridden by application developers, to receive + DDE-data-requests directed to their SfxApplication subclass. + + The base implementation provides no data and returns false. +*/ +bool SfxObjectShell::DdeGetData( const OUString&, // the Item to be addressed + const OUString&, // in: Format + css::uno::Any& )// out: requested data +{ + return false; +} + + +/* [Description] + + This method can be overridden by application developers, to receive + DDE-data directed to their SfxApplication subclass. + + The base implementation is not receiving any data and returns false. +*/ +bool SfxObjectShell::DdeSetData( const OUString&, // the Item to be addressed + const OUString&, // in: Format + const css::uno::Any& )// out: requested data +{ + return false; +} + +#endif + +/* [Description] + + This method can be overridden by application developers, to establish + a DDE-hotlink to their SfxApplication subclass. + + The base implementation is not generate a link and returns 0. +*/ +::sfx2::SvLinkSource* SfxObjectShell::DdeCreateLinkSource( const OUString& ) // the Item to be addressed +{ + return nullptr; +} + +void SfxObjectShell::ReconnectDdeLink(SfxObjectShell& /*rServer*/) +{ +} + +void SfxObjectShell::ReconnectDdeLinks(SfxObjectShell& rServer) +{ + SfxObjectShell* p = GetFirst(nullptr, false); + while (p) + { + if (&rServer != p) + p->ReconnectDdeLink(rServer); + + p = GetNext(*p, nullptr, false); + } +} + +bool SfxApplication::InitializeDde() +{ + int nError = 0; +#if defined(_WIN32) + DBG_ASSERT( !pImpl->pDdeService, + "Dde can not be initialized multiple times" ); + + pImpl->pDdeService.reset(new ImplDdeService( Application::GetAppName() )); + nError = pImpl->pDdeService->GetError(); + if( !nError ) + { + pImpl->pDocTopics.reset(new SfxDdeDocTopics_Impl); + + // we certainly want to support RTF! + pImpl->pDdeService->AddFormat( SotClipboardFormatId::RTF ); + pImpl->pDdeService->AddFormat( SotClipboardFormatId::RICHTEXT ); + + // Config path as a topic because of multiple starts + INetURLObject aOfficeLockFile( SvtPathOptions().GetUserConfigPath() ); + aOfficeLockFile.insertName( u"soffice.lck" ); + OUString aService( SfxDdeServiceName_Impl( + aOfficeLockFile.GetMainURL(INetURLObject::DecodeMechanism::ToIUri) ) ); + aService = aService.toAsciiUpperCase(); + pImpl->pDdeService2.reset( new ImplDdeService( aService )); + pImpl->pTriggerTopic.reset(new SfxDdeTriggerTopic_Impl); + pImpl->pDdeService2->AddTopic( *pImpl->pTriggerTopic ); + } +#endif + return !nError; +} + +void SfxAppData_Impl::DeInitDDE() +{ + pTriggerTopic.reset(); + pDdeService2.reset(); + pDocTopics.reset(); + pDdeService.reset(); +} + +#if defined(_WIN32) +void SfxApplication::AddDdeTopic( SfxObjectShell* pSh ) +{ + //OV: DDE is disconnected in server mode! + if( !pImpl->pDocTopics ) + return; + + // prevent double submit + OUString sShellNm; + bool bFnd = false; + for (size_t n = pImpl->pDocTopics->size(); n;) + { + if( (*pImpl->pDocTopics)[ --n ]->pSh == pSh ) + { + // If the document is untitled, is still a new Topic is created! + if( !bFnd ) + { + bFnd = true; + sShellNm = pSh->GetTitle(SFX_TITLE_FULLNAME).toAsciiLowerCase(); + } + OUString sNm( (*pImpl->pDocTopics)[ n ]->GetName() ); + if( sShellNm == sNm.toAsciiLowerCase() ) + return ; + } + } + + SfxDdeDocTopic_Impl *const pTopic = new SfxDdeDocTopic_Impl(pSh); + pImpl->pDocTopics->push_back(pTopic); + pImpl->pDdeService->AddTopic( *pTopic ); +} +#endif + +void SfxApplication::RemoveDdeTopic( SfxObjectShell const * pSh ) +{ +#if defined(_WIN32) + //OV: DDE is disconnected in server mode! + if( !pImpl->pDocTopics ) + return; + + for (size_t n = pImpl->pDocTopics->size(); n; ) + { + SfxDdeDocTopic_Impl *const pTopic = (*pImpl->pDocTopics)[ --n ]; + if (pTopic->pSh == pSh) + { + pImpl->pDdeService->RemoveTopic( *pTopic ); + delete pTopic; + pImpl->pDocTopics->erase( pImpl->pDocTopics->begin() + n ); + } + } +#else + (void) pSh; +#endif +} + +const DdeService* SfxApplication::GetDdeService() const +{ + return pImpl->pDdeService.get(); +} + +DdeService* SfxApplication::GetDdeService() +{ + return pImpl->pDdeService.get(); +} + +#if defined(_WIN32) + +DdeData* SfxDdeDocTopic_Impl::Get(SotClipboardFormatId nFormat) +{ + OUString sMimeType( SotExchange::GetFormatMimeType( nFormat )); + css::uno::Any aValue; + bool bRet = pSh->DdeGetData( GetCurItem(), sMimeType, aValue ); + if( bRet && aValue.hasValue() && ( aValue >>= aSeq ) ) + { + aData = DdeData( aSeq.getConstArray(), aSeq.getLength(), nFormat ); + return &aData; + } + aSeq.realloc( 0 ); + return nullptr; +} + +bool SfxDdeDocTopic_Impl::Put( const DdeData* pData ) +{ + aSeq = css::uno::Sequence< sal_Int8 >( + static_cast<sal_Int8 const *>(pData->getData()), pData->getSize() ); + bool bRet; + if( aSeq.getLength() ) + { + css::uno::Any aValue; + aValue <<= aSeq; + OUString sMimeType( SotExchange::GetFormatMimeType( pData->GetFormat() )); + bRet = pSh->DdeSetData( GetCurItem(), sMimeType, aValue ); + } + else + bRet = false; + return bRet; +} + +bool SfxDdeDocTopic_Impl::Execute( const OUString* pStr ) +{ + return pStr && pSh->DdeExecute( *pStr ); +} + +bool SfxDdeDocTopic_Impl::MakeItem( const OUString& rItem ) +{ + AddItem( DdeItem( rItem ) ); + return true; +} + +bool SfxDdeDocTopic_Impl::StartAdviseLoop() +{ + bool bRet = false; + ::sfx2::SvLinkSource* pNewObj = pSh->DdeCreateLinkSource( GetCurItem() ); + if( pNewObj ) + { + // then we also establish a corresponding SvBaseLink + OUString sNm, sTmp( Application::GetAppName() ); + ::sfx2::MakeLnkName( sNm, &sTmp, pSh->GetTitle(SFX_TITLE_FULLNAME), GetCurItem() ); + new ::sfx2::SvBaseLink( sNm, sfx2::SvBaseLinkObjectType::DdeExternal, pNewObj ); + bRet = true; + } + return bRet; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |