diff --git a/article-style.css b/article-style.css index c624fabb..7ac4aed1 100644 --- a/article-style.css +++ b/article-style.css @@ -338,6 +338,47 @@ div.sdct_x clear:both; } +/************* Forvo **************/ + +.forvo_headword +{ + margin-top: 15px; + margin-bottom: 10px; + font-weight: bold; + font-size: 15px; +} + +/* A table which contains a play icon and information about the sound */ +.forvo_play +{ + margin-top: 8px; + margin-left: 8px; +} + +.forvo_user +{ + text-decoration: none; + color: green; +} + +.forvo_location +{ + color: grey; +} + +.forvo_positive_votes +{ + font-weight: bold; + color: green; +} + +.forvo_negative_votes +{ + font-weight: bold; + color: red; +} + + /************* MediaWiki articles ***************** The following consist of excerpts from different .css files edited with a .mwiki prepended to each record. diff --git a/config.cc b/config.cc index 410b6e0b..4b282b3f 100644 --- a/config.cc +++ b/config.cc @@ -433,6 +433,19 @@ Class load() throw( exError ) } } + QDomNode forvo = root.namedItem( "forvo" ); + + if ( !forvo.isNull() ) + { + applyBoolOption( c.forvo.enable, + forvo.namedItem( "enable" ) ); + + c.forvo.apiKey = forvo.namedItem( "apiKey" ).toElement().text(); + c.forvo.languageCodes = forvo.namedItem( "languageCodes" ).toElement().text(); + } + else + c.forvo.languageCodes = "en, ru"; // Default demo values + QDomNode mws = root.namedItem( "mediawikis" ); if ( !mws.isNull() ) @@ -783,6 +796,25 @@ void save( Class const & c ) throw( exError ) romaji.appendChild( opt ); } + { + // Forvo + + QDomElement forvo = dd.createElement( "forvo" ); + root.appendChild( forvo ); + + QDomElement opt = dd.createElement( "enable" ); + opt.appendChild( dd.createTextNode( c.forvo.enable ? "1":"0" ) ); + forvo.appendChild( opt ); + + opt = dd.createElement( "apiKey" ); + opt.appendChild( dd.createTextNode( c.forvo.apiKey ) ); + forvo.appendChild( opt ); + + opt = dd.createElement( "languageCodes" ); + opt.appendChild( dd.createTextNode( c.forvo.languageCodes ) ); + forvo.appendChild( opt ); + } + { QDomElement mws = dd.createElement( "mediawikis" ); root.appendChild( mws ); diff --git a/config.hh b/config.hh index fc763de7..b6b253de 100644 --- a/config.hh +++ b/config.hh @@ -278,6 +278,25 @@ struct Transliteration {} }; +struct Forvo +{ + bool enable; + QString apiKey; + QString languageCodes; + + Forvo(): enable( false ) + {} + + bool operator == ( Forvo const & other ) const + { return enable == other.enable && + apiKey == other.apiKey && + languageCodes == other.languageCodes; + } + + bool operator != ( Forvo const & other ) const + { return ! operator == ( other ); } +}; + /// Dictionaries which are temporarily disabled via the dictionary bar. typedef QSet< QString > MutedDictionaries; @@ -293,6 +312,7 @@ struct Class WebSites webSites; Hunspell hunspell; Transliteration transliteration; + Forvo forvo; unsigned lastMainGroupId; // Last used group in main window unsigned lastPopupGroupId; // Last used group in popup window diff --git a/country.cc b/country.cc new file mode 100644 index 00000000..9add46dd --- /dev/null +++ b/country.cc @@ -0,0 +1,298 @@ +/* This file is (c) 2008-2010 Konstantin Isakov + * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ + +#include "country.hh" +#include "folding.hh" +#include +#include "wstring_qt.hh" + +namespace Country { + +namespace +{ + struct Database: public QMap< QString, QString > + { + static Database const & instance() + { static Database db; return db; } + + private: + + Database(); + + void addCountry( QString const & country, QString const & code ) + { + (*this)[ gd::toQString( Folding::apply( gd::toWString( country ) ) ) ] = code.toLower(); + } + }; + + Database::Database() + { + addCountry( "Afghanistan", "AF" ); + addCountry( "Aland Islands", "AX" ); + addCountry( "Albania", "AL" ); + addCountry( "Algeria", "DZ" ); + addCountry( "American Samoa", "AS" ); + addCountry( "Andorra", "AD" ); + addCountry( "Angola", "AO" ); + addCountry( "Anguilla", "AI" ); + addCountry( "Antarctica", "AQ" ); + addCountry( "Antigua and Barbuda", "AG" ); + addCountry( "Argentina", "AR" ); + addCountry( "Armenia", "AM" ); + addCountry( "Aruba", "AW" ); + addCountry( "Australia", "AU" ); + addCountry( "Austria", "AT" ); + addCountry( "Azerbaijan", "AZ" ); + addCountry( "Bahamas", "BS" ); + addCountry( "Bahrain", "BH" ); + addCountry( "Bangladesh", "BD" ); + addCountry( "Barbados", "BB" ); + addCountry( "Belarus", "BY" ); + addCountry( "Belgium", "BE" ); + addCountry( "Belize", "BZ" ); + addCountry( "Benin", "BJ" ); + addCountry( "Bermuda", "BM" ); + addCountry( "Bhutan", "BT" ); + addCountry( "Bolivia, Plurinational State of", "BO" ); + addCountry( "Bolivia", "BO" ); + addCountry( "Bosnia and Herzegovina", "BA" ); + addCountry( "Botswana", "BW" ); + addCountry( "Bouvet Island", "BV" ); + addCountry( "Brazil", "BR" ); + addCountry( "British Indian Ocean Territory", "IO" ); + addCountry( "Brunei Darussalam", "BN" ); + addCountry( "Bulgaria", "BG" ); + addCountry( "Burkina Faso", "BF" ); + addCountry( "Burundi", "BI" ); + addCountry( "Cambodia", "KH" ); + addCountry( "Cameroon", "CM" ); + addCountry( "Canada", "CA" ); + addCountry( "Cape Verde", "CV" ); + addCountry( "Cayman Islands", "KY" ); + addCountry( "Central African Republic", "CF" ); + addCountry( "Chad", "TD" ); + addCountry( "Chile", "CL" ); + addCountry( "China", "CN" ); + addCountry( "Christmas Island", "CX" ); + addCountry( "Cocos (Keeling) Islands", "CC" ); + addCountry( "Colombia", "CO" ); + addCountry( "Comoros", "KM" ); + addCountry( "Congo", "CG" ); + addCountry( "Congo, the Democratic Republic of the", "CD" ); + addCountry( "Cook Islands", "CK" ); + addCountry( "Costa Rica", "CR" ); + addCountry( "Cote d'Ivoire", "CI" ); + addCountry( "Croatia", "HR" ); + addCountry( "Cuba", "CU" ); + addCountry( "Cyprus", "CY" ); + addCountry( "Czech Republic", "CZ" ); + addCountry( "Denmark", "DK" ); + addCountry( "Djibouti", "DJ" ); + addCountry( "Dominica", "DM" ); + addCountry( "Dominican Republic", "DO" ); + addCountry( "Ecuador", "EC" ); + addCountry( "Egypt", "EG" ); + addCountry( "El Salvador", "SV" ); + addCountry( "Equatorial Guinea", "GQ" ); + addCountry( "Eritrea", "ER" ); + addCountry( "Estonia", "EE" ); + addCountry( "Ethiopia", "ET" ); + addCountry( "Falkland Islands (Malvinas)", "FK" ); + addCountry( "Faroe Islands", "FO" ); + addCountry( "Fiji", "FJ" ); + addCountry( "Finland", "FI" ); + addCountry( "France", "FR" ); + addCountry( "French Guiana", "GF" ); + addCountry( "French Polynesia", "PF" ); + addCountry( "French Southern Territories", "TF" ); + addCountry( "Gabon", "GA" ); + addCountry( "Gambia", "GM" ); + addCountry( "Georgia", "GE" ); + addCountry( "Germany", "DE" ); + addCountry( "Ghana", "GH" ); + addCountry( "Gibraltar", "GI" ); + addCountry( "Greece", "GR" ); + addCountry( "Greenland", "GL" ); + addCountry( "Grenada", "GD" ); + addCountry( "Guadeloupe", "GP" ); + addCountry( "Guam", "GU" ); + addCountry( "Guatemala", "GT" ); + addCountry( "Guernsey", "GG" ); + addCountry( "Guinea", "GN" ); + addCountry( "Guinea-Bissau", "GW" ); + addCountry( "Guyana", "GY" ); + addCountry( "Haiti", "HT" ); + addCountry( "Heard Island and McDonald Islands", "HM" ); + addCountry( "Holy See (Vatican City State)", "VA" ); + addCountry( "Honduras", "HN" ); + addCountry( "Hong Kong", "HK" ); + addCountry( "Hungary", "HU" ); + addCountry( "Iceland", "IS" ); + addCountry( "India", "IN" ); + addCountry( "Indonesia", "ID" ); + addCountry( "Iran, Islamic Republic of", "IR" ); + addCountry( "Iran", "IR" ); + addCountry( "Iraq", "IQ" ); + addCountry( "Ireland", "IE" ); + addCountry( "Isle of Man", "IM" ); + addCountry( "Israel", "IL" ); + addCountry( "Italy", "IT" ); + addCountry( "Jamaica", "JM" ); + addCountry( "Japan", "JP" ); + addCountry( "Jersey", "JE" ); + addCountry( "Jordan", "JO" ); + addCountry( "Kazakhstan", "KZ" ); + addCountry( "Kenya", "KE" ); + addCountry( "Kiribati", "KI" ); + addCountry( "Korea, Democratic People's Republic of", "KP" ); + addCountry( "Korea, Republic of", "KR" ); + addCountry( "Korea", "KR" ); + addCountry( "Kuwait", "KW" ); + addCountry( "Kyrgyzstan", "KG" ); + addCountry( "Lao People's Democratic Republic", "LA" ); + addCountry( "Latvia", "LV" ); + addCountry( "Lebanon", "LB" ); + addCountry( "Lesotho", "LS" ); + addCountry( "Liberia", "LR" ); + addCountry( "Libyan Arab Jamahiriya", "LY" ); + addCountry( "Liechtenstein", "LI" ); + addCountry( "Lithuania", "LT" ); + addCountry( "Luxembourg", "LU" ); + addCountry( "Macao", "MO" ); + addCountry( "Macedonia, the former Yugoslav Republic of", "MK" ); + addCountry( "Macedonia", "MK" ); + addCountry( "Madagascar", "MG" ); + addCountry( "Malawi", "MW" ); + addCountry( "Malaysia", "MY" ); + addCountry( "Maldives", "MV" ); + addCountry( "Mali", "ML" ); + addCountry( "Malta", "MT" ); + addCountry( "Marshall Islands", "MH" ); + addCountry( "Martinique", "MQ" ); + addCountry( "Mauritania", "MR" ); + addCountry( "Mauritius", "MU" ); + addCountry( "Mayotte", "YT" ); + addCountry( "Mexico", "MX" ); + addCountry( "Micronesia, Federated States of", "FM" ); + addCountry( "Micronesia", "FM" ); + addCountry( "Moldova, Republic of", "MD" ); + addCountry( "Moldova", "MD" ); + addCountry( "Monaco", "MC" ); + addCountry( "Mongolia", "MN" ); + addCountry( "Montenegro", "ME" ); + addCountry( "Montserrat", "MS" ); + addCountry( "Morocco", "MA" ); + addCountry( "Mozambique", "MZ" ); + addCountry( "Myanmar", "MM" ); + addCountry( "Namibia", "NA" ); + addCountry( "Nauru", "NR" ); + addCountry( "Nepal", "NP" ); + addCountry( "Netherlands", "NL" ); + addCountry( "Netherlands Antilles", "AN" ); + addCountry( "New Caledonia", "NC" ); + addCountry( "New Zealand", "NZ" ); + addCountry( "Nicaragua", "NI" ); + addCountry( "Niger", "NE" ); + addCountry( "Nigeria", "NG" ); + addCountry( "Niue", "NU" ); + addCountry( "Norfolk Island", "NF" ); + addCountry( "Northern Mariana Islands", "MP" ); + addCountry( "Norway", "NO" ); + addCountry( "Oman", "OM" ); + addCountry( "Pakistan", "PK" ); + addCountry( "Palau", "PW" ); + addCountry( "Palestinian Territory, Occupied", "PS" ); + addCountry( "Panama", "PA" ); + addCountry( "Papua New Guinea", "PG" ); + addCountry( "Paraguay", "PY" ); + addCountry( "Peru", "PE" ); + addCountry( "Philippines", "PH" ); + addCountry( "Pitcairn", "PN" ); + addCountry( "Poland", "PL" ); + addCountry( "Portugal", "PT" ); + addCountry( "Puerto Rico", "PR" ); + addCountry( "Qatar", "QA" ); + addCountry( "Reunion", "RE" ); + addCountry( "Romania", "RO" ); + addCountry( "Russian Federation", "RU" ); + addCountry( "Russia", "RU" ); + addCountry( "Rwanda", "RW" ); + addCountry( "Saint Barthélemy", "BL" ); + addCountry( "Saint Helena, Ascension and Tristan da Cunha", "SH" ); + addCountry( "Saint Kitts and Nevis", "KN" ); + addCountry( "Saint Lucia", "LC" ); + addCountry( "Saint Martin (French part)", "MF" ); + addCountry( "Saint Pierre and Miquelon", "PM" ); + addCountry( "Saint Vincent and the Grenadines", "VC" ); + addCountry( "Samoa", "WS" ); + addCountry( "San Marino", "SM" ); + addCountry( "Sao Tome and Principe", "ST" ); + addCountry( "Saudi Arabia", "SA" ); + addCountry( "Senegal", "SN" ); + addCountry( "Serbia", "RS" ); + addCountry( "Seychelles", "SC" ); + addCountry( "Sierra Leone", "SL" ); + addCountry( "Singapore", "SG" ); + addCountry( "Slovakia", "SK" ); + addCountry( "Slovenia", "SI" ); + addCountry( "Solomon Islands", "SB" ); + addCountry( "Somalia", "SO" ); + addCountry( "South Africa", "ZA" ); + addCountry( "South Georgia and the South Sandwich Islands", "GS" ); + addCountry( "Spain", "ES" ); + addCountry( "Sri Lanka", "LK" ); + addCountry( "Sudan", "SD" ); + addCountry( "Suriname", "SR" ); + addCountry( "Svalbard and Jan Mayen", "SJ" ); + addCountry( "Swaziland", "SZ" ); + addCountry( "Sweden", "SE" ); + addCountry( "Switzerland", "CH" ); + addCountry( "Syrian Arab Republic", "SY" ); + addCountry( "Taiwan, Province of China", "TW" ); + addCountry( "Tajikistan", "TJ" ); + addCountry( "Tanzania, United Republic of", "TZ" ); + addCountry( "Tanzania", "TZ" ); + addCountry( "Thailand", "TH" ); + addCountry( "Timor-Leste", "TL" ); + addCountry( "Togo", "TG" ); + addCountry( "Tokelau", "TK" ); + addCountry( "Tonga", "TO" ); + addCountry( "Trinidad and Tobago", "TT" ); + addCountry( "Tunisia", "TN" ); + addCountry( "Turkey", "TR" ); + addCountry( "Turkmenistan", "TM" ); + addCountry( "Turks and Caicos Islands", "TC" ); + addCountry( "Tuvalu", "TV" ); + addCountry( "Uganda", "UG" ); + addCountry( "Ukraine", "UA" ); + addCountry( "United Arab Emirates", "AE" ); + addCountry( "United Kingdom", "GB" ); + addCountry( "United States", "US" ); + addCountry( "United States Minor Outlying Islands", "UM" ); + addCountry( "Uruguay", "UY" ); + addCountry( "Uzbekistan", "UZ" ); + addCountry( "Vanuatu", "VU" ); + addCountry( "Venezuela, Bolivarian Republic of", "VE" ); + addCountry( "Venezuela", "VE" ); + addCountry( "Viet Nam", "VN" ); + addCountry( "Virgin Islands, British", "VG" ); + addCountry( "Virgin Islands, U.S.", "VI" ); + addCountry( "Wallis and Futuna", "WF" ); + addCountry( "Western Sahara", "EH" ); + addCountry( "Yemen", "YE" ); + addCountry( "Zambia", "ZM" ); + addCountry( "Zimbabwe", "ZW" ); + } +} + +QString englishNametoIso2( QString const & name ) +{ + Database::const_iterator i = Database::instance().find( gd::toQString( Folding::apply( gd::toWString( name ) ) ) ); + + if ( i == Database::instance().end() ) + return QString(); + else + return i.value(); +} + +} diff --git a/country.hh b/country.hh new file mode 100644 index 00000000..5277d60d --- /dev/null +++ b/country.hh @@ -0,0 +1,14 @@ +#ifndef COUNTRY_HH +#define COUNTRY_HH + +#include + +namespace Country { + +/// Attempts converting the given country name, in english, to its iso-3166-1 +/// 2-letter code. If fails, empty string is returned. +QString englishNametoIso2( QString const & ); + +} + +#endif // COUNTRY_HH diff --git a/editdictionaries.cc b/editdictionaries.cc index fb52b3c3..5877cee2 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.mediawikis, cfg.webSites ), + cfg.forvo, cfg.mediawikis, cfg.webSites ), orderAndProps( new OrderAndProps( this, cfg.dictionaryOrder, cfg.inactiveDictionaries, dictionaries ) ), groups( new Groups( this, dictionaries, cfg.groups, orderAndProps->getCurrentDictionaryOrder() ) ), @@ -138,6 +138,7 @@ bool EditDictionaries::isSourcesChanged() const sources.getSoundDirs() != cfg.soundDirs || sources.getHunspell() != cfg.hunspell || sources.getTransliteration() != cfg.transliteration || + sources.getForvo() != cfg.forvo || sources.getMediaWikis() != cfg.mediawikis || sources.getWebSites() != cfg.webSites; } @@ -154,6 +155,7 @@ void EditDictionaries::acceptChangedSources( bool rebuildGroups ) cfg.soundDirs = sources.getSoundDirs(); cfg.hunspell = sources.getHunspell(); cfg.transliteration = sources.getTransliteration(); + cfg.forvo = sources.getForvo(); cfg.mediawikis = sources.getMediaWikis(); cfg.webSites = sources.getWebSites(); diff --git a/forvo.cc b/forvo.cc new file mode 100644 index 00000000..1d8f2942 --- /dev/null +++ b/forvo.cc @@ -0,0 +1,414 @@ +/* This file is (c) 2008-2010 Konstantin Isakov + * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ + +#include "forvo.hh" +#include "wstring_qt.hh" +#include +#include +#include +#include +#include +#include "audiolink.hh" +#include "htmlescape.hh" +#include "country.hh" +#include "language.hh" +#include "langcoder.hh" + +namespace Forvo { + +using namespace Dictionary; + +namespace { + +class ForvoDictionary: public Dictionary::Class +{ + string name; + QString apiKey, languageCode; + QNetworkAccessManager & netMgr; + +public: + + ForvoDictionary( string const & id, string const & name_, + QString const & apiKey_, + QString const & languageCode_, + QNetworkAccessManager & netMgr_ ): + Dictionary::Class( id, vector< string >() ), + name( name_ ), + apiKey( apiKey_ ), + languageCode( languageCode_ ), + 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 QIcon getIcon() throw(); + + virtual sptr< WordSearchRequest > prefixMatch( wstring const & word, + unsigned long /*maxResults*/ ) throw( std::exception ) + { + // Dummy + sptr< WordSearchRequestInstant > sr = new WordSearchRequestInstant; + + sr->getMatches().push_back( WordMatch( word, 1 ) ); + + return sr; + } + + virtual sptr< DataRequest > getArticle( wstring const &, vector< wstring > const & alts, + wstring const & ) + throw( std::exception ); +}; + +class ForvoArticleRequest: public ForvoDataRequestSlots +{ + typedef std::list< std::pair< sptr< QNetworkReply >, bool > > NetReplies; + NetReplies netReplies; + QString word, apiKey, languageCode; + string dictionaryId; + +public: + + ForvoArticleRequest( wstring const & word, vector< wstring > const & alts, + QString const & apiKey_, + QString const & languageCode_, + string const & dictionaryId_, + QNetworkAccessManager & mgr ); + + virtual void cancel(); + +private: + + void addQuery( QNetworkAccessManager & mgr, wstring const & word ); + + virtual void requestFinished( QNetworkReply * ); +}; + +void ForvoArticleRequest::cancel() +{ + finish(); +} + +ForvoArticleRequest::ForvoArticleRequest( wstring const & str, + vector< wstring > const & /*alts*/, + QString const & apiKey_, + QString const & languageCode_, + string const & dictionaryId_, + QNetworkAccessManager & mgr ): + word( gd::toQString( str ) ), apiKey( apiKey_ ), + languageCode( languageCode_ ), dictionaryId( dictionaryId_ ) +{ + connect( &mgr, SIGNAL( finished( QNetworkReply * ) ), + this, SLOT( requestFinished( QNetworkReply * ) ), + Qt::QueuedConnection ); + + addQuery( mgr, str ); + + // Don't do alts for now -- the api onlu allows 1000 requests a day a key +#if 0 + for( unsigned x = 0; x < alts.size(); ++x ) + addQuery( mgr, alts[ x ] ); +#endif +} + +void ForvoArticleRequest::addQuery( QNetworkAccessManager & mgr, + wstring const & str ) +{ + printf( "Requesting article %ls\n", str.c_str() ); + + if ( apiKey.simplified().isEmpty() ) + { + // Use the default api key. That's the key I have just registered myself. + // It has a limit of 1000 requests a day, and may also get banned in the + // future. Can't do much about it. Get your own key, it is simple. + apiKey = "5efa5d045a16d10ad9c4705bd5d8e56a"; + } + + QUrl reqUrl = QUrl::fromEncoded( + QString( "http://apifree.forvo.com/key/%1/format/xml/action/word-pronunciations/word/%2/language/%3" ) + .arg( apiKey ).arg( QString::fromAscii( QUrl::toPercentEncoding( gd::toQString( str ) ) ) ).arg( languageCode ).toUtf8() ); + + printf( "req: %s\n", reqUrl.toEncoded().data() ); + + sptr< QNetworkReply > netReply = mgr.get( QNetworkRequest( reqUrl ) ); + + netReplies.push_back( std::make_pair( netReply, false ) ); +} + +void ForvoArticleRequest::requestFinished( QNetworkReply * r ) +{ + printf( "Finished.\n" ); + + if ( isFinished() ) // Was cancelled + return; + + // Find this reply + + bool found = false; + + for( NetReplies::iterator i = netReplies.begin(); i != netReplies.end(); ++i ) + { + if ( i->first.get() == r ) + { + i->second = true; // Mark as finished + found = true; + break; + } + } + + if ( !found ) + { + // Well, that's not our reply, don't do anything + return; + } + + bool updated = false; + + for( ; netReplies.size() && netReplies.front().second; netReplies.pop_front() ) + { + sptr< QNetworkReply > netReply = netReplies.front().first; + + 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 + { + printf( "%s\n", dd.toByteArray().data() ); + + QDomNode items = dd.namedItem( "items" ); + + if ( !items.isNull() ) + { + QDomNodeList nl = items.toElement().elementsByTagName( "item" ); + + if ( nl.count() ) + { + string articleBody; + + articleBody += "
"; + articleBody += Html::escape( word.toUtf8().data() ); + articleBody += "
"; + + articleBody += ""; + + for( unsigned x = 0; x < nl.length(); ++x ) + { + QDomElement item = nl.item( x ).toElement(); + + QDomNode mp3 = item.namedItem( "pathmp3" ); + + if ( !mp3.isNull() ) + { + articleBody += ""; + + QUrl url( mp3.toElement().text() ); + + string ref = string( "\"" ) + url.toEncoded().data() + "\""; + + articleBody += addAudioLink( ref, dictionaryId ).c_str(); + + bool isMale = ( item.namedItem( "sex" ).toElement().text().toLower() != "f" ); + + QString user = item.namedItem( "username" ).toElement().text(); + QString country = item.namedItem( "country" ).toElement().text(); + + string userProfile = string( "http://www.forvo.com/user/" ) + + QUrl::toPercentEncoding( user ).data() + "/"; + + int totalVotes = item.namedItem( "num_votes" ).toElement().text().toInt(); + int positiveVotes = item.namedItem( "num_positive_votes" ).toElement().text().toInt(); + int negativeVotes = totalVotes - positiveVotes; + + string votes; + + if ( positiveVotes || negativeVotes ) + { + votes += " "; + + if ( positiveVotes ) + { + votes += "+"; + votes += QByteArray::number( positiveVotes ).data(); + votes += ""; + } + + if ( negativeVotes ) + { + if ( positiveVotes ) + votes += " "; + + votes += "-"; + votes += QByteArray::number( negativeVotes ).data(); + votes += ""; + } + } + + string addTime = + tr( "Added %1" ).arg( item.namedItem( "addtime" ).toElement().text() ).toUtf8().data(); + + articleBody += ""; + articleBody += string( ""; + articleBody += ""; + } + } + + articleBody += "
\"Play\"/" ) + tr( "by" ).toUtf8().data() + " " + + Html::escape( user.toUtf8().data() ) + + " (" + + ( isMale ? tr( "Male" ) : tr( "Female" ) ).toUtf8().data() + + " " + + tr( "from" ).toUtf8().data() + + " " + + " " + + Html::escape( country.toUtf8().data() ) + + ")" + + votes + + "
"; + + Mutex::Lock _( dataMutex ); + + size_t prevSize = data.size(); + + data.resize( prevSize + articleBody.size() ); + + memcpy( &data.front() + prevSize, articleBody.data(), articleBody.size() ); + + hasAnyData = true; + + updated = true; + } + } + + QDomNode errors = dd.namedItem( "errors" ); + + if ( !errors.isNull() ) + setErrorString( errors.namedItem( "error" ).toElement().text() ); + } + printf( "done.\n" ); + } + else + setErrorString( netReply->errorString() ); + } + + if ( netReplies.empty() ) + finish(); + else + if ( updated ) + update(); +} + +sptr< DataRequest > ForvoDictionary::getArticle( wstring const & word, + vector< wstring > const & alts, + wstring const & ) + throw( std::exception ) +{ + if ( word.size() > 80 ) + { + // Don't make excessively large queries -- they're fruitless anyway + + return new DataRequestInstant( false ); + } + else + return new ForvoArticleRequest( word, alts, apiKey, languageCode, getId(), + netMgr ); +} + +QIcon ForvoDictionary::getIcon() throw() +{ +// Experimental code to generate icon -- but the flags clutter the interface too +// much and we're better with a single icon. +#if 0 + if ( languageCode.size() == 2 ) + { + QString countryCode = Language::countryCodeForId( LangCoder::code2toInt( languageCode.toAscii().data() ) ); + + if ( countryCode.size() ) + { + QImage flag( QString( ":/flags/%1.png" ).arg( countryCode.toLower() ) ); + + if ( !flag.isNull() ) + { + QImage img( ":/icons/forvo_icon_base.png" ); + + { + QPainter painter( &img ); + painter.drawImage( QPoint( 5, 7 ), flag ); + } + + return QIcon( QPixmap::fromImage( img ) ); + } + } + } +#endif + return QIcon( ":/icons/forvo.png" ); +} + +} + +vector< sptr< Dictionary::Class > > makeDictionaries( + Dictionary::Initializing &, + Config::Forvo const & forvo, + QNetworkAccessManager & mgr ) + throw( std::exception ) +{ + vector< sptr< Dictionary::Class > > result; + + if ( forvo.enable ) + { + QStringList codes = forvo.languageCodes.split( ',', QString::SkipEmptyParts ); + + QSet< QString > usedCodes; + + for( int x = 0; x < codes.size(); ++x ) + { + QString code = codes[ x ].simplified(); + + if ( code.size() && !usedCodes.contains( code ) ) + { + // Generate id + + QCryptographicHash hash( QCryptographicHash::Md5 ); + + hash.addData( "Forvo source version 1.0" ); + hash.addData( code.toUtf8() ); + + QString displayedCode( code.toLower() ); + + if ( displayedCode.size() ) + displayedCode[ 0 ] = displayedCode[ 0 ].toUpper(); + + result.push_back( + new ForvoDictionary( hash.result().toHex().data(), + QString( "Forvo (%1)" ).arg( displayedCode ).toUtf8().data(), + forvo.apiKey, code, mgr ) ); + + usedCodes.insert( code ); + } + } + } + + return result; +} + +} diff --git a/forvo.hh b/forvo.hh new file mode 100644 index 00000000..f5531bcb --- /dev/null +++ b/forvo.hh @@ -0,0 +1,36 @@ +/* This file is (c) 2008-2010 Konstantin Isakov + * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ + +#ifndef __FORVO_HH_INCLUDED__ +#define __FORVO_HH_INCLUDED__ + +#include "dictionary.hh" +#include "config.hh" +#include + +/// Support for Forvo pronunciations, based on its API. +namespace Forvo { + +using std::vector; +using std::string; + +vector< sptr< Dictionary::Class > > makeDictionaries( + Dictionary::Initializing &, + Config::Forvo const &, + QNetworkAccessManager & ) + throw( std::exception ); + +/// Exposed here for moc +class ForvoDataRequestSlots: public Dictionary::DataRequest +{ + Q_OBJECT + +protected slots: + + virtual void requestFinished( QNetworkReply * ) + {} +}; + +} + +#endif diff --git a/goldendict.pro b/goldendict.pro index b2a3f949..ee40a11d 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -141,7 +141,9 @@ HEADERS += folding.hh \ indexedzip.hh \ termination.hh \ greektranslit.hh \ - webmultimediadownload.hh + webmultimediadownload.hh \ + forvo.hh \ + country.hh FORMS += groups.ui \ dictgroupwidget.ui \ mainwindow.ui \ @@ -220,7 +222,9 @@ SOURCES += folding.cc \ indexedzip.cc \ termination.cc \ greektranslit.cc \ - webmultimediadownload.cc + webmultimediadownload.cc \ + forvo.cc \ + country.cc win32 { SOURCES += mouseover_win32/ThTypes.c HEADERS += mouseover_win32/ThTypes.h diff --git a/icons/forvo.png b/icons/forvo.png new file mode 100644 index 00000000..0dd7a2fd Binary files /dev/null and b/icons/forvo.png differ diff --git a/loaddictionaries.cc b/loaddictionaries.cc index 5f125a2d..4f8aa899 100644 --- a/loaddictionaries.cc +++ b/loaddictionaries.cc @@ -16,6 +16,7 @@ #include "german.hh" #include "greektranslit.hh" #include "website.hh" +#include "forvo.hh" #include #include @@ -220,6 +221,15 @@ void loadDictionaries( QWidget * parent, bool showInitially, dictionaries.insert( dictionaries.end(), dicts.begin(), dicts.end() ); } + //// Forvo dictionaries + + { + vector< sptr< Dictionary::Class > > dicts = + Forvo::makeDictionaries( loadDicts, cfg.forvo, dictNetMgr ); + + 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 19380bc3..9f4081e2 100644 --- a/resources.qrc +++ b/resources.qrc @@ -39,5 +39,6 @@ article-style-st-lingvo.css icons/internet.png icons/icon_dsl_native.png + icons/forvo.png diff --git a/sources.cc b/sources.cc index e8f8667b..f0a01d4a 100644 --- a/sources.cc +++ b/sources.cc @@ -12,6 +12,7 @@ Sources::Sources( QWidget * parent, Config::Paths const & paths, Config::SoundDirs const & soundDirs, Config::Hunspell const & hunspell, Config::Transliteration const & trs, + Config::Forvo const & forvo, Config::MediaWikis const & mediawikis, Config::WebSites const & webSites ): QWidget( parent ), mediawikisModel( this, mediawikis ), @@ -60,6 +61,10 @@ Sources::Sources( QWidget * parent, Config::Paths const & paths, ui.enableHiragana->setChecked( trs.romaji.enableHiragana ); ui.enableKatakana->setChecked( trs.romaji.enableKatakana ); + ui.forvoEnabled->setChecked( forvo.enable ); + ui.forvoApiKey->setText( forvo.apiKey ); + ui.forvoLanguageCodes->setText( forvo.languageCodes ); + if ( Config::isPortableVersion() ) { // Paths @@ -248,6 +253,17 @@ Config::Transliteration Sources::getTransliteration() const return tr; } +Config::Forvo Sources::getForvo() const +{ + Config::Forvo forvo; + + forvo.enable = ui.forvoEnabled->isChecked(); + forvo.apiKey = ui.forvoApiKey->text(); + forvo.languageCodes = ui.forvoLanguageCodes->text(); + + return forvo; +} + ////////// MediaWikisModel MediaWikisModel::MediaWikisModel( QWidget * parent, diff --git a/sources.hh b/sources.hh index cec1b86e..b05852a0 100644 --- a/sources.hh +++ b/sources.hh @@ -169,6 +169,7 @@ public: Config::SoundDirs const &, Config::Hunspell const &, Config::Transliteration const &, + Config::Forvo const & forvo, Config::MediaWikis const &, Config::WebSites const & ); @@ -188,6 +189,8 @@ public: Config::Transliteration getTransliteration() const; + Config::Forvo getForvo() const; + signals: /// Emitted when a 'Rescan' button is clicked. diff --git a/sources.ui b/sources.ui index fdafd78c..4b52655d 100644 --- a/sources.ui +++ b/sources.ui @@ -6,7 +6,7 @@ 0 0 - 578 + 665 336 @@ -334,6 +334,196 @@ of the appropriate groups to use them. + + + + :/icons/forvo.png:/icons/forvo.png + + + Forvo + + + + + + + 0 + 0 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'DejaVu Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<table border="0" style="-qt-table-type: root; margin-top:4px; margin-bottom:4px; margin-left:4px; margin-right:4px;"> +<tr> +<td style="border: none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Live pronunciations from <a href="http://www.forvo.com/"><span style=" text-decoration: underline; color:#0057ae;">forvo.com</span></a>. The site allows people to record and share word pronunciations. You can listen to them from GoldenDict.</p></td></tr></table></body></html> + + + true + + + true + + + + + + + Enable pronunciations from Forvo + + + true + + + true + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + API Key: + + + + + + + Use of Forvo currently requires an API key. Leave this field +blank to use the default key, which may become unavailable +in the future, or register on the site to get your own key. + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 18 + 20 + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'DejaVu Sans'; font-size:9pt; font-weight:400; font-style:normal;"> +<table style="-qt-table-type: root; margin-top:4px; margin-bottom:4px; margin-left:4px; margin-right:4px;"> +<tr> +<td style="border: none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Get your own key <a href="http://api.forvo.com/key/"><span style=" text-decoration: underline; color:#0057ae;">here</span></a>, or leave blank to use the default one.</p></td></tr></table></body></html> + + + false + + + true + + + + + + + Language codes (comma-separated): + + + + + + + List of language codes you would like to have. Example: "en, ru". + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 18 + 20 + + + + + + + + Full list of language codes is avaiable <a href="http://www.forvo.com/languages-codes/">here</a>. + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Transliteration diff --git a/webmultimediadownload.cc b/webmultimediadownload.cc index 57f8ec83..0dc2dad8 100644 --- a/webmultimediadownload.cc +++ b/webmultimediadownload.cc @@ -46,7 +46,10 @@ void WebMultimediaDownload::replyFinished( QNetworkReply * r ) bool WebMultimediaDownload::isAudioUrl( QUrl const & url ) { - return url.scheme() == "http" && Filetype::isNameOfSound( url.path().toUtf8().data() ); + // Note: we check for forvo sound links explicitly, as they don't have extensions + + return url.scheme() == "http" && ( + Filetype::isNameOfSound( url.path().toUtf8().data() ) || url.host() == "apifree.forvo.com" ); } }