1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <memory>
22 #include <sfx2/lnkbase.hxx>
23 #include <sot/exchange.hxx>
24 #include <com/sun/star/uno/Any.hxx>
25 #include <com/sun/star/uno/Sequence.hxx>
26 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
27 #include <sfx2/linkmgr.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/weld.hxx>
30 #include <sfx2/strings.hrc>
31 #include <sfx2/sfxresid.hxx>
32 #include <sfx2/filedlghelper.hxx>
33 #include <tools/debug.hxx>
34 #include <svl/svdde.hxx>
35 #include <osl/diagnose.h>
36 
37 using namespace ::com::sun::star;
38 using namespace ::com::sun::star::uno;
39 
40 namespace sfx2
41 {
42 
43 namespace {
44 
45 class  ImplDdeItem;
46 
47 }
48 
49 struct BaseLink_Impl
50 {
51     Link<SvBaseLink&,void> m_aEndEditLink;
52     LinkManager*        m_pLinkMgr;
53     weld::Window*       m_pParentWin;
54     std::unique_ptr<FileDialogHelper>
55                         m_pFileDlg;
56     bool                m_bIsConnect;
57 
BaseLink_Implsfx2::BaseLink_Impl58     BaseLink_Impl() :
59           m_pLinkMgr( nullptr )
60         , m_pParentWin( nullptr )
61         , m_bIsConnect( false )
62         {}
63 };
64 
65 // only for internal management
66 struct ImplBaseLinkData
67 {
68     struct tClientType
69     {
70         // applies for all links
71         SotClipboardFormatId nCntntType; // Update Format
72         // Not Ole-Links
73         bool                 bIntrnlLnk;  // It is an internal link
74         SfxLinkUpdateMode    nUpdateMode; // UpdateMode
75     };
76 
77     struct tDDEType
78     {
79         ImplDdeItem* pItem;
80     };
81 
82     union {
83         tClientType ClientType;
84         tDDEType DDEType;
85     };
ImplBaseLinkDatasfx2::ImplBaseLinkData86     ImplBaseLinkData()
87     {
88         ClientType.nCntntType = SotClipboardFormatId::NONE;
89         ClientType.bIntrnlLnk = false;
90         ClientType.nUpdateMode = SfxLinkUpdateMode::NONE;
91         DDEType.pItem = nullptr;
92     }
93 };
94 
95 namespace {
96 
97 class ImplDdeItem : public DdeGetPutItem
98 {
99     SvBaseLink* pLink;
100     DdeData aData;
101     Sequence< sal_Int8 > aSeq;  // Datacontainer for DdeData !!!
102     bool bIsValidData : 1;
103     bool bIsInDTOR : 1;
104 public:
105 #if defined(_WIN32)
ImplDdeItem(SvBaseLink & rLink,const OUString & rStr)106     ImplDdeItem( SvBaseLink& rLink, const OUString& rStr )
107         : DdeGetPutItem( rStr ), pLink( &rLink ), bIsValidData( false ),
108         bIsInDTOR( false )
109     {}
110 #endif
111     virtual ~ImplDdeItem() override;
112 
113     virtual DdeData* Get( SotClipboardFormatId ) override;
114     virtual bool     Put( const DdeData* ) override;
115     virtual void     AdviseLoop( bool ) override;
116 
Notify()117     void Notify()
118     {
119         bIsValidData = false;
120         DdeGetPutItem::NotifyClient();
121     }
122 
IsInDTOR() const123     bool IsInDTOR() const { return bIsInDTOR; }
124 };
125 
126 }
127 
SvBaseLink()128 SvBaseLink::SvBaseLink()
129     : pImpl ( new BaseLink_Impl ),
130       m_bIsReadOnly(false)
131 {
132     mnObjType = SvBaseLinkObjectType::ClientSo;
133     pImplData.reset( new ImplBaseLinkData );
134     bVisible = bSynchron = true;
135     bWasLastEditOK = false;
136 }
137 
138 
SvBaseLink(SfxLinkUpdateMode nUpdateMode,SotClipboardFormatId nContentType)139 SvBaseLink::SvBaseLink( SfxLinkUpdateMode nUpdateMode, SotClipboardFormatId nContentType )
140    : pImpl( new BaseLink_Impl ),
141      m_bIsReadOnly(false)
142 {
143     mnObjType = SvBaseLinkObjectType::ClientSo;
144     pImplData.reset( new ImplBaseLinkData );
145     bVisible = bSynchron = true;
146     bWasLastEditOK = false;
147 
148     // It is going to be an OLE-Link,
149     pImplData->ClientType.nUpdateMode = nUpdateMode;
150     pImplData->ClientType.nCntntType = nContentType;
151     pImplData->ClientType.bIntrnlLnk = false;
152 }
153 
154 #if defined(_WIN32)
155 
FindTopic(const OUString & rLinkName,sal_uInt16 * pItemStt)156 static DdeTopic* FindTopic( const OUString & rLinkName, sal_uInt16* pItemStt )
157 {
158     if( rLinkName.isEmpty() )
159         return nullptr;
160 
161     OUString sNm( rLinkName );
162     sal_Int32 nTokenPos = 0;
163     OUString sService( sNm.getToken( 0, cTokenSeparator, nTokenPos ) );
164 
165     DdeServices& rSvc = DdeService::GetServices();
166     for (auto const& elem : rSvc)
167     {
168         if(elem->GetName() == sService)
169         {
170             // then we search for the Topic
171             OUString sTopic( sNm.getToken( 0, cTokenSeparator, nTokenPos ) );
172             if( pItemStt )
173                 *pItemStt = nTokenPos;
174 
175             std::vector<DdeTopic*>& rTopics = elem->GetTopics();
176 
177             for (auto const& topic : rTopics)
178                 if( topic->GetName() == sTopic )
179                     return topic;
180             break;
181         }
182     }
183     return nullptr;
184 }
185 
SvBaseLink(const OUString & rLinkName,SvBaseLinkObjectType nObjectType,SvLinkSource * pObj)186 SvBaseLink::SvBaseLink( const OUString& rLinkName, SvBaseLinkObjectType nObjectType, SvLinkSource* pObj )
187     : pImpl()
188     , m_bIsReadOnly(false)
189 {
190     bVisible = bSynchron = true;
191     bWasLastEditOK = false;
192     aLinkName = rLinkName;
193     pImplData.reset( new ImplBaseLinkData );
194     mnObjType = nObjectType;
195 
196     if( !pObj )
197     {
198         DBG_ASSERT( pObj, "Where is my left-most object" );
199         return;
200     }
201 
202     if( SvBaseLinkObjectType::DdeExternal == mnObjType )
203     {
204         sal_uInt16 nItemStt = 0;
205         DdeTopic* pTopic = FindTopic( aLinkName, &nItemStt );
206         if( pTopic )
207         {
208             // then we have it all together
209             // MM_TODO how do I get the name
210             OUString aStr = aLinkName; // xLinkName->GetDisplayName();
211             aStr = aStr.copy( nItemStt );
212             pImplData->DDEType.pItem = new ImplDdeItem( *this, aStr );
213             pTopic->InsertItem( pImplData->DDEType.pItem );
214 
215             // store the Advice
216             xObj = pObj;
217         }
218     }
219     else if( pObj->Connect( this ) )
220         xObj = pObj;
221 }
222 
223 #endif
224 
~SvBaseLink()225 SvBaseLink::~SvBaseLink()
226 {
227     Disconnect();
228 
229     if( mnObjType == SvBaseLinkObjectType::DdeExternal )
230     {
231         if( !pImplData->DDEType.pItem->IsInDTOR() )
232             delete pImplData->DDEType.pItem;
233     }
234 
235     pImplData.reset();
236 }
237 
IMPL_LINK(SvBaseLink,EndEditHdl,const OUString &,_rNewName,void)238 IMPL_LINK( SvBaseLink, EndEditHdl, const OUString&, _rNewName, void )
239 {
240     OUString sNewName = _rNewName;
241     if ( !ExecuteEdit( sNewName ) )
242         sNewName.clear();
243     bWasLastEditOK = !sNewName.isEmpty();
244     pImpl->m_aEndEditLink.Call( *this );
245 }
246 
247 
SetObjType(SvBaseLinkObjectType mnObjTypeP)248 void SvBaseLink::SetObjType( SvBaseLinkObjectType mnObjTypeP )
249 {
250     DBG_ASSERT( mnObjType != SvBaseLinkObjectType::ClientDde, "type already set" );
251     DBG_ASSERT( !xObj.is(), "object exist" );
252 
253     mnObjType = mnObjTypeP;
254 }
255 
256 
SetName(const OUString & rNm)257 void SvBaseLink::SetName( const OUString & rNm )
258 {
259     aLinkName = rNm;
260 }
261 
262 
SetObj(SvLinkSource * pObj)263 void SvBaseLink::SetObj( SvLinkSource * pObj )
264 {
265     DBG_ASSERT( (isClientType(mnObjType) &&
266                  pImplData->ClientType.bIntrnlLnk) ||
267                 mnObjType == SvBaseLinkObjectType::ClientGraphic,
268                 "no intern link" );
269     xObj = pObj;
270 }
271 
272 
SetLinkSourceName(const OUString & rLnkNm)273 void SvBaseLink::SetLinkSourceName( const OUString & rLnkNm )
274 {
275     if( aLinkName == rLnkNm )
276         return;
277 
278     AddNextRef(); // should be superfluous
279     // remove old connection
280     Disconnect();
281 
282     aLinkName = rLnkNm;
283 
284     // New Connection
285     GetRealObject_();
286     ReleaseRef(); // should be superfluous
287 }
288 
289 
SetUpdateMode(SfxLinkUpdateMode nMode)290 void SvBaseLink::SetUpdateMode( SfxLinkUpdateMode nMode )
291 {
292     if( isClientType(mnObjType) &&
293         pImplData->ClientType.nUpdateMode != nMode )
294     {
295         AddNextRef();
296         Disconnect();
297 
298         pImplData->ClientType.nUpdateMode = nMode;
299         GetRealObject_();
300         ReleaseRef();
301     }
302 }
303 
304 // #i88291#
clearStreamToLoadFrom()305 void SvBaseLink::clearStreamToLoadFrom()
306 {
307     m_xInputStreamToLoadFrom.clear();
308     if( xObj.is() )
309     {
310         xObj->clearStreamToLoadFrom();
311     }
312 }
313 
Update()314 bool SvBaseLink::Update()
315 {
316     if( isClientType(mnObjType) )
317     {
318         AddNextRef();
319         Disconnect();
320 
321         GetRealObject_();
322         ReleaseRef();
323         if( xObj.is() )
324         {
325             xObj->setStreamToLoadFrom(m_xInputStreamToLoadFrom,m_bIsReadOnly);
326             OUString sMimeType( SotExchange::GetFormatMimeType(
327                             pImplData->ClientType.nCntntType ));
328             Any aData;
329 
330             if( xObj->GetData( aData, sMimeType ) )
331             {
332                 UpdateResult eRes = DataChanged(sMimeType, aData);
333                 bool bSuccess = eRes == SUCCESS;
334                 //for manual Updates there is no need to hold the ServerObject
335                 if( SvBaseLinkObjectType::ClientDde == mnObjType &&
336                     SfxLinkUpdateMode::ONCALL == GetUpdateMode() && xObj.is() )
337                     xObj->RemoveAllDataAdvise( this );
338                 return bSuccess;
339             }
340             if( xObj.is() )
341             {
342                 // should be asynchronous?
343                 if( xObj->IsPending() )
344                     return true;
345 
346                 // we do not need the object anymore
347                 AddNextRef();
348                 Disconnect();
349                 ReleaseRef();
350             }
351         }
352     }
353     return false;
354 }
355 
356 
GetUpdateMode() const357 SfxLinkUpdateMode SvBaseLink::GetUpdateMode() const
358 {
359     return isClientType(mnObjType)
360             ? pImplData->ClientType.nUpdateMode
361             : SfxLinkUpdateMode::ONCALL;
362 }
363 
364 
GetRealObject_(bool bConnect)365 void SvBaseLink::GetRealObject_( bool bConnect)
366 {
367     if( !pImpl->m_pLinkMgr )
368         return;
369 
370     DBG_ASSERT( !xObj.is(), "object already exist" );
371 
372     if( SvBaseLinkObjectType::ClientDde == mnObjType )
373     {
374         OUString sServer;
375         if( sfx2::LinkManager::GetDisplayNames( this, &sServer ) &&
376             sServer == Application::GetAppName() )  // internal Link !!!
377         {
378             // so that the Internal link can be created!
379             mnObjType = SvBaseLinkObjectType::Internal;
380             xObj = sfx2::LinkManager::CreateObj( this );
381 
382             pImplData->ClientType.bIntrnlLnk = true;
383             mnObjType = SvBaseLinkObjectType::ClientDde;  // so we know what it once was!
384         }
385         else
386         {
387             pImplData->ClientType.bIntrnlLnk = false;
388             xObj = sfx2::LinkManager::CreateObj( this );
389         }
390     }
391     else if( isClientType(mnObjType) )
392         xObj = sfx2::LinkManager::CreateObj( this );
393 
394     if( bConnect && ( !xObj.is() || !xObj->Connect( this ) ) )
395         Disconnect();
396 }
397 
GetContentType() const398 SotClipboardFormatId SvBaseLink::GetContentType() const
399 {
400     if( isClientType(mnObjType) )
401         return pImplData->ClientType.nCntntType;
402 
403     return SotClipboardFormatId::NONE;  // all Formats ?
404 }
405 
406 
SetContentType(SotClipboardFormatId nType)407 void SvBaseLink::SetContentType( SotClipboardFormatId nType )
408 {
409     if( isClientType(mnObjType) )
410     {
411         pImplData->ClientType.nCntntType = nType;
412     }
413 }
414 
GetLinkManager()415 LinkManager* SvBaseLink::GetLinkManager()
416 {
417     return pImpl->m_pLinkMgr;
418 }
419 
GetLinkManager() const420 const LinkManager* SvBaseLink::GetLinkManager() const
421 {
422     return pImpl->m_pLinkMgr;
423 }
424 
SetLinkManager(LinkManager * _pMgr)425 void SvBaseLink::SetLinkManager( LinkManager* _pMgr )
426 {
427     pImpl->m_pLinkMgr = _pMgr;
428 }
429 
Disconnect()430 void SvBaseLink::Disconnect()
431 {
432     if( xObj.is() )
433     {
434         xObj->RemoveAllDataAdvise( this );
435         xObj->RemoveConnectAdvise( this );
436         xObj.clear();
437     }
438 }
439 
DataChanged(const OUString &,const css::uno::Any &)440 SvBaseLink::UpdateResult SvBaseLink::DataChanged( const OUString &, const css::uno::Any & )
441 {
442     if ( mnObjType == SvBaseLinkObjectType::DdeExternal )
443     {
444         if( pImplData->DDEType.pItem )
445             pImplData->DDEType.pItem->Notify();
446     }
447     return SUCCESS;
448 }
449 
Edit(weld::Window * pParent,const Link<SvBaseLink &,void> & rEndEditHdl)450 void SvBaseLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& rEndEditHdl )
451 {
452     pImpl->m_pParentWin = pParent;
453     pImpl->m_aEndEditLink = rEndEditHdl;
454     pImpl->m_bIsConnect = xObj.is();
455     if( !pImpl->m_bIsConnect )
456         GetRealObject_( xObj.is() );
457 
458     bool bAsync = false;
459     Link<const OUString&, void> aLink = LINK( this, SvBaseLink, EndEditHdl );
460 
461     if( isClientType(mnObjType) && pImplData->ClientType.bIntrnlLnk )
462     {
463         if( pImpl->m_pLinkMgr )
464         {
465             SvLinkSourceRef ref = sfx2::LinkManager::CreateObj( this );
466             if( ref.is() )
467             {
468                 ref->Edit( pParent, this, aLink );
469                 bAsync = true;
470             }
471         }
472     }
473     else
474     {
475         xObj->Edit( pParent, this, aLink );
476         bAsync = true;
477     }
478 
479     if ( !bAsync )
480     {
481         ExecuteEdit( OUString() );
482         bWasLastEditOK = false;
483         pImpl->m_aEndEditLink.Call( *this );
484     }
485 }
486 
ExecuteEdit(const OUString & _rNewName)487 bool SvBaseLink::ExecuteEdit( const OUString& _rNewName )
488 {
489     if( !_rNewName.isEmpty() )
490     {
491         SetLinkSourceName( _rNewName );
492         if( !Update() )
493         {
494             OUString sApp, sTopic, sItem, sError;
495             sfx2::LinkManager::GetDisplayNames( this, &sApp, &sTopic, &sItem );
496             if( mnObjType == SvBaseLinkObjectType::ClientDde )
497             {
498                 sError = SfxResId(STR_DDE_ERROR);
499 
500                 sal_Int32 nFndPos = sError.indexOf( "%1" );
501                 if( -1 != nFndPos )
502                 {
503                     sError = sError.replaceAt( nFndPos, 2, sApp );
504                     nFndPos = nFndPos + sApp.getLength();
505 
506                     if( -1 != ( nFndPos = sError.indexOf( "%2", nFndPos )))
507                     {
508                         sError = sError.replaceAt( nFndPos, 2, sTopic );
509                         nFndPos = nFndPos + sTopic.getLength();
510 
511                         if( -1 != ( nFndPos = sError.indexOf( "%3", nFndPos )))
512                             sError = sError.replaceAt( nFndPos, 2, sItem );
513                     }
514                 }
515             }
516             else
517                 return false;
518 
519             std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pImpl->m_pParentWin,
520                                                                      VclMessageType::Warning, VclButtonsType::Ok, sError));
521             xBox->run();
522         }
523     }
524     else if( !pImpl->m_bIsConnect )
525         Disconnect();
526     pImpl->m_bIsConnect = false;
527     return true;
528 }
529 
Closed()530 void SvBaseLink::Closed()
531 {
532     if( xObj.is() )
533         xObj->RemoveAllDataAdvise( this );
534 }
535 
GetInsertFileDialog(const OUString & rFactory) const536 FileDialogHelper & SvBaseLink::GetInsertFileDialog(const OUString& rFactory) const
537 {
538     pImpl->m_pFileDlg.reset( new FileDialogHelper(
539             ui::dialogs::TemplateDescription::FILEOPEN_SIMPLE,
540             FileDialogFlags::Insert, rFactory, SfxFilterFlags::NONE, SfxFilterFlags::NONE, pImpl->m_pParentWin) );
541     return *pImpl->m_pFileDlg;
542 }
543 
~ImplDdeItem()544 ImplDdeItem::~ImplDdeItem()
545 {
546     bIsInDTOR = true;
547     // So that no-one gets the idea to delete the pointer when Disconnecting!
548     tools::SvRef<SvBaseLink> aRef( pLink );
549     aRef->Disconnect();
550 }
551 
Get(SotClipboardFormatId nFormat)552 DdeData* ImplDdeItem::Get( SotClipboardFormatId nFormat )
553 {
554     if( pLink->GetObj() )
555     {
556         // is it still valid?
557         if( bIsValidData && nFormat == aData.GetFormat() )
558             return &aData;
559 
560         Any aValue;
561         OUString sMimeType( SotExchange::GetFormatMimeType( nFormat ));
562         if( pLink->GetObj()->GetData( aValue, sMimeType ) )
563         {
564             if( aValue >>= aSeq )
565             {
566                 aData = DdeData( aSeq.getConstArray(), aSeq.getLength(), nFormat );
567 
568                 bIsValidData = true;
569                 return &aData;
570             }
571         }
572     }
573     aSeq.realloc( 0 );
574     bIsValidData = false;
575     return nullptr;
576 }
577 
578 
Put(const DdeData *)579 bool ImplDdeItem::Put( const DdeData*  )
580 {
581     OSL_FAIL( "ImplDdeItem::Put not implemented" );
582     return false;
583 }
584 
585 
AdviseLoop(bool bOpen)586 void ImplDdeItem::AdviseLoop( bool bOpen )
587 {
588     // Connection is closed, so also unsubscribe link
589     if( !pLink->GetObj() )
590         return;
591 
592     if( bOpen )
593     {
594         // A connection is re-established
595         if( SvBaseLinkObjectType::DdeExternal == pLink->GetObjType() )
596         {
597             pLink->GetObj()->AddDataAdvise( pLink, "text/plain;charset=utf-16",  ADVISEMODE_NODATA );
598             pLink->GetObj()->AddConnectAdvise( pLink );
599         }
600     }
601     else
602     {
603         // So that no-one gets the idea to delete the pointer
604         // when Disconnecting!
605         tools::SvRef<SvBaseLink> aRef( pLink );
606         aRef->Disconnect();
607     }
608 }
609 
610 }
611 
612 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
613