summaryrefslogtreecommitdiffstats
path: root/sfx2/source/appl/appdde.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sfx2/source/appl/appdde.cxx')
-rw-r--r--sfx2/source/appl/appdde.cxx567
1 files changed, 567 insertions, 0 deletions
diff --git a/sfx2/source/appl/appdde.cxx b/sfx2/source/appl/appdde.cxx
new file mode 100644
index 0000000000..2f013cfc63
--- /dev/null
+++ b/sfx2/source/appl/appdde.cxx
@@ -0,0 +1,567 @@
+/* -*- 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 <comphelper/diagnose_ex.hxx>
+#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);
+ const SfxPoolItemHolder aResult(SfxGetpApp()->GetDispatcher_Impl()->ExecuteList(SID_OPENDOC,
+ SfxCallMode::SYNCHRON,
+ { &aName, &aNewView, &aSilent }));
+
+ if( auto const item = dynamic_cast< const SfxViewFrameItem *>(aResult.getItem());
+ 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 )
+ {
+ // 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();
+ maDocTopics.clear();
+ pDdeService.reset();
+}
+
+#if defined(_WIN32)
+void SfxApplication::AddDdeTopic( SfxObjectShell* pSh )
+{
+ //OV: DDE is disconnected in server mode!
+ if( pImpl->maDocTopics.empty() )
+ return;
+
+ // prevent double submit
+ OUString sShellNm;
+ bool bFnd = false;
+ for (size_t n = pImpl->maDocTopics.size(); n;)
+ {
+ if( pImpl->maDocTopics[ --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->maDocTopics[ n ]->GetName() );
+ if( sShellNm == sNm.toAsciiLowerCase() )
+ return ;
+ }
+ }
+
+ SfxDdeDocTopic_Impl *const pTopic = new SfxDdeDocTopic_Impl(pSh);
+ pImpl->maDocTopics.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->maDocTopics.empty() )
+ return;
+
+ for (size_t n = pImpl->maDocTopics.size(); n; )
+ {
+ SfxDdeDocTopic_Impl *const pTopic = pImpl->maDocTopics[ --n ];
+ if (pTopic->pSh == pSh)
+ {
+ pImpl->pDdeService->RemoveTopic( *pTopic );
+ delete pTopic;
+ pImpl->maDocTopics.erase( pImpl->maDocTopics.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: */