1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
21 #include <rtl/ref.hxx>
22 #include <tools/diagnose_ex.h>
24 #include <comphelper/documentinfo.hxx>
25 #include <comphelper/namedvaluecollection.hxx>
26 #include <comphelper/sequence.hxx>
28 #include <com/sun/star/awt/XTopWindow.hpp>
29 #include <com/sun/star/beans/XPropertySet.hpp>
30 #include <com/sun/star/document/XDocumentEventBroadcaster.hpp>
31 #include <com/sun/star/document/XStorageBasedDocument.hpp>
32 #include <com/sun/star/frame/UnknownModuleException.hpp>
33 #include <com/sun/star/frame/theGlobalEventBroadcaster.hpp>
34 #include <com/sun/star/frame/ModuleManager.hpp>
35 #include <com/sun/star/lang/DisposedException.hpp>
36 #include <com/sun/star/lang/NotInitializedException.hpp>
37 #include <com/sun/star/util/XCloseBroadcaster.hpp>
39 #include "tdoc_docmgr.hxx"
40 #include "tdoc_provider.hxx"
42 using namespace com::sun::star
;
43 using namespace tdoc_ucp
;
45 // OfficeDocumentsCloseListener Implementation.
48 // util::XCloseListener
52 void SAL_CALL
OfficeDocumentsManager::OfficeDocumentsCloseListener::queryClosing(
53 const lang::EventObject
& /*Source*/, sal_Bool
/*GetsOwnership*/ )
58 void SAL_CALL
OfficeDocumentsManager::OfficeDocumentsCloseListener::notifyClosing(
59 const lang::EventObject
& Source
)
61 if (!m_pManager
) return; // disposed?
63 document::DocumentEvent aDocEvent
;
64 aDocEvent
.Source
= Source
.Source
;
65 aDocEvent
.EventName
= "OfficeDocumentsListener::notifyClosing";
66 m_pManager
->documentEventOccured( aDocEvent
);
70 // lang::XDocumentEventListener (base of util::XCloseListener)
74 void SAL_CALL
OfficeDocumentsManager::OfficeDocumentsCloseListener::disposing(
75 const lang::EventObject
& /*Source*/ )
80 // OfficeDocumentsManager Implementation.
83 OfficeDocumentsManager::OfficeDocumentsManager(
84 const uno::Reference
< uno::XComponentContext
> & rxContext
,
85 ContentProvider
* pDocEventListener
)
86 : m_xContext( rxContext
),
87 m_xDocEvtNotifier( frame::theGlobalEventBroadcaster::get( rxContext
) ),
88 m_pDocEventListener( pDocEventListener
),
89 m_xDocCloseListener( new OfficeDocumentsCloseListener( this ) )
91 // Order is important (multithreaded environment)
92 uno::Reference
< document::XDocumentEventBroadcaster
>(
93 m_xDocEvtNotifier
, uno::UNO_QUERY_THROW
)->addDocumentEventListener( this );
99 OfficeDocumentsManager::~OfficeDocumentsManager()
101 //OSL_ENSURE( m_aDocs.empty(), "document list not empty!" );
102 // no need to assert this: Normal shutdown of LibreOffice could already trigger it, since the order
103 // in which objects are actually released/destroyed upon shutdown is not defined. And when we
104 // arrive *here*, LibreOffice *is* shutting down currently, since we're held by the TDOC provider,
105 // which is disposed upon shutdown.
106 m_xDocCloseListener
->Dispose();
110 void OfficeDocumentsManager::destroy()
112 uno::Reference
< document::XDocumentEventBroadcaster
>(
113 m_xDocEvtNotifier
, uno::UNO_QUERY_THROW
)->removeDocumentEventListener( this );
118 getDocumentId( const uno::Reference
< uno::XInterface
> & xDoc
)
122 // Try to get the UID directly from the document.
123 uno::Reference
< beans::XPropertySet
> xPropSet( xDoc
, uno::UNO_QUERY
);
128 uno::Any aValue
= xPropSet
->getPropertyValue("RuntimeUID");
131 catch ( beans::UnknownPropertyException
const & )
133 // Not actually an error. Property is optional.
135 catch ( lang::WrappedTargetException
const & )
137 TOOLS_WARN_EXCEPTION("ucb.ucp", "Caught WrappedTargetException!");
143 // fallback: generate UID from document's this pointer.
144 // normalize the interface pointer first. Else, calls with different
145 // interfaces to the same object (say, XFoo and XBar) will produce
147 uno::Reference
< uno::XInterface
> xNormalizedIFace( xDoc
, uno::UNO_QUERY
);
148 sal_Int64 nId
= reinterpret_cast< sal_Int64
>( xNormalizedIFace
.get() );
149 aId
= OUString::number( nId
);
152 OSL_ENSURE( !aId
.isEmpty(), "getDocumentId - Empty id!" );
157 // document::XDocumentEventListener
161 void SAL_CALL
OfficeDocumentsManager::documentEventOccured(
162 const document::DocumentEvent
& Event
)
165 Events documentation: OOo Developer's Guide / Writing UNO Components /
166 Integrating Components into OpenOffice.org / Jobs
169 if ( Event
.EventName
== "OnLoadFinished" // document loaded
170 || Event
.EventName
== "OnCreate" ) // document created
172 if ( isOfficeDocument( Event
.Source
) )
174 uno::Reference
<frame::XModel
> const xModel(
175 Event
.Source
, uno::UNO_QUERY
);
176 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
181 osl::MutexGuard
aGuard( m_aMtx
);
183 found
= std::any_of(m_aDocs
.begin(), m_aDocs
.end(),
184 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
189 // no mutex to avoid deadlocks!
190 // need no lock to access const members, ContentProvider is safe
194 uno::Reference
< document::XStorageBasedDocument
>
195 xDoc( Event
.Source
, uno::UNO_QUERY
);
196 OSL_ENSURE( xDoc
.is(), "Got no document::XStorageBasedDocument!" );
198 uno::Reference
< embed::XStorage
> xStorage
199 = xDoc
->getDocumentStorage();
200 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
202 rtl:: OUString aDocId
= getDocumentId( Event
.Source
);
203 rtl:: OUString aTitle
= comphelper::DocumentInfo::getDocumentTitle(
204 uno::Reference
< frame::XModel
>( Event
.Source
, uno::UNO_QUERY
) );
207 osl::MutexGuard
g(m_aMtx
);
208 m_aDocs
[ aDocId
] = StorageInfo( aTitle
, xStorage
, xModel
);
211 uno::Reference
< util::XCloseBroadcaster
> xCloseBroadcaster(
212 Event
.Source
, uno::UNO_QUERY
);
213 OSL_ENSURE( xCloseBroadcaster
.is(),
214 "OnLoadFinished/OnCreate event: got no close broadcaster!" );
216 if ( xCloseBroadcaster
.is() )
217 xCloseBroadcaster
->addCloseListener(m_xDocCloseListener
);
219 // Propagate document closure.
220 OSL_ENSURE( m_pDocEventListener
,
221 "OnLoadFinished/OnCreate event: no owner for insert event propagation!" );
223 if ( m_pDocEventListener
)
224 m_pDocEventListener
->notifyDocumentOpened( aDocId
);
228 else if ( Event
.EventName
== "OfficeDocumentsListener::notifyClosing" )
230 if ( isOfficeDocument( Event
.Source
) )
232 // Document has been closed (unloaded)
234 // Official event "OnUnload" does not work here. Event
235 // gets fired too early. Other OnUnload listeners called after this
236 // listener may still need TDOC access to the document. Remove the
237 // document from TDOC docs list on XCloseListener::notifyClosing.
238 // See OfficeDocumentsManager::OfficeDocumentsListener::notifyClosing.
240 uno::Reference
< frame::XModel
>
241 xModel( Event
.Source
, uno::UNO_QUERY
);
242 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
248 osl::MutexGuard
aGuard( m_aMtx
);
250 auto it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
251 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
252 if ( it
!= m_aDocs
.end() )
254 aDocId
= (*it
).first
;
261 "OnUnload event notified for unknown document!" );
265 // Propagate document closure.
266 OSL_ENSURE( m_pDocEventListener
,
267 "OnUnload event: no owner for close event propagation!" );
268 if (m_pDocEventListener
)
270 m_pDocEventListener
->notifyDocumentClosed(aDocId
);
272 uno::Reference
< util::XCloseBroadcaster
> xCloseBroadcaster(
273 Event
.Source
, uno::UNO_QUERY
);
274 OSL_ENSURE( xCloseBroadcaster
.is(),
275 "OnUnload event: got no XCloseBroadcaster from XModel" );
276 if ( xCloseBroadcaster
.is() )
277 xCloseBroadcaster
->removeCloseListener(m_xDocCloseListener
);
281 else if ( Event
.EventName
== "OnSaveDone" )
283 if ( isOfficeDocument( Event
.Source
) )
285 // Storage gets exchanged while saving.
286 uno::Reference
<document::XStorageBasedDocument
> const xDoc(
287 Event
.Source
, uno::UNO_QUERY
);
288 OSL_ENSURE( xDoc
.is(),
289 "Got no document::XStorageBasedDocument!" );
290 uno::Reference
<embed::XStorage
> const xStorage(
291 xDoc
->getDocumentStorage());
292 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
294 uno::Reference
< frame::XModel
>
295 xModel( Event
.Source
, uno::UNO_QUERY
);
296 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
298 osl::MutexGuard
aGuard( m_aMtx
);
300 DocumentList::iterator it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
301 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
303 OSL_ENSURE( it
!= m_aDocs
.end(),
304 "OnSaveDone event notified for unknown document!" );
305 if ( it
!= m_aDocs
.end() )
307 (*it
).second
.xStorage
= xStorage
;
311 else if ( Event
.EventName
== "OnSaveAsDone" )
313 if ( isOfficeDocument( Event
.Source
) )
315 // Storage gets exchanged while saving.
316 uno::Reference
<document::XStorageBasedDocument
> const xDoc(
317 Event
.Source
, uno::UNO_QUERY
);
318 OSL_ENSURE( xDoc
.is(),
319 "Got no document::XStorageBasedDocument!" );
320 uno::Reference
<embed::XStorage
> const xStorage(
321 xDoc
->getDocumentStorage());
322 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
324 uno::Reference
< frame::XModel
>
325 xModel( Event
.Source
, uno::UNO_QUERY
);
326 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
328 OUString
const title(comphelper::DocumentInfo::getDocumentTitle(xModel
));
330 osl::MutexGuard
aGuard( m_aMtx
);
332 DocumentList::iterator it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
333 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
335 OSL_ENSURE( it
!= m_aDocs
.end(),
336 "OnSaveAsDone event notified for unknown document!" );
337 if ( it
!= m_aDocs
.end() )
339 (*it
).second
.xStorage
= xStorage
;
342 (*it
).second
.aTitle
= title
;
346 else if ( Event
.EventName
== "OnTitleChanged"
347 || Event
.EventName
== "OnStorageChanged" )
349 if ( isOfficeDocument( Event
.Source
) )
351 // Storage gets exchanged while saving.
352 uno::Reference
<document::XStorageBasedDocument
> const xDoc(
353 Event
.Source
, uno::UNO_QUERY
);
354 OSL_ENSURE( xDoc
.is(),
355 "Got no document::XStorageBasedDocument!" );
356 uno::Reference
<embed::XStorage
> const xStorage(
357 xDoc
->getDocumentStorage());
358 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
360 uno::Reference
< frame::XModel
>
361 xModel( Event
.Source
, uno::UNO_QUERY
);
362 OSL_ENSURE( xModel
.is(), "Got no frame::XModel!" );
364 OUString
const aTitle(comphelper::DocumentInfo::getDocumentTitle(xModel
));
366 OUString
const aDocId(getDocumentId(Event
.Source
));
368 osl::MutexGuard
aGuard( m_aMtx
);
370 DocumentList::iterator it
= std::find_if(m_aDocs
.begin(), m_aDocs
.end(),
371 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
372 if ( it
!= m_aDocs
.end() )
375 (*it
).second
.aTitle
= aTitle
;
377 m_aDocs
[ aDocId
] = StorageInfo( aTitle
, xStorage
, xModel
);
380 // OSL_ENSURE( it != m_aDocs.end(),
381 // "TitleChanged event notified for unknown document!" );
382 // TODO: re-enable this assertion. It has been disabled for now, since it breaks the assertion-free smoketest,
383 // and the fix is more difficult than what can be done now.
384 // The problem is that at the moment, when you close a SFX-based document via API, it will first
385 // fire the notifyClosing event, which will make the OfficeDocumentsManager remove the doc from its list.
386 // Then, it will notify an OnTitleChanged, then an OnUnload. Documents closed via call the notifyClosing
387 // *after* OnUnload and all other On* events.
388 // In agreement with MBA, the implementation for SfxBaseModel::Close should be changed to also send notifyClosing
389 // as last event. When this happens, the assertion here must be enabled, again.
394 // lang::XDocumentEventListener (base of document::XDocumentEventListener)
397 void SAL_CALL
OfficeDocumentsManager::disposing(
398 const lang::EventObject
& /*Source*/ )
404 void OfficeDocumentsManager::buildDocumentsList()
406 uno::Reference
< container::XEnumeration
> xEnum
407 = m_xDocEvtNotifier
->createEnumeration();
409 while ( xEnum
->hasMoreElements() )
411 uno::Any aValue
= xEnum
->nextElement();
412 // container::NoSuchElementException
413 // lang::WrappedTargetException
417 uno::Reference
< frame::XModel
> xModel
;
422 if ( isOfficeDocument( xModel
) )
427 osl::MutexGuard
aGuard( m_aMtx
);
429 found
= std::any_of(m_aDocs
.begin(), m_aDocs
.end(),
430 [&xModel
](const DocumentList::value_type
& rEntry
) { return rEntry
.second
.xModel
== xModel
; });
436 OUString aDocId
= getDocumentId( xModel
);
437 OUString aTitle
= comphelper::DocumentInfo::getDocumentTitle( xModel
);
439 uno::Reference
< document::XStorageBasedDocument
>
440 xDoc( xModel
, uno::UNO_QUERY
);
441 OSL_ENSURE( xDoc
.is(),
442 "Got no document::XStorageBasedDocument!" );
444 uno::Reference
< embed::XStorage
> xStorage
445 = xDoc
->getDocumentStorage();
446 OSL_ENSURE( xStorage
.is(), "Got no document storage!" );
449 osl::MutexGuard
aGuard( m_aMtx
);
451 = StorageInfo( aTitle
, xStorage
, xModel
);
454 uno::Reference
< util::XCloseBroadcaster
> xCloseBroadcaster(
455 xModel
, uno::UNO_QUERY
);
456 OSL_ENSURE( xCloseBroadcaster
.is(),
457 "buildDocumentsList: got no close broadcaster!" );
459 if ( xCloseBroadcaster
.is() )
460 xCloseBroadcaster
->addCloseListener(m_xDocCloseListener
);
465 catch ( lang::DisposedException
const & )
467 // Note: Due to race conditions the XEnumeration can
468 // contain docs that have already been closed
470 catch ( lang::NotInitializedException
const & )
472 // Note: Due to race conditions the XEnumeration can
473 // contain docs that are still uninitialized
478 uno::Reference
< embed::XStorage
>
479 OfficeDocumentsManager::queryStorage( const OUString
& rDocId
)
481 osl::MutexGuard
aGuard( m_aMtx
);
483 DocumentList::const_iterator it
= m_aDocs
.find( rDocId
);
484 if ( it
== m_aDocs
.end() )
485 return uno::Reference
< embed::XStorage
>();
487 return (*it
).second
.xStorage
;
491 OUString
OfficeDocumentsManager::queryDocumentId(
492 const uno::Reference
< frame::XModel
> & xModel
)
494 return getDocumentId( xModel
);
498 uno::Reference
< frame::XModel
>
499 OfficeDocumentsManager::queryDocumentModel( const OUString
& rDocId
)
501 osl::MutexGuard
aGuard( m_aMtx
);
503 DocumentList::const_iterator it
= m_aDocs
.find( rDocId
);
504 if ( it
== m_aDocs
.end() )
505 return uno::Reference
< frame::XModel
>();
507 return (*it
).second
.xModel
;
511 uno::Sequence
< OUString
> OfficeDocumentsManager::queryDocuments()
513 osl::MutexGuard
aGuard( m_aMtx
);
515 return comphelper::mapKeysToSequence( m_aDocs
);
520 OfficeDocumentsManager::queryStorageTitle( const OUString
& rDocId
)
522 osl::MutexGuard
aGuard( m_aMtx
);
524 DocumentList::const_iterator it
= m_aDocs
.find( rDocId
);
525 if ( it
== m_aDocs
.end() )
528 return (*it
).second
.aTitle
;
532 bool OfficeDocumentsManager::isDocumentPreview(
533 const uno::Reference
< frame::XModel
> & xModel
)
538 ::comphelper::NamedValueCollection
aArgs(
540 bool bIsPreview
= aArgs
.getOrDefault( "Preview", false );
545 bool OfficeDocumentsManager::isHelpDocument(
546 const uno::Reference
< frame::XModel
> & xModel
)
551 OUString
sURL( xModel
->getURL() );
552 return sURL
.match( "vnd.sun.star.help://" );
556 bool OfficeDocumentsManager::isWithoutOrInTopLevelFrame(
557 const uno::Reference
< frame::XModel
> & xModel
)
562 uno::Reference
< frame::XController
> xController
563 = xModel
->getCurrentController();
564 if ( xController
.is() )
566 uno::Reference
< frame::XFrame
> xFrame
567 = xController
->getFrame();
570 // don't use XFrame::isTop here. This nowadays excludes
571 // "sub documents" such as forms embedded in database documents
572 uno::Reference
< awt::XTopWindow
> xFrameContainer(
573 xFrame
->getContainerWindow(), uno::UNO_QUERY
);
574 if ( !xFrameContainer
.is() )
583 bool OfficeDocumentsManager::isBasicIDE(
584 const uno::Reference
< frame::XModel
> & xModel
)
586 if ( !m_xModuleMgr
.is() )
588 osl::MutexGuard
aGuard( m_aMtx
);
589 if ( !m_xModuleMgr
.is() )
593 m_xModuleMgr
= frame::ModuleManager::create( m_xContext
);
595 catch ( uno::Exception
const & )
600 OSL_ENSURE( m_xModuleMgr
.is(),
601 "Could not instantiate ModuleManager service!" );
605 if ( m_xModuleMgr
.is() )
610 aModule
= m_xModuleMgr
->identify( xModel
);
612 catch ( lang::IllegalArgumentException
const & )
614 TOOLS_WARN_EXCEPTION("ucb.ucp", "");
616 catch ( frame::UnknownModuleException
const & )
618 TOOLS_WARN_EXCEPTION("ucb.ucp", "");
621 if ( !aModule
.isEmpty() )
623 // Filter unwanted items, that are no real documents.
624 if ( aModule
== "com.sun.star.script.BasicIDE" )
635 bool OfficeDocumentsManager::isOfficeDocument(
636 const uno::Reference
< uno::XInterface
> & xDoc
)
638 uno::Reference
< frame::XModel
> xModel( xDoc
, uno::UNO_QUERY
);
639 uno::Reference
< document::XStorageBasedDocument
>
640 xStorageBasedDoc( xModel
, uno::UNO_QUERY
);
641 if ( !xStorageBasedDoc
.is() )
644 if ( !isWithoutOrInTopLevelFrame( xModel
) )
647 if ( isDocumentPreview( xModel
) )
650 if ( isHelpDocument( xModel
) )
653 if ( isBasicIDE( xModel
) )
659 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */