use insert function instead of for loop
[LibreOffice.git] / sfx2 / source / appl / sfxhelp.cxx
blob13793dd6e9df787aaa4020c98ac56b9150c4ddb1
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <config_folders.h>
21 #include <sfx2/sfxhelp.hxx>
22 #include <helpids.h>
24 #include <string_view>
25 #include <algorithm>
26 #include <cassert>
27 #include <cstddef>
28 #ifdef MACOSX
29 #include <premac.h>
30 #include <Foundation/NSString.h>
31 #include <CoreFoundation/CFURL.h>
32 #include <CoreServices/CoreServices.h>
33 #include <postmac.h>
34 #endif
36 #include <sal/log.hxx>
37 #include <com/sun/star/uno/Reference.h>
38 #include <com/sun/star/frame/Desktop.hpp>
39 #include <com/sun/star/frame/UnknownModuleException.hpp>
40 #include <com/sun/star/frame/XFrame2.hpp>
41 #include <comphelper/processfactory.hxx>
42 #include <com/sun/star/awt/XWindow.hpp>
43 #include <com/sun/star/awt/XTopWindow.hpp>
44 #include <com/sun/star/beans/XPropertySet.hpp>
45 #include <com/sun/star/frame/FrameSearchFlag.hpp>
46 #include <toolkit/helper/vclunohelper.hxx>
47 #include <com/sun/star/frame/ModuleManager.hpp>
48 #include <unotools/configmgr.hxx>
49 #include <unotools/moduleoptions.hxx>
50 #include <tools/urlobj.hxx>
51 #include <ucbhelper/content.hxx>
52 #include <unotools/pathoptions.hxx>
53 #include <rtl/byteseq.hxx>
54 #include <rtl/ustring.hxx>
55 #include <o3tl/string_view.hxx>
56 #include <officecfg/Office/Common.hxx>
57 #include <osl/process.h>
58 #include <osl/file.hxx>
59 #include <unotools/tempfile.hxx>
60 #include <unotools/securityoptions.hxx>
61 #include <rtl/uri.hxx>
62 #include <vcl/commandinfoprovider.hxx>
63 #include <vcl/keycod.hxx>
64 #include <vcl/settings.hxx>
65 #include <vcl/locktoplevels.hxx>
66 #include <vcl/weld.hxx>
67 #include <openuriexternally.hxx>
69 #include <comphelper/lok.hxx>
70 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
71 #include <sfx2/viewsh.hxx>
73 #include "newhelp.hxx"
74 #include <sfx2/flatpak.hxx>
75 #include <sfx2/sfxresid.hxx>
76 #include <helper.hxx>
77 #include <sfx2/strings.hrc>
78 #include <vcl/svapp.hxx>
79 #include <rtl/string.hxx>
80 #include <svtools/langtab.hxx>
81 #include <comphelper/diagnose_ex.hxx>
83 using namespace ::com::sun::star::beans;
84 using namespace ::com::sun::star::frame;
85 using namespace ::com::sun::star::uno;
87 namespace {
89 class NoHelpErrorBox
91 private:
92 std::unique_ptr<weld::MessageDialog> m_xErrBox;
93 public:
94 DECL_STATIC_LINK(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool);
95 public:
96 explicit NoHelpErrorBox(weld::Widget* pParent)
97 : m_xErrBox(Application::CreateMessageDialog(pParent, VclMessageType::Error, VclButtonsType::Ok,
98 SfxResId(RID_STR_HLPFILENOTEXIST)))
100 // Error message: "No help available"
101 m_xErrBox->connect_help(LINK(nullptr, NoHelpErrorBox, HelpRequestHdl));
103 void run()
105 m_xErrBox->run();
111 IMPL_STATIC_LINK_NOARG(NoHelpErrorBox, HelpRequestHdl, weld::Widget&, bool)
113 // do nothing, because no help available
114 return false;
117 static OUString const & HelpLocaleString();
119 namespace {
121 /// Root path of the help.
122 OUString const & getHelpRootURL()
124 static OUString const s_instURL = []()
126 OUString tmp = officecfg::Office::Common::Path::Current::Help::get();
127 if (tmp.isEmpty())
129 // try to determine path from default
130 tmp = "$(instpath)/" LIBO_SHARE_HELP_FOLDER;
133 // replace anything like $(instpath);
134 SvtPathOptions aOptions;
135 tmp = aOptions.SubstituteVariable(tmp);
137 OUString url;
138 if (osl::FileBase::getFileURLFromSystemPath(tmp, url) == osl::FileBase::E_None)
139 tmp = url;
140 return tmp;
141 }();
142 return s_instURL;
145 bool impl_checkHelpLocalePath(OUString const & rpPath)
147 osl::DirectoryItem directoryItem;
148 bool bOK = false;
150 osl::FileStatus fileStatus(osl_FileStatus_Mask_Type | osl_FileStatus_Mask_FileURL | osl_FileStatus_Mask_FileName);
151 if (osl::DirectoryItem::get(rpPath, directoryItem) == osl::FileBase::E_None &&
152 directoryItem.getFileStatus(fileStatus) == osl::FileBase::E_None &&
153 fileStatus.isDirectory())
155 bOK = true;
157 return bOK;
160 /// Check for built-in help
161 /// Check if help/<lang>/err.html file exist
162 bool impl_hasHelpInstalled()
164 if (comphelper::LibreOfficeKit::isActive())
165 return false;
167 // detect installed locale
168 static OUString const aLocaleStr = HelpLocaleString();
170 OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/err.html";
171 bool bOK = false;
172 osl::DirectoryItem directoryItem;
173 if(osl::DirectoryItem::get(helpRootURL, directoryItem) == osl::FileBase::E_None){
174 bOK=true;
177 SAL_INFO( "sfx.appl", "Checking old help installed " << bOK);
178 return bOK;
181 /// Check for html built-in help
182 /// Check if help/lang/text folder exist. Only html has it.
183 bool impl_hasHTMLHelpInstalled()
185 if (comphelper::LibreOfficeKit::isActive())
186 return false;
188 // detect installed locale
189 static OUString const aLocaleStr = HelpLocaleString();
191 OUString helpRootURL = getHelpRootURL() + "/" + aLocaleStr + "/text";
192 bool bOK = impl_checkHelpLocalePath( helpRootURL );
193 SAL_INFO( "sfx.appl", "Checking new help (html) installed " << bOK);
194 return bOK;
197 } // namespace
199 /// Return the locale we prefer for displaying help
200 static OUString const & HelpLocaleString()
202 if (comphelper::LibreOfficeKit::isActive())
203 return comphelper::LibreOfficeKit::getLanguageTag().getBcp47();
205 static OUString aLocaleStr;
206 if (!aLocaleStr.isEmpty())
207 return aLocaleStr;
209 static constexpr OUString aEnglish(u"en-US"_ustr);
210 // detect installed locale
211 aLocaleStr = utl::ConfigManager::getUILocale();
213 if ( aLocaleStr.isEmpty() )
215 aLocaleStr = aEnglish;
216 return aLocaleStr;
219 // get fall-back language (country)
220 OUString sLang = aLocaleStr;
221 sal_Int32 nSepPos = sLang.indexOf( '-' );
222 if (nSepPos != -1)
224 sLang = sLang.copy( 0, nSepPos );
226 OUString sHelpPath(u""_ustr);
227 sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aLocaleStr;
228 if (impl_checkHelpLocalePath(sHelpPath))
230 return aLocaleStr;
232 sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + sLang;
233 if (impl_checkHelpLocalePath(sHelpPath))
235 aLocaleStr = sLang;
236 return aLocaleStr;
238 sHelpPath = getHelpRootURL() + "/" + aLocaleStr;
239 if (impl_checkHelpLocalePath(sHelpPath))
241 return aLocaleStr;
243 sHelpPath = getHelpRootURL() + "/" + sLang;
244 if (impl_checkHelpLocalePath(sHelpPath))
246 aLocaleStr = sLang;
247 return aLocaleStr;
249 sHelpPath = getHelpRootURL() + "/" + utl::ConfigManager::getProductVersion() + "/" + aEnglish;
250 if (impl_checkHelpLocalePath(sHelpPath))
252 aLocaleStr = aEnglish;
253 return aLocaleStr;
255 sHelpPath = getHelpRootURL() + "/" + aEnglish;
256 if (impl_checkHelpLocalePath(sHelpPath))
258 aLocaleStr = aEnglish;
259 return aLocaleStr;
261 return aLocaleStr;
266 void AppendConfigToken( OUStringBuffer& rURL, bool bQuestionMark )
268 const OUString& aLocaleStr = HelpLocaleString();
270 // query part exists?
271 if ( bQuestionMark )
272 // no, so start with '?'
273 rURL.append('?');
274 else
275 // yes, so only append with '&'
276 rURL.append('&');
278 // set parameters
279 rURL.append("Language=");
280 rURL.append(aLocaleStr);
281 rURL.append("&System=");
282 rURL.append(officecfg::Office::Common::Help::System::get());
283 rURL.append("&Version=");
284 rURL.append(utl::ConfigManager::getProductVersion());
287 static bool GetHelpAnchor_Impl( std::u16string_view _rURL, OUString& _rAnchor )
289 bool bRet = false;
293 ::ucbhelper::Content aCnt( INetURLObject( _rURL ).GetMainURL( INetURLObject::DecodeMechanism::NONE ),
294 Reference< css::ucb::XCommandEnvironment >(),
295 comphelper::getProcessComponentContext() );
296 OUString sAnchor;
297 if ( aCnt.getPropertyValue(u"AnchorName"_ustr) >>= sAnchor )
300 if ( !sAnchor.isEmpty() )
302 _rAnchor = sAnchor;
303 bRet = true;
306 else
308 SAL_WARN( "sfx.appl", "Property 'AnchorName' is missing" );
311 catch (const css::uno::Exception&)
315 return bRet;
318 namespace {
320 class SfxHelp_Impl
322 public:
323 static OUString GetHelpText( const OUString& aCommandURL, const OUString& rModule );
328 OUString SfxHelp_Impl::GetHelpText( const OUString& aCommandURL, const OUString& rModule )
330 // create help url
331 OUStringBuffer aHelpURL( SfxHelp::CreateHelpURL( aCommandURL, rModule ) );
332 // added 'active' parameter
333 sal_Int32 nIndex = aHelpURL.lastIndexOf( '#' );
334 if ( nIndex < 0 )
335 nIndex = aHelpURL.getLength();
336 aHelpURL.insert( nIndex, "&Active=true" );
337 // load help string
338 return SfxContentHelper::GetActiveHelpString( aHelpURL.makeStringAndClear() );
341 SfxHelp::SfxHelp()
342 : bIsDebug(false)
343 , bLaunchingHelp(false)
345 // read the environment variable "HELP_DEBUG"
346 // if it's set, you will see debug output on active help
347 OUString sHelpDebug;
348 OUString sEnvVarName( u"HELP_DEBUG"_ustr );
349 osl_getEnvironment( sEnvVarName.pData, &sHelpDebug.pData );
350 bIsDebug = !sHelpDebug.isEmpty();
353 SfxHelp::~SfxHelp()
357 static OUString getDefaultModule_Impl()
359 OUString sDefaultModule;
360 SvtModuleOptions aModOpt;
361 if (aModOpt.IsWriterInstalled())
362 sDefaultModule = "swriter";
363 else if (aModOpt.IsCalcInstalled())
364 sDefaultModule = "scalc";
365 else if (aModOpt.IsImpressInstalled())
366 sDefaultModule = "simpress";
367 else if (aModOpt.IsDrawInstalled())
368 sDefaultModule = "sdraw";
369 else if (aModOpt.IsMathInstalled())
370 sDefaultModule = "smath";
371 else if (aModOpt.IsChartInstalled())
372 sDefaultModule = "schart";
373 else if (SvtModuleOptions::IsBasicIDEInstalled())
374 sDefaultModule = "sbasic";
375 else if (aModOpt.IsDataBaseInstalled())
376 sDefaultModule = "sdatabase";
377 else
379 SAL_WARN( "sfx.appl", "getDefaultModule_Impl(): no module installed" );
381 return sDefaultModule;
384 static OUString getCurrentModuleIdentifier_Impl()
386 OUString sIdentifier;
387 const Reference < XComponentContext >& xContext = ::comphelper::getProcessComponentContext();
388 Reference < XModuleManager2 > xModuleManager = ModuleManager::create(xContext);
389 Reference < XDesktop2 > xDesktop = Desktop::create(xContext);
390 Reference < XFrame > xCurrentFrame = xDesktop->getCurrentFrame();
392 if ( xCurrentFrame.is() )
396 sIdentifier = xModuleManager->identify( xCurrentFrame );
398 catch (const css::frame::UnknownModuleException&)
400 SAL_INFO( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): unknown module (help in help?)" );
402 catch (const Exception&)
404 TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): exception of XModuleManager::identify()" );
408 return sIdentifier;
411 namespace
413 OUString MapModuleIdentifier(const OUString &rFactoryShortName)
415 OUString aFactoryShortName(rFactoryShortName);
417 // Map some module identifiers to their "real" help module string.
418 if ( aFactoryShortName == "chart2" )
419 aFactoryShortName = "schart" ;
420 else if ( aFactoryShortName == "BasicIDE" )
421 aFactoryShortName = "sbasic";
422 else if ( aFactoryShortName == "sweb"
423 || aFactoryShortName == "sglobal"
424 || aFactoryShortName == "swxform" )
425 aFactoryShortName = "swriter" ;
426 else if ( aFactoryShortName == "dbquery"
427 || aFactoryShortName == "dbbrowser"
428 || aFactoryShortName == "dbrelation"
429 || aFactoryShortName == "dbtable"
430 || aFactoryShortName == "dbapp"
431 || aFactoryShortName == "dbreport"
432 || aFactoryShortName == "dbtdata"
433 || aFactoryShortName == "swreport"
434 || aFactoryShortName == "swform" )
435 aFactoryShortName = "sdatabase";
436 else if ( aFactoryShortName == "sbibliography"
437 || aFactoryShortName == "sabpilot"
438 || aFactoryShortName == "scanner"
439 || aFactoryShortName == "spropctrlr"
440 || aFactoryShortName == "StartModule" )
441 aFactoryShortName.clear();
443 return aFactoryShortName;
447 OUString SfxHelp::GetHelpModuleName_Impl(std::u16string_view rHelpID)
449 OUString aFactoryShortName;
451 //rhbz#1438876 detect preferred module for this help id, e.g. csv dialog
452 //for calc import before any toplevel is created and so context is
453 //otherwise unknown. Cosmetic, same help is shown in any case because its
454 //in the shared section, but title bar would state "Writer" when context is
455 //expected to be "Calc"
456 std::u16string_view sRemainder;
457 if (o3tl::starts_with(rHelpID, u"modules/", &sRemainder))
459 std::size_t nEndModule = sRemainder.find(u'/');
460 aFactoryShortName = nEndModule != std::u16string_view::npos
461 ? sRemainder.substr(0, nEndModule) : sRemainder;
464 if (aFactoryShortName.isEmpty())
466 OUString aModuleIdentifier = getCurrentModuleIdentifier_Impl();
467 if (!aModuleIdentifier.isEmpty())
471 Reference < XModuleManager2 > xModuleManager(
472 ModuleManager::create(::comphelper::getProcessComponentContext()) );
473 Sequence< PropertyValue > lProps;
474 xModuleManager->getByName( aModuleIdentifier ) >>= lProps;
475 auto pProp = std::find_if(std::cbegin(lProps), std::cend(lProps),
476 [](const PropertyValue& rProp) { return rProp.Name == "ooSetupFactoryShortName"; });
477 if (pProp != std::cend(lProps))
478 pProp->Value >>= aFactoryShortName;
480 catch (const Exception&)
482 TOOLS_WARN_EXCEPTION( "sfx.appl", "SfxHelp::GetHelpModuleName_Impl()" );
487 if (!aFactoryShortName.isEmpty())
488 aFactoryShortName = MapModuleIdentifier(aFactoryShortName);
489 if (aFactoryShortName.isEmpty())
490 aFactoryShortName = getDefaultModule_Impl();
492 return aFactoryShortName;
495 OUString SfxHelp::CreateHelpURL_Impl( const OUString& aCommandURL, const OUString& rModuleName )
497 // build up the help URL
498 OUStringBuffer aHelpURL("vnd.sun.star.help://");
499 bool bHasAnchor = false;
500 OUString aAnchor;
502 OUString aModuleName( rModuleName );
503 if (aModuleName.isEmpty())
504 aModuleName = getDefaultModule_Impl();
506 aHelpURL.append(aModuleName);
508 if ( aCommandURL.isEmpty() )
509 aHelpURL.append("/start");
510 else
512 aHelpURL.append("/" +
513 rtl::Uri::encode(aCommandURL,
514 rtl_UriCharClassRelSegment,
515 rtl_UriEncodeKeepEscapes,
516 RTL_TEXTENCODING_UTF8));
518 OUStringBuffer aTempURL = aHelpURL;
519 AppendConfigToken( aTempURL, true );
520 bHasAnchor = GetHelpAnchor_Impl(aTempURL, aAnchor);
523 AppendConfigToken( aHelpURL, true );
525 if ( bHasAnchor )
526 aHelpURL.append("#" + aAnchor);
528 return aHelpURL.makeStringAndClear();
531 static SfxHelpWindow_Impl* impl_createHelp(Reference< XFrame2 >& rHelpTask ,
532 Reference< XFrame >& rHelpContent)
534 Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
536 // otherwise - create new help task
537 Reference< XFrame2 > xHelpTask(
538 xDesktop->findFrame( u"OFFICE_HELP_TASK"_ustr, FrameSearchFlag::TASKS | FrameSearchFlag::CREATE),
539 UNO_QUERY);
540 if (!xHelpTask.is())
541 return nullptr;
543 // create all internal windows and sub frames ...
544 Reference< css::awt::XWindow > xParentWindow = xHelpTask->getContainerWindow();
545 VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow( xParentWindow );
546 VclPtrInstance<SfxHelpWindow_Impl> pHelpWindow( xHelpTask, pParentWindow );
547 Reference< css::awt::XWindow > xHelpWindow = VCLUnoHelper::GetInterface( pHelpWindow );
549 Reference< XFrame > xHelpContent;
550 if (xHelpTask->setComponent( xHelpWindow, Reference< XController >() ))
552 // Customize UI ...
553 xHelpTask->setName(u"OFFICE_HELP_TASK"_ustr);
555 Reference< XPropertySet > xProps(xHelpTask, UNO_QUERY);
556 if (xProps.is())
557 xProps->setPropertyValue(
558 u"Title"_ustr,
559 Any(SfxResId(STR_HELP_WINDOW_TITLE)));
561 pHelpWindow->setContainerWindow( xParentWindow );
562 xParentWindow->setVisible(true);
563 xHelpWindow->setVisible(true);
565 // This sub frame is created internally (if we called new SfxHelpWindow_Impl() ...)
566 // It should exist :-)
567 xHelpContent = xHelpTask->findFrame(u"OFFICE_HELP"_ustr, FrameSearchFlag::CHILDREN);
570 if (!xHelpContent.is())
572 pHelpWindow.disposeAndClear();
573 return nullptr;
576 xHelpContent->setName(u"OFFICE_HELP"_ustr);
578 rHelpTask = std::move(xHelpTask);
579 rHelpContent = std::move(xHelpContent);
580 return pHelpWindow;
583 OUString SfxHelp::GetHelpText(const OUString& aCommandURL)
585 OUString sModuleName = GetHelpModuleName_Impl(aCommandURL);
586 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(aCommandURL, getCurrentModuleIdentifier_Impl());
587 OUString sRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
588 OUString sHelpText = SfxHelp_Impl::GetHelpText( sRealCommand.isEmpty() ? aCommandURL : sRealCommand, sModuleName );
590 // add some debug information?
591 if ( bIsDebug )
593 sHelpText += "\n-------------\n" +
594 sModuleName + ": " + aCommandURL;
597 return sHelpText;
600 OUString SfxHelp::GetURLHelpText(std::u16string_view aURL)
602 // hyperlinks are handled differently in Online
603 if (comphelper::LibreOfficeKit::isActive())
604 return OUString();
606 bool bCtrlClickHlink = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink);
608 // "ctrl-click to follow link:" for not MacOS
609 // "⌘-click to follow link:" for MacOs
610 vcl::KeyCode aCode(KEY_SPACE);
611 vcl::KeyCode aModifiedCode(KEY_SPACE, KEY_MOD1);
612 OUString aModStr(aModifiedCode.GetName());
613 aModStr = aModStr.replaceFirst(aCode.GetName(), "");
614 aModStr = aModStr.replaceAll("+", "");
615 OUString aHelpStr
616 = bCtrlClickHlink ? SfxResId(STR_CTRLCLICKHYPERLINK) : SfxResId(STR_CLICKHYPERLINK);
617 aHelpStr = aHelpStr.replaceFirst("%{key}", aModStr);
618 aHelpStr = aHelpStr.replaceFirst("%{link}", aURL);
619 return aHelpStr;
622 void SfxHelp::SearchKeyword( const OUString& rKeyword )
624 Start_Impl(OUString(), static_cast<weld::Widget*>(nullptr), rKeyword);
627 bool SfxHelp::Start( const OUString& rURL, const vcl::Window* pWindow )
629 if (bLaunchingHelp)
630 return true;
631 bLaunchingHelp = true;
632 bool bRet = Start_Impl( rURL, pWindow );
633 bLaunchingHelp = false;
634 return bRet;
637 bool SfxHelp::Start(const OUString& rURL, weld::Widget* pWidget)
639 if (bLaunchingHelp)
640 return true;
641 bLaunchingHelp = true;
642 bool bRet = Start_Impl(rURL, pWidget, OUString());
643 bLaunchingHelp = false;
644 return bRet;
647 /// Redirect the vnd.sun.star.help:// urls to http://help.libreoffice.org
648 static bool impl_showOnlineHelp(const OUString& rURL, weld::Widget* pDialogParent)
650 static constexpr OUString aInternal(u"vnd.sun.star.help://"_ustr);
651 if ( rURL.getLength() <= aInternal.getLength() || !rURL.startsWith(aInternal) )
652 return false;
654 OUString aHelpLink = officecfg::Office::Common::Help::HelpRootURL::get();
655 OUString aTarget = OUString::Concat("Target=") + rURL.subView(aInternal.getLength());
656 aTarget = aTarget.replaceAll("%2F", "/").replaceAll("?", "&");
657 aHelpLink += aTarget;
659 if (comphelper::LibreOfficeKit::isActive())
661 if(SfxViewShell* pViewShell = SfxViewShell::Current())
663 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED,
664 aHelpLink.toUtf8());
665 return true;
667 else if (GetpApp())
669 GetpApp()->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED,
670 aHelpLink.toUtf8());
671 return true;
674 return false;
679 #ifdef MACOSX
680 LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault,
681 CFStringCreateWithCString(kCFAllocatorDefault,
682 aHelpLink.toUtf8().getStr(),
683 kCFStringEncodingUTF8),
684 nullptr),
685 nullptr);
686 (void)pDialogParent;
687 #else
688 sfx2::openUriExternally(aHelpLink, false, pDialogParent);
689 #endif
690 return true;
692 catch (const Exception&)
695 return false;
698 namespace {
700 bool rewriteFlatpakHelpRootUrl(OUString * helpRootUrl) {
701 assert(helpRootUrl != nullptr);
702 //TODO: this function for now assumes that the passed-in *helpRootUrl references
703 // /app/libreoffice/help (which belongs to the org.libreoffice.LibreOffice.Help
704 // extension); it replaces it with the corresponding file URL as seen outside the flatpak
705 // sandbox:
706 struct Failure: public std::exception {};
707 try {
708 static auto const url = [] {
709 // From /.flatpak-info [Instance] section, read
710 // app-path=<path>
711 // app-extensions=...;org.libreoffice.LibreOffice.Help=<sha>;...
712 // lines:
713 osl::File ini(u"file:///.flatpak-info"_ustr);
714 auto err = ini.open(osl_File_OpenFlag_Read);
715 if (err != osl::FileBase::E_None) {
716 SAL_WARN("sfx.appl", "LIBO_FLATPAK mode failure opening /.flatpak-info: " << err);
717 throw Failure();
719 OUString path;
720 OUString extensions;
721 bool havePath = false;
722 bool haveExtensions = false;
723 for (bool instance = false; !(havePath && haveExtensions);) {
724 rtl::ByteSequence bytes;
725 err = ini.readLine(bytes);
726 if (err != osl::FileBase::E_None) {
727 SAL_WARN(
728 "sfx.appl",
729 "LIBO_FLATPAK mode reading /.flatpak-info fails with " << err
730 << " before [Instance] app-path");
731 throw Failure();
733 std::string_view const line(
734 reinterpret_cast<char const *>(bytes.getConstArray()), bytes.getLength());
735 if (instance) {
736 static constexpr auto keyPath = std::string_view("app-path=");
737 static constexpr auto keyExtensions = std::string_view("app-extensions=");
738 if (!havePath && line.length() >= keyPath.size()
739 && line.substr(0, keyPath.size()) == keyPath.data())
741 auto const value = line.substr(keyPath.size());
742 if (!rtl_convertStringToUString(
743 &path.pData, value.data(), value.length(),
744 osl_getThreadTextEncoding(),
745 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
746 | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
747 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
749 SAL_WARN(
750 "sfx.appl",
751 "LIBO_FLATPAK mode failure converting app-path \"" << value
752 << "\" encoding");
753 throw Failure();
755 havePath = true;
756 } else if (!haveExtensions && line.length() >= keyExtensions.size()
757 && line.substr(0, keyExtensions.size()) == keyExtensions.data())
759 auto const value = line.substr(keyExtensions.size());
760 if (!rtl_convertStringToUString(
761 &extensions.pData, value.data(), value.length(),
762 osl_getThreadTextEncoding(),
763 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR
764 | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
765 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR)))
767 SAL_WARN(
768 "sfx.appl",
769 "LIBO_FLATPAK mode failure converting app-extensions \"" << value
770 << "\" encoding");
771 throw Failure();
773 haveExtensions = true;
774 } else if (line.length() > 0 && line[0] == '[') {
775 SAL_WARN(
776 "sfx.appl",
777 "LIBO_FLATPAK mode /.flatpak-info lacks [Instance] app-path and"
778 " app-extensions");
779 throw Failure();
781 } else if (line == "[Instance]") {
782 instance = true;
785 ini.close();
786 // Extract <sha> from ...;org.libreoffice.LibreOffice.Help=<sha>;...:
787 std::u16string_view sha;
788 for (sal_Int32 i = 0;;) {
789 OUString elem = extensions.getToken(0, ';', i);
790 if (elem.startsWith("org.libreoffice.LibreOffice.Help=", &sha)) {
791 break;
793 if (i == -1) {
794 SAL_WARN(
795 "sfx.appl",
796 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-extensions \""
797 << extensions << "\" org.libreoffice.LibreOffice.Help");
798 throw Failure();
801 // Assuming that <path> is of the form
802 // /.../app/org.libreoffice.LibreOffice/<arch>/<branch>/<sha'>/files
803 // rewrite it as
804 // /.../runtime/org.libreoffice.LibreOffice.Help/<arch>/<branch>/<sha>/files
805 // because the extension's files are stored at a different place than the app's files,
806 // so use this hack until flatpak itself provides a better solution:
807 static constexpr OUString segments = u"/app/org.libreoffice.LibreOffice/"_ustr;
808 auto const i1 = path.lastIndexOf(segments);
809 // use lastIndexOf instead of indexOf, in case the user-controlled prefix /.../
810 // happens to contain such segments
811 if (i1 == -1) {
812 SAL_WARN(
813 "sfx.appl",
814 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
815 << "\" doesn't contain /app/org.libreoffice.LibreOffice/");
816 throw Failure();
818 auto const i2 = i1 + segments.getLength();
819 auto i3 = path.indexOf('/', i2);
820 if (i3 == -1) {
821 SAL_WARN(
822 "sfx.appl",
823 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
824 << "\" doesn't contain branch segment");
825 throw Failure();
827 i3 = path.indexOf('/', i3 + 1);
828 if (i3 == -1) {
829 SAL_WARN(
830 "sfx.appl",
831 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
832 << "\" doesn't contain sha segment");
833 throw Failure();
835 ++i3;
836 auto const i4 = path.indexOf('/', i3);
837 if (i4 == -1) {
838 SAL_WARN(
839 "sfx.appl",
840 "LIBO_FLATPAK mode /.flatpak-info [Instance] app-path \"" << path
841 << "\" doesn't contain files segment");
842 throw Failure();
844 path = path.subView(0, i1) + OUString::Concat("/runtime/org.libreoffice.LibreOffice.Help/")
845 + path.subView(i2, i3 - i2) + sha + path.subView(i4);
846 // Turn <path> into a file URL:
847 OUString url_;
848 err = osl::FileBase::getFileURLFromSystemPath(path, url_);
849 if (err != osl::FileBase::E_None) {
850 SAL_WARN(
851 "sfx.appl",
852 "LIBO_FLATPAK mode failure converting app-path \"" << path << "\" to URL: "
853 << err);
854 throw Failure();
856 return url_;
857 }();
858 *helpRootUrl = url;
859 return true;
860 } catch (Failure &) {
861 return false;
867 // add <noscript> meta for browsers without javascript
869 constexpr OUStringLiteral SHTML1 = u"<!DOCTYPE HTML><html lang=\"en-US\"><head><meta charset=\"UTF-8\">";
870 constexpr OUStringLiteral SHTML2 = u"<noscript><meta http-equiv=\"refresh\" content=\"0; url='";
871 constexpr OUStringLiteral SHTML3 = u"/noscript.html'\"></noscript><meta http-equiv=\"refresh\" content=\"1; url='";
872 constexpr OUStringLiteral SHTML4 = u"'\"><script type=\"text/javascript\"> window.location.href = \"";
873 constexpr OUStringLiteral SHTML5 = u"\";</script><title>Help Page Redirection</title></head><body></body></html>";
875 // use a tempfile since e.g. xdg-open doesn't support URL-parameters with file:// URLs
876 static bool impl_showOfflineHelp(const OUString& rURL, weld::Widget* pDialogParent)
878 OUString aBaseInstallPath = getHelpRootURL();
879 // For the flatpak case, find the pathname outside the flatpak sandbox that corresponds to
880 // aBaseInstallPath, because that is what needs to be stored in aTempFile below:
881 if (flatpak::isFlatpak() && !rewriteFlatpakHelpRootUrl(&aBaseInstallPath)) {
882 return false;
885 OUString aHelpLink( aBaseInstallPath + "/index.html?" );
886 OUString aTarget = OUString::Concat("Target=") + rURL.subView(RTL_CONSTASCII_LENGTH("vnd.sun.star.help://"));
887 aTarget = aTarget.replaceAll("%2F","/").replaceAll("?","&");
888 aHelpLink += aTarget;
890 // Get a html tempfile (for the flatpak case, create it in XDG_CACHE_HOME instead of /tmp for
891 // technical reasons, so that it can be accessed by the browser running outside the sandbox):
892 static constexpr OUStringLiteral aExtension(u".html");
893 OUString * parent = nullptr;
894 if (flatpak::isFlatpak() && !flatpak::createTemporaryHtmlDirectory(&parent)) {
895 return false;
897 ::utl::TempFileNamed aTempFile(u"NewHelp", true, aExtension, parent, false );
899 SvStream* pStream = aTempFile.GetStream(StreamMode::WRITE);
900 pStream->SetStreamCharSet(RTL_TEXTENCODING_UTF8);
902 OUString aTempStr = SHTML1 + SHTML2 +
903 aBaseInstallPath + "/" + HelpLocaleString() + SHTML3 +
904 aHelpLink + SHTML4 +
905 aHelpLink + SHTML5;
907 pStream->WriteUnicodeOrByteText(aTempStr);
909 aTempFile.CloseStream();
912 #ifdef MACOSX
913 LSOpenCFURLRef(CFURLCreateWithString(kCFAllocatorDefault,
914 CFStringCreateWithCString(kCFAllocatorDefault,
915 aTempFile.GetURL().toUtf8().getStr(),
916 kCFStringEncodingUTF8),
917 nullptr),
918 nullptr);
919 (void)pDialogParent;
920 #else
921 sfx2::openUriExternally(aTempFile.GetURL(), false, pDialogParent);
922 #endif
923 return true;
925 catch (const Exception&)
928 aTempFile.EnableKillingFile();
929 return false;
932 namespace
934 // tdf#119579 skip floating windows as potential parent for missing help dialog
935 const vcl::Window* GetBestParent(const vcl::Window* pWindow)
937 while (pWindow)
939 if (pWindow->IsSystemWindow() && pWindow->GetType() != WindowType::FLOATINGWINDOW)
940 break;
941 pWindow = pWindow->GetParent();
943 return pWindow;
947 namespace {
949 class HelpManualMessage : public weld::MessageDialogController
951 private:
952 std::unique_ptr<weld::LinkButton> m_xDownloadInfo;
953 std::unique_ptr<weld::CheckButton> m_xHideOfflineHelpCB;
955 DECL_LINK(DownloadClickHdl, weld::LinkButton&, bool);
956 public:
957 HelpManualMessage(weld::Widget* pParent)
958 : MessageDialogController(pParent, u"sfx/ui/helpmanual.ui"_ustr, u"onlinehelpmanual"_ustr, u"box"_ustr)
959 , m_xDownloadInfo(m_xBuilder->weld_link_button(u"downloadinfo"_ustr))
960 , m_xHideOfflineHelpCB(m_xBuilder->weld_check_button(u"hidedialog"_ustr))
962 LanguageType aLangType = Application::GetSettings().GetUILanguageTag().getLanguageType();
963 OUString sLocaleString = SvtLanguageTable::GetLanguageString(aLangType);
964 OUString sPrimText = get_primary_text();
965 set_primary_text(sPrimText.replaceAll("$UILOCALE", sLocaleString));
967 m_xDownloadInfo->connect_activate_link(LINK(this, HelpManualMessage, DownloadClickHdl));
970 bool GetOfflineHelpPopUp() const { return !m_xHideOfflineHelpCB->get_active(); }
973 IMPL_LINK(HelpManualMessage, DownloadClickHdl, weld::LinkButton&, /* rButton */, bool)
975 m_xDialog->response(RET_YES);
976 return true;
981 bool SfxHelp::Start_Impl(const OUString& rURL, const vcl::Window* pWindow)
983 OUStringBuffer aHelpRootURL("vnd.sun.star.help://");
984 AppendConfigToken(aHelpRootURL, true);
985 SfxContentHelper::GetResultSet(aHelpRootURL.makeStringAndClear());
987 /* rURL may be
988 * - a "real" URL
989 * - a HelpID (formerly a long, now a string)
990 * If rURL is a URL, CreateHelpURL should be called for this URL
991 * If rURL is an arbitrary string, the same should happen, but the URL should be tried out
992 * if it delivers real help content. In case only the Help Error Document is returned, the
993 * parent of the window for that help was called, is asked for its HelpID.
994 * For compatibility reasons this upward search is not implemented for "real" URLs.
995 * Help keyword search now is implemented as own method; in former versions it
996 * was done via Help::Start, but this implementation conflicted with the upward search.
998 OUString aHelpURL;
999 INetURLObject aParser( rURL );
1000 INetProtocol nProtocol = aParser.GetProtocol();
1002 switch ( nProtocol )
1004 case INetProtocol::VndSunStarHelp:
1005 // already a vnd.sun.star.help URL -> nothing to do
1006 aHelpURL = rURL;
1007 break;
1008 default:
1010 OUString aHelpModuleName(GetHelpModuleName_Impl(rURL));
1011 OUString aRealCommand;
1013 if ( nProtocol == INetProtocol::Uno )
1015 // Command can be just an alias to another command.
1016 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rURL, getCurrentModuleIdentifier_Impl());
1017 aRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
1020 // no URL, just a HelpID (maybe empty in case of keyword search)
1021 aHelpURL = CreateHelpURL_Impl( aRealCommand.isEmpty() ? rURL : aRealCommand, aHelpModuleName );
1023 if ( impl_hasHelpInstalled() && pWindow && SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1025 // no help found -> try with parent help id.
1026 vcl::Window* pParent = pWindow->GetParent();
1027 while ( pParent )
1029 OUString aHelpId = pParent->GetHelpId();
1030 aHelpURL = CreateHelpURL( aHelpId, aHelpModuleName );
1032 if ( !SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1034 break;
1036 else
1038 pParent = pParent->GetParent();
1039 if (!pParent)
1041 // create help url of start page ( helpid == 0 -> start page)
1042 aHelpURL = CreateHelpURL( OUString(), aHelpModuleName );
1047 break;
1051 pWindow = GetBestParent(pWindow);
1052 weld::Window* pWeldWindow = pWindow ? pWindow->GetFrameWeld() : nullptr;
1054 if ( comphelper::LibreOfficeKit::isActive() )
1056 impl_showOnlineHelp(aHelpURL, pWeldWindow);
1057 return true;
1059 #ifdef MACOSX
1060 if (@available(macOS 10.14, *)) {
1061 // Workaround: Safari sandboxing prevents it from accessing files in the LibreOffice.app folder
1062 // force online-help instead if Safari is default browser.
1063 CFURLRef pBrowser = LSCopyDefaultApplicationURLForURL(
1064 CFURLCreateWithString(
1065 kCFAllocatorDefault,
1066 static_cast<CFStringRef>(@"https://www.libreoffice.org"),
1067 nullptr),
1068 kLSRolesAll, nullptr);
1069 if([static_cast<NSString*>(CFURLGetString(pBrowser)) hasSuffix:@"/Applications/Safari.app/"]) {
1070 impl_showOnlineHelp(aHelpURL, pWeldWindow);
1071 return true;
1074 #endif
1076 // If the HTML or no help is installed, but aHelpURL nevertheless references valid help content,
1077 // that implies that this help content belongs to an extension (and thus would not be available
1078 // in neither the offline nor online HTML help); in that case, fall through to the "old-help to
1079 // display" code below:
1080 if (SfxContentHelper::IsHelpErrorDocument(aHelpURL))
1082 if ( impl_hasHTMLHelpInstalled() && impl_showOfflineHelp(aHelpURL, pWeldWindow) )
1084 return true;
1087 if ( !impl_hasHelpInstalled() )
1089 bool bShowOfflineHelpPopUp = officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::get();
1090 short retOnlineHelpBox = RET_CLOSE;
1092 TopLevelWindowLocker aBusy;
1094 if(bShowOfflineHelpPopUp)
1096 aBusy.incBusy(pWeldWindow);
1097 HelpManualMessage aQueryBox(pWeldWindow);
1098 retOnlineHelpBox = aQueryBox.run();
1099 auto xChanges = comphelper::ConfigurationChanges::create();
1100 officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::set(aQueryBox.GetOfflineHelpPopUp(), xChanges);
1101 xChanges->commit();
1102 aBusy.decBusy();
1104 // Checks whether the user clicked "Read Help Online" (RET_OK) or "Information on downloading offline help" (RET_YES)
1105 if(!bShowOfflineHelpPopUp || retOnlineHelpBox == RET_OK || retOnlineHelpBox == RET_YES)
1107 bool bTopicExists;
1109 if (!bShowOfflineHelpPopUp || retOnlineHelpBox == RET_OK)
1111 bTopicExists = impl_showOnlineHelp(aHelpURL, pWeldWindow);
1113 else
1115 // Opens the help page that explains how to install offline help
1116 OUString aOfflineHelpURL(CreateHelpURL_Impl(HID_HELPMANUAL_OFFLINE, u"shared"_ustr));
1117 impl_showOnlineHelp(aOfflineHelpURL, pWeldWindow);
1118 bTopicExists = true;
1121 if (!bTopicExists)
1123 aBusy.incBusy(pWeldWindow);
1124 NoHelpErrorBox aErrBox(pWeldWindow);
1125 aErrBox.run();
1126 aBusy.decBusy();
1127 return false;
1129 else
1131 return true;
1134 else
1136 return false;
1141 // old-help to display
1142 Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
1144 // check if help window is still open
1145 // If not, create a new one and return access directly to the internal sub frame showing the help content
1146 // search must be done here; search one desktop level could return an arbitrary frame
1147 Reference< XFrame2 > xHelp(
1148 xDesktop->findFrame( u"OFFICE_HELP_TASK"_ustr, FrameSearchFlag::CHILDREN),
1149 UNO_QUERY);
1150 Reference< XFrame > xHelpContent = xDesktop->findFrame(
1151 u"OFFICE_HELP"_ustr,
1152 FrameSearchFlag::CHILDREN);
1154 SfxHelpWindow_Impl* pHelpWindow = nullptr;
1155 if (!xHelp.is())
1156 pHelpWindow = impl_createHelp(xHelp, xHelpContent);
1157 else
1158 pHelpWindow = static_cast<SfxHelpWindow_Impl*>(VCLUnoHelper::GetWindow(xHelp->getComponentWindow()));
1159 if (!xHelp.is() || !xHelpContent.is() || !pHelpWindow)
1160 return false;
1162 SAL_INFO("sfx.appl", "HelpId = " << aHelpURL);
1164 pHelpWindow->SetHelpURL( aHelpURL );
1165 pHelpWindow->loadHelpContent(aHelpURL);
1167 Reference < css::awt::XTopWindow > xTopWindow( xHelp->getContainerWindow(), UNO_QUERY );
1168 if ( xTopWindow.is() )
1169 xTopWindow->toFront();
1171 return true;
1174 bool SfxHelp::Start_Impl(const OUString& rURL, weld::Widget* pWidget, const OUString& rKeyword)
1176 OUStringBuffer aHelpRootURL("vnd.sun.star.help://");
1177 AppendConfigToken(aHelpRootURL, true);
1178 SfxContentHelper::GetResultSet(aHelpRootURL.makeStringAndClear());
1180 /* rURL may be
1181 * - a "real" URL
1182 * - a HelpID (formerly a long, now a string)
1183 * If rURL is a URL, CreateHelpURL should be called for this URL
1184 * If rURL is an arbitrary string, the same should happen, but the URL should be tried out
1185 * if it delivers real help content. In case only the Help Error Document is returned, the
1186 * parent of the window for that help was called, is asked for its HelpID.
1187 * For compatibility reasons this upward search is not implemented for "real" URLs.
1188 * Help keyword search now is implemented as own method; in former versions it
1189 * was done via Help::Start, but this implementation conflicted with the upward search.
1191 OUString aHelpURL;
1192 INetURLObject aParser( rURL );
1193 INetProtocol nProtocol = aParser.GetProtocol();
1195 switch ( nProtocol )
1197 case INetProtocol::VndSunStarHelp:
1198 // already a vnd.sun.star.help URL -> nothing to do
1199 aHelpURL = rURL;
1200 break;
1201 default:
1203 OUString aHelpModuleName(GetHelpModuleName_Impl(rURL));
1204 OUString aRealCommand;
1206 if ( nProtocol == INetProtocol::Uno )
1208 // Command can be just an alias to another command.
1209 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(rURL, getCurrentModuleIdentifier_Impl());
1210 aRealCommand = vcl::CommandInfoProvider::GetRealCommandForCommand(aProperties);
1213 // no URL, just a HelpID (maybe empty in case of keyword search)
1214 aHelpURL = CreateHelpURL_Impl( aRealCommand.isEmpty() ? rURL : aRealCommand, aHelpModuleName );
1216 if ( impl_hasHelpInstalled() && pWidget && SfxContentHelper::IsHelpErrorDocument( aHelpURL ) )
1218 bool bUseFinalFallback = true;
1219 // no help found -> try ids of parents.
1220 pWidget->help_hierarchy_foreach([&aHelpModuleName, &aHelpURL, &bUseFinalFallback](const OUString& rHelpId){
1221 if (rHelpId.isEmpty())
1222 return false;
1223 aHelpURL = CreateHelpURL(rHelpId, aHelpModuleName);
1224 bool bFinished = !SfxContentHelper::IsHelpErrorDocument(aHelpURL);
1225 if (bFinished)
1226 bUseFinalFallback = false;
1227 return bFinished;
1230 if (bUseFinalFallback)
1232 // create help url of start page ( helpid == 0 -> start page)
1233 aHelpURL = CreateHelpURL( OUString(), aHelpModuleName );
1236 break;
1240 if ( comphelper::LibreOfficeKit::isActive() )
1242 impl_showOnlineHelp(aHelpURL, pWidget);
1243 return true;
1245 #ifdef MACOSX
1246 if (@available(macOS 10.14, *)) {
1247 // Workaround: Safari sandboxing prevents it from accessing files in the LibreOffice.app folder
1248 // force online-help instead if Safari is default browser.
1249 CFURLRef pBrowser = LSCopyDefaultApplicationURLForURL(
1250 CFURLCreateWithString(
1251 kCFAllocatorDefault,
1252 static_cast<CFStringRef>(@"https://www.libreoffice.org"),
1253 nullptr),
1254 kLSRolesAll, nullptr);
1255 if([static_cast<NSString*>(CFURLGetString(pBrowser)) hasSuffix:@"/Applications/Safari.app/"]) {
1256 impl_showOnlineHelp(aHelpURL, pWidget);
1257 return true;
1260 #endif
1262 // If the HTML or no help is installed, but aHelpURL nevertheless references valid help content,
1263 // that implies that help content belongs to an extension (and thus would not be available
1264 // in neither the offline nor online HTML help); in that case, fall through to the "old-help to
1265 // display" code below:
1266 if (SfxContentHelper::IsHelpErrorDocument(aHelpURL))
1268 if ( impl_hasHTMLHelpInstalled() && impl_showOfflineHelp(aHelpURL, pWidget) )
1270 return true;
1273 if ( !impl_hasHelpInstalled() )
1275 bool bShowOfflineHelpPopUp = officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::get();
1276 short retOnlineHelpBox = RET_CLOSE;
1278 TopLevelWindowLocker aBusy;
1280 if(bShowOfflineHelpPopUp)
1282 aBusy.incBusy(pWidget);
1283 HelpManualMessage aQueryBox(pWidget);
1284 retOnlineHelpBox = aQueryBox.run();
1285 auto xChanges = comphelper::ConfigurationChanges::create();
1286 officecfg::Office::Common::Help::BuiltInHelpNotInstalledPopUp::set(aQueryBox.GetOfflineHelpPopUp(), xChanges);
1287 xChanges->commit();
1288 aBusy.decBusy();
1290 // Checks whether the user clicked "Read Help Online" (RET_OK) or "Information on downloading offline help" (RET_YES)
1291 if(!bShowOfflineHelpPopUp || retOnlineHelpBox == RET_OK || retOnlineHelpBox == RET_YES)
1293 bool bTopicExists;
1295 if (!bShowOfflineHelpPopUp || retOnlineHelpBox == RET_OK)
1297 bTopicExists = impl_showOnlineHelp(aHelpURL, pWidget);
1299 else
1301 // Opens the help page that explains how to install offline help
1302 OUString aOfflineHelpURL(CreateHelpURL_Impl(HID_HELPMANUAL_OFFLINE, u"shared"_ustr));
1303 impl_showOnlineHelp(aOfflineHelpURL, pWidget);
1304 bTopicExists = true;
1307 if (!bTopicExists)
1309 aBusy.incBusy(pWidget);
1310 NoHelpErrorBox aErrBox(pWidget);
1311 aErrBox.run();
1312 aBusy.decBusy();
1313 return false;
1315 else
1317 return true;
1320 else
1322 return false;
1327 // old-help to display
1328 Reference < XDesktop2 > xDesktop = Desktop::create( ::comphelper::getProcessComponentContext() );
1330 // check if help window is still open
1331 // If not, create a new one and return access directly to the internal sub frame showing the help content
1332 // search must be done here; search one desktop level could return an arbitrary frame
1333 Reference< XFrame2 > xHelp(
1334 xDesktop->findFrame( u"OFFICE_HELP_TASK"_ustr, FrameSearchFlag::CHILDREN),
1335 UNO_QUERY);
1336 Reference< XFrame > xHelpContent = xDesktop->findFrame(
1337 u"OFFICE_HELP"_ustr,
1338 FrameSearchFlag::CHILDREN);
1340 SfxHelpWindow_Impl* pHelpWindow = nullptr;
1341 if (!xHelp.is())
1342 pHelpWindow = impl_createHelp(xHelp, xHelpContent);
1343 else
1344 pHelpWindow = static_cast<SfxHelpWindow_Impl*>(VCLUnoHelper::GetWindow(xHelp->getComponentWindow()));
1345 if (!xHelp.is() || !xHelpContent.is() || !pHelpWindow)
1346 return false;
1348 SAL_INFO("sfx.appl", "HelpId = " << aHelpURL);
1350 pHelpWindow->SetHelpURL( aHelpURL );
1351 pHelpWindow->loadHelpContent(aHelpURL);
1352 if (!rKeyword.isEmpty())
1353 pHelpWindow->OpenKeyword( rKeyword );
1355 Reference < css::awt::XTopWindow > xTopWindow( xHelp->getContainerWindow(), UNO_QUERY );
1356 if ( xTopWindow.is() )
1357 xTopWindow->toFront();
1359 return true;
1362 OUString SfxHelp::CreateHelpURL(const OUString& aCommandURL, const OUString& rModuleName)
1364 SfxHelp* pHelp = static_cast< SfxHelp* >(Application::GetHelp());
1365 return pHelp ? SfxHelp::CreateHelpURL_Impl( aCommandURL, rModuleName ) : OUString();
1368 OUString SfxHelp::GetDefaultHelpModule()
1370 return getDefaultModule_Impl();
1373 OUString SfxHelp::GetCurrentModuleIdentifier()
1375 return getCurrentModuleIdentifier_Impl();
1378 bool SfxHelp::IsHelpInstalled()
1380 return impl_hasHelpInstalled();
1383 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */