Add new program type - 'Prefix Match', which allows listing word matches as you type.

This commit is contained in:
Konstantin Isakov 2011-06-04 14:35:09 -07:00
parent 890021379c
commit 40fa922de6
5 changed files with 206 additions and 75 deletions

View file

@ -721,15 +721,22 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref,
if ( i->id == id ) if ( i->id == id )
{ {
// Found the corresponding program. // Found the corresponding program.
Programs::ArticleRequest * req = new Programs::ArticleRequest( Programs::RunInstance * req = new Programs::RunInstance;
url.path().mid( 1 ), *i );
connect( req, SIGNAL( finished() ), req, SLOT( deleteLater() ) ); connect( req, SIGNAL(finished(QByteArray,QString)),
req, SLOT( deleteLater() ) );
// Delete the request if it has finished already QString error;
if ( req->isFinished() )
// Delete the request if it fails to start
if ( !req->start( *i, url.path().mid( 1 ), error ) )
{
delete req; delete req;
QMessageBox::critical( this, tr( "GoldenDict" ),
error );
}
return; return;
} }
} }

View file

@ -312,6 +312,7 @@ struct Program
Audio, Audio,
PlainText, PlainText,
Html, Html,
PrefixMatch,
MaxTypeValue MaxTypeValue
} type; } type;
QString id, name, commandLine; QString id, name, commandLine;

View file

