Implement asyncronous word finding.

This commit is contained in:
Konstantin Isakov 2009-01-29 19:16:25 +00:00
parent 17b90a3cca
commit faeacaff3e
9 changed files with 417 additions and 107 deletions

View file

@ -5,6 +5,7 @@
#include "config.hh"
#include "htmlescape.hh"
#include "utf8.hh"
#include "dictlock.hh"
#include <QFile>
#include <set>
@ -68,6 +69,8 @@ string ArticleMaker::makeDefinitionFor( QString const & inWord,
result += "</head><body>";
DictLock _;
// Accumulate main forms
vector< wstring > alts;

View file

@ -2,6 +2,7 @@
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "article_netmgr.hh"
#include "dictlock.hh"
using std::string;
@ -68,6 +69,8 @@ bool ArticleNetworkAccessManager::getResource( QUrl const & url,
bool search = ( id == "search" );
DictLock _;
for( unsigned x = 0; x < dictionaries.size(); ++x )
{
if ( search || dictionaries[ x ]->getId() == id )

25
src/dictlock.cc Normal file
View file

@ -0,0 +1,25 @@
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.sf.net>
* 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();
}

24
src/dictlock.hh Normal file
View file

@ -0,0 +1,24 @@
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.sf.net>
* 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

@ -53,7 +53,9 @@ HEADERS += folding.hh \
article_maker.hh \
scanpopup.hh \
articleview.hh \
externalviewer.hh
externalviewer.hh \
dictlock.hh \
wordfinder.hh
FORMS += groups.ui dictgroupwidget.ui mainwindow.ui sources.ui initializing.ui\
@ -65,6 +67,6 @@ 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
articleview.cc externalviewer.cc dictlock.cc wordfinder.cc
RESOURCES += resources.qrc flags.qrc

View file

@ -8,6 +8,7 @@
#include "stardict.hh"
#include "lsa.hh"
#include "dsl.hh"
#include "dictlock.hh"
#include <QDir>
#include <QMessageBox>
#include <QIcon>
@ -25,8 +26,8 @@ MainWindow::MainWindow():
cfg( Config::load() ),
articleMaker( dictionaries, groupInstances ),
articleNetMgr( this, dictionaries, articleMaker ),
wordFinder( this ),
scanPopup( 0, articleNetMgr ),
startLookupTimeout( this ),
initializing( 0 )
{
ui.setupUi( this );
@ -58,8 +59,6 @@ MainWindow::MainWindow():
connect( ui.tabWidget, SIGNAL( tabCloseRequested( int ) ),
this, SLOT( tabCloseRequested( int ) ) );
startLookupTimeout.setSingleShot( true );
ui.tabWidget->setTabsClosable( true );
connect( ui.sources, SIGNAL( activated() ),
@ -71,15 +70,17 @@ MainWindow::MainWindow():
connect( ui.translateLine, SIGNAL( textChanged( QString const & ) ),
this, SLOT( translateInputChanged( QString const & ) ) );
connect( &startLookupTimeout, SIGNAL( timeout() ),
this, SLOT( startLookup() ) );
connect( ui.wordList, SIGNAL( itemActivated( QListWidgetItem * ) ),
this, SLOT( wordListItemActivated( QListWidgetItem * ) ) );
connect( wordFinder.qobject(), SIGNAL( prefixMatchComplete( WordFinderResults ) ),
this, SLOT( prefixMatchComplete( WordFinderResults ) ) );
makeDictionaries();
addNewTab();
ui.translateLine->setFocus();
}
LoadDictionaries::LoadDictionaries( vector< string > const & allFiles_ ):
@ -133,7 +134,10 @@ void LoadDictionaries::indexingDictionary( string const & dictionaryName ) throw
void MainWindow::makeDictionaries()
{
dictionaries.clear();
{
DictLock _;
dictionaries.clear();
}
::Initializing init( this );
@ -175,7 +179,11 @@ void MainWindow::makeDictionaries()
loadDicts.wait();
dictionaries = loadDicts.getDictionaries();
{
DictLock _;
dictionaries = loadDicts.getDictionaries();
}
initializing = 0;
@ -235,6 +243,8 @@ void MainWindow::updateGroupList()
groupInstances.clear();
DictLock _;
for( unsigned x = 0; x < cfg.groups.size(); ++x )
{
groupInstances.push_back( Instances::Group( cfg.groups[ x ], dictionaries ) );
@ -347,119 +357,83 @@ void MainWindow::editSources()
void MainWindow::editGroups()
{
Groups groups( this, dictionaries, cfg.groups );
groups.show();
if ( groups.exec() == QDialog::Accepted )
{
cfg.groups = groups.getGroups();
// 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();
Config::save( cfg );
if ( groups.exec() == QDialog::Accepted )
{
cfg.groups = groups.getGroups();
updateGroupList();
Config::save( cfg );
}
else
return;
}
updateGroupList();
}
void MainWindow::translateInputChanged( QString const & )
void MainWindow::translateInputChanged( QString const & newValue )
{
startLookup();
//startLookupTimeout.start( 0 );
}
QString req = newValue.trimmed();
void MainWindow::startLookup()
{
QString word = ui.translateLine->text();
if ( !req.size() )
{
// An empty request always results in an empty result
prefixMatchComplete( WordFinderResults( req, &getActiveDicts() ) );
ui.wordList->clear();
wstring wordW = word.trimmed().toStdWString();
if ( wordW.empty() )
return;
// Maps lowercased string to the original one. This catches all duplicates
// without case sensitivity
map< wstring, wstring > exactResults, prefixResults;
// Where to look?
vector< sptr< Dictionary::Class > > const & activeDicts = getActiveDicts();
for( unsigned x = 0; x < activeDicts.size(); ++x )
{
vector< wstring > exactMatches, prefixMatches;
activeDicts[ x ]->findExact( wordW, exactMatches, prefixMatches, 200 );
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;
}
}
}
}
// Do any sort of collation here in the future. For now we just put the
// strings sorted by the map.
ui.wordList->setCursor( Qt::WaitCursor );
for( map< wstring, wstring >::const_iterator i = exactResults.begin();
i != exactResults.end(); ++i )
wordFinder.prefixMatch( req, &getActiveDicts() );
}
void MainWindow::prefixMatchComplete( WordFinderResults r )
{
if ( r.requestStr != ui.translateLine->text().trimmed() ||
r.requestDicts != &getActiveDicts() )
{
ui.wordList->addItem( QString::fromStdWString( i->second ) );
QListWidgetItem * item = ui.wordList->item( ui.wordList->count() - 1 );
QFont font = item->font();
//font.setWeight( QFont::Bold );
item->setFont( font );
// Those results are already irrelevant, ignore the result
return;
}
if ( prefixResults.size() )
ui.wordList->setUpdatesEnabled( false );
for( unsigned x = 0; x < r.results.size(); ++x )
{
for( map< wstring, wstring >::const_iterator i = prefixResults.begin();
i != prefixResults.end(); ++i )
{
ui.wordList->addItem( QString::fromStdWString( i->second ) );
QListWidgetItem * i = ui.wordList->item( x );
QListWidgetItem * item = ui.wordList->item( ui.wordList->count() - 1 );
QFont font = item->font();
//font.setStyle( QFont::StyleOblique );
item->setFont( font );
}
if ( !i )
ui.wordList->addItem( r.results[ x ] );
else
if ( i->text() != r.results[ x ] )
i->setText( r.results[ x ] );
}
while ( ui.wordList->count() > (int) r.results.size() )
{
// Chop off any extra items that were there
QListWidgetItem * i = ui.wordList->takeItem( ui.wordList->count() - 1 );
if ( i )
delete i;
else
break;
}
if ( ui.wordList->count() )
ui.wordList->scrollToItem( ui.wordList->item( 0 ), QAbstractItemView::PositionAtTop );
ui.wordList->setUpdatesEnabled( true );
ui.wordList->unsetCursor();
}
void MainWindow::wordListItemActivated( QListWidgetItem * item )

View file

@ -17,6 +17,7 @@
#include "article_maker.hh"
#include "scanpopup.hh"
#include "articleview.hh"
#include "wordfinder.hh"
using std::string;
using std::vector;
@ -67,9 +68,9 @@ private:
ArticleMaker articleMaker;
ArticleNetworkAccessManager articleNetMgr;
ScanPopup scanPopup;
WordFinder wordFinder;
QTimer startLookupTimeout;
ScanPopup scanPopup;
::Initializing * initializing;
@ -102,7 +103,7 @@ private slots:
void indexingDictionary( QString dictionaryName );
void translateInputChanged( QString const & );
void startLookup();
void prefixMatchComplete( WordFinderResults );
void wordListItemActivated( QListWidgetItem * );
void showTranslationFor( QString const & );

186
src/wordfinder.cc Normal file
View file

@ -0,0 +1,186 @@
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.sf.net>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "wordfinder.hh"
#include "dictlock.hh"
#include "folding.hh"
#include <QMetaType>
#include <map>
using std::vector;
using std::wstring;
using std::map;
using std::pair;
namespace
{
struct MetaTypeRegister
{
MetaTypeRegister()
{
qRegisterMetaType< WordFinderResults >( "WordFinderResults" );
}
};
}
WordFinder::WordFinder( QObject * parent ): QThread( parent ), op( NoOp )
{
static MetaTypeRegister _;
start();
}
WordFinder::~WordFinder()
{
// Request termination and wait for it to happen
opMutex.lock();
op = QuitOp;
opCondition.wakeOne();
opMutex.unlock();
wait();
}
void WordFinder::prefixMatch( QString const & str,
std::vector< sptr< Dictionary::Class > > const * dicts )
{
opMutex.lock();
op = DoPrefixMatch;
prefixMatchString = str;
prefixMatchDicts = dicts;
opCondition.wakeOne();
opMutex.unlock();
}
void WordFinder::run()
{
opMutex.lock();
for( ; ; )
{
// 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();
}
opMutex.unlock();
}

92
src/wordfinder.hh Normal file
View file

@ -0,0 +1,92 @@
/* This file is (c) 2008-2009 Konstantin Isakov <ikm@users.sf.net>
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#ifndef __WORDFINDER_HH_INCLUDED__
#define __WORDFINDER_HH_INCLUDED__
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#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
{
Q_OBJECT
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
/// list containing the exact matches first, then the prefix ones. Duplicate
/// 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 * );
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 );
private:
QMutex opMutex;
QWaitCondition opCondition;
enum Op
{
NoOp,
QuitOp,
DoPrefixMatch
} op;
QString prefixMatchString;
std::vector< sptr< Dictionary::Class > > const * prefixMatchDicts;
virtual void run();
};
#endif