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::lang
;
64 using namespace ::com::sun::star::sdbc
;
65 using namespace ::com::sun::star::task
;
66 using namespace ::com::sun::star::ucb
;
67 using namespace ::com::sun::star::uno
;
72 std::vector
<OUString
> aURLs
;
73 std::vector
<OUString
> aCompletions
;
74 std::vector
<WildCard
> m_aFilters
;
76 static bool TildeParsing( OUString
& aText
, OUString
& aBaseUrl
);
80 FilterMatch::createWildCardFilterList(u
"",m_aFilters
);
84 class SvtMatchContext_Impl
: public salhelper::Thread
86 std::vector
<OUString
> aPickList
;
87 std::vector
<OUString
> aCompletions
;
88 std::vector
<OUString
> aURLs
;
89 svtools::AsynchronLink aLink
;
92 bool bOnlyDirectories
;
97 css::uno::Reference
< css::ucb::XCommandProcessor
> processor_
;
100 DECL_LINK( Select_Impl
, void*, void );
102 virtual ~SvtMatchContext_Impl() override
;
103 virtual void execute() override
;
105 void Insert( const OUString
& rCompletion
, const OUString
& rURL
, bool bForce
= false);
106 void ReadFolder( const OUString
& rURL
, const OUString
& rMatch
, bool bSmart
);
107 static void FillPicklist(std::vector
<OUString
>& rPickList
);
110 SvtMatchContext_Impl( SvtURLBox
* pBoxP
, OUString aText
);
117 ::osl::Mutex
& theSvtMatchContextMutex()
119 static ::osl::Mutex SINGLETON
;
124 SvtMatchContext_Impl::SvtMatchContext_Impl(SvtURLBox
* pBoxP
, OUString _aText
)
125 : Thread( "MatchContext_Impl" )
126 , aLink( LINK( this, SvtMatchContext_Impl
, Select_Impl
) )
127 , aText(std::move( _aText
))
129 , bOnlyDirectories( pBoxP
->bOnlyDirectories
)
130 , bNoSelection( pBoxP
->bNoSelection
)
134 FillPicklist( aPickList
);
137 SvtMatchContext_Impl::~SvtMatchContext_Impl()
139 aLink
.ClearPendingCall();
142 void SvtMatchContext_Impl::FillPicklist(std::vector
<OUString
>& rPickList
)
144 // Read the history of picks
145 std::vector
< SvtHistoryOptions::HistoryItem
> seqPicklist
= SvtHistoryOptions::GetList( EHistoryType::PickList
);
146 sal_uInt32 nCount
= seqPicklist
.size();
148 for( sal_uInt32 nItem
=0; nItem
< nCount
; nItem
++ )
151 aURL
.SetURL( seqPicklist
[nItem
].sTitle
);
152 rPickList
.insert(rPickList
.begin() + nItem
, aURL
.GetMainURL(INetURLObject::DecodeMechanism::WithCharset
));
156 void SvtMatchContext_Impl::Stop()
158 css::uno::Reference
< css::ucb::XCommandProcessor
> proc
;
161 std::scoped_lock
g(mutex_
);
174 void SvtMatchContext_Impl::execute( )
181 // This method is called via AsynchronLink, so it has the SolarMutex and
182 // calling solar code ( VCL ... ) is safe. It is called when the thread is
183 // terminated ( finished work or stopped ). Cancelling the thread via
184 // Cancellable does not discard the information gained so far, it
185 // inserts all collected completions into the listbox.
187 IMPL_LINK_NOARG( SvtMatchContext_Impl
, Select_Impl
, void*, void )
189 // avoid recursion through cancel button
191 std::scoped_lock
g(mutex_
);
193 // Completion was stopped, no display:
198 // insert all completed strings into the listbox
201 for (auto const& completion
: aCompletions
)
203 // convert the file into a URL
205 osl::FileBase::getFileURLFromSystemPath(completion
, sURL
);
206 // note: if this doesn't work, we're not interested in: we're checking the
207 // untouched sCompletion then
209 if ( !sURL
.isEmpty() && !sURL
.endsWith("/") )
211 OUString
sUpperURL( sURL
.toAsciiUpperCase() );
213 if ( ::std::none_of( pBox
->pImpl
->m_aFilters
.begin(),
214 pBox
->pImpl
->m_aFilters
.end(),
215 FilterMatch( sUpperURL
) ) )
216 { // this URL is not allowed
221 pBox
->append_text(completion
);
224 pBox
->EnableAutocomplete(!bNoSelection
);
226 // transfer string lists to listbox and forget them
227 pBox
->pImpl
->aURLs
= aURLs
;
228 pBox
->pImpl
->aCompletions
= aCompletions
;
230 aCompletions
.clear();
232 // the box has this control as a member so we have to set that member
233 // to zero before deleting ourself.
237 void SvtMatchContext_Impl::Insert( const OUString
& rCompletion
,
238 const OUString
& rURL
,
244 if(find(aCompletions
.begin(), aCompletions
.end(), rCompletion
) != aCompletions
.end())
248 aCompletions
.push_back(rCompletion
);
249 aURLs
.push_back(rURL
);
253 void SvtMatchContext_Impl::ReadFolder( const OUString
& rURL
,
254 const OUString
& rMatch
,
257 // check folder to scan
258 if( !UCBContentHelper::IsFolder( rURL
) )
261 bool bPureHomePath
= false;
263 bPureHomePath
= aText
.startsWith( "~" ) && aText
.indexOf( '/' ) == -1;
266 bool bExectMatch
= bPureHomePath
268 || aText
.endsWith("/.")
269 || aText
.endsWith("/..");
271 // for pure home paths ( ~username ) the '.' at the end of rMatch
272 // means that it points to root catalog
273 // this is done only for file contents since home paths parsing is useful only for them
274 if ( bPureHomePath
&& rMatch
== "file:///." )
276 // a home that refers to /
278 OUString aNewText
= aText
+ "/";
279 Insert( aNewText
, rURL
, true );
284 // string to match with
285 INetURLObject
aMatchObj( rMatch
);
288 if ( rURL
!= aMatchObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
) )
290 aMatchName
= aMatchObj
.getName( INetURLObject::LAST_SEGMENT
, true, INetURLObject::DecodeMechanism::WithCharset
);
292 // matching is always done case insensitive, but completion will be case sensitive and case preserving
293 aMatchName
= aMatchName
.toAsciiLowerCase();
295 // if the matchstring ends with a slash, we must search for this also
296 if ( rMatch
.endsWith("/") )
300 sal_Int32 nMatchLen
= aMatchName
.getLength();
302 INetURLObject
aFolderObj( rURL
);
303 DBG_ASSERT( aFolderObj
.GetProtocol() != INetProtocol::NotValid
, "Invalid URL!" );
307 Content
aCnt( aFolderObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
),
308 new ::ucbhelper::CommandEnvironment( uno::Reference
< XInteractionHandler
>(),
309 uno::Reference
< XProgressHandler
>() ),
310 comphelper::getProcessComponentContext() );
311 uno::Reference
< XResultSet
> xResultSet
;
315 ResultSetInclude eInclude
= INCLUDE_FOLDERS_AND_DOCUMENTS
;
316 if ( bOnlyDirectories
)
317 eInclude
= INCLUDE_FOLDERS_ONLY
;
318 uno::Reference
< XDynamicResultSet
> xDynResultSet
= aCnt
.createDynamicCursor( { "Title", "IsFolder" }, eInclude
);
320 uno::Reference
< XAnyCompareFactory
> xCompare
;
321 uno::Reference
< XSortedDynamicResultSetFactory
> xSRSFac
=
322 SortedDynamicResultSetFactory::create( ::comphelper::getProcessComponentContext() );
324 uno::Reference
< XDynamicResultSet
> xDynamicResultSet
=
325 xSRSFac
->createSortedDynamicResultSet( xDynResultSet
, { { 2, false }, { 1, true } }, xCompare
);
327 if ( xDynamicResultSet
.is() )
329 xResultSet
= xDynamicResultSet
->getStaticResultSet();
332 catch( css::uno::Exception
& ) {}
334 if ( xResultSet
.is() )
336 uno::Reference
< XRow
> xRow( xResultSet
, UNO_QUERY
);
337 uno::Reference
< XContentAccess
> xContentAccess( xResultSet
, UNO_QUERY
);
341 while ( schedule() && xResultSet
->next() )
343 OUString aURL
= xContentAccess
->queryContentIdentifierString();
344 OUString aTitle
= xRow
->getString(1);
345 bool bIsFolder
= xRow
->getBoolean(2);
347 // matching is always done case insensitive, but completion will be case sensitive and case preserving
348 aTitle
= aTitle
.toAsciiLowerCase();
352 (bExectMatch
&& aMatchName
== aTitle
) ||
353 (!bExectMatch
&& aTitle
.startsWith(aMatchName
))
356 // all names fit if matchstring is empty
357 INetURLObject
aObj( aURL
);
358 sal_Unicode aDelimiter
= '/';
360 // when parsing is done "smart", the delimiter must be "guessed"
361 aObj
.getFSysPath( static_cast<FSysStyle
>(FSysStyle::Detect
& ~FSysStyle::Vos
), &aDelimiter
);
364 aObj
.setFinalSlash();
366 // get the last name of the URL
367 OUString aMatch
= aObj
.getName( INetURLObject::LAST_SEGMENT
, true, INetURLObject::DecodeMechanism::WithCharset
);
368 OUString
aInput( aText
);
371 if (aText
.endsWith(".") || bPureHomePath
)
373 // if a "special folder" URL was typed, don't touch the user input
374 aMatch
= aMatch
.copy( nMatchLen
);
378 // make the user input case preserving
379 DBG_ASSERT( aInput
.getLength() >= nMatchLen
, "Suspicious Matching!" );
380 aInput
= aInput
.copy( 0, aInput
.getLength() - nMatchLen
);
386 // folders should get a final slash automatically
388 aInput
+= OUStringChar(aDelimiter
);
390 Insert( aInput
, aObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
), true );
394 catch( css::uno::Exception
& )
399 catch( css::uno::Exception
& )
404 void SvtMatchContext_Impl::doExecute()
406 ::osl::MutexGuard
aGuard( theSvtMatchContextMutex() );
408 // have we been stopped while we were waiting for the mutex?
409 std::scoped_lock
g(mutex_
);
416 aCompletions
.clear();
420 if ( aText
.isEmpty() )
423 if( aText
.indexOf( '*' ) != -1 || aText
.indexOf( '?' ) != -1 )
424 // no autocompletion for wildcards
428 INetProtocol eProt
= INetURLObject::CompareProtocolScheme( aText
);
429 INetProtocol eBaseProt
= INetURLObject::CompareProtocolScheme( pBox
->aBaseURL
);
430 if ( pBox
->aBaseURL
.isEmpty() )
431 eBaseProt
= INetURLObject::CompareProtocolScheme( SvtPathOptions().GetWorkPath() );
432 INetProtocol eSmartProt
= pBox
->GetSmartProtocol();
434 // if the user input is a valid URL, go on with it
435 // otherwise it could be parsed smart with a predefined smart protocol
436 // ( or if this is not set with the protocol of a predefined base URL )
437 if( eProt
== INetProtocol::NotValid
|| eProt
== eSmartProt
|| (eSmartProt
== INetProtocol::NotValid
&& eProt
== eBaseProt
) )
442 if ( eProt
== INetProtocol::NotValid
)
443 aMatch
= SvtURLBox::ParseSmart( aText
, pBox
->aBaseURL
);
446 if ( !aMatch
.isEmpty() )
448 INetURLObject
aURLObject( aMatch
);
449 OUString
aMainURL( aURLObject
.GetMainURL( INetURLObject::DecodeMechanism::NONE
) );
450 // Disable autocompletion for anything but the (local) file
451 // system (for which access is hopefully fast), as the logic of
452 // how SvtMatchContext_Impl is used requires this code to run to
453 // completion before further user input is processed, and even
454 // SvtMatchContext_Impl::Stop does not guarantee a speedy
456 if ( !aMainURL
.isEmpty()
457 && aURLObject
.GetProtocol() == INetProtocol::File
)
459 // if text input is a directory, it must be part of the match list! Until then it is scanned
461 if (aURLObject
.hasFinalSlash()) {
463 css::uno::Reference
< css::uno::XComponentContext
>
464 ctx(comphelper::getProcessComponentContext());
466 css::ucb::XUniversalContentBroker
> ucb(
467 css::ucb::UniversalContentBroker::create(
469 css::uno::Sequence
< css::beans::Property
> prop
{
470 { /* Name */ "IsFolder",
472 /* Type */ cppu::UnoType
< bool >::get(),
473 /* Attributes */ {} }
476 css::uno::Reference
< css::ucb::XCommandProcessor
>
479 ucb
->createContentIdentifier(aMainURL
)),
480 css::uno::UNO_QUERY_THROW
);
481 css::uno::Reference
< css::ucb::XCommandProcessor2
>
482 proc2(proc
, css::uno::UNO_QUERY
);
483 sal_Int32 id
= proc
->createCommandIdentifier();
486 std::scoped_lock
g(mutex_
);
492 "getPropertyValues", -1,
493 css::uno::Any(prop
)),
496 css::ucb::XCommandEnvironment
>());
500 proc2
->releaseCommandIdentifier(id
);
501 } catch (css::uno::RuntimeException
&) {
502 TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
508 proc2
->releaseCommandIdentifier(id
);
511 std::scoped_lock
g(mutex_
);
513 // At least the neon-based WebDAV UCP does not
514 // properly support aborting commands, so return
515 // anyway now if an abort request had been
516 // ignored and the command execution only
517 // returned "successfully" after some timeout:
522 css::uno::Reference
< css::sdbc::XRow
> row(
523 res
, css::uno::UNO_QUERY_THROW
);
524 folder
= row
->getBoolean(1) && !row
->wasNull();
525 } catch (css::uno::Exception
&) {
526 TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
531 Insert( aText
, aMatch
);
533 // otherwise the parent folder will be taken
534 aURLObject
.removeSegment();
536 // scan directory and insert all matches
537 ReadFolder( aURLObject
.GetMainURL( INetURLObject::DecodeMechanism::NONE
), aMatch
, eProt
== INetProtocol::NotValid
);
543 if ( bOnlyDirectories
)
544 // don't scan history picklist if only directories are allowed, picklist contains only files
549 INetURLObject aCurObj
;
550 OUString aCurString
, aCurMainURL
;
552 aObj
.SetSmartProtocol( eSmartProt
== INetProtocol::NotValid
? INetProtocol::Http
: eSmartProt
);
555 for(const auto& rPick
: aPickList
)
560 aCurObj
.SetURL(rPick
);
561 aCurObj
.SetSmartURL( aCurObj
.GetURLNoPass());
562 aCurMainURL
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
564 if( eProt
!= INetProtocol::NotValid
&& aCurObj
.GetProtocol() != eProt
)
567 if( eSmartProt
!= INetProtocol::NotValid
&& aCurObj
.GetProtocol() != eSmartProt
)
570 switch( aCurObj
.GetProtocol() )
572 case INetProtocol::Http
:
573 case INetProtocol::Https
:
574 case INetProtocol::Ftp
:
576 if( eProt
== INetProtocol::NotValid
&& !bFull
)
578 aObj
.SetSmartURL( aText
);
579 if( aObj
.GetURLPath().getLength() > 1 )
583 aCurString
= aCurMainURL
;
584 if( eProt
== INetProtocol::NotValid
)
586 // try if text matches the scheme
587 OUString
aScheme( INetURLObject::GetScheme( aCurObj
.GetProtocol() ) );
588 if ( aScheme
.startsWithIgnoreAsciiCase( aText
) && aText
.getLength() < aScheme
.getLength() )
591 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
594 aCurObj
.SetMark( u
"" );
595 aCurObj
.SetParam( u
"" );
596 aCurObj
.SetURLPath( u
"" );
597 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
600 Insert( aMatch
, aMatch
);
603 // now try smart matching
604 aCurString
= aCurString
.copy( aScheme
.getLength() );
607 if( aCurString
.startsWithIgnoreAsciiCase( aText
) )
610 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
613 aCurObj
.SetMark( u
"" );
614 aCurObj
.SetParam( u
"" );
615 aCurObj
.SetURLPath( u
"" );
616 aMatch
= aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
619 OUString
aURL( aMatch
);
620 if( eProt
== INetProtocol::NotValid
)
621 aMatch
= aMatch
.copy( INetURLObject::GetScheme( aCurObj
.GetProtocol() ).getLength() );
623 if( aText
.getLength() < aMatch
.getLength() )
624 Insert( aMatch
, aURL
);
635 if( aCurMainURL
.startsWith(aText
) )
637 if( aText
.getLength() < aCurMainURL
.getLength() )
638 Insert( aCurMainURL
, aCurMainURL
);
654 /** Parse leading ~ for Unix systems,
655 does nothing for Windows
657 bool SvtURLBox_Impl::TildeParsing(
669 if( aText
.startsWith( "~" ) )
671 OUString aParseTilde
;
672 bool bTrailingSlash
= true; // use trailing slash
674 if( aText
.getLength() == 1 || aText
[ 1 ] == '/' )
676 // covers "~" or "~/..." cases
677 const char* aHomeLocation
= getenv( "HOME" );
681 aParseTilde
= OUString::createFromAscii(aHomeLocation
);
683 // in case the whole path is just "~" then there should
684 // be no trailing slash at the end
685 if( aText
.getLength() == 1 )
686 bTrailingSlash
= false;
690 // covers "~username" and "~username/..." cases
691 sal_Int32 nNameEnd
= aText
.indexOf( '/' );
692 OUString aUserName
= aText
.copy( 1, ( nNameEnd
!= -1 ) ? nNameEnd
: ( aText
.getLength() - 1 ) );
694 struct passwd
* pPasswd
= nullptr;
696 Sequence
< sal_Int8
> sBuf( 1024 );
698 sal_Int32 nRes
= getpwnam_r( OUStringToOString( aUserName
, RTL_TEXTENCODING_ASCII_US
).getStr(),
700 (char*)sBuf
.getArray(),
703 if( !nRes
&& pPasswd
)
704 aParseTilde
= OUString::createFromAscii(pPasswd
->pw_dir
);
706 return false; // no such user
708 pPasswd
= getpwnam( OUStringToOString( aUserName
, RTL_TEXTENCODING_ASCII_US
).getStr() );
710 aParseTilde
= OUString::createFromAscii(pPasswd
->pw_dir
);
712 return false; // no such user
715 // in case the path is "~username" then there should
716 // be no trailing slash at the end
718 bTrailingSlash
= false;
721 if( !bTrailingSlash
)
723 if( aParseTilde
.isEmpty() || aParseTilde
== "/" )
725 // "/" path should be converted to "/."
730 // "blabla/" path should be converted to "blabla"
731 aParseTilde
= comphelper::string::stripEnd(aParseTilde
, '/');
736 if( !aParseTilde
.endsWith("/") )
738 if( aText
.getLength() > 2 )
739 aParseTilde
+= aText
.subView( 2 );
743 aBaseURL
.clear(); // tilde provide absolute path
752 OUString
SvtURLBox::ParseSmart( const OUString
& _aText
, const OUString
& _aBaseURL
)
755 OUString aText
= _aText
;
756 OUString aBaseURL
= _aBaseURL
;
758 // parse ~ for Unix systems
759 // does nothing for Windows
760 if( !SvtURLBox_Impl::TildeParsing( aText
, aBaseURL
) )
763 if( !aBaseURL
.isEmpty() )
765 INetProtocol eBaseProt
= INetURLObject::CompareProtocolScheme( aBaseURL
);
767 // if a base URL is set the string may be parsed relative
768 if( aText
.startsWith( "/" ) )
770 // text starting with slashes means absolute file URLs
771 OUString aTemp
= INetURLObject::GetScheme( eBaseProt
);
773 // file URL must be correctly encoded!
774 OUString aTextURL
= INetURLObject::encode( aText
, INetURLObject::PART_FPATH
,
775 INetURLObject::EncodeMechanism::All
);
778 INetURLObject
aTmp( aTemp
);
779 if ( !aTmp
.HasError() && aTmp
.GetProtocol() != INetProtocol::NotValid
)
780 aMatch
= aTmp
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
784 OUString
aSmart( aText
);
785 INetURLObject
aObj( aBaseURL
);
787 // HRO: I suppose this hack should only be done for Windows !!!???
789 // HRO: INetURLObject::smatRel2Abs does not recognize '\\' as a relative path
790 // but in case of "\\\\" INetURLObject is right - this is an absolute path !
792 if( aText
.startsWith("\\") && (aText
.getLength() < 2 || aText
[ 1 ] != '\\') )
794 // cut to first segment
795 OUString aTmp
= INetURLObject::GetScheme( eBaseProt
) + "/";
796 aTmp
+= aObj
.getName( 0, true, INetURLObject::DecodeMechanism::WithCharset
);
799 aSmart
= aSmart
.copy(1);
802 // base URL must be a directory !
803 aObj
.setFinalSlash();
805 // take base URL and append current input
806 bool bWasAbsolute
= false;
808 // encode file URL correctly
809 aSmart
= INetURLObject::encode( aSmart
, INetURLObject::PART_FPATH
, INetURLObject::EncodeMechanism::All
);
811 INetURLObject
aTmp( aObj
.smartRel2Abs( aSmart
, bWasAbsolute
) );
813 if ( aText
.endsWith(".") )
814 // INetURLObject appends a final slash for the directories "." and "..", this is a bug!
815 // Remove it as a workaround
816 aTmp
.removeFinalSlash();
817 if ( !aTmp
.HasError() && aTmp
.GetProtocol() != INetProtocol::NotValid
)
818 aMatch
= aTmp
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
824 osl::FileBase::getFileURLFromSystemPath( aText
, aTmpMatch
);
831 IMPL_LINK_NOARG(SvtURLBox
, TryAutoComplete
, Timer
*, void)
833 OUString aCurText
= m_xWidget
->get_active_text();
834 int nStartPos
, nEndPos
;
835 m_xWidget
->get_entry_selection_bounds(nStartPos
, nEndPos
);
836 if (std::max(nStartPos
, nEndPos
) != aCurText
.getLength())
839 auto nLen
= std::min(nStartPos
, nEndPos
);
840 aCurText
= aCurText
.copy( 0, nLen
);
841 if (!aCurText
.isEmpty())
849 pCtx
= new SvtMatchContext_Impl(this, aCurText
);
856 SvtURLBox::SvtURLBox(std::unique_ptr
<weld::ComboBox
> pWidget
)
857 : aChangedIdle("svtools::URLBox aChangedIdle")
858 , eSmartProtocol(INetProtocol::NotValid
)
859 , bOnlyDirectories( false )
860 , bHistoryDisabled( false )
861 , bNoSelection( false )
862 , m_xWidget(std::move(pWidget
))
864 //don't grow to fix mega-long urls
865 Size
aSize(m_xWidget
->get_preferred_size());
866 m_xWidget
->set_size_request(aSize
.Width(), -1);
870 m_xWidget
->connect_focus_in(LINK(this, SvtURLBox
, FocusInHdl
));
871 m_xWidget
->connect_focus_out(LINK(this, SvtURLBox
, FocusOutHdl
));
872 m_xWidget
->connect_changed(LINK(this, SvtURLBox
, ChangedHdl
));
874 aChangedIdle
.SetInvokeHandler(LINK(this, SvtURLBox
, TryAutoComplete
));
877 void SvtURLBox::Init()
879 pImpl
.reset( new SvtURLBox_Impl
);
881 m_xWidget
->set_entry_completion(false);
883 UpdatePicklistForSmartProtocol_Impl();
886 SvtURLBox::~SvtURLBox()
895 void SvtURLBox::SetSmartProtocol(INetProtocol eProt
)
897 if ( eSmartProtocol
!= eProt
)
899 eSmartProtocol
= eProt
;
900 UpdatePicklistForSmartProtocol_Impl();
904 void SvtURLBox::UpdatePicklistForSmartProtocol_Impl()
907 if ( bHistoryDisabled
)
910 if (bHistoryDisabled
)
913 // read history pick list
914 const std::vector
< SvtHistoryOptions::HistoryItem
> seqPicklist
= SvtHistoryOptions::GetList( EHistoryType::PickList
);
915 INetURLObject aCurObj
;
917 for( const SvtHistoryOptions::HistoryItem
& rPropertySet
: seqPicklist
)
919 aCurObj
.SetURL( rPropertySet
.sURL
);
921 if ( !rPropertySet
.sURL
.isEmpty() && ( eSmartProtocol
!= INetProtocol::NotValid
) )
923 if( aCurObj
.GetProtocol() != eSmartProtocol
)
927 OUString
aURL( aCurObj
.GetMainURL( INetURLObject::DecodeMechanism::WithCharset
) );
929 if ( !aURL
.isEmpty() )
931 bool bFound
= aURL
.endsWith("/");
934 OUString aUpperURL
= aURL
.toAsciiUpperCase();
936 bFound
= ::std::any_of(pImpl
->m_aFilters
.begin(),
937 pImpl
->m_aFilters
.end(),
938 FilterMatch( aUpperURL
) );
943 if (osl::FileBase::getSystemPathFromFileURL(aURL
, aFile
) == osl::FileBase::E_None
)
944 m_xWidget
->append_text(aFile
);
946 m_xWidget
->append_text(aURL
);
952 IMPL_LINK_NOARG(SvtURLBox
, ChangedHdl
, weld::ComboBox
&, void)
954 aChangeHdl
.Call(*m_xWidget
);
955 aChangedIdle
.Start(); //launch this to happen on idle after cursor position will have been set
958 IMPL_LINK_NOARG(SvtURLBox
, FocusInHdl
, weld::Widget
&, void)
961 // pb: don't select automatically on unix #93251#
962 m_xWidget
->select_entry_region(0, -1);
964 aFocusInHdl
.Call(*m_xWidget
);
967 IMPL_LINK_NOARG(SvtURLBox
, FocusOutHdl
, weld::Widget
&, void)
975 aFocusOutHdl
.Call(*m_xWidget
);
978 void SvtURLBox::SetOnlyDirectories( bool bDir
)
980 bOnlyDirectories
= bDir
;
981 if ( bOnlyDirectories
)
985 void SvtURLBox::SetNoURLSelection( bool bSet
)
990 OUString
SvtURLBox::GetURL()
992 // wait for end of autocompletion
993 ::osl::MutexGuard
aGuard( theSvtMatchContextMutex() );
995 OUString
aText(m_xWidget
->get_active_text());
996 if (MatchesPlaceHolder(aText
))
999 // try to get the right case preserving URL from the list of URLs
1000 for(std::vector
<OUString
>::iterator i
= pImpl
->aCompletions
.begin(), j
= pImpl
->aURLs
.begin(); i
!= pImpl
->aCompletions
.end() && j
!= pImpl
->aURLs
.end(); ++i
, ++j
)
1007 // erase trailing spaces on Windows since they are invalid on this OS and
1008 // most of the time they are inserted by accident via copy / paste
1009 aText
= comphelper::string::stripEnd(aText
, ' ');
1010 if ( aText
.isEmpty() )
1015 INetURLObject
aObj( aText
);
1016 if( aText
.indexOf( '*' ) != -1 || aText
.indexOf( '?' ) != -1 )
1018 // no autocompletion for wildcards
1019 INetURLObject aTempObj
;
1020 if ( eSmartProtocol
!= INetProtocol::NotValid
)
1021 aTempObj
.SetSmartProtocol( eSmartProtocol
);
1022 if ( aTempObj
.SetSmartURL( aText
) )
1023 return aTempObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
1028 if ( aObj
.GetProtocol() == INetProtocol::NotValid
)
1030 OUString aName
= ParseSmart( aText
, aBaseURL
);
1032 OUString
aURL( aObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
) );
1033 if ( aURL
.isEmpty() )
1034 // aText itself is invalid, and even together with aBaseURL, it could not
1035 // made valid -> no chance
1038 bool bSlash
= aObj
.hasFinalSlash();
1042 Any aAny
= UCBContentHelper::GetProperty(aURL
, "CasePreservingURL");
1043 bool success
= (aAny
>>= aFileURL
);
1046 aTitle
= INetURLObject(aFileURL
).getName(
1047 INetURLObject::LAST_SEGMENT
,
1049 INetURLObject::DecodeMechanism::WithCharset
);
1052 UCBContentHelper::GetTitle(aURL
,&aTitle
);
1054 if( success
&& aTitle
!= "/" && aTitle
!= "." )
1056 aObj
.setName( aTitle
);
1058 aObj
.setFinalSlash();
1063 return aObj
.GetMainURL( INetURLObject::DecodeMechanism::NONE
);
1066 void SvtURLBox::SetBaseURL( const OUString
& rURL
)
1068 ::osl::MutexGuard
aGuard( theSvtMatchContextMutex() );
1070 // Reset match lists
1071 pImpl
->aCompletions
.clear();
1072 pImpl
->aURLs
.clear();
1077 void SvtURLBox::DisableHistory()
1079 bHistoryDisabled
= true;
1080 UpdatePicklistForSmartProtocol_Impl();
1083 void SvtURLBox::SetFilter(std::u16string_view _sFilter
)
1085 pImpl
->m_aFilters
.clear();
1086 FilterMatch::createWildCardFilterList(_sFilter
,pImpl
->m_aFilters
);
1089 void FilterMatch::createWildCardFilterList(std::u16string_view _rFilterList
,::std::vector
< WildCard
>& _rFilters
)
1091 if( !_rFilterList
.empty() )
1094 sal_Int32 nIndex
= 0;
1098 sToken
= o3tl::getToken(_rFilterList
, 0, ';', nIndex
);
1099 if ( !sToken
.isEmpty() )
1101 _rFilters
.emplace_back( sToken
.toAsciiUpperCase() );
1104 while ( nIndex
>= 0 );
1108 // no filter is given -> match all
1109 _rFilters
.emplace_back(u
"*" );
1113 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */