From 6c97feec872e82711f58cce61b7796ed594e552c Mon Sep 17 00:00:00 2001 From: Muhammet Kara Date: Fri, 5 Oct 2018 03:35:43 +0300 Subject: Modernize personas under-the-hood - The search * Upgrade the used mozilla API from v1.5 to v3 * Ditch Neon for curl * Get rid of the get-HTML-then-search-within craziness It is much faster and smarter now: * Fetches the search results at once in JSON format, instead of repetitively making http calls for each persona, and relying on an ever-changing HTML design * Doesn't redownload and overwrite files each time, so it is much faster for the second time of the same search query This patch handles the search part. A follow-up patch will handle the apply part. Change-Id: I703fc7b510799e8c205566cf5ffad2a81f12c4ea Reviewed-on: https://gerrit.libreoffice.org/61449 Tested-by: Jenkins Reviewed-by: Heiko Tietze Tested-by: Heiko Tietze Reviewed-by: Muhammet Kara --- cui/Library_cui.mk | 3 + cui/source/options/personalization.cxx | 547 +++++++++++++++++++++------------ 2 files changed, 345 insertions(+), 205 deletions(-) diff --git a/cui/Library_cui.mk b/cui/Library_cui.mk index 1c6185488f36..26900dec8e3a 100644 --- a/cui/Library_cui.mk +++ b/cui/Library_cui.mk @@ -66,8 +66,11 @@ $(eval $(call gb_Library_use_externals,cui,\ boost_headers \ $(call gb_Helper_optional,OPENCL,\ clew) \ + curl \ icuuc \ icu_headers \ + orcus-parser \ + orcus \ )) ifeq ($(DISABLE_GUI),) $(eval $(call gb_Library_use_externals,cui,\ diff --git a/cui/source/options/personalization.cxx b/cui/source/options/personalization.cxx index d30c1c1d2347..bbaa03e8d3a1 100644 --- a/cui/source/options/personalization.cxx +++ b/cui/source/options/personalization.cxx @@ -40,11 +40,131 @@ #include #include +#include + +#include +#include +#include + +#include + using namespace com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::ucb; using namespace ::com::sun::star::beans; +#ifdef UNX +static const char kUserAgent[] = "LibreOffice PersonaDownloader/1.0 (Linux)"; +#else +static const char kUserAgent[] = "LibreOffice PersonaDownloader/1.0 (unknown platform)"; +#endif + +struct PersonaInfo +{ + OUString sSlug; + OUString sName; + OUString sPreviewURL; + OUString sHeaderURL; + OUString sFooterURL; + OUString sTextColor; + OUString sAccentColor; +}; + +namespace { + +// Callback to get the response data from server. +size_t WriteCallback(void *ptr, size_t size, + size_t nmemb, void *userp) +{ + if (!userp) + return 0; + + std::string* response = static_cast(userp); + size_t real_size = size * nmemb; + response->append(static_cast(ptr), real_size); + return real_size; +} + +// Callback to get the response data from server to a file. +size_t WriteCallbackFile(void *ptr, size_t size, + size_t nmemb, void *userp) +{ + if (!userp) + return 0; + + SvStream* response = static_cast(userp); + size_t real_size = size * nmemb; + response->WriteBytes(ptr, real_size); + return real_size; +} + +// Gets the content of the given URL and returns as a standard string +std::string curlGet(const OString& rURL) +{ + CURL* curl = curl_easy_init(); + + if (!curl) + return std::string(); + + curl_easy_setopt(curl, CURLOPT_URL, rURL.getStr()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent); + + std::string response_body; + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, + static_cast(&response_body)); + + CURLcode cc = curl_easy_perform(curl); + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (http_code != 200) + { + SAL_WARN("cui.options", "Download failed. Error code: " << http_code); + } + + if (cc != CURLE_OK) + { + SAL_WARN("cui.options", "curl error: " << cc); + } + + return response_body; +} + +// Downloads and saves the file at the given rURL to to a local path (sFileURL) +void curlDownload(const OString& rURL, const OUString& sFileURL) +{ + CURL* curl = curl_easy_init(); + SvFileStream aFile( sFileURL, StreamMode::WRITE ); + + if (!curl) + return; + + curl_easy_setopt(curl, CURLOPT_URL, rURL.getStr()); + curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent); + + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallbackFile); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, + static_cast(&aFile)); + + CURLcode cc = curl_easy_perform(curl); + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + if (http_code != 200) + { + SAL_WARN("cui.options", "Download failed. Error code: " << http_code); + } + + if (cc != CURLE_OK) + { + SAL_WARN("cui.options", "curl error: " << cc); + } +} + +} //End of anonymous namespace + SelectPersonaDialog::SelectPersonaDialog( vcl::Window *pParent ) : ModalDialog( pParent, "SelectPersonaDialog", "cui/ui/select_persona_dialog.ui" ) { @@ -134,6 +254,12 @@ IMPL_LINK( SelectPersonaDialog, SearchPersonas, Button*, pButton, void ) * These strings should be in sync with the strings of * RID_SVXSTR_PERSONA_CATEGORIES in personalization.hrc */ + /* FIXME: These categories are actual categories of Mozilla themes/personas, + * but we are using them just as regular search terms, and bringing the first + * 9 (MAX_RESULTS) personas which have all the fields set. We should instead bring + * results from the actual categories; maybe the most downloaded one, or the ones with + * the highest ratings. + */ static const OUStringLiteral vSuggestionCategories[] = {"LibreOffice", "Abstract", "Color", "Music", "Nature", "Solid"}; @@ -159,16 +285,19 @@ IMPL_LINK( SelectPersonaDialog, SearchPersonas, Button*, pButton, void ) if( searchTerm.isEmpty( ) ) return; - // 15 results so that invalid and duplicate search results whose names can't be retrieved can be skipped - OUString rSearchURL = "https://services.addons.mozilla.org/en-US/firefox/api/1.5/search/" + searchTerm + "/9/15"; - if ( searchTerm.startsWith( "https://addons.mozilla.org/" ) ) { - searchTerm = "https://addons.mozilla.org/en-US/" + searchTerm.copy( searchTerm.indexOf( "firefox" ) ); - m_pSearchThread = new SearchAndParseThread( this, searchTerm, true ); + OUString sSlug = searchTerm.getToken( 6, '/' ); + + m_pSearchThread = new SearchAndParseThread( this, sSlug, true ); } else + { + // 15 results so that invalid and duplicate search results whose names, textcolors etc. are null can be skipped + OUString rSearchURL = "https://addons.mozilla.org/api/v3/addons/search/?q=" + searchTerm + "&type=persona&page_size=15"; + m_pSearchThread = new SearchAndParseThread( this, rSearchURL, false ); + } m_pSearchThread->launch(); } @@ -216,15 +345,11 @@ IMPL_LINK( SelectPersonaDialog, SelectPersona, Button*, pButton, void ) if( !m_vPersonaSettings[index].isEmpty() ) { m_aSelectedPersona = m_vPersonaSettings[index]; + // get the persona name from the setting variable to show in the progress. - sal_Int32 nSlugIndex, nNameIndex; - OUString aName, aProgress; - - // Skip the slug - nSlugIndex = m_aSelectedPersona.indexOf( ';' ); - nNameIndex = m_aSelectedPersona.indexOf( ';', nSlugIndex ); - aName = m_aSelectedPersona.copy( nSlugIndex + 1, nNameIndex ); - aProgress = CuiResId(RID_SVXSTR_SELECTEDPERSONA) + aName; + OUString aName( m_aSelectedPersona.getToken( 1, ';' ) ); + OUString aProgress( CuiResId(RID_SVXSTR_SELECTEDPERSONA) + aName ); + SetProgress( aProgress ); } break; @@ -579,68 +704,6 @@ IMPL_LINK_NOARG( SvxPersonalizationTabPage, SelectInstalledPersona, ListBox&, vo m_pExtensionPersonaPreview->SetModeImage( Image( aBmp ) ); } -/// Find the value on the Persona page, and convert it to a usable form. -static OUString searchValue( const OString &rBuffer, sal_Int32 from, const OString &rIdentifier ) -{ - sal_Int32 where = rBuffer.indexOf( rIdentifier, from ); - if ( where < 0 ) - return OUString(); - - where += rIdentifier.getLength(); - - sal_Int32 end = rBuffer.indexOf( "\"", where ); - if ( end < 0 ) - return OUString(); - - OString aOString( rBuffer.copy( where, end - where ) ); - OUString aString( aOString.getStr(), aOString.getLength(), RTL_TEXTENCODING_UTF8, OSTRING_TO_OUSTRING_CVTFLAGS ); - - return aString.replaceAll( "\\u002F", "/" ); -} - -/// Parse the Persona web page, and find where to get the bitmaps + the color values. -static bool parsePersonaInfo( const OString &rBufferArg, OUString *pHeaderURL, OUString *pFooterURL, - OUString *pTextColor, OUString *pAccentColor, OUString *pPreviewURL, - OUString *pName, OUString *pSlug ) -{ - // tdf#115417: buffer retrieved from html response can contain " or " - // let's replace the whole buffer with last one so we can treat it easily - OString rBuffer = rBufferArg.replaceAll(OString("""), OString(""")); - // it is the first attribute that contains "persona=" - sal_Int32 persona = rBuffer.indexOf( "\"type\":\"persona\"" ); - if ( persona < 0 ) - return false; - - // now search inside - *pHeaderURL = searchValue( rBuffer, persona, "\"headerURL\":\"" ); - if ( pHeaderURL->isEmpty() ) - return false; - - *pFooterURL = searchValue( rBuffer, persona, "\"footerURL\":\"" ); - if ( pFooterURL->isEmpty() ) - return false; - - *pTextColor = searchValue( rBuffer, persona, "\"textcolor\":\"" ); - if ( pTextColor->isEmpty() ) - return false; - - *pAccentColor = searchValue( rBuffer, persona, "\"accentcolor\":\"" ); - if ( pAccentColor->isEmpty() ) - return false; - - *pPreviewURL = searchValue( rBuffer, persona, "\"previewURL\":\"" ); - if ( pPreviewURL->isEmpty() ) - return false; - - *pName = searchValue( rBuffer, persona, "\"name\":\"" ); - if ( pName->isEmpty() ) - return false; - - *pSlug = searchValue( rBuffer, persona, "\"bySlug\":{\"" ); - - return !pSlug->isEmpty(); -} - SearchAndParseThread::SearchAndParseThread( SelectPersonaDialog* pDialog, const OUString& rURL, bool bDirectURL ) : Thread( "cuiPersonasSearchThread" ), @@ -657,191 +720,265 @@ SearchAndParseThread::~SearchAndParseThread() namespace { -bool getPreviewFile( const OUString& rURL, OUString *pPreviewFile, OUString *pPersonaSetting ) +bool getPreviewFile( const PersonaInfo& aPersonaInfo, OUString& pPreviewFile ) { uno::Reference< ucb::XSimpleFileAccess3 > xFileAccess( ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() ), uno::UNO_QUERY ); - if ( !xFileAccess.is() ) - return false; - Reference xContext( ::comphelper::getProcessComponentContext() ); - uno::Reference< io::XInputStream > xStream; - try { - css:: uno::Reference< task::XInteractionHandler > xIH( - css::task::InteractionHandler::createWithParent( xContext, nullptr ) ); - xFileAccess->setInteractionHandler( new comphelper::SimpleFileAccessInteraction( xIH ) ); - xStream = xFileAccess->openFileRead( rURL ); + // copy the images to the user's gallery + OUString gallery = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; + rtl::Bootstrap::expandMacros( gallery ); + gallery += "/user/gallery/personas/" + aPersonaInfo.sSlug + "/"; + + OUString aPreviewFile( INetURLObject( aPersonaInfo.sPreviewURL ).getName() ); + OString aPreviewURL = OUStringToOString( aPersonaInfo.sPreviewURL, RTL_TEXTENCODING_UTF8 ); - if( !xStream.is() ) - return false; + try { + osl::Directory::createPath( gallery ); + + if ( !xFileAccess->exists( gallery + aPreviewFile ) ) + curlDownload(aPreviewURL, gallery + aPreviewFile); } - catch (...) + catch ( const uno::Exception & ) { return false; } + pPreviewFile = gallery + aPreviewFile; + return true; +} - // read the persona specification - // NOTE: Parsing for real is an overkill here; and worse - I tried, and - // the HTML the site provides is not 100% valid ;-) - const sal_Int32 BUF_LEN = 8000; - uno::Sequence< sal_Int8 > buffer( BUF_LEN ); - OStringBuffer aBuffer( 64000 ); +void parseResponse(const std::string& rResponse, std::vector & aPersonas) +{ + orcus::json::document_tree aJsonDoc; + orcus::json_config aConfig; - sal_Int32 nRead = 0; - while ( ( nRead = xStream->readBytes( buffer, BUF_LEN ) ) == BUF_LEN ) - aBuffer.append( reinterpret_cast< const char* >( buffer.getConstArray() ), nRead ); + if (rResponse.empty()) + return; - if ( nRead > 0 ) - aBuffer.append( reinterpret_cast< const char* >( buffer.getConstArray() ), nRead ); + aJsonDoc.load(rResponse, aConfig); - xStream->closeInput(); + auto aDocumentRoot = aJsonDoc.get_document_root(); + if (aDocumentRoot.type() != orcus::json::node_t::object) + { + SAL_WARN("cui.options", "invalid root entries: " << rResponse); + return; + } - // get the important bits of info - OUString aHeaderURL, aFooterURL, aTextColor, aAccentColor, aPreviewURL, aName, aSlug; + auto resultsArray = aDocumentRoot.child("results"); - if ( !parsePersonaInfo( aBuffer.makeStringAndClear(), &aHeaderURL, &aFooterURL, &aTextColor, &aAccentColor, &aPreviewURL, &aName, &aSlug ) ) - return false; + for (size_t i = 0; i < resultsArray.child_count(); ++i) + { + auto arrayElement = resultsArray.child(i); - // copy the images to the user's gallery - OUString gallery = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE( "bootstrap") "::UserInstallation}"; - rtl::Bootstrap::expandMacros( gallery ); - gallery += "/user/gallery/personas/" + aSlug + "/"; + try + { + PersonaInfo aNewPersona = { + OStringToOUString( OString(arrayElement.child("slug").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(arrayElement.child("name").child("en-US").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(arrayElement.child("theme_data").child("previewURL").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(arrayElement.child("theme_data").child("headerURL").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(arrayElement.child("theme_data").child("footerURL").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(arrayElement.child("theme_data").child("textcolor").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(arrayElement.child("theme_data").child("accentcolor").string_value().get()), + RTL_TEXTENCODING_UTF8 ) + }; + + aPersonas.push_back(aNewPersona); + } + catch(orcus::json::document_error& e) + { + // This usually happens when one of the values is null (type() == orcus::json::node_t::null) + // TODO: Allow null values in personas. + SAL_WARN("cui.options", "Persona JSON parse error: " << e.what()); + } + } +} - OUString aPreviewFile( INetURLObject( aPreviewURL ).getName() ); +PersonaInfo parseSingleResponse(const std::string& rResponse) +{ + orcus::json::document_tree aJsonDoc; + orcus::json_config aConfig; - try { - osl::Directory::createPath( gallery ); + if (rResponse.empty()) + return PersonaInfo(); - if ( !xFileAccess->exists( gallery + aPreviewFile ) ) - xFileAccess->copy( aPreviewURL, gallery + aPreviewFile ); + aJsonDoc.load(rResponse, aConfig); + + auto aDocumentRoot = aJsonDoc.get_document_root(); + if (aDocumentRoot.type() != orcus::json::node_t::object) + { + SAL_WARN("cui.options", "invalid root entries: " << rResponse); + return PersonaInfo(); } - catch ( const uno::Exception & ) + + auto theme_data = aDocumentRoot.child("theme_data"); + + try { - return false; + PersonaInfo aNewPersona = { + OUString(), + OStringToOUString( OString(theme_data.child("name").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(theme_data.child("previewURL").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(theme_data.child("headerURL").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(theme_data.child("footerURL").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(theme_data.child("textcolor").string_value().get()), + RTL_TEXTENCODING_UTF8 ), + OStringToOUString( OString(theme_data.child("accentcolor").string_value().get()), + RTL_TEXTENCODING_UTF8 ) + }; + + return aNewPersona; + } + catch(orcus::json::document_error& e) + { + // This usually happens when one of the values is null (type() == orcus::json::node_t::null) + // TODO: Allow null values in personas. + // TODO: Give a message to the user + SAL_WARN("cui.options", "Persona JSON parse error: " << e.what()); + return PersonaInfo(); } - *pPreviewFile = gallery + aPreviewFile; - *pPersonaSetting = aSlug + ";" + aName + ";" + aHeaderURL + ";" + aFooterURL + ";" + aTextColor + ";" + aAccentColor; - return true; } -} +} //End of anonymous namespace void SearchAndParseThread::execute() { m_pPersonaDialog->ClearSearchResults(); - OUString sProgress( CuiResId( RID_SVXSTR_SEARCHING ) ), sError; + OUString sProgress( CuiResId( RID_SVXSTR_SEARCHING ) ); m_pPersonaDialog->SetProgress( sProgress ); - PersonasDocHandler* pHandler = new PersonasDocHandler(); - Reference xContext( ::comphelper::getProcessComponentContext() ); - Reference< xml::sax::XParser > xParser = xml::sax::Parser::create(xContext); - Reference< xml::sax::XDocumentHandler > xDocHandler = pHandler; uno::Reference< ucb::XSimpleFileAccess3 > xFileAccess( ucb::SimpleFileAccess::create( comphelper::getProcessComponentContext() ), uno::UNO_QUERY ); - uno::Reference< io::XInputStream > xStream; - xParser->setDocumentHandler( xDocHandler ); - if( !m_bDirectURL ) + if (!m_bDirectURL) { - if ( !xFileAccess.is() ) - return; + OString rURL = OUStringToOString( m_aURL, RTL_TEXTENCODING_UTF8 ); + std::string sResponse = curlGet(rURL); - try { - css:: uno::Reference< task::XInteractionHandler > xIH( - css::task::InteractionHandler::createWithParent( xContext, nullptr ) ); + std::vector personaInfos; - xFileAccess->setInteractionHandler( new comphelper::SimpleFileAccessInteraction( xIH ) ); + parseResponse(sResponse, personaInfos); - xStream = xFileAccess->openFileRead( m_aURL ); - if( !xStream.is() ) + if ( personaInfos.empty() ) + { + sProgress = CuiResId( RID_SVXSTR_NORESULTS ); + m_pPersonaDialog->SetProgress( sProgress ); + return; + } + else + { + //Get Preview Files + sal_Int32 nIndex = 0; + for (const auto & personaInfo : personaInfos) { - // in case of a returned CommandFailedException - // SimpleFileAccess serves it, returning an empty stream + if( !m_bExecute ) + return; + + OUString aPreviewFile; + bool bResult = getPreviewFile(personaInfo, aPreviewFile); + + if (!bResult) + { + SAL_INFO("cui.options", "Couldn't get the preview file. Skipping: " << aPreviewFile); + continue; + } + + + GraphicFilter aFilter; + Graphic aGraphic; + + INetURLObject aURLObj( aPreviewFile ); + + OUString aPersonaSetting = personaInfos[nIndex].sSlug + + ";" + personaInfos[nIndex].sName + + ";" + personaInfos[nIndex].sHeaderURL + + ";" + personaInfos[nIndex].sFooterURL + + ";" + personaInfos[nIndex].sTextColor + + ";" + personaInfos[nIndex].sAccentColor; + + m_pPersonaDialog->AddPersonaSetting( aPersonaSetting ); + + // for VCL to be able to create bitmaps / do visual changes in the thread SolarMutexGuard aGuard; - sError = CuiResId(RID_SVXSTR_SEARCHERROR); - sError = sError.replaceAll("%1", m_aURL); - m_pPersonaDialog->SetProgress( OUString() ); - - std::unique_ptr xBox(Application::CreateMessageDialog(nullptr, - VclMessageType::Error, VclButtonsType::Ok, - sError)); - xBox->run(); - return; + aFilter.ImportGraphic( aGraphic, aURLObj ); + BitmapEx aBmp = aGraphic.GetBitmapEx(); + + m_pPersonaDialog->SetImages( Image( aBmp ), nIndex++ ); + m_pPersonaDialog->setOptimalLayoutSize(); + + if (nIndex >= MAX_RESULTS) + break; } + + //TODO: Give a message to the user if nIndex == 0 } - catch (...) - { - // a catch all clause, in case the exception is not - // served elsewhere - SolarMutexGuard aGuard; - sError = CuiResId(RID_SVXSTR_SEARCHERROR); - sError = sError.replaceAll("%1", m_aURL); - m_pPersonaDialog->SetProgress( OUString() ); - std::unique_ptr xBox(Application::CreateMessageDialog(nullptr, - VclMessageType::Error, VclButtonsType::Ok, - sError)); - xBox->run(); - return; - } + } + else + { + //Now we have a slug instead a search term in m_aURL + OString rURL = "https://addons.mozilla.org/api/v3/addons/addon/" + + OUStringToOString( m_aURL, RTL_TEXTENCODING_UTF8 ) + + "/"; + std::string sResponse = curlGet(rURL); - xml::sax::InputSource aParserInput; - aParserInput.aInputStream = xStream; - xParser->parseStream( aParserInput ); + PersonaInfo aPersonaInfo = parseSingleResponse(sResponse); + aPersonaInfo.sSlug = m_aURL; - if( !pHandler->hasResults() ) + if ( aPersonaInfo.sName.isEmpty() ) { + //TODO: Give error message to user sProgress = CuiResId( RID_SVXSTR_NORESULTS ); m_pPersonaDialog->SetProgress( sProgress ); return; } - } - - std::vector vLearnmoreURLs; - sal_Int32 nIndex = 0; - GraphicFilter aFilter; - Graphic aGraphic; + else + { + //Get the preview file + if( !m_bExecute ) + return; - if( !m_bDirectURL ) - vLearnmoreURLs = pHandler->getLearnmoreURLs(); - else - vLearnmoreURLs.push_back( m_aURL ); + OUString aPreviewFile; + bool bResult = getPreviewFile(aPersonaInfo, aPreviewFile); - for (auto const& learnMoreUrl : vLearnmoreURLs) - { - OUString sPreviewFile, aPersonaSetting; - bool bResult = getPreviewFile( learnMoreUrl, &sPreviewFile, &aPersonaSetting ); - // parsing is buggy at times, as HTML is not proper. Skip it. - if(aPersonaSetting.isEmpty() || !bResult) - { - if( m_bDirectURL ) + if (!bResult) { - SolarMutexGuard aGuard; - sError = CuiResId(RID_SVXSTR_SEARCHERROR); - sError = sError.replaceAll("%1", m_aURL); - m_pPersonaDialog->SetProgress( OUString() ); - std::unique_ptr xBox(Application::CreateMessageDialog(nullptr, - VclMessageType::Error, VclButtonsType::Ok, - sError)); - xBox->run(); + //TODO: Inform the user + SAL_WARN("cui.options", "Couldn't get the preview file: " << aPreviewFile); return; } - continue; - } - INetURLObject aURLObj( sPreviewFile ); - // Stop the thread if requested -- before taking the solar mutex. - if( !m_bExecute ) - return; + GraphicFilter aFilter; + Graphic aGraphic; - // for VCL to be able to create bitmaps / do visual changes in the thread - SolarMutexGuard aGuard; - aFilter.ImportGraphic( aGraphic, aURLObj ); - BitmapEx aBmp = aGraphic.GetBitmapEx(); + INetURLObject aURLObj( aPreviewFile ); + + OUString aPersonaSetting = aPersonaInfo.sSlug + + ";" + aPersonaInfo.sName + + ";" + aPersonaInfo.sHeaderURL + + ";" + aPersonaInfo.sFooterURL + + ";" + aPersonaInfo.sTextColor + + ";" + aPersonaInfo.sAccentColor; + + m_pPersonaDialog->AddPersonaSetting( aPersonaSetting ); + + // for VCL to be able to create bitmaps / do visual changes in the thread + SolarMutexGuard aGuard; + aFilter.ImportGraphic( aGraphic, aURLObj ); + BitmapEx aBmp = aGraphic.GetBitmapEx(); + + m_pPersonaDialog->SetImages( Image( aBmp ), 0 ); + m_pPersonaDialog->setOptimalLayoutSize(); + } - m_pPersonaDialog->SetImages( Image( aBmp ), nIndex++ ); - m_pPersonaDialog->setOptimalLayoutSize(); - m_pPersonaDialog->AddPersonaSetting( aPersonaSetting ); - if (nIndex >= MAX_RESULTS) - break; } if( !m_bExecute ) -- cgit