android: Update app-specific/MIME type icons
[LibreOffice.git] / svtools / source / control / inettbc.cxx
blob0ffad5414c0889b464ba19ff5bc5046f6bb1bfa3
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 #ifdef UNX
21 #include <pwd.h>
22 #endif
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>
54 #include <mutex>
55 #include <utility>
56 #include <vector>
57 #include <algorithm>
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;
69 class SvtURLBox_Impl
71 public:
72 std::vector<OUString> aURLs;
73 std::vector<OUString> aCompletions;
74 std::vector<WildCard> m_aFilters;
76 static bool TildeParsing( OUString& aText, OUString& aBaseUrl );
78 SvtURLBox_Impl( )
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;
90 OUString aText;
91 SvtURLBox* pBox;
92 bool bOnlyDirectories;
93 bool bNoSelection;
95 std::mutex mutex_;
96 bool stopped_;
97 css::uno::Reference< css::ucb::XCommandProcessor > processor_;
98 sal_Int32 commandId_;
100 DECL_LINK( Select_Impl, void*, void );
102 virtual ~SvtMatchContext_Impl() override;
103 virtual void execute() override;
104 void doExecute();
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);
109 public:
110 SvtMatchContext_Impl( SvtURLBox* pBoxP, OUString aText );
111 void Stop();
115 namespace
117 ::osl::Mutex& theSvtMatchContextMutex()
119 static ::osl::Mutex SINGLETON;
120 return 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 ))
128 , pBox( pBoxP )
129 , bOnlyDirectories( pBoxP->bOnlyDirectories )
130 , bNoSelection( pBoxP->bNoSelection )
131 , stopped_(false)
132 , commandId_(0)
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++ )
150 INetURLObject aURL;
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;
159 sal_Int32 id(0);
161 std::scoped_lock g(mutex_);
162 if (!stopped_) {
163 stopped_ = true;
164 proc = processor_;
165 id = commandId_;
168 if (proc.is()) {
169 proc->abort(id);
171 terminate();
174 void SvtMatchContext_Impl::execute( )
176 doExecute();
177 aLink.Call( this );
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_);
192 if (stopped_) {
193 // Completion was stopped, no display:
194 return;
198 // insert all completed strings into the listbox
199 pBox->clear();
201 for (auto const& completion : aCompletions)
203 // convert the file into a URL
204 OUString sURL;
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
217 continue;
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;
229 aURLs.clear();
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.
234 pBox->pCtx.clear();
237 void SvtMatchContext_Impl::Insert( const OUString& rCompletion,
238 const OUString& rURL,
239 bool bForce )
241 if( !bForce )
243 // avoid doubles
244 if(find(aCompletions.begin(), aCompletions.end(), rCompletion) != aCompletions.end())
245 return;
248 aCompletions.push_back(rCompletion);
249 aURLs.push_back(rURL);
253 void SvtMatchContext_Impl::ReadFolder( const OUString& rURL,
254 const OUString& rMatch,
255 bool bSmart )
257 // check folder to scan
258 if( !UCBContentHelper::IsFolder( rURL ) )
259 return;
261 bool bPureHomePath = false;
262 #ifdef UNX
263 bPureHomePath = aText.startsWith( "~" ) && aText.indexOf( '/' ) == -1;
264 #endif
266 bool bExectMatch = bPureHomePath
267 || aText == "."
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 );
281 return;
284 // string to match with
285 INetURLObject aMatchObj( rMatch );
286 OUString aMatchName;
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("/") )
297 aMatchName += "/";
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();
350 if (
351 !nMatchLen ||
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 = '/';
359 if ( bSmart )
360 // when parsing is done "smart", the delimiter must be "guessed"
361 aObj.getFSysPath( static_cast<FSysStyle>(FSysStyle::Detect & ~FSysStyle::Vos), &aDelimiter );
363 if ( bIsFolder )
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 );
369 if ( nMatchLen )
371 if (aText.endsWith(".") || bPureHomePath)
373 // if a "special folder" URL was typed, don't touch the user input
374 aMatch = aMatch.copy( nMatchLen );
376 else
378 // make the user input case preserving
379 DBG_ASSERT( aInput.getLength() >= nMatchLen, "Suspicious Matching!" );
380 aInput = aInput.copy( 0, aInput.getLength() - nMatchLen );
384 aInput += aMatch;
386 // folders should get a final slash automatically
387 if ( bIsFolder )
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_);
410 if (stopped_) {
411 return;
415 // Reset match lists
416 aCompletions.clear();
417 aURLs.clear();
419 // check for input
420 if ( aText.isEmpty() )
421 return;
423 if( aText.indexOf( '*' ) != -1 || aText.indexOf( '?' ) != -1 )
424 // no autocompletion for wildcards
425 return;
427 OUString aMatch;
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) )
439 // not stopped yet ?
440 if( schedule() )
442 if ( eProt == INetProtocol::NotValid )
443 aMatch = SvtURLBox::ParseSmart( aText, pBox->aBaseURL );
444 else
445 aMatch = aText;
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
455 // return:
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
460 bool folder = false;
461 if (aURLObject.hasFinalSlash()) {
462 try {
463 css::uno::Reference< css::uno::XComponentContext >
464 ctx(comphelper::getProcessComponentContext());
465 css::uno::Reference<
466 css::ucb::XUniversalContentBroker > ucb(
467 css::ucb::UniversalContentBroker::create(
468 ctx));
469 css::uno::Sequence< css::beans::Property > prop{
470 { /* Name */ "IsFolder",
471 /* Handle */ -1,
472 /* Type */ cppu::UnoType< bool >::get(),
473 /* Attributes */ {} }
475 css::uno::Any res;
476 css::uno::Reference< css::ucb::XCommandProcessor >
477 proc(
478 ucb->queryContent(
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();
484 try {
486 std::scoped_lock g(mutex_);
487 processor_ = proc;
488 commandId_ = id;
490 res = proc->execute(
491 css::ucb::Command(
492 "getPropertyValues", -1,
493 css::uno::Any(prop)),
495 css::uno::Reference<
496 css::ucb::XCommandEnvironment >());
497 } catch (...) {
498 if (proc2.is()) {
499 try {
500 proc2->releaseCommandIdentifier(id);
501 } catch (css::uno::RuntimeException &) {
502 TOOLS_WARN_EXCEPTION("svtools.control", "ignoring");
505 throw;
507 if (proc2.is()) {
508 proc2->releaseCommandIdentifier(id);
511 std::scoped_lock g(mutex_);
512 processor_.clear();
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:
518 if (stopped_) {
519 return;
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");
527 return;
530 if (folder)
531 Insert( aText, aMatch );
532 else
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
545 return;
547 bool bFull = false;
549 INetURLObject aCurObj;
550 OUString aCurString, aCurMainURL;
551 INetURLObject aObj;
552 aObj.SetSmartProtocol( eSmartProt == INetProtocol::NotValid ? INetProtocol::Http : eSmartProt );
553 for( ;; )
555 for(const auto& rPick : aPickList)
557 if (!schedule())
558 break;
560 aCurObj.SetURL(rPick);
561 aCurObj.SetSmartURL( aCurObj.GetURLNoPass());
562 aCurMainURL = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
564 if( eProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eProt )
565 continue;
567 if( eSmartProt != INetProtocol::NotValid && aCurObj.GetProtocol() != eSmartProt )
568 continue;
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 )
580 continue;
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() )
590 if( bFull )
591 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
592 else
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 ) )
609 if( bFull )
610 aMatch = aCurObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
611 else
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 );
626 continue;
628 break;
630 default:
632 if( bFull )
633 continue;
635 if( aCurMainURL.startsWith(aText) )
637 if( aText.getLength() < aCurMainURL.getLength() )
638 Insert( aCurMainURL, aCurMainURL );
640 continue;
642 break;
647 if( !bFull )
648 bFull = true;
649 else
650 break;
654 /** Parse leading ~ for Unix systems,
655 does nothing for Windows
657 bool SvtURLBox_Impl::TildeParsing(
658 OUString&
659 #ifdef UNX
660 aText
661 #endif
662 , OUString&
663 #ifdef UNX
664 aBaseURL
665 #endif
668 #ifdef UNX
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" );
678 if( !aHomeLocation )
679 aHomeLocation = "";
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;
688 else
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;
695 #ifdef __sun
696 Sequence< sal_Int8 > sBuf( 1024 );
697 struct passwd aTmp;
698 sal_Int32 nRes = getpwnam_r( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr(),
699 &aTmp,
700 (char*)sBuf.getArray(),
701 1024,
702 &pPasswd );
703 if( !nRes && pPasswd )
704 aParseTilde = OUString::createFromAscii(pPasswd->pw_dir);
705 else
706 return false; // no such user
707 #else
708 pPasswd = getpwnam( OUStringToOString( aUserName, RTL_TEXTENCODING_ASCII_US ).getStr() );
709 if( pPasswd )
710 aParseTilde = OUString::createFromAscii(pPasswd->pw_dir);
711 else
712 return false; // no such user
713 #endif
715 // in case the path is "~username" then there should
716 // be no trailing slash at the end
717 if( nNameEnd == -1 )
718 bTrailingSlash = false;
721 if( !bTrailingSlash )
723 if( aParseTilde.isEmpty() || aParseTilde == "/" )
725 // "/" path should be converted to "/."
726 aParseTilde = "/.";
728 else
730 // "blabla/" path should be converted to "blabla"
731 aParseTilde = comphelper::string::stripEnd(aParseTilde, '/');
734 else
736 if( !aParseTilde.endsWith("/") )
737 aParseTilde += "/";
738 if( aText.getLength() > 2 )
739 aParseTilde += aText.subView( 2 );
742 aText = aParseTilde;
743 aBaseURL.clear(); // tilde provide absolute path
745 #endif
747 return true;
750 //--
752 OUString SvtURLBox::ParseSmart( const OUString& _aText, const OUString& _aBaseURL )
754 OUString aMatch;
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 ) )
761 return OUString();
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 );
776 aTemp += aTextURL;
778 INetURLObject aTmp( aTemp );
779 if ( !aTmp.HasError() && aTmp.GetProtocol() != INetProtocol::NotValid )
780 aMatch = aTmp.GetMainURL( INetURLObject::DecodeMechanism::NONE );
782 else
784 OUString aSmart( aText );
785 INetURLObject aObj( aBaseURL );
787 // HRO: I suppose this hack should only be done for Windows !!!???
788 #ifdef _WIN32
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 );
797 aObj.SetURL( aTmp );
799 aSmart = aSmart.copy(1);
801 #endif
802 // base URL must be a directory !
803 aObj.setFinalSlash();
805 // take base URL and append current input
806 bool bWasAbsolute = false;
807 #ifdef UNX
808 // encode file URL correctly
809 aSmart = INetURLObject::encode( aSmart, INetURLObject::PART_FPATH, INetURLObject::EncodeMechanism::All );
810 #endif
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 );
821 else
823 OUString aTmpMatch;
824 osl::FileBase::getFileURLFromSystemPath( aText, aTmpMatch );
825 aMatch = aTmpMatch;
828 return aMatch;
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())
837 return;
839 auto nLen = std::min(nStartPos, nEndPos);
840 aCurText = aCurText.copy( 0, nLen );
841 if (!aCurText.isEmpty())
843 if (pCtx.is())
845 pCtx->Stop();
846 pCtx->join();
847 pCtx.clear();
849 pCtx = new SvtMatchContext_Impl(this, aCurText);
850 pCtx->launch();
852 else
853 m_xWidget->clear();
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);
868 Init();
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()
888 if (pCtx.is())
890 pCtx->Stop();
891 pCtx->join();
895 void SvtURLBox::SetSmartProtocol(INetProtocol eProt)
897 if ( eSmartProtocol != eProt )
899 eSmartProtocol = eProt;
900 UpdatePicklistForSmartProtocol_Impl();
904 void SvtURLBox::UpdatePicklistForSmartProtocol_Impl()
906 m_xWidget->clear();
907 if ( bHistoryDisabled )
908 return;
910 if (bHistoryDisabled)
911 return;
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 )
924 continue;
927 OUString aURL( aCurObj.GetMainURL( INetURLObject::DecodeMechanism::WithCharset ) );
929 if ( !aURL.isEmpty() )
931 bool bFound = aURL.endsWith("/");
932 if ( !bFound )
934 OUString aUpperURL = aURL.toAsciiUpperCase();
936 bFound = ::std::any_of(pImpl->m_aFilters.begin(),
937 pImpl->m_aFilters.end(),
938 FilterMatch( aUpperURL ) );
940 if ( bFound )
942 OUString aFile;
943 if (osl::FileBase::getSystemPathFromFileURL(aURL, aFile) == osl::FileBase::E_None)
944 m_xWidget->append_text(aFile);
945 else
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)
960 #ifndef UNX
961 // pb: don't select automatically on unix #93251#
962 m_xWidget->select_entry_region(0, -1);
963 #endif
964 aFocusInHdl.Call(*m_xWidget);
967 IMPL_LINK_NOARG(SvtURLBox, FocusOutHdl, weld::Widget&, void)
969 if (pCtx.is())
971 pCtx->Stop();
972 pCtx->join();
973 pCtx.clear();
975 aFocusOutHdl.Call(*m_xWidget);
978 void SvtURLBox::SetOnlyDirectories( bool bDir )
980 bOnlyDirectories = bDir;
981 if ( bOnlyDirectories )
982 m_xWidget->clear();
985 void SvtURLBox::SetNoURLSelection( bool bSet )
987 bNoSelection = 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))
997 return aPlaceHolder;
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)
1002 if((*i) == aText)
1003 return *j;
1006 #ifdef _WIN32
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() )
1011 return aText;
1012 // #i9739#
1013 #endif
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 );
1024 else
1025 return aText;
1028 if ( aObj.GetProtocol() == INetProtocol::NotValid )
1030 OUString aName = ParseSmart( aText, aBaseURL );
1031 aObj.SetURL(aName);
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
1036 return aText;
1038 bool bSlash = aObj.hasFinalSlash();
1040 OUString aFileURL;
1042 Any aAny = UCBContentHelper::GetProperty(aURL, "CasePreservingURL");
1043 bool success = (aAny >>= aFileURL);
1044 OUString aTitle;
1045 if(success)
1046 aTitle = INetURLObject(aFileURL).getName(
1047 INetURLObject::LAST_SEGMENT,
1048 true,
1049 INetURLObject::DecodeMechanism::WithCharset );
1050 else
1051 success =
1052 UCBContentHelper::GetTitle(aURL,&aTitle);
1054 if( success && aTitle != "/" && aTitle != "." )
1056 aObj.setName( aTitle );
1057 if ( bSlash )
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();
1074 aBaseURL = rURL;
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() )
1093 // filter is given
1094 sal_Int32 nIndex = 0;
1095 OUString sToken;
1098 sToken = o3tl::getToken(_rFilterList, 0, ';', nIndex );
1099 if ( !sToken.isEmpty() )
1101 _rFilters.emplace_back( sToken.toAsciiUpperCase() );
1104 while ( nIndex >= 0 );
1106 else
1108 // no filter is given -> match all
1109 _rFilters.emplace_back(u"*" );
1113 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */