1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 #include "precompiled_sfx2.hxx"
23 
24 #include "ResourceManager.hxx"
25 #include <unotools/confignode.hxx>
26 #include <comphelper/componentcontext.hxx>
27 #include <comphelper/processfactory.hxx>
28 #include <comphelper/namedvaluecollection.hxx>
29 #include <comphelper/types.hxx>
30 #include <comphelper/stlunosequence.hxx>
31 
32 #include <rtl/ustrbuf.hxx>
33 #include <tools/diagnose_ex.h>
34 
35 #include <com/sun/star/frame/XModuleManager.hpp>
36 
37 #include <map>
38 
39 
40 #define A2S(pString) (::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(pString)))
41 
42 using ::rtl::OUString;
43 using namespace css;
44 using namespace cssu;
45 
46 namespace sfx2 { namespace sidebar {
47 
48 #define gsPrivateResourceToolpanelPrefix "private:resource/toolpanel/"
49 
50 
51 
52 class ResourceManager::Deleter
53 {
54 public:
55     void operator() (ResourceManager* pObject)
56     {
57         delete pObject;
58     }
59 };
60 
61 
62 ResourceManager& ResourceManager::Instance (void)
63 {
64     static ResourceManager maInstance;
65     return maInstance;
66 }
67 
68 
69 
70 
71 ResourceManager::ResourceManager (void)
72     : maDecks(),
73       maPanels(),
74       maProcessedApplications()
75 {
76     ReadDeckList();
77     ReadPanelList();
78 }
79 
80 
81 
82 
83 ResourceManager::~ResourceManager (void)
84 {
85     maPanels.clear();
86     maDecks.clear();
87 }
88 
89 
90 
91 
92 const DeckDescriptor* ResourceManager::GetBestMatchingDeck (
93     const Context& rContext,
94     const Reference<frame::XFrame>& rxFrame)
95 {
96     ReadLegacyAddons(rxFrame);
97 
98     for (DeckContainer::const_iterator iDeck(maDecks.begin()), iEnd(maDecks.end());
99          iDeck!=iEnd;
100          ++iDeck)
101     {
102         if (iDeck->maContextList.GetMatch(rContext) != NULL)
103             return &*iDeck;
104     }
105     return NULL;
106 }
107 
108 
109 
110 
111 const DeckDescriptor* ResourceManager::GetDeckDescriptor (
112     const ::rtl::OUString& rsDeckId) const
113 {
114     for (DeckContainer::const_iterator
115              iDeck(maDecks.begin()),
116              iEnd(maDecks.end());
117          iDeck!=iEnd;
118          ++iDeck)
119     {
120         if (iDeck->msId.equals(rsDeckId))
121             return &*iDeck;
122     }
123     return NULL;
124 }
125 
126 
127 
128 
129 const PanelDescriptor* ResourceManager::GetPanelDescriptor (
130     const ::rtl::OUString& rsPanelId) const
131 {
132     for (PanelContainer::const_iterator
133              iPanel(maPanels.begin()),
134              iEnd(maPanels.end());
135          iPanel!=iEnd;
136          ++iPanel)
137     {
138         if (iPanel->msId.equals(rsPanelId))
139             return &*iPanel;
140     }
141     return NULL;
142 }
143 
144 
145 
146 
147 void ResourceManager::SetIsDeckEnabled (
148     const ::rtl::OUString& rsDeckId,
149     const bool bIsEnabled)
150 {
151     for (DeckContainer::iterator
152              iDeck(maDecks.begin()),
153              iEnd(maDecks.end());
154          iDeck!=iEnd;
155          ++iDeck)
156     {
157         if (iDeck->msId.equals(rsDeckId))
158         {
159             iDeck->mbIsEnabled = bIsEnabled;
160             return;
161         }
162     }
163 }
164 
165 
166 
167 
168 const ResourceManager::IdContainer& ResourceManager::GetMatchingDecks (
169     IdContainer& rDeckIds,
170     const Context& rContext,
171     const Reference<frame::XFrame>& rxFrame)
172 {
173     ReadLegacyAddons(rxFrame);
174 
175     ::std::multimap<sal_Int32,OUString> aOrderedIds;
176     for (DeckContainer::const_iterator
177              iDeck(maDecks.begin()),
178              iEnd (maDecks.end());
179          iDeck!=iEnd;
180          ++iDeck)
181     {
182         const DeckDescriptor& rDeckDescriptor (*iDeck);
183         if (rDeckDescriptor.maContextList.GetMatch(rContext) != NULL)
184             aOrderedIds.insert(::std::multimap<sal_Int32,OUString>::value_type(
185                     rDeckDescriptor.mnOrderIndex,
186                     rDeckDescriptor.msId));
187     }
188 
189     for (::std::multimap<sal_Int32,OUString>::const_iterator
190              iId(aOrderedIds.begin()),
191              iEnd(aOrderedIds.end());
192          iId!=iEnd;
193          ++iId)
194     {
195         rDeckIds.push_back(iId->second);
196     }
197 
198     return rDeckIds;
199 }
200 
201 
202 
203 
204 const ResourceManager::PanelContextDescriptorContainer& ResourceManager::GetMatchingPanels (
205     PanelContextDescriptorContainer& rPanelIds,
206     const Context& rContext,
207     const ::rtl::OUString& rsDeckId,
208     const Reference<frame::XFrame>& rxFrame)
209 {
210     ReadLegacyAddons(rxFrame);
211 
212     ::std::multimap<sal_Int32,PanelContextDescriptor> aOrderedIds;
213     for (PanelContainer::const_iterator
214              iPanel(maPanels.begin()),
215              iEnd(maPanels.end());
216          iPanel!=iEnd;
217          ++iPanel)
218     {
219         const PanelDescriptor& rPanelDescriptor (*iPanel);
220         if (rPanelDescriptor.msDeckId.equals(rsDeckId))
221         {
222             const ContextList::Entry* pEntry = rPanelDescriptor.maContextList.GetMatch(rContext);
223             if (pEntry != NULL)
224             {
225                 PanelContextDescriptor aPanelContextDescriptor;
226                 aPanelContextDescriptor.msId = rPanelDescriptor.msId;
227                 aPanelContextDescriptor.msMenuCommand = pEntry->msMenuCommand;
228                 aPanelContextDescriptor.mbIsInitiallyVisible = pEntry->mbIsInitiallyVisible;
229                 aOrderedIds.insert(::std::multimap<sal_Int32,PanelContextDescriptor>::value_type(
230                         rPanelDescriptor.mnOrderIndex,
231                         aPanelContextDescriptor));
232             }
233         }
234     }
235 
236     for (::std::multimap<sal_Int32,PanelContextDescriptor>::const_iterator
237              iId(aOrderedIds.begin()),
238              iEnd(aOrderedIds.end());
239          iId!=iEnd;
240          ++iId)
241     {
242         rPanelIds.push_back(iId->second);
243     }
244 
245     return rPanelIds;
246 }
247 
248 
249 
250 
251 void ResourceManager::ReadDeckList (void)
252 {
253     const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
254     const ::utl::OConfigurationTreeRoot aDeckRootNode (
255         aContext,
256         A2S("org.openoffice.Office.UI.Sidebar/Content/DeckList"),
257         false);
258     if ( ! aDeckRootNode.isValid() )
259         return;
260 
261     const Sequence<OUString> aDeckNodeNames (aDeckRootNode.getNodeNames());
262     const sal_Int32 nCount (aDeckNodeNames.getLength());
263     maDecks.resize(nCount);
264     sal_Int32 nWriteIndex(0);
265     for (sal_Int32 nReadIndex(0); nReadIndex<nCount; ++nReadIndex)
266     {
267         const ::utl::OConfigurationNode aDeckNode (aDeckRootNode.openNode(aDeckNodeNames[nReadIndex]));
268         if ( ! aDeckNode.isValid())
269             continue;
270 
271         DeckDescriptor& rDeckDescriptor (maDecks[nWriteIndex++]);
272 
273         rDeckDescriptor.msTitle = ::comphelper::getString(
274             aDeckNode.getNodeValue("Title"));
275         rDeckDescriptor.msId = ::comphelper::getString(
276             aDeckNode.getNodeValue("Id"));
277         rDeckDescriptor.msIconURL = ::comphelper::getString(
278             aDeckNode.getNodeValue("IconURL"));
279         rDeckDescriptor.msHighContrastIconURL = ::comphelper::getString(
280             aDeckNode.getNodeValue("HighContrastIconURL"));
281         rDeckDescriptor.msHelpURL = ::comphelper::getString(
282             aDeckNode.getNodeValue("HelpURL"));
283         rDeckDescriptor.msHelpText = rDeckDescriptor.msTitle;
284         rDeckDescriptor.mbIsEnabled = true;
285         rDeckDescriptor.mnOrderIndex = ::comphelper::getINT32(
286             aDeckNode.getNodeValue("OrderIndex"));
287 
288         ReadContextList(
289             aDeckNode,
290             rDeckDescriptor.maContextList,
291             OUString());
292     }
293 
294     // When there where invalid nodes then we have to adapt the size
295     // of the deck vector.
296     if (nWriteIndex<nCount)
297         maDecks.resize(nWriteIndex);
298 }
299 
300 
301 
302 
303 void ResourceManager::ReadPanelList (void)
304 {
305     const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
306     const ::utl::OConfigurationTreeRoot aPanelRootNode (
307         aContext,
308         A2S("org.openoffice.Office.UI.Sidebar/Content/PanelList"),
309         false);
310     if ( ! aPanelRootNode.isValid() )
311         return;
312 
313     const Sequence<OUString> aPanelNodeNames (aPanelRootNode.getNodeNames());
314     const sal_Int32 nCount (aPanelNodeNames.getLength());
315     maPanels.resize(nCount);
316     sal_Int32 nWriteIndex (0);
317     for (sal_Int32 nReadIndex(0); nReadIndex<nCount; ++nReadIndex)
318     {
319         const ::utl::OConfigurationNode aPanelNode (aPanelRootNode.openNode(aPanelNodeNames[nReadIndex]));
320         if ( ! aPanelNode.isValid())
321             continue;
322 
323         PanelDescriptor& rPanelDescriptor (maPanels[nWriteIndex++]);
324 
325         rPanelDescriptor.msTitle = ::comphelper::getString(
326             aPanelNode.getNodeValue("Title"));
327         rPanelDescriptor.mbIsTitleBarOptional = ::comphelper::getBOOL(
328             aPanelNode.getNodeValue("TitleBarIsOptional"));
329         rPanelDescriptor.msId = ::comphelper::getString(
330             aPanelNode.getNodeValue("Id"));
331         rPanelDescriptor.msDeckId = ::comphelper::getString(
332             aPanelNode.getNodeValue("DeckId"));
333         rPanelDescriptor.msHelpURL = ::comphelper::getString(
334             aPanelNode.getNodeValue("HelpURL"));
335         rPanelDescriptor.msImplementationURL = ::comphelper::getString(
336             aPanelNode.getNodeValue("ImplementationURL"));
337         rPanelDescriptor.mnOrderIndex = ::comphelper::getINT32(
338             aPanelNode.getNodeValue("OrderIndex"));
339         rPanelDescriptor.mbWantsCanvas = ::comphelper::getBOOL(
340             aPanelNode.getNodeValue("WantsCanvas"));
341         const OUString sDefaultMenuCommand (::comphelper::getString(
342                 aPanelNode.getNodeValue("DefaultMenuCommand")));
343 
344         ReadContextList(
345             aPanelNode,
346             rPanelDescriptor.maContextList,
347             sDefaultMenuCommand);
348     }
349 
350     // When there where invalid nodes then we have to adapt the size
351     // of the deck vector.
352     if (nWriteIndex<nCount)
353         maPanels.resize(nWriteIndex);
354 }
355 
356 
357 
358 
359 void ResourceManager::ReadContextList (
360     const ::utl::OConfigurationNode& rParentNode,
361     ContextList& rContextList,
362     const OUString& rsDefaultMenuCommand) const
363 {
364     const Any aValue = rParentNode.getNodeValue("ContextList");
365     Sequence<OUString> aValues;
366     sal_Int32 nCount;
367     if (aValue >>= aValues)
368         nCount = aValues.getLength();
369     else
370         nCount = 0;
371 
372     for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex)
373     {
374         const OUString sValue (aValues[nIndex]);
375         sal_Int32 nCharacterIndex (0);
376         const OUString sApplicationName (sValue.getToken(0, ',', nCharacterIndex).trim());
377         if (nCharacterIndex < 0)
378         {
379             if (sApplicationName.getLength() == 0)
380             {
381                 // This is a valid case: in the XML file the separator
382                 // was used as terminator.  Using it in the last line
383                 // creates an additional but empty entry.
384                 break;
385             }
386             else
387             {
388                 OSL_ASSERT("expecting three or four values per ContextList entry, separated by comma");
389                 continue;
390             }
391         }
392 
393         const OUString sContextName (sValue.getToken(0, ',', nCharacterIndex).trim());
394         if (nCharacterIndex < 0)
395         {
396             OSL_ASSERT("expecting three or four values per ContextList entry, separated by comma");
397             continue;
398         }
399 
400         const OUString sInitialState (sValue.getToken(0, ',', nCharacterIndex).trim());
401 
402         // The fourth argument is optional.
403         const OUString sMenuCommandOverride (
404             nCharacterIndex<0
405                 ? OUString()
406                 : sValue.getToken(0, ',', nCharacterIndex).trim());
407         const OUString sMenuCommand (
408             sMenuCommandOverride.getLength()>0
409                 ? (sMenuCommandOverride.equalsAscii("none")
410                     ? OUString()
411                     : sMenuCommandOverride)
412                 : rsDefaultMenuCommand);
413 
414         EnumContext::Application eApplication (EnumContext::GetApplicationEnum(sApplicationName));
415         bool bApplicationIsDrawAndImpress = false;
416         if (eApplication == EnumContext::Application_None
417             && !sApplicationName.equals(EnumContext::GetApplicationName(EnumContext::Application_None)))
418         {
419             // Handle some special names: abbreviations that make
420             // context descriptions more readable.
421             if (sApplicationName.equalsAscii("Writer"))
422                 eApplication = EnumContext::Application_Writer;
423             else if (sApplicationName.equalsAscii("Calc"))
424                 eApplication = EnumContext::Application_Calc;
425             else if (sApplicationName.equalsAscii("Draw"))
426                 eApplication = EnumContext::Application_Draw;
427             else if (sApplicationName.equalsAscii("Impress"))
428                 eApplication = EnumContext::Application_Impress;
429             else if (sApplicationName.equalsAscii("DrawImpress"))
430             {
431                 // A special case among the special names:  it is
432                 // common to use the same context descriptions for
433                 // both Draw and Impress.  This special case helps to
434                 // avoid duplication in the .xcu file.
435                 bApplicationIsDrawAndImpress = true;
436             }
437             else
438             {
439                 OSL_ASSERT("application name not recognized");
440                 continue;
441             }
442         }
443 
444         const EnumContext::Context eContext (EnumContext::GetContextEnum(sContextName));
445         if (eContext == EnumContext::Context_Unknown)
446         {
447             OSL_ASSERT("context name not recognized");
448             continue;
449         }
450 
451         bool bIsInitiallyVisible;
452         if (sInitialState.equalsAscii("visible"))
453             bIsInitiallyVisible = true;
454         else if (sInitialState.equalsAscii("hidden"))
455             bIsInitiallyVisible = false;
456         else
457         {
458             OSL_ASSERT("unrecognized state");
459             continue;
460         }
461 
462         if (bApplicationIsDrawAndImpress)
463         {
464             // Add the context description for both Draw and Impress.
465             rContextList.AddContextDescription(
466                 Context(
467                     EnumContext::GetApplicationName(EnumContext::Application_Draw),
468                     EnumContext::GetContextName(eContext)),
469                 bIsInitiallyVisible,
470                 sMenuCommand);
471             rContextList.AddContextDescription(
472                 Context(
473                     EnumContext::GetApplicationName(EnumContext::Application_Impress),
474                     EnumContext::GetContextName(eContext)),
475                 bIsInitiallyVisible,
476                 sMenuCommand);
477         }
478         else
479             rContextList.AddContextDescription(
480                 Context(
481                     EnumContext::GetApplicationName(eApplication),
482                     EnumContext::GetContextName(eContext)),
483                 bIsInitiallyVisible,
484                 sMenuCommand);
485     }
486 }
487 
488 
489 
490 
491 void ResourceManager::ReadLegacyAddons (const Reference<frame::XFrame>& rxFrame)
492 {
493     // Get module name for given frame.
494     ::rtl::OUString sModuleName (GetModuleName(rxFrame));
495     if (sModuleName.getLength() == 0)
496         return;
497     if (maProcessedApplications.find(sModuleName) != maProcessedApplications.end())
498     {
499         // Addons for this application have already been read.
500         // There is nothing more to do.
501         return;
502     }
503 
504     // Mark module as processed.  Even when there is an error that
505     // prevents the configuration data from being read, this error
506     // will not be triggered a second time.
507     maProcessedApplications.insert(sModuleName);
508 
509     // Get access to the configuration root node for the application.
510     ::utl::OConfigurationTreeRoot aLegacyRootNode (GetLegacyAddonRootNode(sModuleName));
511     if ( ! aLegacyRootNode.isValid())
512         return;
513 
514     // Process child nodes.
515     ::std::vector<OUString> aMatchingNodeNames;
516     GetToolPanelNodeNames(aMatchingNodeNames, aLegacyRootNode);
517     const sal_Int32 nCount (aMatchingNodeNames.size());
518     size_t nDeckWriteIndex (maDecks.size());
519     size_t nPanelWriteIndex (maPanels.size());
520     maDecks.resize(maDecks.size() + nCount);
521     maPanels.resize(maPanels.size() + nCount);
522     for (sal_Int32 nReadIndex(0); nReadIndex<nCount; ++nReadIndex)
523     {
524         const OUString& rsNodeName (aMatchingNodeNames[nReadIndex]);
525         const ::utl::OConfigurationNode aChildNode (aLegacyRootNode.openNode(rsNodeName));
526         if ( ! aChildNode.isValid())
527             continue;
528 
529         DeckDescriptor& rDeckDescriptor (maDecks[nDeckWriteIndex++]);
530         rDeckDescriptor.msTitle = ::comphelper::getString(aChildNode.getNodeValue("UIName"));
531         rDeckDescriptor.msId = rsNodeName;
532         rDeckDescriptor.msIconURL = ::comphelper::getString(aChildNode.getNodeValue("ImageURL"));
533         rDeckDescriptor.msHighContrastIconURL = rDeckDescriptor.msIconURL;
534         rDeckDescriptor.msHelpURL = ::comphelper::getString(aChildNode.getNodeValue("HelpURL"));
535         rDeckDescriptor.msHelpText = rDeckDescriptor.msTitle;
536         rDeckDescriptor.maContextList.AddContextDescription(Context(sModuleName, A2S("any")), true, OUString());
537         rDeckDescriptor.mbIsEnabled = true;
538 
539         PanelDescriptor& rPanelDescriptor (maPanels[nPanelWriteIndex++]);
540         rPanelDescriptor.msTitle = ::comphelper::getString(aChildNode.getNodeValue("UIName"));
541         rPanelDescriptor.mbIsTitleBarOptional = true;
542         rPanelDescriptor.msId = rsNodeName;
543         rPanelDescriptor.msDeckId = rsNodeName;
544         rPanelDescriptor.msHelpURL = ::comphelper::getString(aChildNode.getNodeValue("HelpURL"));
545         rPanelDescriptor.maContextList.AddContextDescription(Context(sModuleName, A2S("any")), true, OUString());
546         rPanelDescriptor.msImplementationURL = rsNodeName;
547     }
548 
549     // When there where invalid nodes then we have to adapt the size
550     // of the deck and panel vectors.
551     if (nDeckWriteIndex < maDecks.size())
552         maDecks.resize(nDeckWriteIndex);
553     if (nPanelWriteIndex < maPanels.size())
554         maPanels.resize(nPanelWriteIndex);
555 }
556 
557 
558 
559 
560 ::rtl::OUString ResourceManager::GetModuleName (
561     const cssu::Reference<css::frame::XFrame>& rxFrame)
562 {
563     try
564     {
565         const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
566         const Reference<frame::XModuleManager> xModuleManager (
567             aContext.createComponent("com.sun.star.frame.ModuleManager" ),
568             UNO_QUERY_THROW );
569         return xModuleManager->identify(rxFrame);
570     }
571     catch (const Exception&)
572     {
573         DBG_UNHANDLED_EXCEPTION();
574     }
575     return OUString();
576 }
577 
578 
579 
580 
581 ::utl::OConfigurationTreeRoot ResourceManager::GetLegacyAddonRootNode (
582     const ::rtl::OUString& rsModuleName) const
583 {
584     try
585     {
586         const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
587         const Reference<container::XNameAccess> xModuleAccess (
588             aContext.createComponent("com.sun.star.frame.ModuleManager"),
589             UNO_QUERY_THROW);
590         const ::comphelper::NamedValueCollection aModuleProperties (xModuleAccess->getByName(rsModuleName));
591         const ::rtl::OUString sWindowStateRef (aModuleProperties.getOrDefault(
592                 "ooSetupFactoryWindowStateConfigRef",
593                 ::rtl::OUString()));
594 
595         ::rtl::OUStringBuffer aPathComposer;
596         aPathComposer.appendAscii("org.openoffice.Office.UI.");
597         aPathComposer.append(sWindowStateRef);
598         aPathComposer.appendAscii("/UIElements/States");
599 
600         return ::utl::OConfigurationTreeRoot(aContext, aPathComposer.makeStringAndClear(), false);
601     }
602     catch( const Exception& )
603     {
604         DBG_UNHANDLED_EXCEPTION();
605     }
606 
607     return ::utl::OConfigurationTreeRoot();
608 }
609 
610 
611 
612 
613 void ResourceManager::GetToolPanelNodeNames (
614     ::std::vector<OUString>& rMatchingNames,
615     const ::utl::OConfigurationTreeRoot aRoot) const
616 {
617     Sequence<OUString> aChildNodeNames (aRoot.getNodeNames());
618     const sal_Int32 nCount (aChildNodeNames.getLength());
619     for (sal_Int32 nIndex(0); nIndex<nCount; ++nIndex)
620     {
621         if (aChildNodeNames[nIndex].matchAsciiL(
622                 RTL_CONSTASCII_STRINGPARAM( "private:resource/toolpanel/")))
623             rMatchingNames.push_back(aChildNodeNames[nIndex]);
624     }
625 }
626 
627 
628 
629 } } // end of namespace sfx2::sidebar
630