diff --git a/.clang-format b/.clang-format index 5f744c6d..a845f039 100644 --- a/.clang-format +++ b/.clang-format @@ -1,28 +1,30 @@ +# Format Style Options - Created with Clang Power Tools --- -BasedOnStyle: LLVM AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: AcrossComments AlignEscapedNewlines: Left -AllowAllArgumentsOnNextLine: 'false' -AllowShortBlocksOnASingleLine: 'false' +AlignOperands: Align +AllowAllArgumentsOnNextLine: false +AllowShortBlocksOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: Never -BinPackArguments: 'false' -BinPackParameters: 'false' +BasedOnStyle: LLVM +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: NonAssignment BreakBeforeBraces: Allman BreakConstructorInitializers: AfterColon -ColumnLimit: '120' -ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' -ConstructorInitializerIndentWidth: '2' -ContinuationIndentWidth: '2' -MaxEmptyLinesToKeep: '1' +ColumnLimit: 120 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth : 2 +ContinuationIndentWidth: 2 +MaxEmptyLinesToKeep: 1 PointerAlignment: Middle -SortIncludes: 'false' -SortUsingDeclarations: 'false' +SortIncludes: false +SortUsingDeclarations: false SpaceBeforeParens: Never -SpacesInAngles: 'true' -SpacesInParentheses: 'true' -SpacesInSquareBrackets: 'true' +SpacesInAngles: true +SpacesInParentheses: true +SpacesInSquareBrackets: true UseTab: Never -AlignConsecutiveAssignments: AcrossComments - ... diff --git a/.github/workflows/macos-6.2.yml b/.github/workflows/macos-6.2.yml index 1daa18dc..418a069d 100644 --- a/.github/workflows/macos-6.2.yml +++ b/.github/workflows/macos-6.2.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: os: [macos-11] - qt_ver: [6.2.4,6.3.0] + qt_ver: [6.2.4,6.3.1] qt_arch: [clang_64] env: targetName: GoldenDict @@ -74,6 +74,13 @@ jobs: mv ${targetName}.app ./tmp # --background "installer_background.png" create-dmg --volname "${targetName} Installer" --volicon "icons/macicon.icns" --window-pos 200 120 --window-size 800 400 --icon-size 100 --icon "${targetName}.app" 200 190 --hide-extension "${targetName}.app" --app-drop-link 600 185 --skip-jenkins "${targetName}.dmg" tmp/ + - name: Generate changelog + if: ${{!env.prerelease}} + id: changelog1 + uses: metcalfc/changelog-generator@v3.0.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + - name: Set outputs id: vars run: | @@ -82,6 +89,11 @@ jobs: echo "::set-output name=release_time::$(date +'%H%M%S')" echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" echo "::set-output name=release_hm::$(date +'%y%m%d')" + + - name: changelog + if: $${{env.prerelease}} + id: changelog2 + run: | previousTag=$(git tag --sort=-creatordate | sed -n 2p) echo "previousTag : $previousTag" @@ -91,7 +103,7 @@ jobs: CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" CHANGELOG="${CHANGELOG//'\"'/'%22'}" CHANGELOG="${CHANGELOG//"'"/ }" - echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" + echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" # tag 上传Release - name: uploadRelease uses: svenstaro/upload-release-action@v2 @@ -119,4 +131,5 @@ jobs: auto built by github action. use on your on risk:-) CHANGES: - ${{ steps.vars.outputs.COMMIT_SUMMARY }} + ${{ steps.changelog1.outputs.changelog }} + ${{ steps.changelog2.outputs.COMMIT_SUMMARY }} diff --git a/.github/workflows/macos-PR-check.yml b/.github/workflows/macos-PR-check.yml index 6929b000..c00dba76 100644 --- a/.github/workflows/macos-PR-check.yml +++ b/.github/workflows/macos-PR-check.yml @@ -1,5 +1,7 @@ name: macos-PR-check - +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true on: workflow_dispatch: diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 5e2452cb..45807a1a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -75,7 +75,12 @@ jobs: mv ${targetName}.app ./tmp # --background "installer_background.png" create-dmg --volname "${targetName} Installer" --volicon "icons/macicon.icns" --window-pos 200 120 --window-size 800 400 --icon-size 100 --icon "${targetName}.app" 200 190 --hide-extension "${targetName}.app" --app-drop-link 600 185 --skip-jenkins "${targetName}.dmg" tmp/ - + - name: Generate changelog + if: ${{!env.prerelease}} + id: changelog1 + uses: metcalfc/changelog-generator@v3.0.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} - name: Set outputs id: vars @@ -85,6 +90,11 @@ jobs: echo "::set-output name=release_time::$(date +'%H%M%S')" echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" echo "::set-output name=release_hm::$(date +'%y%m%d')" + + - name: changelog + if: $${{env.prerelease}} + id: changelog2 + run: | previousTag=$(git tag --sort=-creatordate | sed -n 2p) echo "previousTag : $previousTag" @@ -122,4 +132,5 @@ jobs: auto built by github action. use on your on risk:-) CHANGES: - ${{ steps.vars.outputs.COMMIT_SUMMARY }} + ${{ steps.changelog1.outputs.changelog }} + ${{ steps.changelog2.outputs.COMMIT_SUMMARY }} diff --git a/.github/workflows/ubuntu-6.2.yml b/.github/workflows/ubuntu-6.2.yml index f77c7a38..d9e450c5 100644 --- a/.github/workflows/ubuntu-6.2.yml +++ b/.github/workflows/ubuntu-6.2.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - qt_ver: [6.2.4,6.3.0] + qt_ver: [6.3.1] qt_arch: [gcc_64] env: version: 22.5.21 @@ -65,30 +65,39 @@ jobs: qmake CONFIG+=release PREFIX=/usr CONFIG+=zim_support CONFIG+=chinese_conversion_support make INSTALL_ROOT=appdir -j`nproc` install; find appdir/ + ls -al appdir #copy missing shared dll to appdir. - mkdir -p appdir/usr/lib - cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep gobject ) appdir/usr/lib - cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep libpango ) appdir/usr/lib + # mkdir -p appdir/usr/lib + # cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep gobject ) appdir/usr/lib + # cp $(ldd appdir/usr/bin/goldendict | grep -o '\W/[^ ]*' |grep libpango ) appdir/usr/lib - name: Build AppImage run: | - wget -c -nv "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" - chmod a+x linuxdeploy-plugin-qt-x86_64.AppImage - wget -c -nv "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" - chmod a+x linuxdeploy-x86_64.AppImage - ./linuxdeploy-x86_64.AppImage --appdir appdir --output appimage --plugin qt -i redist/icons/goldendict.png -d redist/org.goldendict.GoldenDict.desktop - # - uses: actions/upload-artifact@v2 - # with: - # name: AppImage - # path: './*.AppImage*' + wget -c https://github.com/$(wget -q https://github.com/probonopd/go-appimage/releases -O - | grep "appimagetool-.*-x86_64.AppImage" | head -n 1 | cut -d '"' -f 2) + chmod +x appimagetool-*.AppImage + ./appimagetool-*.AppImage -s deploy appdir/usr/share/applications/*.desktop # Bundle EVERYTHING + # ./linuxdeploy-x86_64.AppImage --appdir appdir --output appimage --plugin qt -i redist/icons/goldendict.png -d redist/org.goldendict.GoldenDict.desktop + + - name: Generate changelog + if: ${{!env.prerelease}} + id: changelog1 + uses: metcalfc/changelog-generator@v3.0.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + - name: Set outputs id: vars run: | echo "::set-output name=sha_short::$(git rev-parse --short=8 HEAD)" echo "::set-output name=release_date::$(date +'%Y%m%d')" echo "::set-output name=release_time::$(date +'%H%M%S')" - echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" - echo "::set-output name=appname::$(ls *.AppImage*)" + echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" echo "::set-output name=release_hm::$(date +'%y%m%d')" + echo "::set-output name=appname::$(ls *.AppImage*)" + + - name: changelog + if: $${{env.prerelease}} + id: changelog2 + run: | previousTag=$(git tag --sort=-creatordate | sed -n 2p) echo "previousTag : $previousTag" @@ -98,7 +107,7 @@ jobs: CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" CHANGELOG="${CHANGELOG//'\"'/'%22'}" CHANGELOG="${CHANGELOG//"'"/ }" - echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" + echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" - name: uploadRelease # if: startsWith(github.event.ref, 'refs/tags/') uses: svenstaro/upload-release-action@v2 @@ -126,4 +135,5 @@ jobs: auto built by github action. use on your on risk:-) CHANGES: - ${{ steps.vars.outputs.COMMIT_SUMMARY }} + ${{ steps.changelog1.outputs.changelog }} + ${{ steps.changelog2.outputs.COMMIT_SUMMARY }} diff --git a/.github/workflows/ubuntu-PR-check.yml b/.github/workflows/ubuntu-PR-check.yml index f12276f3..c734ebb0 100644 --- a/.github/workflows/ubuntu-PR-check.yml +++ b/.github/workflows/ubuntu-PR-check.yml @@ -1,5 +1,7 @@ name: Ubuntu-PR-check - +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true on: workflow_dispatch: diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index b507182c..faed9c76 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -77,15 +77,27 @@ jobs: chmod a+x linuxdeploy-x86_64.AppImage ./linuxdeploy-x86_64.AppImage --appdir appdir --output appimage --plugin qt -i redist/icons/goldendict.png -d redist/org.goldendict.GoldenDict.desktop + - name: Generate changelog + if: ${{!env.prerelease}} + id: changelog1 + uses: metcalfc/changelog-generator@v3.0.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + - name: Set outputs id: vars run: | echo "::set-output name=sha_short::$(git rev-parse --short=8 HEAD)" echo "::set-output name=release_date::$(date +'%Y%m%d')" echo "::set-output name=release_time::$(date +'%H%M%S')" - echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" - echo "::set-output name=appname::$(ls *.AppImage*)" + echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" echo "::set-output name=release_hm::$(date +'%y%m%d')" + echo "::set-output name=appname::$(ls *.AppImage*)" + + - name: changelog + if: $${{env.prerelease}} + id: changelog2 + run: | previousTag=$(git tag --sort=-creatordate | sed -n 2p) echo "previousTag : $previousTag" @@ -95,7 +107,7 @@ jobs: CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" CHANGELOG="${CHANGELOG//'\"'/'%22'}" CHANGELOG="${CHANGELOG//"'"/ }" - echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" + echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" - name: uploadRelease # if: startsWith(github.event.ref, 'refs/tags/') uses: svenstaro/upload-release-action@v2 @@ -123,4 +135,5 @@ jobs: auto built by github action. use on your on risk:-) CHANGES: - ${{ steps.vars.outputs.COMMIT_SUMMARY }} + ${{ steps.changelog1.outputs.changelog }} + ${{ steps.changelog2.outputs.COMMIT_SUMMARY }} diff --git a/.github/workflows/windows-6.2.yml b/.github/workflows/windows-6.2.yml index e0c3c161..303e6ba5 100644 --- a/.github/workflows/windows-6.2.yml +++ b/.github/workflows/windows-6.2.yml @@ -27,7 +27,7 @@ jobs: strategy: matrix: os: [windows-2019] - qt_ver: [6.2.4,6.3.0] + qt_ver: [6.2.4,6.3.1] qt_arch: [win64_msvc2019_64] env: targetName: GoldenDict.exe @@ -52,15 +52,28 @@ jobs: with: fetch-depth: 0 + - name: Generate changelog + if: ${{!env.prerelease}} + id: changelog1 + uses: metcalfc/changelog-generator@v3.0.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + - name: Set outputs id: vars shell: bash run: | echo "::set-output name=sha_short::$(git rev-parse --short=8 HEAD)" echo "::set-output name=release_date::$(date +'%Y%m%d')" - echo "::set-output name=release_time::$(date +'%H%M%S')" - echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" + echo "::set-output name=release_time::$(date +'%H%M%S')" + echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" echo "::set-output name=release_hm::$(date +'%y%m%d')" + + - name: changelog + if: $${{env.prerelease}} + id: changelog2 + shell: bash + run: | previousTag=$(git tag --sort=-creatordate | sed -n 2p) echo "previousTag : $previousTag" @@ -70,7 +83,7 @@ jobs: CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" CHANGELOG="${CHANGELOG//'\"'/'%22'}" CHANGELOG="${CHANGELOG//"'"/ }" - echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" + echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" # # msvc编译 - uses: ilammy/msvc-dev-cmd@v1 @@ -128,6 +141,11 @@ jobs: Qt6.X(Universal Build) Qt5.15.2(Intel Kind) auto built by github action. use on your on risk:-) + + CHANGES: + ${{ steps.changelog1.outputs.changelog }} + ${{ steps.changelog2.outputs.COMMIT_SUMMARY }} + - name: upload goldendict.exe only # if: startsWith(github.event.ref, 'refs/tags/') uses: svenstaro/upload-release-action@v2 diff --git a/.github/workflows/windows-PR-check.yml b/.github/workflows/windows-PR-check.yml index 3b4fdb54..f8def68b 100644 --- a/.github/workflows/windows-PR-check.yml +++ b/.github/workflows/windows-PR-check.yml @@ -1,5 +1,7 @@ name: Windows-PR-check - +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true on: workflow_dispatch: diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9973f060..a5831018 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -54,15 +54,28 @@ jobs: with: fetch-depth: 0 + - name: Generate changelog + if: ${{!env.prerelease}} + id: changelog1 + uses: metcalfc/changelog-generator@v3.0.0 + with: + myToken: ${{ secrets.GITHUB_TOKEN }} + - name: Set outputs id: vars shell: bash run: | echo "::set-output name=sha_short::$(git rev-parse --short=8 HEAD)" echo "::set-output name=release_date::$(date +'%Y%m%d')" - echo "::set-output name=release_time::$(date +'%H%M%S')" - echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" + echo "::set-output name=release_time::$(date +'%H%M%S')" + echo "::set-output name=release_time_clock::$(date +'%H:%M:%S')" echo "::set-output name=release_hm::$(date +'%y%m%d')" + + - name: changelog + if: $${{env.prerelease}} + id: changelog2 + shell: bash + run: | previousTag=$(git tag --sort=-creatordate | sed -n 2p) echo "previousTag : $previousTag" @@ -72,7 +85,7 @@ jobs: CHANGELOG="${CHANGELOG//$'\r'/'%0D'}" CHANGELOG="${CHANGELOG//'\"'/'%22'}" CHANGELOG="${CHANGELOG//"'"/ }" - echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" + echo "::set-output name=COMMIT_SUMMARY::$(echo "$CHANGELOG")" - uses: ilammy/msvc-dev-cmd@v1 # msvc编译 - name: msvc-build goldendict @@ -127,7 +140,8 @@ jobs: auto built by github action. use on your on risk:-) CHANGES: - ${{ steps.vars.outputs.COMMIT_SUMMARY }} + ${{ steps.changelog1.outputs.changelog }} + ${{ steps.changelog2.outputs.COMMIT_SUMMARY }} - name: upload goldendict.exe only # if: startsWith(github.event.ref, 'refs/tags/') uses: svenstaro/upload-release-action@v2 diff --git a/CHANGES.md b/CHANGES.md index b5aca1dc..49f1ea9a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,17 @@ ## Until to now - **CLEANING OLD/USELESS CODE** + - remove Runnable Class in dsl, zim , epwing etc files. + +- add "send to anki" +- right context menu actions, remove nonsense character like `OBJ`,punctuation etc. +- epwing remove duplicate entries when index. +- replace throw() with noexcept +- replace string(***constData) with toStdString +- add built-in support for entry links in javascript files + + +## Until 2022-5-21 - fix a zim about:blank#block [issue](https://github.com/goldendict/goldendict/issues/1472#issuecomment-1086776611) - add fallback font family configuration for dictionary through preference dialog. - fix mdx (compact html) display error on the last item. diff --git a/aard.cc b/aard.cc index 5fe9e694..df1d92fa 100644 --- a/aard.cc +++ b/aard.cc @@ -239,16 +239,16 @@ class AardDictionary: public BtreeIndexing::BtreeDictionary ~AardDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -284,7 +284,7 @@ class AardDictionary: public BtreeIndexing::BtreeDictionary protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; private: @@ -338,7 +338,7 @@ AardDictionary::~AardDictionary() df.close(); } -void AardDictionary::loadIcon() throw() +void AardDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; @@ -429,7 +429,7 @@ void AardDictionary::loadArticle( quint32 address, while( 1 ) { - articleText = string( QObject::tr( "Article loading error" ).toUtf8().constData() ); + articleText = QObject::tr( "Article loading error" ).toStdString(); try { Mutex::Lock _( aardMutex ); @@ -521,7 +521,7 @@ void AardDictionary::loadArticle( quint32 address, articleText = convert( articleText ); } else - articleText = string( QObject::tr( "Article decoding error" ).toUtf8().constData() ); + articleText = QObject::tr( "Article decoding error" ).toStdString(); // See Issue #271: A mechanism to clean-up invalid HTML cards. string cleaner = """""""""""" diff --git a/about.cc b/about.cc index 4aa03804..5e07befa 100644 --- a/about.cc +++ b/about.cc @@ -23,10 +23,8 @@ About::About( QWidget * parent ): QDialog( parent ) ui.version->setText( version ); #if defined (_MSC_VER) - QString compilerVersion = QString( "Visual C++ %1.%2.%3" ) - .arg( GD_CXX_MSVC_MAJOR ) - .arg( GD_CXX_MSVC_MINOR ) - .arg( GD_CXX_MSVC_BUILD ); + QString compilerVersion = QString( "Visual C++ Compiler: %1" ) + .arg( _MSC_FULL_VER ); #elif defined (__clang__) && defined (__clang_version__) QString compilerVersion = QLatin1String( "Clang " ) + QLatin1String( __clang_version__ ); #else diff --git a/about.hh b/about.hh index 2376f19a..0356403c 100644 --- a/about.hh +++ b/about.hh @@ -7,20 +7,6 @@ #include "ui_about.h" #include -// Microsoft Visual C++ version -#if defined (_MSC_VER) - // how many digits does the build number have? -# if _MSC_FULL_VER / 10000 == _MSC_VER -# define GD_CXX_MSVC_BUILD (_MSC_FULL_VER % 10000) // four digits -# elif _MSC_FULL_VER / 100000 == _MSC_VER -# define GD_CXX_MSVC_BUILD (_MSC_FULL_VER % 100000) // five digits -# else -# define GD_CXX_MSVC_BUILD 0 -# endif -# define GD_CXX_MSVC_MAJOR (_MSC_VER/100-6) -# define GD_CXX_MSVC_MINOR (_MSC_VER%100) -#endif - class About: public QDialog { Q_OBJECT diff --git a/ankiconnector.cpp b/ankiconnector.cpp new file mode 100644 index 00000000..b7e11fc0 --- /dev/null +++ b/ankiconnector.cpp @@ -0,0 +1,85 @@ +#include "ankiconnector.h" +#include +#include +#include +#include "utils.hh" +AnkiConnector::AnkiConnector( QObject * parent, Config::Class const & _cfg ) : QObject{ parent }, cfg( _cfg ) +{ + mgr = new QNetworkAccessManager( this ); + connect( mgr, &QNetworkAccessManager::finished, this, &AnkiConnector::finishedSlot ); +} + +void AnkiConnector::sendToAnki( QString const & word, QString const & text ) +{ + //for simplicity. maybe use QJsonDocument in future? + QString postTemplate = QString( "{" + "\"action\": \"addNote\"," + "\"version\": 6," + "\"params\": {" + " \"note\": {" + " \"deckName\": \"%1\"," + " \"modelName\": \"%2\"," + " \"fields\":%3," + " \"options\": {" + " \"allowDuplicate\": true" + " }," + " \"tags\": []" + "}" + "}" + "}" + "" ); + + QJsonObject fields; + fields.insert( "Front", word ); + fields.insert( "Back", text ); + + QString postData = postTemplate.arg( cfg.preferences.ankiConnectServer.deck, + cfg.preferences.ankiConnectServer.model, + Utils::json2String( fields ) ); + +// qDebug().noquote() << postData; + QUrl url; + url.setScheme( "http" ); + url.setHost( cfg.preferences.ankiConnectServer.host ); + url.setPort( cfg.preferences.ankiConnectServer.port ); + QNetworkRequest request( url ); + request.setTransferTimeout( 3000 ); + // request.setAttribute( QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy ); + request.setHeader( QNetworkRequest::ContentTypeHeader, "applicaion/json" ); + auto reply = mgr->post( request, postData.toUtf8() ); + connect( reply, + &QNetworkReply::errorOccurred, + this, + [ this ]( QNetworkReply::NetworkError e ) + { + qWarning() << e; + emit this->errorText( tr( "anki: post to anki failed" ) ); + } ); +} + +void AnkiConnector::finishedSlot( QNetworkReply * reply ) +{ + if( reply->error() == QNetworkReply::NoError ) + { + QByteArray bytes = reply->readAll(); + QJsonDocument json = QJsonDocument::fromJson( bytes ); + auto obj = json.object(); + if( obj.size() != 2 || !obj.contains( "error" ) || !obj.contains( "result" ) || + obj[ "result" ].toString().isEmpty() ) + { + emit errorText( QObject::tr( "anki: post to anki failed" ) ); + } + QString result = obj[ "result" ].toString(); + + qDebug() << "anki result:" << result; + + emit errorText( tr( "anki: post to anki success" ) ); + } + else + { + qDebug() << "anki connect error" << reply->errorString(); + emit errorText( "anki:" + reply->errorString() ); + } + + reply->deleteLater(); +} diff --git a/ankiconnector.h b/ankiconnector.h new file mode 100644 index 00000000..e52a0f80 --- /dev/null +++ b/ankiconnector.h @@ -0,0 +1,28 @@ +#ifndef ANKICONNECTOR_H +#define ANKICONNECTOR_H + +#include "config.hh" + +#include +#include +#include + +class AnkiConnector : public QObject +{ + Q_OBJECT +public: + explicit AnkiConnector( QObject * parent, Config::Class const & cfg ); + + void sendToAnki( QString const & word, QString const & text ); + +private: + QNetworkAccessManager * mgr; + Config::Class const & cfg; +public : +signals: + void errorText( QString const & ); +private slots: + void finishedSlot(QNetworkReply * reply); +}; + +#endif // ANKICONNECTOR_H diff --git a/article-style-st-lingoes.css b/article-style-st-lingoes.css index fdae35d1..455aa75b 100644 --- a/article-style-st-lingoes.css +++ b/article-style-st-lingoes.css @@ -95,7 +95,7 @@ body .gddictnamebodyseparator { - display: inline-block; - width: 100%; + clear: both; border-top: 1px solid #92b0dd; + margin-bottom: 1em; } diff --git a/article-style-st-lingvo.css b/article-style-st-lingvo.css index 8da59179..20974124 100644 --- a/article-style-st-lingvo.css +++ b/article-style-st-lingvo.css @@ -24,24 +24,19 @@ a:hover background: white; } -/* Dictionary's name heading */ -.gddictname -{ - border: 1px dotted black; padding: 0.2em; padding-left: 0.5em; - margin-top: 1.2em; margin-bottom: 0.1em; font-weight: bold; font-size: 14px; - background: #87CEEB; -} - /* The 'From ' string which preceeds dictionary name in the heading */ .gdfromprefix { display: none; } +/* Dictionary's name heading */ .gddictname { + padding: 0.2em; padding-left: 0.5em; + margin-bottom: 0.1em; + font-size: 14px; font-weight: normal; - float: right; border: 1px solid white; margin-top: 7px; diff --git a/article-style.css b/article-style.css index 16ce7887..4f2f871e 100644 --- a/article-style.css +++ b/article-style.css @@ -1,10 +1,14 @@ /******** Global, non-dictionary-specific classes ***********/ +html { + height: 100%; +} body { background: #fefdeb; font-family: Tahoma, Verdana, "Lucida Sans Unicode", sans-serif; + height: 100%; } /* This stylesheet is used to highligh current selection when doing a search. @@ -38,6 +42,16 @@ pre /*background: #ffffdd;*/ } +.gddicttitle +{ + user-select: none; +} + +.gddictnamebodyseparator +{ + clear: both; +} + /* The 'From ' string which precedes dictionary name in the heading */ .gdfromprefix { @@ -55,8 +69,8 @@ pre background: #fefdeb; /*fix for invalid blg*/ font-style:normal; - content-visibility:auto; - contain-intrinsic-size:400px; + content-visibility: auto; + contain-intrinsic-size: 600px; } /* CSS trick to prevent the floating elements to overflow @@ -522,7 +536,7 @@ div.xdxf /************* MDict dictionaries **************/ .mdict { - margin-top: 1em; + } .mdict a[name] @@ -914,7 +928,6 @@ div.xdxf left: 0px; width: 0px; background-color: white; - background-color: Window; border-style: solid; border-color: #AAAAAA; border-width: 1px; @@ -934,9 +947,7 @@ div.xdxf .mwiki td.os-suggest-result, .mwiki td.os-suggest-result-hl { white-space: nowrap; background-color: white; - background-color: Window; color: black; - color: WindowText; padding: 2px; } .mwiki td.os-suggest-result-hl, @@ -944,12 +955,6 @@ div.xdxf background-color: #4C59A6; color: white; } -.mwiki td.os-suggest-result-hl { - /* System colors are misimplemented in Safari 3.0 and earlier, - making highlighted text illegible... */ - background-color: Highlight; - color: HighlightText; -} .mwiki .os-suggest-toggle { position: relative; diff --git a/article_inspect.cpp b/article_inspect.cpp index ff296787..aa20032a 100644 --- a/article_inspect.cpp +++ b/article_inspect.cpp @@ -3,15 +3,15 @@ #if (QT_VERSION > QT_VERSION_CHECK(6,0,0)) #include #endif -ArticleInspector::ArticleInspector( QWidget * parent ) : QWidget( parent, Qt::WindowType::Window ) +ArticleInspector::ArticleInspector( QWidget * parent ) : QWidget( parent, Qt::WindowType::Window ),firstTimeOpened(false) { setWindowTitle(tr("Inspect")); setAttribute( Qt::WidgetAttribute::WA_DeleteOnClose, false ); QVBoxLayout * v = new QVBoxLayout( this ); v->setSpacing( 0 ); v->setContentsMargins( 0, 0, 0, 0 ); - inspectView = new QWebEngineView( this ); - v->addWidget( inspectView ); + viewContainer = new QWebEngineView( this ); + v->addWidget( viewContainer ); resize(800,600); } @@ -19,22 +19,26 @@ ArticleInspector::ArticleInspector( QWidget * parent ) : QWidget( parent, Qt::Wi void ArticleInspector::setInspectPage( QWebEngineView * view ) { auto page=view->page(); - this->inspectedPage = page; - page->setDevToolsPage( inspectView->page() ); + viewContainer->page()->setInspectedPage(page); #if( QT_VERSION > QT_VERSION_CHECK( 6, 0, 0 ) ) // without this line, application will crash on qt6.2 ,see https://bugreports.qt.io/browse/QTBUG-101724 - if( view->lastContextMenuRequest() ) + if( view->lastContextMenuRequest() && firstTimeOpened ) { page->triggerAction( QWebEnginePage::InspectElement ); } + if( !firstTimeOpened ) + { + firstTimeOpened = true; + } #else page->triggerAction( QWebEnginePage::InspectElement ); #endif + raise(); show(); } void ArticleInspector::closeEvent( QCloseEvent * ) { - inspectedPage->setDevToolsPage( nullptr ); + viewContainer->page()->setInspectedPage(nullptr); } diff --git a/article_inspect.h b/article_inspect.h index a99d1ff2..7be7c7a6 100644 --- a/article_inspect.h +++ b/article_inspect.h @@ -9,14 +9,15 @@ class ArticleInspector : public QWidget { Q_OBJECT - QWebEngineView * inspectView = nullptr; - QWebEnginePage * inspectedPage = nullptr; + QWebEngineView * viewContainer = nullptr; public: ArticleInspector( QWidget * parent = nullptr ); void setInspectPage( QWebEngineView * view); private: - + //used to record if the devtool was first time opened. + //if right click on the webpage and open inspect page on the first time ,the application has great possiblity to hang forever. + bool firstTimeOpened; virtual void closeEvent( QCloseEvent * ); }; diff --git a/article_maker.cc b/article_maker.cc index 5df14417..41f97422 100644 --- a/article_maker.cc +++ b/article_maker.cc @@ -465,7 +465,7 @@ void ArticleRequest::altSearchFinished() if ( altSearches.empty() ) { #ifdef QT_DEBUG - qDebug( "alts finished\n" ); + qDebug( "alts finished" ); #endif // They all've finished! Now we can look up bodies @@ -534,7 +534,7 @@ void ArticleRequest::bodyFinished() if ( bodyDone ) return; - GD_DPRINTF( "some body finished\n" ); + GD_DPRINTF( "some body finished" ); bool wasUpdated = false; @@ -546,7 +546,7 @@ void ArticleRequest::bodyFinished() { // Good - GD_DPRINTF( "one finished.\n" ); + GD_DPRINTF( "one finished." ); Dictionary::DataRequest & req = *bodyRequests.front(); @@ -672,13 +672,13 @@ void ArticleRequest::bodyFinished() foundAnyDefinitions = true; } - GD_DPRINTF( "erasing..\n" ); + GD_DPRINTF( "erasing.." ); bodyRequests.pop_front(); - GD_DPRINTF( "erase done..\n" ); + GD_DPRINTF( "erase done.." ); } else { - GD_DPRINTF( "one not finished.\n" ); + GD_DPRINTF( "one not finished." ); break; } } diff --git a/article_netmgr.cc b/article_netmgr.cc index e63ebfff..e0e23650 100644 --- a/article_netmgr.cc +++ b/article_netmgr.cc @@ -8,6 +8,7 @@ #include "gddebug.hh" #include "utils.hh" #include +#include "globalbroadcaster.h" using std::string; @@ -131,22 +132,11 @@ using std::string; emit errorOccurred( code ); } -// void AllowFrameReply::readDataFromBase() -// { -//// QByteArray data; -//// data.resize( baseReply->bytesAvailable() ); -//// baseReply->read( data.data(), data.size() ); -//// buffer += data; -// emit readyRead(); -// } - qint64 AllowFrameReply::readData( char * data, qint64 maxSize ) { auto bytesAvailable= baseReply->bytesAvailable(); qint64 size = qMin( maxSize, bytesAvailable ); baseReply->read( data, size ); -// memcpy( data, buffer.data(), size ); -// buffer.remove( 0, size ); return size; } @@ -209,11 +199,8 @@ 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() ) && - getHostBase( url ) != getHostBase( refererUrl ) && !url.scheme().startsWith("data") ) + getHostBaseFromUrl( url ) != getHostBaseFromUrl( refererUrl ) && !url.scheme().startsWith("data") ) { gdWarning( "Blocking element \"%s\" due to not same domain", url.toEncoded().data() ); @@ -265,9 +252,9 @@ QNetworkReply * ArticleNetworkAccessManager::getArticleReply( QNetworkRequest co sptr< Dictionary::DataRequest > ArticleNetworkAccessManager::getResource( QUrl const & url, QString & contentType ) { - GD_DPRINTF( "getResource: %ls\n", url.toString().toStdWString().c_str() ); - GD_DPRINTF( "scheme: %ls\n", url.scheme().toStdWString().c_str() ); - GD_DPRINTF( "host: %ls\n", url.host().toStdWString().c_str() ); + GD_DPRINTF( "getResource: %ls", url.toString().toStdWString().c_str() ); + GD_DPRINTF( "scheme: %ls", url.scheme().toStdWString().c_str() ); + GD_DPRINTF( "host: %ls", url.host().toStdWString().c_str() ); if ( url.scheme() == "gdlookup" ) { @@ -460,7 +447,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 { @@ -468,7 +455,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; @@ -526,6 +513,16 @@ void LocalSchemeHandler::requestStarted(QWebEngineUrlRequestJob *requestJob) QNetworkRequest request; request.setUrl( url ); + //all the url reached here must be either gdlookup or bword scheme. + auto [valid, word] = Utils::Url::getQueryWord( url ); + // or the condition can be (!queryWord.first || word.isEmpty()) + // ( queryWord.first && word.isEmpty() ) is only part of the above condition. + if( valid && word.isEmpty() ) + { + // invalid gdlookup url. + return; + } + QNetworkReply * reply = this->mManager.getArticleReply( request ); connect( reply, &QNetworkReply::finished, requestJob, [ = ]() { requestJob->reply( "text/html", reply ); } ); connect( requestJob, &QObject::destroyed, reply, &QObject::deleteLater ); diff --git a/articleview.cc b/articleview.cc index d8382f98..82bcc868 100644 --- a/articleview.cc +++ b/articleview.cc @@ -325,6 +325,7 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm, Au settings->defaultSettings()->setAttribute( QWebEngineSettings::PluginsEnabled, cfg.preferences.enableWebPlugins ); settings->defaultSettings()->setAttribute( QWebEngineSettings::PlaybackRequiresUserGesture, false ); settings->defaultSettings()->setAttribute( QWebEngineSettings::JavascriptCanAccessClipboard, true ); + settings->defaultSettings()->setAttribute( QWebEngineSettings::PrintElementBackgrounds, false ); #else settings->setAttribute( QWebEngineSettings::LocalContentCanAccessRemoteUrls, true ); settings->setAttribute( QWebEngineSettings::LocalContentCanAccessFileUrls, true ); @@ -332,6 +333,7 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm, Au settings->setAttribute( QWebEngineSettings::PluginsEnabled, cfg.preferences.enableWebPlugins ); settings->setAttribute( QWebEngineSettings::PlaybackRequiresUserGesture, false ); settings->setAttribute( QWebEngineSettings::JavascriptCanAccessClipboard, true ); + settings->setAttribute( QWebEngineSettings::PrintElementBackgrounds, false ); #endif expandOptionalParts = cfg.preferences.alwaysExpandOptionalParts; @@ -348,6 +350,11 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm, Au channel = new QWebChannel(ui.definition->page()); agent = new ArticleViewAgent(this); attachWebChannelToHtml(); + ankiConnector = new AnkiConnector( this, cfg ); + connect( ankiConnector, + &AnkiConnector::errorText, + this, + [ this ]( QString const & errorText ) { emit statusBarMessage( errorText ); } ); } // explicitly report the minimum size, to avoid @@ -395,6 +402,12 @@ 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 ); + delayedHighlightText.clear(); + } + Contexts::Iterator pos = contexts.find( "gdanchor" ); if( pos != contexts.end() ) { @@ -422,8 +435,7 @@ void ArticleView::showDefinition( Config::InputPhrase const & phrase, unsigned g if ( mutedDicts.size() ) Utils::Url::addQueryItem( req, "muted", mutedDicts ); - // Update both histories (pages history and headwords history) - saveHistoryUserData(); + // Update headwords history emit sendWordToHistory( phrase.phrase ); // Any search opened is probably irrelevant now @@ -434,7 +446,7 @@ void ArticleView::showDefinition( Config::InputPhrase const & phrase, unsigned g emit setExpandMode( expandOptionalParts ); - ui.definition->load( req ); + load( req ); //QApplication::setOverrideCursor( Qt::WaitCursor ); ui.definition->setCursor( Qt::WaitCursor ); @@ -472,8 +484,7 @@ void ArticleView::showDefinition( QString const & word, QStringList const & dict if( ignoreDiacritics ) Utils::Url::addQueryItem( req, "ignore_diacritics", "1" ); - // Update both histories (pages history and headwords history) - saveHistoryUserData(); + // Update headwords history emit sendWordToHistory( word ); // Any search opened is probably irrelevant now @@ -484,11 +495,15 @@ void ArticleView::showDefinition( QString const & word, QStringList const & dict emit setExpandMode( expandOptionalParts ); - ui.definition->load( req ); + load( req ); ui.definition->setCursor( Qt::WaitCursor ); } +void ArticleView::sendToAnki(QString const & word, QString const & text ){ + ankiConnector->sendToAnki(word,text); +} + void ArticleView::showAnticipation() { ui.definition->setHtml( "" ); @@ -520,13 +535,6 @@ void ArticleView::loadFinished( bool result ) // Expand collapsed article if only one loaded ui.definition->page()->runJavaScript( QString( "gdCheckArticlesNumber();" ) ); - // Jump to current article after page reloading - if( !articleToJump.isEmpty() ) - { - setCurrentArticle( articleToJump, true ); - articleToJump.clear(); - } - if( !Utils::Url::queryItemValue( url, "gdanchor" ).isEmpty() ) { QString anchor = QUrl::fromPercentEncoding( Utils::Url::encodedQueryItemValue( url, "gdanchor" ) ); @@ -641,18 +649,18 @@ void ArticleView::jumpToDictionary( QString const & id, bool force ) } } -void ArticleView::setCurrentArticle( QString const & id, bool moveToIt ) +bool ArticleView::setCurrentArticle( QString const & id, bool moveToIt ) { if ( !isScrollTo( id ) ) - return; // Incorrect id + return false; // Incorrect id if ( !ui.definition->isVisible() ) - return; // No action on background page, scrollIntoView there don't work + return false; // No action on background page, scrollIntoView there don't work if(moveToIt){ QString dictId = id.mid( 7 ); if( dictId.isEmpty() ) - return; + return false; QString script = QString( "var elem=document.getElementById('%1'); " "if(elem!=undefined){elem.scrollIntoView(true);} gdMakeArticleActive('%2',true);" ) .arg( id, dictId ); @@ -660,6 +668,7 @@ void ArticleView::setCurrentArticle( QString const & id, bool moveToIt ) ui.definition->page()->runJavaScript( script ); setActiveArticleId( dictId ); } + return true; } void ArticleView::selectCurrentArticle() @@ -696,45 +705,26 @@ void ArticleView::tryMangleWebsiteClickedUrl( QUrl & url, Contexts & contexts ) { if( framed ) { - // QVariant result = runJavaScriptSync( ui.definition->page(), "gdLastUrlText" ); - QVariant result; - - if( result.type() == QVariant::String ) - { - // Looks this way - contexts[ dictionaryIdFromScrollTo( ca ) ] = QString::fromLatin1( url.toEncoded() ); - - QUrl target; - - QString queryWord = result.toString(); - - // Empty requests are treated as no request, so we work this around by - // adding a space. - if( queryWord.isEmpty() ) - queryWord = " "; - - target.setScheme( "gdlookup" ); - target.setHost( "localhost" ); - target.setPath( "/" + queryWord ); - - url = target; - } + // no need to translate website internal url to gd builtin url + // and lack the formulation to convert them. + qDebug() << "in the website with url:" << url; } } ); } } -void ArticleView::updateCurrentArticleFromCurrentFrame( QWebEnginePage * frame ,QPoint * point) -{ - -} - void ArticleView::saveHistoryUserData() { ui.definition->setProperty("sx", ui.definition->page()->scrollPosition().x()); ui.definition->setProperty("sy", ui.definition->page()->scrollPosition().y()); } +void ArticleView::load( QUrl const & url ) +{ + saveHistoryUserData(); + ui.definition->load( url ); +} + void ArticleView::cleanupTemp() { QSet< QString >::iterator it = desktopOpenedTempFiles.begin(); @@ -1088,8 +1078,6 @@ void ArticleView::linkClicked( QUrl const & url_ ) if( kmod & Qt::AltModifier ) return; - updateCurrentArticleFromCurrentFrame(); - QUrl url( url_ ); Contexts contexts; @@ -1122,22 +1110,29 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, audioPlayer->stop(); qDebug() << "open link url:" << url; + auto [valid, word] = Utils::Url::getQueryWord( url ); + if( valid && word.isEmpty() ) + { + // invalid gdlookup url. + return; + } + Contexts contexts( contexts_ ); if( url.scheme().compare( "gdpicture" ) == 0 ) - ui.definition->load( url ); + load( url ); else - if ( url.scheme().compare( "bword" ) == 0 ) + if ( url.scheme().compare( "bword" ) == 0 || url.scheme().compare( "entry" ) == 0 ) { if( Utils::Url::hasQueryItem( ref, "dictionaries" ) ) { QStringList dictsList = Utils::Url::queryItemValue( ref, "dictionaries" ) .split( ",", Qt::SkipEmptyParts ); - showDefinition( url.path(), dictsList, QRegExp(), getGroup( ref ), false ); + showDefinition( word, dictsList, QRegExp(), getGroup( ref ), false ); } else - showDefinition( url.path(), + showDefinition( word, getGroup( ref ), scrollTo, contexts ); } else @@ -1160,16 +1155,6 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, return; } - QString word; - - if( Utils::Url::hasQueryItem( url, "word" ) ) - { - word=Utils::Url::queryItemValue (url,"word"); - } - else{ - word=url.path ().mid (1); - } - QString newScrollTo( scrollTo ); if( Utils::Url::hasQueryItem( url, "dict" ) ) { @@ -1583,9 +1568,7 @@ void ArticleView::updateMutedContents() if ( mutedDicts.size() ) Utils::Url::addQueryItem( currentUrl, "muted", mutedDicts ); - saveHistoryUserData(); - - ui.definition->load( currentUrl ); + load( currentUrl ); //QApplication::setOverrideCursor( Qt::WaitCursor ); ui.definition->setCursor( Qt::WaitCursor ); @@ -1609,6 +1592,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 @@ -1626,9 +1614,14 @@ void ArticleView::forward() ui.definition->forward(); } +void ArticleView::reload() +{ + ui.definition->reload(); +} + void ArticleView::hasSound( const std::function< void( bool ) > & callback ) { - ui.definition->page()->runJavaScript( "gdAudioLinks.first", + ui.definition->page()->runJavaScript( "if(typeof(gdAudioLinks)!=\"undefined\") gdAudioLinks.first", [ callback ]( const QVariant & v ) { bool has = false; @@ -1713,14 +1706,13 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) { // Is that a link? Is there a selection? QWebEnginePage* r=ui.definition->page(); - updateCurrentArticleFromCurrentFrame(ui.definition->page(), const_cast(& pos)); - QMenu menu( this ); QAction * followLink = 0; QAction * followLinkExternal = 0; QAction * followLinkNewTab = 0; QAction * lookupSelection = 0; + QAction * sendToAnkiAction = 0 ; QAction * lookupSelectionGr = 0; QAction * lookupSelectionNewTab = 0; QAction * lookupSelectionNewTabGr = 0; @@ -1730,6 +1722,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()); @@ -1788,7 +1781,7 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) } QString selectedText = ui.definition->selectedText(); - QString text = selectedText.trimmed(); + QString text = Utils::trimNonChar( selectedText ); if ( text.size() && text.size() < 60 ) { @@ -1850,6 +1843,21 @@ void ArticleView::contextMenuRequested( QPoint const & pos ) } } + if(text.size()) + { + // 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 ); + } + + // add anki menu + if( !text.isEmpty() && cfg.preferences.ankiConnectServer.enabled ) + { + QString txt = ui.definition->title(); + sendToAnkiAction = new QAction( tr( "&Send \"%1\" to anki with selected text." ).arg( txt ), &menu ); + menu.addAction( sendToAnkiAction ); + } + if( text.isEmpty() && !cfg.preferences.storeHistory) { QString txt = ui.definition->title(); @@ -1941,7 +1949,15 @@ 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( text.left( 60 ) ); + } + else if( result == sendToAnkiAction ) + { + sendToAnki( ui.definition->title(), ui.definition->selectedText() ); + } else if ( result == lookupSelectionGr && groupComboBox ) showDefinition( selectedText, groupComboBox->getCurrentGroup(), QString() ); @@ -2339,42 +2355,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() @@ -2440,25 +2458,12 @@ void ArticleView::showEvent( QShowEvent * ev ) void ArticleView::receiveExpandOptionalParts( bool expand ) { if( expandOptionalParts != expand ) - { - int n = getArticlesList().indexOf( getActiveArticleId() ); - if( n > 0 ) - articleToJump = getCurrentArticle(); - - emit setExpandMode( expand ); - expandOptionalParts = expand; - reload(); - } + switchExpandOptionalParts(); } void ArticleView::switchExpandOptionalParts() { expandOptionalParts = !expandOptionalParts; - - int n = getArticlesList().indexOf( getActiveArticleId() ); - if( n > 0 ) - articleToJump = getCurrentArticle(); - emit setExpandMode( expandOptionalParts ); reload(); } @@ -2498,6 +2503,12 @@ void ArticleView::highlightFTSResults() else regString = regString.remove( AccentMarkHandler::accentMark() ); + //
watchout
to plainText will return "watchout". + //if application goes here,that means the article text must contains the search text. + //whole word match regString will contain \b . can not match the above senario. + //workaround ,remove \b from the regstring="(\bwatch\b)" + regString.remove( QRegularExpression( "\\\\b" ) ); + QRegularExpression regexp; if( Utils::Url::hasQueryItem( url, "wildcards" ) ) regexp.setPattern( wildcardsToRegexp( regString ) ); @@ -2548,17 +2559,15 @@ void ArticleView::highlightFTSResults() if( ftsSearchMatchCase ) flags |= QWebEnginePage::FindCaseSensitively; - for( int x = 0; x < allMatches.size(); x++ ) - ui.definition->findText( allMatches.at( x ), flags ); - if( !allMatches.isEmpty() ) { +// highlightAllFtsOccurences( flags ); ui.definition->findText( allMatches.at( 0 ), flags ); // if( ui.definition->findText( allMatches.at( 0 ), flags ) ) - { - ui.definition->page()->runJavaScript( - QString( "%1=window.getSelection().getRangeAt(0);_=0;" ).arg( rangeVarName ) ); - } + // { + // ui.definition->page()->runJavaScript( + // QString( "%1=window.getSelection().getRangeAt(0);_=0;" ).arg( rangeVarName ) ); + // } } ui.ftsSearchFrame->show(); @@ -2569,6 +2578,25 @@ void ArticleView::highlightFTSResults() } ); } +void ArticleView::highlightAllFtsOccurences( QWebEnginePage::FindFlags flags ) +{ + // Usually allMatches contains mostly duplicates. Thus searching for each element of + // allMatches to highlight them takes a long time => collect unique elements into a + // set and search for them instead. + // Don't use QList::toSet() or QSet's range constructor because they reserve space + // for QList::size() elements, whereas the final QSet size is likely 1 or 2. + QSet< QString > uniqueMatches; + for( int x = 0; x < allMatches.size(); ++x ) + { + QString const & match = allMatches.at( x ); + // Consider words that differ only in case equal if the search is case-insensitive. + uniqueMatches.insert( ftsSearchMatchCase ? match : match.toLower() ); + } + + for( QSet< QString >::const_iterator it = uniqueMatches.constBegin(); it != uniqueMatches.constEnd(); ++it ) + ui.definition->findText( *it, flags ); +} + void ArticleView::setActiveDictIds(ActiveDictIds ad) { // ignore all other signals. qDebug() << "receive dicts, current word:" << currentWord << ad.word << ":" << ad.dictIds; @@ -2651,9 +2679,9 @@ void ArticleView::performFtsFindOperation( bool backwards ) } #endif // Store new highlighted selection - ui.definition->page()-> - runJavaScript( QString( "%1=window.getSelection().getRangeAt(0);_=0;" ) - .arg( rangeVarName ) ); + // ui.definition->page()-> + // runJavaScript( QString( "%1=window.getSelection().getRangeAt(0);_=0;" ) + // .arg( rangeVarName ) ); } void ArticleView::on_ftsSearchPrevious_clicked() diff --git a/articleview.hh b/articleview.hh index 921d640f..28a1133a 100644 --- a/articleview.hh +++ b/articleview.hh @@ -19,6 +19,7 @@ #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) #include #endif +#include "ankiconnector.h" class ResourceToSaveHandler; class ArticleViewAgent ; @@ -39,13 +40,14 @@ class ArticleView: public QFrame ArticleViewAgent * agent; Ui::ArticleView ui; + AnkiConnector * ankiConnector; + QAction pasteAction, articleUpAction, articleDownAction, goBackAction, goForwardAction, selectCurrentArticleAction, copyAsTextAction, inspectAction; QAction & openSearchAction; bool searchIsOpened; bool expandOptionalParts; - QString articleToJump; QString rangeVarName; /// Any resource we've decided to download off the dictionary gets stored here. @@ -76,9 +78,11 @@ class ArticleView: public QFrame bool ftsSearchIsOpened, ftsSearchMatchCase; int ftsPosition; + QString delayedHighlightText; + void highlightFTSResults(); + void highlightAllFtsOccurences( QWebEnginePage::FindFlags flags ); void performFtsFindOperation( bool backwards ); - void showFindButtons(); public: /// The popupView flag influences contents of the context menus to be @@ -129,6 +133,7 @@ public: QRegExp const & searchRegExp, unsigned group, bool ignoreDiacritics ); + void sendToAnki(QString const & word, QString const & text ); /// Clears the view and sets the application-global waiting cursor, /// which will be restored when some article loads eventually. void showAnticipation(); @@ -153,6 +158,8 @@ public: /// Called when preference changes void setSelectionBySingleClick( bool set ); + void setDelayedHighlightText(QString const & text); + public slots: /// Goes back in history @@ -168,8 +175,7 @@ public slots: public: /// Reloads the view - void reload() - { ui.definition->reload(); } + void reload(); /// Returns true if there's an audio reference on the page, false otherwise. void hasSound( const std::function< void( bool has ) > & callback ); @@ -223,6 +229,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 ); @@ -281,6 +291,8 @@ signals: void inspectSignal(QWebEngineView * view); + void saveBookmarkSignal( const QString & bookmark ); + public slots: void on_searchPrevious_clicked(); @@ -360,7 +372,8 @@ private: /// Sets the current article by executing a javascript code. /// If moveToIt is true, it moves the focus to it as well. - void setCurrentArticle( QString const &, bool moveToIt = false ); + /// Returns true in case of success, false otherwise. + bool setCurrentArticle( QString const &, bool moveToIt = false ); /// Checks if the given article in form of "gdfrom-xxx" is inside a "website" /// frame. @@ -375,14 +388,13 @@ private: /// url to the appropriate "contexts" entry. void tryMangleWebsiteClickedUrl( QUrl & url, Contexts & contexts ); - /// Use the known information about the current frame to update the current - /// article's value. - void updateCurrentArticleFromCurrentFrame( QWebEnginePage * frame = 0 ,QPoint * point=0); - /// Saves current article and scroll position for the current history item. /// Should be used when leaving the page. void saveHistoryUserData(); + /// Loads a page at @p url into view. + void load( QUrl const & url ); + /// Attempts removing last temporary file created. void cleanupTemp(); @@ -390,7 +402,6 @@ private: void performFindOperation( bool restart, bool backwards, bool checkHighlight = false ); - bool findText(QString& text, const QWebEnginePage::FindFlags& f); void reloadStyleSheet(); diff --git a/articlewebpage.cpp b/articlewebpage.cpp index 9b782f8d..bf686114 100644 --- a/articlewebpage.cpp +++ b/articlewebpage.cpp @@ -1,15 +1,38 @@ #include "articlewebpage.h" +#include "utils.hh" ArticleWebPage::ArticleWebPage(QObject *parent) : QWebEnginePage{parent} { } -bool ArticleWebPage::acceptNavigationRequest( const QUrl & url, NavigationType type, bool isMainFrame ) +bool ArticleWebPage::acceptNavigationRequest( const QUrl & resUrl, NavigationType type, bool isMainFrame ) { + QUrl url = resUrl; + if( url.scheme() == "bword" || url.scheme() == "entry" ) + { + url.setScheme( "gdlookup" ); + url.setHost( "localhost" ); + url.setPath( "" ); + auto [ valid, word ] = Utils::Url::getQueryWord( resUrl ); + Utils::Url::addQueryItem( url, "word", word ); + Utils::Url::addQueryItem( url, "group", lastReq.group ); + Utils::Url::addQueryItem( url, "muted", lastReq.mutedDicts ); + setUrl( url ); + return false; + } + + //save current gdlookup's values. + if( url.scheme() == "gdlookup" ) + { + lastReq.group = Utils::Url::queryItemValue( url, "group" ); + lastReq.mutedDicts = Utils::Url::queryItemValue( url, "muted" ); + } + if( type == QWebEnginePage::NavigationTypeLinkClicked ) { emit linkClicked( url ); return true; } + return QWebEnginePage::acceptNavigationRequest( url, type, isMainFrame ); } diff --git a/articlewebpage.h b/articlewebpage.h index 3e1d32d6..19027148 100644 --- a/articlewebpage.h +++ b/articlewebpage.h @@ -3,6 +3,11 @@ #include +struct LastReqInfo{ + QString group; + QString mutedDicts; +}; + class ArticleWebPage : public QWebEnginePage { Q_OBJECT @@ -12,6 +17,8 @@ signals: void linkClicked( const QUrl & url ); protected: virtual bool acceptNavigationRequest( const QUrl & url, NavigationType type, bool isMainFrame ); +private: + LastReqInfo lastReq; }; #endif // ARTICLEWEBPAGE_H diff --git a/base/globalregex.cc b/base/globalregex.cc new file mode 100644 index 00000000..a9a6f8fc --- /dev/null +++ b/base/globalregex.cc @@ -0,0 +1,58 @@ +#include "globalregex.hh" +#include "fulltextsearch.hh" + +using namespace RX; + +QRegularExpression Ftx::regBrackets( + "(\\([\\w\\p{M}]+\\)){0,1}([\\w\\p{M}]+)(\\([\\w\\p{M}]+\\)){0,1}([\\w\\p{M}]+){0,1}(\\([\\w\\p{M}]+\\)){0,1}", + QRegularExpression::UseUnicodePropertiesOption ); +QRegularExpression Ftx::regSplit( "[^\\w\\p{M}]+", QRegularExpression::UseUnicodePropertiesOption ); + +QRegularExpression Ftx::spacesRegExp( "\\W+", QRegularExpression::UseUnicodePropertiesOption ); +QRegularExpression Ftx::wordRegExp( QString( "\\w{" ) + QString::number( FTS::MinimumWordSize ) + ",}", + QRegularExpression::UseUnicodePropertiesOption ); +QRegularExpression Ftx::setsRegExp( "\\[[^\\]]+\\]", QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Ftx::regexRegExp( "\\\\[afnrtvdDwWsSbB]|\\\\x([0-9A-Fa-f]{4})|\\\\0([0-7]{3})", + QRegularExpression::CaseInsensitiveOption ); + +QRegularExpression Ftx::handleRoundBracket( "[^\\w\\(\\)\\p{M}]+" , + QRegularExpression::UseUnicodePropertiesOption ); +QRegularExpression Ftx::noRoundBracket( "[^\\w\\p{M}]+", + QRegularExpression::UseUnicodePropertiesOption ); + + +//mdx + +QRegularExpression Mdx::allLinksRe( "(?:<\\s*(a(?:rea)?|img|link|script|source)(?:\\s+[^>]+|\\s*)>)", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::wordCrossLink( "([\\s\"']href\\s*=)\\s*([\"'])entry://([^>#]*?)((?:#[^>]*?)?)\\2", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::anchorIdRe( "([\\s\"'](?:name|id)\\s*=)\\s*([\"'])\\s*(?=\\S)", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::anchorIdReWord( "([\\s\"'](?:name|id)\\s*=)\\s*([\"'])\\s*(?=\\S)([^\"]*)", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::anchorIdRe2( "([\\s\"'](?:name|id)\\s*=)\\s*(?=[^\"'])([^\\s\">]+)", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::anchorLinkRe( "([\\s\"']href\\s*=\\s*[\"'])entry://#", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::audioRe( "([\\s\"']href\\s*=)\\s*([\"'])sound://([^\">]+)\\2", + QRegularExpression::CaseInsensitiveOption + | QRegularExpression::InvertedGreedinessOption ); +QRegularExpression Mdx::stylesRe( "([\\s\"']href\\s*=)\\s*([\"'])(?!\\s*\\b(?:(?:bres|https?|ftp)://" + "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\">]+)\\2", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::stylesRe2( "([\\s\"']href\\s*=)\\s*(?![\\s\"']|\\b(?:(?:bres|https?|ftp)://" + "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\\s\">]+)", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::inlineScriptRe( "<\\s*script(?:(?=\\s)(?:(?![\\s\"']src\\s*=)[^>])+|\\s*)>", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::closeScriptTagRe( "<\\s*/script\\s*>", QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::srcRe( "([\\s\"']src\\s*=)\\s*([\"'])(?!\\s*\\b(?:(?:bres|https?|ftp)://" + "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\">]+)\\2", + QRegularExpression::CaseInsensitiveOption ); +QRegularExpression Mdx::srcRe2( "([\\s\"']src\\s*=)\\s*(?![\\s\"']|\\b(?:(?:bres|https?|ftp)://" + "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\\s\">]+)", + QRegularExpression::CaseInsensitiveOption ); + +QRegularExpression Mdx::links( "url\\(\\s*(['\"]?)([^'\"]*)(['\"]?)\\s*\\)", + QRegularExpression::CaseInsensitiveOption ); diff --git a/base/globalregex.hh b/base/globalregex.hh new file mode 100644 index 00000000..783a2be8 --- /dev/null +++ b/base/globalregex.hh @@ -0,0 +1,44 @@ +#ifndef GLOBALREGEX_HH +#define GLOBALREGEX_HH + +#include + +namespace RX +{ +class Ftx +{ +public: + static QRegularExpression regBrackets; + static QRegularExpression regSplit; + static QRegularExpression spacesRegExp; + static QRegularExpression wordRegExp; + static QRegularExpression setsRegExp; + static QRegularExpression regexRegExp; + static QRegularExpression handleRoundBracket; + static QRegularExpression noRoundBracket; +}; + + +class Mdx +{ +public: + static QRegularExpression allLinksRe; + static QRegularExpression wordCrossLink; + static QRegularExpression anchorIdRe; + static QRegularExpression anchorIdReWord; + static QRegularExpression anchorIdRe2; + static QRegularExpression anchorLinkRe; + static QRegularExpression audioRe; + static QRegularExpression stylesRe; + static QRegularExpression stylesRe2; + static QRegularExpression inlineScriptRe; + static QRegularExpression closeScriptTagRe; + static QRegularExpression srcRe; + static QRegularExpression srcRe2; + + static QRegularExpression links; +}; + +} // namespace RX + +#endif // GLOBALREGEX_HH diff --git a/bgl.cc b/bgl.cc index 569c2916..6d692dbd 100644 --- a/bgl.cc +++ b/bgl.cc @@ -198,16 +198,16 @@ namespace BglDictionary( string const & id, string const & indexFile, string const & dictionaryFile ); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -249,7 +249,7 @@ namespace protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; private: @@ -302,7 +302,7 @@ namespace FTS_index_completed.ref(); } - void BglDictionary::loadIcon() throw() + void BglDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/btreeidx.cc b/btreeidx.cc index 39dad39a..3f55f051 100644 --- a/btreeidx.cc +++ b/btreeidx.cc @@ -1399,15 +1399,18 @@ void BtreeIndex::getHeadwordsFromOffsets( QList & offsets, for( unsigned i = 0; i < result.size(); i++ ) { uint32_t articleOffset = result.at(i).articleOffset; + QList::Iterator it = std::lower_bound( begOffsets, endOffsets, articleOffset ); - if( it!=offsets.end()) + if( it != offsets.end() && *it == articleOffset ) { if( isCancelled && Utils::AtomicInt::loadAcquire( *isCancelled ) ) return; - headwords.append( QString::fromUtf8( ( result[ i ].prefix + result[ i ].word ).c_str() ) ); + auto word = QString::fromUtf8( ( result[ i ].prefix + result[ i ].word ).c_str() ); + + headwords.append( word ); offsets.erase( it); begOffsets = offsets.begin(); endOffsets = offsets.end(); diff --git a/btreeidx.hh b/btreeidx.hh index deb3a6b6..a98e4841 100644 --- a/btreeidx.hh +++ b/btreeidx.hh @@ -155,7 +155,7 @@ public: BtreeDictionary( string const & id, vector< string > const & dictionaryFiles ); /// Btree-indexed dictionaries are usually a good source for compound searches. - virtual Dictionary::Features getFeatures() const throw() + virtual Dictionary::Features getFeatures() const noexcept { return Dictionary::SuitableForCompoundSearching; } /// This function does the search using the btree index. Derivatives usually diff --git a/chinese.cc b/chinese.cc index 400a0115..35a092f8 100644 --- a/chinese.cc +++ b/chinese.cc @@ -31,7 +31,7 @@ public: ~CharacterConversionDictionary(); std::vector< gd::wstring > getAlternateWritings( gd::wstring const & ) - throw(); + noexcept; }; CharacterConversionDictionary::CharacterConversionDictionary( std::string const & id, @@ -70,7 +70,7 @@ CharacterConversionDictionary::~CharacterConversionDictionary() } std::vector< gd::wstring > CharacterConversionDictionary::getAlternateWritings( gd::wstring const & str ) - throw() + noexcept { std::vector< gd::wstring > results; diff --git a/chunkedstorage.cc b/chunkedstorage.cc index aaf11e88..54a7c13c 100644 --- a/chunkedstorage.cc +++ b/chunkedstorage.cc @@ -4,6 +4,8 @@ #include "chunkedstorage.hh" #include #include +#include +#include namespace ChunkedStorage { @@ -134,25 +136,42 @@ char * Reader::getBlock( uint32_t address, vector< char > & chunk ) // Read and decompress the chunk { - file.seek( offsets[ chunkIdx ] ); + // file.seek( offsets[ chunkIdx ] ); + QMutexLocker _( &file.lock ); + auto bytes = file.map( offsets[ chunkIdx ], 8 ); + if( bytes == nullptr ) + throw mapFailed(); + auto qBytes = QByteArray::fromRawData( reinterpret_cast< char * >(bytes), 8 ); + QDataStream in( qBytes ); + in.setByteOrder( QDataStream::LittleEndian ); - uint32_t uncompressedSize = file.read< uint32_t >(); - uint32_t compressedSize = file.read< uint32_t >(); + uint32_t uncompressedSize; + uint32_t compressedSize; + in >> uncompressedSize >> compressedSize; + + file.unmap( bytes ); chunk.resize( uncompressedSize ); - vector< unsigned char > compressedData( compressedSize ); - - file.read( &compressedData.front(), compressedData.size() ); + // vector< unsigned char > compressedData( compressedSize ); + auto chunkDataBytes = file.map( offsets[ chunkIdx ] + 8, compressedSize ); + if( chunkDataBytes == nullptr ) + throw mapFailed(); + // file.read( &compressedData.front(), compressedData.size() ); + auto autoUnmap = qScopeGuard( + [ & ] { + file.unmap( chunkDataBytes ); + } ); + Q_UNUSED( autoUnmap ) unsigned long decompressedLength = chunk.size(); - if ( uncompress( (unsigned char *)&chunk.front(), - &decompressedLength, - &compressedData.front(), - compressedData.size() ) != Z_OK || - decompressedLength != chunk.size() ) + if( uncompress( (unsigned char *)&chunk.front(), &decompressedLength, chunkDataBytes, compressedSize ) != Z_OK + || decompressedLength != chunk.size() ) + { throw exFailedToDecompressChunk(); + } + } size_t offsetInChunk = address & 0xffFF; diff --git a/chunkedstorage.hh b/chunkedstorage.hh index b92c6aed..4132e30b 100644 --- a/chunkedstorage.hh +++ b/chunkedstorage.hh @@ -25,6 +25,7 @@ DEF_EX( Ex, "Chunked storage exception", std::exception ) DEF_EX( exFailedToCompressChunk, "Failed to compress a chunk", Ex ) DEF_EX( exAddressOutOfRange, "The given chunked address is out of range", Ex ) DEF_EX( exFailedToDecompressChunk, "Failed to decompress a chunk", Ex ) +DEF_EX( mapFailed, "Failed to map/unmap the file", Ex ) /// This class writes data blocks in chunks. class Writer diff --git a/config.cc b/config.cc index 2e926248..a86e8c7c 100644 --- a/config.cc +++ b/config.cc @@ -89,6 +89,10 @@ ProxyServer::ProxyServer(): enabled( false ), useSystemProxy( false ), type( Soc { } +AnkiConnectServer::AnkiConnectServer(): enabled( false ), host("127.0.0.1"), port( 8765 ) +{ +} + HotKey::HotKey(): modifiers( 0 ), key1( 0 ), key2( 0 ) { } @@ -937,6 +941,17 @@ Class load() c.preferences.proxyServer.systemProxyPassword = proxy.namedItem( "systemProxyPassword" ).toElement().text(); } + QDomNode ankiConnectServer = preferences.namedItem( "ankiConnectServer" ); + + if ( !ankiConnectServer.isNull() ) + { + c.preferences.ankiConnectServer.enabled = ( ankiConnectServer.toElement().attribute( "enabled" ) == "1" ); + c.preferences.ankiConnectServer.host = ankiConnectServer.namedItem( "host" ).toElement().text(); + c.preferences.ankiConnectServer.port = ankiConnectServer.namedItem( "port" ).toElement().text().toULong(); + c.preferences.ankiConnectServer.deck = ankiConnectServer.namedItem( "deck" ).toElement().text(); + c.preferences.ankiConnectServer.model = ankiConnectServer.namedItem( "model" ).toElement().text(); + } + if ( !preferences.namedItem( "checkForNewReleases" ).isNull() ) c.preferences.checkForNewReleases = ( preferences.namedItem( "checkForNewReleases" ).toElement().text() == "1" ); @@ -1872,6 +1887,32 @@ void save( Class const & c ) proxy.appendChild( opt ); } + //anki connect + { + QDomElement proxy = dd.createElement( "ankiConnectServer" ); + preferences.appendChild( proxy ); + + QDomAttr enabled = dd.createAttribute( "enabled" ); + enabled.setValue( c.preferences.ankiConnectServer.enabled ? "1" : "0" ); + proxy.setAttributeNode( enabled ); + + opt = dd.createElement( "host" ); + opt.appendChild( dd.createTextNode( c.preferences.ankiConnectServer.host ) ); + proxy.appendChild( opt ); + + opt = dd.createElement( "port" ); + opt.appendChild( dd.createTextNode( QString::number( c.preferences.ankiConnectServer.port ) ) ); + proxy.appendChild( opt ); + + opt = dd.createElement( "deck" ); + opt.appendChild( dd.createTextNode( c.preferences.ankiConnectServer.deck ) ); + proxy.appendChild( opt ); + + opt = dd.createElement( "model" ); + opt.appendChild( dd.createTextNode( c.preferences.ankiConnectServer.model ) ); + proxy.appendChild( opt ); + } + opt = dd.createElement( "checkForNewReleases" ); opt.appendChild( dd.createTextNode( c.preferences.checkForNewReleases ? "1" : "0" ) ); preferences.appendChild( opt ); @@ -2211,7 +2252,7 @@ QString getUserQtCssFileName() return getHomeDir().filePath( "qt-style.css" ); } -QString getProgramDataDir() throw() +QString getProgramDataDir() noexcept { if ( isPortableVersion() ) return QCoreApplication::applicationDirPath(); @@ -2223,12 +2264,12 @@ QString getProgramDataDir() throw() #endif } -QString getEmbedLocDir() throw() +QString getEmbedLocDir() noexcept { return ":/locale"; } -QString getLocDir() throw() +QString getLocDir() noexcept { if ( QDir( getProgramDataDir() ).cd( "locale" ) ) return getProgramDataDir() + "/locale"; @@ -2236,7 +2277,7 @@ QString getLocDir() throw() return QCoreApplication::applicationDirPath() + "/locale"; } -QString getHelpDir() throw() +QString getHelpDir() noexcept { if ( QDir( getProgramDataDir() ).cd( "help" ) ) return getProgramDataDir() + "/help"; @@ -2245,7 +2286,7 @@ QString getHelpDir() throw() } #ifdef MAKE_CHINESE_CONVERSION_SUPPORT -QString getOpenCCDir() throw() +QString getOpenCCDir() noexcept { #if defined( Q_OS_WIN ) if ( QDir( "opencc" ).exists() ) @@ -2264,7 +2305,7 @@ QString getOpenCCDir() throw() } #endif -bool isPortableVersion() throw() +bool isPortableVersion() noexcept { struct IsPortable { @@ -2279,7 +2320,7 @@ bool isPortableVersion() throw() return p.isPortable; } -QString getPortableVersionDictionaryDir() throw() +QString getPortableVersionDictionaryDir() noexcept { if ( isPortableVersion() ) return getProgramDataDir() + "/content"; @@ -2287,7 +2328,7 @@ QString getPortableVersionDictionaryDir() throw() return QString(); } -QString getPortableVersionMorphoDir() throw() +QString getPortableVersionMorphoDir() noexcept { if ( isPortableVersion() ) return getPortableVersionDictionaryDir() + "/morphology"; @@ -2307,7 +2348,7 @@ QString getStylesDir() return result.path() + QDir::separator(); } -QString getCacheDir() throw() +QString getCacheDir() noexcept { return isPortableVersion() ? portableHomeDirPath() + "/cache" #ifdef HAVE_X11 @@ -2317,7 +2358,7 @@ QString getCacheDir() throw() #endif } -QString getNetworkCacheDir() throw() +QString getNetworkCacheDir() noexcept { return getCacheDir() + "/network"; } diff --git a/config.hh b/config.hh index 774dbdef..06d21ccf 100644 --- a/config.hh +++ b/config.hh @@ -137,6 +137,18 @@ struct ProxyServer ProxyServer(); }; +struct AnkiConnectServer +{ + bool enabled; + + QString host; + unsigned port; + QString deck; + QString model; + + AnkiConnectServer(); +}; + // A hotkey -- currently qt modifiers plus one or two keys struct HotKey { @@ -329,6 +341,7 @@ struct Preferences QString audioPlaybackProgram; ProxyServer proxyServer; + AnkiConnectServer ankiConnectServer; bool checkForNewReleases; bool disallowContentFromOtherSites; @@ -795,40 +808,40 @@ QString getUserQtCssFileName() ; /// Returns the program's data dir. Under Linux that would be something like /// /usr/share/apps/goldendict, under Windows C:/Program Files/GoldenDict. -QString getProgramDataDir() throw(); +QString getProgramDataDir() noexcept; /// Returns the directory storing program localizized files (.qm). -QString getEmbedLocDir() throw(); -QString getLocDir() throw(); +QString getEmbedLocDir() noexcept; +QString getLocDir() noexcept; /// Returns the directory storing program help files (.qch). -QString getHelpDir() throw(); +QString getHelpDir() noexcept; #ifdef MAKE_CHINESE_CONVERSION_SUPPORT /// Returns the directory storing OpenCC configuration and dictionary files (.json and .ocd). -QString getOpenCCDir() throw(); +QString getOpenCCDir() noexcept; #endif /// Returns true if the program is configured as a portable version. In that /// mode, all the settings and indices are kept in the program's directory. -bool isPortableVersion() throw(); +bool isPortableVersion() noexcept; /// Returns directory with dictionaries for portable version. It is content/ /// in the application's directory. -QString getPortableVersionDictionaryDir() throw(); +QString getPortableVersionDictionaryDir() noexcept; /// Returns directory with morpgologies for portable version. It is /// content/morphology in the application's directory. -QString getPortableVersionMorphoDir() throw(); +QString getPortableVersionMorphoDir() noexcept; /// Returns the add-on styles directory. QString getStylesDir(); /// Returns the directory where user-specific non-essential (cached) data should be written. -QString getCacheDir() throw(); +QString getCacheDir() noexcept; /// Returns the article network disk cache directory. -QString getNetworkCacheDir() throw(); +QString getNetworkCacheDir() noexcept; } diff --git a/dictdfiles.cc b/dictdfiles.cc index f8607269..ab3fa777 100644 --- a/dictdfiles.cc +++ b/dictdfiles.cc @@ -100,19 +100,19 @@ public: ~DictdDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; inline virtual quint32 getLangFrom() const { return idxHeader.langFrom; } @@ -215,7 +215,7 @@ string nameFromFileName( string const & indexFileName ) return Utf8::encode( FsEncoding::decode( string( sep + 1, dot - sep - 1 ) ) ); } -void DictdDictionary::loadIcon() throw() +void DictdDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/dictionary.cc b/dictionary.cc index 7ccc36e2..11b13c29 100644 --- a/dictionary.cc +++ b/dictionary.cc @@ -159,7 +159,7 @@ sptr< WordSearchRequest > Class::findHeadwordsForSynonym( wstring const & ) } vector< wstring > Class::getAlternateWritings( wstring const & ) - throw() + noexcept { return vector< wstring >(); } @@ -185,21 +185,21 @@ QString Class::getMainFilename() return QString(); } -QIcon const & Class::getIcon() throw() +QIcon const & Class::getIcon() noexcept { if( !dictionaryIconLoaded ) loadIcon(); return dictionaryIcon; } -QIcon const & Class::getNativeIcon() throw() +QIcon const & Class::getNativeIcon() noexcept { if( !dictionaryIconLoaded ) loadIcon(); return dictionaryNativeIcon; } -void Class::loadIcon() throw() +void Class::loadIcon() noexcept { dictionaryIconLoaded = true; } @@ -424,7 +424,7 @@ void Class::isolateCSS( QString & css, QString const & wrapperSelector ) css = newCSS; } -string makeDictionaryId( vector< string > const & dictionaryFiles ) throw() +string makeDictionaryId( vector< string > const & dictionaryFiles ) noexcept { std::vector< string > sortedList; @@ -470,7 +470,7 @@ string makeDictionaryId( vector< string > const & dictionaryFiles ) throw() // of a timestamp of the file, so we use here Qt anyway. It is supposed to // be fixed in the future when it's needed. bool needToRebuildIndex( vector< string > const & dictionaryFiles, - string const & indexFile ) throw() + string const & indexFile ) noexcept { unsigned long lastModified = 0; diff --git a/dictionary.hh b/dictionary.hh index b21933e2..08b9ff5a 100644 --- a/dictionary.hh +++ b/dictionary.hh @@ -270,7 +270,7 @@ protected: // Load user icon if it exist // By default set icon to empty - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; // Load icon from filename directly if isFullName == true // else treat filename as name without extension @@ -295,39 +295,39 @@ public: virtual void deferredInit(); /// Returns the dictionary's id. - string getId() throw() + string getId() noexcept { return id; } /// Returns the list of file names the dictionary consists of. - vector< string > const & getDictionaryFilenames() throw() + vector< string > const & getDictionaryFilenames() noexcept { return dictionaryFiles; } /// Returns the dictionary's full name, utf8. - virtual string getName() throw()=0; + virtual string getName() noexcept=0; /// Returns all the available properties, like the author's name, copyright, /// description etc. All strings are in utf8. - virtual map< Property, string > getProperties() throw()=0; + virtual map< Property, string > getProperties() noexcept=0; /// Returns the features the dictionary possess. See the Feature enum for /// their list. - virtual Features getFeatures() const throw() + virtual Features getFeatures() const noexcept { return NoFeatures; } /// Returns the number of articles in the dictionary. - virtual unsigned long getArticleCount() throw()=0; + virtual unsigned long getArticleCount() noexcept=0; /// Returns the number of words in the dictionary. This can be equal to /// the number of articles, or can be larger if some synonyms are present. - virtual unsigned long getWordCount() throw()=0; + virtual unsigned long getWordCount() noexcept=0; /// Returns the dictionary's icon. - virtual QIcon const & getIcon() throw(); + virtual QIcon const & getIcon() noexcept; /// Returns the dictionary's native icon. Dsl icons are usually rectangular, /// and are adapted by getIcon() to be square. This function allows getting /// the original icon with no geometry transformations applied. - virtual QIcon const & getNativeIcon() throw(); + virtual QIcon const & getNativeIcon() noexcept; /// Returns the dictionary's source language. virtual quint32 getLangFrom() const @@ -371,7 +371,7 @@ public: /// supposed to be very fast and simple, and the results are thus returned /// synchronously. virtual vector< wstring > getAlternateWritings( wstring const & ) - throw(); + noexcept; /// Returns a definition for the given word. The definition should /// be an html fragment (without html/head/body tags) in an utf8 encoding. @@ -454,7 +454,7 @@ public: /// dictionary is being indexed. Since indexing can take some time, this /// is useful to show in some kind of a splash screen. /// The dictionaryName is in utf8. - virtual void indexingDictionary( string const & dictionaryName ) throw()=0; + virtual void indexingDictionary( string const & dictionaryName ) noexcept=0; virtual ~Initializing() {} @@ -465,7 +465,7 @@ public: /// hashing the file names. This id should be used to identify dictionary /// and for the index file name, if one is needed. /// This function is supposed to be used by dictionary implementations. -string makeDictionaryId( vector< string > const & dictionaryFiles ) throw(); +string makeDictionaryId( vector< string > const & dictionaryFiles ) noexcept; /// Checks if it is needed to regenerate index file based on its timestamp /// and the timestamps of the dictionary files. If some files are newer than @@ -473,7 +473,7 @@ string makeDictionaryId( vector< string > const & dictionaryFiles ) throw(); /// dictionary files don't exist, returns true, too. /// This function is supposed to be used by dictionary implementations. bool needToRebuildIndex( vector< string > const & dictionaryFiles, - string const & indexFile ) throw(); + string const & indexFile ) noexcept; /// Returns a random dictionary id useful for interactively created /// dictionaries. diff --git a/dictserver.cc b/dictserver.cc index 65761655..70f3424b 100644 --- a/dictserver.cc +++ b/dictserver.cc @@ -209,16 +209,16 @@ public: strategies.append( "prefix" ); } - virtual string getName() throw() + virtual string getName() noexcept { return name; } - virtual map< Property, string > getProperties() throw() + virtual map< Property, string > getProperties() noexcept { return map< Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return 0; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return 0; } virtual sptr< WordSearchRequest > prefixMatch( wstring const &, @@ -237,7 +237,7 @@ public: virtual QString const & getDescription(); protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; void getServerDatabases(); @@ -245,7 +245,7 @@ protected: friend class DictServerArticleRequest; }; -void DictServerDictionary::loadIcon() throw() +void DictServerDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/dsl.cc b/dsl.cc index 6991e053..164a42fe 100644 --- a/dsl.cc +++ b/dsl.cc @@ -128,11 +128,7 @@ struct InsidedCard InsidedCard( uint32_t _offset, uint32_t _size, QVector< wstring > const & words ) : offset( _offset ), size( _size ), headwords( words ) {} - InsidedCard( InsidedCard const & e ) : - offset( e.offset ), size( e.size ), headwords( e.headwords ) - {} InsidedCard() {} - }; bool indexIsOldOrBad( string const & indexFile, bool hasZipFile ) @@ -187,16 +183,16 @@ public: ~DslDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -251,7 +247,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; private: @@ -470,7 +466,7 @@ void DslDictionary::doDeferredInit() } -void DslDictionary::loadIcon() throw() +void DslDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; @@ -1556,32 +1552,8 @@ void DslDictionary::getArticleText( uint32_t articleAddress, QString & headword, /// DslDictionary::getArticle() -class DslArticleRequest; - -class DslArticleRequestRunnable: public QRunnable -{ - DslArticleRequest & r; - QSemaphore & hasExited; - -public: - - DslArticleRequestRunnable( DslArticleRequest & r_, - QSemaphore & hasExited_ ): r( r_ ), - hasExited( hasExited_ ) - {} - - ~DslArticleRequestRunnable() - { - hasExited.release(); - } - - virtual void run(); -}; - class DslArticleRequest: public Dictionary::DataRequest { - friend class DslArticleRequestRunnable; - wstring word; vector< wstring > alts; DslDictionary & dict; @@ -1597,11 +1569,10 @@ public: DslDictionary & dict_, bool ignoreDiacritics_ ): word( word_ ), alts( alts_ ), dict( dict_ ), ignoreDiacritics( ignoreDiacritics_ ) { - QThreadPool::globalInstance()->start( - new DslArticleRequestRunnable( *this, hasExited ) ); + QThreadPool::globalInstance()->start( [ this ]() { this->run(); } ); } - void run(); // Run from another thread by DslArticleRequestRunnable + void run(); virtual void cancel() { @@ -1611,15 +1582,10 @@ public: ~DslArticleRequest() { isCancelled.ref(); - hasExited.acquire(); + //hasExited.acquire(); } }; -void DslArticleRequestRunnable::run() -{ - r.run(); -} - void DslArticleRequest::run() { if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) @@ -1731,7 +1697,7 @@ void DslArticleRequest::run() { gdWarning( "DSL: Failed loading article from \"%s\", reason: %s\n", dict.getName().c_str(), ex.what() ); articleText = string( "" ) - + string( QObject::tr( "Article loading error" ).toUtf8().constData() ) + + QObject::tr( "Article loading error" ).toStdString() + ""; } @@ -1759,32 +1725,8 @@ sptr< Dictionary::DataRequest > DslDictionary::getArticle( wstring const & word, //// DslDictionary::getResource() -class DslResourceRequest; - -class DslResourceRequestRunnable: public QRunnable -{ - DslResourceRequest & r; - QSemaphore & hasExited; - -public: - - DslResourceRequestRunnable( DslResourceRequest & r_, - QSemaphore & hasExited_ ): r( r_ ), - hasExited( hasExited_ ) - {} - - ~DslResourceRequestRunnable() - { - hasExited.release(); - } - - virtual void run(); -}; - class DslResourceRequest: public Dictionary::DataRequest { - friend class DslResourceRequestRunnable; - DslDictionary & dict; string resourceName; @@ -1799,11 +1741,10 @@ public: dict( dict_ ), resourceName( resourceName_ ) { - QThreadPool::globalInstance()->start( - new DslResourceRequestRunnable( *this, hasExited ) ); + QThreadPool::globalInstance()->start( [ this ]() { this->run(); } ); } - void run(); // Run from another thread by DslResourceRequestRunnable + void run(); virtual void cancel() { @@ -1813,15 +1754,10 @@ public: ~DslResourceRequest() { isCancelled.ref(); - hasExited.acquire(); + //hasExited.acquire(); } }; -void DslResourceRequestRunnable::run() -{ - r.run(); -} - void DslResourceRequest::run() { // Some runnables linger enough that they are cancelled before they start @@ -2153,10 +2089,9 @@ vector< sptr< Dictionary::Class > > makeDictionaries( hasString = false; - // The line read should either consist of pure whitespace, or be a - // headword - - if ( curString.empty() ) + // The line read should either consist of pure whitespace, or be a headword + // skip too long headword,it can never be headword. + if( curString.empty() || curString.size() > 100 ) continue; if ( isDslWs( curString[ 0 ] ) ) diff --git a/dsl_details.cc b/dsl_details.cc index c317b445..465309ac 100644 --- a/dsl_details.cc +++ b/dsl_details.cc @@ -983,7 +983,7 @@ DslScanner::DslScanner( string const & fileName ) : readBufferLeft = 0; } -DslScanner::~DslScanner() throw() +DslScanner::~DslScanner() noexcept { gzclose( f ); } @@ -1155,12 +1155,6 @@ void expandOptionalParts( wstring & str, list< wstring > * result, list< wstring > * headwords; headwords = inside_recurse ? result : &expanded; - //if str is too long ,it can never be headwords. - //todo? - if( str.size() > 100 ) - { - return; - } for( ; x < str.size(); ) { wchar ch = str[ x ]; diff --git a/dsl_details.hh b/dsl_details.hh index 2349c349..34eea56d 100644 --- a/dsl_details.hh +++ b/dsl_details.hh @@ -113,7 +113,6 @@ class DslScanner wstring langFrom, langTo; wstring soundDictionary; char readBuffer[ 65536 ]; - QTextStream* fragStream; char * readBufferPtr; LineFeed lineFeed; size_t readBufferLeft; @@ -130,7 +129,7 @@ public: DEF_EX( exEncodingError, "Encoding error", Ex ) // Should never happen really DslScanner( string const & fileName ) ; - ~DslScanner() throw(); + ~DslScanner() noexcept; /// Returns the detected encoding of this file. Encoding getEncoding() const diff --git a/epwing.cc b/epwing.cc index cd5eec5c..2f9ef607 100644 --- a/epwing.cc +++ b/epwing.cc @@ -44,7 +44,7 @@ namespace { enum { Signature = 0x58575045, // EPWX on little-endian, XWPE on big-endian - CurrentFormatVersion = 5 + BtreeIndexing::FormatVersion + Folding::Version + CurrentFormatVersion = 6 + BtreeIndexing::FormatVersion + Folding::Version }; struct IdxHeader @@ -96,16 +96,16 @@ public: ~EpwingDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return bookName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -163,7 +163,7 @@ public: protected: - void loadIcon() throw(); + void loadIcon() noexcept; private: @@ -244,7 +244,7 @@ EpwingDictionary::~EpwingDictionary() removeDirectory( cacheDirectory ); } -void EpwingDictionary::loadIcon() throw() +void EpwingDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/epwing_book.cc b/epwing_book.cc index bec5995d..3abe85b4 100644 --- a/epwing_book.cc +++ b/epwing_book.cc @@ -850,14 +850,14 @@ void EpwingBook::getFirstHeadword( EpwingHeadword & head ) fixHeadword( head.headword ); EWPos epos( pos.page, pos.offset ); - allHeadwordPositions[ head.headword ] = epos; + allHeadwordPositions[ head.headword ] << epos; } 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() ) @@ -881,13 +881,25 @@ bool EpwingBook::getNextHeadword( EpwingHeadword & head ) if( allHeadwordPositions.contains( head.headword ) ) { - EWPos epos = allHeadwordPositions[ head.headword ]; - if( pos.page != epos.first || abs( pos.offset - epos.second ) > 4 ) + // existed position + bool existed = false; + foreach( EWPos epos, allHeadwordPositions[ head.headword ] ) + { + if( pos.page == epos.first && abs( pos.offset - epos.second ) <= 4 ) + { + existed = true; + break; + } + } + if( !existed ) + { + allHeadwordPositions[ head.headword ] << EWPos( pos.page, pos.offset ); return true; + } } else { - allHeadwordPositions[ head.headword ] = EWPos( pos.page, pos.offset ); + allHeadwordPositions[ head.headword ] << EWPos( pos.page, pos.offset ); return true; } } @@ -943,14 +955,26 @@ bool EpwingBook::getNextHeadword( EpwingHeadword & head ) if( allHeadwordPositions.contains( head.headword ) ) { - EWPos epos = allHeadwordPositions[ head.headword ]; - if( pos.page != epos.first || abs( pos.offset - epos.second ) > 4 ) - break; + // existed position + bool existed = false; + foreach( EWPos epos, allHeadwordPositions[ head.headword ] ) + { + if( pos.page == epos.first && abs( pos.offset - epos.second ) <= 4 ) + { + existed = true; + break; + } + } + if( !existed ) + { + allHeadwordPositions[ head.headword ] << EWPos( pos.page, pos.offset ); + return true; + } } else { - allHeadwordPositions[ head.headword ] = EWPos( pos.page, pos.offset ); - break; + allHeadwordPositions[ head.headword ] << EWPos( pos.page, pos.offset ); + return true; } } @@ -1104,6 +1128,9 @@ void EpwingBook::getArticle( QString & headword, QString & articleText, headword = QString::fromUtf8( buffer, length ); finalizeText( headword ); + if( text_only ) + fixHeadword( headword ); + articleText = getText( pos.page, pos.offset, text_only ); } diff --git a/epwing_book.hh b/epwing_book.hh index 5e2b99b5..558d4684 100644 --- a/epwing_book.hh +++ b/epwing_book.hh @@ -78,7 +78,7 @@ class EpwingBook QStringList imageCacheList, soundsCacheList, moviesCacheList, fontsCacheList; QMap< QString, QString > baseFontsMap, customFontsMap; QVector< int > refPages, refOffsets; - QMap< QString, EWPos > allHeadwordPositions; + QMap< QString, QList< EWPos > > allHeadwordPositions; QVector< EWPos > LinksQueue; int refOpenCount, refCloseCount; static Mutex libMutex; diff --git a/ex.hh b/ex.hh index cc76846a..13dd5fff 100644 --- a/ex.hh +++ b/ex.hh @@ -14,8 +14,8 @@ #define DEF_EX( exName, exDescription, exParent ) \ class exName: public exParent { \ public: \ -virtual const char * what() const throw() { return (exDescription); } \ -virtual ~exName() throw() {} }; +virtual const char * what() const noexcept { return (exDescription); } \ +virtual ~exName() noexcept {} }; /// Same as DEF_EX, but takes a runtime string argument, which gets concatenated /// with the description. @@ -31,7 +31,7 @@ class exName: public exParent { \ std::string value; \ public: \ exName( std::string const & value_ ): value( std::string( exDescription ) + " " + value_ ) {} \ -virtual const char * what() const throw() { return value.c_str(); } \ -virtual ~exName() throw() {} }; +virtual const char * what() const noexcept { return value.c_str(); } \ +virtual ~exName() noexcept {} }; #endif diff --git a/file.cc b/file.cc index 9391028a..dabc8853 100644 --- a/file.cc +++ b/file.cc @@ -65,7 +65,7 @@ void loadFromFile( std::string const & n, std::vector< char > & data ) f.read( &data.front(), data.size() ); } -bool exists( char const * filename ) throw() +bool exists( char const * filename ) noexcept { #ifdef __WIN32 #if defined(__WIN64) || defined(_MSC_VER) @@ -256,6 +256,20 @@ void Class::seek( qint64 offset ) throw exSeekError(); } +uchar * Class::map( qint64 offset, qint64 size ) +{ + if( writeBuffer ) + flushWriteBuffer(); + + return f.map( offset, size ); +} + +bool Class::unmap( uchar * address ) +{ + return f.unmap( address ); +} + + void Class::seekCur( qint64 offset ) { if ( writeBuffer ) @@ -313,7 +327,7 @@ void Class::close() f.close(); } -Class::~Class() throw() +Class::~Class() noexcept { if ( f.isOpen() ) { diff --git a/file.hh b/file.hh index 81281647..fa819306 100644 --- a/file.hh +++ b/file.hh @@ -9,6 +9,7 @@ #include #include #include "ex.hh" +#include "mutex.hh" /// A simple wrapper over FILE * operations with added write-buffering, /// used for non-Qt parts of code. @@ -30,9 +31,9 @@ bool tryPossibleZipName( std::string const & name, std::string & copyTo ); void loadFromFile( std::string const & n, std::vector< char > & data ); -bool exists( char const * filename ) throw(); +bool exists( char const * filename ) noexcept; -inline bool exists( std::string const & filename ) throw() +inline bool exists( std::string const & filename ) noexcept { return exists( filename.c_str() ); } class Class @@ -44,6 +45,7 @@ class Class void open( char const * filename, char const * mode ) ; public: + QMutex lock; Class( char const * filename, char const * mode ) ; @@ -95,6 +97,7 @@ public: /// Seeks in the file, relative to its beginning. void seek( qint64 offset ) ; + uchar * map( qint64 offset, qint64 size ); /// Seeks in the file, relative to the current position. void seekCur( qint64 offset ) ; /// Seeks in the file, relative to the end of file. @@ -116,7 +119,8 @@ public: /// Closes the file. No further operations are valid. void close() ; - ~Class() throw(); + ~Class() noexcept; + bool unmap( uchar * address ); private: diff --git a/forvo.cc b/forvo.cc index a1d63f8d..cdfeecc5 100644 --- a/forvo.cc +++ b/forvo.cc @@ -42,16 +42,16 @@ public: { } - virtual string getName() throw() + virtual string getName() noexcept { return name; } - virtual map< Property, string > getProperties() throw() + virtual map< Property, string > getProperties() noexcept { return map< Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return 0; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return 0; } virtual sptr< WordSearchRequest > prefixMatch( wstring const & /*word*/, @@ -70,7 +70,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; @@ -90,7 +90,7 @@ sptr< DataRequest > ForvoDictionary::getArticle( wstring const & word, netMgr ); } -void ForvoDictionary::loadIcon() throw() +void ForvoDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/ftshelpers.cc b/ftshelpers.cc index 0d775282..29bf509a 100644 --- a/ftshelpers.cc +++ b/ftshelpers.cc @@ -15,7 +15,12 @@ #include #include + #include "wildcard.hh" +#include +#include "base/globalregex.hh" +#include +#include using std::vector; using std::string; @@ -38,76 +43,148 @@ bool ftsIndexIsOldOrBad( string const & indexFile, } static QString makeHiliteRegExpString( QStringList const & words, - int searchMode, - int distanceBetweenWords ) + int searchMode, int distanceBetweenWords, bool hasCJK = false, bool ignoreWordsOrder = false ) { QString searchString( "(" ); QString stripWords( "(?:\\W+\\w+){0," ); + + if( hasCJK ) + { + stripWords = "(?:[\\W\\w]){0,"; + } + if( distanceBetweenWords >= 0 ) stripWords += QString::number( distanceBetweenWords ); - stripWords += "}\\W+"; + stripWords += "}"; + + if(!hasCJK) + { + stripWords += "\\W+"; + } QString boundWord( searchMode == FTS::WholeWords ? "\\b" : "(?:\\w*)"); + if(hasCJK) + { + //no boundary for CJK + boundWord.clear(); + } for( int x = 0; x < words.size(); x++ ) { if( x ) + { searchString += stripWords; + if(ignoreWordsOrder) + searchString += "("; + } searchString += boundWord + words[ x ] + boundWord; + + if( x ) + { + if( ignoreWordsOrder ) + searchString += ")?"; + } + } searchString += ")"; return searchString; } +void tokenizeCJK( QStringList & indexWords, QRegularExpression wordRegExp, QStringList list ) +{ + QStringList wordList, hieroglyphList; + for( int i = 0; i < list.size(); i ++ ) + { + QString word = list.at( i ); + + // Check for CJK symbols in word + bool parsed = false; + QString hieroglyph; + for( int x = 0; x < word.size(); x++ ) + if( isCJKChar( word.at( x ).unicode() ) ) + { + parsed = true; + hieroglyph.append( word[ x ] ); + + if( QChar( word.at( x ) ).isHighSurrogate() + && QChar( word[ x + 1 ] ).isLowSurrogate() ) + hieroglyph.append( word[ ++x ] ); + + hieroglyphList.append( hieroglyph ); + hieroglyph.clear(); + } + + // If word don't contains CJK symbols put it in list as is + if( !parsed ) + wordList.append( word ); + } + + indexWords = wordList.filter( wordRegExp ); + indexWords.removeDuplicates(); + + hieroglyphList.removeDuplicates(); + indexWords += hieroglyphList; +} + +bool containCJK( QString const & str) +{ + bool hasCJK = false; + for( int x = 0; x < str.size(); x++ ) + if( isCJKChar( str.at( x ).unicode() ) ) + { + hasCJK = true; + break; + } + return hasCJK; +} + bool parseSearchString( QString const & str, QStringList & indexWords, QStringList & searchWords, QRegExp & searchRegExp, int searchMode, bool matchCase, int distanceBetweenWords, - bool & hasCJK ) + bool & hasCJK, + bool ignoreWordsOrder ) { searchWords.clear(); indexWords.clear(); - QRegularExpression spacesRegExp( "\\W+" ); - QRegularExpression wordRegExp( QString( "\\w{" ) + QString::number( FTS::MinimumWordSize ) + ",}" ); - QRegularExpression setsRegExp( "\\[[^\\]]+\\]", QRegularExpression::CaseInsensitiveOption ); - QRegularExpression regexRegExp( "\\\\[afnrtvdDwWsSbB]|\\\\x([0-9A-Fa-f]{4})|\\\\0([0-7]{3})", QRegularExpression::CaseInsensitiveOption); + // 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); - hasCJK = false; - for( int x = 0; x < str.size(); x++ ) - if( isCJKChar( str.at( x ).unicode() ) ) - { - hasCJK = true; - break; - } + hasCJK = containCJK( str ); if( searchMode == FTS::WholeWords || searchMode == FTS::PlainText ) { - if( hasCJK ) - return false; - // Make words list for search in article text - searchWords = str.normalized( QString::NormalizationForm_C ) - .split( spacesRegExp, Qt::SkipEmptyParts ); - + searchWords = str.normalized( QString::NormalizationForm_C ).split( RX::Ftx::spacesRegExp, Qt::SkipEmptyParts ); // Make words list for index search - QStringList list = str.normalized( QString::NormalizationForm_C ) - .toLower().split( spacesRegExp, Qt::SkipEmptyParts ); - indexWords = list.filter( wordRegExp ); - indexWords.removeDuplicates(); + QStringList list = + str.normalized( QString::NormalizationForm_C ).toLower().split( RX::Ftx::spacesRegExp, Qt::SkipEmptyParts ); - // Make regexp for results hilite + QString searchString; + if( hasCJK ) + { + tokenizeCJK( indexWords, RX::Ftx::wordRegExp, list ); + // QStringList allWords = str.split( spacesRegExp, Qt::SkipEmptyParts ); + searchString = makeHiliteRegExpString( list, searchMode, distanceBetweenWords, hasCJK , ignoreWordsOrder); + } + else + { + indexWords = list.filter( RX::Ftx::wordRegExp ); + indexWords.removeDuplicates(); - QStringList allWords = str.split( spacesRegExp, Qt::SkipEmptyParts ); - QString searchString = makeHiliteRegExpString( allWords, searchMode, distanceBetweenWords ); + // Make regexp for results hilite - searchRegExp = QRegExp( searchString, matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive, - QRegExp::RegExp2 ); + QStringList allWords = str.split( RX::Ftx::spacesRegExp, Qt::SkipEmptyParts ); + searchString = makeHiliteRegExpString( allWords, searchMode, distanceBetweenWords,false, ignoreWordsOrder ); + } + searchRegExp = QRegExp( searchString, matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive, QRegExp::RegExp2 ); searchRegExp.setMinimal( true ); - return !indexWords.isEmpty(); } else @@ -118,52 +195,21 @@ bool parseSearchString( QString const & str, QStringList & indexWords, // Remove RegExp commands if( searchMode == FTS::RegExp ) - tmp.replace( regexRegExp, " " ); + tmp.replace( RX::Ftx::regexRegExp, " " ); // Remove all symbol sets - tmp.replace( setsRegExp, " " ); + tmp.replace( RX::Ftx::setsRegExp, " " ); QStringList list = tmp.normalized( QString::NormalizationForm_C ) - .toLower().split( spacesRegExp, Qt::SkipEmptyParts ); + .toLower().split( RX::Ftx::spacesRegExp, Qt::SkipEmptyParts ); if( hasCJK ) { - QStringList wordList, hieroglyphList; - for( int i = 0; i < list.size(); i ++ ) - { - QString word = list.at( i ); - - // Check for CJK symbols in word - bool parsed = false; - QString hieroglyph; - for( int x = 0; x < word.size(); x++ ) - if( isCJKChar( word.at( x ).unicode() ) ) - { - parsed = true; - hieroglyph.append( word[ x ] ); - - if( QChar( word.at( x ) ).isHighSurrogate() - && QChar( word[ x + 1 ] ).isLowSurrogate() ) - hieroglyph.append( word[ ++x ] ); - - hieroglyphList.append( hieroglyph ); - hieroglyph.clear(); - } - - // If word don't contains CJK symbols put it in list as is - if( !parsed ) - wordList.append( word ); - } - - indexWords = wordList.filter( wordRegExp ); - indexWords.removeDuplicates(); - - hieroglyphList.removeDuplicates(); - indexWords += hieroglyphList; + tokenizeCJK( indexWords, RX::Ftx::wordRegExp, list ); } else { - indexWords = list.filter( wordRegExp ); + indexWords = list.filter( RX::Ftx::wordRegExp ); indexWords.removeDuplicates(); } @@ -182,16 +228,10 @@ void parseArticleForFts( uint32_t articleAddress, QString & articleText, if( articleText.isEmpty() ) return; - QRegularExpression regBrackets( "(\\([\\w\\p{M}]+\\)){0,1}([\\w\\p{M}]+)(\\([\\w\\p{M}]+\\)){0,1}([\\w\\p{M}]+){0,1}(\\([\\w\\p{M}]+\\)){0,1}", - QRegularExpression::UseUnicodePropertiesOption); - QRegularExpression regSplit( "[^\\w\\p{M}]+", QRegularExpression::UseUnicodePropertiesOption ); - QStringList articleWords = articleText.normalized( QString::NormalizationForm_C ) - .split( QRegularExpression( handleRoundBrackets ? "[^\\w\\(\\)\\p{M}]+" : "[^\\w\\p{M}]+", - QRegularExpression::UseUnicodePropertiesOption ), + .split( handleRoundBrackets ? RX::Ftx::handleRoundBracket : RX::Ftx::noRoundBracket, Qt::SkipEmptyParts ); - QSet< QString > setOfWords; setOfWords.reserve( articleWords.size() ); @@ -233,12 +273,12 @@ void parseArticleForFts( uint32_t articleAddress, QString & articleText, // Special handle for words with round brackets - DSL feature QStringList list; - QStringList oldVariant = word.split( regSplit, Qt::SkipEmptyParts ); + QStringList oldVariant = word.split( RX::Ftx::regSplit, Qt::SkipEmptyParts ); for( QStringList::iterator it = oldVariant.begin(); it != oldVariant.end(); ++it ) if( it->size() >= FTS::MinimumWordSize && !list.contains( *it ) ) list.append( *it ); - QRegularExpressionMatch match = regBrackets.match( word ); + QRegularExpressionMatch match = RX::Ftx::regBrackets.match( word ); if( match.hasMatch() ) { QStringList parts = match.capturedTexts(); @@ -399,56 +439,74 @@ bool isCJKChar( ushort ch ) return false; } -void FTSResultsRequestRunnable::run() -{ - r.run(); -} - void FTSResultsRequest::checkArticles( QVector< uint32_t > const & offsets, QStringList const & words, QRegExp const & searchRegexp ) { - int results = 0; + const int parallel_count = QThread::idealThreadCount()/2; + QSemaphore sem( parallel_count < 1 ? 1 : parallel_count ); + + QFutureSynchronizer< void > synchronizer; + const auto searchRegularExpression = createMatchRegex( searchRegexp ); + + for( auto & address : offsets ) + { + if( Utils::AtomicInt::loadAcquire( isCancelled ) ) + { + synchronizer.setCancelOnWait(true); + return; + } + sem.acquire(); + QFuture< void > f = QtConcurrent::run( + [ =,&sem ]() + { + QSemaphoreReleaser releaser( sem ); + checkSingleArticle( address, words, searchRegularExpression ); + } ); + synchronizer.addFuture( f ); + } +} + +QRegularExpression FTSResultsRequest::createMatchRegex( QRegExp const & searchRegexp ) +{ + QRegularExpression searchRegularExpression; + + if( searchMode == FTS::Wildcards ) + searchRegularExpression.setPattern( wildcardsToRegexp( searchRegexp.pattern() ) ); + else + searchRegularExpression.setPattern( searchRegexp.pattern() ); + QRegularExpression::PatternOptions patternOptions = + QRegularExpression::DotMatchesEverythingOption | QRegularExpression::UseUnicodePropertiesOption + | QRegularExpression::MultilineOption | QRegularExpression::InvertedGreedinessOption; + if( searchRegexp.caseSensitivity() == Qt::CaseInsensitive ) + patternOptions |= QRegularExpression::CaseInsensitiveOption; + searchRegularExpression.setPatternOptions( patternOptions ); + if( !searchRegularExpression.isValid() ) + searchRegularExpression.setPattern( "" ); + return searchRegularExpression; +} + +void FTSResultsRequest::checkSingleArticle( uint32_t offset, + QStringList const & words, + QRegularExpression const & searchRegularExpression ) +{ + // int results = 0; QString headword, articleText; QList< uint32_t > offsetsForHeadwords; QVector< QStringList > hiliteRegExps; QString id = QString::fromUtf8( dict.getId().c_str() ); - bool needHandleBrackets; - { - QString name = QString::fromUtf8( dict.getDictionaryFilenames()[ 0 ].c_str() ).toLower(); - needHandleBrackets = name.endsWith( ".dsl" ) || name.endsWith( ".dsl.dz" ); - } - - QRegularExpression regBrackets( "(\\([\\w\\p{M}]+\\)){0,1}([\\w\\p{M}]+)(\\([\\w\\p{M}]+\\)){0,1}([\\w\\p{M}]+){0,1}(\\([\\w\\p{M}]+\\)){0,1}", - QRegularExpression::UseUnicodePropertiesOption); - QRegularExpression regSplit( "[^\\w\\p{M}]+", QRegularExpression::UseUnicodePropertiesOption ); + // RegExp mode if( searchMode == FTS::Wildcards || searchMode == FTS::RegExp ) { - // RegExp mode - - QRegularExpression searchRegularExpression; - if( searchMode == FTS::Wildcards ) - searchRegularExpression.setPattern( wildcardsToRegexp( searchRegexp.pattern() ) ); - else - searchRegularExpression.setPattern( searchRegexp.pattern() ); - QRegularExpression::PatternOptions patternOptions = QRegularExpression::DotMatchesEverythingOption - | QRegularExpression::UseUnicodePropertiesOption - | QRegularExpression::MultilineOption - | QRegularExpression::InvertedGreedinessOption; - if( searchRegexp.caseSensitivity() == Qt::CaseInsensitive ) - patternOptions |= QRegularExpression::CaseInsensitiveOption; - searchRegularExpression.setPatternOptions( patternOptions ); - if( !searchRegularExpression.isValid() ) - searchRegularExpression.setPattern( "" ); - for( int i = 0; i < offsets.size(); i++ ) - { + // for( int i = 0; i < offsets.size(); i++ ) if( Utils::AtomicInt::loadAcquire( isCancelled ) ) - break; + return; - dict.getArticleText( offsets.at( i ), headword, articleText ); + // auto article_address = offsets.at( i ); + dict.getArticleText( offset, headword, articleText ); articleText = articleText.normalized( QString::NormalizationForm_C ); if( ignoreDiacritics ) @@ -457,24 +515,22 @@ void FTSResultsRequest::checkArticles( QVector< uint32_t > const & offsets, if( articleText.contains( searchRegularExpression ) ) { if( headword.isEmpty() ) - offsetsForHeadwords.append( offsets.at( i ) ); + offsetsForHeadwords.append( offset ); else + { + Mutex::Lock _( dataMutex ); foundHeadwords->append( FTS::FtsHeadword( headword, id, QStringList(), matchCase ) ); + } - results++; + ++results; if( maxResults > 0 && results >= maxResults ) - break; + return; } - } } else { // Words mode - QRegularExpression splitWithBrackets( "[^\\w\\(\\)\\p{M}]+", QRegularExpression::UseUnicodePropertiesOption ); - QRegularExpression splitWithoutBrackets( "[^\\w\\p{M}]+", QRegularExpression::UseUnicodePropertiesOption ); - - Qt::CaseSensitivity cs = matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive; QVector< QPair< QString, bool > > wordsList; if( ignoreWordsOrder ) { @@ -482,18 +538,9 @@ void FTSResultsRequest::checkArticles( QVector< uint32_t > const & offsets, wordsList.append( QPair< QString, bool >( *it, true ) ); } - for( int i = 0; i < offsets.size(); i++ ) - { + // for( int i = 0; i < offsets.size(); i++ ) if( Utils::AtomicInt::loadAcquire( isCancelled ) ) - break; - - int pos = 0; - int matchWordNom = 0; - int unmatchWordNom = 0; - int nextNotFoundPos = 0; - - QVector< QStringList > allOrders; - QStringList order; + return; if( ignoreWordsOrder ) { @@ -501,310 +548,192 @@ void FTSResultsRequest::checkArticles( QVector< uint32_t > const & offsets, wordsList[ i ].second = true; } - dict.getArticleText( offsets.at( i ), headword, articleText ); + dict.getArticleText( offset, headword, articleText ); articleText = articleText.normalized( QString::NormalizationForm_C ); if( ignoreDiacritics ) articleText = gd::toQString( Folding::applyDiacriticsOnly( gd::toWString( articleText ) ) ); - QStringList articleWords = articleText.split( needHandleBrackets ? splitWithBrackets : splitWithoutBrackets, - Qt::SkipEmptyParts ); - - int wordsNum = articleWords.length(); - while ( pos < wordsNum ) + if( ignoreWordsOrder ) { - QString s = articleWords[ pos ]; - bool breakSearch = false; - - QStringList parsedWords; - if( needHandleBrackets && ( s.indexOf( '(' ) >= 0 || s.indexOf( ')' ) >= 0 ) ) + bool allMatch = true; + foreach( QString word, words ) + { + if( containCJK( word ) || searchMode == FTS::PlainText ) { - // Handle brackets - QRegularExpressionMatch match_brackets = regBrackets.match( s ); - if( match_brackets.hasMatch() ) + if( !articleText.contains( word ) ) { - QStringList parts = match_brackets.capturedTexts(); - // Add empty strings for compatibility with QRegExp behaviour - for( int i = match_brackets.lastCapturedIndex() + 1; i < 6; i++ ) - parts.append( QString() ); - - QString word = parts[ 2 ] + parts[ 4 ]; // Brackets removed - parsedWords.append( word ); - - word = parts[ 1 ].remove( '(' ).remove( ')' ) - + parts[ 2 ] - + parts[ 3 ].remove( '(' ).remove( ')' ) - + parts[ 4 ] - + parts[ 5 ].remove( '(' ).remove( ')' ); // Brackets expansed - parsedWords.append( word ); + allMatch = false; + break; } - else - parsedWords = s.split( regSplit, Qt::SkipEmptyParts ); } - else - parsedWords.append( s ); - - int n; - for( n = 0; n < parsedWords.size(); n++ ) + else if( searchMode == FTS::WholeWords ) { - if( ignoreWordsOrder ) + QRegularExpression tmpReg( QString( "\b%1\b" ).arg( word ), + QRegularExpression::CaseInsensitiveOption + | QRegularExpression::UseUnicodePropertiesOption ); + if( !articleText.contains( tmpReg ) ) { - int i; - for( i = 0; i < wordsList.size(); i++ ) - { - if( wordsList.at( i ).second ) - { - if( ( searchMode == FTS::WholeWords && parsedWords.at( n ).compare( wordsList.at( i ).first, cs ) == 0 ) - || ( searchMode == FTS::PlainText && parsedWords.at( n ).contains( wordsList.at( i ).first, cs ) ) ) - { - wordsList[ i ].second = false; - - if( parsedWords.size() > 1 ) - { - QString wordToHilite = s; - while( !wordToHilite.isEmpty() && ( wordToHilite.at( 0 ) == '(' || wordToHilite.at( 0 ) == ')' ) ) - wordToHilite.remove( 0, 1 ); - while( !wordToHilite.isEmpty() && ( wordToHilite.endsWith( '(' ) || wordToHilite.endsWith( ')' ) ) ) - wordToHilite.chop( 1 ); - order.append( wordToHilite.replace( '(', "\\(" ).replace( ')', "\\)" ) ); - } - else - order.append( wordsList.at( i ).first ); - - break; - } - } - } - if( i < wordsList.size() ) - { - // Word found - - matchWordNom += 1; - - if( matchWordNom == 1 ) - { - // Store position to remake search if sequence will not be found - nextNotFoundPos = pos + 1; - } - - if( matchWordNom >= words.size() ) - { - // All words are found - // Store found words sequence and continue search - // It's nesessary for hilite search results - - // Check if such sequence already presented - int x; - for( x = 0; x < allOrders.size(); x++ ) - { - if( allOrders[ x ] == order ) - break; - } - if( x >= allOrders.size() ) - allOrders.append( order ); - - order.clear(); - - matchWordNom = 0; - unmatchWordNom = 0; - for( int i = 0; i < wordsList.size(); i++ ) - wordsList[ i ].second = true; - nextNotFoundPos = 0; - - break; - } - - unmatchWordNom = 0; - break; - } - else - if( matchWordNom > 0 && n >= parsedWords.size() - 1 ) - { - unmatchWordNom += 1; - if( distanceBetweenWords >= 0 && unmatchWordNom > distanceBetweenWords ) - { - // Sequence broken, clear all counters - matchWordNom = 0; - unmatchWordNom = 0; - for( int i = 0; i < wordsList.size(); i++ ) - wordsList[ i ].second = true; - order.clear(); - } - } + allMatch = false; + break; } + } + } + + if( !allMatch ) + { + return; + } + + if( distanceBetweenWords >= 0 ) + { + // the article text contains all the needed words. + // determine if distance restriction is meet + const QRegularExpression replaceReg( QString( "(%1)" ).arg( words.join( '|' ) ), + QRegularExpression::CaseInsensitiveOption + | QRegularExpression::UseUnicodePropertiesOption ); + // use a string that could not be presented in the article. + articleText = articleText.replace( replaceReg, "=@XXXXX@=" ); + + auto hasCJK = false; + foreach( QString word, words ) + { + if( containCJK( word ) ) + { + hasCJK = true; + break; + } + } + + // hascjk value ,perhaps should depend on each word + const auto searchRegStr = makeHiliteRegExpString( Utils::repeat( "=@XXXXX@=", words.size() ), + searchMode, + distanceBetweenWords, + hasCJK ); + const QRegularExpression distanceOrderReg( searchRegStr, + QRegularExpression::CaseInsensitiveOption + | QRegularExpression::UseUnicodePropertiesOption ); + // use a string that could not be presented in the article. + if( articleText.contains( distanceOrderReg ) ) + { + if( headword.isEmpty() ) + offsetsForHeadwords.append( offset ); else { - if( ( searchMode == FTS::WholeWords && parsedWords.at( n ).compare( words.at( matchWordNom ), cs ) == 0 ) - || ( searchMode == FTS::PlainText && parsedWords.at( n ).contains( words.at( matchWordNom ), cs ) ) ) - { - matchWordNom += 1; - - if( matchWordNom == 1 ) - { - // Store position to remake search if sequence will not be found - nextNotFoundPos = pos + 1; - } - - if( needHandleBrackets ) - { - if( parsedWords.size() > 1 ) - { - QString wordToHilite = s; - while( !wordToHilite.isEmpty() && ( wordToHilite.at( 0 ) == '(' || wordToHilite.at( 0 ) == ')' ) ) - wordToHilite.remove( 0, 1 ); - while( !wordToHilite.isEmpty() && ( wordToHilite.endsWith( '(' ) || wordToHilite.endsWith( ')' ) ) ) - wordToHilite.chop( 1 ); - order.append( wordToHilite.replace( '(', "\\(" ).replace( ')', "\\)" ) ); - } - else - order.append( words.at( matchWordNom - 1 ) ); - } - - if( matchWordNom >= words.size() ) - { - // All words are found - if( needHandleBrackets ) - { - if( allOrders.isEmpty() ) - allOrders.append( words ); - - // Check if such sequence already presented - int x; - for( x = 0; x < allOrders.size(); x++ ) - { - if( allOrders[ x ] == order ) - break; - } - if( x >= allOrders.size() ) - allOrders.append( order ); - - matchWordNom = 0; - unmatchWordNom = 0; - order.clear(); - nextNotFoundPos = 0; - } - else - breakSearch = true; - break; - } - unmatchWordNom = 0; - break; - } - else - if( matchWordNom > 0 && n >= parsedWords.size() - 1 ) - { - unmatchWordNom += 1; - if( distanceBetweenWords >= 0 && unmatchWordNom > distanceBetweenWords ) - { - matchWordNom = 0; - unmatchWordNom = 0; - if( needHandleBrackets ) - order.clear(); - } - } + Mutex::Lock _( dataMutex ); + foundHeadwords->append( FTS::FtsHeadword( headword, id, QStringList(), matchCase ) ); } - } - if( breakSearch ) - break; - if( nextNotFoundPos > 0 && matchWordNom == 0 ) - { - pos = nextNotFoundPos; - nextNotFoundPos = 0; - } - else - pos += 1; - } - if( !allOrders.isEmpty() || matchWordNom >= words.size() ) + ++results; + if( maxResults > 0 && results >= maxResults ) + return; + } + } + } + else { - QStringList hiliteReg; - if( !allOrders.isEmpty() ) + if( articleText.contains( searchRegularExpression ) ) { - for( int i = 0; i < allOrders.size(); i++ ) + if( headword.isEmpty() ) + offsetsForHeadwords.append( offset ); + else { - QString hiliteStr = makeHiliteRegExpString( allOrders.at( i ), searchMode, distanceBetweenWords ); - hiliteReg.append( hiliteStr ); + Mutex::Lock _( dataMutex ); + foundHeadwords->append( FTS::FtsHeadword( headword, id, QStringList(), matchCase ) ); } - allOrders.clear(); - } - if( headword.isEmpty() ) - { - offsetsForHeadwords.append( offsets.at( i ) ); - hiliteRegExps.append( hiliteReg ); - } - else - foundHeadwords->append( FTS::FtsHeadword( headword, id, hiliteReg, matchCase ) ); - results++; - if( maxResults > 0 && results >= maxResults ) - break; + ++results; + if( maxResults > 0 && results >= maxResults ) + return; + } } - } } if( !offsetsForHeadwords.isEmpty() ) { QVector< QString > headwords; + Mutex::Lock _( dataMutex ); + dict.getHeadwordsFromOffsets( offsetsForHeadwords, headwords, &isCancelled ); for( int x = 0; x < headwords.size(); x++ ) - foundHeadwords->append( FTS::FtsHeadword( headwords.at( x ), id, x < hiliteRegExps.size() ? hiliteRegExps.at( x ) : QStringList(), matchCase ) ); + { + foundHeadwords->append( FTS::FtsHeadword( headwords.at( x ), + id, + x < hiliteRegExps.size() ? hiliteRegExps.at( x ) : QStringList(), + matchCase ) ); + } } } void FTSResultsRequest::indexSearch( BtreeIndexing::BtreeIndex & ftsIndex, sptr< ChunkedStorage::Reader > chunks, QStringList & indexWords, - QStringList & searchWords ) + QStringList & searchWords, QRegExp & regexp ) { // Find articles which contains all requested words - vector< BtreeIndexing::WordArticleLink > links; - QSet< uint32_t > setOfOffsets, tmp; - uint32_t size; + QSet< uint32_t > setOfOffsets; if( indexWords.isEmpty() ) return; - int n = indexWords.length(); - for( int i = 0; i < n; i++ ) + QList< QSet< uint32_t > > addressLists; + + auto findLinks = [ & ]( const QString & word ) { + QSet< uint32_t > tmp; + uint32_t size; + if( Utils::AtomicInt::loadAcquire( isCancelled ) ) + { + addressLists << tmp; return; + } - tmp.clear(); - - links = ftsIndex.findArticles( gd::toWString( indexWords.at( i ) ), ignoreDiacritics ); + vector< BtreeIndexing::WordArticleLink > links = + ftsIndex.findArticles( gd::toWString( word ), ignoreDiacritics ); for( unsigned x = 0; x < links.size(); x++ ) { - if( Utils::AtomicInt::loadAcquire( isCancelled ) ) + { + addressLists << tmp; return; + } vector< char > chunk; char * linksPtr; { - Mutex::Lock _( dict.getFtsMutex() ); + // Mutex::Lock _( dict.getFtsMutex() ); linksPtr = chunks->getBlock( links[ x ].articleOffset, chunk ); } - memcpy( &size, linksPtr, sizeof(uint32_t) ); - linksPtr += sizeof(uint32_t); + memcpy( &size, linksPtr, sizeof( uint32_t ) ); + linksPtr += sizeof( uint32_t ); for( uint32_t y = 0; y < size; y++ ) { tmp.insert( *( reinterpret_cast< uint32_t * >( linksPtr ) ) ); - linksPtr += sizeof(uint32_t); + linksPtr += sizeof( uint32_t ); } } links.clear(); + { + Mutex::Lock _( dataMutex ); + addressLists << tmp; + } + }; + // int n = indexWords.length(); + QtConcurrent::blockingMap( indexWords, findLinks ); - if( i == 0 ) - setOfOffsets = tmp; + int i = 0; + for( auto & elem : addressLists ) + { + if( i++ == 0 ) + setOfOffsets = elem; else - setOfOffsets = setOfOffsets.intersect( tmp ); + setOfOffsets = setOfOffsets.intersect( elem ); } - tmp.clear(); if( setOfOffsets.isEmpty() ) return; @@ -824,7 +753,7 @@ void FTSResultsRequest::indexSearch( BtreeIndexing::BtreeIndex & ftsIndex, dict.sortArticlesOffsetsForFTS( offsets, isCancelled ); - checkArticles( offsets, searchWords ); + checkArticles( offsets, searchWords, regexp ); } void FTSResultsRequest::combinedIndexSearch( BtreeIndexing::BtreeIndex & ftsIndex, @@ -860,35 +789,43 @@ void FTSResultsRequest::combinedIndexSearch( BtreeIndexing::BtreeIndex & ftsInde int n = wordsList.size(); if( !hieroglyphsList.isEmpty() ) + { + wordsList += hieroglyphsList; n += 1; + } allWordsLinks.resize( n ); - int wordNom = 0; - if( !hieroglyphsList.empty() ) + if( !wordsList.empty() ) { - QSet< uint32_t > tmp; - vector< BtreeIndexing::WordArticleLink > links; - - for( int i = 0; i < hieroglyphsList.size(); i++ ) + QList< QSet< uint32_t > > sets; + auto fn_wordLink = [ & ](const QString & word ) { - links = ftsIndex.findArticles( gd::toWString( hieroglyphsList.at( i ) ) ); + QSet< uint32_t > tmp; + vector< BtreeIndexing::WordArticleLink > links = ftsIndex.findArticles( gd::toWString( word ) ); for( unsigned x = 0; x < links.size(); x++ ) { - if( Utils::AtomicInt::loadAcquire( isCancelled ) ) + { + sets << tmp; return; + } vector< char > chunk; char * linksPtr; { - Mutex::Lock _( dict.getFtsMutex() ); + // Mutex::Lock _( dict.getFtsMutex() ); linksPtr = chunks->getBlock( links[ x ].articleOffset, chunk ); } memcpy( &size, linksPtr, sizeof(uint32_t) ); linksPtr += sizeof(uint32_t); - for( uint32_t y = 0; y < size; y++ ) + // across chunks, need further investigation + uint32_t max = ( chunk.size() - ( linksPtr - &chunk.front() )) / 4; + + tmp.reserve( size ); + uint32_t q_max = qMin(size,max); + for( uint32_t y = 0; y < q_max; y++ ) { tmp.insert( *( reinterpret_cast< uint32_t * >( linksPtr ) ) ); linksPtr += sizeof(uint32_t); @@ -897,67 +834,25 @@ void FTSResultsRequest::combinedIndexSearch( BtreeIndexing::BtreeIndex & ftsInde links.clear(); - if( i == 0 ) - setOfOffsets = tmp; - else - setOfOffsets = setOfOffsets.intersect( tmp ); - } - - allWordsLinks[ wordNom ] = setOfOffsets; - setOfOffsets.clear(); - wordNom += 1; - } - - if( !wordsList.isEmpty() ) - { - QVector< BtreeIndexing::WordArticleLink > links; - links.reserve( wordsInIndex ); - ftsIndex.findArticleLinks( &links, 0, 0, &isCancelled ); - - for( int x = 0; x < links.size(); x++ ) - { - if( Utils::AtomicInt::loadAcquire( isCancelled ) ) - return; - - QString word = QString::fromUtf8( links[ x ].word.data(), links[ x ].word.size() ); - - if( ignoreDiacritics ) - word = gd::toQString( Folding::applyDiacriticsOnly( gd::toWString( word ) ) ); - - for( int i = 0; i < wordsList.size(); i++ ) { - if( word.length() >= wordsList.at( i ).length() && word.contains( wordsList.at( i ) ) ) - { - vector< char > chunk; - char * linksPtr; - { - Mutex::Lock _( dict.getFtsMutex() ); - linksPtr = chunks->getBlock( links[ x ].articleOffset, chunk ); - } - - memcpy( &size, linksPtr, sizeof(uint32_t) ); - linksPtr += sizeof(uint32_t); - for( uint32_t y = 0; y < size; y++ ) - { - allWordsLinks[ wordNom ].insert( *( reinterpret_cast< uint32_t * >( linksPtr ) ) ); - linksPtr += sizeof(uint32_t); - } - wordNom += 1; - if( searchMode == FTS::PlainText || searchMode == FTS::WholeWords ) - break; - } + Mutex::Lock _( dataMutex ); + sets << tmp; } + }; + QtConcurrent::blockingMap( wordsList, fn_wordLink ); + + int i = 0; + for( auto & elem : sets ) + { + if( i++ == 0 ) + setOfOffsets = elem; + else + setOfOffsets = setOfOffsets.intersect( elem ); } - links.clear(); - } - - for( int i = 0; i < allWordsLinks.size(); i++ ) - { - if( i == 0 ) - setOfOffsets = allWordsLinks.at( i ); - else - setOfOffsets = setOfOffsets.intersect( allWordsLinks.at( i ) ); + // allWordsLinks[ wordNom ] = setOfOffsets; + // setOfOffsets.clear(); + // wordNom += 1; } if( setOfOffsets.isEmpty() ) @@ -965,16 +860,16 @@ void FTSResultsRequest::combinedIndexSearch( BtreeIndexing::BtreeIndex & ftsInde allWordsLinks.clear(); - QVector< uint32_t > offsets; - offsets.resize( setOfOffsets.size() ); - uint32_t * ptr = &offsets.front(); - - for( QSet< uint32_t >::ConstIterator it = setOfOffsets.constBegin(); - it != setOfOffsets.constEnd(); ++it ) - { - *ptr = *it; - ptr++; - } + QVector< uint32_t > offsets( setOfOffsets.begin(),setOfOffsets.end() ); + // offsets.resize( setOfOffsets.size() ); + // uint32_t * ptr = &offsets.front(); + // + // for( QSet< uint32_t >::ConstIterator it = setOfOffsets.constBegin(); + // it != setOfOffsets.constEnd(); ++it ) + // { + // *ptr = *it; + // ptr++; + // } setOfOffsets.clear(); @@ -1022,7 +917,7 @@ void FTSResultsRequest::fullIndexSearch( BtreeIndexing::BtreeIndex & ftsIndex, vector< char > chunk; char * linksPtr; { - Mutex::Lock _( dict.getFtsMutex() ); + // Mutex::Lock _( dict.getFtsMutex() ); linksPtr = chunks->getBlock( links[ x ].articleOffset, chunk ); } @@ -1054,16 +949,16 @@ void FTSResultsRequest::fullIndexSearch( BtreeIndexing::BtreeIndex & ftsIndex, allWordsLinks.clear(); - QVector< uint32_t > offsets; - offsets.resize( setOfOffsets.size() ); - uint32_t * ptr = &offsets.front(); - - for( QSet< uint32_t >::ConstIterator it = setOfOffsets.constBegin(); - it != setOfOffsets.constEnd(); ++it ) - { - *ptr = *it; - ptr++; - } + QVector< uint32_t > offsets( setOfOffsets.begin(), setOfOffsets.end() ); + // offsets.resize( setOfOffsets.size() ); + // uint32_t * ptr = &offsets.front(); + // + // for( QSet< uint32_t >::ConstIterator it = setOfOffsets.constBegin(); + // it != setOfOffsets.constEnd(); ++it ) + // { + // *ptr = *it; + // ptr++; + // } setOfOffsets.clear(); @@ -1125,7 +1020,7 @@ void FTSResultsRequest::run() QRegExp searchRegExp; if( !FtsHelpers::parseSearchString( searchString, indexWords, searchWords, searchRegExp, - searchMode, matchCase, distanceBetweenWords, hasCJK ) ) + searchMode, matchCase, distanceBetweenWords, hasCJK, ignoreWordsOrder ) ) { finish(); return; @@ -1158,7 +1053,7 @@ void FTSResultsRequest::run() else { if( searchMode == FTS::WholeWords ) - indexSearch( ftsIndex, chunks, indexWords, searchWords ); + indexSearch( ftsIndex, chunks, indexWords, searchWords, searchRegExp ); else fullIndexSearch( ftsIndex, chunks, indexWords, searchWords, searchRegExp ); } diff --git a/ftshelpers.hh b/ftshelpers.hh index f8392d09..5a5ac6ea 100644 --- a/ftshelpers.hh +++ b/ftshelpers.hh @@ -55,7 +55,8 @@ bool parseSearchString( QString const & str, QStringList & IndexWords, QRegExp & searchRegExp, int searchMode, bool matchCase, int distanceBetweenWords, - bool & hasCJK ); + bool & hasCJK, + bool ignoreWordsOrder = false ); void parseArticleForFts( uint32_t articleAddress, QString & articleText, QMap< QString, QVector< uint32_t > > & words, @@ -65,28 +66,6 @@ void makeFTSIndex( BtreeIndexing::BtreeDictionary * dict, QAtomicInt & isCancell bool isCJKChar( ushort ch ); -class FTSResultsRequest; - -class FTSResultsRequestRunnable : public QRunnable -{ - FTSResultsRequest & r; - QSemaphore & hasExited; - -public: - - FTSResultsRequestRunnable( FTSResultsRequest & r_, - QSemaphore & hasExited_ ) : r( r_ ), - hasExited( hasExited_ ) - {} - - ~FTSResultsRequestRunnable() - { - hasExited.release(); - } - - virtual void run(); -}; - class FTSResultsRequest : public Dictionary::DataRequest { BtreeIndexing::BtreeDictionary & dict; @@ -102,18 +81,24 @@ class FTSResultsRequest : public Dictionary::DataRequest int wordsInIndex; QAtomicInt isCancelled; - QSemaphore hasExited; + + QAtomicInt results; QList< FTS::FtsHeadword > * foundHeadwords; void checkArticles( QVector< uint32_t > const & offsets, QStringList const & words, QRegExp const & searchRegexp = QRegExp() ); + QRegularExpression createMatchRegex( QRegExp const & searchRegexp ); + + void checkSingleArticle( uint32_t offset, + QStringList const & words, + QRegularExpression const & searchRegexp = QRegularExpression() ); void indexSearch( BtreeIndexing::BtreeIndex & ftsIndex, sptr< ChunkedStorage::Reader > chunks, QStringList & indexWords, - QStringList & searchWords ); + QStringList & searchWords, QRegExp & regexp ); void combinedIndexSearch( BtreeIndexing::BtreeIndex & ftsIndex, sptr< ChunkedStorage::Reader > chunks, @@ -149,11 +134,11 @@ public: searchString = gd::toQString( Folding::applyDiacriticsOnly( gd::toWString( searchString_ ) ) ); foundHeadwords = new QList< FTS::FtsHeadword >; - QThreadPool::globalInstance()->start( - new FTSResultsRequestRunnable( *this, hasExited ), -100 ); + results = 0; + QThreadPool::globalInstance()->start( [ this ]() { this->run(); }, -100 ); } - void run(); // Run from another thread by DslResourceRequestRunnable + void run(); virtual void cancel() { @@ -165,7 +150,6 @@ public: isCancelled.ref(); if( foundHeadwords ) delete foundHeadwords; - hasExited.acquire(); } }; diff --git a/fulltextsearch.cc b/fulltextsearch.cc index 7ad1e5d2..311859bc 100644 --- a/fulltextsearch.cc +++ b/fulltextsearch.cc @@ -351,27 +351,15 @@ void FullTextSearchDialog::accept() searchRegExp, mode, ui.matchCase->isChecked(), distanceBetweenWords, - hasCJK ) ) + hasCJK, ignoreWordsOrder ) ) { - if( hasCJK && ( mode == WholeWords || mode == PlainText ) ) - { - QMessageBox message( QMessageBox::Warning, - "GoldenDict", - tr( "CJK symbols in search string are not compatible with search modes \"Whole words\" and \"Plain text\"" ), - QMessageBox::Ok, - this ); - message.exec(); - } - else - { - QMessageBox message( QMessageBox::Warning, - "GoldenDict", - tr( "The search line must contains at least one word containing " ) + QMessageBox message( QMessageBox::Warning, + "GoldenDict", + tr( "The search line must contains at least one word containing " ) + QString::number( MinimumWordSize ) + tr( " or more symbols" ), - QMessageBox::Ok, - this ); - message.exec(); - } + QMessageBox::Ok, + this ); + message.exec(); return; } @@ -393,6 +381,10 @@ void FullTextSearchDialog::accept() for( unsigned x = 0; x < activeDicts.size(); ++x ) { + if( !activeDicts[ x ] ->haveFTSIndex()) + { + continue; + } sptr< Dictionary::DataRequest > req = activeDicts[ x ]->getSearchResults( ui.searchLine->text(), mode, @@ -621,7 +613,7 @@ Q_UNUSED( parent ); for( int x = 0; x < hws.length(); x++ ) { QList< FtsHeadword >::iterator it = std::lower_bound( headwords.begin(), headwords.end(), hws.at( x ) ); - if( it != headwords.end() ) + if( it != headwords.end() && *it == hws.at( x ) ) { it->dictIDs.push_back( hws.at( x ).dictIDs.front() ); for( QStringList::const_iterator itr = it->foundHiliteRegExps.constBegin(); 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 ); } diff --git a/globalbroadcaster.cpp b/globalbroadcaster.cpp index 885bf89a..bcac205f 100644 --- a/globalbroadcaster.cpp +++ b/globalbroadcaster.cpp @@ -20,13 +20,15 @@ Config::Preferences * GlobalBroadcaster::getPreference() return preference; } -void GlobalBroadcaster::addWhitelist(QString url){ - whitelist.push_back(url); - auto baseUrl=::getHostBase(url); - whitelist.push_back(baseUrl); +void GlobalBroadcaster::addWhitelist( QString url ) +{ + whitelist.push_back( url ); + auto baseUrl = ::getHostBase( url ); + whitelist.push_back( baseUrl ); } -bool GlobalBroadcaster::existedInWhitelist(QString url){ - return std::find(whitelist.begin(), whitelist.end(), url) != whitelist.end(); +bool GlobalBroadcaster::existedInWhitelist( QString url ) +{ + return std::find( whitelist.begin(), whitelist.end(), url ) != whitelist.end(); } // namespace global diff --git a/globalbroadcaster.h b/globalbroadcaster.h index 3014a46e..6e6992c2 100644 --- a/globalbroadcaster.h +++ b/globalbroadcaster.h @@ -17,6 +17,7 @@ class GlobalBroadcaster : public QObject private: Config::Preferences * preference; std::vector whitelist; + public: void setPreference( Config::Preferences * _pre ); Config::Preferences * getPreference(); @@ -24,6 +25,7 @@ public: void addWhitelist(QString host); bool existedInWhitelist(QString host); static GlobalBroadcaster * instance(); + signals: void dictionaryChanges( ActiveDictIds ad ); }; diff --git a/gls.cc b/gls.cc index 30d8cf23..48c5a0c5 100644 --- a/gls.cc +++ b/gls.cc @@ -88,7 +88,7 @@ public: DEF_EX( exEncodingError, "Encoding error", Ex ) // Should never happen really GlsScanner( string const & fileName ) ; - ~GlsScanner() throw(); + ~GlsScanner() noexcept; /// Returns the detected encoding of this file. Encoding getEncoding() const @@ -293,7 +293,7 @@ bool GlsScanner::readNextLine( wstring & out, size_t & offset ) } } -GlsScanner::~GlsScanner() throw() +GlsScanner::~GlsScanner() noexcept { gzclose( f ); } @@ -372,16 +372,16 @@ public: ~GlsDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -425,7 +425,7 @@ public: } protected: - void loadIcon() throw(); + void loadIcon() noexcept; private: @@ -517,7 +517,7 @@ GlsDictionary::~GlsDictionary() dict_data_close( dz ); } -void GlsDictionary::loadIcon() throw() +void GlsDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/goldendict.pro b/goldendict.pro index 026b9a37..17a76e69 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -12,7 +12,28 @@ 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. + DATE=$$system(date /T) +} +else{ + DATE=$$system(date '+%Y/%m/%d') +} + +system(echo $${VERSION}.$${GIT_HASH} on $${DATE} > 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 + } +} + +CONFIG( release, debug|release ) { + DEFINES += NDEBUG +} # DEPENDPATH += . generators INCLUDEPATH += . @@ -26,7 +47,8 @@ QT += core \ webenginewidgets\ webchannel\ printsupport \ - help + help \ + concurrent greaterThan(QT_MAJOR_VERSION, 5): QT += webenginecore core5compat @@ -42,11 +64,10 @@ DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00 DEFINES += MAKE_FFMPEG_PLAYER } -#QT += sql CONFIG += exceptions \ rtti \ stl \ - c++14 \ + c++17 \ lrelease \ embed_translations @@ -66,6 +87,8 @@ LIBS += \ CONFIG+=utf8_source +CONFIG+=force_debug_info + win32 { TARGET = GoldenDict @@ -76,13 +99,10 @@ win32 { DEFINES += NOMINMAX __WIN64 } LIBS += -L$${PWD}/winlibs/lib/msvc - QMAKE_CXXFLAGS += /wd4290 # silence the warning C4290: C++ exception specification ignored + # silence the warning C4290: C++ exception specification ignored + QMAKE_CXXFLAGS += /wd4290 /Zc:__cplusplus /std:c++17 /permissive- # QMAKE_LFLAGS_RELEASE += /OPT:REF /OPT:ICF - # QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG - CONFIG+=force_debug_info - QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO - QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO - DEFINES += GD_NO_MANIFEST + # QMAKE_CXXFLAGS_RELEASE += /GL # slows down the linking significantly LIBS += -lshell32 -luser32 -lsapi -lole32 Debug: LIBS+= -lhunspelld @@ -126,7 +146,6 @@ unix:!mac { DEFINES += HAVE_X11 lessThan(QT_MAJOR_VERSION, 6): QT += x11extras - greaterThan(QT_MAJOR_VERSION, 5): QT += gui-private CONFIG += link_pkgconfig @@ -141,9 +160,7 @@ unix:!mac { libavcodec \ libswresample \ } - arm { - #LIBS += -liconv - } else { + !arm { LIBS += -lX11 -lXtst } @@ -223,8 +240,10 @@ DEFINES += PROGRAM_VERSION=\\\"$$VERSION\\\" # Input HEADERS += folding.hh \ + ankiconnector.h \ article_inspect.h \ articlewebpage.h \ + base/globalregex.hh \ globalbroadcaster.h \ iframeschemehandler.h \ inc_case_folding.hh \ @@ -364,8 +383,10 @@ FORMS += groups.ui \ fulltextsearch.ui SOURCES += folding.cc \ + ankiconnector.cpp \ article_inspect.cpp \ articlewebpage.cpp \ + base/globalregex.cc \ globalbroadcaster.cpp \ iframeschemehandler.cpp \ main.cc \ @@ -481,14 +502,11 @@ SOURCES += folding.cc \ win32 { FORMS += texttospeechsource.ui - SOURCES += wordbyauto.cc \ - guids.c \ + SOURCES += guids.c \ speechclient_win.cc \ texttospeechsource.cc \ speechhlp.cc - HEADERS += wordbyauto.hh \ - uiauto.hh \ - texttospeechsource.hh \ + HEADERS += texttospeechsource.hh \ sapi.hh \ sphelper.hh \ speechclient.hh \ diff --git a/hotkeywrapper.cc b/hotkeywrapper.cc index c942253c..1a26d9a4 100644 --- a/hotkeywrapper.cc +++ b/hotkeywrapper.cc @@ -468,10 +468,17 @@ void HotkeyWrapper::init() { keyToUngrab = grabbedKeys.end(); +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif + // We use RECORD extension instead of XGrabKey. That's because XGrabKey // prevents other clients from getting their input if it's grabbed. - Display * display = QX11Info::display(); + Display * display = displayID; lShiftCode = XKeysymToKeycode( display, XK_Shift_L ); rShiftCode = XKeysymToKeycode( display, XK_Shift_R ); @@ -678,13 +685,25 @@ public: ~X11GrabUngrabErrorHandler() { - XFlush( QX11Info::display() ); +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif + XFlush( displayID ); (void) XSetErrorHandler( previousErrorHandler_ ); } bool isError() const { - XFlush( QX11Info::display() ); +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif + XFlush( displayID ); return error; } @@ -706,8 +725,14 @@ HotkeyWrapper::GrabbedKeys::iterator HotkeyWrapper::grabKey( quint32 keyCode, if ( result.second ) { +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif X11GrabUngrabErrorHandler errorHandler; - XGrabKey( QX11Info::display(), keyCode, modifiers, QX11Info::appRootWindow(), + XGrabKey( displayID, keyCode, modifiers, DefaultRootWindow(displayID), True, GrabModeAsync, GrabModeAsync ); if ( errorHandler.isError() ) @@ -722,8 +747,14 @@ HotkeyWrapper::GrabbedKeys::iterator HotkeyWrapper::grabKey( quint32 keyCode, void HotkeyWrapper::ungrabKey( GrabbedKeys::iterator i ) { +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif X11GrabUngrabErrorHandler errorHandler; - XUngrabKey( QX11Info::display(), i->first, i->second, QX11Info::appRootWindow() ); + XUngrabKey( displayID, i->first, i->second, XDefaultRootWindow(displayID) ); grabbedKeys.erase( i ); @@ -746,14 +777,25 @@ quint32 HotkeyWrapper::nativeKey(int key) keySymName = QKeySequence( key ).toString(); break; } - - Display * display = QX11Info::display(); +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif + Display * display = displayID; return XKeysymToKeycode( display, XStringToKeysym( keySymName.toLatin1().data() ) ); } void HotkeyWrapper::unregister() { - Display * display = QX11Info::display(); +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif + Display * display = displayID; XRecordDisableContext( display, recordContext ); XSync( display, False ); diff --git a/hotkeywrapper.hh b/hotkeywrapper.hh index 844458b2..00ad0965 100644 --- a/hotkeywrapper.hh +++ b/hotkeywrapper.hh @@ -10,7 +10,7 @@ #include #include #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) -#include +#include #else #include #endif diff --git a/howto/how to connect with anki.md b/howto/how to connect with anki.md new file mode 100644 index 00000000..d5eeae08 --- /dev/null +++ b/howto/how to connect with anki.md @@ -0,0 +1,23 @@ +# prerequisite +1. install anki +2. install ankiconnect + +# configure anki +## 1. the model must have `Front` and `Back` fields +![Snipaste_2022-05-21_14-08-21](https://user-images.githubusercontent.com/105986/169638410-c6aa8038-df03-40de-8731-9f0b9f43bf59.png) +## 2. configure the template +the front template + +![image](https://user-images.githubusercontent.com/105986/169638457-2358d020-0132-469f-a6b4-0fb6d1590fa2.png) +the back template + +![image](https://user-images.githubusercontent.com/105986/169638440-7191fcdd-c338-48a3-a899-7216a5c77425.png) + +# configure goldendict +## 1. through toolbar=>preference=>network +![image](https://user-images.githubusercontent.com/105986/169657672-d1affbde-e80e-4110-8fd9-55f2645c5ee1.png) + +## 2. action +![image](https://user-images.githubusercontent.com/105986/169638740-abecde84-d33b-45ce-932c-d465c6650334.png) +## 3. result +![image](https://user-images.githubusercontent.com/105986/169638761-f67c009d-27cd-440d-bafa-ebbdce9577e3.png) diff --git a/htmlescape.cc b/htmlescape.cc index 59086e8c..946b1b6c 100644 --- a/htmlescape.cc +++ b/htmlescape.cc @@ -149,7 +149,7 @@ QString unescape( QString const & str, bool saveFormat ) { tmp.replace( QRegularExpression( "<(?:\\s*/?(?:div|h[1-6r]|q|p(?![alr])|br|li(?![ns])|td|blockquote|[uo]l|pre|d[dl]|nav|address))[^>]{0,}>", QRegularExpression::CaseInsensitiveOption ), " " ); - tmp.remove( QRegularExpression( "<[^>]*>" ) ); + tmp.replace( QRegularExpression( "<[^>]*>"), " "); } return QTextDocumentFragment::fromHtml( tmp.trimmed() ).toPlainText(); @@ -157,6 +157,35 @@ QString unescape( QString const & str, bool saveFormat ) return str; } +QString fromHtmlEscaped( QString const & str){ + QString retVal = str; + QRegularExpression regExp("(?\\<\\;)|(?\\>\\;)|(?\\&\\;)|(?\\"\\;)", QRegularExpression::PatternOption::CaseInsensitiveOption); + auto match = regExp.match(str, 0); + + while (match.hasMatch()) + { + if (!match.captured("lt").isEmpty()) + { + retVal.replace(match.capturedStart("lt"), match.capturedLength("lt"), "<"); + } + else if (!match.captured("gt").isEmpty()) + { + retVal.replace(match.capturedStart("gt"), match.capturedLength("gt"), ">"); + } + else if (!match.captured("amp").isEmpty()) + { + retVal.replace(match.capturedStart("amp"), match.capturedLength("amp"), "&"); + } + else if (!match.captured("quot").isEmpty()) + { + retVal.replace(match.capturedStart("quot"), match.capturedLength("quot"), "\""); + } + match = regExp.match(retVal, match.capturedStart() + 1); + } + + return retVal; +} + string unescapeUtf8( const string &str, bool saveFormat ) { return string( unescape( QString::fromUtf8( str.c_str(), str.size() ) ).toUtf8().data(), saveFormat ); diff --git a/htmlescape.hh b/htmlescape.hh index f86e4136..39f3d161 100644 --- a/htmlescape.hh +++ b/htmlescape.hh @@ -4,6 +4,7 @@ #ifndef __HTMLESCAPE_HH_INCLUDED__ #define __HTMLESCAPE_HH_INCLUDED__ +#include #include namespace Html { @@ -24,6 +25,8 @@ string escapeForJavaScript( string const & ); // Replace html entities QString unescape( QString const & str, bool saveFormat = false ); + +QString fromHtmlEscaped( QString const & str); string unescapeUtf8( string const & str, bool saveFormat = false ); } diff --git a/hunspell.cc b/hunspell.cc index c09638c3..70cb9d45 100644 --- a/hunspell.cc +++ b/hunspell.cc @@ -63,16 +63,16 @@ public: { } - virtual string getName() throw() + virtual string getName() noexcept { return name; } - virtual map< Property, string > getProperties() throw() + virtual map< Property, string > getProperties() noexcept { return map< Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return 0; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return 0; } virtual sptr< WordSearchRequest > prefixMatch( wstring const &, @@ -91,11 +91,11 @@ public: virtual bool isLocalDictionary() { return true; } - virtual vector< wstring > getAlternateWritings( const wstring & word ) throw(); + virtual vector< wstring > getAlternateWritings( const wstring & word ) noexcept; protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; private: @@ -142,7 +142,7 @@ bool containsWhitespace( wstring const & str ) return false; } -void HunspellDictionary::loadIcon() throw() +void HunspellDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; @@ -162,7 +162,7 @@ void HunspellDictionary::loadIcon() throw() dictionaryIconLoaded = true; } -vector< wstring > HunspellDictionary::getAlternateWritings( wstring const & word ) throw() +vector< wstring > HunspellDictionary::getAlternateWritings( wstring const & word ) noexcept { vector< wstring > results; 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 diff --git a/keyboardstate.cc b/keyboardstate.cc index b6163f19..2d336ed0 100644 --- a/keyboardstate.cc +++ b/keyboardstate.cc @@ -8,7 +8,7 @@ #include #elif defined(HAVE_X11) #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) -#include +#include #else #include #endif @@ -44,9 +44,17 @@ bool KeyboardState::checkModifiersPressed( int mask ) ( mask & Shift && !( keys & ( 1 << shiftKeyBit ) ) ) || ( mask & Win && !( keys & ( 1 << controlKeyBit ) ) ) ); #else + +#if QT_VERSION < 0x060000 + Display *displayID = QX11Info::display(); +#else + QNativeInterface::QX11Application *x11AppInfo = qApp->nativeInterface(); + Display *displayID = x11AppInfo->display(); +#endif + XkbStateRec state; - XkbGetState( QX11Info::display(), XkbUseCoreKbd, &state ); + XkbGetState( displayID, XkbUseCoreKbd, &state ); return !( ( mask & Alt && !( state.base_mods & Mod1Mask ) ) || diff --git a/loaddictionaries.cc b/loaddictionaries.cc index 24bdd1aa..38c88a1f 100644 --- a/loaddictionaries.cc +++ b/loaddictionaries.cc @@ -240,7 +240,7 @@ void LoadDictionaries::handlePath( Config::Path const & path ) #endif } -void LoadDictionaries::indexingDictionary( string const & dictionaryName ) throw() +void LoadDictionaries::indexingDictionary( string const & dictionaryName ) noexcept { emit indexingDictionarySignal( QString::fromUtf8( dictionaryName.c_str() ) ); } diff --git a/loaddictionaries.hh b/loaddictionaries.hh index 2c31f509..d9960c6a 100644 --- a/loaddictionaries.hh +++ b/loaddictionaries.hh @@ -46,7 +46,7 @@ signals: public: - virtual void indexingDictionary( std::string const & dictionaryName ) throw(); + virtual void indexingDictionary( std::string const & dictionaryName ) noexcept; private: diff --git a/locale/zh_CN.ts b/locale/zh_CN.ts index 9c9557ca..1fbe59c3 100644 --- a/locale/zh_CN.ts +++ b/locale/zh_CN.ts @@ -39,6 +39,20 @@ (c) 2008-2013 Konstantin Isakov (ikm@goldendict.org) + + AnkiConnector + + + anki: post to anki failed + anki:发布成功 + anki:发布失败 + + + + anki: post to anki success + anki: 发布成功 + + ArticleInspector @@ -315,7 +329,17 @@ 引用的音频播放程序不存在。 - + + Save &Bookmark "%1..." + 保存为书签(&S)“%1...” + + + + &Send "%1" to anki with selected text. + 将“%1”发送到anki并附带选择的文本。 + + + Sound files (*.wav *.ogg *.oga *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape);;All files (*.*) 声音文件(*.wav *.ogg *.oga *.mp3 *.mp4 *.aac *.flac *.mid *.wv *.ape);;所有文件(*.*) @@ -3903,7 +3927,27 @@ however, the article from the topmost dictionary is shown. 自定义设置 - + + Anki Connect + Anki连接 + + + + http:// + http:// + + + + Deck: + 牌组: + + + + Model: + 模板: + + + Some sites detect GoldenDict via HTTP headers and block the requests. Enable this option to workaround the problem. 部分网站屏蔽了使用 GoldenDict 浏览器标识(UA)的请求,启用此选项以绕过该问题。 @@ -4420,6 +4464,12 @@ from Stardict, Babylon and GLS dictionaries Date: %1%2 日期:%1%2 + + + + anki: post to anki failed + anki:发布失败 + QuickFilterLine diff --git a/lsa.cc b/lsa.cc index a6944a43..f30d1d6c 100644 --- a/lsa.cc +++ b/lsa.cc @@ -164,15 +164,15 @@ public: LsaDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ); - virtual string getName() throw(); + virtual string getName() noexcept; - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.soundsCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return getArticleCount(); } virtual sptr< Dictionary::DataRequest > getArticle( wstring const &, @@ -186,10 +186,10 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; -string LsaDictionary::getName() throw() +string LsaDictionary::getName() noexcept { string result = FsEncoding::basename( getDictionaryFilenames()[ 0 ] ); @@ -498,7 +498,7 @@ sptr< Dictionary::DataRequest > LsaDictionary::getResource( string const & name return dr; } -void LsaDictionary::loadIcon() throw() +void LsaDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/main.cc b/main.cc index 958e2b5d..1d32034d 100644 --- a/main.cc +++ b/main.cc @@ -253,7 +253,7 @@ int main( int argc, char ** argv ) #endif - QStringList localSchemes={"gdlookup","gdau","gico","qrcx","bres","bword","gdprg","gdvideo","gdpicture","gdtts","ifr"}; + QStringList localSchemes={"gdlookup","gdau","gico","qrcx","bres","bword","gdprg","gdvideo","gdpicture","gdtts","ifr", "entry"}; for (int i = 0; i < localSchemes.size(); ++i) { diff --git a/mainwindow.cc b/mainwindow.cc index 55db62e1..fb4ae931 100644 --- a/mainwindow.cc +++ b/mainwindow.cc @@ -60,9 +60,12 @@ #include "wstring_qt.hh" #endif +#include +#include + #ifdef HAVE_X11 #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) -#include +#include #else #include #endif @@ -90,6 +93,19 @@ class InitSSLRunnable : public QRunnable #endif +void MainWindow::changeWebEngineViewFont() +{ + if( cfg.preferences.webFontFamily.isEmpty() ) + { + QWebEngineProfile::defaultProfile()->settings()->resetFontFamily( QWebEngineSettings::StandardFont ); + } + else + { + QWebEngineProfile::defaultProfile()->settings()->setFontFamily( QWebEngineSettings::StandardFont, + cfg.preferences.webFontFamily ); + } +} + MainWindow::MainWindow( Config::Class & cfg_ ): trayIcon( 0 ), groupLabel( &searchPaneTitleBar ), @@ -148,6 +164,7 @@ MainWindow::MainWindow( Config::Class & cfg_ ): localSchemeHandler = new LocalSchemeHandler( articleNetMgr ); QWebEngineProfile::defaultProfile()->installUrlSchemeHandler( "gdlookup", localSchemeHandler ); QWebEngineProfile::defaultProfile()->installUrlSchemeHandler( "bword", localSchemeHandler ); + QWebEngineProfile::defaultProfile()->installUrlSchemeHandler( "entry", localSchemeHandler ); iframeSchemeHandler = new IframeSchemeHandler( this ); QWebEngineProfile::defaultProfile()->installUrlSchemeHandler( "ifr", iframeSchemeHandler ); @@ -761,6 +778,9 @@ MainWindow::MainWindow( Config::Class & cfg_ ): applyProxySettings(); + //set webengineview font + changeWebEngineViewFont(); + connect( &dictNetMgr, SIGNAL( proxyAuthenticationRequired( QNetworkProxy, QAuthenticator * ) ), this, SLOT( proxyAuthentication( QNetworkProxy, QAuthenticator * ) ) ); @@ -1225,6 +1245,10 @@ void MainWindow::closeEvent( QCloseEvent * ev ) void MainWindow::quitApp() { + if( inspector && inspector->isVisible() ) + { + inspector->close(); + } commitData(); qApp->quit(); } @@ -1393,6 +1417,7 @@ void MainWindow::updateGroupList() groupList->fill( groupInstances ); groupList->setCurrentGroup( cfg.lastMainGroupId ); + updateCurrentGroupProperty(); updateDictionaryBar(); @@ -1612,9 +1637,10 @@ ArticleView * MainWindow::createNewTab( bool switchToIt, groupList ); connect( view, &ArticleView::inspectSignal,this,[this](QWebEngineView * view){ - if(inspector){ - inspector->setInspectPage(view); + if( !inspector ){ + inspector = new ArticleInspector( this ); } + inspector->setInspectPage( view ); }); connect( view, SIGNAL( titleChanged( ArticleView *, QString const & ) ), @@ -1666,6 +1692,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 ); @@ -2790,10 +2817,6 @@ void MainWindow::showTranslationFor( Config::InputPhrase const & phrase, view->showDefinition( phrase, group, scrollTo ); - updatePronounceAvailability(); - updateFoundInDictsList(); - - updateBackForwardButtons(); //ui.tabWidget->setTabText( ui.tabWidget->indexOf(ui.tab), inWord.trimmed() ); } @@ -2815,11 +2838,6 @@ void MainWindow::showTranslationFor( QString const & inWord, view->showDefinition( inWord, dictIDs, searchRegExp, groupInstances[ groupList->currentIndex() ].id, ignoreDiacritics ); - - updatePronounceAvailability(); - updateFoundInDictsList(); - - updateBackForwardButtons(); } #ifdef HAVE_X11 @@ -2889,9 +2907,15 @@ void MainWindow::toggleMainWindow( bool onlyShow ) 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( QX11Info::display(), &wh, &rev ); + XGetInputFocus( displayID, &wh, &rev ); if( wh != translateLine->internalWinId() && !byIconClick ) { QPoint p( 1, 1 ); @@ -2904,17 +2928,17 @@ void MainWindow::toggleMainWindow( bool onlyShow ) event.xbutton.x_root = p.x(); event.xbutton.y_root = p.y(); event.xbutton.window = internalWinId(); - event.xbutton.root = QX11Info::appRootWindow( QX11Info::appScreen() ); + event.xbutton.root = XDefaultRootWindow(displayID); event.xbutton.state = Button1Mask; event.xbutton.button = Button1; event.xbutton.same_screen = true; event.xbutton.time = CurrentTime; - XSendEvent( QX11Info::display(), internalWinId(), true, 0xfff, &event ); - XFlush( QX11Info::display() ); + XSendEvent( displayID, internalWinId(), true, 0xfff, &event ); + XFlush( displayID ); event.type = ButtonRelease; - XSendEvent( QX11Info::display(), internalWinId(), true, 0xfff, &event ); - XFlush( QX11Info::display() ); + XSendEvent( displayID, internalWinId(), true, 0xfff, &event ); + XFlush( displayID ); } #endif } @@ -2966,7 +2990,19 @@ void MainWindow::hotKeyActivated( int hk ) toggleMainWindow(); else if ( scanPopup.get() ) + { +#ifdef HAVE_X11 + // When the user requests translation with the Ctrl+C+C hotkey in certain apps + // on some GNU/Linux systems, GoldenDict appears to handle Ctrl+C+C before the + // active application finishes handling Ctrl+C. As a result, GoldenDict finds + // the clipboard empty, silently cancels the translation request, and users report + // that Ctrl+C+C is broken in these apps. Slightly delay handling the clipboard + // hotkey to give the active application more time and thus work around the issue. + QTimer::singleShot( 10, scanPopup.get(), SLOT( translateWordFromClipboard() ) ); +#else scanPopup->translateWordFromClipboard(); +#endif + } } void MainWindow::prepareNewReleaseChecks() @@ -3470,7 +3506,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 ) @@ -4623,6 +4659,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; @@ -4695,8 +4740,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 7260ce3c..882dc1ae 100644 --- a/mainwindow.hh +++ b/mainwindow.hh @@ -260,6 +260,8 @@ private: TranslateBoxPopup popupAction ); void setTranslateBoxTextAndClearSuffix( QString const & text, WildcardPolicy wildcardPolicy, TranslateBoxPopup popupAction ); + void changeWebEngineViewFont(); + private slots: void hotKeyActivated( int ); @@ -460,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 ); diff --git a/mdictparser.cc b/mdictparser.cc index f3124a46..931a8fff 100644 --- a/mdictparser.cc +++ b/mdictparser.cc @@ -336,6 +336,26 @@ bool MdictParser::readHeader( QDataStream & in ) headerTextUtf16.clear(); in.setByteOrder( QDataStream::BigEndian ); + + //parse stylesheet + QString styleSheets; + + if( headerText.contains( "StyleSheet" ) ) + { + // a workaround to bypass https://bugreports.qt.io/browse/QTBUG-102612 + QRegularExpression rx( "StyleSheet=\"([^\"]*?)\"", QRegularExpression::CaseInsensitiveOption ); + + auto match = rx.match( headerText ); + + if( match.hasMatch() || match.hasPartialMatch() ) + { + styleSheets = match.captured( 1 ); + } + } + + //with this control character ,qt6.x can not parse attribute value. + headerText.remove(QRegularExpression("\\p{C}")); + QDomNamedNodeMap headerAttributes = parseHeaderAttributes( headerText ); encoding_ = headerAttributes.namedItem( "Encoding" ).toAttr().value(); @@ -352,32 +372,14 @@ bool MdictParser::readHeader( QDataStream & in ) // styleId # 1-255 // style.prefix // style.suffix - if ( headerAttributes.contains( "StyleSheet" ) ) + if ( !styleSheets.isEmpty() ) { -#if( QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) ) - //a workaround to bypass https://bugreports.qt.io/browse/QTBUG-102612 - QRegularExpression rx( "StyleSheet=\"([^\"]*?)\"", QRegularExpression::CaseInsensitiveOption ); - - auto match = rx.match( headerText ); - QString styleSheets; - - if( match.hasMatch() || match.hasPartialMatch() ) - { - styleSheets = match.captured( 1 ); - } -#else - QString styleSheets = headerAttributes.namedItem( "StyleSheet" ).toAttr().value(); -#endif QStringList lines = styleSheets.split( QRegularExpression( "[\r\n]" ), Qt::KeepEmptyParts ); for( int i = 0; i < lines.size() - 3; i += 3 ) { -#if( QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) ) styleSheets_[ lines[ i ].toInt() ] = - pair< QString, QString >( Html::unescape( lines[ i + 1 ] ), Html::unescape( lines[ i + 2 ] ) ); -#else - styleSheets_[ lines[ i ].toInt() ] = pair< QString, QString >( lines[ i + 1 ], lines[ i + 2 ] ); -#endif + pair( Html::fromHtmlEscaped( lines[ i + 1 ] ), Html::fromHtmlEscaped( lines[ i + 2 ] ) ); } } diff --git a/mdictparser.hh b/mdictparser.hh index b8dee43e..bd754f56 100644 --- a/mdictparser.hh +++ b/mdictparser.hh @@ -173,7 +173,7 @@ public: { QString s = QString::fromUtf8( article.c_str() ); substituteStylesheet( s, styleSheets ); - return string( s.toUtf8().constData() ); + return s.toStdString(); } protected: diff --git a/mdx.cc b/mdx.cc index 757de69f..b096959c 100644 --- a/mdx.cc +++ b/mdx.cc @@ -42,6 +42,7 @@ #include "tiff.hh" #include "utils.hh" +#include "base/globalregex.hh" namespace Mdx { @@ -162,7 +163,7 @@ public: MdictParser::RecordInfo indexEntry; vector< char > chunk; - Mutex::Lock _( idxMutex ); + // Mutex::Lock _( idxMutex ); const char * indexEntryPtr = chunks.getBlock( links[ 0 ].articleOffset, chunk ); memcpy( &indexEntry, indexEntryPtr, sizeof( indexEntry ) ); @@ -192,51 +193,6 @@ public: }; -struct MdxRegex -{ - MdxRegex() : - allLinksRe( "(?:<\\s*(a(?:rea)?|img|link|script|source)(?:\\s+[^>]+|\\s*)>)", - QRegularExpression::CaseInsensitiveOption ), - wordCrossLink( "([\\s\"']href\\s*=)\\s*([\"'])entry://([^>#]*?)((?:#[^>]*?)?)\\2", - QRegularExpression::CaseInsensitiveOption ), - anchorIdRe( "([\\s\"'](?:name|id)\\s*=)\\s*([\"'])\\s*(?=\\S)", QRegularExpression::CaseInsensitiveOption ), - anchorIdReWord( "([\\s\"'](?:name|id)\\s*=)\\s*([\"'])\\s*(?=\\S)([^\"]*)", QRegularExpression::CaseInsensitiveOption ), - anchorIdRe2( "([\\s\"'](?:name|id)\\s*=)\\s*(?=[^\"'])([^\\s\">]+)", QRegularExpression::CaseInsensitiveOption ), - anchorLinkRe( "([\\s\"']href\\s*=\\s*[\"'])entry://#", QRegularExpression::CaseInsensitiveOption ), - audioRe( "([\\s\"']href\\s*=)\\s*([\"'])sound://([^\">]+)\\2", - QRegularExpression::CaseInsensitiveOption | QRegularExpression::InvertedGreedinessOption ), - stylesRe( "([\\s\"']href\\s*=)\\s*([\"'])(?!\\s*\\b(?:(?:bres|https?|ftp)://" - "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\">]+)\\2", - QRegularExpression::CaseInsensitiveOption ), - stylesRe2( "([\\s\"']href\\s*=)\\s*(?![\\s\"']|\\b(?:(?:bres|https?|ftp)://" - "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\\s\">]+)", - QRegularExpression::CaseInsensitiveOption ), - inlineScriptRe( "<\\s*script(?:(?=\\s)(?:(?![\\s\"']src\\s*=)[^>])+|\\s*)>", - QRegularExpression::CaseInsensitiveOption ), - closeScriptTagRe( "<\\s*/script\\s*>", QRegularExpression::CaseInsensitiveOption ), - srcRe( "([\\s\"']src\\s*=)\\s*([\"'])(?!\\s*\\b(?:(?:bres|https?|ftp)://" - "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\">]+)\\2", - QRegularExpression::CaseInsensitiveOption ), - srcRe2( "([\\s\"']src\\s*=)\\s*(?![\\s\"']|\\b(?:(?:bres|https?|ftp)://" - "|(?:data|javascript):))(?:file://)?[\\x00-\\x1f\\x7f]*\\.*/?([^\\s\">]+)", - QRegularExpression::CaseInsensitiveOption ) - { - } - QRegularExpression allLinksRe; - QRegularExpression wordCrossLink; - QRegularExpression anchorIdRe; - QRegularExpression anchorIdReWord; - QRegularExpression anchorIdRe2; - QRegularExpression anchorLinkRe; - QRegularExpression audioRe; - QRegularExpression stylesRe; - QRegularExpression stylesRe2; - QRegularExpression inlineScriptRe; - QRegularExpression closeScriptTagRe; - QRegularExpression srcRe; - QRegularExpression srcRe2; -}; - class MdxDictionary: public BtreeIndexing::BtreeDictionary { Mutex idxMutex; @@ -256,8 +212,6 @@ class MdxDictionary: public BtreeIndexing::BtreeDictionary string initError; QString cacheDirName; - static MdxRegex mdxRx; - public: MdxDictionary( string const & id, string const & indexFile, vector const & dictionaryFiles ); @@ -266,22 +220,22 @@ public: virtual void deferredInit(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } @@ -327,7 +281,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; private: @@ -347,8 +301,6 @@ private: friend class MddResourceRequest; }; -MdxRegex MdxDictionary::mdxRx; - MdxDictionary::MdxDictionary( string const & id, string const & indexFile, vector const & dictionaryFiles ): BtreeDictionary( id, dictionaryFiles ), @@ -847,14 +799,14 @@ void MddResourceRequest::run() { QString css = QString::fromUtf8( data.data(), data.size() ); - QRegularExpression links( "url\\(\\s*(['\"]?)([^'\"]*)(['\"]?)\\s*\\)", - QRegularExpression::CaseInsensitiveOption ); + // QRegularExpression links( "url\\(\\s*(['\"]?)([^'\"]*)(['\"]?)\\s*\\)", + // QRegularExpression::CaseInsensitiveOption ); QString id = QString::fromUtf8( dict.getId().c_str() ); int pos = 0; QString newCSS; - QRegularExpressionMatchIterator it = links.globalMatch( css ); + QRegularExpressionMatchIterator it = RX::Mdx::links.globalMatch( css ); while ( it.hasNext() ) { QRegularExpressionMatch match = it.next(); @@ -915,7 +867,7 @@ const QString & MdxDictionary::getDescription() } else { - Mutex::Lock _( idxMutex ); + // Mutex::Lock _( idxMutex ); vector< char > chunk; char * dictDescription = chunks.getBlock( idxHeader.descriptionAddress, chunk ); string str( dictDescription ); @@ -925,7 +877,7 @@ const QString & MdxDictionary::getDescription() return dictionaryDescription; } -void MdxDictionary::loadIcon() throw() +void MdxDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; @@ -948,7 +900,7 @@ void MdxDictionary::loadIcon() throw() void MdxDictionary::loadArticle( uint32_t offset, string & articleText, bool noFilter ) { vector< char > chunk; - Mutex::Lock _( idxMutex ); + // Mutex::Lock _( idxMutex ); // Load record info from index MdictParser::RecordInfo recordInfo; @@ -959,25 +911,32 @@ void MdxDictionary::loadArticle( uint32_t offset, string & articleText, bool noF QString articleId; articleId.setNum( ( quint64 )pRecordInfo, 16 ); - ScopedMemMap compressed( dictFile, recordInfo.compressedBlockPos, recordInfo.compressedBlockSize ); - if ( !compressed.startAddress() ) - throw exCorruptDictionary(); - QByteArray decompressed; - if ( !MdictParser::parseCompressedBlock( recordInfo.compressedBlockSize, ( char * )compressed.startAddress(), - recordInfo.decompressedBlockSize, decompressed ) ) - throw exCorruptDictionary(); + + { + Mutex::Lock _( idxMutex ); + ScopedMemMap compressed( dictFile, recordInfo.compressedBlockPos, recordInfo.compressedBlockSize ); + if( !compressed.startAddress() ) + throw exCorruptDictionary(); + + if( !MdictParser::parseCompressedBlock( recordInfo.compressedBlockSize, + (char *)compressed.startAddress(), + recordInfo.decompressedBlockSize, + decompressed ) ) + throw exCorruptDictionary(); + } QString article = MdictParser::toUtf16( encoding.c_str(), decompressed.constData() + recordInfo.recordOffset, recordInfo.recordSize ); - article = MdictParser::substituteStylesheet( article, styleSheets ); - if( !noFilter ) + { + article = MdictParser::substituteStylesheet( article, styleSheets ); article = filterResource( articleId, article ); + } - articleText = string( article.toUtf8().constData() ); + articleText = article.toStdString(); } QString & MdxDictionary::filterResource( QString const & articleId, QString & article ) @@ -987,7 +946,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar QString articleNewText; int linkPos = 0; - QRegularExpressionMatchIterator it = mdxRx.allLinksRe.globalMatch( article ); + QRegularExpressionMatchIterator it = RX::Mdx::allLinksRe.globalMatch( article ); QMap idMap; while( it.hasNext() ) { @@ -1005,10 +964,10 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar if( !linkType.isEmpty() && linkType.at( 0 ) == 'a' ) { - QRegularExpressionMatch match = mdxRx.anchorIdRe.match( linkTxt ); + QRegularExpressionMatch match = RX::Mdx::anchorIdRe.match( linkTxt ); if( match.hasMatch() ) { - auto wordMatch = mdxRx.anchorIdReWord.match( linkTxt ); + auto wordMatch = RX::Mdx::anchorIdReWord.match( linkTxt ); if( wordMatch.hasMatch() ) { idMap.insert( wordMatch.captured( 3 ), uniquePrefix + wordMatch.captured( 3 ) ); @@ -1017,11 +976,11 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText ); } else - newLink = linkTxt.replace( mdxRx.anchorIdRe2, "\\1\"" + uniquePrefix + "\\2\"" ); + newLink = linkTxt.replace( RX::Mdx::anchorIdRe2, "\\1\"" + uniquePrefix + "\\2\"" ); - newLink = newLink.replace( mdxRx.anchorLinkRe, "\\1#" + uniquePrefix ); + newLink = newLink.replace( RX::Mdx::anchorLinkRe, "\\1#" + uniquePrefix ); - match = mdxRx.audioRe.match( newLink ); + match = RX::Mdx::audioRe.match( newLink ); if( match.hasMatch() ) { // sounds and audio link script @@ -1032,7 +991,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar + newLink.replace( match.capturedStart(), match.capturedLength(), newTxt ); } - match = mdxRx.wordCrossLink.match( newLink ); + match = RX::Mdx::wordCrossLink.match( newLink ); if( match.hasMatch() ) { QString newTxt = match.captured( 1 ) + match.captured( 2 ) @@ -1050,7 +1009,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar if( linkType.compare( "link" ) == 0 ) { // stylesheets - QRegularExpressionMatch match = mdxRx.stylesRe.match( linkTxt ); + QRegularExpressionMatch match = RX::Mdx::stylesRe.match( linkTxt ); if( match.hasMatch() ) { QString newText = match.captured( 1 ) + match.captured( 2 ) @@ -1059,7 +1018,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText ); } else - newLink = linkTxt.replace( mdxRx.stylesRe2, + newLink = linkTxt.replace( RX::Mdx::stylesRe2, "\\1\"bres://" + id + "/\\2\"" ); } else @@ -1067,13 +1026,13 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar || linkType.compare( "source" ) == 0 ) { // javascripts and images - QRegularExpressionMatch match = mdxRx.inlineScriptRe.match( linkTxt ); + QRegularExpressionMatch match = RX::Mdx::inlineScriptRe.match( linkTxt ); if( linkType.at( 1 ) == 'c' // "script" tag && match.hasMatch() && match.capturedLength() == linkTxt.length() ) { // skip inline scripts articleNewText += linkTxt; - match = mdxRx.closeScriptTagRe.match( article, linkPos ); + match = RX::Mdx::closeScriptTagRe.match( article, linkPos ); if( match.hasMatch() ) { articleNewText += article.mid( linkPos, match.capturedEnd() - linkPos ); @@ -1083,7 +1042,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar } else { - match = mdxRx.srcRe.match( linkTxt ); + match = RX::Mdx::srcRe.match( linkTxt ); if( match.hasMatch() ) { QString newText; @@ -1104,7 +1063,7 @@ QString & MdxDictionary::filterResource( QString const & articleId, QString & ar newLink = linkTxt.replace( match.capturedStart(), match.capturedLength(), newText ); } else - newLink = linkTxt.replace( mdxRx.srcRe2, + newLink = linkTxt.replace( RX::Mdx::srcRe2, "\\1\"bres://" + id + "/\\2\"" ); } } @@ -1409,7 +1368,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f if ( !parser.open( i->c_str() ) ) continue; - string title = string( parser.title().toUtf8().constData() ); + string title = parser.title().toStdString(); initializing.indexingDictionary( title ); for ( vector< string >::const_iterator mddIter = dictFiles.begin() + 1; @@ -1440,7 +1399,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f // then the encoding { - string encoding = string( parser.encoding().toUtf8().constData() ); + string encoding = parser.encoding().toStdString(); idx.write< uint32_t >( encoding.size() ); idx.write( encoding.data(), encoding.size() ); } @@ -1457,7 +1416,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f // Save dictionary description if there's one { - string description = string( parser.description().toUtf8().constData() ); + string description = parser.description().toStdString(); idxHeader.descriptionAddress = chunks.startNewBlock(); chunks.addToBlock( description.c_str(), description.size() + 1 ); idxHeader.descriptionSize = description.size() + 1; @@ -1491,7 +1450,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f mddIndices.push_back( mddIndexedWords ); // Save filename for .mdd files only QFileInfo fi( mddParser->filename() ); - mddFileNames.push_back( string( fi.fileName().toUtf8().constData() ) ); + mddFileNames.push_back( fi.fileName().toStdString() ); mddParsers.pop_front(); } @@ -1514,8 +1473,8 @@ vector< sptr< Dictionary::Class > > makeDictionaries( vector< string > const & f for ( MdictParser::StyleSheets::const_iterator iter = styleSheets.begin(); iter != styleSheets.end(); ++iter ) { - string styleBegin( iter->second.first.toUtf8().constData() ); - string styleEnd( iter->second.second.toUtf8().constData() ); + string styleBegin(iter->second.first.toStdString()); + string styleEnd( iter->second.second.toStdString() ); // key idx.write( iter->first ); diff --git a/mediawiki.cc b/mediawiki.cc index e0a62325..96998517 100644 --- a/mediawiki.cc +++ b/mediawiki.cc @@ -47,16 +47,16 @@ public: langId = LangCoder::code2toInt( url.mid( n - 2, 2 ).toLatin1().data() ); } - virtual string getName() throw() + virtual string getName() noexcept { return name; } - virtual map< Property, string > getProperties() throw() + virtual map< Property, string > getProperties() noexcept { return map< Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return 0; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return 0; } virtual sptr< WordSearchRequest > prefixMatch( wstring const &, @@ -73,11 +73,11 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; -void MediaWikiDictionary::loadIcon() throw() +void MediaWikiDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/mutex.hh b/mutex.hh index 5a6ce6a1..694ae698 100644 --- a/mutex.hh +++ b/mutex.hh @@ -4,16 +4,16 @@ #ifndef __MUTEX_HH_INCLUDED__ #define __MUTEX_HH_INCLUDED__ -#include +#include /// This provides a mutex class. As you can see, it's just a Qt one, but it /// does provide the Lock class which doesn't seem to exist in Qt, and it does /// provide some abstraction for dictionaries in case they are to be ported /// away from Qt. -class Mutex : public QRecursiveMutex +class Mutex : public QMutex { public: - Mutex() : QRecursiveMutex() + Mutex() : QMutex() {} ~Mutex() {} diff --git a/preferences.cc b/preferences.cc index 6bafa8b7..8a8e6872 100644 --- a/preferences.cc +++ b/preferences.cc @@ -323,6 +323,13 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ): ui.customSettingsGroup->setEnabled( p.proxyServer.enabled ); } + //anki connect + ui.useAnkiConnect->setChecked( p.ankiConnectServer.enabled ); + ui.ankiHost->setText( p.ankiConnectServer.host ); + ui.ankiPort->setValue( p.ankiConnectServer.port ); + ui.ankiModel->setText( p.ankiConnectServer.model ); + ui.ankiDeck->setText(p.ankiConnectServer.deck); + connect( ui.customProxy, SIGNAL( toggled( bool ) ), this, SLOT( customProxyToggled( bool ) ) ); @@ -466,6 +473,13 @@ Config::Preferences Preferences::getPreferences() p.proxyServer.user = ui.proxyUser->text(); p.proxyServer.password = ui.proxyPassword->text(); + //anki connect + p.ankiConnectServer.enabled = ui.useAnkiConnect->isChecked(); + p.ankiConnectServer.host = ui.ankiHost->text(); + p.ankiConnectServer.port = (unsigned)ui.ankiPort->value(); + p.ankiConnectServer.deck = ui.ankiDeck->text(); + p.ankiConnectServer.model = ui.ankiModel->text(); + p.checkForNewReleases = ui.checkForNewReleases->isChecked(); p.disallowContentFromOtherSites = ui.disallowContentFromOtherSites->isChecked(); p.enableWebPlugins = ui.enableWebPlugins->isChecked(); diff --git a/preferences.ui b/preferences.ui index 8ce260f2..f4df1e9f 100644 --- a/preferences.ui +++ b/preferences.ui @@ -993,6 +993,9 @@ for all program's network requests. + + false + Custom settings @@ -1011,6 +1014,9 @@ for all program's network requests. + + false + Host: @@ -1073,20 +1079,110 @@ for all program's network requests. - - - Qt::Vertical + + + true - - QSizePolicy::Fixed + + Anki Connect - - - 20 - 10 - + + true - + + false + + + + + + + + Host: + + + + + + + http:// + + + + + + + + + + Port: + + + + + + + 65535 + + + 8080 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Deck: + + + + + + + + + + Model: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + @@ -1176,22 +1272,6 @@ clears its network cache from disk during exit. - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 10 - - - - diff --git a/programs.cc b/programs.cc index cd2b07df..f3a619ca 100644 --- a/programs.cc +++ b/programs.cc @@ -30,16 +30,16 @@ public: { } - virtual string getName() throw() + virtual string getName() noexcept { return prg.name.toUtf8().data(); } - virtual map< Property, string > getProperties() throw() + virtual map< Property, string > getProperties() noexcept { return map< Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return 0; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return 0; } virtual sptr< WordSearchRequest > prefixMatch( wstring const & word, @@ -53,7 +53,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; sptr< WordSearchRequest > ProgramsDictionary::prefixMatch( wstring const & word, @@ -118,7 +118,7 @@ sptr< Dictionary::DataRequest > ProgramsDictionary::getArticle( } } -void ProgramsDictionary::loadIcon() throw() +void ProgramsDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/resources/gd-custom.js b/resources/gd-custom.js index ae07cf31..d7557151 100644 --- a/resources/gd-custom.js +++ b/resources/gd-custom.js @@ -6,6 +6,11 @@ $(function() { if ('string' != typeof(link)) { return; } + + if(link.indexOf("javascript:")>=0){ + return; + } + if(link.indexOf(":")>=0){ emitClickedEvent(link); return false; diff --git a/sdict.cc b/sdict.cc index 0b1cb8a5..3162d838 100644 --- a/sdict.cc +++ b/sdict.cc @@ -143,16 +143,16 @@ class SdictDictionary: public BtreeIndexing::BtreeDictionary ~SdictDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -187,7 +187,7 @@ class SdictDictionary: public BtreeIndexing::BtreeDictionary } protected: - void loadIcon() throw(); + void loadIcon() noexcept; private: @@ -240,7 +240,7 @@ SdictDictionary::~SdictDictionary() df.close(); } -void SdictDictionary::loadIcon() throw() +void SdictDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/slob.cc b/slob.cc index b66e061c..9b952e6a 100644 --- a/slob.cc +++ b/slob.cc @@ -587,16 +587,16 @@ class SlobDictionary: public BtreeIndexing::BtreeDictionary ~SlobDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -643,7 +643,7 @@ class SlobDictionary: public BtreeIndexing::BtreeDictionary protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; private: @@ -694,12 +694,12 @@ SlobDictionary::SlobDictionary( string const & id, // Read dictionary name - dictionaryName = string( sf.getDictionaryName().toUtf8().constData() ); + dictionaryName = sf.getDictionaryName().toStdString(); if( dictionaryName.empty() ) { QString name = QDir::fromNativeSeparators( FsEncoding::decode( dictionaryFiles[ 0 ].c_str() ) ); int n = name.lastIndexOf( '/' ); - dictionaryName = string( name.mid( n + 1 ).toUtf8().constData() ); + dictionaryName = name.mid( n + 1 ).toStdString(); } // Full-text search parameters @@ -746,7 +746,7 @@ void SlobDictionary::removeDirectory( QString const & directory ) dir.rmdir( directory ); } -void SlobDictionary::loadIcon() throw() +void SlobDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; @@ -799,7 +799,7 @@ void SlobDictionary::loadArticle( quint32 address, articleText = convert( articleText, entry ); } else - articleText = string( QObject::tr( "Article decoding error" ).toUtf8().constData() ); + articleText = QObject::tr( "Article decoding error" ).toStdString(); // See Issue #271: A mechanism to clean-up invalid HTML cards. string cleaner = """""""""""" diff --git a/sounddir.cc b/sounddir.cc index 46b58749..095b3abf 100644 --- a/sounddir.cc +++ b/sounddir.cc @@ -79,16 +79,16 @@ public: vector< string > const & dictionaryFiles, QString const & iconFilename_ ); - virtual string getName() throw() + virtual string getName() noexcept { return name; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.soundsCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return getArticleCount(); } virtual sptr< Dictionary::DataRequest > getArticle( wstring const &, @@ -102,7 +102,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; SoundDirDictionary::SoundDirDictionary( string const & id, @@ -289,7 +289,7 @@ sptr< Dictionary::DataRequest > SoundDirDictionary::getArticle( wstring const & return ret; } -void SoundDirDictionary::loadIcon() throw() +void SoundDirDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/stardict.cc b/stardict.cc index 9c3f347e..34417f29 100644 --- a/stardict.cc +++ b/stardict.cc @@ -155,16 +155,16 @@ public: ~StardictDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return bookName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.wordCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount + idxHeader.synWordCount; } inline virtual quint32 getLangFrom() const @@ -207,7 +207,7 @@ public: } protected: - void loadIcon() throw(); + void loadIcon() noexcept; private: @@ -292,7 +292,7 @@ StardictDictionary::~StardictDictionary() dict_data_close( dz ); } -void StardictDictionary::loadIcon() throw() +void StardictDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/transliteration.cc b/transliteration.cc index aefc13fd..fc8604ae 100644 --- a/transliteration.cc +++ b/transliteration.cc @@ -22,16 +22,16 @@ BaseTransliterationDictionary::BaseTransliterationDictionary( string const & id, dictionaryIconLoaded = true; } -string BaseTransliterationDictionary::getName() throw() +string BaseTransliterationDictionary::getName() noexcept { return name; } -map< Dictionary::Property, string > BaseTransliterationDictionary::getProperties() throw() +map< Dictionary::Property, string > BaseTransliterationDictionary::getProperties() noexcept { return map< Dictionary::Property, string >(); } -unsigned long BaseTransliterationDictionary::getArticleCount() throw() +unsigned long BaseTransliterationDictionary::getArticleCount() noexcept { return 0; } -unsigned long BaseTransliterationDictionary::getWordCount() throw() +unsigned long BaseTransliterationDictionary::getWordCount() noexcept { return 0; } sptr< Dictionary::WordSearchRequest > BaseTransliterationDictionary::prefixMatch( wstring const &, @@ -83,7 +83,7 @@ TransliterationDictionary::TransliterationDictionary( string const & id, } vector< wstring > TransliterationDictionary::getAlternateWritings( wstring const & str ) - throw() + noexcept { vector< wstring > results; diff --git a/transliteration.hh b/transliteration.hh index d6d96198..b35a9785 100644 --- a/transliteration.hh +++ b/transliteration.hh @@ -28,16 +28,16 @@ public: BaseTransliterationDictionary( string const & id, string const & name, QIcon icon, bool caseSensitive = true ); - virtual string getName() throw(); + virtual string getName() noexcept; - virtual map< Dictionary::Property, string > getProperties() throw(); + virtual map< Dictionary::Property, string > getProperties() noexcept; - virtual unsigned long getArticleCount() throw(); + virtual unsigned long getArticleCount() noexcept; - virtual unsigned long getWordCount() throw(); + virtual unsigned long getWordCount() noexcept; virtual vector< wstring > getAlternateWritings( wstring const & ) - throw() = 0; + noexcept = 0; virtual sptr< Dictionary::WordSearchRequest > findHeadwordsForSynonym( wstring const & ) ; @@ -85,7 +85,7 @@ public: bool caseSensitive = true ); virtual vector< wstring > getAlternateWritings( wstring const & ) - throw(); + noexcept; }; } diff --git a/uiauto.hh b/uiauto.hh deleted file mode 100644 index 4e40ff48..00000000 --- a/uiauto.hh +++ /dev/null @@ -1,334 +0,0 @@ -#ifndef __UIAUTO_HH_INCLUDED__ -#define __UIAUTO_HH_INCLUDED__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -EXTERN_C const IID IID_IUIAutomation; -EXTERN_C const IID CLSID_CUIAutomation; -EXTERN_C const IID IID_IUIAutomationElement; -EXTERN_C const IID IID_IUIAutomationTextPattern; -EXTERN_C const IID IID_IUIAutomationTextRange; -EXTERN_C const IID IID_IUIAutomationTreeWalker; - -typedef interface IUIAutomationElement IUIAutomationElement; -typedef interface IUIAutomationElementArray IUIAutomationElementArray; -typedef interface IUIAutomationTextPattern IUIAutomationTextPattern; -typedef interface IUIAutomationTextRange IUIAutomationTextRange; -typedef interface IUIAutomationTextRangeArray IUIAutomationTextRangeArray; -typedef interface IUIAutomationCacheRequest IUIAutomationCacheRequest; -typedef interface IUIAutomationTreeWalker IUIAutomationTreeWalker; -typedef interface IUIAutomationCondition IUIAutomationCondition; -typedef interface IUIAutomationEventHandler IUIAutomationEventHandler; -typedef interface IUIAutomationPropertyChangedEventHandler IUIAutomationPropertyChangedEventHandler; -typedef interface IUIAutomationStructureChangedEventHandler IUIAutomationStructureChangedEventHandler; -typedef interface IUIAutomationFocusChangedEventHandler IUIAutomationFocusChangedEventHandler; -typedef interface IUIAutomationProxyFactory IUIAutomationProxyFactory; -typedef interface IUIAutomationProxyFactoryEntry IUIAutomationProxyFactoryEntry; -typedef interface IUIAutomationProxyFactoryMapping IUIAutomationProxyFactoryMapping; - -typedef void *UIA_HWND; -typedef int PROPERTYID; -typedef int EVENTID; -typedef int PATTERNID; -typedef int CONTROLTYPEID; -typedef int TEXTATTRIBUTEID; - -enum TreeScope -{ - TreeScope_Element = 0x1, - TreeScope_Children = 0x2, - TreeScope_Descendants = 0x4, - TreeScope_Parent = 0x8, - TreeScope_Ancestors = 0x10, - TreeScope_Subtree = ( ( TreeScope_Element | TreeScope_Children ) | TreeScope_Descendants ) -}; - -enum PropertyConditionFlags -{ - PropertyConditionFlags_None = 0, - PropertyConditionFlags_IgnoreCase = 0x1 -}; - -enum OrientationType -{ - OrientationType_None = 0, - OrientationType_Horizontal = 1, - OrientationType_Vertical = 2 -}; - -enum SupportedTextSelection -{ - SupportedTextSelection_None = 0, - SupportedTextSelection_Single = 1, - SupportedTextSelection_Multiple = 2 -}; - -enum TextPatternRangeEndpoint -{ - TextPatternRangeEndpoint_Start = 0, - TextPatternRangeEndpoint_End = 1 -}; - -enum TextUnit -{ - TextUnit_Character = 0, - TextUnit_Format = 1, - TextUnit_Word = 2, - TextUnit_Line = 3, - TextUnit_Paragraph = 4, - TextUnit_Page = 5, - TextUnit_Document = 6 -}; - -enum ProviderOptions -{ - ProviderOptions_ClientSideProvider = 0x1, - ProviderOptions_ServerSideProvider = 0x2, - ProviderOptions_NonClientAreaProvider = 0x4, - ProviderOptions_OverrideProvider = 0x8, - ProviderOptions_ProviderOwnsSetFocus = 0x10, - ProviderOptions_UseComThreading = 0x20 -} ; - -/* UIA_PatternIds */ -const long UIA_InvokePatternId = 10000; -const long UIA_SelectionPatternId = 10001; -const long UIA_ValuePatternId = 10002; -const long UIA_RangeValuePatternId = 10003; -const long UIA_ScrollPatternId = 10004; -const long UIA_ExpandCollapsePatternId = 10005; -const long UIA_GridPatternId = 10006; -const long UIA_GridItemPatternId = 10007; -const long UIA_MultipleViewPatternId = 10008; -const long UIA_WindowPatternId = 10009; -const long UIA_SelectionItemPatternId = 10010; -const long UIA_DockPatternId = 10011; -const long UIA_TablePatternId = 10012; -const long UIA_TableItemPatternId = 10013; -const long UIA_TextPatternId = 10014; -const long UIA_TogglePatternId = 10015; -const long UIA_TransformPatternId = 10016; -const long UIA_ScrollItemPatternId = 10017; -const long UIA_LegacyIAccessiblePatternId = 10018; -const long UIA_ItemContainerPatternId = 10019; -const long UIA_VirtualizedItemPatternId = 10020; -const long UIA_SynchronizedInputPatternId = 10021; - -#ifdef INTERFACE -#undef INTERFACE -#endif - -#define INTERFACE IUIAutomation -DECLARE_INTERFACE_(IUIAutomation, IUnknown) -{ - STDMETHOD(CompareElements)(THIS_ IUIAutomationElement *, IUIAutomationElement *, BOOL *) PURE; - STDMETHOD(CompareRuntimeIds)(THIS_ SAFEARRAY *, SAFEARRAY *, BOOL *) PURE; - STDMETHOD(GetRootElement)(THIS_ IUIAutomationElement **) PURE; - STDMETHOD(ElementFromHandle)(THIS_ UIA_HWND, IUIAutomationElement **) PURE; - STDMETHOD(ElementFromPoint)(THIS_ POINT, IUIAutomationElement **) PURE; - STDMETHOD(GetFocusedElement)(THIS_ IUIAutomationElement **) PURE; - STDMETHOD(GetRootElementBuildCache)(THIS_ IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(ElementFromHandleBuildCache)(THIS_ UIA_HWND, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(ElementFromPointBuildCache)(THIS_ POINT, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(GetFocusedElementBuildCache)(THIS_ IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(CreateTreeWalker)(THIS_ IUIAutomationCondition *, IUIAutomationTreeWalker **) PURE; - STDMETHOD(get_ControlViewWalker)(THIS_ IUIAutomationTreeWalker **) PURE; - STDMETHOD(get_ContentViewWalker)(THIS_ IUIAutomationTreeWalker **) PURE; - STDMETHOD(get_RawViewWalker)(THIS_ IUIAutomationTreeWalker **) PURE; - STDMETHOD(get_RawViewCondition)(THIS_ IUIAutomationCondition **) PURE; - STDMETHOD(get_ControlViewCondition)(THIS_ IUIAutomationCondition **) PURE; - STDMETHOD(get_ContentViewCondition)(THIS_ IUIAutomationCondition **) PURE; - STDMETHOD(CreateCacheRequest)(THIS_ IUIAutomationCacheRequest **) PURE; - STDMETHOD(CreateTrueCondition)(THIS_ IUIAutomationCondition **) PURE; - STDMETHOD(CreateFalseCondition)(THIS_ IUIAutomationCondition **) PURE; - STDMETHOD(CreatePropertyCondition)(THIS_ PROPERTYID, VARIANT, IUIAutomationCondition **) PURE; - STDMETHOD(CreatePropertyConditionEx)(THIS_ PROPERTYID, VARIANT, enum PropertyConditionFlags, IUIAutomationCondition **) PURE; - STDMETHOD(CreateAndCondition)(THIS_ IUIAutomationCondition *, IUIAutomationCondition *, IUIAutomationCondition **) PURE; - STDMETHOD(CreateAndConditionFromArray)(THIS_ SAFEARRAY *, IUIAutomationCondition **) PURE; - STDMETHOD(CreateAndConditionFromNativeArray)(THIS_ IUIAutomationCondition **, int , IUIAutomationCondition **) PURE; - STDMETHOD(CreateOrCondition)(THIS_ IUIAutomationCondition *, IUIAutomationCondition *, IUIAutomationCondition **) PURE; - STDMETHOD(CreateOrConditionFromArray)(THIS_ SAFEARRAY *, IUIAutomationCondition **) PURE; - STDMETHOD(CreateOrConditionFromNativeArray)(THIS_ IUIAutomationCondition **, int , IUIAutomationCondition **) PURE; - STDMETHOD(CreateNotCondition)(THIS_ IUIAutomationCondition *, IUIAutomationCondition **) PURE; - STDMETHOD(AddAutomationEventHandler)(THIS_ EVENTID, IUIAutomationElement *, enum TreeScope, IUIAutomationCacheRequest *, IUIAutomationEventHandler *) PURE; - STDMETHOD(RemoveAutomationEventHandler)(THIS_ EVENTID, IUIAutomationElement *, IUIAutomationEventHandler *) PURE; - STDMETHOD(AddPropertyChangedEventHandlerNativeArray)(THIS_ IUIAutomationElement *, enum TreeScope, IUIAutomationCacheRequest *, - IUIAutomationPropertyChangedEventHandler *, PROPERTYID *, int) PURE; - STDMETHOD(AddPropertyChangedEventHandler)(THIS_ IUIAutomationElement *, enum TreeScope, EVENTID, IUIAutomationCacheRequest *, - IUIAutomationPropertyChangedEventHandler *, SAFEARRAY *) PURE; - STDMETHOD(RemovePropertyChangedEventHandler)(THIS_ IUIAutomationElement *, IUIAutomationPropertyChangedEventHandler *) PURE; - STDMETHOD(AddStructureChangedEventHandler)(THIS_ IUIAutomationElement *, enum TreeScope, IUIAutomationCacheRequest *, IUIAutomationStructureChangedEventHandler *) PURE; - STDMETHOD(RemoveStructureChangedEventHandler)(THIS_ IUIAutomationElement *, IUIAutomationStructureChangedEventHandler *) PURE; - STDMETHOD(AddFocusChangedEventHandler)(THIS_ IUIAutomationCacheRequest *, IUIAutomationFocusChangedEventHandler *) PURE; - STDMETHOD(RemoveFocusChangedEventHandler)(THIS_ IUIAutomationFocusChangedEventHandler *) PURE; - STDMETHOD(RemoveAllEventHandlers)(THIS) PURE; - STDMETHOD(IntNativeArrayToSafeArray)(THIS_ int *, int, SAFEARRAY **) PURE; - STDMETHOD(IntSafeArrayToNativeArray)(THIS_ SAFEARRAY *, int **, int *) PURE; - STDMETHOD(RectToVariant)(THIS_ RECT, VARIANT *) PURE; - STDMETHOD(VariantToRect)(THIS_ VARIANT, RECT *) PURE; - STDMETHOD(SafeArrayToRectNativeArray)(THIS_ SAFEARRAY *, RECT **, int *) PURE; - STDMETHOD(CreateProxyFactoryEntry)(THIS_ IUIAutomationProxyFactory *, IUIAutomationProxyFactoryEntry **) PURE; - STDMETHOD(get_ProxyFactoryMapping)(THIS_ IUIAutomationProxyFactoryMapping **) PURE; - STDMETHOD(GetPropertyProgrammaticName)(THIS_ PROPERTYID, BSTR *) PURE; - STDMETHOD(GetPatternProgrammaticName)(THIS_ PATTERNID, BSTR *) PURE; - STDMETHOD(PollForPotentialSupportedPatterns)(THIS_ IUIAutomationElement *, SAFEARRAY **, SAFEARRAY **) PURE; - STDMETHOD(PollForPotentialSupportedProperties)(THIS_ IUIAutomationElement *, SAFEARRAY **, SAFEARRAY **) PURE; - STDMETHOD(CheckNotSupported)(THIS_ VARIANT, BOOL *) PURE; - STDMETHOD(get_ReservedNotSupportedValue)(THIS_ IUnknown **) PURE; - STDMETHOD(get_ReservedMixedAttributeValue)(THIS_ IUnknown **) PURE; - STDMETHOD(ElementFromIAccessible)(THIS_ IAccessible *, int, IUIAutomationElement **) PURE; - STDMETHOD(ElementFromIAccessibleBuildCache)(THIS_ IAccessible *, int, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; -}; -#undef INTERFACE - -#define INTERFACE IUIAutomationElement -DECLARE_INTERFACE_(IUIAutomationElement, IUnknown) -{ - STDMETHOD(SetFocus)(THIS) PURE; - STDMETHOD(GetRuntimeId)(THIS_ SAFEARRAY **) PURE; - STDMETHOD(FindFirst)(THIS_ enum TreeScope, IUIAutomationCondition *, IUIAutomationElement **) PURE; - STDMETHOD(FindAll)(THIS_ enum TreeScope, IUIAutomationCondition *, IUIAutomationElementArray **) PURE; - STDMETHOD(FindFirstBuildCache)(THIS_ enum TreeScope, IUIAutomationCondition *, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(FindAllBuildCache)(THIS_ enum TreeScope, IUIAutomationCondition *, IUIAutomationCacheRequest *, IUIAutomationElementArray **) PURE; - STDMETHOD(BuildUpdatedCache)(THIS_ IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(GetCurrentPropertyValue)(THIS_ PROPERTYID, VARIANT *) PURE; - STDMETHOD(GetCurrentPropertyValueEx)(THIS_ PROPERTYID, BOOL, VARIANT *) PURE; - STDMETHOD(GetCachedPropertyValue)(THIS_ PROPERTYID, VARIANT *) PURE; - STDMETHOD(GetCachedPropertyValueEx)(THIS_ PROPERTYID, BOOL, VARIANT *) PURE; - STDMETHOD(GetCurrentPatternAs)(THIS_ PATTERNID, REFIID, void **) PURE; - STDMETHOD(GetCachedPatternAs)(THIS_ PATTERNID, REFIID, void **) PURE; - STDMETHOD(GetCurrentPattern)(THIS_ PATTERNID, IUnknown **) PURE; - STDMETHOD(GetCachedPattern)(THIS_ PATTERNID, IUnknown **) PURE; - STDMETHOD(GetCachedParent)(THIS_ IUIAutomationElement **) PURE; - STDMETHOD(GetCachedChildren)(THIS_ IUIAutomationElement **) PURE; - STDMETHOD(get_CurrentProcessId)(THIS_ int *) PURE; - STDMETHOD(get_CurrentControlType)(THIS_ CONTROLTYPEID *) PURE; - STDMETHOD(get_CurrentLocalizedControlType)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentName)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentAcceleratorKey)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentAccessKey)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentHasKeyboardFocus)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentIsKeyboardFocusable)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentIsEnabled)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentAutomationId)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentClassName)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentHelpText)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentCulture)(THIS_ int *) PURE; - STDMETHOD(get_CurrentIsControlElement)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentIsContentElement)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentIsPassword)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentNativeWindowHandle)(THIS_ UIA_HWND *) PURE; - STDMETHOD(get_CurrentItemType)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentIsOffscreen)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentOrientation)(THIS_ enum OrientationType *) PURE; - STDMETHOD(get_CurrentFrameworkId)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentIsRequiredForForm)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentItemStatus)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentBoundingRectangle)(THIS_ RECT *) PURE; - STDMETHOD(get_CurrentLabeledBy)(THIS_ IUIAutomationElement **) PURE; - STDMETHOD(get_CurrentAriaRole)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentAriaProperties)(THIS_ BSTR *) PURE; - STDMETHOD(get_CurrentIsDataValidForForm)(THIS_ BOOL *) PURE; - STDMETHOD(get_CurrentControllerFor)(THIS_ IUIAutomationElementArray **) PURE; - STDMETHOD(get_CurrentDescribedBy)(THIS_ IUIAutomationElementArray **) PURE; - STDMETHOD(get_CurrentFlowsTo)(THIS_ IUIAutomationElementArray **) PURE; - STDMETHOD(get_CurrentProviderDescription)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedProcessId)(THIS_ int *) PURE; - STDMETHOD(get_CachedControlType)(THIS_ CONTROLTYPEID *) PURE; - STDMETHOD(get_CachedLocalizedControlType)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedName)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedAcceleratorKey)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedAccessKey)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedHasKeyboardFocus)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedIsKeyboardFocusable)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedIsEnabled)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedAutomationId)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedClassName)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedHelpText)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedCulture)(THIS_ int *) PURE; - STDMETHOD(get_CachedIsControlElement)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedIsContentElement)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedIsPassword)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedNativeWindowHandle)(THIS_ UIA_HWND *) PURE; - STDMETHOD(get_CachedItemType)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedIsOffscreen)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedOrientation)(THIS_ enum OrientationType *) PURE; - STDMETHOD(get_CachedFrameworkId)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedIsRequiredForForm)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedItemStatus)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedBoundingRectangle)(THIS_ RECT *) PURE; - STDMETHOD(get_CachedLabeledBy)(THIS_ IUIAutomationElement **) PURE; - STDMETHOD(get_CachedAriaRole)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedAriaProperties)(THIS_ BSTR *) PURE; - STDMETHOD(get_CachedIsDataValidForForm)(THIS_ BOOL *) PURE; - STDMETHOD(get_CachedControllerFor)(THIS_ IUIAutomationElementArray **) PURE; - STDMETHOD(get_CachedDescribedBy)(THIS_ IUIAutomationElementArray **) PURE; - STDMETHOD(get_CachedFlowsTo)(THIS_ IUIAutomationElementArray **) PURE; - STDMETHOD(get_CachedProviderDescription)(THIS_ BSTR *) PURE; -}; -#undef INTERFACE - -#define INTERFACE IUIAutomationTextPattern -DECLARE_INTERFACE_(IUIAutomationTextPattern, IUnknown) -{ - STDMETHOD(RangeFromPoint)(THIS_ POINT, IUIAutomationTextRange **) PURE; - STDMETHOD(RangeFromChild)(THIS_ IUIAutomationElement *, IUIAutomationTextRange **) PURE; - STDMETHOD(GetSelection)(THIS_ IUIAutomationTextRangeArray **) PURE; - STDMETHOD(GetVisibleRanges)(THIS_ IUIAutomationTextRangeArray **) PURE; - STDMETHOD(get_DocumentRange)(THIS_ IUIAutomationTextRange **) PURE; - STDMETHOD(get_SupportedTextSelection)(THIS_ enum SupportedTextSelection *) PURE; -}; -#undef INTERFACE - -#define INTERFACE IUIAutomationTreeWalker -DECLARE_INTERFACE_(IUIAutomationTreeWalker, IUnknown) -{ - STDMETHOD(GetParentElement)(THIS_ IUIAutomationElement *, IUIAutomationElement **) PURE; - STDMETHOD(GetFirstChildElement)(THIS_ IUIAutomationElement *, IUIAutomationElement **) PURE; - STDMETHOD(GetLastChildElement)(THIS_ IUIAutomationElement *, IUIAutomationElement **) PURE; - STDMETHOD(GetNextSiblingElement)(THIS_ IUIAutomationElement *, IUIAutomationElement **) PURE; - STDMETHOD(GetPreviousSiblingElement)(THIS_ IUIAutomationElement *, IUIAutomationElement **) PURE; - STDMETHOD(NormalizeElement)(THIS_ IUIAutomationElement *, IUIAutomationElement **) PURE; - STDMETHOD(GetParentElementBuildCache)(THIS_ IUIAutomationElement *, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(GetFirstChildElementBuildCache)(THIS_ IUIAutomationElement *, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(GetLastChildElementBuildCache)(THIS_ IUIAutomationElement *, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(GetNextSiblingElementBuildCache)(THIS_ IUIAutomationElement *, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(GetPreviousSiblingElementBuildCache)(THIS_ IUIAutomationElement *, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(NormalizeElementBuildCache)(THIS_ IUIAutomationElement *, IUIAutomationCacheRequest *, IUIAutomationElement **) PURE; - STDMETHOD(get_Condition)(THIS_ IUIAutomationCondition **) PURE; -}; -#undef INTERFACE - -#define INTERFACE IUIAutomationTextRange -DECLARE_INTERFACE_(IUIAutomationTextRange, IUnknown) -{ - STDMETHOD(Clone)(THIS_ IUIAutomationTextRange **) PURE; - STDMETHOD(Compare)(THIS_ IUIAutomationTextRange *, BOOL *) PURE; - STDMETHOD(CompareEndpoints)(THIS_ enum TextPatternRangeEndpoint, IUIAutomationTextRange *, enum TextPatternRangeEndpoint, int *) PURE; - STDMETHOD(ExpandToEnclosingUnit)(THIS_ enum TextUnit) PURE; - STDMETHOD(FindAttribute)(THIS_ TEXTATTRIBUTEID, VARIANT, BOOL, IUIAutomationTextRange **) PURE; - STDMETHOD(FindText)(THIS_ BSTR, BOOL, BOOL, IUIAutomationTextRange **) PURE; - STDMETHOD(GetAttributeValue)(THIS_ TEXTATTRIBUTEID, VARIANT *) PURE; - STDMETHOD(GetBoundingRectangles)(THIS_ SAFEARRAY **) PURE; - STDMETHOD(GetEnclosingElement)(THIS_ IUIAutomationElement **) PURE; - STDMETHOD(GetText)(THIS_ int, BSTR *) PURE; - STDMETHOD(Move)(THIS_ enum TextUnit, int, int *) PURE; - STDMETHOD(MoveEndpointByUnit)(THIS_ enum TextPatternRangeEndpoint, enum TextUnit, int *) PURE; - STDMETHOD(MoveEndpointByRange)(THIS_ enum TextPatternRangeEndpoint, IUIAutomationTextRange *, enum TextPatternRangeEndpoint) PURE; - STDMETHOD(Select)(THIS) PURE; - STDMETHOD(AddToSelection)(THIS) PURE; - STDMETHOD(RemoveFromSelection)(THIS) PURE; - STDMETHOD(ScrollIntoView)(THIS_ BOOL) PURE; - STDMETHOD(GetChildren)(THIS_ IUIAutomationElementArray **) PURE; -}; -#undef INTERFACE - -#ifdef __cplusplus -} -#endif - -#endif // UIAUTO_HH diff --git a/utf8.cc b/utf8.cc index 91618f92..6a6949de 100644 --- a/utf8.cc +++ b/utf8.cc @@ -132,7 +132,7 @@ long decode( char const * in_, size_t inSize, wchar * out_ ) return out - out_; } -string encode( wstring const & in ) throw() +string encode( wstring const & in ) noexcept { if( in.size() == 0 ) return string(); diff --git a/utf8.hh b/utf8.hh index ce636276..75f96503 100644 --- a/utf8.hh +++ b/utf8.hh @@ -44,7 +44,7 @@ size_t encode( wchar const * in, size_t inSize, char * out ); long decode( char const * in, size_t inSize, wchar * out ); /// Versions for non time-critical code. -string encode( wstring const & ) throw(); +string encode( wstring const & ) noexcept; wstring decode( string const & ) ; /// Since the standard isspace() is locale-specific, we need something diff --git a/utils.hh b/utils.hh index 2021223c..a12175af 100644 --- a/utils.hh +++ b/utils.hh @@ -9,6 +9,8 @@ #include #include #include +#include +#include namespace Utils { @@ -26,6 +28,39 @@ inline QString rstrip(const QString &str) { return ""; } +/** + * remove punctuation , space, symbol + * + * + * " abc, '" should be "abc" + */ +inline QString trimNonChar( const QString & str ) +{ + QString remain; + int n = str.size() - 1; + for( ; n >= 0; --n ) + { + auto c = str.at( n ); + if( !c.isSpace() && !c.isSymbol() && !c.isNonCharacter() && !c.isPunct()&& !c.isNull() ) + { + remain = str.left( n + 1 ); + break; + } + } + + n = 0; + for( ; n < remain.size(); n++ ) + { + auto c = remain.at( n ); + if( !c.isSpace() && !c.isSymbol() && !c.isNonCharacter() && !c.isPunct() ) + { + return remain.mid( n ); + } + } + + return ""; +} + /** * str="abc\r\n\u0000" should be returned as "abc" * @brief rstripnull @@ -35,20 +70,14 @@ inline QString rstrip(const QString &str) { inline QString rstripnull(const QString &str) { int n = str.size() - 1; for (; n >= 0; --n) { - if (!str.at(n).isSpace()&&!str.at(n).isNull()) { + auto c = str.at(n); + if (!c.isSpace()&&!c.isNull()) { return str.left(n + 1); } } return ""; } -inline QString unescapeHtml(const QString &str) { - QTextDocument text; - text.setHtml(str); - return text.toPlainText(); -} - - inline bool isExternalLink(QUrl const &url) { return url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp" || url.scheme() == "mailto" || url.scheme() == "file" || url.toString().startsWith( "//" ); @@ -77,6 +106,21 @@ inline bool ignoreKeyEvent(QKeyEvent *keyEvent) { return false; } +inline QString json2String( const QJsonObject & json ) +{ + return QString( QJsonDocument( json ).toJson( QJsonDocument::Compact ) ); +} + +inline QStringList repeat( const QString str, const int times ) +{ + QStringList list; + for( int i = 0; i < times; i++ ) + { + list << str; + } + return list; +} + namespace AtomicInt { @@ -89,6 +133,7 @@ inline int loadAcquire( QAtomicInt const & ref ) namespace Url { + // This wrapper is created due to behavior change of the setPath() method // See: https://bugreports.qt-project.org/browse/QTBUG-27728 // https://codereview.qt-project.org/#change,38257 @@ -151,16 +196,15 @@ inline QString fragment( const QUrl & url ) return url.fragment( QUrl::FullyDecoded ); } -// extract query word from url -inline QString getWordFromUrl( const QUrl & url ) +// get the query word of bword and gdlookup scheme. +// if the scheme is gdlookup or scheme ,the first value of pair is true,otherwise is false; +inline std::pair< bool, QString > getQueryWord( QUrl const & url ) { QString word; - if( url.scheme().compare( "bword" ) == 0 ) - { - word = url.path(); - } - else if( url.scheme() == "gdlookup" ) // Plain html links inherit gdlookup scheme + bool validScheme = false; + if( url.scheme().compare( "gdlookup" ) == 0 ) { + validScheme = true; if( hasQueryItem( url, "word" ) ) { word = queryItemValue( url, "word" ); @@ -170,8 +214,31 @@ inline QString getWordFromUrl( const QUrl & url ) word = url.path().mid( 1 ); } } + if( url.scheme().compare( "bword" ) == 0 || url.scheme().compare( "entry" ) == 0 ) + { + validScheme = true; - return word; + auto path = url.path(); + // url like this , bword:word or bword://localhost/word + if( !path.isEmpty() ) + { + //url,bword://localhost/word + if( path.startsWith( "/" ) ) + word = path.mid( 1 ); + else + word = path; + } + else + { + // url looks like this, bword://word,or bword://localhost + auto host = url.host(); + if( host != "localhost" ) + { + word = host; + } + } + } + return std::make_pair( validScheme, word ); } } @@ -181,13 +248,6 @@ namespace { /// Uses some heuristics to chop off the first domain name from the host name, /// but only if it's not too base. Returns the resulting host name. -inline QString getHostBase( QUrl const & url ) -{ - QString host = url.host(); - - return getHostBase(host); -} - inline QString getHostBase( QString const & host ) { QStringList domains = host.split( '.' ); @@ -212,6 +272,13 @@ inline QString getHostBase( QString const & host ) else return host; } + +inline QString getHostBaseFromUrl( QUrl const & url ) +{ + QString host = url.host(); + + return getHostBase( host ); +} } #endif // UTILS_HH diff --git a/voiceengines.cc b/voiceengines.cc index 0582dd36..555c5532 100644 --- a/voiceengines.cc +++ b/voiceengines.cc @@ -44,16 +44,16 @@ public: { } - virtual string getName() throw() + virtual string getName() noexcept { return voiceEngine.name.toUtf8().data(); } - virtual map< Property, string > getProperties() throw() + virtual map< Property, string > getProperties() noexcept { return map< Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return 0; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return 0; } virtual sptr< WordSearchRequest > prefixMatch( wstring const & word, @@ -67,7 +67,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; sptr< WordSearchRequest > VoiceEnginesDictionary::prefixMatch( wstring const & /*word*/, @@ -110,7 +110,7 @@ sptr< Dictionary::DataRequest > VoiceEnginesDictionary::getArticle( return ret; } -void VoiceEnginesDictionary::loadIcon() throw() +void VoiceEnginesDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/website.cc b/website.cc index 6c62e19a..4091d198 100644 --- a/website.cc +++ b/website.cc @@ -53,16 +53,16 @@ public: dictionaryDescription = temp; } - virtual string getName() throw() + virtual string getName() noexcept { return name; } - virtual map< Property, string > getProperties() throw() + virtual map< Property, string > getProperties() noexcept { return map< Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return 0; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return 0; } virtual sptr< WordSearchRequest > prefixMatch( wstring const & word, @@ -79,7 +79,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; sptr< WordSearchRequest > WebSiteDictionary::prefixMatch( wstring const & /*word*/, @@ -526,7 +526,7 @@ sptr< Dictionary::DataRequest > WebSiteDictionary::getResource( string const & n return new WebSiteResourceRequest( link, netMgr, this ); } -void WebSiteDictionary::loadIcon() throw() +void WebSiteDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/wordbyauto.cc b/wordbyauto.cc deleted file mode 100644 index a3a20372..00000000 --- a/wordbyauto.cc +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include "wordbyauto.hh" -#include "uiauto.hh" - -#include -#include "gddebug.hh" - -class GDAutomationClient { -public: - GDAutomationClient(); - ~GDAutomationClient(); - bool getWordAtPoint( POINT pt ); - WCHAR *getText() { return buffer; } -private: - WCHAR buffer[256]; - IUIAutomation *pGDAutomation; - IUIAutomationTreeWalker *pTree; -}; - -GDAutomationClient gdAuto; - -GDAutomationClient::GDAutomationClient() -{ -HRESULT hr; - CoInitializeEx( NULL, COINIT_APARTMENTTHREADED ); - hr = CoCreateInstance( CLSID_CUIAutomation , NULL, CLSCTX_INPROC_SERVER, IID_IUIAutomation, (void**)&pGDAutomation ); - if( hr != S_OK ) pGDAutomation = NULL; - pTree = NULL; - if( pGDAutomation != NULL ) - hr = pGDAutomation->get_RawViewWalker( &pTree ); - memset( buffer, 0, sizeof(buffer) ); -} - -GDAutomationClient::~GDAutomationClient() -{ - if( pTree != NULL ) pTree->Release(); - if( pGDAutomation != NULL ) pGDAutomation->Release(); - CoUninitialize(); -} - -bool GDAutomationClient::getWordAtPoint( POINT pt ) -{ -HRESULT hr; -IUIAutomationTextPattern *pTextPattern; -IUIAutomationTextRange *pTextRange; -IUIAutomationElement *pElement, *pParent; -BSTR bstr; -RECT r = { 0, 0, 0, 0 }; -bool bGoUp; - - GD_DPRINTF("\nEntering getWordAtPoint\n"); - - if( pGDAutomation == NULL ) return false; - - buffer[0] = 0; - pElement = NULL; - hr = pGDAutomation->ElementFromPoint( pt, &pElement ); - GD_DPRINTF("ElementFromPoint return hr=%08lX, ptr=%p\n", hr, pElement); - if( hr != S_OK || pElement == NULL ) - return false; - - pTextPattern = NULL; - bGoUp = false; - while( pElement != NULL ) { - hr = pElement->GetCurrentPatternAs( UIA_TextPatternId, IID_IUIAutomationTextPattern, (void**)&pTextPattern ); - if( hr == S_OK && pTextPattern != NULL ) - break; - if( pTree == NULL ) { - pElement->Release(); - return false; - } - pParent = NULL; - hr = pTree->GetParentElement( pElement, &pParent ); - pElement->Release(); - pElement = pParent; - bGoUp = TRUE; - } - if( pElement == NULL ) - return false; - - if( !bGoUp ) { - hr = pElement->get_CurrentBoundingRectangle( &r ); - if( hr == S_OK) { - pt.x -= r.left; - pt.y -= r.top; - } - } - pElement->Release(); - - pTextRange = NULL; - hr = pTextPattern->RangeFromPoint( pt, &pTextRange ); - pTextPattern->Release(); - if( hr != S_OK || pTextRange == NULL ) - return false; - - hr = pTextRange->ExpandToEnclosingUnit( TextUnit_Word ); - if( hr == S_OK) { - hr = pTextRange->GetText( 255, &bstr ); - if (hr == S_OK) { - wsprintfW( buffer, L"%s", (LPCWSTR)bstr ); - SysFreeString( bstr ); - } - } - pTextRange->Release(); - - return ( buffer[0] != 0 ); -} - -WCHAR *gdGetWordAtPointByAutomation( POINT pt ) -{ - if( gdAuto.getWordAtPoint( pt ) ) return gdAuto.getText(); - else return NULL; -} diff --git a/wordbyauto.hh b/wordbyauto.hh deleted file mode 100644 index 6a4cebbf..00000000 --- a/wordbyauto.hh +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __WORD_BY_AUTO_HH_INCLUDED -#define __WORD_BY_AUTO_HH_INCLUDED - -WCHAR *gdGetWordAtPointByAutomation( POINT pt ); - -#endif diff --git a/xdxf.cc b/xdxf.cc index 585e8abe..e33fc55e 100644 --- a/xdxf.cc +++ b/xdxf.cc @@ -147,16 +147,16 @@ public: ~XdxfDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -200,7 +200,7 @@ public: protected: - void loadIcon() throw(); + void loadIcon() noexcept; private: @@ -312,7 +312,7 @@ XdxfDictionary::~XdxfDictionary() dict_data_close( dz ); } -void XdxfDictionary::loadIcon() throw() +void XdxfDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; diff --git a/zim.cc b/zim.cc index 15e8a5be..6dc8a9b1 100644 --- a/zim.cc +++ b/zim.cc @@ -78,7 +78,8 @@ enum CompressionType struct ZIM_header { quint32 magicNumber; - quint32 version; + quint16 majorVersion; + quint16 minorVersion; quint8 uuid[ 16 ]; quint32 articleCount; quint32 clusterCount; @@ -125,7 +126,7 @@ __attribute__((packed)) enum { Signature = 0x584D495A, // ZIMX on little-endian, XMIZ on big-endian - CurrentFormatVersion = 1 + BtreeIndexing::FormatVersion + Folding::Version + CurrentFormatVersion = 3 + BtreeIndexing::FormatVersion + Folding::Version }; struct IdxHeader @@ -158,13 +159,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 ) {} }; @@ -184,13 +187,25 @@ 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 ).startsWith( "text/html", Qt::CaseInsensitive ) + || getMimeType( mime_type ).startsWith( "text/plain", Qt::CaseInsensitive ); } + + + quint16 redirectedMimeType( RedirectEntry const & redEntry ); private: ZIM_header zimHeader; Cache cache[ CACHE_SIZE ]; int stamp; QVector< QPair< quint64, quint32 > > clusterOffsets; + QStringList mimeTypes; void clearCache(); }; @@ -291,10 +306,33 @@ 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; } -string ZimFile::getClusterData( quint32 cluster_nom ) +string ZimFile::getClusterData( quint32 cluster_nom, unsigned & blobs_offset_size ) { // Check cache int target = 0; @@ -328,6 +366,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 ); } @@ -353,9 +392,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; @@ -384,9 +425,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 ) { @@ -410,12 +458,52 @@ 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; } } 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 ) @@ -516,23 +604,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; } @@ -562,16 +669,16 @@ class ZimDictionary: public BtreeIndexing::BtreeDictionary ~ZimDictionary(); - virtual string getName() throw() + virtual string getName() noexcept { return dictionaryName; } - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.articleCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return idxHeader.wordCount; } inline virtual quint32 getLangFrom() const @@ -618,7 +725,7 @@ class ZimDictionary: public BtreeIndexing::BtreeDictionary protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; private: @@ -662,7 +769,7 @@ ZimDictionary::ZimDictionary( string const & id, { QString name = QDir::fromNativeSeparators( FsEncoding::decode( dictionaryFiles[ 0 ].c_str() ) ); int n = name.lastIndexOf( '/' ); - dictionaryName = string( name.mid( n + 1 ).toUtf8().constData() ); + dictionaryName = name.mid( n + 1 ).toStdString(); } else { @@ -685,7 +792,7 @@ ZimDictionary::~ZimDictionary() df.close(); } -void ZimDictionary::loadIcon() throw() +void ZimDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return; @@ -1132,32 +1239,8 @@ sptr< Dictionary::DataRequest > ZimDictionary::getSearchResults( QString const & /// ZimDictionary::getArticle() -class ZimArticleRequest; - -class ZimArticleRequestRunnable: public QRunnable -{ - ZimArticleRequest & r; - QSemaphore & hasExited; - -public: - - ZimArticleRequestRunnable( ZimArticleRequest & r_, - QSemaphore & hasExited_ ): r( r_ ), - hasExited( hasExited_ ) - {} - - ~ZimArticleRequestRunnable() - { - hasExited.release(); - } - - virtual void run(); -}; - class ZimArticleRequest: public Dictionary::DataRequest { - friend class ZimArticleRequestRunnable; - wstring word; vector< wstring > alts; ZimDictionary & dict; @@ -1173,11 +1256,10 @@ public: ZimDictionary & dict_, bool ignoreDiacritics_ ): word( word_ ), alts( alts_ ), dict( dict_ ), ignoreDiacritics( ignoreDiacritics_ ) { - QThreadPool::globalInstance()->start( - new ZimArticleRequestRunnable( *this, hasExited ) ); + QThreadPool::globalInstance()->start( [ this ]() { this->run(); } ); } - void run(); // Run from another thread by ZimArticleRequestRunnable + void run(); virtual void cancel() { @@ -1191,11 +1273,6 @@ public: } }; -void ZimArticleRequestRunnable::run() -{ - r.run(); -} - void ZimArticleRequest::run() { if ( Utils::AtomicInt::loadAcquire( isCancelled ) ) @@ -1336,32 +1413,8 @@ sptr< Dictionary::DataRequest > ZimDictionary::getArticle( wstring const & word, //// ZimDictionary::getResource() -class ZimResourceRequest; - -class ZimResourceRequestRunnable: public QRunnable -{ - ZimResourceRequest & r; - QSemaphore & hasExited; - -public: - - ZimResourceRequestRunnable( ZimResourceRequest & r_, - QSemaphore & hasExited_ ): r( r_ ), - hasExited( hasExited_ ) - {} - - ~ZimResourceRequestRunnable() - { - //hasExited.release(); - } - - virtual void run(); -}; - class ZimResourceRequest: public Dictionary::DataRequest { - friend class ZimResourceRequestRunnable; - ZimDictionary & dict; string resourceName; @@ -1372,12 +1425,10 @@ class ZimResourceRequest: public Dictionary::DataRequest public: ZimResourceRequest(ZimDictionary &dict_, string const &resourceName_) : dict(dict_), resourceName(resourceName_) { - //(new ZimResourceRequestRunnable(*this, hasExited))->run(); - QThreadPool::globalInstance()->start( - new ZimResourceRequestRunnable( *this, hasExited ) ); + QThreadPool::globalInstance()->start( [ this ]() { this->run(); } ); } - void run(); // Run from another thread by ZimResourceRequestRunnable + void run(); virtual void cancel() { @@ -1391,11 +1442,6 @@ public: } }; -void ZimResourceRequestRunnable::run() -{ - r.run(); -} - void ZimResourceRequest::run() { // Some runnables linger enough that they are cancelled before they start @@ -1498,6 +1544,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() ); @@ -1534,7 +1581,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( } const quint64 * ptr; - quint16 mimetype; + quint16 mimetype, redirected_mime = 0xFFFF; ArticleEntry artEntry; RedirectEntry redEntry; string url, title; @@ -1551,6 +1598,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 @@ -1562,7 +1610,7 @@ vector< sptr< Dictionary::Class > > makeDictionaries( nameSpace = artEntry.nameSpace; - if( nameSpace == 'A' ) + if( ( nameSpace == 'A' || ( nameSpace == 'C' && new_namespaces ) ) && df.isArticleMime( mimetype ) ) articleCount++; } @@ -1585,7 +1633,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() ) @@ -1593,16 +1642,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; @@ -1610,10 +1669,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 ); @@ -1626,6 +1685,11 @@ vector< sptr< Dictionary::Class > > makeDictionaries( } } else + if( nameSpace == 'X' ) + { + continue; + } + else { url.insert( url.begin(), '/' ); url.insert( url.begin(), nameSpace ); diff --git a/zipsounds.cc b/zipsounds.cc index 95c903a4..07595c14 100644 --- a/zipsounds.cc +++ b/zipsounds.cc @@ -114,15 +114,15 @@ public: ZipSoundsDictionary( string const & id, string const & indexFile, vector< string > const & dictionaryFiles ); - virtual string getName() throw(); + virtual string getName() noexcept; - virtual map< Dictionary::Property, string > getProperties() throw() + virtual map< Dictionary::Property, string > getProperties() noexcept { return map< Dictionary::Property, string >(); } - virtual unsigned long getArticleCount() throw() + virtual unsigned long getArticleCount() noexcept { return idxHeader.soundsCount; } - virtual unsigned long getWordCount() throw() + virtual unsigned long getWordCount() noexcept { return getArticleCount(); } virtual sptr< Dictionary::DataRequest > getArticle( wstring const &, @@ -136,7 +136,7 @@ public: protected: - virtual void loadIcon() throw(); + virtual void loadIcon() noexcept; }; ZipSoundsDictionary::ZipSoundsDictionary( string const & id, @@ -164,7 +164,7 @@ ZipSoundsDictionary::ZipSoundsDictionary( string const & id, } -string ZipSoundsDictionary::getName() throw() +string ZipSoundsDictionary::getName() noexcept { string result = FsEncoding::basename( getDictionaryFilenames()[ 0 ] ); @@ -384,7 +384,7 @@ sptr< Dictionary::DataRequest > ZipSoundsDictionary::getResource( string const & return new Dictionary::DataRequestInstant( false ); } -void ZipSoundsDictionary::loadIcon() throw() +void ZipSoundsDictionary::loadIcon() noexcept { if ( dictionaryIconLoaded ) return;