xref: /core/framework/source/helper/statusindicatorfactory.cxx (revision 7bbe74b2be45afff4418b29deca6448795d86715)
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 
joinThreads()289 bool StatusIndicatorFactory::joinThreads()
290 {
291     WakeUpThread::joinThread();
292     return true;
293 }
294 
startThreads()295 void StatusIndicatorFactory::startThreads()
296 {
297     WakeUpThread::startThread();
298 }
299 
implts_makeParentVisibleIfAllowed()300 void StatusIndicatorFactory::implts_makeParentVisibleIfAllowed()
301 {
302     css::uno::Reference< css::frame::XFrame > xFrame;
303     css::uno::Reference< css::awt::XWindow >  xPluggWindow;
304     css::uno::Reference< css::uno::XComponentContext > xContext;
305     // SAFE -> ----------------------------------
306     {
307         std::scoped_lock aReadLock(m_mutex);
308 
309         if (!m_bAllowParentShow)
310             return;
311 
312         xFrame = m_xFrame;
313         xPluggWindow = m_xPluggWindow;
314         xContext = m_xContext;
315     }
316     // <- SAFE ----------------------------------
317 
318     css::uno::Reference< css::awt::XWindow > xParentWindow;
319     if (xFrame.is())
320         xParentWindow = xFrame->getContainerWindow();
321     else
322         xParentWindow = std::move(xPluggWindow);
323 
324     // don't disturb user in case he put the loading document into the background!
325     // Suppress any setVisible() or toFront() call in case the initial show was
326     // already made.
327     css::uno::Reference< css::awt::XWindow2 > xVisibleCheck(xParentWindow, css::uno::UNO_QUERY);
328     bool bIsVisible = false;
329     if (xVisibleCheck.is())
330         bIsVisible = xVisibleCheck->isVisible();
331 
332     if (bIsVisible)
333     {
334         impl_showProgress();
335         return;
336     }
337 
338     // Check if the layout manager has been set to invisible state. It this case we are also
339     // not allowed to set the frame visible!
340     css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
341     if (xPropSet.is())
342     {
343         css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
344         xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
345         if (xLayoutManager.is())
346         {
347             if ( !xLayoutManager->isVisible() )
348                 return;
349         }
350     }
351 
352     // Ok the window should be made visible... because it is not currently visible.
353     // BUT..!
354     // We need a Hack for our applications: They get her progress from the frame directly
355     // on saving documents. Because there is no progress set on the MediaDescriptor.
356     // But that's wrong. In case the document was opened hidden, they should not use any progress .-(
357     // They only possible workaround: don't show the parent window here, if the document was opened hidden.
358     bool bHiddenDoc = false;
359     if (xFrame.is())
360     {
361         css::uno::Reference< css::frame::XController > xController;
362         css::uno::Reference< css::frame::XModel >      xModel;
363         xController = xFrame->getController();
364         if (xController.is())
365             xModel = xController->getModel();
366         if (xModel.is())
367         {
368             utl::MediaDescriptor lDocArgs(xModel->getArgs());
369             bHiddenDoc = lDocArgs.getUnpackedValueOrDefault(
370                 utl::MediaDescriptor::PROP_HIDDEN,
371                 false);
372         }
373     }
374 
375     if (bHiddenDoc)
376         return;
377 
378     // OK: The document was not opened in hidden mode ...
379     // and the window isn't already visible.
380     // Show it and bring it to front.
381     // But before we have to be sure, that our internal used helper progress
382     // is visible too.
383     impl_showProgress();
384 
385     SolarMutexGuard aSolarGuard;
386     VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xParentWindow);
387     if ( pWindow )
388     {
389         bool bForceFrontAndFocus(officecfg::Office::Common::View::NewDocumentHandling::ForceFocusAndToFront::get());
390         pWindow->Show(true, bForceFrontAndFocus ? ShowFlags::ForegroundTask : ShowFlags::NONE );
391     }
392 
393 }
394 
impl_createProgress()395 void StatusIndicatorFactory::impl_createProgress()
396 {
397     css::uno::Reference< css::frame::XFrame > xFrame;
398     css::uno::Reference< css::awt::XWindow > xWindow;
399     // SAFE -> ----------------------------------
400     {
401         std::scoped_lock aReadLock(m_mutex);
402 
403         xFrame = m_xFrame;
404         xWindow = m_xPluggWindow;
405     }
406     // <- SAFE ----------------------------------
407 
408     css::uno::Reference< css::task::XStatusIndicator > xProgress;
409 
410     if (xWindow.is())
411     {
412         // use vcl based progress implementation in plugged mode
413         xProgress = new VCLStatusIndicator(xWindow);
414     }
415     else if (xFrame.is())
416     {
417         // use frame layouted progress implementation
418         css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
419         if (xPropSet.is())
420         {
421             css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
422             xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
423             if (xLayoutManager.is())
424             {
425                 xLayoutManager->lock();
426                 OUString sPROGRESS_RESOURCE(PROGRESS_RESOURCE);
427                 xLayoutManager->createElement( sPROGRESS_RESOURCE );
428                 xLayoutManager->hideElement( sPROGRESS_RESOURCE );
429 
430                 css::uno::Reference< css::ui::XUIElement > xProgressBar = xLayoutManager->getElement(sPROGRESS_RESOURCE);
431                 if (xProgressBar.is())
432                     xProgress.set(xProgressBar->getRealInterface(), css::uno::UNO_QUERY);
433                 xLayoutManager->unlock();
434             }
435         }
436     }
437 
438     std::scoped_lock g(m_mutex);
439     m_xProgress = std::move(xProgress);
440 }
441 
impl_showProgress()442 void StatusIndicatorFactory::impl_showProgress()
443 {
444     css::uno::Reference< css::frame::XFrame > xFrame;
445     // SAFE -> ----------------------------------
446     {
447         std::scoped_lock aReadLock(m_mutex);
448 
449         xFrame = m_xFrame;
450     }
451     // <- SAFE ----------------------------------
452 
453     css::uno::Reference< css::task::XStatusIndicator > xProgress;
454 
455     if (!xFrame.is())
456         return;
457 
458     // use frame layouted progress implementation
459     css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
460     if (xPropSet.is())
461     {
462         css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
463         xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
464         if (xLayoutManager.is())
465         {
466             // Be sure that we have always a progress. It can be that our frame
467             // was recycled and therefore the progress was destroyed!
468             // CreateElement does nothing if there is already a valid progress.
469             OUString sPROGRESS_RESOURCE(PROGRESS_RESOURCE);
470             xLayoutManager->createElement( sPROGRESS_RESOURCE );
471             xLayoutManager->showElement( sPROGRESS_RESOURCE );
472 
473             css::uno::Reference< css::ui::XUIElement > xProgressBar = xLayoutManager->getElement(sPROGRESS_RESOURCE);
474             if (xProgressBar.is())
475                 xProgress.set(xProgressBar->getRealInterface(), css::uno::UNO_QUERY);
476         }
477     }
478 
479     std::scoped_lock g(m_mutex);
480     m_xProgress = std::move(xProgress);
481 }
482 
impl_hideProgress()483 void StatusIndicatorFactory::impl_hideProgress()
484 {
485     css::uno::Reference< css::frame::XFrame > xFrame;
486     // SAFE -> ----------------------------------
487     {
488         std::scoped_lock aReadLock(m_mutex);
489 
490         xFrame = m_xFrame;
491     }
492     // <- SAFE ----------------------------------
493 
494     if (xFrame.is())
495     {
496         // use frame layouted progress implementation
497         css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY);
498         if (xPropSet.is())
499         {
500             css::uno::Reference< css::frame::XLayoutManager2 > xLayoutManager;
501             xPropSet->getPropertyValue(FRAME_PROPNAME_ASCII_LAYOUTMANAGER) >>= xLayoutManager;
502             if (xLayoutManager.is())
503                 xLayoutManager->hideElement( PROGRESS_RESOURCE );
504         }
505     }
506 }
507 
impl_reschedule(bool bForce)508 void StatusIndicatorFactory::impl_reschedule(bool bForce)
509 {
510     // SAFE ->
511     {
512         std::scoped_lock aReadLock(m_mutex);
513         if (m_bDisableReschedule)
514             return;
515     }
516     // <- SAFE
517 
518     bool bReschedule = bForce;
519     if (!bReschedule)
520     {
521         std::scoped_lock g(m_mutex);
522         bReschedule        = m_bAllowReschedule;
523         m_bAllowReschedule = false;
524     }
525 
526     if (!bReschedule)
527         return;
528 
529     static std::mutex rescheduleLock;
530     // SAFE ->
531     std::unique_lock aRescheduleGuard(rescheduleLock);
532 
533     if (m_nInReschedule != 0)
534         return;
535 
536     // coverity[missing_lock: FALSE] - coverity fails to see the aRescheduleGuard ctor as taking a lock
537     ++m_nInReschedule;
538     aRescheduleGuard.unlock();
539     // <- SAFE
540 
541     {
542         SolarMutexGuard g;
543         Application::Reschedule(true);
544     }
545 
546     // SAFE ->
547     aRescheduleGuard.lock();
548     --m_nInReschedule;
549 }
550 
impl_startWakeUpThread()551 void StatusIndicatorFactory::impl_startWakeUpThread()
552 {
553     std::scoped_lock g(m_mutex);
554 
555     if (m_bDisableReschedule)
556         return;
557 
558     if (!m_pWakeUp)
559         m_pWakeUp.reset(new WakeUpThread(this));
560 }
561 
impl_stopWakeUpThread()562 void StatusIndicatorFactory::impl_stopWakeUpThread()
563 {
564     std::unique_ptr<WakeUpThread> wakeUp;
565     {
566         std::scoped_lock g(m_mutex);
567         std::swap(wakeUp, m_pWakeUp);
568     }
569     if (wakeUp)
570         wakeUp->stop();
571 }
572 
573 } // namespace framework
574 
575 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 &)576 com_sun_star_comp_framework_StatusIndicatorFactory_get_implementation(
577     css::uno::XComponentContext *context,
578     css::uno::Sequence<css::uno::Any> const &)
579 {
580     return cppu::acquire(new framework::StatusIndicatorFactory(context));
581 }
582 
583 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
584