1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <sfx2/sfxhelp.hxx>
24 #include <com/sun/star/uno/Reference.h>
25 #include <com/sun/star/frame/Desktop.hpp>
26 #include <com/sun/star/frame/XFrame2.hpp>
27 #include <com/sun/star/frame/XComponentLoader.hpp>
28 #include <com/sun/star/lang/XComponent.hpp>
29 #include <comphelper/processfactory.hxx>
30 #include <com/sun/star/awt/XWindow.hpp>
31 #include <com/sun/star/awt/XTopWindow.hpp>
32 #include <com/sun/star/awt/PosSize.hpp>
33 #include <com/sun/star/frame/XDesktop.hpp>
34 #include <com/sun/star/util/URLTransformer.hpp>
35 #include <com/sun/star/util/XURLTransformer.hpp>
36 #include <com/sun/star/frame/XDispatch.hpp>
37 #include <com/sun/star/frame/XDispatchProvider.hpp>
38 #include <com/sun/star/beans/XPropertySet.hpp>
39 #include <com/sun/star/frame/FrameSearchFlag.hpp>
40 #include <toolkit/helper/vclunohelper.hxx>
41 #include <com/sun/star/frame/ModuleManager.hpp>
42 #include <com/sun/star/system/SystemShellExecute.hpp>
43 #include <com/sun/star/system/SystemShellExecuteFlags.hpp>
44 #include <unotools/configmgr.hxx>
45 #include <unotools/configitem.hxx>
46 #include <svtools/helpopt.hxx>
47 #include <unotools/moduleoptions.hxx>
48 #include <tools/urlobj.hxx>
49 #include <ucbhelper/content.hxx>
50 #include <unotools/pathoptions.hxx>
51 #include <rtl/ustring.hxx>
52 #include <osl/process.h>
53 #include <osl/file.hxx>
54 #include <unotools/bootstrap.hxx>
55 #include <rtl/uri.hxx>
56 #include <vcl/layout.hxx>
57 #include <svtools/ehdl.hxx>
58 #include <svtools/sfxecode.hxx>
60 #include "newhelp.hxx"
61 #include <sfx2/objsh.hxx>
62 #include <sfx2/docfac.hxx>
63 #include <sfx2/sfxresid.hxx>
66 #include <sfx2/sfxuno.hxx>
67 #include <vcl/svapp.hxx>
68 #include <sfx2/frame.hxx>
69 #include <rtl/string.hxx>
71 using namespace ::com::sun::star::beans
;
72 using namespace ::com::sun::star::frame
;
73 using namespace ::com::sun::star::uno
;
74 using namespace ::com::sun::star::util
;
75 using namespace ::com::sun::star::lang
;
76 using namespace ::com::sun::star::system
;
78 class NoHelpErrorBox
: public MessageDialog
81 NoHelpErrorBox( vcl::Window
* _pParent
);
83 virtual void RequestHelp( const HelpEvent
& rHEvt
) SAL_OVERRIDE
;
86 NoHelpErrorBox::NoHelpErrorBox( vcl::Window
* _pParent
)
87 : MessageDialog(_pParent
, SfxResId(RID_STR_HLPFILENOTEXIST
))
89 // Error message: "No help available"
92 void NoHelpErrorBox::RequestHelp( const HelpEvent
& )
94 // do nothing, because no help available
97 static bool impl_hasHelpInstalled( const OUString
&rLang
);
99 /// Return the locale we prefer for displaying help
100 static OUString
HelpLocaleString()
102 static OUString aLocaleStr
;
103 if (aLocaleStr
.isEmpty())
105 const OUString
aEnglish( "en" );
106 // detect installed locale
107 aLocaleStr
= utl::ConfigManager::getLocale();
108 bool bOk
= !aLocaleStr
.isEmpty();
110 aLocaleStr
= aEnglish
;
113 OUString aBaseInstallPath
;
114 utl::Bootstrap::locateBaseInstallation(aBaseInstallPath
);
115 static const char *szHelpPath
= "/help/";
117 OUString sHelpPath
= aBaseInstallPath
+
118 OUString::createFromAscii(szHelpPath
) + aLocaleStr
;
119 osl::DirectoryItem aDirItem
;
121 if (osl::DirectoryItem::get(sHelpPath
, aDirItem
) != osl::FileBase::E_None
)
124 OUString
sLang(aLocaleStr
);
125 sal_Int32 nSepPos
= sLang
.indexOf( '-' );
129 sLang
= sLang
.copy( 0, nSepPos
);
130 sHelpPath
= aBaseInstallPath
+
131 OUString::createFromAscii(szHelpPath
) + sLang
;
132 if (osl::DirectoryItem::get(sHelpPath
, aDirItem
) != osl::FileBase::E_None
)
137 // if not OK, and not even English installed, we use online help, and
138 // have to preserve the full locale name
139 if ( !bOk
&& impl_hasHelpInstalled( aEnglish
) )
140 aLocaleStr
= aEnglish
;
145 void AppendConfigToken( OUStringBuffer
& rURL
, bool bQuestionMark
, const OUString
&rLang
)
147 OUString
aLocaleStr( rLang
);
148 if ( aLocaleStr
.isEmpty() )
149 aLocaleStr
= HelpLocaleString();
151 // query part exists?
153 // no, so start with '?'
156 // yes, so only append with '&'
160 rURL
.append("Language=");
161 rURL
.append(aLocaleStr
);
162 rURL
.append("&System=");
163 rURL
.append(SvtHelpOptions().GetSystem());
164 rURL
.append("&Version=");
165 rURL
.append(utl::ConfigManager::getProductVersion());
168 bool GetHelpAnchor_Impl( const OUString
& _rURL
, OUString
& _rAnchor
)
175 ::ucbhelper::Content
aCnt( INetURLObject( _rURL
).GetMainURL( INetURLObject::NO_DECODE
),
176 Reference
< ::com::sun::star::ucb::XCommandEnvironment
>(),
177 comphelper::getProcessComponentContext() );
178 if ( ( aCnt
.getPropertyValue("AnchorName") >>= sAnchor
) )
181 if ( !sAnchor
.isEmpty() )
189 SAL_WARN( "sfx.appl", "Property 'AnchorName' is missing" );
192 catch (const ::com::sun::star::uno::Exception
&)
202 static OUString
GetHelpText( const OUString
& aCommandURL
, const OUString
& rModule
);
205 OUString
SfxHelp_Impl::GetHelpText( const OUString
& aCommandURL
, const OUString
& rModule
)
208 OUStringBuffer
aHelpURL( SfxHelp::CreateHelpURL( aCommandURL
, rModule
) );
209 // added 'active' parameter
210 sal_Int32 nIndex
= aHelpURL
.lastIndexOf( '#' );
212 nIndex
= aHelpURL
.getLength();
213 aHelpURL
.insert( nIndex
, "&Active=true" );
215 return SfxContentHelper::GetActiveHelpString( aHelpURL
.makeStringAndClear() );
222 // read the environment variable "HELP_DEBUG"
223 // if it's set, you will see debug output on active help
226 OUString
sEnvVarName( "HELP_DEBUG" );
227 osl_getEnvironment( sEnvVarName
.pData
, &sHelpDebug
.pData
);
228 bIsDebug
= !sHelpDebug
.isEmpty();
231 pImp
= new SfxHelp_Impl();
239 OUString
getDefaultModule_Impl()
241 OUString sDefaultModule
;
242 SvtModuleOptions aModOpt
;
243 if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::WRITER
) )
244 sDefaultModule
= "swriter";
245 else if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::CALC
) )
246 sDefaultModule
= "scalc";
247 else if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::IMPRESS
) )
248 sDefaultModule
= "simpress";
249 else if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::DRAW
) )
250 sDefaultModule
= "sdraw";
251 else if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::MATH
) )
252 sDefaultModule
= "smath";
253 else if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::CHART
) )
254 sDefaultModule
= "schart";
255 else if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::BASIC
) )
256 sDefaultModule
= "sbasic";
257 else if ( aModOpt
.IsModuleInstalled( SvtModuleOptions::EModule::DATABASE
) )
258 sDefaultModule
= "sdatabase";
261 SAL_WARN( "sfx.appl", "getDefaultModule_Impl(): no module installed" );
263 return sDefaultModule
;
266 OUString
getCurrentModuleIdentifier_Impl()
268 OUString sIdentifier
;
269 Reference
< XComponentContext
> xContext
= ::comphelper::getProcessComponentContext();
270 Reference
< XModuleManager2
> xModuleManager
= ModuleManager::create(xContext
);
271 Reference
< XDesktop2
> xDesktop
= Desktop::create(xContext
);
272 Reference
< XFrame
> xCurrentFrame
= xDesktop
->getCurrentFrame();
274 if ( xCurrentFrame
.is() )
278 sIdentifier
= xModuleManager
->identify( xCurrentFrame
);
280 catch (const ::com::sun::star::frame::UnknownModuleException
&)
282 DBG_WARNING( "SfxHelp::getCurrentModuleIdentifier_Impl(): unknown module (help in help?)" );
284 catch (const Exception
&)
286 SAL_WARN( "sfx.appl", "SfxHelp::getCurrentModuleIdentifier_Impl(): exception of XModuleManager::identify()" );
293 OUString
SfxHelp::GetHelpModuleName_Impl()
295 OUString aFactoryShortName
;
296 OUString aModuleIdentifier
= getCurrentModuleIdentifier_Impl();
298 if ( !aModuleIdentifier
.isEmpty() )
302 Reference
< XModuleManager2
> xModuleManager(
303 ModuleManager::create(::comphelper::getProcessComponentContext()) );
304 Sequence
< PropertyValue
> lProps
;
305 xModuleManager
->getByName( aModuleIdentifier
) >>= lProps
;
306 for ( sal_Int32 i
= 0; i
< lProps
.getLength(); ++i
)
308 if ( lProps
[i
].Name
== "ooSetupFactoryShortName" )
310 lProps
[i
].Value
>>= aFactoryShortName
;
315 catch (const Exception
&)
317 SAL_WARN( "sfx.appl", "SfxHelp::GetHelpModuleName_Impl(): exception of XNameAccess::getByName()" );
321 OUString sDefaultModule
= getDefaultModule_Impl();
322 if ( !aFactoryShortName
.isEmpty() )
324 // Map some module identifiers to their "real" help module string.
325 if ( aFactoryShortName
== "chart2" )
326 aFactoryShortName
= "schart" ;
327 else if ( aFactoryShortName
== "BasicIDE" )
328 aFactoryShortName
= "sbasic";
329 else if ( aFactoryShortName
== "sweb"
330 || aFactoryShortName
== "sglobal"
331 || aFactoryShortName
== "swxform" )
332 aFactoryShortName
= "swriter" ;
333 else if ( aFactoryShortName
== "dbquery"
334 || aFactoryShortName
== "dbbrowser"
335 || aFactoryShortName
== "dbrelation"
336 || aFactoryShortName
== "dbtable"
337 || aFactoryShortName
== "dbapp"
338 || aFactoryShortName
== "dbreport"
339 || aFactoryShortName
== "swreport"
340 || aFactoryShortName
== "swform" )
341 aFactoryShortName
= "sdatabase";
342 else if ( aFactoryShortName
== "sbibliography"
343 || aFactoryShortName
== "StartModule" )
344 aFactoryShortName
= sDefaultModule
;
347 aFactoryShortName
= sDefaultModule
;
349 return aFactoryShortName
;
352 OUString
SfxHelp::CreateHelpURL_Impl( const OUString
& aCommandURL
, const OUString
& rModuleName
)
354 // build up the help URL
355 OUStringBuffer
aHelpURL("vnd.sun.star.help://");
356 bool bHasAnchor
= false;
359 OUString
aModuleName( rModuleName
);
360 if (aModuleName
.isEmpty())
361 aModuleName
= getDefaultModule_Impl();
363 aHelpURL
.append(aModuleName
);
365 if ( aCommandURL
.isEmpty() )
366 aHelpURL
.append("/start");
369 aHelpURL
.append('/');
370 aHelpURL
.append(rtl::Uri::encode(aCommandURL
,
371 rtl_UriCharClassRelSegment
,
372 rtl_UriEncodeKeepEscapes
,
373 RTL_TEXTENCODING_UTF8
));
375 OUStringBuffer aTempURL
= aHelpURL
;
376 AppendConfigToken( aTempURL
, true );
377 bHasAnchor
= GetHelpAnchor_Impl(aTempURL
.makeStringAndClear(), aAnchor
);
380 AppendConfigToken( aHelpURL
, true );
384 aHelpURL
.append('#');
385 aHelpURL
.append(aAnchor
);
388 return aHelpURL
.makeStringAndClear();
391 SfxHelpWindow_Impl
* impl_createHelp(Reference
< XFrame2
>& rHelpTask
,
392 Reference
< XFrame
>& rHelpContent
)
394 Reference
< XDesktop2
> xDesktop
= Desktop::create( ::comphelper::getProcessComponentContext() );
396 // otherwise - create new help task
397 Reference
< XFrame2
> xHelpTask(
398 xDesktop
->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::TASKS
| FrameSearchFlag::CREATE
),
403 // create all internal windows and sub frames ...
404 Reference
< ::com::sun::star::awt::XWindow
> xParentWindow
= xHelpTask
->getContainerWindow();
405 vcl::Window
* pParentWindow
= VCLUnoHelper::GetWindow( xParentWindow
);
406 VclPtrInstance
<SfxHelpWindow_Impl
> pHelpWindow( xHelpTask
, pParentWindow
, WB_DOCKBORDER
);
407 Reference
< ::com::sun::star::awt::XWindow
> xHelpWindow
= VCLUnoHelper::GetInterface( pHelpWindow
);
409 Reference
< XFrame
> xHelpContent
;
410 if (xHelpTask
->setComponent( xHelpWindow
, Reference
< XController
>() ))
413 xHelpTask
->setName("OFFICE_HELP_TASK");
415 Reference
< XPropertySet
> xProps(xHelpTask
, UNO_QUERY
);
417 xProps
->setPropertyValue(
419 makeAny(SfxResId(STR_HELP_WINDOW_TITLE
).toString()));
421 pHelpWindow
->setContainerWindow( xParentWindow
);
422 xParentWindow
->setVisible(sal_True
);
423 xHelpWindow
->setVisible(sal_True
);
425 // This sub frame is created internally (if we called new SfxHelpWindow_Impl() ...)
426 // It should exist :-)
427 xHelpContent
= xHelpTask
->findFrame(OUString("OFFICE_HELP"), FrameSearchFlag::CHILDREN
);
430 if (!xHelpContent
.is())
432 pHelpWindow
.disposeAndClear();
436 xHelpContent
->setName("OFFICE_HELP");
438 rHelpTask
= xHelpTask
;
439 rHelpContent
= xHelpContent
;
443 OUString
SfxHelp::GetHelpText( const OUString
& aCommandURL
, const vcl::Window
* pWindow
)
445 OUString sModuleName
= GetHelpModuleName_Impl();
446 OUString sHelpText
= SfxHelp_Impl::GetHelpText( aCommandURL
, sModuleName
);
450 if (pWindow
&& sHelpText
.isEmpty())
452 // no help text found -> try with parent help id.
453 vcl::Window
* pParent
= pWindow
->GetParent();
456 aNewHelpId
= pParent
->GetHelpId();
457 sHelpText
= SfxHelp_Impl::GetHelpText( OStringToOUString(aNewHelpId
, RTL_TEXTENCODING_UTF8
), sModuleName
);
458 if (!sHelpText
.isEmpty())
461 pParent
= pParent
->GetParent();
464 if (bIsDebug
&& sHelpText
.isEmpty())
468 // add some debug information?
471 sHelpText
+= "\n-------------\n";
472 sHelpText
+= sModuleName
;
474 sHelpText
+= aCommandURL
;
475 if ( !aNewHelpId
.isEmpty() )
478 sHelpText
+= OStringToOUString(aNewHelpId
, RTL_TEXTENCODING_UTF8
);
485 /// Check for built-in help
486 static bool impl_hasHelpInstalled( const OUString
&rLang
= OUString() )
488 OUStringBuffer
aHelpRootURL("vnd.sun.star.help://");
489 AppendConfigToken(aHelpRootURL
, true, rLang
);
490 std::vector
< OUString
> aFactories
= SfxContentHelper::GetResultSet(aHelpRootURL
.makeStringAndClear());
492 return !aFactories
.empty();
495 bool SfxHelp::SearchKeyword( const OUString
& rKeyword
)
497 return Start_Impl( OUString(), NULL
, rKeyword
);
500 bool SfxHelp::Start( const OUString
& rURL
, const vcl::Window
* pWindow
)
502 return Start_Impl( rURL
, pWindow
, OUString() );
505 /// Redirect the vnd.sun.star.help:// urls to http://help.libreoffice.org
506 static bool impl_showOnlineHelp( const OUString
& rURL
)
508 OUString
aInternal( "vnd.sun.star.help://" );
509 if ( rURL
.getLength() <= aInternal
.getLength() || !rURL
.startsWith(aInternal
) )
512 OUString
aHelpLink( "http://help.libreoffice.org/" );
513 aHelpLink
+= rURL
.copy( aInternal
.getLength() );
514 aHelpLink
= aHelpLink
.replaceAll("%2F","/");
517 Reference
< XSystemShellExecute
> xSystemShell(
518 SystemShellExecute::create(::comphelper::getProcessComponentContext()) );
520 xSystemShell
->execute( aHelpLink
, OUString(), SystemShellExecuteFlags::URIS_ONLY
);
523 catch (const Exception
&)
529 bool SfxHelp::Start_Impl(const OUString
& rURL
, const vcl::Window
* pWindow
, const OUString
& rKeyword
)
531 OUStringBuffer
aHelpRootURL("vnd.sun.star.help://");
532 AppendConfigToken(aHelpRootURL
, true);
533 SfxContentHelper::GetResultSet(aHelpRootURL
.makeStringAndClear());
537 - a HelpID (formerly a long, now a string)
538 If rURL is a URL, CreateHelpURL should be called for this URL
539 If rURL is an arbitrary string, the same should happen, but the URL should be tried out
540 if it delivers real help content. In case only the Help Error Document is returned, the
541 parent of the window for that help was called, is asked for its HelpID.
542 For compatibility reasons this upward search is not implemented for "real" URLs.
543 Help keyword search now is implemented as own method; in former versions it
544 was done via Help::Start, but this implementation conflicted with the upward search.
547 INetURLObject
aParser( rURL
);
548 INetProtocol nProtocol
= aParser
.GetProtocol();
552 case INetProtocol::VndSunStarHelp
:
553 // already a vnd.sun.star.help URL -> nothing to do
558 OUString
aHelpModuleName( GetHelpModuleName_Impl() );
559 // no URL, just a HelpID (maybe empty in case of keyword search)
560 aHelpURL
= CreateHelpURL_Impl( rURL
, aHelpModuleName
);
562 if ( impl_hasHelpInstalled() && pWindow
&& SfxContentHelper::IsHelpErrorDocument( aHelpURL
) )
564 // no help found -> try with parent help id.
565 vcl::Window
* pParent
= pWindow
->GetParent();
566 bool bTriedTabPage
= false;
569 OString aHelpId
= pParent
->GetHelpId();
570 aHelpURL
= CreateHelpURL( OStringToOUString(aHelpId
, RTL_TEXTENCODING_UTF8
), aHelpModuleName
);
571 if ( !SfxContentHelper::IsHelpErrorDocument( aHelpURL
) )
577 pParent
= pParent
->GetParent();
580 // create help url of start page ( helpid == 0 -> start page)
581 aHelpURL
= CreateHelpURL( OUString(), aHelpModuleName
);
583 else if (pParent
->IsDialog() && !bTriedTabPage
)
585 //During help fallback, before we ask a dialog for its help
586 //see if it has a TabControl and ask the active tab of
588 bTriedTabPage
= true;
589 Dialog
*pDialog
= static_cast<Dialog
*>(pParent
);
590 TabControl
*pCtrl
= pDialog
->hasBuilder() ? pDialog
->get
<TabControl
>("tabcontrol") : NULL
;
591 TabPage
* pTabPage
= pCtrl
? pCtrl
->GetTabPage(pCtrl
->GetCurPageId()) : NULL
;
592 vcl::Window
*pTabChild
= pTabPage
? pTabPage
->GetWindow(GetWindowType::FirstChild
) : NULL
;
603 if ( !impl_hasHelpInstalled() )
605 if ( impl_showOnlineHelp( aHelpURL
) )
608 ScopedVclPtrInstance
< NoHelpErrorBox
> aErrBox(const_cast< vcl::Window
* >( pWindow
));
613 Reference
< XDesktop2
> xDesktop
= Desktop::create( ::comphelper::getProcessComponentContext() );
615 // check if help window is still open
616 // If not, create a new one and return access directly to the internal sub frame showing the help content
617 // search must be done here; search one desktop level could return an arbitraty frame
618 Reference
< XFrame2
> xHelp(
619 xDesktop
->findFrame( "OFFICE_HELP_TASK", FrameSearchFlag::CHILDREN
),
621 Reference
< XFrame
> xHelpContent
= xDesktop
->findFrame(
622 OUString("OFFICE_HELP"),
623 FrameSearchFlag::CHILDREN
);
625 SfxHelpWindow_Impl
* pHelpWindow
= 0;
627 pHelpWindow
= impl_createHelp(xHelp
, xHelpContent
);
629 pHelpWindow
= static_cast<SfxHelpWindow_Impl
*>(VCLUnoHelper::GetWindow(xHelp
->getComponentWindow()).get());
630 if (!xHelp
.is() || !xHelpContent
.is() || !pHelpWindow
)
633 SAL_INFO("sfx.appl", "HelpId = " << aHelpURL
);
635 pHelpWindow
->SetHelpURL( aHelpURL
);
636 pHelpWindow
->loadHelpContent(aHelpURL
);
637 if (!rKeyword
.isEmpty())
638 pHelpWindow
->OpenKeyword( rKeyword
);
640 Reference
< ::com::sun::star::awt::XTopWindow
> xTopWindow( xHelp
->getContainerWindow(), UNO_QUERY
);
641 if ( xTopWindow
.is() )
642 xTopWindow
->toFront();
647 OUString
SfxHelp::CreateHelpURL(const OUString
& aCommandURL
, const OUString
& rModuleName
)
649 SfxHelp
* pHelp
= static_cast< SfxHelp
* >(Application::GetHelp());
650 return pHelp
? pHelp
->CreateHelpURL_Impl( aCommandURL
, rModuleName
) : OUString();
653 OUString
SfxHelp::GetDefaultHelpModule()
655 return getDefaultModule_Impl();
658 OUString
SfxHelp::GetCurrentModuleIdentifier()
660 return getCurrentModuleIdentifier_Impl();
663 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */