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     sal_Int32 nBestMatch (EnumContext::NoMatch);
99     const DeckContainer::const_iterator iEnd (maDecks.end());
100     DeckContainer::const_iterator iBestDeck (iEnd);
101 
102     for (DeckContainer::const_iterator iDeck(maDecks.begin());
103          iDeck!=iEnd;
104          ++iDeck)
105     {
106         const sal_Int32 nMatch (iDeck->maContextMatcher.EvaluateMatch(rContext));
107         if (nMatch < nBestMatch)
108         {
109             // Found a better matching deck.
110             nBestMatch = nMatch;
111             iBestDeck = iDeck;
112             if (nBestMatch == EnumContext::OptimalMatch)
113             {
114                 // We will not find a better match.
115                 break;
116             }
117         }
118     }
119     if (iBestDeck != iEnd)
120         return &*iBestDeck;
121     else
122         return NULL;
123 }
124 
125 
126 
127 
128 const DeckDescriptor* ResourceManager::GetDeckDescriptor (
129     const ::rtl::OUString& rsDeckId) const
130 {
131     for (DeckContainer::const_iterator
132              iDeck(maDecks.begin()),
133              iEnd(maDecks.end());
134          iDeck!=iEnd;
135          ++iDeck)
136     {
137         if (iDeck->msId.equals(rsDeckId))
138             return &*iDeck;
139     }
140     return NULL;
141 }
142 
143 
144 
145 
146 const PanelDescriptor* ResourceManager::GetPanelDescriptor (
147     const ::rtl::OUString& rsPanelId) const
148 {
149     for (PanelContainer::const_iterator
150              iPanel(maPanels.begin()),
151              iEnd(maPanels.end());
152          iPanel!=iEnd;
153          ++iPanel)
154     {
155         if (iPanel->msId.equals(rsPanelId))
156             return &*iPanel;
157     }
158     return NULL;
159 }
160 
161 
162 
163 
164 void ResourceManager::SetIsDeckEnabled (
165     const ::rtl::OUString& rsDeckId,
166     const bool bIsEnabled)
167 {
168     for (DeckContainer::iterator
169              iDeck(maDecks.begin()),
170              iEnd(maDecks.end());
171          iDeck!=iEnd;
172          ++iDeck)
173     {
174         if (iDeck->msId.equals(rsDeckId))
175         {
176             iDeck->mbIsEnabled = bIsEnabled;
177             return;
178         }
179     }
180 }
181 
182 
183 
184 
185 const ResourceManager::IdContainer& ResourceManager::GetMatchingDecks (
186     IdContainer& rDeckIds,
187     const Context& rContext,
188     const Reference<frame::XFrame>& rxFrame)
189 {
190     ReadLegacyAddons(rxFrame);
191 
192     ::std::multimap<sal_Int32,OUString> aOrderedIds;
193     for (DeckContainer::const_iterator
194              iDeck(maDecks.begin()),
195              iEnd (maDecks.end());
196          iDeck!=iEnd;
197          ++iDeck)
198     {
199         const DeckDescriptor& rDeckDescriptor (*iDeck);
200         if (rDeckDescriptor.maContextMatcher.EvaluateMatch(rContext) != EnumContext::NoMatch)
201             aOrderedIds.insert(::std::multimap<sal_Int32,OUString>::value_type(
202                     rDeckDescriptor.mnOrderIndex,
203                     rDeckDescriptor.msId));
204     }
205 
206     for (::std::multimap<sal_Int32,OUString>::const_iterator
207              iId(aOrderedIds.begin()),
208              iEnd(aOrderedIds.end());
209          iId!=iEnd;
210          ++iId)
211     {
212         rDeckIds.push_back(iId->second);
213     }
214 
215     return rDeckIds;
216 }
217 
218 
219 
220 
221 const ResourceManager::IdContainer& ResourceManager::GetMatchingPanels (
222     IdContainer& rPanelIds,
223     const Context& rContext,
224     const ::rtl::OUString& rsDeckId,
225     const Reference<frame::XFrame>& rxFrame)
226 {
227     ReadLegacyAddons(rxFrame);
228 
229     ::std::multimap<sal_Int32,OUString> aOrderedIds;
230     for (PanelContainer::const_iterator
231              iPanel(maPanels.begin()),
232              iEnd(maPanels.end());
233          iPanel!=iEnd;
234          ++iPanel)
235     {
236         const PanelDescriptor& rPanelDescriptor (*iPanel);
237         if (rPanelDescriptor.msDeckId.equals(rsDeckId))
238             if (rPanelDescriptor.maContextMatcher.EvaluateMatch(rContext) != EnumContext::NoMatch)
239                 aOrderedIds.insert(::std::multimap<sal_Int32,OUString>::value_type(
240                         rPanelDescriptor.mnOrderIndex,
241                         rPanelDescriptor.msId));
242     }
243 
244     for (::std::multimap<sal_Int32,OUString>::const_iterator
245              iId(aOrderedIds.begin()),
246              iEnd(aOrderedIds.end());
247          iId!=iEnd;
248          ++iId)
249     {
250         rPanelIds.push_back(iId->second);
251     }
252 
253     return rPanelIds;
254 }
255 
256 
257 
258 
259 void ResourceManager::ReadDeckList (void)
260 {
261     const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
262     const ::utl::OConfigurationTreeRoot aDeckRootNode (
263         aContext,
264         A2S("org.openoffice.Office.UI.Sidebar/Content/DeckList"),
265         false);
266     if ( ! aDeckRootNode.isValid() )
267         return;
268 
269     const Sequence<OUString> aDeckNodeNames (aDeckRootNode.getNodeNames());
270     const sal_Int32 nCount (aDeckNodeNames.getLength());
271     maDecks.resize(nCount);
272     sal_Int32 nWriteIndex(0);
273     for (sal_Int32 nReadIndex(0); nReadIndex<nCount; ++nReadIndex)
274     {
275         const ::utl::OConfigurationNode aDeckNode (aDeckRootNode.openNode(aDeckNodeNames[nReadIndex]));
276         if ( ! aDeckNode.isValid())
277             continue;
278 
279         DeckDescriptor& rDeckDescriptor (maDecks[nWriteIndex++]);
280 
281         rDeckDescriptor.msTitle = ::comphelper::getString(
282             aDeckNode.getNodeValue("Title"));
283         rDeckDescriptor.msId = ::comphelper::getString(
284             aDeckNode.getNodeValue("Id"));
285         rDeckDescriptor.msIconURL = ::comphelper::getString(
286             aDeckNode.getNodeValue("IconURL"));
287         rDeckDescriptor.msHighContrastIconURL = ::comphelper::getString(
288             aDeckNode.getNodeValue("HighContrastIconURL"));
289         rDeckDescriptor.msHelpURL = ::comphelper::getString(
290             aDeckNode.getNodeValue("HelpURL"));
291         rDeckDescriptor.msHelpText = rDeckDescriptor.msTitle;
292         rDeckDescriptor.mbIsEnabled = true;
293         rDeckDescriptor.mnOrderIndex = ::comphelper::getINT32(
294             aDeckNode.getNodeValue("OrderIndex"));
295 
296         ReadContextMatcher(aDeckNode.openNode("ContextMatchers"), rDeckDescriptor.maContextMatcher);
297     }
298 
299     // When there where invalid nodes then we have to adapt the size
300     // of the deck vector.
301     if (nWriteIndex<nCount)
302         maDecks.resize(nWriteIndex);
303 }
304 
305 
306 
307 
308 void ResourceManager::ReadPanelList (void)
309 {
310     const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
311     const ::utl::OConfigurationTreeRoot aPanelRootNode (
312         aContext,
313         A2S("org.openoffice.Office.UI.Sidebar/Content/PanelList"),
314         false);
315     if ( ! aPanelRootNode.isValid() )
316         return;
317 
318     const Sequence<OUString> aPanelNodeNames (aPanelRootNode.getNodeNames());
319     const sal_Int32 nCount (aPanelNodeNames.getLength());
320     maPanels.resize(nCount);
321     sal_Int32 nWriteIndex (0);
322     for (sal_Int32 nReadIndex(0); nReadIndex<nCount; ++nReadIndex)
323     {
324         const ::utl::OConfigurationNode aPanelNode (aPanelRootNode.openNode(aPanelNodeNames[nReadIndex]));
325         if ( ! aPanelNode.isValid())
326             continue;
327 
328         PanelDescriptor& rPanelDescriptor (maPanels[nWriteIndex++]);
329 
330         rPanelDescriptor.msTitle = ::comphelper::getString(
331             aPanelNode.getNodeValue("Title"));
332         rPanelDescriptor.mbIsTitleBarOptional = ::comphelper::getBOOL(
333             aPanelNode.getNodeValue("TitleBarIsOptional"));
334         rPanelDescriptor.msId = ::comphelper::getString(
335             aPanelNode.getNodeValue("Id"));
336         rPanelDescriptor.msDeckId = ::comphelper::getString(
337             aPanelNode.getNodeValue("DeckId"));
338         rPanelDescriptor.msHelpURL = ::comphelper::getString(
339             aPanelNode.getNodeValue("HelpURL"));
340         rPanelDescriptor.msImplementationURL = ::comphelper::getString(
341             aPanelNode.getNodeValue("ImplementationURL"));
342         rPanelDescriptor.mnOrderIndex = ::comphelper::getINT32(
343             aPanelNode.getNodeValue("OrderIndex"));
344         rPanelDescriptor.mbHasMenu = ::comphelper::getBOOL(
345             aPanelNode.getNodeValue("HasMenu"));
346         rPanelDescriptor.mbWantsCanvas = ::comphelper::getBOOL(
347             aPanelNode.getNodeValue("WantsCanvas"));
348         ReadContextMatcher(aPanelNode.openNode("ContextMatchers"), rPanelDescriptor.maContextMatcher);
349     }
350 
351     // When there where invalid nodes then we have to adapt the size
352     // of the deck vector.
353     if (nWriteIndex<nCount)
354         maPanels.resize(nWriteIndex);
355 }
356 
357 
358 
359 
360 void ResourceManager::ReadContextMatcher (
361     const ::utl::OConfigurationNode& rNode,
362     ContextMatcher& rContextMatcher) const
363 {
364     const Sequence<OUString> aMatcherNodeNames (rNode.getNodeNames());
365     const sal_Int32 nMatcherCount (aMatcherNodeNames.getLength());
366     for (sal_Int32 nMatcherIndex(0); nMatcherIndex<nMatcherCount; ++nMatcherIndex)
367     {
368         const ::utl::OConfigurationNode aMatcherNode (rNode.openNode(aMatcherNodeNames[nMatcherIndex]));
369 
370         const OUString sApplicationName (
371             ::comphelper::getString(aMatcherNode.getNodeValue("Application")));
372         const bool bIsContextListNegated (
373             ::comphelper::getBOOL(aMatcherNode.getNodeValue("IsContextListNegated")));
374 
375         // Read the context names.
376         Any aContextListValue (aMatcherNode.getNodeValue("ContextList"));
377         Sequence<OUString> aContextList;
378         ::std::vector<OUString> aContextVector;
379         if (aContextListValue >>= aContextList)
380         {
381             aContextVector.reserve(aContextList.getLength());
382             ::std::copy(
383                 ::comphelper::stl_begin(aContextList),
384                 ::comphelper::stl_end(aContextList),
385                 ::std::back_inserter(aContextVector));
386         }
387         // Empty list defaults to "any".
388         if (aContextVector.empty())
389             aContextVector.push_back(A2S("any"));
390 
391         rContextMatcher.AddMatcher(
392                 sApplicationName,
393                 aContextVector,
394                 bIsContextListNegated);
395     }
396 }
397 
398 
399 
400 
401 void ResourceManager::ReadLegacyAddons (const Reference<frame::XFrame>& rxFrame)
402 {
403     // Get module name for given frame.
404     ::rtl::OUString sModuleName (GetModuleName(rxFrame));
405     if (sModuleName.getLength() == 0)
406         return;
407     if (maProcessedApplications.find(sModuleName) != maProcessedApplications.end())
408     {
409         // Addons for this application have already been read.
410         // There is nothing more to do.
411         return;
412     }
413 
414     // Mark module as processed.  Even when there is an error that
415     // prevents the configuration data from being read, this error
416     // will not be triggered a second time.
417     maProcessedApplications.insert(sModuleName);
418 
419     // Get access to the configuration root node for the application.
420     ::utl::OConfigurationTreeRoot aLegacyRootNode (GetLegacyAddonRootNode(sModuleName));
421     if ( ! aLegacyRootNode.isValid())
422         return;
423 
424     // Process child nodes.
425     ::std::vector<OUString> aMatchingNodeNames;
426     GetToolPanelNodeNames(aMatchingNodeNames, aLegacyRootNode);
427     const sal_Int32 nCount (aMatchingNodeNames.size());
428     size_t nDeckWriteIndex (maDecks.size());
429     size_t nPanelWriteIndex (maPanels.size());
430     maDecks.resize(maDecks.size() + nCount);
431     maPanels.resize(maPanels.size() + nCount);
432     for (sal_Int32 nReadIndex(0); nReadIndex<nCount; ++nReadIndex)
433     {
434         const OUString& rsNodeName (aMatchingNodeNames[nReadIndex]);
435         const ::utl::OConfigurationNode aChildNode (aLegacyRootNode.openNode(rsNodeName));
436         if ( ! aChildNode.isValid())
437             continue;
438 
439         DeckDescriptor& rDeckDescriptor (maDecks[nDeckWriteIndex++]);
440         rDeckDescriptor.msTitle = ::comphelper::getString(aChildNode.getNodeValue("UIName"));
441         rDeckDescriptor.msId = rsNodeName;
442         rDeckDescriptor.msIconURL = ::comphelper::getString(aChildNode.getNodeValue("ImageURL"));
443         rDeckDescriptor.msHighContrastIconURL = rDeckDescriptor.msIconURL;
444         rDeckDescriptor.msHelpURL = ::comphelper::getString(aChildNode.getNodeValue("HelpURL"));
445         rDeckDescriptor.msHelpText = rDeckDescriptor.msTitle;
446         rDeckDescriptor.maContextMatcher.AddMatcher(sModuleName, A2S("any"));
447         rDeckDescriptor.mbIsEnabled = true;
448 
449         PanelDescriptor& rPanelDescriptor (maPanels[nPanelWriteIndex++]);
450         rPanelDescriptor.msTitle = ::comphelper::getString(aChildNode.getNodeValue("UIName"));
451         rPanelDescriptor.mbIsTitleBarOptional = true;
452         rPanelDescriptor.msId = rsNodeName;
453         rPanelDescriptor.msDeckId = rsNodeName;
454         rPanelDescriptor.msHelpURL = ::comphelper::getString(aChildNode.getNodeValue("HelpURL"));
455         rPanelDescriptor.maContextMatcher.AddMatcher(sModuleName, A2S("any"));
456         rPanelDescriptor.msImplementationURL = rsNodeName;
457     }
458 
459     // When there where invalid nodes then we have to adapt the size
460     // of the deck and panel vectors.
461     if (nDeckWriteIndex < maDecks.size())
462         maDecks.resize(nDeckWriteIndex);
463     if (nPanelWriteIndex < maPanels.size())
464         maPanels.resize(nPanelWriteIndex);
465 }
466 
467 
468 
469 
470 ::rtl::OUString ResourceManager::GetModuleName (
471     const cssu::Reference<css::frame::XFrame>& rxFrame)
472 {
473     try
474     {
475         const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
476         const Reference<frame::XModuleManager> xModuleManager (
477             aContext.createComponent("com.sun.star.frame.ModuleManager" ),
478             UNO_QUERY_THROW );
479         return xModuleManager->identify(rxFrame);
480     }
481     catch (const Exception&)
482     {
483         DBG_UNHANDLED_EXCEPTION();
484     }
485     return OUString();
486 }
487 
488 
489 
490 
491 ::utl::OConfigurationTreeRoot ResourceManager::GetLegacyAddonRootNode (
492     const ::rtl::OUString& rsModuleName) const
493 {
494     try
495     {
496         const ::comphelper::ComponentContext aContext (::comphelper::getProcessServiceFactory());
497         const Reference<container::XNameAccess> xModuleAccess (
498             aContext.createComponent("com.sun.star.frame.ModuleManager"),
499             UNO_QUERY_THROW);
500         const ::comphelper::NamedValueCollection aModuleProperties (xModuleAccess->getByName(rsModuleName));
501         const ::rtl::OUString sWindowStateRef (aModuleProperties.getOrDefault(
502                 "ooSetupFactoryWindowStateConfigRef",
503                 ::rtl::OUString()));
504 
505         ::rtl::OUStringBuffer aPathComposer;
506         aPathComposer.appendAscii("org.openoffice.Office.UI.");
507         aPathComposer.append(sWindowStateRef);
508         aPathComposer.appendAscii("/UIElements/States");
509 
510         return ::utl::OConfigurationTreeRoot(aContext, aPathComposer.makeStringAndClear(), false);
511     }
512     catch( const Exception& )
513     {
514         DBG_UNHANDLED_EXCEPTION();
515     }
516 
517     return ::utl::OConfigurationTreeRoot();
518 }
519 
520 
521 
522 
523 void ResourceManager::GetToolPanelNodeNames (
524     ::std::vector<OUString>& rMatchingNames,
525     const ::utl::OConfigurationTreeRoot aRoot) const
526 {
527     Sequence<OUString> aChildNodeNames (aRoot.getNodeNames());
528     const sal_Int32 nCount (aChildNodeNames.getLength());
529     for (sal_Int32 nIndex(0); nIndex<nCount; ++nIndex)
530     {
531         if (aChildNodeNames[nIndex].matchAsciiL(
532                 RTL_CONSTASCII_STRINGPARAM( "private:resource/toolpanel/")))
533             rMatchingNames.push_back(aChildNodeNames[nIndex]);
534     }
535 }
536 
537 
538 
539 } } // end of namespace sfx2::sidebar
540