From 486edab3f72947740d8ba7863d2f921f94386188 Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Sat, 1 Oct 2022 09:16:36 +0800 Subject: [PATCH 1/2] fix: when show mainwindows in fedora ,the menu got focused seems like a regression caused by goldendict/goldendict#235 fix #158 --- mainwindow.cc | 34 ---------------------------------- 1 file changed, 34 deletions(-) diff --git a/mainwindow.cc b/mainwindow.cc index 201b8ae3..3a3dfd2c 100644 --- a/mainwindow.cc +++ b/mainwindow.cc @@ -2956,41 +2956,7 @@ void MainWindow::toggleMainWindow( bool onlyShow ) ftsDlg->show(); focusTranslateLine(); -#ifdef HAVE_X11 -#if QT_VERSION < 0x060000 - Display *displayID = QX11Info::display(); -#else - QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); - Display *displayID = x11AppInfo->display(); -#endif - Window wh = 0; - int rev = 0; - XGetInputFocus( displayID, &wh, &rev ); - if( wh != translateLine->internalWinId() && !byIconClick ) - { - QPoint p( 1, 1 ); - mapToGlobal( p ); - XEvent event; - memset( &event, 0, sizeof( event) ); - event.type = ButtonPress; - event.xbutton.x = 1; - event.xbutton.y = 1; - event.xbutton.x_root = p.x(); - event.xbutton.y_root = p.y(); - event.xbutton.window = internalWinId(); - event.xbutton.root = XDefaultRootWindow(displayID); - event.xbutton.state = Button1Mask; - event.xbutton.button = Button1; - event.xbutton.same_screen = true; - event.xbutton.time = CurrentTime; - XSendEvent( displayID, internalWinId(), true, 0xfff, &event ); - XFlush( displayID ); - event.type = ButtonRelease; - XSendEvent( displayID, internalWinId(), true, 0xfff, &event ); - XFlush( displayID ); - } -#endif } } From 4c8ec4e3f2544e68a3eab5a522202cb23ea1f1b1 Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Mon, 19 Sep 2022 22:19:22 +0800 Subject: [PATCH 2/2] opt: improve headword dialog performance especially when the dictionary has a very large collecton of headword. include export function --- btreeidx.cc | 92 +++++++++++++++++++++++ btreeidx.hh | 6 ++ dictheadwords.cc | 65 +++++++++++------ dictheadwords.hh | 5 +- dictheadwords.ui | 80 ++++++++++---------- dictionary.hh | 1 + goldendict.pro | 2 + headwordslistmodel.cpp | 161 +++++++++++++++++++++++++++++++++++++++++ headwordslistmodel.h | 48 ++++++++++++ 9 files changed, 394 insertions(+), 66 deletions(-) create mode 100644 headwordslistmodel.cpp create mode 100644 headwordslistmodel.h diff --git a/btreeidx.cc b/btreeidx.cc index cb1ed783..f5df9b9a 100644 --- a/btreeidx.cc +++ b/btreeidx.cc @@ -1331,6 +1331,92 @@ void BtreeIndex::findArticleLinks( QVector< WordArticleLink > * articleLinks, } } +void BtreeIndex::findHeadWords( QSet offsets,int& index, + QSet< QString > * headwords, + uint32_t length ) +{ + int i=0; + for(auto begin=offsets.begin();begin!=offsets.end();begin++,i++){ + if(isize()>=length) + break; + } +} + +void BtreeIndex::findSingleNodeHeadwords( uint32_t offsets, + QSet< QString > * headwords) +{ + uint32_t currentNodeOffset = offsets; + + Mutex::Lock _( *idxFileMutex ); + + char const * leaf = 0; + char const * leafEnd = 0; + char const * chainPtr = 0; + + vector< char > extLeaf; + + // A node + readNode( currentNodeOffset, extLeaf ); + leaf = &extLeaf.front(); + leafEnd = leaf + extLeaf.size(); + + // A leaf + chainPtr = leaf + sizeof( uint32_t ); + + for( ;; ) + { + vector< WordArticleLink > result = readChain( chainPtr ); + + if( headwords ) + { + for( unsigned i = 0; i < result.size(); i++ ) + { + headwords->insert( QString::fromUtf8( ( result[ i ].prefix + result[ i ].word ).c_str() ) ); + } + } + + if( chainPtr >= leafEnd ) + { + break; // That was the last leaf + } + } +} + +//find the next chain ptr ,which is large than this currentChainPtr +QSet BtreeIndex::findNodes() +{ + Mutex::Lock _( *idxFileMutex ); + + if( !rootNodeLoaded ) + { + // Time to load our root node. We do it only once, at the first request. + readNode( rootOffset, rootNode ); + rootNodeLoaded = true; + } + + char const * leaf = &rootNode.front(); + + vector< char > extLeaf; + QSet leafOffset; + // the current the btree's implementation has the height = 2. + + // A node offset + uint32_t * offsets = (uint32_t *)leaf + 1; + // char const * ptr = leaf + sizeof( uint32_t ) + + // ( indexNodeSize + 1 ) * sizeof( uint32_t ); + int i=0; + while(i++ < (indexNodeSize+1) ) + leafOffset.insert(*(offsets++)); + + return leafOffset; +} + void BtreeIndex::getHeadwordsFromOffsets( QList & offsets, QVector & headwords, QAtomicInt * isCancelled ) @@ -1481,6 +1567,12 @@ bool BtreeDictionary::getHeadwords( QStringList &headwords ) return headwords.size() > 0; } +void BtreeDictionary::findHeadWordsWithLenth( int & index, QSet< QString > * headwords, uint32_t length ) +{ + auto leafNodeOffsets = findNodes(); + findHeadWords(leafNodeOffsets,index,headwords,length); +} + void BtreeDictionary::getArticleText(uint32_t, QString &, QString & ) { } diff --git a/btreeidx.hh b/btreeidx.hh index f0ccdf2c..ef44448c 100644 --- a/btreeidx.hh +++ b/btreeidx.hh @@ -95,6 +95,11 @@ public: QSet< QString > * headwords, QAtomicInt * isCancelled = 0 ); + void findHeadWords( QSet offsets,int& index, QSet< QString > * headwords, uint32_t length ); + void findSingleNodeHeadwords( uint32_t offsets, + QSet< QString > * headwords); + QSet findNodes( ); + /// Retrieve headwords for presented article addresses void getHeadwordsFromOffsets( QList< uint32_t > & offsets, QVector< QString > & headwords, @@ -174,6 +179,7 @@ public: { return true; } virtual bool getHeadwords( QStringList &headwords ); + virtual void findHeadWordsWithLenth( int &, QSet< QString > * headwords, uint32_t length ); virtual void getArticleText( uint32_t articleAddress, QString & headword, QString & text ); diff --git a/dictheadwords.cc b/dictheadwords.cc index 3e370e3d..f1fd0a7e 100644 --- a/dictheadwords.cc +++ b/dictheadwords.cc @@ -51,9 +51,7 @@ DictHeadwords::DictHeadwords( QWidget *parent, Config::Class & cfg_, ui.matchCase->setChecked( cfg.headwordsDialog.matchCase ); - model = new QStringListModel( this ); - model->setStringList( headers ); - + model = new HeadwordListModel( this ); proxy = new QSortFilterProxyModel( this ); proxy->setSourceModel( model ); @@ -134,16 +132,12 @@ void DictHeadwords::setup( Dictionary::Class *dict_ ) setWindowTitle( QString::fromUtf8( dict->getName().c_str() ) ); - headers.clear(); - model->setStringList( headers ); - - dict->getHeadwords( headers ); - model->setStringList( headers ); - + auto size = dict->getWordCount(); + model->setDict(dict); proxy->sort( 0 ); filterChanged(); - if( headers.size() > AUTO_APPLY_LIMIT ) + if( size > AUTO_APPLY_LIMIT ) { cfg.headwordsDialog.autoApply = ui.autoApply->isChecked(); ui.autoApply->setChecked( false ); @@ -169,7 +163,7 @@ void DictHeadwords::savePos() cfg.headwordsDialog.searchMode = ui.searchModeCombo->currentIndex(); cfg.headwordsDialog.matchCase = ui.matchCase->isChecked(); - if( headers.size() <= AUTO_APPLY_LIMIT ) + if( model->totalCount() <= AUTO_APPLY_LIMIT ) cfg.headwordsDialog.autoApply = ui.autoApply->isChecked(); cfg.headwordsDialog.headwordsDialogGeometry = saveGeometry(); @@ -226,12 +220,15 @@ void DictHeadwords::filterChanged() QString pattern; switch( syntax ) { - case QRegExp::FixedString: pattern = QRegularExpression::escape( ui.filterLine->text() ); - break; - case QRegExp::WildcardUnix: pattern = wildcardsToRegexp( ui.filterLine->text() ); - break; - default: pattern = ui.filterLine->text(); - break; + case QRegExp::FixedString: + pattern = QRegularExpression::escape( ui.filterLine->text() ); + break; + case QRegExp::WildcardUnix: + pattern = wildcardsToRegexp( ui.filterLine->text() ); + break; + default: + pattern = ui.filterLine->text(); + break; } QRegularExpression regExp( pattern, options ); @@ -244,9 +241,9 @@ void DictHeadwords::filterChanged() QApplication::setOverrideCursor( Qt::WaitCursor ); + model->setFilter(regExp); + proxy->setFilterRegularExpression( regExp ); - - proxy->sort( 0 ); QApplication::restoreOverrideCursor(); @@ -272,8 +269,7 @@ void DictHeadwords::autoApplyStateChanged( int state ) void DictHeadwords::showHeadwordsNumber() { ui.headersNumber->setText( tr( "Unique headwords total: %1, filtered: %2" ) - .arg( QString::number( headers.size() ) ) - .arg( QString::number( proxy->rowCount() ) ) ); + .arg( QString::number( model->totalCount() ), QString::number( proxy->rowCount() ) ) ); } void DictHeadwords::saveHeadersToFile() @@ -303,7 +299,7 @@ void DictHeadwords::saveHeadersToFile() if ( !file.open( QFile::WriteOnly | QIODevice::Text ) ) break; - int headwordsNumber = proxy->rowCount(); + int headwordsNumber = model->totalCount(); // Setup progress dialog int n = headwordsNumber; @@ -327,7 +323,7 @@ void DictHeadwords::saveHeadersToFile() // Write headwords int i; - for( i = 0; i < headwordsNumber; ++i ) + for( i = 0; i < headwordsNumber&&iwordCount(); ++i ) { if( i % step == 0 ) progress.setValue( i / step ); @@ -335,7 +331,7 @@ void DictHeadwords::saveHeadersToFile() if( progress.wasCanceled() ) break; - QVariant value = proxy->data( proxy->index( i, 0 ) ); + QVariant value = model->getRow(i); if( !value.canConvert< QString >() ) continue; @@ -350,6 +346,27 @@ void DictHeadwords::saveHeadersToFile() break; } + //continue to write the remaining headword + int nodeIndex = model->getCurrentIndex(); + auto headwords = model->getRemainRows(nodeIndex); + while(!headwords.isEmpty()) + { + if( progress.wasCanceled() ) + break; + for(auto & w:headwords){ + //progress + if( ++i % step == 0 ) + progress.setValue( i / step ); + + line = w.toUtf8(); + line += "\n"; + + if ( file.write( line ) != line.size() ) + break; + } + headwords = model->getRemainRows(nodeIndex); + } + if( i < headwordsNumber && !progress.wasCanceled() ) break; diff --git a/dictheadwords.hh b/dictheadwords.hh index e2053cce..59676993 100644 --- a/dictheadwords.hh +++ b/dictheadwords.hh @@ -14,6 +14,7 @@ #include "dictionary.hh" #include "delegate.hh" #include "helpwindow.hh" +#include "headwordslistmodel.h" class DictHeadwords : public QDialog { @@ -29,8 +30,8 @@ public: protected: Config::Class & cfg; Dictionary::Class * dict; - QStringList headers; - QStringListModel * model; + + HeadwordListModel * model; QSortFilterProxyModel * proxy; WordListItemDelegate * delegate; QString dictId; diff --git a/dictheadwords.ui b/dictheadwords.ui index 4989edac..d0f7dabe 100644 --- a/dictheadwords.ui +++ b/dictheadwords.ui @@ -16,9 +16,49 @@ + + + + If checked any filter changes will we immediately applied to headwords list + + + Auto apply + + + + + + + Press this button to apply filter to headwords list + + + Apply + + + false + + + true + + + + + + + Filter string (fixed string, wildcards or regular expression) + + + + + + + Filter: + + + @@ -95,46 +135,6 @@ - - - - Press this button to apply filter to headwords list - - - Apply - - - false - - - true - - - - - - - If checked any filter changes will we immediately applied to headwords list - - - Auto apply - - - - - - - Filter: - - - - - - - Filter string (fixed string, wildcards or regular expression) - - - diff --git a/dictionary.hh b/dictionary.hh index 6f266b6a..4e767a9b 100644 --- a/dictionary.hh +++ b/dictionary.hh @@ -443,6 +443,7 @@ public: /// Retrieve all dictionary headwords virtual bool getHeadwords( QStringList & ) { return false; } + virtual void findHeadWordsWithLenth( int &, QSet< QString > * headwords, uint32_t length ){} /// Enable/disable search via synonyms void setSynonymSearchEnabled( bool enabled ) diff --git a/goldendict.pro b/goldendict.pro index cc79e20f..ab513eea 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -263,6 +263,7 @@ HEADERS += folding.hh \ base/globalregex.hh \ globalbroadcaster.h \ iframeschemehandler.h \ + headwordslistmodel.h \ inc_case_folding.hh \ inc_diacritic_folding.hh \ mainwindow.hh \ @@ -406,6 +407,7 @@ SOURCES += folding.cc \ base/globalregex.cc \ globalbroadcaster.cpp \ iframeschemehandler.cpp \ + headwordslistmodel.cpp \ main.cc \ dictionary.cc \ config.cc \ diff --git a/headwordslistmodel.cpp b/headwordslistmodel.cpp new file mode 100644 index 00000000..931d1d57 --- /dev/null +++ b/headwordslistmodel.cpp @@ -0,0 +1,161 @@ +#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 new file mode 100644 index 00000000..e184f63a --- /dev/null +++ b/headwordslistmodel.h @@ -0,0 +1,48 @@ +#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