From 5302403d8affaa2d6d035d1f96d464f39f3e5610 Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Tue, 31 May 2022 20:11:04 +0800 Subject: [PATCH 01/11] add date to version --- goldendict.pro | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/goldendict.pro b/goldendict.pro index 7ff16554..8984587c 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -12,7 +12,17 @@ system(git describe --tags --always --dirty): hasGit=1 !isEmpty(hasGit){ GIT_HASH=$$system(git rev-parse --short=8 HEAD ) } -system(echo $${VERSION}.$${GIT_HASH} > version.txt) + +win32{ +# date /T output is locale aware. + DD=$$system(date /T) + DATE =$$replace(DD, / , ) +} +else{ + DATE=$$system(date '+%y%m%d') +} + +system(echo $${VERSION}.$${GIT_HASH} on $${DATE} > version.txt) # DEPENDPATH += . generators INCLUDEPATH += . From 55122eb73946787a5b6d67bf7a9251b830c59714 Mon Sep 17 00:00:00 2001 From: Abs62 Date: Tue, 31 May 2022 21:07:03 +0300 Subject: [PATCH 02/11] Zim: Handle new namespace (issue #1502) --- zim.cc | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 12 deletions(-) diff --git a/zim.cc b/zim.cc index 9bba8fc9..23bdc8ad 100644 --- a/zim.cc +++ b/zim.cc @@ -80,7 +80,8 @@ enum CompressionType struct ZIM_header { quint32 magicNumber; - quint32 version; + quint16 majorVersion; + quint16 minorVersion; quint8 uuid[ 16 ]; quint32 articleCount; quint32 clusterCount; @@ -127,7 +128,7 @@ __attribute__((packed)) enum { Signature = 0x584D495A, // ZIMX on little-endian, XMIZ on big-endian - CurrentFormatVersion = 1 + BtreeIndexing::FormatVersion + Folding::Version + CurrentFormatVersion = 2 + BtreeIndexing::FormatVersion + Folding::Version }; struct IdxHeader @@ -186,13 +187,25 @@ public: } const ZIM_header & header() const { return zimHeader; } + string getClusterData( quint32 cluster_nom ); + const QString getMimeType( quint16 nom ) + { return mimeTypes.value( nom ); } + + bool isArticleMime( quint16 mime_type ) + { return getMimeType( mime_type ).compare( "text/html", Qt::CaseInsensitive ) == 0 + || getMimeType( mime_type ).compare( "text/plain", Qt::CaseInsensitive ) == 0; } + + + quint16 redirectedMimeType( RedirectEntry const & redEntry ); + private: ZIM_header zimHeader; Cache cache[ CACHE_SIZE ]; int stamp; QVector< QPair< quint64, quint32 > > clusterOffsets; + QStringList mimeTypes; void clearCache(); }; @@ -293,6 +306,29 @@ bool ZimFile::open() std::sort( clusterOffsets.begin(), clusterOffsets.end() ); +// Read mime types + + string type; + char ch; + + seek( zimHeader.mimeListPos ); + + for( ; ; ) + { + type.clear(); + while( getChar( &ch ) ) + { + if( ch == 0 ) + break; + type.push_back( ch ); + } + if( type.empty() ) + break; + + QString s = QString::fromUtf8( type.c_str(), type.size() ); + mimeTypes.append( s ); + } + return true; } @@ -418,6 +454,45 @@ string ZimFile::getClusterData( quint32 cluster_nom ) return decompressedData; } +quint16 ZimFile::redirectedMimeType( RedirectEntry const & redEntry ) +{ + RedirectEntry current_entry = redEntry; + quint64 current_pos = pos(); + quint16 mimetype = 0xFFFF; + + for( ; ; ) + { + quint32 current_nom = current_entry.redirectIndex; + + seek( zimHeader.urlPtrPos + (quint64)current_nom * 8 ); + quint64 new_pos; + if( read( reinterpret_cast< char * >( &new_pos ), sizeof(new_pos) ) != sizeof(new_pos) ) + break; + + seek( new_pos ); + quint16 new_mimetype; + if( read( reinterpret_cast< char * >( &new_mimetype ), sizeof(new_mimetype) ) != sizeof(new_mimetype) ) + break; + + if( new_mimetype == 0xFFFF ) // Redirect to other article + { + if( read( reinterpret_cast< char * >( ¤t_entry ) + 2, sizeof( current_entry ) - 2 ) != sizeof( current_entry ) - 2 ) + break; + if( current_nom == current_entry.redirectIndex ) + break; + } + else + { + mimetype = new_mimetype; + break; + } + } + + seek( current_pos ); + return mimetype; +} + + // Some supporting functions bool indexIsOldOrBad( string const & indexFile ) @@ -1584,6 +1659,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( df.open(); ZIM_header const & zh = df.header(); + bool new_namespaces = ( zh.majorVersion >= 6 && zh.minorVersion >= 1 ); if( zh.magicNumber != 0x44D495A ) throw exNotZimFile( i->c_str() ); @@ -1620,7 +1696,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( } const quint64 * ptr; - quint16 mimetype; + quint16 mimetype, redirected_mime; ArticleEntry artEntry; RedirectEntry redEntry; string url, title; @@ -1637,6 +1713,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( if( ret != sizeof(RedirectEntry) - 2 ) throw exCantReadFile( i->c_str() ); + redirected_mime = df.redirectedMimeType( redEntry ); nameSpace = redEntry.nameSpace; } else @@ -1648,7 +1725,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( nameSpace = artEntry.nameSpace; - if( nameSpace == 'A' ) + if( ( nameSpace == 'A' || ( nameSpace == 'C' && new_namespaces ) ) && df.isArticleMime( mimetype ) ) articleCount++; } @@ -1671,7 +1748,8 @@ vector< sptr< Dictionary::Class > > makeDictionaries( title.push_back( ch ); } - if( nameSpace == 'A' ) + if( nameSpace == 'A' || ( nameSpace == 'C' && new_namespaces && ( df.isArticleMime( mimetype ) + || ( mimetype == 0xFFFF && df.isArticleMime( redirected_mime ) ) ) ) ) { wstring word; if( !title.empty() ) @@ -1679,16 +1757,26 @@ vector< sptr< Dictionary::Class > > makeDictionaries( else word = Utf8::decode( url ); - if( maxHeadwordsToExpand && zh.articleCount >= maxHeadwordsToExpand ) - indexedWords.addSingleWord( word, n ); + if( df.isArticleMime( mimetype ) + || ( mimetype == 0xFFFF && df.isArticleMime( redirected_mime ) ) ) + { + if( maxHeadwordsToExpand && zh.articleCount >= maxHeadwordsToExpand ) + indexedWords.addSingleWord( word, n ); + else + indexedWords.addWord( word, n ); + wordCount++; + } else - indexedWords.addWord( word, n ); - wordCount++; + { + url.insert( url.begin(), '/' ); + url.insert( url.begin(), nameSpace ); + indexedResources.addSingleWord( Utf8::decode( url ), n ); + } } else if( nameSpace == 'M' ) { - if( url.compare( "Title") == 0 ) + if( url.compare( "Title" ) == 0 ) { idxHeader.namePtr = n; string name; @@ -1696,10 +1784,10 @@ vector< sptr< Dictionary::Class > > makeDictionaries( initializing.indexingDictionary( name ); } else - if( url.compare( "Description") == 0 ) + if( url.compare( "Description" ) == 0 ) idxHeader.descriptionPtr = n; else - if( url.compare( "Language") == 0 ) + if( url.compare( "Language" ) == 0 ) { string lang; readArticle( df, n, lang ); @@ -1712,6 +1800,11 @@ vector< sptr< Dictionary::Class > > makeDictionaries( } } else + if( nameSpace == 'X' ) + { + continue; + } + else { url.insert( url.begin(), '/' ); url.insert( url.begin(), nameSpace ); From 530591ffbd795bb605651d2898943e4f94fb0771 Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Wed, 1 Jun 2022 21:15:09 +0800 Subject: [PATCH 03/11] fix: add unicodeoption to qregularexpression --- epwing_book.cc | 2 +- ftshelpers.cc | 4 ++-- mainwindow.cc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/epwing_book.cc b/epwing_book.cc index 89aa6959..3abe85b4 100644 --- a/epwing_book.cc +++ b/epwing_book.cc @@ -857,7 +857,7 @@ bool EpwingBook::getNextHeadword( EpwingHeadword & head ) { EB_Position pos; - QRegularExpression badLinks( "#(v|n)\\d" ); + QRegularExpression badLinks( "#(v|n)\\d", QRegularExpression::UseUnicodePropertiesOption); // At first we check references queue while( !LinksQueue.isEmpty() ) diff --git a/ftshelpers.cc b/ftshelpers.cc index c1a8f08f..1b7942cb 100644 --- a/ftshelpers.cc +++ b/ftshelpers.cc @@ -71,8 +71,8 @@ bool parseSearchString( QString const & str, QStringList & indexWords, { searchWords.clear(); indexWords.clear(); - QRegularExpression spacesRegExp( "\\W+" ); - QRegularExpression wordRegExp( QString( "\\w{" ) + QString::number( FTS::MinimumWordSize ) + ",}" ); + QRegularExpression spacesRegExp( "\\W+", QRegularExpression::UseUnicodePropertiesOption ); + QRegularExpression wordRegExp( QString( "\\w{" ) + QString::number( FTS::MinimumWordSize ) + ",}", QRegularExpression::UseUnicodePropertiesOption ); QRegularExpression setsRegExp( "\\[[^\\]]+\\]", QRegularExpression::CaseInsensitiveOption ); QRegularExpression regexRegExp( "\\\\[afnrtvdDwWsSbB]|\\\\x([0-9A-Fa-f]{4})|\\\\0([0-7]{3})", QRegularExpression::CaseInsensitiveOption); diff --git a/mainwindow.cc b/mainwindow.cc index 2ba00eb2..af84c699 100644 --- a/mainwindow.cc +++ b/mainwindow.cc @@ -3503,7 +3503,7 @@ void MainWindow::on_saveArticle_triggered() // MDict anchors QRegularExpression anchorLinkRe( "(<\\s*a\\s+[^>]*\\b(?:name|id)\\b\\s*=\\s*[\"']*g[0-9a-f]{32}_)([0-9a-f]+_)(?=[^\"'])", - QRegularExpression::PatternOption::CaseInsensitiveOption ); + QRegularExpression::PatternOption::CaseInsensitiveOption|QRegularExpression::UseUnicodePropertiesOption ); html.replace( anchorLinkRe, "\\1" ); if( complete ) From f9652487b9b748e776c92771b07ac524124bf009 Mon Sep 17 00:00:00 2001 From: Abs62 Date: Wed, 1 Jun 2022 17:51:56 +0300 Subject: [PATCH 04/11] Zim: A little more support for new format features --- zim.cc | 74 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/zim.cc b/zim.cc index 23bdc8ad..599ea9ea 100644 --- a/zim.cc +++ b/zim.cc @@ -128,7 +128,7 @@ __attribute__((packed)) enum { Signature = 0x584D495A, // ZIMX on little-endian, XMIZ on big-endian - CurrentFormatVersion = 2 + BtreeIndexing::FormatVersion + Folding::Version + CurrentFormatVersion = 3 + BtreeIndexing::FormatVersion + Folding::Version }; struct IdxHeader @@ -161,13 +161,15 @@ struct Cache quint32 clusterNumber; int stamp; int count, size; + unsigned blobs_offset_size; Cache() : data( 0 ), clusterNumber( 0 ), stamp( -1 ), count( 0 ), - size( 0 ) + size( 0 ), + blobs_offset_size( 0 ) {} }; @@ -188,14 +190,14 @@ public: const ZIM_header & header() const { return zimHeader; } - string getClusterData( quint32 cluster_nom ); + string getClusterData( quint32 cluster_nom, unsigned & blob_offset_size ); const QString getMimeType( quint16 nom ) { return mimeTypes.value( nom ); } bool isArticleMime( quint16 mime_type ) - { return getMimeType( mime_type ).compare( "text/html", Qt::CaseInsensitive ) == 0 - || getMimeType( mime_type ).compare( "text/plain", Qt::CaseInsensitive ) == 0; } + { return getMimeType( mime_type ).startsWith( "text/html", Qt::CaseInsensitive ) + || getMimeType( mime_type ).startsWith( "text/plain", Qt::CaseInsensitive ); } quint16 redirectedMimeType( RedirectEntry const & redEntry ); @@ -332,7 +334,7 @@ bool ZimFile::open() return true; } -string ZimFile::getClusterData( quint32 cluster_nom ) +string ZimFile::getClusterData( quint32 cluster_nom, unsigned & blobs_offset_size ) { // Check cache int target = 0; @@ -366,6 +368,7 @@ string ZimFile::getClusterData( quint32 cluster_nom ) if( found ) { // Cache hit + blobs_offset_size = cache[ target ].blobs_offset_size; return string( cache[ target ].data, cache[ target ].count ); } @@ -391,9 +394,11 @@ string ZimFile::getClusterData( quint32 cluster_nom ) seek( clusterOffsets.at( nom ).first ); - char compressionType; - if( !getChar( &compressionType ) ) + char compressionType, cluster_info; + if( !getChar( &cluster_info ) ) return string(); + compressionType = cluster_info & 0x0F; + blobs_offset_size = cluster_info & 0x10 && zimHeader.majorVersion >= 6 ? 8 : 4; string decompressedData; @@ -422,9 +427,16 @@ string ZimFile::getClusterData( quint32 cluster_nom ) // Check BLOBs number in the cluster // We cache multi-element clusters only - quint32 firstOffset; - memcpy( &firstOffset, decompressedData.data(), sizeof(firstOffset) ); - quint32 blobCount = ( firstOffset - 4 ) / 4; + quint32 firstOffset32; + quint64 firstOffset; + if( blobs_offset_size == 8 ) + memcpy( &firstOffset, decompressedData.data(), sizeof(firstOffset) ); + else + { + memcpy( &firstOffset32, decompressedData.data(), sizeof(firstOffset32) ); + firstOffset = firstOffset32; + } + quint32 blobCount = ( firstOffset - blobs_offset_size ) / blobs_offset_size; if( blobCount > 1 ) { @@ -448,6 +460,7 @@ string ZimFile::getClusterData( quint32 cluster_nom ) memcpy( cache[ target ].data, decompressedData.c_str(), size ); cache[ target ].count = size; cache[ target ].clusterNumber = cluster_nom; + cache[ target ].blobs_offset_size = blobs_offset_size; } } @@ -593,23 +606,42 @@ quint32 readArticle( ZimFile & file, quint32 articleNumber, string & result, // Read cluster data - string decompressedData = file.getClusterData( artEntry.clusterNumber ); + unsigned offset_size = 0; + string decompressedData = file.getClusterData( artEntry.clusterNumber, offset_size ); if( decompressedData.empty() ) break; // Take article data from cluster - quint32 firstOffset; - memcpy( &firstOffset, decompressedData.data(), sizeof(firstOffset) ); - quint32 blobCount = ( firstOffset - 4 ) / 4; + quint32 firstOffset32; + quint64 firstOffset; + + if( offset_size == 8 ) + memcpy( &firstOffset, decompressedData.data(), sizeof(firstOffset) ); + else + { + memcpy( &firstOffset32, decompressedData.data(), sizeof(firstOffset32) ); + firstOffset = firstOffset32; + } + quint32 blobCount = ( firstOffset - offset_size ) / offset_size; if( artEntry.blobNumber > blobCount ) break; - quint32 offsets[ 2 ]; - memcpy( offsets, decompressedData.data() + artEntry.blobNumber * 4, sizeof(offsets) ); - quint32 size = offsets[ 1 ] - offsets[ 0 ]; - - result.append( decompressedData, offsets[ 0 ], size ); + quint32 size; + if( offset_size == 8 ) + { + quint64 offsets[ 2 ]; + memcpy( offsets, decompressedData.data() + artEntry.blobNumber * 8, sizeof(offsets) ); + size = offsets[ 1 ] - offsets[ 0 ]; + result.append( decompressedData, offsets[ 0 ], size ); + } + else + { + quint32 offsets[ 2 ]; + memcpy( offsets, decompressedData.data() + artEntry.blobNumber * 4, sizeof(offsets) ); + size = offsets[ 1 ] - offsets[ 0 ]; + result.append( decompressedData, offsets[ 0 ], size ); + } return articleNumber; } @@ -1696,7 +1728,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( } const quint64 * ptr; - quint16 mimetype, redirected_mime; + quint16 mimetype, redirected_mime = 0xFFFF; ArticleEntry artEntry; RedirectEntry redEntry; string url, title; From 8acec2e0628b1c2e1ec80a2ef06c0d16af44bbc0 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Fri, 27 May 2022 11:52:16 +0300 Subject: [PATCH 05/11] Reduce build log verbosity The default qmake build output is overly verbose. Adding the "silent" switch to CONFIG makes it much more concise, comparable to default CMake output. This way warnings and errors are much easier to find. Adding CONFIG-=silent to the qmake command line doesn't restore the verbosity because it is applied before the "CONFIG += silent" line in goldendict.pro. Add a new CONFIG switch "verbose_build_output" to allow increasing build log verbosity without editing goldendict.pro. --- goldendict.pro | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/goldendict.pro b/goldendict.pro index 4a4a3094..5223e933 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -14,6 +14,14 @@ isEmpty( hasGit ) { system(echo $$VERSION > version.txt) } +!CONFIG( verbose_build_output ) { + !win32|*-msvc* { + # Reduce build log verbosity except for MinGW builds (mingw-make cannot + # execute "@echo ..." commands inserted by qmake). + CONFIG += silent + } +} + # DEPENDPATH += . generators INCLUDEPATH += . From aa33b8bebc913f923f56c56ae6ac41c163e90125 Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Wed, 1 Jun 2022 18:39:24 +0300 Subject: [PATCH 06/11] Disable assert macro checks in Release builds --- goldendict.pro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/goldendict.pro b/goldendict.pro index 5223e933..241745f6 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -22,6 +22,10 @@ isEmpty( hasGit ) { } } +CONFIG( release, debug|release ) { + DEFINES += NDEBUG +} + # DEPENDPATH += . generators INCLUDEPATH += . From f015ff555f9db5c535fafab40329122a35ac16af Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Wed, 1 Jun 2022 23:11:41 +0800 Subject: [PATCH 07/11] feature: save bookmark to favorite panel reuse fulltext match to implement this bookmark feature --- articleview.cc | 81 +++++++++++++++++++++++++++++++++----------------- articleview.hh | 11 ++++++- mainwindow.cc | 28 +++++++++++++++-- mainwindow.hh | 2 ++ 4 files changed, 92 insertions(+), 30 deletions(-) diff --git a/articleview.cc b/articleview.cc index 4f997a8d..2df8b49e 100644 --- a/articleview.cc +++ b/articleview.cc @@ -402,6 +402,9 @@ void ArticleView::showDefinition( Config::InputPhrase const & phrase, unsigned g if ( scrollTo.size() ) Utils::Url::addQueryItem( req, "scrollto", scrollTo ); + if(delayedHighlightText.size()) + Utils::Url::addQueryItem( req, "regexp", delayedHighlightText ); + Contexts::Iterator pos = contexts.find( "gdanchor" ); if( pos != contexts.end() ) { @@ -579,6 +582,12 @@ void ArticleView::loadFinished( bool result ) } if( Utils::Url::hasQueryItem( ui.definition->url(), "regexp" ) ) highlightFTSResults(); + + if( !delayedHighlightText.isEmpty() ) + { + // findText( delayedHighlightText, QWebEnginePage::FindCaseSensitively ,[](bool){}); + delayedHighlightText.clear(); + } } void ArticleView::loadProgress(int ){ @@ -1592,6 +1601,11 @@ void ArticleView::setSelectionBySingleClick( bool set ) ui.definition->setSelectionBySingleClick( set ); } +void ArticleView::setDelayedHighlightText(QString const & text) +{ + delayedHighlightText = text; +} + void ArticleView::back() { // Don't allow navigating back to page 0, which is usually the initial @@ -1712,6 +1726,7 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) QAction * sendWordToInputLineAction = 0; QAction * saveImageAction = 0; QAction * saveSoundAction = 0; + QAction * saveBookmark = 0; #if( QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) ) const QWebEngineContextMenuData * menuData = &(r->contextMenuData()); @@ -1832,6 +1847,12 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) } } + if(text.size()) + { + saveBookmark = new QAction( tr( "Save &Bookmark \"%1\"" ).arg( text.left( 60 ) ), &menu ); + menu.addAction( saveBookmark ); + } + // add anki menu if( !text.isEmpty() && cfg.preferences.ankiConnectServer.enabled ) { @@ -1932,6 +1953,10 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) else if ( result == lookupSelection ) showDefinition( selectedText, getGroup( ui.definition->url() ), getCurrentArticle() ); + else if( result == saveBookmark ) + { + emit saveBookmarkSignal( selectedText ); + } else if( result == sendToAnkiAction ) { sendToAnki( ui.definition->title(), ui.definition->selectedText() ); @@ -2333,42 +2358,44 @@ void ArticleView::performFindOperation( bool restart, bool backwards, bool check if ( backwards ) f |= QWebEnginePage::FindBackward; - bool setMark = text.size() && !findText(text, f); + findText( text, + f, + [ &text, this ]( bool match ) + { + bool setMark = !text.isEmpty() && !match; - if ( ui.searchText->property( "noResults" ).toBool() != setMark ) - { - ui.searchText->setProperty( "noResults", setMark ); + if( ui.searchText->property( "noResults" ).toBool() != setMark ) + { + ui.searchText->setProperty( "noResults", setMark ); - // Reload stylesheet - reloadStyleSheet(); - } + // Reload stylesheet + reloadStyleSheet(); + } + } ); } -bool ArticleView::findText(QString& text, const QWebEnginePage::FindFlags& f) +void ArticleView::findText( QString & text, + const QWebEnginePage::FindFlags & f, + const std::function< void( bool match ) > & callback ) { - bool r; - // turn async to sync invoke. - QSharedPointer loop = QSharedPointer(new QEventLoop()); - QTimer::singleShot(1000, loop.data(), &QEventLoop::quit); -#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) - ui.definition->findText(text, f, [&](const QWebEngineFindTextResult& result) +#if( QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) ) + ui.definition->findText( text, + f, + [ callback ]( const QWebEngineFindTextResult & result ) { - if(loop->isRunning()){ - r = result.numberOfMatches()>0; - loop->quit(); - } }); + auto r = result.numberOfMatches() > 0; + if( callback ) + callback( r ); + } ); #else - ui.definition->findText(text, f, [&](bool result) + ui.definition->findText( text, + f, + [ callback ]( bool result ) { - if(loop->isRunning()){ - r = result; - loop->quit(); - } }); + if( callback ) + callback( result ); + } ); #endif - - - loop->exec(); - return r; } void ArticleView::reloadStyleSheet() diff --git a/articleview.hh b/articleview.hh index 35aa99a1..e2a570d0 100644 --- a/articleview.hh +++ b/articleview.hh @@ -79,6 +79,8 @@ class ArticleView: public QFrame bool ftsSearchIsOpened, ftsSearchMatchCase; int ftsPosition; + QString delayedHighlightText; + void highlightFTSResults(); void highlightAllFtsOccurences( QWebEnginePage::FindFlags flags ); void performFtsFindOperation( bool backwards ); @@ -157,6 +159,8 @@ public: /// Called when preference changes void setSelectionBySingleClick( bool set ); + void setDelayedHighlightText(QString const & text); + public slots: /// Goes back in history @@ -227,6 +231,10 @@ public: ResourceToSaveHandler * saveResource( const QUrl & url, const QString & fileName ); ResourceToSaveHandler * saveResource( const QUrl & url, const QUrl & ref, const QString & fileName ); + void findText( QString & text, + const QWebEnginePage::FindFlags & f, + const std::function< void( bool match ) > & callback = nullptr ); + signals: void iconChanged( ArticleView *, QIcon const & icon ); @@ -285,6 +293,8 @@ signals: void inspectSignal(QWebEngineView * view); + void saveBookmarkSignal( const QString & bookmark ); + public slots: void on_searchPrevious_clicked(); @@ -391,7 +401,6 @@ private: void performFindOperation( bool restart, bool backwards, bool checkHighlight = false ); - bool findText(QString& text, const QWebEnginePage::FindFlags& f); void reloadStyleSheet(); diff --git a/mainwindow.cc b/mainwindow.cc index af84c699..f8103eae 100644 --- a/mainwindow.cc +++ b/mainwindow.cc @@ -1690,6 +1690,7 @@ ArticleView * MainWindow::createNewTab( bool switchToIt, connect( view, SIGNAL( zoomIn()), this, SLOT( zoomin() ) ); connect( view, SIGNAL( zoomOut()), this, SLOT( zoomout() ) ); + connect( view, &ArticleView::saveBookmarkSignal, this, &MainWindow::addBookmarkToFavorite ); view->setSelectionBySingleClick( cfg.preferences.selectWordBySingleClick ); @@ -4656,6 +4657,15 @@ void MainWindow::addWordToFavorites( QString const & word, unsigned groupId ) ui.favoritesPaneWidget->addHeadword( folder, word ); } +void MainWindow::addBookmarkToFavorite( QString const & text ) +{ + // get current tab word. + QString word = unescapeTabHeader( ui.tabWidget->tabText( ui.tabWidget->currentIndex() ) ); + const auto bookmark = QString( "%1~~~%2" ).arg( word, text ); + + ui.favoritesPaneWidget->addHeadword( nullptr, bookmark ); +} + void MainWindow::addAllTabsToFavorites() { QString folder; @@ -4728,8 +4738,22 @@ void MainWindow::headwordFromFavorites( QString const & headword, } // Show headword without lost of focus on Favorites tree - setTranslateBoxTextAndClearSuffix( headword, EscapeWildcards, DisablePopup ); - showTranslationFor(headword ); + // bookmark cases: the favorite item may like this "word~~~selectedtext" + auto words = headword.split( "~~~" ); + + setTranslateBoxTextAndClearSuffix( words[0], EscapeWildcards, DisablePopup ); + + //must be a bookmark. + if(words.size()>1) + { + auto view = getCurrentArticleView(); + if(view) + { + view->setDelayedHighlightText(words[1]);// findText( words[ 1 ], QWebEnginePage::FindCaseSensitively ); + } + } + + showTranslationFor( words[ 0 ] ); } #ifdef Q_OS_WIN32 diff --git a/mainwindow.hh b/mainwindow.hh index 4e3e4831..882dc1ae 100644 --- a/mainwindow.hh +++ b/mainwindow.hh @@ -462,6 +462,8 @@ private slots: void addWordToFavorites( QString const & word, unsigned groupId ); + void addBookmarkToFavorite( QString const & text ); + bool isWordPresentedInFavorites( QString const & word, unsigned groupId ); void sendWordToInputLine( QString const & word ); From 99982a1c1127035e98ae60cf47f5a987f59a9dfc Mon Sep 17 00:00:00 2001 From: Igor Kushnir Date: Thu, 2 Jun 2022 10:51:30 +0300 Subject: [PATCH 08/11] ArticleView: expose only minimal API to JavaScript https://doc.qt.io/archives/qt-5.5/qtwebkit-bridge.html#internet-security Qt WebKit Bridge documentation recommends: When exposing native objects to an open web environment, it is important to understand the security implications. Think whether the exposed object enables the web environment access things that shouldn't be open, and whether the web content loaded by that web page comes from a trusted source. The author of Qt WebChannel has said the following in a talk that introduced this Qt module (WebKit Bridge replacement for Qt WebEngine): My suggestion here is to write dedicated QObjects with a slim, minimal API that only have the signals and methods that you deem safe to be used from the outside. - see a comment under https://redirect.invidious.io/watch?v=KnvnTi6XafA --- articleview.cc | 23 ++++++++++++++++++++++- articleview.hh | 3 +++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/articleview.cc b/articleview.cc index 781e700e..01711cbc 100644 --- a/articleview.cc +++ b/articleview.cc @@ -50,6 +50,24 @@ using std::map; using std::list; +/// This class exposes only slim, minimal API to JavaScript clients in order to +/// reduce attack surface available to potentionally malicious external scripts. +class ArticleViewJsProxy: public QObject +{ + Q_OBJECT +public: + /// Note: view becomes the parent of this proxy object. + explicit ArticleViewJsProxy( ArticleView & view ): + QObject( &view ), articleView( view ) + {} + + Q_INVOKABLE void onJsActiveArticleChanged( QString const & id ) + { articleView.onJsActiveArticleChanged( id ); } + +private: + ArticleView & articleView; +}; + /// AccentMarkHandler class /// /// Remove accent marks from text @@ -222,6 +240,7 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm, groups( groups_ ), popupView( popupView_ ), cfg( cfg_ ), + jsProxy( new ArticleViewJsProxy( *this ) ), pasteAction( this ), articleUpAction( this ), articleDownAction( this ), @@ -1140,7 +1159,7 @@ void ArticleView::linkHovered ( const QString & link, const QString & , const QS void ArticleView::attachToJavaScript() { - ui.definition->page()->mainFrame()->addToJavaScriptWindowObject( QString( "articleview" ), this ); + ui.definition->page()->mainFrame()->addToJavaScriptWindowObject( "articleview", jsProxy ); } void ArticleView::linkClicked( QUrl const & url_ ) @@ -3090,3 +3109,5 @@ void ResourceToSaveHandler::downloadFinished() deleteLater(); } } + +#include "articleview.moc" diff --git a/articleview.hh b/articleview.hh index 8512d821..484c2d5f 100644 --- a/articleview.hh +++ b/articleview.hh @@ -15,6 +15,7 @@ #include "groupcombobox.hh" #include "ui_articleview.h" +class ArticleViewJsProxy; class ResourceToSaveHandler; /// A widget with the web view tailored to view and handle articles -- it @@ -32,6 +33,8 @@ class ArticleView: public QFrame Ui::ArticleView ui; + ArticleViewJsProxy * const jsProxy; + QAction pasteAction, articleUpAction, articleDownAction, goBackAction, goForwardAction, selectCurrentArticleAction, copyAsTextAction, inspectAction; From 66499007f6208736f6867ccd0d0c7eab5f238aa9 Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Wed, 1 Jun 2022 23:40:00 +0800 Subject: [PATCH 09/11] clean code: remove \n in parameter of GD_DPRINTF --- article_netmgr.cc | 7 ++----- articleview.cc | 7 ++++--- indexedzip.cc | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/article_netmgr.cc b/article_netmgr.cc index 7ca15408..6d0d3f5c 100644 --- a/article_netmgr.cc +++ b/article_netmgr.cc @@ -198,9 +198,6 @@ QNetworkReply * ArticleNetworkAccessManager::getArticleReply( QNetworkRequest co QUrl refererUrl = QUrl::fromEncoded( referer ); - //GD_DPRINTF( "Considering %s vs %s\n", getHostBase( req.url() ).toUtf8().data(), - // getHostBase( refererUrl ).toUtf8().data() ); - if ( !url.host().endsWith( refererUrl.host() ) && getHostBaseFromUrl( url ) != getHostBaseFromUrl( refererUrl ) && !url.scheme().startsWith("data") ) { @@ -449,7 +446,7 @@ qint64 ArticleResourceReply::readData( char * out, qint64 maxSize ) qint64 left = avail - alreadyRead; qint64 toRead = maxSize < left ? maxSize : left; - GD_DPRINTF( "====reading %d bytes\n", (int)toRead ); + GD_DPRINTF( "====reading %d bytes", (int)toRead ); try { @@ -457,7 +454,7 @@ qint64 ArticleResourceReply::readData( char * out, qint64 maxSize ) } catch( std::exception & e ) { - qWarning( "getDataSlice error: %s\n", e.what() ); + qWarning( "getDataSlice error: %s", e.what() ); } alreadyRead += toRead; diff --git a/articleview.cc b/articleview.cc index 2df8b49e..a5443044 100644 --- a/articleview.cc +++ b/articleview.cc @@ -1849,7 +1849,8 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) if(text.size()) { - saveBookmark = new QAction( tr( "Save &Bookmark \"%1\"" ).arg( text.left( 60 ) ), &menu ); + // avoid too long in the menu ,use left 30 characters. + saveBookmark = new QAction( tr( "Save &Bookmark \"%1...\"" ).arg( text.left( 30 ) ), &menu ); menu.addAction( saveBookmark ); } @@ -1952,10 +1953,10 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) QDesktopServices::openUrl( targetUrl ); else if ( result == lookupSelection ) - showDefinition( selectedText, getGroup( ui.definition->url() ), getCurrentArticle() ); + showDefinition( text, getGroup( ui.definition->url() ), getCurrentArticle() ); else if( result == saveBookmark ) { - emit saveBookmarkSignal( selectedText ); + emit saveBookmarkSignal( text.left( 60 ) ); } else if( result == sendToAnkiAction ) { diff --git a/indexedzip.cc b/indexedzip.cc index c5b77713..438700eb 100644 --- a/indexedzip.cc +++ b/indexedzip.cc @@ -64,7 +64,7 @@ bool IndexedZip::loadFile( uint32_t offset, vector< char > & data ) if ( !ZipFile::readLocalHeader( zip, header ) ) { - GD_DPRINTF( "Failed to load header\n" ); + GD_DPRINTF( "Failed to load header" ); return false; } @@ -73,13 +73,13 @@ bool IndexedZip::loadFile( uint32_t offset, vector< char > & data ) switch( header.compressionMethod ) { case ZipFile::Uncompressed: - GD_DPRINTF( "Uncompressed\n" ); + GD_DPRINTF( "Uncompressed" ); data.resize( header.uncompressedSize ); return (size_t) zip.read( &data.front(), data.size() ) == data.size(); case ZipFile::Deflated: { - GD_DPRINTF( "Deflated\n" ); + GD_DPRINTF( "Deflated" ); // Now do the deflation From 235bd8dc571a1aa03290bc95da70f97df5182202 Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Thu, 2 Jun 2022 20:32:31 +0800 Subject: [PATCH 10/11] add translation to save bookmark action --- locale/zh_CN.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/locale/zh_CN.ts b/locale/zh_CN.ts index 832cea28..1fbe59c3 100644 --- a/locale/zh_CN.ts +++ b/locale/zh_CN.ts @@ -329,7 +329,12 @@ 引用的音频播放程序不存在。 - + + Save &Bookmark "%1..." + 保存为书签(&S)“%1...” + + + &Send "%1" to anki with selected text. 将“%1”发送到anki并附带选择的文本。 From 5fdfaa5aec351182f603c1b4eebeb9b8d79d29dd Mon Sep 17 00:00:00 2001 From: Xiao YiFang Date: Thu, 2 Jun 2022 20:38:55 +0800 Subject: [PATCH 11/11] remove uselesscode the gdDebug gdWarning method, set the QTextCodec, does not seem to take effect --- gddebug.cc | 49 +++++++++---------------------------------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/gddebug.cc b/gddebug.cc index 9fce9dd3..59958bc0 100644 --- a/gddebug.cc +++ b/gddebug.cc @@ -4,61 +4,30 @@ #include #include "gddebug.hh" #include -#if(QT_VERSION >= QT_VERSION_CHECK(6,0,0)) +#if( QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) ) #include #else #include #endif QFile * logFilePtr; -static QTextCodec * utf8Codec; -void gdWarning(const char *msg, ...) +void gdWarning( const char * msg, ... ) { -va_list ap; -va_start(ap, msg); -QTextCodec *localeCodec = 0; - - if( logFilePtr && logFilePtr->isOpen() ) - { - if( utf8Codec == 0 ) - utf8Codec = QTextCodec::codecForName( "UTF8" ); - - localeCodec = QTextCodec::codecForLocale(); - QTextCodec::setCodecForLocale( utf8Codec ); - } + va_list ap; + va_start( ap, msg ); qWarning() << QString().vasprintf( msg, ap ); - if( logFilePtr && logFilePtr->isOpen() ) - { - QTextCodec::setCodecForLocale( localeCodec ); - } - - va_end(ap); + va_end( ap ); } -void gdDebug(const char *msg, ...) +void gdDebug( const char * msg, ... ) { -va_list ap; -va_start(ap, msg); -// QTextCodec *localeCodec = 0; - - // if( logFilePtr && logFilePtr->isOpen() ) - // { - // if( utf8Codec == 0 ) - // utf8Codec = QTextCodec::codecForName( "UTF8" ); - - // localeCodec = QTextCodec::codecForLocale(); - // QTextCodec::setCodecForLocale( utf8Codec ); - // } + va_list ap; + va_start( ap, msg ); qDebug().noquote() << QString().vasprintf( msg, ap ); - // if( logFilePtr && logFilePtr->isOpen() ) - // { - // QTextCodec::setCodecForLocale( localeCodec ); - // } - - va_end(ap); + va_end( ap ); }