@ -40,8 +40,23 @@ public:
virtual QIcon getIcon() throw(); virtual QIcon getIcon() throw();
virtual sptr< WordSearchRequest > prefixMatch( wstring const & /*word*/, virtual sptr< WordSearchRequest > prefixMatch( wstring const & word,
unsigned long /*maxResults*/ ) throw( std::exception ) unsigned long maxResults )
throw( std::exception );
virtual sptr< DataRequest > getArticle( wstring const &,
vector< wstring > const & alts,
wstring const & )
throw( std::exception );
};
sptr< WordSearchRequest > ProgramsDictionary::prefixMatch( wstring const & word,
unsigned long /*maxResults*/ )
throw( std::exception )
{
if ( prg.type == Config::Program::PrefixMatch )
return new ProgramWordSearchRequest( gd::toQString( word ), prg );
else
{ {
sptr< WordSearchRequestInstant > sr = new WordSearchRequestInstant; sptr< WordSearchRequestInstant > sr = new WordSearchRequestInstant;
@ -49,50 +64,52 @@ public:
return sr; return sr;
} }
}
virtual sptr< DataRequest > getArticle( wstring const &, vector< wstring > const & alts, sptr< Dictionary::DataRequest > ProgramsDictionary::getArticle(
wstring const & ) wstring const & word, vector< wstring > const &, wstring const & )
throw( std::exception );
};
sptr< DataRequest > ProgramsDictionary::getArticle( wstring const & word,
vector< wstring > const &,
wstring const & )
throw( std::exception ) throw( std::exception )
{ {
if ( prg.type == Config::Program::Audio ) switch( prg.type )
{ {
// Audio results are instantaneous case Config::Program::Audio:
string result; {
// Audio results are instantaneous
string result;
string wordUtf8( Utf8::encode( word ) ); string wordUtf8( Utf8::encode( word ) );
result += "<table class=\"programs_play\"><tr>"; result += "<table class=\"programs_play\"><tr>";
QUrl url; QUrl url;
url.setScheme( "gdprg" ); url.setScheme( "gdprg" );
url.setHost( QString::fromUtf8( getId().c_str() ) ); url.setHost( QString::fromUtf8( getId().c_str() ) );
url.setPath( QString::fromUtf8( wordUtf8.c_str() ) ); url.setPath( QString::fromUtf8( wordUtf8.c_str() ) );
string ref = string( "\"" ) + url.toEncoded().data() + "\""; string ref = string( "\"" ) + url.toEncoded().data() + "\"";
result += addAudioLink( ref, getId() ); result += addAudioLink( ref, getId() );
result += "<td><a href=" + ref + "><img src=\"qrcx://localhost/icons/playsound.png\" border=\"0\" alt=\"Play\"/></a></td>"; result += "<td><a href=" + ref + "><img src=\"qrcx://localhost/icons/playsound.png\" border=\"0\" alt=\"Play\"/></a></td>";
result += "<td><a href=" + ref + ">" + result += "<td><a href=" + ref + ">" +
Html::escape( wordUtf8 ) + "</a></td>"; Html::escape( wordUtf8 ) + "</a></td>";
result += "</tr></table>"; result += "</tr></table>";
sptr< Dictionary::DataRequestInstant > ret = sptr< DataRequestInstant > ret = new DataRequestInstant( true );
new Dictionary::DataRequestInstant( true );
ret->getData().resize( result.size() ); ret->getData().resize( result.size() );
memcpy( &(ret->getData().front()), result.data(), result.size() ); memcpy( &(ret->getData().front()), result.data(), result.size() );
return ret; return ret;
}
case Config::Program::Html:
case Config::Program::PlainText:
return new ProgramDataRequest( gd::toQString( word ), prg );
default:
return new DataRequestInstant( false );
} }
else
return new ArticleRequest( gd::toQString( word ), prg );
} }
QIcon ProgramsDictionary::getIcon() throw() QIcon ProgramsDictionary::getIcon() throw()
@ -102,9 +119,17 @@ QIcon ProgramsDictionary::getIcon() throw()
} }
ArticleRequest::ArticleRequest( QString const & word, RunInstance::RunInstance(): process( this )
Config::Program const & prg_ ): {
prg( prg_ ), process( this ) connect( this, SIGNAL(processFinished()), this,
SLOT(handleProcessFinished()), Qt::QueuedConnection );
connect( &process, SIGNAL(finished(int)), this, SIGNAL(processFinished()));
connect( &process, SIGNAL(error(QProcess::ProcessError)), this,
SIGNAL(processFinished()) );
}
bool RunInstance::start( Config::Program const & prg, QString const & word,
QString & error )
{ {
QStringList args = parseCommandLine( prg.commandLine ); QStringList args = parseCommandLine( prg.commandLine );
@ -116,34 +141,65 @@ ArticleRequest::ArticleRequest( QString const & word,
for( int x = 0; x < args.size(); ++x ) for( int x = 0; x < args.size(); ++x )
args[ x ].replace( "%GDWORD%", word ); args[ x ].replace( "%GDWORD%", word );
connect( this, SIGNAL(processFinished()), this,
SLOT(handleProcessFinished()), Qt::QueuedConnection );
connect( &process, SIGNAL(finished(int)), this, SIGNAL(processFinished()));
connect( &process, SIGNAL(error(QProcess::ProcessError)), this,
SIGNAL(processFinished()) );
process.start( programName, args ); process.start( programName, args );
process.write( word.toLocal8Bit() ); process.write( word.toLocal8Bit() );
process.closeWriteChannel(); process.closeWriteChannel();
return true;
} }
else else
{ {
setErrorString( tr( "No program name was given." ) ); error = tr( "No program name was given." );
return false;
}
}
void RunInstance::handleProcessFinished()
{
// It seems that sometimes the process isn't finished yet despite being
// signalled as such. So we wait for it here, which should hopefully be
// nearly instant.
process.waitForFinished();
QByteArray output = process.readAllStandardOutput();
QString error;
if ( process.exitStatus() != QProcess::NormalExit )
error = tr( "The program has crashed." );
else
if ( int code = process.exitCode() )
error = tr( "The program has returned exit code %1." ).arg( code );
if ( !error.isEmpty() )
{
QByteArray err = process.readAllStandardError();
if ( !err.isEmpty() )
error += "\n\n" + QString::fromLocal8Bit( err );
}
emit finished( output, error );
}
ProgramDataRequest::ProgramDataRequest( QString const & word,
Config::Program const & prg_ ):
prg( prg_ )
{
connect( &instance, SIGNAL(finished(QByteArray,QString)),
this, SLOT(instanceFinished(QByteArray,QString)) );
QString error;
if ( !instance.start( prg, word, error ) )
{
setErrorString( error );
finish(); finish();
} }
} }
void ArticleRequest::handleProcessFinished() void ProgramDataRequest::instanceFinished( QByteArray output, QString error )
{ {
if ( !isFinished() ) if ( !isFinished() )
{ {
// It seems that sometimes the process isn't finished yet despite being
// signalled as such. So we wait for it here, which should hopefully be
// nearly instant.
process.waitForFinished();
QByteArray output = process.readAllStandardOutput();
if ( !output.isEmpty() ) if ( !output.isEmpty() )
{ {
string result = "<div class='programs_"; string result = "<div class='programs_";
@ -167,32 +223,56 @@ void ArticleRequest::handleProcessFinished()
hasAnyData = true; hasAnyData = true;
} }
QString error;
if ( process.exitStatus() != QProcess::NormalExit )
error = tr( "The program has crashed." );
else
if ( int code = process.exitCode() )
error = tr( "The program has returned exit code %1." ).arg( code );
if ( !error.isEmpty() ) if ( !error.isEmpty() )
{
QByteArray err = process.readAllStandardError();
if ( !err.isEmpty() )
error += "\n\n" + QString::fromLocal8Bit( err );
setErrorString( error ); setErrorString( error );
}
finish(); finish();
} }
} }
void ArticleRequest::cancel() void ProgramDataRequest::cancel()
{ {
finish(); finish();
} }
ProgramWordSearchRequest::ProgramWordSearchRequest( QString const & word,
Config::Program const & prg_ ):
prg( prg_ )
{
connect( &instance, SIGNAL(finished(QByteArray,QString)),
this, SLOT(instanceFinished(QByteArray,QString)) );
QString error;
if ( !instance.start( prg, word, error ) )
{
setErrorString( error );
finish();
}
}
void ProgramWordSearchRequest::instanceFinished( QByteArray output, QString error )
{
if ( !isFinished() )
{
// Handle any Windows artifacts
output.replace( "\r\n", "\n" );
QStringList result =
QString::fromUtf8( output ).split( "\n", QString::SkipEmptyParts );
for( int x = 0; x < result.size(); ++x )
matches.push_back( Dictionary::WordMatch( gd::toWString( result[ x ] ) ) );
if ( !error.isEmpty() )
setErrorString( error );
finish();
}
}
void ProgramWordSearchRequest::cancel()
{
finish();
}
vector< sptr< Dictionary::Class > > makeDictionaries( vector< sptr< Dictionary::Class > > makeDictionaries(
Config::Programs const & programs ) Config::Programs const & programs )

