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 
10 #include <sfx2/bindings.hxx>
11 #include <sfx2/viewsh.hxx>
12 #include <sfx2/dispatch.hxx>
13 #include <sfx2/notebookbar/SfxNotebookBar.hxx>
14 #include <vcl/notebookbar.hxx>
15 #include <vcl/syswin.hxx>
16 #include <sfx2/viewfrm.hxx>
17 #include <sfx2/sfxsids.hrc>
18 #include <comphelper/processfactory.hxx>
19 #include <comphelper/lok.hxx>
20 #include <com/sun/star/frame/UnknownModuleException.hpp>
21 #include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
22 #include <com/sun/star/ui/XContextChangeEventMultiplexer.hpp>
23 #include <com/sun/star/frame/XLayoutManager.hpp>
24 #include <officecfg/Office/UI/ToolbarMode.hxx>
25 #include <com/sun/star/frame/XModuleManager.hpp>
26 #include <com/sun/star/frame/ModuleManager.hpp>
27 #include <com/sun/star/beans/XPropertySet.hpp>
28 #include <unotools/confignode.hxx>
29 #include <comphelper/types.hxx>
30 #include <framework/addonsoptions.hxx>
31 #include <vcl/NotebookBarAddonsMerger.hxx>
32 #include <vector>
33 #include <map>
34 #include <vcl/WeldedTabbedNotebookbar.hxx>
35 
36 using namespace sfx2;
37 using namespace css::uno;
38 using namespace css::ui;
39 using namespace css;
40 
41 #define MENUBAR_STR "private:resource/menubar/menubar"
42 
43 const char MERGE_NOTEBOOKBAR_URL[] = "URL";
44 
45 bool SfxNotebookBar::m_bLock = false;
46 bool SfxNotebookBar::m_bHide = false;
47 std::map<const SfxViewShell*, std::shared_ptr<WeldedTabbedNotebookbar>> SfxNotebookBar::m_pNotebookBarWeldedWrapper;
48 
49 static void NotebookbarAddonValues(
50     std::vector<Image>& aImageValues,
51     std::vector<css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>>>&
52         aExtensionValues)
53 {
54     framework::AddonsOptions aAddonsItems;
55 
56     for (int nIdx = 0; nIdx < aAddonsItems.GetAddonsNotebookBarCount(); nIdx++)
57     {
58         const css::uno::Sequence<css::uno::Sequence<css::beans::PropertyValue>> aExtension
59             = aAddonsItems.GetAddonsNotebookBarPart(nIdx);
60         for (const css::uno::Sequence<css::beans::PropertyValue>& rExtensionVal : aExtension)
61         {
62             Image aImage;
63             bool isBigImage = true;
64             for (const auto& rProp : rExtensionVal)
65             {
66                 if (rProp.Name == MERGE_NOTEBOOKBAR_URL)
67                 {
68                     OUString sImage;
69                     rProp.Value >>= sImage;
70                     aImage = Image(framework::AddonsOptions().GetImageFromURL(sImage, isBigImage));
71                 }
72             }
73             aImageValues.push_back(aImage);
74         }
75         aExtensionValues.push_back(aExtension);
76     }
77 }
78 
79 static Reference<frame::XLayoutManager> lcl_getLayoutManager( const Reference<frame::XFrame>& xFrame )
80 {
81     css::uno::Reference<css::frame::XLayoutManager> xLayoutManager;
82 
83     if (xFrame.is())
84     {
85         Reference<css::beans::XPropertySet> xPropSet(xFrame, UNO_QUERY);
86 
87         if (xPropSet.is())
88         {
89             Any aValue = xPropSet->getPropertyValue("LayoutManager");
90             aValue >>= xLayoutManager;
91         }
92     }
93 
94     return xLayoutManager;
95 }
96 
97 static OUString lcl_getAppName( vcl::EnumContext::Application eApp )
98 {
99     switch ( eApp )
100     {
101         case vcl::EnumContext::Application::Writer:
102             return "Writer";
103             break;
104         case vcl::EnumContext::Application::Calc:
105             return "Calc";
106             break;
107         case vcl::EnumContext::Application::Impress:
108             return "Impress";
109             break;
110         case vcl::EnumContext::Application::Draw:
111             return "Draw";
112             break;
113         case vcl::EnumContext::Application::Formula:
114             return "Formula";
115             break;
116         default:
117             return OUString();
118             break;
119     }
120 }
121 
122 static void lcl_setNotebookbarFileName( vcl::EnumContext::Application eApp, const OUString& sFileName )
123 {
124     std::shared_ptr<comphelper::ConfigurationChanges> aBatch(
125                 comphelper::ConfigurationChanges::create( ::comphelper::getProcessComponentContext() ) );
126     switch ( eApp )
127     {
128         case vcl::EnumContext::Application::Writer:
129             officecfg::Office::UI::ToolbarMode::ActiveWriter::set( sFileName, aBatch );
130             break;
131         case vcl::EnumContext::Application::Calc:
132             officecfg::Office::UI::ToolbarMode::ActiveCalc::set( sFileName, aBatch );
133             break;
134         case vcl::EnumContext::Application::Impress:
135             officecfg::Office::UI::ToolbarMode::ActiveImpress::set( sFileName, aBatch );
136             break;
137         case vcl::EnumContext::Application::Draw:
138             officecfg::Office::UI::ToolbarMode::ActiveDraw::set( sFileName, aBatch );
139             break;
140         default:
141             break;
142     }
143     aBatch->commit();
144 }
145 
146 static OUString lcl_getNotebookbarFileName( vcl::EnumContext::Application eApp )
147 {
148     switch ( eApp )
149     {
150         case vcl::EnumContext::Application::Writer:
151             return officecfg::Office::UI::ToolbarMode::ActiveWriter::get();
152             break;
153         case vcl::EnumContext::Application::Calc:
154             return officecfg::Office::UI::ToolbarMode::ActiveCalc::get();
155             break;
156         case vcl::EnumContext::Application::Impress:
157             return officecfg::Office::UI::ToolbarMode::ActiveImpress::get();
158             break;
159         case vcl::EnumContext::Application::Draw:
160             return officecfg::Office::UI::ToolbarMode::ActiveDraw::get();
161             break;
162 
163         default:
164             break;
165     }
166     return OUString();
167 }
168 
169 static utl::OConfigurationTreeRoot lcl_getCurrentImplConfigRoot()
170 {
171     return utl::OConfigurationTreeRoot(::comphelper::getProcessComponentContext(),
172                                        "org.openoffice.Office.UI.ToolbarMode/",
173                                        true);
174 }
175 
176 static utl::OConfigurationNode lcl_getCurrentImplConfigNode(const Reference<css::frame::XFrame>& xFrame,
177                                                                   utl::OConfigurationTreeRoot const & rNotebookbarNode )
178 {
179     if (!rNotebookbarNode.isValid())
180         return utl::OConfigurationNode();
181 
182     const Reference<frame::XModuleManager> xModuleManager  = frame::ModuleManager::create( ::comphelper::getProcessComponentContext() );
183 
184     vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum( xModuleManager->identify( xFrame ) );
185     OUString aActive = lcl_getNotebookbarFileName( eApp );
186 
187     const utl::OConfigurationNode aImplsNode = rNotebookbarNode.openNode("Applications/" + lcl_getAppName( eApp) + "/Modes");
188     const Sequence<OUString> aModeNodeNames( aImplsNode.getNodeNames() );
189 
190     for ( const auto& rModeNodeName : aModeNodeNames )
191     {
192         const utl::OConfigurationNode aImplNode( aImplsNode.openNode( rModeNodeName ) );
193         if ( !aImplNode.isValid() )
194             continue;
195 
196         OUString aCommandArg = comphelper::getString( aImplNode.getNodeValue( "CommandArg" ) );
197 
198         if ( aCommandArg == aActive )
199         {
200             return aImplNode;
201         }
202     }
203 
204     return utl::OConfigurationNode();
205 }
206 
207 void SfxNotebookBar::CloseMethod(SfxBindings& rBindings)
208 {
209     SfxFrame& rFrame = rBindings.GetDispatcher_Impl()->GetFrame()->GetFrame();
210     CloseMethod(rFrame.GetSystemWindow());
211 }
212 
213 void SfxNotebookBar::CloseMethod(SystemWindow* pSysWindow)
214 {
215     if (pSysWindow)
216     {
217         RemoveListeners(pSysWindow);
218         if(pSysWindow->GetNotebookBar())
219             pSysWindow->CloseNotebookBar();
220         if (SfxViewFrame::Current())
221             SfxNotebookBar::ShowMenubar(SfxViewFrame::Current(), true);
222     }
223 }
224 
225 void SfxNotebookBar::LockNotebookBar()
226 {
227     m_bHide = true;
228     m_bLock = true;
229 }
230 
231 void SfxNotebookBar::UnlockNotebookBar()
232 {
233     m_bHide = false;
234     m_bLock = false;
235 }
236 
237 bool SfxNotebookBar::IsActive()
238 {
239     if (m_bHide)
240         return false;
241 
242     vcl::EnumContext::Application eApp = vcl::EnumContext::Application::Any;
243 
244     if (SfxViewFrame::Current())
245     {
246         const Reference<frame::XFrame>& xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
247         if (!xFrame.is())
248             return false;
249 
250         const Reference<frame::XModuleManager> xModuleManager  = frame::ModuleManager::create( ::comphelper::getProcessComponentContext() );
251         try
252         {
253             eApp = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(xFrame));
254         }
255         catch (css::frame::UnknownModuleException& e)
256         {
257             SAL_WARN("sfx.appl", "SfxNotebookBar::IsActive(): " + e.Message);
258             return false;
259         }
260     }
261     else
262         return false;
263 
264     OUString appName(lcl_getAppName( eApp ));
265 
266     if (appName.isEmpty())
267         return false;
268 
269 
270     OUString aPath = "org.openoffice.Office.UI.ToolbarMode/Applications/" + appName;
271 
272     const utl::OConfigurationTreeRoot aAppNode(
273                                         ::comphelper::getProcessComponentContext(),
274                                         aPath,
275                                         false);
276     if ( !aAppNode.isValid() )
277         return false;
278 
279     OUString aActive = comphelper::getString( aAppNode.getNodeValue( "Active" ) );
280 
281     const utl::OConfigurationNode aModesNode = aAppNode.openNode("Modes");
282     const Sequence<OUString> aModeNodeNames( aModesNode.getNodeNames() );
283 
284     for ( const auto& rModeNodeName : aModeNodeNames )
285     {
286         const utl::OConfigurationNode aModeNode( aModesNode.openNode( rModeNodeName ) );
287         if ( !aModeNode.isValid() )
288             continue;
289 
290         OUString aCommandArg = comphelper::getString( aModeNode.getNodeValue( "CommandArg" ) );
291 
292         if ( aCommandArg == aActive )
293         {
294             return comphelper::getBOOL( aModeNode.getNodeValue( "HasNotebookbar" ) );
295         }
296     }
297     return false;
298 }
299 
300 void SfxNotebookBar::ExecMethod(SfxBindings& rBindings, const OUString& rUIName)
301 {
302     // Save active UI file name
303     if ( !rUIName.isEmpty() && SfxViewFrame::Current() )
304     {
305         const Reference<frame::XFrame>& xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
306         if (xFrame.is())
307         {
308             const Reference<frame::XModuleManager> xModuleManager  = frame::ModuleManager::create( ::comphelper::getProcessComponentContext() );
309             vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum(xModuleManager->identify(xFrame));
310             lcl_setNotebookbarFileName( eApp, rUIName );
311         }
312     }
313 
314     // trigger the StateMethod
315     rBindings.Invalidate(SID_NOTEBOOKBAR);
316     rBindings.Update();
317 }
318 
319 bool SfxNotebookBar::StateMethod(SfxBindings& rBindings, const OUString& rUIFile,
320                                  bool bReloadNotebookbar)
321 {
322     SfxFrame& rFrame = rBindings.GetDispatcher_Impl()->GetFrame()->GetFrame();
323     return StateMethod(rFrame.GetSystemWindow(), rFrame.GetFrameInterface(), rUIFile,
324                        bReloadNotebookbar);
325 }
326 
327 bool SfxNotebookBar::StateMethod(SystemWindow* pSysWindow,
328                                  const Reference<css::frame::XFrame>& xFrame,
329                                  const OUString& rUIFile, bool bReloadNotebookbar)
330 {
331     if (!pSysWindow)
332     {
333         if (SfxViewFrame::Current() && SfxViewFrame::Current()->GetWindow().GetSystemWindow())
334             pSysWindow = SfxViewFrame::Current()->GetWindow().GetSystemWindow();
335         else
336             return false;
337     }
338 
339     if (IsActive())
340     {
341         css::uno::Reference<css::uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
342         const Reference<frame::XModuleManager> xModuleManager  = frame::ModuleManager::create( xContext );
343         OUString aModuleName = xModuleManager->identify( xFrame );
344         vcl::EnumContext::Application eApp = vcl::EnumContext::GetApplicationEnum( aModuleName );
345         OUString sFile = lcl_getNotebookbarFileName( eApp );
346         OUString sNewFile = rUIFile + sFile;
347         OUString sCurrentFile;
348         VclPtr<NotebookBar> pNotebookBar = pSysWindow->GetNotebookBar();
349         if ( pNotebookBar )
350             sCurrentFile = pNotebookBar->GetUIFilePath();
351 
352         bool bChangedFile = sNewFile != sCurrentFile;
353 
354         if ((!sFile.isEmpty() && bChangedFile) || !pNotebookBar || !pNotebookBar->IsVisible()
355             || bReloadNotebookbar || comphelper::LibreOfficeKit::isActive())
356         {
357             const SfxViewShell* pViewShell = SfxViewShell::Current();
358 
359             // Notebookbar was loaded too early what caused:
360             //   * in LOK: Paste Special feature was incorrectly initialized
361             // Skip first request so Notebookbar will be initialized after document was loaded
362             static std::map<const void*, bool> bSkippedFirstInit;
363             if (comphelper::LibreOfficeKit::isActive() && eApp == vcl::EnumContext::Application::Writer
364                 && bSkippedFirstInit.find(pViewShell) == bSkippedFirstInit.end())
365             {
366                 bSkippedFirstInit[pViewShell] = true;
367                 return false;
368             }
369 
370             RemoveListeners(pSysWindow);
371 
372             OUString aBuf = rUIFile + sFile;
373 
374             //Addons For Notebookbar
375             std::vector<Image> aImageValues;
376             std::vector<css::uno::Sequence< css::uno::Sequence< css::beans::PropertyValue > > > aExtensionValues;
377             NotebookBarAddonsItem aNotebookBarAddonsItem;
378             NotebookbarAddonValues(aImageValues , aExtensionValues);
379             aNotebookBarAddonsItem.aAddonValues = aExtensionValues;
380             aNotebookBarAddonsItem.aImageValues = aImageValues;
381 
382             // setup if necessary
383             if (comphelper::LibreOfficeKit::isActive())
384             {
385                 // update the current LOK language and locale for the dialog tunneling
386                 comphelper::LibreOfficeKit::setLanguageTag(pViewShell->GetLOKLanguageTag());
387                 comphelper::LibreOfficeKit::setLocale(pViewShell->GetLOKLocale());
388             }
389 
390             pSysWindow->SetNotebookBar(aBuf, xFrame, aNotebookBarAddonsItem , bReloadNotebookbar);
391             pNotebookBar = pSysWindow->GetNotebookBar();
392             pNotebookBar->Show();
393 
394 
395             bool hasWeldedWrapper = m_pNotebookBarWeldedWrapper.find(pViewShell) != m_pNotebookBarWeldedWrapper.end();
396             if ((!hasWeldedWrapper || bReloadNotebookbar) && pNotebookBar->IsWelded())
397             {
398                 sal_uInt64 nWindowId = reinterpret_cast<sal_uInt64>(pViewShell);
399                 m_pNotebookBarWeldedWrapper.emplace(std::make_pair(pViewShell,
400                         new WeldedTabbedNotebookbar(pNotebookBar->GetMainContainer(),
401                                                     pNotebookBar->GetUIFilePath(),
402                                                     xFrame,
403                                                     nWindowId)));
404                 pNotebookBar->SetDisposeCallback(LINK(nullptr, SfxNotebookBar, VclDisposeHdl), pViewShell);
405             }
406 
407             pNotebookBar->GetParent()->Resize();
408 
409             utl::OConfigurationTreeRoot aRoot(lcl_getCurrentImplConfigRoot());
410             const utl::OConfigurationNode aModeNode(lcl_getCurrentImplConfigNode(xFrame, aRoot));
411             SfxNotebookBar::ShowMenubar( comphelper::getBOOL( aModeNode.getNodeValue( "HasMenubar" ) ) );
412 
413             SfxViewFrame* pView = SfxViewFrame::Current();
414 
415             if(pView)
416             {
417                 pNotebookBar->ControlListenerForCurrentController(true);
418             }
419         }
420 
421         return true;
422     }
423     else if (auto pNotebookBar = pSysWindow->GetNotebookBar())
424     {
425         pNotebookBar->Hide();
426         pNotebookBar->GetParent()->Resize();
427         SfxNotebookBar::ShowMenubar(true);
428     }
429 
430     return false;
431 }
432 
433 void SfxNotebookBar::RemoveListeners(SystemWindow const * pSysWindow)
434 {
435     if (auto pNotebookBar = pSysWindow->GetNotebookBar())
436     {
437         pNotebookBar->StopListeningAllControllers();
438     }
439 }
440 
441 void SfxNotebookBar::ShowMenubar(bool bShow)
442 {
443     if (m_bLock)
444         return;
445 
446     m_bLock = true;
447 
448     Reference<frame::XFrame> xFrame;
449     vcl::EnumContext::Application eCurrentApp = vcl::EnumContext::Application::NONE;
450     uno::Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext();
451     const Reference<frame::XModuleManager> xModuleManager = frame::ModuleManager::create( xContext );
452 
453     if ( SfxViewFrame::Current() )
454     {
455         xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
456         eCurrentApp = vcl::EnumContext::GetApplicationEnum( xModuleManager->identify( xFrame ) );
457     }
458 
459     SfxViewFrame* pViewFrame = SfxViewFrame::GetFirst();
460     while( pViewFrame )
461     {
462         xFrame = pViewFrame->GetFrame().GetFrameInterface();
463         if ( xFrame.is() )
464         {
465             vcl::EnumContext::Application eApp =
466                     vcl::EnumContext::GetApplicationEnum( xModuleManager->identify( xFrame ) );
467 
468             if ( eApp == eCurrentApp )
469             {
470                 const Reference<frame::XLayoutManager>& xLayoutManager =
471                                                         lcl_getLayoutManager( xFrame );
472 
473                 if (xLayoutManager.is())
474                 {
475                     xLayoutManager->lock();
476 
477                     if (xLayoutManager->getElement(MENUBAR_STR).is())
478                     {
479                         if (xLayoutManager->isElementVisible(MENUBAR_STR) && !bShow)
480                             xLayoutManager->hideElement(MENUBAR_STR);
481                         else if(!xLayoutManager->isElementVisible(MENUBAR_STR) && bShow)
482                             xLayoutManager->showElement(MENUBAR_STR);
483                     }
484 
485                     xLayoutManager->unlock();
486                 }
487             }
488         }
489 
490         pViewFrame = SfxViewFrame::GetNext( *pViewFrame );
491     }
492     m_bLock = false;
493 }
494 
495 void SfxNotebookBar::ShowMenubar(SfxViewFrame const * pViewFrame, bool bShow)
496 {
497     if (m_bLock)
498         return;
499 
500     m_bLock = true;
501 
502     Reference<frame::XFrame> xFrame = pViewFrame->GetFrame().GetFrameInterface();
503     if (xFrame.is())
504     {
505         const Reference<frame::XLayoutManager>& xLayoutManager = lcl_getLayoutManager(xFrame);
506         if (xLayoutManager.is())
507         {
508             xLayoutManager->lock();
509 
510             if (xLayoutManager->getElement(MENUBAR_STR).is())
511             {
512                 if (xLayoutManager->isElementVisible(MENUBAR_STR) && !bShow)
513                     xLayoutManager->hideElement(MENUBAR_STR);
514                 else if (!xLayoutManager->isElementVisible(MENUBAR_STR) && bShow)
515                     xLayoutManager->showElement(MENUBAR_STR);
516             }
517 
518             xLayoutManager->unlock();
519         }
520     }
521     m_bLock = false;
522 }
523 
524 void SfxNotebookBar::ToggleMenubar()
525 {
526     if (!SfxViewFrame::Current())
527         return;
528 
529     const Reference<frame::XFrame>& xFrame = SfxViewFrame::Current()->GetFrame().GetFrameInterface();
530     if (!xFrame.is())
531         return;
532 
533     const Reference<frame::XLayoutManager>& xLayoutManager =
534                                             lcl_getLayoutManager(xFrame);
535 
536     bool bShow = true;
537     if (xLayoutManager.is() && xLayoutManager->getElement(MENUBAR_STR).is())
538     {
539         if (xLayoutManager->isElementVisible(MENUBAR_STR))
540         {
541             SfxNotebookBar::ShowMenubar(false);
542             bShow = false;
543         }
544         else
545             SfxNotebookBar::ShowMenubar(true);
546     }
547 
548     // Save menubar settings
549     if (IsActive())
550     {
551         utl::OConfigurationTreeRoot aRoot(lcl_getCurrentImplConfigRoot());
552         utl::OConfigurationNode aModeNode(lcl_getCurrentImplConfigNode(xFrame, aRoot));
553         aModeNode.setNodeValue( "HasMenubar", toAny<bool>( bShow ) );
554         aRoot.commit();
555     }
556 }
557 
558 void SfxNotebookBar::ReloadNotebookBar(const OUString& sUIPath)
559 {
560     if (SfxNotebookBar::IsActive())
561     {
562         SfxViewShell* pViewShell = SfxViewShell::Current();
563         sfx2::SfxNotebookBar::StateMethod(pViewShell->GetViewFrame()->GetBindings(), sUIPath, true);
564     }
565 }
566 
567 IMPL_STATIC_LINK(SfxNotebookBar, VclDisposeHdl, const SfxViewShell*, pViewShell, void)
568 {
569     m_pNotebookBarWeldedWrapper.erase(pViewShell);
570 }
571 
572 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
573