diff --git a/base/globalregex.cc b/base/globalregex.cc index 6f72276e..1710cb8b 100644 --- a/base/globalregex.cc +++ b/base/globalregex.cc @@ -56,3 +56,6 @@ QRegularExpression Mdx::srcRe2( "([\\s\"'](?:src|srcset)\\s*=)\\s*(?![\\s\"']|\\ QRegularExpression Mdx::links( "url\\(\\s*(['\"]?)([^'\"]*)(['\"]?)\\s*\\)", QRegularExpression::CaseInsensitiveOption ); + +QRegularExpression Mdx::fontFace( "(?:url\\s*\\(\\s*\\\"(.*?)\\\"\\s*)\\)", + QRegularExpression::CaseInsensitiveOption|QRegularExpression::DotMatchesEverythingOption ); diff --git a/base/globalregex.hh b/base/globalregex.hh index 783a2be8..5aeeb0fa 100644 --- a/base/globalregex.hh +++ b/base/globalregex.hh @@ -37,6 +37,7 @@ public: static QRegularExpression srcRe2; static QRegularExpression links; + static QRegularExpression fontFace; }; } // namespace RX diff --git a/dictheadwords.hh b/dictheadwords.hh index 59676993..18b5d025 100644 --- a/dictheadwords.hh +++ b/dictheadwords.hh @@ -14,7 +14,7 @@ #include "dictionary.hh" #include "delegate.hh" #include "helpwindow.hh" -#include "headwordslistmodel.h" +#include "headwordsmodel.h" class DictHeadwords : public QDialog { diff --git a/dictionary.hh b/dictionary.hh index 4e767a9b..369f8300 100644 --- a/dictionary.hh +++ b/dictionary.hh @@ -443,7 +443,7 @@ public: /// Retrieve all dictionary headwords virtual bool getHeadwords( QStringList & ) { return false; } - virtual void findHeadWordsWithLenth( int &, QSet< QString > * headwords, uint32_t length ){} + virtual void findHeadWordsWithLenth( int &, QSet< QString > * /*headwords*/, uint32_t ){} /// Enable/disable search via synonyms void setSynonymSearchEnabled( bool enabled ) diff --git a/goldendict.pro b/goldendict.pro index 4369eaa0..ec6317b1 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -268,8 +268,8 @@ HEADERS += folding.hh \ articlewebpage.h \ base/globalregex.hh \ globalbroadcaster.h \ + headwordsmodel.h \ iframeschemehandler.h \ - headwordslistmodel.h \ inc_case_folding.hh \ inc_diacritic_folding.hh \ mainwindow.hh \ @@ -412,8 +412,8 @@ SOURCES += folding.cc \ articlewebpage.cpp \ base/globalregex.cc \ globalbroadcaster.cpp \ + headwordsmodel.cpp \ iframeschemehandler.cpp \ - headwordslistmodel.cpp \ main.cc \ dictionary.cc \ config.cc \ diff --git a/headwordslistmodel.cpp b/headwordslistmodel.cpp deleted file mode 100644 index 931d1d57..00000000 --- a/headwordslistmodel.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include "headwordslistmodel.h" -#include "wstring_qt.hh" - -HeadwordListModel::HeadwordListModel(QObject *parent) - : QAbstractListModel(parent), index(0),ptr(0) -{} - -int HeadwordListModel::rowCount(const QModelIndex &parent) const -{ - return parent.isValid() ? 0 : words.size(); -} - -int HeadwordListModel::totalCount() const{ - return totalSize; -} - -bool HeadwordListModel::isFinish() const{ - return words.size() >=totalSize; -} - -//export headword -QString HeadwordListModel::getRow(int row) -{ - if(fileSortedList.empty()){ - fileSortedList<prefixMatch(gd::toWString(reg.pattern()),30); - connect( sr.get(), SIGNAL( finished() ), - this, SLOT( requestFinished() ), Qt::QueuedConnection ); - queuedRequests.push_back( sr ); -} - -void HeadwordListModel::requestFinished() -{ - // See how many new requests have finished, and if we have any new results - for( std::list< sptr< Dictionary::WordSearchRequest > >::iterator i = - queuedRequests.begin(); i != queuedRequests.end(); ) - { - if ( (*i)->isFinished() ) - { - if ( !(*i)->getErrorString().isEmpty() ) - { - qDebug()<<"error:"<<(*i)->getErrorString(); - } - - if ( (*i)->matchesCount() ) - { - auto allmatches = (*i)->getAllMatches(); - for(auto& match:allmatches) - filterWords.append(gd::toQString(match.word)); - } - queuedRequests.erase( i++ ); - } - else - ++i; - } - - if(queuedRequests.empty()){ - QStringList filtered; - for(auto& w:filterWords){ - if(!words.contains(w)){ - filtered<= totalSize || index.row() < 0 || index.row()>=words.size()) - return QVariant(); - - if (role == Qt::DisplayRole) { - return words.at(index.row()); - } - return QVariant(); -} - -bool HeadwordListModel::canFetchMore(const QModelIndex &parent) const -{ - if (parent.isValid()) - return false; - return (words.size() < totalSize); -} - -void HeadwordListModel::fetchMore(const QModelIndex &parent) -{ - if (parent.isValid()) - return; - - QSet headword; - Mutex::Lock _(lock); - _dict->findHeadWordsWithLenth(index,&headword,10000); - if(headword.isEmpty()){ - return; - } - - QSet filtered; - for(const auto & word:qAsConst(headword)) - { - if(!words.contains(word)) - filtered.insert(word); - } - - beginInsertRows(QModelIndex(), words.size(), words.size() + filtered.count() - 1); - for(const auto & word:filtered) - { - words.append(word); - } - endInsertRows(); - - emit numberPopulated(words.size()); -} - -int HeadwordListModel::getCurrentIndex() -{ - return index; -} - -QSet HeadwordListModel::getRemainRows(int & nodeIndex) -{ - QSet headword; - Mutex::Lock _(lock); - _dict->findHeadWordsWithLenth(nodeIndex, &headword,10000); - - QSet filtered; - for(const auto & word:headword) - { - if(!words.contains(word)) - filtered.insert(word); - } - return filtered; -} - -void HeadwordListModel::setDict(Dictionary::Class * dict){ - _dict = dict; - totalSize = _dict->getWordCount(); -} - diff --git a/headwordslistmodel.h b/headwordslistmodel.h deleted file mode 100644 index e184f63a..00000000 --- a/headwordslistmodel.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef HEADWORDSLISTMODEL_H -#define HEADWORDSLISTMODEL_H - -#include "dictionary.hh" - -#include -#include - -class HeadwordListModel : public QAbstractListModel -{ - Q_OBJECT - -public: - HeadwordListModel(QObject *parent = nullptr); - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - int totalCount() const; - int wordCount() const; - bool isFinish() const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QString getRow(int row); - void setFilter(QRegularExpression); - int getCurrentIndex(); - QSet getRemainRows(int & nodeIndex); -signals: - void numberPopulated(int number); - void finished(int number); - -public slots: - void setDict(Dictionary::Class * dict); - void requestFinished(); - -protected: - bool canFetchMore(const QModelIndex &parent) const override; - void fetchMore(const QModelIndex &parent) override; -private: - QStringList words; - QStringList filterWords; - QStringList fileSortedList; - long totalSize; - Dictionary::Class * _dict; - int index; - char* ptr; - Mutex lock; - std::list< sptr< Dictionary::WordSearchRequest > > queuedRequests; -}; - -#endif // HEADWORDSLISTMODEL_H diff --git a/headwordsmodel.cpp b/headwordsmodel.cpp new file mode 100644 index 00000000..3d4d7f69 --- /dev/null +++ b/headwordsmodel.cpp @@ -0,0 +1,176 @@ +#include "headwordsmodel.h" +#include "wstring_qt.hh" + +HeadwordListModel::HeadwordListModel( QObject * parent ) : + QAbstractListModel( parent ), filtering( false ), index( 0 ), ptr( 0 ) +{ +} + +int HeadwordListModel::rowCount( const QModelIndex & parent ) const +{ + return parent.isValid() ? 0 : words.size(); +} + +int HeadwordListModel::totalCount() const +{ + return totalSize; +} + +bool HeadwordListModel::isFinish() const +{ + return words.size() >= totalSize; +} + +// export headword +QString HeadwordListModel::getRow( int row ) +{ + if( fileSortedList.empty() ) + { + fileSortedList << words; + fileSortedList.sort(); + } + return fileSortedList.at( row ); +} + +void HeadwordListModel::setFilter( QRegularExpression reg ) +{ + if( reg.pattern().isEmpty() ) + { + filtering = false; + return; + } + filtering = true; + filterWords.clear(); + auto sr = _dict->prefixMatch( gd::toWString( reg.pattern() ), 30 ); + connect( sr.get(), SIGNAL( finished() ), this, SLOT( requestFinished() ), Qt::QueuedConnection ); + queuedRequests.push_back( sr ); +} + +void HeadwordListModel::requestFinished() +{ + // See how many new requests have finished, and if we have any new results + for( std::list< sptr< Dictionary::WordSearchRequest > >::iterator i = queuedRequests.begin(); + i != queuedRequests.end(); ) + { + if( ( *i )->isFinished() ) + { + if( !( *i )->getErrorString().isEmpty() ) + { + qDebug() << "error:" << ( *i )->getErrorString(); + } + + if( ( *i )->matchesCount() ) + { + auto allmatches = ( *i )->getAllMatches(); + for( auto & match : allmatches ) + filterWords.append( gd::toQString( match.word ) ); + } + queuedRequests.erase( i++ ); + } + else + ++i; + } + + if( queuedRequests.empty() ) + { + QStringList filtered; + for( auto & w : filterWords ) + { + if( !words.contains( w ) ) + { + filtered << w; + } + } + + beginInsertRows( QModelIndex(), words.size(), words.size() + filtered.count() - 1 ); + + for( const auto & word : filtered ) + words.append( word ); + + endInsertRows(); + } +} + +int HeadwordListModel::wordCount() const +{ + return words.size(); +} + +QVariant HeadwordListModel::data( const QModelIndex & index, int role ) const +{ + if( !index.isValid() ) + return QVariant(); + + if( index.row() >= totalSize || index.row() < 0 || index.row() >= words.size() ) + return QVariant(); + + if( role == Qt::DisplayRole ) + { + return words.at( index.row() ); + } + return QVariant(); +} + +bool HeadwordListModel::canFetchMore( const QModelIndex & parent ) const +{ + if( parent.isValid() || filtering ) + return false; + return ( words.size() < totalSize ); +} + +void HeadwordListModel::fetchMore( const QModelIndex & parent ) +{ + if( parent.isValid() || filtering ) + return; + + QSet< QString > headword; + Mutex::Lock _( lock ); + + _dict->findHeadWordsWithLenth( index, &headword, 1000 ); + if( headword.isEmpty() ) + { + return; + } + + QSet< QString > filtered; + for( const auto & word : qAsConst( headword ) ) + { + if( !words.contains( word ) ) + filtered.insert( word ); + } + + beginInsertRows( QModelIndex(), words.size(), words.size() + filtered.count() - 1 ); + for( const auto & word : filtered ) + { + words.append( word ); + } + endInsertRows(); + + emit numberPopulated( words.size() ); +} + +int HeadwordListModel::getCurrentIndex() +{ + return index; +} + +QSet< QString > HeadwordListModel::getRemainRows( int & nodeIndex ) +{ + QSet< QString > headword; + Mutex::Lock _( lock ); + _dict->findHeadWordsWithLenth( nodeIndex, &headword, 10000 ); + + QSet< QString > filtered; + for( const auto & word : headword ) + { + if( !words.contains( word ) ) + filtered.insert( word ); + } + return filtered; +} + +void HeadwordListModel::setDict( Dictionary::Class * dict ) +{ + _dict = dict; + totalSize = _dict->getWordCount(); +} diff --git a/headwordsmodel.h b/headwordsmodel.h new file mode 100644 index 00000000..e6ea49cb --- /dev/null +++ b/headwordsmodel.h @@ -0,0 +1,50 @@ +#ifndef HEADWORDSMODEL_H +#define HEADWORDSMODEL_H + +#include "dictionary.hh" + +#include +#include + +class HeadwordListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + HeadwordListModel( QObject * parent = nullptr ); + + int rowCount( const QModelIndex & parent = QModelIndex() ) const override; + int totalCount() const; + int wordCount() const; + bool isFinish() const; + QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override; + QString getRow( int row ); + void setFilter( QRegularExpression ); + int getCurrentIndex(); + QSet< QString > getRemainRows( int & nodeIndex ); +signals: + void numberPopulated( int number ); + void finished( int number ); + +public slots: + void setDict( Dictionary::Class * dict ); + void requestFinished(); + +protected: + bool canFetchMore( const QModelIndex & parent ) const override; + void fetchMore( const QModelIndex & parent ) override; + +private: + QStringList words; + QStringList filterWords; + bool filtering; + QStringList fileSortedList; + long totalSize; + Dictionary::Class * _dict; + int index; + char * ptr; + Mutex lock; + std::list< sptr< Dictionary::WordSearchRequest > > queuedRequests; +}; + +#endif // HEADWORDSMODEL_H diff --git a/mdx.cc b/mdx.cc index aedfff41..efe3d4dc 100644 --- a/mdx.cc +++ b/mdx.cc @@ -301,6 +301,10 @@ private: /// Process resource links (images, audios, etc) QString & filterResource( QString const & articleId, QString & article ); + void replaceLinks( QString & id, const QString & articleId, QString & article ); + //@font-face + void replaceFontLinks( QString & id, QString & article ); + void removeDirectory( QString const & directory ); friend class MdxHeadwordsRequest; @@ -954,12 +958,19 @@ void MdxDictionary::loadArticle( uint32_t offset, string & articleText, bool noF QString & MdxDictionary::filterResource( QString const & articleId, QString & article ) { QString id = QString::fromStdString( getId() ); + replaceLinks( id, articleId, article ); + replaceFontLinks( id, article); + return article; +} + +void MdxDictionary::replaceLinks( QString & id, const QString & articleId, QString & article ) +{ QString uniquePrefix = QString::fromLatin1( "g" ) + id + "_" + articleId + "_"; QString articleNewText; - int linkPos = 0; + int linkPos = 0; QRegularExpressionMatchIterator it = RX::Mdx::allLinksRe.globalMatch( article ); - QMap idMap; + QMap< QString, QString > idMap; while( it.hasNext() ) { QRegularExpressionMatch allLinksMatch = it.next(); @@ -970,7 +981,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar articleNewText += article.mid( linkPos, allLinksMatch.capturedStart() - linkPos ); linkPos = allLinksMatch.capturedEnd(); - QString linkTxt = allLinksMatch.captured(); + QString linkTxt = allLinksMatch.captured(); QString linkType = allLinksMatch.captured( 1 ).toLower(); QString newLink; @@ -985,7 +996,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar idMap.insert( wordMatch.captured( 3 ), uniquePrefix + wordMatch.captured( 3 ) ); } QString newText = match.captured( 1 ) + match.captured( 2 ) + uniquePrefix; - newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText ); + newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText ); } else newLink = linkTxt.replace( RX::Mdx::anchorIdRe2, "\\1\"" + uniquePrefix + "\\2\"" ); @@ -996,19 +1007,18 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar if( match.hasMatch() ) { // sounds and audio link script - QString newTxt = match.captured( 1 ) + match.captured( 2 ) - + "gdau://" + id + "/" - + match.captured( 3 ) + match.captured( 2 ); - newLink = QString::fromUtf8( addAudioLink( "\"gdau://" + getId() + "/" + match.captured( 3 ).toUtf8().data() + "\"", getId() ).c_str() ) - + newLink.replace( match.capturedStart(), match.capturedLength(), newTxt ); + QString newTxt = + match.captured( 1 ) + match.captured( 2 ) + "gdau://" + id + "/" + match.captured( 3 ) + match.captured( 2 ); + newLink = + QString::fromUtf8( + addAudioLink( "\"gdau://" + getId() + "/" + match.captured( 3 ).toUtf8().data() + "\"", getId() ).c_str() ) + + newLink.replace( match.capturedStart(), match.capturedLength(), newTxt ); } match = RX::Mdx::wordCrossLink.match( newLink ); if( match.hasMatch() ) { - QString newTxt = match.captured( 1 ) + match.captured( 2 ) - + "gdlookup://localhost/" - + match.captured( 3 ); + QString newTxt = match.captured( 1 ) + match.captured( 2 ) + "gdlookup://localhost/" + match.captured( 3 ); if( match.lastCapturedIndex() >= 4 && !match.captured( 4 ).isEmpty() ) newTxt += QString( "?gdanchor=" ) + uniquePrefix + match.captured( 4 ).mid( 1 ); @@ -1017,25 +1027,20 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar newLink.replace( match.capturedStart(), match.capturedLength(), newTxt ); } } - else - if( linkType.compare( "link" ) == 0 ) + else if( linkType.compare( "link" ) == 0 ) { // stylesheets QRegularExpressionMatch match = RX::Mdx::stylesRe.match( linkTxt ); if( match.hasMatch() ) { - QString newText = match.captured( 1 ) + match.captured( 2 ) - + "bres://" + id + "/" - + match.captured( 3 ) + match.captured( 2 ); + QString newText = + match.captured( 1 ) + match.captured( 2 ) + "bres://" + id + "/" + match.captured( 3 ) + match.captured( 2 ); newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText ); } else - newLink = linkTxt.replace( RX::Mdx::stylesRe2, - "\\1\"bres://" + id + "/\\2\"" ); + newLink = linkTxt.replace( RX::Mdx::stylesRe2, "\\1\"bres://" + id + "/\\2\"" ); } - else - if( linkType.compare( "script" ) == 0 || linkType.compare( "img" ) == 0 - || linkType.compare( "source" ) == 0 ) + else if( linkType.compare( "script" ) == 0 || linkType.compare( "img" ) == 0 || linkType.compare( "source" ) == 0 ) { // javascripts and images QRegularExpressionMatch match = RX::Mdx::inlineScriptRe.match( linkTxt ); @@ -1061,22 +1066,19 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar if( linkType.at( 1 ) == 'o' ) // "source" tag { QString filename = match.captured( 3 ); - QString newName = getCachedFileName( filename ); + QString newName = getCachedFileName( filename ); newName.replace( '\\', '/' ); - newText = match.captured( 1 ) + match.captured( 2 ) - + "file:///" + newName + match.captured( 2 ); + newText = match.captured( 1 ) + match.captured( 2 ) + "file:///" + newName + match.captured( 2 ); } else { - newText = match.captured( 1 ) + match.captured( 2 ) - + "bres://" + id + "/" - + match.captured( 3 ) + match.captured( 2 ); + newText = match.captured( 1 ) + match.captured( 2 ) + "bres://" + id + "/" + match.captured( 3 ) + + match.captured( 2 ); } newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText ); } else - newLink = linkTxt.replace( RX::Mdx::srcRe2, - "\\1\"bres://" + id + "/\\2\"" ); + newLink = linkTxt.replace( RX::Mdx::srcRe2, "\\1\"bres://" + id + "/\\2\"" ); } } if( !newLink.isEmpty() ) @@ -1092,14 +1094,46 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar article = articleNewText; } - //some built-in javascript may reference this id. replace "idxxx" with "unique_idxxx" - foreach ( const auto& key, idMap.keys() ) + // some built-in javascript may reference this id. replace "idxxx" with "unique_idxxx" + foreach( const auto & key, idMap.keys() ) { - const auto& value = idMap[ key ]; - article.replace("\""+key+"\"","\""+value+"\""); + const auto & value = idMap[ key ]; + article.replace( "\"" + key + "\"", "\"" + value + "\"" ); } +} - return article; +void MdxDictionary::replaceFontLinks( QString & id, QString & article ) +{ + //article = article.replace( RX::Mdx::fontFace, "src:url(\"bres://" + id + "/" + "\\1\")" ); + QString articleNewText; + int linkPos = 0; + QRegularExpressionMatchIterator it = RX::Mdx::fontFace.globalMatch( article ); + while( it.hasNext() ) + { + QRegularExpressionMatch allLinksMatch = it.next(); + + if( allLinksMatch.capturedEnd() < linkPos ) + continue; + + articleNewText += article.mid( linkPos, allLinksMatch.capturedStart() - linkPos ); + linkPos = allLinksMatch.capturedEnd(); + + QString linkTxt = allLinksMatch.captured(); + QString linkType = allLinksMatch.captured( 1 ); + QString newLink = linkTxt; + + //skip remote url + if( !linkType.contains( ":" ) ) + { + newLink = QString( "url(\"bres://%1/%2\")" ).arg( id, linkType ); + } + articleNewText += newLink; + } + if( linkPos ) + { + articleNewText += article.mid( linkPos ); + article = articleNewText; + } } diff --git a/scanpopup.cc b/scanpopup.cc index dacce48e..26197c54 100644 --- a/scanpopup.cc +++ b/scanpopup.cc @@ -1136,11 +1136,15 @@ void ScanPopup::altModePoll() void ScanPopup::pageLoaded( ArticleView * ) { - - - definition->hasSound([this](bool has){ - ui.pronounceButton->setVisible( has ); - }); + if( !isVisible() ) + return; + auto pronounceBtn = ui.pronounceButton; + definition->hasSound( + [ pronounceBtn ]( bool has ) + { + if( pronounceBtn ) + pronounceBtn->setVisible( has ); + } ); updateBackForwardButtons();