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 <algorithm>
21 #include <utility>
22 #include <helper/statusindicatorfactory.hxx>
23 #include <helper/statusindicator.hxx>
24 #include <helper/vclstatusindicator.hxx>
25 #include <properties.h>
26 
27 #include <com/sun/star/awt/XWindow2.hpp>
28 #include <com/sun/star/beans/XPropertySet.hpp>
29 #include <com/sun/star/frame/XLayoutManager2.hpp>
30 
31 #include <toolkit/helper/vclunohelper.hxx>
32 
33 #include <comphelper/sequenceashashmap.hxx>
34 #include <unotools/mediadescriptor.hxx>
35 #include <vcl/svapp.hxx>
36 #include <mutex>
37 #include <rtl/ref.hxx>
38 
39 #include <officecfg/Office/Common.hxx>
40 
41 namespace framework{
42 
43 sal_Int32 StatusIndicatorFactory::m_nInReschedule = 0;  ///< static counter for rescheduling
44 
45 constexpr OUString PROGRESS_RESOURCE = u"private:resource/progressbar/progressbar"_ustr;
46 
StatusIndicatorFactory(css::uno::Reference<css::uno::XComponentContext> xContext)47 StatusIndicatorFactory::StatusIndicatorFactory(css::uno::Reference< css::uno::XComponentContext >  xContext)
48     : m_xContext          (std::move(xContext ))
49     , m_bAllowReschedule  (false)
50     , m_bAllowParentShow  (false)
51     , m_bDisableReschedule(false)
52 {
53 }
54 
~StatusIndicatorFactory()55 StatusIndicatorFactory::~StatusIndicatorFactory()
56 {
57     impl_stopWakeUpThread();
58 }
59 
initialize(const css::uno::Sequence<css::uno::Any> & lArguments)60 void SAL_CALL StatusIndicatorFactory::initialize(const css::uno::Sequence< css::uno::Any >& lArguments)
61 {
62     if (lArguments.hasElements()) {
63         std::scoped_lock g(m_mutex);
64 
65         css::uno::Reference< css::frame::XFrame > xTmpFrame;
66         css::uno::Reference< css::awt::XWindow > xTmpWindow;
67         bool b1 = lArguments[0] >>= xTmpFrame;
68         bool b2 = lArguments[0] >>= xTmpWindow;
69         if (lArguments.getLength() == 3 && b1) {
70            // it's the first service constructor "createWithFrame"
71             m_xFrame = xTmpFrame;
72             lArguments[1] >>= m_bDisableReschedule;
73             lArguments[2] >>= m_bAllowParentShow;
74         } else if (lArguments.getLength() == 3 && b2) {
75            // it's the second service constructor "createWithWindow"
76             m_xPluggWindow = xTmpWindow;
77             lArguments[1] >>= m_bDisableReschedule;
78             lArguments[2] >>= m_bAllowParentShow;
79         } else {
80            // it's an old-style initialisation using properties
81             ::comphelper::SequenceAsHashMap lArgs(lArguments);
82 
83             m_xFrame             = lArgs.getUnpackedValueOrDefault(u"Frame"_ustr            , css::uno::Reference< css::frame::XFrame >());
84             m_xPluggWindow       = lArgs.getUnpackedValueOrDefault(u"Window"_ustr           , css::uno::Reference< css::awt::XWindow >() );
85             m_bAllowParentShow   = lArgs.getUnpackedValueOrDefault(u"AllowParentShow"_ustr  , false );
86             m_bDisableReschedule = lArgs.getUnpackedValueOrDefault(u"DisableReschedule"_ustr, false );
87        }
88     }
89 
90 #ifdef EMSCRIPTEN
91     m_bDisableReschedule = true;
92 #endif
93     impl_createProgress();
94 }
95 
createStatusIndicator()96 css::uno::Reference< css::task::XStatusIndicator > SAL_CALL StatusIndicatorFactory::createStatusIndicator()
97 {
98     return new StatusIndicator(this);
99 }
100 
update()101 void SAL_CALL StatusIndicatorFactory::update()
102 {
103     std::scoped_lock g(m_mutex);
104     m_bAllowReschedule = true;
105 }
106 
start(const css::uno::Reference<css::task::XStatusIndicator> & xChild,const OUString & sText,sal_Int32 nRange)107 void StatusIndicatorFactory::start(const css::uno::Reference< css::task::XStatusIndicator >& xChild,
108                                    const OUString&                                    sText ,
109                                          sal_Int32                                           nRange)
110 {
111     css::uno::Reference< css::task::XStatusIndicator > xProgress;
112     // SAFE -> ----------------------------------
113     {
114         std::scoped_lock aWriteLock(m_mutex);
115 
116         // create new info structure for this child or move it to the front of our stack
117         IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild);
118         if (pItem != m_aStack.end())
119             m_aStack.erase(pItem);
120         IndicatorInfo aInfo(xChild, sText);
121         m_aStack.push_back (aInfo                );
122 
123         m_xActiveChild = xChild;
124         xProgress = m_xProgress;
125     }
126     // <- SAFE ----------------------------------
127 
128     implts_makeParentVisibleIfAllowed();
129 
130     if (xProgress.is())
131         xProgress->start(sText, nRange);
132 
133     impl_startWakeUpThread();
134     impl_reschedule(true);
135 }
136 
reset(const css::uno::Reference<css::task::XStatusIndicator> & xChild)137 void StatusIndicatorFactory::reset(const css::uno::Reference< css::task::XStatusIndicator >& xChild)
138 {
139     css::uno::Reference< css::task::XStatusIndicator > xActive;
140     css::uno::Reference< css::task::XStatusIndicator > xProgress;
141     // SAFE -> ----------------------------------
142     {
143         std::scoped_lock aReadLock(m_mutex);
144 
145         // reset the internal info structure related to this child
146         IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild);
147         if (pItem != m_aStack.end())
148         {
149             pItem->m_nValue = 0;
150             pItem->m_sText.clear();
151         }
152 
153         xActive   = m_xActiveChild;
154         xProgress = m_xProgress;
155     }
156     // <- SAFE ----------------------------------
157 
158     // not the top most child => don't change UI
159     // But don't forget Reschedule!
160     if (
161         (xChild == xActive) &&
162         (xProgress.is()   )
163        )
164         xProgress->reset();
165 
166     impl_reschedule(true);
167 }
168 
end(const css::uno::Reference<css::task::XStatusIndicator> & xChild)169 void StatusIndicatorFactory::end(const css::uno::Reference< css::task::XStatusIndicator >& xChild)
170 {
171     css::uno::Reference< css::task::XStatusIndicator > xActive;
172     css::uno::Reference< css::task::XStatusIndicator > xProgress;
173     OUString sText;
174     sal_Int32 nValue = 0;
175     // SAFE -> ----------------------------------
176     {
177         std::scoped_lock aWriteLock(m_mutex);
178 
179         // remove this child from our stack
180         IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild);
181         if (pItem != m_aStack.end())
182             m_aStack.erase(pItem);
183 
184         // activate next child ... or finish the progress if there is no further one.
185         m_xActiveChild.clear();
186         IndicatorStack::reverse_iterator pNext  = m_aStack.rbegin();
187         if (pNext != m_aStack.rend())
188         {
189             m_xActiveChild = pNext->m_xIndicator;
190             sText          = pNext->m_sText;
191             nValue         = pNext->m_nValue;
192         }
193 
194         xActive   = m_xActiveChild;
195         xProgress = m_xProgress;
196     }
197     // <- SAFE ----------------------------------
198 
199     if (xActive.is())
200     {
201         // There is at least one further child indicator.
202         // Actualize our progress, so it shows these values from now.
203         if (xProgress.is())
204         {
205             xProgress->setText (sText );
206             xProgress->setValue(nValue);
207         }
208     }
209     else
210     {
211         // Our stack is empty. No further child exists.
212         // Se we must "end" our progress really
213         if (xProgress.is())
214             xProgress->end();
215         // Now hide the progress bar again.
216         impl_hideProgress();
217 
218         impl_stopWakeUpThread();
219     }
220 
221     impl_reschedule(true);
222 }
223 
setText(const css::uno::Reference<css::task::XStatusIndicator> & xChild,const OUString & sText)224 void StatusIndicatorFactory::setText(const css::uno::Reference< css::task::XStatusIndicator >& xChild,
225                                      const OUString&                                    sText )
226 {
227     css::uno::Reference< css::task::XStatusIndicator > xActive;
228     css::uno::Reference< css::task::XStatusIndicator > xProgress;
229     // SAFE -> ----------------------------------
230     {
231         std::scoped_lock aWriteLock(m_mutex);
232 
233         IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild);
234         if (pItem != m_aStack.end())
235             pItem->m_sText = sText;
236 
237         xActive   = m_xActiveChild;
238         xProgress = m_xProgress;
239     }
240     // SAFE -> ----------------------------------
241 
242     // paint only the top most indicator
243     // but don't forget to Reschedule!
244     if (
245         (xChild == xActive) &&
246         (xProgress.is()   )
247        )
248     {
249         xProgress->setText(sText);
250     }
251 
252     impl_reschedule(true);
253 }
254 
setValue(const css::uno::Reference<css::task::XStatusIndicator> & xChild,sal_Int32 nValue)255 void StatusIndicatorFactory::setValue( const css::uno::Reference< css::task::XStatusIndicator >& xChild ,
256                                              sal_Int32                                           nValue )
257 {
258     sal_Int32 nOldValue = 0;
259     css::uno::Reference< css::task::XStatusIndicator > xActive;
260     css::uno::Reference< css::task::XStatusIndicator > xProgress;
261     // SAFE -> ----------------------------------
262     {
263         std::scoped_lock aWriteLock(m_mutex);
264 
265         IndicatorStack::iterator pItem = ::std::find(m_aStack.begin(), m_aStack.end(), xChild);
266         if (pItem != m_aStack.end())
267         {
268             nOldValue       = pItem->m_nValue;
269             pItem->m_nValue = nValue;
270         }
271 
272         xActive    = m_xActiveChild;
273         xProgress  = m_xProgress;
274     }
275     // SAFE -> ----------------------------------
276 
277     if (
278         (xChild    == xActive) &&
279         (nOldValue != nValue ) &&
280         (xProgress.is()      )
281        )
282     {
283         xProgress->setValue(nValue);
284     }
285 
286     impl_reschedule(false);
287 }
288 
implts_makeParentVisibleIfAllowed()289 void StatusIndicatorFactory::implts_makeParentVisibleIfAllowed()
290 {
291     css::uno::Reference< css::frame::XFrame > xFrame;
292     css::uno::Reference< css::awt::XWindow >  xPluggWindow;
293     css::uno::Reference< css::uno::XComponentContext > xContext;
294     // SAFE -> ----------------------------------
295     {
296         std::scoped_lock aReadLock(m_mutex);
297 
298         if (!m_bAllowParentShow)
299             return;
300 
301         xFrame = m_xFrame;
302         xPluggWindow = m_xPluggWindow;
303         xContext = m_xContext;
304     }
305     // <- SAFE ----------------------------------
306 
307     css::uno::Reference< css::awt::XWindow > xParentWindow;
308     if (xFrame.is())
309         xParentWindow = xFrame->getContainerWindow();
310     else
311         xParentWindow = xPluggWindow;
312 
313     // don't disturb user in case he put the loading document into the background!
314     // Suppress any setVisible() or toFront() call in case the initial show was
315     // already made.
316     css::uno::Reference< css::awt::XWindow2 > xVisibleCheck(xParentWindow, css::uno::UNO_QUERY);
317     bool bIsVisible = false;
318     if (xVisibleCheck.is())
319         bIsVisible = xVisibleCheck->isVisible();
320 
321     if (bIsVisible)
322     {
323         impl_showProgress();
324         return;
325     }
326 
327     // Check if the layout manager has been set to invisible state. It this case we are also
328     // not allowed to set the frame visible!
329     css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
330     if (xPropSet.is())
331     {
332         css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
333         xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
334         if (xLayoutManager.is())
335         {
336             if ( !xLayoutManager->isVisible() )
337                 return;
338         }
339     }
340 
341     // Ok the window should be made visible... because it is not currently visible.
342     // BUT..!
343     // We need a Hack for our applications: They get her progress from the frame directly
344     // on saving documents. Because there is no progress set on the MediaDescriptor.
345     // But that's wrong. In case the document was opened hidden, they should not use any progress .-(
346     // They only possible workaround: don't show the parent window here, if the document was opened hidden.
347     bool bHiddenDoc = false;
348     if (xFrame.is())
349     {
350         css::uno::Reference< css::frame::XController > xController;
351         css::uno::Reference< css::frame::XModel >      xModel;
352         xController = xFrame->getController();
353         if (xController.is())
354             xModel = xController->getModel();
355         if (xModel.is())
356         {
357             utl::MediaDescriptor lDocArgs(xModel->getArgs());
358             bHiddenDoc = lDocArgs.getUnpackedValueOrDefault(
359                 utl::MediaDescriptor::PROP_HIDDEN,
360                 false);
361         }
362     }
363 
364     if (bHiddenDoc)
365         return;
366 
367     // OK: The document was not opened in hidden mode ...
368     // and the window isn't already visible.
369     // Show it and bring it to front.
370     // But before we have to be sure, that our internal used helper progress
371     // is visible too.
372     impl_showProgress();
373 
374     SolarMutexGuard aSolarGuard;
375     VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xParentWindow);
376     if ( pWindow )
377     {
378         bool bForceFrontAndFocus(officecfg::Office::Common::View::NewDocumentHandling::ForceFocusAndToFront::get());
379         pWindow->Show(true, bForceFrontAndFocus ? ShowFlags::ForegroundTask : ShowFlags::NONE );
380     }
381 
382 }
383 
impl_createProgress()384 void StatusIndicatorFactory::impl_createProgress()
385 {
386     css::uno::Reference< css::frame::XFrame > xFrame;
387     css::uno::Reference< css::awt::XWindow > xWindow;
388     // SAFE -> ----------------------------------
389     {
390         std::scoped_lock aReadLock(m_mutex);
391 
392         xFrame = m_xFrame;
393         xWindow = m_xPluggWindow;
394     }
395     // <- SAFE ----------------------------------
396 
397     css::uno::Reference< css::task::XStatusIndicator > xProgress;
398 
399     if (xWindow.is())
400     {
401         // use vcl based progress implementation in plugged mode
402         xProgress = new VCLStatusIndicator(xWindow);
403     }
404     else if (xFrame.is())
405     {
406         // use frame layouted progress implementation
407         css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
408         if (xPropSet.is())
409         {
410             css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
411             xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
412             if (xLayoutManager.is())
413             {
414                 xLayoutManager->lock();
415                 OUString sPROGRESS_RESOURCE(PROGRESS_RESOURCE);
416                 xLayoutManager->createElement( sPROGRESS_RESOURCE );
417                 xLayoutManager->hideElement( sPROGRESS_RESOURCE );
418 
419                 css::uno::Reference< css::ui::XUIElement > xProgressBar = xLayoutManager->getElement(sPROGRESS_RESOURCE);
420                 if (xProgressBar.is())
421                     xProgress.set(xProgressBar->getRealInterface(), css::uno::UNO_QUERY);
422                 xLayoutManager->unlock();
423             }
424         }
425     }
426 
427     std::scoped_lock g(m_mutex);
428     m_xProgress = xProgress;
429 }
430 
impl_showProgress()431 void StatusIndicatorFactory::impl_showProgress()
432 {
433     css::uno::Reference< css::frame::XFrame > xFrame;
434     // SAFE -> ----------------------------------
435     {
436         std::scoped_lock aReadLock(m_mutex);
437 
438         xFrame = m_xFrame;
439     }
440     // <- SAFE ----------------------------------
441 
442     css::uno::Reference< css::task::XStatusIndicator > xProgress;
443 
444     if (!xFrame.is())
445         return;
446 
447     // use frame layouted progress implementation
448     css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
449     if (xPropSet.is())
450     {
451         css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
452         xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
453         if (xLayoutManager.is())
454         {
455             // Be sure that we have always a progress. It can be that our frame
456             // was recycled and therefore the progress was destroyed!
457             // CreateElement does nothing if there is already a valid progress.
458             OUString sPROGRESS_RESOURCE(PROGRESS_RESOURCE);
459             xLayoutManager->createElement( sPROGRESS_RESOURCE );
460             xLayoutManager->showElement( sPROGRESS_RESOURCE );
461 
462             css::uno::Reference< css::ui::XUIElement > xProgressBar = xLayoutManager->getElement(sPROGRESS_RESOURCE);
463             if (xProgressBar.is())
464                 xProgress.set(xProgressBar->getRealInterface(), css::uno::UNO_QUERY);
465         }
466     }
467 
468     std::scoped_lock g(m_mutex);
469     m_xProgress = xProgress;
470 }
471 
impl_hideProgress()472 void StatusIndicatorFactory::impl_hideProgress()
473 {
474     css::uno::Reference< css::frame::XFrame > xFrame;
475     // SAFE -> ----------------------------------
476     {
477         std::scoped_lock aReadLock(m_mutex);
478 
479         xFrame = m_xFrame;
480     }
481     // <- SAFE ----------------------------------
482 
483     if (xFrame.is())
484     {
485         // use frame layouted progress implementation
486         css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
487         if (xPropSet.is())
488         {
489             css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
490             xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
491             if (xLayoutManager.is())
492                 xLayoutManager->hideElement( PROGRESS_RESOURCE );
493         }
494     }
495 }
496 
impl_reschedule(bool bForce)497 void StatusIndicatorFactory::impl_reschedule(bool bForce)
498 {
499     // SAFE ->
500     {
501         std::scoped_lock aReadLock(m_mutex);
502         if (m_bDisableReschedule)
503             return;
504     }
505     // <- SAFE
506 
507     bool bReschedule = bForce;
508     if (!bReschedule)
509     {
510         std::scoped_lock g(m_mutex);
511         bReschedule        = m_bAllowReschedule;
512         m_bAllowReschedule = false;
513     }
514 
515     if (!bReschedule)
516         return;
517 
518     static std::mutex rescheduleLock;
519     // SAFE ->
520     std::unique_lock aRescheduleGuard(rescheduleLock);
521 
522     if (m_nInReschedule != 0)
523         return;
524 
525     // coverity[missing_lock: FALSE] - coverity fails to see the aRescheduleGuard ctor as taking a lock
526     ++m_nInReschedule;
527     aRescheduleGuard.unlock();
528     // <- SAFE
529 
530     {
531         SolarMutexGuard g;
532         Application::Reschedule(true);
533     }
534 
535     // SAFE ->
536     aRescheduleGuard.lock();
537     --m_nInReschedule;
538 }
539 
impl_startWakeUpThread()540 void StatusIndicatorFactory::impl_startWakeUpThread()
541 {
542     std::scoped_lock g(m_mutex);
543 
544     if (m_bDisableReschedule)
545         return;
546 
547     if (!m_pWakeUp)
548         m_pWakeUp.reset(new WakeUpThread(this));
549 }
550 
impl_stopWakeUpThread()551 void StatusIndicatorFactory::impl_stopWakeUpThread()
552 {
553     std::unique_ptr<WakeUpThread> wakeUp;
554     {
555         std::scoped_lock g(m_mutex);
556         std::swap(wakeUp, m_pWakeUp);
557     }
558     if (wakeUp)
559         wakeUp->stop();
560 }
561 
562 } // namespace framework
563 
564 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_comp_framework_StatusIndicatorFactory_get_implementation(css::uno::XComponentContext * context,css::uno::Sequence<css::uno::Any> const &)565 com_sun_star_comp_framework_StatusIndicatorFactory_get_implementation(
566     css::uno::XComponentContext *context,
567     css::uno::Sequence<css::uno::Any> const &)
568 {
569     return cppu::acquire(new framework::StatusIndicatorFactory(context));
570 }
571 
572 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
573