From 9960efc00d35abbce9ec7e8df087ada73d1aaff0 Mon Sep 17 00:00:00 2001 From: Konstantin Isakov Date: Sat, 28 May 2011 22:08:37 -0700 Subject: [PATCH] Add support for launching arbitrary executables (tts, manpages etc). --- article-style.css | 18 ++++ articleview.cc | 45 +++++--- config.cc | 69 ++++++++++++ config.hh | 34 ++++++ dictionary.cc | 11 ++ dictionary.hh | 4 + editdictionaries.cc | 6 +- editdictionaries.hh | 2 +- externalviewer.cc | 42 ++------ externalviewer.hh | 30 +----- goldendict.pro | 6 +- icons/programs.png | Bin 0 -> 2405 bytes loaddictionaries.cc | 9 ++ resources.qrc | 1 + sources.cc | 249 +++++++++++++++++++++++++++++++++++++++++--- sources.hh | 63 ++++++++++- sources.ui | 61 ++++++++++- 17 files changed, 554 insertions(+), 96 deletions(-) create mode 100644 icons/programs.png diff --git a/article-style.css b/article-style.css index 7ac4aed1..39804524 100644 --- a/article-style.css +++ b/article-style.css @@ -378,6 +378,24 @@ div.sdct_x color: red; } +/************* Programs **************/ + +/* A table which contains a play icon and a word's link */ +.programs_play +{ + margin-top: 8px; + margin-left: 8px; +} + +.programs_play a +{ + text-decoration: none; +} + +.programs_plaintext, .programs_html +{ + margin-top: 15px; +} /************* MediaWiki articles ***************** The following consist of excerpts from different .css files edited diff --git a/articleview.cc b/articleview.cc index de28c6fa..95399a44 100644 --- a/articleview.cc +++ b/articleview.cc @@ -14,6 +14,7 @@ #include "folding.hh" #include "wstring_qt.hh" #include "webmultimediadownload.hh" +#include "programs.hh" #ifdef Q_OS_WIN32 #include @@ -709,6 +710,35 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, resourceDownloadFinished(); // Check any requests finished already } else + if ( url.scheme() == "gdprg" ) + { + // Program. Run it. + QString id( url.host() ); + + for( Config::Programs::const_iterator i = cfg.programs.begin(); + i != cfg.programs.end(); ++i ) + { + if ( i->id == id ) + { + // Found the corresponding program. + Programs::ArticleRequest * req = new Programs::ArticleRequest( + url.path().mid( 1 ), *i ); + + connect( req, SIGNAL( finished() ), req, SLOT( deleteLater() ) ); + + // Delete the request if it has finished already + if ( req->isFinished() ) + delete req; + + return; + } + } + + // Still here? No such program exists. + QMessageBox::critical( this, tr( "GoldenDict" ), + tr( "The referenced audio program doesn't exist." ) ); + } + else if ( isExternalLink( url ) ) { // Use the system handler for the conventional external links @@ -1037,17 +1067,8 @@ void ArticleView::resourceDownloadFinished() { ExternalViewer * viewer = new ExternalViewer( this, data, "wav", cfg.preferences.audioPlaybackProgram.trimmed() ); - try - { - viewer->start(); - - // Once started, it will erase itself - } - catch( ... ) - { - delete viewer; - throw; - } + // Once started, it will erase itself + viewer->start(); } catch( ExternalViewer::Ex & e ) { @@ -1091,7 +1112,7 @@ void ArticleView::resourceDownloadFinished() } else { - // This one had no data. Erase it. + // This one had no data. Erase it. resourceDownloadRequests.erase( i++ ); } } diff --git a/config.cc b/config.cc index d072a4d7..4abebb6c 100644 --- a/config.cc +++ b/config.cc @@ -162,6 +162,20 @@ WebSites makeDefaultWebSites() return ws; } +Programs makeDefaultPrograms() +{ + Programs programs; + + // The following list doesn't make a lot of sense under Windows +#ifndef Q_WS_WIN + programs.push_back( Program( false, Program::Audio, "428b4c2b905ef568a43d9a16f59559b0", "Festival", "festival --tts" ) ); + programs.push_back( Program( false, Program::Audio, "2cf8b3a60f27e1ac812de0b57c148340", "Espeak", "espeak %GDWORD%" ) ); + programs.push_back( Program( false, Program::PlainText, "4f898f7582596cea518c6b0bfdceb8b3", "Manpages", "man -a %GDWORD%" ) ); +#endif + + return programs; +} + /// Sets option to true of false if node is "1" or "0" respectively, or leaves /// it intact if it's neither "1" nor "0". void applyBoolOption( bool & option, QDomNode const & node ) @@ -459,6 +473,30 @@ Class load() throw( exError ) else c.forvo.languageCodes = "en, ru"; // Default demo values + QDomNode programs = root.namedItem( "programs" ); + + if ( !programs.isNull() ) + { + QDomNodeList nl = programs.toElement().elementsByTagName( "program" ); + + for( unsigned x = 0; x < nl.length(); ++x ) + { + QDomElement pr = nl.item( x ).toElement(); + + Program p; + + p.id = pr.attribute( "id" ); + p.name = pr.attribute( "name" ); + p.commandLine = pr.attribute( "commandLine" ); + p.enabled = ( pr.attribute( "enabled" ) == "1" ); + p.type = (Program::Type)( pr.attribute( "type" ).toInt() ); + + c.programs.push_back( p ); + } + } + else + c.programs = makeDefaultPrograms(); + QDomNode mws = root.namedItem( "mediawikis" ); if ( !mws.isNull() ) @@ -916,6 +954,37 @@ void save( Class const & c ) throw( exError ) } } + { + QDomElement programs = dd.createElement( "programs" ); + root.appendChild( programs ); + + for( Programs::const_iterator i = c.programs.begin(); i != c.programs.end(); ++i ) + { + QDomElement p = dd.createElement( "program" ); + programs.appendChild( p ); + + QDomAttr id = dd.createAttribute( "id" ); + id.setValue( i->id ); + p.setAttributeNode( id ); + + QDomAttr name = dd.createAttribute( "name" ); + name.setValue( i->name ); + p.setAttributeNode( name ); + + QDomAttr commandLine = dd.createAttribute( "commandLine" ); + commandLine.setValue( i->commandLine ); + p.setAttributeNode( commandLine ); + + QDomAttr enabled = dd.createAttribute( "enabled" ); + enabled.setValue( i->enabled ? "1" : "0" ); + p.setAttributeNode( enabled ); + + QDomAttr type = dd.createAttribute( "type" ); + type.setValue( QString::number( i->type ) ); + p.setAttributeNode( type ); + } + } + { QDomElement muted = dd.createElement( "mutedDictionaries" ); diff --git a/config.hh b/config.hh index 4996f1f8..0ddaead6 100644 --- a/config.hh +++ b/config.hh @@ -304,6 +304,39 @@ struct Forvo { return ! operator == ( other ); } }; +struct Program +{ + bool enabled; + enum Type + { + Audio, + PlainText, + Html, + MaxTypeValue + } type; + QString id, name, commandLine; + + Program(): enabled( false ) + {} + + Program( bool enabled_, Type type_, QString const & id_, + QString const & name_, QString const & commandLine_ ): + enabled( enabled_ ), type( type_ ), id( id_ ), name( name_ ), + commandLine( commandLine_ ) {} + + bool operator == ( Program const & other ) const + { return enabled == other.enabled && + type == other.type && + name == other.name && + commandLine == other.commandLine; + } + + bool operator != ( Program const & other ) const + { return ! operator == ( other ); } +}; + +typedef vector< Program > Programs; + /// Dictionaries which are temporarily disabled via the dictionary bar. typedef QSet< QString > MutedDictionaries; @@ -320,6 +353,7 @@ struct Class Hunspell hunspell; Transliteration transliteration; Forvo forvo; + Programs programs; unsigned lastMainGroupId; // Last used group in main window unsigned lastPopupGroupId; // Last used group in popup window diff --git a/dictionary.cc b/dictionary.cc index f2a222e6..101a3a4c 100644 --- a/dictionary.cc +++ b/dictionary.cc @@ -15,6 +15,8 @@ #include "config.hh" #include #include +#include +#include namespace Dictionary { @@ -218,4 +220,13 @@ bool needToRebuildIndex( vector< string > const & dictionaryFiles, return fileInfo.lastModified().toTime_t() < lastModified; } +QString generateRandomDictionaryId() +{ + return QString( + QCryptographicHash::hash( + QDateTime::currentDateTime().toString( "\"Random\"dd.MM.yyyy hh:mm:ss.zzz" ).toUtf8(), + QCryptographicHash::Md5 ).toHex() ); +} + + } diff --git a/dictionary.hh b/dictionary.hh index 70a69d7f..e8d696ef 100644 --- a/dictionary.hh +++ b/dictionary.hh @@ -403,6 +403,10 @@ string makeDictionaryId( vector< string > const & dictionaryFiles ) throw(); bool needToRebuildIndex( vector< string > const & dictionaryFiles, string const & indexFile ) throw(); +/// Returns a random dictionary id useful for interactively created +/// dictionaries. +QString generateRandomDictionaryId(); + } #endif diff --git a/editdictionaries.cc b/editdictionaries.cc index 4557be91..43dc3ba1 100644 --- a/editdictionaries.cc +++ b/editdictionaries.cc @@ -16,7 +16,7 @@ EditDictionaries::EditDictionaries( QWidget * parent, Config::Class & cfg_, dictNetMgr( dictNetMgr_ ), origCfg( cfg ), sources( this, cfg.paths, cfg.soundDirs, cfg.hunspell, cfg.transliteration, - cfg.forvo, cfg.mediawikis, cfg.webSites ), + cfg.forvo, cfg.mediawikis, cfg.webSites, cfg.programs ), orderAndProps( new OrderAndProps( this, cfg.dictionaryOrder, cfg.inactiveDictionaries, dictionaries ) ), groups( new Groups( this, dictionaries, cfg.groups, orderAndProps->getCurrentDictionaryOrder() ) ), @@ -140,7 +140,8 @@ bool EditDictionaries::isSourcesChanged() const sources.getTransliteration() != cfg.transliteration || sources.getForvo() != cfg.forvo || sources.getMediaWikis() != cfg.mediawikis || - sources.getWebSites() != cfg.webSites; + sources.getWebSites() != cfg.webSites || + sources.getPrograms() != cfg.programs; } void EditDictionaries::acceptChangedSources( bool rebuildGroups ) @@ -158,6 +159,7 @@ void EditDictionaries::acceptChangedSources( bool rebuildGroups ) cfg.forvo = sources.getForvo(); cfg.mediawikis = sources.getMediaWikis(); cfg.webSites = sources.getWebSites(); + cfg.programs = sources.getPrograms(); groupInstances.clear(); // Those hold pointers to dictionaries, we need to // free them. diff --git a/editdictionaries.hh b/editdictionaries.hh index 6e77d3ec..2665dde4 100644 --- a/editdictionaries.hh +++ b/editdictionaries.hh @@ -44,7 +44,7 @@ private slots: void on_tabs_currentChanged( int index ); void rescanSources(); - + private: bool isSourcesChanged() const; diff --git a/externalviewer.cc b/externalviewer.cc index 65b7d928..262e7d42 100644 --- a/externalviewer.cc +++ b/externalviewer.cc @@ -1,8 +1,8 @@ /* This file is (c) 2008-2011 Konstantin Isakov * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ -#include "externalviewer.hh" #include +#include "externalviewer.hh" using std::vector; @@ -23,47 +23,17 @@ ExternalViewer::ExternalViewer( QObject * parent, vector< char > const & data, tempFile.close(); printf( "%s\n", tempFile.fileName().toLocal8Bit().data() ); - - connect( &viewer, SIGNAL( finished( int, QProcess::ExitStatus ) ), - this, SLOT( viewerFinished( int, QProcess::ExitStatus ) ) ); - - connect( this, SIGNAL( finished( ExternalViewer * ) ), - &ExternalViewerDeleter::instance(), SLOT( deleteExternalViewer( ExternalViewer * ) ), - Qt::QueuedConnection ); -} - -ExternalViewer::~ExternalViewer() -{ - // No need to delete us once we're being destructed. This fixes some - // double-free corruption if the object is being freed prematurely. - disconnect( this, SIGNAL( finished( ExternalViewer * ) ), - &ExternalViewerDeleter::instance(), SLOT( deleteExternalViewer( ExternalViewer * ) ) ); } void ExternalViewer::start() throw( exCantRunViewer ) { + connect( &viewer, SIGNAL( finished( int, QProcess::ExitStatus ) ), + this, SLOT( deleteLater() ) ); + connect( &viewer, SIGNAL( error( QProcess::ProcessError ) ), + this, SLOT( deleteLater() ) ); + viewer.start( viewerProgram, QStringList( tempFileName ), QIODevice::NotOpen ); if ( !viewer.waitForStarted() ) throw exCantRunViewer( viewerProgram.toStdString() ); } - -void ExternalViewer::viewerFinished( int, QProcess::ExitStatus ) -{ - emit finished( this ); -} - -ExternalViewerDeleter & ExternalViewerDeleter::instance() -{ - static ExternalViewerDeleter evd( 0 ); - - return evd; -} - -void ExternalViewerDeleter::deleteExternalViewer( ExternalViewer * e ) -{ - printf( "Deleting external viewer\n" ); - - delete e; -} - diff --git a/externalviewer.hh b/externalviewer.hh index 2f14e296..6e926e60 100644 --- a/externalviewer.hh +++ b/externalviewer.hh @@ -30,35 +30,9 @@ public: QString const & extension, QString const & viewerProgram ) throw( exCantCreateTempFile ); - ~ExternalViewer(); - + // Once this is called, the object will be deleted when it's done, even if + // the function throws. void start() throw( exCantRunViewer ); - -private slots: - - void viewerFinished( int, QProcess::ExitStatus ); - -signals: - - void finished( ExternalViewer * ); -}; - -class ExternalViewerDeleter: public QObject -{ - Q_OBJECT - -public: - - static ExternalViewerDeleter & instance(); - -public slots: - - void deleteExternalViewer( ExternalViewer * e ); - -private: - - ExternalViewerDeleter( QObject * parent ): QObject( parent ) - {} }; #endif diff --git a/goldendict.pro b/goldendict.pro index a4d5d01d..6790d93b 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -156,7 +156,8 @@ HEADERS += folding.hh \ webmultimediadownload.hh \ forvo.hh \ country.hh \ - about.hh + about.hh \ + programs.hh FORMS += groups.ui \ dictgroupwidget.ui \ mainwindow.ui \ @@ -238,7 +239,8 @@ SOURCES += folding.cc \ webmultimediadownload.cc \ forvo.cc \ country.cc \ - about.cc + about.cc \ + programs.cc win32 { SOURCES += mouseover_win32/ThTypes.c HEADERS += mouseover_win32/ThTypes.h diff --git a/icons/programs.png b/icons/programs.png new file mode 100644 index 0000000000000000000000000000000000000000..580c86d1e0fdb1b4ce3d393e0768b66415d9bd53 GIT binary patch literal 2405 zcmV-r37YnaP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00VXa00VXbebs`@00007bV*G`2ipZ5 z1qU#(?IQjF00`wtL_t(o!<|=aXk1ku|J~QzduQ&<++-ffWM`75HnyZmW+O#YT~I38 zWx>=h)ev?;y1FdOF8gWGuXbTsHlTF3meqa`60I02VuWo4V`U4mrX`>zsSoVLOeW9C zJnp@7=f2LI{ou@OTTQASI2`Wfod554&i~C&O7UaI>#x5qYMRz}_Kp=SXNf`f*tU(CnHg+s zY^<)Wt&RD7zEg=rf*T$lzTV#6zHe%3syaSC{)_YH&(GbFz~tm)`iUo=xB@@|fDbpC3AK`+4_`2GM8~9LE6w3WWkZ&x2tYa9tO&EW_vXfn{0LYBjJd zi*Ps$Q4|3H*=!cJZDVe34llm=qRI1o>hk5wg$5>nGaJJ&jsggIo`==dRS1FrmSvky zT`HBpaU2Z8fNk3_3dzn&*!14D%RK6(bm=mmSs_?RN(jfp=laY zsT2qyU|AM~5SXTkxw$z!`|PvW*w|R0l=j_>$KP%gnx>_7UEfeuHDa13s?{nC!+_&B z@cDd*MxzJ@gHRL&e!m}%%p-{la#zp}^`gXkU z5a{XYq3Lv*Z)$RWfLHUV^32}6EG&?^(@9XaF_6-aS$b=A7t5r}+ z!7vO2L4fPJn4h0-fS;k1{_$qwPd@qNzn7Mle%II6$5@tyN~Hph;~+^A6h%RAZ!ZKv zNXfEXnVp?=r>Cc#D_5>0*;1*rXLEBi($UcowryKpSy{msUwqMYqsCgR)$q|rAE8_> zuiXv~0B^qe=I;%|SQ7*Rx~?|~1Ofs0d_M3zkM8bnq*5talBCe&5bGV6tKIQ(1FG{N&cWLbu)s<12zwrw|K zf~INEb)EG*FTpyFLmTeqc^*8^L#$>1Lu4&-_kV>Tx3Wdy_2uPA-Mj{dLJdaQ) zgqD^T@I2p4TAt^@bzM}eRoJ!-$8ji22w7WOTdNg|#al?Yu8Uf&22m8@I1UIQ7#bSF zU3cAeY$pPJeSMDufGo?1$Ky~G1?6%XrfI^qZRom=LZN_cHj7H7QX_<{alO60#^U1Q ziI-k_iCtb^rh!0!k3=G17^bNPP1E4>`Jk#Qd_G@zVq!uX9v+^)8T{jqKmOaj_ul)f zTrS7dYBdA`0kABKt*tG%u8S|f{1RhhV;CPFw=Z3~WS2^%?MNhY@)kEdbm$QC@WT(U z9655NLs1k6f&jnYkJZ&xD2f7}=TWItu(`QOIga}#5{X<<6b12k{BT!SS0@1G^UptT zs*U3~1Z^z)^pj9XoH~`+R zHDP~$|Fxl^p{VP+O;ANqpsFg8$s`UQJc#!8b|exB2!a65^B@QUYPA|dp%7fxg<%-5 zEDJpU?fqI>S}-~~8cQaVbGsI$XV0E}yuZIcy|uLk$8iviM$z8hj#w;)*49=?l7wQh z2wm4vEEZ9#)xa=JGihyaZ$s0xW*DcGg6DaNqKLM(HY_hM_wU`i_wgN7@YPpeefY*3 zZBf#W!=tgJLEUqw-xrI~Hppp?S%zJ1X~0g}(>Q7V-XjYgp;O0#}EbLI@L zU%#$}!{HBi&4y1s_0+-6&d#fYgM+fJ>v-XX7jWaojY3C92m8=N4}~9js!oosxzW@IFG@VXU09gRPrj%mu-o5;T4?g(G zo;`agfHHu-@59=$WjcEFs2YpKhU>Rv0B`{K&dyHpt+(E?GMNlrTwJ6J3k!5|a*_&y zVAV@`p{`mM0MpggHLNI#x)Tn+Q(V7z@nQ+URQ)pmzz@K`yu2*O<8h*@YP06c=ks7$ zmH^lfzye@4=F-yA)NZXA{{vgNAONMlhTeVm-O6*%JtyaKImWUqY;SK<*LBT$$E4RM zUw^skZi1N~(w;L6BLWcXPb@2nqH-M9?YeFoA%yWfZ;Mizsnu%bI-pfw9ZD(LP4@o) X8;eL1=3RmP00000NkvXXu0mjfFd~vx literal 0 HcmV?d00001 diff --git a/loaddictionaries.cc b/loaddictionaries.cc index 68a02725..40d044ad 100644 --- a/loaddictionaries.cc +++ b/loaddictionaries.cc @@ -17,6 +17,7 @@ #include "greektranslit.hh" #include "website.hh" #include "forvo.hh" +#include "programs.hh" #include #include @@ -230,6 +231,14 @@ void loadDictionaries( QWidget * parent, bool showInitially, dictionaries.insert( dictionaries.end(), dicts.begin(), dicts.end() ); } + //// Programs + { + vector< sptr< Dictionary::Class > > dicts = + Programs::makeDictionaries( cfg.programs ); + + dictionaries.insert( dictionaries.end(), dicts.begin(), dicts.end() ); + } + printf( "Load done\n" ); // Remove any stale index files diff --git a/resources.qrc b/resources.qrc index 753d7450..0cdc3cf4 100644 --- a/resources.qrc +++ b/resources.qrc @@ -13,6 +13,7 @@ icons/reload.png icons/programicon.png icons/programicon_scan.png + icons/programs.png icons/wizard.png icons/warning.png article-style.css diff --git a/sources.cc b/sources.cc index a92acadf..c49dbd1a 100644 --- a/sources.cc +++ b/sources.cc @@ -4,8 +4,6 @@ #include "sources.hh" #include #include -#include -#include #include Sources::Sources( QWidget * parent, Config::Paths const & paths, @@ -14,15 +12,28 @@ Sources::Sources( QWidget * parent, Config::Paths const & paths, Config::Transliteration const & trs, Config::Forvo const & forvo, Config::MediaWikis const & mediawikis, - Config::WebSites const & webSites ): QWidget( parent ), + Config::WebSites const & webSites, + Config::Programs const & programs ): QWidget( parent ), + itemDelegate( new QItemDelegate( this ) ), + itemEditorFactory( new QItemEditorFactory() ), mediawikisModel( this, mediawikis ), webSitesModel( this, webSites ), + programsModel( this, programs ), pathsModel( this, paths ), soundDirsModel( this, soundDirs ), hunspellDictsModel( this, hunspell ) { ui.setupUi( this ); + // TODO: will programTypeEditorCreator and itemEditorFactory be destoryed by + // anyone? + QItemEditorCreatorBase * programTypeEditorCreator = + new QStandardItemEditorCreator< ProgramTypeEditor >(); + + itemEditorFactory->registerEditor( QVariant::Int, programTypeEditorCreator ); + + itemDelegate->setItemEditorFactory( itemEditorFactory ); + ui.mediaWikis->setTabKeyNavigation( true ); ui.mediaWikis->setModel( &mediawikisModel ); ui.mediaWikis->resizeColumnToContents( 0 ); @@ -35,6 +46,17 @@ Sources::Sources( QWidget * parent, Config::Paths const & paths, ui.webSites->resizeColumnToContents( 1 ); ui.webSites->resizeColumnToContents( 2 ); + ui.programs->setTabKeyNavigation( true ); + ui.programs->setModel( &programsModel ); + ui.programs->resizeColumnToContents( 0 ); + // Make sure this thing will be large enough + ui.programs->setColumnWidth( 1, + QFontMetrics( QFont() ).width( + ProgramTypeEditor::getNameForType( Config::Program::PlainText ) ) + 16 ); + ui.programs->resizeColumnToContents( 2 ); + ui.programs->resizeColumnToContents( 3 ); + ui.programs->setItemDelegate( itemDelegate ); + ui.paths->setTabKeyNavigation( true ); ui.paths->setModel( &pathsModel ); @@ -226,6 +248,30 @@ void Sources::on_removeWebSite_clicked() webSitesModel.removeSite( current.row() ); } +void Sources::on_addProgram_clicked() +{ + programsModel.addNewProgram(); + + QModelIndex result = + programsModel.index( programsModel.rowCount( QModelIndex() ) - 1, + 1, QModelIndex() ); + + ui.programs->scrollTo( result ); + ui.programs->edit( result ); +} + +void Sources::on_removeProgram_clicked() +{ + QModelIndex current = ui.programs->currentIndex(); + + if ( current.isValid() && + QMessageBox::question( this, tr( "Confirm removal" ), + tr( "Remove program %1 from the list?" ).arg( programsModel.getCurrentPrograms()[ current.row() ].name ), + QMessageBox::Ok, + QMessageBox::Cancel ) == QMessageBox::Ok ) + programsModel.removeProgram( current.row() ); +} + Config::Hunspell Sources::getHunspell() const { Config::Hunspell h; @@ -284,11 +330,7 @@ void MediaWikisModel::addNewWiki() 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.id = Dictionary::generateRandomDictionaryId(); w.url = "http://"; @@ -437,11 +479,7 @@ void WebSitesModel::addNewSite() w.enabled = false; - // That's quite some rng - w.id = QString( - QCryptographicHash::hash( - QDateTime::currentDateTime().toString( "\"WebSite\"dd.MM.yyyy hh:mm:ss.zzz" ).toUtf8(), - QCryptographicHash::Md5 ).toHex() ); + w.id = Dictionary::generateRandomDictionaryId(); w.url = "http://"; @@ -570,6 +608,191 @@ bool WebSitesModel::setData( QModelIndex const & index, const QVariant & value, } +////////// ProgramsModel + +ProgramsModel::ProgramsModel( QWidget * parent, + Config::Programs const & programs_ ): + QAbstractItemModel( parent ), programs( programs_ ) +{ +} + +void ProgramsModel::removeProgram( int index ) +{ + beginRemoveRows( QModelIndex(), index, index ); + programs.erase( programs.begin() + index ); + endRemoveRows(); +} + +void ProgramsModel::addNewProgram() +{ + Config::Program p; + + p.enabled = false; + + p.id = Dictionary::generateRandomDictionaryId(); + + beginInsertRows( QModelIndex(), programs.size(), programs.size() ); + programs.push_back( p ); + endInsertRows(); +} + +QModelIndex ProgramsModel::index( int row, int column, QModelIndex const & /*parent*/ ) const +{ + return createIndex( row, column, 0 ); +} + +QModelIndex ProgramsModel::parent( QModelIndex const & /*parent*/ ) const +{ + return QModelIndex(); +} + +Qt::ItemFlags ProgramsModel::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 ProgramsModel::rowCount( QModelIndex const & parent ) const +{ + if ( parent.isValid() ) + return 0; + else + return programs.size(); +} + +int ProgramsModel::columnCount( QModelIndex const & parent ) const +{ + if ( parent.isValid() ) + return 0; + else + return 4; +} + +QVariant ProgramsModel::headerData( int section, Qt::Orientation /*orientation*/, int role ) const +{ + if ( role == Qt::DisplayRole ) + switch( section ) + { + case 0: + return tr( "Enabled" ); + case 1: + return tr( "Type" ); + case 2: + return tr( "Name" ); + case 3: + return tr( "Command Line" ); + default: + return QVariant(); + } + + return QVariant(); +} + +QVariant ProgramsModel::data( QModelIndex const & index, int role ) const +{ + if ( (unsigned) index.row() >= programs.size() ) + return QVariant(); + + if ( role == Qt::DisplayRole || role == Qt::EditRole ) + { + switch( index.column() ) + { + case 1: + if ( role == Qt::DisplayRole ) + return ProgramTypeEditor::getNameForType( programs[ index.row() ].type ); + else + return QVariant( ( int ) programs[ index.row() ].type ); + case 2: + return programs[ index.row() ].name; + case 3: + return programs[ index.row() ].commandLine; + default: + return QVariant(); + } + } + + if ( role == Qt::CheckStateRole && !index.column() ) + return programs[ index.row() ].enabled; + + return QVariant(); +} + +bool ProgramsModel::setData( QModelIndex const & index, const QVariant & value, + int role ) +{ + if ( (unsigned)index.row() >= programs.size() ) + return false; + + if ( role == Qt::CheckStateRole && !index.column() ) + { + programs[ index.row() ].enabled = !programs[ index.row() ].enabled; + + dataChanged( index, index ); + return true; + } + + if ( role == Qt::DisplayRole || role == Qt::EditRole ) + switch( index.column() ) + { + case 1: + programs[ index.row() ].type = Config::Program::Type( value.toInt() ); + dataChanged( index, index ); + return true; + case 2: + programs[ index.row() ].name = value.toString(); + dataChanged( index, index ); + return true; + case 3: + programs[ index.row() ].commandLine = value.toString(); + dataChanged( index, index ); + return true; + default: + return false; + } + + return false; +} + +QString ProgramTypeEditor::getNameForType( int v ) +{ + switch( v ) + { + case Config::Program::Audio: + return tr( "Audio" ); + case Config::Program::PlainText: + return tr( "Plain Text" ); + case Config::Program::Html: + return tr( "Html" ); + default: + return tr( "Unknown" ); + } +} + +ProgramTypeEditor::ProgramTypeEditor( QWidget * widget ): QComboBox( widget ) +{ + for( int x = 0; x < Config::Program::MaxTypeValue; ++x ) + addItem( getNameForType( x ) ); +} + +int ProgramTypeEditor::getType() const +{ + return currentIndex(); +} + +void ProgramTypeEditor::setType( int t ) +{ + setCurrentIndex( t ); +} + ////////// PathsModel PathsModel::PathsModel( QWidget * parent, diff --git a/sources.hh b/sources.hh index f985b457..a18c0ebb 100644 --- a/sources.hh +++ b/sources.hh @@ -8,6 +8,9 @@ #include "config.hh" #include "hunspell.hh" #include +#include +#include +#include /// A model to be projected into the mediawikis view, according to Qt's MVC model class MediaWikisModel: public QAbstractItemModel @@ -69,6 +72,52 @@ private: Config::WebSites webSites; }; +/// A model to be projected into the programs view, according to Qt's MVC model +class ProgramsModel: public QAbstractItemModel +{ + Q_OBJECT + +public: + + ProgramsModel( QWidget * parent, Config::Programs const & ); + + void removeProgram( int index ); + void addNewProgram(); + + /// Returns the sites the model currently has listed + Config::Programs const & getCurrentPrograms() const + { return programs; } + + 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::Programs programs; +}; + +class ProgramTypeEditor: public QComboBox +{ +Q_OBJECT +Q_PROPERTY(int type READ getType WRITE setType USER true) + +public: + ProgramTypeEditor( QWidget * widget = 0 ); + + // Returns localized name for the given program type + static QString getNameForType( int ); + +public: + int getType() const; + void setType( int ); +}; + /// A model to be projected into the paths view, according to Qt's MVC model class PathsModel: public QAbstractItemModel { @@ -171,7 +220,8 @@ public: Config::Transliteration const &, Config::Forvo const & forvo, Config::MediaWikis const &, - Config::WebSites const & ); + Config::WebSites const &, + Config::Programs const &); Config::Paths const & getPaths() const { return pathsModel.getCurrentPaths(); } @@ -185,6 +235,9 @@ public: Config::WebSites const & getWebSites() const { return webSitesModel.getCurrentWebSites(); } + Config::Programs const & getPrograms() const + { return programsModel.getCurrentPrograms(); } + Config::Hunspell getHunspell() const; Config::Transliteration getTransliteration() const; @@ -198,8 +251,13 @@ signals: private: Ui::Sources ui; + + QItemDelegate * itemDelegate; + QItemEditorFactory * itemEditorFactory; + MediaWikisModel mediawikisModel; WebSitesModel webSitesModel; + ProgramsModel programsModel; PathsModel pathsModel; SoundDirsModel soundDirsModel; HunspellDictsModel hunspellDictsModel; @@ -224,6 +282,9 @@ private slots: void on_addWebSite_clicked(); void on_removeWebSite_clicked(); + void on_addProgram_clicked(); + void on_removeProgram_clicked(); + void on_rescan_clicked(); }; diff --git a/sources.ui b/sources.ui index f07fd04b..bf906ca7 100644 --- a/sources.ui +++ b/sources.ui @@ -6,7 +6,7 @@ 0 0 - 665 + 690 336 @@ -334,6 +334,65 @@ of the appropriate groups to use them. + + + + :/icons/programs.png:/icons/programs.png + + + Programs + + + + + + Any external programs. A string %GDWORD% will be replaced with the query word. The word will also be fed into standard input. + + + true + + + + + + + + + + + + + + &Add... + + + + + + + &Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + +