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 "commonpicker.hxx"
22 #include "fpdialogbase.hxx"
23 #include "OfficeControlAccess.hxx"
24 #include <com/sun/star/beans/PropertyAttribute.hpp>
25 #include <com/sun/star/beans/PropertyValue.hpp>
26 #include <com/sun/star/beans/NamedValue.hpp>
27 #include <vcl/svapp.hxx>
28 #include <vcl/window.hxx>
29 #include <osl/mutex.hxx>
30 #include <sal/log.hxx>
31 #include <tools/debug.hxx>
32 #include <toolkit/helper/vclunohelper.hxx>
33 #include <comphelper/weakeventlistener.hxx>
34 #include <comphelper/types.hxx>
35 
36 
37 namespace svt
38 {
39 
40 
41 #define PROPERTY_ID_HELPURL     1
42 #define PROPERTY_ID_WINDOW      2
43 
44     // using --------------------------------------------------------------
45 
46     using namespace     ::com::sun::star::lang;
47     using namespace     ::com::sun::star::ui::dialogs;
48     using namespace     ::com::sun::star::uno;
49     using namespace     ::com::sun::star::beans;
50     using namespace     ::comphelper;
51 
52 
OCommonPicker()53     OCommonPicker::OCommonPicker()
54         :OCommonPicker_Base( m_aMutex )
55         ,OPropertyContainer( GetBroadcastHelper() )
56         ,m_nCancelEvent( nullptr )
57         ,m_bExecuting( false )
58     {
59         // the two properties we have
60         registerProperty(
61             "HelpURL", PROPERTY_ID_HELPURL,
62             PropertyAttribute::TRANSIENT,
63             &m_sHelpURL, cppu::UnoType<decltype(m_sHelpURL)>::get()
64         );
65 
66         registerProperty(
67             "Window", PROPERTY_ID_WINDOW,
68             PropertyAttribute::TRANSIENT | PropertyAttribute::READONLY,
69             &m_xWindow, cppu::UnoType<decltype(m_xWindow)>::get()
70         );
71     }
72 
73 
~OCommonPicker()74     OCommonPicker::~OCommonPicker()
75     {
76         if ( !GetBroadcastHelper().bDisposed )
77         {
78             acquire();
79             dispose();
80         }
81     }
82 
83 
84     // disambiguate XInterface
85 
IMPLEMENT_FORWARD_XINTERFACE2(OCommonPicker,OCommonPicker_Base,OPropertyContainer)86     IMPLEMENT_FORWARD_XINTERFACE2( OCommonPicker, OCommonPicker_Base, OPropertyContainer )
87 
88 
89     // disambiguate XTypeProvider
90 
91     IMPLEMENT_FORWARD_XTYPEPROVIDER2( OCommonPicker, OCommonPicker_Base, OPropertyContainer )
92 
93 
94     // XComponent related methods
95 
96     void OCommonPicker::checkAlive() const
97     {
98         if ( GetBroadcastHelper().bInDispose || GetBroadcastHelper().bDisposed )
99             throw DisposedException();
100     }
101 
prepareDialog()102     void OCommonPicker::prepareDialog()
103     {
104         if(createPicker())
105         {
106             // set the title
107             if ( !m_aTitle.isEmpty() )
108                 m_xDlg->set_title(m_aTitle);
109         }
110     }
111 
112 
disposing()113     void SAL_CALL OCommonPicker::disposing()
114     {
115         SolarMutexGuard aGuard;
116 
117         stopWindowListening();
118 
119         if ( m_nCancelEvent )
120             Application::RemoveUserEvent( m_nCancelEvent );
121 
122         {
123             ::osl::MutexGuard aOwnGuard( m_aMutex );
124             if ( m_bExecuting && m_xDlg )
125                 m_xDlg->response(RET_CANCEL);
126         }
127 
128         m_xDlg.reset();
129         m_xWindow = nullptr;
130         m_xDialogParent = nullptr;
131     }
132 
133 
stopWindowListening()134     void OCommonPicker::stopWindowListening()
135     {
136         disposeComponent( m_xWindowListenerAdapter );
137         disposeComponent( m_xParentListenerAdapter );
138     }
139 
140     // XEventListener
disposing(const EventObject & _rSource)141     void SAL_CALL OCommonPicker::disposing( const EventObject& _rSource )
142     {
143         SolarMutexGuard aGuard;
144         bool bDialogDying = _rSource.Source == m_xWindow;
145         bool bParentDying = _rSource.Source == m_xDialogParent;
146 
147         if ( bDialogDying || bParentDying )
148         {
149             stopWindowListening();
150 
151             SAL_WARN_IF(bDialogDying && m_bExecuting, "fpicker.office", "unexpected disposing before response" );
152 
153             // it's the parent which is dying -> delete the dialog
154             {
155                 ::osl::MutexGuard aOwnGuard(m_aMutex);
156                 if (m_bExecuting && m_xDlg)
157                     m_xDlg->response(RET_CANCEL);
158             }
159 
160             m_xDlg.reset();
161             m_xWindow = nullptr;
162             m_xDialogParent = nullptr;
163         }
164         else
165         {
166             OSL_FAIL( "OCommonPicker::disposing: where did this come from?" );
167         }
168     }
169 
170     // property set related methods
createArrayHelper() const171     ::cppu::IPropertyArrayHelper* OCommonPicker::createArrayHelper( ) const
172     {
173         Sequence< Property > aProps;
174         describeProperties( aProps );
175         return new cppu::OPropertyArrayHelper( aProps );
176     }
177 
getInfoHelper()178     ::cppu::IPropertyArrayHelper& SAL_CALL OCommonPicker::getInfoHelper()
179     {
180         return *getArrayHelper();
181     }
182 
getPropertySetInfo()183     Reference< XPropertySetInfo > SAL_CALL OCommonPicker::getPropertySetInfo(  )
184     {
185         return ::cppu::OPropertySetHelper::createPropertySetInfo( getInfoHelper() );
186     }
187 
setFastPropertyValue_NoBroadcast(sal_Int32 nHandle,const Any & rValue)188     void SAL_CALL OCommonPicker::setFastPropertyValue_NoBroadcast(sal_Int32 nHandle, const Any& rValue)
189     {
190         OPropertyContainer::setFastPropertyValue_NoBroadcast(nHandle, rValue);
191 
192         // if the HelpURL changed, forward this to the dialog
193         if (PROPERTY_ID_HELPURL == nHandle && m_xDlg)
194         {
195             ::svt::OControlAccess aAccess(m_xDlg.get(), m_xDlg->GetView());
196             aAccess.setHelpURL(m_xDlg->getDialog(), m_sHelpURL);
197         }
198     }
199 
createPicker()200     bool OCommonPicker::createPicker()
201     {
202         if ( !m_xDlg )
203         {
204             m_xDlg = implCreateDialog(Application::GetFrameWeld(m_xDialogParent));
205             SAL_WARN_IF( !m_xDlg, "fpicker.office", "OCommonPicker::createPicker: invalid dialog returned!" );
206 
207             if ( m_xDlg )
208             {
209                 weld::Dialog* pDlg = m_xDlg->getDialog();
210 
211                 ::svt::OControlAccess aAccess(m_xDlg.get(), m_xDlg->GetView());
212                 // synchronize the help id of the dialog without help URL property
213                 if ( !m_sHelpURL.isEmpty() )
214                 {   // somebody already set the help URL while we had no dialog yet
215                     aAccess.setHelpURL(pDlg, m_sHelpURL);
216                 }
217                 else
218                 {
219                     m_sHelpURL = aAccess.getHelpURL(pDlg);
220                 }
221 
222                 m_xWindow = pDlg->GetXWindow();
223 
224                 // add as event listener to the window
225                 OSL_ENSURE( m_xWindow.is(), "OCommonPicker::createFileDialog: invalid window component!" );
226                 if ( m_xWindow.is() )
227                 {
228                     m_xWindowListenerAdapter = new OWeakEventListenerAdapter( this, m_xWindow );
229                         // the adapter will add itself as listener, and forward notifications
230                 }
231 
232                 VclPtr<vcl::Window> xVclDialog(VCLUnoHelper::GetWindow(m_xWindow));
233                 if (xVclDialog) // this block is quite possibly unnecessary by now
234                 {
235                     // _and_ add as event listener to the parent - in case the parent is destroyed
236                     // before we are disposed, our disposal would access dead VCL windows then...
237                     m_xDialogParent = VCLUnoHelper::GetInterface(xVclDialog->GetParent());
238                     OSL_ENSURE(m_xDialogParent.is() || !xVclDialog->GetParent(), "OCommonPicker::createFileDialog: invalid window component (the parent this time)!");
239                 }
240                 if ( m_xDialogParent.is() )
241                 {
242                     m_xParentListenerAdapter = new OWeakEventListenerAdapter( this, m_xDialogParent );
243                         // the adapter will add itself as listener, and forward notifications
244                 }
245             }
246         }
247 
248         return nullptr != m_xDlg;
249     }
250 
251     // XControlAccess functions
setControlProperty(const OUString & aControlName,const OUString & aControlProperty,const Any & aValue)252     void SAL_CALL OCommonPicker::setControlProperty( const OUString& aControlName, const OUString& aControlProperty, const Any& aValue )
253     {
254         checkAlive();
255 
256         SolarMutexGuard aGuard;
257         if ( createPicker() )
258         {
259             ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() );
260             aAccess.setControlProperty( aControlName, aControlProperty, aValue );
261         }
262     }
263 
getControlProperty(const OUString & aControlName,const OUString & aControlProperty)264     Any SAL_CALL OCommonPicker::getControlProperty( const OUString& aControlName, const OUString& aControlProperty )
265     {
266         checkAlive();
267 
268         SolarMutexGuard aGuard;
269         if ( createPicker() )
270         {
271             ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() );
272             return aAccess.getControlProperty( aControlName, aControlProperty );
273         }
274 
275         return Any();
276     }
277 
278     // XControlInformation functions
getSupportedControls()279     Sequence< OUString > SAL_CALL OCommonPicker::getSupportedControls(  )
280     {
281         checkAlive();
282 
283         SolarMutexGuard aGuard;
284         if ( createPicker() )
285         {
286             ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() );
287             return aAccess.getSupportedControls( );
288         }
289 
290         return Sequence< OUString >();
291     }
292 
isControlSupported(const OUString & aControlName)293     sal_Bool SAL_CALL OCommonPicker::isControlSupported( const OUString& aControlName )
294     {
295         checkAlive();
296 
297         SolarMutexGuard aGuard;
298         if ( createPicker() )
299         {
300             return svt::OControlAccess::isControlSupported( aControlName );
301         }
302 
303         return false;
304     }
305 
getSupportedControlProperties(const OUString & aControlName)306     Sequence< OUString > SAL_CALL OCommonPicker::getSupportedControlProperties( const OUString& aControlName )
307     {
308         checkAlive();
309 
310         SolarMutexGuard aGuard;
311         if ( createPicker() )
312         {
313             ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() );
314             return aAccess.getSupportedControlProperties( aControlName );
315         }
316 
317         return Sequence< OUString >();
318     }
319 
isControlPropertySupported(const OUString & aControlName,const OUString & aControlProperty)320     sal_Bool SAL_CALL OCommonPicker::isControlPropertySupported( const OUString& aControlName, const OUString& aControlProperty )
321     {
322         checkAlive();
323 
324         SolarMutexGuard aGuard;
325         if ( createPicker() )
326         {
327             ::svt::OControlAccess aAccess( m_xDlg.get(), m_xDlg->GetView() );
328             return aAccess.isControlPropertySupported( aControlName, aControlProperty );
329         }
330 
331         return false;
332     }
333 
334 
335     // XExecutableDialog functions
336 
setTitle(const OUString & _rTitle)337     void OCommonPicker::setTitle( const OUString& _rTitle )
338     {
339         SolarMutexGuard aGuard;
340         m_aTitle = _rTitle;
341     }
342 
343 
execute()344     sal_Int16 OCommonPicker::execute()
345     {
346         SolarMutexGuard aGuard;
347 
348         prepareDialog();
349 
350         {
351             ::osl::MutexGuard aOwnGuard( m_aMutex );
352             m_bExecuting = true;
353         }
354         sal_Int16 nResult = implExecutePicker();
355         {
356             ::osl::MutexGuard aOwnGuard( m_aMutex );
357             m_bExecuting = false;
358         }
359 
360         return nResult;
361     }
362 
363 
364     // XCancellable functions
365 
cancel()366     void SAL_CALL OCommonPicker::cancel(  )
367     {
368         {
369             ::osl::MutexGuard aGuard( m_aMutex );
370             if ( m_nCancelEvent )
371                 // nothing to do - the event for cancelling the dialog is already on the way
372                 return;
373         }
374 
375         // The thread which executes our dialog has locked the solar mutex for
376         // sure. Cancelling the dialog should be done with a locked solar mutex, too.
377         // Thus we post ourself a message for cancelling the dialog. This way, the message
378         // is either handled in the thread which opened the dialog (which may even be
379         // this thread here), or, if no dialog is open, in the thread doing scheduling
380         // currently. Both is okay for us...
381 
382         // Note that we could do check if we are really executing the dialog currently.
383         // but the information would be potentially obsolete at the moment our event
384         // arrives, so we need to check it there, anyway...
385         m_nCancelEvent = Application::PostUserEvent( LINK( this, OCommonPicker, OnCancelPicker ) );
386     }
387 
IMPL_LINK_NOARG(OCommonPicker,OnCancelPicker,void *,void)388     IMPL_LINK_NOARG(OCommonPicker, OnCancelPicker, void*, void)
389     {
390         // By definition, the solar mutex is locked when we arrive here. Note that this
391         // is important, as for instance the consistency of m_xDlg depends on this mutex.
392         ::osl::MutexGuard aGuard( m_aMutex );
393         m_nCancelEvent = nullptr;
394 
395         if ( !m_bExecuting )
396             // nothing to do. This may be because the dialog was canceled after our cancel method
397             // posted this async event, or because somebody called cancel without the dialog
398             // being executed at this time.
399             return;
400 
401         OSL_ENSURE( m_xDlg, "OCommonPicker::OnCancelPicker: executing, but no dialog!" );
402         if (m_xDlg)
403             m_xDlg->response(RET_CANCEL);
404     }
405 
406     // XInitialization functions
initialize(const Sequence<Any> & _rArguments)407     void SAL_CALL OCommonPicker::initialize( const Sequence< Any >& _rArguments )
408     {
409         checkAlive();
410 
411         OUString sSettingName;
412         Any             aSettingValue;
413 
414         PropertyValue   aPropArg;
415         NamedValue      aPairArg;
416 
417 
418         const Any* pArguments       = _rArguments.getConstArray();
419         const Any* pArgumentsEnd    = _rArguments.getConstArray() + _rArguments.getLength();
420         for (   const Any* pArgument = pArguments;
421                 pArgument != pArgumentsEnd;
422                 ++pArgument
423             )
424         {
425             if ( *pArgument >>= aPropArg )
426             {
427                 if ( aPropArg.Name.isEmpty())
428                     continue;
429 
430                 sSettingName = aPropArg.Name;
431                 aSettingValue = aPropArg.Value;
432             }
433             else if ( *pArgument >>= aPairArg )
434             {
435                 if ( aPairArg.Name.isEmpty())
436                     continue;
437 
438                 sSettingName = aPairArg.Name;
439                 aSettingValue = aPairArg.Value;
440 
441 
442             }
443             else
444             {
445                 SAL_WARN( "fpicker", "OCommonPicker::initialize: unknown argument type at position "
446                     << (pArguments - _rArguments.getConstArray()));
447                 continue;
448             }
449 
450             bool bKnownSetting =
451                 implHandleInitializationArgument( sSettingName, aSettingValue );
452             DBG_ASSERT( bKnownSetting,
453                 OString(
454                     "OCommonPicker::initialize: unknown argument \""
455                     + OString(sSettingName.getStr(), sSettingName.getLength(), osl_getThreadTextEncoding())
456                     + "\"!").getStr() );
457         }
458     }
459 
implHandleInitializationArgument(const OUString & _rName,const Any & _rValue)460     bool OCommonPicker::implHandleInitializationArgument( const OUString& _rName, const Any& _rValue )
461     {
462         bool bKnown = true;
463         if ( _rName == "ParentWindow" )
464         {
465             m_xDialogParent.clear();
466             OSL_VERIFY( _rValue >>= m_xDialogParent );
467             OSL_ENSURE( m_xDialogParent.is(), "OCommonPicker::implHandleInitializationArgument: invalid parent window given!" );
468         }
469         else
470             bKnown = false;
471         return bKnown;
472     }
473 
474 }   // namespace svt
475 
476 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
477