Kaydet (Commit) 6c97feec authored tarafından Muhammet Kara's avatar Muhammet Kara

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: 's avatarHeiko Tietze <tietze.heiko@gmail.com>
Tested-by: 's avatarHeiko Tietze <tietze.heiko@gmail.com>
Reviewed-by: 's avatarMuhammet Kara <muhammet.kara@pardus.org.tr>
üst 1aae63b6
......@@ -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,\
......
......@@ -40,11 +40,131 @@
#include <ucbhelper/content.hxx>
#include <comphelper/simplefileaccessinteraction.hxx>
#include <curl/curl.h>
#include <orcus/json_document_tree.hpp>
#include <orcus/config.hpp>
#include <orcus/pstring.hpp>
#include <vector>
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<std::string *>(userp);
size_t real_size = size * nmemb;
response->append(static_cast<char *>(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<SvStream *>(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<void *>(&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<void *>(&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 &quot; or &#34;
// let's replace the whole buffer with last one so we can treat it easily
OString rBuffer = rBufferArg.replaceAll(OString("&quot;"), OString("&#34;"));
// 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<XComponentContext> 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<PersonaInfo> & 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<XComponentContext> 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<PersonaInfo> 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<weld::MessageDialog> 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<weld::MessageDialog> 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<OUString> 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<weld::MessageDialog> 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 )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment