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 .
24 #include <svtools/inettbc.hxx>
25 #include <comphelper/diagnose_ex.hxx>
26 #include <com/sun/star/uno/Any.hxx>
27 #include <com/sun/star/uno/Reference.hxx>
28 #include <com/sun/star/beans/Property.hpp>
29 #include <com/sun/star/sdbc/XResultSet.hpp>
30 #include <com/sun/star/sdbc/XRow.hpp>
31 #include <com/sun/star/task/XInteractionHandler.hpp>
32 #include <com/sun/star/ucb/NumberedSortingInfo.hpp>
33 #include <com/sun/star/ucb/UniversalContentBroker.hpp>
34 #include <com/sun/star/ucb/XAnyCompareFactory.hpp>
35 #include <com/sun/star/ucb/XCommandProcessor2.hpp>
36 #include <com/sun/star/ucb/XProgressHandler.hpp>
37 #include <com/sun/star/ucb/XContentAccess.hpp>
38 #include <com/sun/star/ucb/SortedDynamicResultSetFactory.hpp>
39 #include <comphelper/processfactory.hxx>
40 #include <comphelper/string.hxx>
41 #include <salhelper/thread.hxx>
42 #include <tools/debug.hxx>
43 #include <o3tl/string_view.hxx>
44 #include <osl/file.hxx>
45 #include <osl/mutex.hxx>
46 #include <unotools/historyoptions.hxx>
47 #include <unotools/pathoptions.hxx>
48 #include <ucbhelper/commandenvironment.hxx>
49 #include <ucbhelper/content.hxx>
50 #include <unotools/ucbhelper.hxx>
51 #include <svtools/asynclink.hxx>
52 #include <svtools/urlfilter.hxx>
59 using namespace ::ucbhelper
;
60 using namespace ::utl
;
61 using namespace ::com::sun::star
;
62 using namespace ::com::sun::star::beans
;
63 using namespace ::com::sun::star::sdbc
;
64 using namespace ::com::sun::star::task
;
65 using namespace ::com::sun::star::ucb
;
66 using namespace ::com::sun::star::uno
;
71 std::vector
<OUString
> aURLs
;
72 std::vector
<OUString
> aCompletions
;
73 std::vector
<WildCard
> m_aFilters
;
75 static bool TildeParsing( OUString
& aText
, OUString
& aBaseUrl
);
79 FilterMatch::createWildCardFilterList(u
"",m_aFilters
);
83 class SvtMatchContext_Impl
: public salhelper::Thread
85 std::vector
<OUString
> aPickList
;
86 std::vector
<OUString
> aCompletions
;
87 std::vector
<OUString
> aURLs
;
88 svtools::AsynchronLink aLink
;
91 bool bOnlyDirectories
;
96 css::uno::Reference
< css::ucb::XCommandProcessor
> processor_
;
99 DECL_LINK( Select_Impl
, void*, void );
101 virtual ~SvtMatchContext_Impl() override
;
102 virtual void execute() override
;
104 void Insert( const OUString
& rCompletion
, const OUString
& rURL
, bool bForce
= false);
105 void ReadFolder( const OUString
& rURL
, const OUString
& rMatch
, bool bSmart
);
106 static void FillPicklist(std::vector
<OUString
>& rPickList
);
109 SvtMatchContext_Impl( SvtURLBox
* pBoxP
, OUString aText
);
116 ::osl::Mutex
& theSvtMatchContextMutex()
118 static ::osl::Mutex SINGLETON
;
123 SvtMatchContext_Impl::SvtMatchContext_Impl(SvtURLBox
* pBoxP
, OUString _aText
)
124 : Thread( "MatchContext_Impl" )
125 , aLink( LINK( this, SvtMatchContext_Impl
, Select_Impl
) )
126 , aText(std::move( _aText
))
128 , bOnlyDirectories( pBoxP
->bOnlyDirectories
)
129 , bNoSelection( pBoxP
->bNoSelection
)
133 FillPicklist( aPickList
);
136 SvtMatchContext_Impl::~SvtMatchContext_Impl()
138 aLink
.ClearPendingCall();
141 void SvtMatchContext_Impl::FillPicklist(std::vector
<OUString
>& rPickList
)
143 // Read the history of picks
144 std::vector
< SvtHistoryOptions::HistoryItem
> seqPicklist
= SvtHistoryOptions::GetList( EHistoryType::PickList
);
145 sal_uInt32 nCount
= seqPicklist
.size();
147 for( sal_uInt32 nItem
=0; nItem
< nCount
; nItem
++ )
150 aURL
.SetURL( seqPicklist
[nItem
].sTitle
);
151 rPickList
.insert(rPickList
.begin() + nItem
, aURL
.GetMainURL(INetURLObject::DecodeMechanism::WithCharset
));
155 void SvtMatchContext_Impl::Stop()
157 css::uno::Reference
< css::ucb::XCommandProcessor
> proc
;
160 std::scoped_lock
g(mutex_
);
173 void SvtMatchContext_Impl::execute( )
180 // This method is called via AsynchronLink, so it has the SolarMutex and
181 // calling solar code ( VCL ... ) is safe. It is called when the thread is
182 // terminated ( finished work or stopped ). Cancelling the thread via
183 // Cancellable does not discard the information gained so far, it
184 // inserts all collected completions into the listbox.
186 IMPL_LINK_NOARG( SvtMatchContext_Impl
, Select_Impl
, void*, void )
188 // avoid recursion through cancel button
190 std::scoped_lock
g(mutex_
);
192 // Completion was stopped, no display:
197 // insert all completed strings into the listbox
200 for (auto const& completion
: aCompletions
)
202 // convert the file into a URL
204 osl::FileBase::getFileURLFromSystemPath(completion
, sURL
);
205 // note: if this doesn't work, we're not interested in: we're checking the
206 // untouched sCompletion then
208 if ( !sURL
.isEmpty() && !sURL
.endsWith("/") )
210 OUString
sUpperURL( sURL
.toAsciiUpperCase() );
212 if ( ::std::none_of( pBox
->pImpl
->m_aFilters
.begin(),
213 pBox
->pImpl
->m_aFilters
.end(),
214 FilterMatch( sUpperURL
) ) )
215 { // this URL is not allowed
220 pBox
->append_text(completion
);
223 pBox
->EnableAutocomplete(!bNoSelection
);
225 // transfer string lists to listbox and forget them
226 pBox
->pImpl
->aURLs
= aURLs
;
227 pBox
->pImpl
->aCompletions
= aCompletions
;
229 aCompletions
.clear();
231 // the box has this control as a member so we have to set that member
232 // to zero before deleting ourself.
236 void SvtMatchContext_Impl::Insert( const OUString
& rCompletion
,
237 const OUString
& rURL
,
243 if(find(aCompletions
.begin(), aCompletions
.end(), rCompletion
) != aCompletions
.end())
247 aCompletions
.push_back(rCompletion
);
248 aURLs
.push_back(rURL
);
252 void SvtMatchContext_Impl::ReadFolder( const OUString
& rURL
,
253 const OUString
& rMatch
,
256 // check folder to scan
257 if( !UCBContentHelper::IsFolder( rURL
) )
260 bool bPureHomePath
= false;
262 bPureHomePath
= aText
.startsWith( "~" ) && aText
.indexOf( '/' ) == -1;
265 bool bExectMatch
= bPureHomePath
267 || aText
.endsWith("/.")
268 || aText
.endsWith("/..");
270 // for pure home paths ( ~username ) the '.' at the end of rMatch
271 // means that it points to root catalog
272 // this is done only for file contents since home paths parsing is useful only for them
273 if ( bPureHomePath
&& rMatch
== "file:///." )
275 // a home that refers to /
277 OUString aNewText
= aText
+ "/";
278 Insert( aNewText
, rURL
, true );
283 // string to match with
284 INetURLObject
aMatchObj( rMatch
);
287 if ( rURL
!= aMatchObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
) )
289 aMatchName
= aMatchObj
.getName( INetURLObject::LAST_SEGMENT
, true, INetURLObject::DecodeMechanism::WithCharset
);
291 // matching is always done case insensitive, but completion will be case sensitive and case preserving
292 aMatchName
= aMatchName
.toAsciiLowerCase();
294 // if the matchstring ends with a slash, we must search for this also
295 if ( rMatch
.endsWith("/") )
299 sal_Int32 nMatchLen
= aMatchName
.getLength();
301 INetURLObject
aFolderObj( rURL
);
302 DBG_ASSERT( aFolderObj
.GetProtocol() != INetProtocol::NotValid
, "Invalid URL!" );
306 Content
aCnt( aFolderObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
),
307 new ::ucbhelper::CommandEnvironment( uno::Reference
< XInteractionHandler
>(),
308 uno::Reference
< XProgressHandler
>() ),
309 comphelper::getProcessComponentContext() );
310 uno::Reference
< XResultSet
> xResultSet
;
314 ResultSetInclude eInclude
= INCLUDE_FOLDERS_AND_DOCUMENTS
;
315 if ( bOnlyDirectories
)
316 eInclude
= INCLUDE_FOLDERS_ONLY
;
317 uno::Reference
< XDynamicResultSet
> xDynResultSet
= aCnt
.createDynamicCursor( { u
"Title"_ustr
, u
"IsFolder"_ustr
}, eInclude
);
319 uno::Reference
< XAnyCompareFactory
> xCompare
;
320 uno::Reference
< XSortedDynamicResultSetFactory
> xSRSFac
=
321 SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() );
323 uno::Reference
< XDynamicResultSet
> xDynamicResultSet
=
324 xSRSFac
->createSortedDynamicResultSet( xDynResultSet
, { { 2, false }, { 1, true } }, xCompare
);
326 if ( xDynamicResultSet
.is() )
328 xResultSet
= xDynamicResultSet
->getStaticResultSet();
331 catch( css::uno::Exception
& ) {}
333 if ( xResultSet
.is() )
335 uno::Reference
< XRow
> xRow( xResultSet
, UNO_QUERY
);
336 uno::Reference
< XContentAccess
> xContentAccess( xResultSet
, UNO_QUERY
);
340 while ( schedule() && xResultSet
->next() )
342 OUString aURL
= xContentAccess
->queryContentIdentifierString();
343 OUString aTitle
= xRow
->getString(1);
344 bool bIsFolder
= xRow
->getBoolean(2);
346 // matching is always done case insensitive, but completion will be case sensitive and case preserving
347 aTitle
= aTitle
.toAsciiLowerCase();
351 (bExectMatch
&& aMatchName
== aTitle
) ||
352 (!bExectMatch
&& aTitle
.startsWith(aMatchName
))
355 // all names fit if matchstring is empty
356 INetURLObject
aObj( aURL
);
357 sal_Unicode aDelimiter
= '/';
359 // when parsing is done "smart", the delimiter must be "guessed"
360 aObj
.getFSysPath( static_cast<FSysStyle
>(FSysStyle::Detect
& ~FSysStyle::Vos
), &aDelimiter
);
363 aObj
.setFinalSlash();
365 // get the last name of the URL
366 OUString aMatch
= aObj
.getName( INetURLObject::LAST_SEGMENT
, true, INetURLObject::DecodeMechanism::WithCharset
);
367 OUString
aInput( aText
);
370 if (aText
.endsWith(".") || bPureHomePath
)
372 // if a "special folder" URL was typed, don't touch the user input
373 aMatch
= aMatch
.copy( nMatchLen
);
377 // make the user input case preserving
378 DBG_ASSERT( aInput
.getLength() >= nMatchLen
, "Suspicious Matching!" );
379 aInput
= aInput
.copy( 0, aInput
.getLength() - nMatchLen
);
385 // folders should get a final slash automatically
387 aInput
+= OUStringChar(aDelimiter
);
389 Insert( aInput
, aObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
), true );
393 catch( css::uno::Exception
& )
398 catch( css::uno::Exception
& )
403 void SvtMatchContext_Impl::doExecute()
405 ::osl::MutexGuard
aGuard( theSvtMatchContextMutex() );
407 // have we been stopped while we were waiting for the mutex?
408 std::scoped_lock
g(mutex_
);
415 aCompletions
.clear();
419 if ( aText
.isEmpty() )
422 if( aText
.indexOf( '*' ) != -1 || aText
.indexOf( '?' ) != -1 )
423 // no autocompletion for wildcards
427 INetProtocol eProt
= INetURLObject::CompareProtocolScheme( aText
);
428 INetProtocol eBaseProt
= INetURLObject::CompareProtocolScheme( pBox
->aBaseURL
);
429 if ( pBox
->aBaseURL
.isEmpty() )
430 eBaseProt
= INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() );
431 INetProtocol eSmartProt
= pBox
->GetSmartProtocol();
433 // if the user input is a valid URL, go on with it
434 // otherwise it could be parsed smart with a predefined smart protocol
435 // ( or if this is not set with the protocol of a predefined base URL )
436 if( eProt
== INetProtocol::NotValid
|| eProt
== eSmartProt
|| (eSmartProt
== INetProtocol::NotValid
&& eProt
== eBaseProt
) )
441 if ( eProt
== INetProtocol::NotValid
)
442 aMatch
= SvtURLBox::ParseSmart( aText
, pBox
->aBaseURL
);
445 if ( !aMatch
.isEmpty() )
447 INetURLObject
aURLObject( aMatch
);
448 OUString
aMainURL( aURLObject
.GetMainURL( INetURLObject::DecodeMechanism::NONE
) );
449 // Disable autocompletion for anything but the (local) file
450 // system (for which access is hopefully fast), as the logic of
451 // how SvtMatchContext_Impl is used requires this code to run to
452 // completion before further user input is processed, and even
453 // SvtMatchContext_Impl::Stop does not guarantee a speedy
455 if ( !aMainURL
.isEmpty()
456 && aURLObject
.GetProtocol() == INetProtocol::File
)
458 // if text input is a directory, it must be part of the match list! Until then it is scanned
460 if (aURLObject
.hasFinalSlash()) {
462 const css::uno::Reference
< css::uno::XComponentContext
>&
463 ctx(comphelper::getProcessComponentContext());
465 css::ucb::XUniversalContentBroker
> ucb(
466 css::ucb::UniversalContentBroker::create(
468 css::uno::Sequence
< css::beans::Property
> prop
{
469 { /* Name */ u
"IsFolder"_ustr
,
471 /* Type */ cppu::UnoType
< bool >::get(),
472 /* Attributes */ {} }
475 css::uno::Reference
< css::ucb::XCommandProcessor
>
478 ucb
->createContentIdentifier(aMainURL
)),
479 css::uno::UNO_QUERY_THROW
);
480 css::uno::Reference
< css::ucb::XCommandProcessor2
>
481 proc2(proc
, css::uno::UNO_QUERY
);
482 sal_Int32 id
= proc
->createCommandIdentifier();
485 std::scoped_lock
g(mutex_
);
491 u
"getPropertyValues"_ustr
, -1,
492 css::uno::Any(prop
)),
495 css::ucb::XCommandEnvironment
>());
499 proc2
->releaseCommandIdentifier(id
);
500 } catch (css::uno::RuntimeException
&) {
501 TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
507 proc2
->releaseCommandIdentifier(id
);
510 std::scoped_lock
g(mutex_
);
512 // At least the neon-based WebDAV UCP does not
513 // properly support aborting commands, so return
514 // anyway now if an abort request had been
515 // ignored and the command execution only
516 // returned "successfully" after some timeout:
521 css::uno::Reference
< css::sdbc::XRow
> row(
522 res
, css::uno::UNO_QUERY_THROW
);
523 folder
= row
->getBoolean(1) && !row
->wasNull();
524 } catch (css::uno::Exception
&) {
525 TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
530 Insert( aText
, aMatch
);
532 // otherwise the parent folder will be taken
533 aURLObject
.removeSegment();
535 // scan directory and insert all matches
536 ReadFolder( aURLObject
.GetMainURL( INetURLObject::DecodeMechanism::NONE
), aMatch
, eProt
== INetProtocol::NotValid
);
542 if ( bOnlyDirectories
)
543 // don't scan history picklist if only directories are allowed, picklist contains only files
548 INetURLObject aCurObj
;
549 OUString aCurString
, aCurMainURL
;
551 aObj
.SetSmartProtocol( eSmartProt
== INetProtocol::NotValid
? INetProtocol::Http
: eSmartProt
);
554 for(const auto& rPick
: aPickList
)
559 aCurObj
.SetURL(rPick
);
560 aCurObj
.SetSmartURL( aCurObj
.GetURLNoPass());
561 aCurMainURL
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
563 if( eProt
!= INetProtocol::NotValid
&& aCurObj
.GetProtocol() != eProt
)
566 if( eSmartProt
!= INetProtocol::NotValid
&& aCurObj
.GetProtocol() != eSmartProt
)
569 switch( aCurObj
.GetProtocol() )
571 case INetProtocol::Http
:
572 case INetProtocol::Https
:
573 case INetProtocol::Ftp
:
575 if( eProt
== INetProtocol::NotValid
&& !bFull
)
577 aObj
.SetSmartURL( aText
);
578 if( aObj
.GetURLPath().getLength() > 1 )
582 aCurString
= aCurMainURL
;
583 if( eProt
== INetProtocol::NotValid
)
585 // try if text matches the scheme
586 OUString
aScheme( INetURLObject::GetScheme( aCurObj
.GetProtocol() ) );
587 if ( aScheme
.startsWithIgnoreAsciiCase( aText
) && aText
.getLength() < aScheme
.getLength() )
590 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
593 aCurObj
.SetMark( u
"" );
594 aCurObj
.SetParam( u
"" );
595 aCurObj
.SetURLPath( u
"" );
596 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
599 Insert( aMatch
, aMatch
);
602 // now try smart matching
603 aCurString
= aCurString
.copy( aScheme
.getLength() );
606 if( aCurString
.startsWithIgnoreAsciiCase( aText
) )
609 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
612 aCurObj
.SetMark( u
"" );
613 aCurObj
.SetParam( u
"" );
614 aCurObj
.SetURLPath( u
"" );
615 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
618 OUString
aURL( aMatch
);
619 if( eProt
== INetProtocol::NotValid
)
620 aMatch
= aMatch
.copy( INetURLObject::GetScheme( aCurObj
.GetProtocol() ).getLength() );
622 if( aText
.getLength() < aMatch
.getLength() )
623 Insert( aMatch
, aURL
);
634 if( aCurMainURL
.startsWith(aText
) )
636 if( aText
.getLength() < aCurMainURL
.getLength() )
637 Insert( aCurMainURL
, aCurMainURL
);
653 /** Parse leading ~ for Unix systems,
654 does nothing for Windows
656 bool SvtURLBox_Impl::TildeParsing(
668 if( aText
.startsWith( "~" ) )
670 OUString aParseTilde
;
671 bool bTrailingSlash
= true; // use trailing slash
673 if( aText
.getLength() == 1 || aText
[ 1 ] == '/' )
675 // covers "~" or "~/..." cases
676 const char* aHomeLocation
= getenv( "HOME" );
680 aParseTilde
= OUString::createFromAscii(aHomeLocation
);
682 // in case the whole path is just "~" then there should
683 // be no trailing slash at the end
684 if( aText
.getLength() == 1 )
685 bTrailingSlash
= false;
689 // covers "~username" and "~username/..." cases
690 sal_Int32 nNameEnd
= aText
.indexOf( '/' );
691 OUString aUserName
= aText
.copy( 1, ( nNameEnd
!= -1 ) ? nNameEnd
: ( aText
.getLength() - 1 ) );
693 struct passwd
* pPasswd
= nullptr;
695 Sequence
< sal_Int8
> sBuf( 1024 );
697 sal_Int32 nRes
= getpwnam_r( OUStringToOString( aUserName
, RTL_TEXTENCODING_ASCII_US
).getStr(),
699 (char*)sBuf
.getArray(),
702 if( !nRes
&& pPasswd
)
703 aParseTilde
= OUString::createFromAscii(pPasswd
->pw_dir
);
705 return false; // no such user
707 pPasswd
= getpwnam( OUStringToOString( aUserName
, RTL_TEXTENCODING_ASCII_US
).getStr() );
709 aParseTilde
= OUString::createFromAscii(pPasswd
->pw_dir
);
711 return false; // no such user
714 // in case the path is "~username" then there should
715 // be no trailing slash at the end
717 bTrailingSlash
= false;
720 if( !bTrailingSlash
)
722 if( aParseTilde
.isEmpty() || aParseTilde
== "/" )
724 // "/" path should be converted to "/."
729 // "blabla/" path should be converted to "blabla"
730 aParseTilde
= comphelper::string::stripEnd(aParseTilde
, '/');
735 if( !aParseTilde
.endsWith("/") )
737 if( aText
.getLength() > 2 )
738 aParseTilde
+= aText
.subView( 2 );
742 aBaseURL
.clear(); // tilde provide absolute path
751 OUString
SvtURLBox::ParseSmart( const OUString
& _aText
, const OUString
& _aBaseURL
)
754 OUString aText
= _aText
;
755 OUString aBaseURL
= _aBaseURL
;
757 // parse ~ for Unix systems
758 // does nothing for Windows
759 if( !SvtURLBox_Impl::TildeParsing( aText
, aBaseURL
) )
762 if( !aBaseURL
.isEmpty() )
764 INetProtocol eBaseProt
= INetURLObject::CompareProtocolScheme( aBaseURL
);
766 // if a base URL is set the string may be parsed relative
767 if( aText
.startsWith( "/" ) )
769 // text starting with slashes means absolute file URLs
770 OUString aTemp
= INetURLObject::GetScheme( eBaseProt
);
772 // file URL must be correctly encoded!
773 OUString aTextURL
= INetURLObject::encode( aText
, INetURLObject::PART_FPATH
,
774 INetURLObject::EncodeMechanism::All
);
777 INetURLObject
aTmp( aTemp
);
778 if ( !aTmp
.HasError() && aTmp
.GetProtocol() != INetProtocol::NotValid
)
779 aMatch
= aTmp
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
783 OUString
aSmart( aText
);
784 INetURLObject
aObj( aBaseURL
);
786 // HRO: I suppose this hack should only be done for Windows !!!???
788 // HRO: INetURLObject::smatRel2Abs does not recognize '\\' as a relative path
789 // but in case of "\\\\" INetURLObject is right - this is an absolute path !
791 if( aText
.startsWith("\\") && (aText
.getLength() < 2 || aText
[ 1 ] != '\\') )
793 // cut to first segment
794 OUString aTmp
= INetURLObject::GetScheme( eBaseProt
) + "/";
795 aTmp
+= aObj
.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset
);
798 aSmart
= aSmart
.copy(1);
801 // base URL must be a directory !
802 aObj
.setFinalSlash();
804 // take base URL and append current input
805 bool bWasAbsolute
= false;
807 // encode file URL correctly
808 aSmart
= INetURLObject::encode( aSmart
, INetURLObject::PART_FPATH
, INetURLObject::EncodeMechanism::All
);
810 INetURLObject
aTmp( aObj
.smartRel2Abs( aSmart
, bWasAbsolute
) );
812 if ( aText
.endsWith(".") )
813 // INetURLObject appends a final slash for the directories "." and "..", this is a bug!
814 // Remove it as a workaround
815 aTmp
.removeFinalSlash();
816 if ( !aTmp
.HasError() && aTmp
.GetProtocol() != INetProtocol::NotValid
)
817 aMatch
= aTmp
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
823 osl::FileBase::getFileURLFromSystemPath( aText
, aTmpMatch
);
830 IMPL_LINK_NOARG(SvtURLBox
, TryAutoComplete
, Timer
*, void)
832 OUString aCurText
= m_xWidget
->get_active_text();
833 int nStartPos
, nEndPos
;
834 m_xWidget
->get_entry_selection_bounds(nStartPos
, nEndPos
);
835 if (std::max(nStartPos
, nEndPos
) != aCurText
.getLength())
838 auto nLen
= std::min(nStartPos
, nEndPos
);
839 aCurText
= aCurText
.copy( 0, nLen
);
840 if (!aCurText
.isEmpty())
848 pCtx
= new SvtMatchContext_Impl(this, aCurText
);
855 SvtURLBox::SvtURLBox(std::unique_ptr
<weld::ComboBox
> pWidget
)
856 : aChangedIdle("svtools::URLBox aChangedIdle")
857 , eSmartProtocol(INetProtocol::NotValid
)
858 , bOnlyDirectories( false )
859 , bHistoryDisabled( false )
860 , bNoSelection( false )
861 , m_xWidget(std::move(pWidget
))
863 //don't grow to fix mega-long urls
864 Size
aSize(m_xWidget
->get_preferred_size());
865 m_xWidget
->set_size_request(aSize
.Width(), -1);
869 m_xWidget
->connect_focus_in(LINK(this, SvtURLBox
, FocusInHdl
));
870 m_xWidget
->connect_focus_out(LINK(this, SvtURLBox
, FocusOutHdl
));
871 m_xWidget
->connect_changed(LINK(this, SvtURLBox
, ChangedHdl
));
873 aChangedIdle
.SetInvokeHandler(LINK(this, SvtURLBox
, TryAutoComplete
));
876 void SvtURLBox::Init()
878 pImpl
.reset( new SvtURLBox_Impl
);
880 m_xWidget
->set_entry_completion(false);
882 UpdatePicklistForSmartProtocol_Impl();
885 SvtURLBox::~SvtURLBox()
894 void SvtURLBox::SetSmartProtocol(INetProtocol eProt
)
896 if ( eSmartProtocol
!= eProt
)
898 eSmartProtocol
= eProt
;
899 UpdatePicklistForSmartProtocol_Impl();
903 void SvtURLBox::UpdatePicklistForSmartProtocol_Impl()
906 if ( bHistoryDisabled
)
909 if (bHistoryDisabled
)
912 // read history pick list
913 const std::vector
< SvtHistoryOptions::HistoryItem
> seqPicklist
= SvtHistoryOptions::GetList( EHistoryType::PickList
);
914 INetURLObject aCurObj
;
916 for( const SvtHistoryOptions::HistoryItem
& rPropertySet
: seqPicklist
)
918 aCurObj
.SetURL( rPropertySet
.sURL
);
920 if ( !rPropertySet
.sURL
.isEmpty() && ( eSmartProtocol
!= INetProtocol::NotValid
) )
922 if( aCurObj
.GetProtocol() != eSmartProtocol
)
926 OUString
aURL( aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::WithCharset
) );
928 if ( !aURL
.isEmpty() )
930 bool bFound
= aURL
.endsWith("/");
933 OUString aUpperURL
= aURL
.toAsciiUpperCase();
935 bFound
= ::std::any_of(pImpl
->m_aFilters
.begin(),
936 pImpl
->m_aFilters
.end(),
937 FilterMatch( aUpperURL
) );
942 if (osl::FileBase::getSystemPathFromFileURL(aURL
, aFile
) == osl::FileBase::E_None
)
943 m_xWidget
->append_text(aFile
);
945 m_xWidget
->append_text(aURL
);
951 IMPL_LINK_NOARG(SvtURLBox
, ChangedHdl
, weld::ComboBox
&, void)
953 aChangeHdl
.Call(*m_xWidget
);
954 aChangedIdle
.Start(); //launch this to happen on idle after cursor position will have been set
957 IMPL_LINK_NOARG(SvtURLBox
, FocusInHdl
, weld::Widget
&, void)
960 // pb: don't select automatically on unix #93251#
961 m_xWidget
->select_entry_region(0, -1);
963 aFocusInHdl
.Call(*m_xWidget
);
966 IMPL_LINK_NOARG(SvtURLBox
, FocusOutHdl
, weld::Widget
&, void)
974 aFocusOutHdl
.Call(*m_xWidget
);
977 void SvtURLBox::SetOnlyDirectories( bool bDir
)
979 bOnlyDirectories
= bDir
;
980 if ( bOnlyDirectories
)
984 void SvtURLBox::SetNoURLSelection( bool bSet
)
989 OUString
SvtURLBox::GetURL()
991 // wait for end of autocompletion
992 ::osl::MutexGuard
aGuard( theSvtMatchContextMutex() );
994 OUString
aText(m_xWidget
->get_active_text());
995 if (MatchesPlaceHolder(aText
))
998 // try to get the right case preserving URL from the list of URLs
999 for(std::vector
<OUString
>::iterator i
= pImpl
->aCompletions
.begin(), j
= pImpl
->aURLs
.begin(); i
!= pImpl
->aCompletions
.end() && j
!= pImpl
->aURLs
.end(); ++i
, ++j
)
1006 // erase trailing spaces on Windows since they are invalid on this OS and
1007 // most of the time they are inserted by accident via copy / paste
1008 aText
= comphelper::string::stripEnd(aText
, ' ');
1009 if ( aText
.isEmpty() )
1014 INetURLObject
aObj( aText
);
1015 if( aText
.indexOf( '*' ) != -1 || aText
.indexOf( '?' ) != -1 )
1017 // no autocompletion for wildcards
1018 INetURLObject aTempObj
;
1019 if ( eSmartProtocol
!= INetProtocol::NotValid
)
1020 aTempObj
.SetSmartProtocol( eSmartProtocol
);
1021 if ( aTempObj
.SetSmartURL( aText
) )
1022 return aTempObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
1027 if ( aObj
.GetProtocol() == INetProtocol::NotValid
)
1029 OUString aName
= ParseSmart( aText
, aBaseURL
);
1031 OUString
aURL( aObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
) );
1032 if ( aURL
.isEmpty() )
1033 // aText itself is invalid, and even together with aBaseURL, it could not
1034 // made valid -> no chance
1037 bool bSlash
= aObj
.hasFinalSlash();
1041 Any aAny
= UCBContentHelper::GetProperty(aURL
, u
"CasePreservingURL"_ustr
);
1042 bool success
= (aAny
>>= aFileURL
);
1045 aTitle
= INetURLObject(aFileURL
).getName(
1046 INetURLObject::LAST_SEGMENT
,
1048 INetURLObject::DecodeMechanism::WithCharset
);
1051 UCBContentHelper::GetTitle(aURL
,&aTitle
);
1053 if( success
&& aTitle
!= "/" && aTitle
!= "." )
1055 aObj
.setName( aTitle
);
1057 aObj
.setFinalSlash();
1062 return aObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
1065 void SvtURLBox::SetBaseURL( const OUString
& rURL
)
1067 ::osl::MutexGuard
aGuard( theSvtMatchContextMutex() );
1069 // Reset match lists
1070 pImpl
->aCompletions
.clear();
1071 pImpl
->aURLs
.clear();
1076 void SvtURLBox::DisableHistory()
1078 bHistoryDisabled
= true;
1079 UpdatePicklistForSmartProtocol_Impl();
1082 void SvtURLBox::SetFilter(std::u16string_view _sFilter
)
1084 pImpl
->m_aFilters
.clear();
1085 FilterMatch::createWildCardFilterList(_sFilter
,pImpl
->m_aFilters
);
1088 void FilterMatch::createWildCardFilterList(std::u16string_view _rFilterList
,::std::vector
< WildCard
>& _rFilters
)
1090 if( !_rFilterList
.empty() )
1093 sal_Int32 nIndex
= 0;
1097 sToken
= o3tl::getToken(_rFilterList
, 0, ';', nIndex
);
1098 if ( !sToken
.isEmpty() )
1100 _rFilters
.emplace_back( sToken
.toAsciiUpperCase() );
1103 while ( nIndex
>= 0 );
1107 // no filter is given -> match all
1108 _rFilters
.emplace_back(u
"*" );
1112 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */