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 #include <sal/config.h>
21 
22 #include <string_view>
23 
24 #include "wizardshell.hxx"
25 
26 #include <com/sun/star/container/NoSuchElementException.hpp>
27 #include <com/sun/star/beans/XPropertySetInfo.hpp>
28 #include <com/sun/star/uno/XComponentContext.hpp>
29 #include <com/sun/star/ucb/AlreadyInitializedException.hpp>
30 #include <com/sun/star/ui/dialogs/XWizard.hpp>
31 #include <com/sun/star/ui/dialogs/XWizardController.hpp>
32 #include <com/sun/star/ui/dialogs/WizardButton.hpp>
33 #include <com/sun/star/util/InvalidStateException.hpp>
34 
35 #include <comphelper/proparrhlp.hxx>
36 #include <cppuhelper/implbase.hxx>
37 #include <svtools/genericunodialog.hxx>
38 #include <toolkit/helper/vclunohelper.hxx>
39 #include <tools/diagnose_ex.h>
40 #include <osl/mutex.hxx>
41 #include <vcl/svapp.hxx>
42 #include <tools/urlobj.hxx>
43 
44 using namespace ::com::sun::star;
45 using namespace ::svt::uno;
46 
47 namespace {
48 
49     using css::uno::Reference;
50     using css::uno::XInterface;
51     using css::uno::UNO_QUERY;
52     using css::uno::Any;
53     using css::uno::Sequence;
54     using css::ui::dialogs::XWizard;
55     using css::beans::XPropertySetInfo;
56     using css::uno::XComponentContext;
57     using css::beans::Property;
58     using css::lang::IllegalArgumentException;
59     using css::ucb::AlreadyInitializedException;
60     using css::ui::dialogs::XWizardController;
61     using css::ui::dialogs::XWizardPage;
62     using css::container::NoSuchElementException;
63     using css::util::InvalidStateException;
64     using css::awt::XWindow;
65 
66     namespace WizardButton = css::ui::dialogs::WizardButton;
67 
lcl_convertWizardButtonToWZB(const sal_Int16 i_nWizardButton)68     WizardButtonFlags lcl_convertWizardButtonToWZB( const sal_Int16 i_nWizardButton )
69     {
70         switch ( i_nWizardButton )
71         {
72         case WizardButton::NONE:        return WizardButtonFlags::NONE;
73         case WizardButton::NEXT:        return WizardButtonFlags::NEXT;
74         case WizardButton::PREVIOUS:    return WizardButtonFlags::PREVIOUS;
75         case WizardButton::FINISH:      return WizardButtonFlags::FINISH;
76         case WizardButton::CANCEL:      return WizardButtonFlags::CANCEL;
77         case WizardButton::HELP:        return WizardButtonFlags::HELP;
78         }
79         OSL_FAIL( "lcl_convertWizardButtonToWZB: invalid WizardButton constant!" );
80         return WizardButtonFlags::NONE;
81     }
82 
83     typedef ::cppu::ImplInheritanceHelper  <   ::svt::OGenericUnoDialog
84                                             ,   ui::dialogs::XWizard
85                                             >   Wizard_Base;
86     class Wizard;
87     typedef ::comphelper::OPropertyArrayUsageHelper< Wizard >  Wizard_PBase;
88     class Wizard    : public Wizard_Base
89                     , public Wizard_PBase
90     {
91     public:
92         explicit Wizard( const css::uno::Reference< css::uno::XComponentContext >& i_rContext );
93 
94         // lang::XServiceInfo
95         virtual OUString SAL_CALL getImplementationName() override;
96         virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
97 
98         // beans::XPropertySet
99         virtual css::uno::Reference< beans::XPropertySetInfo >  SAL_CALL getPropertySetInfo() override;
100         virtual ::cppu::IPropertyArrayHelper& SAL_CALL getInfoHelper() override;
101 
102         // OPropertyArrayUsageHelper
103         virtual ::cppu::IPropertyArrayHelper* createArrayHelper( ) const override;
104 
105         // ui::dialogs::XWizard
106         virtual OUString SAL_CALL getHelpURL() override;
107         virtual void SAL_CALL setHelpURL( const OUString& _helpurl ) override;
108         virtual css::uno::Reference< awt::XWindow > SAL_CALL getDialogWindow() override;
109         virtual css::uno::Reference< ui::dialogs::XWizardPage > SAL_CALL getCurrentPage(  ) override;
110         virtual void SAL_CALL enableButton( ::sal_Int16 WizardButton, sal_Bool Enable ) override;
111         virtual void SAL_CALL setDefaultButton( ::sal_Int16 WizardButton ) override;
112         virtual sal_Bool SAL_CALL travelNext(  ) override;
113         virtual sal_Bool SAL_CALL travelPrevious(  ) override;
114         virtual void SAL_CALL enablePage( ::sal_Int16 PageID, sal_Bool Enable ) override;
115         virtual void SAL_CALL updateTravelUI(  ) override;
116         virtual sal_Bool SAL_CALL advanceTo( ::sal_Int16 PageId ) override;
117         virtual sal_Bool SAL_CALL goBackTo( ::sal_Int16 PageId ) override;
118         virtual void SAL_CALL activatePath( ::sal_Int16 PathIndex, sal_Bool Final ) override;
119 
120         // ui::dialogs::XExecutableDialog
121         virtual void SAL_CALL setTitle( const OUString& aTitle ) override;
122         virtual ::sal_Int16 SAL_CALL execute(  ) override;
123 
124         // lang::XInitialization
125         virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
126 
127    protected:
128         virtual ~Wizard() override;
129 
130     protected:
131         virtual std::unique_ptr<weld::DialogController> createDialog(const css::uno::Reference<css::awt::XWindow>& rParent) override;
132 
133     private:
134         css::uno::Sequence< css::uno::Sequence< sal_Int16 > >         m_aWizardSteps;
135         css::uno::Reference< ui::dialogs::XWizardController >    m_xController;
136         OUString                                            m_sHelpURL;
137     };
138 
Wizard(const Reference<XComponentContext> & _rxContext)139     Wizard::Wizard( const Reference< XComponentContext >& _rxContext )
140         :Wizard_Base( _rxContext )
141     {
142     }
143 
lcl_getHelpURL(std::string_view sHelpId)144     OUString lcl_getHelpURL( std::string_view sHelpId )
145     {
146         OUStringBuffer aBuffer;
147         OUString aTmp(
148             OStringToOUString( sHelpId, RTL_TEXTENCODING_UTF8 ) );
149         INetURLObject aHID( aTmp );
150         if ( aHID.GetProtocol() == INetProtocol::NotValid )
151             aBuffer.append( INET_HID_SCHEME );
152         aBuffer.append( aTmp );
153         return aBuffer.makeStringAndClear();
154     }
155 
~Wizard()156     Wizard::~Wizard()
157     {
158         if (m_xDialog)
159         {
160             ::osl::MutexGuard aGuard( m_aMutex );
161             if (m_xDialog)
162             {
163                 m_sHelpURL = lcl_getHelpURL(m_xDialog->get_help_id());
164                 destroyDialog();
165             }
166         }
167     }
168 
lcl_checkPaths(const Sequence<Sequence<sal_Int16>> & i_rPaths,const Reference<XInterface> & i_rContext)169     void lcl_checkPaths( const Sequence< Sequence< sal_Int16 > >& i_rPaths, const Reference< XInterface >& i_rContext )
170     {
171         // need at least one path
172         if ( !i_rPaths.hasElements() )
173             throw IllegalArgumentException( OUString(), i_rContext, 2 );
174 
175         // each path must be of length 1, at least
176         sal_Int32 i = 0;
177         for ( const Sequence< sal_Int16 >& rPath : i_rPaths )
178         {
179             if ( !rPath.hasElements() )
180                 throw IllegalArgumentException( OUString(), i_rContext, 2 );
181 
182             // page IDs must be in ascending order
183             auto pPageId = std::adjacent_find(rPath.begin(), rPath.end(), std::greater_equal<sal_Int16>());
184             if (pPageId != rPath.end())
185             {
186                 throw IllegalArgumentException(
187                     "Path " + OUString::number(i)
188                     + ": invalid page ID sequence - each page ID must be greater than the previous one.",
189                     i_rContext, 2 );
190             }
191             ++i;
192         }
193 
194         // if we have one path, that's okay
195         if ( i_rPaths.getLength() == 1 )
196             return;
197 
198         // if we have multiple paths, they must start with the same page id
199         const sal_Int16 nFirstPageId = i_rPaths[0][0];
200         if (std::any_of(i_rPaths.begin(), i_rPaths.end(),
201                 [nFirstPageId](const Sequence< sal_Int16 >& rPath) { return rPath[0] != nFirstPageId; }))
202             throw IllegalArgumentException(
203                 "All paths must start with the same page id.",
204                 i_rContext, 2 );
205     }
206 
initialize(const Sequence<Any> & i_Arguments)207     void SAL_CALL Wizard::initialize( const Sequence< Any >& i_Arguments )
208     {
209         ::osl::MutexGuard aGuard( m_aMutex );
210         if ( m_bInitialized )
211             throw AlreadyInitializedException( OUString(), *this );
212 
213         if ( i_Arguments.getLength() != 2 )
214             throw IllegalArgumentException( OUString(), *this, -1 );
215 
216         // the second argument must be a XWizardController, for each constructor
217         m_xController.set( i_Arguments[1], UNO_QUERY );
218         if ( !m_xController.is() )
219             throw IllegalArgumentException( OUString(), *this, 2 );
220 
221         // the first arg is either a single path (short[]), or multiple paths (short[][])
222         Sequence< sal_Int16 > aSinglePath;
223         i_Arguments[0] >>= aSinglePath;
224         Sequence< Sequence< sal_Int16 > > aMultiplePaths;
225         i_Arguments[0] >>= aMultiplePaths;
226 
227         if ( !aMultiplePaths.hasElements() )
228         {
229             aMultiplePaths.realloc(1);
230             aMultiplePaths[0] = aSinglePath;
231         }
232         lcl_checkPaths( aMultiplePaths, *this );
233         // if we survived this, the paths are valid, and we're done here ...
234         m_aWizardSteps = aMultiplePaths;
235 
236         m_bInitialized = true;
237     }
238 
lcl_getHelpId(const OUString & _rHelpURL)239     OString lcl_getHelpId( const OUString& _rHelpURL )
240     {
241         INetURLObject aHID( _rHelpURL );
242         if ( aHID.GetProtocol() == INetProtocol::Hid )
243             return OUStringToOString( aHID.GetURLPath(), RTL_TEXTENCODING_UTF8 );
244         else
245             return OUStringToOString( _rHelpURL, RTL_TEXTENCODING_UTF8 );
246     }
247 
createDialog(const css::uno::Reference<css::awt::XWindow> & rParent)248     std::unique_ptr<weld::DialogController> Wizard::createDialog(const css::uno::Reference<css::awt::XWindow>& rParent)
249     {
250         auto xDialog = std::make_unique<WizardShell>(Application::GetFrameWeld(rParent), m_xController, m_aWizardSteps);
251         xDialog->set_help_id(lcl_getHelpId(m_sHelpURL));
252         xDialog->setTitleBase( m_sTitle );
253         return xDialog;
254     }
255 
getImplementationName()256     OUString SAL_CALL Wizard::getImplementationName()
257     {
258         return "com.sun.star.comp.svtools.uno.Wizard";
259     }
260 
getSupportedServiceNames()261     Sequence< OUString > SAL_CALL Wizard::getSupportedServiceNames()
262     {
263         return { "com.sun.star.ui.dialogs.Wizard" };
264     }
265 
getPropertySetInfo()266     Reference< XPropertySetInfo > SAL_CALL Wizard::getPropertySetInfo()
267     {
268         return createPropertySetInfo( getInfoHelper() );
269     }
270 
getInfoHelper()271     ::cppu::IPropertyArrayHelper& SAL_CALL Wizard::getInfoHelper()
272     {
273         return *getArrayHelper();
274     }
275 
createArrayHelper() const276     ::cppu::IPropertyArrayHelper* Wizard::createArrayHelper( ) const
277     {
278         Sequence< Property > aProps;
279         describeProperties( aProps );
280         return new ::cppu::OPropertyArrayHelper( aProps );
281     }
282 
getHelpURL()283     OUString SAL_CALL Wizard::getHelpURL()
284     {
285         SolarMutexGuard aSolarGuard;
286         ::osl::MutexGuard aGuard( m_aMutex );
287 
288         if (!m_xDialog)
289             return m_sHelpURL;
290 
291         return lcl_getHelpURL(m_xDialog->get_help_id());
292     }
293 
setHelpURL(const OUString & i_HelpURL)294     void SAL_CALL Wizard::setHelpURL( const OUString& i_HelpURL )
295     {
296         SolarMutexGuard aSolarGuard;
297         ::osl::MutexGuard aGuard( m_aMutex );
298 
299         if (!m_xDialog)
300             m_sHelpURL = i_HelpURL;
301         else
302             m_xDialog->set_help_id(lcl_getHelpId(i_HelpURL));
303     }
304 
getDialogWindow()305     Reference< XWindow > SAL_CALL Wizard::getDialogWindow()
306     {
307         SolarMutexGuard aSolarGuard;
308         ::osl::MutexGuard aGuard( m_aMutex );
309 
310         ENSURE_OR_RETURN( m_xDialog, "Wizard::getDialogWindow: illegal call (execution did not start, yet)!", nullptr );
311         return m_xDialog->getDialog()->GetXWindow();
312     }
313 
enableButton(::sal_Int16 i_WizardButton,sal_Bool i_Enable)314     void SAL_CALL Wizard::enableButton( ::sal_Int16 i_WizardButton, sal_Bool i_Enable )
315     {
316         SolarMutexGuard aSolarGuard;
317         ::osl::MutexGuard aGuard( m_aMutex );
318 
319         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
320         ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::enableButtons: invalid dialog implementation!" );
321 
322         pWizardImpl->enableButtons( lcl_convertWizardButtonToWZB( i_WizardButton ), i_Enable );
323     }
324 
setDefaultButton(::sal_Int16 i_WizardButton)325     void SAL_CALL Wizard::setDefaultButton( ::sal_Int16 i_WizardButton )
326     {
327         SolarMutexGuard aSolarGuard;
328         ::osl::MutexGuard aGuard( m_aMutex );
329 
330         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
331         ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::setDefaultButton: invalid dialog implementation!" );
332 
333         pWizardImpl->defaultButton( lcl_convertWizardButtonToWZB( i_WizardButton ) );
334     }
335 
travelNext()336     sal_Bool SAL_CALL Wizard::travelNext(  )
337     {
338         SolarMutexGuard aSolarGuard;
339         ::osl::MutexGuard aGuard( m_aMutex );
340 
341         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
342         ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::travelNext: invalid dialog implementation!" );
343 
344         return pWizardImpl->travelNext();
345     }
346 
travelPrevious()347     sal_Bool SAL_CALL Wizard::travelPrevious(  )
348     {
349         SolarMutexGuard aSolarGuard;
350         ::osl::MutexGuard aGuard( m_aMutex );
351 
352         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
353         ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::travelPrevious: invalid dialog implementation!" );
354 
355         return pWizardImpl->travelPrevious();
356     }
357 
enablePage(::sal_Int16 i_PageID,sal_Bool i_Enable)358     void SAL_CALL Wizard::enablePage( ::sal_Int16 i_PageID, sal_Bool i_Enable )
359     {
360         SolarMutexGuard aSolarGuard;
361         ::osl::MutexGuard aGuard( m_aMutex );
362 
363         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
364         ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::enablePage: invalid dialog implementation!" );
365 
366         if ( !pWizardImpl->knowsPage( i_PageID ) )
367             throw NoSuchElementException( OUString(), *this );
368 
369         if ( i_PageID == pWizardImpl->getCurrentPage() )
370             throw InvalidStateException( OUString(), *this );
371 
372         pWizardImpl->enablePage( i_PageID, i_Enable );
373     }
374 
updateTravelUI()375     void SAL_CALL Wizard::updateTravelUI(  )
376     {
377         SolarMutexGuard aSolarGuard;
378         ::osl::MutexGuard aGuard( m_aMutex );
379 
380         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
381         ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::updateTravelUI: invalid dialog implementation!" );
382 
383         pWizardImpl->updateTravelUI();
384     }
385 
advanceTo(::sal_Int16 i_PageId)386     sal_Bool SAL_CALL Wizard::advanceTo( ::sal_Int16 i_PageId )
387     {
388         SolarMutexGuard aSolarGuard;
389         ::osl::MutexGuard aGuard( m_aMutex );
390 
391         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
392         ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::advanceTo: invalid dialog implementation!" );
393 
394         return pWizardImpl->advanceTo( i_PageId );
395     }
396 
goBackTo(::sal_Int16 i_PageId)397     sal_Bool SAL_CALL Wizard::goBackTo( ::sal_Int16 i_PageId )
398     {
399         SolarMutexGuard aSolarGuard;
400         ::osl::MutexGuard aGuard( m_aMutex );
401 
402         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
403         ENSURE_OR_RETURN_FALSE( pWizardImpl, "Wizard::goBackTo: invalid dialog implementation!" );
404 
405         return pWizardImpl->goBackTo( i_PageId );
406     }
407 
getCurrentPage()408     Reference< XWizardPage > SAL_CALL Wizard::getCurrentPage(  )
409     {
410         SolarMutexGuard aSolarGuard;
411         ::osl::MutexGuard aGuard( m_aMutex );
412 
413         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
414         ENSURE_OR_RETURN( pWizardImpl, "Wizard::getCurrentPage: invalid dialog implementation!", Reference< XWizardPage >() );
415 
416         return pWizardImpl->getCurrentWizardPage();
417     }
418 
activatePath(::sal_Int16 i_PathIndex,sal_Bool i_Final)419     void SAL_CALL Wizard::activatePath( ::sal_Int16 i_PathIndex, sal_Bool i_Final )
420     {
421         SolarMutexGuard aSolarGuard;
422         ::osl::MutexGuard aGuard( m_aMutex );
423 
424         if ( ( i_PathIndex < 0 ) || ( i_PathIndex >= m_aWizardSteps.getLength() ) )
425             throw NoSuchElementException( OUString(), *this );
426 
427         WizardShell* pWizardImpl = dynamic_cast<WizardShell*>(m_xDialog.get());
428         ENSURE_OR_RETURN_VOID( pWizardImpl, "Wizard::activatePath: invalid dialog implementation!" );
429 
430         pWizardImpl->activatePath( i_PathIndex, i_Final );
431     }
432 
setTitle(const OUString & i_Title)433     void SAL_CALL Wizard::setTitle( const OUString& i_Title )
434     {
435         // simply disambiguate
436         Wizard_Base::OGenericUnoDialog::setTitle( i_Title );
437     }
438 
execute()439     ::sal_Int16 SAL_CALL Wizard::execute(  )
440     {
441         return Wizard_Base::OGenericUnoDialog::execute();
442     }
443 }
444 
445 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_comp_svtools_uno_Wizard_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)446 com_sun_star_comp_svtools_uno_Wizard_get_implementation(
447     css::uno::XComponentContext *context,
448     css::uno::Sequence<css::uno::Any> const &)
449 {
450     return cppu::acquire(new Wizard(context));
451 }
452 
453 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
454