* 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:
Konstantin Isakov 2009-03-26 19:00:08 +00:00
parent b2b9798663
commit 7859daaff6
41 changed files with 2462 additions and 841 deletions

View file

@ -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();
}

View file

@ -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

View file

@ -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();
}

View file

@ -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();
};

View file

@ -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 );

View file

@ -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,

View file

@ -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 &#x1234;
@ -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

View file

@ -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 );
}

View file

@ -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 );

View file

@ -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.

View file

@ -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 );

View file

@ -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

View file

@ -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;

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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

View file

@ -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 );

View file

@ -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 );
};
}

View file

@ -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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

View file

@ -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

View file

@ -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 );
}

View file

@ -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();
}

View file

@ -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 )

View file

@ -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
View 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
View 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
View 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
View 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

View file

@ -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>

View file

@ -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() );
}
}
}

View file

@ -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();

View file

@ -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">

View file

@ -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;
}

View file

@ -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

View file

@ -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>&amp;Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove">
<property name="text">
<string>&amp;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>&amp;Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeMediaWiki">
<property name="text">
<string>&amp;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>&amp;Add...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove" >
<property name="text" >
<string>&amp;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>

View file

@ -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

View file

@ -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 );
}

View file

@ -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();
}

View file

@ -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