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