View file

@ -19,18 +19,25 @@ using gd::wstring;
vector< sptr< Dictionary::Class > > makeDictionaries( Config::Programs const & ) vector< sptr< Dictionary::Class > > makeDictionaries( Config::Programs const & )
throw( std::exception ); throw( std::exception );
class ArticleRequest: public Dictionary::DataRequest class RunInstance: public QObject
{ {
Q_OBJECT Q_OBJECT
Config::Program prg;
QProcess process; QProcess process;
public: public:
ArticleRequest( QString const & word, Config::Program const & ); RunInstance();
virtual void cancel(); // Starts the process. Should only be used once. The finished() signal will
// be emitted once it finishes. If there's an error, returns false and the
// description is saved to 'error'.
bool start( Config::Program const &, QString const & word, QString & error );
signals:
// Connect to this signal to get run results
void finished( QByteArray stdout, QString error );
// Used internally only
signals: signals:
void processFinished(); void processFinished();
private slots: private slots:
@ -38,6 +45,40 @@ private slots:
void handleProcessFinished(); void handleProcessFinished();
}; };
class ProgramDataRequest: public Dictionary::DataRequest
{
Q_OBJECT
Config::Program prg;
RunInstance instance;
public:
ProgramDataRequest( QString const & word, Config::Program const & );
virtual void cancel();
private slots:
void instanceFinished( QByteArray output, QString error );
};
class ProgramWordSearchRequest: public Dictionary::WordSearchRequest
{
Q_OBJECT
Config::Program prg;
RunInstance instance;
public:
ProgramWordSearchRequest( QString const & word, Config::Program const & );
virtual void cancel();
private slots:
void instanceFinished( QByteArray output, QString error );
};
} }
#endif #endif

View file

@ -52,7 +52,7 @@ Sources::Sources( QWidget * parent, Config::Paths const & paths,
// Make sure this thing will be large enough // Make sure this thing will be large enough
ui.programs->setColumnWidth( 1, ui.programs->setColumnWidth( 1,
QFontMetrics( QFont() ).width( QFontMetrics( QFont() ).width(
ProgramTypeEditor::getNameForType( Config::Program::PlainText ) ) + 16 ); ProgramTypeEditor::getNameForType( Config::Program::PrefixMatch ) ) + 16 );
ui.programs->resizeColumnToContents( 2 ); ui.programs->resizeColumnToContents( 2 );
ui.programs->resizeColumnToContents( 3 ); ui.programs->resizeColumnToContents( 3 );
ui.programs->setItemDelegate( itemDelegate ); ui.programs->setItemDelegate( itemDelegate );
@ -773,6 +773,8 @@ QString ProgramTypeEditor::getNameForType( int v )
return tr( "Plain Text" ); return tr( "Plain Text" );
case Config::Program::Html: case Config::Program::Html:
return tr( "Html" ); return tr( "Html" );
case Config::Program::PrefixMatch:
return tr( "Prefix Match" );
default: default:
return tr( "Unknown" ); return tr( "Unknown" );
} }