mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-27 15:24:05 +00:00
* Major dictionary interface rework. Dictionaries now return Request objects
which can provide results asyncronously, be discarded prematurely etc. This work touches just about every piece of exiting code. + On top of the new interface, MediaWiki (Wikipedia) support was added. * Some other small fixes might went along.
This commit is contained in:
parent
b2b9798663
commit
7859daaff6
|
@ -5,15 +5,13 @@
|
|||
#include "config.hh"
|
||||
#include "htmlescape.hh"
|
||||
#include "utf8.hh"
|
||||
#include "dictlock.hh"
|
||||
#include <QFile>
|
||||
#include <set>
|
||||
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
using std::wstring;
|
||||
using std::set;
|
||||
using std::list;
|
||||
|
||||
ArticleMaker::ArticleMaker( vector< sptr< Dictionary::Class > > const & dictionaries_,
|
||||
vector< Instances::Group > const & groups_ ):
|
||||
|
@ -26,6 +24,7 @@ std::string ArticleMaker::makeHtmlHeader( QString const & word,
|
|||
QString const & icon )
|
||||
{
|
||||
string result =
|
||||
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"
|
||||
"<html><head>"
|
||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">";
|
||||
|
||||
|
@ -56,13 +55,22 @@ std::string ArticleMaker::makeHtmlHeader( QString const & word,
|
|||
return result;
|
||||
}
|
||||
|
||||
string ArticleMaker::makeDefinitionFor( QString const & inWord,
|
||||
QString const & group ) const
|
||||
std::string ArticleMaker::makeNotFoundBody( QString const & word, QString const & group )
|
||||
{
|
||||
|
||||
return string( "<div class=\"gdnotfound\"><p>" ) +
|
||||
tr( "No translation for <b>%1</b> was found in group <b>%2</b>." ).
|
||||
arg( QString::fromUtf8( Html::escape( word.toUtf8().data() ).c_str() ) ).
|
||||
arg( QString::fromUtf8( Html::escape( group.toUtf8().data() ).c_str() ) ).
|
||||
toUtf8().data()
|
||||
+"</p></div>";
|
||||
}
|
||||
|
||||
sptr< Dictionary::DataRequest > ArticleMaker::makeDefinitionFor(
|
||||
QString const & inWord, QString const & group ) const
|
||||
{
|
||||
printf( "group = %ls\n", group.toStdWString().c_str() );
|
||||
|
||||
wstring word = inWord.trimmed().toStdWString();
|
||||
|
||||
if ( group == "internal:about" )
|
||||
{
|
||||
// This is a special group containing internal welcome/help pages
|
||||
|
@ -74,6 +82,7 @@ string ArticleMaker::makeDefinitionFor( QString const & inWord,
|
|||
"<h3 align=\"center\">Welcome to <b>GoldenDict</b>!</h3>"
|
||||
"<p>To start working with the program, first add some directory paths where to search "
|
||||
"for the dictionary files at <b>Edit|Sources</b>. Note that each subdirectory is to be added separately. "
|
||||
"You can also set up Wikipedia sources there. "
|
||||
"After that, you can organize all the dictionaries found into groups "
|
||||
"in <b>Edit|Groups</b>."
|
||||
"<p>You can also check out the available program preferences at <b>Edit|Preferences</b>. "
|
||||
|
@ -115,9 +124,14 @@ string ArticleMaker::makeDefinitionFor( QString const & inWord,
|
|||
|
||||
result += "</body></html>";
|
||||
|
||||
return result;
|
||||
sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true );
|
||||
|
||||
r->getData().resize( result.size() );
|
||||
memcpy( &( r->getData().front() ), result.data(), result.size() );
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
// Find the given group
|
||||
|
||||
Instances::Group const * activeGroup = 0;
|
||||
|
@ -134,66 +148,204 @@ string ArticleMaker::makeDefinitionFor( QString const & inWord,
|
|||
std::vector< sptr< Dictionary::Class > > const & activeDicts =
|
||||
activeGroup ? activeGroup->dictionaries : dictionaries;
|
||||
|
||||
string result = makeHtmlHeader( inWord.trimmed(),
|
||||
string header = makeHtmlHeader( inWord.trimmed(),
|
||||
activeGroup && activeGroup->icon.size() ?
|
||||
activeGroup->icon : QString() );
|
||||
|
||||
DictLock _;
|
||||
return new ArticleRequest( inWord.trimmed(), group, activeDicts, header );
|
||||
}
|
||||
|
||||
sptr< Dictionary::DataRequest > ArticleMaker::makeNotFoundTextFor(
|
||||
QString const & word, QString const & group ) const
|
||||
{
|
||||
string result = makeHtmlHeader( word, QString() ) + makeNotFoundBody( word, group ) +
|
||||
"</body></html>";
|
||||
|
||||
sptr< Dictionary::DataRequestInstant > r = new Dictionary::DataRequestInstant( true );
|
||||
|
||||
r->getData().resize( result.size() );
|
||||
memcpy( &( r->getData().front() ), result.data(), result.size() );
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
//////// ArticleRequest
|
||||
|
||||
ArticleRequest::ArticleRequest(
|
||||
QString const & word_, QString const & group_,
|
||||
vector< sptr< Dictionary::Class > > const & activeDicts_,
|
||||
string const & header ):
|
||||
word( word_ ), group( group_ ), activeDicts( activeDicts_ ),
|
||||
altsDone( false ), bodyDone( false ), foundAnyDefinitions( false )
|
||||
{
|
||||
// No need to lock dataMutex on construction
|
||||
|
||||
hasAnyData = true;
|
||||
|
||||
data.resize( header.size() );
|
||||
memcpy( &data.front(), header.data(), header.size() );
|
||||
|
||||
// Accumulate main forms
|
||||
|
||||
vector< wstring > alts;
|
||||
|
||||
for( unsigned x = 0; x < activeDicts.size(); ++x )
|
||||
{
|
||||
set< wstring > altsSet;
|
||||
sptr< Dictionary::WordSearchRequest > s = activeDicts[ x ]->findHeadwordsForSynonym( word.toStdWString() );
|
||||
|
||||
connect( s.get(), SIGNAL( finished() ),
|
||||
this, SLOT( altSearchFinished() ) );
|
||||
|
||||
altSearches.push_back( s );
|
||||
}
|
||||
|
||||
altSearchFinished(); // Handle any ones which have already finished
|
||||
}
|
||||
|
||||
void ArticleRequest::altSearchFinished()
|
||||
{
|
||||
if ( altsDone )
|
||||
return;
|
||||
|
||||
// Check every request for finishing
|
||||
for( list< sptr< Dictionary::WordSearchRequest > >::iterator i =
|
||||
altSearches.begin(); i != altSearches.end(); )
|
||||
{
|
||||
if ( (*i)->isFinished() )
|
||||
{
|
||||
// This one's finished
|
||||
for( size_t count = (*i)->matchesCount(), x = 0; x < count; ++x )
|
||||
alts.insert( (**i)[ x ].word );
|
||||
|
||||
altSearches.erase( i++ );
|
||||
}
|
||||
else
|
||||
++i;
|
||||
}
|
||||
|
||||
if ( altSearches.empty() )
|
||||
{
|
||||
printf( "alts finished\n" );
|
||||
|
||||
// They all've finished! Now we can look up bodies
|
||||
|
||||
altsDone = true; // So any pending signals in queued mode won't mess us up
|
||||
|
||||
vector< wstring > altsVector( alts.begin(), alts.end() );
|
||||
|
||||
for( unsigned x = 0; x < altsVector.size(); ++x )
|
||||
{
|
||||
printf( "Alt: %ls\n", altsVector[ x ].c_str() );
|
||||
}
|
||||
|
||||
wstring wordStd = word.toStdWString();
|
||||
|
||||
for( unsigned x = 0; x < activeDicts.size(); ++x )
|
||||
{
|
||||
vector< wstring > found = activeDicts[ x ]->findHeadwordsForSynonym( word );
|
||||
sptr< Dictionary::DataRequest > r =
|
||||
activeDicts[ x ]->getArticle( wordStd, altsVector );
|
||||
|
||||
altsSet.insert( found.begin(), found.end() );
|
||||
connect( r.get(), SIGNAL( finished() ),
|
||||
this, SLOT( bodyFinished() ) );
|
||||
|
||||
bodyRequests.push_back( r );
|
||||
}
|
||||
|
||||
alts.insert( alts.begin(), altsSet.begin(), altsSet.end() );
|
||||
bodyFinished(); // Handle any ones which have already finished
|
||||
}
|
||||
|
||||
for( unsigned x = 0; x < alts.size(); ++x )
|
||||
{
|
||||
printf( "Alt: %ls\n", alts[ x ].c_str() );
|
||||
}
|
||||
|
||||
for( unsigned x = 0; x < activeDicts.size(); ++x )
|
||||
{
|
||||
try
|
||||
{
|
||||
string body = activeDicts[ x ]->getArticle( word, alts );
|
||||
|
||||
printf( "From %s: %s\n", activeDicts[ x ]->getName().c_str(), body.c_str() );
|
||||
|
||||
result += string( "<div class=\"gddictname\">" ) +
|
||||
tr( "From " ).toUtf8().data() +
|
||||
Html::escape( activeDicts[ x ]->getName() ) + "</div>" + body;
|
||||
}
|
||||
catch( Dictionary::exNoSuchWord & )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
result += "</body></html>";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string ArticleMaker::makeNotFoundTextFor( QString const & word,
|
||||
QString const & group ) const
|
||||
void ArticleRequest::bodyFinished()
|
||||
{
|
||||
return makeHtmlHeader( word, QString() ) +
|
||||
"<div class=\"gdnotfound\"><p>" +
|
||||
tr( "No translation for <b>%1</b> was found in group <b>%2</b>." ).
|
||||
arg( QString::fromUtf8( Html::escape( word.toUtf8().data() ).c_str() ) ).
|
||||
arg( QString::fromUtf8(Html::escape( group.toUtf8().data() ).c_str() ) ).
|
||||
toUtf8().data()
|
||||
+"</p></div>"
|
||||
"</body></html>";
|
||||
if ( bodyDone )
|
||||
return;
|
||||
|
||||
printf( "some body finished\n" );
|
||||
|
||||
bool wasUpdated = false;
|
||||
|
||||
while ( bodyRequests.size() )
|
||||
{
|
||||
// Since requests should go in order, check the first one first
|
||||
if ( bodyRequests.front()->isFinished() )
|
||||
{
|
||||
// Good
|
||||
|
||||
printf( "one finished.\n" );
|
||||
|
||||
Dictionary::DataRequest & req = *bodyRequests.front();
|
||||
|
||||
QString errorString = req.getErrorString();
|
||||
|
||||
if ( req.dataSize() >= 0 || errorString.size() )
|
||||
{
|
||||
string head = string( "<div class=\"gddictname\">" ) +
|
||||
Html::escape(
|
||||
tr( "From %1" ).arg( QString::fromUtf8( activeDicts[ activeDicts.size() - bodyRequests.size() ]->getName().c_str() ) ).toUtf8().data() )
|
||||
+ "</div>";
|
||||
|
||||
if ( errorString.size() )
|
||||
{
|
||||
head += "<div class=\"gderrordesc\">" +
|
||||
Html::escape( tr( "Query error: %1" ).arg( errorString ).toUtf8().data() )
|
||||
+ "</div>";
|
||||
}
|
||||
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
size_t offset = data.size();
|
||||
|
||||
data.resize( data.size() + head.size() + ( req.dataSize() > 0 ? req.dataSize() : 0 ) );
|
||||
|
||||
memcpy( &data.front() + offset, head.data(), head.size() );
|
||||
|
||||
if ( req.dataSize() > 0 )
|
||||
bodyRequests.front()->getDataSlice( 0, req.dataSize(),
|
||||
&data.front() + offset + head.size() );
|
||||
|
||||
wasUpdated = true;
|
||||
|
||||
foundAnyDefinitions = true;
|
||||
}
|
||||
printf( "erasing..\n" );
|
||||
bodyRequests.pop_front();
|
||||
printf( "erase done..\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "one not finished.\n" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( bodyRequests.empty() )
|
||||
{
|
||||
// No requests left, end the article
|
||||
|
||||
bodyDone = true;
|
||||
|
||||
{
|
||||
string footer;
|
||||
|
||||
if ( !foundAnyDefinitions )
|
||||
{
|
||||
// No definitions were ever found, say so to the user.
|
||||
footer += ArticleMaker::makeNotFoundBody( word, group );
|
||||
}
|
||||
|
||||
footer += "</body></html>";
|
||||
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
size_t offset = data.size();
|
||||
|
||||
data.resize( data.size() + footer.size() );
|
||||
|
||||
memcpy( &data.front() + offset, footer.data(), footer.size() );
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
else
|
||||
if ( wasUpdated )
|
||||
update();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#define __ARTICLE_MAKER_HH_INCLUDED__
|
||||
|
||||
#include <QObject>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include "dictionary.hh"
|
||||
#include "instances.hh"
|
||||
|
||||
|
@ -27,17 +29,57 @@ public:
|
|||
|
||||
/// Looks up the given word within the given group, and creates a full html
|
||||
/// page text containing its definition.
|
||||
std::string makeDefinitionFor( QString const & word, QString const & group ) const;
|
||||
/// The result is returned as Dictionary::DataRequest just like dictionaries
|
||||
/// themselves do. The difference is that the result is a complete html page
|
||||
/// with all definitions from all the relevant dictionaries.
|
||||
sptr< Dictionary::DataRequest > makeDefinitionFor( QString const & word, QString const & group ) const;
|
||||
|
||||
/// Makes up a text which states that no translation for the given word
|
||||
/// was found. Sometimes it's better to call this directly when it's already
|
||||
/// known that there's no translation.
|
||||
std::string makeNotFoundTextFor( QString const & word, QString const & group ) const;
|
||||
sptr< Dictionary::DataRequest > makeNotFoundTextFor( QString const & word, QString const & group ) const;
|
||||
|
||||
private:
|
||||
|
||||
/// Makes everything up to and including the opening body tag.
|
||||
static std::string makeHtmlHeader( QString const & word, QString const & icon );
|
||||
|
||||
/// Makes the html body for makeNotFoundTextFor()
|
||||
static std::string makeNotFoundBody( QString const & word, QString const & group );
|
||||
|
||||
friend class ArticleRequest; // Allow it calling makeNotFoundBody()
|
||||
};
|
||||
|
||||
/// The request specific to article maker. This should really be private,
|
||||
/// but we need it to be handled by moc.
|
||||
class ArticleRequest: public Dictionary::DataRequest
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QString word, group;
|
||||
std::vector< sptr< Dictionary::Class > > const & activeDicts;
|
||||
|
||||
std::set< std::wstring > alts; // Accumulated main forms
|
||||
std::list< sptr< Dictionary::WordSearchRequest > > altSearches;
|
||||
bool altsDone, bodyDone;
|
||||
std::list< sptr< Dictionary::DataRequest > > bodyRequests;
|
||||
bool foundAnyDefinitions;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
ArticleRequest( QString const & word, QString const & group,
|
||||
std::vector< sptr< Dictionary::Class > > const & activeDicts,
|
||||
std::string const & header );
|
||||
|
||||
virtual void cancel()
|
||||
{ finish(); } // Add our own requests cancellation here
|
||||
|
||||
private slots:
|
||||
|
||||
void altSearchFinished();
|
||||
void bodyFinished();
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "article_netmgr.hh"
|
||||
#include "dictlock.hh"
|
||||
|
||||
using std::string;
|
||||
|
||||
|
@ -27,40 +26,34 @@ QNetworkReply * ArticleNetworkAccessManager::createRequest( Operation op,
|
|||
return QNetworkAccessManager::createRequest( op, newReq, outgoingData );
|
||||
}
|
||||
|
||||
vector< char > data;
|
||||
QString contentType;
|
||||
|
||||
if ( getResource( req.url(), data, contentType ) )
|
||||
return new ArticleResourceReply( this, req, data, contentType );
|
||||
sptr< Dictionary::DataRequest > dr = getResource( req.url(), contentType );
|
||||
|
||||
if ( dr.get() )
|
||||
return new ArticleResourceReply( this, req, dr, contentType );
|
||||
}
|
||||
|
||||
return QNetworkAccessManager::createRequest( op, req, outgoingData );
|
||||
}
|
||||
|
||||
bool ArticleNetworkAccessManager::getResource( QUrl const & url,
|
||||
vector< char > & data,
|
||||
QString & contentType )
|
||||
sptr< Dictionary::DataRequest > ArticleNetworkAccessManager::getResource(
|
||||
QUrl const & url, QString & contentType )
|
||||
{
|
||||
//printf( "getResource: %ls\n", url.toString().toStdWString().c_str() );
|
||||
//printf( "scheme: %ls\n", url.scheme().toStdWString().c_str() );
|
||||
//printf( "host: %ls\n", url.host().toStdWString().c_str() );
|
||||
printf( "getResource: %ls\n", url.toString().toStdWString().c_str() );
|
||||
printf( "scheme: %ls\n", url.scheme().toStdWString().c_str() );
|
||||
printf( "host: %ls\n", url.host().toStdWString().c_str() );
|
||||
|
||||
if ( url.scheme() == "gdlookup" )
|
||||
{
|
||||
QString word = url.queryItemValue( "word" );
|
||||
QString group = url.queryItemValue( "group" );
|
||||
|
||||
string result = ( url.queryItemValue( "notfound" ) != "1" ) ?
|
||||
articleMaker.makeDefinitionFor( word, group ) :
|
||||
articleMaker.makeNotFoundTextFor( word, group );
|
||||
|
||||
data.resize( result.size() );
|
||||
|
||||
memcpy( &data.front(), result.data(), data.size() );
|
||||
|
||||
contentType = "text/html";
|
||||
|
||||
return true;
|
||||
return ( url.queryItemValue( "notfound" ) != "1" ) ?
|
||||
articleMaker.makeDefinitionFor( word, group ) :
|
||||
articleMaker.makeNotFoundTextFor( word, group );
|
||||
}
|
||||
|
||||
if ( ( url.scheme() == "bres" || url.scheme() == "gdau" ) &&
|
||||
|
@ -73,69 +66,118 @@ bool ArticleNetworkAccessManager::getResource( QUrl const & url,
|
|||
|
||||
bool search = ( id == "search" );
|
||||
|
||||
DictLock _;
|
||||
|
||||
for( unsigned x = 0; x < dictionaries.size(); ++x )
|
||||
if ( !search )
|
||||
{
|
||||
if ( search || dictionaries[ x ]->getId() == id )
|
||||
for( unsigned x = 0; x < dictionaries.size(); ++x )
|
||||
if ( dictionaries[ x ]->getId() == id )
|
||||
return dictionaries[ x ]->getResource( url.path().mid( 1 ).toUtf8().data() );
|
||||
}
|
||||
else
|
||||
{
|
||||
// We don't do search requests for now
|
||||
#if 0
|
||||
for( unsigned x = 0; x < dictionaries.size(); ++x )
|
||||
{
|
||||
try
|
||||
if ( search || dictionaries[ x ]->getId() == id )
|
||||
{
|
||||
dictionaries[ x ]->getResource( url.path().mid( 1 ).toUtf8().data(),
|
||||
data );
|
||||
try
|
||||
{
|
||||
dictionaries[ x ]->getResource( url.path().mid( 1 ).toUtf8().data(),
|
||||
data );
|
||||
|
||||
return true;
|
||||
}
|
||||
catch( Dictionary::exNoSuchResource & )
|
||||
{
|
||||
if ( !search )
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
catch( Dictionary::exNoSuchResource & )
|
||||
{
|
||||
if ( !search )
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return sptr< Dictionary::DataRequest >();
|
||||
}
|
||||
|
||||
ArticleResourceReply::ArticleResourceReply( QObject * parent,
|
||||
QNetworkRequest const & req,
|
||||
vector< char > const & data_,
|
||||
QNetworkRequest const & netReq,
|
||||
sptr< Dictionary::DataRequest > const & req_,
|
||||
QString const & contentType ):
|
||||
QNetworkReply( parent ), data( data_ ), left( data.size() )
|
||||
QNetworkReply( parent ), req( req_ ), alreadyRead( 0 )
|
||||
{
|
||||
setRequest( req );
|
||||
setRequest( netReq );
|
||||
|
||||
setOpenMode( ReadOnly );
|
||||
|
||||
if ( contentType.size() )
|
||||
setHeader( QNetworkRequest::ContentTypeHeader, contentType );
|
||||
|
||||
connect( this, SIGNAL( readyReadSignal() ),
|
||||
this, SLOT( readyReadSlot() ), Qt::QueuedConnection );
|
||||
connect( this, SIGNAL( finishedSignal() ),
|
||||
this, SLOT( finishedSlot() ), Qt::QueuedConnection );
|
||||
connect( req.get(), SIGNAL( updated() ),
|
||||
this, SLOT( reqUpdated() ) );
|
||||
|
||||
connect( req.get(), SIGNAL( finished() ),
|
||||
this, SLOT( reqFinished() ) );
|
||||
|
||||
if ( req->isFinished() || req->dataSize() > 0 )
|
||||
{
|
||||
connect( this, SIGNAL( readyReadSignal() ),
|
||||
this, SLOT( readyReadSlot() ), Qt::QueuedConnection );
|
||||
connect( this, SIGNAL( finishedSignal() ),
|
||||
this, SLOT( finishedSlot() ), Qt::QueuedConnection );
|
||||
|
||||
emit readyReadSignal();
|
||||
emit finishedSignal();
|
||||
emit readyReadSignal();
|
||||
|
||||
if ( req->isFinished() )
|
||||
{
|
||||
emit finishedSignal();
|
||||
printf( "In-place finish.\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ArticleResourceReply::reqUpdated()
|
||||
{
|
||||
emit readyRead();
|
||||
}
|
||||
|
||||
void ArticleResourceReply::reqFinished()
|
||||
{
|
||||
emit readyRead();
|
||||
finishedSlot();
|
||||
}
|
||||
|
||||
qint64 ArticleResourceReply::bytesAvailable() const
|
||||
{
|
||||
return left + QNetworkReply::bytesAvailable();
|
||||
long avail = req->dataSize();
|
||||
|
||||
if ( avail < 0 )
|
||||
return 0;
|
||||
|
||||
return (size_t) avail - alreadyRead + QNetworkReply::bytesAvailable();
|
||||
}
|
||||
|
||||
qint64 ArticleResourceReply::readData( char * out, qint64 maxSize )
|
||||
{
|
||||
printf( "====reading %d bytes\n", (int)maxSize );
|
||||
|
||||
bool finished = req->isFinished();
|
||||
|
||||
long avail = req->dataSize();
|
||||
|
||||
if ( avail < 0 )
|
||||
return finished ? -1 : 0;
|
||||
|
||||
size_t left = (size_t) avail - alreadyRead;
|
||||
|
||||
size_t toRead = maxSize < left ? maxSize : left;
|
||||
|
||||
memcpy( out, &data[ data.size() - left ], toRead );
|
||||
req->getDataSlice( alreadyRead, toRead, out );
|
||||
|
||||
left -= toRead;
|
||||
alreadyRead += toRead;
|
||||
|
||||
if ( !toRead )
|
||||
if ( !toRead && finished )
|
||||
return -1;
|
||||
else
|
||||
return toRead;
|
||||
|
@ -148,6 +190,9 @@ void ArticleResourceReply::readyReadSlot()
|
|||
|
||||
void ArticleResourceReply::finishedSlot()
|
||||
{
|
||||
if ( req->dataSize() < 0 )
|
||||
error( ContentNotFoundError );
|
||||
|
||||
finished();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,12 +28,12 @@ public:
|
|||
articleMaker( articleMaker_ )
|
||||
{}
|
||||
|
||||
/// Tries reading a resource referenced by a "bres://" url. If it succeeds,
|
||||
/// the vector is filled with data, and true is returned. If it doesn't
|
||||
/// succeed, it returns false. The function can optionally set the Content-Type
|
||||
/// header correspondingly.
|
||||
bool getResource( QUrl const & url, vector< char > & data,
|
||||
QString & contentType );
|
||||
/// Tries handling any kind of internal resources referenced by dictionaries.
|
||||
/// If it succeeds, the result is a dictionary request object. Otherwise, an
|
||||
/// empty pointer is returned.
|
||||
/// The function can optionally set the Content-Type header correspondingly.
|
||||
sptr< Dictionary::DataRequest > getResource( QUrl const & url,
|
||||
QString & contentType );
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -46,15 +46,14 @@ class ArticleResourceReply: public QNetworkReply
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
vector< char > data;
|
||||
|
||||
size_t left;
|
||||
sptr< Dictionary::DataRequest > req;
|
||||
size_t alreadyRead;
|
||||
|
||||
public:
|
||||
|
||||
ArticleResourceReply( QObject * parent,
|
||||
QNetworkRequest const &,
|
||||
vector< char > const & data,
|
||||
sptr< Dictionary::DataRequest > const &,
|
||||
QString const & contentType );
|
||||
|
||||
protected:
|
||||
|
@ -74,6 +73,9 @@ signals:
|
|||
|
||||
private slots:
|
||||
|
||||
void reqUpdated();
|
||||
void reqFinished();
|
||||
|
||||
void readyReadSlot();
|
||||
void finishedSlot();
|
||||
};
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include <mmsystem.h> // For PlaySound
|
||||
#endif
|
||||
|
||||
using std::list;
|
||||
|
||||
ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
|
||||
Instances::Groups const & groups_, bool popupView_ ):
|
||||
QFrame( parent ),
|
||||
|
@ -67,7 +69,9 @@ void ArticleView::showDefinition( QString const & word, QString const & group )
|
|||
req.addQueryItem( "word", word );
|
||||
req.addQueryItem( "group", group );
|
||||
|
||||
ui.definition->load( req );
|
||||
ui.definition->setUrl( req );
|
||||
//QApplication::setOverrideCursor( Qt::WaitCursor );
|
||||
ui.definition->setCursor( Qt::WaitCursor );
|
||||
}
|
||||
|
||||
void ArticleView::showNotFound( QString const & word, QString const & group )
|
||||
|
@ -80,20 +84,20 @@ void ArticleView::showNotFound( QString const & word, QString const & group )
|
|||
req.addQueryItem( "group", group );
|
||||
req.addQueryItem( "notfound", "1" );
|
||||
|
||||
ui.definition->load( req );
|
||||
ui.definition->setUrl( req );
|
||||
}
|
||||
|
||||
void ArticleView::showAnticipation()
|
||||
{
|
||||
ui.definition->setHtml( "" );
|
||||
//ui.definition->setCursor( Qt::WaitCursor );
|
||||
QApplication::setOverrideCursor( Qt::WaitCursor );
|
||||
ui.definition->setCursor( Qt::WaitCursor );
|
||||
//QApplication::setOverrideCursor( Qt::WaitCursor );
|
||||
}
|
||||
|
||||
void ArticleView::loadFinished( bool )
|
||||
{
|
||||
//ui.definition->unsetCursor();
|
||||
QApplication::restoreOverrideCursor();
|
||||
ui.definition->unsetCursor();
|
||||
//QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
void ArticleView::handleTitleChanged( QString const & title )
|
||||
|
@ -159,16 +163,26 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref )
|
|||
getGroup( ref ) );
|
||||
else
|
||||
if ( url.scheme() == "gdlookup" ) // Plain html links inherit gdlookup scheme
|
||||
{
|
||||
if ( url.hasFragment() )
|
||||
{
|
||||
ui.definition->page()->currentFrame()->evaluateJavaScript(
|
||||
QString( "window.location = \"%1\"" ).arg( QString::fromUtf8( url.toEncoded() ) ) );
|
||||
}
|
||||
else
|
||||
showDefinition( url.path().mid( 1 ),
|
||||
getGroup( ref ) );
|
||||
}
|
||||
else
|
||||
if ( url.scheme() == "bres" || url.scheme() == "gdau" )
|
||||
{
|
||||
// Download it
|
||||
|
||||
vector< char > data;
|
||||
bool found = false;
|
||||
// Clear any pending ones
|
||||
|
||||
resourceDownloadRequests.clear();
|
||||
|
||||
resourceDownloadUrl = url;
|
||||
|
||||
if ( url.scheme() == "gdau" && url.host() == "search" && groups.size() )
|
||||
{
|
||||
|
@ -183,95 +197,69 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref )
|
|||
{
|
||||
for( unsigned y = 0; y < groups[ x ].dictionaries.size(); ++y )
|
||||
{
|
||||
try
|
||||
{
|
||||
sptr< Dictionary::DataRequest > req =
|
||||
groups[ x ].dictionaries[ y ]->getResource(
|
||||
url.path().mid( 1 ).toUtf8().data(), data );
|
||||
url.path().mid( 1 ).toUtf8().data() );
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
catch( Dictionary::exNoSuchResource & )
|
||||
if ( req->isFinished() && req->dataSize() >= 0 )
|
||||
{
|
||||
continue;
|
||||
// A request was instantly finished with success.
|
||||
// If we've managed to spawn some lingering requests alrady,
|
||||
// erase them.
|
||||
resourceDownloadRequests.clear();
|
||||
|
||||
// Handle the result
|
||||
resourceDownloadRequests.push_back( req );
|
||||
resourceDownloadFinished();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
if ( !req->isFinished() )
|
||||
{
|
||||
resourceDownloadRequests.push_back( req );
|
||||
|
||||
connect( req.get(), SIGNAL( finished() ),
|
||||
this, SLOT( resourceDownloadFinished() ) );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Normal resource download
|
||||
QString contentType;
|
||||
|
||||
sptr< Dictionary::DataRequest > req =
|
||||
articleNetMgr.getResource( url, contentType );
|
||||
|
||||
if ( req->isFinished() && req->dataSize() >= 0 )
|
||||
{
|
||||
// Have data ready, handle it
|
||||
resourceDownloadRequests.push_back( req );
|
||||
resourceDownloadFinished();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
if ( !req->isFinished() )
|
||||
{
|
||||
// Queue to be handled when done
|
||||
|
||||
resourceDownloadRequests.push_back( req );
|
||||
|
||||
connect( req.get(), SIGNAL( finished() ),
|
||||
this, SLOT( resourceDownloadFinished() ) );
|
||||
}
|
||||
}
|
||||
|
||||
QString contentType;
|
||||
|
||||
if ( !found && !articleNetMgr.getResource( url, data, contentType ) )
|
||||
if ( resourceDownloadRequests.empty() ) // No requests were queued
|
||||
{
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "The referenced resource doesn't exist." ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( url.scheme() == "gdau" )
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
// Windows-only: use system PlaySound function
|
||||
|
||||
if ( winWavData.size() )
|
||||
PlaySoundA( 0, 0, 0 ); // Stop any currently playing sound to make sure
|
||||
// previous data isn't used anymore
|
||||
//
|
||||
winWavData = data;
|
||||
|
||||
PlaySoundA( &winWavData.front(), 0,
|
||||
SND_ASYNC | SND_MEMORY | SND_NODEFAULT | SND_NOWAIT );
|
||||
#else
|
||||
|
||||
// Use external viewer to play the file
|
||||
try
|
||||
{
|
||||
ExternalViewer * viewer = new ExternalViewer( this, data, ".wav", "play" );
|
||||
|
||||
try
|
||||
{
|
||||
viewer->start();
|
||||
|
||||
// Once started, it will erase itself
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
delete viewer;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch( ExternalViewer::Ex & e )
|
||||
{
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to run a player to play sound file: %1" ).arg( e.what() ) );
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a temporary file
|
||||
|
||||
|
||||
// Remove the one previously used, if any
|
||||
cleanupTemp();
|
||||
|
||||
{
|
||||
QTemporaryFile tmp( QDir::temp().filePath( "XXXXXX-" + url.path().section( '/', -1 ) ), this );
|
||||
|
||||
if ( !tmp.open() || tmp.write( &data.front(), data.size() ) != data.size() )
|
||||
{
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to create temporary file." ) );
|
||||
return;
|
||||
}
|
||||
|
||||
tmp.setAutoRemove( false );
|
||||
|
||||
desktopOpenedTempFile = tmp.fileName();
|
||||
}
|
||||
|
||||
if ( !QDesktopServices::openUrl( QUrl::fromLocalFile( desktopOpenedTempFile ) ) )
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to auto-open resource file, try opening manually: %1." ).arg( desktopOpenedTempFile ) );
|
||||
}
|
||||
else
|
||||
if ( url.scheme() == "http" || url.scheme() == "https" ||
|
||||
|
@ -350,6 +338,113 @@ void ArticleView::contextMenuRequested( QPoint const & pos )
|
|||
#endif
|
||||
}
|
||||
|
||||
void ArticleView::resourceDownloadFinished()
|
||||
{
|
||||
if ( resourceDownloadRequests.empty() )
|
||||
return; // Stray signal
|
||||
|
||||
// Find any finished resources
|
||||
for( list< sptr< Dictionary::DataRequest > >::iterator i =
|
||||
resourceDownloadRequests.begin(); i != resourceDownloadRequests.end(); )
|
||||
{
|
||||
if ( (*i)->isFinished() )
|
||||
{
|
||||
if ( (*i)->dataSize() >= 0 )
|
||||
{
|
||||
// Ok, got one finished, all others are irrelevant now
|
||||
|
||||
vector< char > const & data = (*i)->getFullData();
|
||||
|
||||
if ( resourceDownloadUrl.scheme() == "gdau" )
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
// Windows-only: use system PlaySound function
|
||||
|
||||
if ( winWavData.size() )
|
||||
PlaySoundA( 0, 0, 0 ); // Stop any currently playing sound to make sure
|
||||
// previous data isn't used anymore
|
||||
//
|
||||
winWavData = data;
|
||||
|
||||
PlaySoundA( &winWavData.front(), 0,
|
||||
SND_ASYNC | SND_MEMORY | SND_NODEFAULT | SND_NOWAIT );
|
||||
#else
|
||||
|
||||
// Use external viewer to play the file
|
||||
try
|
||||
{
|
||||
ExternalViewer * viewer = new ExternalViewer( this, data, ".wav", "mplayer" );
|
||||
|
||||
try
|
||||
{
|
||||
viewer->start();
|
||||
|
||||
// Once started, it will erase itself
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
delete viewer;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch( ExternalViewer::Ex & e )
|
||||
{
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to run a player to play sound file: %1" ).arg( e.what() ) );
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a temporary file
|
||||
|
||||
|
||||
// Remove the one previously used, if any
|
||||
cleanupTemp();
|
||||
|
||||
{
|
||||
QTemporaryFile tmp(
|
||||
QDir::temp().filePath( "XXXXXX-" + resourceDownloadUrl.path().section( '/', -1 ) ), this );
|
||||
|
||||
if ( !tmp.open() || tmp.write( &data.front(), data.size() ) != data.size() )
|
||||
{
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to create temporary file." ) );
|
||||
return;
|
||||
}
|
||||
|
||||
tmp.setAutoRemove( false );
|
||||
|
||||
desktopOpenedTempFile = tmp.fileName();
|
||||
}
|
||||
|
||||
if ( !QDesktopServices::openUrl( QUrl::fromLocalFile( desktopOpenedTempFile ) ) )
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to auto-open resource file, try opening manually: %1." ).arg( desktopOpenedTempFile ) );
|
||||
}
|
||||
|
||||
// Ok, whatever it was, it's finished. Remove this and any other
|
||||
// requests and finish.
|
||||
|
||||
resourceDownloadRequests.clear();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This one had no data. Erase it.
|
||||
resourceDownloadRequests.erase( i++ );
|
||||
}
|
||||
}
|
||||
else // Unfinished, try the next one.
|
||||
i++;
|
||||
}
|
||||
|
||||
if ( resourceDownloadRequests.empty() )
|
||||
{
|
||||
// No requests suceeded.
|
||||
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "The referenced resource failed to download." ) );
|
||||
}
|
||||
}
|
||||
|
||||
void ArticleView::showEvent( QShowEvent * ev )
|
||||
{
|
||||
QFrame::showEvent( ev );
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <QWebView>
|
||||
#include <QUrl>
|
||||
#include <list>
|
||||
#include "article_netmgr.hh"
|
||||
#include "instances.hh"
|
||||
#include "ui_articleview.h"
|
||||
|
@ -27,7 +28,14 @@ class ArticleView: public QFrame
|
|||
vector< char > winWavData;
|
||||
#endif
|
||||
|
||||
// For resources opened via desktop services
|
||||
/// Any resource we've decided to download off the dictionary gets stored here.
|
||||
/// Full vector capacity is used for search requests, where we have to make
|
||||
/// a multitude of requests.
|
||||
std::list< sptr< Dictionary::DataRequest > > resourceDownloadRequests;
|
||||
/// Url of the resourceDownloadRequests
|
||||
QUrl resourceDownloadUrl;
|
||||
|
||||
/// For resources opened via desktop services
|
||||
QString desktopOpenedTempFile;
|
||||
|
||||
public:
|
||||
|
@ -83,6 +91,8 @@ private slots:
|
|||
void linkClicked( QUrl const & );
|
||||
void contextMenuRequested( QPoint const & );
|
||||
|
||||
void resourceDownloadFinished();
|
||||
|
||||
private:
|
||||
|
||||
/// Deduces group from the url. If there doesn't seem to be any group,
|
||||
|
|
82
src/bgl.cc
82
src/bgl.cc
|
@ -192,6 +192,7 @@ namespace
|
|||
|
||||
class BglDictionary: public BtreeIndexing::BtreeDictionary
|
||||
{
|
||||
Mutex idxMutex;
|
||||
File::Class idx;
|
||||
IdxHeader idxHeader;
|
||||
string dictionaryName;
|
||||
|
@ -214,15 +215,15 @@ namespace
|
|||
virtual unsigned long getWordCount() throw()
|
||||
{ return idxHeader.wordCount; }
|
||||
|
||||
virtual vector< wstring > findHeadwordsForSynonym( wstring const & )
|
||||
virtual sptr< Dictionary::WordSearchRequest > findHeadwordsForSynonym( wstring const & )
|
||||
throw( std::exception );
|
||||
|
||||
virtual string getArticle( wstring const &, vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception );
|
||||
virtual sptr< Dictionary::DataRequest > getArticle( wstring const &,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception );
|
||||
|
||||
virtual void getResource( string const & name,
|
||||
vector< char > & data ) throw( Dictionary::exNoSuchResource,
|
||||
std::exception );
|
||||
virtual sptr< Dictionary::DataRequest > getResource( string const & name )
|
||||
throw( std::exception );
|
||||
|
||||
private:
|
||||
|
||||
|
@ -257,7 +258,7 @@ namespace
|
|||
|
||||
idx.seek( idxHeader.indexOffset );
|
||||
|
||||
openIndex( idx );
|
||||
openIndex( idx, idxMutex );
|
||||
}
|
||||
|
||||
|
||||
|
@ -267,6 +268,8 @@ namespace
|
|||
{
|
||||
vector< char > chunk;
|
||||
|
||||
Mutex::Lock _( idxMutex );
|
||||
|
||||
char * articleData = chunks.getBlock( offset, chunk );
|
||||
|
||||
headword = articleData;
|
||||
|
@ -278,10 +281,12 @@ namespace
|
|||
displayedHeadword.size() + 2 );
|
||||
}
|
||||
|
||||
vector< wstring > BglDictionary::findHeadwordsForSynonym( wstring const & str )
|
||||
throw( std::exception )
|
||||
sptr< Dictionary::WordSearchRequest >
|
||||
BglDictionary::findHeadwordsForSynonym( wstring const & str )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< wstring > result;
|
||||
sptr< Dictionary::WordSearchRequestInstant > result =
|
||||
new Dictionary::WordSearchRequestInstant;
|
||||
|
||||
vector< WordArticleLink > chain = findArticles( str );
|
||||
|
||||
|
@ -300,7 +305,7 @@ namespace
|
|||
{
|
||||
// The headword seems to differ from the input word, which makes the
|
||||
// input word its synonym.
|
||||
result.push_back( headwordDecoded );
|
||||
result->getMatches().push_back( headwordDecoded );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,9 +340,9 @@ namespace
|
|||
return in;
|
||||
}
|
||||
|
||||
string BglDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception )
|
||||
sptr< Dictionary::DataRequest > BglDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< WordArticleLink > chain = findArticles( word );
|
||||
|
||||
|
@ -392,7 +397,7 @@ namespace
|
|||
}
|
||||
|
||||
if ( mainArticles.empty() && alternateArticles.empty() )
|
||||
throw Dictionary::exNoSuchWord();
|
||||
return new Dictionary::DataRequestInstant( false ); // No such word
|
||||
|
||||
string result;
|
||||
|
||||
|
@ -424,12 +429,19 @@ namespace
|
|||
|
||||
replaceCharsetEntities( result );
|
||||
|
||||
return result;
|
||||
|
||||
Dictionary::DataRequestInstant * ret =
|
||||
new Dictionary::DataRequestInstant( true );
|
||||
|
||||
ret->getData().resize( result.size() );
|
||||
|
||||
memcpy( &(ret->getData().front()), result.data(), result.size() );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BglDictionary::getResource( string const & name,
|
||||
vector< char > & data )
|
||||
throw( Dictionary::exNoSuchResource, std::exception )
|
||||
sptr< Dictionary::DataRequest > BglDictionary::getResource( string const & name )
|
||||
throw( std::exception )
|
||||
{
|
||||
string nameLowercased = name;
|
||||
|
||||
|
@ -437,6 +449,8 @@ namespace
|
|||
++i )
|
||||
*i = tolower( *i );
|
||||
|
||||
Mutex::Lock _( idxMutex );
|
||||
|
||||
idx.seek( idxHeader.resourceListOffset );
|
||||
|
||||
for( size_t count = idxHeader.resourcesCount; count--; )
|
||||
|
@ -455,30 +469,33 @@ namespace
|
|||
|
||||
idx.seek( offset );
|
||||
|
||||
data.resize( idx.read< uint32_t >() );
|
||||
sptr< Dictionary::DataRequestInstant > result = new
|
||||
Dictionary::DataRequestInstant( true );
|
||||
|
||||
result->getData().resize( idx.read< uint32_t >() );
|
||||
|
||||
vector< unsigned char > compressedData( idx.read< uint32_t >() );
|
||||
|
||||
idx.read( &compressedData.front(), compressedData.size() );
|
||||
|
||||
unsigned long decompressedLength = data.size();
|
||||
unsigned long decompressedLength = result->getData().size();
|
||||
|
||||
if ( uncompress( (unsigned char *)&data.front(),
|
||||
if ( uncompress( (unsigned char *) &( result->getData().front() ),
|
||||
&decompressedLength,
|
||||
&compressedData.front(),
|
||||
compressedData.size() ) != Z_OK ||
|
||||
decompressedLength != data.size() )
|
||||
decompressedLength != result->getData().size() )
|
||||
{
|
||||
printf( "Failed to decompress resource %s, ignoring it.\n",
|
||||
name.c_str() );
|
||||
throw Dictionary::exNoSuchResource();
|
||||
return new Dictionary::DataRequestInstant( false );
|
||||
}
|
||||
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
throw Dictionary::exNoSuchResource();
|
||||
return new Dictionary::DataRequestInstant( false );
|
||||
}
|
||||
|
||||
/// Replaces <CHARSET c="t">1234;</CHARSET> occurences with ሴ
|
||||
|
@ -628,10 +645,10 @@ namespace
|
|||
|
||||
|
||||
|
||||
vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< sptr< Dictionary::Class > > dictionaries;
|
||||
|
@ -654,11 +671,12 @@ vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
|||
|
||||
vector< string > dictFiles( 1, *i );
|
||||
|
||||
string dictId = makeDictionaryId( dictFiles );
|
||||
string dictId = Dictionary::makeDictionaryId( dictFiles );
|
||||
|
||||
string indexFile = indicesDir + dictId;
|
||||
|
||||
if ( needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) )
|
||||
if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) ||
|
||||
indexIsOldOrBad( indexFile ) )
|
||||
{
|
||||
// Building the index
|
||||
|
||||
|
|
22
src/bgl.hh
22
src/bgl.hh
|
@ -12,16 +12,18 @@ namespace Bgl {
|
|||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
class Format: public Dictionary::Format
|
||||
{
|
||||
public:
|
||||
|
||||
virtual vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & )
|
||||
throw( std::exception );
|
||||
};
|
||||
/// Goes through the given list of file names, trying each one as a possible
|
||||
/// dictionary. Upon finding one, creates a corresponding dictionary instance.
|
||||
/// As a result, a list of dictionaries is created.
|
||||
/// indicesDir indicates a directory where index files can be created, should
|
||||
/// there be need for them. The index file name would be the same as the
|
||||
/// dictionary's id, made by makeDictionaryId() from the list of file names.
|
||||
/// Any exception thrown would terminate the program with an error.
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & )
|
||||
throw( std::exception );
|
||||
|
||||
}
|
||||
|
||||
|
|
160
src/btreeidx.cc
160
src/btreeidx.cc
|
@ -4,8 +4,11 @@
|
|||
#include "btreeidx.hh"
|
||||
#include "folding.hh"
|
||||
#include "utf8.hh"
|
||||
#include <QRunnable>
|
||||
#include <QThreadPool>
|
||||
#include <QSemaphore>
|
||||
#include <math.h>
|
||||
#include<string.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
//#define __BTREE_USE_LZO
|
||||
|
@ -43,12 +46,15 @@ BtreeDictionary::BtreeDictionary( string const & id,
|
|||
{
|
||||
}
|
||||
|
||||
void BtreeDictionary::openIndex( File::Class & file )
|
||||
void BtreeDictionary::openIndex( File::Class & file, Mutex & mutex )
|
||||
{
|
||||
Mutex::Lock _( mutex );
|
||||
|
||||
indexNodeSize = file.read< uint32_t >();
|
||||
rootOffset = file.read< uint32_t >();
|
||||
|
||||
idxFile = &file;
|
||||
idxFileMutex = &mutex;
|
||||
}
|
||||
|
||||
vector< WordArticleLink > BtreeDictionary::findArticles( wstring const & str )
|
||||
|
@ -75,16 +81,76 @@ vector< WordArticleLink > BtreeDictionary::findArticles( wstring const & str )
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
void BtreeDictionary::findExact( wstring const & str,
|
||||
vector< wstring > & exactMatches,
|
||||
vector< wstring > & prefixMatches,
|
||||
unsigned long maxPrefixResults )
|
||||
throw( std::exception )
|
||||
class BtreeWordSearchRequest;
|
||||
|
||||
class BtreeWordSearchRunnable: public QRunnable
|
||||
{
|
||||
exactMatches.clear();
|
||||
prefixMatches.clear();
|
||||
BtreeWordSearchRequest & r;
|
||||
QSemaphore & hasExited;
|
||||
|
||||
public:
|
||||
|
||||
BtreeWordSearchRunnable( BtreeWordSearchRequest & r_,
|
||||
QSemaphore & hasExited_ ): r( r_ ),
|
||||
hasExited( hasExited_ )
|
||||
{}
|
||||
|
||||
~BtreeWordSearchRunnable()
|
||||
{
|
||||
hasExited.release();
|
||||
}
|
||||
|
||||
virtual void run();
|
||||
};
|
||||
|
||||
class BtreeWordSearchRequest: public Dictionary::WordSearchRequest
|
||||
{
|
||||
friend class BtreeWordSearchRunnable;
|
||||
|
||||
BtreeDictionary & dict;
|
||||
wstring str;
|
||||
unsigned long maxResults;
|
||||
QAtomicInt isCancelled;
|
||||
QSemaphore hasExited;
|
||||
|
||||
public:
|
||||
|
||||
BtreeWordSearchRequest( BtreeDictionary & dict_,
|
||||
wstring const & str_,
|
||||
unsigned long maxResults_ ):
|
||||
dict( dict_ ), str( str_ ), maxResults( maxResults_ )
|
||||
{
|
||||
QThreadPool::globalInstance()->start(
|
||||
new BtreeWordSearchRunnable( *this, hasExited ) );
|
||||
}
|
||||
|
||||
void run(); // Run from another thread by BtreeWordSearchRunnable
|
||||
|
||||
virtual void cancel()
|
||||
{
|
||||
isCancelled.ref();
|
||||
}
|
||||
|
||||
~BtreeWordSearchRequest()
|
||||
{
|
||||
isCancelled.ref();
|
||||
hasExited.acquire();
|
||||
}
|
||||
};
|
||||
|
||||
void BtreeWordSearchRunnable::run()
|
||||
{
|
||||
r.run();
|
||||
}
|
||||
|
||||
void BtreeWordSearchRequest::run()
|
||||
{
|
||||
if ( isCancelled )
|
||||
{
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
wstring folded = Folding::apply( str );
|
||||
|
||||
bool exactMatch;
|
||||
|
@ -92,33 +158,49 @@ void BtreeDictionary::findExact( wstring const & str,
|
|||
vector< char > leaf;
|
||||
uint32_t nextLeaf;
|
||||
|
||||
char const * chainOffset = findChainOffsetExactOrPrefix( folded, exactMatch,
|
||||
leaf, nextLeaf );
|
||||
|
||||
if ( !chainOffset )
|
||||
return;
|
||||
char const * chainOffset = dict.findChainOffsetExactOrPrefix( folded, exactMatch,
|
||||
leaf, nextLeaf );
|
||||
|
||||
if ( chainOffset )
|
||||
for( ; ; )
|
||||
{
|
||||
if ( isCancelled )
|
||||
break;
|
||||
|
||||
//printf( "offset = %u, size = %u\n", chainOffset - &leaf.front(), leaf.size() );
|
||||
|
||||
vector< WordArticleLink > chain = readChain( chainOffset );
|
||||
vector< wstring > wstrings = convertChainToWstrings( chain );
|
||||
vector< WordArticleLink > chain = dict.readChain( chainOffset );
|
||||
vector< wstring > wstrings = dict.convertChainToWstrings( chain );
|
||||
|
||||
wstring resultFolded = Folding::apply( wstrings[ 0 ] );
|
||||
|
||||
if ( resultFolded == folded )
|
||||
// Exact match
|
||||
exactMatches.insert( exactMatches.end(), wstrings.begin(), wstrings.end() );
|
||||
{
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
matches.insert( matches.end(), wstrings.begin(), wstrings.end() );
|
||||
|
||||
if ( matches.size() >= maxResults )
|
||||
{
|
||||
// For now we actually allow more than maxResults if the last
|
||||
// chain yield more than one result. That's ok and maybe even more
|
||||
// desirable.
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
if ( resultFolded.size() > folded.size() && !resultFolded.compare( 0, folded.size(), folded ) )
|
||||
{
|
||||
// Prefix match
|
||||
prefixMatches.insert( prefixMatches.end(), wstrings.begin(), wstrings.end() );
|
||||
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
matches.insert( matches.end(), wstrings.begin(), wstrings.end() );
|
||||
|
||||
if ( prefixMatches.size() >= maxPrefixResults )
|
||||
if ( matches.size() >= maxResults )
|
||||
{
|
||||
// For now we actually allow more than maxPrefixResults if the last
|
||||
// For now we actually allow more than maxResults if the last
|
||||
// chain yield more than one result. That's ok and maybe even more
|
||||
// desirable.
|
||||
break;
|
||||
|
@ -138,8 +220,10 @@ void BtreeDictionary::findExact( wstring const & str,
|
|||
|
||||
if ( nextLeaf )
|
||||
{
|
||||
readNode( nextLeaf, leaf );
|
||||
nextLeaf = idxFile->read< uint32_t >();
|
||||
Mutex::Lock _( *dict.idxFileMutex );
|
||||
|
||||
dict.readNode( nextLeaf, leaf );
|
||||
nextLeaf = dict.idxFile->read< uint32_t >();
|
||||
chainOffset = &leaf.front() + sizeof( uint32_t );
|
||||
|
||||
uint32_t leafEntries = *(uint32_t *)&leaf.front();
|
||||
|
@ -154,6 +238,15 @@ void BtreeDictionary::findExact( wstring const & str,
|
|||
break; // That was the last leaf
|
||||
}
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
sptr< Dictionary::WordSearchRequest > BtreeDictionary::prefixMatch(
|
||||
wstring const & str, unsigned long maxResults )
|
||||
throw( std::exception )
|
||||
{
|
||||
return new BtreeWordSearchRequest( *this, str, maxResults );
|
||||
}
|
||||
|
||||
void BtreeDictionary::readNode( uint32_t offset, vector< char > & out )
|
||||
|
@ -200,7 +293,9 @@ char const * BtreeDictionary::findChainOffsetExactOrPrefix( wstring const & targ
|
|||
{
|
||||
if ( !idxFile )
|
||||
throw exIndexWasNotOpened();
|
||||
|
||||
|
||||
Mutex::Lock _( *idxFileMutex );
|
||||
|
||||
// Lookup the index by traversing the index btree
|
||||
|
||||
vector< char > charBuffer;
|
||||
|
@ -608,10 +703,22 @@ static uint32_t buildBtreeNode( IndexedWords::const_iterator & nextIndex,
|
|||
|
||||
uint32_t buildIndex( IndexedWords const & indexedWords, File::Class & file )
|
||||
{
|
||||
size_t indexSize = indexedWords.size();
|
||||
IndexedWords::const_iterator nextIndex = indexedWords.begin();
|
||||
|
||||
// Skip any empty words. No point in indexing those, and some dictionaries
|
||||
// are known to have buggy empty-word entries (Stardict's jargon for instance).
|
||||
|
||||
while( indexSize && nextIndex->first.empty() )
|
||||
{
|
||||
indexSize--;
|
||||
++nextIndex;
|
||||
}
|
||||
|
||||
// We try to stick to two-level tree for most dictionaries. Try finding
|
||||
// the right size for it.
|
||||
|
||||
size_t btreeMaxElements = ( (size_t) sqrt( indexedWords.size() ) ) + 1;
|
||||
size_t btreeMaxElements = ( (size_t) sqrt( indexSize ) ) + 1;
|
||||
|
||||
if ( btreeMaxElements < BtreeMinElements )
|
||||
btreeMaxElements = BtreeMinElements;
|
||||
|
@ -621,11 +728,10 @@ uint32_t buildIndex( IndexedWords const & indexedWords, File::Class & file )
|
|||
|
||||
printf( "Building a tree of %u elements\n", btreeMaxElements );
|
||||
|
||||
IndexedWords::const_iterator nextIndex = indexedWords.begin();
|
||||
|
||||
uint32_t lastLeafOffset = 0;
|
||||
|
||||
uint32_t rootOffset = buildBtreeNode( nextIndex, indexedWords.size(),
|
||||
uint32_t rootOffset = buildBtreeNode( nextIndex, indexSize,
|
||||
file, btreeMaxElements,
|
||||
lastLeafOffset );
|
||||
|
||||
|
|
|
@ -48,6 +48,8 @@ struct WordArticleLink
|
|||
{}
|
||||
};
|
||||
|
||||
class BtreeWordSearchRequest;
|
||||
|
||||
/// A base for the dictionary that utilizes a btree index build using
|
||||
/// buildIndex() function declared below.
|
||||
class BtreeDictionary: public Dictionary::Class
|
||||
|
@ -58,17 +60,17 @@ public:
|
|||
|
||||
/// This function does the search using the btree index. Derivatives
|
||||
/// need not to implement this function.
|
||||
virtual void findExact( wstring const &,
|
||||
vector< wstring > &,
|
||||
vector< wstring > &,
|
||||
unsigned long ) throw( std::exception );
|
||||
virtual sptr< Dictionary::WordSearchRequest > prefixMatch( wstring const &,
|
||||
unsigned long )
|
||||
throw( std::exception );
|
||||
|
||||
protected:
|
||||
|
||||
/// Opens the index. The file must be positioned at the offset previously
|
||||
/// returned by buildIndex(). The file reference is saved to be used for
|
||||
/// subsequent lookups.
|
||||
void openIndex( File::Class & );
|
||||
/// The mutex is the one to be locked when working with the file.
|
||||
void openIndex( File::Class &, Mutex & );
|
||||
|
||||
/// Finds articles that match the given string. A case-insensitive search
|
||||
/// is performed.
|
||||
|
@ -76,6 +78,7 @@ protected:
|
|||
|
||||
private:
|
||||
|
||||
Mutex * idxFileMutex;
|
||||
File::Class * idxFile;
|
||||
uint32_t indexNodeSize;
|
||||
uint32_t rootOffset;
|
||||
|
@ -107,6 +110,8 @@ private:
|
|||
/// Drops any alises which arose due to folding. Only case-folded aliases
|
||||
/// are left.
|
||||
void antialias( wstring const &, vector< WordArticleLink > & );
|
||||
|
||||
friend class BtreeWordSearchRequest;
|
||||
};
|
||||
|
||||
// Everything below is for building the index data.
|
||||
|
|
|
@ -63,6 +63,15 @@ Class load() throw( exError )
|
|||
|
||||
#endif
|
||||
|
||||
c.mediawikis.push_back( MediaWiki( "ae6f89aac7151829681b85f035d54e48", "English Wikipedia", "http://en.wikipedia.org/w", true ) );
|
||||
c.mediawikis.push_back( MediaWiki( "affcf9678e7bfe701c9b071f97eccba3", "English Wiktionary", "http://en.wiktionary.org/w", false ) );
|
||||
c.mediawikis.push_back( MediaWiki( "8e0c1c2b6821dab8bdba8eb869ca7176", "Russian Wikipedia", "http://ru.wikipedia.org/w", false ) );
|
||||
c.mediawikis.push_back( MediaWiki( "b09947600ae3902654f8ad4567ae8567", "Russain Wiktionary", "http://ru.wiktionary.org/w", false ) );
|
||||
c.mediawikis.push_back( MediaWiki( "a8a66331a1242ca2aeb0b4aed361c41d", "German Wikipedia", "http://de.wikipedia.org/w", false ) );
|
||||
c.mediawikis.push_back( MediaWiki( "21c64bca5ec10ba17ff19f3066bc962a", "German Wiktionary", "http://de.wiktionary.org/w", false ) );
|
||||
c.mediawikis.push_back( MediaWiki( "96957cb2ad73a20c7a1d561fc83c253a", "Portuguese Wikipedia", "http://pt.wikipedia.org/w", false ) );
|
||||
c.mediawikis.push_back( MediaWiki( "ed4c3929196afdd93cc08b9a903aad6a", "Portuguese Wiktionary", "http://pt.wiktionary.org/w", false ) );
|
||||
|
||||
save( c );
|
||||
|
||||
return c;
|
||||
|
@ -124,6 +133,27 @@ Class load() throw( exError )
|
|||
}
|
||||
}
|
||||
|
||||
QDomNode mws = root.namedItem( "mediawikis" );
|
||||
|
||||
if ( !mws.isNull() )
|
||||
{
|
||||
QDomNodeList nl = mws.toElement().elementsByTagName( "mediawiki" );
|
||||
|
||||
for( unsigned x = 0; x < nl.length(); ++x )
|
||||
{
|
||||
QDomElement mw = nl.item( x ).toElement();
|
||||
|
||||
MediaWiki w;
|
||||
|
||||
w.id = mw.attribute( "id" );
|
||||
w.name = mw.attribute( "name" );
|
||||
w.url = mw.attribute( "url" );
|
||||
w.enabled = ( mw.attribute( "enabled" ) == "1" );
|
||||
|
||||
c.mediawikis.push_back( w );
|
||||
}
|
||||
}
|
||||
|
||||
QDomNode preferences = root.namedItem( "preferences" );
|
||||
|
||||
if ( !preferences.isNull() )
|
||||
|
@ -216,6 +246,33 @@ void save( Class const & c ) throw( exError )
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
QDomElement mws = dd.createElement( "mediawikis" );
|
||||
root.appendChild( mws );
|
||||
|
||||
for( MediaWikis::const_iterator i = c.mediawikis.begin(); i != c.mediawikis.end(); ++i )
|
||||
{
|
||||
QDomElement mw = dd.createElement( "mediawiki" );
|
||||
mws.appendChild( mw );
|
||||
|
||||
QDomAttr id = dd.createAttribute( "id" );
|
||||
id.setValue( i->id );
|
||||
mw.setAttributeNode( id );
|
||||
|
||||
QDomAttr name = dd.createAttribute( "name" );
|
||||
name.setValue( i->name );
|
||||
mw.setAttributeNode( name );
|
||||
|
||||
QDomAttr url = dd.createAttribute( "url" );
|
||||
url.setValue( i->url );
|
||||
mw.setAttributeNode( url );
|
||||
|
||||
QDomAttr enabled = dd.createAttribute( "enabled" );
|
||||
enabled.setValue( i->enabled ? "1" : "0" );
|
||||
mw.setAttributeNode( enabled );
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QDomElement preferences = dd.createElement( "preferences" );
|
||||
root.appendChild( preferences );
|
||||
|
|
|
@ -41,11 +41,29 @@ struct Preferences
|
|||
Preferences();
|
||||
};
|
||||
|
||||
/// A MediaWiki network dictionary definition
|
||||
struct MediaWiki
|
||||
{
|
||||
QString id, name, url;
|
||||
bool enabled;
|
||||
|
||||
MediaWiki(): enabled( false )
|
||||
{}
|
||||
|
||||
MediaWiki( QString const & id_, QString const & name_, QString const & url_,
|
||||
bool enabled_ ):
|
||||
id( id_ ), name( name_ ), url( url_ ), enabled( enabled_ ) {}
|
||||
};
|
||||
|
||||
/// All the MediaWikis
|
||||
typedef vector< MediaWiki > MediaWikis;
|
||||
|
||||
struct Class
|
||||
{
|
||||
Paths paths;
|
||||
Groups groups;
|
||||
Preferences preferences;
|
||||
MediaWikis mediawikis;
|
||||
|
||||
QString lastMainGroup; // Last used group in main window
|
||||
QString lastPopupGroup; // Last used group in popup window
|
||||
|
|
|
@ -13,12 +13,108 @@
|
|||
|
||||
namespace Dictionary {
|
||||
|
||||
bool Request::isFinished()
|
||||
{
|
||||
return (int)isFinishedFlag;
|
||||
}
|
||||
|
||||
void Request::update()
|
||||
{
|
||||
if ( !isFinishedFlag )
|
||||
emit updated();
|
||||
}
|
||||
|
||||
void Request::finish()
|
||||
{
|
||||
if ( !isFinishedFlag )
|
||||
{
|
||||
isFinishedFlag.ref();
|
||||
|
||||
emit finished();
|
||||
}
|
||||
}
|
||||
|
||||
void Request::setErrorString( QString const & str )
|
||||
{
|
||||
Mutex::Lock _( errorStringMutex );
|
||||
|
||||
errorString = str;
|
||||
}
|
||||
|
||||
QString Request::getErrorString()
|
||||
{
|
||||
Mutex::Lock _( errorStringMutex );
|
||||
|
||||
return errorString;
|
||||
}
|
||||
|
||||
|
||||
///////// WordSearchRequest
|
||||
|
||||
size_t WordSearchRequest::matchesCount()
|
||||
{
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
return matches.size();
|
||||
}
|
||||
|
||||
WordMatch WordSearchRequest::operator [] ( size_t index ) throw( exIndexOutOfRange )
|
||||
{
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
if ( index >= matches.size() )
|
||||
throw exIndexOutOfRange();
|
||||
|
||||
return matches[ index ];
|
||||
}
|
||||
|
||||
////////////// DataRequest
|
||||
|
||||
long DataRequest::dataSize()
|
||||
{
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
return hasAnyData ? data.size() : -1;
|
||||
}
|
||||
|
||||
void DataRequest::getDataSlice( size_t offset, size_t size, void * buffer )
|
||||
throw( exSliceOutOfRange )
|
||||
{
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
if ( offset + size > data.size() || !hasAnyData )
|
||||
throw exSliceOutOfRange();
|
||||
|
||||
memcpy( buffer, &data[ offset ], size );
|
||||
}
|
||||
|
||||
vector< char > & DataRequest::getFullData() throw( exRequestUnfinished )
|
||||
{
|
||||
if ( !isFinished() )
|
||||
throw exRequestUnfinished();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
Class::Class( string const & id_, vector< string > const & dictionaryFiles_ ):
|
||||
id( id_ ), dictionaryFiles( dictionaryFiles_ )
|
||||
{
|
||||
}
|
||||
|
||||
string Format::makeDictionaryId( vector< string > const & dictionaryFiles ) throw()
|
||||
sptr< WordSearchRequest > Class::findHeadwordsForSynonym( wstring const & )
|
||||
throw( std::exception )
|
||||
{
|
||||
return new WordSearchRequestInstant();
|
||||
}
|
||||
|
||||
sptr< DataRequest > Class::getResource( string const & /*name*/ )
|
||||
throw( std::exception )
|
||||
{
|
||||
return new DataRequestInstant( false );
|
||||
}
|
||||
|
||||
|
||||
string makeDictionaryId( vector< string > const & dictionaryFiles ) throw()
|
||||
{
|
||||
std::vector< string > sortedList( dictionaryFiles );
|
||||
|
||||
|
@ -47,8 +143,8 @@ string Format::makeDictionaryId( vector< string > const & dictionaryFiles ) thro
|
|||
// the dictionary backends, there's no platform-independent way to get hold
|
||||
// of a timestamp of the file, so we use here Qt anyway. It is supposed to
|
||||
// be fixed in the future when it's needed.
|
||||
bool Format::needToRebuildIndex( vector< string > const & dictionaryFiles,
|
||||
string const & indexFile ) throw()
|
||||
bool needToRebuildIndex( vector< string > const & dictionaryFiles,
|
||||
string const & indexFile ) throw()
|
||||
{
|
||||
unsigned long lastModified = 0;
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <QObject>
|
||||
#include "sptr.hh"
|
||||
#include "ex.hh"
|
||||
#include "mutex.hh"
|
||||
|
||||
/// Abstract dictionary-related stuff
|
||||
namespace Dictionary {
|
||||
|
@ -27,8 +29,191 @@ enum Property
|
|||
};
|
||||
|
||||
DEF_EX( Ex, "Dictionary error", std::exception )
|
||||
DEF_EX( exNoSuchWord, "The given word does not exist", Ex )
|
||||
DEF_EX( exNoSuchResource, "The given resource does not exist", Ex )
|
||||
DEF_EX( exIndexOutOfRange, "The supplied index is out of range", Ex )
|
||||
DEF_EX( exSliceOutOfRange, "The requested data slice is out of range", Ex )
|
||||
DEF_EX( exRequestUnfinished, "The request hasn't yet finished", Ex )
|
||||
|
||||
/// When you request a search to be performed in a dictionary, you get
|
||||
/// this structure in return. It accumulates search results over time.
|
||||
/// The finished() signal is emitted when the search has finished and there's
|
||||
/// no more matches to be expected. Note that before connecting to it, check
|
||||
/// the result of isFinished() -- if it's 'true', the search was instantaneous.
|
||||
/// Destroy the object when you are not interested in results anymore.
|
||||
///
|
||||
/// Creating, destroying and calling member functions of the requests is done
|
||||
/// in the GUI thread, however. Therefore, it is important to make sure those
|
||||
/// operations are fast (this is most important for word searches, where
|
||||
/// new requests are created and old ones deleted immediately upon a user
|
||||
/// changing query).
|
||||
class Request: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Returns whether the request has been processed in full and finished.
|
||||
/// This means that the data accumulated is final and won't change anymore.
|
||||
bool isFinished();
|
||||
|
||||
/// Either returns an empty string in case there was no error processing
|
||||
/// the request, or otherwise a human-readable string describing the problem.
|
||||
/// Note that an empty result, such as a lack of word or of an article isn't
|
||||
/// an error -- but any kind of failure to connect to, or read the dictionary
|
||||
/// is.
|
||||
QString getErrorString();
|
||||
|
||||
/// Cancels the ongoing request. This may make Request destruct faster some
|
||||
/// time in the future, Use this in preparation to destruct many Requests,
|
||||
/// so that they'd be cancelling in parallel. When the request was fully
|
||||
/// cancelled, it must emit the finished() signal, either as a result of an
|
||||
/// actual finish which has happened just before the cancellation, or solely as
|
||||
/// a result of a request being cancelled (in the latter case, the actual
|
||||
/// request result may be empty or incomplete). That is, finish() must be
|
||||
/// called by a derivative at least once if cancel() was called, either after
|
||||
/// or before it was called.
|
||||
virtual void cancel()=0;
|
||||
|
||||
virtual ~Request()
|
||||
{}
|
||||
|
||||
signals:
|
||||
|
||||
/// This signal is emitted when more data becomes available. Local
|
||||
/// dictionaries typically don't call this, since it is preferred that all
|
||||
/// data would be available from them at once, but network dictionaries
|
||||
/// might call that.
|
||||
void updated();
|
||||
|
||||
/// This signal is emitted when the request has been processed in full and
|
||||
/// finished. That is, it's emitted when isFinished() turns true.
|
||||
void finished();
|
||||
|
||||
protected:
|
||||
|
||||
/// Called by derivatives to signal update().
|
||||
void update();
|
||||
|
||||
/// Called by derivatives to set isFinished() flag and signal finished().
|
||||
void finish();
|
||||
|
||||
/// Sets the error string to be returned by getErrorString().
|
||||
void setErrorString( QString const & );
|
||||
|
||||
private:
|
||||
|
||||
QAtomicInt isFinishedFlag;
|
||||
|
||||
Mutex errorStringMutex;
|
||||
QString errorString;
|
||||
};
|
||||
|
||||
/// This structure represents the word found. In addition to holding the
|
||||
/// word itself, it also holds its weight. Words with larger weight are always
|
||||
/// presented before ones with the smaller weight. It is 0 by default and
|
||||
/// should only be used for Levenstein-like matching algorithms to indicate
|
||||
/// search distance, in which cases it should be negative.
|
||||
struct WordMatch
|
||||
{
|
||||
wstring word;
|
||||
int weight;
|
||||
|
||||
WordMatch(): weight( 0 ) {}
|
||||
WordMatch( wstring const & word_ ): word( word_ ), weight( 0 ){}
|
||||
WordMatch( wstring const & word_, int weight_ ): word( word_ ),
|
||||
weight( weight_ ) {}
|
||||
};
|
||||
|
||||
/// This request type corresponds to all types of word searching operations.
|
||||
class WordSearchRequest: public Request
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Returns the number of matches found. The value can grow over time
|
||||
/// unless isFinished() is true.
|
||||
size_t matchesCount();
|
||||
|
||||
/// Returns the match with the given zero-based index, which should be less
|
||||
/// than matchesCount().
|
||||
WordMatch operator [] ( size_t index ) throw( exIndexOutOfRange );
|
||||
|
||||
protected:
|
||||
|
||||
// Subclasses should be filling up the 'matches' array, locking the mutex when
|
||||
// whey work with it.
|
||||
Mutex dataMutex;
|
||||
|
||||
vector< WordMatch > matches;
|
||||
};
|
||||
|
||||
/// This request type corresponds to any kinds of data responses where a
|
||||
/// single large blob of binary data is returned. It currently used of article
|
||||
/// bodies and resources.
|
||||
class DataRequest: public Request
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/// Returns the number of bytes read, with a -1 meaning that so far it's
|
||||
/// uncertain whether resource even exists or not, and any non-negative value
|
||||
/// meaning that that amount of bytes is not available.
|
||||
/// If -1 is still being returned after the request has finished, that means
|
||||
/// the resource wasn't found.
|
||||
long dataSize();
|
||||
|
||||
/// Writes "size" bytes starting from "offset" of the data read to the given
|
||||
/// buffer. "size + offset" must be <= than dataSize().
|
||||
void getDataSlice( size_t offset, size_t size, void * buffer )
|
||||
throw( exSliceOutOfRange );
|
||||
|
||||
/// Returns all the data read. Since no further locking can or would be
|
||||
/// done, this can only be called after the request has finished.
|
||||
vector< char > & getFullData() throw( exRequestUnfinished );
|
||||
|
||||
DataRequest(): hasAnyData( false ) {}
|
||||
|
||||
protected:
|
||||
|
||||
// Subclasses should be filling up the 'data' array, locking the mutex when
|
||||
// whey work with it.
|
||||
Mutex dataMutex;
|
||||
|
||||
bool hasAnyData; // With this being false, dataSize() always returns -1
|
||||
vector< char > data;
|
||||
};
|
||||
|
||||
/// A helper class for syncronous word search implementations.
|
||||
class WordSearchRequestInstant: public WordSearchRequest
|
||||
{
|
||||
public:
|
||||
|
||||
WordSearchRequestInstant()
|
||||
{ finish(); }
|
||||
|
||||
virtual void cancel()
|
||||
{}
|
||||
|
||||
vector< WordMatch > & getMatches()
|
||||
{ return matches; }
|
||||
};
|
||||
|
||||
/// A helper class for syncronous data read implementations.
|
||||
class DataRequestInstant: public DataRequest
|
||||
{
|
||||
public:
|
||||
|
||||
DataRequestInstant( bool succeeded )
|
||||
{ hasAnyData = succeeded; finish(); }
|
||||
|
||||
virtual void cancel()
|
||||
{}
|
||||
|
||||
vector< char > & getData()
|
||||
{ return data; }
|
||||
};
|
||||
|
||||
|
||||
/// A dictionary. Can be used to query words.
|
||||
class Class
|
||||
|
@ -66,39 +251,35 @@ public:
|
|||
/// the number of articles, or can be larger if some synonyms are present.
|
||||
virtual unsigned long getWordCount() throw()=0;
|
||||
|
||||
/// Looks up a given word in the dictionary, aiming for exact matches. The
|
||||
/// result is a list of such matches. If it is possible to also look up words
|
||||
/// that begin with the given substring without much expense, they should be
|
||||
/// put into the prefix results (if not, it should be left empty). Not more
|
||||
/// than maxPrefixResults prefix results should be stored. The whole
|
||||
/// operation is supposed to be fast and is executed in a GUI thread.
|
||||
virtual void findExact( wstring const &,
|
||||
vector< wstring > & exactMatches,
|
||||
vector< wstring > & prefixMatches,
|
||||
unsigned long maxPrefixResults ) throw( std::exception )=0;
|
||||
/// Looks up a given word in the dictionary, aiming for exact matches and
|
||||
/// prefix matches. If it's not possible to locate any prefix matches, no
|
||||
/// prefix results should be added. Not more than maxResults results should
|
||||
/// be stored. The whole operation is supposed to be fast, though some
|
||||
/// dictionaries, the network ones particularly, may of course be slow.
|
||||
virtual sptr< WordSearchRequest > prefixMatch( wstring const &,
|
||||
unsigned long maxResults ) throw( std::exception )=0;
|
||||
|
||||
/// Finds known headwords for the given word, that is, the words for which
|
||||
/// the given word is a synonym. If a dictionary can't perform this operation,
|
||||
/// it should leave the default implementation which always returns an empty
|
||||
/// vector.
|
||||
virtual vector< wstring > findHeadwordsForSynonym( wstring const & )
|
||||
throw( std::exception )
|
||||
{ return vector< wstring >(); }
|
||||
/// result.
|
||||
virtual sptr< WordSearchRequest > findHeadwordsForSynonym( wstring const & )
|
||||
throw( std::exception );
|
||||
|
||||
/// Returns a definition for the given word. The definition should
|
||||
/// be an html fragment (without html/head/body tags) in an utf8 encoding.
|
||||
/// The 'alts' vector could contain a list of words the definitions of which
|
||||
/// should be included in the output as well, being treated as additional
|
||||
/// synonyms for the main word.
|
||||
virtual string getArticle( wstring const &, vector< wstring > const & alts )
|
||||
throw( exNoSuchWord, std::exception )=0;
|
||||
virtual sptr< DataRequest > getArticle( wstring const &, vector< wstring > const & alts )
|
||||
throw( std::exception )=0;
|
||||
|
||||
/// Loads contents of a resource named 'name' into the 'data' vector. This is
|
||||
/// usually a picture file referenced in the article or something like that.
|
||||
virtual void getResource( string const & /*name*/,
|
||||
vector< char > & /*data*/ ) throw( exNoSuchResource,
|
||||
std::exception )
|
||||
{ throw exNoSuchResource(); }
|
||||
/// The default implementation always returns the non-existing resource
|
||||
/// response.
|
||||
virtual sptr< DataRequest > getResource( string const & /*name*/ )
|
||||
throw( std::exception );
|
||||
|
||||
virtual ~Class()
|
||||
{}
|
||||
|
@ -119,43 +300,20 @@ public:
|
|||
{}
|
||||
};
|
||||
|
||||
/// A dictionary format. This is a factory to create dictionaries' instances.
|
||||
/// It is fed filenames to check if they are dictionaries, and it creates
|
||||
/// instances when they are.
|
||||
class Format
|
||||
{
|
||||
public:
|
||||
/// Generates an id based on the set of file names which the dictionary
|
||||
/// consists of. The resulting id is an alphanumeric hex value made by
|
||||
/// hashing the file names. This id should be used to identify dictionary
|
||||
/// and for the index file name, if one is needed.
|
||||
/// This function is supposed to be used by dictionary implementations.
|
||||
string makeDictionaryId( vector< string > const & dictionaryFiles ) throw();
|
||||
|
||||
/// Should go through the given list of file names, trying each one as a
|
||||
/// possible dictionary of the supported format. Upon finding one, creates a
|
||||
/// corresponding dictionary instance. As a result, a list of dictionaries
|
||||
/// is created.
|
||||
/// indicesDir indicates a directory where index files can be created, should
|
||||
/// there be need for them. The index file name must be the same as the
|
||||
/// dictionary's id, made by makeDictionaryId() from the list of file names.
|
||||
/// Any exception thrown would terminate the program with an error.
|
||||
virtual vector< sptr< Class > > makeDictionaries( vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Initializing & )
|
||||
throw( std::exception )=0;
|
||||
|
||||
virtual ~Format()
|
||||
{}
|
||||
|
||||
public://protected:
|
||||
|
||||
/// Generates an id based on the set of file names which the dictionary
|
||||
/// consists of. The resulting id is an alphanumeric hex value made by
|
||||
/// hashing the file names. This id should be used to identify dictionary
|
||||
/// and for the index file name, if one is needed.
|
||||
static string makeDictionaryId( vector< string > const & dictionaryFiles ) throw();
|
||||
/// Checks if it is needed to regenerate index file based on its timestamp
|
||||
/// and the timestamps of the dictionary files. If some files are newer than
|
||||
/// the index file, or the index file doesn't exist, returns true. If some
|
||||
/// dictionary files don't exist, returns true, too.
|
||||
static bool needToRebuildIndex( vector< string > const & dictionaryFiles,
|
||||
string const & indexFile ) throw();
|
||||
};
|
||||
/// Checks if it is needed to regenerate index file based on its timestamp
|
||||
/// and the timestamps of the dictionary files. If some files are newer than
|
||||
/// the index file, or the index file doesn't exist, returns true. If some
|
||||
/// dictionary files don't exist, returns true, too.
|
||||
/// This function is supposed to be used by dictionary implementations.
|
||||
bool needToRebuildIndex( vector< string > const & dictionaryFiles,
|
||||
string const & indexFile ) throw();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "dictlock.hh"
|
||||
#include <QMutex>
|
||||
|
||||
namespace
|
||||
{
|
||||
QMutex & mutexInstance()
|
||||
{
|
||||
static QMutex mutex;
|
||||
|
||||
return mutex;
|
||||
}
|
||||
}
|
||||
|
||||
DictLock::DictLock()
|
||||
{
|
||||
mutexInstance().lock();
|
||||
}
|
||||
|
||||
DictLock::~DictLock()
|
||||
{
|
||||
mutexInstance().unlock();
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#ifndef __DICTLOCK_HH_INCLUDED__
|
||||
#define __DICTLOCK_HH_INCLUDED__
|
||||
|
||||
/// This is a one large coarse-grained lock class used to lock all the
|
||||
/// dictionaries and all their shared containers (usually vectors) in order
|
||||
/// to work with them concurrently in different threads. Since we do matching
|
||||
/// in a different thread, we need this, and since matching doesn't happen
|
||||
/// too much and doesn't typically take a lot of time, there's no need for any
|
||||
/// more fine-grained locking approaches.
|
||||
/// Create this object before working with any dictionary objects or their
|
||||
/// containers which are known to be shared with the other objects. Destory
|
||||
/// this object when you're done (i.e,, leave the scope where it was declared).
|
||||
class DictLock
|
||||
{
|
||||
public:
|
||||
|
||||
DictLock();
|
||||
~DictLock();
|
||||
};
|
||||
|
||||
#endif
|
90
src/dsl.cc
90
src/dsl.cc
|
@ -75,6 +75,7 @@ bool indexIsOldOrBad( string const & indexFile )
|
|||
|
||||
class DslDictionary: public BtreeIndexing::BtreeDictionary
|
||||
{
|
||||
Mutex idxMutex;
|
||||
File::Class idx;
|
||||
IdxHeader idxHeader;
|
||||
ChunkedStorage::Reader chunks;
|
||||
|
@ -101,18 +102,20 @@ public:
|
|||
virtual unsigned long getWordCount() throw()
|
||||
{ return 0; }
|
||||
|
||||
#if 0
|
||||
virtual vector< wstring > findHeadwordsForSynonym( wstring const & )
|
||||
throw( std::exception )
|
||||
{
|
||||
return vector< wstring >();
|
||||
}
|
||||
#endif
|
||||
|
||||
virtual string getArticle( wstring const &, vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception );
|
||||
virtual sptr< Dictionary::DataRequest > getArticle( wstring const &,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception );
|
||||
|
||||
virtual void getResource( string const & name,
|
||||
vector< char > & data )
|
||||
throw( Dictionary::exNoSuchResource, std::exception );
|
||||
virtual sptr< Dictionary::DataRequest > getResource( string const & name )
|
||||
throw( std::exception );
|
||||
|
||||
private:
|
||||
|
||||
|
@ -191,7 +194,7 @@ DslDictionary::DslDictionary( string const & id,
|
|||
|
||||
idx.seek( idxHeader.indexOffset );
|
||||
|
||||
openIndex( idx );
|
||||
openIndex( idx, idxMutex );
|
||||
}
|
||||
|
||||
DslDictionary::~DslDictionary()
|
||||
|
@ -210,7 +213,13 @@ void DslDictionary::loadArticle( uint32_t address,
|
|||
{
|
||||
vector< char > chunk;
|
||||
|
||||
char * articleProps = chunks.getBlock( address, chunk );
|
||||
char * articleProps;
|
||||
|
||||
{
|
||||
Mutex::Lock _( idxMutex );
|
||||
|
||||
articleProps = chunks.getBlock( address, chunk );
|
||||
}
|
||||
|
||||
uint32_t articleOffset, articleSize;
|
||||
|
||||
|
@ -508,9 +517,9 @@ vector< wstring > StardictDictionary::findHeadwordsForSynonym( wstring const & s
|
|||
#endif
|
||||
|
||||
|
||||
string DslDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception )
|
||||
sptr< Dictionary::DataRequest > DslDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< WordArticleLink > chain = findArticles( word );
|
||||
|
||||
|
@ -585,7 +594,7 @@ string DslDictionary::getArticle( wstring const & word,
|
|||
}
|
||||
|
||||
if ( mainArticles.empty() && alternateArticles.empty() )
|
||||
throw Dictionary::exNoSuchWord();
|
||||
return new Dictionary::DataRequestInstant( false );
|
||||
|
||||
string result;
|
||||
|
||||
|
@ -597,7 +606,14 @@ string DslDictionary::getArticle( wstring const & word,
|
|||
for( i = alternateArticles.begin(); i != alternateArticles.end(); ++i )
|
||||
result += i->second;
|
||||
|
||||
return result;
|
||||
Dictionary::DataRequestInstant * ret =
|
||||
new Dictionary::DataRequestInstant( true );
|
||||
|
||||
ret->getData().resize( result.size() );
|
||||
|
||||
memcpy( &(ret->getData().front()), result.data(), result.size() );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void loadFromFile( string const & n, vector< char > & data )
|
||||
|
@ -613,9 +629,8 @@ void loadFromFile( string const & n, vector< char > & data )
|
|||
f.read( &data.front(), data.size() );
|
||||
}
|
||||
|
||||
void DslDictionary::getResource( string const & name,
|
||||
vector< char > & data )
|
||||
throw( Dictionary::exNoSuchResource, std::exception )
|
||||
sptr< Dictionary::DataRequest > DslDictionary::getResource( string const & name )
|
||||
throw( std::exception )
|
||||
{
|
||||
string n =
|
||||
FsEncoding::dirname( getDictionaryFilenames()[ 0 ] ) +
|
||||
|
@ -626,6 +641,11 @@ void DslDictionary::getResource( string const & name,
|
|||
|
||||
try
|
||||
{
|
||||
sptr< Dictionary::DataRequestInstant > result = new
|
||||
Dictionary::DataRequestInstant( true );
|
||||
|
||||
vector< char > & data = result->getData();
|
||||
|
||||
try
|
||||
{
|
||||
loadFromFile( n, data );
|
||||
|
@ -646,25 +666,27 @@ void DslDictionary::getResource( string const & name,
|
|||
QImage img = QImage::fromData( (unsigned char *) &data.front(),
|
||||
data.size() );
|
||||
|
||||
if ( img.isNull() )
|
||||
if ( !img.isNull() )
|
||||
{
|
||||
// Failed to load, return data as is
|
||||
return;
|
||||
// Managed to load -- now store it back as BMP
|
||||
|
||||
QByteArray ba;
|
||||
QBuffer buffer( &ba );
|
||||
buffer.open( QIODevice::WriteOnly );
|
||||
img.save( &buffer, "BMP" );
|
||||
|
||||
data.resize( buffer.size() );
|
||||
|
||||
memcpy( &data.front(), buffer.data(), data.size() );
|
||||
}
|
||||
|
||||
QByteArray ba;
|
||||
QBuffer buffer( &ba );
|
||||
buffer.open( QIODevice::WriteOnly );
|
||||
img.save( &buffer, "BMP" );
|
||||
|
||||
data.resize( buffer.size() );
|
||||
|
||||
memcpy( &data.front(), buffer.data(), data.size() );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch( File::Ex & )
|
||||
{
|
||||
throw Dictionary::exNoSuchResource();
|
||||
// No such resource
|
||||
return new Dictionary::DataRequestInstant( false );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -723,10 +745,10 @@ static void findCorrespondingFiles( string const & ifo,
|
|||
}
|
||||
#endif
|
||||
|
||||
vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< sptr< Dictionary::Class > > dictionaries;
|
||||
|
@ -759,11 +781,11 @@ vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
|||
tryPossibleName( baseName + "_ABRV.DSL.dz", abrvFileName ) )
|
||||
dictFiles.push_back( abrvFileName );
|
||||
|
||||
string dictId = makeDictionaryId( dictFiles );
|
||||
string dictId = Dictionary::makeDictionaryId( dictFiles );
|
||||
|
||||
string indexFile = indicesDir + dictId;
|
||||
|
||||
if ( needToRebuildIndex( dictFiles, indexFile ) ||
|
||||
if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) ||
|
||||
indexIsOldOrBad( indexFile ) )
|
||||
{
|
||||
DslScanner scanner( *i );
|
||||
|
|
|
@ -6,22 +6,17 @@
|
|||
|
||||
#include "dictionary.hh"
|
||||
|
||||
/// Support for the ABBYY Lingo .DSL files.
|
||||
/// Support for the ABBYY Lingvo .DSL files.
|
||||
namespace Dsl {
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
class Format: public Dictionary::Format
|
||||
{
|
||||
public:
|
||||
|
||||
virtual vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & )
|
||||
throw( std::exception );
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -61,12 +61,13 @@ HEADERS += folding.hh \
|
|||
scanpopup.hh \
|
||||
articleview.hh \
|
||||
externalviewer.hh \
|
||||
dictlock.hh \
|
||||
wordfinder.hh \
|
||||
groupcombobox.hh \
|
||||
keyboardstate.hh \
|
||||
mouseover.hh \
|
||||
preferences.hh
|
||||
preferences.hh \
|
||||
mutex.hh \
|
||||
mediawiki.hh
|
||||
|
||||
|
||||
FORMS += groups.ui dictgroupwidget.ui mainwindow.ui sources.ui initializing.ui\
|
||||
|
@ -79,8 +80,9 @@ SOURCES += folding.cc main.cc dictionary.cc md5.c config.cc sources.cc \
|
|||
chunkedstorage.cc xdxf2html.cc iconv.cc lsa.cc htmlescape.cc \
|
||||
dsl.cc dsl_details.cc filetype.cc fsencoding.cc groups.cc \
|
||||
groups_widgets.cc instances.cc article_maker.cc scanpopup.cc \
|
||||
articleview.cc externalviewer.cc dictlock.cc wordfinder.cc \
|
||||
groupcombobox.cc keyboardstate.cc mouseover.cc preferences.cc
|
||||
articleview.cc externalviewer.cc wordfinder.cc \
|
||||
groupcombobox.cc keyboardstate.cc mouseover.cc preferences.cc \
|
||||
mutex.cc mediawiki.cc
|
||||
|
||||
win32 {
|
||||
SOURCES += mouseover_win32/ThTypes.c
|
||||
|
|
|
@ -24,7 +24,7 @@ DictGroupWidget::DictGroupWidget( QWidget * parent,
|
|||
|
||||
// Populate icons' list
|
||||
|
||||
QStringList icons = QDir( ":/flags/" ).entryList( QDir::Files, QDir::Name );
|
||||
QStringList icons = QDir( ":/flags/" ).entryList( QDir::Files, QDir::NoSort );
|
||||
|
||||
ui.groupIcon->addItem( "None", "" );
|
||||
|
||||
|
|
BIN
src/icons/warning.png
Normal file
BIN
src/icons/warning.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 839 B |
58
src/lsa.cc
58
src/lsa.cc
|
@ -134,6 +134,7 @@ Entry::Entry( File::Class & f )
|
|||
|
||||
class LsaDictionary: public BtreeIndexing::BtreeDictionary
|
||||
{
|
||||
Mutex idxMutex;
|
||||
File::Class idx;
|
||||
IdxHeader idxHeader;
|
||||
|
||||
|
@ -154,12 +155,12 @@ public:
|
|||
virtual unsigned long getWordCount() throw()
|
||||
{ return getArticleCount(); }
|
||||
|
||||
virtual string getArticle( wstring const &, vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception );
|
||||
virtual sptr< Dictionary::DataRequest > getArticle( wstring const &,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception );
|
||||
|
||||
virtual void getResource( string const & name,
|
||||
vector< char > & data ) throw( Dictionary::exNoSuchResource,
|
||||
std::exception );
|
||||
virtual sptr< Dictionary::DataRequest > getResource( string const & name )
|
||||
throw( std::exception );
|
||||
};
|
||||
|
||||
LsaDictionary::LsaDictionary( string const & id,
|
||||
|
@ -173,12 +174,12 @@ LsaDictionary::LsaDictionary( string const & id,
|
|||
|
||||
idx.seek( idxHeader.indexOffset );
|
||||
|
||||
openIndex( idx );
|
||||
openIndex( idx, idxMutex );
|
||||
}
|
||||
|
||||
string LsaDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception )
|
||||
sptr< Dictionary::DataRequest > LsaDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< WordArticleLink > chain = findArticles( word );
|
||||
|
||||
|
@ -223,7 +224,7 @@ string LsaDictionary::getArticle( wstring const & word,
|
|||
}
|
||||
|
||||
if ( mainArticles.empty() && alternateArticles.empty() )
|
||||
throw Dictionary::exNoSuchWord();
|
||||
return new Dictionary::DataRequestInstant( false ); // No such word
|
||||
|
||||
string result;
|
||||
|
||||
|
@ -255,7 +256,14 @@ string LsaDictionary::getArticle( wstring const & word,
|
|||
|
||||
result += "</table>";
|
||||
|
||||
return result;
|
||||
Dictionary::DataRequestInstant * ret =
|
||||
new Dictionary::DataRequestInstant( true );
|
||||
|
||||
ret->getData().resize( result.size() );
|
||||
|
||||
memcpy( &(ret->getData().front()), result.data(), result.size() );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// This wraps around file operations
|
||||
|
@ -325,9 +333,8 @@ struct WavHeader
|
|||
uint32_t dataLength;
|
||||
} __attribute__((packed));
|
||||
|
||||
void LsaDictionary::getResource( string const & name,
|
||||
vector< char > & data )
|
||||
throw( Dictionary::exNoSuchResource, std::exception )
|
||||
sptr< Dictionary::DataRequest > LsaDictionary::getResource( string const & name )
|
||||
throw( std::exception )
|
||||
{
|
||||
// See if the name ends in .wav. Remove that extension then
|
||||
|
||||
|
@ -338,8 +345,8 @@ void LsaDictionary::getResource( string const & name,
|
|||
vector< WordArticleLink > chain = findArticles( Utf8::decode( strippedName ) );
|
||||
|
||||
if ( chain.empty() )
|
||||
throw Dictionary::exNoSuchResource();
|
||||
|
||||
return new Dictionary::DataRequestInstant( false ); // No such resource
|
||||
|
||||
File::Class f( getDictionaryFilenames()[ 0 ], "rb" );
|
||||
|
||||
f.seek( chain[ 0 ].articleOffset );
|
||||
|
@ -368,6 +375,11 @@ void LsaDictionary::getResource( string const & name,
|
|||
throw exFailedToRetrieveVorbisInfo();
|
||||
}
|
||||
|
||||
sptr< Dictionary::DataRequestInstant > dr = new
|
||||
Dictionary::DataRequestInstant( true );
|
||||
|
||||
vector< char > & data = dr->getData();
|
||||
|
||||
data.resize( sizeof( WavHeader ) + e.samplesLength * 2 );
|
||||
|
||||
WavHeader * wh = (WavHeader *)&data.front();
|
||||
|
@ -417,14 +429,16 @@ void LsaDictionary::getResource( string const & name,
|
|||
}
|
||||
|
||||
ov_clear( &vf );
|
||||
|
||||
return dr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< sptr< Dictionary::Class > > dictionaries;
|
||||
|
@ -455,11 +469,11 @@ vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
|||
|
||||
vector< string > dictFiles( 1, *i );
|
||||
|
||||
string dictId = makeDictionaryId( dictFiles );
|
||||
string dictId = Dictionary::makeDictionaryId( dictFiles );
|
||||
|
||||
string indexFile = indicesDir + dictId;
|
||||
|
||||
if ( needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) )
|
||||
if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) || indexIsOldOrBad( indexFile ) )
|
||||
{
|
||||
// Building the index
|
||||
|
||||
|
|
|
@ -12,16 +12,11 @@ namespace Lsa {
|
|||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
class Format: public Dictionary::Format
|
||||
{
|
||||
public:
|
||||
|
||||
virtual vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & )
|
||||
throw( std::exception );
|
||||
};
|
||||
throw( std::exception );
|
||||
|
||||
}
|
||||
|
||||
|
|
19
src/main.cc
19
src/main.cc
|
@ -6,8 +6,25 @@
|
|||
#include "mainwindow.hh"
|
||||
#include "config.hh"
|
||||
|
||||
#define __DO_DEBUG
|
||||
|
||||
#ifdef __DO_DEBUG
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
{
|
||||
#ifdef __DO_DEBUG
|
||||
{
|
||||
rlimit limit;
|
||||
|
||||
memset( &limit, 0, sizeof( limit ) );
|
||||
limit.rlim_cur = RLIM_INFINITY;
|
||||
limit.rlim_max = RLIM_INFINITY;
|
||||
setrlimit( RLIMIT_CORE, &limit );
|
||||
}
|
||||
#endif
|
||||
|
||||
QApplication app( argc, argv );
|
||||
|
||||
app.setWindowIcon( QIcon( ":/icons/programicon.png" ) );
|
||||
|
@ -31,5 +48,3 @@ int main( int argc, char ** argv )
|
|||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include "stardict.hh"
|
||||
#include "lsa.hh"
|
||||
#include "dsl.hh"
|
||||
#include "dictlock.hh"
|
||||
#include "mediawiki.hh"
|
||||
#include "ui_about.h"
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
|
@ -32,6 +32,7 @@ MainWindow::MainWindow():
|
|||
cfg( Config::load() ),
|
||||
articleMaker( dictionaries, groupInstances ),
|
||||
articleNetMgr( this, dictionaries, articleMaker ),
|
||||
dictNetMgr( this ),
|
||||
wordFinder( this ),
|
||||
initializing( 0 )
|
||||
{
|
||||
|
@ -124,8 +125,10 @@ MainWindow::MainWindow():
|
|||
connect( ui.wordList, SIGNAL( itemSelectionChanged() ),
|
||||
this, SLOT( wordListSelectionChanged() ) );
|
||||
|
||||
connect( wordFinder.qobject(), SIGNAL( prefixMatchComplete( WordFinderResults ) ),
|
||||
this, SLOT( prefixMatchComplete( WordFinderResults ) ) );
|
||||
connect( &wordFinder, SIGNAL( updated() ),
|
||||
this, SLOT( prefixMatchUpdated() ) );
|
||||
connect( &wordFinder, SIGNAL( finished() ),
|
||||
this, SLOT( prefixMatchFinished() ) );
|
||||
|
||||
makeDictionaries();
|
||||
|
||||
|
@ -155,44 +158,35 @@ MainWindow::~MainWindow()
|
|||
Config::save( cfg );
|
||||
}
|
||||
|
||||
LoadDictionaries::LoadDictionaries( vector< string > const & allFiles_ ):
|
||||
allFiles( allFiles_ )
|
||||
LoadDictionaries::LoadDictionaries( vector< string > const & allFiles_,
|
||||
Config::Class const & cfg_ ):
|
||||
allFiles( allFiles_ ), cfg( cfg_ )
|
||||
{
|
||||
}
|
||||
|
||||
void LoadDictionaries::run()
|
||||
{
|
||||
{
|
||||
Bgl::Format bglFormat;
|
||||
dictionaries = Bgl::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
|
||||
dictionaries = bglFormat.makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
}
|
||||
|
||||
{
|
||||
Stardict::Format stardictFormat;
|
||||
|
||||
vector< sptr< Dictionary::Class > > stardictDictionaries =
|
||||
stardictFormat.makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
Stardict::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
|
||||
dictionaries.insert( dictionaries.end(), stardictDictionaries.begin(),
|
||||
stardictDictionaries.end() );
|
||||
}
|
||||
|
||||
{
|
||||
Lsa::Format lsaFormat;
|
||||
|
||||
vector< sptr< Dictionary::Class > > lsaDictionaries =
|
||||
lsaFormat.makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
Lsa::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
|
||||
dictionaries.insert( dictionaries.end(), lsaDictionaries.begin(),
|
||||
lsaDictionaries.end() );
|
||||
}
|
||||
|
||||
{
|
||||
Dsl::Format dslFormat;
|
||||
|
||||
vector< sptr< Dictionary::Class > > dslDictionaries =
|
||||
dslFormat.makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
Dsl::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(), *this );
|
||||
|
||||
dictionaries.insert( dictionaries.end(), dslDictionaries.begin(),
|
||||
dslDictionaries.end() );
|
||||
|
@ -249,10 +243,11 @@ void MainWindow::closeEvent( QCloseEvent * ev )
|
|||
|
||||
void MainWindow::makeDictionaries()
|
||||
{
|
||||
{
|
||||
DictLock _;
|
||||
dictionaries.clear();
|
||||
}
|
||||
scanPopup.reset();
|
||||
|
||||
wordFinder.clear();
|
||||
|
||||
dictionaries.clear();
|
||||
|
||||
::Initializing init( this );
|
||||
|
||||
|
@ -278,7 +273,7 @@ void MainWindow::makeDictionaries()
|
|||
|
||||
// Now start a thread to load all the dictionaries
|
||||
|
||||
LoadDictionaries loadDicts( allFiles );
|
||||
LoadDictionaries loadDicts( allFiles, cfg );
|
||||
|
||||
connect( &loadDicts, SIGNAL( indexingDictionarySignal( QString ) ),
|
||||
this, SLOT( indexingDictionary( QString ) ) );
|
||||
|
@ -294,10 +289,16 @@ void MainWindow::makeDictionaries()
|
|||
|
||||
loadDicts.wait();
|
||||
|
||||
{
|
||||
DictLock _;
|
||||
dictionaries = loadDicts.getDictionaries();
|
||||
|
||||
dictionaries = loadDicts.getDictionaries();
|
||||
///// We create MediaWiki dicts syncronously, since they use netmgr
|
||||
|
||||
{
|
||||
vector< sptr< Dictionary::Class > > dicts =
|
||||
MediaWiki::makeDictionaries( allFiles, Config::getIndexDir().toLocal8Bit().data(),
|
||||
loadDicts, cfg.mediawikis, dictNetMgr );
|
||||
|
||||
dictionaries.insert( dictionaries.end(), dicts.begin(), dicts.end() );
|
||||
}
|
||||
|
||||
initializing = 0;
|
||||
|
@ -360,14 +361,10 @@ void MainWindow::updateGroupList()
|
|||
disconnect( ui.groupList, SIGNAL( currentIndexChanged( QString const & ) ),
|
||||
this, SLOT( currentGroupChanged( QString const & ) ) );
|
||||
|
||||
{
|
||||
DictLock _;
|
||||
|
||||
groupInstances.clear();
|
||||
|
||||
for( unsigned x = 0; x < cfg.groups.size(); ++x )
|
||||
groupInstances.push_back( Instances::Group( cfg.groups[ x ], dictionaries ) );
|
||||
}
|
||||
groupInstances.clear();
|
||||
|
||||
for( unsigned x = 0; x < cfg.groups.size(); ++x )
|
||||
groupInstances.push_back( Instances::Group( cfg.groups[ x ], dictionaries ) );
|
||||
|
||||
ui.groupList->fill( groupInstances );
|
||||
ui.groupList->setCurrentGroup( cfg.lastMainGroup );
|
||||
|
@ -477,13 +474,14 @@ void MainWindow::iconChanged( ArticleView * view, QIcon const & icon )
|
|||
|
||||
void MainWindow::editSources()
|
||||
{
|
||||
Sources src( this, cfg.paths );
|
||||
Sources src( this, cfg.paths, cfg.mediawikis );
|
||||
|
||||
src.show();
|
||||
|
||||
if ( src.exec() == QDialog::Accepted )
|
||||
{
|
||||
cfg.paths = src.getPaths();
|
||||
cfg.mediawikis = src.getMediaWikis();
|
||||
|
||||
makeDictionaries();
|
||||
|
||||
|
@ -493,25 +491,20 @@ void MainWindow::editSources()
|
|||
|
||||
void MainWindow::editGroups()
|
||||
{
|
||||
Groups groups( this, dictionaries, cfg.groups );
|
||||
|
||||
groups.show();
|
||||
|
||||
if ( groups.exec() == QDialog::Accepted )
|
||||
{
|
||||
// We lock all dictionaries during the entire group editing process, since
|
||||
// the dictionaries might get queried for various infos there
|
||||
DictLock _;
|
||||
|
||||
Groups groups( this, dictionaries, cfg.groups );
|
||||
|
||||
groups.show();
|
||||
cfg.groups = groups.getGroups();
|
||||
|
||||
if ( groups.exec() == QDialog::Accepted )
|
||||
{
|
||||
cfg.groups = groups.getGroups();
|
||||
|
||||
Config::save( cfg );
|
||||
}
|
||||
else
|
||||
return;
|
||||
Config::save( cfg );
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
scanPopup.reset(); // It was holding group instances
|
||||
updateGroupList();
|
||||
makeScanPopup();
|
||||
}
|
||||
|
@ -548,19 +541,32 @@ void MainWindow::currentGroupChanged( QString const & gr )
|
|||
|
||||
void MainWindow::translateInputChanged( QString const & newValue )
|
||||
{
|
||||
// If there's some status bar message present, clear it since it may be
|
||||
// about the previous search that has failed.
|
||||
if ( !statusBar()->currentMessage().isEmpty() )
|
||||
statusBar()->clearMessage();
|
||||
|
||||
QString req = newValue.trimmed();
|
||||
|
||||
if ( !req.size() )
|
||||
{
|
||||
// An empty request always results in an empty result
|
||||
prefixMatchComplete( WordFinderResults( req, &getActiveDicts() ) );
|
||||
wordFinder.cancel();
|
||||
ui.wordList->clear();
|
||||
ui.wordList->unsetCursor();
|
||||
|
||||
// Reset the noResults mark if it's on right now
|
||||
if ( ui.translateLine->property( "noResults" ).toBool() )
|
||||
{
|
||||
ui.translateLine->setProperty( "noResults", false );
|
||||
qApp->setStyleSheet( qApp->styleSheet() );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ui.wordList->setCursor( Qt::WaitCursor );
|
||||
|
||||
wordFinder.prefixMatch( req, &getActiveDicts() );
|
||||
wordFinder.prefixMatch( req, getActiveDicts() );
|
||||
}
|
||||
|
||||
void MainWindow::translateInputFinished()
|
||||
|
@ -569,29 +575,34 @@ void MainWindow::translateInputFinished()
|
|||
wordListItemActivated( ui.wordList->item( 0 ) );
|
||||
}
|
||||
|
||||
void MainWindow::prefixMatchComplete( WordFinderResults r )
|
||||
void MainWindow::prefixMatchUpdated()
|
||||
{
|
||||
if ( r.requestStr != ui.translateLine->text().trimmed() ||
|
||||
r.requestDicts != &getActiveDicts() )
|
||||
{
|
||||
// Those results are already irrelevant, ignore the result
|
||||
return;
|
||||
}
|
||||
updateMatchResults( false );
|
||||
}
|
||||
|
||||
void MainWindow::prefixMatchFinished()
|
||||
{
|
||||
updateMatchResults( true );
|
||||
}
|
||||
|
||||
void MainWindow::updateMatchResults( bool finished )
|
||||
{
|
||||
std::vector< QString > const & results = wordFinder.getPrefixMatchResults();
|
||||
|
||||
ui.wordList->setUpdatesEnabled( false );
|
||||
|
||||
for( unsigned x = 0; x < r.results.size(); ++x )
|
||||
|
||||
for( unsigned x = 0; x < results.size(); ++x )
|
||||
{
|
||||
QListWidgetItem * i = ui.wordList->item( x );
|
||||
|
||||
if ( !i )
|
||||
ui.wordList->addItem( r.results[ x ] );
|
||||
ui.wordList->addItem( results[ x ] );
|
||||
else
|
||||
if ( i->text() != r.results[ x ] )
|
||||
i->setText( r.results[ x ] );
|
||||
if ( i->text() != results[ x ] )
|
||||
i->setText( results[ x ] );
|
||||
}
|
||||
|
||||
while ( ui.wordList->count() > (int) r.results.size() )
|
||||
while ( ui.wordList->count() > (int) results.size() )
|
||||
{
|
||||
// Chop off any extra items that were there
|
||||
QListWidgetItem * i = ui.wordList->takeItem( ui.wordList->count() - 1 );
|
||||
|
@ -609,7 +620,24 @@ void MainWindow::prefixMatchComplete( WordFinderResults r )
|
|||
}
|
||||
|
||||
ui.wordList->setUpdatesEnabled( true );
|
||||
ui.wordList->unsetCursor();
|
||||
|
||||
if ( finished )
|
||||
{
|
||||
ui.wordList->unsetCursor();
|
||||
|
||||
// Visually mark the input line to mark if there's no results
|
||||
|
||||
bool setMark = results.empty();
|
||||
|
||||
if ( ui.translateLine->property( "noResults" ).toBool() != setMark )
|
||||
{
|
||||
ui.translateLine->setProperty( "noResults", setMark );
|
||||
qApp->setStyleSheet( qApp->styleSheet() );
|
||||
}
|
||||
|
||||
if ( !wordFinder.getErrorString().isEmpty() )
|
||||
statusBar()->showMessage( tr( "WARNING: %1" ).arg( wordFinder.getErrorString() ) );
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::wordListItemActivated( QListWidgetItem * item )
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QThread>
|
||||
#include <QToolButton>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QNetworkAccessManager>
|
||||
#include "ui_mainwindow.h"
|
||||
#include "folding.hh"
|
||||
#include "config.hh"
|
||||
|
@ -23,16 +24,17 @@
|
|||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
class LoadDictionaries: public QThread, Dictionary::Initializing
|
||||
class LoadDictionaries: public QThread, public Dictionary::Initializing
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
vector< string > const & allFiles;
|
||||
Config::Class const & cfg;
|
||||
vector< sptr< Dictionary::Class > > dictionaries;
|
||||
|
||||
public:
|
||||
|
||||
LoadDictionaries( vector< string > const & allFiles );
|
||||
LoadDictionaries( vector< string > const & allFiles, Config::Class const & cfg );
|
||||
|
||||
virtual void run();
|
||||
|
||||
|
@ -43,7 +45,7 @@ signals:
|
|||
|
||||
void indexingDictionarySignal( QString dictionaryName );
|
||||
|
||||
private:
|
||||
public:
|
||||
|
||||
virtual void indexingDictionary( string const & dictionaryName ) throw();
|
||||
};
|
||||
|
@ -72,6 +74,9 @@ private:
|
|||
vector< Instances::Group > groupInstances;
|
||||
ArticleMaker articleMaker;
|
||||
ArticleNetworkAccessManager articleNetMgr;
|
||||
QNetworkAccessManager dictNetMgr; // We give dictionaries a separate manager,
|
||||
// since their requests can be destroyed
|
||||
// in a separate thread
|
||||
|
||||
WordFinder wordFinder;
|
||||
|
||||
|
@ -90,6 +95,8 @@ private:
|
|||
void updateGroupList();
|
||||
void makeScanPopup();
|
||||
|
||||
void updateMatchResults( bool finished );
|
||||
|
||||
/// Returns the reference to dictionaries stored in the currently active
|
||||
/// group, or to all dictionaries if there are no groups.
|
||||
vector< sptr< Dictionary::Class > > const & getActiveDicts();
|
||||
|
@ -118,7 +125,10 @@ private slots:
|
|||
void currentGroupChanged( QString const & );
|
||||
void translateInputChanged( QString const & );
|
||||
void translateInputFinished();
|
||||
void prefixMatchComplete( WordFinderResults );
|
||||
|
||||
void prefixMatchUpdated();
|
||||
void prefixMatchFinished();
|
||||
|
||||
void wordListItemActivated( QListWidgetItem * );
|
||||
void wordListSelectionChanged();
|
||||
|
||||
|
|
321
src/mediawiki.cc
Normal file
321
src/mediawiki.cc
Normal file
|
@ -0,0 +1,321 @@
|
|||
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "mediawiki.hh"
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <QtXml>
|
||||
|
||||
namespace MediaWiki {
|
||||
|
||||
using namespace Dictionary;
|
||||
|
||||
namespace {
|
||||
|
||||
class MediaWikiDictionary: public Dictionary::Class
|
||||
{
|
||||
string name;
|
||||
QString url;
|
||||
QNetworkAccessManager & netMgr;
|
||||
|
||||
public:
|
||||
|
||||
MediaWikiDictionary( string const & id, string const & name_,
|
||||
QString const & url_,
|
||||
QNetworkAccessManager & netMgr_ ):
|
||||
Dictionary::Class( id, vector< string >() ),
|
||||
name( name_ ),
|
||||
url( url_ ),
|
||||
netMgr( netMgr_ )
|
||||
{
|
||||
}
|
||||
|
||||
virtual string getName() throw()
|
||||
{ return name; }
|
||||
|
||||
virtual map< Property, string > getProperties() throw()
|
||||
{ return map< Property, string >(); }
|
||||
|
||||
virtual unsigned long getArticleCount() throw()
|
||||
{ return 0; }
|
||||
|
||||
virtual unsigned long getWordCount() throw()
|
||||
{ return 0; }
|
||||
|
||||
virtual sptr< WordSearchRequest > prefixMatch( wstring const &,
|
||||
unsigned long maxResults ) throw( std::exception );
|
||||
|
||||
virtual sptr< DataRequest > getArticle( wstring const &, vector< wstring > const & alts )
|
||||
throw( std::exception );
|
||||
};
|
||||
|
||||
class MediaWikiWordSearchRequest: public MediaWikiWordSearchRequestSlots
|
||||
{
|
||||
sptr< QNetworkReply > netReply;
|
||||
bool livedLongEnough; // Indicates that the request has lived long enough
|
||||
// to be destroyed prematurely. Used to prevent excessive
|
||||
// network loads when typing search terms rapidly.
|
||||
bool isCancelling;
|
||||
|
||||
public:
|
||||
|
||||
MediaWikiWordSearchRequest( wstring const &,
|
||||
QString const & url, QNetworkAccessManager & mgr );
|
||||
|
||||
~MediaWikiWordSearchRequest();
|
||||
|
||||
virtual void cancel();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void timerEvent( QTimerEvent * );
|
||||
|
||||
private:
|
||||
|
||||
virtual void downloadFinished();
|
||||
};
|
||||
|
||||
MediaWikiWordSearchRequest::MediaWikiWordSearchRequest( wstring const & str,
|
||||
QString const & url,
|
||||
QNetworkAccessManager & mgr ):
|
||||
livedLongEnough( false ), isCancelling( false )
|
||||
{
|
||||
printf( "request begin\n" );
|
||||
QUrl reqUrl( url + "/api.php?action=query&list=allpages&aplimit=40&format=xml" );
|
||||
|
||||
reqUrl.addQueryItem( "apfrom", QString::fromStdWString( str ) );
|
||||
|
||||
netReply = mgr.get( QNetworkRequest( reqUrl ) );
|
||||
|
||||
connect( netReply.get(), SIGNAL( finished() ),
|
||||
this, SLOT( downloadFinished() ) );
|
||||
|
||||
// We start a timer to postpone early destruction, so a rapid type won't make
|
||||
// unnecessary network load
|
||||
startTimer( 200 );
|
||||
}
|
||||
|
||||
void MediaWikiWordSearchRequest::timerEvent( QTimerEvent * ev )
|
||||
{
|
||||
killTimer( ev->timerId() );
|
||||
livedLongEnough = true;
|
||||
|
||||
if ( isCancelling )
|
||||
finish();
|
||||
}
|
||||
|
||||
MediaWikiWordSearchRequest::~MediaWikiWordSearchRequest()
|
||||
{
|
||||
printf( "request end\n" );
|
||||
}
|
||||
|
||||
void MediaWikiWordSearchRequest::cancel()
|
||||
{
|
||||
// We either finish it in place, or in the timer handler
|
||||
isCancelling = true;
|
||||
|
||||
if ( netReply.get() )
|
||||
netReply.reset();
|
||||
|
||||
if ( livedLongEnough )
|
||||
{
|
||||
finish();
|
||||
}
|
||||
else
|
||||
printf("not long enough\n" );
|
||||
}
|
||||
|
||||
void MediaWikiWordSearchRequest::downloadFinished()
|
||||
{
|
||||
if ( isCancelling || isFinished() ) // Was cancelled
|
||||
return;
|
||||
|
||||
if ( netReply->error() == QNetworkReply::NoError )
|
||||
{
|
||||
QDomDocument dd;
|
||||
|
||||
QString errorStr;
|
||||
int errorLine, errorColumn;
|
||||
|
||||
if ( !dd.setContent( netReply.get(), false, &errorStr, &errorLine, &errorColumn ) )
|
||||
{
|
||||
setErrorString( QString( tr( "XML parse error: %1 at %2,%3" ).
|
||||
arg( errorStr ).arg( errorLine ).arg( errorColumn ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
QDomNode pages = dd.namedItem( "api" ).namedItem( "query" ).namedItem( "allpages" );
|
||||
|
||||
if ( !pages.isNull() )
|
||||
{
|
||||
QDomNodeList nl = pages.toElement().elementsByTagName( "p" );
|
||||
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
for( unsigned x = 0; x < nl.length(); ++x )
|
||||
matches.push_back( nl.item( x ).toElement().attribute( "title" ).toStdWString() );
|
||||
}
|
||||
}
|
||||
printf( "done.\n" );
|
||||
}
|
||||
else
|
||||
setErrorString( netReply->errorString() );
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
class MediaWikiArticleRequest: public MediaWikiDataRequestSlots
|
||||
{
|
||||
sptr< QNetworkReply > netReply;
|
||||
QString url;
|
||||
|
||||
public:
|
||||
|
||||
MediaWikiArticleRequest( wstring const &,
|
||||
QString const & url, QNetworkAccessManager & mgr );
|
||||
|
||||
virtual void cancel();
|
||||
|
||||
private:
|
||||
|
||||
virtual void downloadFinished();
|
||||
};
|
||||
|
||||
void MediaWikiArticleRequest::cancel()
|
||||
{
|
||||
finish();
|
||||
}
|
||||
|
||||
MediaWikiArticleRequest::MediaWikiArticleRequest( wstring const & str,
|
||||
QString const & url_,
|
||||
QNetworkAccessManager & mgr ):
|
||||
url( url_ )
|
||||
{
|
||||
printf( "Requesting article %ls\n", str.c_str() );
|
||||
|
||||
QUrl reqUrl( url + "/api.php?action=parse&prop=text|revid&format=xml&redirects" );
|
||||
|
||||
reqUrl.addQueryItem( "page", QString::fromStdWString( str ) );
|
||||
|
||||
netReply = mgr.get( QNetworkRequest( reqUrl ) );
|
||||
|
||||
printf( "request begin\n" );
|
||||
connect( netReply.get(), SIGNAL( finished() ),
|
||||
this, SLOT( downloadFinished() ) );
|
||||
printf( "request end\n" );
|
||||
}
|
||||
|
||||
void MediaWikiArticleRequest::downloadFinished()
|
||||
{
|
||||
printf( "Finished.\n" );
|
||||
|
||||
if ( isFinished() ) // Was cancelled
|
||||
return;
|
||||
|
||||
if ( netReply->error() == QNetworkReply::NoError )
|
||||
{
|
||||
QDomDocument dd;
|
||||
|
||||
QString errorStr;
|
||||
int errorLine, errorColumn;
|
||||
|
||||
if ( !dd.setContent( netReply.get(), false, &errorStr, &errorLine, &errorColumn ) )
|
||||
{
|
||||
setErrorString( QString( tr( "XML parse error: %1 at %2,%3" ).
|
||||
arg( errorStr ).arg( errorLine ).arg( errorColumn ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
QDomNode parseNode = dd.namedItem( "api" ).namedItem( "parse" );
|
||||
|
||||
if ( !parseNode.isNull() && parseNode.toElement().attribute( "revid" ) != "0" )
|
||||
{
|
||||
QDomNode textNode = parseNode.namedItem( "text" );
|
||||
|
||||
if ( !textNode.isNull() )
|
||||
{
|
||||
QString articleString = textNode.toElement().text();
|
||||
|
||||
QUrl wikiUrl( url );
|
||||
wikiUrl.setPath( "" );
|
||||
|
||||
// Update any special index.php pages to be absolute
|
||||
articleString.replace( QRegExp( "<a\\shref=\"(/(\\w*/)*index.php\\?)" ),
|
||||
QString( "<a href=\"%1\\1" ).arg( wikiUrl.toString() ) );
|
||||
// Replace the href="/foo/bar/Baz" to just href="Baz".
|
||||
articleString.replace( QRegExp( "<a\\shref=\"/(\\w*/)*" ), "<a href=\"" );
|
||||
|
||||
// In those strings, change any underscores to spaces
|
||||
for( ; ; )
|
||||
{
|
||||
QString before = articleString;
|
||||
articleString.replace( QRegExp( "<a href=\"((\\w)*)_" ), "<a href=\"\\1 " );
|
||||
|
||||
if ( articleString == before )
|
||||
break;
|
||||
}
|
||||
|
||||
QByteArray articleBody = articleString.toUtf8();
|
||||
|
||||
printf( "Article body after: %s\n", articleBody.data() );
|
||||
|
||||
articleBody.prepend( "<div class=\"mwiki\">" );
|
||||
articleBody.append( "</div>" );
|
||||
|
||||
Mutex::Lock _( dataMutex );
|
||||
|
||||
data.resize( articleBody.size() );
|
||||
|
||||
memcpy( &data.front(), articleBody.data(), data.size() );
|
||||
|
||||
hasAnyData = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf( "done.\n" );
|
||||
}
|
||||
else
|
||||
setErrorString( netReply->errorString() );
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
sptr< WordSearchRequest > MediaWikiDictionary::prefixMatch( wstring const & word,
|
||||
unsigned long maxResults )
|
||||
throw( std::exception )
|
||||
{
|
||||
return new MediaWikiWordSearchRequest( word, url, netMgr );
|
||||
}
|
||||
|
||||
sptr< DataRequest > MediaWikiDictionary::getArticle( wstring const & word, vector< wstring > const & alts )
|
||||
throw( std::exception )
|
||||
{
|
||||
return new MediaWikiArticleRequest( word, url, netMgr );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing &,
|
||||
Config::MediaWikis const & wikis,
|
||||
QNetworkAccessManager & mgr )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< sptr< Dictionary::Class > > result;
|
||||
|
||||
for( unsigned x = 0; x < wikis.size(); ++x )
|
||||
{
|
||||
if ( wikis[ x ].enabled )
|
||||
result.push_back( new MediaWikiDictionary( wikis[ x ].id.toStdString(),
|
||||
wikis[ x ].name.toUtf8().data(),
|
||||
wikis[ x ].url,
|
||||
mgr ) );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
49
src/mediawiki.hh
Normal file
49
src/mediawiki.hh
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#ifndef __MEDIAWIKI_HH_INCLUDED__
|
||||
#define __MEDIAWIKI_HH_INCLUDED__
|
||||
|
||||
#include "dictionary.hh"
|
||||
#include "config.hh"
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
/// Support for MediaWiki-based wikis, based on its public API.
|
||||
namespace MediaWiki {
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing &,
|
||||
Config::MediaWikis const & wikis,
|
||||
QNetworkAccessManager & )
|
||||
throw( std::exception );
|
||||
|
||||
/// Exposed here for moc
|
||||
class MediaWikiWordSearchRequestSlots: public Dictionary::WordSearchRequest
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void downloadFinished()
|
||||
{}
|
||||
};
|
||||
|
||||
/// Exposed here for moc
|
||||
class MediaWikiDataRequestSlots: public Dictionary::DataRequest
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected slots:
|
||||
|
||||
virtual void downloadFinished()
|
||||
{}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
14
src/mutex.cc
Normal file
14
src/mutex.cc
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "mutex.hh"
|
||||
|
||||
Mutex::Lock::Lock( Mutex & m_ ): m( m_ )
|
||||
{
|
||||
m.lock();
|
||||
}
|
||||
|
||||
Mutex::Lock::~Lock()
|
||||
{
|
||||
m.unlock();
|
||||
}
|
32
src/mutex.hh
Normal file
32
src/mutex.hh
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.berlios.de>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#ifndef __MUTEX_HH_INCLUDED__
|
||||
#define __MUTEX_HH_INCLUDED__
|
||||
|
||||
#include <QMutex>
|
||||
|
||||
/// This provides a mutex class. As you can see, it's just a Qt one, but it
|
||||
/// does provide the Lock class which doesn't seem to exist in Qt, and it does
|
||||
/// provide some abstraction for dictionaries in case they are to be ported
|
||||
/// away from Qt.
|
||||
class Mutex: public QMutex
|
||||
{
|
||||
public:
|
||||
|
||||
/// Locks the given mutex on construction and unlocks on destruction
|
||||
class Lock
|
||||
{
|
||||
Mutex & m;
|
||||
|
||||
public:
|
||||
|
||||
Lock( Mutex & );
|
||||
~Lock();
|
||||
|
||||
private:
|
||||
Lock( Lock const & );
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
|
@ -11,6 +11,7 @@
|
|||
<file>icons/programicon.png</file>
|
||||
<file>icons/programicon_scan.png</file>
|
||||
<file>icons/wizard.png</file>
|
||||
<file>icons/warning.png</file>
|
||||
<file>article-style.css</file>
|
||||
<file>qt-style.css</file>
|
||||
</qresource>
|
||||
|
|
|
@ -28,6 +28,9 @@ ScanPopup::ScanPopup( QWidget * parent,
|
|||
mouseEnteredOnce( false )
|
||||
{
|
||||
ui.setupUi( this );
|
||||
|
||||
ui.queryError->hide();
|
||||
|
||||
definition = new ArticleView( ui.outerFrame, articleNetMgr, groups, true ),
|
||||
ui.mainLayout->addWidget( definition );
|
||||
|
||||
|
@ -62,8 +65,8 @@ ScanPopup::ScanPopup( QWidget * parent,
|
|||
connect( ui.groupList, SIGNAL( currentIndexChanged( QString const & ) ),
|
||||
this, SLOT( currentGroupChanged( QString const & ) ) );
|
||||
|
||||
connect( wordFinder.qobject(), SIGNAL( prefixMatchComplete( WordFinderResults ) ),
|
||||
this, SLOT( prefixMatchComplete( WordFinderResults ) ) );
|
||||
connect( &wordFinder, SIGNAL( finished() ),
|
||||
this, SLOT( prefixMatchFinished() ) );
|
||||
|
||||
connect( ui.word, SIGNAL( clicked() ),
|
||||
this, SLOT( initialWordClicked() ) );
|
||||
|
@ -205,8 +208,8 @@ void ScanPopup::currentGroupChanged( QString const & gr )
|
|||
|
||||
void ScanPopup::initiateTranslation()
|
||||
{
|
||||
definition->showAnticipation();
|
||||
wordFinder.prefixMatch( inputWord, &getActiveDicts() );
|
||||
definition->showDefinition( inputWord, ui.groupList->currentText() );
|
||||
wordFinder.prefixMatch( inputWord, getActiveDicts() );
|
||||
}
|
||||
|
||||
vector< sptr< Dictionary::Class > > const & ScanPopup::getActiveDicts()
|
||||
|
@ -306,13 +309,19 @@ void ScanPopup::showEvent( QShowEvent * ev )
|
|||
ui.groupList->hide();
|
||||
}
|
||||
|
||||
void ScanPopup::prefixMatchComplete( WordFinderResults r )
|
||||
void ScanPopup::prefixMatchFinished()
|
||||
{
|
||||
// Check that the request wasn't already overridden by another one and
|
||||
// that there's a window there at all
|
||||
if ( isVisible() && r.requestStr == inputWord &&
|
||||
r.requestDicts == &getActiveDicts() )
|
||||
// Check that there's a window there at all
|
||||
if ( isVisible() )
|
||||
{
|
||||
if ( wordFinder.getErrorString().size() )
|
||||
{
|
||||
ui.queryError->setToolTip( wordFinder.getErrorString() );
|
||||
ui.queryError->show();
|
||||
}
|
||||
else
|
||||
ui.queryError->hide();
|
||||
|
||||
// Find the matches that aren't prefix. If there're more than one,
|
||||
// show the diacritic toolbutton. If there are prefix matches, show
|
||||
// the prefix toolbutton.
|
||||
|
@ -322,12 +331,14 @@ void ScanPopup::prefixMatchComplete( WordFinderResults r )
|
|||
|
||||
wstring foldedInputWord = Folding::apply( inputWord.toStdWString() );
|
||||
|
||||
for( unsigned x = 0; x < r.results.size(); ++x )
|
||||
std::vector< QString > const & results = wordFinder.getPrefixMatchResults();
|
||||
|
||||
for( unsigned x = 0; x < results.size(); ++x )
|
||||
{
|
||||
if ( Folding::apply( r.results[ x ].toStdWString() ) == foldedInputWord )
|
||||
diacriticMatches.push_back( r.results[ x ] );
|
||||
if ( Folding::apply( results[ x ].toStdWString() ) == foldedInputWord )
|
||||
diacriticMatches.push_back( results[ x ] );
|
||||
else
|
||||
prefixMatches.push_back( r.results[ x ] );
|
||||
prefixMatches.push_back( results[ x ] );
|
||||
}
|
||||
|
||||
if ( diacriticMatches.size() > 1 )
|
||||
|
@ -345,14 +356,6 @@ void ScanPopup::prefixMatchComplete( WordFinderResults r )
|
|||
}
|
||||
else
|
||||
ui.prefixButton->hide();
|
||||
|
||||
if ( diacriticMatches.size() )
|
||||
definition->showDefinition( diacriticMatches[ 0 ], ui.groupList->currentText() );
|
||||
else
|
||||
{
|
||||
// No matches
|
||||
definition->showNotFound( inputWord, ui.groupList->currentText() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ private slots:
|
|||
void clipboardChanged( QClipboard::Mode );
|
||||
void mouseHovered( QString const & );
|
||||
void currentGroupChanged( QString const & );
|
||||
void prefixMatchComplete( WordFinderResults r );
|
||||
void prefixMatchFinished();
|
||||
void diacriticButtonClicked();
|
||||
void prefixButtonClicked();
|
||||
void initialWordClicked();
|
||||
|
|
|
@ -37,6 +37,19 @@
|
|||
<item>
|
||||
<widget class="GroupComboBox" name="groupList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="queryError">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="resources.qrc">:/icons/warning.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="word">
|
||||
<property name="sizePolicy">
|
||||
|
|
189
src/sources.cc
189
src/sources.cc
|
@ -4,12 +4,21 @@
|
|||
#include "sources.hh"
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
|
||||
Sources::Sources( QWidget * parent, Config::Paths const & paths_ ): QDialog( parent ),
|
||||
paths( paths_ )
|
||||
Sources::Sources( QWidget * parent, Config::Paths const & paths_,
|
||||
Config::MediaWikis const & mediawikis ): QDialog( parent ),
|
||||
mediawikisModel( this, mediawikis ), paths( paths_ )
|
||||
{
|
||||
ui.setupUi( this );
|
||||
|
||||
ui.mediaWikis->setTabKeyNavigation( true );
|
||||
ui.mediaWikis->setModel( &mediawikisModel );
|
||||
ui.mediaWikis->resizeColumnToContents( 0 );
|
||||
ui.mediaWikis->resizeColumnToContents( 1 );
|
||||
ui.mediaWikis->resizeColumnToContents( 2 );
|
||||
|
||||
for( Config::Paths::const_iterator i = paths.begin(); i != paths.end(); ++i )
|
||||
ui.dictionaries->addItem( *i );
|
||||
|
||||
|
@ -56,3 +65,179 @@ void Sources::remove()
|
|||
}
|
||||
}
|
||||
|
||||
void Sources::on_addMediaWiki_clicked()
|
||||
{
|
||||
mediawikisModel.addNewWiki();
|
||||
QModelIndex result =
|
||||
mediawikisModel.index( mediawikisModel.rowCount( QModelIndex() ) - 1,
|
||||
1, QModelIndex() );
|
||||
|
||||
ui.mediaWikis->scrollTo( result );
|
||||
//ui.mediaWikis->setCurrentIndex( result );
|
||||
ui.mediaWikis->edit( result );
|
||||
}
|
||||
|
||||
void Sources::on_removeMediaWiki_clicked()
|
||||
{
|
||||
QModelIndex current = ui.mediaWikis->currentIndex();
|
||||
|
||||
if ( current.isValid() &&
|
||||
QMessageBox::question( this, tr( "Confirm removal" ),
|
||||
tr( "Remove site <b>%1</b> from the list?" ).arg( mediawikisModel.getCurrentWikis()[ current.row() ].name ),
|
||||
QMessageBox::Ok,
|
||||
QMessageBox::Cancel ) == QMessageBox::Ok )
|
||||
mediawikisModel.removeWiki( current.row() );
|
||||
}
|
||||
|
||||
////////// MediaWikisModel
|
||||
|
||||
MediaWikisModel::MediaWikisModel( QWidget * parent,
|
||||
Config::MediaWikis const & mediawikis_ ):
|
||||
QAbstractItemModel( parent ), mediawikis( mediawikis_ )
|
||||
{
|
||||
}
|
||||
void MediaWikisModel::removeWiki( int index )
|
||||
{
|
||||
beginRemoveRows( QModelIndex(), index, index );
|
||||
mediawikis.erase( mediawikis.begin() + index );
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void MediaWikisModel::addNewWiki()
|
||||
{
|
||||
Config::MediaWiki w;
|
||||
|
||||
w.enabled = false;
|
||||
|
||||
// That's quite some rng
|
||||
w.id = QString(
|
||||
QCryptographicHash::hash(
|
||||
QDateTime::currentDateTime().toString( "\"MediaWiki\"dd.MM.yyyy hh:mm:ss.zzz" ).toUtf8(),
|
||||
QCryptographicHash::Md5 ).toHex() );
|
||||
|
||||
w.url = "http://";
|
||||
|
||||
beginInsertRows( QModelIndex(), mediawikis.size(), mediawikis.size() );
|
||||
mediawikis.push_back( w );
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
QModelIndex MediaWikisModel::index( int row, int column, QModelIndex const & /*parent*/ ) const
|
||||
{
|
||||
return createIndex( row, column, 0 );
|
||||
}
|
||||
|
||||
QModelIndex MediaWikisModel::parent( QModelIndex const & /*parent*/ ) const
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
Qt::ItemFlags MediaWikisModel::flags( QModelIndex const & index ) const
|
||||
{
|
||||
Qt::ItemFlags result = QAbstractItemModel::flags( index );
|
||||
|
||||
if ( index.isValid() )
|
||||
{
|
||||
if ( !index.column() )
|
||||
result |= Qt::ItemIsUserCheckable;
|
||||
else
|
||||
result |= Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int MediaWikisModel::rowCount( QModelIndex const & parent ) const
|
||||
{
|
||||
if ( parent.isValid() )
|
||||
return 0;
|
||||
else
|
||||
return mediawikis.size();
|
||||
}
|
||||
|
||||
int MediaWikisModel::columnCount( QModelIndex const & parent ) const
|
||||
{
|
||||
if ( parent.isValid() )
|
||||
return 0;
|
||||
else
|
||||
return 3;
|
||||
}
|
||||
|
||||
QVariant MediaWikisModel::headerData( int section, Qt::Orientation /*orientation*/, int role ) const
|
||||
{
|
||||
if ( role == Qt::DisplayRole )
|
||||
switch( section )
|
||||
{
|
||||
case 0:
|
||||
return tr( "Enabled" );
|
||||
case 1:
|
||||
return tr( "Name" );
|
||||
case 2:
|
||||
return tr( "Address" );
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant MediaWikisModel::data( QModelIndex const & index, int role ) const
|
||||
{
|
||||
if ( (unsigned)index.row() >= mediawikis.size() )
|
||||
return QVariant();
|
||||
|
||||
if ( role == Qt::DisplayRole || role == Qt::EditRole )
|
||||
{
|
||||
switch( index.column() )
|
||||
{
|
||||
case 1:
|
||||
return mediawikis[ index.row() ].name;
|
||||
case 2:
|
||||
return mediawikis[ index.row() ].url;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
if ( role == Qt::CheckStateRole && !index.column() )
|
||||
return mediawikis[ index.row() ].enabled;
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool MediaWikisModel::setData( QModelIndex const & index, const QVariant & value,
|
||||
int role )
|
||||
{
|
||||
if ( (unsigned)index.row() >= mediawikis.size() )
|
||||
return false;
|
||||
|
||||
if ( role == Qt::CheckStateRole && !index.column() )
|
||||
{
|
||||
//printf( "type = %d\n", (int)value.type() );
|
||||
//printf( "value = %d\n", (int)value.toInt() );
|
||||
|
||||
// XXX it seems to be always passing Int( 2 ) as a value, so we just toggle
|
||||
mediawikis[ index.row() ].enabled = !mediawikis[ index.row() ].enabled;
|
||||
|
||||
dataChanged( index, index );
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( role == Qt::DisplayRole || role == Qt::EditRole )
|
||||
switch( index.column() )
|
||||
{
|
||||
case 1:
|
||||
mediawikis[ index.row() ].name = value.toString();
|
||||
dataChanged( index, index );
|
||||
return true;
|
||||
case 2:
|
||||
mediawikis[ index.row() ].url = value.toString();
|
||||
dataChanged( index, index );
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,25 +6,64 @@
|
|||
|
||||
#include "ui_sources.h"
|
||||
#include "config.hh"
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
/// A model to be projected into the mediawikis view, according to Qt's MVC model
|
||||
class MediaWikisModel: public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MediaWikisModel( QWidget * parent, Config::MediaWikis const & );
|
||||
|
||||
void removeWiki( int index );
|
||||
void addNewWiki();
|
||||
|
||||
/// Returns the wikis the model currently has listed
|
||||
Config::MediaWikis const & getCurrentWikis() const
|
||||
{ return mediawikis; }
|
||||
|
||||
QModelIndex index( int row, int column, QModelIndex const & parent ) const;
|
||||
QModelIndex parent( QModelIndex const & parent ) const;
|
||||
Qt::ItemFlags flags( QModelIndex const & index ) const;
|
||||
int rowCount( QModelIndex const & parent ) const;
|
||||
int columnCount( QModelIndex const & parent ) const;
|
||||
QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
|
||||
QVariant data( QModelIndex const & index, int role ) const;
|
||||
bool setData( QModelIndex const & index, const QVariant & value, int role );
|
||||
|
||||
private:
|
||||
|
||||
Config::MediaWikis mediawikis;
|
||||
};
|
||||
|
||||
|
||||
class Sources: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Sources( QWidget * parent, Config::Paths const & );
|
||||
Sources( QWidget * parent, Config::Paths const &, Config::MediaWikis const & );
|
||||
|
||||
Config::Paths const & getPaths() const
|
||||
{ return paths; }
|
||||
|
||||
Config::MediaWikis const & getMediaWikis() const
|
||||
{ return mediawikisModel.getCurrentWikis(); }
|
||||
|
||||
private:
|
||||
Ui::Sources ui;
|
||||
MediaWikisModel mediawikisModel;
|
||||
Config::Paths paths;
|
||||
|
||||
private slots:
|
||||
|
||||
void add();
|
||||
void remove();
|
||||
|
||||
void on_addMediaWiki_clicked();
|
||||
void on_removeMediaWiki_clicked();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
172
src/sources.ui
172
src/sources.ui
|
@ -1,69 +1,139 @@
|
|||
<ui version="4.0" >
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Sources</class>
|
||||
<widget class="QDialog" name="Sources" >
|
||||
<property name="windowModality" >
|
||||
<widget class="QDialog" name="Sources">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::ApplicationModal</enum>
|
||||
</property>
|
||||
<property name="geometry" >
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>360</width>
|
||||
<height>328</height>
|
||||
<width>518</width>
|
||||
<height>337</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<property name="windowTitle">
|
||||
<string>Sources</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" >
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>Paths to search for the dictionaries:</string>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="filesTab">
|
||||
<attribute name="title">
|
||||
<string>Files</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Paths to search for the dictionary files:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="dictionaries"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="add">
|
||||
<property name="text">
|
||||
<string>&Add...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="remove">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>17</width>
|
||||
<height>68</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="mediaWikisTab">
|
||||
<attribute name="title">
|
||||
<string>Wikipedia</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Wikipedia (MediaWiki) sites:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QTreeView" name="mediaWikis"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="addMediaWiki">
|
||||
<property name="text">
|
||||
<string>&Add...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeMediaWiki">
|
||||
<property name="text">
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||
<item>
|
||||
<widget class="QListWidget" name="dictionaries" />
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||
<item>
|
||||
<widget class="QPushButton" name="add" >
|
||||
<property name="text" >
|
||||
<string>&Add...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="remove" >
|
||||
<property name="text" >
|
||||
<string>&Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0" >
|
||||
<size>
|
||||
<width>17</width>
|
||||
<height>68</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttons" >
|
||||
<property name="standardButtons" >
|
||||
<widget class="QDialogButtonBox" name="buttons">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
|
|
|
@ -91,6 +91,7 @@ bool indexIsOldOrBad( string const & indexFile )
|
|||
class StardictDictionary: public BtreeIndexing::BtreeDictionary
|
||||
{
|
||||
Ifo ifo;
|
||||
Mutex idxMutex;
|
||||
File::Class idx;
|
||||
IdxHeader idxHeader;
|
||||
ChunkedStorage::Reader chunks;
|
||||
|
@ -116,11 +117,12 @@ public:
|
|||
virtual unsigned long getWordCount() throw()
|
||||
{ return ifo.wordcount + ifo.synwordcount; }
|
||||
|
||||
virtual vector< wstring > findHeadwordsForSynonym( wstring const & )
|
||||
virtual sptr< Dictionary::WordSearchRequest > findHeadwordsForSynonym( wstring const & )
|
||||
throw( std::exception );
|
||||
|
||||
virtual string getArticle( wstring const &, vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception );
|
||||
virtual sptr< Dictionary::DataRequest > getArticle( wstring const &,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception );
|
||||
|
||||
private:
|
||||
|
||||
|
@ -157,7 +159,7 @@ StardictDictionary::StardictDictionary( string const & id,
|
|||
|
||||
idx.seek( idxHeader.indexOffset );
|
||||
|
||||
openIndex( idx );
|
||||
openIndex( idx, idxMutex );
|
||||
}
|
||||
|
||||
StardictDictionary::~StardictDictionary()
|
||||
|
@ -172,6 +174,8 @@ void StardictDictionary::getArticleProps( uint32_t articleAddress,
|
|||
{
|
||||
vector< char > chunk;
|
||||
|
||||
Mutex::Lock _( idxMutex );
|
||||
|
||||
char * articleData = chunks.getBlock( articleAddress, chunk );
|
||||
|
||||
memcpy( &offset, articleData, sizeof( uint32_t ) );
|
||||
|
@ -390,10 +394,11 @@ void StardictDictionary::loadArticle( uint32_t address,
|
|||
free( articleBody );
|
||||
}
|
||||
|
||||
vector< wstring > StardictDictionary::findHeadwordsForSynonym( wstring const & str )
|
||||
sptr< Dictionary::WordSearchRequest > StardictDictionary::findHeadwordsForSynonym( wstring const & str )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< wstring > result;
|
||||
sptr< Dictionary::WordSearchRequestInstant > result =
|
||||
new Dictionary::WordSearchRequestInstant;
|
||||
|
||||
vector< WordArticleLink > chain = findArticles( str );
|
||||
|
||||
|
@ -412,16 +417,16 @@ vector< wstring > StardictDictionary::findHeadwordsForSynonym( wstring const & s
|
|||
{
|
||||
// The headword seems to differ from the input word, which makes the
|
||||
// input word its synonym.
|
||||
result.push_back( headwordDecoded );
|
||||
result->getMatches().push_back( headwordDecoded );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
string StardictDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( Dictionary::exNoSuchWord, std::exception )
|
||||
sptr< Dictionary::DataRequest > StardictDictionary::getArticle( wstring const & word,
|
||||
vector< wstring > const & alts )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< WordArticleLink > chain = findArticles( word );
|
||||
|
||||
|
@ -473,7 +478,7 @@ string StardictDictionary::getArticle( wstring const & word,
|
|||
}
|
||||
|
||||
if ( mainArticles.empty() && alternateArticles.empty() )
|
||||
throw Dictionary::exNoSuchWord();
|
||||
return new Dictionary::DataRequestInstant( false ); // No such word
|
||||
|
||||
string result;
|
||||
|
||||
|
@ -502,7 +507,14 @@ string StardictDictionary::getArticle( wstring const & word,
|
|||
result += cleaner;
|
||||
}
|
||||
|
||||
return result;
|
||||
Dictionary::DataRequestInstant * ret =
|
||||
new Dictionary::DataRequestInstant( true );
|
||||
|
||||
ret->getData().resize( result.size() );
|
||||
|
||||
memcpy( &(ret->getData().front()), result.data(), result.size() );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
@ -743,10 +755,10 @@ static void handleIdxSynFile( string const & fileName,
|
|||
printf( "%u entires made\n", indexedWords.size() );
|
||||
}
|
||||
|
||||
vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & initializing )
|
||||
throw( std::exception )
|
||||
{
|
||||
vector< sptr< Dictionary::Class > > dictionaries;
|
||||
|
@ -786,11 +798,11 @@ vector< sptr< Dictionary::Class > > Format::makeDictionaries(
|
|||
if ( ifo.synwordcount )
|
||||
dictFiles.push_back( synFileName );
|
||||
|
||||
string dictId = makeDictionaryId( dictFiles );
|
||||
string dictId = Dictionary::makeDictionaryId( dictFiles );
|
||||
|
||||
string indexFile = indicesDir + dictId;
|
||||
|
||||
if ( needToRebuildIndex( dictFiles, indexFile ) ||
|
||||
if ( Dictionary::needToRebuildIndex( dictFiles, indexFile ) ||
|
||||
indexIsOldOrBad( indexFile ) )
|
||||
{
|
||||
// Building the index
|
||||
|
|
|
@ -12,16 +12,11 @@ namespace Stardict {
|
|||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
class Format: public Dictionary::Format
|
||||
{
|
||||
public:
|
||||
|
||||
virtual vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< sptr< Dictionary::Class > > makeDictionaries(
|
||||
vector< string > const & fileNames,
|
||||
string const & indicesDir,
|
||||
Dictionary::Initializing & )
|
||||
throw( std::exception );
|
||||
};
|
||||
throw( std::exception );
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,185 +2,224 @@
|
|||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "wordfinder.hh"
|
||||
#include "dictlock.hh"
|
||||
#include "folding.hh"
|
||||
#include <QMetaType>
|
||||
#include <QThreadPool>
|
||||
#include <map>
|
||||
|
||||
using std::vector;
|
||||
using std::list;
|
||||
using std::wstring;
|
||||
using std::map;
|
||||
using std::pair;
|
||||
|
||||
namespace
|
||||
WordFinder::WordFinder( QObject * parent ):
|
||||
QObject( parent ), searchInProgress( false ),
|
||||
updateResultsTimer( this ),
|
||||
searchQueued( false )
|
||||
{
|
||||
struct MetaTypeRegister
|
||||
{
|
||||
MetaTypeRegister()
|
||||
{
|
||||
qRegisterMetaType< WordFinderResults >( "WordFinderResults" );
|
||||
}
|
||||
};
|
||||
}
|
||||
updateResultsTimer.setInterval( 1000 ); // We use a one second update timer
|
||||
updateResultsTimer.setSingleShot( true );
|
||||
|
||||
WordFinder::WordFinder( QObject * parent ): QThread( parent ), op( NoOp )
|
||||
{
|
||||
static MetaTypeRegister _;
|
||||
|
||||
start();
|
||||
connect( &updateResultsTimer, SIGNAL( timeout() ),
|
||||
this, SLOT( updateResults() ) );
|
||||
}
|
||||
|
||||
WordFinder::~WordFinder()
|
||||
{
|
||||
// Request termination and wait for it to happen
|
||||
opMutex.lock();
|
||||
|
||||
op = QuitOp;
|
||||
|
||||
opCondition.wakeOne();
|
||||
|
||||
opMutex.unlock();
|
||||
|
||||
wait();
|
||||
clear();
|
||||
}
|
||||
|
||||
void WordFinder::prefixMatch( QString const & str,
|
||||
std::vector< sptr< Dictionary::Class > > const * dicts )
|
||||
std::vector< sptr< Dictionary::Class > > const & dicts )
|
||||
{
|
||||
opMutex.lock();
|
||||
cancel();
|
||||
|
||||
op = DoPrefixMatch;
|
||||
prefixMatchString = str;
|
||||
prefixMatchDicts = dicts;
|
||||
searchQueued = true;
|
||||
inputWord = str;
|
||||
inputDicts = &dicts;
|
||||
|
||||
opCondition.wakeOne();
|
||||
results.clear();
|
||||
searchResults.clear();
|
||||
|
||||
opMutex.unlock();
|
||||
}
|
||||
|
||||
void WordFinder::run()
|
||||
{
|
||||
opMutex.lock();
|
||||
|
||||
for( ; ; )
|
||||
if ( queuedRequests.empty() )
|
||||
{
|
||||
// Check the operation requested
|
||||
|
||||
if ( op == NoOp )
|
||||
{
|
||||
opCondition.wait( &opMutex );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( op == QuitOp )
|
||||
break;
|
||||
|
||||
// The only one op value left is DoPrefixMatch
|
||||
|
||||
Q_ASSERT( op == DoPrefixMatch );
|
||||
|
||||
QString prefixMatchReq = prefixMatchString;
|
||||
vector< sptr< Dictionary::Class > > const & activeDicts = *prefixMatchDicts;
|
||||
|
||||
op = NoOp;
|
||||
|
||||
opMutex.unlock();
|
||||
|
||||
map< wstring, wstring > exactResults, prefixResults;
|
||||
|
||||
{
|
||||
wstring word = prefixMatchReq.toStdWString();
|
||||
|
||||
DictLock _;
|
||||
|
||||
// Maps lowercased string to the original one. This catches all duplicates
|
||||
// without case sensitivity
|
||||
|
||||
bool cancel = false;
|
||||
|
||||
for( unsigned x = 0; x < activeDicts.size(); ++x )
|
||||
{
|
||||
vector< wstring > exactMatches, prefixMatches;
|
||||
|
||||
activeDicts[ x ]->findExact( word, exactMatches, prefixMatches, 40 );
|
||||
|
||||
for( unsigned y = 0; y < exactMatches.size(); ++y )
|
||||
{
|
||||
wstring lowerCased = Folding::applySimpleCaseOnly( exactMatches[ y ] );
|
||||
|
||||
pair< map< wstring, wstring >::iterator, bool > insertResult =
|
||||
exactResults.insert( pair< wstring, wstring >( lowerCased,
|
||||
exactMatches[ y ] ) );
|
||||
|
||||
if ( !insertResult.second )
|
||||
{
|
||||
// Wasn't inserted since there was already an item -- check the case
|
||||
if ( insertResult.first->second != exactMatches[ y ] )
|
||||
{
|
||||
// The case is different -- agree on a lowercase version
|
||||
insertResult.first->second = lowerCased;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for( unsigned y = 0; y < prefixMatches.size(); ++y )
|
||||
{
|
||||
wstring lowerCased = Folding::applySimpleCaseOnly( prefixMatches[ y ] );
|
||||
|
||||
pair< map< wstring, wstring >::iterator, bool > insertResult =
|
||||
prefixResults.insert( pair< wstring, wstring >( lowerCased,
|
||||
prefixMatches[ y ] ) );
|
||||
|
||||
if ( !insertResult.second )
|
||||
{
|
||||
// Wasn't inserted since there was already an item -- check the case
|
||||
if ( insertResult.first->second != prefixMatches[ y ] )
|
||||
{
|
||||
// The case is different -- agree on a lowercase version
|
||||
insertResult.first->second = lowerCased;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we've got an op request -- abort the query then
|
||||
|
||||
opMutex.lock();
|
||||
|
||||
if ( op != NoOp )
|
||||
{
|
||||
cancel = true;
|
||||
break;
|
||||
}
|
||||
|
||||
opMutex.unlock();
|
||||
}
|
||||
|
||||
if ( cancel )
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do any sort of collation here in the future. For now we just put the
|
||||
// strings sorted by the map.
|
||||
|
||||
WordFinderResults r( prefixMatchReq, &activeDicts );
|
||||
|
||||
r.results.reserve( exactResults.size() + prefixResults.size() );
|
||||
|
||||
for( map< wstring, wstring >::const_iterator i = exactResults.begin();
|
||||
i != exactResults.end(); ++i )
|
||||
r.results.push_back( QString::fromStdWString( i->second ) );
|
||||
|
||||
for( map< wstring, wstring >::const_iterator i = prefixResults.begin();
|
||||
i != prefixResults.end(); ++i )
|
||||
r.results.push_back( QString::fromStdWString( i->second ) );
|
||||
|
||||
emit prefixMatchComplete( r );
|
||||
|
||||
// Continue serving op requests
|
||||
|
||||
opMutex.lock();
|
||||
// No requests are queued, no need to wait for them to finish.
|
||||
startSearch();
|
||||
}
|
||||
|
||||
opMutex.unlock();
|
||||
// Else some requests are still queued, last one to finish would trigger
|
||||
// new search. This shouldn't take a lot of time, since they were all
|
||||
// cancelled, but still it could take some time.
|
||||
}
|
||||
|
||||
void WordFinder::startSearch()
|
||||
{
|
||||
if ( !searchQueued )
|
||||
return; // Search was probably cancelled
|
||||
|
||||
// Clear the requests just in case
|
||||
queuedRequests.clear();
|
||||
finishedRequests.clear();
|
||||
|
||||
searchErrorString.clear();
|
||||
|
||||
searchQueued = false;
|
||||
searchInProgress = true;
|
||||
|
||||
wstring word = inputWord.toStdWString();
|
||||
|
||||
for( size_t x = 0; x < inputDicts->size(); ++x )
|
||||
{
|
||||
sptr< Dictionary::WordSearchRequest > sr = (*inputDicts)[ x ]->prefixMatch( word, 40 );
|
||||
|
||||
connect( sr.get(), SIGNAL( finished() ),
|
||||
this, SLOT( requestFinished() ), Qt::QueuedConnection );
|
||||
|
||||
queuedRequests.push_back( sr );
|
||||
}
|
||||
|
||||
// Handle any requests finished already
|
||||
|
||||
requestFinished();
|
||||
}
|
||||
|
||||
void WordFinder::cancel()
|
||||
{
|
||||
searchQueued = false;
|
||||
searchInProgress = false;
|
||||
|
||||
cancelSearches();
|
||||
}
|
||||
|
||||
void WordFinder::clear()
|
||||
{
|
||||
cancel();
|
||||
queuedRequests.clear();
|
||||
finishedRequests.clear();
|
||||
}
|
||||
|
||||
void WordFinder::requestFinished()
|
||||
{
|
||||
bool newResults = false;
|
||||
|
||||
// See how many new requests have finished, and if we have any new results
|
||||
for( list< sptr< Dictionary::WordSearchRequest > >::iterator i =
|
||||
queuedRequests.begin(); i != queuedRequests.end(); )
|
||||
{
|
||||
if ( (*i)->isFinished() )
|
||||
{
|
||||
if ( searchInProgress && !(*i)->getErrorString().isEmpty() )
|
||||
searchErrorString = tr( "Failed to query some dictionaries." );
|
||||
|
||||
if ( (*i)->matchesCount() )
|
||||
{
|
||||
newResults = true;
|
||||
|
||||
// This list is handled by updateResults()
|
||||
finishedRequests.splice( finishedRequests.end(), queuedRequests, i++ );
|
||||
}
|
||||
else // We won't do anything with it anymore, so we erase it
|
||||
queuedRequests.erase( i++ );
|
||||
}
|
||||
else
|
||||
++i;
|
||||
}
|
||||
|
||||
if ( !searchInProgress )
|
||||
{
|
||||
// There is no search in progress, so we just wait until there's
|
||||
// no requests left
|
||||
|
||||
if ( queuedRequests.empty() )
|
||||
{
|
||||
// We got rid of all queries, queued search can now start
|
||||
finishedRequests.clear();
|
||||
|
||||
if ( searchQueued )
|
||||
startSearch();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( newResults && queuedRequests.size() && !updateResultsTimer.isActive() )
|
||||
{
|
||||
// If we have got some new results, but not all of them, we would start a
|
||||
// timer to update a user some time in the future
|
||||
updateResultsTimer.start();
|
||||
}
|
||||
|
||||
if ( queuedRequests.empty() )
|
||||
{
|
||||
// Search is finished.
|
||||
updateResults();
|
||||
}
|
||||
}
|
||||
|
||||
void WordFinder::updateResults()
|
||||
{
|
||||
if ( !searchInProgress )
|
||||
return; // Old queued signal
|
||||
|
||||
if ( updateResultsTimer.isActive() )
|
||||
updateResultsTimer.stop(); // Can happen when we were done before it'd expire
|
||||
|
||||
for( list< sptr< Dictionary::WordSearchRequest > >::iterator i =
|
||||
finishedRequests.begin(); i != finishedRequests.end(); )
|
||||
{
|
||||
for( size_t count = (*i)->matchesCount(), x = 0; x < count; ++x )
|
||||
{
|
||||
wstring match = (**i)[ x ].word;
|
||||
wstring lowerCased = Folding::applySimpleCaseOnly( match );
|
||||
|
||||
pair< map< wstring, wstring >::iterator, bool > insertResult =
|
||||
results.insert( pair< wstring, wstring >( lowerCased, match ) );
|
||||
|
||||
if ( !insertResult.second )
|
||||
{
|
||||
// Wasn't inserted since there was already an item -- check the case
|
||||
if ( insertResult.first->second != match )
|
||||
{
|
||||
// The case is different -- agree on a lowercase version
|
||||
insertResult.first->second = lowerCased;
|
||||
}
|
||||
}
|
||||
}
|
||||
finishedRequests.erase( i++ );
|
||||
}
|
||||
|
||||
// Do any sort of collation here in the future. For now we just put the
|
||||
// strings sorted by the map.
|
||||
|
||||
searchResults.clear();
|
||||
searchResults.reserve( results.size() );
|
||||
|
||||
for( map< wstring, wstring >::const_iterator i = results.begin();
|
||||
i != results.end(); ++i )
|
||||
{
|
||||
if ( searchResults.size() < 500 )
|
||||
searchResults.push_back( QString::fromStdWString( i->second ) );
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
if ( queuedRequests.size() )
|
||||
{
|
||||
// There are still some unhandled results.
|
||||
emit updated();
|
||||
}
|
||||
else
|
||||
{
|
||||
// That were all of them.
|
||||
searchInProgress = false;
|
||||
emit finished();
|
||||
}
|
||||
}
|
||||
|
||||
void WordFinder::cancelSearches()
|
||||
{
|
||||
for( list< sptr< Dictionary::WordSearchRequest > >::iterator i =
|
||||
queuedRequests.begin(); i != queuedRequests.end(); ++i )
|
||||
(*i)->cancel();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,52 +4,44 @@
|
|||
#ifndef __WORDFINDER_HH_INCLUDED__
|
||||
#define __WORDFINDER_HH_INCLUDED__
|
||||
|
||||
#include <QThread>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QRunnable>
|
||||
#include "dictionary.hh"
|
||||
|
||||
|
||||
/// This struct represents results of a WordFinder match operation.
|
||||
/// We need to separate this since it's needed to register is as a metatype
|
||||
/// for the signal-slot connections to be able to pass it in queued mode.
|
||||
struct WordFinderResults
|
||||
{
|
||||
/// The initial request parameters. They are passed back so that it'd be
|
||||
/// possible to tell the results apart from any previously cancelled
|
||||
/// operations.
|
||||
QString requestStr;
|
||||
std::vector< sptr< Dictionary::Class > > const * requestDicts;
|
||||
|
||||
/// The results themselves
|
||||
std::vector< QString > results;
|
||||
|
||||
WordFinderResults()
|
||||
{}
|
||||
|
||||
WordFinderResults( QString const & requestStr_,
|
||||
std::vector< sptr< Dictionary::Class > > const * requestDicts_ ):
|
||||
requestStr( requestStr_ ), requestDicts( requestDicts_ )
|
||||
{}
|
||||
};
|
||||
|
||||
/// This component takes care of finding words in dictionaries asyncronously,
|
||||
/// in another thread. This means the GUI doesn't get blocked during the
|
||||
/// sometimes lenghtly process of finding words.
|
||||
class WordFinder: QThread
|
||||
/// This component takes care of finding words. The search is asyncronous.
|
||||
/// This means the GUI doesn't get blocked during the sometimes lenghtly
|
||||
/// process of finding words.
|
||||
class WordFinder: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
std::vector< QString > searchResults;
|
||||
QString searchErrorString;
|
||||
std::list< sptr< Dictionary::WordSearchRequest > > queuedRequests,
|
||||
finishedRequests;
|
||||
bool searchInProgress;
|
||||
|
||||
QTimer updateResultsTimer;
|
||||
|
||||
// Saved search params
|
||||
bool searchQueued;
|
||||
QString inputWord;
|
||||
std::vector< sptr< Dictionary::Class > > const * inputDicts;
|
||||
|
||||
// Maps lowercased string to the original one. This catches all duplicates
|
||||
// without case sensitivity
|
||||
std::map< std::wstring, std::wstring > results;
|
||||
|
||||
public:
|
||||
|
||||
WordFinder( QObject * parent );
|
||||
~WordFinder();
|
||||
|
||||
/// Allows accessing the otherwise inaccessible QObject indirect base,
|
||||
/// which is QThread's base. This is needed to connect signals of the object.
|
||||
QObject * qobject()
|
||||
{ return this; }
|
||||
|
||||
/// Do the standard prefix-match search in the given list of dictionaries.
|
||||
/// Some dictionaries might only support exact matches -- for them, only
|
||||
/// the exact matches would be found. All search results are put into a single
|
||||
|
@ -57,35 +49,53 @@ public:
|
|||
/// matches from different dictionaries are merged together.
|
||||
/// If there already was a prefixMatch operation underway, it gets cancelled
|
||||
/// and the new one replaces it.
|
||||
/// The dictionaries and the containing vector get locked during the search
|
||||
/// using the DictLock object. Be sure you do that too in case you'd want
|
||||
/// to work with them during the search underway.
|
||||
void prefixMatch( QString const &,
|
||||
std::vector< sptr< Dictionary::Class > > const * );
|
||||
std::vector< sptr< Dictionary::Class > > const & );
|
||||
|
||||
/// Returns the vector containing search results from the last prefixMatch()
|
||||
/// operation. If it didn't finish yet, the result is not final and may
|
||||
/// be changing over time.
|
||||
std::vector< QString > const & getPrefixMatchResults() const
|
||||
{ return searchResults; }
|
||||
|
||||
/// Returns a human-readable error string for the last finished request. Empty
|
||||
/// string means it finished without any error.
|
||||
QString const & getErrorString()
|
||||
{ return searchErrorString; }
|
||||
|
||||
/// Cancels any pending search operation, if any.
|
||||
void cancel();
|
||||
|
||||
/// Cancels any pending search operation, if any, and makes sure no pending
|
||||
/// requests exist, and hence no dictionaries are used anymore. Unlike
|
||||
/// cancel(), this may take some time to finish.
|
||||
void clear();
|
||||
|
||||
signals:
|
||||
|
||||
/// This singal gets emitted from another thread and indicates that the
|
||||
/// previously requested prefixMatch() operation was finished. See the
|
||||
/// WordFinderResults structure description for further details.
|
||||
void prefixMatchComplete( WordFinderResults );
|
||||
/// Indicates that the search has got some more results, and continues
|
||||
/// searching.
|
||||
void updated();
|
||||
|
||||
/// Idicates that the search has finished.
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
|
||||
/// Called each time one of the requests gets finished
|
||||
void requestFinished();
|
||||
|
||||
/// Called by updateResultsTimer to update searchResults and signal updated()
|
||||
void updateResults();
|
||||
|
||||
private:
|
||||
|
||||
QMutex opMutex;
|
||||
QWaitCondition opCondition;
|
||||
// Starts the previously queued search.
|
||||
void startSearch();
|
||||
|
||||
enum Op
|
||||
{
|
||||
NoOp,
|
||||
QuitOp,
|
||||
DoPrefixMatch
|
||||
} op;
|
||||
|
||||
QString prefixMatchString;
|
||||
std::vector< sptr< Dictionary::Class > > const * prefixMatchDicts;
|
||||
|
||||
virtual void run();
|
||||
// Cancels all searches. Useful to do before destroying them all, since they
|
||||
// would cancel in parallel.
|
||||
void cancelSearches();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue